public inbox for gcc-cvs@sourceware.org
help / color / mirror / Atom feed
* [gcc(refs/users/aoliva/heads/strub)] -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests
@ 2021-09-05  5:38 Alexandre Oliva
  0 siblings, 0 replies; 13+ messages in thread
From: Alexandre Oliva @ 2021-09-05  5:38 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:1cd382db490d2304afae614863c09ef93d5e43ff

commit 1cd382db490d2304afae614863c09ef93d5e43ff
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Thu Sep 2 23:15:36 2021 -0300

    -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests

Diff:
---
 .../doc/gnat_rm/security_hardening_features.rst    |  44 +-
 gcc/ada/gcc-interface/utils.c                      |  95 ++-
 gcc/attribs.c                                      |  14 +-
 gcc/c-family/c-attribs.c                           |  84 ++-
 gcc/common.opt                                     |  27 +-
 gcc/doc/extend.texi                                | 332 +++++++++--
 gcc/doc/invoke.texi                                |  76 ++-
 gcc/ipa-inline.c                                   |   2 +-
 gcc/ipa-strub.c                                    | 652 ++++++++++++++++-----
 gcc/ipa-strub.h                                    |  20 +-
 gcc/testsuite/c-c++-common/strub-O0.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O1.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2fni.c           |   2 +-
 gcc/testsuite/c-c++-common/strub-O3.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O3fni.c           |   2 +-
 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            |  12 +-
 gcc/testsuite/c-c++-common/strub-all2.c            |   6 +-
 gcc/testsuite/c-c++-common/strub-apply1.c          |   6 +-
 gcc/testsuite/c-c++-common/strub-apply2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-apply3.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-apply4.c          |   4 +-
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-at-calls2.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-default1.c        |  42 --
 gcc/testsuite/c-c++-common/strub-defer-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |  14 +-
 gcc/testsuite/c-c++-common/strub-defer-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-internal1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-internal2.c       |   6 +-
 gcc/testsuite/c-c++-common/strub-parms1.c          |  10 +-
 gcc/testsuite/c-c++-common/strub-parms2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-parms3.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-relaxed1.c        |  18 +
 gcc/testsuite/c-c++-common/strub-relaxed2.c        |  14 +
 gcc/testsuite/c-c++-common/strub-short-O0-exc.c    |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O0.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O3.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-strict1.c         |  48 ++
 .../{strub-default2.c => strub-strict2.c}          |  14 +-
 gcc/testsuite/c-c++-common/strub-tail-O1.c         |   2 +-
 gcc/testsuite/c-c++-common/strub-tail-O2.c         |   2 +-
 .../c-c++-common/torture/strub-callable1.c         |   2 +-
 .../c-c++-common/torture/strub-callable2.c         |  44 +-
 gcc/testsuite/c-c++-common/torture/strub-const1.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const2.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const3.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-const4.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-data1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data3.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data4.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |   2 +-
 .../c-c++-common/torture/strub-indcall1.c          |   2 +-
 .../c-c++-common/torture/strub-indcall2.c          |   2 +-
 .../c-c++-common/torture/strub-indcall3.c          |   2 +-
 .../c-c++-common/torture/strub-inlinable1.c        |  22 +
 .../c-c++-common/torture/strub-inlinable2.c        |   7 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |  10 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |  19 +
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure3.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-pure4.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run1.c    |  10 +-
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run4.c    |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run4d.c   |   4 +-
 gcc/testsuite/g++.dg/torture/strub-init1.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init2.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init3.C         |   2 +-
 gcc/testsuite/gnat.dg/strub_attr.adb               |   2 +-
 libgcc/strub.c                                     |   2 +-
 80 files changed, 1261 insertions(+), 551 deletions(-)

diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 309291f0341..fb5d14fed40 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -19,6 +19,18 @@ It can be enabled with the *-fzero-call-used-regs* command line
 option, to affect all subprograms in a compilation, and with a
 :samp:`Machine_Attribute` pragma, to affect only specific subprograms.
 
+.. code-block:: ada
+
+     procedure Foo;
+     pragma Machine_Attribute (Foo, "zero_call_used_regs", "used");
+     --  Before returning, Foo scrubs only call-clobbered registers
+     --  that it uses itself.
+
+     function Bar return Integer;
+     pragma Machine_Attribute (Bar, "zero_call_used_regs", "all");
+     --  Before returning, Bar scrubs all call-clobbered registers.
+
+
 For usage and more details on the command line option, and on the
 ``zero_call_used_regs`` attribute, see :title:`Using the GNU Compiler
 Collection (GCC)`.
@@ -31,13 +43,31 @@ Stack Scrubbing
 
 GNAT can generate code to zero-out stack frames used by subprograms.
 
-It can be activated with the *-fstrub* command line option, to affect
-all (viable) subprograms in a compilation, and with a
-:samp:`Machine_Attribute` pragma.
+It can be activated with the :samp:`Machine_Attribute` pragma, on
+specific subprograms, variables, and types.
 
-For usage and more details on the command line option, and on the
-``strub`` attribute, see :title:`Using the GNU Compiler Collection
-(GCC)`.
+.. code-block:: ada
+
+     function Foo returns Integer;
+     pragma Machine_Attribute (Foo, "strub");
+     --  Foo and its callers are modified so as to scrub the stack
+     --  space used by Foo after it returns.
+
+     procedure Bar;
+     pragma Machine_Attribute (Bar, "strub", "internal");
+     --  Bar is turned into a wrapper for its original body,
+     --  and they scrub the stack used by the original body.
+
+     Var : Integer;
+     pragma Machine_Attribute (Var, "strub");
+     --  Reading from Var in a subprogram enables stack scrubbing
+     --  of the stack space used by the subprogram.
+
+
+There are also *-fstrub* command line options to control default
+settings.  For usage and more details on the command line option, and
+on the ``strub`` attribute, see :title:`Using the GNU Compiler
+Collection (GCC)`.
 
 Note that Ada secondary stacks are not scrubbed.  The restriction
 ``No_Secondary_Stack`` avoids their use, and thus their accidental
@@ -49,4 +79,4 @@ is not fully reflected in Ada types, ``Access`` attributes, renaming
 and overriding.  Every access type, renaming, and overriding and
 overridden dispatching operations that may refer to an entity with an
 attribute-modified interface must be annotated with the same
-interface-modifying attribute.
+interface-modifying attribute, or with an interface-compatible one.
diff --git a/gcc/ada/gcc-interface/utils.c b/gcc/ada/gcc-interface/utils.c
index 6b20ddc93c9..47e98425417 100644
--- a/gcc/ada/gcc-interface/utils.c
+++ b/gcc/ada/gcc-interface/utils.c
@@ -39,6 +39,7 @@
 #include "varasm.h"
 #include "toplev.h"
 #include "opts.h"
+#include "ipa-strub.h"
 #include "output.h"
 #include "debug.h"
 #include "convert.h"
@@ -6611,6 +6612,7 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
+  bool compat_type = false;
   tree orig_fnptr_type = NULL_TREE;
   tree orig_args = args;
 
@@ -6624,68 +6626,36 @@ handle_strub_attribute (tree *node, tree name,
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      /* pragma Machine_Attribute turns string arguments into identifiers.
-	 Reverse it.  */
-      if (TREE_CODE (TREE_VALUE (args)) == IDENTIFIER_NODE)
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
 	{
-	  gcc_checking_assert (IDENTIFIER_POINTER (TREE_VALUE (args))
-			       [IDENTIFIER_LENGTH (TREE_VALUE (args))] == 0);
-	  TREE_VALUE (args) = build_string
-	    (IDENTIFIER_LENGTH (TREE_VALUE (args)) + 1,
-	     IDENTIFIER_POINTER (TREE_VALUE (args)));
-	}
-
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
-	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
-
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
+	case 1:
+	  compat_type = true;
+	  /* Fall through.  */
 
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
+	  compat_type = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	  compat_type = true;
+	  /* Fall through.  */
+
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
@@ -6699,23 +6669,30 @@ handle_strub_attribute (tree *node, tree name,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   if (!*no_add_attrs)
     {
       *no_add_attrs = true;
       if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
 	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
+	{
+	  if (compat_type)
+	    *node = build_variant_type_copy (*node);
+	  else
+	    *node = build_distinct_type_copy (*node);
+	}
       TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
 					   TYPE_ATTRIBUTES (*node));
 
       if (orig_fnptr_type)
 	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
+	  tree fnptr_type = ((flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
+			     ? orig_fnptr_type
+			     : compat_type
+			     ? build_variant_type_copy (orig_fnptr_type)
+			     : build_distinct_type_copy (orig_fnptr_type));
 	  TREE_TYPE (fnptr_type) = *node;
 	  *node = fnptr_type;
 	}
diff --git a/gcc/attribs.c b/gcc/attribs.c
index 0d22c20a35e..acac634afb5 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "attribs.h"
 #include "fold-const.h"
+#include "ipa-strub.h"
 #include "stor-layout.h"
 #include "langhooks.h"
 #include "plugin.h"
@@ -1353,9 +1354,20 @@ comp_type_attributes (const_tree type1, const_tree type2)
   if ((lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type1)) != NULL)
       ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) != NULL))
     return 0;
+  int strub_ret = strub_comptypes (CONST_CAST_TREE (type1),
+				   CONST_CAST_TREE (type2));
+  if (strub_ret == 0)
+    return strub_ret;
   /* As some type combinations - like default calling-convention - might
      be compatible, we have to call the target hook to get the final result.  */
-  return targetm.comp_type_attributes (type1, type2);
+  int target_ret = targetm.comp_type_attributes (type1, type2);
+  if (target_ret == 0)
+    return target_ret;
+  if (strub_ret == 2 || target_ret == 2)
+    return 2;
+  if (strub_ret == 1 && target_ret == 1)
+    return 1;
+  gcc_unreachable ();
 }
 
 /* PREDICATE acts as a function of type:
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index dc4a4d4d200..cb5c8d8f100 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "common/common-target.h"
 #include "langhooks.h"
 #include "tree-inline.h"
+#include "ipa-strub.h"
 #include "toplev.h"
 #include "tree-iterator.h"
 #include "opts.h"
@@ -1306,6 +1307,7 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
+  bool compat_type = false;
   tree orig_fnptr_type = NULL_TREE;
   tree orig_args = args;
 
@@ -1319,57 +1321,36 @@ handle_strub_attribute (tree *node, tree name,
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
 	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
+	case 1:
+	  compat_type = true;
+	  /* Fall through.  */
 
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
-
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
+	  compat_type = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	  compat_type = true;
+	  /* Fall through.  */
+
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
@@ -1383,23 +1364,30 @@ handle_strub_attribute (tree *node, tree name,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   if (!*no_add_attrs)
     {
       *no_add_attrs = true;
       if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
 	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
+	{
+	  if (compat_type)
+	    *node = build_variant_type_copy (*node);
+	  else
+	    *node = build_distinct_type_copy (*node);
+	}
       TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
 					   TYPE_ATTRIBUTES (*node));
 
       if (orig_fnptr_type)
 	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
+	  tree fnptr_type = ((flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
+			     ? orig_fnptr_type
+			     : compat_type
+			     ? build_variant_type_copy (orig_fnptr_type)
+			     : build_distinct_type_copy (orig_fnptr_type));
 	  TREE_TYPE (fnptr_type) = *node;
 	  *node = fnptr_type;
 	}
diff --git a/gcc/common.opt b/gcc/common.opt
index 71e0c971344..ef018ebf735 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,13 +2687,22 @@ fstrict-overflow
 Common
 Treat signed overflow as undefined.  Negated as -fwrapv -fwrapv-pointer.
 
-; If any strub-enabling attribute is seen when the default value is
-; selected, it's bumped up to -1.  The scrub mode gate function will
-; then bump -2 to 0 if no strub-enabling attribute is seen.  This
-; minimizes the strub overhead.
-fstrub=default
-Common RejectNegative Var(flag_strub, -2) Init(-2)
-Enable stack scrub as requested through attributes.
+fstrub=disable
+Common RejectNegative Var(flag_strub, 0)
+Disable stack scrub entirely, disregarding strub attributes.
+
+fstrub=strict
+Common RejectNegative Var(flag_strub, -4)
+Enable stack scrub as per attributes, with strict call checking.
+
+; If any strub-enabling attribute is seen when the default or strict
+; initializer values are in effect, flag_strub is bumped up by 2.  The
+; scrub mode gate function will then bump these initializer values to
+; 0 if no strub-enabling attribute is seen.  This minimizes the strub
+; overhead.
+fstrub=relaxed
+Common RejectNegative Var(flag_strub, -3) Init(-3)
+Restore default strub mode: as per attributes, with relaxed checking.
 
 fstrub=all
 Common RejectNegative Var(flag_strub, 3)
@@ -2707,10 +2716,6 @@ fstrub=internal
 Common RejectNegative Var(flag_strub, 2)
 Enable internal stack scrubbing for all viable functions.
 
-fstrub=disable
-Common RejectNegative Var(flag_strub, 0)
-Disable stack scrub entirely, disregarding strub attributes.
-
 fsync-libcalls
 Common Var(flag_sync_libcalls) Init(1)
 Implement __atomic operations via libcalls to legacy __sync functions.
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 1fcf60a3875..d85e695d85c 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -77,7 +77,7 @@ extensions, accepted by GCC in C90 mode and in C++.
 * Function Names::      Printable strings which are the name of the current
                         function.
 * Return Address::      Getting the return or frame address of a function.
-* Stack Scrubbing::     Stack scrubbing interfaces.
+* Stack Scrubbing::     Stack scrubbing internal interfaces.
 * Vector Extensions::   Using vector instructions through built-in functions.
 * Offsetof::            Special syntax for implementing @code{offsetof}.
 * __sync Builtins::     Legacy built-in functions for atomic memory access.
@@ -8729,51 +8729,259 @@ pid_t wait (wait_status_ptr_t p)
 @item strub
 @cindex @code{strub} type attribute
 This attribute defines stack-scrubbing properties of functions and
-variables.  When applied to function types, it takes an optional
-argument.  When applied to a pointer-to-function type, it gets
-propagated to the function type if the optional argument is given.
-
-A function whose type is annotated with @code{at-calls} @code{strub}
-mode (@code{strub("at-calls")}, @code{strub(1)}, or @code{strub})
-undergoes interface changes.  Its callers are adjusted to match the
-changes, and to automatically scrub (overwrite with zeros) the stack
-space used by the function.
-
-A function whose type indicates @code{internal} @code{strub} mode
-(@code{strub("internal")} or @code{strub(2)}) retains an unmodified
-interface, but may be turned into a wrapper that calls the wrapped body
-using a custom interface.  The wrapper then scrubs the stack space used
-by the wrapped body.
-
-An automatically-allocated variable whose type carries the @code{strub}
-attribute causes the enclosing function to have @code{strub} enabled.
-
-A statically-allocated variable whose type carries the @code{strub}
-attribute causes functions that @emph{read} it with that type to have
-@code{strub} enabled.  Reading from data referenced by pointers to such
-types has the same effect.  Note: The attribute does not carry over from
-a composite type to the types of its components, so the intended effect
-may not be obtained with non-scalar types.
-
-A @code{strub} context is the body of a function that has strub enabled,
-be it explicitly, by @code{at-calls} or @code{internal} mode, or
-implicitly, by reading from a @code{strub}-marked data type.
+variables.  Being a type attribute, it attaches to types, even when
+specified in function and variable declarations.  When applied to
+function types, it takes an optional string argument.  When applied to a
+pointer-to-function type, if the optional argument is given, it gets
+propagated to the function type.
+
+@smallexample
+/* A strub variable.  */
+int __attribute__ ((strub)) var;
+/* A strub variable that happens to be a pointer.  */
+__attribute__ ((strub)) int *strub_ptr_to_int;
+/* A pointer type that may point to a strub variable.  */
+typedef int __attribute__ ((strub)) *ptr_to_strub_int_type;
+
+/* A declaration of a strub function.  */
+extern int __attribute__ ((strub)) foo (void);
+/* A pointer to that strub function.  */
+int __attribute__ ((strub ("at-calls"))) (*ptr_to_strub_fn)(void) = foo;
+@end smallexample
+
+A function associated with @code{at-calls} @code{strub} mode
+(@code{strub("at-calls")}, or just @code{strub}) undergoes interface
+changes.  Its callers are adjusted to match the changes, and to scrub
+(overwrite with zeros) the stack space used by the called function after
+it returns.  The interface change makes the function type incompatible
+with an unadorned but otherwise equivalent type, so @emph{every}
+declaration and every type that may be used to call the function must be
+associated with this strub mode.
+
+A function associated with @code{internal} @code{strub} mode
+(@code{strub("internal")}) retains an unmodified, type-compatible
+interface, but it may be turned into a wrapper that calls the wrapped
+body using a custom interface.  The wrapper then scrubs the stack space
+used by the wrapped body.  Though the wrapped body has its stack space
+scrubbed, the wrapper does not, so arguments and return values may
+remain unscrubbed even when such a function is called by another
+function that enables @code{strub}.  This is why, when compiling with
+@option{-fstrub=strict}, a @code{strub} context is not allowed to call
+@code{internal} @code{strub} functions.
+
+@smallexample
+/* A declaration of an internal-strub function.  */
+extern int __attribute__ ((strub ("internal"))) bar (void);
+
+int __attribute__ ((strub))
+baz (void)
+@{
+  /* Ok, foo was declared above as an at-calls strub function.  */
+  foo ();
+  /* Not allowed in strict mode, otherwise allowed.  */
+  bar ();
+@}
+@end smallexample
+
+An automatically-allocated variable associated with the @code{strub}
+attribute causes the (immediately) enclosing function to have
+@code{strub} enabled.
+
+A statically-allocated variable associated with the @code{strub}
+attribute causes functions that @emph{read} it, through its @code{strub}
+data type, to have @code{strub} enabled.  Reading data by dereferencing
+a pointer to a @code{strub} data type has the same effect.  Note: The
+attribute does not carry over from a composite type to the types of its
+components, so the intended effect may not be obtained with non-scalar
+types.
+
+When selecting a @code{strub}-enabled mode for a function that is not
+explicitly associated with one, because of @code{strub} variables or
+data pointers, the function must satisfy @code{internal} mode viability
+requirements (see below), even when @code{at-calls} mode is also viable
+and, being more efficient, ends up selected as an optimization.
+
+@smallexample
+/* zapme is implicitly strub-enabled because of strub variables.
+   Optimization may change its strub mode, but not the requirements.  */
+static int
+zapme (int i)
+@{
+  /* A local strub variable enables strub.  */
+  int __attribute__ ((strub)) lvar;
+  /* Reading strub data through a pointer-to-strub enables strub.  */
+  lvar = * (ptr_to_strub_int_type) &i;
+  /* Writing to a global strub variable does not enable strub.  */
+  var = lvar;
+  /* Reading from a global strub variable enables strub.  */
+  return var;
+@}
+@end smallexample
+
+A @code{strub} context is the body (as opposed to the interface) of a
+function that has @code{strub} enabled, be it explicitly, by
+@code{at-calls} or @code{internal} mode, or implicitly, due to
+@code{strub} variables or command-line options.
 
 A function of a type associated with the @code{disabled} @code{strub}
-mode (@code{strub("disabled")}, @code{strub(0)}, or no @code{strub} mode
-specified) will not have its own stack space scrubbed, and it cannot be
-called from @code{strub} contexts.
+mode (@code{strub("disabled")} will not have its own stack space
+scrubbed.  Such functions @emph{cannot} be called from within
+@code{strub} contexts.
+
+In order to enable a function to be called from within @code{strub}
+contexts without having its stack space scrubbed, associate it with the
+@code{callable} @code{strub} mode (@code{strub("callable")}).
+
+When a function is not assigned a @code{strub} mode, explicitly or
+implicitly, the mode defaults to @code{callable}, except when compiling
+with @option{-fstrub=strict}, that causes @code{strub} mode to default
+to @code{disabled}.
+
+@example
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+ /* Implicitly disabled with -fstrub=strict, otherwise callable.  */
+extern int bah (void);
+
+int __attribute__ ((strub))
+bal (void)
+@{
+  /* Not allowed, bad is not strub-callable.  */
+  bad ();
+  /* Ok, bac is strub-callable.  */
+  bac ();
+  /* Not allowed with -fstrub=strict, otherwise allowed.  */
+  bah ();
+@}
+@end example
 
-A function that does not have @code{strub} enabled can only be called
-from within @code{strub} contexts through a function type marked with
-the @code{callable} @code{strub} mode (@code{strub("callable")} or
-@code{strub(3)}).
+Function types marked @code{callable} and @code{disabled} are not
+mutually compatible types, but the underlying interfaces are compatible,
+so it is safe to convert pointers between them, and to use such pointers
+or alternate declarations to call them.  Interfaces are also
+interchangeable between them and @code{internal} (but not
+@code{at-calls}!), but adding @code{internal} to a pointer type will not
+cause the pointed-to function to perform stack scrubbing.
+
+@example
+void __attribute__ ((strub))
+bap (void)
+@{
+  /* Assign a callable function to pointer-to-disabled.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bac;
+  /* Not allowed: calls disabled type in a strub context.  */
+  d_p ();
+
+  /* Assign a disabled function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bad;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an internal function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  c_p = bar;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an at-calls function to pointer-to-callable.
+     Flaggged as incompatible.  */
+  c_p = bal;
+  /* The call through an interface-incompatible type will not use the
+     modified interface expected by the at-calls function, so it is
+     likely to misbehave at runtime.  */
+  c_p ();
+@}
+@end example
 
 @code{Strub} contexts are never inlined into non-@code{strub} contexts.
-When an @code{internal}-strub function is split, the wrapper can often
-be inlined, but the wrapped body never is.  Functions marked as
-@code{always_inline}, even if explicitly assigned @code{internal} strub
-mode, will not undergo wrapping, so their body gets inlined.
+When an @code{internal}-strub function is split up, the wrapper can
+often be inlined, but the wrapped body @emph{never} is.  A function
+marked as @code{always_inline}, even if explicitly assigned
+@code{internal} strub mode, will not undergo wrapping, so its body gets
+inlined as required.
+
+@example
+inline int __attribute__ ((strub ("at-calls")))
+inl_atc (void)
+@{
+  /* This body may get inlined into strub contexts.  */
+@}
+
+inline int __attribute__ ((strub ("internal")))
+inl_int (void)
+@{
+  /* This body NEVER gets inlined, though its wrapper may.  */
+@}
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+@{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+@}
+
+void __attribute__ ((strub ("disabled")))
+bat (void)
+@{
+  /* Not allowed, cannot inline into a non-strub context.  */
+  inl_int_ali ();
+@}
+@end example
+
+@cindex strub eligibility and viability
+Some @option{-fstrub=*} command line options enable @code{strub} modes
+implicitly where viable.  A @code{strub} mode is only viable for a
+function if the function is eligible for that mode, and if other
+conditions, detailed below, are satisfied.  If it's not eligible for a
+mode, attempts to explicitly associate it with that mode are rejected
+with an error message.  If it is eligible, that mode may be assigned
+explicitly through this attribute, but implicit assignment through
+command-line options may involve additional viability requirements.
+
+A function is ineligible for @code{at-calls} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+@code{At-calls} @code{strub} mode, if not requested through the function
+type, is only viable for an eligible function if the function is not
+visible to other translation units, and if it doesn't have its address
+taken.
+
+@smallexample
+/* bar is eligible for at-calls strub mode,
+   but not viable for that mode because it is visible to other units.
+   It is eligible and viable for internal strub mode.  */
+void bav () @{@}
+
+/* setp is eligible for at-calls strub mode,
+   but not viable for that mode because its address is taken.
+   It is eligible and viable for internal strub mode.  */
+void setp (void) @{ static void (*p)(void); = setp; @}
+@end smallexample
+
+A function is ineligible for @code{internal} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, or if attribute
+@code{noipa} is present.  For an @code{always_inline} function, meeting
+these requirements is enough to make it eligible.  Any function that has
+attribute @code{noclone}, that uses such extensions as non-local labels,
+computed gotos, alternate variable argument passing interfaces,
+@code{__builtin_next_arg}, or @code{__builtin_return_address}, or that
+takes too many (about 64Ki) arguments is ineligible, unless it is
+@code{always_inline}.  For @code{internal} @code{strub} mode, all
+eligible functions are viable.
+
+@smallexample
+/* flop is not eligible, thus not viable, for at-calls strub mode.
+   Likewise for internal strub mode.  */
+__attribute__ ((noipa)) void flop (void) @{@}
+
+/* flip is eligible and viable for at-calls strub mode.
+   It would be ineligible for internal strub mode, because of noclone,
+   if it weren't for always_inline.  With always_inline, noclone is not
+   an obstacle, so it is also eligible and viable for internal strub mode.  */
+inline __attribute__ ((noclone, always_inline)) void flip (void) @{@}
+@end smallexample
 
 @item unused
 @cindex @code{unused} type attribute
@@ -11804,40 +12012,48 @@ This function returns the value of the stack pointer register.
 @end deftypefn
 
 @node Stack Scrubbing
-@section Stack scrubbing interfaces
+@section Stack scrubbing internal interfaces
 
 Stack scrubbing involves cooperation between a @code{strub} context,
 i.e., a function whose stack frame is to be zeroed-out, and its callers.
 The caller initializes a stack watermark, the @code{strub} context
-updates the watermark to reflect its stack use, and the caller zeroes it
-out once it regains control.
-
-Each of these steps relies on a different builtin function call.  The
-functions are available in libgcc but, depending on optimization levels,
-they are expanded internally, adjusted to account for inlining, or
-combined/deferred (e.g. passing the caller-supplied watermark on to
-callees, refraining from erasing stack areas that the caller will).
+updates the watermark according to its stack use, and the caller zeroes
+it out once it regains control, whether by the callee's returning or by
+an exception.
+
+Each of these steps is performed by a different builtin function call.
+Calls to these builtins are introduced automatically, in response to
+@code{strub} attributes and command-line options; they are not expected
+to be explicitly called by source code.
+
+The functions that implement the builtins are available in libgcc but,
+depending on optimization levels, they are expanded internally, adjusted
+to account for inlining, and sometimes combined/deferred (e.g. passing
+the caller-supplied watermark on to callees, refraining from erasing
+stack areas that the caller will) to enable tail calls and to optimize
+for code size.
 
 @deftypefn {Built-in Function} {void} __builtin___strub_enter (void **@var{wmptr})
 This function initializes a stack @var{watermark} variable with the
-current top of the stack.  This builtin function should be called before
-entering a @code{strub} context.  It remains as a function call if optimization
-is not enabled.
+current top of the stack.  A call to this builtin function is introduced
+before entering a @code{strub} context.  It remains as a function call
+if optimization is not enabled.
 @end deftypefn
 
 @deftypefn {Built-in Function} {void} __builtin___strub_update (void **@var{wmptr})
 This function updates a stack @var{watermark} variable with the current
-top of the stack, if it tops the previous watermark.  This builtin
-function should be called within a @code{strub} context whenever
+top of the stack, if it tops the previous watermark.  A call to this
+builtin function is inserted within @code{strub} contexts, whenever
 additional stack space may have been used.  It remains as a function
 call at optimization levels lower than 2.
 @end deftypefn
 
 @deftypefn {Built-in Function} {void} __builtin___strub_leave (void **@var{wmptr})
 This function overwrites the memory area between the current top of the
-stack, and the @var{watermark}ed address.  This builtin function should
-be called after leaving a @code{strub} context.  It remains as a
-function call at optimization levels lower than 3.
+stack, and the @var{watermark}ed address.  A call to this builtin
+function is inserted after leaving a @code{strub} context.  It remains
+as a function call at optimization levels lower than 3, and it is guarded by
+a condition at level 2.
 @end deftypefn
 
 @node Vector Extensions
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 838d9ff1198..afcb37914ac 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,7 +599,8 @@ Objective-C and Objective-C++ Dialects}.
 -fstack-protector-explicit  -fstack-check @gol
 -fstack-limit-register=@var{reg}  -fstack-limit-symbol=@var{sym} @gol
 -fno-stack-limit  -fsplit-stack @gol
--fstrub=default -fstrub=disable -fstrub=at-calls -fstrub=internal -fstrub=all @gol
+-fstrub=disable -fstrub=strict -fstrub=relaxed @gol
+-fstrub=all -fstrub=at-calls -fstrub=internal @gol
 -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
 -fvtv-counts  -fvtv-debug @gol
 -finstrument-functions @gol
@@ -15513,46 +15514,55 @@ without @option{-fsplit-stack} always has a large stack.  Support for
 this is implemented in the gold linker in GNU binutils release 2.21
 and later.
 
-@item -fstrub=default
-@opindex fstrub=default
-Restore the default stack scrub (@code{strub}) setting, namely,
-@code{strub} is only enabled as required by @code{strub} attributes
-associated with function or variable types.  This is only useful to
-override earlier @samp{-fstrub=*} options.
-
 @item -fstrub=disable
 @opindex -fstrub=disable
 Disable stack scrubbing entirely, ignoring any @code{strub} attributes.
+See @xref{Common Type Attributes}.
+
+@item -fstrub=strict
+@opindex fstrub=strict
+Functions default to @code{strub} mode @code{disabled}, and apply
+@option{strict}ly the restriction that only functions associated with
+@code{strub}-@code{callable} modes (@code{at-calls}, @code{callable} and
+@code{always_inline} @code{internal}) are @code{callable} by functions
+with @code{strub}-enabled modes (@code{at-calls} and @code{internal}).
+
+@item -fstrub=relaxed
+@opindex fstrub=relaxed
+Restore the default stack scrub (@code{strub}) setting, namely,
+@code{strub} is only enabled as required by @code{strub} attributes
+associated with function and data types.  @code{Relaxed} means that
+strub contexts are only prevented from calling functions explicitly
+associated with @code{strub} mode @code{disabled}.  This option is only
+useful to override other @option{-fstrub=*} options that precede it in
+the command line.
 
 @item -fstrub=at-calls
 @opindex fstrub=at-calls
-Enable @code{at-calls} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{at-calls} @code{strub} if a different @code{strub}
-mode is explicitly requested, if attribute @code{noipa} is present, or
-if it calls @code{__builtin_apply_args}.  @code{At-calls} @code{strub}
-mode, if not requested through the function type, is only viable for an
-eligible function if the function is not visible to other translation
-units, and it doesn't have its address taken.
+Enable @code{at-calls} @code{strub} mode where viable.  The primary use
+of this option is for testing.  It exercises the @code{strub} machinery
+in scenarios strictly local to a translation unit.  This @code{strub}
+mode modifies function interfaces, so any function that is visible to
+other translation units, or that has its address taken, will @emph{not}
+be affected by this option.  Optimization options may also affect
+viability.  See the @code{strub} attribute documentation for details on
+viability and eligibility requirements.
 
 @item -fstrub=internal
 @opindex fstrub=internal
-Enable @code{internal} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{internal} @code{strub} if a different @code{strub}
-mode is explicitly requested, or if attribute @code{noipa} is present.
-Non-@code{always_inline} functions also become ineligible if attribute
-@code{noclone} is present, if the function uses such features as user
-labels, non-default variable argument interfaces,
-@code{__builtin_next_arg}, or @code{__builtin_return_address}, or if
-they have too many (about 64Ki) arguments.  For @code{internal}
-@code{strub}, all eligible functions are viable.
+Enable @code{internal} @code{strub} mode where viable.  The primary use
+of this option is for testing.  This option is intended to exercise
+thoroughly parts of the @code{strub} machinery that implement the less
+efficient, but interface-preserving @code{strub} mode.  Functions that
+would not be affected by this option are quite uncommon.
 
 @item -fstrub=all
 @opindex fstrub=all
-Enable @code{strub} for all viable functions, and consider non-viable
-functions as @code{callable}.  When both strub modes are viable,
-@code{at-calls} is preferred.
+Enable some @code{strub} mode where viable.  When both strub modes are
+viable, @code{at-calls} is preferred.  @option{-fdump-ipa-strubm} adds
+function attributes that tell which mode was selected for each function.
+The primary use of this option is for testing, to exercise thoroughly
+the @code{strub} machinery.
 
 @item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 @opindex fvtable-verify
@@ -17427,6 +17437,14 @@ and inlining decisions.
 @item inline
 Dump after function inlining.
 
+@item strubm
+Dump after selecting @code{strub} modes, and recording the selections as
+function attributes.
+
+@item strub
+Dump @code{strub} transformations: interface changes, function wrapping,
+and insertion of builtin calls for stack scrubbing and watermarking.
+
 @end table
 
 Additionally, the options @option{-optimized}, @option{-missed},
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 7f4bc44d2bb..932e49dba8e 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -397,7 +397,7 @@ can_inline_edge_p (struct cgraph_edge *e, bool report,
       e->inline_failed = CIF_SANITIZE_ATTRIBUTE_MISMATCH;
       inlinable = false;
     }
-  if (!strub_inlinable_p (callee, caller))
+  if (!strub_inlinable_to_p (callee, caller))
     {
       e->inline_failed = CIF_UNSPECIFIED;
       inlinable = false;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
index cf7d811779e..c262f105070 100644
--- a/gcc/ipa-strub.c
+++ b/gcc/ipa-strub.c
@@ -67,6 +67,7 @@ along with GCC; see the file COPYING3.  If not see
 #else
 # define HAVE_ATTR_FNSPEC 0
 # define FOR_GCC_11P 0
+# define PROP_gimple 0
 #endif
 
 /* Const and pure functions that gain a watermark parameter for strub purposes
@@ -159,12 +160,16 @@ enum strub_mode {
 
 };
 
+/* Look up a strub attribute in TYPE, and return it.  */
+
 static tree
 get_strub_attr_from_type (tree type)
 {
   return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
 }
 
+/* Look up a strub attribute in DECL or in its type, and return it.  */
+
 static tree
 get_strub_attr_from_decl (tree decl)
 {
@@ -174,23 +179,161 @@ get_strub_attr_from_decl (tree decl)
   return get_strub_attr_from_type (TREE_TYPE (decl));
 }
 
-tree
+/* Define a function to cache identifier ID, to be used as a strub attribute
+   parameter for a strub mode named after NAME.  */
+#define DEF_STRUB_IDS(NAME, ID)				\
+static inline tree get_strub_mode_id_ ## NAME () {	\
+  static tree identifier = NULL_TREE;			\
+  if (!identifier)					\
+    identifier = get_identifier (ID);			\
+  return identifier;					\
+}
+/* Same as DEF_STRUB_IDS, but use the string expansion of NAME as ID.  */
+#define DEF_STRUB_ID(NAME)				\
+DEF_STRUB_IDS (NAME, #NAME)
+
+/* Define functions for each of the strub mode identifiers.
+   Expose dashes rather than underscores.  */
+DEF_STRUB_ID (disabled)
+DEF_STRUB_IDS (at_calls, "at-calls")
+DEF_STRUB_ID (internal)
+DEF_STRUB_ID (callable)
+DEF_STRUB_ID (wrapped)
+DEF_STRUB_ID (wrapper)
+DEF_STRUB_ID (inlinable)
+DEF_STRUB_IDS (at_calls_opt, "at-calls-opt")
+
+/* Release the temporary macro names.  */
+#undef DEF_STRUB_IDS
+#undef DEF_STRUB_ID
+
+/* Return the identifier corresponding to strub MODE.  */
+
+static tree
+get_strub_mode_attr_parm (enum strub_mode mode)
+{
+  switch (mode)
+    {
+    case STRUB_DISABLED:
+      return get_strub_mode_id_disabled ();
+
+    case STRUB_AT_CALLS:
+      return get_strub_mode_id_at_calls ();
+
+    case STRUB_INTERNAL:
+      return get_strub_mode_id_internal ();
+
+    case STRUB_CALLABLE:
+      return get_strub_mode_id_callable ();
+
+    case STRUB_WRAPPED:
+      return get_strub_mode_id_wrapped ();
+
+    case STRUB_WRAPPER:
+      return get_strub_mode_id_wrapper ();
+
+    case STRUB_INLINABLE:
+      return get_strub_mode_id_inlinable ();
+
+    case STRUB_AT_CALLS_OPT:
+      return get_strub_mode_id_at_calls_opt ();
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+/* Return the parmeters (TREE_VALUE) for a strub attribute of MODE.
+   We know we use a single parameter, so we bypass the creation of a
+   tree list.  */
+
+static tree
 get_strub_mode_attr_value (enum strub_mode mode)
 {
-  return tree_cons (NULL_TREE,
-		    build_int_cst (integer_type_node, (int)mode),
-		    NULL_TREE);
+  return get_strub_mode_attr_parm (mode);
+}
+
+/* Determine whether ID is a well-formed strub mode-specifying attribute
+   parameter for a function (type).  Only user-visible modes are accepted, and
+   ID must be non-NULL.
+
+   For unacceptable parms, return 0, otherwise a nonzero value as below.
 
-#if 0 /* ??? use symbolic mode names with interned strings?  */
-  char *s = NULL;
+   If the parm enables strub, return positive, otherwise negative.
 
-  switch (strub_mode)
+   If the affected type must be a distinct, incompatible type,return an integer
+   of absolute value 2, otherwise 1.  */
+
+int
+strub_validate_fn_attr_parm (tree id)
+{
+  int ret;
+  const char *s = NULL;
+  size_t len = 0;
+
+  /* do NOT test for NULL.  This is only to be called with non-NULL arguments.
+     We assume that the strub parameter applies to a function, because only
+     functions accept an explicit argument.  If we accepted NULL, and we
+     happened to be called to verify the argument for a variable, our return
+     values would be wrong.  */
+  if (TREE_CODE (id) == STRING_CST)
     {
-      
+      s = TREE_STRING_POINTER (id);
+      len = TREE_STRING_LENGTH (id) - 1;
     }
-#endif
+  else if (TREE_CODE (id) == IDENTIFIER_NODE)
+    {
+      s = IDENTIFIER_POINTER (id);
+      len = IDENTIFIER_LENGTH (id);
+    }
+  else
+    return 0;
+
+  enum strub_mode mode;
+
+  if (len != 8)
+    return 0;
+
+  switch (s[0])
+    {
+    case 'd':
+      mode = STRUB_DISABLED;
+      ret = -1;
+      break;
+
+    case 'a':
+      mode = STRUB_AT_CALLS;
+      ret = 2;
+      break;
+
+    case 'i':
+      mode = STRUB_INTERNAL;
+      ret = 1;
+      break;
+
+    case 'c':
+      mode = STRUB_CALLABLE;
+      ret = -2;
+      break;
+
+    default:
+      /* Other parms are for internal use only.  */
+      return 0;
+    }
+
+  tree mode_id = get_strub_mode_attr_parm (mode);
+
+  if (TREE_CODE (id) == IDENTIFIER_NODE
+      ? id != mode_id
+      : strncmp (s, IDENTIFIER_POINTER (mode_id), len) != 0)
+    return 0;
+
+  return ret;
 }
 
+/* Return the strub mode from STRUB_ATTR.  VAR_P should be true if the attribute
+   is taken from a variable, rather than from a function, or a type thereof.  */
+
 static enum strub_mode
 get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
 {
@@ -200,28 +343,102 @@ get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
     {
       if (!TREE_VALUE (strub_attr))
 	mode = !var_p ? STRUB_AT_CALLS : STRUB_INTERNAL;
-      else if (TREE_CODE (TREE_VALUE (TREE_VALUE (strub_attr))) == INTEGER_CST)
-	mode = (enum strub_mode) tree_to_shwi (TREE_VALUE
-					       (TREE_VALUE (strub_attr)));
-      else /* Handlers convert symbolic mode names to INTEGER_CST.  */
-	gcc_unreachable ();
+      else
+	{
+	  gcc_checking_assert (!var_p);
+	  tree id = TREE_VALUE (strub_attr);
+	  if (TREE_CODE (id) == TREE_LIST)
+	    id = TREE_VALUE (id);
+	  const char *s = (TREE_CODE (id) == STRING_CST
+			   ? TREE_STRING_POINTER (id)
+			   : IDENTIFIER_POINTER (id));
+	  size_t len = (TREE_CODE (id) == STRING_CST
+			? TREE_STRING_LENGTH (id) - 1
+			: IDENTIFIER_LENGTH (id));
+
+	  switch (len)
+	    {
+	    case 7:
+	      switch (s[6])
+		{
+		case 'r':
+		  mode = STRUB_WRAPPER;
+		  break;
+
+		case 'd':
+		  mode = STRUB_WRAPPED;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 8:
+	      switch (s[0])
+		{
+		case 'd':
+		  mode = STRUB_DISABLED;
+		  break;
+
+		case 'a':
+		  mode = STRUB_AT_CALLS;
+		  break;
+
+		case 'i':
+		  mode = STRUB_INTERNAL;
+		  break;
+
+		case 'c':
+		  mode = STRUB_CALLABLE;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 9:
+	      mode = STRUB_INLINABLE;
+	      break;
+
+	    case 12:
+	      mode = STRUB_AT_CALLS_OPT;
+	      break;
+
+	    default:
+	      gcc_unreachable ();
+	    }
+
+	  gcc_checking_assert (TREE_CODE (id) == IDENTIFIER_NODE
+			       ? id == get_strub_mode_attr_parm (mode)
+			       : strncmp (IDENTIFIER_POINTER
+					  (get_strub_mode_attr_parm (mode)),
+					  s, len) == 0);
+	}
     }
 
   return mode;
 }
 
+/* Look up, decode and return the strub mode associated with FNDECL.  */
+
 static enum strub_mode
-get_strub_mode_from_decl (tree fndecl)
+get_strub_mode_from_fndecl (tree fndecl)
 {
   return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
 }
 
+/* Look up, decode and return the strub mode associated with NODE.  */
+
 static enum strub_mode
 get_strub_mode (cgraph_node *node)
 {
-  return get_strub_mode_from_decl (node->decl);
+  return get_strub_mode_from_fndecl (node->decl);
 }
 
+/* Look up, decode and return the strub mode associated with TYPE.  */
+
 static enum strub_mode
 get_strub_mode_from_type (tree type)
 {
@@ -231,12 +448,15 @@ get_strub_mode_from_type (tree type)
   if (attr)
     return get_strub_mode_from_attr (attr, var_p);
 
-  if (flag_strub > 0 && !var_p)
+  if (flag_strub >= -1 && !var_p)
     return STRUB_CALLABLE;
 
   return STRUB_DISABLED;
 }
 
+\f
+/* Return true iff NODE calls builtin va_start.  */
+
 static bool
 calls_builtin_va_start_p (cgraph_node *node)
 {
@@ -252,6 +472,8 @@ calls_builtin_va_start_p (cgraph_node *node)
   return result;
 }
 
+/* Return true iff NODE calls builtin apply_args, and optionally REPORT it.  */
+
 static bool
 calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
 {
@@ -276,12 +498,17 @@ calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE carries the always_inline attribute.  */
+
 static inline bool
 strub_always_inline_p (cgraph_node *node)
 {
   return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
 }
 
+/* Return true iff NODE is potentially eligible for any strub-enabled mode, and
+   optionally REPORT the reasons for ineligibility.  */
+
 static inline bool
 can_strub_p (cgraph_node *node, bool report = false)
 {
@@ -321,6 +548,10 @@ can_strub_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE is eligible for at-calls strub, and optionally REPORT
+   the reasons for ineligibility.  Besides general non-eligibility for
+   strub-enabled modes, at-calls rules out calling builtin apply_args.  */
+
 static bool
 can_strub_at_calls_p (cgraph_node *node, bool report = false)
 {
@@ -332,6 +563,9 @@ can_strub_at_calls_p (cgraph_node *node, bool report = false)
   return !calls_builtin_apply_args_p (node, report);
 }
 
+/* Symbolic macro for the max number of arguments that internal strub may add to
+   a function.  */
+
 #define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
 
 /* We can't perform internal strubbing if the function body involves certain
@@ -438,14 +672,12 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 			   || tree_versionable_function_p (node->decl));
 
 
-      /* Label values referenced are not preserved when copying.  If referenced
+      /* Label values references are not preserved when copying.  If referenced
 	 in nested functions, as in 920415-1.c and 920721-4.c their decls get
-	 remapped independently.  That might be too broad, in that we might be
-	 able to support correctly cases in which the labels are only used
-	 internally in a function, but disconnecting user labels from their
-	 original declarations is undesirable in general, and it probably
-	 doesn't matter, since explicitly-requested strub likely uses
-	 STRUB_AT_CALLS mode anyway.  */
+	 remapped independently.  The exclusion below might be too broad, in
+	 that we might be able to support correctly cases in which the labels
+	 are only used internally in a function, but disconnecting forced labels
+	 from their original declarations is undesirable in general.  */
       basic_block bb;
       FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
 	for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -459,8 +691,6 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 
 	    target = gimple_label_label (label_stmt);
 
-	    /* Make an edge to every label block that has been marked as a
-	       potential target for a computed goto or a non-local goto.  */
 	    if (!FORCED_LABEL (target))
 	      continue;
 
@@ -470,7 +700,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 forced labels");
 	  }
     }
 
@@ -491,6 +721,9 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return TRUE iff NODE has any strub-requiring local variable, or accesses (as
+   in reading) any variable through a strub-requiring type.  */
+
 static bool
 strub_from_body_p (cgraph_node *node)
 {
@@ -506,7 +739,9 @@ strub_from_body_p (cgraph_node *node)
 	!= STRUB_DISABLED)
       return true;
 
-  /* Now scan the body for loads with strub types.  */
+  /* Now scan the body for loads with strub-requiring types.
+     ??? Compound types don't propagate the strub requirement to
+     component types.  */
   basic_block bb;
   FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
     for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -528,6 +763,7 @@ strub_from_body_p (cgraph_node *node)
 
 /* Return true iff node is associated with a builtin that should be callable
    from strub contexts.  */
+
 static inline bool
 strub_callable_builtin_p (cgraph_node *node)
 {
@@ -568,17 +804,23 @@ strub_callable_builtin_p (cgraph_node *node)
     }
 }
 
+/* Compute the strub mode to be used for NODE.  STRUB_ATTR should be the strub
+   attribute,found for NODE, if any.  */
+
 static enum strub_mode
 compute_strub_mode (cgraph_node *node, tree strub_attr)
 {
   enum strub_mode req_mode = get_strub_mode_from_attr (strub_attr);
 
-  gcc_checking_assert (flag_strub >= -1 && flag_strub <= 3);
+  gcc_checking_assert (flag_strub >= -2 && flag_strub <= 3);
 
   /* Symbolic encodings of the -fstrub-* flags.  */
   /* Enable strub when explicitly requested through attributes to functions or
      variables, reporting errors if the requests cannot be satisfied.  */
   const bool strub_flag_auto = flag_strub < 0;
+  /* strub_flag_auto with strub call verification; without this, functions are
+     implicitly callable.  */
+  const bool strub_flag_strict = flag_strub < -1;
   /* Disable strub altogether, ignore attributes entirely.  */
   const bool strub_flag_disabled = flag_strub == 0;
   /* On top of _auto, also enable strub implicitly for functions that can
@@ -625,7 +867,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (!strub_flag_disabled
        && (strub_attr
 	   ? req_mode == STRUB_CALLABLE
-	   : (strub_flag_viable
+	   : (!strub_flag_strict
 	      || strub_callable_builtin_p (node))));
 
   /* This is a shorthand for either strub-enabled mode.  */
@@ -674,19 +916,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (at_calls_eligible
        && (strub_attr
 	   || (node->has_gimple_body_p ()
-#if 0 /* We no longer use collect_callers, so we can probably drop it.  */
-	       && node->get_availability () > AVAIL_INTERPOSABLE
-#endif
-	       && ((!node->externally_visible
-#if 0
-		    /* We wish to bypass the test below for functions that are
-		       not externally visible, but that's a little too broad: we
-		       do not wish to skip them for e.g. gnu_inline
-		       functions.  */
-		    && !TREE_PUBLIC (node->decl)
-		    && !DECL_EXTERNAL (node->decl)
-#endif
-		    )
+	       && (!node->externally_visible
 		   || (node->binds_to_current_def_p ()
 		       && node->can_be_local_p ()))
 	       && node->only_called_directly_p ())));
@@ -737,10 +967,6 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
   const enum strub_mode mode
     = ((strub_enable && is_always_inline)
        ? (strub_required ? STRUB_INLINABLE : STRUB_CALLABLE)
-#if 0
-       : (!strub_enable && strub_required && strub_attr)
-       ? req_mode
-#endif
        : (strub_enable && internal_viable
 	  && (strub_flag_internal || !at_calls_viable))
        ? STRUB_INTERNAL
@@ -802,6 +1028,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 /* Set FNDT's strub mode to MODE; FNDT may be a function decl or
    function type.  If OVERRIDE, do not check whether a mode is already
    set.  */
+
 static void
 strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
 {
@@ -828,12 +1055,18 @@ strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
   *attrp = attr;
 }
 
+/* Set FNDT's strub mode to callable.
+   FNDT may be a function decl or a function type.  */
+
 void
 strub_make_callable (tree fndt)
 {
   strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
 }
 
+/* Set NODE to strub MODE.  Report incompatibilities between MODE and the mode
+   requested through explicit attributes, and cases of non-eligibility.  */
+
 static void
 set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
 {
@@ -853,9 +1086,10 @@ 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",
-		    (int) mode, node->decl,
-		    (int) get_strub_mode_from_attr (attr));
+		    "%<strub%> mode %qE selected for %qD, when %qE was requested",
+		    get_strub_mode_attr_parm (mode),
+		    node->decl,
+		    get_strub_mode_attr_parm (req_mode));
 	  if (node->alias)
 	    {
 	      cgraph_node *target = node->ultimate_alias_target ();
@@ -906,6 +1140,8 @@ set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
   strub_set_fndt_mode_to (node->decl, mode, attr);
 }
 
+/* Compute and set NODE's strub mode.  */
+
 static void
 set_strub_mode (cgraph_node *node)
 {
@@ -946,6 +1182,7 @@ set_strub_mode (cgraph_node *node)
   set_strub_mode_to (node, mode);
 }
 
+\f
 /* Non-strub functions shouldn't be called from within strub contexts,
    except through callable ones.  Always inline strub functions can
    only be called from strub functions.  */
@@ -984,7 +1221,7 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_AT_CALLS_OPT:
     case STRUB_INTERNAL:
     case STRUB_WRAPPER:
-      return (flag_strub >= 0);
+      return (flag_strub >= -1);
 
     case STRUB_DISABLED:
       return false;
@@ -999,12 +1236,16 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
   return true;
 }
 
-/* We wish to avoid inlining WRAPPED functions back into their
-   WRAPPERs.  More generally, we wish to avoid inlining
-   strubbed functions into non-strubbed ones.  */
+/* Return true if CALLEE can be inlined into CALLER.  We wish to avoid inlining
+   WRAPPED functions back into their WRAPPERs.  More generally, we wish to avoid
+   inlining strubbed functions into non-strubbed ones.  CALLER doesn't have to
+   be an immediate caller of CALLEE: the immediate caller may have already been
+   cloned for inlining, and then CALLER may be further up the original call
+   chain.  ???  It would be nice if our own caller would retry inlining callee
+   if caller gets inlined.  */
 
 bool
-strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+strub_inlinable_to_p (cgraph_node *callee, cgraph_node *caller)
 {
   strub_mode callee_mode = get_strub_mode (callee);
 
@@ -1020,6 +1261,10 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_WRAPPER:
     case STRUB_DISABLED:
     case STRUB_CALLABLE:
+      /* When we consider inlining, we've already verified callability, so we
+	 can even inline callable and then disabled into a strub context.  That
+	 will get strubbed along with the context, so it's hopefully not a
+	 problem.  */
       return true;
 
     default:
@@ -1049,14 +1294,43 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
   return false;
 }
 
+/* Check that types T1 and T2 are strub-compatible.  Return 1 if the strub modes
+   are the same, 2 if they are interchangeable, and 0 otherwise.  */
+
+int
+strub_comptypes (tree t1, tree t2)
+{
+  enum strub_mode m1 = get_strub_mode_from_type (t1);
+  enum strub_mode m2 = get_strub_mode_from_type (t2);
+
+  if (m1 == m2)
+    return 1;
+
+  /* We're dealing with types, so only strub modes that can be selected by
+     attributes in the front end matter.  If either mode is at-calls, the
+     conversion is not compatible.  */
+  if (m1 == STRUB_AT_CALLS || m2 == STRUB_AT_CALLS)
+    return 0;
+
+  return 2;
+}
+
 /* Check that strub functions don't call non-strub functions, and that
    always_inline strub functions are only called by strub
    functions.  */
+
 static void
 verify_strub ()
 {
   cgraph_node *node;
 
+  /* It's expected that check strub-wise pointer type compatibility of variables
+     and of functions is already taken care of by front-ends, on account of the
+     attribute's being marked as affecting type identity and of the creation of
+     distinct types.  */
+
+  /* Check that call targets in strub contexts have strub-callable types.  */
+
   FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
   {
     enum strub_mode caller_mode = get_strub_mode (node);
@@ -1105,20 +1379,17 @@ verify_strub ()
 	  }
       }
   }
-
-  /* ??? Check strub-wise pointer type compatibility of variables and
-     functions, or is this already taken care of on account of the
-     attribute's being marked as affecting type identity?  */
 }
 
 namespace {
 
+/* Define a pass to compute strub modes.  */
 const pass_data pass_data_ipa_strub_mode = {
   SIMPLE_IPA_PASS,
   "strubm",
   OPTGROUP_NONE,
   TV_NONE,
-  PROP_cfg, // properties_required
+  PROP_cfg | PROP_gimple, // properties_required
   0,	    // properties_provided
   0,	    // properties_destroyed
   0,	    // properties_start
@@ -1133,24 +1404,26 @@ public:
   {}
   opt_pass *clone () { return new pass_ipa_strub_mode (m_ctxt); }
   virtual bool gate (function *) {
-    /* In the default setting, the attribute handler changes
-       flag_strub to -1 if any strub-enabling occurence of the
-       attribute is found.  If it remains at -2, nothing that would
-       enable strub was found, so we can disable it and avoid the
+    /* In relaxed (-3) and strict (-4) settings, that only enable strub at a
+       function or variable attribute's request, the attribute handler changes
+       flag_strub to -1 or -2, respectively, if any strub-enabling occurence of
+       the attribute is found.  Therefore, if it remains at -3 or -4, nothing
+       that would enable strub was found, so we can disable it and avoid the
        overhead.  */
-    if (flag_strub == -2)
+    if (flag_strub < -2)
       flag_strub = 0;
     return flag_strub;
   }
   virtual unsigned int execute (function *);
 };
 
+/* Define a pass to introduce strub transformations.  */
 const pass_data pass_data_ipa_strub = {
   SIMPLE_IPA_PASS,
   "strub",
   OPTGROUP_NONE,
   TV_NONE,
-  PROP_cfg, // properties_required
+  PROP_cfg | PROP_gimple | PROP_ssa, // properties_required
   0,	    // properties_provided
   0,	    // properties_destroyed
   0,	    // properties_start
@@ -1170,6 +1443,7 @@ public:
   virtual bool gate (function *) { return flag_strub; }
   virtual unsigned int execute (function *);
 
+  /* Define on demand and cache some types we use often.  */
 #define DEF_TYPE(NAME, INIT)			\
   static inline tree get_ ## NAME () {		\
     static tree type = NULL_TREE;		\
@@ -1202,6 +1476,7 @@ public:
 
 #undef DEF_TYPE
 
+  /* Define non-strub builtins on demand.  */
 #define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST)			\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1224,6 +1499,7 @@ public:
 
 #undef DEF_NM_BUILTIN
 
+  /* Define strub builtins on demand.  */
 #define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST)		\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1260,6 +1536,7 @@ public:
 
 #undef DEF_SS_BUILTIN
 
+    /* Define strub identifiers on demand.  */
 #define DEF_IDENT(NAME)					\
   static inline tree get_ ## NAME () {			\
     static tree identifier = NULL_TREE;			\
@@ -1278,6 +1555,10 @@ public:
   static inline void adjust_at_calls_call (cgraph_edge *, int);
   static inline void adjust_at_calls_calls (cgraph_node *);
 
+  /* Add to SEQ a call to the strub watermark update builtin, taking NODE's
+     location if given.  Optionally add the corresponding edge from NODE, with
+     execution frequency COUNT.  Return the modified SEQ.  */
+
   static inline gimple_seq
   call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
 			 gimple_seq seq = NULL)
@@ -1300,8 +1581,15 @@ public:
 
 } // anon namespace
 
+/* Gather with this type a collection of parameters that we're turning into
+   explicit references.  */
+
 typedef hash_set<tree> indirect_parms_t;
 
+/* Dereference OP's incoming turned-into-reference parm if it's an
+   INDIRECT_PARMS or an ADDR_EXPR thereof.  Set *REC and return according to
+   gimple-walking expectations.  */
+
 static tree
 maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
 {
@@ -1324,12 +1612,17 @@ maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
     {
       *rec = 0;
       if (indirect_parms.contains (TREE_OPERAND (op, 0)))
-	return TREE_OPERAND (op, 0);
+	{
+	  op = TREE_OPERAND (op, 0);
+	  return op;
+	}
     }
 
   return NULL_TREE;
 }
 
+/* A gimple-walking function that adds dereferencing to indirect parms.  */
+
 static tree
 walk_make_indirect (tree *op, int *rec, void *arg)
 {
@@ -1351,6 +1644,11 @@ walk_make_indirect (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* A gimple-walking function that turns any non-gimple-val ADDR_EXPRs into a
+   separate SSA.  Though addresses of e.g. parameters, and of members thereof,
+   are gimple vals, turning parameters into references, with an extra layer of
+   indirection and thus explicit dereferencing, need to be regimplified.  */
+
 static tree
 walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
 {
@@ -1374,6 +1672,57 @@ walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* Turn STMT's PHI arg defs into separate SSA defs if they've become
+   non-gimple_val.  Return TRUE if any edge insertions need to be committed.  */
+
+static bool
+walk_regimplify_phi (gphi *stmt)
+{
+  bool needs_commit = false;
+
+  for (unsigned i = 0, n = gimple_phi_num_args (stmt); i < n; i++)
+    {
+      tree op = gimple_phi_arg_def (stmt, i);
+      if ((TREE_CODE (op) == ADDR_EXPR
+	   && !is_gimple_val (op))
+	  /* ??? A PARM_DECL that was addressable in the original function and
+	     had its address in PHI nodes, but that became a reference in the
+	     wrapped clone would NOT be updated by update_ssa in PHI nodes.
+	     Alas, if we were to create a default def for it now, update_ssa
+	     would complain that the symbol that needed rewriting already has
+	     SSA names associated with it.  OTOH, leaving the PARM_DECL alone,
+	     it eventually causes errors because it remains unchanged in PHI
+	     nodes, but it gets rewritten as expected if it appears in other
+	     stmts.  So we cheat a little here, and force the PARM_DECL out of
+	     the PHI node and into an assignment.  It's a little expensive,
+	     because we insert it at the edge, which introduces a basic block
+	     that's entirely unnecessary, but it works, and the block will be
+	     removed as the default def gets propagated back into the PHI node,
+	     so the final optimized code looks just as expected.  */
+	  || (TREE_CODE (op) == PARM_DECL
+	      && !TREE_ADDRESSABLE (op)))
+	{
+	  tree temp = make_ssa_name (TREE_TYPE (op), stmt);
+	  if (TREE_CODE (op) == PARM_DECL)
+	    SET_SSA_NAME_VAR_OR_IDENTIFIER (temp, DECL_NAME (op));
+	  SET_PHI_ARG_DEF (stmt, i, temp);
+
+	  gimple *assign = gimple_build_assign (temp, op);
+	  if (gimple_phi_arg_has_location (stmt, i))
+	    gimple_set_location (assign, gimple_phi_arg_location (stmt, i));
+	  gsi_insert_on_edge (gimple_phi_arg_edge (stmt, i), assign);
+	  needs_commit = true;
+	}
+    }
+
+  return needs_commit;
+}
+
+/* Create a reference type to use for PARM when turning it into a reference.
+   NONALIASED causes the reference type to gain its own separate alias set, so
+   that accessing the indirectly-passed parm won'will not add aliasing
+   noise.  */
+
 static tree
 build_ref_type_for (tree parm, bool nonaliased = true)
 {
@@ -1411,6 +1760,7 @@ build_ref_type_for (tree parm, bool nonaliased = true)
 
 /* Add cgraph edges from current_function_decl to callees in SEQ with frequency
    COUNT, assuming all calls in SEQ are direct.  */
+
 static void
 add_call_edges_for_seq (gimple_seq seq, profile_count count)
 {
@@ -1425,16 +1775,22 @@ add_call_edges_for_seq (gimple_seq seq, profile_count count)
     {
       gimple *stmt = gsi_stmt (gsi);
 
-      if (!is_a <gcall *> (stmt))
+      gcall *call = dyn_cast <gcall *> (stmt);
+      if (!call)
 	continue;
 
-      gcall *call = as_a <gcall *> (stmt);
       tree callee = gimple_call_fndecl (call);
       gcc_checking_assert (callee);
       node->create_edge (cgraph_node::get_create (callee), call, count, false);
     }
 }
 
+/* Insert SEQ after the call at GSI, as if the call was in a try block with SEQ
+   as finally, i.e., SEQ will run after the call whether it returns or
+   propagates an exception.  This handles block splitting, EH edge and block
+   creation, noreturn and nothrow optimizations, and even throwing calls without
+   preexisting local handlers.  */
+
 static void
 gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
 {
@@ -1446,7 +1802,7 @@ gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
   if (gimple_has_location (stmt))
     annotate_all_with_location (seq, gimple_location (stmt));
 
-  gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+  gcall *call = dyn_cast <gcall *> (stmt);
   bool noreturn_p = call && gimple_call_noreturn_p (call);
   int eh_lp = lookup_stmt_eh_lp (stmt);
   bool must_not_throw_p = eh_lp < 0;
@@ -1586,8 +1942,12 @@ remove_named_attribute_unsharing (const char *name, tree *attrs)
     }
 }
 
+/* Record the order of the last cgraph entry whose mode we've already set, so
+   that we can perform mode setting incrementally without duplication.  */
 static int last_cgraph_order;
 
+/* Set strub modes for functions introduced since the last call.  */
+
 static void
 ipa_strub_set_mode_for_new_functions ()
 {
@@ -1616,6 +1976,7 @@ ipa_strub_set_mode_for_new_functions ()
 }
 
 /* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
+
 bool
 strub_splittable_p (cgraph_node *node)
 {
@@ -1641,10 +2002,11 @@ strub_splittable_p (cgraph_node *node)
 }
 
 /* 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))
+  switch (get_strub_mode_from_fndecl (fndecl))
     {
     case STRUB_WRAPPED:
     case STRUB_AT_CALLS:
@@ -1676,6 +2038,7 @@ strub_watermark_parm (tree fndecl)
 
 /* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
    hasn't been added yet.  Return the named argument count.  */
+
 int
 pass_ipa_strub::adjust_at_calls_type (tree type)
 {
@@ -1751,6 +2114,12 @@ pass_ipa_strub::adjust_at_calls_type (tree type)
   return named_args;
 }
 
+/* Adjust a call to an at-calls call target.  Create a watermark local variable
+   if needed, initialize it before, pass it to the callee according to the
+   modified at-calls interface, and release the callee's stack space after the
+   call, if not deferred.  If the call is const or pure, arrange for the
+   watermark to not be assumed unused or unchanged.  */
+
 void
 pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 {
@@ -1819,10 +2188,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 
     if (gimple_call_internal_p (stmt))
 #if 0
-      /*
-	new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
-	vargs);
-      */
+      new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
+						 vargs);
 #endif
       gcc_unreachable ();
     else
@@ -1917,6 +2284,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
   gsi_insert_finally_seq_after_call (gsi, seq);
 }
 
+/* Adjust all at-calls calls in NODE. */
+
 void
 pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
 {
@@ -1965,6 +2334,10 @@ pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
     }
 }
 
+/* The strubm (strub mode) pass computes a strub mode for each function in the
+   call graph, and checks, before any inlining, that strub callability
+   requirements in effect are satisfied.  */
+
 unsigned int
 pass_ipa_strub_mode::execute (function *)
 {
@@ -1977,12 +2350,17 @@ pass_ipa_strub_mode::execute (function *)
   return 0;
 }
 
+/* Create a strub mode pass.  */
+
 simple_ipa_opt_pass *
 make_pass_ipa_strub_mode (gcc::context *ctxt)
 {
   return new pass_ipa_strub_mode (ctxt);
 }
 
+/* The strub pass proper adjusts types, signatures, and at-calls calls, and
+   splits internal-strub functions.  */
+
 unsigned int
 pass_ipa_strub::execute (function *)
 {
@@ -2042,17 +2420,6 @@ pass_ipa_strub::execute (function *)
 	if (onode->alias)
 	  continue;
 
-#if 0 /* Calls are now adjusted when examining callers.  */
-	unsigned c;
-	cgraph_edge *e;
-	FOR_EACH_VEC_ELT (onode->collect_callers (), c, e)
-	  {
-	    push_cfun (DECL_STRUCT_FUNCTION (e->caller->decl));
-	    adjust_at_calls_call (e, named_args);
-	    pop_cfun ();
-	  }
-#endif
-
 	cgraph_node *nnode = onode;
 	push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
 
@@ -2071,10 +2438,10 @@ pass_ipa_strub::execute (function *)
 		{
 		  gimple *stmt = gsi_stmt (gsi);
 
-		  if (!is_gimple_call (stmt))
-		    continue;
+		  gcall *call = dyn_cast <gcall *> (stmt);
 
-		  gcall *call = as_a <gcall *> (stmt);
+		  if (!call)
+		    continue;
 
 		  if (gimple_alloca_call_p (call))
 		    {
@@ -2088,10 +2455,6 @@ pass_ipa_strub::execute (function *)
 	  }
 
 	pop_cfun ();
-
-#if 0
-	compute_fn_summary (onode, true);
-#endif
       }
   }
 
@@ -2108,43 +2471,6 @@ pass_ipa_strub::execute (function *)
 	continue;
       }
 
-#if 0
-    /* Hmm, this is an i386-specific attribute.  Do we need machine-specific
-       logic?  */
-    remove_named_attribute_unsharing ("interrupt",
-				      &DECL_ATTRIBUTES (onode->decl));
-#endif
-
-#if 0
-    if (!DECL_STRUCT_FUNCTION (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting struct-less function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    if (!onode->lowered)
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting non-lowered function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    /* 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.  FIXME.  */
-    if (!tree_versionable_function_p (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"%qD cannot be split for %<strub%>",
-		onode->decl);
-	continue;
-      }
-#endif
-
     bool is_stdarg = calls_builtin_va_start_p (onode);;
     bool apply_args = calls_builtin_apply_args_p (onode);
 
@@ -2753,26 +3079,45 @@ pass_ipa_strub::execute (function *)
       if (any_indirect)
 	{
 	  basic_block bb;
+	  bool needs_commit = false;
 	  FOR_EACH_BB_FN (bb, cfun)
-	    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
-		 !gsi_end_p (gsi); gsi_next (&gsi))
-	      {
-		gimple *stmt = gsi_stmt (gsi);
+	    {
+	      for (gphi_iterator gsi = gsi_start_nonvirtual_phis (bb);
+		   !gsi_end_p (gsi);
+		   gsi_next_nonvirtual_phi (&gsi))
+		{
+		  gphi *stmt = gsi.phi ();
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed && !is_gimple_debug (gsi_stmt (gsi)))
+		    if (walk_regimplify_phi (stmt))
+		      needs_commit = true;
+		}
 
-		walk_stmt_info wi = {};
-		wi.info = &indirect_nparms;
-		walk_gimple_op (stmt, walk_make_indirect, &wi);
-		if (wi.changed)
-		  {
-		    if (!is_gimple_debug (gsi_stmt (gsi)))
-		      {
-			wi.info = &gsi;
-			walk_gimple_op (stmt, walk_regimplify_addr_expr,
-					&wi);
-		      }
-		    update_stmt (stmt);
-		  }
-	      }
+	      for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+		   !gsi_end_p (gsi); gsi_next (&gsi))
+		{
+		  gimple *stmt = gsi_stmt (gsi);
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed)
+		    {
+		      if (!is_gimple_debug (stmt))
+			{
+			  wi.info = &gsi;
+			  walk_gimple_op (stmt, walk_regimplify_addr_expr,
+					  &wi);
+			}
+		      update_stmt (stmt);
+		    }
+		}
+	    }
+	  if (needs_commit)
+	    gsi_commit_edge_inserts ();
 	}
 
       if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca
@@ -2842,10 +3187,10 @@ pass_ipa_strub::execute (function *)
       push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
       gimple_stmt_iterator gsi
 	= gsi_after_labels (single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun)));
-      while (!is_gimple_call (gsi_stmt (gsi)))
-	gsi_next (&gsi);
 
-      gcall *wrcall = as_a <gcall *> (gsi_stmt (gsi));
+      gcall *wrcall;
+      while (!(wrcall = dyn_cast <gcall *> (gsi_stmt (gsi))))
+	gsi_next (&gsi);
 
       tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
       TREE_ADDRESSABLE (swm) = true;
@@ -2973,11 +3318,6 @@ pass_ipa_strub::execute (function *)
 
       pop_cfun ();
     }
-
-#if 0
-    compute_fn_summary (onode, true);
-    compute_fn_summary (nnode, true);
-#endif
   }
 
   if (flag_checking)
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
index c335ab42097..5c0b266c3f8 100644
--- a/gcc/ipa-strub.h
+++ b/gcc/ipa-strub.h
@@ -18,11 +18,10 @@ You should have received a copy of the GNU General Public License
 along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
-/* Return TRUE if the first function can be inlined into the second,
-   as far as stack scrubbing constraints are concerned.  CALLEE
-   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 TRUE if CALLEE can be inlined into CALLER, as far as stack scrubbing
+   constraints are concerned.  CALLEE doesn't have to be called directly by
+   CALLER, but the returned value says nothing about intervening functions.  */
+extern bool strub_inlinable_to_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);
@@ -33,3 +32,14 @@ extern tree strub_watermark_parm (tree fndecl);
 
 /* Make a function type or declaration callable.  */
 extern void strub_make_callable (tree fndecl);
+
+/* Return zero iff ID is NOT an acceptable parameter for a user-supplied strub
+   attribute for a function.  Otherwise, return >0 if it enables strub, <0 if it
+   does not.  Return +/-1 if the attribute-modified type is compatible with the
+   type without the attribute, or +/-2 if it is not compatible.  */
+extern int strub_validate_fn_attr_parm (tree id);
+
+/* Like comptypes, return 0 if t1 and t2 are not compatible, 1 if they are
+   compatible, and 2 if they are nearly compatible.  Same strub mode is
+   compatible, interface-compatible strub modes are nearly compatible.  */
+extern int strub_comptypes (tree t1, tree t2);
diff --git a/gcc/testsuite/c-c++-common/strub-O0.c b/gcc/testsuite/c-c++-common/strub-O0.c
index 6afe0fd5de1..c7a79a6ea0d 100644
--- a/gcc/testsuite/c-c++-common/strub-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O0 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O0, none of the strub builtins are expanded inline.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O1.c b/gcc/testsuite/c-c++-common/strub-O1.c
index 1cdeaecaf32..96285c975d9 100644
--- a/gcc/testsuite/c-c++-common/strub-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O1 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O1, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
index 7848c46d179..8edc0d8aa13 100644
--- a/gcc/testsuite/c-c++-common/strub-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O2, without -fno-inline, we fully expand enter and update, and add a test
    around the leave call.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
index 85a8f76785e..c6d900cf3c4 100644
--- a/gcc/testsuite/c-c++-common/strub-O2fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3.c b/gcc/testsuite/c-c++-common/strub-O3.c
index 1fcde345d36..33ee465e51c 100644
--- a/gcc/testsuite/c-c++-common/strub-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand" } */
 
 int __attribute__ ((__strub__)) var;
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
index a2eedfd96b2..2936f82079e 100644
--- a/gcc/testsuite/c-c++-common/strub-O3fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
index e5cb1f60541..479746e57d8 100644
--- a/gcc/testsuite/c-c++-common/strub-Og.c
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Og -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Og -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Og, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
index 194aacc2c05..2241d4ea07f 100644
--- a/gcc/testsuite/c-c++-common/strub-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Os, without -fno-inline, we fully expand enter, and also update.  The
    expanded update might be larger than a call proper, but argument saving and
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
index 46e84bf6560..a322bcc5da6 100644
--- a/gcc/testsuite/c-c++-common/strub-all1.c
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -22,11 +22,11 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
index f377541cff0..db60026d0e0 100644
--- a/gcc/testsuite/c-c++-common/strub-all2.c
+++ b/gcc/testsuite/c-c++-common/strub-all2.c
@@ -17,8 +17,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
index f180b17f30e..2f462adc1ef 100644
--- a/gcc/testsuite/c-c++-common/strub-apply1.c
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -1,13 +1,13 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
-void __attribute__ ((__strub__ (3)))
+void __attribute__ ((__strub__ ("callable")))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0);
 }
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   void *args = __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
index 379a54b73b7..bab2dd10f5b 100644
--- a/gcc/testsuite/c-c++-common/strub-apply2.c
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 extern void __attribute__ ((__strub__))
 apply_function (void *args);
@@ -10,3 +10,9 @@ apply_args (int i, int j, double d) /* { dg-error "selected" } */
   void *args = __builtin_apply_args (); /* { dg-message "does not support" } */
   apply_function (args);
 }
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After the error and sorry messages in strubm, build_ssa_passes are gated out,
+   so we don't get the PROP_ssa property that targetclone requires.  When
+   checking is not enabled at configure time, we admit to being confused and
+   bail out, but when checking is eneabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
index 9b4786be698..85f9d090fda 100644
--- a/gcc/testsuite/c-c++-common/strub-apply3.c
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -1,8 +1,14 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 void __attribute__ ((__strub__))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */
 }
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
index 409f747743e..15ffaa031b8 100644
--- a/gcc/testsuite/c-c++-common/strub-apply4.c
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+/* { dg-options "-O2 -fstrub=strict -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
@@ -18,4 +18,4 @@ void f() {
   apply_args (1, 2, 3);
 }
 
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 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 d964b07ae5d..b70843b4215 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -22,9 +22,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
index 530eee36d06..97a3988a6b9 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls2.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls2.c
@@ -17,7 +17,7 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-default1.c b/gcc/testsuite/c-c++-common/strub-default1.c
deleted file mode 100644
index 79d00cedb9a..00000000000
--- a/gcc/testsuite/c-c++-common/strub-default1.c
+++ /dev/null
@@ -1,42 +0,0 @@
-/* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
-
-static int __attribute__ ((__strub__)) var;
-
-/* 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 inline void
-__attribute__ ((__always_inline__))
-h() {
-  var++;
-}
-
-/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
-   the viability of at-calls strubbing.  Though internally a strub context, its
-   interface is not strub-enabled, so it's not callable from within strub
-   contexts.  */
-static inline void
-g() {
-  var--;
-  h();
-}
-
-/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
-   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
-void
-f() {
-  var++;
-  g();  /* { dg-error "calling non-.strub." } */
-}
-
-/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-4\[)\]" 1 "strubm" } } */
-/* { 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 "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
index 7b04eea35d9..3d73431b3dc 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O1" } */
+/* { dg-options "-fstrub=strict -O1" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O1.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
index 67d96419a5e..fddf3c745e7 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O2" } */
+/* { dg-options "-fstrub=strict -O2" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O2.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
index 34828d2711e..2bc384ee8d1 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O3" } */
+/* { dg-options "-fstrub=strict -O3" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -O3.  */
@@ -10,7 +10,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -18,7 +18,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -32,7 +32,7 @@ leak_string (void)
   return 0;
 }
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 int
 look_for_string (char *e)
 {
@@ -56,14 +56,14 @@ look_for_string (char *e)
   return 0;
 }
 
-static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+static __attribute__ ((__strub__ ("at-calls"), __noinline__, __noclone__))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 deferred_at_calls ()
 {
@@ -73,7 +73,7 @@ deferred_at_calls ()
   return ret;
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 deferred_internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
index b273660aea1..fbaf85fe0fa 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -Os" } */
+/* { dg-options "-fstrub=strict -Os" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -Os.  */
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
index a74658c9ac9..e9d7b7b9ee0 100644
--- a/gcc/testsuite/c-c++-common/strub-internal1.c
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -23,9 +23,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
index a6e69357b23..8b8e15a51c7 100644
--- a/gcc/testsuite/c-c++-common/strub-internal2.c
+++ b/gcc/testsuite/c-c++-common/strub-internal2.c
@@ -14,8 +14,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
index 7e22a266ad9..0a4a7539d34 100644
--- a/gcc/testsuite/c-c++-common/strub-parms1.c
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -16,7 +16,7 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 large_byref_arg (struct large_arg la)
 {
 }
@@ -24,7 +24,7 @@ large_byref_arg (struct large_arg la)
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*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)))
+void __attribute__ ((__strub__ ("internal")))
 std_arg (int i, ...)
 {
   va_list vl;
@@ -38,7 +38,7 @@ std_arg (int i, ...)
 /* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
index 2d8036c0fbc..147171d96d5 100644
--- a/gcc/testsuite/c-c++-common/strub-parms2.c
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -15,14 +15,14 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 large_byref_arg (struct large_arg la)
 {
 }
 
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 std_arg (int i, ...)
 {
   va_list vl;
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
index f64361f1235..4e92682895a 100644
--- a/gcc/testsuite/c-c++-common/strub-parms3.c
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
new file mode 100644
index 00000000000..e2f9d8aebca
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  Without the error,
+   inlining takes place.  */
+
+#include "strub-strict1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
new file mode 100644
index 00000000000..98474435d2e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  */
+
+#include "strub-strict2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
index a4226ce0119..1de15342595 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fexceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fexceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
index 3bab553478b..f9209c81900 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
index c89cc7c2c47..bed1dcfb54a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
index b869fafb691..6bf0071f52b 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
index 1d3dd2f2c2c..4732f515bf7 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
index 4dd4281b03b..8d6424c479a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-strict1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
new file mode 100644
index 00000000000..7f5247cfaf0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-strict1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+static int __attribute__ ((__strub__)) var;
+
+/* 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 inline void
+__attribute__ ((__always_inline__))
+h() {
+  var++;
+}
+
+/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
+   the viability of at-calls strubbing.  Though internally a strub context, its
+   interface is not strub-enabled, so it's not callable from within strub
+   contexts.  */
+static inline void
+g() {
+  var--;
+  h();
+}
+
+/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
+   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
+void
+f() {
+  var++;
+  g();  /* { dg-error "calling non-.strub." } */
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-default2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
similarity index 54%
rename from gcc/testsuite/c-c++-common/strub-default2.c
rename to gcc/testsuite/c-c++-common/strub-strict2.c
index 487253e9227..17463b6e554 100644
--- a/gcc/testsuite/c-c++-common/strub-default2.c
+++ b/gcc/testsuite/c-c++-common/strub-strict2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
 
 static int __attribute__ ((__strub__)) var;
 
@@ -22,8 +22,14 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
index 0840dddd136..e48e0610e07 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 #include "strub-tail-O2.c"
 
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
index 9330d6ff4c1..87cda7ab21b 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.
    Tail calls are short-circuited at -O2+.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
index 5b33ff1f530..b5e45ab0525 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
index 38935e3270b..96aa7fe4b07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -1,39 +1,39 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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);
+extern int __attribute__ ((__strub__ ("callable"))) xcallable (void);
+extern int __attribute__ ((__strub__ ("internal"))) xinternal (void);
+extern int __attribute__ ((__strub__ ("at-calls"))) xat_calls (void);
+extern int __attribute__ ((__strub__ ("disabled"))) 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__ ("callable"))) callable (void);
+int __attribute__ ((__strub__ ("internal"))) internal (void);
+int __attribute__ ((__strub__ ("at-calls"))) at_calls (void);
+int __attribute__ ((__strub__ ("disabled"))) disabled (void);
 
 int __attribute__ ((__strub__)) var;
 int var_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 icallable (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 iinternal (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 idisabled (void);
 static inline int __attribute__ ((__always_inline__))
 ivar_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 i_callable (void) { return 0; }
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 i_internal (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 i_at_calls (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 i_disabled (void) { return 0; }
 static inline int __attribute__ ((__always_inline__))
 i_var_user (void) { return var; }
@@ -70,7 +70,7 @@ i_var_user (void) { return var; }
 
 /* Not a strub context, so it can call anything.
    Explicitly declared as callable even from within strub contexts.  */
-int __attribute__ ((__strub__ (3)))
+int __attribute__ ((__strub__ ("callable")))
 callable (void) {
   int ret = 0;
 
@@ -88,7 +88,7 @@ callable (void) {
 
 /* 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)))
+int __attribute__ ((__strub__ ("internal")))
 internal (void) {
   int ret = var;
 
@@ -109,7 +109,7 @@ internal (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (1)))
+int __attribute__ ((__strub__ ("at-calls")))
 at_calls (void) {
   int ret = var;
 
@@ -130,7 +130,7 @@ at_calls (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (0)))
+int __attribute__ ((__strub__ ("disabled")))
 disabled () {
   int ret = 0;
 
@@ -205,7 +205,7 @@ iinternal (void) {
   return ret;
 }
 
-int
+int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void) {
   int ret = var;
 
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
index 100fb0c59a9..2857195706e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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,
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
index 9e818ac9748..98a92bc9eac 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
index d40e8aa45cb..5511a6e1e71 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __const__))
 f() {
   return 0;
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
index d4cbdaf10f3..47ee927964d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -1,12 +1,12 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__
 __attribute__ ((__const__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data1.c b/gcc/testsuite/c-c++-common/torture/strub-data1.c
index 62a03891ab6..7c27a2a1a6d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointed-to data enables strubbing if accessed.  */
 int __attribute__ ((__strub__)) var;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data2.c b/gcc/testsuite/c-c++-common/torture/strub-data2.c
index 9b7df13a280..e66d903780a 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointer itself is a strub variable, enabling internal strubbing when
    its value is used.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data3.c b/gcc/testsuite/c-c++-common/torture/strub-data3.c
index 515706caa32..5e08e0e58c6 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data4.c b/gcc/testsuite/c-c++-common/torture/strub-data4.c
index 0ec9e35429f..a818e7a38bb 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
index 69f3d65ed44..90e8ad51c1e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -Werror" } */
+/* { dg-options "-fstrub=strict -Werror" } */
 
 typedef int __attribute__ ((__strub__)) strub_int;
 strub_int *ptr;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
index b8adf8009e8..c165f312f16 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype ();
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
index 5b2c35ad6a7..69fcff8d376 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
index 5ee50456dc9..ff006224909 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int, ...);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
new file mode 100644
index 00000000000..b416ba06b59
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+}
+
+void
+bat (void)
+{
+  /* Not allowed, not a strub context.  */
+  inl_int_ali (); /* { dg-error "context" } */
+}
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
new file mode 100644
index 00000000000..f9a6b4a16fa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=all" } */
+
+#include "strub-inlinable1.c"
+
+/* With -fstrub=all, the caller becomes a strub context, so the strub-inlinable
+   callee is not rejected.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
new file mode 100644
index 00000000000..3fc5a5c4c4d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+typedef void ft (void);
+typedef void ft2 (int, int);
+extern ft __attribute__ ((__strub__)) fnac;
+
+ft * f (void) {
+  return fnac; /* { dg-warning "incompatible" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
new file mode 100644
index 00000000000..7f01cb3372f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
@@ -0,0 +1,19 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic" } */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+
+  d_p = bac; /* { dg-warning "not quite compatible" } */
+  c_p = bad; /* { dg-warning "not quite compatible" } */
+  c_p = bar; /* { dg-warning "not quite compatible" } */
+  c_p = bal; /* { 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
index cb223da6efc..a262a086837 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
index 67d1434b1f8..4c4bd50c209 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
index 59f02ea901f..ce195c6b1f1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -1,10 +1,10 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __pure__))
 f() {
   static int i; /* Stop it from being detected as const.  */
   return i;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
index 973e909217d..75cd54ccb5b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
 __attribute__ ((__pure__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
index a4077c35a60..c596066a045 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -14,7 +14,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -59,14 +59,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
index 94e4156ea73..1fdba214a07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
index 0ca74beb59d..afbc2cc9ab4 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
 /* Check that a non-strub function leaves a string behind in the stack, and that
@@ -7,7 +7,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
index 4ab11c0682e..5300f1d330b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -85,14 +85,14 @@ intermediate ()
   return ret;
 }
 
-static inline __attribute__ ((__strub__ (2)))
+static inline __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
   return intermediate ();
 }
 
-int __attribute__ ((strub (0)))
+int __attribute__ ((__strub__ ("disabled")))
 main ()
 {
   if (look_for_string (internal ()))
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4d.c b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
index e4f7445607c..08de3f1c3b1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4d.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -1,7 +1,7 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
-#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ (1)))
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
 
 #include "strub-run4.c"
diff --git a/gcc/testsuite/g++.dg/torture/strub-init1.C b/gcc/testsuite/g++.dg/torture/strub-init1.C
index e51ae802be4..c226ab10ff6 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init1.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init2.C b/gcc/testsuite/g++.dg/torture/strub-init2.C
index edcb7bf8ad2..a7911f1fa72 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init2.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init3.C b/gcc/testsuite/g++.dg/torture/strub-init3.C
index bacf823ca4e..6ebebcd01e8 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init3.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
index 697ac9de764..2d6f6394993 100644
--- a/gcc/testsuite/gnat.dg/strub_attr.adb
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -1,5 +1,5 @@
 --  { dg-do compile }
---  { dg-options "-fstrub=default -fdump-ipa-strubm" }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
 
 package body Strub_Attr is
    E : exception;
diff --git a/libgcc/strub.c b/libgcc/strub.c
index fd6e27556e4..7c58deee1e6 100644
--- a/libgcc/strub.c
+++ b/libgcc/strub.c
@@ -36,7 +36,7 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 # define TOPS <
 #endif
 
-#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ ("callable")))
 
 /* Enter a stack scrubbing context, initializing the watermark to the caller's
    stack address.  */


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

* [gcc(refs/users/aoliva/heads/strub)] -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests
@ 2021-09-08 19:25 Alexandre Oliva
  0 siblings, 0 replies; 13+ messages in thread
From: Alexandre Oliva @ 2021-09-08 19:25 UTC (permalink / raw)
  To: gcc-cvs

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

commit f402c4e6436a8f2d4d13f6a6890c3e12759d4f72
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Thu Sep 2 23:15:36 2021 -0300

    -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests

Diff:
---
 .../doc/gnat_rm/security_hardening_features.rst    |  50 +-
 gcc/ada/gcc-interface/utils.c                      | 111 +---
 gcc/attribs.c                                      |  39 +-
 gcc/c-family/c-attribs.c                           | 100 +--
 gcc/common.opt                                     |  27 +-
 gcc/doc/extend.texi                                | 332 ++++++++--
 gcc/doc/invoke.texi                                |  76 ++-
 gcc/ipa-inline.c                                   |   2 +-
 gcc/ipa-strub.c                                    | 667 ++++++++++++++++-----
 gcc/ipa-strub.h                                    |  20 +-
 gcc/multiple_target.c                              |   2 +-
 gcc/testsuite/c-c++-common/strub-O0.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O1.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2fni.c           |   2 +-
 gcc/testsuite/c-c++-common/strub-O3.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O3fni.c           |   2 +-
 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            |  12 +-
 gcc/testsuite/c-c++-common/strub-all2.c            |   6 +-
 gcc/testsuite/c-c++-common/strub-apply1.c          |   6 +-
 gcc/testsuite/c-c++-common/strub-apply2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-apply3.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-apply4.c          |   4 +-
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-at-calls2.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-default1.c        |  42 --
 gcc/testsuite/c-c++-common/strub-defer-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |  14 +-
 gcc/testsuite/c-c++-common/strub-defer-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-internal1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-internal2.c       |   6 +-
 gcc/testsuite/c-c++-common/strub-parms1.c          |  10 +-
 gcc/testsuite/c-c++-common/strub-parms2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-parms3.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-relaxed1.c        |  18 +
 gcc/testsuite/c-c++-common/strub-relaxed2.c        |  14 +
 gcc/testsuite/c-c++-common/strub-short-O0-exc.c    |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O0.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O3.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-strict1.c         |  48 ++
 .../{strub-default2.c => strub-strict2.c}          |  14 +-
 gcc/testsuite/c-c++-common/strub-tail-O1.c         |   2 +-
 gcc/testsuite/c-c++-common/strub-tail-O2.c         |   2 +-
 .../c-c++-common/torture/strub-callable1.c         |   2 +-
 .../c-c++-common/torture/strub-callable2.c         |  44 +-
 gcc/testsuite/c-c++-common/torture/strub-const1.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const2.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const3.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-const4.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-data1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data3.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data4.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |   8 +-
 .../c-c++-common/torture/strub-indcall1.c          |   2 +-
 .../c-c++-common/torture/strub-indcall2.c          |   2 +-
 .../c-c++-common/torture/strub-indcall3.c          |   2 +-
 .../c-c++-common/torture/strub-inlinable1.c        |  22 +
 .../c-c++-common/torture/strub-inlinable2.c        |   7 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |  10 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |  55 ++
 gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c  |  50 ++
 gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c  |  43 ++
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure3.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-pure4.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run1.c    |  10 +-
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run4.c    |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run4d.c   |   4 +-
 gcc/testsuite/g++.dg/torture/strub-init1.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init2.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init3.C         |   2 +-
 gcc/testsuite/gnat.dg/strub_attr.adb               |  22 +-
 gcc/testsuite/gnat.dg/strub_ind.adb                |  50 ++
 gcc/testsuite/gnat.dg/strub_ind.ads                |  23 +
 libgcc/strub.c                                     |   2 +-
 85 files changed, 1500 insertions(+), 620 deletions(-)

diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 309291f0341..75345290412 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -19,6 +19,18 @@ It can be enabled with the *-fzero-call-used-regs* command line
 option, to affect all subprograms in a compilation, and with a
 :samp:`Machine_Attribute` pragma, to affect only specific subprograms.
 
+.. code-block:: ada
+
+     procedure Foo;
+     pragma Machine_Attribute (Foo, "zero_call_used_regs", "used");
+     --  Before returning, Foo scrubs only call-clobbered registers
+     --  that it uses itself.
+
+     function Bar return Integer;
+     pragma Machine_Attribute (Bar, "zero_call_used_regs", "all");
+     --  Before returning, Bar scrubs all call-clobbered registers.
+
+
 For usage and more details on the command line option, and on the
 ``zero_call_used_regs`` attribute, see :title:`Using the GNU Compiler
 Collection (GCC)`.
@@ -31,13 +43,31 @@ Stack Scrubbing
 
 GNAT can generate code to zero-out stack frames used by subprograms.
 
-It can be activated with the *-fstrub* command line option, to affect
-all (viable) subprograms in a compilation, and with a
-:samp:`Machine_Attribute` pragma.
+It can be activated with the :samp:`Machine_Attribute` pragma, on
+specific subprograms, variables, and types.
 
-For usage and more details on the command line option, and on the
-``strub`` attribute, see :title:`Using the GNU Compiler Collection
-(GCC)`.
+.. code-block:: ada
+
+     function Foo returns Integer;
+     pragma Machine_Attribute (Foo, "strub");
+     --  Foo and its callers are modified so as to scrub the stack
+     --  space used by Foo after it returns.
+
+     procedure Bar;
+     pragma Machine_Attribute (Bar, "strub", "internal");
+     --  Bar is turned into a wrapper for its original body,
+     --  and they scrub the stack used by the original body.
+
+     Var : Integer;
+     pragma Machine_Attribute (Var, "strub");
+     --  Reading from Var in a subprogram enables stack scrubbing
+     --  of the stack space used by the subprogram.
+
+
+There are also *-fstrub* command line options to control default
+settings.  For usage and more details on the command line option, and
+on the ``strub`` attribute, see :title:`Using the GNU Compiler
+Collection (GCC)`.
 
 Note that Ada secondary stacks are not scrubbed.  The restriction
 ``No_Secondary_Stack`` avoids their use, and thus their accidental
@@ -49,4 +79,10 @@ is not fully reflected in Ada types, ``Access`` attributes, renaming
 and overriding.  Every access type, renaming, and overriding and
 overridden dispatching operations that may refer to an entity with an
 attribute-modified interface must be annotated with the same
-interface-modifying attribute.
+interface-modifying attribute, or with an interface-compatible one.
+
+Even then, applying the pragma to anything other than subprograms and
+scalar variables is not currently supported, and may be silently
+ignored.  Specifically, it is not recommended to rely on any effects
+of this pragma on access types and objects to data variables and to
+subprograms.
diff --git a/gcc/ada/gcc-interface/utils.c b/gcc/ada/gcc-interface/utils.c
index 6b20ddc93c9..193772cc829 100644
--- a/gcc/ada/gcc-interface/utils.c
+++ b/gcc/ada/gcc-interface/utils.c
@@ -39,6 +39,7 @@
 #include "varasm.h"
 #include "toplev.h"
 #include "opts.h"
+#include "ipa-strub.h"
 #include "output.h"
 #include "debug.h"
 #include "convert.h"
@@ -6611,115 +6612,57 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
-  tree orig_fnptr_type = NULL_TREE;
-  tree orig_args = args;
 
   if (args
       && POINTER_TYPE_P (*node)
       && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*node)))
-    {
-      orig_fnptr_type = *node;
-      *node = TREE_TYPE (orig_fnptr_type);
-    }
+    *node = TREE_TYPE (*node);
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      /* pragma Machine_Attribute turns string arguments into identifiers.
-	 Reverse it.  */
-      if (TREE_CODE (TREE_VALUE (args)) == IDENTIFIER_NODE)
-	{
-	  gcc_checking_assert (IDENTIFIER_POINTER (TREE_VALUE (args))
-			       [IDENTIFIER_LENGTH (TREE_VALUE (args))] == 0);
-	  TREE_VALUE (args) = build_string
-	    (IDENTIFIER_LENGTH (TREE_VALUE (args)) + 1,
-	     IDENTIFIER_POINTER (TREE_VALUE (args)));
-	}
-
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
 	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
-
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
-
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
 
   if (args)
-    warning (OPT_Wattributes,
-	     "ignoring excess %qE attribute arguments starting at %qE",
-	     name, TREE_VALUE (args));
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
 
   /* If we see a strub-enabling attribute, and we're at the default setting,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
-
-  if (!*no_add_attrs)
-    {
-      *no_add_attrs = true;
-      if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
-      TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
-					   TYPE_ATTRIBUTES (*node));
-
-      if (orig_fnptr_type)
-	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
-	  TREE_TYPE (fnptr_type) = *node;
-	  *node = fnptr_type;
-	}
-    }
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   return NULL_TREE;
 }
diff --git a/gcc/attribs.c b/gcc/attribs.c
index 0d22c20a35e..07f81285d7a 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "attribs.h"
 #include "fold-const.h"
+#include "ipa-strub.h"
 #include "stor-layout.h"
 #include "langhooks.h"
 #include "plugin.h"
@@ -617,12 +618,11 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  flags &= ~(int) ATTR_FLAG_TYPE_IN_PLACE;
 	}
 
-      if (spec->function_type_required && TREE_CODE (*anode) != FUNCTION_TYPE
-	  && TREE_CODE (*anode) != METHOD_TYPE)
+      if (spec->function_type_required
+	  && !FUNC_OR_METHOD_TYPE_P (*anode))
 	{
 	  if (TREE_CODE (*anode) == POINTER_TYPE
-	      && (TREE_CODE (TREE_TYPE (*anode)) == FUNCTION_TYPE
-		  || TREE_CODE (TREE_TYPE (*anode)) == METHOD_TYPE))
+	      && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
 	    {
 	      /* OK, this is a bit convoluted.  We can't just make a copy
 		 of the pointer type and modify its TREE_TYPE, because if
@@ -715,7 +715,23 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  tree ret = (spec->handler) (cur_and_last_decl, name, args,
 				      flags|cxx11_flag, &no_add_attrs);
 
-	  *anode = cur_and_last_decl[0];
+	  if (*anode != cur_and_last_decl[0])
+	    {
+	      /* Even if !spec->function_type_required, allow the attribute
+		 handler to request the attribute to be applied to the function
+		 type, rather than to the function pointer type, by setting
+		 cur_and_last_decl[0] to the function type.  */
+	      if (!fn_ptr_tmp
+		  && POINTER_TYPE_P (*anode)
+		  && TREE_TYPE (*anode) == cur_and_last_decl[0]
+		  && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
+		{
+		  fn_ptr_tmp = TREE_TYPE (*anode);
+		  fn_ptr_quals = TYPE_QUALS (*anode);
+		  anode = &fn_ptr_tmp;
+		}
+	      *anode = cur_and_last_decl[0];
+	    }
 	  if (ret == error_mark_node)
 	    {
 	      warning (OPT_Wattributes, "%qE attribute ignored", name);
@@ -1353,9 +1369,20 @@ comp_type_attributes (const_tree type1, const_tree type2)
   if ((lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type1)) != NULL)
       ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) != NULL))
     return 0;
+  int strub_ret = strub_comptypes (CONST_CAST_TREE (type1),
+				   CONST_CAST_TREE (type2));
+  if (strub_ret == 0)
+    return strub_ret;
   /* As some type combinations - like default calling-convention - might
      be compatible, we have to call the target hook to get the final result.  */
-  return targetm.comp_type_attributes (type1, type2);
+  int target_ret = targetm.comp_type_attributes (type1, type2);
+  if (target_ret == 0)
+    return target_ret;
+  if (strub_ret == 2 || target_ret == 2)
+    return 2;
+  if (strub_ret == 1 && target_ret == 1)
+    return 1;
+  gcc_unreachable ();
 }
 
 /* PREDICATE acts as a function of type:
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index dc4a4d4d200..0453ac563cc 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "common/common-target.h"
 #include "langhooks.h"
 #include "tree-inline.h"
+#include "ipa-strub.h"
 #include "toplev.h"
 #include "tree-iterator.h"
 #include "opts.h"
@@ -1306,104 +1307,57 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
-  tree orig_fnptr_type = NULL_TREE;
-  tree orig_args = args;
 
   if (args
       && POINTER_TYPE_P (*node)
       && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*node)))
-    {
-      orig_fnptr_type = *node;
-      *node = TREE_TYPE (orig_fnptr_type);
-    }
+    *node = TREE_TYPE (*node);
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
 	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
-
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
-
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
 
   if (args)
-    warning (OPT_Wattributes,
-	     "ignoring excess %qE attribute arguments starting at %qE",
-	     name, TREE_VALUE (args));
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
 
   /* If we see a strub-enabling attribute, and we're at the default setting,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
-
-  if (!*no_add_attrs)
-    {
-      *no_add_attrs = true;
-      if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
-      TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
-					   TYPE_ATTRIBUTES (*node));
-
-      if (orig_fnptr_type)
-	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
-	  TREE_TYPE (fnptr_type) = *node;
-	  *node = fnptr_type;
-	}
-    }
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   return NULL_TREE;
 }
diff --git a/gcc/common.opt b/gcc/common.opt
index 71e0c971344..ef018ebf735 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,13 +2687,22 @@ fstrict-overflow
 Common
 Treat signed overflow as undefined.  Negated as -fwrapv -fwrapv-pointer.
 
-; If any strub-enabling attribute is seen when the default value is
-; selected, it's bumped up to -1.  The scrub mode gate function will
-; then bump -2 to 0 if no strub-enabling attribute is seen.  This
-; minimizes the strub overhead.
-fstrub=default
-Common RejectNegative Var(flag_strub, -2) Init(-2)
-Enable stack scrub as requested through attributes.
+fstrub=disable
+Common RejectNegative Var(flag_strub, 0)
+Disable stack scrub entirely, disregarding strub attributes.
+
+fstrub=strict
+Common RejectNegative Var(flag_strub, -4)
+Enable stack scrub as per attributes, with strict call checking.
+
+; If any strub-enabling attribute is seen when the default or strict
+; initializer values are in effect, flag_strub is bumped up by 2.  The
+; scrub mode gate function will then bump these initializer values to
+; 0 if no strub-enabling attribute is seen.  This minimizes the strub
+; overhead.
+fstrub=relaxed
+Common RejectNegative Var(flag_strub, -3) Init(-3)
+Restore default strub mode: as per attributes, with relaxed checking.
 
 fstrub=all
 Common RejectNegative Var(flag_strub, 3)
@@ -2707,10 +2716,6 @@ fstrub=internal
 Common RejectNegative Var(flag_strub, 2)
 Enable internal stack scrubbing for all viable functions.
 
-fstrub=disable
-Common RejectNegative Var(flag_strub, 0)
-Disable stack scrub entirely, disregarding strub attributes.
-
 fsync-libcalls
 Common Var(flag_sync_libcalls) Init(1)
 Implement __atomic operations via libcalls to legacy __sync functions.
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 1fcf60a3875..d85e695d85c 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -77,7 +77,7 @@ extensions, accepted by GCC in C90 mode and in C++.
 * Function Names::      Printable strings which are the name of the current
                         function.
 * Return Address::      Getting the return or frame address of a function.
-* Stack Scrubbing::     Stack scrubbing interfaces.
+* Stack Scrubbing::     Stack scrubbing internal interfaces.
 * Vector Extensions::   Using vector instructions through built-in functions.
 * Offsetof::            Special syntax for implementing @code{offsetof}.
 * __sync Builtins::     Legacy built-in functions for atomic memory access.
@@ -8729,51 +8729,259 @@ pid_t wait (wait_status_ptr_t p)
 @item strub
 @cindex @code{strub} type attribute
 This attribute defines stack-scrubbing properties of functions and
-variables.  When applied to function types, it takes an optional
-argument.  When applied to a pointer-to-function type, it gets
-propagated to the function type if the optional argument is given.
-
-A function whose type is annotated with @code{at-calls} @code{strub}
-mode (@code{strub("at-calls")}, @code{strub(1)}, or @code{strub})
-undergoes interface changes.  Its callers are adjusted to match the
-changes, and to automatically scrub (overwrite with zeros) the stack
-space used by the function.
-
-A function whose type indicates @code{internal} @code{strub} mode
-(@code{strub("internal")} or @code{strub(2)}) retains an unmodified
-interface, but may be turned into a wrapper that calls the wrapped body
-using a custom interface.  The wrapper then scrubs the stack space used
-by the wrapped body.
-
-An automatically-allocated variable whose type carries the @code{strub}
-attribute causes the enclosing function to have @code{strub} enabled.
-
-A statically-allocated variable whose type carries the @code{strub}
-attribute causes functions that @emph{read} it with that type to have
-@code{strub} enabled.  Reading from data referenced by pointers to such
-types has the same effect.  Note: The attribute does not carry over from
-a composite type to the types of its components, so the intended effect
-may not be obtained with non-scalar types.
-
-A @code{strub} context is the body of a function that has strub enabled,
-be it explicitly, by @code{at-calls} or @code{internal} mode, or
-implicitly, by reading from a @code{strub}-marked data type.
+variables.  Being a type attribute, it attaches to types, even when
+specified in function and variable declarations.  When applied to
+function types, it takes an optional string argument.  When applied to a
+pointer-to-function type, if the optional argument is given, it gets
+propagated to the function type.
+
+@smallexample
+/* A strub variable.  */
+int __attribute__ ((strub)) var;
+/* A strub variable that happens to be a pointer.  */
+__attribute__ ((strub)) int *strub_ptr_to_int;
+/* A pointer type that may point to a strub variable.  */
+typedef int __attribute__ ((strub)) *ptr_to_strub_int_type;
+
+/* A declaration of a strub function.  */
+extern int __attribute__ ((strub)) foo (void);
+/* A pointer to that strub function.  */
+int __attribute__ ((strub ("at-calls"))) (*ptr_to_strub_fn)(void) = foo;
+@end smallexample
+
+A function associated with @code{at-calls} @code{strub} mode
+(@code{strub("at-calls")}, or just @code{strub}) undergoes interface
+changes.  Its callers are adjusted to match the changes, and to scrub
+(overwrite with zeros) the stack space used by the called function after
+it returns.  The interface change makes the function type incompatible
+with an unadorned but otherwise equivalent type, so @emph{every}
+declaration and every type that may be used to call the function must be
+associated with this strub mode.
+
+A function associated with @code{internal} @code{strub} mode
+(@code{strub("internal")}) retains an unmodified, type-compatible
+interface, but it may be turned into a wrapper that calls the wrapped
+body using a custom interface.  The wrapper then scrubs the stack space
+used by the wrapped body.  Though the wrapped body has its stack space
+scrubbed, the wrapper does not, so arguments and return values may
+remain unscrubbed even when such a function is called by another
+function that enables @code{strub}.  This is why, when compiling with
+@option{-fstrub=strict}, a @code{strub} context is not allowed to call
+@code{internal} @code{strub} functions.
+
+@smallexample
+/* A declaration of an internal-strub function.  */
+extern int __attribute__ ((strub ("internal"))) bar (void);
+
+int __attribute__ ((strub))
+baz (void)
+@{
+  /* Ok, foo was declared above as an at-calls strub function.  */
+  foo ();
+  /* Not allowed in strict mode, otherwise allowed.  */
+  bar ();
+@}
+@end smallexample
+
+An automatically-allocated variable associated with the @code{strub}
+attribute causes the (immediately) enclosing function to have
+@code{strub} enabled.
+
+A statically-allocated variable associated with the @code{strub}
+attribute causes functions that @emph{read} it, through its @code{strub}
+data type, to have @code{strub} enabled.  Reading data by dereferencing
+a pointer to a @code{strub} data type has the same effect.  Note: The
+attribute does not carry over from a composite type to the types of its
+components, so the intended effect may not be obtained with non-scalar
+types.
+
+When selecting a @code{strub}-enabled mode for a function that is not
+explicitly associated with one, because of @code{strub} variables or
+data pointers, the function must satisfy @code{internal} mode viability
+requirements (see below), even when @code{at-calls} mode is also viable
+and, being more efficient, ends up selected as an optimization.
+
+@smallexample
+/* zapme is implicitly strub-enabled because of strub variables.
+   Optimization may change its strub mode, but not the requirements.  */
+static int
+zapme (int i)
+@{
+  /* A local strub variable enables strub.  */
+  int __attribute__ ((strub)) lvar;
+  /* Reading strub data through a pointer-to-strub enables strub.  */
+  lvar = * (ptr_to_strub_int_type) &i;
+  /* Writing to a global strub variable does not enable strub.  */
+  var = lvar;
+  /* Reading from a global strub variable enables strub.  */
+  return var;
+@}
+@end smallexample
+
+A @code{strub} context is the body (as opposed to the interface) of a
+function that has @code{strub} enabled, be it explicitly, by
+@code{at-calls} or @code{internal} mode, or implicitly, due to
+@code{strub} variables or command-line options.
 
 A function of a type associated with the @code{disabled} @code{strub}
-mode (@code{strub("disabled")}, @code{strub(0)}, or no @code{strub} mode
-specified) will not have its own stack space scrubbed, and it cannot be
-called from @code{strub} contexts.
+mode (@code{strub("disabled")} will not have its own stack space
+scrubbed.  Such functions @emph{cannot} be called from within
+@code{strub} contexts.
+
+In order to enable a function to be called from within @code{strub}
+contexts without having its stack space scrubbed, associate it with the
+@code{callable} @code{strub} mode (@code{strub("callable")}).
+
+When a function is not assigned a @code{strub} mode, explicitly or
+implicitly, the mode defaults to @code{callable}, except when compiling
+with @option{-fstrub=strict}, that causes @code{strub} mode to default
+to @code{disabled}.
+
+@example
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+ /* Implicitly disabled with -fstrub=strict, otherwise callable.  */
+extern int bah (void);
+
+int __attribute__ ((strub))
+bal (void)
+@{
+  /* Not allowed, bad is not strub-callable.  */
+  bad ();
+  /* Ok, bac is strub-callable.  */
+  bac ();
+  /* Not allowed with -fstrub=strict, otherwise allowed.  */
+  bah ();
+@}
+@end example
 
-A function that does not have @code{strub} enabled can only be called
-from within @code{strub} contexts through a function type marked with
-the @code{callable} @code{strub} mode (@code{strub("callable")} or
-@code{strub(3)}).
+Function types marked @code{callable} and @code{disabled} are not
+mutually compatible types, but the underlying interfaces are compatible,
+so it is safe to convert pointers between them, and to use such pointers
+or alternate declarations to call them.  Interfaces are also
+interchangeable between them and @code{internal} (but not
+@code{at-calls}!), but adding @code{internal} to a pointer type will not
+cause the pointed-to function to perform stack scrubbing.
+
+@example
+void __attribute__ ((strub))
+bap (void)
+@{
+  /* Assign a callable function to pointer-to-disabled.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bac;
+  /* Not allowed: calls disabled type in a strub context.  */
+  d_p ();
+
+  /* Assign a disabled function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bad;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an internal function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  c_p = bar;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an at-calls function to pointer-to-callable.
+     Flaggged as incompatible.  */
+  c_p = bal;
+  /* The call through an interface-incompatible type will not use the
+     modified interface expected by the at-calls function, so it is
+     likely to misbehave at runtime.  */
+  c_p ();
+@}
+@end example
 
 @code{Strub} contexts are never inlined into non-@code{strub} contexts.
-When an @code{internal}-strub function is split, the wrapper can often
-be inlined, but the wrapped body never is.  Functions marked as
-@code{always_inline}, even if explicitly assigned @code{internal} strub
-mode, will not undergo wrapping, so their body gets inlined.
+When an @code{internal}-strub function is split up, the wrapper can
+often be inlined, but the wrapped body @emph{never} is.  A function
+marked as @code{always_inline}, even if explicitly assigned
+@code{internal} strub mode, will not undergo wrapping, so its body gets
+inlined as required.
+
+@example
+inline int __attribute__ ((strub ("at-calls")))
+inl_atc (void)
+@{
+  /* This body may get inlined into strub contexts.  */
+@}
+
+inline int __attribute__ ((strub ("internal")))
+inl_int (void)
+@{
+  /* This body NEVER gets inlined, though its wrapper may.  */
+@}
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+@{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+@}
+
+void __attribute__ ((strub ("disabled")))
+bat (void)
+@{
+  /* Not allowed, cannot inline into a non-strub context.  */
+  inl_int_ali ();
+@}
+@end example
+
+@cindex strub eligibility and viability
+Some @option{-fstrub=*} command line options enable @code{strub} modes
+implicitly where viable.  A @code{strub} mode is only viable for a
+function if the function is eligible for that mode, and if other
+conditions, detailed below, are satisfied.  If it's not eligible for a
+mode, attempts to explicitly associate it with that mode are rejected
+with an error message.  If it is eligible, that mode may be assigned
+explicitly through this attribute, but implicit assignment through
+command-line options may involve additional viability requirements.
+
+A function is ineligible for @code{at-calls} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+@code{At-calls} @code{strub} mode, if not requested through the function
+type, is only viable for an eligible function if the function is not
+visible to other translation units, and if it doesn't have its address
+taken.
+
+@smallexample
+/* bar is eligible for at-calls strub mode,
+   but not viable for that mode because it is visible to other units.
+   It is eligible and viable for internal strub mode.  */
+void bav () @{@}
+
+/* setp is eligible for at-calls strub mode,
+   but not viable for that mode because its address is taken.
+   It is eligible and viable for internal strub mode.  */
+void setp (void) @{ static void (*p)(void); = setp; @}
+@end smallexample
+
+A function is ineligible for @code{internal} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, or if attribute
+@code{noipa} is present.  For an @code{always_inline} function, meeting
+these requirements is enough to make it eligible.  Any function that has
+attribute @code{noclone}, that uses such extensions as non-local labels,
+computed gotos, alternate variable argument passing interfaces,
+@code{__builtin_next_arg}, or @code{__builtin_return_address}, or that
+takes too many (about 64Ki) arguments is ineligible, unless it is
+@code{always_inline}.  For @code{internal} @code{strub} mode, all
+eligible functions are viable.
+
+@smallexample
+/* flop is not eligible, thus not viable, for at-calls strub mode.
+   Likewise for internal strub mode.  */
+__attribute__ ((noipa)) void flop (void) @{@}
+
+/* flip is eligible and viable for at-calls strub mode.
+   It would be ineligible for internal strub mode, because of noclone,
+   if it weren't for always_inline.  With always_inline, noclone is not
+   an obstacle, so it is also eligible and viable for internal strub mode.  */
+inline __attribute__ ((noclone, always_inline)) void flip (void) @{@}
+@end smallexample
 
 @item unused
 @cindex @code{unused} type attribute
@@ -11804,40 +12012,48 @@ This function returns the value of the stack pointer register.
 @end deftypefn
 
 @node Stack Scrubbing
-@section Stack scrubbing interfaces
+@section Stack scrubbing internal interfaces
 
 Stack scrubbing involves cooperation between a @code{strub} context,
 i.e., a function whose stack frame is to be zeroed-out, and its callers.
 The caller initializes a stack watermark, the @code{strub} context
-updates the watermark to reflect its stack use, and the caller zeroes it
-out once it regains control.
-
-Each of these steps relies on a different builtin function call.  The
-functions are available in libgcc but, depending on optimization levels,
-they are expanded internally, adjusted to account for inlining, or
-combined/deferred (e.g. passing the caller-supplied watermark on to
-callees, refraining from erasing stack areas that the caller will).
+updates the watermark according to its stack use, and the caller zeroes
+it out once it regains control, whether by the callee's returning or by
+an exception.
+
+Each of these steps is performed by a different builtin function call.
+Calls to these builtins are introduced automatically, in response to
+@code{strub} attributes and command-line options; they are not expected
+to be explicitly called by source code.
+
+The functions that implement the builtins are available in libgcc but,
+depending on optimization levels, they are expanded internally, adjusted
+to account for inlining, and sometimes combined/deferred (e.g. passing
+the caller-supplied watermark on to callees, refraining from erasing
+stack areas that the caller will) to enable tail calls and to optimize
+for code size.
 
 @deftypefn {Built-in Function} {void} __builtin___strub_enter (void **@var{wmptr})
 This function initializes a stack @var{watermark} variable with the
-current top of the stack.  This builtin function should be called before
-entering a @code{strub} context.  It remains as a function call if optimization
-is not enabled.
+current top of the stack.  A call to this builtin function is introduced
+before entering a @code{strub} context.  It remains as a function call
+if optimization is not enabled.
 @end deftypefn
 
 @deftypefn {Built-in Function} {void} __builtin___strub_update (void **@var{wmptr})
 This function updates a stack @var{watermark} variable with the current
-top of the stack, if it tops the previous watermark.  This builtin
-function should be called within a @code{strub} context whenever
+top of the stack, if it tops the previous watermark.  A call to this
+builtin function is inserted within @code{strub} contexts, whenever
 additional stack space may have been used.  It remains as a function
 call at optimization levels lower than 2.
 @end deftypefn
 
 @deftypefn {Built-in Function} {void} __builtin___strub_leave (void **@var{wmptr})
 This function overwrites the memory area between the current top of the
-stack, and the @var{watermark}ed address.  This builtin function should
-be called after leaving a @code{strub} context.  It remains as a
-function call at optimization levels lower than 3.
+stack, and the @var{watermark}ed address.  A call to this builtin
+function is inserted after leaving a @code{strub} context.  It remains
+as a function call at optimization levels lower than 3, and it is guarded by
+a condition at level 2.
 @end deftypefn
 
 @node Vector Extensions
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 838d9ff1198..afcb37914ac 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,7 +599,8 @@ Objective-C and Objective-C++ Dialects}.
 -fstack-protector-explicit  -fstack-check @gol
 -fstack-limit-register=@var{reg}  -fstack-limit-symbol=@var{sym} @gol
 -fno-stack-limit  -fsplit-stack @gol
--fstrub=default -fstrub=disable -fstrub=at-calls -fstrub=internal -fstrub=all @gol
+-fstrub=disable -fstrub=strict -fstrub=relaxed @gol
+-fstrub=all -fstrub=at-calls -fstrub=internal @gol
 -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
 -fvtv-counts  -fvtv-debug @gol
 -finstrument-functions @gol
@@ -15513,46 +15514,55 @@ without @option{-fsplit-stack} always has a large stack.  Support for
 this is implemented in the gold linker in GNU binutils release 2.21
 and later.
 
-@item -fstrub=default
-@opindex fstrub=default
-Restore the default stack scrub (@code{strub}) setting, namely,
-@code{strub} is only enabled as required by @code{strub} attributes
-associated with function or variable types.  This is only useful to
-override earlier @samp{-fstrub=*} options.
-
 @item -fstrub=disable
 @opindex -fstrub=disable
 Disable stack scrubbing entirely, ignoring any @code{strub} attributes.
+See @xref{Common Type Attributes}.
+
+@item -fstrub=strict
+@opindex fstrub=strict
+Functions default to @code{strub} mode @code{disabled}, and apply
+@option{strict}ly the restriction that only functions associated with
+@code{strub}-@code{callable} modes (@code{at-calls}, @code{callable} and
+@code{always_inline} @code{internal}) are @code{callable} by functions
+with @code{strub}-enabled modes (@code{at-calls} and @code{internal}).
+
+@item -fstrub=relaxed
+@opindex fstrub=relaxed
+Restore the default stack scrub (@code{strub}) setting, namely,
+@code{strub} is only enabled as required by @code{strub} attributes
+associated with function and data types.  @code{Relaxed} means that
+strub contexts are only prevented from calling functions explicitly
+associated with @code{strub} mode @code{disabled}.  This option is only
+useful to override other @option{-fstrub=*} options that precede it in
+the command line.
 
 @item -fstrub=at-calls
 @opindex fstrub=at-calls
-Enable @code{at-calls} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{at-calls} @code{strub} if a different @code{strub}
-mode is explicitly requested, if attribute @code{noipa} is present, or
-if it calls @code{__builtin_apply_args}.  @code{At-calls} @code{strub}
-mode, if not requested through the function type, is only viable for an
-eligible function if the function is not visible to other translation
-units, and it doesn't have its address taken.
+Enable @code{at-calls} @code{strub} mode where viable.  The primary use
+of this option is for testing.  It exercises the @code{strub} machinery
+in scenarios strictly local to a translation unit.  This @code{strub}
+mode modifies function interfaces, so any function that is visible to
+other translation units, or that has its address taken, will @emph{not}
+be affected by this option.  Optimization options may also affect
+viability.  See the @code{strub} attribute documentation for details on
+viability and eligibility requirements.
 
 @item -fstrub=internal
 @opindex fstrub=internal
-Enable @code{internal} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{internal} @code{strub} if a different @code{strub}
-mode is explicitly requested, or if attribute @code{noipa} is present.
-Non-@code{always_inline} functions also become ineligible if attribute
-@code{noclone} is present, if the function uses such features as user
-labels, non-default variable argument interfaces,
-@code{__builtin_next_arg}, or @code{__builtin_return_address}, or if
-they have too many (about 64Ki) arguments.  For @code{internal}
-@code{strub}, all eligible functions are viable.
+Enable @code{internal} @code{strub} mode where viable.  The primary use
+of this option is for testing.  This option is intended to exercise
+thoroughly parts of the @code{strub} machinery that implement the less
+efficient, but interface-preserving @code{strub} mode.  Functions that
+would not be affected by this option are quite uncommon.
 
 @item -fstrub=all
 @opindex fstrub=all
-Enable @code{strub} for all viable functions, and consider non-viable
-functions as @code{callable}.  When both strub modes are viable,
-@code{at-calls} is preferred.
+Enable some @code{strub} mode where viable.  When both strub modes are
+viable, @code{at-calls} is preferred.  @option{-fdump-ipa-strubm} adds
+function attributes that tell which mode was selected for each function.
+The primary use of this option is for testing, to exercise thoroughly
+the @code{strub} machinery.
 
 @item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 @opindex fvtable-verify
@@ -17427,6 +17437,14 @@ and inlining decisions.
 @item inline
 Dump after function inlining.
 
+@item strubm
+Dump after selecting @code{strub} modes, and recording the selections as
+function attributes.
+
+@item strub
+Dump @code{strub} transformations: interface changes, function wrapping,
+and insertion of builtin calls for stack scrubbing and watermarking.
+
 @end table
 
 Additionally, the options @option{-optimized}, @option{-missed},
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 7f4bc44d2bb..932e49dba8e 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -397,7 +397,7 @@ can_inline_edge_p (struct cgraph_edge *e, bool report,
       e->inline_failed = CIF_SANITIZE_ATTRIBUTE_MISMATCH;
       inlinable = false;
     }
-  if (!strub_inlinable_p (callee, caller))
+  if (!strub_inlinable_to_p (callee, caller))
     {
       e->inline_failed = CIF_UNSPECIFIED;
       inlinable = false;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
index cf7d811779e..663fe7b2920 100644
--- a/gcc/ipa-strub.c
+++ b/gcc/ipa-strub.c
@@ -159,12 +159,16 @@ enum strub_mode {
 
 };
 
+/* Look up a strub attribute in TYPE, and return it.  */
+
 static tree
 get_strub_attr_from_type (tree type)
 {
   return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
 }
 
+/* Look up a strub attribute in DECL or in its type, and return it.  */
+
 static tree
 get_strub_attr_from_decl (tree decl)
 {
@@ -174,23 +178,161 @@ get_strub_attr_from_decl (tree decl)
   return get_strub_attr_from_type (TREE_TYPE (decl));
 }
 
-tree
+/* Define a function to cache identifier ID, to be used as a strub attribute
+   parameter for a strub mode named after NAME.  */
+#define DEF_STRUB_IDS(NAME, ID)				\
+static inline tree get_strub_mode_id_ ## NAME () {	\
+  static tree identifier = NULL_TREE;			\
+  if (!identifier)					\
+    identifier = get_identifier (ID);			\
+  return identifier;					\
+}
+/* Same as DEF_STRUB_IDS, but use the string expansion of NAME as ID.  */
+#define DEF_STRUB_ID(NAME)				\
+DEF_STRUB_IDS (NAME, #NAME)
+
+/* Define functions for each of the strub mode identifiers.
+   Expose dashes rather than underscores.  */
+DEF_STRUB_ID (disabled)
+DEF_STRUB_IDS (at_calls, "at-calls")
+DEF_STRUB_ID (internal)
+DEF_STRUB_ID (callable)
+DEF_STRUB_ID (wrapped)
+DEF_STRUB_ID (wrapper)
+DEF_STRUB_ID (inlinable)
+DEF_STRUB_IDS (at_calls_opt, "at-calls-opt")
+
+/* Release the temporary macro names.  */
+#undef DEF_STRUB_IDS
+#undef DEF_STRUB_ID
+
+/* Return the identifier corresponding to strub MODE.  */
+
+static tree
+get_strub_mode_attr_parm (enum strub_mode mode)
+{
+  switch (mode)
+    {
+    case STRUB_DISABLED:
+      return get_strub_mode_id_disabled ();
+
+    case STRUB_AT_CALLS:
+      return get_strub_mode_id_at_calls ();
+
+    case STRUB_INTERNAL:
+      return get_strub_mode_id_internal ();
+
+    case STRUB_CALLABLE:
+      return get_strub_mode_id_callable ();
+
+    case STRUB_WRAPPED:
+      return get_strub_mode_id_wrapped ();
+
+    case STRUB_WRAPPER:
+      return get_strub_mode_id_wrapper ();
+
+    case STRUB_INLINABLE:
+      return get_strub_mode_id_inlinable ();
+
+    case STRUB_AT_CALLS_OPT:
+      return get_strub_mode_id_at_calls_opt ();
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+/* Return the parmeters (TREE_VALUE) for a strub attribute of MODE.
+   We know we use a single parameter, so we bypass the creation of a
+   tree list.  */
+
+static tree
 get_strub_mode_attr_value (enum strub_mode mode)
 {
-  return tree_cons (NULL_TREE,
-		    build_int_cst (integer_type_node, (int)mode),
-		    NULL_TREE);
+  return get_strub_mode_attr_parm (mode);
+}
 
-#if 0 /* ??? use symbolic mode names with interned strings?  */
-  char *s = NULL;
+/* Determine whether ID is a well-formed strub mode-specifying attribute
+   parameter for a function (type).  Only user-visible modes are accepted, and
+   ID must be non-NULL.
 
-  switch (strub_mode)
+   For unacceptable parms, return 0, otherwise a nonzero value as below.
+
+   If the parm enables strub, return positive, otherwise negative.
+
+   If the affected type must be a distinct, incompatible type,return an integer
+   of absolute value 2, otherwise 1.  */
+
+int
+strub_validate_fn_attr_parm (tree id)
+{
+  int ret;
+  const char *s = NULL;
+  size_t len = 0;
+
+  /* do NOT test for NULL.  This is only to be called with non-NULL arguments.
+     We assume that the strub parameter applies to a function, because only
+     functions accept an explicit argument.  If we accepted NULL, and we
+     happened to be called to verify the argument for a variable, our return
+     values would be wrong.  */
+  if (TREE_CODE (id) == STRING_CST)
     {
-      
+      s = TREE_STRING_POINTER (id);
+      len = TREE_STRING_LENGTH (id) - 1;
     }
-#endif
+  else if (TREE_CODE (id) == IDENTIFIER_NODE)
+    {
+      s = IDENTIFIER_POINTER (id);
+      len = IDENTIFIER_LENGTH (id);
+    }
+  else
+    return 0;
+
+  enum strub_mode mode;
+
+  if (len != 8)
+    return 0;
+
+  switch (s[0])
+    {
+    case 'd':
+      mode = STRUB_DISABLED;
+      ret = -1;
+      break;
+
+    case 'a':
+      mode = STRUB_AT_CALLS;
+      ret = 2;
+      break;
+
+    case 'i':
+      mode = STRUB_INTERNAL;
+      ret = 1;
+      break;
+
+    case 'c':
+      mode = STRUB_CALLABLE;
+      ret = -2;
+      break;
+
+    default:
+      /* Other parms are for internal use only.  */
+      return 0;
+    }
+
+  tree mode_id = get_strub_mode_attr_parm (mode);
+
+  if (TREE_CODE (id) == IDENTIFIER_NODE
+      ? id != mode_id
+      : strncmp (s, IDENTIFIER_POINTER (mode_id), len) != 0)
+    return 0;
+
+  return ret;
 }
 
+/* Return the strub mode from STRUB_ATTR.  VAR_P should be true if the attribute
+   is taken from a variable, rather than from a function, or a type thereof.  */
+
 static enum strub_mode
 get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
 {
@@ -200,28 +342,102 @@ get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
     {
       if (!TREE_VALUE (strub_attr))
 	mode = !var_p ? STRUB_AT_CALLS : STRUB_INTERNAL;
-      else if (TREE_CODE (TREE_VALUE (TREE_VALUE (strub_attr))) == INTEGER_CST)
-	mode = (enum strub_mode) tree_to_shwi (TREE_VALUE
-					       (TREE_VALUE (strub_attr)));
-      else /* Handlers convert symbolic mode names to INTEGER_CST.  */
-	gcc_unreachable ();
+      else
+	{
+	  gcc_checking_assert (!var_p);
+	  tree id = TREE_VALUE (strub_attr);
+	  if (TREE_CODE (id) == TREE_LIST)
+	    id = TREE_VALUE (id);
+	  const char *s = (TREE_CODE (id) == STRING_CST
+			   ? TREE_STRING_POINTER (id)
+			   : IDENTIFIER_POINTER (id));
+	  size_t len = (TREE_CODE (id) == STRING_CST
+			? TREE_STRING_LENGTH (id) - 1
+			: IDENTIFIER_LENGTH (id));
+
+	  switch (len)
+	    {
+	    case 7:
+	      switch (s[6])
+		{
+		case 'r':
+		  mode = STRUB_WRAPPER;
+		  break;
+
+		case 'd':
+		  mode = STRUB_WRAPPED;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 8:
+	      switch (s[0])
+		{
+		case 'd':
+		  mode = STRUB_DISABLED;
+		  break;
+
+		case 'a':
+		  mode = STRUB_AT_CALLS;
+		  break;
+
+		case 'i':
+		  mode = STRUB_INTERNAL;
+		  break;
+
+		case 'c':
+		  mode = STRUB_CALLABLE;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 9:
+	      mode = STRUB_INLINABLE;
+	      break;
+
+	    case 12:
+	      mode = STRUB_AT_CALLS_OPT;
+	      break;
+
+	    default:
+	      gcc_unreachable ();
+	    }
+
+	  gcc_checking_assert (TREE_CODE (id) == IDENTIFIER_NODE
+			       ? id == get_strub_mode_attr_parm (mode)
+			       : strncmp (IDENTIFIER_POINTER
+					  (get_strub_mode_attr_parm (mode)),
+					  s, len) == 0);
+	}
     }
 
   return mode;
 }
 
+/* Look up, decode and return the strub mode associated with FNDECL.  */
+
 static enum strub_mode
-get_strub_mode_from_decl (tree fndecl)
+get_strub_mode_from_fndecl (tree fndecl)
 {
   return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
 }
 
+/* Look up, decode and return the strub mode associated with NODE.  */
+
 static enum strub_mode
 get_strub_mode (cgraph_node *node)
 {
-  return get_strub_mode_from_decl (node->decl);
+  return get_strub_mode_from_fndecl (node->decl);
 }
 
+/* Look up, decode and return the strub mode associated with TYPE.  */
+
 static enum strub_mode
 get_strub_mode_from_type (tree type)
 {
@@ -231,12 +447,15 @@ get_strub_mode_from_type (tree type)
   if (attr)
     return get_strub_mode_from_attr (attr, var_p);
 
-  if (flag_strub > 0 && !var_p)
+  if (flag_strub >= -1 && !var_p)
     return STRUB_CALLABLE;
 
   return STRUB_DISABLED;
 }
 
+\f
+/* Return true iff NODE calls builtin va_start.  */
+
 static bool
 calls_builtin_va_start_p (cgraph_node *node)
 {
@@ -252,6 +471,8 @@ calls_builtin_va_start_p (cgraph_node *node)
   return result;
 }
 
+/* Return true iff NODE calls builtin apply_args, and optionally REPORT it.  */
+
 static bool
 calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
 {
@@ -276,12 +497,17 @@ calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE carries the always_inline attribute.  */
+
 static inline bool
 strub_always_inline_p (cgraph_node *node)
 {
   return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
 }
 
+/* Return true iff NODE is potentially eligible for any strub-enabled mode, and
+   optionally REPORT the reasons for ineligibility.  */
+
 static inline bool
 can_strub_p (cgraph_node *node, bool report = false)
 {
@@ -321,6 +547,10 @@ can_strub_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE is eligible for at-calls strub, and optionally REPORT
+   the reasons for ineligibility.  Besides general non-eligibility for
+   strub-enabled modes, at-calls rules out calling builtin apply_args.  */
+
 static bool
 can_strub_at_calls_p (cgraph_node *node, bool report = false)
 {
@@ -332,6 +562,9 @@ can_strub_at_calls_p (cgraph_node *node, bool report = false)
   return !calls_builtin_apply_args_p (node, report);
 }
 
+/* Symbolic macro for the max number of arguments that internal strub may add to
+   a function.  */
+
 #define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
 
 /* We can't perform internal strubbing if the function body involves certain
@@ -438,14 +671,12 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 			   || tree_versionable_function_p (node->decl));
 
 
-      /* Label values referenced are not preserved when copying.  If referenced
+      /* Label values references are not preserved when copying.  If referenced
 	 in nested functions, as in 920415-1.c and 920721-4.c their decls get
-	 remapped independently.  That might be too broad, in that we might be
-	 able to support correctly cases in which the labels are only used
-	 internally in a function, but disconnecting user labels from their
-	 original declarations is undesirable in general, and it probably
-	 doesn't matter, since explicitly-requested strub likely uses
-	 STRUB_AT_CALLS mode anyway.  */
+	 remapped independently.  The exclusion below might be too broad, in
+	 that we might be able to support correctly cases in which the labels
+	 are only used internally in a function, but disconnecting forced labels
+	 from their original declarations is undesirable in general.  */
       basic_block bb;
       FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
 	for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -459,8 +690,6 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 
 	    target = gimple_label_label (label_stmt);
 
-	    /* Make an edge to every label block that has been marked as a
-	       potential target for a computed goto or a non-local goto.  */
 	    if (!FORCED_LABEL (target))
 	      continue;
 
@@ -470,7 +699,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 forced labels");
 	  }
     }
 
@@ -491,6 +720,9 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return TRUE iff NODE has any strub-requiring local variable, or accesses (as
+   in reading) any variable through a strub-requiring type.  */
+
 static bool
 strub_from_body_p (cgraph_node *node)
 {
@@ -506,7 +738,9 @@ strub_from_body_p (cgraph_node *node)
 	!= STRUB_DISABLED)
       return true;
 
-  /* Now scan the body for loads with strub types.  */
+  /* Now scan the body for loads with strub-requiring types.
+     ??? Compound types don't propagate the strub requirement to
+     component types.  */
   basic_block bb;
   FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
     for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -528,6 +762,7 @@ strub_from_body_p (cgraph_node *node)
 
 /* Return true iff node is associated with a builtin that should be callable
    from strub contexts.  */
+
 static inline bool
 strub_callable_builtin_p (cgraph_node *node)
 {
@@ -568,17 +803,23 @@ strub_callable_builtin_p (cgraph_node *node)
     }
 }
 
+/* Compute the strub mode to be used for NODE.  STRUB_ATTR should be the strub
+   attribute,found for NODE, if any.  */
+
 static enum strub_mode
 compute_strub_mode (cgraph_node *node, tree strub_attr)
 {
   enum strub_mode req_mode = get_strub_mode_from_attr (strub_attr);
 
-  gcc_checking_assert (flag_strub >= -1 && flag_strub <= 3);
+  gcc_checking_assert (flag_strub >= -2 && flag_strub <= 3);
 
   /* Symbolic encodings of the -fstrub-* flags.  */
   /* Enable strub when explicitly requested through attributes to functions or
      variables, reporting errors if the requests cannot be satisfied.  */
   const bool strub_flag_auto = flag_strub < 0;
+  /* strub_flag_auto with strub call verification; without this, functions are
+     implicitly callable.  */
+  const bool strub_flag_strict = flag_strub < -1;
   /* Disable strub altogether, ignore attributes entirely.  */
   const bool strub_flag_disabled = flag_strub == 0;
   /* On top of _auto, also enable strub implicitly for functions that can
@@ -625,7 +866,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (!strub_flag_disabled
        && (strub_attr
 	   ? req_mode == STRUB_CALLABLE
-	   : (strub_flag_viable
+	   : (!strub_flag_strict
 	      || strub_callable_builtin_p (node))));
 
   /* This is a shorthand for either strub-enabled mode.  */
@@ -674,19 +915,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (at_calls_eligible
        && (strub_attr
 	   || (node->has_gimple_body_p ()
-#if 0 /* We no longer use collect_callers, so we can probably drop it.  */
-	       && node->get_availability () > AVAIL_INTERPOSABLE
-#endif
-	       && ((!node->externally_visible
-#if 0
-		    /* We wish to bypass the test below for functions that are
-		       not externally visible, but that's a little too broad: we
-		       do not wish to skip them for e.g. gnu_inline
-		       functions.  */
-		    && !TREE_PUBLIC (node->decl)
-		    && !DECL_EXTERNAL (node->decl)
-#endif
-		    )
+	       && (!node->externally_visible
 		   || (node->binds_to_current_def_p ()
 		       && node->can_be_local_p ()))
 	       && node->only_called_directly_p ())));
@@ -737,10 +966,6 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
   const enum strub_mode mode
     = ((strub_enable && is_always_inline)
        ? (strub_required ? STRUB_INLINABLE : STRUB_CALLABLE)
-#if 0
-       : (!strub_enable && strub_required && strub_attr)
-       ? req_mode
-#endif
        : (strub_enable && internal_viable
 	  && (strub_flag_internal || !at_calls_viable))
        ? STRUB_INTERNAL
@@ -802,6 +1027,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 /* Set FNDT's strub mode to MODE; FNDT may be a function decl or
    function type.  If OVERRIDE, do not check whether a mode is already
    set.  */
+
 static void
 strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
 {
@@ -828,12 +1054,18 @@ strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
   *attrp = attr;
 }
 
+/* Set FNDT's strub mode to callable.
+   FNDT may be a function decl or a function type.  */
+
 void
 strub_make_callable (tree fndt)
 {
   strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
 }
 
+/* Set NODE to strub MODE.  Report incompatibilities between MODE and the mode
+   requested through explicit attributes, and cases of non-eligibility.  */
+
 static void
 set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
 {
@@ -853,9 +1085,10 @@ 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",
-		    (int) mode, node->decl,
-		    (int) get_strub_mode_from_attr (attr));
+		    "%<strub%> mode %qE selected for %qD, when %qE was requested",
+		    get_strub_mode_attr_parm (mode),
+		    node->decl,
+		    get_strub_mode_attr_parm (req_mode));
 	  if (node->alias)
 	    {
 	      cgraph_node *target = node->ultimate_alias_target ();
@@ -906,6 +1139,8 @@ set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
   strub_set_fndt_mode_to (node->decl, mode, attr);
 }
 
+/* Compute and set NODE's strub mode.  */
+
 static void
 set_strub_mode (cgraph_node *node)
 {
@@ -946,6 +1181,7 @@ set_strub_mode (cgraph_node *node)
   set_strub_mode_to (node, mode);
 }
 
+\f
 /* Non-strub functions shouldn't be called from within strub contexts,
    except through callable ones.  Always inline strub functions can
    only be called from strub functions.  */
@@ -984,7 +1220,7 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_AT_CALLS_OPT:
     case STRUB_INTERNAL:
     case STRUB_WRAPPER:
-      return (flag_strub >= 0);
+      return (flag_strub >= -1);
 
     case STRUB_DISABLED:
       return false;
@@ -999,12 +1235,16 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
   return true;
 }
 
-/* We wish to avoid inlining WRAPPED functions back into their
-   WRAPPERs.  More generally, we wish to avoid inlining
-   strubbed functions into non-strubbed ones.  */
+/* Return true if CALLEE can be inlined into CALLER.  We wish to avoid inlining
+   WRAPPED functions back into their WRAPPERs.  More generally, we wish to avoid
+   inlining strubbed functions into non-strubbed ones.  CALLER doesn't have to
+   be an immediate caller of CALLEE: the immediate caller may have already been
+   cloned for inlining, and then CALLER may be further up the original call
+   chain.  ???  It would be nice if our own caller would retry inlining callee
+   if caller gets inlined.  */
 
 bool
-strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+strub_inlinable_to_p (cgraph_node *callee, cgraph_node *caller)
 {
   strub_mode callee_mode = get_strub_mode (callee);
 
@@ -1020,6 +1260,10 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_WRAPPER:
     case STRUB_DISABLED:
     case STRUB_CALLABLE:
+      /* When we consider inlining, we've already verified callability, so we
+	 can even inline callable and then disabled into a strub context.  That
+	 will get strubbed along with the context, so it's hopefully not a
+	 problem.  */
       return true;
 
     default:
@@ -1049,20 +1293,56 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
   return false;
 }
 
+/* Check that types T1 and T2 are strub-compatible.  Return 1 if the strub modes
+   are the same, 2 if they are interchangeable, and 0 otherwise.  */
+
+int
+strub_comptypes (tree t1, tree t2)
+{
+  if (TREE_CODE (t1) != TREE_CODE (t2))
+    return 0;
+
+  enum strub_mode m1 = get_strub_mode_from_type (t1);
+  enum strub_mode m2 = get_strub_mode_from_type (t2);
+
+  if (m1 == m2)
+    return 1;
+
+  /* We're dealing with types, so only strub modes that can be selected by
+     attributes in the front end matter.  If either mode is at-calls (for
+     functions) or internal (for variables), the conversion is not
+     compatible.  */
+  bool var_p = !FUNC_OR_METHOD_TYPE_P (t1);
+  enum strub_mode mr = var_p ? STRUB_INTERNAL : STRUB_AT_CALLS;
+  if (m1 == mr || m2 == mr)
+    return 0;
+
+  return 2;
+}
+
 /* Check that strub functions don't call non-strub functions, and that
    always_inline strub functions are only called by strub
    functions.  */
+
 static void
 verify_strub ()
 {
   cgraph_node *node;
 
+  /* It's expected that check strub-wise pointer type compatibility of variables
+     and of functions is already taken care of by front-ends, on account of the
+     attribute's being marked as affecting type identity and of the creation of
+     distinct types.  */
+
+  /* Check that call targets in strub contexts have strub-callable types.  */
+
   FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
   {
     enum strub_mode caller_mode = get_strub_mode (node);
     bool strub_context
       = (caller_mode == STRUB_AT_CALLS
 	 || caller_mode == STRUB_AT_CALLS_OPT
+	 || caller_mode == STRUB_INTERNAL
 	 || caller_mode == STRUB_WRAPPED
 	 || caller_mode == STRUB_INLINABLE);
 
@@ -1105,14 +1385,11 @@ verify_strub ()
 	  }
       }
   }
-
-  /* ??? Check strub-wise pointer type compatibility of variables and
-     functions, or is this already taken care of on account of the
-     attribute's being marked as affecting type identity?  */
 }
 
 namespace {
 
+/* Define a pass to compute strub modes.  */
 const pass_data pass_data_ipa_strub_mode = {
   SIMPLE_IPA_PASS,
   "strubm",
@@ -1133,24 +1410,26 @@ public:
   {}
   opt_pass *clone () { return new pass_ipa_strub_mode (m_ctxt); }
   virtual bool gate (function *) {
-    /* In the default setting, the attribute handler changes
-       flag_strub to -1 if any strub-enabling occurence of the
-       attribute is found.  If it remains at -2, nothing that would
-       enable strub was found, so we can disable it and avoid the
+    /* In relaxed (-3) and strict (-4) settings, that only enable strub at a
+       function or variable attribute's request, the attribute handler changes
+       flag_strub to -1 or -2, respectively, if any strub-enabling occurence of
+       the attribute is found.  Therefore, if it remains at -3 or -4, nothing
+       that would enable strub was found, so we can disable it and avoid the
        overhead.  */
-    if (flag_strub == -2)
+    if (flag_strub < -2)
       flag_strub = 0;
     return flag_strub;
   }
   virtual unsigned int execute (function *);
 };
 
+/* Define a pass to introduce strub transformations.  */
 const pass_data pass_data_ipa_strub = {
   SIMPLE_IPA_PASS,
   "strub",
   OPTGROUP_NONE,
   TV_NONE,
-  PROP_cfg, // properties_required
+  PROP_cfg | PROP_ssa, // properties_required
   0,	    // properties_provided
   0,	    // properties_destroyed
   0,	    // properties_start
@@ -1167,9 +1446,10 @@ public:
     : simple_ipa_opt_pass (pass_data_ipa_strub, ctxt)
   {}
   opt_pass *clone () { return new pass_ipa_strub (m_ctxt); }
-  virtual bool gate (function *) { return flag_strub; }
+  virtual bool gate (function *) { return flag_strub && !seen_error (); }
   virtual unsigned int execute (function *);
 
+  /* Define on demand and cache some types we use often.  */
 #define DEF_TYPE(NAME, INIT)			\
   static inline tree get_ ## NAME () {		\
     static tree type = NULL_TREE;		\
@@ -1202,6 +1482,7 @@ public:
 
 #undef DEF_TYPE
 
+  /* Define non-strub builtins on demand.  */
 #define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST)			\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1224,6 +1505,7 @@ public:
 
 #undef DEF_NM_BUILTIN
 
+  /* Define strub builtins on demand.  */
 #define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST)		\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1260,6 +1542,7 @@ public:
 
 #undef DEF_SS_BUILTIN
 
+    /* Define strub identifiers on demand.  */
 #define DEF_IDENT(NAME)					\
   static inline tree get_ ## NAME () {			\
     static tree identifier = NULL_TREE;			\
@@ -1278,6 +1561,10 @@ public:
   static inline void adjust_at_calls_call (cgraph_edge *, int);
   static inline void adjust_at_calls_calls (cgraph_node *);
 
+  /* Add to SEQ a call to the strub watermark update builtin, taking NODE's
+     location if given.  Optionally add the corresponding edge from NODE, with
+     execution frequency COUNT.  Return the modified SEQ.  */
+
   static inline gimple_seq
   call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
 			 gimple_seq seq = NULL)
@@ -1300,8 +1587,15 @@ public:
 
 } // anon namespace
 
+/* Gather with this type a collection of parameters that we're turning into
+   explicit references.  */
+
 typedef hash_set<tree> indirect_parms_t;
 
+/* Dereference OP's incoming turned-into-reference parm if it's an
+   INDIRECT_PARMS or an ADDR_EXPR thereof.  Set *REC and return according to
+   gimple-walking expectations.  */
+
 static tree
 maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
 {
@@ -1324,12 +1618,17 @@ maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
     {
       *rec = 0;
       if (indirect_parms.contains (TREE_OPERAND (op, 0)))
-	return TREE_OPERAND (op, 0);
+	{
+	  op = TREE_OPERAND (op, 0);
+	  return op;
+	}
     }
 
   return NULL_TREE;
 }
 
+/* A gimple-walking function that adds dereferencing to indirect parms.  */
+
 static tree
 walk_make_indirect (tree *op, int *rec, void *arg)
 {
@@ -1351,6 +1650,11 @@ walk_make_indirect (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* A gimple-walking function that turns any non-gimple-val ADDR_EXPRs into a
+   separate SSA.  Though addresses of e.g. parameters, and of members thereof,
+   are gimple vals, turning parameters into references, with an extra layer of
+   indirection and thus explicit dereferencing, need to be regimplified.  */
+
 static tree
 walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
 {
@@ -1374,6 +1678,57 @@ walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* Turn STMT's PHI arg defs into separate SSA defs if they've become
+   non-gimple_val.  Return TRUE if any edge insertions need to be committed.  */
+
+static bool
+walk_regimplify_phi (gphi *stmt)
+{
+  bool needs_commit = false;
+
+  for (unsigned i = 0, n = gimple_phi_num_args (stmt); i < n; i++)
+    {
+      tree op = gimple_phi_arg_def (stmt, i);
+      if ((TREE_CODE (op) == ADDR_EXPR
+	   && !is_gimple_val (op))
+	  /* ??? A PARM_DECL that was addressable in the original function and
+	     had its address in PHI nodes, but that became a reference in the
+	     wrapped clone would NOT be updated by update_ssa in PHI nodes.
+	     Alas, if we were to create a default def for it now, update_ssa
+	     would complain that the symbol that needed rewriting already has
+	     SSA names associated with it.  OTOH, leaving the PARM_DECL alone,
+	     it eventually causes errors because it remains unchanged in PHI
+	     nodes, but it gets rewritten as expected if it appears in other
+	     stmts.  So we cheat a little here, and force the PARM_DECL out of
+	     the PHI node and into an assignment.  It's a little expensive,
+	     because we insert it at the edge, which introduces a basic block
+	     that's entirely unnecessary, but it works, and the block will be
+	     removed as the default def gets propagated back into the PHI node,
+	     so the final optimized code looks just as expected.  */
+	  || (TREE_CODE (op) == PARM_DECL
+	      && !TREE_ADDRESSABLE (op)))
+	{
+	  tree temp = make_ssa_name (TREE_TYPE (op), stmt);
+	  if (TREE_CODE (op) == PARM_DECL)
+	    SET_SSA_NAME_VAR_OR_IDENTIFIER (temp, DECL_NAME (op));
+	  SET_PHI_ARG_DEF (stmt, i, temp);
+
+	  gimple *assign = gimple_build_assign (temp, op);
+	  if (gimple_phi_arg_has_location (stmt, i))
+	    gimple_set_location (assign, gimple_phi_arg_location (stmt, i));
+	  gsi_insert_on_edge (gimple_phi_arg_edge (stmt, i), assign);
+	  needs_commit = true;
+	}
+    }
+
+  return needs_commit;
+}
+
+/* Create a reference type to use for PARM when turning it into a reference.
+   NONALIASED causes the reference type to gain its own separate alias set, so
+   that accessing the indirectly-passed parm won'will not add aliasing
+   noise.  */
+
 static tree
 build_ref_type_for (tree parm, bool nonaliased = true)
 {
@@ -1411,6 +1766,7 @@ build_ref_type_for (tree parm, bool nonaliased = true)
 
 /* Add cgraph edges from current_function_decl to callees in SEQ with frequency
    COUNT, assuming all calls in SEQ are direct.  */
+
 static void
 add_call_edges_for_seq (gimple_seq seq, profile_count count)
 {
@@ -1425,16 +1781,22 @@ add_call_edges_for_seq (gimple_seq seq, profile_count count)
     {
       gimple *stmt = gsi_stmt (gsi);
 
-      if (!is_a <gcall *> (stmt))
+      gcall *call = dyn_cast <gcall *> (stmt);
+      if (!call)
 	continue;
 
-      gcall *call = as_a <gcall *> (stmt);
       tree callee = gimple_call_fndecl (call);
       gcc_checking_assert (callee);
       node->create_edge (cgraph_node::get_create (callee), call, count, false);
     }
 }
 
+/* Insert SEQ after the call at GSI, as if the call was in a try block with SEQ
+   as finally, i.e., SEQ will run after the call whether it returns or
+   propagates an exception.  This handles block splitting, EH edge and block
+   creation, noreturn and nothrow optimizations, and even throwing calls without
+   preexisting local handlers.  */
+
 static void
 gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
 {
@@ -1446,7 +1808,7 @@ gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
   if (gimple_has_location (stmt))
     annotate_all_with_location (seq, gimple_location (stmt));
 
-  gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+  gcall *call = dyn_cast <gcall *> (stmt);
   bool noreturn_p = call && gimple_call_noreturn_p (call);
   int eh_lp = lookup_stmt_eh_lp (stmt);
   bool must_not_throw_p = eh_lp < 0;
@@ -1586,8 +1948,12 @@ remove_named_attribute_unsharing (const char *name, tree *attrs)
     }
 }
 
+/* Record the order of the last cgraph entry whose mode we've already set, so
+   that we can perform mode setting incrementally without duplication.  */
 static int last_cgraph_order;
 
+/* Set strub modes for functions introduced since the last call.  */
+
 static void
 ipa_strub_set_mode_for_new_functions ()
 {
@@ -1616,6 +1982,7 @@ ipa_strub_set_mode_for_new_functions ()
 }
 
 /* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
+
 bool
 strub_splittable_p (cgraph_node *node)
 {
@@ -1641,10 +2008,11 @@ strub_splittable_p (cgraph_node *node)
 }
 
 /* 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))
+  switch (get_strub_mode_from_fndecl (fndecl))
     {
     case STRUB_WRAPPED:
     case STRUB_AT_CALLS:
@@ -1676,6 +2044,7 @@ strub_watermark_parm (tree fndecl)
 
 /* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
    hasn't been added yet.  Return the named argument count.  */
+
 int
 pass_ipa_strub::adjust_at_calls_type (tree type)
 {
@@ -1751,6 +2120,12 @@ pass_ipa_strub::adjust_at_calls_type (tree type)
   return named_args;
 }
 
+/* Adjust a call to an at-calls call target.  Create a watermark local variable
+   if needed, initialize it before, pass it to the callee according to the
+   modified at-calls interface, and release the callee's stack space after the
+   call, if not deferred.  If the call is const or pure, arrange for the
+   watermark to not be assumed unused or unchanged.  */
+
 void
 pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 {
@@ -1819,10 +2194,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 
     if (gimple_call_internal_p (stmt))
 #if 0
-      /*
-	new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
-	vargs);
-      */
+      new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
+						 vargs);
 #endif
       gcc_unreachable ();
     else
@@ -1917,6 +2290,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
   gsi_insert_finally_seq_after_call (gsi, seq);
 }
 
+/* Adjust all at-calls calls in NODE. */
+
 void
 pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
 {
@@ -1965,6 +2340,10 @@ pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
     }
 }
 
+/* The strubm (strub mode) pass computes a strub mode for each function in the
+   call graph, and checks, before any inlining, that strub callability
+   requirements in effect are satisfied.  */
+
 unsigned int
 pass_ipa_strub_mode::execute (function *)
 {
@@ -1977,12 +2356,17 @@ pass_ipa_strub_mode::execute (function *)
   return 0;
 }
 
+/* Create a strub mode pass.  */
+
 simple_ipa_opt_pass *
 make_pass_ipa_strub_mode (gcc::context *ctxt)
 {
   return new pass_ipa_strub_mode (ctxt);
 }
 
+/* The strub pass proper adjusts types, signatures, and at-calls calls, and
+   splits internal-strub functions.  */
+
 unsigned int
 pass_ipa_strub::execute (function *)
 {
@@ -2042,17 +2426,6 @@ pass_ipa_strub::execute (function *)
 	if (onode->alias)
 	  continue;
 
-#if 0 /* Calls are now adjusted when examining callers.  */
-	unsigned c;
-	cgraph_edge *e;
-	FOR_EACH_VEC_ELT (onode->collect_callers (), c, e)
-	  {
-	    push_cfun (DECL_STRUCT_FUNCTION (e->caller->decl));
-	    adjust_at_calls_call (e, named_args);
-	    pop_cfun ();
-	  }
-#endif
-
 	cgraph_node *nnode = onode;
 	push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
 
@@ -2071,10 +2444,10 @@ pass_ipa_strub::execute (function *)
 		{
 		  gimple *stmt = gsi_stmt (gsi);
 
-		  if (!is_gimple_call (stmt))
-		    continue;
+		  gcall *call = dyn_cast <gcall *> (stmt);
 
-		  gcall *call = as_a <gcall *> (stmt);
+		  if (!call)
+		    continue;
 
 		  if (gimple_alloca_call_p (call))
 		    {
@@ -2088,10 +2461,6 @@ pass_ipa_strub::execute (function *)
 	  }
 
 	pop_cfun ();
-
-#if 0
-	compute_fn_summary (onode, true);
-#endif
       }
   }
 
@@ -2108,43 +2477,6 @@ pass_ipa_strub::execute (function *)
 	continue;
       }
 
-#if 0
-    /* Hmm, this is an i386-specific attribute.  Do we need machine-specific
-       logic?  */
-    remove_named_attribute_unsharing ("interrupt",
-				      &DECL_ATTRIBUTES (onode->decl));
-#endif
-
-#if 0
-    if (!DECL_STRUCT_FUNCTION (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting struct-less function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    if (!onode->lowered)
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting non-lowered function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    /* 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.  FIXME.  */
-    if (!tree_versionable_function_p (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"%qD cannot be split for %<strub%>",
-		onode->decl);
-	continue;
-      }
-#endif
-
     bool is_stdarg = calls_builtin_va_start_p (onode);;
     bool apply_args = calls_builtin_apply_args_p (onode);
 
@@ -2753,26 +3085,45 @@ pass_ipa_strub::execute (function *)
       if (any_indirect)
 	{
 	  basic_block bb;
+	  bool needs_commit = false;
 	  FOR_EACH_BB_FN (bb, cfun)
-	    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
-		 !gsi_end_p (gsi); gsi_next (&gsi))
-	      {
-		gimple *stmt = gsi_stmt (gsi);
+	    {
+	      for (gphi_iterator gsi = gsi_start_nonvirtual_phis (bb);
+		   !gsi_end_p (gsi);
+		   gsi_next_nonvirtual_phi (&gsi))
+		{
+		  gphi *stmt = gsi.phi ();
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed && !is_gimple_debug (gsi_stmt (gsi)))
+		    if (walk_regimplify_phi (stmt))
+		      needs_commit = true;
+		}
 
-		walk_stmt_info wi = {};
-		wi.info = &indirect_nparms;
-		walk_gimple_op (stmt, walk_make_indirect, &wi);
-		if (wi.changed)
-		  {
-		    if (!is_gimple_debug (gsi_stmt (gsi)))
-		      {
-			wi.info = &gsi;
-			walk_gimple_op (stmt, walk_regimplify_addr_expr,
-					&wi);
-		      }
-		    update_stmt (stmt);
-		  }
-	      }
+	      for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+		   !gsi_end_p (gsi); gsi_next (&gsi))
+		{
+		  gimple *stmt = gsi_stmt (gsi);
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed)
+		    {
+		      if (!is_gimple_debug (stmt))
+			{
+			  wi.info = &gsi;
+			  walk_gimple_op (stmt, walk_regimplify_addr_expr,
+					  &wi);
+			}
+		      update_stmt (stmt);
+		    }
+		}
+	    }
+	  if (needs_commit)
+	    gsi_commit_edge_inserts ();
 	}
 
       if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca
@@ -2842,10 +3193,10 @@ pass_ipa_strub::execute (function *)
       push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
       gimple_stmt_iterator gsi
 	= gsi_after_labels (single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun)));
-      while (!is_gimple_call (gsi_stmt (gsi)))
-	gsi_next (&gsi);
 
-      gcall *wrcall = as_a <gcall *> (gsi_stmt (gsi));
+      gcall *wrcall;
+      while (!(wrcall = dyn_cast <gcall *> (gsi_stmt (gsi))))
+	gsi_next (&gsi);
 
       tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
       TREE_ADDRESSABLE (swm) = true;
@@ -2973,22 +3324,8 @@ pass_ipa_strub::execute (function *)
 
       pop_cfun ();
     }
-
-#if 0
-    compute_fn_summary (onode, true);
-    compute_fn_summary (nnode, true);
-#endif
   }
 
-  if (flag_checking)
-    {
-      /* We've already verified before any inlining or other transformations.
-	 Recheck after strub transformations only if checking is enabled, since
-	 they should not introduce any incompatibilities.  */
-      ipa_strub_set_mode_for_new_functions ();
-      verify_strub ();
-    }
-
   return 0;
 }
 
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
index c335ab42097..5c0b266c3f8 100644
--- a/gcc/ipa-strub.h
+++ b/gcc/ipa-strub.h
@@ -18,11 +18,10 @@ You should have received a copy of the GNU General Public License
 along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
-/* Return TRUE if the first function can be inlined into the second,
-   as far as stack scrubbing constraints are concerned.  CALLEE
-   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 TRUE if CALLEE can be inlined into CALLER, as far as stack scrubbing
+   constraints are concerned.  CALLEE doesn't have to be called directly by
+   CALLER, but the returned value says nothing about intervening functions.  */
+extern bool strub_inlinable_to_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);
@@ -33,3 +32,14 @@ extern tree strub_watermark_parm (tree fndecl);
 
 /* Make a function type or declaration callable.  */
 extern void strub_make_callable (tree fndecl);
+
+/* Return zero iff ID is NOT an acceptable parameter for a user-supplied strub
+   attribute for a function.  Otherwise, return >0 if it enables strub, <0 if it
+   does not.  Return +/-1 if the attribute-modified type is compatible with the
+   type without the attribute, or +/-2 if it is not compatible.  */
+extern int strub_validate_fn_attr_parm (tree id);
+
+/* Like comptypes, return 0 if t1 and t2 are not compatible, 1 if they are
+   compatible, and 2 if they are nearly compatible.  Same strub mode is
+   compatible, interface-compatible strub modes are nearly compatible.  */
+extern int strub_comptypes (tree t1, tree t2);
diff --git a/gcc/multiple_target.c b/gcc/multiple_target.c
index 28d5f95dca5..07cdb66e337 100644
--- a/gcc/multiple_target.c
+++ b/gcc/multiple_target.c
@@ -557,7 +557,7 @@ public:
 bool
 pass_target_clone::gate (function *)
 {
-  return true;
+  return !seen_error ();
 }
 
 } // anon namespace
diff --git a/gcc/testsuite/c-c++-common/strub-O0.c b/gcc/testsuite/c-c++-common/strub-O0.c
index 6afe0fd5de1..c7a79a6ea0d 100644
--- a/gcc/testsuite/c-c++-common/strub-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O0 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O0, none of the strub builtins are expanded inline.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O1.c b/gcc/testsuite/c-c++-common/strub-O1.c
index 1cdeaecaf32..96285c975d9 100644
--- a/gcc/testsuite/c-c++-common/strub-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O1 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O1, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
index 7848c46d179..8edc0d8aa13 100644
--- a/gcc/testsuite/c-c++-common/strub-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O2, without -fno-inline, we fully expand enter and update, and add a test
    around the leave call.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
index 85a8f76785e..c6d900cf3c4 100644
--- a/gcc/testsuite/c-c++-common/strub-O2fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3.c b/gcc/testsuite/c-c++-common/strub-O3.c
index 1fcde345d36..33ee465e51c 100644
--- a/gcc/testsuite/c-c++-common/strub-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand" } */
 
 int __attribute__ ((__strub__)) var;
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
index a2eedfd96b2..2936f82079e 100644
--- a/gcc/testsuite/c-c++-common/strub-O3fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
index e5cb1f60541..479746e57d8 100644
--- a/gcc/testsuite/c-c++-common/strub-Og.c
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Og -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Og -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Og, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
index 194aacc2c05..2241d4ea07f 100644
--- a/gcc/testsuite/c-c++-common/strub-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Os, without -fno-inline, we fully expand enter, and also update.  The
    expanded update might be larger than a call proper, but argument saving and
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
index 46e84bf6560..a322bcc5da6 100644
--- a/gcc/testsuite/c-c++-common/strub-all1.c
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -22,11 +22,11 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
index f377541cff0..db60026d0e0 100644
--- a/gcc/testsuite/c-c++-common/strub-all2.c
+++ b/gcc/testsuite/c-c++-common/strub-all2.c
@@ -17,8 +17,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
index f180b17f30e..2f462adc1ef 100644
--- a/gcc/testsuite/c-c++-common/strub-apply1.c
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -1,13 +1,13 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
-void __attribute__ ((__strub__ (3)))
+void __attribute__ ((__strub__ ("callable")))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0);
 }
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   void *args = __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
index 379a54b73b7..bab2dd10f5b 100644
--- a/gcc/testsuite/c-c++-common/strub-apply2.c
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 extern void __attribute__ ((__strub__))
 apply_function (void *args);
@@ -10,3 +10,9 @@ apply_args (int i, int j, double d) /* { dg-error "selected" } */
   void *args = __builtin_apply_args (); /* { dg-message "does not support" } */
   apply_function (args);
 }
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After the error and sorry messages in strubm, build_ssa_passes are gated out,
+   so we don't get the PROP_ssa property that targetclone requires.  When
+   checking is not enabled at configure time, we admit to being confused and
+   bail out, but when checking is eneabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
index 9b4786be698..85f9d090fda 100644
--- a/gcc/testsuite/c-c++-common/strub-apply3.c
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -1,8 +1,14 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 void __attribute__ ((__strub__))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */
 }
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
index 409f747743e..15ffaa031b8 100644
--- a/gcc/testsuite/c-c++-common/strub-apply4.c
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+/* { dg-options "-O2 -fstrub=strict -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
@@ -18,4 +18,4 @@ void f() {
   apply_args (1, 2, 3);
 }
 
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 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 d964b07ae5d..b70843b4215 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -22,9 +22,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
index 530eee36d06..97a3988a6b9 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls2.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls2.c
@@ -17,7 +17,7 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-default1.c b/gcc/testsuite/c-c++-common/strub-default1.c
deleted file mode 100644
index 79d00cedb9a..00000000000
--- a/gcc/testsuite/c-c++-common/strub-default1.c
+++ /dev/null
@@ -1,42 +0,0 @@
-/* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
-
-static int __attribute__ ((__strub__)) var;
-
-/* 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 inline void
-__attribute__ ((__always_inline__))
-h() {
-  var++;
-}
-
-/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
-   the viability of at-calls strubbing.  Though internally a strub context, its
-   interface is not strub-enabled, so it's not callable from within strub
-   contexts.  */
-static inline void
-g() {
-  var--;
-  h();
-}
-
-/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
-   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
-void
-f() {
-  var++;
-  g();  /* { dg-error "calling non-.strub." } */
-}
-
-/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-4\[)\]" 1 "strubm" } } */
-/* { 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 "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
index 7b04eea35d9..3d73431b3dc 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O1" } */
+/* { dg-options "-fstrub=strict -O1" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O1.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
index 67d96419a5e..fddf3c745e7 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O2" } */
+/* { dg-options "-fstrub=strict -O2" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O2.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
index 34828d2711e..2bc384ee8d1 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O3" } */
+/* { dg-options "-fstrub=strict -O3" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -O3.  */
@@ -10,7 +10,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -18,7 +18,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -32,7 +32,7 @@ leak_string (void)
   return 0;
 }
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 int
 look_for_string (char *e)
 {
@@ -56,14 +56,14 @@ look_for_string (char *e)
   return 0;
 }
 
-static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+static __attribute__ ((__strub__ ("at-calls"), __noinline__, __noclone__))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 deferred_at_calls ()
 {
@@ -73,7 +73,7 @@ deferred_at_calls ()
   return ret;
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 deferred_internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
index b273660aea1..fbaf85fe0fa 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -Os" } */
+/* { dg-options "-fstrub=strict -Os" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -Os.  */
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
index a74658c9ac9..e9d7b7b9ee0 100644
--- a/gcc/testsuite/c-c++-common/strub-internal1.c
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -23,9 +23,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
index a6e69357b23..8b8e15a51c7 100644
--- a/gcc/testsuite/c-c++-common/strub-internal2.c
+++ b/gcc/testsuite/c-c++-common/strub-internal2.c
@@ -14,8 +14,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
index 7e22a266ad9..0a4a7539d34 100644
--- a/gcc/testsuite/c-c++-common/strub-parms1.c
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -16,7 +16,7 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 large_byref_arg (struct large_arg la)
 {
 }
@@ -24,7 +24,7 @@ large_byref_arg (struct large_arg la)
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*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)))
+void __attribute__ ((__strub__ ("internal")))
 std_arg (int i, ...)
 {
   va_list vl;
@@ -38,7 +38,7 @@ std_arg (int i, ...)
 /* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
index 2d8036c0fbc..147171d96d5 100644
--- a/gcc/testsuite/c-c++-common/strub-parms2.c
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -15,14 +15,14 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 large_byref_arg (struct large_arg la)
 {
 }
 
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 std_arg (int i, ...)
 {
   va_list vl;
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
index f64361f1235..4e92682895a 100644
--- a/gcc/testsuite/c-c++-common/strub-parms3.c
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
new file mode 100644
index 00000000000..e2f9d8aebca
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  Without the error,
+   inlining takes place.  */
+
+#include "strub-strict1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
new file mode 100644
index 00000000000..98474435d2e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  */
+
+#include "strub-strict2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
index a4226ce0119..1de15342595 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fexceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fexceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
index 3bab553478b..f9209c81900 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
index c89cc7c2c47..bed1dcfb54a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
index b869fafb691..6bf0071f52b 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
index 1d3dd2f2c2c..4732f515bf7 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
index 4dd4281b03b..8d6424c479a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-strict1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
new file mode 100644
index 00000000000..7f5247cfaf0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-strict1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+static int __attribute__ ((__strub__)) var;
+
+/* 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 inline void
+__attribute__ ((__always_inline__))
+h() {
+  var++;
+}
+
+/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
+   the viability of at-calls strubbing.  Though internally a strub context, its
+   interface is not strub-enabled, so it's not callable from within strub
+   contexts.  */
+static inline void
+g() {
+  var--;
+  h();
+}
+
+/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
+   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
+void
+f() {
+  var++;
+  g();  /* { dg-error "calling non-.strub." } */
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-default2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
similarity index 54%
rename from gcc/testsuite/c-c++-common/strub-default2.c
rename to gcc/testsuite/c-c++-common/strub-strict2.c
index 487253e9227..17463b6e554 100644
--- a/gcc/testsuite/c-c++-common/strub-default2.c
+++ b/gcc/testsuite/c-c++-common/strub-strict2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
 
 static int __attribute__ ((__strub__)) var;
 
@@ -22,8 +22,14 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
index 0840dddd136..e48e0610e07 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 #include "strub-tail-O2.c"
 
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
index 9330d6ff4c1..87cda7ab21b 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.
    Tail calls are short-circuited at -O2+.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
index 5b33ff1f530..b5e45ab0525 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
index 38935e3270b..96aa7fe4b07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -1,39 +1,39 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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);
+extern int __attribute__ ((__strub__ ("callable"))) xcallable (void);
+extern int __attribute__ ((__strub__ ("internal"))) xinternal (void);
+extern int __attribute__ ((__strub__ ("at-calls"))) xat_calls (void);
+extern int __attribute__ ((__strub__ ("disabled"))) 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__ ("callable"))) callable (void);
+int __attribute__ ((__strub__ ("internal"))) internal (void);
+int __attribute__ ((__strub__ ("at-calls"))) at_calls (void);
+int __attribute__ ((__strub__ ("disabled"))) disabled (void);
 
 int __attribute__ ((__strub__)) var;
 int var_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 icallable (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 iinternal (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 idisabled (void);
 static inline int __attribute__ ((__always_inline__))
 ivar_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 i_callable (void) { return 0; }
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 i_internal (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 i_at_calls (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 i_disabled (void) { return 0; }
 static inline int __attribute__ ((__always_inline__))
 i_var_user (void) { return var; }
@@ -70,7 +70,7 @@ i_var_user (void) { return var; }
 
 /* Not a strub context, so it can call anything.
    Explicitly declared as callable even from within strub contexts.  */
-int __attribute__ ((__strub__ (3)))
+int __attribute__ ((__strub__ ("callable")))
 callable (void) {
   int ret = 0;
 
@@ -88,7 +88,7 @@ callable (void) {
 
 /* 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)))
+int __attribute__ ((__strub__ ("internal")))
 internal (void) {
   int ret = var;
 
@@ -109,7 +109,7 @@ internal (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (1)))
+int __attribute__ ((__strub__ ("at-calls")))
 at_calls (void) {
   int ret = var;
 
@@ -130,7 +130,7 @@ at_calls (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (0)))
+int __attribute__ ((__strub__ ("disabled")))
 disabled () {
   int ret = 0;
 
@@ -205,7 +205,7 @@ iinternal (void) {
   return ret;
 }
 
-int
+int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void) {
   int ret = var;
 
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
index 100fb0c59a9..2857195706e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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,
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
index 9e818ac9748..98a92bc9eac 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
index d40e8aa45cb..5511a6e1e71 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __const__))
 f() {
   return 0;
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
index d4cbdaf10f3..47ee927964d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -1,12 +1,12 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__
 __attribute__ ((__const__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data1.c b/gcc/testsuite/c-c++-common/torture/strub-data1.c
index 62a03891ab6..7c27a2a1a6d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointed-to data enables strubbing if accessed.  */
 int __attribute__ ((__strub__)) var;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data2.c b/gcc/testsuite/c-c++-common/torture/strub-data2.c
index 9b7df13a280..e66d903780a 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointer itself is a strub variable, enabling internal strubbing when
    its value is used.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data3.c b/gcc/testsuite/c-c++-common/torture/strub-data3.c
index 515706caa32..5e08e0e58c6 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data4.c b/gcc/testsuite/c-c++-common/torture/strub-data4.c
index 0ec9e35429f..a818e7a38bb 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
index 69f3d65ed44..538a639084b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -1,13 +1,15 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -Werror" } */
+/* { dg-options "-fstrub=strict" } */
+
+/* It would be desirable to issue at least warnings for these.  */
 
 typedef int __attribute__ ((__strub__)) strub_int;
 strub_int *ptr;
 
 int *f () {
-  return ptr; /* { dg-error "incompatible" } */
+  return ptr; /* { dg-message "incompatible" } */
 }
 
 strub_int *g () {
-  return f (); /* { dg-error "incompatible" } */
+  return f (); /* { dg-message "incompatible" } */
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
index b8adf8009e8..c165f312f16 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype ();
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
index 5b2c35ad6a7..69fcff8d376 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
index 5ee50456dc9..ff006224909 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int, ...);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
new file mode 100644
index 00000000000..95996cd5c25
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+inline void __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+}
+
+void
+bat (void)
+{
+  /* Not allowed, not a strub context.  */
+  inl_int_ali (); /* { dg-error "context" } */
+}
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
new file mode 100644
index 00000000000..f9a6b4a16fa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=all" } */
+
+#include "strub-inlinable1.c"
+
+/* With -fstrub=all, the caller becomes a strub context, so the strub-inlinable
+   callee is not rejected.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
new file mode 100644
index 00000000000..b4a7f3992bb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+typedef void ft (void);
+typedef void ft2 (int, int);
+extern ft __attribute__ ((__strub__)) fnac;
+
+ft * f (void) {
+  return fnac; /* { dg-message "incompatible|invalid conversion" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
new file mode 100644
index 00000000000..d9d2c0caec4
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
@@ -0,0 +1,55 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic" } */
+
+/* C++ does not warn about the partial incompatibilities.
+
+   The d_p () calls are actually rejected, even in C++, but they are XFAILed
+   here because we don't get far enough in the compilation as to observe them,
+   because the incompatibilities are errors without -fpermissive.
+   strub-ptrfn3.c uses -fpermissive to check those.
+ */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
new file mode 100644
index 00000000000..e1f179e160e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
@@ -0,0 +1,50 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic -fpermissive" } */
+/* { dg-prune-output "command-line option .-fpermissive." } */
+
+/* See strub-ptrfn2.c.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
new file mode 100644
index 00000000000..70b558afad0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+/* This is strub-ptrfn2.c without -Wpedantic.
+
+   Even C doesn't report the (not-quite-)compatible conversions without it.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
index cb223da6efc..a262a086837 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
index 67d1434b1f8..4c4bd50c209 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
index 59f02ea901f..ce195c6b1f1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -1,10 +1,10 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __pure__))
 f() {
   static int i; /* Stop it from being detected as const.  */
   return i;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
index 973e909217d..75cd54ccb5b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
 __attribute__ ((__pure__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
index a4077c35a60..c596066a045 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -14,7 +14,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -59,14 +59,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
index 94e4156ea73..1fdba214a07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
index 0ca74beb59d..afbc2cc9ab4 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
 /* Check that a non-strub function leaves a string behind in the stack, and that
@@ -7,7 +7,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
index 4ab11c0682e..5300f1d330b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -85,14 +85,14 @@ intermediate ()
   return ret;
 }
 
-static inline __attribute__ ((__strub__ (2)))
+static inline __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
   return intermediate ();
 }
 
-int __attribute__ ((strub (0)))
+int __attribute__ ((__strub__ ("disabled")))
 main ()
 {
   if (look_for_string (internal ()))
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4d.c b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
index e4f7445607c..08de3f1c3b1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4d.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -1,7 +1,7 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
-#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ (1)))
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
 
 #include "strub-run4.c"
diff --git a/gcc/testsuite/g++.dg/torture/strub-init1.C b/gcc/testsuite/g++.dg/torture/strub-init1.C
index e51ae802be4..c226ab10ff6 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init1.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init2.C b/gcc/testsuite/g++.dg/torture/strub-init2.C
index edcb7bf8ad2..a7911f1fa72 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init2.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init3.C b/gcc/testsuite/g++.dg/torture/strub-init3.C
index bacf823ca4e..6ebebcd01e8 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init3.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
index 697ac9de764..10445d7cf84 100644
--- a/gcc/testsuite/gnat.dg/strub_attr.adb
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -1,5 +1,5 @@
 --  { dg-do compile }
---  { dg-options "-fstrub=default -fdump-ipa-strubm" }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" }
 
 package body Strub_Attr is
    E : exception;
@@ -14,8 +14,24 @@ package body Strub_Attr is
       return X * X;
    end;
    
-   function G return Integer is (X);
+   function G return Integer is (F (X));
+   --  function G return Integer is (FP (X));
+   --  Calling G would likely raise an exception, because although FP
+   --  carries the strub at-calls attribute needed to call F, the
+   --  attribute is dropped from the type used for the call proper.
 end Strub_Attr;
 
---  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]2\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 0 "strubm" } }
 --  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark_ptr" 6 "strub" } }
+--  We have 1 at-calls subprogram (F) and 2 wrapped (P and G).
+--  For each of them, there's one match for the wrapped signature, 
+--  and one for the update call.
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 27 "strub" } }
+--  The 6 matches above, plus:
+--  5*2: wm var decl, enter, call, leave and clobber for each wrapper;
+--  2*1: an extra leave and clobber for the exception paths in the wrappers.
+--  7*1: for the F call in G, including EH path.
diff --git a/gcc/testsuite/gnat.dg/strub_ind.adb b/gcc/testsuite/gnat.dg/strub_ind.adb
new file mode 100644
index 00000000000..cfd7a76b02b
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind.adb
@@ -0,0 +1,50 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" }
+
+package body Strub_Ind is
+   E : exception;
+
+   procedure P (X : Integer) is
+   begin
+      raise E;
+   end;
+   
+   function F (X : Integer) return Integer is
+   begin
+      return X * X;
+   end;
+   
+   function G return Integer is (FP (X)); -- { dg-bogus "non-.strub." "" { xfail *-*-* } }
+   --  Calling G would likely raise an exception, because although FP
+   --  carries the strub at-calls attribute needed to call F, the
+   --  attribute is dropped from the type used for the call proper.
+
+
+   type GT is access function return Integer;
+   pragma Machine_Attribute (GT, "strub", "at-calls");
+   --  The pragma above seems to have no effect.
+
+   GP : GT := G'Access; -- { dg-warning "incompatible" "" { xfail *-*-* } }
+   pragma Machine_Attribute (GP, "strub", "at-calls");
+   --  The pragma above does modify GP's type,
+   --  but dereferencing it uses an unmodified copy of the type.
+   --  The initializer should be diagnosed:
+   --  GT should only reference functions with at-calls strub.
+
+end Strub_Ind;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 0 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark_ptr" 6 "strub" } }
+--  We have 1 at-calls subprogram (F) and 2 wrapped (P and G).
+--  For each of them, there's one match for the wrapped signature, 
+--  and one for the update call.
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 20 "strub" } }
+--  The 6 matches above, plus:
+--  5*2: wm var decl, enter, call, leave and clobber for each wrapper;
+--  2*1: an extra leave and clobber for the exception paths in the wrappers.
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 27 "strub" { xfail *-*-* } } }
+--  7*1: for the FP call in G, including EH path (currently missing the watermarking)
diff --git a/gcc/testsuite/gnat.dg/strub_ind.ads b/gcc/testsuite/gnat.dg/strub_ind.ads
new file mode 100644
index 00000000000..53dede60eac
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind.ads
@@ -0,0 +1,23 @@
+package Strub_Ind is
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "internal");
+
+   function F (X : Integer) return Integer;
+   pragma Machine_Attribute (F, "strub");
+
+   X : Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+
+   function G return Integer;
+
+   type FT is access function (X : Integer) return Integer;
+   pragma Machine_Attribute (FT, "strub", "at-calls");
+   --  The pragma above seems to get discarded in GNAT; Gigi doesn't see it.
+
+   FP : FT := F'Access;
+   pragma Machine_Attribute (FP, "strub", "at-calls");
+   --  The pragma above does modify FP's type,
+   --  but a call with it gets it converted to its Ada type,
+   --  that is cached by the translator as the unmodified type.
+
+end Strub_Ind;
diff --git a/libgcc/strub.c b/libgcc/strub.c
index fd6e27556e4..7c58deee1e6 100644
--- a/libgcc/strub.c
+++ b/libgcc/strub.c
@@ -36,7 +36,7 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 # define TOPS <
 #endif
 
-#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ ("callable")))
 
 /* Enter a stack scrubbing context, initializing the watermark to the caller's
    stack address.  */


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

* [gcc(refs/users/aoliva/heads/strub)] -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests
@ 2021-09-08 18:43 Alexandre Oliva
  0 siblings, 0 replies; 13+ messages in thread
From: Alexandre Oliva @ 2021-09-08 18:43 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:7781fcce934d9206b23a625d42ae9ea3d58c237f

commit 7781fcce934d9206b23a625d42ae9ea3d58c237f
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Thu Sep 2 23:15:36 2021 -0300

    -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests

Diff:
---
 .../doc/gnat_rm/security_hardening_features.rst    |  50 +-
 gcc/ada/gcc-interface/utils.c                      | 111 +---
 gcc/attribs.c                                      |  39 +-
 gcc/c-family/c-attribs.c                           | 100 +---
 gcc/common.opt                                     |  27 +-
 gcc/doc/extend.texi                                | 332 +++++++++--
 gcc/doc/invoke.texi                                |  76 ++-
 gcc/ipa-inline.c                                   |   2 +-
 gcc/ipa-strub.c                                    | 661 ++++++++++++++++-----
 gcc/ipa-strub.h                                    |  20 +-
 gcc/multiple_target.c                              |   2 +-
 gcc/testsuite/c-c++-common/strub-O0.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O1.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2fni.c           |   2 +-
 gcc/testsuite/c-c++-common/strub-O3.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O3fni.c           |   2 +-
 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            |  12 +-
 gcc/testsuite/c-c++-common/strub-all2.c            |   6 +-
 gcc/testsuite/c-c++-common/strub-apply1.c          |   6 +-
 gcc/testsuite/c-c++-common/strub-apply2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-apply3.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-apply4.c          |   4 +-
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-at-calls2.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-default1.c        |  42 --
 gcc/testsuite/c-c++-common/strub-defer-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |  14 +-
 gcc/testsuite/c-c++-common/strub-defer-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-internal1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-internal2.c       |   6 +-
 gcc/testsuite/c-c++-common/strub-parms1.c          |  10 +-
 gcc/testsuite/c-c++-common/strub-parms2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-parms3.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-relaxed1.c        |  18 +
 gcc/testsuite/c-c++-common/strub-relaxed2.c        |  14 +
 gcc/testsuite/c-c++-common/strub-short-O0-exc.c    |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O0.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O3.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-strict1.c         |  48 ++
 .../{strub-default2.c => strub-strict2.c}          |  14 +-
 gcc/testsuite/c-c++-common/strub-tail-O1.c         |   2 +-
 gcc/testsuite/c-c++-common/strub-tail-O2.c         |   2 +-
 .../c-c++-common/torture/strub-callable1.c         |   2 +-
 .../c-c++-common/torture/strub-callable2.c         |  44 +-
 gcc/testsuite/c-c++-common/torture/strub-const1.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const2.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const3.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-const4.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-data1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data3.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data4.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |   8 +-
 .../c-c++-common/torture/strub-indcall1.c          |   2 +-
 .../c-c++-common/torture/strub-indcall2.c          |   2 +-
 .../c-c++-common/torture/strub-indcall3.c          |   2 +-
 .../c-c++-common/torture/strub-inlinable1.c        |  22 +
 .../c-c++-common/torture/strub-inlinable2.c        |   7 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |  10 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |  55 ++
 gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c  |  50 ++
 gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c  |  43 ++
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure3.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-pure4.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run1.c    |  10 +-
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run4.c    |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run4d.c   |   4 +-
 gcc/testsuite/g++.dg/torture/strub-init1.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init2.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init3.C         |   2 +-
 gcc/testsuite/gnat.dg/strub_attr.adb               |  22 +-
 gcc/testsuite/gnat.dg/strub_ind.adb                |  50 ++
 gcc/testsuite/gnat.dg/strub_ind.ads                |  23 +
 libgcc/strub.c                                     |   2 +-
 85 files changed, 1494 insertions(+), 620 deletions(-)

diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 309291f0341..75345290412 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -19,6 +19,18 @@ It can be enabled with the *-fzero-call-used-regs* command line
 option, to affect all subprograms in a compilation, and with a
 :samp:`Machine_Attribute` pragma, to affect only specific subprograms.
 
+.. code-block:: ada
+
+     procedure Foo;
+     pragma Machine_Attribute (Foo, "zero_call_used_regs", "used");
+     --  Before returning, Foo scrubs only call-clobbered registers
+     --  that it uses itself.
+
+     function Bar return Integer;
+     pragma Machine_Attribute (Bar, "zero_call_used_regs", "all");
+     --  Before returning, Bar scrubs all call-clobbered registers.
+
+
 For usage and more details on the command line option, and on the
 ``zero_call_used_regs`` attribute, see :title:`Using the GNU Compiler
 Collection (GCC)`.
@@ -31,13 +43,31 @@ Stack Scrubbing
 
 GNAT can generate code to zero-out stack frames used by subprograms.
 
-It can be activated with the *-fstrub* command line option, to affect
-all (viable) subprograms in a compilation, and with a
-:samp:`Machine_Attribute` pragma.
+It can be activated with the :samp:`Machine_Attribute` pragma, on
+specific subprograms, variables, and types.
 
-For usage and more details on the command line option, and on the
-``strub`` attribute, see :title:`Using the GNU Compiler Collection
-(GCC)`.
+.. code-block:: ada
+
+     function Foo returns Integer;
+     pragma Machine_Attribute (Foo, "strub");
+     --  Foo and its callers are modified so as to scrub the stack
+     --  space used by Foo after it returns.
+
+     procedure Bar;
+     pragma Machine_Attribute (Bar, "strub", "internal");
+     --  Bar is turned into a wrapper for its original body,
+     --  and they scrub the stack used by the original body.
+
+     Var : Integer;
+     pragma Machine_Attribute (Var, "strub");
+     --  Reading from Var in a subprogram enables stack scrubbing
+     --  of the stack space used by the subprogram.
+
+
+There are also *-fstrub* command line options to control default
+settings.  For usage and more details on the command line option, and
+on the ``strub`` attribute, see :title:`Using the GNU Compiler
+Collection (GCC)`.
 
 Note that Ada secondary stacks are not scrubbed.  The restriction
 ``No_Secondary_Stack`` avoids their use, and thus their accidental
@@ -49,4 +79,10 @@ is not fully reflected in Ada types, ``Access`` attributes, renaming
 and overriding.  Every access type, renaming, and overriding and
 overridden dispatching operations that may refer to an entity with an
 attribute-modified interface must be annotated with the same
-interface-modifying attribute.
+interface-modifying attribute, or with an interface-compatible one.
+
+Even then, applying the pragma to anything other than subprograms and
+scalar variables is not currently supported, and may be silently
+ignored.  Specifically, it is not recommended to rely on any effects
+of this pragma on access types and objects to data variables and to
+subprograms.
diff --git a/gcc/ada/gcc-interface/utils.c b/gcc/ada/gcc-interface/utils.c
index 6b20ddc93c9..193772cc829 100644
--- a/gcc/ada/gcc-interface/utils.c
+++ b/gcc/ada/gcc-interface/utils.c
@@ -39,6 +39,7 @@
 #include "varasm.h"
 #include "toplev.h"
 #include "opts.h"
+#include "ipa-strub.h"
 #include "output.h"
 #include "debug.h"
 #include "convert.h"
@@ -6611,115 +6612,57 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
-  tree orig_fnptr_type = NULL_TREE;
-  tree orig_args = args;
 
   if (args
       && POINTER_TYPE_P (*node)
       && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*node)))
-    {
-      orig_fnptr_type = *node;
-      *node = TREE_TYPE (orig_fnptr_type);
-    }
+    *node = TREE_TYPE (*node);
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      /* pragma Machine_Attribute turns string arguments into identifiers.
-	 Reverse it.  */
-      if (TREE_CODE (TREE_VALUE (args)) == IDENTIFIER_NODE)
-	{
-	  gcc_checking_assert (IDENTIFIER_POINTER (TREE_VALUE (args))
-			       [IDENTIFIER_LENGTH (TREE_VALUE (args))] == 0);
-	  TREE_VALUE (args) = build_string
-	    (IDENTIFIER_LENGTH (TREE_VALUE (args)) + 1,
-	     IDENTIFIER_POINTER (TREE_VALUE (args)));
-	}
-
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
 	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
-
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
-
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
 
   if (args)
-    warning (OPT_Wattributes,
-	     "ignoring excess %qE attribute arguments starting at %qE",
-	     name, TREE_VALUE (args));
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
 
   /* If we see a strub-enabling attribute, and we're at the default setting,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
-
-  if (!*no_add_attrs)
-    {
-      *no_add_attrs = true;
-      if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
-      TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
-					   TYPE_ATTRIBUTES (*node));
-
-      if (orig_fnptr_type)
-	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
-	  TREE_TYPE (fnptr_type) = *node;
-	  *node = fnptr_type;
-	}
-    }
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   return NULL_TREE;
 }
diff --git a/gcc/attribs.c b/gcc/attribs.c
index 0d22c20a35e..07f81285d7a 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "attribs.h"
 #include "fold-const.h"
+#include "ipa-strub.h"
 #include "stor-layout.h"
 #include "langhooks.h"
 #include "plugin.h"
@@ -617,12 +618,11 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  flags &= ~(int) ATTR_FLAG_TYPE_IN_PLACE;
 	}
 
-      if (spec->function_type_required && TREE_CODE (*anode) != FUNCTION_TYPE
-	  && TREE_CODE (*anode) != METHOD_TYPE)
+      if (spec->function_type_required
+	  && !FUNC_OR_METHOD_TYPE_P (*anode))
 	{
 	  if (TREE_CODE (*anode) == POINTER_TYPE
-	      && (TREE_CODE (TREE_TYPE (*anode)) == FUNCTION_TYPE
-		  || TREE_CODE (TREE_TYPE (*anode)) == METHOD_TYPE))
+	      && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
 	    {
 	      /* OK, this is a bit convoluted.  We can't just make a copy
 		 of the pointer type and modify its TREE_TYPE, because if
@@ -715,7 +715,23 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  tree ret = (spec->handler) (cur_and_last_decl, name, args,
 				      flags|cxx11_flag, &no_add_attrs);
 
-	  *anode = cur_and_last_decl[0];
+	  if (*anode != cur_and_last_decl[0])
+	    {
+	      /* Even if !spec->function_type_required, allow the attribute
+		 handler to request the attribute to be applied to the function
+		 type, rather than to the function pointer type, by setting
+		 cur_and_last_decl[0] to the function type.  */
+	      if (!fn_ptr_tmp
+		  && POINTER_TYPE_P (*anode)
+		  && TREE_TYPE (*anode) == cur_and_last_decl[0]
+		  && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
+		{
+		  fn_ptr_tmp = TREE_TYPE (*anode);
+		  fn_ptr_quals = TYPE_QUALS (*anode);
+		  anode = &fn_ptr_tmp;
+		}
+	      *anode = cur_and_last_decl[0];
+	    }
 	  if (ret == error_mark_node)
 	    {
 	      warning (OPT_Wattributes, "%qE attribute ignored", name);
@@ -1353,9 +1369,20 @@ comp_type_attributes (const_tree type1, const_tree type2)
   if ((lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type1)) != NULL)
       ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) != NULL))
     return 0;
+  int strub_ret = strub_comptypes (CONST_CAST_TREE (type1),
+				   CONST_CAST_TREE (type2));
+  if (strub_ret == 0)
+    return strub_ret;
   /* As some type combinations - like default calling-convention - might
      be compatible, we have to call the target hook to get the final result.  */
-  return targetm.comp_type_attributes (type1, type2);
+  int target_ret = targetm.comp_type_attributes (type1, type2);
+  if (target_ret == 0)
+    return target_ret;
+  if (strub_ret == 2 || target_ret == 2)
+    return 2;
+  if (strub_ret == 1 && target_ret == 1)
+    return 1;
+  gcc_unreachable ();
 }
 
 /* PREDICATE acts as a function of type:
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index dc4a4d4d200..0453ac563cc 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "common/common-target.h"
 #include "langhooks.h"
 #include "tree-inline.h"
+#include "ipa-strub.h"
 #include "toplev.h"
 #include "tree-iterator.h"
 #include "opts.h"
@@ -1306,104 +1307,57 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
-  tree orig_fnptr_type = NULL_TREE;
-  tree orig_args = args;
 
   if (args
       && POINTER_TYPE_P (*node)
       && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*node)))
-    {
-      orig_fnptr_type = *node;
-      *node = TREE_TYPE (orig_fnptr_type);
-    }
+    *node = TREE_TYPE (*node);
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
 	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
-
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
-
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
 
   if (args)
-    warning (OPT_Wattributes,
-	     "ignoring excess %qE attribute arguments starting at %qE",
-	     name, TREE_VALUE (args));
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
 
   /* If we see a strub-enabling attribute, and we're at the default setting,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
-
-  if (!*no_add_attrs)
-    {
-      *no_add_attrs = true;
-      if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
-      TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
-					   TYPE_ATTRIBUTES (*node));
-
-      if (orig_fnptr_type)
-	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
-	  TREE_TYPE (fnptr_type) = *node;
-	  *node = fnptr_type;
-	}
-    }
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   return NULL_TREE;
 }
diff --git a/gcc/common.opt b/gcc/common.opt
index 71e0c971344..ef018ebf735 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,13 +2687,22 @@ fstrict-overflow
 Common
 Treat signed overflow as undefined.  Negated as -fwrapv -fwrapv-pointer.
 
-; If any strub-enabling attribute is seen when the default value is
-; selected, it's bumped up to -1.  The scrub mode gate function will
-; then bump -2 to 0 if no strub-enabling attribute is seen.  This
-; minimizes the strub overhead.
-fstrub=default
-Common RejectNegative Var(flag_strub, -2) Init(-2)
-Enable stack scrub as requested through attributes.
+fstrub=disable
+Common RejectNegative Var(flag_strub, 0)
+Disable stack scrub entirely, disregarding strub attributes.
+
+fstrub=strict
+Common RejectNegative Var(flag_strub, -4)
+Enable stack scrub as per attributes, with strict call checking.
+
+; If any strub-enabling attribute is seen when the default or strict
+; initializer values are in effect, flag_strub is bumped up by 2.  The
+; scrub mode gate function will then bump these initializer values to
+; 0 if no strub-enabling attribute is seen.  This minimizes the strub
+; overhead.
+fstrub=relaxed
+Common RejectNegative Var(flag_strub, -3) Init(-3)
+Restore default strub mode: as per attributes, with relaxed checking.
 
 fstrub=all
 Common RejectNegative Var(flag_strub, 3)
@@ -2707,10 +2716,6 @@ fstrub=internal
 Common RejectNegative Var(flag_strub, 2)
 Enable internal stack scrubbing for all viable functions.
 
-fstrub=disable
-Common RejectNegative Var(flag_strub, 0)
-Disable stack scrub entirely, disregarding strub attributes.
-
 fsync-libcalls
 Common Var(flag_sync_libcalls) Init(1)
 Implement __atomic operations via libcalls to legacy __sync functions.
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 1fcf60a3875..d85e695d85c 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -77,7 +77,7 @@ extensions, accepted by GCC in C90 mode and in C++.
 * Function Names::      Printable strings which are the name of the current
                         function.
 * Return Address::      Getting the return or frame address of a function.
-* Stack Scrubbing::     Stack scrubbing interfaces.
+* Stack Scrubbing::     Stack scrubbing internal interfaces.
 * Vector Extensions::   Using vector instructions through built-in functions.
 * Offsetof::            Special syntax for implementing @code{offsetof}.
 * __sync Builtins::     Legacy built-in functions for atomic memory access.
@@ -8729,51 +8729,259 @@ pid_t wait (wait_status_ptr_t p)
 @item strub
 @cindex @code{strub} type attribute
 This attribute defines stack-scrubbing properties of functions and
-variables.  When applied to function types, it takes an optional
-argument.  When applied to a pointer-to-function type, it gets
-propagated to the function type if the optional argument is given.
-
-A function whose type is annotated with @code{at-calls} @code{strub}
-mode (@code{strub("at-calls")}, @code{strub(1)}, or @code{strub})
-undergoes interface changes.  Its callers are adjusted to match the
-changes, and to automatically scrub (overwrite with zeros) the stack
-space used by the function.
-
-A function whose type indicates @code{internal} @code{strub} mode
-(@code{strub("internal")} or @code{strub(2)}) retains an unmodified
-interface, but may be turned into a wrapper that calls the wrapped body
-using a custom interface.  The wrapper then scrubs the stack space used
-by the wrapped body.
-
-An automatically-allocated variable whose type carries the @code{strub}
-attribute causes the enclosing function to have @code{strub} enabled.
-
-A statically-allocated variable whose type carries the @code{strub}
-attribute causes functions that @emph{read} it with that type to have
-@code{strub} enabled.  Reading from data referenced by pointers to such
-types has the same effect.  Note: The attribute does not carry over from
-a composite type to the types of its components, so the intended effect
-may not be obtained with non-scalar types.
-
-A @code{strub} context is the body of a function that has strub enabled,
-be it explicitly, by @code{at-calls} or @code{internal} mode, or
-implicitly, by reading from a @code{strub}-marked data type.
+variables.  Being a type attribute, it attaches to types, even when
+specified in function and variable declarations.  When applied to
+function types, it takes an optional string argument.  When applied to a
+pointer-to-function type, if the optional argument is given, it gets
+propagated to the function type.
+
+@smallexample
+/* A strub variable.  */
+int __attribute__ ((strub)) var;
+/* A strub variable that happens to be a pointer.  */
+__attribute__ ((strub)) int *strub_ptr_to_int;
+/* A pointer type that may point to a strub variable.  */
+typedef int __attribute__ ((strub)) *ptr_to_strub_int_type;
+
+/* A declaration of a strub function.  */
+extern int __attribute__ ((strub)) foo (void);
+/* A pointer to that strub function.  */
+int __attribute__ ((strub ("at-calls"))) (*ptr_to_strub_fn)(void) = foo;
+@end smallexample
+
+A function associated with @code{at-calls} @code{strub} mode
+(@code{strub("at-calls")}, or just @code{strub}) undergoes interface
+changes.  Its callers are adjusted to match the changes, and to scrub
+(overwrite with zeros) the stack space used by the called function after
+it returns.  The interface change makes the function type incompatible
+with an unadorned but otherwise equivalent type, so @emph{every}
+declaration and every type that may be used to call the function must be
+associated with this strub mode.
+
+A function associated with @code{internal} @code{strub} mode
+(@code{strub("internal")}) retains an unmodified, type-compatible
+interface, but it may be turned into a wrapper that calls the wrapped
+body using a custom interface.  The wrapper then scrubs the stack space
+used by the wrapped body.  Though the wrapped body has its stack space
+scrubbed, the wrapper does not, so arguments and return values may
+remain unscrubbed even when such a function is called by another
+function that enables @code{strub}.  This is why, when compiling with
+@option{-fstrub=strict}, a @code{strub} context is not allowed to call
+@code{internal} @code{strub} functions.
+
+@smallexample
+/* A declaration of an internal-strub function.  */
+extern int __attribute__ ((strub ("internal"))) bar (void);
+
+int __attribute__ ((strub))
+baz (void)
+@{
+  /* Ok, foo was declared above as an at-calls strub function.  */
+  foo ();
+  /* Not allowed in strict mode, otherwise allowed.  */
+  bar ();
+@}
+@end smallexample
+
+An automatically-allocated variable associated with the @code{strub}
+attribute causes the (immediately) enclosing function to have
+@code{strub} enabled.
+
+A statically-allocated variable associated with the @code{strub}
+attribute causes functions that @emph{read} it, through its @code{strub}
+data type, to have @code{strub} enabled.  Reading data by dereferencing
+a pointer to a @code{strub} data type has the same effect.  Note: The
+attribute does not carry over from a composite type to the types of its
+components, so the intended effect may not be obtained with non-scalar
+types.
+
+When selecting a @code{strub}-enabled mode for a function that is not
+explicitly associated with one, because of @code{strub} variables or
+data pointers, the function must satisfy @code{internal} mode viability
+requirements (see below), even when @code{at-calls} mode is also viable
+and, being more efficient, ends up selected as an optimization.
+
+@smallexample
+/* zapme is implicitly strub-enabled because of strub variables.
+   Optimization may change its strub mode, but not the requirements.  */
+static int
+zapme (int i)
+@{
+  /* A local strub variable enables strub.  */
+  int __attribute__ ((strub)) lvar;
+  /* Reading strub data through a pointer-to-strub enables strub.  */
+  lvar = * (ptr_to_strub_int_type) &i;
+  /* Writing to a global strub variable does not enable strub.  */
+  var = lvar;
+  /* Reading from a global strub variable enables strub.  */
+  return var;
+@}
+@end smallexample
+
+A @code{strub} context is the body (as opposed to the interface) of a
+function that has @code{strub} enabled, be it explicitly, by
+@code{at-calls} or @code{internal} mode, or implicitly, due to
+@code{strub} variables or command-line options.
 
 A function of a type associated with the @code{disabled} @code{strub}
-mode (@code{strub("disabled")}, @code{strub(0)}, or no @code{strub} mode
-specified) will not have its own stack space scrubbed, and it cannot be
-called from @code{strub} contexts.
+mode (@code{strub("disabled")} will not have its own stack space
+scrubbed.  Such functions @emph{cannot} be called from within
+@code{strub} contexts.
+
+In order to enable a function to be called from within @code{strub}
+contexts without having its stack space scrubbed, associate it with the
+@code{callable} @code{strub} mode (@code{strub("callable")}).
+
+When a function is not assigned a @code{strub} mode, explicitly or
+implicitly, the mode defaults to @code{callable}, except when compiling
+with @option{-fstrub=strict}, that causes @code{strub} mode to default
+to @code{disabled}.
+
+@example
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+ /* Implicitly disabled with -fstrub=strict, otherwise callable.  */
+extern int bah (void);
+
+int __attribute__ ((strub))
+bal (void)
+@{
+  /* Not allowed, bad is not strub-callable.  */
+  bad ();
+  /* Ok, bac is strub-callable.  */
+  bac ();
+  /* Not allowed with -fstrub=strict, otherwise allowed.  */
+  bah ();
+@}
+@end example
 
-A function that does not have @code{strub} enabled can only be called
-from within @code{strub} contexts through a function type marked with
-the @code{callable} @code{strub} mode (@code{strub("callable")} or
-@code{strub(3)}).
+Function types marked @code{callable} and @code{disabled} are not
+mutually compatible types, but the underlying interfaces are compatible,
+so it is safe to convert pointers between them, and to use such pointers
+or alternate declarations to call them.  Interfaces are also
+interchangeable between them and @code{internal} (but not
+@code{at-calls}!), but adding @code{internal} to a pointer type will not
+cause the pointed-to function to perform stack scrubbing.
+
+@example
+void __attribute__ ((strub))
+bap (void)
+@{
+  /* Assign a callable function to pointer-to-disabled.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bac;
+  /* Not allowed: calls disabled type in a strub context.  */
+  d_p ();
+
+  /* Assign a disabled function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bad;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an internal function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  c_p = bar;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an at-calls function to pointer-to-callable.
+     Flaggged as incompatible.  */
+  c_p = bal;
+  /* The call through an interface-incompatible type will not use the
+     modified interface expected by the at-calls function, so it is
+     likely to misbehave at runtime.  */
+  c_p ();
+@}
+@end example
 
 @code{Strub} contexts are never inlined into non-@code{strub} contexts.
-When an @code{internal}-strub function is split, the wrapper can often
-be inlined, but the wrapped body never is.  Functions marked as
-@code{always_inline}, even if explicitly assigned @code{internal} strub
-mode, will not undergo wrapping, so their body gets inlined.
+When an @code{internal}-strub function is split up, the wrapper can
+often be inlined, but the wrapped body @emph{never} is.  A function
+marked as @code{always_inline}, even if explicitly assigned
+@code{internal} strub mode, will not undergo wrapping, so its body gets
+inlined as required.
+
+@example
+inline int __attribute__ ((strub ("at-calls")))
+inl_atc (void)
+@{
+  /* This body may get inlined into strub contexts.  */
+@}
+
+inline int __attribute__ ((strub ("internal")))
+inl_int (void)
+@{
+  /* This body NEVER gets inlined, though its wrapper may.  */
+@}
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+@{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+@}
+
+void __attribute__ ((strub ("disabled")))
+bat (void)
+@{
+  /* Not allowed, cannot inline into a non-strub context.  */
+  inl_int_ali ();
+@}
+@end example
+
+@cindex strub eligibility and viability
+Some @option{-fstrub=*} command line options enable @code{strub} modes
+implicitly where viable.  A @code{strub} mode is only viable for a
+function if the function is eligible for that mode, and if other
+conditions, detailed below, are satisfied.  If it's not eligible for a
+mode, attempts to explicitly associate it with that mode are rejected
+with an error message.  If it is eligible, that mode may be assigned
+explicitly through this attribute, but implicit assignment through
+command-line options may involve additional viability requirements.
+
+A function is ineligible for @code{at-calls} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+@code{At-calls} @code{strub} mode, if not requested through the function
+type, is only viable for an eligible function if the function is not
+visible to other translation units, and if it doesn't have its address
+taken.
+
+@smallexample
+/* bar is eligible for at-calls strub mode,
+   but not viable for that mode because it is visible to other units.
+   It is eligible and viable for internal strub mode.  */
+void bav () @{@}
+
+/* setp is eligible for at-calls strub mode,
+   but not viable for that mode because its address is taken.
+   It is eligible and viable for internal strub mode.  */
+void setp (void) @{ static void (*p)(void); = setp; @}
+@end smallexample
+
+A function is ineligible for @code{internal} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, or if attribute
+@code{noipa} is present.  For an @code{always_inline} function, meeting
+these requirements is enough to make it eligible.  Any function that has
+attribute @code{noclone}, that uses such extensions as non-local labels,
+computed gotos, alternate variable argument passing interfaces,
+@code{__builtin_next_arg}, or @code{__builtin_return_address}, or that
+takes too many (about 64Ki) arguments is ineligible, unless it is
+@code{always_inline}.  For @code{internal} @code{strub} mode, all
+eligible functions are viable.
+
+@smallexample
+/* flop is not eligible, thus not viable, for at-calls strub mode.
+   Likewise for internal strub mode.  */
+__attribute__ ((noipa)) void flop (void) @{@}
+
+/* flip is eligible and viable for at-calls strub mode.
+   It would be ineligible for internal strub mode, because of noclone,
+   if it weren't for always_inline.  With always_inline, noclone is not
+   an obstacle, so it is also eligible and viable for internal strub mode.  */
+inline __attribute__ ((noclone, always_inline)) void flip (void) @{@}
+@end smallexample
 
 @item unused
 @cindex @code{unused} type attribute
@@ -11804,40 +12012,48 @@ This function returns the value of the stack pointer register.
 @end deftypefn
 
 @node Stack Scrubbing
-@section Stack scrubbing interfaces
+@section Stack scrubbing internal interfaces
 
 Stack scrubbing involves cooperation between a @code{strub} context,
 i.e., a function whose stack frame is to be zeroed-out, and its callers.
 The caller initializes a stack watermark, the @code{strub} context
-updates the watermark to reflect its stack use, and the caller zeroes it
-out once it regains control.
-
-Each of these steps relies on a different builtin function call.  The
-functions are available in libgcc but, depending on optimization levels,
-they are expanded internally, adjusted to account for inlining, or
-combined/deferred (e.g. passing the caller-supplied watermark on to
-callees, refraining from erasing stack areas that the caller will).
+updates the watermark according to its stack use, and the caller zeroes
+it out once it regains control, whether by the callee's returning or by
+an exception.
+
+Each of these steps is performed by a different builtin function call.
+Calls to these builtins are introduced automatically, in response to
+@code{strub} attributes and command-line options; they are not expected
+to be explicitly called by source code.
+
+The functions that implement the builtins are available in libgcc but,
+depending on optimization levels, they are expanded internally, adjusted
+to account for inlining, and sometimes combined/deferred (e.g. passing
+the caller-supplied watermark on to callees, refraining from erasing
+stack areas that the caller will) to enable tail calls and to optimize
+for code size.
 
 @deftypefn {Built-in Function} {void} __builtin___strub_enter (void **@var{wmptr})
 This function initializes a stack @var{watermark} variable with the
-current top of the stack.  This builtin function should be called before
-entering a @code{strub} context.  It remains as a function call if optimization
-is not enabled.
+current top of the stack.  A call to this builtin function is introduced
+before entering a @code{strub} context.  It remains as a function call
+if optimization is not enabled.
 @end deftypefn
 
 @deftypefn {Built-in Function} {void} __builtin___strub_update (void **@var{wmptr})
 This function updates a stack @var{watermark} variable with the current
-top of the stack, if it tops the previous watermark.  This builtin
-function should be called within a @code{strub} context whenever
+top of the stack, if it tops the previous watermark.  A call to this
+builtin function is inserted within @code{strub} contexts, whenever
 additional stack space may have been used.  It remains as a function
 call at optimization levels lower than 2.
 @end deftypefn
 
 @deftypefn {Built-in Function} {void} __builtin___strub_leave (void **@var{wmptr})
 This function overwrites the memory area between the current top of the
-stack, and the @var{watermark}ed address.  This builtin function should
-be called after leaving a @code{strub} context.  It remains as a
-function call at optimization levels lower than 3.
+stack, and the @var{watermark}ed address.  A call to this builtin
+function is inserted after leaving a @code{strub} context.  It remains
+as a function call at optimization levels lower than 3, and it is guarded by
+a condition at level 2.
 @end deftypefn
 
 @node Vector Extensions
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 838d9ff1198..afcb37914ac 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,7 +599,8 @@ Objective-C and Objective-C++ Dialects}.
 -fstack-protector-explicit  -fstack-check @gol
 -fstack-limit-register=@var{reg}  -fstack-limit-symbol=@var{sym} @gol
 -fno-stack-limit  -fsplit-stack @gol
--fstrub=default -fstrub=disable -fstrub=at-calls -fstrub=internal -fstrub=all @gol
+-fstrub=disable -fstrub=strict -fstrub=relaxed @gol
+-fstrub=all -fstrub=at-calls -fstrub=internal @gol
 -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
 -fvtv-counts  -fvtv-debug @gol
 -finstrument-functions @gol
@@ -15513,46 +15514,55 @@ without @option{-fsplit-stack} always has a large stack.  Support for
 this is implemented in the gold linker in GNU binutils release 2.21
 and later.
 
-@item -fstrub=default
-@opindex fstrub=default
-Restore the default stack scrub (@code{strub}) setting, namely,
-@code{strub} is only enabled as required by @code{strub} attributes
-associated with function or variable types.  This is only useful to
-override earlier @samp{-fstrub=*} options.
-
 @item -fstrub=disable
 @opindex -fstrub=disable
 Disable stack scrubbing entirely, ignoring any @code{strub} attributes.
+See @xref{Common Type Attributes}.
+
+@item -fstrub=strict
+@opindex fstrub=strict
+Functions default to @code{strub} mode @code{disabled}, and apply
+@option{strict}ly the restriction that only functions associated with
+@code{strub}-@code{callable} modes (@code{at-calls}, @code{callable} and
+@code{always_inline} @code{internal}) are @code{callable} by functions
+with @code{strub}-enabled modes (@code{at-calls} and @code{internal}).
+
+@item -fstrub=relaxed
+@opindex fstrub=relaxed
+Restore the default stack scrub (@code{strub}) setting, namely,
+@code{strub} is only enabled as required by @code{strub} attributes
+associated with function and data types.  @code{Relaxed} means that
+strub contexts are only prevented from calling functions explicitly
+associated with @code{strub} mode @code{disabled}.  This option is only
+useful to override other @option{-fstrub=*} options that precede it in
+the command line.
 
 @item -fstrub=at-calls
 @opindex fstrub=at-calls
-Enable @code{at-calls} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{at-calls} @code{strub} if a different @code{strub}
-mode is explicitly requested, if attribute @code{noipa} is present, or
-if it calls @code{__builtin_apply_args}.  @code{At-calls} @code{strub}
-mode, if not requested through the function type, is only viable for an
-eligible function if the function is not visible to other translation
-units, and it doesn't have its address taken.
+Enable @code{at-calls} @code{strub} mode where viable.  The primary use
+of this option is for testing.  It exercises the @code{strub} machinery
+in scenarios strictly local to a translation unit.  This @code{strub}
+mode modifies function interfaces, so any function that is visible to
+other translation units, or that has its address taken, will @emph{not}
+be affected by this option.  Optimization options may also affect
+viability.  See the @code{strub} attribute documentation for details on
+viability and eligibility requirements.
 
 @item -fstrub=internal
 @opindex fstrub=internal
-Enable @code{internal} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{internal} @code{strub} if a different @code{strub}
-mode is explicitly requested, or if attribute @code{noipa} is present.
-Non-@code{always_inline} functions also become ineligible if attribute
-@code{noclone} is present, if the function uses such features as user
-labels, non-default variable argument interfaces,
-@code{__builtin_next_arg}, or @code{__builtin_return_address}, or if
-they have too many (about 64Ki) arguments.  For @code{internal}
-@code{strub}, all eligible functions are viable.
+Enable @code{internal} @code{strub} mode where viable.  The primary use
+of this option is for testing.  This option is intended to exercise
+thoroughly parts of the @code{strub} machinery that implement the less
+efficient, but interface-preserving @code{strub} mode.  Functions that
+would not be affected by this option are quite uncommon.
 
 @item -fstrub=all
 @opindex fstrub=all
-Enable @code{strub} for all viable functions, and consider non-viable
-functions as @code{callable}.  When both strub modes are viable,
-@code{at-calls} is preferred.
+Enable some @code{strub} mode where viable.  When both strub modes are
+viable, @code{at-calls} is preferred.  @option{-fdump-ipa-strubm} adds
+function attributes that tell which mode was selected for each function.
+The primary use of this option is for testing, to exercise thoroughly
+the @code{strub} machinery.
 
 @item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 @opindex fvtable-verify
@@ -17427,6 +17437,14 @@ and inlining decisions.
 @item inline
 Dump after function inlining.
 
+@item strubm
+Dump after selecting @code{strub} modes, and recording the selections as
+function attributes.
+
+@item strub
+Dump @code{strub} transformations: interface changes, function wrapping,
+and insertion of builtin calls for stack scrubbing and watermarking.
+
 @end table
 
 Additionally, the options @option{-optimized}, @option{-missed},
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 7f4bc44d2bb..932e49dba8e 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -397,7 +397,7 @@ can_inline_edge_p (struct cgraph_edge *e, bool report,
       e->inline_failed = CIF_SANITIZE_ATTRIBUTE_MISMATCH;
       inlinable = false;
     }
-  if (!strub_inlinable_p (callee, caller))
+  if (!strub_inlinable_to_p (callee, caller))
     {
       e->inline_failed = CIF_UNSPECIFIED;
       inlinable = false;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
index cf7d811779e..91c9f0c9453 100644
--- a/gcc/ipa-strub.c
+++ b/gcc/ipa-strub.c
@@ -159,12 +159,16 @@ enum strub_mode {
 
 };
 
+/* Look up a strub attribute in TYPE, and return it.  */
+
 static tree
 get_strub_attr_from_type (tree type)
 {
   return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
 }
 
+/* Look up a strub attribute in DECL or in its type, and return it.  */
+
 static tree
 get_strub_attr_from_decl (tree decl)
 {
@@ -174,23 +178,161 @@ get_strub_attr_from_decl (tree decl)
   return get_strub_attr_from_type (TREE_TYPE (decl));
 }
 
-tree
+/* Define a function to cache identifier ID, to be used as a strub attribute
+   parameter for a strub mode named after NAME.  */
+#define DEF_STRUB_IDS(NAME, ID)				\
+static inline tree get_strub_mode_id_ ## NAME () {	\
+  static tree identifier = NULL_TREE;			\
+  if (!identifier)					\
+    identifier = get_identifier (ID);			\
+  return identifier;					\
+}
+/* Same as DEF_STRUB_IDS, but use the string expansion of NAME as ID.  */
+#define DEF_STRUB_ID(NAME)				\
+DEF_STRUB_IDS (NAME, #NAME)
+
+/* Define functions for each of the strub mode identifiers.
+   Expose dashes rather than underscores.  */
+DEF_STRUB_ID (disabled)
+DEF_STRUB_IDS (at_calls, "at-calls")
+DEF_STRUB_ID (internal)
+DEF_STRUB_ID (callable)
+DEF_STRUB_ID (wrapped)
+DEF_STRUB_ID (wrapper)
+DEF_STRUB_ID (inlinable)
+DEF_STRUB_IDS (at_calls_opt, "at-calls-opt")
+
+/* Release the temporary macro names.  */
+#undef DEF_STRUB_IDS
+#undef DEF_STRUB_ID
+
+/* Return the identifier corresponding to strub MODE.  */
+
+static tree
+get_strub_mode_attr_parm (enum strub_mode mode)
+{
+  switch (mode)
+    {
+    case STRUB_DISABLED:
+      return get_strub_mode_id_disabled ();
+
+    case STRUB_AT_CALLS:
+      return get_strub_mode_id_at_calls ();
+
+    case STRUB_INTERNAL:
+      return get_strub_mode_id_internal ();
+
+    case STRUB_CALLABLE:
+      return get_strub_mode_id_callable ();
+
+    case STRUB_WRAPPED:
+      return get_strub_mode_id_wrapped ();
+
+    case STRUB_WRAPPER:
+      return get_strub_mode_id_wrapper ();
+
+    case STRUB_INLINABLE:
+      return get_strub_mode_id_inlinable ();
+
+    case STRUB_AT_CALLS_OPT:
+      return get_strub_mode_id_at_calls_opt ();
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+/* Return the parmeters (TREE_VALUE) for a strub attribute of MODE.
+   We know we use a single parameter, so we bypass the creation of a
+   tree list.  */
+
+static tree
 get_strub_mode_attr_value (enum strub_mode mode)
 {
-  return tree_cons (NULL_TREE,
-		    build_int_cst (integer_type_node, (int)mode),
-		    NULL_TREE);
+  return get_strub_mode_attr_parm (mode);
+}
 
-#if 0 /* ??? use symbolic mode names with interned strings?  */
-  char *s = NULL;
+/* Determine whether ID is a well-formed strub mode-specifying attribute
+   parameter for a function (type).  Only user-visible modes are accepted, and
+   ID must be non-NULL.
 
-  switch (strub_mode)
+   For unacceptable parms, return 0, otherwise a nonzero value as below.
+
+   If the parm enables strub, return positive, otherwise negative.
+
+   If the affected type must be a distinct, incompatible type,return an integer
+   of absolute value 2, otherwise 1.  */
+
+int
+strub_validate_fn_attr_parm (tree id)
+{
+  int ret;
+  const char *s = NULL;
+  size_t len = 0;
+
+  /* do NOT test for NULL.  This is only to be called with non-NULL arguments.
+     We assume that the strub parameter applies to a function, because only
+     functions accept an explicit argument.  If we accepted NULL, and we
+     happened to be called to verify the argument for a variable, our return
+     values would be wrong.  */
+  if (TREE_CODE (id) == STRING_CST)
     {
-      
+      s = TREE_STRING_POINTER (id);
+      len = TREE_STRING_LENGTH (id) - 1;
     }
-#endif
+  else if (TREE_CODE (id) == IDENTIFIER_NODE)
+    {
+      s = IDENTIFIER_POINTER (id);
+      len = IDENTIFIER_LENGTH (id);
+    }
+  else
+    return 0;
+
+  enum strub_mode mode;
+
+  if (len != 8)
+    return 0;
+
+  switch (s[0])
+    {
+    case 'd':
+      mode = STRUB_DISABLED;
+      ret = -1;
+      break;
+
+    case 'a':
+      mode = STRUB_AT_CALLS;
+      ret = 2;
+      break;
+
+    case 'i':
+      mode = STRUB_INTERNAL;
+      ret = 1;
+      break;
+
+    case 'c':
+      mode = STRUB_CALLABLE;
+      ret = -2;
+      break;
+
+    default:
+      /* Other parms are for internal use only.  */
+      return 0;
+    }
+
+  tree mode_id = get_strub_mode_attr_parm (mode);
+
+  if (TREE_CODE (id) == IDENTIFIER_NODE
+      ? id != mode_id
+      : strncmp (s, IDENTIFIER_POINTER (mode_id), len) != 0)
+    return 0;
+
+  return ret;
 }
 
+/* Return the strub mode from STRUB_ATTR.  VAR_P should be true if the attribute
+   is taken from a variable, rather than from a function, or a type thereof.  */
+
 static enum strub_mode
 get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
 {
@@ -200,28 +342,102 @@ get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
     {
       if (!TREE_VALUE (strub_attr))
 	mode = !var_p ? STRUB_AT_CALLS : STRUB_INTERNAL;
-      else if (TREE_CODE (TREE_VALUE (TREE_VALUE (strub_attr))) == INTEGER_CST)
-	mode = (enum strub_mode) tree_to_shwi (TREE_VALUE
-					       (TREE_VALUE (strub_attr)));
-      else /* Handlers convert symbolic mode names to INTEGER_CST.  */
-	gcc_unreachable ();
+      else
+	{
+	  gcc_checking_assert (!var_p);
+	  tree id = TREE_VALUE (strub_attr);
+	  if (TREE_CODE (id) == TREE_LIST)
+	    id = TREE_VALUE (id);
+	  const char *s = (TREE_CODE (id) == STRING_CST
+			   ? TREE_STRING_POINTER (id)
+			   : IDENTIFIER_POINTER (id));
+	  size_t len = (TREE_CODE (id) == STRING_CST
+			? TREE_STRING_LENGTH (id) - 1
+			: IDENTIFIER_LENGTH (id));
+
+	  switch (len)
+	    {
+	    case 7:
+	      switch (s[6])
+		{
+		case 'r':
+		  mode = STRUB_WRAPPER;
+		  break;
+
+		case 'd':
+		  mode = STRUB_WRAPPED;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 8:
+	      switch (s[0])
+		{
+		case 'd':
+		  mode = STRUB_DISABLED;
+		  break;
+
+		case 'a':
+		  mode = STRUB_AT_CALLS;
+		  break;
+
+		case 'i':
+		  mode = STRUB_INTERNAL;
+		  break;
+
+		case 'c':
+		  mode = STRUB_CALLABLE;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 9:
+	      mode = STRUB_INLINABLE;
+	      break;
+
+	    case 12:
+	      mode = STRUB_AT_CALLS_OPT;
+	      break;
+
+	    default:
+	      gcc_unreachable ();
+	    }
+
+	  gcc_checking_assert (TREE_CODE (id) == IDENTIFIER_NODE
+			       ? id == get_strub_mode_attr_parm (mode)
+			       : strncmp (IDENTIFIER_POINTER
+					  (get_strub_mode_attr_parm (mode)),
+					  s, len) == 0);
+	}
     }
 
   return mode;
 }
 
+/* Look up, decode and return the strub mode associated with FNDECL.  */
+
 static enum strub_mode
-get_strub_mode_from_decl (tree fndecl)
+get_strub_mode_from_fndecl (tree fndecl)
 {
   return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
 }
 
+/* Look up, decode and return the strub mode associated with NODE.  */
+
 static enum strub_mode
 get_strub_mode (cgraph_node *node)
 {
-  return get_strub_mode_from_decl (node->decl);
+  return get_strub_mode_from_fndecl (node->decl);
 }
 
+/* Look up, decode and return the strub mode associated with TYPE.  */
+
 static enum strub_mode
 get_strub_mode_from_type (tree type)
 {
@@ -231,12 +447,15 @@ get_strub_mode_from_type (tree type)
   if (attr)
     return get_strub_mode_from_attr (attr, var_p);
 
-  if (flag_strub > 0 && !var_p)
+  if (flag_strub >= -1 && !var_p)
     return STRUB_CALLABLE;
 
   return STRUB_DISABLED;
 }
 
+\f
+/* Return true iff NODE calls builtin va_start.  */
+
 static bool
 calls_builtin_va_start_p (cgraph_node *node)
 {
@@ -252,6 +471,8 @@ calls_builtin_va_start_p (cgraph_node *node)
   return result;
 }
 
+/* Return true iff NODE calls builtin apply_args, and optionally REPORT it.  */
+
 static bool
 calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
 {
@@ -276,12 +497,17 @@ calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE carries the always_inline attribute.  */
+
 static inline bool
 strub_always_inline_p (cgraph_node *node)
 {
   return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
 }
 
+/* Return true iff NODE is potentially eligible for any strub-enabled mode, and
+   optionally REPORT the reasons for ineligibility.  */
+
 static inline bool
 can_strub_p (cgraph_node *node, bool report = false)
 {
@@ -321,6 +547,10 @@ can_strub_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE is eligible for at-calls strub, and optionally REPORT
+   the reasons for ineligibility.  Besides general non-eligibility for
+   strub-enabled modes, at-calls rules out calling builtin apply_args.  */
+
 static bool
 can_strub_at_calls_p (cgraph_node *node, bool report = false)
 {
@@ -332,6 +562,9 @@ can_strub_at_calls_p (cgraph_node *node, bool report = false)
   return !calls_builtin_apply_args_p (node, report);
 }
 
+/* Symbolic macro for the max number of arguments that internal strub may add to
+   a function.  */
+
 #define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
 
 /* We can't perform internal strubbing if the function body involves certain
@@ -438,14 +671,12 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 			   || tree_versionable_function_p (node->decl));
 
 
-      /* Label values referenced are not preserved when copying.  If referenced
+      /* Label values references are not preserved when copying.  If referenced
 	 in nested functions, as in 920415-1.c and 920721-4.c their decls get
-	 remapped independently.  That might be too broad, in that we might be
-	 able to support correctly cases in which the labels are only used
-	 internally in a function, but disconnecting user labels from their
-	 original declarations is undesirable in general, and it probably
-	 doesn't matter, since explicitly-requested strub likely uses
-	 STRUB_AT_CALLS mode anyway.  */
+	 remapped independently.  The exclusion below might be too broad, in
+	 that we might be able to support correctly cases in which the labels
+	 are only used internally in a function, but disconnecting forced labels
+	 from their original declarations is undesirable in general.  */
       basic_block bb;
       FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
 	for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -459,8 +690,6 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 
 	    target = gimple_label_label (label_stmt);
 
-	    /* Make an edge to every label block that has been marked as a
-	       potential target for a computed goto or a non-local goto.  */
 	    if (!FORCED_LABEL (target))
 	      continue;
 
@@ -470,7 +699,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 forced labels");
 	  }
     }
 
@@ -491,6 +720,9 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return TRUE iff NODE has any strub-requiring local variable, or accesses (as
+   in reading) any variable through a strub-requiring type.  */
+
 static bool
 strub_from_body_p (cgraph_node *node)
 {
@@ -506,7 +738,9 @@ strub_from_body_p (cgraph_node *node)
 	!= STRUB_DISABLED)
       return true;
 
-  /* Now scan the body for loads with strub types.  */
+  /* Now scan the body for loads with strub-requiring types.
+     ??? Compound types don't propagate the strub requirement to
+     component types.  */
   basic_block bb;
   FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
     for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -528,6 +762,7 @@ strub_from_body_p (cgraph_node *node)
 
 /* Return true iff node is associated with a builtin that should be callable
    from strub contexts.  */
+
 static inline bool
 strub_callable_builtin_p (cgraph_node *node)
 {
@@ -568,17 +803,23 @@ strub_callable_builtin_p (cgraph_node *node)
     }
 }
 
+/* Compute the strub mode to be used for NODE.  STRUB_ATTR should be the strub
+   attribute,found for NODE, if any.  */
+
 static enum strub_mode
 compute_strub_mode (cgraph_node *node, tree strub_attr)
 {
   enum strub_mode req_mode = get_strub_mode_from_attr (strub_attr);
 
-  gcc_checking_assert (flag_strub >= -1 && flag_strub <= 3);
+  gcc_checking_assert (flag_strub >= -2 && flag_strub <= 3);
 
   /* Symbolic encodings of the -fstrub-* flags.  */
   /* Enable strub when explicitly requested through attributes to functions or
      variables, reporting errors if the requests cannot be satisfied.  */
   const bool strub_flag_auto = flag_strub < 0;
+  /* strub_flag_auto with strub call verification; without this, functions are
+     implicitly callable.  */
+  const bool strub_flag_strict = flag_strub < -1;
   /* Disable strub altogether, ignore attributes entirely.  */
   const bool strub_flag_disabled = flag_strub == 0;
   /* On top of _auto, also enable strub implicitly for functions that can
@@ -625,7 +866,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (!strub_flag_disabled
        && (strub_attr
 	   ? req_mode == STRUB_CALLABLE
-	   : (strub_flag_viable
+	   : (!strub_flag_strict
 	      || strub_callable_builtin_p (node))));
 
   /* This is a shorthand for either strub-enabled mode.  */
@@ -674,19 +915,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (at_calls_eligible
        && (strub_attr
 	   || (node->has_gimple_body_p ()
-#if 0 /* We no longer use collect_callers, so we can probably drop it.  */
-	       && node->get_availability () > AVAIL_INTERPOSABLE
-#endif
-	       && ((!node->externally_visible
-#if 0
-		    /* We wish to bypass the test below for functions that are
-		       not externally visible, but that's a little too broad: we
-		       do not wish to skip them for e.g. gnu_inline
-		       functions.  */
-		    && !TREE_PUBLIC (node->decl)
-		    && !DECL_EXTERNAL (node->decl)
-#endif
-		    )
+	       && (!node->externally_visible
 		   || (node->binds_to_current_def_p ()
 		       && node->can_be_local_p ()))
 	       && node->only_called_directly_p ())));
@@ -737,10 +966,6 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
   const enum strub_mode mode
     = ((strub_enable && is_always_inline)
        ? (strub_required ? STRUB_INLINABLE : STRUB_CALLABLE)
-#if 0
-       : (!strub_enable && strub_required && strub_attr)
-       ? req_mode
-#endif
        : (strub_enable && internal_viable
 	  && (strub_flag_internal || !at_calls_viable))
        ? STRUB_INTERNAL
@@ -802,6 +1027,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 /* Set FNDT's strub mode to MODE; FNDT may be a function decl or
    function type.  If OVERRIDE, do not check whether a mode is already
    set.  */
+
 static void
 strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
 {
@@ -828,12 +1054,18 @@ strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
   *attrp = attr;
 }
 
+/* Set FNDT's strub mode to callable.
+   FNDT may be a function decl or a function type.  */
+
 void
 strub_make_callable (tree fndt)
 {
   strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
 }
 
+/* Set NODE to strub MODE.  Report incompatibilities between MODE and the mode
+   requested through explicit attributes, and cases of non-eligibility.  */
+
 static void
 set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
 {
@@ -853,9 +1085,10 @@ 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",
-		    (int) mode, node->decl,
-		    (int) get_strub_mode_from_attr (attr));
+		    "%<strub%> mode %qE selected for %qD, when %qE was requested",
+		    get_strub_mode_attr_parm (mode),
+		    node->decl,
+		    get_strub_mode_attr_parm (req_mode));
 	  if (node->alias)
 	    {
 	      cgraph_node *target = node->ultimate_alias_target ();
@@ -906,6 +1139,8 @@ set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
   strub_set_fndt_mode_to (node->decl, mode, attr);
 }
 
+/* Compute and set NODE's strub mode.  */
+
 static void
 set_strub_mode (cgraph_node *node)
 {
@@ -946,6 +1181,7 @@ set_strub_mode (cgraph_node *node)
   set_strub_mode_to (node, mode);
 }
 
+\f
 /* Non-strub functions shouldn't be called from within strub contexts,
    except through callable ones.  Always inline strub functions can
    only be called from strub functions.  */
@@ -984,7 +1220,7 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_AT_CALLS_OPT:
     case STRUB_INTERNAL:
     case STRUB_WRAPPER:
-      return (flag_strub >= 0);
+      return (flag_strub >= -1);
 
     case STRUB_DISABLED:
       return false;
@@ -999,12 +1235,16 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
   return true;
 }
 
-/* We wish to avoid inlining WRAPPED functions back into their
-   WRAPPERs.  More generally, we wish to avoid inlining
-   strubbed functions into non-strubbed ones.  */
+/* Return true if CALLEE can be inlined into CALLER.  We wish to avoid inlining
+   WRAPPED functions back into their WRAPPERs.  More generally, we wish to avoid
+   inlining strubbed functions into non-strubbed ones.  CALLER doesn't have to
+   be an immediate caller of CALLEE: the immediate caller may have already been
+   cloned for inlining, and then CALLER may be further up the original call
+   chain.  ???  It would be nice if our own caller would retry inlining callee
+   if caller gets inlined.  */
 
 bool
-strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+strub_inlinable_to_p (cgraph_node *callee, cgraph_node *caller)
 {
   strub_mode callee_mode = get_strub_mode (callee);
 
@@ -1020,6 +1260,10 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_WRAPPER:
     case STRUB_DISABLED:
     case STRUB_CALLABLE:
+      /* When we consider inlining, we've already verified callability, so we
+	 can even inline callable and then disabled into a strub context.  That
+	 will get strubbed along with the context, so it's hopefully not a
+	 problem.  */
       return true;
 
     default:
@@ -1049,20 +1293,50 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
   return false;
 }
 
+/* Check that types T1 and T2 are strub-compatible.  Return 1 if the strub modes
+   are the same, 2 if they are interchangeable, and 0 otherwise.  */
+
+int
+strub_comptypes (tree t1, tree t2)
+{
+  enum strub_mode m1 = get_strub_mode_from_type (t1);
+  enum strub_mode m2 = get_strub_mode_from_type (t2);
+
+  if (m1 == m2)
+    return 1;
+
+  /* We're dealing with types, so only strub modes that can be selected by
+     attributes in the front end matter.  If either mode is at-calls, the
+     conversion is not compatible.  */
+  if (m1 == STRUB_AT_CALLS || m2 == STRUB_AT_CALLS)
+    return 0;
+
+  return 2;
+}
+
 /* Check that strub functions don't call non-strub functions, and that
    always_inline strub functions are only called by strub
    functions.  */
+
 static void
 verify_strub ()
 {
   cgraph_node *node;
 
+  /* It's expected that check strub-wise pointer type compatibility of variables
+     and of functions is already taken care of by front-ends, on account of the
+     attribute's being marked as affecting type identity and of the creation of
+     distinct types.  */
+
+  /* Check that call targets in strub contexts have strub-callable types.  */
+
   FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
   {
     enum strub_mode caller_mode = get_strub_mode (node);
     bool strub_context
       = (caller_mode == STRUB_AT_CALLS
 	 || caller_mode == STRUB_AT_CALLS_OPT
+	 || caller_mode == STRUB_INTERNAL
 	 || caller_mode == STRUB_WRAPPED
 	 || caller_mode == STRUB_INLINABLE);
 
@@ -1105,14 +1379,11 @@ verify_strub ()
 	  }
       }
   }
-
-  /* ??? Check strub-wise pointer type compatibility of variables and
-     functions, or is this already taken care of on account of the
-     attribute's being marked as affecting type identity?  */
 }
 
 namespace {
 
+/* Define a pass to compute strub modes.  */
 const pass_data pass_data_ipa_strub_mode = {
   SIMPLE_IPA_PASS,
   "strubm",
@@ -1133,24 +1404,26 @@ public:
   {}
   opt_pass *clone () { return new pass_ipa_strub_mode (m_ctxt); }
   virtual bool gate (function *) {
-    /* In the default setting, the attribute handler changes
-       flag_strub to -1 if any strub-enabling occurence of the
-       attribute is found.  If it remains at -2, nothing that would
-       enable strub was found, so we can disable it and avoid the
+    /* In relaxed (-3) and strict (-4) settings, that only enable strub at a
+       function or variable attribute's request, the attribute handler changes
+       flag_strub to -1 or -2, respectively, if any strub-enabling occurence of
+       the attribute is found.  Therefore, if it remains at -3 or -4, nothing
+       that would enable strub was found, so we can disable it and avoid the
        overhead.  */
-    if (flag_strub == -2)
+    if (flag_strub < -2)
       flag_strub = 0;
     return flag_strub;
   }
   virtual unsigned int execute (function *);
 };
 
+/* Define a pass to introduce strub transformations.  */
 const pass_data pass_data_ipa_strub = {
   SIMPLE_IPA_PASS,
   "strub",
   OPTGROUP_NONE,
   TV_NONE,
-  PROP_cfg, // properties_required
+  PROP_cfg | PROP_ssa, // properties_required
   0,	    // properties_provided
   0,	    // properties_destroyed
   0,	    // properties_start
@@ -1167,9 +1440,10 @@ public:
     : simple_ipa_opt_pass (pass_data_ipa_strub, ctxt)
   {}
   opt_pass *clone () { return new pass_ipa_strub (m_ctxt); }
-  virtual bool gate (function *) { return flag_strub; }
+  virtual bool gate (function *) { return flag_strub && !seen_error (); }
   virtual unsigned int execute (function *);
 
+  /* Define on demand and cache some types we use often.  */
 #define DEF_TYPE(NAME, INIT)			\
   static inline tree get_ ## NAME () {		\
     static tree type = NULL_TREE;		\
@@ -1202,6 +1476,7 @@ public:
 
 #undef DEF_TYPE
 
+  /* Define non-strub builtins on demand.  */
 #define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST)			\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1224,6 +1499,7 @@ public:
 
 #undef DEF_NM_BUILTIN
 
+  /* Define strub builtins on demand.  */
 #define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST)		\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1260,6 +1536,7 @@ public:
 
 #undef DEF_SS_BUILTIN
 
+    /* Define strub identifiers on demand.  */
 #define DEF_IDENT(NAME)					\
   static inline tree get_ ## NAME () {			\
     static tree identifier = NULL_TREE;			\
@@ -1278,6 +1555,10 @@ public:
   static inline void adjust_at_calls_call (cgraph_edge *, int);
   static inline void adjust_at_calls_calls (cgraph_node *);
 
+  /* Add to SEQ a call to the strub watermark update builtin, taking NODE's
+     location if given.  Optionally add the corresponding edge from NODE, with
+     execution frequency COUNT.  Return the modified SEQ.  */
+
   static inline gimple_seq
   call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
 			 gimple_seq seq = NULL)
@@ -1300,8 +1581,15 @@ public:
 
 } // anon namespace
 
+/* Gather with this type a collection of parameters that we're turning into
+   explicit references.  */
+
 typedef hash_set<tree> indirect_parms_t;
 
+/* Dereference OP's incoming turned-into-reference parm if it's an
+   INDIRECT_PARMS or an ADDR_EXPR thereof.  Set *REC and return according to
+   gimple-walking expectations.  */
+
 static tree
 maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
 {
@@ -1324,12 +1612,17 @@ maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
     {
       *rec = 0;
       if (indirect_parms.contains (TREE_OPERAND (op, 0)))
-	return TREE_OPERAND (op, 0);
+	{
+	  op = TREE_OPERAND (op, 0);
+	  return op;
+	}
     }
 
   return NULL_TREE;
 }
 
+/* A gimple-walking function that adds dereferencing to indirect parms.  */
+
 static tree
 walk_make_indirect (tree *op, int *rec, void *arg)
 {
@@ -1351,6 +1644,11 @@ walk_make_indirect (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* A gimple-walking function that turns any non-gimple-val ADDR_EXPRs into a
+   separate SSA.  Though addresses of e.g. parameters, and of members thereof,
+   are gimple vals, turning parameters into references, with an extra layer of
+   indirection and thus explicit dereferencing, need to be regimplified.  */
+
 static tree
 walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
 {
@@ -1374,6 +1672,57 @@ walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* Turn STMT's PHI arg defs into separate SSA defs if they've become
+   non-gimple_val.  Return TRUE if any edge insertions need to be committed.  */
+
+static bool
+walk_regimplify_phi (gphi *stmt)
+{
+  bool needs_commit = false;
+
+  for (unsigned i = 0, n = gimple_phi_num_args (stmt); i < n; i++)
+    {
+      tree op = gimple_phi_arg_def (stmt, i);
+      if ((TREE_CODE (op) == ADDR_EXPR
+	   && !is_gimple_val (op))
+	  /* ??? A PARM_DECL that was addressable in the original function and
+	     had its address in PHI nodes, but that became a reference in the
+	     wrapped clone would NOT be updated by update_ssa in PHI nodes.
+	     Alas, if we were to create a default def for it now, update_ssa
+	     would complain that the symbol that needed rewriting already has
+	     SSA names associated with it.  OTOH, leaving the PARM_DECL alone,
+	     it eventually causes errors because it remains unchanged in PHI
+	     nodes, but it gets rewritten as expected if it appears in other
+	     stmts.  So we cheat a little here, and force the PARM_DECL out of
+	     the PHI node and into an assignment.  It's a little expensive,
+	     because we insert it at the edge, which introduces a basic block
+	     that's entirely unnecessary, but it works, and the block will be
+	     removed as the default def gets propagated back into the PHI node,
+	     so the final optimized code looks just as expected.  */
+	  || (TREE_CODE (op) == PARM_DECL
+	      && !TREE_ADDRESSABLE (op)))
+	{
+	  tree temp = make_ssa_name (TREE_TYPE (op), stmt);
+	  if (TREE_CODE (op) == PARM_DECL)
+	    SET_SSA_NAME_VAR_OR_IDENTIFIER (temp, DECL_NAME (op));
+	  SET_PHI_ARG_DEF (stmt, i, temp);
+
+	  gimple *assign = gimple_build_assign (temp, op);
+	  if (gimple_phi_arg_has_location (stmt, i))
+	    gimple_set_location (assign, gimple_phi_arg_location (stmt, i));
+	  gsi_insert_on_edge (gimple_phi_arg_edge (stmt, i), assign);
+	  needs_commit = true;
+	}
+    }
+
+  return needs_commit;
+}
+
+/* Create a reference type to use for PARM when turning it into a reference.
+   NONALIASED causes the reference type to gain its own separate alias set, so
+   that accessing the indirectly-passed parm won'will not add aliasing
+   noise.  */
+
 static tree
 build_ref_type_for (tree parm, bool nonaliased = true)
 {
@@ -1411,6 +1760,7 @@ build_ref_type_for (tree parm, bool nonaliased = true)
 
 /* Add cgraph edges from current_function_decl to callees in SEQ with frequency
    COUNT, assuming all calls in SEQ are direct.  */
+
 static void
 add_call_edges_for_seq (gimple_seq seq, profile_count count)
 {
@@ -1425,16 +1775,22 @@ add_call_edges_for_seq (gimple_seq seq, profile_count count)
     {
       gimple *stmt = gsi_stmt (gsi);
 
-      if (!is_a <gcall *> (stmt))
+      gcall *call = dyn_cast <gcall *> (stmt);
+      if (!call)
 	continue;
 
-      gcall *call = as_a <gcall *> (stmt);
       tree callee = gimple_call_fndecl (call);
       gcc_checking_assert (callee);
       node->create_edge (cgraph_node::get_create (callee), call, count, false);
     }
 }
 
+/* Insert SEQ after the call at GSI, as if the call was in a try block with SEQ
+   as finally, i.e., SEQ will run after the call whether it returns or
+   propagates an exception.  This handles block splitting, EH edge and block
+   creation, noreturn and nothrow optimizations, and even throwing calls without
+   preexisting local handlers.  */
+
 static void
 gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
 {
@@ -1446,7 +1802,7 @@ gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
   if (gimple_has_location (stmt))
     annotate_all_with_location (seq, gimple_location (stmt));
 
-  gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+  gcall *call = dyn_cast <gcall *> (stmt);
   bool noreturn_p = call && gimple_call_noreturn_p (call);
   int eh_lp = lookup_stmt_eh_lp (stmt);
   bool must_not_throw_p = eh_lp < 0;
@@ -1586,8 +1942,12 @@ remove_named_attribute_unsharing (const char *name, tree *attrs)
     }
 }
 
+/* Record the order of the last cgraph entry whose mode we've already set, so
+   that we can perform mode setting incrementally without duplication.  */
 static int last_cgraph_order;
 
+/* Set strub modes for functions introduced since the last call.  */
+
 static void
 ipa_strub_set_mode_for_new_functions ()
 {
@@ -1616,6 +1976,7 @@ ipa_strub_set_mode_for_new_functions ()
 }
 
 /* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
+
 bool
 strub_splittable_p (cgraph_node *node)
 {
@@ -1641,10 +2002,11 @@ strub_splittable_p (cgraph_node *node)
 }
 
 /* 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))
+  switch (get_strub_mode_from_fndecl (fndecl))
     {
     case STRUB_WRAPPED:
     case STRUB_AT_CALLS:
@@ -1676,6 +2038,7 @@ strub_watermark_parm (tree fndecl)
 
 /* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
    hasn't been added yet.  Return the named argument count.  */
+
 int
 pass_ipa_strub::adjust_at_calls_type (tree type)
 {
@@ -1751,6 +2114,12 @@ pass_ipa_strub::adjust_at_calls_type (tree type)
   return named_args;
 }
 
+/* Adjust a call to an at-calls call target.  Create a watermark local variable
+   if needed, initialize it before, pass it to the callee according to the
+   modified at-calls interface, and release the callee's stack space after the
+   call, if not deferred.  If the call is const or pure, arrange for the
+   watermark to not be assumed unused or unchanged.  */
+
 void
 pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 {
@@ -1819,10 +2188,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 
     if (gimple_call_internal_p (stmt))
 #if 0
-      /*
-	new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
-	vargs);
-      */
+      new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
+						 vargs);
 #endif
       gcc_unreachable ();
     else
@@ -1917,6 +2284,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
   gsi_insert_finally_seq_after_call (gsi, seq);
 }
 
+/* Adjust all at-calls calls in NODE. */
+
 void
 pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
 {
@@ -1965,6 +2334,10 @@ pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
     }
 }
 
+/* The strubm (strub mode) pass computes a strub mode for each function in the
+   call graph, and checks, before any inlining, that strub callability
+   requirements in effect are satisfied.  */
+
 unsigned int
 pass_ipa_strub_mode::execute (function *)
 {
@@ -1977,12 +2350,17 @@ pass_ipa_strub_mode::execute (function *)
   return 0;
 }
 
+/* Create a strub mode pass.  */
+
 simple_ipa_opt_pass *
 make_pass_ipa_strub_mode (gcc::context *ctxt)
 {
   return new pass_ipa_strub_mode (ctxt);
 }
 
+/* The strub pass proper adjusts types, signatures, and at-calls calls, and
+   splits internal-strub functions.  */
+
 unsigned int
 pass_ipa_strub::execute (function *)
 {
@@ -2042,17 +2420,6 @@ pass_ipa_strub::execute (function *)
 	if (onode->alias)
 	  continue;
 
-#if 0 /* Calls are now adjusted when examining callers.  */
-	unsigned c;
-	cgraph_edge *e;
-	FOR_EACH_VEC_ELT (onode->collect_callers (), c, e)
-	  {
-	    push_cfun (DECL_STRUCT_FUNCTION (e->caller->decl));
-	    adjust_at_calls_call (e, named_args);
-	    pop_cfun ();
-	  }
-#endif
-
 	cgraph_node *nnode = onode;
 	push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
 
@@ -2071,10 +2438,10 @@ pass_ipa_strub::execute (function *)
 		{
 		  gimple *stmt = gsi_stmt (gsi);
 
-		  if (!is_gimple_call (stmt))
-		    continue;
+		  gcall *call = dyn_cast <gcall *> (stmt);
 
-		  gcall *call = as_a <gcall *> (stmt);
+		  if (!call)
+		    continue;
 
 		  if (gimple_alloca_call_p (call))
 		    {
@@ -2088,10 +2455,6 @@ pass_ipa_strub::execute (function *)
 	  }
 
 	pop_cfun ();
-
-#if 0
-	compute_fn_summary (onode, true);
-#endif
       }
   }
 
@@ -2108,43 +2471,6 @@ pass_ipa_strub::execute (function *)
 	continue;
       }
 
-#if 0
-    /* Hmm, this is an i386-specific attribute.  Do we need machine-specific
-       logic?  */
-    remove_named_attribute_unsharing ("interrupt",
-				      &DECL_ATTRIBUTES (onode->decl));
-#endif
-
-#if 0
-    if (!DECL_STRUCT_FUNCTION (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting struct-less function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    if (!onode->lowered)
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting non-lowered function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    /* 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.  FIXME.  */
-    if (!tree_versionable_function_p (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"%qD cannot be split for %<strub%>",
-		onode->decl);
-	continue;
-      }
-#endif
-
     bool is_stdarg = calls_builtin_va_start_p (onode);;
     bool apply_args = calls_builtin_apply_args_p (onode);
 
@@ -2753,26 +3079,45 @@ pass_ipa_strub::execute (function *)
       if (any_indirect)
 	{
 	  basic_block bb;
+	  bool needs_commit = false;
 	  FOR_EACH_BB_FN (bb, cfun)
-	    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
-		 !gsi_end_p (gsi); gsi_next (&gsi))
-	      {
-		gimple *stmt = gsi_stmt (gsi);
+	    {
+	      for (gphi_iterator gsi = gsi_start_nonvirtual_phis (bb);
+		   !gsi_end_p (gsi);
+		   gsi_next_nonvirtual_phi (&gsi))
+		{
+		  gphi *stmt = gsi.phi ();
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed && !is_gimple_debug (gsi_stmt (gsi)))
+		    if (walk_regimplify_phi (stmt))
+		      needs_commit = true;
+		}
 
-		walk_stmt_info wi = {};
-		wi.info = &indirect_nparms;
-		walk_gimple_op (stmt, walk_make_indirect, &wi);
-		if (wi.changed)
-		  {
-		    if (!is_gimple_debug (gsi_stmt (gsi)))
-		      {
-			wi.info = &gsi;
-			walk_gimple_op (stmt, walk_regimplify_addr_expr,
-					&wi);
-		      }
-		    update_stmt (stmt);
-		  }
-	      }
+	      for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+		   !gsi_end_p (gsi); gsi_next (&gsi))
+		{
+		  gimple *stmt = gsi_stmt (gsi);
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed)
+		    {
+		      if (!is_gimple_debug (stmt))
+			{
+			  wi.info = &gsi;
+			  walk_gimple_op (stmt, walk_regimplify_addr_expr,
+					  &wi);
+			}
+		      update_stmt (stmt);
+		    }
+		}
+	    }
+	  if (needs_commit)
+	    gsi_commit_edge_inserts ();
 	}
 
       if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca
@@ -2842,10 +3187,10 @@ pass_ipa_strub::execute (function *)
       push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
       gimple_stmt_iterator gsi
 	= gsi_after_labels (single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun)));
-      while (!is_gimple_call (gsi_stmt (gsi)))
-	gsi_next (&gsi);
 
-      gcall *wrcall = as_a <gcall *> (gsi_stmt (gsi));
+      gcall *wrcall;
+      while (!(wrcall = dyn_cast <gcall *> (gsi_stmt (gsi))))
+	gsi_next (&gsi);
 
       tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
       TREE_ADDRESSABLE (swm) = true;
@@ -2973,22 +3318,8 @@ pass_ipa_strub::execute (function *)
 
       pop_cfun ();
     }
-
-#if 0
-    compute_fn_summary (onode, true);
-    compute_fn_summary (nnode, true);
-#endif
   }
 
-  if (flag_checking)
-    {
-      /* We've already verified before any inlining or other transformations.
-	 Recheck after strub transformations only if checking is enabled, since
-	 they should not introduce any incompatibilities.  */
-      ipa_strub_set_mode_for_new_functions ();
-      verify_strub ();
-    }
-
   return 0;
 }
 
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
index c335ab42097..5c0b266c3f8 100644
--- a/gcc/ipa-strub.h
+++ b/gcc/ipa-strub.h
@@ -18,11 +18,10 @@ You should have received a copy of the GNU General Public License
 along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
-/* Return TRUE if the first function can be inlined into the second,
-   as far as stack scrubbing constraints are concerned.  CALLEE
-   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 TRUE if CALLEE can be inlined into CALLER, as far as stack scrubbing
+   constraints are concerned.  CALLEE doesn't have to be called directly by
+   CALLER, but the returned value says nothing about intervening functions.  */
+extern bool strub_inlinable_to_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);
@@ -33,3 +32,14 @@ extern tree strub_watermark_parm (tree fndecl);
 
 /* Make a function type or declaration callable.  */
 extern void strub_make_callable (tree fndecl);
+
+/* Return zero iff ID is NOT an acceptable parameter for a user-supplied strub
+   attribute for a function.  Otherwise, return >0 if it enables strub, <0 if it
+   does not.  Return +/-1 if the attribute-modified type is compatible with the
+   type without the attribute, or +/-2 if it is not compatible.  */
+extern int strub_validate_fn_attr_parm (tree id);
+
+/* Like comptypes, return 0 if t1 and t2 are not compatible, 1 if they are
+   compatible, and 2 if they are nearly compatible.  Same strub mode is
+   compatible, interface-compatible strub modes are nearly compatible.  */
+extern int strub_comptypes (tree t1, tree t2);
diff --git a/gcc/multiple_target.c b/gcc/multiple_target.c
index 28d5f95dca5..07cdb66e337 100644
--- a/gcc/multiple_target.c
+++ b/gcc/multiple_target.c
@@ -557,7 +557,7 @@ public:
 bool
 pass_target_clone::gate (function *)
 {
-  return true;
+  return !seen_error ();
 }
 
 } // anon namespace
diff --git a/gcc/testsuite/c-c++-common/strub-O0.c b/gcc/testsuite/c-c++-common/strub-O0.c
index 6afe0fd5de1..c7a79a6ea0d 100644
--- a/gcc/testsuite/c-c++-common/strub-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O0 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O0, none of the strub builtins are expanded inline.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O1.c b/gcc/testsuite/c-c++-common/strub-O1.c
index 1cdeaecaf32..96285c975d9 100644
--- a/gcc/testsuite/c-c++-common/strub-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O1 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O1, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
index 7848c46d179..8edc0d8aa13 100644
--- a/gcc/testsuite/c-c++-common/strub-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O2, without -fno-inline, we fully expand enter and update, and add a test
    around the leave call.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
index 85a8f76785e..c6d900cf3c4 100644
--- a/gcc/testsuite/c-c++-common/strub-O2fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3.c b/gcc/testsuite/c-c++-common/strub-O3.c
index 1fcde345d36..33ee465e51c 100644
--- a/gcc/testsuite/c-c++-common/strub-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand" } */
 
 int __attribute__ ((__strub__)) var;
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
index a2eedfd96b2..2936f82079e 100644
--- a/gcc/testsuite/c-c++-common/strub-O3fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
index e5cb1f60541..479746e57d8 100644
--- a/gcc/testsuite/c-c++-common/strub-Og.c
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Og -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Og -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Og, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
index 194aacc2c05..2241d4ea07f 100644
--- a/gcc/testsuite/c-c++-common/strub-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Os, without -fno-inline, we fully expand enter, and also update.  The
    expanded update might be larger than a call proper, but argument saving and
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
index 46e84bf6560..a322bcc5da6 100644
--- a/gcc/testsuite/c-c++-common/strub-all1.c
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -22,11 +22,11 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
index f377541cff0..db60026d0e0 100644
--- a/gcc/testsuite/c-c++-common/strub-all2.c
+++ b/gcc/testsuite/c-c++-common/strub-all2.c
@@ -17,8 +17,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
index f180b17f30e..2f462adc1ef 100644
--- a/gcc/testsuite/c-c++-common/strub-apply1.c
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -1,13 +1,13 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
-void __attribute__ ((__strub__ (3)))
+void __attribute__ ((__strub__ ("callable")))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0);
 }
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   void *args = __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
index 379a54b73b7..bab2dd10f5b 100644
--- a/gcc/testsuite/c-c++-common/strub-apply2.c
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 extern void __attribute__ ((__strub__))
 apply_function (void *args);
@@ -10,3 +10,9 @@ apply_args (int i, int j, double d) /* { dg-error "selected" } */
   void *args = __builtin_apply_args (); /* { dg-message "does not support" } */
   apply_function (args);
 }
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After the error and sorry messages in strubm, build_ssa_passes are gated out,
+   so we don't get the PROP_ssa property that targetclone requires.  When
+   checking is not enabled at configure time, we admit to being confused and
+   bail out, but when checking is eneabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
index 9b4786be698..85f9d090fda 100644
--- a/gcc/testsuite/c-c++-common/strub-apply3.c
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -1,8 +1,14 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 void __attribute__ ((__strub__))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */
 }
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
index 409f747743e..15ffaa031b8 100644
--- a/gcc/testsuite/c-c++-common/strub-apply4.c
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+/* { dg-options "-O2 -fstrub=strict -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
@@ -18,4 +18,4 @@ void f() {
   apply_args (1, 2, 3);
 }
 
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 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 d964b07ae5d..b70843b4215 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -22,9 +22,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
index 530eee36d06..97a3988a6b9 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls2.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls2.c
@@ -17,7 +17,7 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-default1.c b/gcc/testsuite/c-c++-common/strub-default1.c
deleted file mode 100644
index 79d00cedb9a..00000000000
--- a/gcc/testsuite/c-c++-common/strub-default1.c
+++ /dev/null
@@ -1,42 +0,0 @@
-/* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
-
-static int __attribute__ ((__strub__)) var;
-
-/* 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 inline void
-__attribute__ ((__always_inline__))
-h() {
-  var++;
-}
-
-/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
-   the viability of at-calls strubbing.  Though internally a strub context, its
-   interface is not strub-enabled, so it's not callable from within strub
-   contexts.  */
-static inline void
-g() {
-  var--;
-  h();
-}
-
-/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
-   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
-void
-f() {
-  var++;
-  g();  /* { dg-error "calling non-.strub." } */
-}
-
-/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-4\[)\]" 1 "strubm" } } */
-/* { 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 "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
index 7b04eea35d9..3d73431b3dc 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O1" } */
+/* { dg-options "-fstrub=strict -O1" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O1.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
index 67d96419a5e..fddf3c745e7 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O2" } */
+/* { dg-options "-fstrub=strict -O2" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O2.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
index 34828d2711e..2bc384ee8d1 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O3" } */
+/* { dg-options "-fstrub=strict -O3" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -O3.  */
@@ -10,7 +10,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -18,7 +18,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -32,7 +32,7 @@ leak_string (void)
   return 0;
 }
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 int
 look_for_string (char *e)
 {
@@ -56,14 +56,14 @@ look_for_string (char *e)
   return 0;
 }
 
-static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+static __attribute__ ((__strub__ ("at-calls"), __noinline__, __noclone__))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 deferred_at_calls ()
 {
@@ -73,7 +73,7 @@ deferred_at_calls ()
   return ret;
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 deferred_internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
index b273660aea1..fbaf85fe0fa 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -Os" } */
+/* { dg-options "-fstrub=strict -Os" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -Os.  */
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
index a74658c9ac9..e9d7b7b9ee0 100644
--- a/gcc/testsuite/c-c++-common/strub-internal1.c
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -23,9 +23,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
index a6e69357b23..8b8e15a51c7 100644
--- a/gcc/testsuite/c-c++-common/strub-internal2.c
+++ b/gcc/testsuite/c-c++-common/strub-internal2.c
@@ -14,8 +14,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
index 7e22a266ad9..0a4a7539d34 100644
--- a/gcc/testsuite/c-c++-common/strub-parms1.c
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -16,7 +16,7 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 large_byref_arg (struct large_arg la)
 {
 }
@@ -24,7 +24,7 @@ large_byref_arg (struct large_arg la)
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*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)))
+void __attribute__ ((__strub__ ("internal")))
 std_arg (int i, ...)
 {
   va_list vl;
@@ -38,7 +38,7 @@ std_arg (int i, ...)
 /* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
index 2d8036c0fbc..147171d96d5 100644
--- a/gcc/testsuite/c-c++-common/strub-parms2.c
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -15,14 +15,14 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 large_byref_arg (struct large_arg la)
 {
 }
 
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 std_arg (int i, ...)
 {
   va_list vl;
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
index f64361f1235..4e92682895a 100644
--- a/gcc/testsuite/c-c++-common/strub-parms3.c
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
new file mode 100644
index 00000000000..e2f9d8aebca
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  Without the error,
+   inlining takes place.  */
+
+#include "strub-strict1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
new file mode 100644
index 00000000000..98474435d2e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  */
+
+#include "strub-strict2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
index a4226ce0119..1de15342595 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fexceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fexceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
index 3bab553478b..f9209c81900 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
index c89cc7c2c47..bed1dcfb54a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
index b869fafb691..6bf0071f52b 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
index 1d3dd2f2c2c..4732f515bf7 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
index 4dd4281b03b..8d6424c479a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-strict1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
new file mode 100644
index 00000000000..7f5247cfaf0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-strict1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+static int __attribute__ ((__strub__)) var;
+
+/* 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 inline void
+__attribute__ ((__always_inline__))
+h() {
+  var++;
+}
+
+/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
+   the viability of at-calls strubbing.  Though internally a strub context, its
+   interface is not strub-enabled, so it's not callable from within strub
+   contexts.  */
+static inline void
+g() {
+  var--;
+  h();
+}
+
+/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
+   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
+void
+f() {
+  var++;
+  g();  /* { dg-error "calling non-.strub." } */
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-default2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
similarity index 54%
rename from gcc/testsuite/c-c++-common/strub-default2.c
rename to gcc/testsuite/c-c++-common/strub-strict2.c
index 487253e9227..17463b6e554 100644
--- a/gcc/testsuite/c-c++-common/strub-default2.c
+++ b/gcc/testsuite/c-c++-common/strub-strict2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
 
 static int __attribute__ ((__strub__)) var;
 
@@ -22,8 +22,14 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
index 0840dddd136..e48e0610e07 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 #include "strub-tail-O2.c"
 
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
index 9330d6ff4c1..87cda7ab21b 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.
    Tail calls are short-circuited at -O2+.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
index 5b33ff1f530..b5e45ab0525 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
index 38935e3270b..96aa7fe4b07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -1,39 +1,39 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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);
+extern int __attribute__ ((__strub__ ("callable"))) xcallable (void);
+extern int __attribute__ ((__strub__ ("internal"))) xinternal (void);
+extern int __attribute__ ((__strub__ ("at-calls"))) xat_calls (void);
+extern int __attribute__ ((__strub__ ("disabled"))) 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__ ("callable"))) callable (void);
+int __attribute__ ((__strub__ ("internal"))) internal (void);
+int __attribute__ ((__strub__ ("at-calls"))) at_calls (void);
+int __attribute__ ((__strub__ ("disabled"))) disabled (void);
 
 int __attribute__ ((__strub__)) var;
 int var_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 icallable (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 iinternal (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 idisabled (void);
 static inline int __attribute__ ((__always_inline__))
 ivar_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 i_callable (void) { return 0; }
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 i_internal (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 i_at_calls (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 i_disabled (void) { return 0; }
 static inline int __attribute__ ((__always_inline__))
 i_var_user (void) { return var; }
@@ -70,7 +70,7 @@ i_var_user (void) { return var; }
 
 /* Not a strub context, so it can call anything.
    Explicitly declared as callable even from within strub contexts.  */
-int __attribute__ ((__strub__ (3)))
+int __attribute__ ((__strub__ ("callable")))
 callable (void) {
   int ret = 0;
 
@@ -88,7 +88,7 @@ callable (void) {
 
 /* 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)))
+int __attribute__ ((__strub__ ("internal")))
 internal (void) {
   int ret = var;
 
@@ -109,7 +109,7 @@ internal (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (1)))
+int __attribute__ ((__strub__ ("at-calls")))
 at_calls (void) {
   int ret = var;
 
@@ -130,7 +130,7 @@ at_calls (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (0)))
+int __attribute__ ((__strub__ ("disabled")))
 disabled () {
   int ret = 0;
 
@@ -205,7 +205,7 @@ iinternal (void) {
   return ret;
 }
 
-int
+int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void) {
   int ret = var;
 
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
index 100fb0c59a9..2857195706e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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,
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
index 9e818ac9748..98a92bc9eac 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
index d40e8aa45cb..5511a6e1e71 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __const__))
 f() {
   return 0;
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
index d4cbdaf10f3..47ee927964d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -1,12 +1,12 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__
 __attribute__ ((__const__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data1.c b/gcc/testsuite/c-c++-common/torture/strub-data1.c
index 62a03891ab6..7c27a2a1a6d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointed-to data enables strubbing if accessed.  */
 int __attribute__ ((__strub__)) var;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data2.c b/gcc/testsuite/c-c++-common/torture/strub-data2.c
index 9b7df13a280..e66d903780a 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointer itself is a strub variable, enabling internal strubbing when
    its value is used.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data3.c b/gcc/testsuite/c-c++-common/torture/strub-data3.c
index 515706caa32..5e08e0e58c6 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data4.c b/gcc/testsuite/c-c++-common/torture/strub-data4.c
index 0ec9e35429f..a818e7a38bb 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
index 69f3d65ed44..aad17aaa5c0 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -1,13 +1,15 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -Werror" } */
+/* { dg-options "-fstrub=strict" } */
+
+/* It would be desirable to issue at least warnings for these.  */
 
 typedef int __attribute__ ((__strub__)) strub_int;
 strub_int *ptr;
 
 int *f () {
-  return ptr; /* { dg-error "incompatible" } */
+  return ptr; /* { dg-message "incompatible" "" { xfail *-*-* } } */
 }
 
 strub_int *g () {
-  return f (); /* { dg-error "incompatible" } */
+  return f (); /* { dg-message "incompatible" "" { xfail *-*-* } } */
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
index b8adf8009e8..c165f312f16 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype ();
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
index 5b2c35ad6a7..69fcff8d376 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
index 5ee50456dc9..ff006224909 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int, ...);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
new file mode 100644
index 00000000000..95996cd5c25
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+inline void __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+}
+
+void
+bat (void)
+{
+  /* Not allowed, not a strub context.  */
+  inl_int_ali (); /* { dg-error "context" } */
+}
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
new file mode 100644
index 00000000000..f9a6b4a16fa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=all" } */
+
+#include "strub-inlinable1.c"
+
+/* With -fstrub=all, the caller becomes a strub context, so the strub-inlinable
+   callee is not rejected.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
new file mode 100644
index 00000000000..b4a7f3992bb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+typedef void ft (void);
+typedef void ft2 (int, int);
+extern ft __attribute__ ((__strub__)) fnac;
+
+ft * f (void) {
+  return fnac; /* { dg-message "incompatible|invalid conversion" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
new file mode 100644
index 00000000000..d9d2c0caec4
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
@@ -0,0 +1,55 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic" } */
+
+/* C++ does not warn about the partial incompatibilities.
+
+   The d_p () calls are actually rejected, even in C++, but they are XFAILed
+   here because we don't get far enough in the compilation as to observe them,
+   because the incompatibilities are errors without -fpermissive.
+   strub-ptrfn3.c uses -fpermissive to check those.
+ */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
new file mode 100644
index 00000000000..e1f179e160e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
@@ -0,0 +1,50 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic -fpermissive" } */
+/* { dg-prune-output "command-line option .-fpermissive." } */
+
+/* See strub-ptrfn2.c.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
new file mode 100644
index 00000000000..70b558afad0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+/* This is strub-ptrfn2.c without -Wpedantic.
+
+   Even C doesn't report the (not-quite-)compatible conversions without it.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
index cb223da6efc..a262a086837 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
index 67d1434b1f8..4c4bd50c209 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
index 59f02ea901f..ce195c6b1f1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -1,10 +1,10 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __pure__))
 f() {
   static int i; /* Stop it from being detected as const.  */
   return i;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
index 973e909217d..75cd54ccb5b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
 __attribute__ ((__pure__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
index a4077c35a60..c596066a045 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -14,7 +14,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -59,14 +59,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
index 94e4156ea73..1fdba214a07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
index 0ca74beb59d..afbc2cc9ab4 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
 /* Check that a non-strub function leaves a string behind in the stack, and that
@@ -7,7 +7,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
index 4ab11c0682e..5300f1d330b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -85,14 +85,14 @@ intermediate ()
   return ret;
 }
 
-static inline __attribute__ ((__strub__ (2)))
+static inline __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
   return intermediate ();
 }
 
-int __attribute__ ((strub (0)))
+int __attribute__ ((__strub__ ("disabled")))
 main ()
 {
   if (look_for_string (internal ()))
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4d.c b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
index e4f7445607c..08de3f1c3b1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4d.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -1,7 +1,7 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
-#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ (1)))
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
 
 #include "strub-run4.c"
diff --git a/gcc/testsuite/g++.dg/torture/strub-init1.C b/gcc/testsuite/g++.dg/torture/strub-init1.C
index e51ae802be4..c226ab10ff6 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init1.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init2.C b/gcc/testsuite/g++.dg/torture/strub-init2.C
index edcb7bf8ad2..a7911f1fa72 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init2.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init3.C b/gcc/testsuite/g++.dg/torture/strub-init3.C
index bacf823ca4e..6ebebcd01e8 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init3.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
index 697ac9de764..10445d7cf84 100644
--- a/gcc/testsuite/gnat.dg/strub_attr.adb
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -1,5 +1,5 @@
 --  { dg-do compile }
---  { dg-options "-fstrub=default -fdump-ipa-strubm" }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" }
 
 package body Strub_Attr is
    E : exception;
@@ -14,8 +14,24 @@ package body Strub_Attr is
       return X * X;
    end;
    
-   function G return Integer is (X);
+   function G return Integer is (F (X));
+   --  function G return Integer is (FP (X));
+   --  Calling G would likely raise an exception, because although FP
+   --  carries the strub at-calls attribute needed to call F, the
+   --  attribute is dropped from the type used for the call proper.
 end Strub_Attr;
 
---  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]2\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 0 "strubm" } }
 --  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark_ptr" 6 "strub" } }
+--  We have 1 at-calls subprogram (F) and 2 wrapped (P and G).
+--  For each of them, there's one match for the wrapped signature, 
+--  and one for the update call.
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 27 "strub" } }
+--  The 6 matches above, plus:
+--  5*2: wm var decl, enter, call, leave and clobber for each wrapper;
+--  2*1: an extra leave and clobber for the exception paths in the wrappers.
+--  7*1: for the F call in G, including EH path.
diff --git a/gcc/testsuite/gnat.dg/strub_ind.adb b/gcc/testsuite/gnat.dg/strub_ind.adb
new file mode 100644
index 00000000000..cfd7a76b02b
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind.adb
@@ -0,0 +1,50 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" }
+
+package body Strub_Ind is
+   E : exception;
+
+   procedure P (X : Integer) is
+   begin
+      raise E;
+   end;
+   
+   function F (X : Integer) return Integer is
+   begin
+      return X * X;
+   end;
+   
+   function G return Integer is (FP (X)); -- { dg-bogus "non-.strub." "" { xfail *-*-* } }
+   --  Calling G would likely raise an exception, because although FP
+   --  carries the strub at-calls attribute needed to call F, the
+   --  attribute is dropped from the type used for the call proper.
+
+
+   type GT is access function return Integer;
+   pragma Machine_Attribute (GT, "strub", "at-calls");
+   --  The pragma above seems to have no effect.
+
+   GP : GT := G'Access; -- { dg-warning "incompatible" "" { xfail *-*-* } }
+   pragma Machine_Attribute (GP, "strub", "at-calls");
+   --  The pragma above does modify GP's type,
+   --  but dereferencing it uses an unmodified copy of the type.
+   --  The initializer should be diagnosed:
+   --  GT should only reference functions with at-calls strub.
+
+end Strub_Ind;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 0 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark_ptr" 6 "strub" } }
+--  We have 1 at-calls subprogram (F) and 2 wrapped (P and G).
+--  For each of them, there's one match for the wrapped signature, 
+--  and one for the update call.
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 20 "strub" } }
+--  The 6 matches above, plus:
+--  5*2: wm var decl, enter, call, leave and clobber for each wrapper;
+--  2*1: an extra leave and clobber for the exception paths in the wrappers.
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 27 "strub" { xfail *-*-* } } }
+--  7*1: for the FP call in G, including EH path (currently missing the watermarking)
diff --git a/gcc/testsuite/gnat.dg/strub_ind.ads b/gcc/testsuite/gnat.dg/strub_ind.ads
new file mode 100644
index 00000000000..53dede60eac
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind.ads
@@ -0,0 +1,23 @@
+package Strub_Ind is
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "internal");
+
+   function F (X : Integer) return Integer;
+   pragma Machine_Attribute (F, "strub");
+
+   X : Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+
+   function G return Integer;
+
+   type FT is access function (X : Integer) return Integer;
+   pragma Machine_Attribute (FT, "strub", "at-calls");
+   --  The pragma above seems to get discarded in GNAT; Gigi doesn't see it.
+
+   FP : FT := F'Access;
+   pragma Machine_Attribute (FP, "strub", "at-calls");
+   --  The pragma above does modify FP's type,
+   --  but a call with it gets it converted to its Ada type,
+   --  that is cached by the translator as the unmodified type.
+
+end Strub_Ind;
diff --git a/libgcc/strub.c b/libgcc/strub.c
index fd6e27556e4..7c58deee1e6 100644
--- a/libgcc/strub.c
+++ b/libgcc/strub.c
@@ -36,7 +36,7 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 # define TOPS <
 #endif
 
-#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ ("callable")))
 
 /* Enter a stack scrubbing context, initializing the watermark to the caller's
    stack address.  */


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

* [gcc(refs/users/aoliva/heads/strub)] -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests
@ 2021-09-07 23:42 Alexandre Oliva
  0 siblings, 0 replies; 13+ messages in thread
From: Alexandre Oliva @ 2021-09-07 23:42 UTC (permalink / raw)
  To: gcc-cvs

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

commit c2421a5baab7fd8b06867ae3ddc03f1a252b8704
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Thu Sep 2 23:15:36 2021 -0300

    -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests

Diff:
---
 .../doc/gnat_rm/security_hardening_features.rst    |  50 +-
 gcc/ada/gcc-interface/utils.c                      | 111 +---
 gcc/attribs.c                                      |  39 +-
 gcc/c-family/c-attribs.c                           | 100 +---
 gcc/common.opt                                     |  27 +-
 gcc/doc/extend.texi                                | 332 +++++++++--
 gcc/doc/invoke.texi                                |  76 ++-
 gcc/ipa-inline.c                                   |   2 +-
 gcc/ipa-strub.c                                    | 652 ++++++++++++++++-----
 gcc/ipa-strub.h                                    |  20 +-
 gcc/multiple_target.c                              |   2 +-
 gcc/testsuite/c-c++-common/strub-O0.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O1.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2fni.c           |   2 +-
 gcc/testsuite/c-c++-common/strub-O3.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O3fni.c           |   2 +-
 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            |  12 +-
 gcc/testsuite/c-c++-common/strub-all2.c            |   6 +-
 gcc/testsuite/c-c++-common/strub-apply1.c          |   6 +-
 gcc/testsuite/c-c++-common/strub-apply2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-apply3.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-apply4.c          |   4 +-
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-at-calls2.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-default1.c        |  42 --
 gcc/testsuite/c-c++-common/strub-defer-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |  14 +-
 gcc/testsuite/c-c++-common/strub-defer-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-internal1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-internal2.c       |   6 +-
 gcc/testsuite/c-c++-common/strub-parms1.c          |  10 +-
 gcc/testsuite/c-c++-common/strub-parms2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-parms3.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-relaxed1.c        |  18 +
 gcc/testsuite/c-c++-common/strub-relaxed2.c        |  14 +
 gcc/testsuite/c-c++-common/strub-short-O0-exc.c    |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O0.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O3.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-strict1.c         |  48 ++
 .../{strub-default2.c => strub-strict2.c}          |  14 +-
 gcc/testsuite/c-c++-common/strub-tail-O1.c         |   2 +-
 gcc/testsuite/c-c++-common/strub-tail-O2.c         |   2 +-
 .../c-c++-common/torture/strub-callable1.c         |   2 +-
 .../c-c++-common/torture/strub-callable2.c         |  44 +-
 gcc/testsuite/c-c++-common/torture/strub-const1.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const2.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const3.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-const4.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-data1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data3.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data4.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |   8 +-
 .../c-c++-common/torture/strub-indcall1.c          |   2 +-
 .../c-c++-common/torture/strub-indcall2.c          |   2 +-
 .../c-c++-common/torture/strub-indcall3.c          |   2 +-
 .../c-c++-common/torture/strub-inlinable1.c        |  22 +
 .../c-c++-common/torture/strub-inlinable2.c        |   7 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |  10 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |  55 ++
 gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c  |  50 ++
 gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c  |  43 ++
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure3.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-pure4.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run1.c    |  10 +-
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run4.c    |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run4d.c   |   4 +-
 gcc/testsuite/g++.dg/torture/strub-init1.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init2.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init3.C         |   2 +-
 gcc/testsuite/gnat.dg/strub_attr.adb               |  22 +-
 gcc/testsuite/gnat.dg/strub_ind.adb                |  50 ++
 gcc/testsuite/gnat.dg/strub_ind.ads                |  23 +
 libgcc/strub.c                                     |   2 +-
 85 files changed, 1494 insertions(+), 611 deletions(-)

diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 309291f0341..75345290412 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -19,6 +19,18 @@ It can be enabled with the *-fzero-call-used-regs* command line
 option, to affect all subprograms in a compilation, and with a
 :samp:`Machine_Attribute` pragma, to affect only specific subprograms.
 
+.. code-block:: ada
+
+     procedure Foo;
+     pragma Machine_Attribute (Foo, "zero_call_used_regs", "used");
+     --  Before returning, Foo scrubs only call-clobbered registers
+     --  that it uses itself.
+
+     function Bar return Integer;
+     pragma Machine_Attribute (Bar, "zero_call_used_regs", "all");
+     --  Before returning, Bar scrubs all call-clobbered registers.
+
+
 For usage and more details on the command line option, and on the
 ``zero_call_used_regs`` attribute, see :title:`Using the GNU Compiler
 Collection (GCC)`.
@@ -31,13 +43,31 @@ Stack Scrubbing
 
 GNAT can generate code to zero-out stack frames used by subprograms.
 
-It can be activated with the *-fstrub* command line option, to affect
-all (viable) subprograms in a compilation, and with a
-:samp:`Machine_Attribute` pragma.
+It can be activated with the :samp:`Machine_Attribute` pragma, on
+specific subprograms, variables, and types.
 
-For usage and more details on the command line option, and on the
-``strub`` attribute, see :title:`Using the GNU Compiler Collection
-(GCC)`.
+.. code-block:: ada
+
+     function Foo returns Integer;
+     pragma Machine_Attribute (Foo, "strub");
+     --  Foo and its callers are modified so as to scrub the stack
+     --  space used by Foo after it returns.
+
+     procedure Bar;
+     pragma Machine_Attribute (Bar, "strub", "internal");
+     --  Bar is turned into a wrapper for its original body,
+     --  and they scrub the stack used by the original body.
+
+     Var : Integer;
+     pragma Machine_Attribute (Var, "strub");
+     --  Reading from Var in a subprogram enables stack scrubbing
+     --  of the stack space used by the subprogram.
+
+
+There are also *-fstrub* command line options to control default
+settings.  For usage and more details on the command line option, and
+on the ``strub`` attribute, see :title:`Using the GNU Compiler
+Collection (GCC)`.
 
 Note that Ada secondary stacks are not scrubbed.  The restriction
 ``No_Secondary_Stack`` avoids their use, and thus their accidental
@@ -49,4 +79,10 @@ is not fully reflected in Ada types, ``Access`` attributes, renaming
 and overriding.  Every access type, renaming, and overriding and
 overridden dispatching operations that may refer to an entity with an
 attribute-modified interface must be annotated with the same
-interface-modifying attribute.
+interface-modifying attribute, or with an interface-compatible one.
+
+Even then, applying the pragma to anything other than subprograms and
+scalar variables is not currently supported, and may be silently
+ignored.  Specifically, it is not recommended to rely on any effects
+of this pragma on access types and objects to data variables and to
+subprograms.
diff --git a/gcc/ada/gcc-interface/utils.c b/gcc/ada/gcc-interface/utils.c
index 6b20ddc93c9..193772cc829 100644
--- a/gcc/ada/gcc-interface/utils.c
+++ b/gcc/ada/gcc-interface/utils.c
@@ -39,6 +39,7 @@
 #include "varasm.h"
 #include "toplev.h"
 #include "opts.h"
+#include "ipa-strub.h"
 #include "output.h"
 #include "debug.h"
 #include "convert.h"
@@ -6611,115 +6612,57 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
-  tree orig_fnptr_type = NULL_TREE;
-  tree orig_args = args;
 
   if (args
       && POINTER_TYPE_P (*node)
       && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*node)))
-    {
-      orig_fnptr_type = *node;
-      *node = TREE_TYPE (orig_fnptr_type);
-    }
+    *node = TREE_TYPE (*node);
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      /* pragma Machine_Attribute turns string arguments into identifiers.
-	 Reverse it.  */
-      if (TREE_CODE (TREE_VALUE (args)) == IDENTIFIER_NODE)
-	{
-	  gcc_checking_assert (IDENTIFIER_POINTER (TREE_VALUE (args))
-			       [IDENTIFIER_LENGTH (TREE_VALUE (args))] == 0);
-	  TREE_VALUE (args) = build_string
-	    (IDENTIFIER_LENGTH (TREE_VALUE (args)) + 1,
-	     IDENTIFIER_POINTER (TREE_VALUE (args)));
-	}
-
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
 	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
-
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
-
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
 
   if (args)
-    warning (OPT_Wattributes,
-	     "ignoring excess %qE attribute arguments starting at %qE",
-	     name, TREE_VALUE (args));
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
 
   /* If we see a strub-enabling attribute, and we're at the default setting,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
-
-  if (!*no_add_attrs)
-    {
-      *no_add_attrs = true;
-      if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
-      TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
-					   TYPE_ATTRIBUTES (*node));
-
-      if (orig_fnptr_type)
-	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
-	  TREE_TYPE (fnptr_type) = *node;
-	  *node = fnptr_type;
-	}
-    }
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   return NULL_TREE;
 }
diff --git a/gcc/attribs.c b/gcc/attribs.c
index 0d22c20a35e..07f81285d7a 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "attribs.h"
 #include "fold-const.h"
+#include "ipa-strub.h"
 #include "stor-layout.h"
 #include "langhooks.h"
 #include "plugin.h"
@@ -617,12 +618,11 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  flags &= ~(int) ATTR_FLAG_TYPE_IN_PLACE;
 	}
 
-      if (spec->function_type_required && TREE_CODE (*anode) != FUNCTION_TYPE
-	  && TREE_CODE (*anode) != METHOD_TYPE)
+      if (spec->function_type_required
+	  && !FUNC_OR_METHOD_TYPE_P (*anode))
 	{
 	  if (TREE_CODE (*anode) == POINTER_TYPE
-	      && (TREE_CODE (TREE_TYPE (*anode)) == FUNCTION_TYPE
-		  || TREE_CODE (TREE_TYPE (*anode)) == METHOD_TYPE))
+	      && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
 	    {
 	      /* OK, this is a bit convoluted.  We can't just make a copy
 		 of the pointer type and modify its TREE_TYPE, because if
@@ -715,7 +715,23 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  tree ret = (spec->handler) (cur_and_last_decl, name, args,
 				      flags|cxx11_flag, &no_add_attrs);
 
-	  *anode = cur_and_last_decl[0];
+	  if (*anode != cur_and_last_decl[0])
+	    {
+	      /* Even if !spec->function_type_required, allow the attribute
+		 handler to request the attribute to be applied to the function
+		 type, rather than to the function pointer type, by setting
+		 cur_and_last_decl[0] to the function type.  */
+	      if (!fn_ptr_tmp
+		  && POINTER_TYPE_P (*anode)
+		  && TREE_TYPE (*anode) == cur_and_last_decl[0]
+		  && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
+		{
+		  fn_ptr_tmp = TREE_TYPE (*anode);
+		  fn_ptr_quals = TYPE_QUALS (*anode);
+		  anode = &fn_ptr_tmp;
+		}
+	      *anode = cur_and_last_decl[0];
+	    }
 	  if (ret == error_mark_node)
 	    {
 	      warning (OPT_Wattributes, "%qE attribute ignored", name);
@@ -1353,9 +1369,20 @@ comp_type_attributes (const_tree type1, const_tree type2)
   if ((lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type1)) != NULL)
       ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) != NULL))
     return 0;
+  int strub_ret = strub_comptypes (CONST_CAST_TREE (type1),
+				   CONST_CAST_TREE (type2));
+  if (strub_ret == 0)
+    return strub_ret;
   /* As some type combinations - like default calling-convention - might
      be compatible, we have to call the target hook to get the final result.  */
-  return targetm.comp_type_attributes (type1, type2);
+  int target_ret = targetm.comp_type_attributes (type1, type2);
+  if (target_ret == 0)
+    return target_ret;
+  if (strub_ret == 2 || target_ret == 2)
+    return 2;
+  if (strub_ret == 1 && target_ret == 1)
+    return 1;
+  gcc_unreachable ();
 }
 
 /* PREDICATE acts as a function of type:
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index dc4a4d4d200..0453ac563cc 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "common/common-target.h"
 #include "langhooks.h"
 #include "tree-inline.h"
+#include "ipa-strub.h"
 #include "toplev.h"
 #include "tree-iterator.h"
 #include "opts.h"
@@ -1306,104 +1307,57 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
-  tree orig_fnptr_type = NULL_TREE;
-  tree orig_args = args;
 
   if (args
       && POINTER_TYPE_P (*node)
       && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*node)))
-    {
-      orig_fnptr_type = *node;
-      *node = TREE_TYPE (orig_fnptr_type);
-    }
+    *node = TREE_TYPE (*node);
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
 	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
-
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
-
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
 
   if (args)
-    warning (OPT_Wattributes,
-	     "ignoring excess %qE attribute arguments starting at %qE",
-	     name, TREE_VALUE (args));
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
 
   /* If we see a strub-enabling attribute, and we're at the default setting,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
-
-  if (!*no_add_attrs)
-    {
-      *no_add_attrs = true;
-      if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
-      TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
-					   TYPE_ATTRIBUTES (*node));
-
-      if (orig_fnptr_type)
-	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
-	  TREE_TYPE (fnptr_type) = *node;
-	  *node = fnptr_type;
-	}
-    }
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   return NULL_TREE;
 }
diff --git a/gcc/common.opt b/gcc/common.opt
index 71e0c971344..ef018ebf735 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,13 +2687,22 @@ fstrict-overflow
 Common
 Treat signed overflow as undefined.  Negated as -fwrapv -fwrapv-pointer.
 
-; If any strub-enabling attribute is seen when the default value is
-; selected, it's bumped up to -1.  The scrub mode gate function will
-; then bump -2 to 0 if no strub-enabling attribute is seen.  This
-; minimizes the strub overhead.
-fstrub=default
-Common RejectNegative Var(flag_strub, -2) Init(-2)
-Enable stack scrub as requested through attributes.
+fstrub=disable
+Common RejectNegative Var(flag_strub, 0)
+Disable stack scrub entirely, disregarding strub attributes.
+
+fstrub=strict
+Common RejectNegative Var(flag_strub, -4)
+Enable stack scrub as per attributes, with strict call checking.
+
+; If any strub-enabling attribute is seen when the default or strict
+; initializer values are in effect, flag_strub is bumped up by 2.  The
+; scrub mode gate function will then bump these initializer values to
+; 0 if no strub-enabling attribute is seen.  This minimizes the strub
+; overhead.
+fstrub=relaxed
+Common RejectNegative Var(flag_strub, -3) Init(-3)
+Restore default strub mode: as per attributes, with relaxed checking.
 
 fstrub=all
 Common RejectNegative Var(flag_strub, 3)
@@ -2707,10 +2716,6 @@ fstrub=internal
 Common RejectNegative Var(flag_strub, 2)
 Enable internal stack scrubbing for all viable functions.
 
-fstrub=disable
-Common RejectNegative Var(flag_strub, 0)
-Disable stack scrub entirely, disregarding strub attributes.
-
 fsync-libcalls
 Common Var(flag_sync_libcalls) Init(1)
 Implement __atomic operations via libcalls to legacy __sync functions.
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 1fcf60a3875..d85e695d85c 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -77,7 +77,7 @@ extensions, accepted by GCC in C90 mode and in C++.
 * Function Names::      Printable strings which are the name of the current
                         function.
 * Return Address::      Getting the return or frame address of a function.
-* Stack Scrubbing::     Stack scrubbing interfaces.
+* Stack Scrubbing::     Stack scrubbing internal interfaces.
 * Vector Extensions::   Using vector instructions through built-in functions.
 * Offsetof::            Special syntax for implementing @code{offsetof}.
 * __sync Builtins::     Legacy built-in functions for atomic memory access.
@@ -8729,51 +8729,259 @@ pid_t wait (wait_status_ptr_t p)
 @item strub
 @cindex @code{strub} type attribute
 This attribute defines stack-scrubbing properties of functions and
-variables.  When applied to function types, it takes an optional
-argument.  When applied to a pointer-to-function type, it gets
-propagated to the function type if the optional argument is given.
-
-A function whose type is annotated with @code{at-calls} @code{strub}
-mode (@code{strub("at-calls")}, @code{strub(1)}, or @code{strub})
-undergoes interface changes.  Its callers are adjusted to match the
-changes, and to automatically scrub (overwrite with zeros) the stack
-space used by the function.
-
-A function whose type indicates @code{internal} @code{strub} mode
-(@code{strub("internal")} or @code{strub(2)}) retains an unmodified
-interface, but may be turned into a wrapper that calls the wrapped body
-using a custom interface.  The wrapper then scrubs the stack space used
-by the wrapped body.
-
-An automatically-allocated variable whose type carries the @code{strub}
-attribute causes the enclosing function to have @code{strub} enabled.
-
-A statically-allocated variable whose type carries the @code{strub}
-attribute causes functions that @emph{read} it with that type to have
-@code{strub} enabled.  Reading from data referenced by pointers to such
-types has the same effect.  Note: The attribute does not carry over from
-a composite type to the types of its components, so the intended effect
-may not be obtained with non-scalar types.
-
-A @code{strub} context is the body of a function that has strub enabled,
-be it explicitly, by @code{at-calls} or @code{internal} mode, or
-implicitly, by reading from a @code{strub}-marked data type.
+variables.  Being a type attribute, it attaches to types, even when
+specified in function and variable declarations.  When applied to
+function types, it takes an optional string argument.  When applied to a
+pointer-to-function type, if the optional argument is given, it gets
+propagated to the function type.
+
+@smallexample
+/* A strub variable.  */
+int __attribute__ ((strub)) var;
+/* A strub variable that happens to be a pointer.  */
+__attribute__ ((strub)) int *strub_ptr_to_int;
+/* A pointer type that may point to a strub variable.  */
+typedef int __attribute__ ((strub)) *ptr_to_strub_int_type;
+
+/* A declaration of a strub function.  */
+extern int __attribute__ ((strub)) foo (void);
+/* A pointer to that strub function.  */
+int __attribute__ ((strub ("at-calls"))) (*ptr_to_strub_fn)(void) = foo;
+@end smallexample
+
+A function associated with @code{at-calls} @code{strub} mode
+(@code{strub("at-calls")}, or just @code{strub}) undergoes interface
+changes.  Its callers are adjusted to match the changes, and to scrub
+(overwrite with zeros) the stack space used by the called function after
+it returns.  The interface change makes the function type incompatible
+with an unadorned but otherwise equivalent type, so @emph{every}
+declaration and every type that may be used to call the function must be
+associated with this strub mode.
+
+A function associated with @code{internal} @code{strub} mode
+(@code{strub("internal")}) retains an unmodified, type-compatible
+interface, but it may be turned into a wrapper that calls the wrapped
+body using a custom interface.  The wrapper then scrubs the stack space
+used by the wrapped body.  Though the wrapped body has its stack space
+scrubbed, the wrapper does not, so arguments and return values may
+remain unscrubbed even when such a function is called by another
+function that enables @code{strub}.  This is why, when compiling with
+@option{-fstrub=strict}, a @code{strub} context is not allowed to call
+@code{internal} @code{strub} functions.
+
+@smallexample
+/* A declaration of an internal-strub function.  */
+extern int __attribute__ ((strub ("internal"))) bar (void);
+
+int __attribute__ ((strub))
+baz (void)
+@{
+  /* Ok, foo was declared above as an at-calls strub function.  */
+  foo ();
+  /* Not allowed in strict mode, otherwise allowed.  */
+  bar ();
+@}
+@end smallexample
+
+An automatically-allocated variable associated with the @code{strub}
+attribute causes the (immediately) enclosing function to have
+@code{strub} enabled.
+
+A statically-allocated variable associated with the @code{strub}
+attribute causes functions that @emph{read} it, through its @code{strub}
+data type, to have @code{strub} enabled.  Reading data by dereferencing
+a pointer to a @code{strub} data type has the same effect.  Note: The
+attribute does not carry over from a composite type to the types of its
+components, so the intended effect may not be obtained with non-scalar
+types.
+
+When selecting a @code{strub}-enabled mode for a function that is not
+explicitly associated with one, because of @code{strub} variables or
+data pointers, the function must satisfy @code{internal} mode viability
+requirements (see below), even when @code{at-calls} mode is also viable
+and, being more efficient, ends up selected as an optimization.
+
+@smallexample
+/* zapme is implicitly strub-enabled because of strub variables.
+   Optimization may change its strub mode, but not the requirements.  */
+static int
+zapme (int i)
+@{
+  /* A local strub variable enables strub.  */
+  int __attribute__ ((strub)) lvar;
+  /* Reading strub data through a pointer-to-strub enables strub.  */
+  lvar = * (ptr_to_strub_int_type) &i;
+  /* Writing to a global strub variable does not enable strub.  */
+  var = lvar;
+  /* Reading from a global strub variable enables strub.  */
+  return var;
+@}
+@end smallexample
+
+A @code{strub} context is the body (as opposed to the interface) of a
+function that has @code{strub} enabled, be it explicitly, by
+@code{at-calls} or @code{internal} mode, or implicitly, due to
+@code{strub} variables or command-line options.
 
 A function of a type associated with the @code{disabled} @code{strub}
-mode (@code{strub("disabled")}, @code{strub(0)}, or no @code{strub} mode
-specified) will not have its own stack space scrubbed, and it cannot be
-called from @code{strub} contexts.
+mode (@code{strub("disabled")} will not have its own stack space
+scrubbed.  Such functions @emph{cannot} be called from within
+@code{strub} contexts.
+
+In order to enable a function to be called from within @code{strub}
+contexts without having its stack space scrubbed, associate it with the
+@code{callable} @code{strub} mode (@code{strub("callable")}).
+
+When a function is not assigned a @code{strub} mode, explicitly or
+implicitly, the mode defaults to @code{callable}, except when compiling
+with @option{-fstrub=strict}, that causes @code{strub} mode to default
+to @code{disabled}.
+
+@example
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+ /* Implicitly disabled with -fstrub=strict, otherwise callable.  */
+extern int bah (void);
+
+int __attribute__ ((strub))
+bal (void)
+@{
+  /* Not allowed, bad is not strub-callable.  */
+  bad ();
+  /* Ok, bac is strub-callable.  */
+  bac ();
+  /* Not allowed with -fstrub=strict, otherwise allowed.  */
+  bah ();
+@}
+@end example
 
-A function that does not have @code{strub} enabled can only be called
-from within @code{strub} contexts through a function type marked with
-the @code{callable} @code{strub} mode (@code{strub("callable")} or
-@code{strub(3)}).
+Function types marked @code{callable} and @code{disabled} are not
+mutually compatible types, but the underlying interfaces are compatible,
+so it is safe to convert pointers between them, and to use such pointers
+or alternate declarations to call them.  Interfaces are also
+interchangeable between them and @code{internal} (but not
+@code{at-calls}!), but adding @code{internal} to a pointer type will not
+cause the pointed-to function to perform stack scrubbing.
+
+@example
+void __attribute__ ((strub))
+bap (void)
+@{
+  /* Assign a callable function to pointer-to-disabled.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bac;
+  /* Not allowed: calls disabled type in a strub context.  */
+  d_p ();
+
+  /* Assign a disabled function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bad;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an internal function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  c_p = bar;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an at-calls function to pointer-to-callable.
+     Flaggged as incompatible.  */
+  c_p = bal;
+  /* The call through an interface-incompatible type will not use the
+     modified interface expected by the at-calls function, so it is
+     likely to misbehave at runtime.  */
+  c_p ();
+@}
+@end example
 
 @code{Strub} contexts are never inlined into non-@code{strub} contexts.
-When an @code{internal}-strub function is split, the wrapper can often
-be inlined, but the wrapped body never is.  Functions marked as
-@code{always_inline}, even if explicitly assigned @code{internal} strub
-mode, will not undergo wrapping, so their body gets inlined.
+When an @code{internal}-strub function is split up, the wrapper can
+often be inlined, but the wrapped body @emph{never} is.  A function
+marked as @code{always_inline}, even if explicitly assigned
+@code{internal} strub mode, will not undergo wrapping, so its body gets
+inlined as required.
+
+@example
+inline int __attribute__ ((strub ("at-calls")))
+inl_atc (void)
+@{
+  /* This body may get inlined into strub contexts.  */
+@}
+
+inline int __attribute__ ((strub ("internal")))
+inl_int (void)
+@{
+  /* This body NEVER gets inlined, though its wrapper may.  */
+@}
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+@{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+@}
+
+void __attribute__ ((strub ("disabled")))
+bat (void)
+@{
+  /* Not allowed, cannot inline into a non-strub context.  */
+  inl_int_ali ();
+@}
+@end example
+
+@cindex strub eligibility and viability
+Some @option{-fstrub=*} command line options enable @code{strub} modes
+implicitly where viable.  A @code{strub} mode is only viable for a
+function if the function is eligible for that mode, and if other
+conditions, detailed below, are satisfied.  If it's not eligible for a
+mode, attempts to explicitly associate it with that mode are rejected
+with an error message.  If it is eligible, that mode may be assigned
+explicitly through this attribute, but implicit assignment through
+command-line options may involve additional viability requirements.
+
+A function is ineligible for @code{at-calls} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+@code{At-calls} @code{strub} mode, if not requested through the function
+type, is only viable for an eligible function if the function is not
+visible to other translation units, and if it doesn't have its address
+taken.
+
+@smallexample
+/* bar is eligible for at-calls strub mode,
+   but not viable for that mode because it is visible to other units.
+   It is eligible and viable for internal strub mode.  */
+void bav () @{@}
+
+/* setp is eligible for at-calls strub mode,
+   but not viable for that mode because its address is taken.
+   It is eligible and viable for internal strub mode.  */
+void setp (void) @{ static void (*p)(void); = setp; @}
+@end smallexample
+
+A function is ineligible for @code{internal} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, or if attribute
+@code{noipa} is present.  For an @code{always_inline} function, meeting
+these requirements is enough to make it eligible.  Any function that has
+attribute @code{noclone}, that uses such extensions as non-local labels,
+computed gotos, alternate variable argument passing interfaces,
+@code{__builtin_next_arg}, or @code{__builtin_return_address}, or that
+takes too many (about 64Ki) arguments is ineligible, unless it is
+@code{always_inline}.  For @code{internal} @code{strub} mode, all
+eligible functions are viable.
+
+@smallexample
+/* flop is not eligible, thus not viable, for at-calls strub mode.
+   Likewise for internal strub mode.  */
+__attribute__ ((noipa)) void flop (void) @{@}
+
+/* flip is eligible and viable for at-calls strub mode.
+   It would be ineligible for internal strub mode, because of noclone,
+   if it weren't for always_inline.  With always_inline, noclone is not
+   an obstacle, so it is also eligible and viable for internal strub mode.  */
+inline __attribute__ ((noclone, always_inline)) void flip (void) @{@}
+@end smallexample
 
 @item unused
 @cindex @code{unused} type attribute
@@ -11804,40 +12012,48 @@ This function returns the value of the stack pointer register.
 @end deftypefn
 
 @node Stack Scrubbing
-@section Stack scrubbing interfaces
+@section Stack scrubbing internal interfaces
 
 Stack scrubbing involves cooperation between a @code{strub} context,
 i.e., a function whose stack frame is to be zeroed-out, and its callers.
 The caller initializes a stack watermark, the @code{strub} context
-updates the watermark to reflect its stack use, and the caller zeroes it
-out once it regains control.
-
-Each of these steps relies on a different builtin function call.  The
-functions are available in libgcc but, depending on optimization levels,
-they are expanded internally, adjusted to account for inlining, or
-combined/deferred (e.g. passing the caller-supplied watermark on to
-callees, refraining from erasing stack areas that the caller will).
+updates the watermark according to its stack use, and the caller zeroes
+it out once it regains control, whether by the callee's returning or by
+an exception.
+
+Each of these steps is performed by a different builtin function call.
+Calls to these builtins are introduced automatically, in response to
+@code{strub} attributes and command-line options; they are not expected
+to be explicitly called by source code.
+
+The functions that implement the builtins are available in libgcc but,
+depending on optimization levels, they are expanded internally, adjusted
+to account for inlining, and sometimes combined/deferred (e.g. passing
+the caller-supplied watermark on to callees, refraining from erasing
+stack areas that the caller will) to enable tail calls and to optimize
+for code size.
 
 @deftypefn {Built-in Function} {void} __builtin___strub_enter (void **@var{wmptr})
 This function initializes a stack @var{watermark} variable with the
-current top of the stack.  This builtin function should be called before
-entering a @code{strub} context.  It remains as a function call if optimization
-is not enabled.
+current top of the stack.  A call to this builtin function is introduced
+before entering a @code{strub} context.  It remains as a function call
+if optimization is not enabled.
 @end deftypefn
 
 @deftypefn {Built-in Function} {void} __builtin___strub_update (void **@var{wmptr})
 This function updates a stack @var{watermark} variable with the current
-top of the stack, if it tops the previous watermark.  This builtin
-function should be called within a @code{strub} context whenever
+top of the stack, if it tops the previous watermark.  A call to this
+builtin function is inserted within @code{strub} contexts, whenever
 additional stack space may have been used.  It remains as a function
 call at optimization levels lower than 2.
 @end deftypefn
 
 @deftypefn {Built-in Function} {void} __builtin___strub_leave (void **@var{wmptr})
 This function overwrites the memory area between the current top of the
-stack, and the @var{watermark}ed address.  This builtin function should
-be called after leaving a @code{strub} context.  It remains as a
-function call at optimization levels lower than 3.
+stack, and the @var{watermark}ed address.  A call to this builtin
+function is inserted after leaving a @code{strub} context.  It remains
+as a function call at optimization levels lower than 3, and it is guarded by
+a condition at level 2.
 @end deftypefn
 
 @node Vector Extensions
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 838d9ff1198..afcb37914ac 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,7 +599,8 @@ Objective-C and Objective-C++ Dialects}.
 -fstack-protector-explicit  -fstack-check @gol
 -fstack-limit-register=@var{reg}  -fstack-limit-symbol=@var{sym} @gol
 -fno-stack-limit  -fsplit-stack @gol
--fstrub=default -fstrub=disable -fstrub=at-calls -fstrub=internal -fstrub=all @gol
+-fstrub=disable -fstrub=strict -fstrub=relaxed @gol
+-fstrub=all -fstrub=at-calls -fstrub=internal @gol
 -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
 -fvtv-counts  -fvtv-debug @gol
 -finstrument-functions @gol
@@ -15513,46 +15514,55 @@ without @option{-fsplit-stack} always has a large stack.  Support for
 this is implemented in the gold linker in GNU binutils release 2.21
 and later.
 
-@item -fstrub=default
-@opindex fstrub=default
-Restore the default stack scrub (@code{strub}) setting, namely,
-@code{strub} is only enabled as required by @code{strub} attributes
-associated with function or variable types.  This is only useful to
-override earlier @samp{-fstrub=*} options.
-
 @item -fstrub=disable
 @opindex -fstrub=disable
 Disable stack scrubbing entirely, ignoring any @code{strub} attributes.
+See @xref{Common Type Attributes}.
+
+@item -fstrub=strict
+@opindex fstrub=strict
+Functions default to @code{strub} mode @code{disabled}, and apply
+@option{strict}ly the restriction that only functions associated with
+@code{strub}-@code{callable} modes (@code{at-calls}, @code{callable} and
+@code{always_inline} @code{internal}) are @code{callable} by functions
+with @code{strub}-enabled modes (@code{at-calls} and @code{internal}).
+
+@item -fstrub=relaxed
+@opindex fstrub=relaxed
+Restore the default stack scrub (@code{strub}) setting, namely,
+@code{strub} is only enabled as required by @code{strub} attributes
+associated with function and data types.  @code{Relaxed} means that
+strub contexts are only prevented from calling functions explicitly
+associated with @code{strub} mode @code{disabled}.  This option is only
+useful to override other @option{-fstrub=*} options that precede it in
+the command line.
 
 @item -fstrub=at-calls
 @opindex fstrub=at-calls
-Enable @code{at-calls} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{at-calls} @code{strub} if a different @code{strub}
-mode is explicitly requested, if attribute @code{noipa} is present, or
-if it calls @code{__builtin_apply_args}.  @code{At-calls} @code{strub}
-mode, if not requested through the function type, is only viable for an
-eligible function if the function is not visible to other translation
-units, and it doesn't have its address taken.
+Enable @code{at-calls} @code{strub} mode where viable.  The primary use
+of this option is for testing.  It exercises the @code{strub} machinery
+in scenarios strictly local to a translation unit.  This @code{strub}
+mode modifies function interfaces, so any function that is visible to
+other translation units, or that has its address taken, will @emph{not}
+be affected by this option.  Optimization options may also affect
+viability.  See the @code{strub} attribute documentation for details on
+viability and eligibility requirements.
 
 @item -fstrub=internal
 @opindex fstrub=internal
-Enable @code{internal} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{internal} @code{strub} if a different @code{strub}
-mode is explicitly requested, or if attribute @code{noipa} is present.
-Non-@code{always_inline} functions also become ineligible if attribute
-@code{noclone} is present, if the function uses such features as user
-labels, non-default variable argument interfaces,
-@code{__builtin_next_arg}, or @code{__builtin_return_address}, or if
-they have too many (about 64Ki) arguments.  For @code{internal}
-@code{strub}, all eligible functions are viable.
+Enable @code{internal} @code{strub} mode where viable.  The primary use
+of this option is for testing.  This option is intended to exercise
+thoroughly parts of the @code{strub} machinery that implement the less
+efficient, but interface-preserving @code{strub} mode.  Functions that
+would not be affected by this option are quite uncommon.
 
 @item -fstrub=all
 @opindex fstrub=all
-Enable @code{strub} for all viable functions, and consider non-viable
-functions as @code{callable}.  When both strub modes are viable,
-@code{at-calls} is preferred.
+Enable some @code{strub} mode where viable.  When both strub modes are
+viable, @code{at-calls} is preferred.  @option{-fdump-ipa-strubm} adds
+function attributes that tell which mode was selected for each function.
+The primary use of this option is for testing, to exercise thoroughly
+the @code{strub} machinery.
 
 @item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 @opindex fvtable-verify
@@ -17427,6 +17437,14 @@ and inlining decisions.
 @item inline
 Dump after function inlining.
 
+@item strubm
+Dump after selecting @code{strub} modes, and recording the selections as
+function attributes.
+
+@item strub
+Dump @code{strub} transformations: interface changes, function wrapping,
+and insertion of builtin calls for stack scrubbing and watermarking.
+
 @end table
 
 Additionally, the options @option{-optimized}, @option{-missed},
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 7f4bc44d2bb..932e49dba8e 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -397,7 +397,7 @@ can_inline_edge_p (struct cgraph_edge *e, bool report,
       e->inline_failed = CIF_SANITIZE_ATTRIBUTE_MISMATCH;
       inlinable = false;
     }
-  if (!strub_inlinable_p (callee, caller))
+  if (!strub_inlinable_to_p (callee, caller))
     {
       e->inline_failed = CIF_UNSPECIFIED;
       inlinable = false;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
index cf7d811779e..159cb4df2f2 100644
--- a/gcc/ipa-strub.c
+++ b/gcc/ipa-strub.c
@@ -159,12 +159,16 @@ enum strub_mode {
 
 };
 
+/* Look up a strub attribute in TYPE, and return it.  */
+
 static tree
 get_strub_attr_from_type (tree type)
 {
   return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
 }
 
+/* Look up a strub attribute in DECL or in its type, and return it.  */
+
 static tree
 get_strub_attr_from_decl (tree decl)
 {
@@ -174,23 +178,161 @@ get_strub_attr_from_decl (tree decl)
   return get_strub_attr_from_type (TREE_TYPE (decl));
 }
 
-tree
+/* Define a function to cache identifier ID, to be used as a strub attribute
+   parameter for a strub mode named after NAME.  */
+#define DEF_STRUB_IDS(NAME, ID)				\
+static inline tree get_strub_mode_id_ ## NAME () {	\
+  static tree identifier = NULL_TREE;			\
+  if (!identifier)					\
+    identifier = get_identifier (ID);			\
+  return identifier;					\
+}
+/* Same as DEF_STRUB_IDS, but use the string expansion of NAME as ID.  */
+#define DEF_STRUB_ID(NAME)				\
+DEF_STRUB_IDS (NAME, #NAME)
+
+/* Define functions for each of the strub mode identifiers.
+   Expose dashes rather than underscores.  */
+DEF_STRUB_ID (disabled)
+DEF_STRUB_IDS (at_calls, "at-calls")
+DEF_STRUB_ID (internal)
+DEF_STRUB_ID (callable)
+DEF_STRUB_ID (wrapped)
+DEF_STRUB_ID (wrapper)
+DEF_STRUB_ID (inlinable)
+DEF_STRUB_IDS (at_calls_opt, "at-calls-opt")
+
+/* Release the temporary macro names.  */
+#undef DEF_STRUB_IDS
+#undef DEF_STRUB_ID
+
+/* Return the identifier corresponding to strub MODE.  */
+
+static tree
+get_strub_mode_attr_parm (enum strub_mode mode)
+{
+  switch (mode)
+    {
+    case STRUB_DISABLED:
+      return get_strub_mode_id_disabled ();
+
+    case STRUB_AT_CALLS:
+      return get_strub_mode_id_at_calls ();
+
+    case STRUB_INTERNAL:
+      return get_strub_mode_id_internal ();
+
+    case STRUB_CALLABLE:
+      return get_strub_mode_id_callable ();
+
+    case STRUB_WRAPPED:
+      return get_strub_mode_id_wrapped ();
+
+    case STRUB_WRAPPER:
+      return get_strub_mode_id_wrapper ();
+
+    case STRUB_INLINABLE:
+      return get_strub_mode_id_inlinable ();
+
+    case STRUB_AT_CALLS_OPT:
+      return get_strub_mode_id_at_calls_opt ();
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+/* Return the parmeters (TREE_VALUE) for a strub attribute of MODE.
+   We know we use a single parameter, so we bypass the creation of a
+   tree list.  */
+
+static tree
 get_strub_mode_attr_value (enum strub_mode mode)
 {
-  return tree_cons (NULL_TREE,
-		    build_int_cst (integer_type_node, (int)mode),
-		    NULL_TREE);
+  return get_strub_mode_attr_parm (mode);
+}
+
+/* Determine whether ID is a well-formed strub mode-specifying attribute
+   parameter for a function (type).  Only user-visible modes are accepted, and
+   ID must be non-NULL.
+
+   For unacceptable parms, return 0, otherwise a nonzero value as below.
 
-#if 0 /* ??? use symbolic mode names with interned strings?  */
-  char *s = NULL;
+   If the parm enables strub, return positive, otherwise negative.
 
-  switch (strub_mode)
+   If the affected type must be a distinct, incompatible type,return an integer
+   of absolute value 2, otherwise 1.  */
+
+int
+strub_validate_fn_attr_parm (tree id)
+{
+  int ret;
+  const char *s = NULL;
+  size_t len = 0;
+
+  /* do NOT test for NULL.  This is only to be called with non-NULL arguments.
+     We assume that the strub parameter applies to a function, because only
+     functions accept an explicit argument.  If we accepted NULL, and we
+     happened to be called to verify the argument for a variable, our return
+     values would be wrong.  */
+  if (TREE_CODE (id) == STRING_CST)
     {
-      
+      s = TREE_STRING_POINTER (id);
+      len = TREE_STRING_LENGTH (id) - 1;
     }
-#endif
+  else if (TREE_CODE (id) == IDENTIFIER_NODE)
+    {
+      s = IDENTIFIER_POINTER (id);
+      len = IDENTIFIER_LENGTH (id);
+    }
+  else
+    return 0;
+
+  enum strub_mode mode;
+
+  if (len != 8)
+    return 0;
+
+  switch (s[0])
+    {
+    case 'd':
+      mode = STRUB_DISABLED;
+      ret = -1;
+      break;
+
+    case 'a':
+      mode = STRUB_AT_CALLS;
+      ret = 2;
+      break;
+
+    case 'i':
+      mode = STRUB_INTERNAL;
+      ret = 1;
+      break;
+
+    case 'c':
+      mode = STRUB_CALLABLE;
+      ret = -2;
+      break;
+
+    default:
+      /* Other parms are for internal use only.  */
+      return 0;
+    }
+
+  tree mode_id = get_strub_mode_attr_parm (mode);
+
+  if (TREE_CODE (id) == IDENTIFIER_NODE
+      ? id != mode_id
+      : strncmp (s, IDENTIFIER_POINTER (mode_id), len) != 0)
+    return 0;
+
+  return ret;
 }
 
+/* Return the strub mode from STRUB_ATTR.  VAR_P should be true if the attribute
+   is taken from a variable, rather than from a function, or a type thereof.  */
+
 static enum strub_mode
 get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
 {
@@ -200,28 +342,102 @@ get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
     {
       if (!TREE_VALUE (strub_attr))
 	mode = !var_p ? STRUB_AT_CALLS : STRUB_INTERNAL;
-      else if (TREE_CODE (TREE_VALUE (TREE_VALUE (strub_attr))) == INTEGER_CST)
-	mode = (enum strub_mode) tree_to_shwi (TREE_VALUE
-					       (TREE_VALUE (strub_attr)));
-      else /* Handlers convert symbolic mode names to INTEGER_CST.  */
-	gcc_unreachable ();
+      else
+	{
+	  gcc_checking_assert (!var_p);
+	  tree id = TREE_VALUE (strub_attr);
+	  if (TREE_CODE (id) == TREE_LIST)
+	    id = TREE_VALUE (id);
+	  const char *s = (TREE_CODE (id) == STRING_CST
+			   ? TREE_STRING_POINTER (id)
+			   : IDENTIFIER_POINTER (id));
+	  size_t len = (TREE_CODE (id) == STRING_CST
+			? TREE_STRING_LENGTH (id) - 1
+			: IDENTIFIER_LENGTH (id));
+
+	  switch (len)
+	    {
+	    case 7:
+	      switch (s[6])
+		{
+		case 'r':
+		  mode = STRUB_WRAPPER;
+		  break;
+
+		case 'd':
+		  mode = STRUB_WRAPPED;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 8:
+	      switch (s[0])
+		{
+		case 'd':
+		  mode = STRUB_DISABLED;
+		  break;
+
+		case 'a':
+		  mode = STRUB_AT_CALLS;
+		  break;
+
+		case 'i':
+		  mode = STRUB_INTERNAL;
+		  break;
+
+		case 'c':
+		  mode = STRUB_CALLABLE;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 9:
+	      mode = STRUB_INLINABLE;
+	      break;
+
+	    case 12:
+	      mode = STRUB_AT_CALLS_OPT;
+	      break;
+
+	    default:
+	      gcc_unreachable ();
+	    }
+
+	  gcc_checking_assert (TREE_CODE (id) == IDENTIFIER_NODE
+			       ? id == get_strub_mode_attr_parm (mode)
+			       : strncmp (IDENTIFIER_POINTER
+					  (get_strub_mode_attr_parm (mode)),
+					  s, len) == 0);
+	}
     }
 
   return mode;
 }
 
+/* Look up, decode and return the strub mode associated with FNDECL.  */
+
 static enum strub_mode
-get_strub_mode_from_decl (tree fndecl)
+get_strub_mode_from_fndecl (tree fndecl)
 {
   return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
 }
 
+/* Look up, decode and return the strub mode associated with NODE.  */
+
 static enum strub_mode
 get_strub_mode (cgraph_node *node)
 {
-  return get_strub_mode_from_decl (node->decl);
+  return get_strub_mode_from_fndecl (node->decl);
 }
 
+/* Look up, decode and return the strub mode associated with TYPE.  */
+
 static enum strub_mode
 get_strub_mode_from_type (tree type)
 {
@@ -231,12 +447,15 @@ get_strub_mode_from_type (tree type)
   if (attr)
     return get_strub_mode_from_attr (attr, var_p);
 
-  if (flag_strub > 0 && !var_p)
+  if (flag_strub >= -1 && !var_p)
     return STRUB_CALLABLE;
 
   return STRUB_DISABLED;
 }
 
+\f
+/* Return true iff NODE calls builtin va_start.  */
+
 static bool
 calls_builtin_va_start_p (cgraph_node *node)
 {
@@ -252,6 +471,8 @@ calls_builtin_va_start_p (cgraph_node *node)
   return result;
 }
 
+/* Return true iff NODE calls builtin apply_args, and optionally REPORT it.  */
+
 static bool
 calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
 {
@@ -276,12 +497,17 @@ calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE carries the always_inline attribute.  */
+
 static inline bool
 strub_always_inline_p (cgraph_node *node)
 {
   return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
 }
 
+/* Return true iff NODE is potentially eligible for any strub-enabled mode, and
+   optionally REPORT the reasons for ineligibility.  */
+
 static inline bool
 can_strub_p (cgraph_node *node, bool report = false)
 {
@@ -321,6 +547,10 @@ can_strub_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE is eligible for at-calls strub, and optionally REPORT
+   the reasons for ineligibility.  Besides general non-eligibility for
+   strub-enabled modes, at-calls rules out calling builtin apply_args.  */
+
 static bool
 can_strub_at_calls_p (cgraph_node *node, bool report = false)
 {
@@ -332,6 +562,9 @@ can_strub_at_calls_p (cgraph_node *node, bool report = false)
   return !calls_builtin_apply_args_p (node, report);
 }
 
+/* Symbolic macro for the max number of arguments that internal strub may add to
+   a function.  */
+
 #define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
 
 /* We can't perform internal strubbing if the function body involves certain
@@ -438,14 +671,12 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 			   || tree_versionable_function_p (node->decl));
 
 
-      /* Label values referenced are not preserved when copying.  If referenced
+      /* Label values references are not preserved when copying.  If referenced
 	 in nested functions, as in 920415-1.c and 920721-4.c their decls get
-	 remapped independently.  That might be too broad, in that we might be
-	 able to support correctly cases in which the labels are only used
-	 internally in a function, but disconnecting user labels from their
-	 original declarations is undesirable in general, and it probably
-	 doesn't matter, since explicitly-requested strub likely uses
-	 STRUB_AT_CALLS mode anyway.  */
+	 remapped independently.  The exclusion below might be too broad, in
+	 that we might be able to support correctly cases in which the labels
+	 are only used internally in a function, but disconnecting forced labels
+	 from their original declarations is undesirable in general.  */
       basic_block bb;
       FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
 	for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -459,8 +690,6 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 
 	    target = gimple_label_label (label_stmt);
 
-	    /* Make an edge to every label block that has been marked as a
-	       potential target for a computed goto or a non-local goto.  */
 	    if (!FORCED_LABEL (target))
 	      continue;
 
@@ -470,7 +699,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 forced labels");
 	  }
     }
 
@@ -491,6 +720,9 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return TRUE iff NODE has any strub-requiring local variable, or accesses (as
+   in reading) any variable through a strub-requiring type.  */
+
 static bool
 strub_from_body_p (cgraph_node *node)
 {
@@ -506,7 +738,9 @@ strub_from_body_p (cgraph_node *node)
 	!= STRUB_DISABLED)
       return true;
 
-  /* Now scan the body for loads with strub types.  */
+  /* Now scan the body for loads with strub-requiring types.
+     ??? Compound types don't propagate the strub requirement to
+     component types.  */
   basic_block bb;
   FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
     for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -528,6 +762,7 @@ strub_from_body_p (cgraph_node *node)
 
 /* Return true iff node is associated with a builtin that should be callable
    from strub contexts.  */
+
 static inline bool
 strub_callable_builtin_p (cgraph_node *node)
 {
@@ -568,17 +803,23 @@ strub_callable_builtin_p (cgraph_node *node)
     }
 }
 
+/* Compute the strub mode to be used for NODE.  STRUB_ATTR should be the strub
+   attribute,found for NODE, if any.  */
+
 static enum strub_mode
 compute_strub_mode (cgraph_node *node, tree strub_attr)
 {
   enum strub_mode req_mode = get_strub_mode_from_attr (strub_attr);
 
-  gcc_checking_assert (flag_strub >= -1 && flag_strub <= 3);
+  gcc_checking_assert (flag_strub >= -2 && flag_strub <= 3);
 
   /* Symbolic encodings of the -fstrub-* flags.  */
   /* Enable strub when explicitly requested through attributes to functions or
      variables, reporting errors if the requests cannot be satisfied.  */
   const bool strub_flag_auto = flag_strub < 0;
+  /* strub_flag_auto with strub call verification; without this, functions are
+     implicitly callable.  */
+  const bool strub_flag_strict = flag_strub < -1;
   /* Disable strub altogether, ignore attributes entirely.  */
   const bool strub_flag_disabled = flag_strub == 0;
   /* On top of _auto, also enable strub implicitly for functions that can
@@ -625,7 +866,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (!strub_flag_disabled
        && (strub_attr
 	   ? req_mode == STRUB_CALLABLE
-	   : (strub_flag_viable
+	   : (!strub_flag_strict
 	      || strub_callable_builtin_p (node))));
 
   /* This is a shorthand for either strub-enabled mode.  */
@@ -674,19 +915,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (at_calls_eligible
        && (strub_attr
 	   || (node->has_gimple_body_p ()
-#if 0 /* We no longer use collect_callers, so we can probably drop it.  */
-	       && node->get_availability () > AVAIL_INTERPOSABLE
-#endif
-	       && ((!node->externally_visible
-#if 0
-		    /* We wish to bypass the test below for functions that are
-		       not externally visible, but that's a little too broad: we
-		       do not wish to skip them for e.g. gnu_inline
-		       functions.  */
-		    && !TREE_PUBLIC (node->decl)
-		    && !DECL_EXTERNAL (node->decl)
-#endif
-		    )
+	       && (!node->externally_visible
 		   || (node->binds_to_current_def_p ()
 		       && node->can_be_local_p ()))
 	       && node->only_called_directly_p ())));
@@ -737,10 +966,6 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
   const enum strub_mode mode
     = ((strub_enable && is_always_inline)
        ? (strub_required ? STRUB_INLINABLE : STRUB_CALLABLE)
-#if 0
-       : (!strub_enable && strub_required && strub_attr)
-       ? req_mode
-#endif
        : (strub_enable && internal_viable
 	  && (strub_flag_internal || !at_calls_viable))
        ? STRUB_INTERNAL
@@ -802,6 +1027,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 /* Set FNDT's strub mode to MODE; FNDT may be a function decl or
    function type.  If OVERRIDE, do not check whether a mode is already
    set.  */
+
 static void
 strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
 {
@@ -828,12 +1054,18 @@ strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
   *attrp = attr;
 }
 
+/* Set FNDT's strub mode to callable.
+   FNDT may be a function decl or a function type.  */
+
 void
 strub_make_callable (tree fndt)
 {
   strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
 }
 
+/* Set NODE to strub MODE.  Report incompatibilities between MODE and the mode
+   requested through explicit attributes, and cases of non-eligibility.  */
+
 static void
 set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
 {
@@ -853,9 +1085,10 @@ 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",
-		    (int) mode, node->decl,
-		    (int) get_strub_mode_from_attr (attr));
+		    "%<strub%> mode %qE selected for %qD, when %qE was requested",
+		    get_strub_mode_attr_parm (mode),
+		    node->decl,
+		    get_strub_mode_attr_parm (req_mode));
 	  if (node->alias)
 	    {
 	      cgraph_node *target = node->ultimate_alias_target ();
@@ -906,6 +1139,8 @@ set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
   strub_set_fndt_mode_to (node->decl, mode, attr);
 }
 
+/* Compute and set NODE's strub mode.  */
+
 static void
 set_strub_mode (cgraph_node *node)
 {
@@ -946,6 +1181,7 @@ set_strub_mode (cgraph_node *node)
   set_strub_mode_to (node, mode);
 }
 
+\f
 /* Non-strub functions shouldn't be called from within strub contexts,
    except through callable ones.  Always inline strub functions can
    only be called from strub functions.  */
@@ -984,7 +1220,7 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_AT_CALLS_OPT:
     case STRUB_INTERNAL:
     case STRUB_WRAPPER:
-      return (flag_strub >= 0);
+      return (flag_strub >= -1);
 
     case STRUB_DISABLED:
       return false;
@@ -999,12 +1235,16 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
   return true;
 }
 
-/* We wish to avoid inlining WRAPPED functions back into their
-   WRAPPERs.  More generally, we wish to avoid inlining
-   strubbed functions into non-strubbed ones.  */
+/* Return true if CALLEE can be inlined into CALLER.  We wish to avoid inlining
+   WRAPPED functions back into their WRAPPERs.  More generally, we wish to avoid
+   inlining strubbed functions into non-strubbed ones.  CALLER doesn't have to
+   be an immediate caller of CALLEE: the immediate caller may have already been
+   cloned for inlining, and then CALLER may be further up the original call
+   chain.  ???  It would be nice if our own caller would retry inlining callee
+   if caller gets inlined.  */
 
 bool
-strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+strub_inlinable_to_p (cgraph_node *callee, cgraph_node *caller)
 {
   strub_mode callee_mode = get_strub_mode (callee);
 
@@ -1020,6 +1260,10 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_WRAPPER:
     case STRUB_DISABLED:
     case STRUB_CALLABLE:
+      /* When we consider inlining, we've already verified callability, so we
+	 can even inline callable and then disabled into a strub context.  That
+	 will get strubbed along with the context, so it's hopefully not a
+	 problem.  */
       return true;
 
     default:
@@ -1049,20 +1293,50 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
   return false;
 }
 
+/* Check that types T1 and T2 are strub-compatible.  Return 1 if the strub modes
+   are the same, 2 if they are interchangeable, and 0 otherwise.  */
+
+int
+strub_comptypes (tree t1, tree t2)
+{
+  enum strub_mode m1 = get_strub_mode_from_type (t1);
+  enum strub_mode m2 = get_strub_mode_from_type (t2);
+
+  if (m1 == m2)
+    return 1;
+
+  /* We're dealing with types, so only strub modes that can be selected by
+     attributes in the front end matter.  If either mode is at-calls, the
+     conversion is not compatible.  */
+  if (m1 == STRUB_AT_CALLS || m2 == STRUB_AT_CALLS)
+    return 0;
+
+  return 2;
+}
+
 /* Check that strub functions don't call non-strub functions, and that
    always_inline strub functions are only called by strub
    functions.  */
+
 static void
 verify_strub ()
 {
   cgraph_node *node;
 
+  /* It's expected that check strub-wise pointer type compatibility of variables
+     and of functions is already taken care of by front-ends, on account of the
+     attribute's being marked as affecting type identity and of the creation of
+     distinct types.  */
+
+  /* Check that call targets in strub contexts have strub-callable types.  */
+
   FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
   {
     enum strub_mode caller_mode = get_strub_mode (node);
     bool strub_context
       = (caller_mode == STRUB_AT_CALLS
 	 || caller_mode == STRUB_AT_CALLS_OPT
+	 || caller_mode == STRUB_INTERNAL
 	 || caller_mode == STRUB_WRAPPED
 	 || caller_mode == STRUB_INLINABLE);
 
@@ -1105,14 +1379,11 @@ verify_strub ()
 	  }
       }
   }
-
-  /* ??? Check strub-wise pointer type compatibility of variables and
-     functions, or is this already taken care of on account of the
-     attribute's being marked as affecting type identity?  */
 }
 
 namespace {
 
+/* Define a pass to compute strub modes.  */
 const pass_data pass_data_ipa_strub_mode = {
   SIMPLE_IPA_PASS,
   "strubm",
@@ -1133,24 +1404,26 @@ public:
   {}
   opt_pass *clone () { return new pass_ipa_strub_mode (m_ctxt); }
   virtual bool gate (function *) {
-    /* In the default setting, the attribute handler changes
-       flag_strub to -1 if any strub-enabling occurence of the
-       attribute is found.  If it remains at -2, nothing that would
-       enable strub was found, so we can disable it and avoid the
+    /* In relaxed (-3) and strict (-4) settings, that only enable strub at a
+       function or variable attribute's request, the attribute handler changes
+       flag_strub to -1 or -2, respectively, if any strub-enabling occurence of
+       the attribute is found.  Therefore, if it remains at -3 or -4, nothing
+       that would enable strub was found, so we can disable it and avoid the
        overhead.  */
-    if (flag_strub == -2)
+    if (flag_strub < -2)
       flag_strub = 0;
     return flag_strub;
   }
   virtual unsigned int execute (function *);
 };
 
+/* Define a pass to introduce strub transformations.  */
 const pass_data pass_data_ipa_strub = {
   SIMPLE_IPA_PASS,
   "strub",
   OPTGROUP_NONE,
   TV_NONE,
-  PROP_cfg, // properties_required
+  PROP_cfg | PROP_ssa, // properties_required
   0,	    // properties_provided
   0,	    // properties_destroyed
   0,	    // properties_start
@@ -1167,9 +1440,10 @@ public:
     : simple_ipa_opt_pass (pass_data_ipa_strub, ctxt)
   {}
   opt_pass *clone () { return new pass_ipa_strub (m_ctxt); }
-  virtual bool gate (function *) { return flag_strub; }
+  virtual bool gate (function *) { return flag_strub && !seen_error (); }
   virtual unsigned int execute (function *);
 
+  /* Define on demand and cache some types we use often.  */
 #define DEF_TYPE(NAME, INIT)			\
   static inline tree get_ ## NAME () {		\
     static tree type = NULL_TREE;		\
@@ -1202,6 +1476,7 @@ public:
 
 #undef DEF_TYPE
 
+  /* Define non-strub builtins on demand.  */
 #define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST)			\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1224,6 +1499,7 @@ public:
 
 #undef DEF_NM_BUILTIN
 
+  /* Define strub builtins on demand.  */
 #define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST)		\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1260,6 +1536,7 @@ public:
 
 #undef DEF_SS_BUILTIN
 
+    /* Define strub identifiers on demand.  */
 #define DEF_IDENT(NAME)					\
   static inline tree get_ ## NAME () {			\
     static tree identifier = NULL_TREE;			\
@@ -1278,6 +1555,10 @@ public:
   static inline void adjust_at_calls_call (cgraph_edge *, int);
   static inline void adjust_at_calls_calls (cgraph_node *);
 
+  /* Add to SEQ a call to the strub watermark update builtin, taking NODE's
+     location if given.  Optionally add the corresponding edge from NODE, with
+     execution frequency COUNT.  Return the modified SEQ.  */
+
   static inline gimple_seq
   call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
 			 gimple_seq seq = NULL)
@@ -1300,8 +1581,15 @@ public:
 
 } // anon namespace
 
+/* Gather with this type a collection of parameters that we're turning into
+   explicit references.  */
+
 typedef hash_set<tree> indirect_parms_t;
 
+/* Dereference OP's incoming turned-into-reference parm if it's an
+   INDIRECT_PARMS or an ADDR_EXPR thereof.  Set *REC and return according to
+   gimple-walking expectations.  */
+
 static tree
 maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
 {
@@ -1324,12 +1612,17 @@ maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
     {
       *rec = 0;
       if (indirect_parms.contains (TREE_OPERAND (op, 0)))
-	return TREE_OPERAND (op, 0);
+	{
+	  op = TREE_OPERAND (op, 0);
+	  return op;
+	}
     }
 
   return NULL_TREE;
 }
 
+/* A gimple-walking function that adds dereferencing to indirect parms.  */
+
 static tree
 walk_make_indirect (tree *op, int *rec, void *arg)
 {
@@ -1351,6 +1644,11 @@ walk_make_indirect (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* A gimple-walking function that turns any non-gimple-val ADDR_EXPRs into a
+   separate SSA.  Though addresses of e.g. parameters, and of members thereof,
+   are gimple vals, turning parameters into references, with an extra layer of
+   indirection and thus explicit dereferencing, need to be regimplified.  */
+
 static tree
 walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
 {
@@ -1374,6 +1672,57 @@ walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* Turn STMT's PHI arg defs into separate SSA defs if they've become
+   non-gimple_val.  Return TRUE if any edge insertions need to be committed.  */
+
+static bool
+walk_regimplify_phi (gphi *stmt)
+{
+  bool needs_commit = false;
+
+  for (unsigned i = 0, n = gimple_phi_num_args (stmt); i < n; i++)
+    {
+      tree op = gimple_phi_arg_def (stmt, i);
+      if ((TREE_CODE (op) == ADDR_EXPR
+	   && !is_gimple_val (op))
+	  /* ??? A PARM_DECL that was addressable in the original function and
+	     had its address in PHI nodes, but that became a reference in the
+	     wrapped clone would NOT be updated by update_ssa in PHI nodes.
+	     Alas, if we were to create a default def for it now, update_ssa
+	     would complain that the symbol that needed rewriting already has
+	     SSA names associated with it.  OTOH, leaving the PARM_DECL alone,
+	     it eventually causes errors because it remains unchanged in PHI
+	     nodes, but it gets rewritten as expected if it appears in other
+	     stmts.  So we cheat a little here, and force the PARM_DECL out of
+	     the PHI node and into an assignment.  It's a little expensive,
+	     because we insert it at the edge, which introduces a basic block
+	     that's entirely unnecessary, but it works, and the block will be
+	     removed as the default def gets propagated back into the PHI node,
+	     so the final optimized code looks just as expected.  */
+	  || (TREE_CODE (op) == PARM_DECL
+	      && !TREE_ADDRESSABLE (op)))
+	{
+	  tree temp = make_ssa_name (TREE_TYPE (op), stmt);
+	  if (TREE_CODE (op) == PARM_DECL)
+	    SET_SSA_NAME_VAR_OR_IDENTIFIER (temp, DECL_NAME (op));
+	  SET_PHI_ARG_DEF (stmt, i, temp);
+
+	  gimple *assign = gimple_build_assign (temp, op);
+	  if (gimple_phi_arg_has_location (stmt, i))
+	    gimple_set_location (assign, gimple_phi_arg_location (stmt, i));
+	  gsi_insert_on_edge (gimple_phi_arg_edge (stmt, i), assign);
+	  needs_commit = true;
+	}
+    }
+
+  return needs_commit;
+}
+
+/* Create a reference type to use for PARM when turning it into a reference.
+   NONALIASED causes the reference type to gain its own separate alias set, so
+   that accessing the indirectly-passed parm won'will not add aliasing
+   noise.  */
+
 static tree
 build_ref_type_for (tree parm, bool nonaliased = true)
 {
@@ -1411,6 +1760,7 @@ build_ref_type_for (tree parm, bool nonaliased = true)
 
 /* Add cgraph edges from current_function_decl to callees in SEQ with frequency
    COUNT, assuming all calls in SEQ are direct.  */
+
 static void
 add_call_edges_for_seq (gimple_seq seq, profile_count count)
 {
@@ -1425,16 +1775,22 @@ add_call_edges_for_seq (gimple_seq seq, profile_count count)
     {
       gimple *stmt = gsi_stmt (gsi);
 
-      if (!is_a <gcall *> (stmt))
+      gcall *call = dyn_cast <gcall *> (stmt);
+      if (!call)
 	continue;
 
-      gcall *call = as_a <gcall *> (stmt);
       tree callee = gimple_call_fndecl (call);
       gcc_checking_assert (callee);
       node->create_edge (cgraph_node::get_create (callee), call, count, false);
     }
 }
 
+/* Insert SEQ after the call at GSI, as if the call was in a try block with SEQ
+   as finally, i.e., SEQ will run after the call whether it returns or
+   propagates an exception.  This handles block splitting, EH edge and block
+   creation, noreturn and nothrow optimizations, and even throwing calls without
+   preexisting local handlers.  */
+
 static void
 gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
 {
@@ -1446,7 +1802,7 @@ gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
   if (gimple_has_location (stmt))
     annotate_all_with_location (seq, gimple_location (stmt));
 
-  gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+  gcall *call = dyn_cast <gcall *> (stmt);
   bool noreturn_p = call && gimple_call_noreturn_p (call);
   int eh_lp = lookup_stmt_eh_lp (stmt);
   bool must_not_throw_p = eh_lp < 0;
@@ -1586,8 +1942,12 @@ remove_named_attribute_unsharing (const char *name, tree *attrs)
     }
 }
 
+/* Record the order of the last cgraph entry whose mode we've already set, so
+   that we can perform mode setting incrementally without duplication.  */
 static int last_cgraph_order;
 
+/* Set strub modes for functions introduced since the last call.  */
+
 static void
 ipa_strub_set_mode_for_new_functions ()
 {
@@ -1616,6 +1976,7 @@ ipa_strub_set_mode_for_new_functions ()
 }
 
 /* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
+
 bool
 strub_splittable_p (cgraph_node *node)
 {
@@ -1641,10 +2002,11 @@ strub_splittable_p (cgraph_node *node)
 }
 
 /* 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))
+  switch (get_strub_mode_from_fndecl (fndecl))
     {
     case STRUB_WRAPPED:
     case STRUB_AT_CALLS:
@@ -1676,6 +2038,7 @@ strub_watermark_parm (tree fndecl)
 
 /* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
    hasn't been added yet.  Return the named argument count.  */
+
 int
 pass_ipa_strub::adjust_at_calls_type (tree type)
 {
@@ -1751,6 +2114,12 @@ pass_ipa_strub::adjust_at_calls_type (tree type)
   return named_args;
 }
 
+/* Adjust a call to an at-calls call target.  Create a watermark local variable
+   if needed, initialize it before, pass it to the callee according to the
+   modified at-calls interface, and release the callee's stack space after the
+   call, if not deferred.  If the call is const or pure, arrange for the
+   watermark to not be assumed unused or unchanged.  */
+
 void
 pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 {
@@ -1819,10 +2188,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 
     if (gimple_call_internal_p (stmt))
 #if 0
-      /*
-	new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
-	vargs);
-      */
+      new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
+						 vargs);
 #endif
       gcc_unreachable ();
     else
@@ -1917,6 +2284,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
   gsi_insert_finally_seq_after_call (gsi, seq);
 }
 
+/* Adjust all at-calls calls in NODE. */
+
 void
 pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
 {
@@ -1965,6 +2334,10 @@ pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
     }
 }
 
+/* The strubm (strub mode) pass computes a strub mode for each function in the
+   call graph, and checks, before any inlining, that strub callability
+   requirements in effect are satisfied.  */
+
 unsigned int
 pass_ipa_strub_mode::execute (function *)
 {
@@ -1977,12 +2350,17 @@ pass_ipa_strub_mode::execute (function *)
   return 0;
 }
 
+/* Create a strub mode pass.  */
+
 simple_ipa_opt_pass *
 make_pass_ipa_strub_mode (gcc::context *ctxt)
 {
   return new pass_ipa_strub_mode (ctxt);
 }
 
+/* The strub pass proper adjusts types, signatures, and at-calls calls, and
+   splits internal-strub functions.  */
+
 unsigned int
 pass_ipa_strub::execute (function *)
 {
@@ -2042,17 +2420,6 @@ pass_ipa_strub::execute (function *)
 	if (onode->alias)
 	  continue;
 
-#if 0 /* Calls are now adjusted when examining callers.  */
-	unsigned c;
-	cgraph_edge *e;
-	FOR_EACH_VEC_ELT (onode->collect_callers (), c, e)
-	  {
-	    push_cfun (DECL_STRUCT_FUNCTION (e->caller->decl));
-	    adjust_at_calls_call (e, named_args);
-	    pop_cfun ();
-	  }
-#endif
-
 	cgraph_node *nnode = onode;
 	push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
 
@@ -2071,10 +2438,10 @@ pass_ipa_strub::execute (function *)
 		{
 		  gimple *stmt = gsi_stmt (gsi);
 
-		  if (!is_gimple_call (stmt))
-		    continue;
+		  gcall *call = dyn_cast <gcall *> (stmt);
 
-		  gcall *call = as_a <gcall *> (stmt);
+		  if (!call)
+		    continue;
 
 		  if (gimple_alloca_call_p (call))
 		    {
@@ -2088,10 +2455,6 @@ pass_ipa_strub::execute (function *)
 	  }
 
 	pop_cfun ();
-
-#if 0
-	compute_fn_summary (onode, true);
-#endif
       }
   }
 
@@ -2108,43 +2471,6 @@ pass_ipa_strub::execute (function *)
 	continue;
       }
 
-#if 0
-    /* Hmm, this is an i386-specific attribute.  Do we need machine-specific
-       logic?  */
-    remove_named_attribute_unsharing ("interrupt",
-				      &DECL_ATTRIBUTES (onode->decl));
-#endif
-
-#if 0
-    if (!DECL_STRUCT_FUNCTION (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting struct-less function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    if (!onode->lowered)
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting non-lowered function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    /* 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.  FIXME.  */
-    if (!tree_versionable_function_p (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"%qD cannot be split for %<strub%>",
-		onode->decl);
-	continue;
-      }
-#endif
-
     bool is_stdarg = calls_builtin_va_start_p (onode);;
     bool apply_args = calls_builtin_apply_args_p (onode);
 
@@ -2753,26 +3079,45 @@ pass_ipa_strub::execute (function *)
       if (any_indirect)
 	{
 	  basic_block bb;
+	  bool needs_commit = false;
 	  FOR_EACH_BB_FN (bb, cfun)
-	    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
-		 !gsi_end_p (gsi); gsi_next (&gsi))
-	      {
-		gimple *stmt = gsi_stmt (gsi);
+	    {
+	      for (gphi_iterator gsi = gsi_start_nonvirtual_phis (bb);
+		   !gsi_end_p (gsi);
+		   gsi_next_nonvirtual_phi (&gsi))
+		{
+		  gphi *stmt = gsi.phi ();
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed && !is_gimple_debug (gsi_stmt (gsi)))
+		    if (walk_regimplify_phi (stmt))
+		      needs_commit = true;
+		}
 
-		walk_stmt_info wi = {};
-		wi.info = &indirect_nparms;
-		walk_gimple_op (stmt, walk_make_indirect, &wi);
-		if (wi.changed)
-		  {
-		    if (!is_gimple_debug (gsi_stmt (gsi)))
-		      {
-			wi.info = &gsi;
-			walk_gimple_op (stmt, walk_regimplify_addr_expr,
-					&wi);
-		      }
-		    update_stmt (stmt);
-		  }
-	      }
+	      for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+		   !gsi_end_p (gsi); gsi_next (&gsi))
+		{
+		  gimple *stmt = gsi_stmt (gsi);
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed)
+		    {
+		      if (!is_gimple_debug (stmt))
+			{
+			  wi.info = &gsi;
+			  walk_gimple_op (stmt, walk_regimplify_addr_expr,
+					  &wi);
+			}
+		      update_stmt (stmt);
+		    }
+		}
+	    }
+	  if (needs_commit)
+	    gsi_commit_edge_inserts ();
 	}
 
       if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca
@@ -2842,10 +3187,10 @@ pass_ipa_strub::execute (function *)
       push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
       gimple_stmt_iterator gsi
 	= gsi_after_labels (single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun)));
-      while (!is_gimple_call (gsi_stmt (gsi)))
-	gsi_next (&gsi);
 
-      gcall *wrcall = as_a <gcall *> (gsi_stmt (gsi));
+      gcall *wrcall;
+      while (!(wrcall = dyn_cast <gcall *> (gsi_stmt (gsi))))
+	gsi_next (&gsi);
 
       tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
       TREE_ADDRESSABLE (swm) = true;
@@ -2973,11 +3318,6 @@ pass_ipa_strub::execute (function *)
 
       pop_cfun ();
     }
-
-#if 0
-    compute_fn_summary (onode, true);
-    compute_fn_summary (nnode, true);
-#endif
   }
 
   if (flag_checking)
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
index c335ab42097..5c0b266c3f8 100644
--- a/gcc/ipa-strub.h
+++ b/gcc/ipa-strub.h
@@ -18,11 +18,10 @@ You should have received a copy of the GNU General Public License
 along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
-/* Return TRUE if the first function can be inlined into the second,
-   as far as stack scrubbing constraints are concerned.  CALLEE
-   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 TRUE if CALLEE can be inlined into CALLER, as far as stack scrubbing
+   constraints are concerned.  CALLEE doesn't have to be called directly by
+   CALLER, but the returned value says nothing about intervening functions.  */
+extern bool strub_inlinable_to_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);
@@ -33,3 +32,14 @@ extern tree strub_watermark_parm (tree fndecl);
 
 /* Make a function type or declaration callable.  */
 extern void strub_make_callable (tree fndecl);
+
+/* Return zero iff ID is NOT an acceptable parameter for a user-supplied strub
+   attribute for a function.  Otherwise, return >0 if it enables strub, <0 if it
+   does not.  Return +/-1 if the attribute-modified type is compatible with the
+   type without the attribute, or +/-2 if it is not compatible.  */
+extern int strub_validate_fn_attr_parm (tree id);
+
+/* Like comptypes, return 0 if t1 and t2 are not compatible, 1 if they are
+   compatible, and 2 if they are nearly compatible.  Same strub mode is
+   compatible, interface-compatible strub modes are nearly compatible.  */
+extern int strub_comptypes (tree t1, tree t2);
diff --git a/gcc/multiple_target.c b/gcc/multiple_target.c
index 28d5f95dca5..07cdb66e337 100644
--- a/gcc/multiple_target.c
+++ b/gcc/multiple_target.c
@@ -557,7 +557,7 @@ public:
 bool
 pass_target_clone::gate (function *)
 {
-  return true;
+  return !seen_error ();
 }
 
 } // anon namespace
diff --git a/gcc/testsuite/c-c++-common/strub-O0.c b/gcc/testsuite/c-c++-common/strub-O0.c
index 6afe0fd5de1..c7a79a6ea0d 100644
--- a/gcc/testsuite/c-c++-common/strub-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O0 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O0, none of the strub builtins are expanded inline.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O1.c b/gcc/testsuite/c-c++-common/strub-O1.c
index 1cdeaecaf32..96285c975d9 100644
--- a/gcc/testsuite/c-c++-common/strub-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O1 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O1, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
index 7848c46d179..8edc0d8aa13 100644
--- a/gcc/testsuite/c-c++-common/strub-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O2, without -fno-inline, we fully expand enter and update, and add a test
    around the leave call.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
index 85a8f76785e..c6d900cf3c4 100644
--- a/gcc/testsuite/c-c++-common/strub-O2fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3.c b/gcc/testsuite/c-c++-common/strub-O3.c
index 1fcde345d36..33ee465e51c 100644
--- a/gcc/testsuite/c-c++-common/strub-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand" } */
 
 int __attribute__ ((__strub__)) var;
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
index a2eedfd96b2..2936f82079e 100644
--- a/gcc/testsuite/c-c++-common/strub-O3fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
index e5cb1f60541..479746e57d8 100644
--- a/gcc/testsuite/c-c++-common/strub-Og.c
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Og -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Og -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Og, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
index 194aacc2c05..2241d4ea07f 100644
--- a/gcc/testsuite/c-c++-common/strub-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Os, without -fno-inline, we fully expand enter, and also update.  The
    expanded update might be larger than a call proper, but argument saving and
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
index 46e84bf6560..a322bcc5da6 100644
--- a/gcc/testsuite/c-c++-common/strub-all1.c
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -22,11 +22,11 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
index f377541cff0..db60026d0e0 100644
--- a/gcc/testsuite/c-c++-common/strub-all2.c
+++ b/gcc/testsuite/c-c++-common/strub-all2.c
@@ -17,8 +17,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
index f180b17f30e..2f462adc1ef 100644
--- a/gcc/testsuite/c-c++-common/strub-apply1.c
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -1,13 +1,13 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
-void __attribute__ ((__strub__ (3)))
+void __attribute__ ((__strub__ ("callable")))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0);
 }
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   void *args = __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
index 379a54b73b7..bab2dd10f5b 100644
--- a/gcc/testsuite/c-c++-common/strub-apply2.c
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 extern void __attribute__ ((__strub__))
 apply_function (void *args);
@@ -10,3 +10,9 @@ apply_args (int i, int j, double d) /* { dg-error "selected" } */
   void *args = __builtin_apply_args (); /* { dg-message "does not support" } */
   apply_function (args);
 }
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After the error and sorry messages in strubm, build_ssa_passes are gated out,
+   so we don't get the PROP_ssa property that targetclone requires.  When
+   checking is not enabled at configure time, we admit to being confused and
+   bail out, but when checking is eneabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
index 9b4786be698..85f9d090fda 100644
--- a/gcc/testsuite/c-c++-common/strub-apply3.c
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -1,8 +1,14 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 void __attribute__ ((__strub__))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */
 }
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
index 409f747743e..15ffaa031b8 100644
--- a/gcc/testsuite/c-c++-common/strub-apply4.c
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+/* { dg-options "-O2 -fstrub=strict -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
@@ -18,4 +18,4 @@ void f() {
   apply_args (1, 2, 3);
 }
 
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 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 d964b07ae5d..b70843b4215 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -22,9 +22,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
index 530eee36d06..97a3988a6b9 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls2.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls2.c
@@ -17,7 +17,7 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-default1.c b/gcc/testsuite/c-c++-common/strub-default1.c
deleted file mode 100644
index 79d00cedb9a..00000000000
--- a/gcc/testsuite/c-c++-common/strub-default1.c
+++ /dev/null
@@ -1,42 +0,0 @@
-/* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
-
-static int __attribute__ ((__strub__)) var;
-
-/* 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 inline void
-__attribute__ ((__always_inline__))
-h() {
-  var++;
-}
-
-/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
-   the viability of at-calls strubbing.  Though internally a strub context, its
-   interface is not strub-enabled, so it's not callable from within strub
-   contexts.  */
-static inline void
-g() {
-  var--;
-  h();
-}
-
-/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
-   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
-void
-f() {
-  var++;
-  g();  /* { dg-error "calling non-.strub." } */
-}
-
-/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-4\[)\]" 1 "strubm" } } */
-/* { 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 "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
index 7b04eea35d9..3d73431b3dc 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O1" } */
+/* { dg-options "-fstrub=strict -O1" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O1.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
index 67d96419a5e..fddf3c745e7 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O2" } */
+/* { dg-options "-fstrub=strict -O2" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O2.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
index 34828d2711e..2bc384ee8d1 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O3" } */
+/* { dg-options "-fstrub=strict -O3" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -O3.  */
@@ -10,7 +10,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -18,7 +18,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -32,7 +32,7 @@ leak_string (void)
   return 0;
 }
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 int
 look_for_string (char *e)
 {
@@ -56,14 +56,14 @@ look_for_string (char *e)
   return 0;
 }
 
-static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+static __attribute__ ((__strub__ ("at-calls"), __noinline__, __noclone__))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 deferred_at_calls ()
 {
@@ -73,7 +73,7 @@ deferred_at_calls ()
   return ret;
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 deferred_internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
index b273660aea1..fbaf85fe0fa 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -Os" } */
+/* { dg-options "-fstrub=strict -Os" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -Os.  */
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
index a74658c9ac9..e9d7b7b9ee0 100644
--- a/gcc/testsuite/c-c++-common/strub-internal1.c
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -23,9 +23,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
index a6e69357b23..8b8e15a51c7 100644
--- a/gcc/testsuite/c-c++-common/strub-internal2.c
+++ b/gcc/testsuite/c-c++-common/strub-internal2.c
@@ -14,8 +14,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
index 7e22a266ad9..0a4a7539d34 100644
--- a/gcc/testsuite/c-c++-common/strub-parms1.c
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -16,7 +16,7 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 large_byref_arg (struct large_arg la)
 {
 }
@@ -24,7 +24,7 @@ large_byref_arg (struct large_arg la)
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*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)))
+void __attribute__ ((__strub__ ("internal")))
 std_arg (int i, ...)
 {
   va_list vl;
@@ -38,7 +38,7 @@ std_arg (int i, ...)
 /* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
index 2d8036c0fbc..147171d96d5 100644
--- a/gcc/testsuite/c-c++-common/strub-parms2.c
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -15,14 +15,14 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 large_byref_arg (struct large_arg la)
 {
 }
 
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 std_arg (int i, ...)
 {
   va_list vl;
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
index f64361f1235..4e92682895a 100644
--- a/gcc/testsuite/c-c++-common/strub-parms3.c
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
new file mode 100644
index 00000000000..e2f9d8aebca
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  Without the error,
+   inlining takes place.  */
+
+#include "strub-strict1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
new file mode 100644
index 00000000000..98474435d2e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  */
+
+#include "strub-strict2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
index a4226ce0119..1de15342595 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fexceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fexceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
index 3bab553478b..f9209c81900 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
index c89cc7c2c47..bed1dcfb54a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
index b869fafb691..6bf0071f52b 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
index 1d3dd2f2c2c..4732f515bf7 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
index 4dd4281b03b..8d6424c479a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-strict1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
new file mode 100644
index 00000000000..7f5247cfaf0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-strict1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+static int __attribute__ ((__strub__)) var;
+
+/* 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 inline void
+__attribute__ ((__always_inline__))
+h() {
+  var++;
+}
+
+/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
+   the viability of at-calls strubbing.  Though internally a strub context, its
+   interface is not strub-enabled, so it's not callable from within strub
+   contexts.  */
+static inline void
+g() {
+  var--;
+  h();
+}
+
+/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
+   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
+void
+f() {
+  var++;
+  g();  /* { dg-error "calling non-.strub." } */
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-default2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
similarity index 54%
rename from gcc/testsuite/c-c++-common/strub-default2.c
rename to gcc/testsuite/c-c++-common/strub-strict2.c
index 487253e9227..17463b6e554 100644
--- a/gcc/testsuite/c-c++-common/strub-default2.c
+++ b/gcc/testsuite/c-c++-common/strub-strict2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
 
 static int __attribute__ ((__strub__)) var;
 
@@ -22,8 +22,14 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
index 0840dddd136..e48e0610e07 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 #include "strub-tail-O2.c"
 
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
index 9330d6ff4c1..87cda7ab21b 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.
    Tail calls are short-circuited at -O2+.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
index 5b33ff1f530..b5e45ab0525 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
index 38935e3270b..96aa7fe4b07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -1,39 +1,39 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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);
+extern int __attribute__ ((__strub__ ("callable"))) xcallable (void);
+extern int __attribute__ ((__strub__ ("internal"))) xinternal (void);
+extern int __attribute__ ((__strub__ ("at-calls"))) xat_calls (void);
+extern int __attribute__ ((__strub__ ("disabled"))) 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__ ("callable"))) callable (void);
+int __attribute__ ((__strub__ ("internal"))) internal (void);
+int __attribute__ ((__strub__ ("at-calls"))) at_calls (void);
+int __attribute__ ((__strub__ ("disabled"))) disabled (void);
 
 int __attribute__ ((__strub__)) var;
 int var_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 icallable (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 iinternal (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 idisabled (void);
 static inline int __attribute__ ((__always_inline__))
 ivar_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 i_callable (void) { return 0; }
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 i_internal (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 i_at_calls (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 i_disabled (void) { return 0; }
 static inline int __attribute__ ((__always_inline__))
 i_var_user (void) { return var; }
@@ -70,7 +70,7 @@ i_var_user (void) { return var; }
 
 /* Not a strub context, so it can call anything.
    Explicitly declared as callable even from within strub contexts.  */
-int __attribute__ ((__strub__ (3)))
+int __attribute__ ((__strub__ ("callable")))
 callable (void) {
   int ret = 0;
 
@@ -88,7 +88,7 @@ callable (void) {
 
 /* 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)))
+int __attribute__ ((__strub__ ("internal")))
 internal (void) {
   int ret = var;
 
@@ -109,7 +109,7 @@ internal (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (1)))
+int __attribute__ ((__strub__ ("at-calls")))
 at_calls (void) {
   int ret = var;
 
@@ -130,7 +130,7 @@ at_calls (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (0)))
+int __attribute__ ((__strub__ ("disabled")))
 disabled () {
   int ret = 0;
 
@@ -205,7 +205,7 @@ iinternal (void) {
   return ret;
 }
 
-int
+int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void) {
   int ret = var;
 
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
index 100fb0c59a9..2857195706e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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,
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
index 9e818ac9748..98a92bc9eac 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
index d40e8aa45cb..5511a6e1e71 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __const__))
 f() {
   return 0;
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
index d4cbdaf10f3..47ee927964d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -1,12 +1,12 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__
 __attribute__ ((__const__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data1.c b/gcc/testsuite/c-c++-common/torture/strub-data1.c
index 62a03891ab6..7c27a2a1a6d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointed-to data enables strubbing if accessed.  */
 int __attribute__ ((__strub__)) var;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data2.c b/gcc/testsuite/c-c++-common/torture/strub-data2.c
index 9b7df13a280..e66d903780a 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointer itself is a strub variable, enabling internal strubbing when
    its value is used.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data3.c b/gcc/testsuite/c-c++-common/torture/strub-data3.c
index 515706caa32..5e08e0e58c6 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data4.c b/gcc/testsuite/c-c++-common/torture/strub-data4.c
index 0ec9e35429f..a818e7a38bb 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
index 69f3d65ed44..aad17aaa5c0 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -1,13 +1,15 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -Werror" } */
+/* { dg-options "-fstrub=strict" } */
+
+/* It would be desirable to issue at least warnings for these.  */
 
 typedef int __attribute__ ((__strub__)) strub_int;
 strub_int *ptr;
 
 int *f () {
-  return ptr; /* { dg-error "incompatible" } */
+  return ptr; /* { dg-message "incompatible" "" { xfail *-*-* } } */
 }
 
 strub_int *g () {
-  return f (); /* { dg-error "incompatible" } */
+  return f (); /* { dg-message "incompatible" "" { xfail *-*-* } } */
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
index b8adf8009e8..c165f312f16 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype ();
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
index 5b2c35ad6a7..69fcff8d376 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
index 5ee50456dc9..ff006224909 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int, ...);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
new file mode 100644
index 00000000000..95996cd5c25
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+inline void __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+}
+
+void
+bat (void)
+{
+  /* Not allowed, not a strub context.  */
+  inl_int_ali (); /* { dg-error "context" } */
+}
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
new file mode 100644
index 00000000000..f9a6b4a16fa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=all" } */
+
+#include "strub-inlinable1.c"
+
+/* With -fstrub=all, the caller becomes a strub context, so the strub-inlinable
+   callee is not rejected.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
new file mode 100644
index 00000000000..b4a7f3992bb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+typedef void ft (void);
+typedef void ft2 (int, int);
+extern ft __attribute__ ((__strub__)) fnac;
+
+ft * f (void) {
+  return fnac; /* { dg-message "incompatible|invalid conversion" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
new file mode 100644
index 00000000000..d9d2c0caec4
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
@@ -0,0 +1,55 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic" } */
+
+/* C++ does not warn about the partial incompatibilities.
+
+   The d_p () calls are actually rejected, even in C++, but they are XFAILed
+   here because we don't get far enough in the compilation as to observe them,
+   because the incompatibilities are errors without -fpermissive.
+   strub-ptrfn3.c uses -fpermissive to check those.
+ */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
new file mode 100644
index 00000000000..e1f179e160e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
@@ -0,0 +1,50 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic -fpermissive" } */
+/* { dg-prune-output "command-line option .-fpermissive." } */
+
+/* See strub-ptrfn2.c.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
new file mode 100644
index 00000000000..70b558afad0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+/* This is strub-ptrfn2.c without -Wpedantic.
+
+   Even C doesn't report the (not-quite-)compatible conversions without it.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
index cb223da6efc..a262a086837 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
index 67d1434b1f8..4c4bd50c209 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
index 59f02ea901f..ce195c6b1f1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -1,10 +1,10 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __pure__))
 f() {
   static int i; /* Stop it from being detected as const.  */
   return i;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
index 973e909217d..75cd54ccb5b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
 __attribute__ ((__pure__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
index a4077c35a60..c596066a045 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -14,7 +14,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -59,14 +59,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
index 94e4156ea73..1fdba214a07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
index 0ca74beb59d..afbc2cc9ab4 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
 /* Check that a non-strub function leaves a string behind in the stack, and that
@@ -7,7 +7,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
index 4ab11c0682e..5300f1d330b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -85,14 +85,14 @@ intermediate ()
   return ret;
 }
 
-static inline __attribute__ ((__strub__ (2)))
+static inline __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
   return intermediate ();
 }
 
-int __attribute__ ((strub (0)))
+int __attribute__ ((__strub__ ("disabled")))
 main ()
 {
   if (look_for_string (internal ()))
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4d.c b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
index e4f7445607c..08de3f1c3b1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4d.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -1,7 +1,7 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
-#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ (1)))
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
 
 #include "strub-run4.c"
diff --git a/gcc/testsuite/g++.dg/torture/strub-init1.C b/gcc/testsuite/g++.dg/torture/strub-init1.C
index e51ae802be4..c226ab10ff6 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init1.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init2.C b/gcc/testsuite/g++.dg/torture/strub-init2.C
index edcb7bf8ad2..a7911f1fa72 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init2.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init3.C b/gcc/testsuite/g++.dg/torture/strub-init3.C
index bacf823ca4e..6ebebcd01e8 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init3.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
index 697ac9de764..10445d7cf84 100644
--- a/gcc/testsuite/gnat.dg/strub_attr.adb
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -1,5 +1,5 @@
 --  { dg-do compile }
---  { dg-options "-fstrub=default -fdump-ipa-strubm" }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" }
 
 package body Strub_Attr is
    E : exception;
@@ -14,8 +14,24 @@ package body Strub_Attr is
       return X * X;
    end;
    
-   function G return Integer is (X);
+   function G return Integer is (F (X));
+   --  function G return Integer is (FP (X));
+   --  Calling G would likely raise an exception, because although FP
+   --  carries the strub at-calls attribute needed to call F, the
+   --  attribute is dropped from the type used for the call proper.
 end Strub_Attr;
 
---  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]2\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 0 "strubm" } }
 --  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark_ptr" 6 "strub" } }
+--  We have 1 at-calls subprogram (F) and 2 wrapped (P and G).
+--  For each of them, there's one match for the wrapped signature, 
+--  and one for the update call.
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 27 "strub" } }
+--  The 6 matches above, plus:
+--  5*2: wm var decl, enter, call, leave and clobber for each wrapper;
+--  2*1: an extra leave and clobber for the exception paths in the wrappers.
+--  7*1: for the F call in G, including EH path.
diff --git a/gcc/testsuite/gnat.dg/strub_ind.adb b/gcc/testsuite/gnat.dg/strub_ind.adb
new file mode 100644
index 00000000000..cfd7a76b02b
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind.adb
@@ -0,0 +1,50 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" }
+
+package body Strub_Ind is
+   E : exception;
+
+   procedure P (X : Integer) is
+   begin
+      raise E;
+   end;
+   
+   function F (X : Integer) return Integer is
+   begin
+      return X * X;
+   end;
+   
+   function G return Integer is (FP (X)); -- { dg-bogus "non-.strub." "" { xfail *-*-* } }
+   --  Calling G would likely raise an exception, because although FP
+   --  carries the strub at-calls attribute needed to call F, the
+   --  attribute is dropped from the type used for the call proper.
+
+
+   type GT is access function return Integer;
+   pragma Machine_Attribute (GT, "strub", "at-calls");
+   --  The pragma above seems to have no effect.
+
+   GP : GT := G'Access; -- { dg-warning "incompatible" "" { xfail *-*-* } }
+   pragma Machine_Attribute (GP, "strub", "at-calls");
+   --  The pragma above does modify GP's type,
+   --  but dereferencing it uses an unmodified copy of the type.
+   --  The initializer should be diagnosed:
+   --  GT should only reference functions with at-calls strub.
+
+end Strub_Ind;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 0 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark_ptr" 6 "strub" } }
+--  We have 1 at-calls subprogram (F) and 2 wrapped (P and G).
+--  For each of them, there's one match for the wrapped signature, 
+--  and one for the update call.
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 20 "strub" } }
+--  The 6 matches above, plus:
+--  5*2: wm var decl, enter, call, leave and clobber for each wrapper;
+--  2*1: an extra leave and clobber for the exception paths in the wrappers.
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 27 "strub" { xfail *-*-* } } }
+--  7*1: for the FP call in G, including EH path (currently missing the watermarking)
diff --git a/gcc/testsuite/gnat.dg/strub_ind.ads b/gcc/testsuite/gnat.dg/strub_ind.ads
new file mode 100644
index 00000000000..53dede60eac
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind.ads
@@ -0,0 +1,23 @@
+package Strub_Ind is
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "internal");
+
+   function F (X : Integer) return Integer;
+   pragma Machine_Attribute (F, "strub");
+
+   X : Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+
+   function G return Integer;
+
+   type FT is access function (X : Integer) return Integer;
+   pragma Machine_Attribute (FT, "strub", "at-calls");
+   --  The pragma above seems to get discarded in GNAT; Gigi doesn't see it.
+
+   FP : FT := F'Access;
+   pragma Machine_Attribute (FP, "strub", "at-calls");
+   --  The pragma above does modify FP's type,
+   --  but a call with it gets it converted to its Ada type,
+   --  that is cached by the translator as the unmodified type.
+
+end Strub_Ind;
diff --git a/libgcc/strub.c b/libgcc/strub.c
index fd6e27556e4..7c58deee1e6 100644
--- a/libgcc/strub.c
+++ b/libgcc/strub.c
@@ -36,7 +36,7 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 # define TOPS <
 #endif
 
-#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ ("callable")))
 
 /* Enter a stack scrubbing context, initializing the watermark to the caller's
    stack address.  */


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

* [gcc(refs/users/aoliva/heads/strub)] -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests
@ 2021-09-07 22:44 Alexandre Oliva
  0 siblings, 0 replies; 13+ messages in thread
From: Alexandre Oliva @ 2021-09-07 22:44 UTC (permalink / raw)
  To: gcc-cvs

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

commit a41c3525e6d0396563cf70bb3c61c6fdbc6307cd
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Thu Sep 2 23:15:36 2021 -0300

    -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests

Diff:
---
 .../doc/gnat_rm/security_hardening_features.rst    |  50 +-
 gcc/ada/gcc-interface/utils.c                      | 111 +---
 gcc/attribs.c                                      |  39 +-
 gcc/c-family/c-attribs.c                           | 100 +---
 gcc/common.opt                                     |  27 +-
 gcc/doc/extend.texi                                | 332 +++++++++--
 gcc/doc/invoke.texi                                |  76 ++-
 gcc/ipa-inline.c                                   |   2 +-
 gcc/ipa-strub.c                                    | 652 ++++++++++++++++-----
 gcc/ipa-strub.h                                    |  20 +-
 gcc/multiple_target.c                              |   2 +-
 gcc/testsuite/c-c++-common/strub-O0.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O1.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2fni.c           |   2 +-
 gcc/testsuite/c-c++-common/strub-O3.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O3fni.c           |   2 +-
 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            |  12 +-
 gcc/testsuite/c-c++-common/strub-all2.c            |   6 +-
 gcc/testsuite/c-c++-common/strub-apply1.c          |   6 +-
 gcc/testsuite/c-c++-common/strub-apply2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-apply3.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-apply4.c          |   4 +-
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-at-calls2.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-default1.c        |  42 --
 gcc/testsuite/c-c++-common/strub-defer-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |  14 +-
 gcc/testsuite/c-c++-common/strub-defer-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-internal1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-internal2.c       |   6 +-
 gcc/testsuite/c-c++-common/strub-parms1.c          |  10 +-
 gcc/testsuite/c-c++-common/strub-parms2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-parms3.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-relaxed1.c        |  18 +
 gcc/testsuite/c-c++-common/strub-relaxed2.c        |  14 +
 gcc/testsuite/c-c++-common/strub-short-O0-exc.c    |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O0.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O3.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-strict1.c         |  48 ++
 .../{strub-default2.c => strub-strict2.c}          |  14 +-
 gcc/testsuite/c-c++-common/strub-tail-O1.c         |   2 +-
 gcc/testsuite/c-c++-common/strub-tail-O2.c         |   2 +-
 .../c-c++-common/torture/strub-callable1.c         |   2 +-
 .../c-c++-common/torture/strub-callable2.c         |  44 +-
 gcc/testsuite/c-c++-common/torture/strub-const1.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const2.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const3.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-const4.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-data1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data3.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data4.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |   2 +-
 .../c-c++-common/torture/strub-indcall1.c          |   2 +-
 .../c-c++-common/torture/strub-indcall2.c          |   2 +-
 .../c-c++-common/torture/strub-indcall3.c          |   2 +-
 .../c-c++-common/torture/strub-inlinable1.c        |  22 +
 .../c-c++-common/torture/strub-inlinable2.c        |   7 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |  10 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |  55 ++
 gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c  |  50 ++
 gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c  |  43 ++
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure3.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-pure4.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run1.c    |  10 +-
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run4.c    |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run4d.c   |   4 +-
 gcc/testsuite/g++.dg/torture/strub-init1.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init2.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init3.C         |   2 +-
 gcc/testsuite/gnat.dg/strub_attr.adb               |  22 +-
 gcc/testsuite/gnat.dg/strub_ind.adb                |  50 ++
 gcc/testsuite/gnat.dg/strub_ind.ads                |  23 +
 libgcc/strub.c                                     |   2 +-
 85 files changed, 1490 insertions(+), 609 deletions(-)

diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 309291f0341..75345290412 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -19,6 +19,18 @@ It can be enabled with the *-fzero-call-used-regs* command line
 option, to affect all subprograms in a compilation, and with a
 :samp:`Machine_Attribute` pragma, to affect only specific subprograms.
 
+.. code-block:: ada
+
+     procedure Foo;
+     pragma Machine_Attribute (Foo, "zero_call_used_regs", "used");
+     --  Before returning, Foo scrubs only call-clobbered registers
+     --  that it uses itself.
+
+     function Bar return Integer;
+     pragma Machine_Attribute (Bar, "zero_call_used_regs", "all");
+     --  Before returning, Bar scrubs all call-clobbered registers.
+
+
 For usage and more details on the command line option, and on the
 ``zero_call_used_regs`` attribute, see :title:`Using the GNU Compiler
 Collection (GCC)`.
@@ -31,13 +43,31 @@ Stack Scrubbing
 
 GNAT can generate code to zero-out stack frames used by subprograms.
 
-It can be activated with the *-fstrub* command line option, to affect
-all (viable) subprograms in a compilation, and with a
-:samp:`Machine_Attribute` pragma.
+It can be activated with the :samp:`Machine_Attribute` pragma, on
+specific subprograms, variables, and types.
 
-For usage and more details on the command line option, and on the
-``strub`` attribute, see :title:`Using the GNU Compiler Collection
-(GCC)`.
+.. code-block:: ada
+
+     function Foo returns Integer;
+     pragma Machine_Attribute (Foo, "strub");
+     --  Foo and its callers are modified so as to scrub the stack
+     --  space used by Foo after it returns.
+
+     procedure Bar;
+     pragma Machine_Attribute (Bar, "strub", "internal");
+     --  Bar is turned into a wrapper for its original body,
+     --  and they scrub the stack used by the original body.
+
+     Var : Integer;
+     pragma Machine_Attribute (Var, "strub");
+     --  Reading from Var in a subprogram enables stack scrubbing
+     --  of the stack space used by the subprogram.
+
+
+There are also *-fstrub* command line options to control default
+settings.  For usage and more details on the command line option, and
+on the ``strub`` attribute, see :title:`Using the GNU Compiler
+Collection (GCC)`.
 
 Note that Ada secondary stacks are not scrubbed.  The restriction
 ``No_Secondary_Stack`` avoids their use, and thus their accidental
@@ -49,4 +79,10 @@ is not fully reflected in Ada types, ``Access`` attributes, renaming
 and overriding.  Every access type, renaming, and overriding and
 overridden dispatching operations that may refer to an entity with an
 attribute-modified interface must be annotated with the same
-interface-modifying attribute.
+interface-modifying attribute, or with an interface-compatible one.
+
+Even then, applying the pragma to anything other than subprograms and
+scalar variables is not currently supported, and may be silently
+ignored.  Specifically, it is not recommended to rely on any effects
+of this pragma on access types and objects to data variables and to
+subprograms.
diff --git a/gcc/ada/gcc-interface/utils.c b/gcc/ada/gcc-interface/utils.c
index 6b20ddc93c9..193772cc829 100644
--- a/gcc/ada/gcc-interface/utils.c
+++ b/gcc/ada/gcc-interface/utils.c
@@ -39,6 +39,7 @@
 #include "varasm.h"
 #include "toplev.h"
 #include "opts.h"
+#include "ipa-strub.h"
 #include "output.h"
 #include "debug.h"
 #include "convert.h"
@@ -6611,115 +6612,57 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
-  tree orig_fnptr_type = NULL_TREE;
-  tree orig_args = args;
 
   if (args
       && POINTER_TYPE_P (*node)
       && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*node)))
-    {
-      orig_fnptr_type = *node;
-      *node = TREE_TYPE (orig_fnptr_type);
-    }
+    *node = TREE_TYPE (*node);
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      /* pragma Machine_Attribute turns string arguments into identifiers.
-	 Reverse it.  */
-      if (TREE_CODE (TREE_VALUE (args)) == IDENTIFIER_NODE)
-	{
-	  gcc_checking_assert (IDENTIFIER_POINTER (TREE_VALUE (args))
-			       [IDENTIFIER_LENGTH (TREE_VALUE (args))] == 0);
-	  TREE_VALUE (args) = build_string
-	    (IDENTIFIER_LENGTH (TREE_VALUE (args)) + 1,
-	     IDENTIFIER_POINTER (TREE_VALUE (args)));
-	}
-
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
 	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
-
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
-
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
 
   if (args)
-    warning (OPT_Wattributes,
-	     "ignoring excess %qE attribute arguments starting at %qE",
-	     name, TREE_VALUE (args));
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
 
   /* If we see a strub-enabling attribute, and we're at the default setting,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
-
-  if (!*no_add_attrs)
-    {
-      *no_add_attrs = true;
-      if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
-      TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
-					   TYPE_ATTRIBUTES (*node));
-
-      if (orig_fnptr_type)
-	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
-	  TREE_TYPE (fnptr_type) = *node;
-	  *node = fnptr_type;
-	}
-    }
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   return NULL_TREE;
 }
diff --git a/gcc/attribs.c b/gcc/attribs.c
index 0d22c20a35e..07f81285d7a 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "attribs.h"
 #include "fold-const.h"
+#include "ipa-strub.h"
 #include "stor-layout.h"
 #include "langhooks.h"
 #include "plugin.h"
@@ -617,12 +618,11 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  flags &= ~(int) ATTR_FLAG_TYPE_IN_PLACE;
 	}
 
-      if (spec->function_type_required && TREE_CODE (*anode) != FUNCTION_TYPE
-	  && TREE_CODE (*anode) != METHOD_TYPE)
+      if (spec->function_type_required
+	  && !FUNC_OR_METHOD_TYPE_P (*anode))
 	{
 	  if (TREE_CODE (*anode) == POINTER_TYPE
-	      && (TREE_CODE (TREE_TYPE (*anode)) == FUNCTION_TYPE
-		  || TREE_CODE (TREE_TYPE (*anode)) == METHOD_TYPE))
+	      && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
 	    {
 	      /* OK, this is a bit convoluted.  We can't just make a copy
 		 of the pointer type and modify its TREE_TYPE, because if
@@ -715,7 +715,23 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  tree ret = (spec->handler) (cur_and_last_decl, name, args,
 				      flags|cxx11_flag, &no_add_attrs);
 
-	  *anode = cur_and_last_decl[0];
+	  if (*anode != cur_and_last_decl[0])
+	    {
+	      /* Even if !spec->function_type_required, allow the attribute
+		 handler to request the attribute to be applied to the function
+		 type, rather than to the function pointer type, by setting
+		 cur_and_last_decl[0] to the function type.  */
+	      if (!fn_ptr_tmp
+		  && POINTER_TYPE_P (*anode)
+		  && TREE_TYPE (*anode) == cur_and_last_decl[0]
+		  && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
+		{
+		  fn_ptr_tmp = TREE_TYPE (*anode);
+		  fn_ptr_quals = TYPE_QUALS (*anode);
+		  anode = &fn_ptr_tmp;
+		}
+	      *anode = cur_and_last_decl[0];
+	    }
 	  if (ret == error_mark_node)
 	    {
 	      warning (OPT_Wattributes, "%qE attribute ignored", name);
@@ -1353,9 +1369,20 @@ comp_type_attributes (const_tree type1, const_tree type2)
   if ((lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type1)) != NULL)
       ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) != NULL))
     return 0;
+  int strub_ret = strub_comptypes (CONST_CAST_TREE (type1),
+				   CONST_CAST_TREE (type2));
+  if (strub_ret == 0)
+    return strub_ret;
   /* As some type combinations - like default calling-convention - might
      be compatible, we have to call the target hook to get the final result.  */
-  return targetm.comp_type_attributes (type1, type2);
+  int target_ret = targetm.comp_type_attributes (type1, type2);
+  if (target_ret == 0)
+    return target_ret;
+  if (strub_ret == 2 || target_ret == 2)
+    return 2;
+  if (strub_ret == 1 && target_ret == 1)
+    return 1;
+  gcc_unreachable ();
 }
 
 /* PREDICATE acts as a function of type:
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index dc4a4d4d200..0453ac563cc 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "common/common-target.h"
 #include "langhooks.h"
 #include "tree-inline.h"
+#include "ipa-strub.h"
 #include "toplev.h"
 #include "tree-iterator.h"
 #include "opts.h"
@@ -1306,104 +1307,57 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
-  tree orig_fnptr_type = NULL_TREE;
-  tree orig_args = args;
 
   if (args
       && POINTER_TYPE_P (*node)
       && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*node)))
-    {
-      orig_fnptr_type = *node;
-      *node = TREE_TYPE (orig_fnptr_type);
-    }
+    *node = TREE_TYPE (*node);
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
 	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
-
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
-
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
 
   if (args)
-    warning (OPT_Wattributes,
-	     "ignoring excess %qE attribute arguments starting at %qE",
-	     name, TREE_VALUE (args));
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
 
   /* If we see a strub-enabling attribute, and we're at the default setting,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
-
-  if (!*no_add_attrs)
-    {
-      *no_add_attrs = true;
-      if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
-      TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
-					   TYPE_ATTRIBUTES (*node));
-
-      if (orig_fnptr_type)
-	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
-	  TREE_TYPE (fnptr_type) = *node;
-	  *node = fnptr_type;
-	}
-    }
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   return NULL_TREE;
 }
diff --git a/gcc/common.opt b/gcc/common.opt
index 71e0c971344..ef018ebf735 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,13 +2687,22 @@ fstrict-overflow
 Common
 Treat signed overflow as undefined.  Negated as -fwrapv -fwrapv-pointer.
 
-; If any strub-enabling attribute is seen when the default value is
-; selected, it's bumped up to -1.  The scrub mode gate function will
-; then bump -2 to 0 if no strub-enabling attribute is seen.  This
-; minimizes the strub overhead.
-fstrub=default
-Common RejectNegative Var(flag_strub, -2) Init(-2)
-Enable stack scrub as requested through attributes.
+fstrub=disable
+Common RejectNegative Var(flag_strub, 0)
+Disable stack scrub entirely, disregarding strub attributes.
+
+fstrub=strict
+Common RejectNegative Var(flag_strub, -4)
+Enable stack scrub as per attributes, with strict call checking.
+
+; If any strub-enabling attribute is seen when the default or strict
+; initializer values are in effect, flag_strub is bumped up by 2.  The
+; scrub mode gate function will then bump these initializer values to
+; 0 if no strub-enabling attribute is seen.  This minimizes the strub
+; overhead.
+fstrub=relaxed
+Common RejectNegative Var(flag_strub, -3) Init(-3)
+Restore default strub mode: as per attributes, with relaxed checking.
 
 fstrub=all
 Common RejectNegative Var(flag_strub, 3)
@@ -2707,10 +2716,6 @@ fstrub=internal
 Common RejectNegative Var(flag_strub, 2)
 Enable internal stack scrubbing for all viable functions.
 
-fstrub=disable
-Common RejectNegative Var(flag_strub, 0)
-Disable stack scrub entirely, disregarding strub attributes.
-
 fsync-libcalls
 Common Var(flag_sync_libcalls) Init(1)
 Implement __atomic operations via libcalls to legacy __sync functions.
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 1fcf60a3875..d85e695d85c 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -77,7 +77,7 @@ extensions, accepted by GCC in C90 mode and in C++.
 * Function Names::      Printable strings which are the name of the current
                         function.
 * Return Address::      Getting the return or frame address of a function.
-* Stack Scrubbing::     Stack scrubbing interfaces.
+* Stack Scrubbing::     Stack scrubbing internal interfaces.
 * Vector Extensions::   Using vector instructions through built-in functions.
 * Offsetof::            Special syntax for implementing @code{offsetof}.
 * __sync Builtins::     Legacy built-in functions for atomic memory access.
@@ -8729,51 +8729,259 @@ pid_t wait (wait_status_ptr_t p)
 @item strub
 @cindex @code{strub} type attribute
 This attribute defines stack-scrubbing properties of functions and
-variables.  When applied to function types, it takes an optional
-argument.  When applied to a pointer-to-function type, it gets
-propagated to the function type if the optional argument is given.
-
-A function whose type is annotated with @code{at-calls} @code{strub}
-mode (@code{strub("at-calls")}, @code{strub(1)}, or @code{strub})
-undergoes interface changes.  Its callers are adjusted to match the
-changes, and to automatically scrub (overwrite with zeros) the stack
-space used by the function.
-
-A function whose type indicates @code{internal} @code{strub} mode
-(@code{strub("internal")} or @code{strub(2)}) retains an unmodified
-interface, but may be turned into a wrapper that calls the wrapped body
-using a custom interface.  The wrapper then scrubs the stack space used
-by the wrapped body.
-
-An automatically-allocated variable whose type carries the @code{strub}
-attribute causes the enclosing function to have @code{strub} enabled.
-
-A statically-allocated variable whose type carries the @code{strub}
-attribute causes functions that @emph{read} it with that type to have
-@code{strub} enabled.  Reading from data referenced by pointers to such
-types has the same effect.  Note: The attribute does not carry over from
-a composite type to the types of its components, so the intended effect
-may not be obtained with non-scalar types.
-
-A @code{strub} context is the body of a function that has strub enabled,
-be it explicitly, by @code{at-calls} or @code{internal} mode, or
-implicitly, by reading from a @code{strub}-marked data type.
+variables.  Being a type attribute, it attaches to types, even when
+specified in function and variable declarations.  When applied to
+function types, it takes an optional string argument.  When applied to a
+pointer-to-function type, if the optional argument is given, it gets
+propagated to the function type.
+
+@smallexample
+/* A strub variable.  */
+int __attribute__ ((strub)) var;
+/* A strub variable that happens to be a pointer.  */
+__attribute__ ((strub)) int *strub_ptr_to_int;
+/* A pointer type that may point to a strub variable.  */
+typedef int __attribute__ ((strub)) *ptr_to_strub_int_type;
+
+/* A declaration of a strub function.  */
+extern int __attribute__ ((strub)) foo (void);
+/* A pointer to that strub function.  */
+int __attribute__ ((strub ("at-calls"))) (*ptr_to_strub_fn)(void) = foo;
+@end smallexample
+
+A function associated with @code{at-calls} @code{strub} mode
+(@code{strub("at-calls")}, or just @code{strub}) undergoes interface
+changes.  Its callers are adjusted to match the changes, and to scrub
+(overwrite with zeros) the stack space used by the called function after
+it returns.  The interface change makes the function type incompatible
+with an unadorned but otherwise equivalent type, so @emph{every}
+declaration and every type that may be used to call the function must be
+associated with this strub mode.
+
+A function associated with @code{internal} @code{strub} mode
+(@code{strub("internal")}) retains an unmodified, type-compatible
+interface, but it may be turned into a wrapper that calls the wrapped
+body using a custom interface.  The wrapper then scrubs the stack space
+used by the wrapped body.  Though the wrapped body has its stack space
+scrubbed, the wrapper does not, so arguments and return values may
+remain unscrubbed even when such a function is called by another
+function that enables @code{strub}.  This is why, when compiling with
+@option{-fstrub=strict}, a @code{strub} context is not allowed to call
+@code{internal} @code{strub} functions.
+
+@smallexample
+/* A declaration of an internal-strub function.  */
+extern int __attribute__ ((strub ("internal"))) bar (void);
+
+int __attribute__ ((strub))
+baz (void)
+@{
+  /* Ok, foo was declared above as an at-calls strub function.  */
+  foo ();
+  /* Not allowed in strict mode, otherwise allowed.  */
+  bar ();
+@}
+@end smallexample
+
+An automatically-allocated variable associated with the @code{strub}
+attribute causes the (immediately) enclosing function to have
+@code{strub} enabled.
+
+A statically-allocated variable associated with the @code{strub}
+attribute causes functions that @emph{read} it, through its @code{strub}
+data type, to have @code{strub} enabled.  Reading data by dereferencing
+a pointer to a @code{strub} data type has the same effect.  Note: The
+attribute does not carry over from a composite type to the types of its
+components, so the intended effect may not be obtained with non-scalar
+types.
+
+When selecting a @code{strub}-enabled mode for a function that is not
+explicitly associated with one, because of @code{strub} variables or
+data pointers, the function must satisfy @code{internal} mode viability
+requirements (see below), even when @code{at-calls} mode is also viable
+and, being more efficient, ends up selected as an optimization.
+
+@smallexample
+/* zapme is implicitly strub-enabled because of strub variables.
+   Optimization may change its strub mode, but not the requirements.  */
+static int
+zapme (int i)
+@{
+  /* A local strub variable enables strub.  */
+  int __attribute__ ((strub)) lvar;
+  /* Reading strub data through a pointer-to-strub enables strub.  */
+  lvar = * (ptr_to_strub_int_type) &i;
+  /* Writing to a global strub variable does not enable strub.  */
+  var = lvar;
+  /* Reading from a global strub variable enables strub.  */
+  return var;
+@}
+@end smallexample
+
+A @code{strub} context is the body (as opposed to the interface) of a
+function that has @code{strub} enabled, be it explicitly, by
+@code{at-calls} or @code{internal} mode, or implicitly, due to
+@code{strub} variables or command-line options.
 
 A function of a type associated with the @code{disabled} @code{strub}
-mode (@code{strub("disabled")}, @code{strub(0)}, or no @code{strub} mode
-specified) will not have its own stack space scrubbed, and it cannot be
-called from @code{strub} contexts.
+mode (@code{strub("disabled")} will not have its own stack space
+scrubbed.  Such functions @emph{cannot} be called from within
+@code{strub} contexts.
+
+In order to enable a function to be called from within @code{strub}
+contexts without having its stack space scrubbed, associate it with the
+@code{callable} @code{strub} mode (@code{strub("callable")}).
+
+When a function is not assigned a @code{strub} mode, explicitly or
+implicitly, the mode defaults to @code{callable}, except when compiling
+with @option{-fstrub=strict}, that causes @code{strub} mode to default
+to @code{disabled}.
+
+@example
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+ /* Implicitly disabled with -fstrub=strict, otherwise callable.  */
+extern int bah (void);
+
+int __attribute__ ((strub))
+bal (void)
+@{
+  /* Not allowed, bad is not strub-callable.  */
+  bad ();
+  /* Ok, bac is strub-callable.  */
+  bac ();
+  /* Not allowed with -fstrub=strict, otherwise allowed.  */
+  bah ();
+@}
+@end example
 
-A function that does not have @code{strub} enabled can only be called
-from within @code{strub} contexts through a function type marked with
-the @code{callable} @code{strub} mode (@code{strub("callable")} or
-@code{strub(3)}).
+Function types marked @code{callable} and @code{disabled} are not
+mutually compatible types, but the underlying interfaces are compatible,
+so it is safe to convert pointers between them, and to use such pointers
+or alternate declarations to call them.  Interfaces are also
+interchangeable between them and @code{internal} (but not
+@code{at-calls}!), but adding @code{internal} to a pointer type will not
+cause the pointed-to function to perform stack scrubbing.
+
+@example
+void __attribute__ ((strub))
+bap (void)
+@{
+  /* Assign a callable function to pointer-to-disabled.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bac;
+  /* Not allowed: calls disabled type in a strub context.  */
+  d_p ();
+
+  /* Assign a disabled function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bad;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an internal function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  c_p = bar;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an at-calls function to pointer-to-callable.
+     Flaggged as incompatible.  */
+  c_p = bal;
+  /* The call through an interface-incompatible type will not use the
+     modified interface expected by the at-calls function, so it is
+     likely to misbehave at runtime.  */
+  c_p ();
+@}
+@end example
 
 @code{Strub} contexts are never inlined into non-@code{strub} contexts.
-When an @code{internal}-strub function is split, the wrapper can often
-be inlined, but the wrapped body never is.  Functions marked as
-@code{always_inline}, even if explicitly assigned @code{internal} strub
-mode, will not undergo wrapping, so their body gets inlined.
+When an @code{internal}-strub function is split up, the wrapper can
+often be inlined, but the wrapped body @emph{never} is.  A function
+marked as @code{always_inline}, even if explicitly assigned
+@code{internal} strub mode, will not undergo wrapping, so its body gets
+inlined as required.
+
+@example
+inline int __attribute__ ((strub ("at-calls")))
+inl_atc (void)
+@{
+  /* This body may get inlined into strub contexts.  */
+@}
+
+inline int __attribute__ ((strub ("internal")))
+inl_int (void)
+@{
+  /* This body NEVER gets inlined, though its wrapper may.  */
+@}
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+@{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+@}
+
+void __attribute__ ((strub ("disabled")))
+bat (void)
+@{
+  /* Not allowed, cannot inline into a non-strub context.  */
+  inl_int_ali ();
+@}
+@end example
+
+@cindex strub eligibility and viability
+Some @option{-fstrub=*} command line options enable @code{strub} modes
+implicitly where viable.  A @code{strub} mode is only viable for a
+function if the function is eligible for that mode, and if other
+conditions, detailed below, are satisfied.  If it's not eligible for a
+mode, attempts to explicitly associate it with that mode are rejected
+with an error message.  If it is eligible, that mode may be assigned
+explicitly through this attribute, but implicit assignment through
+command-line options may involve additional viability requirements.
+
+A function is ineligible for @code{at-calls} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+@code{At-calls} @code{strub} mode, if not requested through the function
+type, is only viable for an eligible function if the function is not
+visible to other translation units, and if it doesn't have its address
+taken.
+
+@smallexample
+/* bar is eligible for at-calls strub mode,
+   but not viable for that mode because it is visible to other units.
+   It is eligible and viable for internal strub mode.  */
+void bav () @{@}
+
+/* setp is eligible for at-calls strub mode,
+   but not viable for that mode because its address is taken.
+   It is eligible and viable for internal strub mode.  */
+void setp (void) @{ static void (*p)(void); = setp; @}
+@end smallexample
+
+A function is ineligible for @code{internal} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, or if attribute
+@code{noipa} is present.  For an @code{always_inline} function, meeting
+these requirements is enough to make it eligible.  Any function that has
+attribute @code{noclone}, that uses such extensions as non-local labels,
+computed gotos, alternate variable argument passing interfaces,
+@code{__builtin_next_arg}, or @code{__builtin_return_address}, or that
+takes too many (about 64Ki) arguments is ineligible, unless it is
+@code{always_inline}.  For @code{internal} @code{strub} mode, all
+eligible functions are viable.
+
+@smallexample
+/* flop is not eligible, thus not viable, for at-calls strub mode.
+   Likewise for internal strub mode.  */
+__attribute__ ((noipa)) void flop (void) @{@}
+
+/* flip is eligible and viable for at-calls strub mode.
+   It would be ineligible for internal strub mode, because of noclone,
+   if it weren't for always_inline.  With always_inline, noclone is not
+   an obstacle, so it is also eligible and viable for internal strub mode.  */
+inline __attribute__ ((noclone, always_inline)) void flip (void) @{@}
+@end smallexample
 
 @item unused
 @cindex @code{unused} type attribute
@@ -11804,40 +12012,48 @@ This function returns the value of the stack pointer register.
 @end deftypefn
 
 @node Stack Scrubbing
-@section Stack scrubbing interfaces
+@section Stack scrubbing internal interfaces
 
 Stack scrubbing involves cooperation between a @code{strub} context,
 i.e., a function whose stack frame is to be zeroed-out, and its callers.
 The caller initializes a stack watermark, the @code{strub} context
-updates the watermark to reflect its stack use, and the caller zeroes it
-out once it regains control.
-
-Each of these steps relies on a different builtin function call.  The
-functions are available in libgcc but, depending on optimization levels,
-they are expanded internally, adjusted to account for inlining, or
-combined/deferred (e.g. passing the caller-supplied watermark on to
-callees, refraining from erasing stack areas that the caller will).
+updates the watermark according to its stack use, and the caller zeroes
+it out once it regains control, whether by the callee's returning or by
+an exception.
+
+Each of these steps is performed by a different builtin function call.
+Calls to these builtins are introduced automatically, in response to
+@code{strub} attributes and command-line options; they are not expected
+to be explicitly called by source code.
+
+The functions that implement the builtins are available in libgcc but,
+depending on optimization levels, they are expanded internally, adjusted
+to account for inlining, and sometimes combined/deferred (e.g. passing
+the caller-supplied watermark on to callees, refraining from erasing
+stack areas that the caller will) to enable tail calls and to optimize
+for code size.
 
 @deftypefn {Built-in Function} {void} __builtin___strub_enter (void **@var{wmptr})
 This function initializes a stack @var{watermark} variable with the
-current top of the stack.  This builtin function should be called before
-entering a @code{strub} context.  It remains as a function call if optimization
-is not enabled.
+current top of the stack.  A call to this builtin function is introduced
+before entering a @code{strub} context.  It remains as a function call
+if optimization is not enabled.
 @end deftypefn
 
 @deftypefn {Built-in Function} {void} __builtin___strub_update (void **@var{wmptr})
 This function updates a stack @var{watermark} variable with the current
-top of the stack, if it tops the previous watermark.  This builtin
-function should be called within a @code{strub} context whenever
+top of the stack, if it tops the previous watermark.  A call to this
+builtin function is inserted within @code{strub} contexts, whenever
 additional stack space may have been used.  It remains as a function
 call at optimization levels lower than 2.
 @end deftypefn
 
 @deftypefn {Built-in Function} {void} __builtin___strub_leave (void **@var{wmptr})
 This function overwrites the memory area between the current top of the
-stack, and the @var{watermark}ed address.  This builtin function should
-be called after leaving a @code{strub} context.  It remains as a
-function call at optimization levels lower than 3.
+stack, and the @var{watermark}ed address.  A call to this builtin
+function is inserted after leaving a @code{strub} context.  It remains
+as a function call at optimization levels lower than 3, and it is guarded by
+a condition at level 2.
 @end deftypefn
 
 @node Vector Extensions
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 838d9ff1198..afcb37914ac 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,7 +599,8 @@ Objective-C and Objective-C++ Dialects}.
 -fstack-protector-explicit  -fstack-check @gol
 -fstack-limit-register=@var{reg}  -fstack-limit-symbol=@var{sym} @gol
 -fno-stack-limit  -fsplit-stack @gol
--fstrub=default -fstrub=disable -fstrub=at-calls -fstrub=internal -fstrub=all @gol
+-fstrub=disable -fstrub=strict -fstrub=relaxed @gol
+-fstrub=all -fstrub=at-calls -fstrub=internal @gol
 -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
 -fvtv-counts  -fvtv-debug @gol
 -finstrument-functions @gol
@@ -15513,46 +15514,55 @@ without @option{-fsplit-stack} always has a large stack.  Support for
 this is implemented in the gold linker in GNU binutils release 2.21
 and later.
 
-@item -fstrub=default
-@opindex fstrub=default
-Restore the default stack scrub (@code{strub}) setting, namely,
-@code{strub} is only enabled as required by @code{strub} attributes
-associated with function or variable types.  This is only useful to
-override earlier @samp{-fstrub=*} options.
-
 @item -fstrub=disable
 @opindex -fstrub=disable
 Disable stack scrubbing entirely, ignoring any @code{strub} attributes.
+See @xref{Common Type Attributes}.
+
+@item -fstrub=strict
+@opindex fstrub=strict
+Functions default to @code{strub} mode @code{disabled}, and apply
+@option{strict}ly the restriction that only functions associated with
+@code{strub}-@code{callable} modes (@code{at-calls}, @code{callable} and
+@code{always_inline} @code{internal}) are @code{callable} by functions
+with @code{strub}-enabled modes (@code{at-calls} and @code{internal}).
+
+@item -fstrub=relaxed
+@opindex fstrub=relaxed
+Restore the default stack scrub (@code{strub}) setting, namely,
+@code{strub} is only enabled as required by @code{strub} attributes
+associated with function and data types.  @code{Relaxed} means that
+strub contexts are only prevented from calling functions explicitly
+associated with @code{strub} mode @code{disabled}.  This option is only
+useful to override other @option{-fstrub=*} options that precede it in
+the command line.
 
 @item -fstrub=at-calls
 @opindex fstrub=at-calls
-Enable @code{at-calls} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{at-calls} @code{strub} if a different @code{strub}
-mode is explicitly requested, if attribute @code{noipa} is present, or
-if it calls @code{__builtin_apply_args}.  @code{At-calls} @code{strub}
-mode, if not requested through the function type, is only viable for an
-eligible function if the function is not visible to other translation
-units, and it doesn't have its address taken.
+Enable @code{at-calls} @code{strub} mode where viable.  The primary use
+of this option is for testing.  It exercises the @code{strub} machinery
+in scenarios strictly local to a translation unit.  This @code{strub}
+mode modifies function interfaces, so any function that is visible to
+other translation units, or that has its address taken, will @emph{not}
+be affected by this option.  Optimization options may also affect
+viability.  See the @code{strub} attribute documentation for details on
+viability and eligibility requirements.
 
 @item -fstrub=internal
 @opindex fstrub=internal
-Enable @code{internal} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{internal} @code{strub} if a different @code{strub}
-mode is explicitly requested, or if attribute @code{noipa} is present.
-Non-@code{always_inline} functions also become ineligible if attribute
-@code{noclone} is present, if the function uses such features as user
-labels, non-default variable argument interfaces,
-@code{__builtin_next_arg}, or @code{__builtin_return_address}, or if
-they have too many (about 64Ki) arguments.  For @code{internal}
-@code{strub}, all eligible functions are viable.
+Enable @code{internal} @code{strub} mode where viable.  The primary use
+of this option is for testing.  This option is intended to exercise
+thoroughly parts of the @code{strub} machinery that implement the less
+efficient, but interface-preserving @code{strub} mode.  Functions that
+would not be affected by this option are quite uncommon.
 
 @item -fstrub=all
 @opindex fstrub=all
-Enable @code{strub} for all viable functions, and consider non-viable
-functions as @code{callable}.  When both strub modes are viable,
-@code{at-calls} is preferred.
+Enable some @code{strub} mode where viable.  When both strub modes are
+viable, @code{at-calls} is preferred.  @option{-fdump-ipa-strubm} adds
+function attributes that tell which mode was selected for each function.
+The primary use of this option is for testing, to exercise thoroughly
+the @code{strub} machinery.
 
 @item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 @opindex fvtable-verify
@@ -17427,6 +17437,14 @@ and inlining decisions.
 @item inline
 Dump after function inlining.
 
+@item strubm
+Dump after selecting @code{strub} modes, and recording the selections as
+function attributes.
+
+@item strub
+Dump @code{strub} transformations: interface changes, function wrapping,
+and insertion of builtin calls for stack scrubbing and watermarking.
+
 @end table
 
 Additionally, the options @option{-optimized}, @option{-missed},
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 7f4bc44d2bb..932e49dba8e 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -397,7 +397,7 @@ can_inline_edge_p (struct cgraph_edge *e, bool report,
       e->inline_failed = CIF_SANITIZE_ATTRIBUTE_MISMATCH;
       inlinable = false;
     }
-  if (!strub_inlinable_p (callee, caller))
+  if (!strub_inlinable_to_p (callee, caller))
     {
       e->inline_failed = CIF_UNSPECIFIED;
       inlinable = false;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
index cf7d811779e..159cb4df2f2 100644
--- a/gcc/ipa-strub.c
+++ b/gcc/ipa-strub.c
@@ -159,12 +159,16 @@ enum strub_mode {
 
 };
 
+/* Look up a strub attribute in TYPE, and return it.  */
+
 static tree
 get_strub_attr_from_type (tree type)
 {
   return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
 }
 
+/* Look up a strub attribute in DECL or in its type, and return it.  */
+
 static tree
 get_strub_attr_from_decl (tree decl)
 {
@@ -174,23 +178,161 @@ get_strub_attr_from_decl (tree decl)
   return get_strub_attr_from_type (TREE_TYPE (decl));
 }
 
-tree
+/* Define a function to cache identifier ID, to be used as a strub attribute
+   parameter for a strub mode named after NAME.  */
+#define DEF_STRUB_IDS(NAME, ID)				\
+static inline tree get_strub_mode_id_ ## NAME () {	\
+  static tree identifier = NULL_TREE;			\
+  if (!identifier)					\
+    identifier = get_identifier (ID);			\
+  return identifier;					\
+}
+/* Same as DEF_STRUB_IDS, but use the string expansion of NAME as ID.  */
+#define DEF_STRUB_ID(NAME)				\
+DEF_STRUB_IDS (NAME, #NAME)
+
+/* Define functions for each of the strub mode identifiers.
+   Expose dashes rather than underscores.  */
+DEF_STRUB_ID (disabled)
+DEF_STRUB_IDS (at_calls, "at-calls")
+DEF_STRUB_ID (internal)
+DEF_STRUB_ID (callable)
+DEF_STRUB_ID (wrapped)
+DEF_STRUB_ID (wrapper)
+DEF_STRUB_ID (inlinable)
+DEF_STRUB_IDS (at_calls_opt, "at-calls-opt")
+
+/* Release the temporary macro names.  */
+#undef DEF_STRUB_IDS
+#undef DEF_STRUB_ID
+
+/* Return the identifier corresponding to strub MODE.  */
+
+static tree
+get_strub_mode_attr_parm (enum strub_mode mode)
+{
+  switch (mode)
+    {
+    case STRUB_DISABLED:
+      return get_strub_mode_id_disabled ();
+
+    case STRUB_AT_CALLS:
+      return get_strub_mode_id_at_calls ();
+
+    case STRUB_INTERNAL:
+      return get_strub_mode_id_internal ();
+
+    case STRUB_CALLABLE:
+      return get_strub_mode_id_callable ();
+
+    case STRUB_WRAPPED:
+      return get_strub_mode_id_wrapped ();
+
+    case STRUB_WRAPPER:
+      return get_strub_mode_id_wrapper ();
+
+    case STRUB_INLINABLE:
+      return get_strub_mode_id_inlinable ();
+
+    case STRUB_AT_CALLS_OPT:
+      return get_strub_mode_id_at_calls_opt ();
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+/* Return the parmeters (TREE_VALUE) for a strub attribute of MODE.
+   We know we use a single parameter, so we bypass the creation of a
+   tree list.  */
+
+static tree
 get_strub_mode_attr_value (enum strub_mode mode)
 {
-  return tree_cons (NULL_TREE,
-		    build_int_cst (integer_type_node, (int)mode),
-		    NULL_TREE);
+  return get_strub_mode_attr_parm (mode);
+}
+
+/* Determine whether ID is a well-formed strub mode-specifying attribute
+   parameter for a function (type).  Only user-visible modes are accepted, and
+   ID must be non-NULL.
+
+   For unacceptable parms, return 0, otherwise a nonzero value as below.
 
-#if 0 /* ??? use symbolic mode names with interned strings?  */
-  char *s = NULL;
+   If the parm enables strub, return positive, otherwise negative.
 
-  switch (strub_mode)
+   If the affected type must be a distinct, incompatible type,return an integer
+   of absolute value 2, otherwise 1.  */
+
+int
+strub_validate_fn_attr_parm (tree id)
+{
+  int ret;
+  const char *s = NULL;
+  size_t len = 0;
+
+  /* do NOT test for NULL.  This is only to be called with non-NULL arguments.
+     We assume that the strub parameter applies to a function, because only
+     functions accept an explicit argument.  If we accepted NULL, and we
+     happened to be called to verify the argument for a variable, our return
+     values would be wrong.  */
+  if (TREE_CODE (id) == STRING_CST)
     {
-      
+      s = TREE_STRING_POINTER (id);
+      len = TREE_STRING_LENGTH (id) - 1;
     }
-#endif
+  else if (TREE_CODE (id) == IDENTIFIER_NODE)
+    {
+      s = IDENTIFIER_POINTER (id);
+      len = IDENTIFIER_LENGTH (id);
+    }
+  else
+    return 0;
+
+  enum strub_mode mode;
+
+  if (len != 8)
+    return 0;
+
+  switch (s[0])
+    {
+    case 'd':
+      mode = STRUB_DISABLED;
+      ret = -1;
+      break;
+
+    case 'a':
+      mode = STRUB_AT_CALLS;
+      ret = 2;
+      break;
+
+    case 'i':
+      mode = STRUB_INTERNAL;
+      ret = 1;
+      break;
+
+    case 'c':
+      mode = STRUB_CALLABLE;
+      ret = -2;
+      break;
+
+    default:
+      /* Other parms are for internal use only.  */
+      return 0;
+    }
+
+  tree mode_id = get_strub_mode_attr_parm (mode);
+
+  if (TREE_CODE (id) == IDENTIFIER_NODE
+      ? id != mode_id
+      : strncmp (s, IDENTIFIER_POINTER (mode_id), len) != 0)
+    return 0;
+
+  return ret;
 }
 
+/* Return the strub mode from STRUB_ATTR.  VAR_P should be true if the attribute
+   is taken from a variable, rather than from a function, or a type thereof.  */
+
 static enum strub_mode
 get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
 {
@@ -200,28 +342,102 @@ get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
     {
       if (!TREE_VALUE (strub_attr))
 	mode = !var_p ? STRUB_AT_CALLS : STRUB_INTERNAL;
-      else if (TREE_CODE (TREE_VALUE (TREE_VALUE (strub_attr))) == INTEGER_CST)
-	mode = (enum strub_mode) tree_to_shwi (TREE_VALUE
-					       (TREE_VALUE (strub_attr)));
-      else /* Handlers convert symbolic mode names to INTEGER_CST.  */
-	gcc_unreachable ();
+      else
+	{
+	  gcc_checking_assert (!var_p);
+	  tree id = TREE_VALUE (strub_attr);
+	  if (TREE_CODE (id) == TREE_LIST)
+	    id = TREE_VALUE (id);
+	  const char *s = (TREE_CODE (id) == STRING_CST
+			   ? TREE_STRING_POINTER (id)
+			   : IDENTIFIER_POINTER (id));
+	  size_t len = (TREE_CODE (id) == STRING_CST
+			? TREE_STRING_LENGTH (id) - 1
+			: IDENTIFIER_LENGTH (id));
+
+	  switch (len)
+	    {
+	    case 7:
+	      switch (s[6])
+		{
+		case 'r':
+		  mode = STRUB_WRAPPER;
+		  break;
+
+		case 'd':
+		  mode = STRUB_WRAPPED;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 8:
+	      switch (s[0])
+		{
+		case 'd':
+		  mode = STRUB_DISABLED;
+		  break;
+
+		case 'a':
+		  mode = STRUB_AT_CALLS;
+		  break;
+
+		case 'i':
+		  mode = STRUB_INTERNAL;
+		  break;
+
+		case 'c':
+		  mode = STRUB_CALLABLE;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 9:
+	      mode = STRUB_INLINABLE;
+	      break;
+
+	    case 12:
+	      mode = STRUB_AT_CALLS_OPT;
+	      break;
+
+	    default:
+	      gcc_unreachable ();
+	    }
+
+	  gcc_checking_assert (TREE_CODE (id) == IDENTIFIER_NODE
+			       ? id == get_strub_mode_attr_parm (mode)
+			       : strncmp (IDENTIFIER_POINTER
+					  (get_strub_mode_attr_parm (mode)),
+					  s, len) == 0);
+	}
     }
 
   return mode;
 }
 
+/* Look up, decode and return the strub mode associated with FNDECL.  */
+
 static enum strub_mode
-get_strub_mode_from_decl (tree fndecl)
+get_strub_mode_from_fndecl (tree fndecl)
 {
   return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
 }
 
+/* Look up, decode and return the strub mode associated with NODE.  */
+
 static enum strub_mode
 get_strub_mode (cgraph_node *node)
 {
-  return get_strub_mode_from_decl (node->decl);
+  return get_strub_mode_from_fndecl (node->decl);
 }
 
+/* Look up, decode and return the strub mode associated with TYPE.  */
+
 static enum strub_mode
 get_strub_mode_from_type (tree type)
 {
@@ -231,12 +447,15 @@ get_strub_mode_from_type (tree type)
   if (attr)
     return get_strub_mode_from_attr (attr, var_p);
 
-  if (flag_strub > 0 && !var_p)
+  if (flag_strub >= -1 && !var_p)
     return STRUB_CALLABLE;
 
   return STRUB_DISABLED;
 }
 
+\f
+/* Return true iff NODE calls builtin va_start.  */
+
 static bool
 calls_builtin_va_start_p (cgraph_node *node)
 {
@@ -252,6 +471,8 @@ calls_builtin_va_start_p (cgraph_node *node)
   return result;
 }
 
+/* Return true iff NODE calls builtin apply_args, and optionally REPORT it.  */
+
 static bool
 calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
 {
@@ -276,12 +497,17 @@ calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE carries the always_inline attribute.  */
+
 static inline bool
 strub_always_inline_p (cgraph_node *node)
 {
   return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
 }
 
+/* Return true iff NODE is potentially eligible for any strub-enabled mode, and
+   optionally REPORT the reasons for ineligibility.  */
+
 static inline bool
 can_strub_p (cgraph_node *node, bool report = false)
 {
@@ -321,6 +547,10 @@ can_strub_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE is eligible for at-calls strub, and optionally REPORT
+   the reasons for ineligibility.  Besides general non-eligibility for
+   strub-enabled modes, at-calls rules out calling builtin apply_args.  */
+
 static bool
 can_strub_at_calls_p (cgraph_node *node, bool report = false)
 {
@@ -332,6 +562,9 @@ can_strub_at_calls_p (cgraph_node *node, bool report = false)
   return !calls_builtin_apply_args_p (node, report);
 }
 
+/* Symbolic macro for the max number of arguments that internal strub may add to
+   a function.  */
+
 #define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
 
 /* We can't perform internal strubbing if the function body involves certain
@@ -438,14 +671,12 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 			   || tree_versionable_function_p (node->decl));
 
 
-      /* Label values referenced are not preserved when copying.  If referenced
+      /* Label values references are not preserved when copying.  If referenced
 	 in nested functions, as in 920415-1.c and 920721-4.c their decls get
-	 remapped independently.  That might be too broad, in that we might be
-	 able to support correctly cases in which the labels are only used
-	 internally in a function, but disconnecting user labels from their
-	 original declarations is undesirable in general, and it probably
-	 doesn't matter, since explicitly-requested strub likely uses
-	 STRUB_AT_CALLS mode anyway.  */
+	 remapped independently.  The exclusion below might be too broad, in
+	 that we might be able to support correctly cases in which the labels
+	 are only used internally in a function, but disconnecting forced labels
+	 from their original declarations is undesirable in general.  */
       basic_block bb;
       FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
 	for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -459,8 +690,6 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 
 	    target = gimple_label_label (label_stmt);
 
-	    /* Make an edge to every label block that has been marked as a
-	       potential target for a computed goto or a non-local goto.  */
 	    if (!FORCED_LABEL (target))
 	      continue;
 
@@ -470,7 +699,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 forced labels");
 	  }
     }
 
@@ -491,6 +720,9 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return TRUE iff NODE has any strub-requiring local variable, or accesses (as
+   in reading) any variable through a strub-requiring type.  */
+
 static bool
 strub_from_body_p (cgraph_node *node)
 {
@@ -506,7 +738,9 @@ strub_from_body_p (cgraph_node *node)
 	!= STRUB_DISABLED)
       return true;
 
-  /* Now scan the body for loads with strub types.  */
+  /* Now scan the body for loads with strub-requiring types.
+     ??? Compound types don't propagate the strub requirement to
+     component types.  */
   basic_block bb;
   FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
     for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -528,6 +762,7 @@ strub_from_body_p (cgraph_node *node)
 
 /* Return true iff node is associated with a builtin that should be callable
    from strub contexts.  */
+
 static inline bool
 strub_callable_builtin_p (cgraph_node *node)
 {
@@ -568,17 +803,23 @@ strub_callable_builtin_p (cgraph_node *node)
     }
 }
 
+/* Compute the strub mode to be used for NODE.  STRUB_ATTR should be the strub
+   attribute,found for NODE, if any.  */
+
 static enum strub_mode
 compute_strub_mode (cgraph_node *node, tree strub_attr)
 {
   enum strub_mode req_mode = get_strub_mode_from_attr (strub_attr);
 
-  gcc_checking_assert (flag_strub >= -1 && flag_strub <= 3);
+  gcc_checking_assert (flag_strub >= -2 && flag_strub <= 3);
 
   /* Symbolic encodings of the -fstrub-* flags.  */
   /* Enable strub when explicitly requested through attributes to functions or
      variables, reporting errors if the requests cannot be satisfied.  */
   const bool strub_flag_auto = flag_strub < 0;
+  /* strub_flag_auto with strub call verification; without this, functions are
+     implicitly callable.  */
+  const bool strub_flag_strict = flag_strub < -1;
   /* Disable strub altogether, ignore attributes entirely.  */
   const bool strub_flag_disabled = flag_strub == 0;
   /* On top of _auto, also enable strub implicitly for functions that can
@@ -625,7 +866,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (!strub_flag_disabled
        && (strub_attr
 	   ? req_mode == STRUB_CALLABLE
-	   : (strub_flag_viable
+	   : (!strub_flag_strict
 	      || strub_callable_builtin_p (node))));
 
   /* This is a shorthand for either strub-enabled mode.  */
@@ -674,19 +915,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (at_calls_eligible
        && (strub_attr
 	   || (node->has_gimple_body_p ()
-#if 0 /* We no longer use collect_callers, so we can probably drop it.  */
-	       && node->get_availability () > AVAIL_INTERPOSABLE
-#endif
-	       && ((!node->externally_visible
-#if 0
-		    /* We wish to bypass the test below for functions that are
-		       not externally visible, but that's a little too broad: we
-		       do not wish to skip them for e.g. gnu_inline
-		       functions.  */
-		    && !TREE_PUBLIC (node->decl)
-		    && !DECL_EXTERNAL (node->decl)
-#endif
-		    )
+	       && (!node->externally_visible
 		   || (node->binds_to_current_def_p ()
 		       && node->can_be_local_p ()))
 	       && node->only_called_directly_p ())));
@@ -737,10 +966,6 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
   const enum strub_mode mode
     = ((strub_enable && is_always_inline)
        ? (strub_required ? STRUB_INLINABLE : STRUB_CALLABLE)
-#if 0
-       : (!strub_enable && strub_required && strub_attr)
-       ? req_mode
-#endif
        : (strub_enable && internal_viable
 	  && (strub_flag_internal || !at_calls_viable))
        ? STRUB_INTERNAL
@@ -802,6 +1027,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 /* Set FNDT's strub mode to MODE; FNDT may be a function decl or
    function type.  If OVERRIDE, do not check whether a mode is already
    set.  */
+
 static void
 strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
 {
@@ -828,12 +1054,18 @@ strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
   *attrp = attr;
 }
 
+/* Set FNDT's strub mode to callable.
+   FNDT may be a function decl or a function type.  */
+
 void
 strub_make_callable (tree fndt)
 {
   strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
 }
 
+/* Set NODE to strub MODE.  Report incompatibilities between MODE and the mode
+   requested through explicit attributes, and cases of non-eligibility.  */
+
 static void
 set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
 {
@@ -853,9 +1085,10 @@ 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",
-		    (int) mode, node->decl,
-		    (int) get_strub_mode_from_attr (attr));
+		    "%<strub%> mode %qE selected for %qD, when %qE was requested",
+		    get_strub_mode_attr_parm (mode),
+		    node->decl,
+		    get_strub_mode_attr_parm (req_mode));
 	  if (node->alias)
 	    {
 	      cgraph_node *target = node->ultimate_alias_target ();
@@ -906,6 +1139,8 @@ set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
   strub_set_fndt_mode_to (node->decl, mode, attr);
 }
 
+/* Compute and set NODE's strub mode.  */
+
 static void
 set_strub_mode (cgraph_node *node)
 {
@@ -946,6 +1181,7 @@ set_strub_mode (cgraph_node *node)
   set_strub_mode_to (node, mode);
 }
 
+\f
 /* Non-strub functions shouldn't be called from within strub contexts,
    except through callable ones.  Always inline strub functions can
    only be called from strub functions.  */
@@ -984,7 +1220,7 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_AT_CALLS_OPT:
     case STRUB_INTERNAL:
     case STRUB_WRAPPER:
-      return (flag_strub >= 0);
+      return (flag_strub >= -1);
 
     case STRUB_DISABLED:
       return false;
@@ -999,12 +1235,16 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
   return true;
 }
 
-/* We wish to avoid inlining WRAPPED functions back into their
-   WRAPPERs.  More generally, we wish to avoid inlining
-   strubbed functions into non-strubbed ones.  */
+/* Return true if CALLEE can be inlined into CALLER.  We wish to avoid inlining
+   WRAPPED functions back into their WRAPPERs.  More generally, we wish to avoid
+   inlining strubbed functions into non-strubbed ones.  CALLER doesn't have to
+   be an immediate caller of CALLEE: the immediate caller may have already been
+   cloned for inlining, and then CALLER may be further up the original call
+   chain.  ???  It would be nice if our own caller would retry inlining callee
+   if caller gets inlined.  */
 
 bool
-strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+strub_inlinable_to_p (cgraph_node *callee, cgraph_node *caller)
 {
   strub_mode callee_mode = get_strub_mode (callee);
 
@@ -1020,6 +1260,10 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_WRAPPER:
     case STRUB_DISABLED:
     case STRUB_CALLABLE:
+      /* When we consider inlining, we've already verified callability, so we
+	 can even inline callable and then disabled into a strub context.  That
+	 will get strubbed along with the context, so it's hopefully not a
+	 problem.  */
       return true;
 
     default:
@@ -1049,20 +1293,50 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
   return false;
 }
 
+/* Check that types T1 and T2 are strub-compatible.  Return 1 if the strub modes
+   are the same, 2 if they are interchangeable, and 0 otherwise.  */
+
+int
+strub_comptypes (tree t1, tree t2)
+{
+  enum strub_mode m1 = get_strub_mode_from_type (t1);
+  enum strub_mode m2 = get_strub_mode_from_type (t2);
+
+  if (m1 == m2)
+    return 1;
+
+  /* We're dealing with types, so only strub modes that can be selected by
+     attributes in the front end matter.  If either mode is at-calls, the
+     conversion is not compatible.  */
+  if (m1 == STRUB_AT_CALLS || m2 == STRUB_AT_CALLS)
+    return 0;
+
+  return 2;
+}
+
 /* Check that strub functions don't call non-strub functions, and that
    always_inline strub functions are only called by strub
    functions.  */
+
 static void
 verify_strub ()
 {
   cgraph_node *node;
 
+  /* It's expected that check strub-wise pointer type compatibility of variables
+     and of functions is already taken care of by front-ends, on account of the
+     attribute's being marked as affecting type identity and of the creation of
+     distinct types.  */
+
+  /* Check that call targets in strub contexts have strub-callable types.  */
+
   FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
   {
     enum strub_mode caller_mode = get_strub_mode (node);
     bool strub_context
       = (caller_mode == STRUB_AT_CALLS
 	 || caller_mode == STRUB_AT_CALLS_OPT
+	 || caller_mode == STRUB_INTERNAL
 	 || caller_mode == STRUB_WRAPPED
 	 || caller_mode == STRUB_INLINABLE);
 
@@ -1105,14 +1379,11 @@ verify_strub ()
 	  }
       }
   }
-
-  /* ??? Check strub-wise pointer type compatibility of variables and
-     functions, or is this already taken care of on account of the
-     attribute's being marked as affecting type identity?  */
 }
 
 namespace {
 
+/* Define a pass to compute strub modes.  */
 const pass_data pass_data_ipa_strub_mode = {
   SIMPLE_IPA_PASS,
   "strubm",
@@ -1133,24 +1404,26 @@ public:
   {}
   opt_pass *clone () { return new pass_ipa_strub_mode (m_ctxt); }
   virtual bool gate (function *) {
-    /* In the default setting, the attribute handler changes
-       flag_strub to -1 if any strub-enabling occurence of the
-       attribute is found.  If it remains at -2, nothing that would
-       enable strub was found, so we can disable it and avoid the
+    /* In relaxed (-3) and strict (-4) settings, that only enable strub at a
+       function or variable attribute's request, the attribute handler changes
+       flag_strub to -1 or -2, respectively, if any strub-enabling occurence of
+       the attribute is found.  Therefore, if it remains at -3 or -4, nothing
+       that would enable strub was found, so we can disable it and avoid the
        overhead.  */
-    if (flag_strub == -2)
+    if (flag_strub < -2)
       flag_strub = 0;
     return flag_strub;
   }
   virtual unsigned int execute (function *);
 };
 
+/* Define a pass to introduce strub transformations.  */
 const pass_data pass_data_ipa_strub = {
   SIMPLE_IPA_PASS,
   "strub",
   OPTGROUP_NONE,
   TV_NONE,
-  PROP_cfg, // properties_required
+  PROP_cfg | PROP_ssa, // properties_required
   0,	    // properties_provided
   0,	    // properties_destroyed
   0,	    // properties_start
@@ -1167,9 +1440,10 @@ public:
     : simple_ipa_opt_pass (pass_data_ipa_strub, ctxt)
   {}
   opt_pass *clone () { return new pass_ipa_strub (m_ctxt); }
-  virtual bool gate (function *) { return flag_strub; }
+  virtual bool gate (function *) { return flag_strub && !seen_error (); }
   virtual unsigned int execute (function *);
 
+  /* Define on demand and cache some types we use often.  */
 #define DEF_TYPE(NAME, INIT)			\
   static inline tree get_ ## NAME () {		\
     static tree type = NULL_TREE;		\
@@ -1202,6 +1476,7 @@ public:
 
 #undef DEF_TYPE
 
+  /* Define non-strub builtins on demand.  */
 #define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST)			\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1224,6 +1499,7 @@ public:
 
 #undef DEF_NM_BUILTIN
 
+  /* Define strub builtins on demand.  */
 #define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST)		\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1260,6 +1536,7 @@ public:
 
 #undef DEF_SS_BUILTIN
 
+    /* Define strub identifiers on demand.  */
 #define DEF_IDENT(NAME)					\
   static inline tree get_ ## NAME () {			\
     static tree identifier = NULL_TREE;			\
@@ -1278,6 +1555,10 @@ public:
   static inline void adjust_at_calls_call (cgraph_edge *, int);
   static inline void adjust_at_calls_calls (cgraph_node *);
 
+  /* Add to SEQ a call to the strub watermark update builtin, taking NODE's
+     location if given.  Optionally add the corresponding edge from NODE, with
+     execution frequency COUNT.  Return the modified SEQ.  */
+
   static inline gimple_seq
   call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
 			 gimple_seq seq = NULL)
@@ -1300,8 +1581,15 @@ public:
 
 } // anon namespace
 
+/* Gather with this type a collection of parameters that we're turning into
+   explicit references.  */
+
 typedef hash_set<tree> indirect_parms_t;
 
+/* Dereference OP's incoming turned-into-reference parm if it's an
+   INDIRECT_PARMS or an ADDR_EXPR thereof.  Set *REC and return according to
+   gimple-walking expectations.  */
+
 static tree
 maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
 {
@@ -1324,12 +1612,17 @@ maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
     {
       *rec = 0;
       if (indirect_parms.contains (TREE_OPERAND (op, 0)))
-	return TREE_OPERAND (op, 0);
+	{
+	  op = TREE_OPERAND (op, 0);
+	  return op;
+	}
     }
 
   return NULL_TREE;
 }
 
+/* A gimple-walking function that adds dereferencing to indirect parms.  */
+
 static tree
 walk_make_indirect (tree *op, int *rec, void *arg)
 {
@@ -1351,6 +1644,11 @@ walk_make_indirect (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* A gimple-walking function that turns any non-gimple-val ADDR_EXPRs into a
+   separate SSA.  Though addresses of e.g. parameters, and of members thereof,
+   are gimple vals, turning parameters into references, with an extra layer of
+   indirection and thus explicit dereferencing, need to be regimplified.  */
+
 static tree
 walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
 {
@@ -1374,6 +1672,57 @@ walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* Turn STMT's PHI arg defs into separate SSA defs if they've become
+   non-gimple_val.  Return TRUE if any edge insertions need to be committed.  */
+
+static bool
+walk_regimplify_phi (gphi *stmt)
+{
+  bool needs_commit = false;
+
+  for (unsigned i = 0, n = gimple_phi_num_args (stmt); i < n; i++)
+    {
+      tree op = gimple_phi_arg_def (stmt, i);
+      if ((TREE_CODE (op) == ADDR_EXPR
+	   && !is_gimple_val (op))
+	  /* ??? A PARM_DECL that was addressable in the original function and
+	     had its address in PHI nodes, but that became a reference in the
+	     wrapped clone would NOT be updated by update_ssa in PHI nodes.
+	     Alas, if we were to create a default def for it now, update_ssa
+	     would complain that the symbol that needed rewriting already has
+	     SSA names associated with it.  OTOH, leaving the PARM_DECL alone,
+	     it eventually causes errors because it remains unchanged in PHI
+	     nodes, but it gets rewritten as expected if it appears in other
+	     stmts.  So we cheat a little here, and force the PARM_DECL out of
+	     the PHI node and into an assignment.  It's a little expensive,
+	     because we insert it at the edge, which introduces a basic block
+	     that's entirely unnecessary, but it works, and the block will be
+	     removed as the default def gets propagated back into the PHI node,
+	     so the final optimized code looks just as expected.  */
+	  || (TREE_CODE (op) == PARM_DECL
+	      && !TREE_ADDRESSABLE (op)))
+	{
+	  tree temp = make_ssa_name (TREE_TYPE (op), stmt);
+	  if (TREE_CODE (op) == PARM_DECL)
+	    SET_SSA_NAME_VAR_OR_IDENTIFIER (temp, DECL_NAME (op));
+	  SET_PHI_ARG_DEF (stmt, i, temp);
+
+	  gimple *assign = gimple_build_assign (temp, op);
+	  if (gimple_phi_arg_has_location (stmt, i))
+	    gimple_set_location (assign, gimple_phi_arg_location (stmt, i));
+	  gsi_insert_on_edge (gimple_phi_arg_edge (stmt, i), assign);
+	  needs_commit = true;
+	}
+    }
+
+  return needs_commit;
+}
+
+/* Create a reference type to use for PARM when turning it into a reference.
+   NONALIASED causes the reference type to gain its own separate alias set, so
+   that accessing the indirectly-passed parm won'will not add aliasing
+   noise.  */
+
 static tree
 build_ref_type_for (tree parm, bool nonaliased = true)
 {
@@ -1411,6 +1760,7 @@ build_ref_type_for (tree parm, bool nonaliased = true)
 
 /* Add cgraph edges from current_function_decl to callees in SEQ with frequency
    COUNT, assuming all calls in SEQ are direct.  */
+
 static void
 add_call_edges_for_seq (gimple_seq seq, profile_count count)
 {
@@ -1425,16 +1775,22 @@ add_call_edges_for_seq (gimple_seq seq, profile_count count)
     {
       gimple *stmt = gsi_stmt (gsi);
 
-      if (!is_a <gcall *> (stmt))
+      gcall *call = dyn_cast <gcall *> (stmt);
+      if (!call)
 	continue;
 
-      gcall *call = as_a <gcall *> (stmt);
       tree callee = gimple_call_fndecl (call);
       gcc_checking_assert (callee);
       node->create_edge (cgraph_node::get_create (callee), call, count, false);
     }
 }
 
+/* Insert SEQ after the call at GSI, as if the call was in a try block with SEQ
+   as finally, i.e., SEQ will run after the call whether it returns or
+   propagates an exception.  This handles block splitting, EH edge and block
+   creation, noreturn and nothrow optimizations, and even throwing calls without
+   preexisting local handlers.  */
+
 static void
 gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
 {
@@ -1446,7 +1802,7 @@ gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
   if (gimple_has_location (stmt))
     annotate_all_with_location (seq, gimple_location (stmt));
 
-  gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+  gcall *call = dyn_cast <gcall *> (stmt);
   bool noreturn_p = call && gimple_call_noreturn_p (call);
   int eh_lp = lookup_stmt_eh_lp (stmt);
   bool must_not_throw_p = eh_lp < 0;
@@ -1586,8 +1942,12 @@ remove_named_attribute_unsharing (const char *name, tree *attrs)
     }
 }
 
+/* Record the order of the last cgraph entry whose mode we've already set, so
+   that we can perform mode setting incrementally without duplication.  */
 static int last_cgraph_order;
 
+/* Set strub modes for functions introduced since the last call.  */
+
 static void
 ipa_strub_set_mode_for_new_functions ()
 {
@@ -1616,6 +1976,7 @@ ipa_strub_set_mode_for_new_functions ()
 }
 
 /* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
+
 bool
 strub_splittable_p (cgraph_node *node)
 {
@@ -1641,10 +2002,11 @@ strub_splittable_p (cgraph_node *node)
 }
 
 /* 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))
+  switch (get_strub_mode_from_fndecl (fndecl))
     {
     case STRUB_WRAPPED:
     case STRUB_AT_CALLS:
@@ -1676,6 +2038,7 @@ strub_watermark_parm (tree fndecl)
 
 /* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
    hasn't been added yet.  Return the named argument count.  */
+
 int
 pass_ipa_strub::adjust_at_calls_type (tree type)
 {
@@ -1751,6 +2114,12 @@ pass_ipa_strub::adjust_at_calls_type (tree type)
   return named_args;
 }
 
+/* Adjust a call to an at-calls call target.  Create a watermark local variable
+   if needed, initialize it before, pass it to the callee according to the
+   modified at-calls interface, and release the callee's stack space after the
+   call, if not deferred.  If the call is const or pure, arrange for the
+   watermark to not be assumed unused or unchanged.  */
+
 void
 pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 {
@@ -1819,10 +2188,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 
     if (gimple_call_internal_p (stmt))
 #if 0
-      /*
-	new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
-	vargs);
-      */
+      new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
+						 vargs);
 #endif
       gcc_unreachable ();
     else
@@ -1917,6 +2284,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
   gsi_insert_finally_seq_after_call (gsi, seq);
 }
 
+/* Adjust all at-calls calls in NODE. */
+
 void
 pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
 {
@@ -1965,6 +2334,10 @@ pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
     }
 }
 
+/* The strubm (strub mode) pass computes a strub mode for each function in the
+   call graph, and checks, before any inlining, that strub callability
+   requirements in effect are satisfied.  */
+
 unsigned int
 pass_ipa_strub_mode::execute (function *)
 {
@@ -1977,12 +2350,17 @@ pass_ipa_strub_mode::execute (function *)
   return 0;
 }
 
+/* Create a strub mode pass.  */
+
 simple_ipa_opt_pass *
 make_pass_ipa_strub_mode (gcc::context *ctxt)
 {
   return new pass_ipa_strub_mode (ctxt);
 }
 
+/* The strub pass proper adjusts types, signatures, and at-calls calls, and
+   splits internal-strub functions.  */
+
 unsigned int
 pass_ipa_strub::execute (function *)
 {
@@ -2042,17 +2420,6 @@ pass_ipa_strub::execute (function *)
 	if (onode->alias)
 	  continue;
 
-#if 0 /* Calls are now adjusted when examining callers.  */
-	unsigned c;
-	cgraph_edge *e;
-	FOR_EACH_VEC_ELT (onode->collect_callers (), c, e)
-	  {
-	    push_cfun (DECL_STRUCT_FUNCTION (e->caller->decl));
-	    adjust_at_calls_call (e, named_args);
-	    pop_cfun ();
-	  }
-#endif
-
 	cgraph_node *nnode = onode;
 	push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
 
@@ -2071,10 +2438,10 @@ pass_ipa_strub::execute (function *)
 		{
 		  gimple *stmt = gsi_stmt (gsi);
 
-		  if (!is_gimple_call (stmt))
-		    continue;
+		  gcall *call = dyn_cast <gcall *> (stmt);
 
-		  gcall *call = as_a <gcall *> (stmt);
+		  if (!call)
+		    continue;
 
 		  if (gimple_alloca_call_p (call))
 		    {
@@ -2088,10 +2455,6 @@ pass_ipa_strub::execute (function *)
 	  }
 
 	pop_cfun ();
-
-#if 0
-	compute_fn_summary (onode, true);
-#endif
       }
   }
 
@@ -2108,43 +2471,6 @@ pass_ipa_strub::execute (function *)
 	continue;
       }
 
-#if 0
-    /* Hmm, this is an i386-specific attribute.  Do we need machine-specific
-       logic?  */
-    remove_named_attribute_unsharing ("interrupt",
-				      &DECL_ATTRIBUTES (onode->decl));
-#endif
-
-#if 0
-    if (!DECL_STRUCT_FUNCTION (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting struct-less function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    if (!onode->lowered)
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting non-lowered function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    /* 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.  FIXME.  */
-    if (!tree_versionable_function_p (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"%qD cannot be split for %<strub%>",
-		onode->decl);
-	continue;
-      }
-#endif
-
     bool is_stdarg = calls_builtin_va_start_p (onode);;
     bool apply_args = calls_builtin_apply_args_p (onode);
 
@@ -2753,26 +3079,45 @@ pass_ipa_strub::execute (function *)
       if (any_indirect)
 	{
 	  basic_block bb;
+	  bool needs_commit = false;
 	  FOR_EACH_BB_FN (bb, cfun)
-	    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
-		 !gsi_end_p (gsi); gsi_next (&gsi))
-	      {
-		gimple *stmt = gsi_stmt (gsi);
+	    {
+	      for (gphi_iterator gsi = gsi_start_nonvirtual_phis (bb);
+		   !gsi_end_p (gsi);
+		   gsi_next_nonvirtual_phi (&gsi))
+		{
+		  gphi *stmt = gsi.phi ();
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed && !is_gimple_debug (gsi_stmt (gsi)))
+		    if (walk_regimplify_phi (stmt))
+		      needs_commit = true;
+		}
 
-		walk_stmt_info wi = {};
-		wi.info = &indirect_nparms;
-		walk_gimple_op (stmt, walk_make_indirect, &wi);
-		if (wi.changed)
-		  {
-		    if (!is_gimple_debug (gsi_stmt (gsi)))
-		      {
-			wi.info = &gsi;
-			walk_gimple_op (stmt, walk_regimplify_addr_expr,
-					&wi);
-		      }
-		    update_stmt (stmt);
-		  }
-	      }
+	      for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+		   !gsi_end_p (gsi); gsi_next (&gsi))
+		{
+		  gimple *stmt = gsi_stmt (gsi);
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed)
+		    {
+		      if (!is_gimple_debug (stmt))
+			{
+			  wi.info = &gsi;
+			  walk_gimple_op (stmt, walk_regimplify_addr_expr,
+					  &wi);
+			}
+		      update_stmt (stmt);
+		    }
+		}
+	    }
+	  if (needs_commit)
+	    gsi_commit_edge_inserts ();
 	}
 
       if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca
@@ -2842,10 +3187,10 @@ pass_ipa_strub::execute (function *)
       push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
       gimple_stmt_iterator gsi
 	= gsi_after_labels (single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun)));
-      while (!is_gimple_call (gsi_stmt (gsi)))
-	gsi_next (&gsi);
 
-      gcall *wrcall = as_a <gcall *> (gsi_stmt (gsi));
+      gcall *wrcall;
+      while (!(wrcall = dyn_cast <gcall *> (gsi_stmt (gsi))))
+	gsi_next (&gsi);
 
       tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
       TREE_ADDRESSABLE (swm) = true;
@@ -2973,11 +3318,6 @@ pass_ipa_strub::execute (function *)
 
       pop_cfun ();
     }
-
-#if 0
-    compute_fn_summary (onode, true);
-    compute_fn_summary (nnode, true);
-#endif
   }
 
   if (flag_checking)
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
index c335ab42097..5c0b266c3f8 100644
--- a/gcc/ipa-strub.h
+++ b/gcc/ipa-strub.h
@@ -18,11 +18,10 @@ You should have received a copy of the GNU General Public License
 along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
-/* Return TRUE if the first function can be inlined into the second,
-   as far as stack scrubbing constraints are concerned.  CALLEE
-   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 TRUE if CALLEE can be inlined into CALLER, as far as stack scrubbing
+   constraints are concerned.  CALLEE doesn't have to be called directly by
+   CALLER, but the returned value says nothing about intervening functions.  */
+extern bool strub_inlinable_to_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);
@@ -33,3 +32,14 @@ extern tree strub_watermark_parm (tree fndecl);
 
 /* Make a function type or declaration callable.  */
 extern void strub_make_callable (tree fndecl);
+
+/* Return zero iff ID is NOT an acceptable parameter for a user-supplied strub
+   attribute for a function.  Otherwise, return >0 if it enables strub, <0 if it
+   does not.  Return +/-1 if the attribute-modified type is compatible with the
+   type without the attribute, or +/-2 if it is not compatible.  */
+extern int strub_validate_fn_attr_parm (tree id);
+
+/* Like comptypes, return 0 if t1 and t2 are not compatible, 1 if they are
+   compatible, and 2 if they are nearly compatible.  Same strub mode is
+   compatible, interface-compatible strub modes are nearly compatible.  */
+extern int strub_comptypes (tree t1, tree t2);
diff --git a/gcc/multiple_target.c b/gcc/multiple_target.c
index 28d5f95dca5..07cdb66e337 100644
--- a/gcc/multiple_target.c
+++ b/gcc/multiple_target.c
@@ -557,7 +557,7 @@ public:
 bool
 pass_target_clone::gate (function *)
 {
-  return true;
+  return !seen_error ();
 }
 
 } // anon namespace
diff --git a/gcc/testsuite/c-c++-common/strub-O0.c b/gcc/testsuite/c-c++-common/strub-O0.c
index 6afe0fd5de1..c7a79a6ea0d 100644
--- a/gcc/testsuite/c-c++-common/strub-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O0 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O0, none of the strub builtins are expanded inline.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O1.c b/gcc/testsuite/c-c++-common/strub-O1.c
index 1cdeaecaf32..96285c975d9 100644
--- a/gcc/testsuite/c-c++-common/strub-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O1 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O1, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
index 7848c46d179..8edc0d8aa13 100644
--- a/gcc/testsuite/c-c++-common/strub-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O2, without -fno-inline, we fully expand enter and update, and add a test
    around the leave call.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
index 85a8f76785e..c6d900cf3c4 100644
--- a/gcc/testsuite/c-c++-common/strub-O2fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3.c b/gcc/testsuite/c-c++-common/strub-O3.c
index 1fcde345d36..33ee465e51c 100644
--- a/gcc/testsuite/c-c++-common/strub-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand" } */
 
 int __attribute__ ((__strub__)) var;
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
index a2eedfd96b2..2936f82079e 100644
--- a/gcc/testsuite/c-c++-common/strub-O3fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
index e5cb1f60541..479746e57d8 100644
--- a/gcc/testsuite/c-c++-common/strub-Og.c
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Og -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Og -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Og, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
index 194aacc2c05..2241d4ea07f 100644
--- a/gcc/testsuite/c-c++-common/strub-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Os, without -fno-inline, we fully expand enter, and also update.  The
    expanded update might be larger than a call proper, but argument saving and
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
index 46e84bf6560..a322bcc5da6 100644
--- a/gcc/testsuite/c-c++-common/strub-all1.c
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -22,11 +22,11 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
index f377541cff0..db60026d0e0 100644
--- a/gcc/testsuite/c-c++-common/strub-all2.c
+++ b/gcc/testsuite/c-c++-common/strub-all2.c
@@ -17,8 +17,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
index f180b17f30e..2f462adc1ef 100644
--- a/gcc/testsuite/c-c++-common/strub-apply1.c
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -1,13 +1,13 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
-void __attribute__ ((__strub__ (3)))
+void __attribute__ ((__strub__ ("callable")))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0);
 }
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   void *args = __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
index 379a54b73b7..bab2dd10f5b 100644
--- a/gcc/testsuite/c-c++-common/strub-apply2.c
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 extern void __attribute__ ((__strub__))
 apply_function (void *args);
@@ -10,3 +10,9 @@ apply_args (int i, int j, double d) /* { dg-error "selected" } */
   void *args = __builtin_apply_args (); /* { dg-message "does not support" } */
   apply_function (args);
 }
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After the error and sorry messages in strubm, build_ssa_passes are gated out,
+   so we don't get the PROP_ssa property that targetclone requires.  When
+   checking is not enabled at configure time, we admit to being confused and
+   bail out, but when checking is eneabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
index 9b4786be698..85f9d090fda 100644
--- a/gcc/testsuite/c-c++-common/strub-apply3.c
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -1,8 +1,14 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 void __attribute__ ((__strub__))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */
 }
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
index 409f747743e..15ffaa031b8 100644
--- a/gcc/testsuite/c-c++-common/strub-apply4.c
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+/* { dg-options "-O2 -fstrub=strict -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
@@ -18,4 +18,4 @@ void f() {
   apply_args (1, 2, 3);
 }
 
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 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 d964b07ae5d..b70843b4215 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -22,9 +22,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
index 530eee36d06..97a3988a6b9 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls2.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls2.c
@@ -17,7 +17,7 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-default1.c b/gcc/testsuite/c-c++-common/strub-default1.c
deleted file mode 100644
index 79d00cedb9a..00000000000
--- a/gcc/testsuite/c-c++-common/strub-default1.c
+++ /dev/null
@@ -1,42 +0,0 @@
-/* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
-
-static int __attribute__ ((__strub__)) var;
-
-/* 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 inline void
-__attribute__ ((__always_inline__))
-h() {
-  var++;
-}
-
-/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
-   the viability of at-calls strubbing.  Though internally a strub context, its
-   interface is not strub-enabled, so it's not callable from within strub
-   contexts.  */
-static inline void
-g() {
-  var--;
-  h();
-}
-
-/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
-   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
-void
-f() {
-  var++;
-  g();  /* { dg-error "calling non-.strub." } */
-}
-
-/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-4\[)\]" 1 "strubm" } } */
-/* { 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 "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
index 7b04eea35d9..3d73431b3dc 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O1" } */
+/* { dg-options "-fstrub=strict -O1" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O1.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
index 67d96419a5e..fddf3c745e7 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O2" } */
+/* { dg-options "-fstrub=strict -O2" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O2.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
index 34828d2711e..2bc384ee8d1 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O3" } */
+/* { dg-options "-fstrub=strict -O3" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -O3.  */
@@ -10,7 +10,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -18,7 +18,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -32,7 +32,7 @@ leak_string (void)
   return 0;
 }
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 int
 look_for_string (char *e)
 {
@@ -56,14 +56,14 @@ look_for_string (char *e)
   return 0;
 }
 
-static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+static __attribute__ ((__strub__ ("at-calls"), __noinline__, __noclone__))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 deferred_at_calls ()
 {
@@ -73,7 +73,7 @@ deferred_at_calls ()
   return ret;
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 deferred_internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
index b273660aea1..fbaf85fe0fa 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -Os" } */
+/* { dg-options "-fstrub=strict -Os" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -Os.  */
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
index a74658c9ac9..e9d7b7b9ee0 100644
--- a/gcc/testsuite/c-c++-common/strub-internal1.c
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -23,9 +23,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
index a6e69357b23..8b8e15a51c7 100644
--- a/gcc/testsuite/c-c++-common/strub-internal2.c
+++ b/gcc/testsuite/c-c++-common/strub-internal2.c
@@ -14,8 +14,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
index 7e22a266ad9..0a4a7539d34 100644
--- a/gcc/testsuite/c-c++-common/strub-parms1.c
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -16,7 +16,7 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 large_byref_arg (struct large_arg la)
 {
 }
@@ -24,7 +24,7 @@ large_byref_arg (struct large_arg la)
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*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)))
+void __attribute__ ((__strub__ ("internal")))
 std_arg (int i, ...)
 {
   va_list vl;
@@ -38,7 +38,7 @@ std_arg (int i, ...)
 /* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
index 2d8036c0fbc..147171d96d5 100644
--- a/gcc/testsuite/c-c++-common/strub-parms2.c
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -15,14 +15,14 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 large_byref_arg (struct large_arg la)
 {
 }
 
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 std_arg (int i, ...)
 {
   va_list vl;
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
index f64361f1235..4e92682895a 100644
--- a/gcc/testsuite/c-c++-common/strub-parms3.c
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
new file mode 100644
index 00000000000..e2f9d8aebca
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  Without the error,
+   inlining takes place.  */
+
+#include "strub-strict1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
new file mode 100644
index 00000000000..98474435d2e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  */
+
+#include "strub-strict2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
index a4226ce0119..1de15342595 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fexceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fexceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
index 3bab553478b..f9209c81900 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
index c89cc7c2c47..bed1dcfb54a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
index b869fafb691..6bf0071f52b 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
index 1d3dd2f2c2c..4732f515bf7 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
index 4dd4281b03b..8d6424c479a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-strict1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
new file mode 100644
index 00000000000..7f5247cfaf0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-strict1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+static int __attribute__ ((__strub__)) var;
+
+/* 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 inline void
+__attribute__ ((__always_inline__))
+h() {
+  var++;
+}
+
+/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
+   the viability of at-calls strubbing.  Though internally a strub context, its
+   interface is not strub-enabled, so it's not callable from within strub
+   contexts.  */
+static inline void
+g() {
+  var--;
+  h();
+}
+
+/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
+   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
+void
+f() {
+  var++;
+  g();  /* { dg-error "calling non-.strub." } */
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-default2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
similarity index 54%
rename from gcc/testsuite/c-c++-common/strub-default2.c
rename to gcc/testsuite/c-c++-common/strub-strict2.c
index 487253e9227..17463b6e554 100644
--- a/gcc/testsuite/c-c++-common/strub-default2.c
+++ b/gcc/testsuite/c-c++-common/strub-strict2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
 
 static int __attribute__ ((__strub__)) var;
 
@@ -22,8 +22,14 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
index 0840dddd136..e48e0610e07 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 #include "strub-tail-O2.c"
 
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
index 9330d6ff4c1..87cda7ab21b 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.
    Tail calls are short-circuited at -O2+.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
index 5b33ff1f530..b5e45ab0525 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
index 38935e3270b..96aa7fe4b07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -1,39 +1,39 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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);
+extern int __attribute__ ((__strub__ ("callable"))) xcallable (void);
+extern int __attribute__ ((__strub__ ("internal"))) xinternal (void);
+extern int __attribute__ ((__strub__ ("at-calls"))) xat_calls (void);
+extern int __attribute__ ((__strub__ ("disabled"))) 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__ ("callable"))) callable (void);
+int __attribute__ ((__strub__ ("internal"))) internal (void);
+int __attribute__ ((__strub__ ("at-calls"))) at_calls (void);
+int __attribute__ ((__strub__ ("disabled"))) disabled (void);
 
 int __attribute__ ((__strub__)) var;
 int var_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 icallable (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 iinternal (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 idisabled (void);
 static inline int __attribute__ ((__always_inline__))
 ivar_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 i_callable (void) { return 0; }
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 i_internal (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 i_at_calls (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 i_disabled (void) { return 0; }
 static inline int __attribute__ ((__always_inline__))
 i_var_user (void) { return var; }
@@ -70,7 +70,7 @@ i_var_user (void) { return var; }
 
 /* Not a strub context, so it can call anything.
    Explicitly declared as callable even from within strub contexts.  */
-int __attribute__ ((__strub__ (3)))
+int __attribute__ ((__strub__ ("callable")))
 callable (void) {
   int ret = 0;
 
@@ -88,7 +88,7 @@ callable (void) {
 
 /* 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)))
+int __attribute__ ((__strub__ ("internal")))
 internal (void) {
   int ret = var;
 
@@ -109,7 +109,7 @@ internal (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (1)))
+int __attribute__ ((__strub__ ("at-calls")))
 at_calls (void) {
   int ret = var;
 
@@ -130,7 +130,7 @@ at_calls (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (0)))
+int __attribute__ ((__strub__ ("disabled")))
 disabled () {
   int ret = 0;
 
@@ -205,7 +205,7 @@ iinternal (void) {
   return ret;
 }
 
-int
+int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void) {
   int ret = var;
 
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
index 100fb0c59a9..2857195706e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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,
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
index 9e818ac9748..98a92bc9eac 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
index d40e8aa45cb..5511a6e1e71 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __const__))
 f() {
   return 0;
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
index d4cbdaf10f3..47ee927964d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -1,12 +1,12 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__
 __attribute__ ((__const__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data1.c b/gcc/testsuite/c-c++-common/torture/strub-data1.c
index 62a03891ab6..7c27a2a1a6d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointed-to data enables strubbing if accessed.  */
 int __attribute__ ((__strub__)) var;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data2.c b/gcc/testsuite/c-c++-common/torture/strub-data2.c
index 9b7df13a280..e66d903780a 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointer itself is a strub variable, enabling internal strubbing when
    its value is used.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data3.c b/gcc/testsuite/c-c++-common/torture/strub-data3.c
index 515706caa32..5e08e0e58c6 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data4.c b/gcc/testsuite/c-c++-common/torture/strub-data4.c
index 0ec9e35429f..a818e7a38bb 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
index 69f3d65ed44..90e8ad51c1e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -Werror" } */
+/* { dg-options "-fstrub=strict -Werror" } */
 
 typedef int __attribute__ ((__strub__)) strub_int;
 strub_int *ptr;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
index b8adf8009e8..c165f312f16 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype ();
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
index 5b2c35ad6a7..69fcff8d376 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
index 5ee50456dc9..ff006224909 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int, ...);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
new file mode 100644
index 00000000000..b416ba06b59
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+}
+
+void
+bat (void)
+{
+  /* Not allowed, not a strub context.  */
+  inl_int_ali (); /* { dg-error "context" } */
+}
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
new file mode 100644
index 00000000000..f9a6b4a16fa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=all" } */
+
+#include "strub-inlinable1.c"
+
+/* With -fstrub=all, the caller becomes a strub context, so the strub-inlinable
+   callee is not rejected.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
new file mode 100644
index 00000000000..07cc432024d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+typedef void ft (void);
+typedef void ft2 (int, int);
+extern ft __attribute__ ((__strub__)) fnac;
+
+ft * f (void) {
+  return fnac; /* { dg-message "incompatible" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
new file mode 100644
index 00000000000..c29567eb937
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
@@ -0,0 +1,55 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic" } */
+
+/* C++ does not warn about the partial incompatibilities.
+
+   The d_p () calls are actually rejected, even in C++, but they are XFAILed
+   here because we don't get far enough in the compilation as to observe them,
+   because the incompatibilities are errors without -fpermissive.
+   strub-ptrfn3.c uses -fpermissive to check those.
+ */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
new file mode 100644
index 00000000000..e1f179e160e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
@@ -0,0 +1,50 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic -fpermissive" } */
+/* { dg-prune-output "command-line option .-fpermissive." } */
+
+/* See strub-ptrfn2.c.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
new file mode 100644
index 00000000000..70b558afad0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+/* This is strub-ptrfn2.c without -Wpedantic.
+
+   Even C doesn't report the (not-quite-)compatible conversions without it.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
index cb223da6efc..a262a086837 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
index 67d1434b1f8..4c4bd50c209 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
index 59f02ea901f..ce195c6b1f1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -1,10 +1,10 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __pure__))
 f() {
   static int i; /* Stop it from being detected as const.  */
   return i;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
index 973e909217d..75cd54ccb5b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
 __attribute__ ((__pure__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
index a4077c35a60..c596066a045 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -14,7 +14,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -59,14 +59,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
index 94e4156ea73..1fdba214a07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
index 0ca74beb59d..afbc2cc9ab4 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
 /* Check that a non-strub function leaves a string behind in the stack, and that
@@ -7,7 +7,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
index 4ab11c0682e..5300f1d330b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -85,14 +85,14 @@ intermediate ()
   return ret;
 }
 
-static inline __attribute__ ((__strub__ (2)))
+static inline __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
   return intermediate ();
 }
 
-int __attribute__ ((strub (0)))
+int __attribute__ ((__strub__ ("disabled")))
 main ()
 {
   if (look_for_string (internal ()))
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4d.c b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
index e4f7445607c..08de3f1c3b1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4d.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -1,7 +1,7 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
-#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ (1)))
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
 
 #include "strub-run4.c"
diff --git a/gcc/testsuite/g++.dg/torture/strub-init1.C b/gcc/testsuite/g++.dg/torture/strub-init1.C
index e51ae802be4..c226ab10ff6 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init1.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init2.C b/gcc/testsuite/g++.dg/torture/strub-init2.C
index edcb7bf8ad2..a7911f1fa72 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init2.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init3.C b/gcc/testsuite/g++.dg/torture/strub-init3.C
index bacf823ca4e..6ebebcd01e8 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init3.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
index 697ac9de764..10445d7cf84 100644
--- a/gcc/testsuite/gnat.dg/strub_attr.adb
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -1,5 +1,5 @@
 --  { dg-do compile }
---  { dg-options "-fstrub=default -fdump-ipa-strubm" }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" }
 
 package body Strub_Attr is
    E : exception;
@@ -14,8 +14,24 @@ package body Strub_Attr is
       return X * X;
    end;
    
-   function G return Integer is (X);
+   function G return Integer is (F (X));
+   --  function G return Integer is (FP (X));
+   --  Calling G would likely raise an exception, because although FP
+   --  carries the strub at-calls attribute needed to call F, the
+   --  attribute is dropped from the type used for the call proper.
 end Strub_Attr;
 
---  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]2\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 0 "strubm" } }
 --  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark_ptr" 6 "strub" } }
+--  We have 1 at-calls subprogram (F) and 2 wrapped (P and G).
+--  For each of them, there's one match for the wrapped signature, 
+--  and one for the update call.
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 27 "strub" } }
+--  The 6 matches above, plus:
+--  5*2: wm var decl, enter, call, leave and clobber for each wrapper;
+--  2*1: an extra leave and clobber for the exception paths in the wrappers.
+--  7*1: for the F call in G, including EH path.
diff --git a/gcc/testsuite/gnat.dg/strub_ind.adb b/gcc/testsuite/gnat.dg/strub_ind.adb
new file mode 100644
index 00000000000..cfd7a76b02b
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind.adb
@@ -0,0 +1,50 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" }
+
+package body Strub_Ind is
+   E : exception;
+
+   procedure P (X : Integer) is
+   begin
+      raise E;
+   end;
+   
+   function F (X : Integer) return Integer is
+   begin
+      return X * X;
+   end;
+   
+   function G return Integer is (FP (X)); -- { dg-bogus "non-.strub." "" { xfail *-*-* } }
+   --  Calling G would likely raise an exception, because although FP
+   --  carries the strub at-calls attribute needed to call F, the
+   --  attribute is dropped from the type used for the call proper.
+
+
+   type GT is access function return Integer;
+   pragma Machine_Attribute (GT, "strub", "at-calls");
+   --  The pragma above seems to have no effect.
+
+   GP : GT := G'Access; -- { dg-warning "incompatible" "" { xfail *-*-* } }
+   pragma Machine_Attribute (GP, "strub", "at-calls");
+   --  The pragma above does modify GP's type,
+   --  but dereferencing it uses an unmodified copy of the type.
+   --  The initializer should be diagnosed:
+   --  GT should only reference functions with at-calls strub.
+
+end Strub_Ind;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 0 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark_ptr" 6 "strub" } }
+--  We have 1 at-calls subprogram (F) and 2 wrapped (P and G).
+--  For each of them, there's one match for the wrapped signature, 
+--  and one for the update call.
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 20 "strub" } }
+--  The 6 matches above, plus:
+--  5*2: wm var decl, enter, call, leave and clobber for each wrapper;
+--  2*1: an extra leave and clobber for the exception paths in the wrappers.
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 27 "strub" { xfail *-*-* } } }
+--  7*1: for the FP call in G, including EH path (currently missing the watermarking)
diff --git a/gcc/testsuite/gnat.dg/strub_ind.ads b/gcc/testsuite/gnat.dg/strub_ind.ads
new file mode 100644
index 00000000000..53dede60eac
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind.ads
@@ -0,0 +1,23 @@
+package Strub_Ind is
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "internal");
+
+   function F (X : Integer) return Integer;
+   pragma Machine_Attribute (F, "strub");
+
+   X : Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+
+   function G return Integer;
+
+   type FT is access function (X : Integer) return Integer;
+   pragma Machine_Attribute (FT, "strub", "at-calls");
+   --  The pragma above seems to get discarded in GNAT; Gigi doesn't see it.
+
+   FP : FT := F'Access;
+   pragma Machine_Attribute (FP, "strub", "at-calls");
+   --  The pragma above does modify FP's type,
+   --  but a call with it gets it converted to its Ada type,
+   --  that is cached by the translator as the unmodified type.
+
+end Strub_Ind;
diff --git a/libgcc/strub.c b/libgcc/strub.c
index fd6e27556e4..7c58deee1e6 100644
--- a/libgcc/strub.c
+++ b/libgcc/strub.c
@@ -36,7 +36,7 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 # define TOPS <
 #endif
 
-#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ ("callable")))
 
 /* Enter a stack scrubbing context, initializing the watermark to the caller's
    stack address.  */


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

* [gcc(refs/users/aoliva/heads/strub)] -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests
@ 2021-09-07 22:35 Alexandre Oliva
  0 siblings, 0 replies; 13+ messages in thread
From: Alexandre Oliva @ 2021-09-07 22:35 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:90d3d4fa357cbc23b525264b542a26b8987a03df

commit 90d3d4fa357cbc23b525264b542a26b8987a03df
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Thu Sep 2 23:15:36 2021 -0300

    -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests

Diff:
---
 .../doc/gnat_rm/security_hardening_features.rst    |  50 +-
 gcc/ada/gcc-interface/utils.c                      | 111 +---
 gcc/attribs.c                                      |  39 +-
 gcc/c-family/c-attribs.c                           | 100 +---
 gcc/common.opt                                     |  27 +-
 gcc/doc/extend.texi                                | 332 +++++++++--
 gcc/doc/invoke.texi                                |  76 ++-
 gcc/ipa-inline.c                                   |   2 +-
 gcc/ipa-strub.c                                    | 650 ++++++++++++++++-----
 gcc/ipa-strub.h                                    |  20 +-
 gcc/multiple_target.c                              |   2 +-
 gcc/testsuite/c-c++-common/strub-O0.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O1.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2fni.c           |   2 +-
 gcc/testsuite/c-c++-common/strub-O3.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O3fni.c           |   2 +-
 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            |  12 +-
 gcc/testsuite/c-c++-common/strub-all2.c            |   6 +-
 gcc/testsuite/c-c++-common/strub-apply1.c          |   6 +-
 gcc/testsuite/c-c++-common/strub-apply2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-apply3.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-apply4.c          |   4 +-
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-at-calls2.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-default1.c        |  42 --
 gcc/testsuite/c-c++-common/strub-defer-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |  14 +-
 gcc/testsuite/c-c++-common/strub-defer-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-internal1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-internal2.c       |   6 +-
 gcc/testsuite/c-c++-common/strub-parms1.c          |  10 +-
 gcc/testsuite/c-c++-common/strub-parms2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-parms3.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-relaxed1.c        |  18 +
 gcc/testsuite/c-c++-common/strub-relaxed2.c        |  14 +
 gcc/testsuite/c-c++-common/strub-short-O0-exc.c    |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O0.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O3.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-strict1.c         |  48 ++
 .../{strub-default2.c => strub-strict2.c}          |  14 +-
 gcc/testsuite/c-c++-common/strub-tail-O1.c         |   2 +-
 gcc/testsuite/c-c++-common/strub-tail-O2.c         |   2 +-
 .../c-c++-common/torture/strub-callable1.c         |   2 +-
 .../c-c++-common/torture/strub-callable2.c         |  44 +-
 gcc/testsuite/c-c++-common/torture/strub-const1.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const2.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const3.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-const4.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-data1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data3.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data4.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |   2 +-
 .../c-c++-common/torture/strub-indcall1.c          |   2 +-
 .../c-c++-common/torture/strub-indcall2.c          |   2 +-
 .../c-c++-common/torture/strub-indcall3.c          |   2 +-
 .../c-c++-common/torture/strub-inlinable1.c        |  22 +
 .../c-c++-common/torture/strub-inlinable2.c        |   7 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |  10 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |  55 ++
 gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c  |  50 ++
 gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c  |  43 ++
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure3.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-pure4.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run1.c    |  10 +-
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run4.c    |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run4d.c   |   4 +-
 gcc/testsuite/g++.dg/torture/strub-init1.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init2.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init3.C         |   2 +-
 gcc/testsuite/gnat.dg/strub_attr.adb               |  22 +-
 gcc/testsuite/gnat.dg/strub_ind.adb                |  50 ++
 gcc/testsuite/gnat.dg/strub_ind.ads                |  23 +
 libgcc/strub.c                                     |   2 +-
 85 files changed, 1489 insertions(+), 608 deletions(-)

diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 309291f0341..75345290412 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -19,6 +19,18 @@ It can be enabled with the *-fzero-call-used-regs* command line
 option, to affect all subprograms in a compilation, and with a
 :samp:`Machine_Attribute` pragma, to affect only specific subprograms.
 
+.. code-block:: ada
+
+     procedure Foo;
+     pragma Machine_Attribute (Foo, "zero_call_used_regs", "used");
+     --  Before returning, Foo scrubs only call-clobbered registers
+     --  that it uses itself.
+
+     function Bar return Integer;
+     pragma Machine_Attribute (Bar, "zero_call_used_regs", "all");
+     --  Before returning, Bar scrubs all call-clobbered registers.
+
+
 For usage and more details on the command line option, and on the
 ``zero_call_used_regs`` attribute, see :title:`Using the GNU Compiler
 Collection (GCC)`.
@@ -31,13 +43,31 @@ Stack Scrubbing
 
 GNAT can generate code to zero-out stack frames used by subprograms.
 
-It can be activated with the *-fstrub* command line option, to affect
-all (viable) subprograms in a compilation, and with a
-:samp:`Machine_Attribute` pragma.
+It can be activated with the :samp:`Machine_Attribute` pragma, on
+specific subprograms, variables, and types.
 
-For usage and more details on the command line option, and on the
-``strub`` attribute, see :title:`Using the GNU Compiler Collection
-(GCC)`.
+.. code-block:: ada
+
+     function Foo returns Integer;
+     pragma Machine_Attribute (Foo, "strub");
+     --  Foo and its callers are modified so as to scrub the stack
+     --  space used by Foo after it returns.
+
+     procedure Bar;
+     pragma Machine_Attribute (Bar, "strub", "internal");
+     --  Bar is turned into a wrapper for its original body,
+     --  and they scrub the stack used by the original body.
+
+     Var : Integer;
+     pragma Machine_Attribute (Var, "strub");
+     --  Reading from Var in a subprogram enables stack scrubbing
+     --  of the stack space used by the subprogram.
+
+
+There are also *-fstrub* command line options to control default
+settings.  For usage and more details on the command line option, and
+on the ``strub`` attribute, see :title:`Using the GNU Compiler
+Collection (GCC)`.
 
 Note that Ada secondary stacks are not scrubbed.  The restriction
 ``No_Secondary_Stack`` avoids their use, and thus their accidental
@@ -49,4 +79,10 @@ is not fully reflected in Ada types, ``Access`` attributes, renaming
 and overriding.  Every access type, renaming, and overriding and
 overridden dispatching operations that may refer to an entity with an
 attribute-modified interface must be annotated with the same
-interface-modifying attribute.
+interface-modifying attribute, or with an interface-compatible one.
+
+Even then, applying the pragma to anything other than subprograms and
+scalar variables is not currently supported, and may be silently
+ignored.  Specifically, it is not recommended to rely on any effects
+of this pragma on access types and objects to data variables and to
+subprograms.
diff --git a/gcc/ada/gcc-interface/utils.c b/gcc/ada/gcc-interface/utils.c
index 6b20ddc93c9..193772cc829 100644
--- a/gcc/ada/gcc-interface/utils.c
+++ b/gcc/ada/gcc-interface/utils.c
@@ -39,6 +39,7 @@
 #include "varasm.h"
 #include "toplev.h"
 #include "opts.h"
+#include "ipa-strub.h"
 #include "output.h"
 #include "debug.h"
 #include "convert.h"
@@ -6611,115 +6612,57 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
-  tree orig_fnptr_type = NULL_TREE;
-  tree orig_args = args;
 
   if (args
       && POINTER_TYPE_P (*node)
       && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*node)))
-    {
-      orig_fnptr_type = *node;
-      *node = TREE_TYPE (orig_fnptr_type);
-    }
+    *node = TREE_TYPE (*node);
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      /* pragma Machine_Attribute turns string arguments into identifiers.
-	 Reverse it.  */
-      if (TREE_CODE (TREE_VALUE (args)) == IDENTIFIER_NODE)
-	{
-	  gcc_checking_assert (IDENTIFIER_POINTER (TREE_VALUE (args))
-			       [IDENTIFIER_LENGTH (TREE_VALUE (args))] == 0);
-	  TREE_VALUE (args) = build_string
-	    (IDENTIFIER_LENGTH (TREE_VALUE (args)) + 1,
-	     IDENTIFIER_POINTER (TREE_VALUE (args)));
-	}
-
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
 	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
-
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
-
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
 
   if (args)
-    warning (OPT_Wattributes,
-	     "ignoring excess %qE attribute arguments starting at %qE",
-	     name, TREE_VALUE (args));
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
 
   /* If we see a strub-enabling attribute, and we're at the default setting,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
-
-  if (!*no_add_attrs)
-    {
-      *no_add_attrs = true;
-      if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
-      TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
-					   TYPE_ATTRIBUTES (*node));
-
-      if (orig_fnptr_type)
-	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
-	  TREE_TYPE (fnptr_type) = *node;
-	  *node = fnptr_type;
-	}
-    }
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   return NULL_TREE;
 }
diff --git a/gcc/attribs.c b/gcc/attribs.c
index 0d22c20a35e..07f81285d7a 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "attribs.h"
 #include "fold-const.h"
+#include "ipa-strub.h"
 #include "stor-layout.h"
 #include "langhooks.h"
 #include "plugin.h"
@@ -617,12 +618,11 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  flags &= ~(int) ATTR_FLAG_TYPE_IN_PLACE;
 	}
 
-      if (spec->function_type_required && TREE_CODE (*anode) != FUNCTION_TYPE
-	  && TREE_CODE (*anode) != METHOD_TYPE)
+      if (spec->function_type_required
+	  && !FUNC_OR_METHOD_TYPE_P (*anode))
 	{
 	  if (TREE_CODE (*anode) == POINTER_TYPE
-	      && (TREE_CODE (TREE_TYPE (*anode)) == FUNCTION_TYPE
-		  || TREE_CODE (TREE_TYPE (*anode)) == METHOD_TYPE))
+	      && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
 	    {
 	      /* OK, this is a bit convoluted.  We can't just make a copy
 		 of the pointer type and modify its TREE_TYPE, because if
@@ -715,7 +715,23 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  tree ret = (spec->handler) (cur_and_last_decl, name, args,
 				      flags|cxx11_flag, &no_add_attrs);
 
-	  *anode = cur_and_last_decl[0];
+	  if (*anode != cur_and_last_decl[0])
+	    {
+	      /* Even if !spec->function_type_required, allow the attribute
+		 handler to request the attribute to be applied to the function
+		 type, rather than to the function pointer type, by setting
+		 cur_and_last_decl[0] to the function type.  */
+	      if (!fn_ptr_tmp
+		  && POINTER_TYPE_P (*anode)
+		  && TREE_TYPE (*anode) == cur_and_last_decl[0]
+		  && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
+		{
+		  fn_ptr_tmp = TREE_TYPE (*anode);
+		  fn_ptr_quals = TYPE_QUALS (*anode);
+		  anode = &fn_ptr_tmp;
+		}
+	      *anode = cur_and_last_decl[0];
+	    }
 	  if (ret == error_mark_node)
 	    {
 	      warning (OPT_Wattributes, "%qE attribute ignored", name);
@@ -1353,9 +1369,20 @@ comp_type_attributes (const_tree type1, const_tree type2)
   if ((lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type1)) != NULL)
       ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) != NULL))
     return 0;
+  int strub_ret = strub_comptypes (CONST_CAST_TREE (type1),
+				   CONST_CAST_TREE (type2));
+  if (strub_ret == 0)
+    return strub_ret;
   /* As some type combinations - like default calling-convention - might
      be compatible, we have to call the target hook to get the final result.  */
-  return targetm.comp_type_attributes (type1, type2);
+  int target_ret = targetm.comp_type_attributes (type1, type2);
+  if (target_ret == 0)
+    return target_ret;
+  if (strub_ret == 2 || target_ret == 2)
+    return 2;
+  if (strub_ret == 1 && target_ret == 1)
+    return 1;
+  gcc_unreachable ();
 }
 
 /* PREDICATE acts as a function of type:
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index dc4a4d4d200..0453ac563cc 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "common/common-target.h"
 #include "langhooks.h"
 #include "tree-inline.h"
+#include "ipa-strub.h"
 #include "toplev.h"
 #include "tree-iterator.h"
 #include "opts.h"
@@ -1306,104 +1307,57 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
-  tree orig_fnptr_type = NULL_TREE;
-  tree orig_args = args;
 
   if (args
       && POINTER_TYPE_P (*node)
       && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*node)))
-    {
-      orig_fnptr_type = *node;
-      *node = TREE_TYPE (orig_fnptr_type);
-    }
+    *node = TREE_TYPE (*node);
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
 	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
-
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
-
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
 
   if (args)
-    warning (OPT_Wattributes,
-	     "ignoring excess %qE attribute arguments starting at %qE",
-	     name, TREE_VALUE (args));
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
 
   /* If we see a strub-enabling attribute, and we're at the default setting,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
-
-  if (!*no_add_attrs)
-    {
-      *no_add_attrs = true;
-      if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
-      TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
-					   TYPE_ATTRIBUTES (*node));
-
-      if (orig_fnptr_type)
-	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
-	  TREE_TYPE (fnptr_type) = *node;
-	  *node = fnptr_type;
-	}
-    }
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   return NULL_TREE;
 }
diff --git a/gcc/common.opt b/gcc/common.opt
index 71e0c971344..ef018ebf735 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,13 +2687,22 @@ fstrict-overflow
 Common
 Treat signed overflow as undefined.  Negated as -fwrapv -fwrapv-pointer.
 
-; If any strub-enabling attribute is seen when the default value is
-; selected, it's bumped up to -1.  The scrub mode gate function will
-; then bump -2 to 0 if no strub-enabling attribute is seen.  This
-; minimizes the strub overhead.
-fstrub=default
-Common RejectNegative Var(flag_strub, -2) Init(-2)
-Enable stack scrub as requested through attributes.
+fstrub=disable
+Common RejectNegative Var(flag_strub, 0)
+Disable stack scrub entirely, disregarding strub attributes.
+
+fstrub=strict
+Common RejectNegative Var(flag_strub, -4)
+Enable stack scrub as per attributes, with strict call checking.
+
+; If any strub-enabling attribute is seen when the default or strict
+; initializer values are in effect, flag_strub is bumped up by 2.  The
+; scrub mode gate function will then bump these initializer values to
+; 0 if no strub-enabling attribute is seen.  This minimizes the strub
+; overhead.
+fstrub=relaxed
+Common RejectNegative Var(flag_strub, -3) Init(-3)
+Restore default strub mode: as per attributes, with relaxed checking.
 
 fstrub=all
 Common RejectNegative Var(flag_strub, 3)
@@ -2707,10 +2716,6 @@ fstrub=internal
 Common RejectNegative Var(flag_strub, 2)
 Enable internal stack scrubbing for all viable functions.
 
-fstrub=disable
-Common RejectNegative Var(flag_strub, 0)
-Disable stack scrub entirely, disregarding strub attributes.
-
 fsync-libcalls
 Common Var(flag_sync_libcalls) Init(1)
 Implement __atomic operations via libcalls to legacy __sync functions.
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 1fcf60a3875..d85e695d85c 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -77,7 +77,7 @@ extensions, accepted by GCC in C90 mode and in C++.
 * Function Names::      Printable strings which are the name of the current
                         function.
 * Return Address::      Getting the return or frame address of a function.
-* Stack Scrubbing::     Stack scrubbing interfaces.
+* Stack Scrubbing::     Stack scrubbing internal interfaces.
 * Vector Extensions::   Using vector instructions through built-in functions.
 * Offsetof::            Special syntax for implementing @code{offsetof}.
 * __sync Builtins::     Legacy built-in functions for atomic memory access.
@@ -8729,51 +8729,259 @@ pid_t wait (wait_status_ptr_t p)
 @item strub
 @cindex @code{strub} type attribute
 This attribute defines stack-scrubbing properties of functions and
-variables.  When applied to function types, it takes an optional
-argument.  When applied to a pointer-to-function type, it gets
-propagated to the function type if the optional argument is given.
-
-A function whose type is annotated with @code{at-calls} @code{strub}
-mode (@code{strub("at-calls")}, @code{strub(1)}, or @code{strub})
-undergoes interface changes.  Its callers are adjusted to match the
-changes, and to automatically scrub (overwrite with zeros) the stack
-space used by the function.
-
-A function whose type indicates @code{internal} @code{strub} mode
-(@code{strub("internal")} or @code{strub(2)}) retains an unmodified
-interface, but may be turned into a wrapper that calls the wrapped body
-using a custom interface.  The wrapper then scrubs the stack space used
-by the wrapped body.
-
-An automatically-allocated variable whose type carries the @code{strub}
-attribute causes the enclosing function to have @code{strub} enabled.
-
-A statically-allocated variable whose type carries the @code{strub}
-attribute causes functions that @emph{read} it with that type to have
-@code{strub} enabled.  Reading from data referenced by pointers to such
-types has the same effect.  Note: The attribute does not carry over from
-a composite type to the types of its components, so the intended effect
-may not be obtained with non-scalar types.
-
-A @code{strub} context is the body of a function that has strub enabled,
-be it explicitly, by @code{at-calls} or @code{internal} mode, or
-implicitly, by reading from a @code{strub}-marked data type.
+variables.  Being a type attribute, it attaches to types, even when
+specified in function and variable declarations.  When applied to
+function types, it takes an optional string argument.  When applied to a
+pointer-to-function type, if the optional argument is given, it gets
+propagated to the function type.
+
+@smallexample
+/* A strub variable.  */
+int __attribute__ ((strub)) var;
+/* A strub variable that happens to be a pointer.  */
+__attribute__ ((strub)) int *strub_ptr_to_int;
+/* A pointer type that may point to a strub variable.  */
+typedef int __attribute__ ((strub)) *ptr_to_strub_int_type;
+
+/* A declaration of a strub function.  */
+extern int __attribute__ ((strub)) foo (void);
+/* A pointer to that strub function.  */
+int __attribute__ ((strub ("at-calls"))) (*ptr_to_strub_fn)(void) = foo;
+@end smallexample
+
+A function associated with @code{at-calls} @code{strub} mode
+(@code{strub("at-calls")}, or just @code{strub}) undergoes interface
+changes.  Its callers are adjusted to match the changes, and to scrub
+(overwrite with zeros) the stack space used by the called function after
+it returns.  The interface change makes the function type incompatible
+with an unadorned but otherwise equivalent type, so @emph{every}
+declaration and every type that may be used to call the function must be
+associated with this strub mode.
+
+A function associated with @code{internal} @code{strub} mode
+(@code{strub("internal")}) retains an unmodified, type-compatible
+interface, but it may be turned into a wrapper that calls the wrapped
+body using a custom interface.  The wrapper then scrubs the stack space
+used by the wrapped body.  Though the wrapped body has its stack space
+scrubbed, the wrapper does not, so arguments and return values may
+remain unscrubbed even when such a function is called by another
+function that enables @code{strub}.  This is why, when compiling with
+@option{-fstrub=strict}, a @code{strub} context is not allowed to call
+@code{internal} @code{strub} functions.
+
+@smallexample
+/* A declaration of an internal-strub function.  */
+extern int __attribute__ ((strub ("internal"))) bar (void);
+
+int __attribute__ ((strub))
+baz (void)
+@{
+  /* Ok, foo was declared above as an at-calls strub function.  */
+  foo ();
+  /* Not allowed in strict mode, otherwise allowed.  */
+  bar ();
+@}
+@end smallexample
+
+An automatically-allocated variable associated with the @code{strub}
+attribute causes the (immediately) enclosing function to have
+@code{strub} enabled.
+
+A statically-allocated variable associated with the @code{strub}
+attribute causes functions that @emph{read} it, through its @code{strub}
+data type, to have @code{strub} enabled.  Reading data by dereferencing
+a pointer to a @code{strub} data type has the same effect.  Note: The
+attribute does not carry over from a composite type to the types of its
+components, so the intended effect may not be obtained with non-scalar
+types.
+
+When selecting a @code{strub}-enabled mode for a function that is not
+explicitly associated with one, because of @code{strub} variables or
+data pointers, the function must satisfy @code{internal} mode viability
+requirements (see below), even when @code{at-calls} mode is also viable
+and, being more efficient, ends up selected as an optimization.
+
+@smallexample
+/* zapme is implicitly strub-enabled because of strub variables.
+   Optimization may change its strub mode, but not the requirements.  */
+static int
+zapme (int i)
+@{
+  /* A local strub variable enables strub.  */
+  int __attribute__ ((strub)) lvar;
+  /* Reading strub data through a pointer-to-strub enables strub.  */
+  lvar = * (ptr_to_strub_int_type) &i;
+  /* Writing to a global strub variable does not enable strub.  */
+  var = lvar;
+  /* Reading from a global strub variable enables strub.  */
+  return var;
+@}
+@end smallexample
+
+A @code{strub} context is the body (as opposed to the interface) of a
+function that has @code{strub} enabled, be it explicitly, by
+@code{at-calls} or @code{internal} mode, or implicitly, due to
+@code{strub} variables or command-line options.
 
 A function of a type associated with the @code{disabled} @code{strub}
-mode (@code{strub("disabled")}, @code{strub(0)}, or no @code{strub} mode
-specified) will not have its own stack space scrubbed, and it cannot be
-called from @code{strub} contexts.
+mode (@code{strub("disabled")} will not have its own stack space
+scrubbed.  Such functions @emph{cannot} be called from within
+@code{strub} contexts.
+
+In order to enable a function to be called from within @code{strub}
+contexts without having its stack space scrubbed, associate it with the
+@code{callable} @code{strub} mode (@code{strub("callable")}).
+
+When a function is not assigned a @code{strub} mode, explicitly or
+implicitly, the mode defaults to @code{callable}, except when compiling
+with @option{-fstrub=strict}, that causes @code{strub} mode to default
+to @code{disabled}.
+
+@example
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+ /* Implicitly disabled with -fstrub=strict, otherwise callable.  */
+extern int bah (void);
+
+int __attribute__ ((strub))
+bal (void)
+@{
+  /* Not allowed, bad is not strub-callable.  */
+  bad ();
+  /* Ok, bac is strub-callable.  */
+  bac ();
+  /* Not allowed with -fstrub=strict, otherwise allowed.  */
+  bah ();
+@}
+@end example
 
-A function that does not have @code{strub} enabled can only be called
-from within @code{strub} contexts through a function type marked with
-the @code{callable} @code{strub} mode (@code{strub("callable")} or
-@code{strub(3)}).
+Function types marked @code{callable} and @code{disabled} are not
+mutually compatible types, but the underlying interfaces are compatible,
+so it is safe to convert pointers between them, and to use such pointers
+or alternate declarations to call them.  Interfaces are also
+interchangeable between them and @code{internal} (but not
+@code{at-calls}!), but adding @code{internal} to a pointer type will not
+cause the pointed-to function to perform stack scrubbing.
+
+@example
+void __attribute__ ((strub))
+bap (void)
+@{
+  /* Assign a callable function to pointer-to-disabled.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bac;
+  /* Not allowed: calls disabled type in a strub context.  */
+  d_p ();
+
+  /* Assign a disabled function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bad;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an internal function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  c_p = bar;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an at-calls function to pointer-to-callable.
+     Flaggged as incompatible.  */
+  c_p = bal;
+  /* The call through an interface-incompatible type will not use the
+     modified interface expected by the at-calls function, so it is
+     likely to misbehave at runtime.  */
+  c_p ();
+@}
+@end example
 
 @code{Strub} contexts are never inlined into non-@code{strub} contexts.
-When an @code{internal}-strub function is split, the wrapper can often
-be inlined, but the wrapped body never is.  Functions marked as
-@code{always_inline}, even if explicitly assigned @code{internal} strub
-mode, will not undergo wrapping, so their body gets inlined.
+When an @code{internal}-strub function is split up, the wrapper can
+often be inlined, but the wrapped body @emph{never} is.  A function
+marked as @code{always_inline}, even if explicitly assigned
+@code{internal} strub mode, will not undergo wrapping, so its body gets
+inlined as required.
+
+@example
+inline int __attribute__ ((strub ("at-calls")))
+inl_atc (void)
+@{
+  /* This body may get inlined into strub contexts.  */
+@}
+
+inline int __attribute__ ((strub ("internal")))
+inl_int (void)
+@{
+  /* This body NEVER gets inlined, though its wrapper may.  */
+@}
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+@{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+@}
+
+void __attribute__ ((strub ("disabled")))
+bat (void)
+@{
+  /* Not allowed, cannot inline into a non-strub context.  */
+  inl_int_ali ();
+@}
+@end example
+
+@cindex strub eligibility and viability
+Some @option{-fstrub=*} command line options enable @code{strub} modes
+implicitly where viable.  A @code{strub} mode is only viable for a
+function if the function is eligible for that mode, and if other
+conditions, detailed below, are satisfied.  If it's not eligible for a
+mode, attempts to explicitly associate it with that mode are rejected
+with an error message.  If it is eligible, that mode may be assigned
+explicitly through this attribute, but implicit assignment through
+command-line options may involve additional viability requirements.
+
+A function is ineligible for @code{at-calls} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+@code{At-calls} @code{strub} mode, if not requested through the function
+type, is only viable for an eligible function if the function is not
+visible to other translation units, and if it doesn't have its address
+taken.
+
+@smallexample
+/* bar is eligible for at-calls strub mode,
+   but not viable for that mode because it is visible to other units.
+   It is eligible and viable for internal strub mode.  */
+void bav () @{@}
+
+/* setp is eligible for at-calls strub mode,
+   but not viable for that mode because its address is taken.
+   It is eligible and viable for internal strub mode.  */
+void setp (void) @{ static void (*p)(void); = setp; @}
+@end smallexample
+
+A function is ineligible for @code{internal} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, or if attribute
+@code{noipa} is present.  For an @code{always_inline} function, meeting
+these requirements is enough to make it eligible.  Any function that has
+attribute @code{noclone}, that uses such extensions as non-local labels,
+computed gotos, alternate variable argument passing interfaces,
+@code{__builtin_next_arg}, or @code{__builtin_return_address}, or that
+takes too many (about 64Ki) arguments is ineligible, unless it is
+@code{always_inline}.  For @code{internal} @code{strub} mode, all
+eligible functions are viable.
+
+@smallexample
+/* flop is not eligible, thus not viable, for at-calls strub mode.
+   Likewise for internal strub mode.  */
+__attribute__ ((noipa)) void flop (void) @{@}
+
+/* flip is eligible and viable for at-calls strub mode.
+   It would be ineligible for internal strub mode, because of noclone,
+   if it weren't for always_inline.  With always_inline, noclone is not
+   an obstacle, so it is also eligible and viable for internal strub mode.  */
+inline __attribute__ ((noclone, always_inline)) void flip (void) @{@}
+@end smallexample
 
 @item unused
 @cindex @code{unused} type attribute
@@ -11804,40 +12012,48 @@ This function returns the value of the stack pointer register.
 @end deftypefn
 
 @node Stack Scrubbing
-@section Stack scrubbing interfaces
+@section Stack scrubbing internal interfaces
 
 Stack scrubbing involves cooperation between a @code{strub} context,
 i.e., a function whose stack frame is to be zeroed-out, and its callers.
 The caller initializes a stack watermark, the @code{strub} context
-updates the watermark to reflect its stack use, and the caller zeroes it
-out once it regains control.
-
-Each of these steps relies on a different builtin function call.  The
-functions are available in libgcc but, depending on optimization levels,
-they are expanded internally, adjusted to account for inlining, or
-combined/deferred (e.g. passing the caller-supplied watermark on to
-callees, refraining from erasing stack areas that the caller will).
+updates the watermark according to its stack use, and the caller zeroes
+it out once it regains control, whether by the callee's returning or by
+an exception.
+
+Each of these steps is performed by a different builtin function call.
+Calls to these builtins are introduced automatically, in response to
+@code{strub} attributes and command-line options; they are not expected
+to be explicitly called by source code.
+
+The functions that implement the builtins are available in libgcc but,
+depending on optimization levels, they are expanded internally, adjusted
+to account for inlining, and sometimes combined/deferred (e.g. passing
+the caller-supplied watermark on to callees, refraining from erasing
+stack areas that the caller will) to enable tail calls and to optimize
+for code size.
 
 @deftypefn {Built-in Function} {void} __builtin___strub_enter (void **@var{wmptr})
 This function initializes a stack @var{watermark} variable with the
-current top of the stack.  This builtin function should be called before
-entering a @code{strub} context.  It remains as a function call if optimization
-is not enabled.
+current top of the stack.  A call to this builtin function is introduced
+before entering a @code{strub} context.  It remains as a function call
+if optimization is not enabled.
 @end deftypefn
 
 @deftypefn {Built-in Function} {void} __builtin___strub_update (void **@var{wmptr})
 This function updates a stack @var{watermark} variable with the current
-top of the stack, if it tops the previous watermark.  This builtin
-function should be called within a @code{strub} context whenever
+top of the stack, if it tops the previous watermark.  A call to this
+builtin function is inserted within @code{strub} contexts, whenever
 additional stack space may have been used.  It remains as a function
 call at optimization levels lower than 2.
 @end deftypefn
 
 @deftypefn {Built-in Function} {void} __builtin___strub_leave (void **@var{wmptr})
 This function overwrites the memory area between the current top of the
-stack, and the @var{watermark}ed address.  This builtin function should
-be called after leaving a @code{strub} context.  It remains as a
-function call at optimization levels lower than 3.
+stack, and the @var{watermark}ed address.  A call to this builtin
+function is inserted after leaving a @code{strub} context.  It remains
+as a function call at optimization levels lower than 3, and it is guarded by
+a condition at level 2.
 @end deftypefn
 
 @node Vector Extensions
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 838d9ff1198..afcb37914ac 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,7 +599,8 @@ Objective-C and Objective-C++ Dialects}.
 -fstack-protector-explicit  -fstack-check @gol
 -fstack-limit-register=@var{reg}  -fstack-limit-symbol=@var{sym} @gol
 -fno-stack-limit  -fsplit-stack @gol
--fstrub=default -fstrub=disable -fstrub=at-calls -fstrub=internal -fstrub=all @gol
+-fstrub=disable -fstrub=strict -fstrub=relaxed @gol
+-fstrub=all -fstrub=at-calls -fstrub=internal @gol
 -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
 -fvtv-counts  -fvtv-debug @gol
 -finstrument-functions @gol
@@ -15513,46 +15514,55 @@ without @option{-fsplit-stack} always has a large stack.  Support for
 this is implemented in the gold linker in GNU binutils release 2.21
 and later.
 
-@item -fstrub=default
-@opindex fstrub=default
-Restore the default stack scrub (@code{strub}) setting, namely,
-@code{strub} is only enabled as required by @code{strub} attributes
-associated with function or variable types.  This is only useful to
-override earlier @samp{-fstrub=*} options.
-
 @item -fstrub=disable
 @opindex -fstrub=disable
 Disable stack scrubbing entirely, ignoring any @code{strub} attributes.
+See @xref{Common Type Attributes}.
+
+@item -fstrub=strict
+@opindex fstrub=strict
+Functions default to @code{strub} mode @code{disabled}, and apply
+@option{strict}ly the restriction that only functions associated with
+@code{strub}-@code{callable} modes (@code{at-calls}, @code{callable} and
+@code{always_inline} @code{internal}) are @code{callable} by functions
+with @code{strub}-enabled modes (@code{at-calls} and @code{internal}).
+
+@item -fstrub=relaxed
+@opindex fstrub=relaxed
+Restore the default stack scrub (@code{strub}) setting, namely,
+@code{strub} is only enabled as required by @code{strub} attributes
+associated with function and data types.  @code{Relaxed} means that
+strub contexts are only prevented from calling functions explicitly
+associated with @code{strub} mode @code{disabled}.  This option is only
+useful to override other @option{-fstrub=*} options that precede it in
+the command line.
 
 @item -fstrub=at-calls
 @opindex fstrub=at-calls
-Enable @code{at-calls} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{at-calls} @code{strub} if a different @code{strub}
-mode is explicitly requested, if attribute @code{noipa} is present, or
-if it calls @code{__builtin_apply_args}.  @code{At-calls} @code{strub}
-mode, if not requested through the function type, is only viable for an
-eligible function if the function is not visible to other translation
-units, and it doesn't have its address taken.
+Enable @code{at-calls} @code{strub} mode where viable.  The primary use
+of this option is for testing.  It exercises the @code{strub} machinery
+in scenarios strictly local to a translation unit.  This @code{strub}
+mode modifies function interfaces, so any function that is visible to
+other translation units, or that has its address taken, will @emph{not}
+be affected by this option.  Optimization options may also affect
+viability.  See the @code{strub} attribute documentation for details on
+viability and eligibility requirements.
 
 @item -fstrub=internal
 @opindex fstrub=internal
-Enable @code{internal} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{internal} @code{strub} if a different @code{strub}
-mode is explicitly requested, or if attribute @code{noipa} is present.
-Non-@code{always_inline} functions also become ineligible if attribute
-@code{noclone} is present, if the function uses such features as user
-labels, non-default variable argument interfaces,
-@code{__builtin_next_arg}, or @code{__builtin_return_address}, or if
-they have too many (about 64Ki) arguments.  For @code{internal}
-@code{strub}, all eligible functions are viable.
+Enable @code{internal} @code{strub} mode where viable.  The primary use
+of this option is for testing.  This option is intended to exercise
+thoroughly parts of the @code{strub} machinery that implement the less
+efficient, but interface-preserving @code{strub} mode.  Functions that
+would not be affected by this option are quite uncommon.
 
 @item -fstrub=all
 @opindex fstrub=all
-Enable @code{strub} for all viable functions, and consider non-viable
-functions as @code{callable}.  When both strub modes are viable,
-@code{at-calls} is preferred.
+Enable some @code{strub} mode where viable.  When both strub modes are
+viable, @code{at-calls} is preferred.  @option{-fdump-ipa-strubm} adds
+function attributes that tell which mode was selected for each function.
+The primary use of this option is for testing, to exercise thoroughly
+the @code{strub} machinery.
 
 @item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 @opindex fvtable-verify
@@ -17427,6 +17437,14 @@ and inlining decisions.
 @item inline
 Dump after function inlining.
 
+@item strubm
+Dump after selecting @code{strub} modes, and recording the selections as
+function attributes.
+
+@item strub
+Dump @code{strub} transformations: interface changes, function wrapping,
+and insertion of builtin calls for stack scrubbing and watermarking.
+
 @end table
 
 Additionally, the options @option{-optimized}, @option{-missed},
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 7f4bc44d2bb..932e49dba8e 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -397,7 +397,7 @@ can_inline_edge_p (struct cgraph_edge *e, bool report,
       e->inline_failed = CIF_SANITIZE_ATTRIBUTE_MISMATCH;
       inlinable = false;
     }
-  if (!strub_inlinable_p (callee, caller))
+  if (!strub_inlinable_to_p (callee, caller))
     {
       e->inline_failed = CIF_UNSPECIFIED;
       inlinable = false;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
index cf7d811779e..6b271f580af 100644
--- a/gcc/ipa-strub.c
+++ b/gcc/ipa-strub.c
@@ -159,12 +159,16 @@ enum strub_mode {
 
 };
 
+/* Look up a strub attribute in TYPE, and return it.  */
+
 static tree
 get_strub_attr_from_type (tree type)
 {
   return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
 }
 
+/* Look up a strub attribute in DECL or in its type, and return it.  */
+
 static tree
 get_strub_attr_from_decl (tree decl)
 {
@@ -174,23 +178,161 @@ get_strub_attr_from_decl (tree decl)
   return get_strub_attr_from_type (TREE_TYPE (decl));
 }
 
-tree
+/* Define a function to cache identifier ID, to be used as a strub attribute
+   parameter for a strub mode named after NAME.  */
+#define DEF_STRUB_IDS(NAME, ID)				\
+static inline tree get_strub_mode_id_ ## NAME () {	\
+  static tree identifier = NULL_TREE;			\
+  if (!identifier)					\
+    identifier = get_identifier (ID);			\
+  return identifier;					\
+}
+/* Same as DEF_STRUB_IDS, but use the string expansion of NAME as ID.  */
+#define DEF_STRUB_ID(NAME)				\
+DEF_STRUB_IDS (NAME, #NAME)
+
+/* Define functions for each of the strub mode identifiers.
+   Expose dashes rather than underscores.  */
+DEF_STRUB_ID (disabled)
+DEF_STRUB_IDS (at_calls, "at-calls")
+DEF_STRUB_ID (internal)
+DEF_STRUB_ID (callable)
+DEF_STRUB_ID (wrapped)
+DEF_STRUB_ID (wrapper)
+DEF_STRUB_ID (inlinable)
+DEF_STRUB_IDS (at_calls_opt, "at-calls-opt")
+
+/* Release the temporary macro names.  */
+#undef DEF_STRUB_IDS
+#undef DEF_STRUB_ID
+
+/* Return the identifier corresponding to strub MODE.  */
+
+static tree
+get_strub_mode_attr_parm (enum strub_mode mode)
+{
+  switch (mode)
+    {
+    case STRUB_DISABLED:
+      return get_strub_mode_id_disabled ();
+
+    case STRUB_AT_CALLS:
+      return get_strub_mode_id_at_calls ();
+
+    case STRUB_INTERNAL:
+      return get_strub_mode_id_internal ();
+
+    case STRUB_CALLABLE:
+      return get_strub_mode_id_callable ();
+
+    case STRUB_WRAPPED:
+      return get_strub_mode_id_wrapped ();
+
+    case STRUB_WRAPPER:
+      return get_strub_mode_id_wrapper ();
+
+    case STRUB_INLINABLE:
+      return get_strub_mode_id_inlinable ();
+
+    case STRUB_AT_CALLS_OPT:
+      return get_strub_mode_id_at_calls_opt ();
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+/* Return the parmeters (TREE_VALUE) for a strub attribute of MODE.
+   We know we use a single parameter, so we bypass the creation of a
+   tree list.  */
+
+static tree
 get_strub_mode_attr_value (enum strub_mode mode)
 {
-  return tree_cons (NULL_TREE,
-		    build_int_cst (integer_type_node, (int)mode),
-		    NULL_TREE);
+  return get_strub_mode_attr_parm (mode);
+}
+
+/* Determine whether ID is a well-formed strub mode-specifying attribute
+   parameter for a function (type).  Only user-visible modes are accepted, and
+   ID must be non-NULL.
+
+   For unacceptable parms, return 0, otherwise a nonzero value as below.
 
-#if 0 /* ??? use symbolic mode names with interned strings?  */
-  char *s = NULL;
+   If the parm enables strub, return positive, otherwise negative.
 
-  switch (strub_mode)
+   If the affected type must be a distinct, incompatible type,return an integer
+   of absolute value 2, otherwise 1.  */
+
+int
+strub_validate_fn_attr_parm (tree id)
+{
+  int ret;
+  const char *s = NULL;
+  size_t len = 0;
+
+  /* do NOT test for NULL.  This is only to be called with non-NULL arguments.
+     We assume that the strub parameter applies to a function, because only
+     functions accept an explicit argument.  If we accepted NULL, and we
+     happened to be called to verify the argument for a variable, our return
+     values would be wrong.  */
+  if (TREE_CODE (id) == STRING_CST)
     {
-      
+      s = TREE_STRING_POINTER (id);
+      len = TREE_STRING_LENGTH (id) - 1;
     }
-#endif
+  else if (TREE_CODE (id) == IDENTIFIER_NODE)
+    {
+      s = IDENTIFIER_POINTER (id);
+      len = IDENTIFIER_LENGTH (id);
+    }
+  else
+    return 0;
+
+  enum strub_mode mode;
+
+  if (len != 8)
+    return 0;
+
+  switch (s[0])
+    {
+    case 'd':
+      mode = STRUB_DISABLED;
+      ret = -1;
+      break;
+
+    case 'a':
+      mode = STRUB_AT_CALLS;
+      ret = 2;
+      break;
+
+    case 'i':
+      mode = STRUB_INTERNAL;
+      ret = 1;
+      break;
+
+    case 'c':
+      mode = STRUB_CALLABLE;
+      ret = -2;
+      break;
+
+    default:
+      /* Other parms are for internal use only.  */
+      return 0;
+    }
+
+  tree mode_id = get_strub_mode_attr_parm (mode);
+
+  if (TREE_CODE (id) == IDENTIFIER_NODE
+      ? id != mode_id
+      : strncmp (s, IDENTIFIER_POINTER (mode_id), len) != 0)
+    return 0;
+
+  return ret;
 }
 
+/* Return the strub mode from STRUB_ATTR.  VAR_P should be true if the attribute
+   is taken from a variable, rather than from a function, or a type thereof.  */
+
 static enum strub_mode
 get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
 {
@@ -200,28 +342,102 @@ get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
     {
       if (!TREE_VALUE (strub_attr))
 	mode = !var_p ? STRUB_AT_CALLS : STRUB_INTERNAL;
-      else if (TREE_CODE (TREE_VALUE (TREE_VALUE (strub_attr))) == INTEGER_CST)
-	mode = (enum strub_mode) tree_to_shwi (TREE_VALUE
-					       (TREE_VALUE (strub_attr)));
-      else /* Handlers convert symbolic mode names to INTEGER_CST.  */
-	gcc_unreachable ();
+      else
+	{
+	  gcc_checking_assert (!var_p);
+	  tree id = TREE_VALUE (strub_attr);
+	  if (TREE_CODE (id) == TREE_LIST)
+	    id = TREE_VALUE (id);
+	  const char *s = (TREE_CODE (id) == STRING_CST
+			   ? TREE_STRING_POINTER (id)
+			   : IDENTIFIER_POINTER (id));
+	  size_t len = (TREE_CODE (id) == STRING_CST
+			? TREE_STRING_LENGTH (id) - 1
+			: IDENTIFIER_LENGTH (id));
+
+	  switch (len)
+	    {
+	    case 7:
+	      switch (s[6])
+		{
+		case 'r':
+		  mode = STRUB_WRAPPER;
+		  break;
+
+		case 'd':
+		  mode = STRUB_WRAPPED;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 8:
+	      switch (s[0])
+		{
+		case 'd':
+		  mode = STRUB_DISABLED;
+		  break;
+
+		case 'a':
+		  mode = STRUB_AT_CALLS;
+		  break;
+
+		case 'i':
+		  mode = STRUB_INTERNAL;
+		  break;
+
+		case 'c':
+		  mode = STRUB_CALLABLE;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 9:
+	      mode = STRUB_INLINABLE;
+	      break;
+
+	    case 12:
+	      mode = STRUB_AT_CALLS_OPT;
+	      break;
+
+	    default:
+	      gcc_unreachable ();
+	    }
+
+	  gcc_checking_assert (TREE_CODE (id) == IDENTIFIER_NODE
+			       ? id == get_strub_mode_attr_parm (mode)
+			       : strncmp (IDENTIFIER_POINTER
+					  (get_strub_mode_attr_parm (mode)),
+					  s, len) == 0);
+	}
     }
 
   return mode;
 }
 
+/* Look up, decode and return the strub mode associated with FNDECL.  */
+
 static enum strub_mode
-get_strub_mode_from_decl (tree fndecl)
+get_strub_mode_from_fndecl (tree fndecl)
 {
   return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
 }
 
+/* Look up, decode and return the strub mode associated with NODE.  */
+
 static enum strub_mode
 get_strub_mode (cgraph_node *node)
 {
-  return get_strub_mode_from_decl (node->decl);
+  return get_strub_mode_from_fndecl (node->decl);
 }
 
+/* Look up, decode and return the strub mode associated with TYPE.  */
+
 static enum strub_mode
 get_strub_mode_from_type (tree type)
 {
@@ -231,12 +447,15 @@ get_strub_mode_from_type (tree type)
   if (attr)
     return get_strub_mode_from_attr (attr, var_p);
 
-  if (flag_strub > 0 && !var_p)
+  if (flag_strub >= -1 && !var_p)
     return STRUB_CALLABLE;
 
   return STRUB_DISABLED;
 }
 
+\f
+/* Return true iff NODE calls builtin va_start.  */
+
 static bool
 calls_builtin_va_start_p (cgraph_node *node)
 {
@@ -252,6 +471,8 @@ calls_builtin_va_start_p (cgraph_node *node)
   return result;
 }
 
+/* Return true iff NODE calls builtin apply_args, and optionally REPORT it.  */
+
 static bool
 calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
 {
@@ -276,12 +497,17 @@ calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE carries the always_inline attribute.  */
+
 static inline bool
 strub_always_inline_p (cgraph_node *node)
 {
   return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
 }
 
+/* Return true iff NODE is potentially eligible for any strub-enabled mode, and
+   optionally REPORT the reasons for ineligibility.  */
+
 static inline bool
 can_strub_p (cgraph_node *node, bool report = false)
 {
@@ -321,6 +547,10 @@ can_strub_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE is eligible for at-calls strub, and optionally REPORT
+   the reasons for ineligibility.  Besides general non-eligibility for
+   strub-enabled modes, at-calls rules out calling builtin apply_args.  */
+
 static bool
 can_strub_at_calls_p (cgraph_node *node, bool report = false)
 {
@@ -332,6 +562,9 @@ can_strub_at_calls_p (cgraph_node *node, bool report = false)
   return !calls_builtin_apply_args_p (node, report);
 }
 
+/* Symbolic macro for the max number of arguments that internal strub may add to
+   a function.  */
+
 #define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
 
 /* We can't perform internal strubbing if the function body involves certain
@@ -438,14 +671,12 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 			   || tree_versionable_function_p (node->decl));
 
 
-      /* Label values referenced are not preserved when copying.  If referenced
+      /* Label values references are not preserved when copying.  If referenced
 	 in nested functions, as in 920415-1.c and 920721-4.c their decls get
-	 remapped independently.  That might be too broad, in that we might be
-	 able to support correctly cases in which the labels are only used
-	 internally in a function, but disconnecting user labels from their
-	 original declarations is undesirable in general, and it probably
-	 doesn't matter, since explicitly-requested strub likely uses
-	 STRUB_AT_CALLS mode anyway.  */
+	 remapped independently.  The exclusion below might be too broad, in
+	 that we might be able to support correctly cases in which the labels
+	 are only used internally in a function, but disconnecting forced labels
+	 from their original declarations is undesirable in general.  */
       basic_block bb;
       FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
 	for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -459,8 +690,6 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 
 	    target = gimple_label_label (label_stmt);
 
-	    /* Make an edge to every label block that has been marked as a
-	       potential target for a computed goto or a non-local goto.  */
 	    if (!FORCED_LABEL (target))
 	      continue;
 
@@ -470,7 +699,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 forced labels");
 	  }
     }
 
@@ -491,6 +720,9 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return TRUE iff NODE has any strub-requiring local variable, or accesses (as
+   in reading) any variable through a strub-requiring type.  */
+
 static bool
 strub_from_body_p (cgraph_node *node)
 {
@@ -506,7 +738,9 @@ strub_from_body_p (cgraph_node *node)
 	!= STRUB_DISABLED)
       return true;
 
-  /* Now scan the body for loads with strub types.  */
+  /* Now scan the body for loads with strub-requiring types.
+     ??? Compound types don't propagate the strub requirement to
+     component types.  */
   basic_block bb;
   FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
     for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -528,6 +762,7 @@ strub_from_body_p (cgraph_node *node)
 
 /* Return true iff node is associated with a builtin that should be callable
    from strub contexts.  */
+
 static inline bool
 strub_callable_builtin_p (cgraph_node *node)
 {
@@ -568,17 +803,23 @@ strub_callable_builtin_p (cgraph_node *node)
     }
 }
 
+/* Compute the strub mode to be used for NODE.  STRUB_ATTR should be the strub
+   attribute,found for NODE, if any.  */
+
 static enum strub_mode
 compute_strub_mode (cgraph_node *node, tree strub_attr)
 {
   enum strub_mode req_mode = get_strub_mode_from_attr (strub_attr);
 
-  gcc_checking_assert (flag_strub >= -1 && flag_strub <= 3);
+  gcc_checking_assert (flag_strub >= -2 && flag_strub <= 3);
 
   /* Symbolic encodings of the -fstrub-* flags.  */
   /* Enable strub when explicitly requested through attributes to functions or
      variables, reporting errors if the requests cannot be satisfied.  */
   const bool strub_flag_auto = flag_strub < 0;
+  /* strub_flag_auto with strub call verification; without this, functions are
+     implicitly callable.  */
+  const bool strub_flag_strict = flag_strub < -1;
   /* Disable strub altogether, ignore attributes entirely.  */
   const bool strub_flag_disabled = flag_strub == 0;
   /* On top of _auto, also enable strub implicitly for functions that can
@@ -625,7 +866,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (!strub_flag_disabled
        && (strub_attr
 	   ? req_mode == STRUB_CALLABLE
-	   : (strub_flag_viable
+	   : (!strub_flag_strict
 	      || strub_callable_builtin_p (node))));
 
   /* This is a shorthand for either strub-enabled mode.  */
@@ -674,19 +915,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (at_calls_eligible
        && (strub_attr
 	   || (node->has_gimple_body_p ()
-#if 0 /* We no longer use collect_callers, so we can probably drop it.  */
-	       && node->get_availability () > AVAIL_INTERPOSABLE
-#endif
-	       && ((!node->externally_visible
-#if 0
-		    /* We wish to bypass the test below for functions that are
-		       not externally visible, but that's a little too broad: we
-		       do not wish to skip them for e.g. gnu_inline
-		       functions.  */
-		    && !TREE_PUBLIC (node->decl)
-		    && !DECL_EXTERNAL (node->decl)
-#endif
-		    )
+	       && (!node->externally_visible
 		   || (node->binds_to_current_def_p ()
 		       && node->can_be_local_p ()))
 	       && node->only_called_directly_p ())));
@@ -737,10 +966,6 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
   const enum strub_mode mode
     = ((strub_enable && is_always_inline)
        ? (strub_required ? STRUB_INLINABLE : STRUB_CALLABLE)
-#if 0
-       : (!strub_enable && strub_required && strub_attr)
-       ? req_mode
-#endif
        : (strub_enable && internal_viable
 	  && (strub_flag_internal || !at_calls_viable))
        ? STRUB_INTERNAL
@@ -802,6 +1027,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 /* Set FNDT's strub mode to MODE; FNDT may be a function decl or
    function type.  If OVERRIDE, do not check whether a mode is already
    set.  */
+
 static void
 strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
 {
@@ -828,12 +1054,18 @@ strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
   *attrp = attr;
 }
 
+/* Set FNDT's strub mode to callable.
+   FNDT may be a function decl or a function type.  */
+
 void
 strub_make_callable (tree fndt)
 {
   strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
 }
 
+/* Set NODE to strub MODE.  Report incompatibilities between MODE and the mode
+   requested through explicit attributes, and cases of non-eligibility.  */
+
 static void
 set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
 {
@@ -853,9 +1085,10 @@ 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",
-		    (int) mode, node->decl,
-		    (int) get_strub_mode_from_attr (attr));
+		    "%<strub%> mode %qE selected for %qD, when %qE was requested",
+		    get_strub_mode_attr_parm (mode),
+		    node->decl,
+		    get_strub_mode_attr_parm (req_mode));
 	  if (node->alias)
 	    {
 	      cgraph_node *target = node->ultimate_alias_target ();
@@ -906,6 +1139,8 @@ set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
   strub_set_fndt_mode_to (node->decl, mode, attr);
 }
 
+/* Compute and set NODE's strub mode.  */
+
 static void
 set_strub_mode (cgraph_node *node)
 {
@@ -946,6 +1181,7 @@ set_strub_mode (cgraph_node *node)
   set_strub_mode_to (node, mode);
 }
 
+\f
 /* Non-strub functions shouldn't be called from within strub contexts,
    except through callable ones.  Always inline strub functions can
    only be called from strub functions.  */
@@ -984,7 +1220,7 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_AT_CALLS_OPT:
     case STRUB_INTERNAL:
     case STRUB_WRAPPER:
-      return (flag_strub >= 0);
+      return (flag_strub >= -1);
 
     case STRUB_DISABLED:
       return false;
@@ -999,12 +1235,16 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
   return true;
 }
 
-/* We wish to avoid inlining WRAPPED functions back into their
-   WRAPPERs.  More generally, we wish to avoid inlining
-   strubbed functions into non-strubbed ones.  */
+/* Return true if CALLEE can be inlined into CALLER.  We wish to avoid inlining
+   WRAPPED functions back into their WRAPPERs.  More generally, we wish to avoid
+   inlining strubbed functions into non-strubbed ones.  CALLER doesn't have to
+   be an immediate caller of CALLEE: the immediate caller may have already been
+   cloned for inlining, and then CALLER may be further up the original call
+   chain.  ???  It would be nice if our own caller would retry inlining callee
+   if caller gets inlined.  */
 
 bool
-strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+strub_inlinable_to_p (cgraph_node *callee, cgraph_node *caller)
 {
   strub_mode callee_mode = get_strub_mode (callee);
 
@@ -1020,6 +1260,10 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_WRAPPER:
     case STRUB_DISABLED:
     case STRUB_CALLABLE:
+      /* When we consider inlining, we've already verified callability, so we
+	 can even inline callable and then disabled into a strub context.  That
+	 will get strubbed along with the context, so it's hopefully not a
+	 problem.  */
       return true;
 
     default:
@@ -1049,20 +1293,50 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
   return false;
 }
 
+/* Check that types T1 and T2 are strub-compatible.  Return 1 if the strub modes
+   are the same, 2 if they are interchangeable, and 0 otherwise.  */
+
+int
+strub_comptypes (tree t1, tree t2)
+{
+  enum strub_mode m1 = get_strub_mode_from_type (t1);
+  enum strub_mode m2 = get_strub_mode_from_type (t2);
+
+  if (m1 == m2)
+    return 1;
+
+  /* We're dealing with types, so only strub modes that can be selected by
+     attributes in the front end matter.  If either mode is at-calls, the
+     conversion is not compatible.  */
+  if (m1 == STRUB_AT_CALLS || m2 == STRUB_AT_CALLS)
+    return 0;
+
+  return 2;
+}
+
 /* Check that strub functions don't call non-strub functions, and that
    always_inline strub functions are only called by strub
    functions.  */
+
 static void
 verify_strub ()
 {
   cgraph_node *node;
 
+  /* It's expected that check strub-wise pointer type compatibility of variables
+     and of functions is already taken care of by front-ends, on account of the
+     attribute's being marked as affecting type identity and of the creation of
+     distinct types.  */
+
+  /* Check that call targets in strub contexts have strub-callable types.  */
+
   FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
   {
     enum strub_mode caller_mode = get_strub_mode (node);
     bool strub_context
       = (caller_mode == STRUB_AT_CALLS
 	 || caller_mode == STRUB_AT_CALLS_OPT
+	 || caller_mode == STRUB_INTERNAL
 	 || caller_mode == STRUB_WRAPPED
 	 || caller_mode == STRUB_INLINABLE);
 
@@ -1105,14 +1379,11 @@ verify_strub ()
 	  }
       }
   }
-
-  /* ??? Check strub-wise pointer type compatibility of variables and
-     functions, or is this already taken care of on account of the
-     attribute's being marked as affecting type identity?  */
 }
 
 namespace {
 
+/* Define a pass to compute strub modes.  */
 const pass_data pass_data_ipa_strub_mode = {
   SIMPLE_IPA_PASS,
   "strubm",
@@ -1133,24 +1404,26 @@ public:
   {}
   opt_pass *clone () { return new pass_ipa_strub_mode (m_ctxt); }
   virtual bool gate (function *) {
-    /* In the default setting, the attribute handler changes
-       flag_strub to -1 if any strub-enabling occurence of the
-       attribute is found.  If it remains at -2, nothing that would
-       enable strub was found, so we can disable it and avoid the
+    /* In relaxed (-3) and strict (-4) settings, that only enable strub at a
+       function or variable attribute's request, the attribute handler changes
+       flag_strub to -1 or -2, respectively, if any strub-enabling occurence of
+       the attribute is found.  Therefore, if it remains at -3 or -4, nothing
+       that would enable strub was found, so we can disable it and avoid the
        overhead.  */
-    if (flag_strub == -2)
+    if (flag_strub < -2)
       flag_strub = 0;
     return flag_strub;
   }
   virtual unsigned int execute (function *);
 };
 
+/* Define a pass to introduce strub transformations.  */
 const pass_data pass_data_ipa_strub = {
   SIMPLE_IPA_PASS,
   "strub",
   OPTGROUP_NONE,
   TV_NONE,
-  PROP_cfg, // properties_required
+  PROP_cfg | PROP_ssa, // properties_required
   0,	    // properties_provided
   0,	    // properties_destroyed
   0,	    // properties_start
@@ -1170,6 +1443,7 @@ public:
   virtual bool gate (function *) { return flag_strub; }
   virtual unsigned int execute (function *);
 
+  /* Define on demand and cache some types we use often.  */
 #define DEF_TYPE(NAME, INIT)			\
   static inline tree get_ ## NAME () {		\
     static tree type = NULL_TREE;		\
@@ -1202,6 +1476,7 @@ public:
 
 #undef DEF_TYPE
 
+  /* Define non-strub builtins on demand.  */
 #define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST)			\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1224,6 +1499,7 @@ public:
 
 #undef DEF_NM_BUILTIN
 
+  /* Define strub builtins on demand.  */
 #define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST)		\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1260,6 +1536,7 @@ public:
 
 #undef DEF_SS_BUILTIN
 
+    /* Define strub identifiers on demand.  */
 #define DEF_IDENT(NAME)					\
   static inline tree get_ ## NAME () {			\
     static tree identifier = NULL_TREE;			\
@@ -1278,6 +1555,10 @@ public:
   static inline void adjust_at_calls_call (cgraph_edge *, int);
   static inline void adjust_at_calls_calls (cgraph_node *);
 
+  /* Add to SEQ a call to the strub watermark update builtin, taking NODE's
+     location if given.  Optionally add the corresponding edge from NODE, with
+     execution frequency COUNT.  Return the modified SEQ.  */
+
   static inline gimple_seq
   call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
 			 gimple_seq seq = NULL)
@@ -1300,8 +1581,15 @@ public:
 
 } // anon namespace
 
+/* Gather with this type a collection of parameters that we're turning into
+   explicit references.  */
+
 typedef hash_set<tree> indirect_parms_t;
 
+/* Dereference OP's incoming turned-into-reference parm if it's an
+   INDIRECT_PARMS or an ADDR_EXPR thereof.  Set *REC and return according to
+   gimple-walking expectations.  */
+
 static tree
 maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
 {
@@ -1324,12 +1612,17 @@ maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
     {
       *rec = 0;
       if (indirect_parms.contains (TREE_OPERAND (op, 0)))
-	return TREE_OPERAND (op, 0);
+	{
+	  op = TREE_OPERAND (op, 0);
+	  return op;
+	}
     }
 
   return NULL_TREE;
 }
 
+/* A gimple-walking function that adds dereferencing to indirect parms.  */
+
 static tree
 walk_make_indirect (tree *op, int *rec, void *arg)
 {
@@ -1351,6 +1644,11 @@ walk_make_indirect (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* A gimple-walking function that turns any non-gimple-val ADDR_EXPRs into a
+   separate SSA.  Though addresses of e.g. parameters, and of members thereof,
+   are gimple vals, turning parameters into references, with an extra layer of
+   indirection and thus explicit dereferencing, need to be regimplified.  */
+
 static tree
 walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
 {
@@ -1374,6 +1672,57 @@ walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* Turn STMT's PHI arg defs into separate SSA defs if they've become
+   non-gimple_val.  Return TRUE if any edge insertions need to be committed.  */
+
+static bool
+walk_regimplify_phi (gphi *stmt)
+{
+  bool needs_commit = false;
+
+  for (unsigned i = 0, n = gimple_phi_num_args (stmt); i < n; i++)
+    {
+      tree op = gimple_phi_arg_def (stmt, i);
+      if ((TREE_CODE (op) == ADDR_EXPR
+	   && !is_gimple_val (op))
+	  /* ??? A PARM_DECL that was addressable in the original function and
+	     had its address in PHI nodes, but that became a reference in the
+	     wrapped clone would NOT be updated by update_ssa in PHI nodes.
+	     Alas, if we were to create a default def for it now, update_ssa
+	     would complain that the symbol that needed rewriting already has
+	     SSA names associated with it.  OTOH, leaving the PARM_DECL alone,
+	     it eventually causes errors because it remains unchanged in PHI
+	     nodes, but it gets rewritten as expected if it appears in other
+	     stmts.  So we cheat a little here, and force the PARM_DECL out of
+	     the PHI node and into an assignment.  It's a little expensive,
+	     because we insert it at the edge, which introduces a basic block
+	     that's entirely unnecessary, but it works, and the block will be
+	     removed as the default def gets propagated back into the PHI node,
+	     so the final optimized code looks just as expected.  */
+	  || (TREE_CODE (op) == PARM_DECL
+	      && !TREE_ADDRESSABLE (op)))
+	{
+	  tree temp = make_ssa_name (TREE_TYPE (op), stmt);
+	  if (TREE_CODE (op) == PARM_DECL)
+	    SET_SSA_NAME_VAR_OR_IDENTIFIER (temp, DECL_NAME (op));
+	  SET_PHI_ARG_DEF (stmt, i, temp);
+
+	  gimple *assign = gimple_build_assign (temp, op);
+	  if (gimple_phi_arg_has_location (stmt, i))
+	    gimple_set_location (assign, gimple_phi_arg_location (stmt, i));
+	  gsi_insert_on_edge (gimple_phi_arg_edge (stmt, i), assign);
+	  needs_commit = true;
+	}
+    }
+
+  return needs_commit;
+}
+
+/* Create a reference type to use for PARM when turning it into a reference.
+   NONALIASED causes the reference type to gain its own separate alias set, so
+   that accessing the indirectly-passed parm won'will not add aliasing
+   noise.  */
+
 static tree
 build_ref_type_for (tree parm, bool nonaliased = true)
 {
@@ -1411,6 +1760,7 @@ build_ref_type_for (tree parm, bool nonaliased = true)
 
 /* Add cgraph edges from current_function_decl to callees in SEQ with frequency
    COUNT, assuming all calls in SEQ are direct.  */
+
 static void
 add_call_edges_for_seq (gimple_seq seq, profile_count count)
 {
@@ -1425,16 +1775,22 @@ add_call_edges_for_seq (gimple_seq seq, profile_count count)
     {
       gimple *stmt = gsi_stmt (gsi);
 
-      if (!is_a <gcall *> (stmt))
+      gcall *call = dyn_cast <gcall *> (stmt);
+      if (!call)
 	continue;
 
-      gcall *call = as_a <gcall *> (stmt);
       tree callee = gimple_call_fndecl (call);
       gcc_checking_assert (callee);
       node->create_edge (cgraph_node::get_create (callee), call, count, false);
     }
 }
 
+/* Insert SEQ after the call at GSI, as if the call was in a try block with SEQ
+   as finally, i.e., SEQ will run after the call whether it returns or
+   propagates an exception.  This handles block splitting, EH edge and block
+   creation, noreturn and nothrow optimizations, and even throwing calls without
+   preexisting local handlers.  */
+
 static void
 gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
 {
@@ -1446,7 +1802,7 @@ gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
   if (gimple_has_location (stmt))
     annotate_all_with_location (seq, gimple_location (stmt));
 
-  gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+  gcall *call = dyn_cast <gcall *> (stmt);
   bool noreturn_p = call && gimple_call_noreturn_p (call);
   int eh_lp = lookup_stmt_eh_lp (stmt);
   bool must_not_throw_p = eh_lp < 0;
@@ -1586,8 +1942,12 @@ remove_named_attribute_unsharing (const char *name, tree *attrs)
     }
 }
 
+/* Record the order of the last cgraph entry whose mode we've already set, so
+   that we can perform mode setting incrementally without duplication.  */
 static int last_cgraph_order;
 
+/* Set strub modes for functions introduced since the last call.  */
+
 static void
 ipa_strub_set_mode_for_new_functions ()
 {
@@ -1616,6 +1976,7 @@ ipa_strub_set_mode_for_new_functions ()
 }
 
 /* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
+
 bool
 strub_splittable_p (cgraph_node *node)
 {
@@ -1641,10 +2002,11 @@ strub_splittable_p (cgraph_node *node)
 }
 
 /* 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))
+  switch (get_strub_mode_from_fndecl (fndecl))
     {
     case STRUB_WRAPPED:
     case STRUB_AT_CALLS:
@@ -1676,6 +2038,7 @@ strub_watermark_parm (tree fndecl)
 
 /* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
    hasn't been added yet.  Return the named argument count.  */
+
 int
 pass_ipa_strub::adjust_at_calls_type (tree type)
 {
@@ -1751,6 +2114,12 @@ pass_ipa_strub::adjust_at_calls_type (tree type)
   return named_args;
 }
 
+/* Adjust a call to an at-calls call target.  Create a watermark local variable
+   if needed, initialize it before, pass it to the callee according to the
+   modified at-calls interface, and release the callee's stack space after the
+   call, if not deferred.  If the call is const or pure, arrange for the
+   watermark to not be assumed unused or unchanged.  */
+
 void
 pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 {
@@ -1819,10 +2188,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 
     if (gimple_call_internal_p (stmt))
 #if 0
-      /*
-	new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
-	vargs);
-      */
+      new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
+						 vargs);
 #endif
       gcc_unreachable ();
     else
@@ -1917,6 +2284,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
   gsi_insert_finally_seq_after_call (gsi, seq);
 }
 
+/* Adjust all at-calls calls in NODE. */
+
 void
 pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
 {
@@ -1965,6 +2334,10 @@ pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
     }
 }
 
+/* The strubm (strub mode) pass computes a strub mode for each function in the
+   call graph, and checks, before any inlining, that strub callability
+   requirements in effect are satisfied.  */
+
 unsigned int
 pass_ipa_strub_mode::execute (function *)
 {
@@ -1977,12 +2350,17 @@ pass_ipa_strub_mode::execute (function *)
   return 0;
 }
 
+/* Create a strub mode pass.  */
+
 simple_ipa_opt_pass *
 make_pass_ipa_strub_mode (gcc::context *ctxt)
 {
   return new pass_ipa_strub_mode (ctxt);
 }
 
+/* The strub pass proper adjusts types, signatures, and at-calls calls, and
+   splits internal-strub functions.  */
+
 unsigned int
 pass_ipa_strub::execute (function *)
 {
@@ -2042,17 +2420,6 @@ pass_ipa_strub::execute (function *)
 	if (onode->alias)
 	  continue;
 
-#if 0 /* Calls are now adjusted when examining callers.  */
-	unsigned c;
-	cgraph_edge *e;
-	FOR_EACH_VEC_ELT (onode->collect_callers (), c, e)
-	  {
-	    push_cfun (DECL_STRUCT_FUNCTION (e->caller->decl));
-	    adjust_at_calls_call (e, named_args);
-	    pop_cfun ();
-	  }
-#endif
-
 	cgraph_node *nnode = onode;
 	push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
 
@@ -2071,10 +2438,10 @@ pass_ipa_strub::execute (function *)
 		{
 		  gimple *stmt = gsi_stmt (gsi);
 
-		  if (!is_gimple_call (stmt))
-		    continue;
+		  gcall *call = dyn_cast <gcall *> (stmt);
 
-		  gcall *call = as_a <gcall *> (stmt);
+		  if (!call)
+		    continue;
 
 		  if (gimple_alloca_call_p (call))
 		    {
@@ -2088,10 +2455,6 @@ pass_ipa_strub::execute (function *)
 	  }
 
 	pop_cfun ();
-
-#if 0
-	compute_fn_summary (onode, true);
-#endif
       }
   }
 
@@ -2108,43 +2471,6 @@ pass_ipa_strub::execute (function *)
 	continue;
       }
 
-#if 0
-    /* Hmm, this is an i386-specific attribute.  Do we need machine-specific
-       logic?  */
-    remove_named_attribute_unsharing ("interrupt",
-				      &DECL_ATTRIBUTES (onode->decl));
-#endif
-
-#if 0
-    if (!DECL_STRUCT_FUNCTION (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting struct-less function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    if (!onode->lowered)
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting non-lowered function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    /* 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.  FIXME.  */
-    if (!tree_versionable_function_p (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"%qD cannot be split for %<strub%>",
-		onode->decl);
-	continue;
-      }
-#endif
-
     bool is_stdarg = calls_builtin_va_start_p (onode);;
     bool apply_args = calls_builtin_apply_args_p (onode);
 
@@ -2753,26 +3079,45 @@ pass_ipa_strub::execute (function *)
       if (any_indirect)
 	{
 	  basic_block bb;
+	  bool needs_commit = false;
 	  FOR_EACH_BB_FN (bb, cfun)
-	    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
-		 !gsi_end_p (gsi); gsi_next (&gsi))
-	      {
-		gimple *stmt = gsi_stmt (gsi);
+	    {
+	      for (gphi_iterator gsi = gsi_start_nonvirtual_phis (bb);
+		   !gsi_end_p (gsi);
+		   gsi_next_nonvirtual_phi (&gsi))
+		{
+		  gphi *stmt = gsi.phi ();
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed && !is_gimple_debug (gsi_stmt (gsi)))
+		    if (walk_regimplify_phi (stmt))
+		      needs_commit = true;
+		}
 
-		walk_stmt_info wi = {};
-		wi.info = &indirect_nparms;
-		walk_gimple_op (stmt, walk_make_indirect, &wi);
-		if (wi.changed)
-		  {
-		    if (!is_gimple_debug (gsi_stmt (gsi)))
-		      {
-			wi.info = &gsi;
-			walk_gimple_op (stmt, walk_regimplify_addr_expr,
-					&wi);
-		      }
-		    update_stmt (stmt);
-		  }
-	      }
+	      for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+		   !gsi_end_p (gsi); gsi_next (&gsi))
+		{
+		  gimple *stmt = gsi_stmt (gsi);
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed)
+		    {
+		      if (!is_gimple_debug (stmt))
+			{
+			  wi.info = &gsi;
+			  walk_gimple_op (stmt, walk_regimplify_addr_expr,
+					  &wi);
+			}
+		      update_stmt (stmt);
+		    }
+		}
+	    }
+	  if (needs_commit)
+	    gsi_commit_edge_inserts ();
 	}
 
       if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca
@@ -2842,10 +3187,10 @@ pass_ipa_strub::execute (function *)
       push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
       gimple_stmt_iterator gsi
 	= gsi_after_labels (single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun)));
-      while (!is_gimple_call (gsi_stmt (gsi)))
-	gsi_next (&gsi);
 
-      gcall *wrcall = as_a <gcall *> (gsi_stmt (gsi));
+      gcall *wrcall;
+      while (!(wrcall = dyn_cast <gcall *> (gsi_stmt (gsi))))
+	gsi_next (&gsi);
 
       tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
       TREE_ADDRESSABLE (swm) = true;
@@ -2973,11 +3318,6 @@ pass_ipa_strub::execute (function *)
 
       pop_cfun ();
     }
-
-#if 0
-    compute_fn_summary (onode, true);
-    compute_fn_summary (nnode, true);
-#endif
   }
 
   if (flag_checking)
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
index c335ab42097..5c0b266c3f8 100644
--- a/gcc/ipa-strub.h
+++ b/gcc/ipa-strub.h
@@ -18,11 +18,10 @@ You should have received a copy of the GNU General Public License
 along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
-/* Return TRUE if the first function can be inlined into the second,
-   as far as stack scrubbing constraints are concerned.  CALLEE
-   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 TRUE if CALLEE can be inlined into CALLER, as far as stack scrubbing
+   constraints are concerned.  CALLEE doesn't have to be called directly by
+   CALLER, but the returned value says nothing about intervening functions.  */
+extern bool strub_inlinable_to_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);
@@ -33,3 +32,14 @@ extern tree strub_watermark_parm (tree fndecl);
 
 /* Make a function type or declaration callable.  */
 extern void strub_make_callable (tree fndecl);
+
+/* Return zero iff ID is NOT an acceptable parameter for a user-supplied strub
+   attribute for a function.  Otherwise, return >0 if it enables strub, <0 if it
+   does not.  Return +/-1 if the attribute-modified type is compatible with the
+   type without the attribute, or +/-2 if it is not compatible.  */
+extern int strub_validate_fn_attr_parm (tree id);
+
+/* Like comptypes, return 0 if t1 and t2 are not compatible, 1 if they are
+   compatible, and 2 if they are nearly compatible.  Same strub mode is
+   compatible, interface-compatible strub modes are nearly compatible.  */
+extern int strub_comptypes (tree t1, tree t2);
diff --git a/gcc/multiple_target.c b/gcc/multiple_target.c
index 28d5f95dca5..07cdb66e337 100644
--- a/gcc/multiple_target.c
+++ b/gcc/multiple_target.c
@@ -557,7 +557,7 @@ public:
 bool
 pass_target_clone::gate (function *)
 {
-  return true;
+  return !seen_error ();
 }
 
 } // anon namespace
diff --git a/gcc/testsuite/c-c++-common/strub-O0.c b/gcc/testsuite/c-c++-common/strub-O0.c
index 6afe0fd5de1..c7a79a6ea0d 100644
--- a/gcc/testsuite/c-c++-common/strub-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O0 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O0, none of the strub builtins are expanded inline.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O1.c b/gcc/testsuite/c-c++-common/strub-O1.c
index 1cdeaecaf32..96285c975d9 100644
--- a/gcc/testsuite/c-c++-common/strub-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O1 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O1, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
index 7848c46d179..8edc0d8aa13 100644
--- a/gcc/testsuite/c-c++-common/strub-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O2, without -fno-inline, we fully expand enter and update, and add a test
    around the leave call.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
index 85a8f76785e..c6d900cf3c4 100644
--- a/gcc/testsuite/c-c++-common/strub-O2fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3.c b/gcc/testsuite/c-c++-common/strub-O3.c
index 1fcde345d36..33ee465e51c 100644
--- a/gcc/testsuite/c-c++-common/strub-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand" } */
 
 int __attribute__ ((__strub__)) var;
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
index a2eedfd96b2..2936f82079e 100644
--- a/gcc/testsuite/c-c++-common/strub-O3fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
index e5cb1f60541..479746e57d8 100644
--- a/gcc/testsuite/c-c++-common/strub-Og.c
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Og -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Og -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Og, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
index 194aacc2c05..2241d4ea07f 100644
--- a/gcc/testsuite/c-c++-common/strub-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Os, without -fno-inline, we fully expand enter, and also update.  The
    expanded update might be larger than a call proper, but argument saving and
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
index 46e84bf6560..a322bcc5da6 100644
--- a/gcc/testsuite/c-c++-common/strub-all1.c
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -22,11 +22,11 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
index f377541cff0..db60026d0e0 100644
--- a/gcc/testsuite/c-c++-common/strub-all2.c
+++ b/gcc/testsuite/c-c++-common/strub-all2.c
@@ -17,8 +17,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
index f180b17f30e..2f462adc1ef 100644
--- a/gcc/testsuite/c-c++-common/strub-apply1.c
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -1,13 +1,13 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
-void __attribute__ ((__strub__ (3)))
+void __attribute__ ((__strub__ ("callable")))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0);
 }
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   void *args = __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
index 379a54b73b7..bab2dd10f5b 100644
--- a/gcc/testsuite/c-c++-common/strub-apply2.c
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 extern void __attribute__ ((__strub__))
 apply_function (void *args);
@@ -10,3 +10,9 @@ apply_args (int i, int j, double d) /* { dg-error "selected" } */
   void *args = __builtin_apply_args (); /* { dg-message "does not support" } */
   apply_function (args);
 }
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After the error and sorry messages in strubm, build_ssa_passes are gated out,
+   so we don't get the PROP_ssa property that targetclone requires.  When
+   checking is not enabled at configure time, we admit to being confused and
+   bail out, but when checking is eneabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
index 9b4786be698..85f9d090fda 100644
--- a/gcc/testsuite/c-c++-common/strub-apply3.c
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -1,8 +1,14 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 void __attribute__ ((__strub__))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */
 }
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
index 409f747743e..15ffaa031b8 100644
--- a/gcc/testsuite/c-c++-common/strub-apply4.c
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+/* { dg-options "-O2 -fstrub=strict -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
@@ -18,4 +18,4 @@ void f() {
   apply_args (1, 2, 3);
 }
 
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 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 d964b07ae5d..b70843b4215 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -22,9 +22,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
index 530eee36d06..97a3988a6b9 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls2.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls2.c
@@ -17,7 +17,7 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-default1.c b/gcc/testsuite/c-c++-common/strub-default1.c
deleted file mode 100644
index 79d00cedb9a..00000000000
--- a/gcc/testsuite/c-c++-common/strub-default1.c
+++ /dev/null
@@ -1,42 +0,0 @@
-/* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
-
-static int __attribute__ ((__strub__)) var;
-
-/* 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 inline void
-__attribute__ ((__always_inline__))
-h() {
-  var++;
-}
-
-/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
-   the viability of at-calls strubbing.  Though internally a strub context, its
-   interface is not strub-enabled, so it's not callable from within strub
-   contexts.  */
-static inline void
-g() {
-  var--;
-  h();
-}
-
-/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
-   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
-void
-f() {
-  var++;
-  g();  /* { dg-error "calling non-.strub." } */
-}
-
-/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-4\[)\]" 1 "strubm" } } */
-/* { 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 "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
index 7b04eea35d9..3d73431b3dc 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O1" } */
+/* { dg-options "-fstrub=strict -O1" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O1.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
index 67d96419a5e..fddf3c745e7 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O2" } */
+/* { dg-options "-fstrub=strict -O2" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O2.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
index 34828d2711e..2bc384ee8d1 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O3" } */
+/* { dg-options "-fstrub=strict -O3" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -O3.  */
@@ -10,7 +10,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -18,7 +18,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -32,7 +32,7 @@ leak_string (void)
   return 0;
 }
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 int
 look_for_string (char *e)
 {
@@ -56,14 +56,14 @@ look_for_string (char *e)
   return 0;
 }
 
-static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+static __attribute__ ((__strub__ ("at-calls"), __noinline__, __noclone__))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 deferred_at_calls ()
 {
@@ -73,7 +73,7 @@ deferred_at_calls ()
   return ret;
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 deferred_internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
index b273660aea1..fbaf85fe0fa 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -Os" } */
+/* { dg-options "-fstrub=strict -Os" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -Os.  */
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
index a74658c9ac9..e9d7b7b9ee0 100644
--- a/gcc/testsuite/c-c++-common/strub-internal1.c
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -23,9 +23,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
index a6e69357b23..8b8e15a51c7 100644
--- a/gcc/testsuite/c-c++-common/strub-internal2.c
+++ b/gcc/testsuite/c-c++-common/strub-internal2.c
@@ -14,8 +14,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
index 7e22a266ad9..0a4a7539d34 100644
--- a/gcc/testsuite/c-c++-common/strub-parms1.c
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -16,7 +16,7 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 large_byref_arg (struct large_arg la)
 {
 }
@@ -24,7 +24,7 @@ large_byref_arg (struct large_arg la)
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*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)))
+void __attribute__ ((__strub__ ("internal")))
 std_arg (int i, ...)
 {
   va_list vl;
@@ -38,7 +38,7 @@ std_arg (int i, ...)
 /* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
index 2d8036c0fbc..147171d96d5 100644
--- a/gcc/testsuite/c-c++-common/strub-parms2.c
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -15,14 +15,14 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 large_byref_arg (struct large_arg la)
 {
 }
 
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 std_arg (int i, ...)
 {
   va_list vl;
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
index f64361f1235..4e92682895a 100644
--- a/gcc/testsuite/c-c++-common/strub-parms3.c
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
new file mode 100644
index 00000000000..e2f9d8aebca
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  Without the error,
+   inlining takes place.  */
+
+#include "strub-strict1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
new file mode 100644
index 00000000000..98474435d2e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  */
+
+#include "strub-strict2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
index a4226ce0119..1de15342595 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fexceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fexceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
index 3bab553478b..f9209c81900 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
index c89cc7c2c47..bed1dcfb54a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
index b869fafb691..6bf0071f52b 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
index 1d3dd2f2c2c..4732f515bf7 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
index 4dd4281b03b..8d6424c479a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-strict1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
new file mode 100644
index 00000000000..7f5247cfaf0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-strict1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+static int __attribute__ ((__strub__)) var;
+
+/* 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 inline void
+__attribute__ ((__always_inline__))
+h() {
+  var++;
+}
+
+/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
+   the viability of at-calls strubbing.  Though internally a strub context, its
+   interface is not strub-enabled, so it's not callable from within strub
+   contexts.  */
+static inline void
+g() {
+  var--;
+  h();
+}
+
+/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
+   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
+void
+f() {
+  var++;
+  g();  /* { dg-error "calling non-.strub." } */
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-default2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
similarity index 54%
rename from gcc/testsuite/c-c++-common/strub-default2.c
rename to gcc/testsuite/c-c++-common/strub-strict2.c
index 487253e9227..17463b6e554 100644
--- a/gcc/testsuite/c-c++-common/strub-default2.c
+++ b/gcc/testsuite/c-c++-common/strub-strict2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
 
 static int __attribute__ ((__strub__)) var;
 
@@ -22,8 +22,14 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
index 0840dddd136..e48e0610e07 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 #include "strub-tail-O2.c"
 
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
index 9330d6ff4c1..87cda7ab21b 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.
    Tail calls are short-circuited at -O2+.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
index 5b33ff1f530..b5e45ab0525 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
index 38935e3270b..96aa7fe4b07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -1,39 +1,39 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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);
+extern int __attribute__ ((__strub__ ("callable"))) xcallable (void);
+extern int __attribute__ ((__strub__ ("internal"))) xinternal (void);
+extern int __attribute__ ((__strub__ ("at-calls"))) xat_calls (void);
+extern int __attribute__ ((__strub__ ("disabled"))) 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__ ("callable"))) callable (void);
+int __attribute__ ((__strub__ ("internal"))) internal (void);
+int __attribute__ ((__strub__ ("at-calls"))) at_calls (void);
+int __attribute__ ((__strub__ ("disabled"))) disabled (void);
 
 int __attribute__ ((__strub__)) var;
 int var_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 icallable (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 iinternal (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 idisabled (void);
 static inline int __attribute__ ((__always_inline__))
 ivar_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 i_callable (void) { return 0; }
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 i_internal (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 i_at_calls (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 i_disabled (void) { return 0; }
 static inline int __attribute__ ((__always_inline__))
 i_var_user (void) { return var; }
@@ -70,7 +70,7 @@ i_var_user (void) { return var; }
 
 /* Not a strub context, so it can call anything.
    Explicitly declared as callable even from within strub contexts.  */
-int __attribute__ ((__strub__ (3)))
+int __attribute__ ((__strub__ ("callable")))
 callable (void) {
   int ret = 0;
 
@@ -88,7 +88,7 @@ callable (void) {
 
 /* 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)))
+int __attribute__ ((__strub__ ("internal")))
 internal (void) {
   int ret = var;
 
@@ -109,7 +109,7 @@ internal (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (1)))
+int __attribute__ ((__strub__ ("at-calls")))
 at_calls (void) {
   int ret = var;
 
@@ -130,7 +130,7 @@ at_calls (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (0)))
+int __attribute__ ((__strub__ ("disabled")))
 disabled () {
   int ret = 0;
 
@@ -205,7 +205,7 @@ iinternal (void) {
   return ret;
 }
 
-int
+int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void) {
   int ret = var;
 
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
index 100fb0c59a9..2857195706e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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,
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
index 9e818ac9748..98a92bc9eac 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
index d40e8aa45cb..5511a6e1e71 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __const__))
 f() {
   return 0;
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
index d4cbdaf10f3..47ee927964d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -1,12 +1,12 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__
 __attribute__ ((__const__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data1.c b/gcc/testsuite/c-c++-common/torture/strub-data1.c
index 62a03891ab6..7c27a2a1a6d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointed-to data enables strubbing if accessed.  */
 int __attribute__ ((__strub__)) var;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data2.c b/gcc/testsuite/c-c++-common/torture/strub-data2.c
index 9b7df13a280..e66d903780a 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointer itself is a strub variable, enabling internal strubbing when
    its value is used.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data3.c b/gcc/testsuite/c-c++-common/torture/strub-data3.c
index 515706caa32..5e08e0e58c6 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data4.c b/gcc/testsuite/c-c++-common/torture/strub-data4.c
index 0ec9e35429f..a818e7a38bb 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
index 69f3d65ed44..90e8ad51c1e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -Werror" } */
+/* { dg-options "-fstrub=strict -Werror" } */
 
 typedef int __attribute__ ((__strub__)) strub_int;
 strub_int *ptr;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
index b8adf8009e8..c165f312f16 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype ();
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
index 5b2c35ad6a7..69fcff8d376 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
index 5ee50456dc9..ff006224909 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int, ...);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
new file mode 100644
index 00000000000..b416ba06b59
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+}
+
+void
+bat (void)
+{
+  /* Not allowed, not a strub context.  */
+  inl_int_ali (); /* { dg-error "context" } */
+}
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
new file mode 100644
index 00000000000..f9a6b4a16fa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=all" } */
+
+#include "strub-inlinable1.c"
+
+/* With -fstrub=all, the caller becomes a strub context, so the strub-inlinable
+   callee is not rejected.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
new file mode 100644
index 00000000000..07cc432024d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+typedef void ft (void);
+typedef void ft2 (int, int);
+extern ft __attribute__ ((__strub__)) fnac;
+
+ft * f (void) {
+  return fnac; /* { dg-message "incompatible" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
new file mode 100644
index 00000000000..c29567eb937
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
@@ -0,0 +1,55 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic" } */
+
+/* C++ does not warn about the partial incompatibilities.
+
+   The d_p () calls are actually rejected, even in C++, but they are XFAILed
+   here because we don't get far enough in the compilation as to observe them,
+   because the incompatibilities are errors without -fpermissive.
+   strub-ptrfn3.c uses -fpermissive to check those.
+ */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
new file mode 100644
index 00000000000..e1f179e160e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
@@ -0,0 +1,50 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic -fpermissive" } */
+/* { dg-prune-output "command-line option .-fpermissive." } */
+
+/* See strub-ptrfn2.c.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
new file mode 100644
index 00000000000..70b558afad0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+/* This is strub-ptrfn2.c without -Wpedantic.
+
+   Even C doesn't report the (not-quite-)compatible conversions without it.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
index cb223da6efc..a262a086837 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
index 67d1434b1f8..4c4bd50c209 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
index 59f02ea901f..ce195c6b1f1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -1,10 +1,10 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __pure__))
 f() {
   static int i; /* Stop it from being detected as const.  */
   return i;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
index 973e909217d..75cd54ccb5b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
 __attribute__ ((__pure__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
index a4077c35a60..c596066a045 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -14,7 +14,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -59,14 +59,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
index 94e4156ea73..1fdba214a07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
index 0ca74beb59d..afbc2cc9ab4 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
 /* Check that a non-strub function leaves a string behind in the stack, and that
@@ -7,7 +7,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
index 4ab11c0682e..5300f1d330b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -85,14 +85,14 @@ intermediate ()
   return ret;
 }
 
-static inline __attribute__ ((__strub__ (2)))
+static inline __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
   return intermediate ();
 }
 
-int __attribute__ ((strub (0)))
+int __attribute__ ((__strub__ ("disabled")))
 main ()
 {
   if (look_for_string (internal ()))
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4d.c b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
index e4f7445607c..08de3f1c3b1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4d.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -1,7 +1,7 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
-#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ (1)))
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
 
 #include "strub-run4.c"
diff --git a/gcc/testsuite/g++.dg/torture/strub-init1.C b/gcc/testsuite/g++.dg/torture/strub-init1.C
index e51ae802be4..c226ab10ff6 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init1.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init2.C b/gcc/testsuite/g++.dg/torture/strub-init2.C
index edcb7bf8ad2..a7911f1fa72 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init2.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init3.C b/gcc/testsuite/g++.dg/torture/strub-init3.C
index bacf823ca4e..6ebebcd01e8 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init3.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
index 697ac9de764..10445d7cf84 100644
--- a/gcc/testsuite/gnat.dg/strub_attr.adb
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -1,5 +1,5 @@
 --  { dg-do compile }
---  { dg-options "-fstrub=default -fdump-ipa-strubm" }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" }
 
 package body Strub_Attr is
    E : exception;
@@ -14,8 +14,24 @@ package body Strub_Attr is
       return X * X;
    end;
    
-   function G return Integer is (X);
+   function G return Integer is (F (X));
+   --  function G return Integer is (FP (X));
+   --  Calling G would likely raise an exception, because although FP
+   --  carries the strub at-calls attribute needed to call F, the
+   --  attribute is dropped from the type used for the call proper.
 end Strub_Attr;
 
---  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]2\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 0 "strubm" } }
 --  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark_ptr" 6 "strub" } }
+--  We have 1 at-calls subprogram (F) and 2 wrapped (P and G).
+--  For each of them, there's one match for the wrapped signature, 
+--  and one for the update call.
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 27 "strub" } }
+--  The 6 matches above, plus:
+--  5*2: wm var decl, enter, call, leave and clobber for each wrapper;
+--  2*1: an extra leave and clobber for the exception paths in the wrappers.
+--  7*1: for the F call in G, including EH path.
diff --git a/gcc/testsuite/gnat.dg/strub_ind.adb b/gcc/testsuite/gnat.dg/strub_ind.adb
new file mode 100644
index 00000000000..cfd7a76b02b
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind.adb
@@ -0,0 +1,50 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" }
+
+package body Strub_Ind is
+   E : exception;
+
+   procedure P (X : Integer) is
+   begin
+      raise E;
+   end;
+   
+   function F (X : Integer) return Integer is
+   begin
+      return X * X;
+   end;
+   
+   function G return Integer is (FP (X)); -- { dg-bogus "non-.strub." "" { xfail *-*-* } }
+   --  Calling G would likely raise an exception, because although FP
+   --  carries the strub at-calls attribute needed to call F, the
+   --  attribute is dropped from the type used for the call proper.
+
+
+   type GT is access function return Integer;
+   pragma Machine_Attribute (GT, "strub", "at-calls");
+   --  The pragma above seems to have no effect.
+
+   GP : GT := G'Access; -- { dg-warning "incompatible" "" { xfail *-*-* } }
+   pragma Machine_Attribute (GP, "strub", "at-calls");
+   --  The pragma above does modify GP's type,
+   --  but dereferencing it uses an unmodified copy of the type.
+   --  The initializer should be diagnosed:
+   --  GT should only reference functions with at-calls strub.
+
+end Strub_Ind;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 0 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark_ptr" 6 "strub" } }
+--  We have 1 at-calls subprogram (F) and 2 wrapped (P and G).
+--  For each of them, there's one match for the wrapped signature, 
+--  and one for the update call.
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 20 "strub" } }
+--  The 6 matches above, plus:
+--  5*2: wm var decl, enter, call, leave and clobber for each wrapper;
+--  2*1: an extra leave and clobber for the exception paths in the wrappers.
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 27 "strub" { xfail *-*-* } } }
+--  7*1: for the FP call in G, including EH path (currently missing the watermarking)
diff --git a/gcc/testsuite/gnat.dg/strub_ind.ads b/gcc/testsuite/gnat.dg/strub_ind.ads
new file mode 100644
index 00000000000..53dede60eac
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind.ads
@@ -0,0 +1,23 @@
+package Strub_Ind is
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "internal");
+
+   function F (X : Integer) return Integer;
+   pragma Machine_Attribute (F, "strub");
+
+   X : Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+
+   function G return Integer;
+
+   type FT is access function (X : Integer) return Integer;
+   pragma Machine_Attribute (FT, "strub", "at-calls");
+   --  The pragma above seems to get discarded in GNAT; Gigi doesn't see it.
+
+   FP : FT := F'Access;
+   pragma Machine_Attribute (FP, "strub", "at-calls");
+   --  The pragma above does modify FP's type,
+   --  but a call with it gets it converted to its Ada type,
+   --  that is cached by the translator as the unmodified type.
+
+end Strub_Ind;
diff --git a/libgcc/strub.c b/libgcc/strub.c
index fd6e27556e4..7c58deee1e6 100644
--- a/libgcc/strub.c
+++ b/libgcc/strub.c
@@ -36,7 +36,7 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 # define TOPS <
 #endif
 
-#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ ("callable")))
 
 /* Enter a stack scrubbing context, initializing the watermark to the caller's
    stack address.  */


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

* [gcc(refs/users/aoliva/heads/strub)] -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests
@ 2021-09-07 19:08 Alexandre Oliva
  0 siblings, 0 replies; 13+ messages in thread
From: Alexandre Oliva @ 2021-09-07 19:08 UTC (permalink / raw)
  To: gcc-cvs

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

commit f84ed2809295cf8ee924149377f6b49932d16a67
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Thu Sep 2 23:15:36 2021 -0300

    -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests

Diff:
---
 .../doc/gnat_rm/security_hardening_features.rst    |  50 +-
 gcc/ada/gcc-interface/utils.c                      | 111 +---
 gcc/attribs.c                                      |  39 +-
 gcc/c-family/c-attribs.c                           | 100 +---
 gcc/common.opt                                     |  27 +-
 gcc/doc/extend.texi                                | 332 +++++++++--
 gcc/doc/invoke.texi                                |  76 ++-
 gcc/ipa-inline.c                                   |   2 +-
 gcc/ipa-strub.c                                    | 649 ++++++++++++++++-----
 gcc/ipa-strub.h                                    |  20 +-
 gcc/testsuite/c-c++-common/strub-O0.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O1.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2fni.c           |   2 +-
 gcc/testsuite/c-c++-common/strub-O3.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O3fni.c           |   2 +-
 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            |  12 +-
 gcc/testsuite/c-c++-common/strub-all2.c            |   6 +-
 gcc/testsuite/c-c++-common/strub-apply1.c          |   6 +-
 gcc/testsuite/c-c++-common/strub-apply2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-apply3.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-apply4.c          |   4 +-
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-at-calls2.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-default1.c        |  42 --
 gcc/testsuite/c-c++-common/strub-defer-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |  14 +-
 gcc/testsuite/c-c++-common/strub-defer-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-internal1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-internal2.c       |   6 +-
 gcc/testsuite/c-c++-common/strub-parms1.c          |  10 +-
 gcc/testsuite/c-c++-common/strub-parms2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-parms3.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-relaxed1.c        |  18 +
 gcc/testsuite/c-c++-common/strub-relaxed2.c        |  14 +
 gcc/testsuite/c-c++-common/strub-short-O0-exc.c    |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O0.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O3.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-strict1.c         |  48 ++
 .../{strub-default2.c => strub-strict2.c}          |  14 +-
 gcc/testsuite/c-c++-common/strub-tail-O1.c         |   2 +-
 gcc/testsuite/c-c++-common/strub-tail-O2.c         |   2 +-
 .../c-c++-common/torture/strub-callable1.c         |   2 +-
 .../c-c++-common/torture/strub-callable2.c         |  44 +-
 gcc/testsuite/c-c++-common/torture/strub-const1.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const2.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const3.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-const4.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-data1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data3.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data4.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |   2 +-
 .../c-c++-common/torture/strub-indcall1.c          |   2 +-
 .../c-c++-common/torture/strub-indcall2.c          |   2 +-
 .../c-c++-common/torture/strub-indcall3.c          |   2 +-
 .../c-c++-common/torture/strub-inlinable1.c        |  22 +
 .../c-c++-common/torture/strub-inlinable2.c        |   7 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |  10 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |  55 ++
 gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c  |  50 ++
 gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c  |  43 ++
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure3.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-pure4.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run1.c    |  10 +-
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run4.c    |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run4d.c   |   4 +-
 gcc/testsuite/g++.dg/torture/strub-init1.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init2.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init3.C         |   2 +-
 gcc/testsuite/gnat.dg/strub_attr.adb               |  35 +-
 gcc/testsuite/gnat.dg/strub_attr.ads               |   9 +
 libgcc/strub.c                                     |   2 +-
 83 files changed, 1436 insertions(+), 607 deletions(-)

diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 309291f0341..75345290412 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -19,6 +19,18 @@ It can be enabled with the *-fzero-call-used-regs* command line
 option, to affect all subprograms in a compilation, and with a
 :samp:`Machine_Attribute` pragma, to affect only specific subprograms.
 
+.. code-block:: ada
+
+     procedure Foo;
+     pragma Machine_Attribute (Foo, "zero_call_used_regs", "used");
+     --  Before returning, Foo scrubs only call-clobbered registers
+     --  that it uses itself.
+
+     function Bar return Integer;
+     pragma Machine_Attribute (Bar, "zero_call_used_regs", "all");
+     --  Before returning, Bar scrubs all call-clobbered registers.
+
+
 For usage and more details on the command line option, and on the
 ``zero_call_used_regs`` attribute, see :title:`Using the GNU Compiler
 Collection (GCC)`.
@@ -31,13 +43,31 @@ Stack Scrubbing
 
 GNAT can generate code to zero-out stack frames used by subprograms.
 
-It can be activated with the *-fstrub* command line option, to affect
-all (viable) subprograms in a compilation, and with a
-:samp:`Machine_Attribute` pragma.
+It can be activated with the :samp:`Machine_Attribute` pragma, on
+specific subprograms, variables, and types.
 
-For usage and more details on the command line option, and on the
-``strub`` attribute, see :title:`Using the GNU Compiler Collection
-(GCC)`.
+.. code-block:: ada
+
+     function Foo returns Integer;
+     pragma Machine_Attribute (Foo, "strub");
+     --  Foo and its callers are modified so as to scrub the stack
+     --  space used by Foo after it returns.
+
+     procedure Bar;
+     pragma Machine_Attribute (Bar, "strub", "internal");
+     --  Bar is turned into a wrapper for its original body,
+     --  and they scrub the stack used by the original body.
+
+     Var : Integer;
+     pragma Machine_Attribute (Var, "strub");
+     --  Reading from Var in a subprogram enables stack scrubbing
+     --  of the stack space used by the subprogram.
+
+
+There are also *-fstrub* command line options to control default
+settings.  For usage and more details on the command line option, and
+on the ``strub`` attribute, see :title:`Using the GNU Compiler
+Collection (GCC)`.
 
 Note that Ada secondary stacks are not scrubbed.  The restriction
 ``No_Secondary_Stack`` avoids their use, and thus their accidental
@@ -49,4 +79,10 @@ is not fully reflected in Ada types, ``Access`` attributes, renaming
 and overriding.  Every access type, renaming, and overriding and
 overridden dispatching operations that may refer to an entity with an
 attribute-modified interface must be annotated with the same
-interface-modifying attribute.
+interface-modifying attribute, or with an interface-compatible one.
+
+Even then, applying the pragma to anything other than subprograms and
+scalar variables is not currently supported, and may be silently
+ignored.  Specifically, it is not recommended to rely on any effects
+of this pragma on access types and objects to data variables and to
+subprograms.
diff --git a/gcc/ada/gcc-interface/utils.c b/gcc/ada/gcc-interface/utils.c
index 6b20ddc93c9..193772cc829 100644
--- a/gcc/ada/gcc-interface/utils.c
+++ b/gcc/ada/gcc-interface/utils.c
@@ -39,6 +39,7 @@
 #include "varasm.h"
 #include "toplev.h"
 #include "opts.h"
+#include "ipa-strub.h"
 #include "output.h"
 #include "debug.h"
 #include "convert.h"
@@ -6611,115 +6612,57 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
-  tree orig_fnptr_type = NULL_TREE;
-  tree orig_args = args;
 
   if (args
       && POINTER_TYPE_P (*node)
       && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*node)))
-    {
-      orig_fnptr_type = *node;
-      *node = TREE_TYPE (orig_fnptr_type);
-    }
+    *node = TREE_TYPE (*node);
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      /* pragma Machine_Attribute turns string arguments into identifiers.
-	 Reverse it.  */
-      if (TREE_CODE (TREE_VALUE (args)) == IDENTIFIER_NODE)
-	{
-	  gcc_checking_assert (IDENTIFIER_POINTER (TREE_VALUE (args))
-			       [IDENTIFIER_LENGTH (TREE_VALUE (args))] == 0);
-	  TREE_VALUE (args) = build_string
-	    (IDENTIFIER_LENGTH (TREE_VALUE (args)) + 1,
-	     IDENTIFIER_POINTER (TREE_VALUE (args)));
-	}
-
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
 	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
-
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
-
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
 
   if (args)
-    warning (OPT_Wattributes,
-	     "ignoring excess %qE attribute arguments starting at %qE",
-	     name, TREE_VALUE (args));
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
 
   /* If we see a strub-enabling attribute, and we're at the default setting,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
-
-  if (!*no_add_attrs)
-    {
-      *no_add_attrs = true;
-      if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
-      TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
-					   TYPE_ATTRIBUTES (*node));
-
-      if (orig_fnptr_type)
-	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
-	  TREE_TYPE (fnptr_type) = *node;
-	  *node = fnptr_type;
-	}
-    }
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   return NULL_TREE;
 }
diff --git a/gcc/attribs.c b/gcc/attribs.c
index 0d22c20a35e..07f81285d7a 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "attribs.h"
 #include "fold-const.h"
+#include "ipa-strub.h"
 #include "stor-layout.h"
 #include "langhooks.h"
 #include "plugin.h"
@@ -617,12 +618,11 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  flags &= ~(int) ATTR_FLAG_TYPE_IN_PLACE;
 	}
 
-      if (spec->function_type_required && TREE_CODE (*anode) != FUNCTION_TYPE
-	  && TREE_CODE (*anode) != METHOD_TYPE)
+      if (spec->function_type_required
+	  && !FUNC_OR_METHOD_TYPE_P (*anode))
 	{
 	  if (TREE_CODE (*anode) == POINTER_TYPE
-	      && (TREE_CODE (TREE_TYPE (*anode)) == FUNCTION_TYPE
-		  || TREE_CODE (TREE_TYPE (*anode)) == METHOD_TYPE))
+	      && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
 	    {
 	      /* OK, this is a bit convoluted.  We can't just make a copy
 		 of the pointer type and modify its TREE_TYPE, because if
@@ -715,7 +715,23 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  tree ret = (spec->handler) (cur_and_last_decl, name, args,
 				      flags|cxx11_flag, &no_add_attrs);
 
-	  *anode = cur_and_last_decl[0];
+	  if (*anode != cur_and_last_decl[0])
+	    {
+	      /* Even if !spec->function_type_required, allow the attribute
+		 handler to request the attribute to be applied to the function
+		 type, rather than to the function pointer type, by setting
+		 cur_and_last_decl[0] to the function type.  */
+	      if (!fn_ptr_tmp
+		  && POINTER_TYPE_P (*anode)
+		  && TREE_TYPE (*anode) == cur_and_last_decl[0]
+		  && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
+		{
+		  fn_ptr_tmp = TREE_TYPE (*anode);
+		  fn_ptr_quals = TYPE_QUALS (*anode);
+		  anode = &fn_ptr_tmp;
+		}
+	      *anode = cur_and_last_decl[0];
+	    }
 	  if (ret == error_mark_node)
 	    {
 	      warning (OPT_Wattributes, "%qE attribute ignored", name);
@@ -1353,9 +1369,20 @@ comp_type_attributes (const_tree type1, const_tree type2)
   if ((lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type1)) != NULL)
       ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) != NULL))
     return 0;
+  int strub_ret = strub_comptypes (CONST_CAST_TREE (type1),
+				   CONST_CAST_TREE (type2));
+  if (strub_ret == 0)
+    return strub_ret;
   /* As some type combinations - like default calling-convention - might
      be compatible, we have to call the target hook to get the final result.  */
-  return targetm.comp_type_attributes (type1, type2);
+  int target_ret = targetm.comp_type_attributes (type1, type2);
+  if (target_ret == 0)
+    return target_ret;
+  if (strub_ret == 2 || target_ret == 2)
+    return 2;
+  if (strub_ret == 1 && target_ret == 1)
+    return 1;
+  gcc_unreachable ();
 }
 
 /* PREDICATE acts as a function of type:
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index dc4a4d4d200..0453ac563cc 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "common/common-target.h"
 #include "langhooks.h"
 #include "tree-inline.h"
+#include "ipa-strub.h"
 #include "toplev.h"
 #include "tree-iterator.h"
 #include "opts.h"
@@ -1306,104 +1307,57 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
-  tree orig_fnptr_type = NULL_TREE;
-  tree orig_args = args;
 
   if (args
       && POINTER_TYPE_P (*node)
       && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*node)))
-    {
-      orig_fnptr_type = *node;
-      *node = TREE_TYPE (orig_fnptr_type);
-    }
+    *node = TREE_TYPE (*node);
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
 	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
-
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
-
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
 
   if (args)
-    warning (OPT_Wattributes,
-	     "ignoring excess %qE attribute arguments starting at %qE",
-	     name, TREE_VALUE (args));
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
 
   /* If we see a strub-enabling attribute, and we're at the default setting,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
-
-  if (!*no_add_attrs)
-    {
-      *no_add_attrs = true;
-      if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
-      TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
-					   TYPE_ATTRIBUTES (*node));
-
-      if (orig_fnptr_type)
-	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
-	  TREE_TYPE (fnptr_type) = *node;
-	  *node = fnptr_type;
-	}
-    }
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   return NULL_TREE;
 }
diff --git a/gcc/common.opt b/gcc/common.opt
index 71e0c971344..ef018ebf735 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,13 +2687,22 @@ fstrict-overflow
 Common
 Treat signed overflow as undefined.  Negated as -fwrapv -fwrapv-pointer.
 
-; If any strub-enabling attribute is seen when the default value is
-; selected, it's bumped up to -1.  The scrub mode gate function will
-; then bump -2 to 0 if no strub-enabling attribute is seen.  This
-; minimizes the strub overhead.
-fstrub=default
-Common RejectNegative Var(flag_strub, -2) Init(-2)
-Enable stack scrub as requested through attributes.
+fstrub=disable
+Common RejectNegative Var(flag_strub, 0)
+Disable stack scrub entirely, disregarding strub attributes.
+
+fstrub=strict
+Common RejectNegative Var(flag_strub, -4)
+Enable stack scrub as per attributes, with strict call checking.
+
+; If any strub-enabling attribute is seen when the default or strict
+; initializer values are in effect, flag_strub is bumped up by 2.  The
+; scrub mode gate function will then bump these initializer values to
+; 0 if no strub-enabling attribute is seen.  This minimizes the strub
+; overhead.
+fstrub=relaxed
+Common RejectNegative Var(flag_strub, -3) Init(-3)
+Restore default strub mode: as per attributes, with relaxed checking.
 
 fstrub=all
 Common RejectNegative Var(flag_strub, 3)
@@ -2707,10 +2716,6 @@ fstrub=internal
 Common RejectNegative Var(flag_strub, 2)
 Enable internal stack scrubbing for all viable functions.
 
-fstrub=disable
-Common RejectNegative Var(flag_strub, 0)
-Disable stack scrub entirely, disregarding strub attributes.
-
 fsync-libcalls
 Common Var(flag_sync_libcalls) Init(1)
 Implement __atomic operations via libcalls to legacy __sync functions.
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 1fcf60a3875..d85e695d85c 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -77,7 +77,7 @@ extensions, accepted by GCC in C90 mode and in C++.
 * Function Names::      Printable strings which are the name of the current
                         function.
 * Return Address::      Getting the return or frame address of a function.
-* Stack Scrubbing::     Stack scrubbing interfaces.
+* Stack Scrubbing::     Stack scrubbing internal interfaces.
 * Vector Extensions::   Using vector instructions through built-in functions.
 * Offsetof::            Special syntax for implementing @code{offsetof}.
 * __sync Builtins::     Legacy built-in functions for atomic memory access.
@@ -8729,51 +8729,259 @@ pid_t wait (wait_status_ptr_t p)
 @item strub
 @cindex @code{strub} type attribute
 This attribute defines stack-scrubbing properties of functions and
-variables.  When applied to function types, it takes an optional
-argument.  When applied to a pointer-to-function type, it gets
-propagated to the function type if the optional argument is given.
-
-A function whose type is annotated with @code{at-calls} @code{strub}
-mode (@code{strub("at-calls")}, @code{strub(1)}, or @code{strub})
-undergoes interface changes.  Its callers are adjusted to match the
-changes, and to automatically scrub (overwrite with zeros) the stack
-space used by the function.
-
-A function whose type indicates @code{internal} @code{strub} mode
-(@code{strub("internal")} or @code{strub(2)}) retains an unmodified
-interface, but may be turned into a wrapper that calls the wrapped body
-using a custom interface.  The wrapper then scrubs the stack space used
-by the wrapped body.
-
-An automatically-allocated variable whose type carries the @code{strub}
-attribute causes the enclosing function to have @code{strub} enabled.
-
-A statically-allocated variable whose type carries the @code{strub}
-attribute causes functions that @emph{read} it with that type to have
-@code{strub} enabled.  Reading from data referenced by pointers to such
-types has the same effect.  Note: The attribute does not carry over from
-a composite type to the types of its components, so the intended effect
-may not be obtained with non-scalar types.
-
-A @code{strub} context is the body of a function that has strub enabled,
-be it explicitly, by @code{at-calls} or @code{internal} mode, or
-implicitly, by reading from a @code{strub}-marked data type.
+variables.  Being a type attribute, it attaches to types, even when
+specified in function and variable declarations.  When applied to
+function types, it takes an optional string argument.  When applied to a
+pointer-to-function type, if the optional argument is given, it gets
+propagated to the function type.
+
+@smallexample
+/* A strub variable.  */
+int __attribute__ ((strub)) var;
+/* A strub variable that happens to be a pointer.  */
+__attribute__ ((strub)) int *strub_ptr_to_int;
+/* A pointer type that may point to a strub variable.  */
+typedef int __attribute__ ((strub)) *ptr_to_strub_int_type;
+
+/* A declaration of a strub function.  */
+extern int __attribute__ ((strub)) foo (void);
+/* A pointer to that strub function.  */
+int __attribute__ ((strub ("at-calls"))) (*ptr_to_strub_fn)(void) = foo;
+@end smallexample
+
+A function associated with @code{at-calls} @code{strub} mode
+(@code{strub("at-calls")}, or just @code{strub}) undergoes interface
+changes.  Its callers are adjusted to match the changes, and to scrub
+(overwrite with zeros) the stack space used by the called function after
+it returns.  The interface change makes the function type incompatible
+with an unadorned but otherwise equivalent type, so @emph{every}
+declaration and every type that may be used to call the function must be
+associated with this strub mode.
+
+A function associated with @code{internal} @code{strub} mode
+(@code{strub("internal")}) retains an unmodified, type-compatible
+interface, but it may be turned into a wrapper that calls the wrapped
+body using a custom interface.  The wrapper then scrubs the stack space
+used by the wrapped body.  Though the wrapped body has its stack space
+scrubbed, the wrapper does not, so arguments and return values may
+remain unscrubbed even when such a function is called by another
+function that enables @code{strub}.  This is why, when compiling with
+@option{-fstrub=strict}, a @code{strub} context is not allowed to call
+@code{internal} @code{strub} functions.
+
+@smallexample
+/* A declaration of an internal-strub function.  */
+extern int __attribute__ ((strub ("internal"))) bar (void);
+
+int __attribute__ ((strub))
+baz (void)
+@{
+  /* Ok, foo was declared above as an at-calls strub function.  */
+  foo ();
+  /* Not allowed in strict mode, otherwise allowed.  */
+  bar ();
+@}
+@end smallexample
+
+An automatically-allocated variable associated with the @code{strub}
+attribute causes the (immediately) enclosing function to have
+@code{strub} enabled.
+
+A statically-allocated variable associated with the @code{strub}
+attribute causes functions that @emph{read} it, through its @code{strub}
+data type, to have @code{strub} enabled.  Reading data by dereferencing
+a pointer to a @code{strub} data type has the same effect.  Note: The
+attribute does not carry over from a composite type to the types of its
+components, so the intended effect may not be obtained with non-scalar
+types.
+
+When selecting a @code{strub}-enabled mode for a function that is not
+explicitly associated with one, because of @code{strub} variables or
+data pointers, the function must satisfy @code{internal} mode viability
+requirements (see below), even when @code{at-calls} mode is also viable
+and, being more efficient, ends up selected as an optimization.
+
+@smallexample
+/* zapme is implicitly strub-enabled because of strub variables.
+   Optimization may change its strub mode, but not the requirements.  */
+static int
+zapme (int i)
+@{
+  /* A local strub variable enables strub.  */
+  int __attribute__ ((strub)) lvar;
+  /* Reading strub data through a pointer-to-strub enables strub.  */
+  lvar = * (ptr_to_strub_int_type) &i;
+  /* Writing to a global strub variable does not enable strub.  */
+  var = lvar;
+  /* Reading from a global strub variable enables strub.  */
+  return var;
+@}
+@end smallexample
+
+A @code{strub} context is the body (as opposed to the interface) of a
+function that has @code{strub} enabled, be it explicitly, by
+@code{at-calls} or @code{internal} mode, or implicitly, due to
+@code{strub} variables or command-line options.
 
 A function of a type associated with the @code{disabled} @code{strub}
-mode (@code{strub("disabled")}, @code{strub(0)}, or no @code{strub} mode
-specified) will not have its own stack space scrubbed, and it cannot be
-called from @code{strub} contexts.
+mode (@code{strub("disabled")} will not have its own stack space
+scrubbed.  Such functions @emph{cannot} be called from within
+@code{strub} contexts.
+
+In order to enable a function to be called from within @code{strub}
+contexts without having its stack space scrubbed, associate it with the
+@code{callable} @code{strub} mode (@code{strub("callable")}).
+
+When a function is not assigned a @code{strub} mode, explicitly or
+implicitly, the mode defaults to @code{callable}, except when compiling
+with @option{-fstrub=strict}, that causes @code{strub} mode to default
+to @code{disabled}.
+
+@example
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+ /* Implicitly disabled with -fstrub=strict, otherwise callable.  */
+extern int bah (void);
+
+int __attribute__ ((strub))
+bal (void)
+@{
+  /* Not allowed, bad is not strub-callable.  */
+  bad ();
+  /* Ok, bac is strub-callable.  */
+  bac ();
+  /* Not allowed with -fstrub=strict, otherwise allowed.  */
+  bah ();
+@}
+@end example
 
-A function that does not have @code{strub} enabled can only be called
-from within @code{strub} contexts through a function type marked with
-the @code{callable} @code{strub} mode (@code{strub("callable")} or
-@code{strub(3)}).
+Function types marked @code{callable} and @code{disabled} are not
+mutually compatible types, but the underlying interfaces are compatible,
+so it is safe to convert pointers between them, and to use such pointers
+or alternate declarations to call them.  Interfaces are also
+interchangeable between them and @code{internal} (but not
+@code{at-calls}!), but adding @code{internal} to a pointer type will not
+cause the pointed-to function to perform stack scrubbing.
+
+@example
+void __attribute__ ((strub))
+bap (void)
+@{
+  /* Assign a callable function to pointer-to-disabled.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bac;
+  /* Not allowed: calls disabled type in a strub context.  */
+  d_p ();
+
+  /* Assign a disabled function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bad;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an internal function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  c_p = bar;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an at-calls function to pointer-to-callable.
+     Flaggged as incompatible.  */
+  c_p = bal;
+  /* The call through an interface-incompatible type will not use the
+     modified interface expected by the at-calls function, so it is
+     likely to misbehave at runtime.  */
+  c_p ();
+@}
+@end example
 
 @code{Strub} contexts are never inlined into non-@code{strub} contexts.
-When an @code{internal}-strub function is split, the wrapper can often
-be inlined, but the wrapped body never is.  Functions marked as
-@code{always_inline}, even if explicitly assigned @code{internal} strub
-mode, will not undergo wrapping, so their body gets inlined.
+When an @code{internal}-strub function is split up, the wrapper can
+often be inlined, but the wrapped body @emph{never} is.  A function
+marked as @code{always_inline}, even if explicitly assigned
+@code{internal} strub mode, will not undergo wrapping, so its body gets
+inlined as required.
+
+@example
+inline int __attribute__ ((strub ("at-calls")))
+inl_atc (void)
+@{
+  /* This body may get inlined into strub contexts.  */
+@}
+
+inline int __attribute__ ((strub ("internal")))
+inl_int (void)
+@{
+  /* This body NEVER gets inlined, though its wrapper may.  */
+@}
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+@{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+@}
+
+void __attribute__ ((strub ("disabled")))
+bat (void)
+@{
+  /* Not allowed, cannot inline into a non-strub context.  */
+  inl_int_ali ();
+@}
+@end example
+
+@cindex strub eligibility and viability
+Some @option{-fstrub=*} command line options enable @code{strub} modes
+implicitly where viable.  A @code{strub} mode is only viable for a
+function if the function is eligible for that mode, and if other
+conditions, detailed below, are satisfied.  If it's not eligible for a
+mode, attempts to explicitly associate it with that mode are rejected
+with an error message.  If it is eligible, that mode may be assigned
+explicitly through this attribute, but implicit assignment through
+command-line options may involve additional viability requirements.
+
+A function is ineligible for @code{at-calls} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+@code{At-calls} @code{strub} mode, if not requested through the function
+type, is only viable for an eligible function if the function is not
+visible to other translation units, and if it doesn't have its address
+taken.
+
+@smallexample
+/* bar is eligible for at-calls strub mode,
+   but not viable for that mode because it is visible to other units.
+   It is eligible and viable for internal strub mode.  */
+void bav () @{@}
+
+/* setp is eligible for at-calls strub mode,
+   but not viable for that mode because its address is taken.
+   It is eligible and viable for internal strub mode.  */
+void setp (void) @{ static void (*p)(void); = setp; @}
+@end smallexample
+
+A function is ineligible for @code{internal} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, or if attribute
+@code{noipa} is present.  For an @code{always_inline} function, meeting
+these requirements is enough to make it eligible.  Any function that has
+attribute @code{noclone}, that uses such extensions as non-local labels,
+computed gotos, alternate variable argument passing interfaces,
+@code{__builtin_next_arg}, or @code{__builtin_return_address}, or that
+takes too many (about 64Ki) arguments is ineligible, unless it is
+@code{always_inline}.  For @code{internal} @code{strub} mode, all
+eligible functions are viable.
+
+@smallexample
+/* flop is not eligible, thus not viable, for at-calls strub mode.
+   Likewise for internal strub mode.  */
+__attribute__ ((noipa)) void flop (void) @{@}
+
+/* flip is eligible and viable for at-calls strub mode.
+   It would be ineligible for internal strub mode, because of noclone,
+   if it weren't for always_inline.  With always_inline, noclone is not
+   an obstacle, so it is also eligible and viable for internal strub mode.  */
+inline __attribute__ ((noclone, always_inline)) void flip (void) @{@}
+@end smallexample
 
 @item unused
 @cindex @code{unused} type attribute
@@ -11804,40 +12012,48 @@ This function returns the value of the stack pointer register.
 @end deftypefn
 
 @node Stack Scrubbing
-@section Stack scrubbing interfaces
+@section Stack scrubbing internal interfaces
 
 Stack scrubbing involves cooperation between a @code{strub} context,
 i.e., a function whose stack frame is to be zeroed-out, and its callers.
 The caller initializes a stack watermark, the @code{strub} context
-updates the watermark to reflect its stack use, and the caller zeroes it
-out once it regains control.
-
-Each of these steps relies on a different builtin function call.  The
-functions are available in libgcc but, depending on optimization levels,
-they are expanded internally, adjusted to account for inlining, or
-combined/deferred (e.g. passing the caller-supplied watermark on to
-callees, refraining from erasing stack areas that the caller will).
+updates the watermark according to its stack use, and the caller zeroes
+it out once it regains control, whether by the callee's returning or by
+an exception.
+
+Each of these steps is performed by a different builtin function call.
+Calls to these builtins are introduced automatically, in response to
+@code{strub} attributes and command-line options; they are not expected
+to be explicitly called by source code.
+
+The functions that implement the builtins are available in libgcc but,
+depending on optimization levels, they are expanded internally, adjusted
+to account for inlining, and sometimes combined/deferred (e.g. passing
+the caller-supplied watermark on to callees, refraining from erasing
+stack areas that the caller will) to enable tail calls and to optimize
+for code size.
 
 @deftypefn {Built-in Function} {void} __builtin___strub_enter (void **@var{wmptr})
 This function initializes a stack @var{watermark} variable with the
-current top of the stack.  This builtin function should be called before
-entering a @code{strub} context.  It remains as a function call if optimization
-is not enabled.
+current top of the stack.  A call to this builtin function is introduced
+before entering a @code{strub} context.  It remains as a function call
+if optimization is not enabled.
 @end deftypefn
 
 @deftypefn {Built-in Function} {void} __builtin___strub_update (void **@var{wmptr})
 This function updates a stack @var{watermark} variable with the current
-top of the stack, if it tops the previous watermark.  This builtin
-function should be called within a @code{strub} context whenever
+top of the stack, if it tops the previous watermark.  A call to this
+builtin function is inserted within @code{strub} contexts, whenever
 additional stack space may have been used.  It remains as a function
 call at optimization levels lower than 2.
 @end deftypefn
 
 @deftypefn {Built-in Function} {void} __builtin___strub_leave (void **@var{wmptr})
 This function overwrites the memory area between the current top of the
-stack, and the @var{watermark}ed address.  This builtin function should
-be called after leaving a @code{strub} context.  It remains as a
-function call at optimization levels lower than 3.
+stack, and the @var{watermark}ed address.  A call to this builtin
+function is inserted after leaving a @code{strub} context.  It remains
+as a function call at optimization levels lower than 3, and it is guarded by
+a condition at level 2.
 @end deftypefn
 
 @node Vector Extensions
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 838d9ff1198..afcb37914ac 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,7 +599,8 @@ Objective-C and Objective-C++ Dialects}.
 -fstack-protector-explicit  -fstack-check @gol
 -fstack-limit-register=@var{reg}  -fstack-limit-symbol=@var{sym} @gol
 -fno-stack-limit  -fsplit-stack @gol
--fstrub=default -fstrub=disable -fstrub=at-calls -fstrub=internal -fstrub=all @gol
+-fstrub=disable -fstrub=strict -fstrub=relaxed @gol
+-fstrub=all -fstrub=at-calls -fstrub=internal @gol
 -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
 -fvtv-counts  -fvtv-debug @gol
 -finstrument-functions @gol
@@ -15513,46 +15514,55 @@ without @option{-fsplit-stack} always has a large stack.  Support for
 this is implemented in the gold linker in GNU binutils release 2.21
 and later.
 
-@item -fstrub=default
-@opindex fstrub=default
-Restore the default stack scrub (@code{strub}) setting, namely,
-@code{strub} is only enabled as required by @code{strub} attributes
-associated with function or variable types.  This is only useful to
-override earlier @samp{-fstrub=*} options.
-
 @item -fstrub=disable
 @opindex -fstrub=disable
 Disable stack scrubbing entirely, ignoring any @code{strub} attributes.
+See @xref{Common Type Attributes}.
+
+@item -fstrub=strict
+@opindex fstrub=strict
+Functions default to @code{strub} mode @code{disabled}, and apply
+@option{strict}ly the restriction that only functions associated with
+@code{strub}-@code{callable} modes (@code{at-calls}, @code{callable} and
+@code{always_inline} @code{internal}) are @code{callable} by functions
+with @code{strub}-enabled modes (@code{at-calls} and @code{internal}).
+
+@item -fstrub=relaxed
+@opindex fstrub=relaxed
+Restore the default stack scrub (@code{strub}) setting, namely,
+@code{strub} is only enabled as required by @code{strub} attributes
+associated with function and data types.  @code{Relaxed} means that
+strub contexts are only prevented from calling functions explicitly
+associated with @code{strub} mode @code{disabled}.  This option is only
+useful to override other @option{-fstrub=*} options that precede it in
+the command line.
 
 @item -fstrub=at-calls
 @opindex fstrub=at-calls
-Enable @code{at-calls} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{at-calls} @code{strub} if a different @code{strub}
-mode is explicitly requested, if attribute @code{noipa} is present, or
-if it calls @code{__builtin_apply_args}.  @code{At-calls} @code{strub}
-mode, if not requested through the function type, is only viable for an
-eligible function if the function is not visible to other translation
-units, and it doesn't have its address taken.
+Enable @code{at-calls} @code{strub} mode where viable.  The primary use
+of this option is for testing.  It exercises the @code{strub} machinery
+in scenarios strictly local to a translation unit.  This @code{strub}
+mode modifies function interfaces, so any function that is visible to
+other translation units, or that has its address taken, will @emph{not}
+be affected by this option.  Optimization options may also affect
+viability.  See the @code{strub} attribute documentation for details on
+viability and eligibility requirements.
 
 @item -fstrub=internal
 @opindex fstrub=internal
-Enable @code{internal} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{internal} @code{strub} if a different @code{strub}
-mode is explicitly requested, or if attribute @code{noipa} is present.
-Non-@code{always_inline} functions also become ineligible if attribute
-@code{noclone} is present, if the function uses such features as user
-labels, non-default variable argument interfaces,
-@code{__builtin_next_arg}, or @code{__builtin_return_address}, or if
-they have too many (about 64Ki) arguments.  For @code{internal}
-@code{strub}, all eligible functions are viable.
+Enable @code{internal} @code{strub} mode where viable.  The primary use
+of this option is for testing.  This option is intended to exercise
+thoroughly parts of the @code{strub} machinery that implement the less
+efficient, but interface-preserving @code{strub} mode.  Functions that
+would not be affected by this option are quite uncommon.
 
 @item -fstrub=all
 @opindex fstrub=all
-Enable @code{strub} for all viable functions, and consider non-viable
-functions as @code{callable}.  When both strub modes are viable,
-@code{at-calls} is preferred.
+Enable some @code{strub} mode where viable.  When both strub modes are
+viable, @code{at-calls} is preferred.  @option{-fdump-ipa-strubm} adds
+function attributes that tell which mode was selected for each function.
+The primary use of this option is for testing, to exercise thoroughly
+the @code{strub} machinery.
 
 @item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 @opindex fvtable-verify
@@ -17427,6 +17437,14 @@ and inlining decisions.
 @item inline
 Dump after function inlining.
 
+@item strubm
+Dump after selecting @code{strub} modes, and recording the selections as
+function attributes.
+
+@item strub
+Dump @code{strub} transformations: interface changes, function wrapping,
+and insertion of builtin calls for stack scrubbing and watermarking.
+
 @end table
 
 Additionally, the options @option{-optimized}, @option{-missed},
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 7f4bc44d2bb..932e49dba8e 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -397,7 +397,7 @@ can_inline_edge_p (struct cgraph_edge *e, bool report,
       e->inline_failed = CIF_SANITIZE_ATTRIBUTE_MISMATCH;
       inlinable = false;
     }
-  if (!strub_inlinable_p (callee, caller))
+  if (!strub_inlinable_to_p (callee, caller))
     {
       e->inline_failed = CIF_UNSPECIFIED;
       inlinable = false;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
index cf7d811779e..17d20141fab 100644
--- a/gcc/ipa-strub.c
+++ b/gcc/ipa-strub.c
@@ -159,12 +159,16 @@ enum strub_mode {
 
 };
 
+/* Look up a strub attribute in TYPE, and return it.  */
+
 static tree
 get_strub_attr_from_type (tree type)
 {
   return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
 }
 
+/* Look up a strub attribute in DECL or in its type, and return it.  */
+
 static tree
 get_strub_attr_from_decl (tree decl)
 {
@@ -174,23 +178,161 @@ get_strub_attr_from_decl (tree decl)
   return get_strub_attr_from_type (TREE_TYPE (decl));
 }
 
-tree
+/* Define a function to cache identifier ID, to be used as a strub attribute
+   parameter for a strub mode named after NAME.  */
+#define DEF_STRUB_IDS(NAME, ID)				\
+static inline tree get_strub_mode_id_ ## NAME () {	\
+  static tree identifier = NULL_TREE;			\
+  if (!identifier)					\
+    identifier = get_identifier (ID);			\
+  return identifier;					\
+}
+/* Same as DEF_STRUB_IDS, but use the string expansion of NAME as ID.  */
+#define DEF_STRUB_ID(NAME)				\
+DEF_STRUB_IDS (NAME, #NAME)
+
+/* Define functions for each of the strub mode identifiers.
+   Expose dashes rather than underscores.  */
+DEF_STRUB_ID (disabled)
+DEF_STRUB_IDS (at_calls, "at-calls")
+DEF_STRUB_ID (internal)
+DEF_STRUB_ID (callable)
+DEF_STRUB_ID (wrapped)
+DEF_STRUB_ID (wrapper)
+DEF_STRUB_ID (inlinable)
+DEF_STRUB_IDS (at_calls_opt, "at-calls-opt")
+
+/* Release the temporary macro names.  */
+#undef DEF_STRUB_IDS
+#undef DEF_STRUB_ID
+
+/* Return the identifier corresponding to strub MODE.  */
+
+static tree
+get_strub_mode_attr_parm (enum strub_mode mode)
+{
+  switch (mode)
+    {
+    case STRUB_DISABLED:
+      return get_strub_mode_id_disabled ();
+
+    case STRUB_AT_CALLS:
+      return get_strub_mode_id_at_calls ();
+
+    case STRUB_INTERNAL:
+      return get_strub_mode_id_internal ();
+
+    case STRUB_CALLABLE:
+      return get_strub_mode_id_callable ();
+
+    case STRUB_WRAPPED:
+      return get_strub_mode_id_wrapped ();
+
+    case STRUB_WRAPPER:
+      return get_strub_mode_id_wrapper ();
+
+    case STRUB_INLINABLE:
+      return get_strub_mode_id_inlinable ();
+
+    case STRUB_AT_CALLS_OPT:
+      return get_strub_mode_id_at_calls_opt ();
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+/* Return the parmeters (TREE_VALUE) for a strub attribute of MODE.
+   We know we use a single parameter, so we bypass the creation of a
+   tree list.  */
+
+static tree
 get_strub_mode_attr_value (enum strub_mode mode)
 {
-  return tree_cons (NULL_TREE,
-		    build_int_cst (integer_type_node, (int)mode),
-		    NULL_TREE);
+  return get_strub_mode_attr_parm (mode);
+}
+
+/* Determine whether ID is a well-formed strub mode-specifying attribute
+   parameter for a function (type).  Only user-visible modes are accepted, and
+   ID must be non-NULL.
+
+   For unacceptable parms, return 0, otherwise a nonzero value as below.
 
-#if 0 /* ??? use symbolic mode names with interned strings?  */
-  char *s = NULL;
+   If the parm enables strub, return positive, otherwise negative.
 
-  switch (strub_mode)
+   If the affected type must be a distinct, incompatible type,return an integer
+   of absolute value 2, otherwise 1.  */
+
+int
+strub_validate_fn_attr_parm (tree id)
+{
+  int ret;
+  const char *s = NULL;
+  size_t len = 0;
+
+  /* do NOT test for NULL.  This is only to be called with non-NULL arguments.
+     We assume that the strub parameter applies to a function, because only
+     functions accept an explicit argument.  If we accepted NULL, and we
+     happened to be called to verify the argument for a variable, our return
+     values would be wrong.  */
+  if (TREE_CODE (id) == STRING_CST)
     {
-      
+      s = TREE_STRING_POINTER (id);
+      len = TREE_STRING_LENGTH (id) - 1;
     }
-#endif
+  else if (TREE_CODE (id) == IDENTIFIER_NODE)
+    {
+      s = IDENTIFIER_POINTER (id);
+      len = IDENTIFIER_LENGTH (id);
+    }
+  else
+    return 0;
+
+  enum strub_mode mode;
+
+  if (len != 8)
+    return 0;
+
+  switch (s[0])
+    {
+    case 'd':
+      mode = STRUB_DISABLED;
+      ret = -1;
+      break;
+
+    case 'a':
+      mode = STRUB_AT_CALLS;
+      ret = 2;
+      break;
+
+    case 'i':
+      mode = STRUB_INTERNAL;
+      ret = 1;
+      break;
+
+    case 'c':
+      mode = STRUB_CALLABLE;
+      ret = -2;
+      break;
+
+    default:
+      /* Other parms are for internal use only.  */
+      return 0;
+    }
+
+  tree mode_id = get_strub_mode_attr_parm (mode);
+
+  if (TREE_CODE (id) == IDENTIFIER_NODE
+      ? id != mode_id
+      : strncmp (s, IDENTIFIER_POINTER (mode_id), len) != 0)
+    return 0;
+
+  return ret;
 }
 
+/* Return the strub mode from STRUB_ATTR.  VAR_P should be true if the attribute
+   is taken from a variable, rather than from a function, or a type thereof.  */
+
 static enum strub_mode
 get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
 {
@@ -200,28 +342,102 @@ get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
     {
       if (!TREE_VALUE (strub_attr))
 	mode = !var_p ? STRUB_AT_CALLS : STRUB_INTERNAL;
-      else if (TREE_CODE (TREE_VALUE (TREE_VALUE (strub_attr))) == INTEGER_CST)
-	mode = (enum strub_mode) tree_to_shwi (TREE_VALUE
-					       (TREE_VALUE (strub_attr)));
-      else /* Handlers convert symbolic mode names to INTEGER_CST.  */
-	gcc_unreachable ();
+      else
+	{
+	  gcc_checking_assert (!var_p);
+	  tree id = TREE_VALUE (strub_attr);
+	  if (TREE_CODE (id) == TREE_LIST)
+	    id = TREE_VALUE (id);
+	  const char *s = (TREE_CODE (id) == STRING_CST
+			   ? TREE_STRING_POINTER (id)
+			   : IDENTIFIER_POINTER (id));
+	  size_t len = (TREE_CODE (id) == STRING_CST
+			? TREE_STRING_LENGTH (id) - 1
+			: IDENTIFIER_LENGTH (id));
+
+	  switch (len)
+	    {
+	    case 7:
+	      switch (s[6])
+		{
+		case 'r':
+		  mode = STRUB_WRAPPER;
+		  break;
+
+		case 'd':
+		  mode = STRUB_WRAPPED;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 8:
+	      switch (s[0])
+		{
+		case 'd':
+		  mode = STRUB_DISABLED;
+		  break;
+
+		case 'a':
+		  mode = STRUB_AT_CALLS;
+		  break;
+
+		case 'i':
+		  mode = STRUB_INTERNAL;
+		  break;
+
+		case 'c':
+		  mode = STRUB_CALLABLE;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 9:
+	      mode = STRUB_INLINABLE;
+	      break;
+
+	    case 12:
+	      mode = STRUB_AT_CALLS_OPT;
+	      break;
+
+	    default:
+	      gcc_unreachable ();
+	    }
+
+	  gcc_checking_assert (TREE_CODE (id) == IDENTIFIER_NODE
+			       ? id == get_strub_mode_attr_parm (mode)
+			       : strncmp (IDENTIFIER_POINTER
+					  (get_strub_mode_attr_parm (mode)),
+					  s, len) == 0);
+	}
     }
 
   return mode;
 }
 
+/* Look up, decode and return the strub mode associated with FNDECL.  */
+
 static enum strub_mode
-get_strub_mode_from_decl (tree fndecl)
+get_strub_mode_from_fndecl (tree fndecl)
 {
   return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
 }
 
+/* Look up, decode and return the strub mode associated with NODE.  */
+
 static enum strub_mode
 get_strub_mode (cgraph_node *node)
 {
-  return get_strub_mode_from_decl (node->decl);
+  return get_strub_mode_from_fndecl (node->decl);
 }
 
+/* Look up, decode and return the strub mode associated with TYPE.  */
+
 static enum strub_mode
 get_strub_mode_from_type (tree type)
 {
@@ -231,12 +447,15 @@ get_strub_mode_from_type (tree type)
   if (attr)
     return get_strub_mode_from_attr (attr, var_p);
 
-  if (flag_strub > 0 && !var_p)
+  if (flag_strub >= -1 && !var_p)
     return STRUB_CALLABLE;
 
   return STRUB_DISABLED;
 }
 
+\f
+/* Return true iff NODE calls builtin va_start.  */
+
 static bool
 calls_builtin_va_start_p (cgraph_node *node)
 {
@@ -252,6 +471,8 @@ calls_builtin_va_start_p (cgraph_node *node)
   return result;
 }
 
+/* Return true iff NODE calls builtin apply_args, and optionally REPORT it.  */
+
 static bool
 calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
 {
@@ -276,12 +497,17 @@ calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE carries the always_inline attribute.  */
+
 static inline bool
 strub_always_inline_p (cgraph_node *node)
 {
   return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
 }
 
+/* Return true iff NODE is potentially eligible for any strub-enabled mode, and
+   optionally REPORT the reasons for ineligibility.  */
+
 static inline bool
 can_strub_p (cgraph_node *node, bool report = false)
 {
@@ -321,6 +547,10 @@ can_strub_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE is eligible for at-calls strub, and optionally REPORT
+   the reasons for ineligibility.  Besides general non-eligibility for
+   strub-enabled modes, at-calls rules out calling builtin apply_args.  */
+
 static bool
 can_strub_at_calls_p (cgraph_node *node, bool report = false)
 {
@@ -332,6 +562,9 @@ can_strub_at_calls_p (cgraph_node *node, bool report = false)
   return !calls_builtin_apply_args_p (node, report);
 }
 
+/* Symbolic macro for the max number of arguments that internal strub may add to
+   a function.  */
+
 #define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
 
 /* We can't perform internal strubbing if the function body involves certain
@@ -438,14 +671,12 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 			   || tree_versionable_function_p (node->decl));
 
 
-      /* Label values referenced are not preserved when copying.  If referenced
+      /* Label values references are not preserved when copying.  If referenced
 	 in nested functions, as in 920415-1.c and 920721-4.c their decls get
-	 remapped independently.  That might be too broad, in that we might be
-	 able to support correctly cases in which the labels are only used
-	 internally in a function, but disconnecting user labels from their
-	 original declarations is undesirable in general, and it probably
-	 doesn't matter, since explicitly-requested strub likely uses
-	 STRUB_AT_CALLS mode anyway.  */
+	 remapped independently.  The exclusion below might be too broad, in
+	 that we might be able to support correctly cases in which the labels
+	 are only used internally in a function, but disconnecting forced labels
+	 from their original declarations is undesirable in general.  */
       basic_block bb;
       FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
 	for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -459,8 +690,6 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 
 	    target = gimple_label_label (label_stmt);
 
-	    /* Make an edge to every label block that has been marked as a
-	       potential target for a computed goto or a non-local goto.  */
 	    if (!FORCED_LABEL (target))
 	      continue;
 
@@ -470,7 +699,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 forced labels");
 	  }
     }
 
@@ -491,6 +720,9 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return TRUE iff NODE has any strub-requiring local variable, or accesses (as
+   in reading) any variable through a strub-requiring type.  */
+
 static bool
 strub_from_body_p (cgraph_node *node)
 {
@@ -506,7 +738,9 @@ strub_from_body_p (cgraph_node *node)
 	!= STRUB_DISABLED)
       return true;
 
-  /* Now scan the body for loads with strub types.  */
+  /* Now scan the body for loads with strub-requiring types.
+     ??? Compound types don't propagate the strub requirement to
+     component types.  */
   basic_block bb;
   FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
     for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -528,6 +762,7 @@ strub_from_body_p (cgraph_node *node)
 
 /* Return true iff node is associated with a builtin that should be callable
    from strub contexts.  */
+
 static inline bool
 strub_callable_builtin_p (cgraph_node *node)
 {
@@ -568,17 +803,23 @@ strub_callable_builtin_p (cgraph_node *node)
     }
 }
 
+/* Compute the strub mode to be used for NODE.  STRUB_ATTR should be the strub
+   attribute,found for NODE, if any.  */
+
 static enum strub_mode
 compute_strub_mode (cgraph_node *node, tree strub_attr)
 {
   enum strub_mode req_mode = get_strub_mode_from_attr (strub_attr);
 
-  gcc_checking_assert (flag_strub >= -1 && flag_strub <= 3);
+  gcc_checking_assert (flag_strub >= -2 && flag_strub <= 3);
 
   /* Symbolic encodings of the -fstrub-* flags.  */
   /* Enable strub when explicitly requested through attributes to functions or
      variables, reporting errors if the requests cannot be satisfied.  */
   const bool strub_flag_auto = flag_strub < 0;
+  /* strub_flag_auto with strub call verification; without this, functions are
+     implicitly callable.  */
+  const bool strub_flag_strict = flag_strub < -1;
   /* Disable strub altogether, ignore attributes entirely.  */
   const bool strub_flag_disabled = flag_strub == 0;
   /* On top of _auto, also enable strub implicitly for functions that can
@@ -625,7 +866,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (!strub_flag_disabled
        && (strub_attr
 	   ? req_mode == STRUB_CALLABLE
-	   : (strub_flag_viable
+	   : (!strub_flag_strict
 	      || strub_callable_builtin_p (node))));
 
   /* This is a shorthand for either strub-enabled mode.  */
@@ -674,19 +915,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (at_calls_eligible
        && (strub_attr
 	   || (node->has_gimple_body_p ()
-#if 0 /* We no longer use collect_callers, so we can probably drop it.  */
-	       && node->get_availability () > AVAIL_INTERPOSABLE
-#endif
-	       && ((!node->externally_visible
-#if 0
-		    /* We wish to bypass the test below for functions that are
-		       not externally visible, but that's a little too broad: we
-		       do not wish to skip them for e.g. gnu_inline
-		       functions.  */
-		    && !TREE_PUBLIC (node->decl)
-		    && !DECL_EXTERNAL (node->decl)
-#endif
-		    )
+	       && (!node->externally_visible
 		   || (node->binds_to_current_def_p ()
 		       && node->can_be_local_p ()))
 	       && node->only_called_directly_p ())));
@@ -737,10 +966,6 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
   const enum strub_mode mode
     = ((strub_enable && is_always_inline)
        ? (strub_required ? STRUB_INLINABLE : STRUB_CALLABLE)
-#if 0
-       : (!strub_enable && strub_required && strub_attr)
-       ? req_mode
-#endif
        : (strub_enable && internal_viable
 	  && (strub_flag_internal || !at_calls_viable))
        ? STRUB_INTERNAL
@@ -802,6 +1027,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 /* Set FNDT's strub mode to MODE; FNDT may be a function decl or
    function type.  If OVERRIDE, do not check whether a mode is already
    set.  */
+
 static void
 strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
 {
@@ -828,12 +1054,18 @@ strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
   *attrp = attr;
 }
 
+/* Set FNDT's strub mode to callable.
+   FNDT may be a function decl or a function type.  */
+
 void
 strub_make_callable (tree fndt)
 {
   strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
 }
 
+/* Set NODE to strub MODE.  Report incompatibilities between MODE and the mode
+   requested through explicit attributes, and cases of non-eligibility.  */
+
 static void
 set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
 {
@@ -853,9 +1085,10 @@ 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",
-		    (int) mode, node->decl,
-		    (int) get_strub_mode_from_attr (attr));
+		    "%<strub%> mode %qE selected for %qD, when %qE was requested",
+		    get_strub_mode_attr_parm (mode),
+		    node->decl,
+		    get_strub_mode_attr_parm (req_mode));
 	  if (node->alias)
 	    {
 	      cgraph_node *target = node->ultimate_alias_target ();
@@ -906,6 +1139,8 @@ set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
   strub_set_fndt_mode_to (node->decl, mode, attr);
 }
 
+/* Compute and set NODE's strub mode.  */
+
 static void
 set_strub_mode (cgraph_node *node)
 {
@@ -946,6 +1181,7 @@ set_strub_mode (cgraph_node *node)
   set_strub_mode_to (node, mode);
 }
 
+\f
 /* Non-strub functions shouldn't be called from within strub contexts,
    except through callable ones.  Always inline strub functions can
    only be called from strub functions.  */
@@ -984,7 +1220,7 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_AT_CALLS_OPT:
     case STRUB_INTERNAL:
     case STRUB_WRAPPER:
-      return (flag_strub >= 0);
+      return (flag_strub >= -1);
 
     case STRUB_DISABLED:
       return false;
@@ -999,12 +1235,16 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
   return true;
 }
 
-/* We wish to avoid inlining WRAPPED functions back into their
-   WRAPPERs.  More generally, we wish to avoid inlining
-   strubbed functions into non-strubbed ones.  */
+/* Return true if CALLEE can be inlined into CALLER.  We wish to avoid inlining
+   WRAPPED functions back into their WRAPPERs.  More generally, we wish to avoid
+   inlining strubbed functions into non-strubbed ones.  CALLER doesn't have to
+   be an immediate caller of CALLEE: the immediate caller may have already been
+   cloned for inlining, and then CALLER may be further up the original call
+   chain.  ???  It would be nice if our own caller would retry inlining callee
+   if caller gets inlined.  */
 
 bool
-strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+strub_inlinable_to_p (cgraph_node *callee, cgraph_node *caller)
 {
   strub_mode callee_mode = get_strub_mode (callee);
 
@@ -1020,6 +1260,10 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_WRAPPER:
     case STRUB_DISABLED:
     case STRUB_CALLABLE:
+      /* When we consider inlining, we've already verified callability, so we
+	 can even inline callable and then disabled into a strub context.  That
+	 will get strubbed along with the context, so it's hopefully not a
+	 problem.  */
       return true;
 
     default:
@@ -1049,14 +1293,43 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
   return false;
 }
 
+/* Check that types T1 and T2 are strub-compatible.  Return 1 if the strub modes
+   are the same, 2 if they are interchangeable, and 0 otherwise.  */
+
+int
+strub_comptypes (tree t1, tree t2)
+{
+  enum strub_mode m1 = get_strub_mode_from_type (t1);
+  enum strub_mode m2 = get_strub_mode_from_type (t2);
+
+  if (m1 == m2)
+    return 1;
+
+  /* We're dealing with types, so only strub modes that can be selected by
+     attributes in the front end matter.  If either mode is at-calls, the
+     conversion is not compatible.  */
+  if (m1 == STRUB_AT_CALLS || m2 == STRUB_AT_CALLS)
+    return 0;
+
+  return 2;
+}
+
 /* Check that strub functions don't call non-strub functions, and that
    always_inline strub functions are only called by strub
    functions.  */
+
 static void
 verify_strub ()
 {
   cgraph_node *node;
 
+  /* It's expected that check strub-wise pointer type compatibility of variables
+     and of functions is already taken care of by front-ends, on account of the
+     attribute's being marked as affecting type identity and of the creation of
+     distinct types.  */
+
+  /* Check that call targets in strub contexts have strub-callable types.  */
+
   FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
   {
     enum strub_mode caller_mode = get_strub_mode (node);
@@ -1105,14 +1378,11 @@ verify_strub ()
 	  }
       }
   }
-
-  /* ??? Check strub-wise pointer type compatibility of variables and
-     functions, or is this already taken care of on account of the
-     attribute's being marked as affecting type identity?  */
 }
 
 namespace {
 
+/* Define a pass to compute strub modes.  */
 const pass_data pass_data_ipa_strub_mode = {
   SIMPLE_IPA_PASS,
   "strubm",
@@ -1133,24 +1403,26 @@ public:
   {}
   opt_pass *clone () { return new pass_ipa_strub_mode (m_ctxt); }
   virtual bool gate (function *) {
-    /* In the default setting, the attribute handler changes
-       flag_strub to -1 if any strub-enabling occurence of the
-       attribute is found.  If it remains at -2, nothing that would
-       enable strub was found, so we can disable it and avoid the
+    /* In relaxed (-3) and strict (-4) settings, that only enable strub at a
+       function or variable attribute's request, the attribute handler changes
+       flag_strub to -1 or -2, respectively, if any strub-enabling occurence of
+       the attribute is found.  Therefore, if it remains at -3 or -4, nothing
+       that would enable strub was found, so we can disable it and avoid the
        overhead.  */
-    if (flag_strub == -2)
+    if (flag_strub < -2)
       flag_strub = 0;
     return flag_strub;
   }
   virtual unsigned int execute (function *);
 };
 
+/* Define a pass to introduce strub transformations.  */
 const pass_data pass_data_ipa_strub = {
   SIMPLE_IPA_PASS,
   "strub",
   OPTGROUP_NONE,
   TV_NONE,
-  PROP_cfg, // properties_required
+  PROP_cfg | PROP_ssa, // properties_required
   0,	    // properties_provided
   0,	    // properties_destroyed
   0,	    // properties_start
@@ -1170,6 +1442,7 @@ public:
   virtual bool gate (function *) { return flag_strub; }
   virtual unsigned int execute (function *);
 
+  /* Define on demand and cache some types we use often.  */
 #define DEF_TYPE(NAME, INIT)			\
   static inline tree get_ ## NAME () {		\
     static tree type = NULL_TREE;		\
@@ -1202,6 +1475,7 @@ public:
 
 #undef DEF_TYPE
 
+  /* Define non-strub builtins on demand.  */
 #define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST)			\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1224,6 +1498,7 @@ public:
 
 #undef DEF_NM_BUILTIN
 
+  /* Define strub builtins on demand.  */
 #define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST)		\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1260,6 +1535,7 @@ public:
 
 #undef DEF_SS_BUILTIN
 
+    /* Define strub identifiers on demand.  */
 #define DEF_IDENT(NAME)					\
   static inline tree get_ ## NAME () {			\
     static tree identifier = NULL_TREE;			\
@@ -1278,6 +1554,10 @@ public:
   static inline void adjust_at_calls_call (cgraph_edge *, int);
   static inline void adjust_at_calls_calls (cgraph_node *);
 
+  /* Add to SEQ a call to the strub watermark update builtin, taking NODE's
+     location if given.  Optionally add the corresponding edge from NODE, with
+     execution frequency COUNT.  Return the modified SEQ.  */
+
   static inline gimple_seq
   call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
 			 gimple_seq seq = NULL)
@@ -1300,8 +1580,15 @@ public:
 
 } // anon namespace
 
+/* Gather with this type a collection of parameters that we're turning into
+   explicit references.  */
+
 typedef hash_set<tree> indirect_parms_t;
 
+/* Dereference OP's incoming turned-into-reference parm if it's an
+   INDIRECT_PARMS or an ADDR_EXPR thereof.  Set *REC and return according to
+   gimple-walking expectations.  */
+
 static tree
 maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
 {
@@ -1324,12 +1611,17 @@ maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
     {
       *rec = 0;
       if (indirect_parms.contains (TREE_OPERAND (op, 0)))
-	return TREE_OPERAND (op, 0);
+	{
+	  op = TREE_OPERAND (op, 0);
+	  return op;
+	}
     }
 
   return NULL_TREE;
 }
 
+/* A gimple-walking function that adds dereferencing to indirect parms.  */
+
 static tree
 walk_make_indirect (tree *op, int *rec, void *arg)
 {
@@ -1351,6 +1643,11 @@ walk_make_indirect (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* A gimple-walking function that turns any non-gimple-val ADDR_EXPRs into a
+   separate SSA.  Though addresses of e.g. parameters, and of members thereof,
+   are gimple vals, turning parameters into references, with an extra layer of
+   indirection and thus explicit dereferencing, need to be regimplified.  */
+
 static tree
 walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
 {
@@ -1374,6 +1671,57 @@ walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* Turn STMT's PHI arg defs into separate SSA defs if they've become
+   non-gimple_val.  Return TRUE if any edge insertions need to be committed.  */
+
+static bool
+walk_regimplify_phi (gphi *stmt)
+{
+  bool needs_commit = false;
+
+  for (unsigned i = 0, n = gimple_phi_num_args (stmt); i < n; i++)
+    {
+      tree op = gimple_phi_arg_def (stmt, i);
+      if ((TREE_CODE (op) == ADDR_EXPR
+	   && !is_gimple_val (op))
+	  /* ??? A PARM_DECL that was addressable in the original function and
+	     had its address in PHI nodes, but that became a reference in the
+	     wrapped clone would NOT be updated by update_ssa in PHI nodes.
+	     Alas, if we were to create a default def for it now, update_ssa
+	     would complain that the symbol that needed rewriting already has
+	     SSA names associated with it.  OTOH, leaving the PARM_DECL alone,
+	     it eventually causes errors because it remains unchanged in PHI
+	     nodes, but it gets rewritten as expected if it appears in other
+	     stmts.  So we cheat a little here, and force the PARM_DECL out of
+	     the PHI node and into an assignment.  It's a little expensive,
+	     because we insert it at the edge, which introduces a basic block
+	     that's entirely unnecessary, but it works, and the block will be
+	     removed as the default def gets propagated back into the PHI node,
+	     so the final optimized code looks just as expected.  */
+	  || (TREE_CODE (op) == PARM_DECL
+	      && !TREE_ADDRESSABLE (op)))
+	{
+	  tree temp = make_ssa_name (TREE_TYPE (op), stmt);
+	  if (TREE_CODE (op) == PARM_DECL)
+	    SET_SSA_NAME_VAR_OR_IDENTIFIER (temp, DECL_NAME (op));
+	  SET_PHI_ARG_DEF (stmt, i, temp);
+
+	  gimple *assign = gimple_build_assign (temp, op);
+	  if (gimple_phi_arg_has_location (stmt, i))
+	    gimple_set_location (assign, gimple_phi_arg_location (stmt, i));
+	  gsi_insert_on_edge (gimple_phi_arg_edge (stmt, i), assign);
+	  needs_commit = true;
+	}
+    }
+
+  return needs_commit;
+}
+
+/* Create a reference type to use for PARM when turning it into a reference.
+   NONALIASED causes the reference type to gain its own separate alias set, so
+   that accessing the indirectly-passed parm won'will not add aliasing
+   noise.  */
+
 static tree
 build_ref_type_for (tree parm, bool nonaliased = true)
 {
@@ -1411,6 +1759,7 @@ build_ref_type_for (tree parm, bool nonaliased = true)
 
 /* Add cgraph edges from current_function_decl to callees in SEQ with frequency
    COUNT, assuming all calls in SEQ are direct.  */
+
 static void
 add_call_edges_for_seq (gimple_seq seq, profile_count count)
 {
@@ -1425,16 +1774,22 @@ add_call_edges_for_seq (gimple_seq seq, profile_count count)
     {
       gimple *stmt = gsi_stmt (gsi);
 
-      if (!is_a <gcall *> (stmt))
+      gcall *call = dyn_cast <gcall *> (stmt);
+      if (!call)
 	continue;
 
-      gcall *call = as_a <gcall *> (stmt);
       tree callee = gimple_call_fndecl (call);
       gcc_checking_assert (callee);
       node->create_edge (cgraph_node::get_create (callee), call, count, false);
     }
 }
 
+/* Insert SEQ after the call at GSI, as if the call was in a try block with SEQ
+   as finally, i.e., SEQ will run after the call whether it returns or
+   propagates an exception.  This handles block splitting, EH edge and block
+   creation, noreturn and nothrow optimizations, and even throwing calls without
+   preexisting local handlers.  */
+
 static void
 gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
 {
@@ -1446,7 +1801,7 @@ gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
   if (gimple_has_location (stmt))
     annotate_all_with_location (seq, gimple_location (stmt));
 
-  gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+  gcall *call = dyn_cast <gcall *> (stmt);
   bool noreturn_p = call && gimple_call_noreturn_p (call);
   int eh_lp = lookup_stmt_eh_lp (stmt);
   bool must_not_throw_p = eh_lp < 0;
@@ -1586,8 +1941,12 @@ remove_named_attribute_unsharing (const char *name, tree *attrs)
     }
 }
 
+/* Record the order of the last cgraph entry whose mode we've already set, so
+   that we can perform mode setting incrementally without duplication.  */
 static int last_cgraph_order;
 
+/* Set strub modes for functions introduced since the last call.  */
+
 static void
 ipa_strub_set_mode_for_new_functions ()
 {
@@ -1616,6 +1975,7 @@ ipa_strub_set_mode_for_new_functions ()
 }
 
 /* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
+
 bool
 strub_splittable_p (cgraph_node *node)
 {
@@ -1641,10 +2001,11 @@ strub_splittable_p (cgraph_node *node)
 }
 
 /* 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))
+  switch (get_strub_mode_from_fndecl (fndecl))
     {
     case STRUB_WRAPPED:
     case STRUB_AT_CALLS:
@@ -1676,6 +2037,7 @@ strub_watermark_parm (tree fndecl)
 
 /* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
    hasn't been added yet.  Return the named argument count.  */
+
 int
 pass_ipa_strub::adjust_at_calls_type (tree type)
 {
@@ -1751,6 +2113,12 @@ pass_ipa_strub::adjust_at_calls_type (tree type)
   return named_args;
 }
 
+/* Adjust a call to an at-calls call target.  Create a watermark local variable
+   if needed, initialize it before, pass it to the callee according to the
+   modified at-calls interface, and release the callee's stack space after the
+   call, if not deferred.  If the call is const or pure, arrange for the
+   watermark to not be assumed unused or unchanged.  */
+
 void
 pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 {
@@ -1819,10 +2187,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 
     if (gimple_call_internal_p (stmt))
 #if 0
-      /*
-	new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
-	vargs);
-      */
+      new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
+						 vargs);
 #endif
       gcc_unreachable ();
     else
@@ -1917,6 +2283,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
   gsi_insert_finally_seq_after_call (gsi, seq);
 }
 
+/* Adjust all at-calls calls in NODE. */
+
 void
 pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
 {
@@ -1965,6 +2333,10 @@ pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
     }
 }
 
+/* The strubm (strub mode) pass computes a strub mode for each function in the
+   call graph, and checks, before any inlining, that strub callability
+   requirements in effect are satisfied.  */
+
 unsigned int
 pass_ipa_strub_mode::execute (function *)
 {
@@ -1977,12 +2349,17 @@ pass_ipa_strub_mode::execute (function *)
   return 0;
 }
 
+/* Create a strub mode pass.  */
+
 simple_ipa_opt_pass *
 make_pass_ipa_strub_mode (gcc::context *ctxt)
 {
   return new pass_ipa_strub_mode (ctxt);
 }
 
+/* The strub pass proper adjusts types, signatures, and at-calls calls, and
+   splits internal-strub functions.  */
+
 unsigned int
 pass_ipa_strub::execute (function *)
 {
@@ -2042,17 +2419,6 @@ pass_ipa_strub::execute (function *)
 	if (onode->alias)
 	  continue;
 
-#if 0 /* Calls are now adjusted when examining callers.  */
-	unsigned c;
-	cgraph_edge *e;
-	FOR_EACH_VEC_ELT (onode->collect_callers (), c, e)
-	  {
-	    push_cfun (DECL_STRUCT_FUNCTION (e->caller->decl));
-	    adjust_at_calls_call (e, named_args);
-	    pop_cfun ();
-	  }
-#endif
-
 	cgraph_node *nnode = onode;
 	push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
 
@@ -2071,10 +2437,10 @@ pass_ipa_strub::execute (function *)
 		{
 		  gimple *stmt = gsi_stmt (gsi);
 
-		  if (!is_gimple_call (stmt))
-		    continue;
+		  gcall *call = dyn_cast <gcall *> (stmt);
 
-		  gcall *call = as_a <gcall *> (stmt);
+		  if (!call)
+		    continue;
 
 		  if (gimple_alloca_call_p (call))
 		    {
@@ -2088,10 +2454,6 @@ pass_ipa_strub::execute (function *)
 	  }
 
 	pop_cfun ();
-
-#if 0
-	compute_fn_summary (onode, true);
-#endif
       }
   }
 
@@ -2108,43 +2470,6 @@ pass_ipa_strub::execute (function *)
 	continue;
       }
 
-#if 0
-    /* Hmm, this is an i386-specific attribute.  Do we need machine-specific
-       logic?  */
-    remove_named_attribute_unsharing ("interrupt",
-				      &DECL_ATTRIBUTES (onode->decl));
-#endif
-
-#if 0
-    if (!DECL_STRUCT_FUNCTION (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting struct-less function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    if (!onode->lowered)
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting non-lowered function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    /* 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.  FIXME.  */
-    if (!tree_versionable_function_p (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"%qD cannot be split for %<strub%>",
-		onode->decl);
-	continue;
-      }
-#endif
-
     bool is_stdarg = calls_builtin_va_start_p (onode);;
     bool apply_args = calls_builtin_apply_args_p (onode);
 
@@ -2753,26 +3078,45 @@ pass_ipa_strub::execute (function *)
       if (any_indirect)
 	{
 	  basic_block bb;
+	  bool needs_commit = false;
 	  FOR_EACH_BB_FN (bb, cfun)
-	    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
-		 !gsi_end_p (gsi); gsi_next (&gsi))
-	      {
-		gimple *stmt = gsi_stmt (gsi);
+	    {
+	      for (gphi_iterator gsi = gsi_start_nonvirtual_phis (bb);
+		   !gsi_end_p (gsi);
+		   gsi_next_nonvirtual_phi (&gsi))
+		{
+		  gphi *stmt = gsi.phi ();
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed && !is_gimple_debug (gsi_stmt (gsi)))
+		    if (walk_regimplify_phi (stmt))
+		      needs_commit = true;
+		}
 
-		walk_stmt_info wi = {};
-		wi.info = &indirect_nparms;
-		walk_gimple_op (stmt, walk_make_indirect, &wi);
-		if (wi.changed)
-		  {
-		    if (!is_gimple_debug (gsi_stmt (gsi)))
-		      {
-			wi.info = &gsi;
-			walk_gimple_op (stmt, walk_regimplify_addr_expr,
-					&wi);
-		      }
-		    update_stmt (stmt);
-		  }
-	      }
+	      for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+		   !gsi_end_p (gsi); gsi_next (&gsi))
+		{
+		  gimple *stmt = gsi_stmt (gsi);
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed)
+		    {
+		      if (!is_gimple_debug (stmt))
+			{
+			  wi.info = &gsi;
+			  walk_gimple_op (stmt, walk_regimplify_addr_expr,
+					  &wi);
+			}
+		      update_stmt (stmt);
+		    }
+		}
+	    }
+	  if (needs_commit)
+	    gsi_commit_edge_inserts ();
 	}
 
       if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca
@@ -2842,10 +3186,10 @@ pass_ipa_strub::execute (function *)
       push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
       gimple_stmt_iterator gsi
 	= gsi_after_labels (single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun)));
-      while (!is_gimple_call (gsi_stmt (gsi)))
-	gsi_next (&gsi);
 
-      gcall *wrcall = as_a <gcall *> (gsi_stmt (gsi));
+      gcall *wrcall;
+      while (!(wrcall = dyn_cast <gcall *> (gsi_stmt (gsi))))
+	gsi_next (&gsi);
 
       tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
       TREE_ADDRESSABLE (swm) = true;
@@ -2973,11 +3317,6 @@ pass_ipa_strub::execute (function *)
 
       pop_cfun ();
     }
-
-#if 0
-    compute_fn_summary (onode, true);
-    compute_fn_summary (nnode, true);
-#endif
   }
 
   if (flag_checking)
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
index c335ab42097..5c0b266c3f8 100644
--- a/gcc/ipa-strub.h
+++ b/gcc/ipa-strub.h
@@ -18,11 +18,10 @@ You should have received a copy of the GNU General Public License
 along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
-/* Return TRUE if the first function can be inlined into the second,
-   as far as stack scrubbing constraints are concerned.  CALLEE
-   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 TRUE if CALLEE can be inlined into CALLER, as far as stack scrubbing
+   constraints are concerned.  CALLEE doesn't have to be called directly by
+   CALLER, but the returned value says nothing about intervening functions.  */
+extern bool strub_inlinable_to_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);
@@ -33,3 +32,14 @@ extern tree strub_watermark_parm (tree fndecl);
 
 /* Make a function type or declaration callable.  */
 extern void strub_make_callable (tree fndecl);
+
+/* Return zero iff ID is NOT an acceptable parameter for a user-supplied strub
+   attribute for a function.  Otherwise, return >0 if it enables strub, <0 if it
+   does not.  Return +/-1 if the attribute-modified type is compatible with the
+   type without the attribute, or +/-2 if it is not compatible.  */
+extern int strub_validate_fn_attr_parm (tree id);
+
+/* Like comptypes, return 0 if t1 and t2 are not compatible, 1 if they are
+   compatible, and 2 if they are nearly compatible.  Same strub mode is
+   compatible, interface-compatible strub modes are nearly compatible.  */
+extern int strub_comptypes (tree t1, tree t2);
diff --git a/gcc/testsuite/c-c++-common/strub-O0.c b/gcc/testsuite/c-c++-common/strub-O0.c
index 6afe0fd5de1..c7a79a6ea0d 100644
--- a/gcc/testsuite/c-c++-common/strub-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O0 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O0, none of the strub builtins are expanded inline.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O1.c b/gcc/testsuite/c-c++-common/strub-O1.c
index 1cdeaecaf32..96285c975d9 100644
--- a/gcc/testsuite/c-c++-common/strub-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O1 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O1, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
index 7848c46d179..8edc0d8aa13 100644
--- a/gcc/testsuite/c-c++-common/strub-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O2, without -fno-inline, we fully expand enter and update, and add a test
    around the leave call.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
index 85a8f76785e..c6d900cf3c4 100644
--- a/gcc/testsuite/c-c++-common/strub-O2fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3.c b/gcc/testsuite/c-c++-common/strub-O3.c
index 1fcde345d36..33ee465e51c 100644
--- a/gcc/testsuite/c-c++-common/strub-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand" } */
 
 int __attribute__ ((__strub__)) var;
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
index a2eedfd96b2..2936f82079e 100644
--- a/gcc/testsuite/c-c++-common/strub-O3fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
index e5cb1f60541..479746e57d8 100644
--- a/gcc/testsuite/c-c++-common/strub-Og.c
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Og -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Og -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Og, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
index 194aacc2c05..2241d4ea07f 100644
--- a/gcc/testsuite/c-c++-common/strub-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Os, without -fno-inline, we fully expand enter, and also update.  The
    expanded update might be larger than a call proper, but argument saving and
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
index 46e84bf6560..a322bcc5da6 100644
--- a/gcc/testsuite/c-c++-common/strub-all1.c
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -22,11 +22,11 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
index f377541cff0..db60026d0e0 100644
--- a/gcc/testsuite/c-c++-common/strub-all2.c
+++ b/gcc/testsuite/c-c++-common/strub-all2.c
@@ -17,8 +17,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
index f180b17f30e..2f462adc1ef 100644
--- a/gcc/testsuite/c-c++-common/strub-apply1.c
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -1,13 +1,13 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
-void __attribute__ ((__strub__ (3)))
+void __attribute__ ((__strub__ ("callable")))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0);
 }
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   void *args = __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
index 379a54b73b7..bab2dd10f5b 100644
--- a/gcc/testsuite/c-c++-common/strub-apply2.c
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 extern void __attribute__ ((__strub__))
 apply_function (void *args);
@@ -10,3 +10,9 @@ apply_args (int i, int j, double d) /* { dg-error "selected" } */
   void *args = __builtin_apply_args (); /* { dg-message "does not support" } */
   apply_function (args);
 }
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After the error and sorry messages in strubm, build_ssa_passes are gated out,
+   so we don't get the PROP_ssa property that targetclone requires.  When
+   checking is not enabled at configure time, we admit to being confused and
+   bail out, but when checking is eneabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
index 9b4786be698..85f9d090fda 100644
--- a/gcc/testsuite/c-c++-common/strub-apply3.c
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -1,8 +1,14 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 void __attribute__ ((__strub__))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */
 }
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
index 409f747743e..15ffaa031b8 100644
--- a/gcc/testsuite/c-c++-common/strub-apply4.c
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+/* { dg-options "-O2 -fstrub=strict -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
@@ -18,4 +18,4 @@ void f() {
   apply_args (1, 2, 3);
 }
 
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 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 d964b07ae5d..b70843b4215 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -22,9 +22,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
index 530eee36d06..97a3988a6b9 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls2.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls2.c
@@ -17,7 +17,7 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-default1.c b/gcc/testsuite/c-c++-common/strub-default1.c
deleted file mode 100644
index 79d00cedb9a..00000000000
--- a/gcc/testsuite/c-c++-common/strub-default1.c
+++ /dev/null
@@ -1,42 +0,0 @@
-/* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
-
-static int __attribute__ ((__strub__)) var;
-
-/* 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 inline void
-__attribute__ ((__always_inline__))
-h() {
-  var++;
-}
-
-/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
-   the viability of at-calls strubbing.  Though internally a strub context, its
-   interface is not strub-enabled, so it's not callable from within strub
-   contexts.  */
-static inline void
-g() {
-  var--;
-  h();
-}
-
-/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
-   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
-void
-f() {
-  var++;
-  g();  /* { dg-error "calling non-.strub." } */
-}
-
-/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-4\[)\]" 1 "strubm" } } */
-/* { 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 "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
index 7b04eea35d9..3d73431b3dc 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O1" } */
+/* { dg-options "-fstrub=strict -O1" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O1.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
index 67d96419a5e..fddf3c745e7 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O2" } */
+/* { dg-options "-fstrub=strict -O2" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O2.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
index 34828d2711e..2bc384ee8d1 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O3" } */
+/* { dg-options "-fstrub=strict -O3" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -O3.  */
@@ -10,7 +10,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -18,7 +18,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -32,7 +32,7 @@ leak_string (void)
   return 0;
 }
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 int
 look_for_string (char *e)
 {
@@ -56,14 +56,14 @@ look_for_string (char *e)
   return 0;
 }
 
-static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+static __attribute__ ((__strub__ ("at-calls"), __noinline__, __noclone__))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 deferred_at_calls ()
 {
@@ -73,7 +73,7 @@ deferred_at_calls ()
   return ret;
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 deferred_internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
index b273660aea1..fbaf85fe0fa 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -Os" } */
+/* { dg-options "-fstrub=strict -Os" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -Os.  */
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
index a74658c9ac9..e9d7b7b9ee0 100644
--- a/gcc/testsuite/c-c++-common/strub-internal1.c
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -23,9 +23,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
index a6e69357b23..8b8e15a51c7 100644
--- a/gcc/testsuite/c-c++-common/strub-internal2.c
+++ b/gcc/testsuite/c-c++-common/strub-internal2.c
@@ -14,8 +14,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
index 7e22a266ad9..0a4a7539d34 100644
--- a/gcc/testsuite/c-c++-common/strub-parms1.c
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -16,7 +16,7 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 large_byref_arg (struct large_arg la)
 {
 }
@@ -24,7 +24,7 @@ large_byref_arg (struct large_arg la)
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*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)))
+void __attribute__ ((__strub__ ("internal")))
 std_arg (int i, ...)
 {
   va_list vl;
@@ -38,7 +38,7 @@ std_arg (int i, ...)
 /* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
index 2d8036c0fbc..147171d96d5 100644
--- a/gcc/testsuite/c-c++-common/strub-parms2.c
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -15,14 +15,14 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 large_byref_arg (struct large_arg la)
 {
 }
 
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 std_arg (int i, ...)
 {
   va_list vl;
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
index f64361f1235..4e92682895a 100644
--- a/gcc/testsuite/c-c++-common/strub-parms3.c
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
new file mode 100644
index 00000000000..e2f9d8aebca
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  Without the error,
+   inlining takes place.  */
+
+#include "strub-strict1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
new file mode 100644
index 00000000000..98474435d2e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  */
+
+#include "strub-strict2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
index a4226ce0119..1de15342595 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fexceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fexceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
index 3bab553478b..f9209c81900 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
index c89cc7c2c47..bed1dcfb54a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
index b869fafb691..6bf0071f52b 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
index 1d3dd2f2c2c..4732f515bf7 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
index 4dd4281b03b..8d6424c479a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-strict1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
new file mode 100644
index 00000000000..7f5247cfaf0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-strict1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+static int __attribute__ ((__strub__)) var;
+
+/* 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 inline void
+__attribute__ ((__always_inline__))
+h() {
+  var++;
+}
+
+/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
+   the viability of at-calls strubbing.  Though internally a strub context, its
+   interface is not strub-enabled, so it's not callable from within strub
+   contexts.  */
+static inline void
+g() {
+  var--;
+  h();
+}
+
+/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
+   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
+void
+f() {
+  var++;
+  g();  /* { dg-error "calling non-.strub." } */
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-default2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
similarity index 54%
rename from gcc/testsuite/c-c++-common/strub-default2.c
rename to gcc/testsuite/c-c++-common/strub-strict2.c
index 487253e9227..17463b6e554 100644
--- a/gcc/testsuite/c-c++-common/strub-default2.c
+++ b/gcc/testsuite/c-c++-common/strub-strict2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
 
 static int __attribute__ ((__strub__)) var;
 
@@ -22,8 +22,14 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
index 0840dddd136..e48e0610e07 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 #include "strub-tail-O2.c"
 
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
index 9330d6ff4c1..87cda7ab21b 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.
    Tail calls are short-circuited at -O2+.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
index 5b33ff1f530..b5e45ab0525 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
index 38935e3270b..96aa7fe4b07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -1,39 +1,39 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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);
+extern int __attribute__ ((__strub__ ("callable"))) xcallable (void);
+extern int __attribute__ ((__strub__ ("internal"))) xinternal (void);
+extern int __attribute__ ((__strub__ ("at-calls"))) xat_calls (void);
+extern int __attribute__ ((__strub__ ("disabled"))) 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__ ("callable"))) callable (void);
+int __attribute__ ((__strub__ ("internal"))) internal (void);
+int __attribute__ ((__strub__ ("at-calls"))) at_calls (void);
+int __attribute__ ((__strub__ ("disabled"))) disabled (void);
 
 int __attribute__ ((__strub__)) var;
 int var_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 icallable (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 iinternal (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 idisabled (void);
 static inline int __attribute__ ((__always_inline__))
 ivar_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 i_callable (void) { return 0; }
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 i_internal (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 i_at_calls (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 i_disabled (void) { return 0; }
 static inline int __attribute__ ((__always_inline__))
 i_var_user (void) { return var; }
@@ -70,7 +70,7 @@ i_var_user (void) { return var; }
 
 /* Not a strub context, so it can call anything.
    Explicitly declared as callable even from within strub contexts.  */
-int __attribute__ ((__strub__ (3)))
+int __attribute__ ((__strub__ ("callable")))
 callable (void) {
   int ret = 0;
 
@@ -88,7 +88,7 @@ callable (void) {
 
 /* 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)))
+int __attribute__ ((__strub__ ("internal")))
 internal (void) {
   int ret = var;
 
@@ -109,7 +109,7 @@ internal (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (1)))
+int __attribute__ ((__strub__ ("at-calls")))
 at_calls (void) {
   int ret = var;
 
@@ -130,7 +130,7 @@ at_calls (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (0)))
+int __attribute__ ((__strub__ ("disabled")))
 disabled () {
   int ret = 0;
 
@@ -205,7 +205,7 @@ iinternal (void) {
   return ret;
 }
 
-int
+int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void) {
   int ret = var;
 
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
index 100fb0c59a9..2857195706e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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,
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
index 9e818ac9748..98a92bc9eac 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
index d40e8aa45cb..5511a6e1e71 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __const__))
 f() {
   return 0;
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
index d4cbdaf10f3..47ee927964d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -1,12 +1,12 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__
 __attribute__ ((__const__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data1.c b/gcc/testsuite/c-c++-common/torture/strub-data1.c
index 62a03891ab6..7c27a2a1a6d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointed-to data enables strubbing if accessed.  */
 int __attribute__ ((__strub__)) var;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data2.c b/gcc/testsuite/c-c++-common/torture/strub-data2.c
index 9b7df13a280..e66d903780a 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointer itself is a strub variable, enabling internal strubbing when
    its value is used.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data3.c b/gcc/testsuite/c-c++-common/torture/strub-data3.c
index 515706caa32..5e08e0e58c6 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data4.c b/gcc/testsuite/c-c++-common/torture/strub-data4.c
index 0ec9e35429f..a818e7a38bb 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
index 69f3d65ed44..90e8ad51c1e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -Werror" } */
+/* { dg-options "-fstrub=strict -Werror" } */
 
 typedef int __attribute__ ((__strub__)) strub_int;
 strub_int *ptr;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
index b8adf8009e8..c165f312f16 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype ();
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
index 5b2c35ad6a7..69fcff8d376 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
index 5ee50456dc9..ff006224909 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int, ...);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
new file mode 100644
index 00000000000..b416ba06b59
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+}
+
+void
+bat (void)
+{
+  /* Not allowed, not a strub context.  */
+  inl_int_ali (); /* { dg-error "context" } */
+}
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
new file mode 100644
index 00000000000..f9a6b4a16fa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=all" } */
+
+#include "strub-inlinable1.c"
+
+/* With -fstrub=all, the caller becomes a strub context, so the strub-inlinable
+   callee is not rejected.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
new file mode 100644
index 00000000000..07cc432024d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+typedef void ft (void);
+typedef void ft2 (int, int);
+extern ft __attribute__ ((__strub__)) fnac;
+
+ft * f (void) {
+  return fnac; /* { dg-message "incompatible" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
new file mode 100644
index 00000000000..c29567eb937
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
@@ -0,0 +1,55 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic" } */
+
+/* C++ does not warn about the partial incompatibilities.
+
+   The d_p () calls are actually rejected, even in C++, but they are XFAILed
+   here because we don't get far enough in the compilation as to observe them,
+   because the incompatibilities are errors without -fpermissive.
+   strub-ptrfn3.c uses -fpermissive to check those.
+ */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
new file mode 100644
index 00000000000..e1f179e160e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
@@ -0,0 +1,50 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic -fpermissive" } */
+/* { dg-prune-output "command-line option .-fpermissive." } */
+
+/* See strub-ptrfn2.c.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
new file mode 100644
index 00000000000..70b558afad0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+/* This is strub-ptrfn2.c without -Wpedantic.
+
+   Even C doesn't report the (not-quite-)compatible conversions without it.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
index cb223da6efc..a262a086837 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
index 67d1434b1f8..4c4bd50c209 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
index 59f02ea901f..ce195c6b1f1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -1,10 +1,10 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __pure__))
 f() {
   static int i; /* Stop it from being detected as const.  */
   return i;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
index 973e909217d..75cd54ccb5b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
 __attribute__ ((__pure__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
index a4077c35a60..c596066a045 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -14,7 +14,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -59,14 +59,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
index 94e4156ea73..1fdba214a07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
index 0ca74beb59d..afbc2cc9ab4 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
 /* Check that a non-strub function leaves a string behind in the stack, and that
@@ -7,7 +7,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
index 4ab11c0682e..5300f1d330b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -85,14 +85,14 @@ intermediate ()
   return ret;
 }
 
-static inline __attribute__ ((__strub__ (2)))
+static inline __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
   return intermediate ();
 }
 
-int __attribute__ ((strub (0)))
+int __attribute__ ((__strub__ ("disabled")))
 main ()
 {
   if (look_for_string (internal ()))
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4d.c b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
index e4f7445607c..08de3f1c3b1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4d.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -1,7 +1,7 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
-#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ (1)))
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
 
 #include "strub-run4.c"
diff --git a/gcc/testsuite/g++.dg/torture/strub-init1.C b/gcc/testsuite/g++.dg/torture/strub-init1.C
index e51ae802be4..c226ab10ff6 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init1.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init2.C b/gcc/testsuite/g++.dg/torture/strub-init2.C
index edcb7bf8ad2..a7911f1fa72 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init2.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init3.C b/gcc/testsuite/g++.dg/torture/strub-init3.C
index bacf823ca4e..6ebebcd01e8 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init3.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
index 697ac9de764..9dba8797043 100644
--- a/gcc/testsuite/gnat.dg/strub_attr.adb
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -1,5 +1,5 @@
 --  { dg-do compile }
---  { dg-options "-fstrub=default -fdump-ipa-strubm" }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" }
 
 package body Strub_Attr is
    E : exception;
@@ -14,8 +14,37 @@ package body Strub_Attr is
       return X * X;
    end;
    
-   function G return Integer is (X);
+   function G return Integer is (FP (X));
+   --  Calling G would likely raise an exception, because although FP
+   --  carries the strub at-calls attribute needed to call F, the
+   --  attribute is dropped from the type used for the call proper.
+
+   
+   type GT is access function return Integer;
+   pragma Machine_Attribute (GT, "strub", "at-calls");
+   --  The pragma above seems to have no effect.
+
+   GP : GT := G'Access; -- { dg-warning "incompatible" "" { xfail *-*-* } }
+   pragma Machine_Attribute (GP, "strub", "at-calls");
+   --  The pragma above does modify GP's type,
+   --  but dereferencing it uses an unmodified copy of the type.
+   --  The initializer should be diagnosed:
+   --  GT should only reference functions with at-calls strub.
+
 end Strub_Attr;
 
---  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]2\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 0 "strubm" } }
 --  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark_ptr" 6 "strub" } }
+--  We have 1 at-calls subprogram (F) and 2 wrapped (P and G).
+--  For each of them, there's one match for the wrapped signature, 
+--  and one for the update call.
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 20 "strub" } }
+--  The 6 matches above, plus:
+--  5*2: wm var decl, enter, call, leave and clobber for each wrapper;
+--  2*1: an extra leave and clobber for the exception paths in the wrappers.
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 25 "strub" { xfail *-*-* } } }
+--  5*1: for the FP call in G (currently missing the watermarking)
diff --git a/gcc/testsuite/gnat.dg/strub_attr.ads b/gcc/testsuite/gnat.dg/strub_attr.ads
index a94c23bf418..eab60f77167 100644
--- a/gcc/testsuite/gnat.dg/strub_attr.ads
+++ b/gcc/testsuite/gnat.dg/strub_attr.ads
@@ -8,5 +8,14 @@ package Strub_Attr is
    X : Integer := 0;
    pragma Machine_Attribute (X, "strub");
 
+   type FT is access function (X : Integer) return Integer;
+   pragma Machine_Attribute (FT, "strub", "at-calls");
+   --  The pragma above seems to have no effect.
+
+   FP : FT := F'Access;
+   pragma Machine_Attribute (FP, "strub", "at-calls");
+   --  The pragma above does modify FP's type,
+   --  but dereferencing it uses an unmodified copy of the type.
+
    function G return Integer;
 end Strub_Attr;
diff --git a/libgcc/strub.c b/libgcc/strub.c
index fd6e27556e4..7c58deee1e6 100644
--- a/libgcc/strub.c
+++ b/libgcc/strub.c
@@ -36,7 +36,7 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 # define TOPS <
 #endif
 
-#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ ("callable")))
 
 /* Enter a stack scrubbing context, initializing the watermark to the caller's
    stack address.  */


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

* [gcc(refs/users/aoliva/heads/strub)] -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests
@ 2021-09-05  9:50 Alexandre Oliva
  0 siblings, 0 replies; 13+ messages in thread
From: Alexandre Oliva @ 2021-09-05  9:50 UTC (permalink / raw)
  To: gcc-cvs

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

commit d8d20330cc0abd9aa0a8b2d952cc504ac2d57876
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Thu Sep 2 23:15:36 2021 -0300

    -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests

Diff:
---
 .../doc/gnat_rm/security_hardening_features.rst    |  44 +-
 gcc/ada/gcc-interface/utils.c                      |  95 ++-
 gcc/attribs.c                                      |  14 +-
 gcc/c-family/c-attribs.c                           |  84 ++-
 gcc/common.opt                                     |  27 +-
 gcc/doc/extend.texi                                | 332 +++++++++--
 gcc/doc/invoke.texi                                |  76 ++-
 gcc/ipa-inline.c                                   |   2 +-
 gcc/ipa-strub.c                                    | 649 ++++++++++++++++-----
 gcc/ipa-strub.h                                    |  20 +-
 gcc/testsuite/c-c++-common/strub-O0.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O1.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2fni.c           |   2 +-
 gcc/testsuite/c-c++-common/strub-O3.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O3fni.c           |   2 +-
 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            |  12 +-
 gcc/testsuite/c-c++-common/strub-all2.c            |   6 +-
 gcc/testsuite/c-c++-common/strub-apply1.c          |   6 +-
 gcc/testsuite/c-c++-common/strub-apply2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-apply3.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-apply4.c          |   4 +-
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-at-calls2.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-default1.c        |  42 --
 gcc/testsuite/c-c++-common/strub-defer-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |  14 +-
 gcc/testsuite/c-c++-common/strub-defer-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-internal1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-internal2.c       |   6 +-
 gcc/testsuite/c-c++-common/strub-parms1.c          |  10 +-
 gcc/testsuite/c-c++-common/strub-parms2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-parms3.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-relaxed1.c        |  18 +
 gcc/testsuite/c-c++-common/strub-relaxed2.c        |  14 +
 gcc/testsuite/c-c++-common/strub-short-O0-exc.c    |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O0.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O3.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-strict1.c         |  48 ++
 .../{strub-default2.c => strub-strict2.c}          |  14 +-
 gcc/testsuite/c-c++-common/strub-tail-O1.c         |   2 +-
 gcc/testsuite/c-c++-common/strub-tail-O2.c         |   2 +-
 .../c-c++-common/torture/strub-callable1.c         |   2 +-
 .../c-c++-common/torture/strub-callable2.c         |  44 +-
 gcc/testsuite/c-c++-common/torture/strub-const1.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const2.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const3.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-const4.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-data1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data3.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data4.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |   2 +-
 .../c-c++-common/torture/strub-indcall1.c          |   2 +-
 .../c-c++-common/torture/strub-indcall2.c          |   2 +-
 .../c-c++-common/torture/strub-indcall3.c          |   2 +-
 .../c-c++-common/torture/strub-inlinable1.c        |  22 +
 .../c-c++-common/torture/strub-inlinable2.c        |   7 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |  10 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |  19 +
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure3.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-pure4.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run1.c    |  10 +-
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run4.c    |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run4d.c   |   4 +-
 gcc/testsuite/g++.dg/torture/strub-init1.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init2.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init3.C         |   2 +-
 gcc/testsuite/gnat.dg/strub_attr.adb               |   2 +-
 libgcc/strub.c                                     |   2 +-
 80 files changed, 1259 insertions(+), 550 deletions(-)

diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 309291f0341..fb5d14fed40 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -19,6 +19,18 @@ It can be enabled with the *-fzero-call-used-regs* command line
 option, to affect all subprograms in a compilation, and with a
 :samp:`Machine_Attribute` pragma, to affect only specific subprograms.
 
+.. code-block:: ada
+
+     procedure Foo;
+     pragma Machine_Attribute (Foo, "zero_call_used_regs", "used");
+     --  Before returning, Foo scrubs only call-clobbered registers
+     --  that it uses itself.
+
+     function Bar return Integer;
+     pragma Machine_Attribute (Bar, "zero_call_used_regs", "all");
+     --  Before returning, Bar scrubs all call-clobbered registers.
+
+
 For usage and more details on the command line option, and on the
 ``zero_call_used_regs`` attribute, see :title:`Using the GNU Compiler
 Collection (GCC)`.
@@ -31,13 +43,31 @@ Stack Scrubbing
 
 GNAT can generate code to zero-out stack frames used by subprograms.
 
-It can be activated with the *-fstrub* command line option, to affect
-all (viable) subprograms in a compilation, and with a
-:samp:`Machine_Attribute` pragma.
+It can be activated with the :samp:`Machine_Attribute` pragma, on
+specific subprograms, variables, and types.
 
-For usage and more details on the command line option, and on the
-``strub`` attribute, see :title:`Using the GNU Compiler Collection
-(GCC)`.
+.. code-block:: ada
+
+     function Foo returns Integer;
+     pragma Machine_Attribute (Foo, "strub");
+     --  Foo and its callers are modified so as to scrub the stack
+     --  space used by Foo after it returns.
+
+     procedure Bar;
+     pragma Machine_Attribute (Bar, "strub", "internal");
+     --  Bar is turned into a wrapper for its original body,
+     --  and they scrub the stack used by the original body.
+
+     Var : Integer;
+     pragma Machine_Attribute (Var, "strub");
+     --  Reading from Var in a subprogram enables stack scrubbing
+     --  of the stack space used by the subprogram.
+
+
+There are also *-fstrub* command line options to control default
+settings.  For usage and more details on the command line option, and
+on the ``strub`` attribute, see :title:`Using the GNU Compiler
+Collection (GCC)`.
 
 Note that Ada secondary stacks are not scrubbed.  The restriction
 ``No_Secondary_Stack`` avoids their use, and thus their accidental
@@ -49,4 +79,4 @@ is not fully reflected in Ada types, ``Access`` attributes, renaming
 and overriding.  Every access type, renaming, and overriding and
 overridden dispatching operations that may refer to an entity with an
 attribute-modified interface must be annotated with the same
-interface-modifying attribute.
+interface-modifying attribute, or with an interface-compatible one.
diff --git a/gcc/ada/gcc-interface/utils.c b/gcc/ada/gcc-interface/utils.c
index 6b20ddc93c9..47e98425417 100644
--- a/gcc/ada/gcc-interface/utils.c
+++ b/gcc/ada/gcc-interface/utils.c
@@ -39,6 +39,7 @@
 #include "varasm.h"
 #include "toplev.h"
 #include "opts.h"
+#include "ipa-strub.h"
 #include "output.h"
 #include "debug.h"
 #include "convert.h"
@@ -6611,6 +6612,7 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
+  bool compat_type = false;
   tree orig_fnptr_type = NULL_TREE;
   tree orig_args = args;
 
@@ -6624,68 +6626,36 @@ handle_strub_attribute (tree *node, tree name,
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      /* pragma Machine_Attribute turns string arguments into identifiers.
-	 Reverse it.  */
-      if (TREE_CODE (TREE_VALUE (args)) == IDENTIFIER_NODE)
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
 	{
-	  gcc_checking_assert (IDENTIFIER_POINTER (TREE_VALUE (args))
-			       [IDENTIFIER_LENGTH (TREE_VALUE (args))] == 0);
-	  TREE_VALUE (args) = build_string
-	    (IDENTIFIER_LENGTH (TREE_VALUE (args)) + 1,
-	     IDENTIFIER_POINTER (TREE_VALUE (args)));
-	}
-
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
-	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
-
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
+	case 1:
+	  compat_type = true;
+	  /* Fall through.  */
 
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
+	  compat_type = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	  compat_type = true;
+	  /* Fall through.  */
+
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
@@ -6699,23 +6669,30 @@ handle_strub_attribute (tree *node, tree name,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   if (!*no_add_attrs)
     {
       *no_add_attrs = true;
       if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
 	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
+	{
+	  if (compat_type)
+	    *node = build_variant_type_copy (*node);
+	  else
+	    *node = build_distinct_type_copy (*node);
+	}
       TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
 					   TYPE_ATTRIBUTES (*node));
 
       if (orig_fnptr_type)
 	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
+	  tree fnptr_type = ((flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
+			     ? orig_fnptr_type
+			     : compat_type
+			     ? build_variant_type_copy (orig_fnptr_type)
+			     : build_distinct_type_copy (orig_fnptr_type));
 	  TREE_TYPE (fnptr_type) = *node;
 	  *node = fnptr_type;
 	}
diff --git a/gcc/attribs.c b/gcc/attribs.c
index 0d22c20a35e..acac634afb5 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "attribs.h"
 #include "fold-const.h"
+#include "ipa-strub.h"
 #include "stor-layout.h"
 #include "langhooks.h"
 #include "plugin.h"
@@ -1353,9 +1354,20 @@ comp_type_attributes (const_tree type1, const_tree type2)
   if ((lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type1)) != NULL)
       ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) != NULL))
     return 0;
+  int strub_ret = strub_comptypes (CONST_CAST_TREE (type1),
+				   CONST_CAST_TREE (type2));
+  if (strub_ret == 0)
+    return strub_ret;
   /* As some type combinations - like default calling-convention - might
      be compatible, we have to call the target hook to get the final result.  */
-  return targetm.comp_type_attributes (type1, type2);
+  int target_ret = targetm.comp_type_attributes (type1, type2);
+  if (target_ret == 0)
+    return target_ret;
+  if (strub_ret == 2 || target_ret == 2)
+    return 2;
+  if (strub_ret == 1 && target_ret == 1)
+    return 1;
+  gcc_unreachable ();
 }
 
 /* PREDICATE acts as a function of type:
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index dc4a4d4d200..cb5c8d8f100 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "common/common-target.h"
 #include "langhooks.h"
 #include "tree-inline.h"
+#include "ipa-strub.h"
 #include "toplev.h"
 #include "tree-iterator.h"
 #include "opts.h"
@@ -1306,6 +1307,7 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
+  bool compat_type = false;
   tree orig_fnptr_type = NULL_TREE;
   tree orig_args = args;
 
@@ -1319,57 +1321,36 @@ handle_strub_attribute (tree *node, tree name,
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
 	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
+	case 1:
+	  compat_type = true;
+	  /* Fall through.  */
 
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
-
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
+	  compat_type = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	  compat_type = true;
+	  /* Fall through.  */
+
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
@@ -1383,23 +1364,30 @@ handle_strub_attribute (tree *node, tree name,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   if (!*no_add_attrs)
     {
       *no_add_attrs = true;
       if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
 	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
+	{
+	  if (compat_type)
+	    *node = build_variant_type_copy (*node);
+	  else
+	    *node = build_distinct_type_copy (*node);
+	}
       TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
 					   TYPE_ATTRIBUTES (*node));
 
       if (orig_fnptr_type)
 	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
+	  tree fnptr_type = ((flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
+			     ? orig_fnptr_type
+			     : compat_type
+			     ? build_variant_type_copy (orig_fnptr_type)
+			     : build_distinct_type_copy (orig_fnptr_type));
 	  TREE_TYPE (fnptr_type) = *node;
 	  *node = fnptr_type;
 	}
diff --git a/gcc/common.opt b/gcc/common.opt
index 71e0c971344..ef018ebf735 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,13 +2687,22 @@ fstrict-overflow
 Common
 Treat signed overflow as undefined.  Negated as -fwrapv -fwrapv-pointer.
 
-; If any strub-enabling attribute is seen when the default value is
-; selected, it's bumped up to -1.  The scrub mode gate function will
-; then bump -2 to 0 if no strub-enabling attribute is seen.  This
-; minimizes the strub overhead.
-fstrub=default
-Common RejectNegative Var(flag_strub, -2) Init(-2)
-Enable stack scrub as requested through attributes.
+fstrub=disable
+Common RejectNegative Var(flag_strub, 0)
+Disable stack scrub entirely, disregarding strub attributes.
+
+fstrub=strict
+Common RejectNegative Var(flag_strub, -4)
+Enable stack scrub as per attributes, with strict call checking.
+
+; If any strub-enabling attribute is seen when the default or strict
+; initializer values are in effect, flag_strub is bumped up by 2.  The
+; scrub mode gate function will then bump these initializer values to
+; 0 if no strub-enabling attribute is seen.  This minimizes the strub
+; overhead.
+fstrub=relaxed
+Common RejectNegative Var(flag_strub, -3) Init(-3)
+Restore default strub mode: as per attributes, with relaxed checking.
 
 fstrub=all
 Common RejectNegative Var(flag_strub, 3)
@@ -2707,10 +2716,6 @@ fstrub=internal
 Common RejectNegative Var(flag_strub, 2)
 Enable internal stack scrubbing for all viable functions.
 
-fstrub=disable
-Common RejectNegative Var(flag_strub, 0)
-Disable stack scrub entirely, disregarding strub attributes.
-
 fsync-libcalls
 Common Var(flag_sync_libcalls) Init(1)
 Implement __atomic operations via libcalls to legacy __sync functions.
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 1fcf60a3875..d85e695d85c 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -77,7 +77,7 @@ extensions, accepted by GCC in C90 mode and in C++.
 * Function Names::      Printable strings which are the name of the current
                         function.
 * Return Address::      Getting the return or frame address of a function.
-* Stack Scrubbing::     Stack scrubbing interfaces.
+* Stack Scrubbing::     Stack scrubbing internal interfaces.
 * Vector Extensions::   Using vector instructions through built-in functions.
 * Offsetof::            Special syntax for implementing @code{offsetof}.
 * __sync Builtins::     Legacy built-in functions for atomic memory access.
@@ -8729,51 +8729,259 @@ pid_t wait (wait_status_ptr_t p)
 @item strub
 @cindex @code{strub} type attribute
 This attribute defines stack-scrubbing properties of functions and
-variables.  When applied to function types, it takes an optional
-argument.  When applied to a pointer-to-function type, it gets
-propagated to the function type if the optional argument is given.
-
-A function whose type is annotated with @code{at-calls} @code{strub}
-mode (@code{strub("at-calls")}, @code{strub(1)}, or @code{strub})
-undergoes interface changes.  Its callers are adjusted to match the
-changes, and to automatically scrub (overwrite with zeros) the stack
-space used by the function.
-
-A function whose type indicates @code{internal} @code{strub} mode
-(@code{strub("internal")} or @code{strub(2)}) retains an unmodified
-interface, but may be turned into a wrapper that calls the wrapped body
-using a custom interface.  The wrapper then scrubs the stack space used
-by the wrapped body.
-
-An automatically-allocated variable whose type carries the @code{strub}
-attribute causes the enclosing function to have @code{strub} enabled.
-
-A statically-allocated variable whose type carries the @code{strub}
-attribute causes functions that @emph{read} it with that type to have
-@code{strub} enabled.  Reading from data referenced by pointers to such
-types has the same effect.  Note: The attribute does not carry over from
-a composite type to the types of its components, so the intended effect
-may not be obtained with non-scalar types.
-
-A @code{strub} context is the body of a function that has strub enabled,
-be it explicitly, by @code{at-calls} or @code{internal} mode, or
-implicitly, by reading from a @code{strub}-marked data type.
+variables.  Being a type attribute, it attaches to types, even when
+specified in function and variable declarations.  When applied to
+function types, it takes an optional string argument.  When applied to a
+pointer-to-function type, if the optional argument is given, it gets
+propagated to the function type.
+
+@smallexample
+/* A strub variable.  */
+int __attribute__ ((strub)) var;
+/* A strub variable that happens to be a pointer.  */
+__attribute__ ((strub)) int *strub_ptr_to_int;
+/* A pointer type that may point to a strub variable.  */
+typedef int __attribute__ ((strub)) *ptr_to_strub_int_type;
+
+/* A declaration of a strub function.  */
+extern int __attribute__ ((strub)) foo (void);
+/* A pointer to that strub function.  */
+int __attribute__ ((strub ("at-calls"))) (*ptr_to_strub_fn)(void) = foo;
+@end smallexample
+
+A function associated with @code{at-calls} @code{strub} mode
+(@code{strub("at-calls")}, or just @code{strub}) undergoes interface
+changes.  Its callers are adjusted to match the changes, and to scrub
+(overwrite with zeros) the stack space used by the called function after
+it returns.  The interface change makes the function type incompatible
+with an unadorned but otherwise equivalent type, so @emph{every}
+declaration and every type that may be used to call the function must be
+associated with this strub mode.
+
+A function associated with @code{internal} @code{strub} mode
+(@code{strub("internal")}) retains an unmodified, type-compatible
+interface, but it may be turned into a wrapper that calls the wrapped
+body using a custom interface.  The wrapper then scrubs the stack space
+used by the wrapped body.  Though the wrapped body has its stack space
+scrubbed, the wrapper does not, so arguments and return values may
+remain unscrubbed even when such a function is called by another
+function that enables @code{strub}.  This is why, when compiling with
+@option{-fstrub=strict}, a @code{strub} context is not allowed to call
+@code{internal} @code{strub} functions.
+
+@smallexample
+/* A declaration of an internal-strub function.  */
+extern int __attribute__ ((strub ("internal"))) bar (void);
+
+int __attribute__ ((strub))
+baz (void)
+@{
+  /* Ok, foo was declared above as an at-calls strub function.  */
+  foo ();
+  /* Not allowed in strict mode, otherwise allowed.  */
+  bar ();
+@}
+@end smallexample
+
+An automatically-allocated variable associated with the @code{strub}
+attribute causes the (immediately) enclosing function to have
+@code{strub} enabled.
+
+A statically-allocated variable associated with the @code{strub}
+attribute causes functions that @emph{read} it, through its @code{strub}
+data type, to have @code{strub} enabled.  Reading data by dereferencing
+a pointer to a @code{strub} data type has the same effect.  Note: The
+attribute does not carry over from a composite type to the types of its
+components, so the intended effect may not be obtained with non-scalar
+types.
+
+When selecting a @code{strub}-enabled mode for a function that is not
+explicitly associated with one, because of @code{strub} variables or
+data pointers, the function must satisfy @code{internal} mode viability
+requirements (see below), even when @code{at-calls} mode is also viable
+and, being more efficient, ends up selected as an optimization.
+
+@smallexample
+/* zapme is implicitly strub-enabled because of strub variables.
+   Optimization may change its strub mode, but not the requirements.  */
+static int
+zapme (int i)
+@{
+  /* A local strub variable enables strub.  */
+  int __attribute__ ((strub)) lvar;
+  /* Reading strub data through a pointer-to-strub enables strub.  */
+  lvar = * (ptr_to_strub_int_type) &i;
+  /* Writing to a global strub variable does not enable strub.  */
+  var = lvar;
+  /* Reading from a global strub variable enables strub.  */
+  return var;
+@}
+@end smallexample
+
+A @code{strub} context is the body (as opposed to the interface) of a
+function that has @code{strub} enabled, be it explicitly, by
+@code{at-calls} or @code{internal} mode, or implicitly, due to
+@code{strub} variables or command-line options.
 
 A function of a type associated with the @code{disabled} @code{strub}
-mode (@code{strub("disabled")}, @code{strub(0)}, or no @code{strub} mode
-specified) will not have its own stack space scrubbed, and it cannot be
-called from @code{strub} contexts.
+mode (@code{strub("disabled")} will not have its own stack space
+scrubbed.  Such functions @emph{cannot} be called from within
+@code{strub} contexts.
+
+In order to enable a function to be called from within @code{strub}
+contexts without having its stack space scrubbed, associate it with the
+@code{callable} @code{strub} mode (@code{strub("callable")}).
+
+When a function is not assigned a @code{strub} mode, explicitly or
+implicitly, the mode defaults to @code{callable}, except when compiling
+with @option{-fstrub=strict}, that causes @code{strub} mode to default
+to @code{disabled}.
+
+@example
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+ /* Implicitly disabled with -fstrub=strict, otherwise callable.  */
+extern int bah (void);
+
+int __attribute__ ((strub))
+bal (void)
+@{
+  /* Not allowed, bad is not strub-callable.  */
+  bad ();
+  /* Ok, bac is strub-callable.  */
+  bac ();
+  /* Not allowed with -fstrub=strict, otherwise allowed.  */
+  bah ();
+@}
+@end example
 
-A function that does not have @code{strub} enabled can only be called
-from within @code{strub} contexts through a function type marked with
-the @code{callable} @code{strub} mode (@code{strub("callable")} or
-@code{strub(3)}).
+Function types marked @code{callable} and @code{disabled} are not
+mutually compatible types, but the underlying interfaces are compatible,
+so it is safe to convert pointers between them, and to use such pointers
+or alternate declarations to call them.  Interfaces are also
+interchangeable between them and @code{internal} (but not
+@code{at-calls}!), but adding @code{internal} to a pointer type will not
+cause the pointed-to function to perform stack scrubbing.
+
+@example
+void __attribute__ ((strub))
+bap (void)
+@{
+  /* Assign a callable function to pointer-to-disabled.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bac;
+  /* Not allowed: calls disabled type in a strub context.  */
+  d_p ();
+
+  /* Assign a disabled function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bad;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an internal function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  c_p = bar;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an at-calls function to pointer-to-callable.
+     Flaggged as incompatible.  */
+  c_p = bal;
+  /* The call through an interface-incompatible type will not use the
+     modified interface expected by the at-calls function, so it is
+     likely to misbehave at runtime.  */
+  c_p ();
+@}
+@end example
 
 @code{Strub} contexts are never inlined into non-@code{strub} contexts.
-When an @code{internal}-strub function is split, the wrapper can often
-be inlined, but the wrapped body never is.  Functions marked as
-@code{always_inline}, even if explicitly assigned @code{internal} strub
-mode, will not undergo wrapping, so their body gets inlined.
+When an @code{internal}-strub function is split up, the wrapper can
+often be inlined, but the wrapped body @emph{never} is.  A function
+marked as @code{always_inline}, even if explicitly assigned
+@code{internal} strub mode, will not undergo wrapping, so its body gets
+inlined as required.
+
+@example
+inline int __attribute__ ((strub ("at-calls")))
+inl_atc (void)
+@{
+  /* This body may get inlined into strub contexts.  */
+@}
+
+inline int __attribute__ ((strub ("internal")))
+inl_int (void)
+@{
+  /* This body NEVER gets inlined, though its wrapper may.  */
+@}
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+@{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+@}
+
+void __attribute__ ((strub ("disabled")))
+bat (void)
+@{
+  /* Not allowed, cannot inline into a non-strub context.  */
+  inl_int_ali ();
+@}
+@end example
+
+@cindex strub eligibility and viability
+Some @option{-fstrub=*} command line options enable @code{strub} modes
+implicitly where viable.  A @code{strub} mode is only viable for a
+function if the function is eligible for that mode, and if other
+conditions, detailed below, are satisfied.  If it's not eligible for a
+mode, attempts to explicitly associate it with that mode are rejected
+with an error message.  If it is eligible, that mode may be assigned
+explicitly through this attribute, but implicit assignment through
+command-line options may involve additional viability requirements.
+
+A function is ineligible for @code{at-calls} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+@code{At-calls} @code{strub} mode, if not requested through the function
+type, is only viable for an eligible function if the function is not
+visible to other translation units, and if it doesn't have its address
+taken.
+
+@smallexample
+/* bar is eligible for at-calls strub mode,
+   but not viable for that mode because it is visible to other units.
+   It is eligible and viable for internal strub mode.  */
+void bav () @{@}
+
+/* setp is eligible for at-calls strub mode,
+   but not viable for that mode because its address is taken.
+   It is eligible and viable for internal strub mode.  */
+void setp (void) @{ static void (*p)(void); = setp; @}
+@end smallexample
+
+A function is ineligible for @code{internal} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, or if attribute
+@code{noipa} is present.  For an @code{always_inline} function, meeting
+these requirements is enough to make it eligible.  Any function that has
+attribute @code{noclone}, that uses such extensions as non-local labels,
+computed gotos, alternate variable argument passing interfaces,
+@code{__builtin_next_arg}, or @code{__builtin_return_address}, or that
+takes too many (about 64Ki) arguments is ineligible, unless it is
+@code{always_inline}.  For @code{internal} @code{strub} mode, all
+eligible functions are viable.
+
+@smallexample
+/* flop is not eligible, thus not viable, for at-calls strub mode.
+   Likewise for internal strub mode.  */
+__attribute__ ((noipa)) void flop (void) @{@}
+
+/* flip is eligible and viable for at-calls strub mode.
+   It would be ineligible for internal strub mode, because of noclone,
+   if it weren't for always_inline.  With always_inline, noclone is not
+   an obstacle, so it is also eligible and viable for internal strub mode.  */
+inline __attribute__ ((noclone, always_inline)) void flip (void) @{@}
+@end smallexample
 
 @item unused
 @cindex @code{unused} type attribute
@@ -11804,40 +12012,48 @@ This function returns the value of the stack pointer register.
 @end deftypefn
 
 @node Stack Scrubbing
-@section Stack scrubbing interfaces
+@section Stack scrubbing internal interfaces
 
 Stack scrubbing involves cooperation between a @code{strub} context,
 i.e., a function whose stack frame is to be zeroed-out, and its callers.
 The caller initializes a stack watermark, the @code{strub} context
-updates the watermark to reflect its stack use, and the caller zeroes it
-out once it regains control.
-
-Each of these steps relies on a different builtin function call.  The
-functions are available in libgcc but, depending on optimization levels,
-they are expanded internally, adjusted to account for inlining, or
-combined/deferred (e.g. passing the caller-supplied watermark on to
-callees, refraining from erasing stack areas that the caller will).
+updates the watermark according to its stack use, and the caller zeroes
+it out once it regains control, whether by the callee's returning or by
+an exception.
+
+Each of these steps is performed by a different builtin function call.
+Calls to these builtins are introduced automatically, in response to
+@code{strub} attributes and command-line options; they are not expected
+to be explicitly called by source code.
+
+The functions that implement the builtins are available in libgcc but,
+depending on optimization levels, they are expanded internally, adjusted
+to account for inlining, and sometimes combined/deferred (e.g. passing
+the caller-supplied watermark on to callees, refraining from erasing
+stack areas that the caller will) to enable tail calls and to optimize
+for code size.
 
 @deftypefn {Built-in Function} {void} __builtin___strub_enter (void **@var{wmptr})
 This function initializes a stack @var{watermark} variable with the
-current top of the stack.  This builtin function should be called before
-entering a @code{strub} context.  It remains as a function call if optimization
-is not enabled.
+current top of the stack.  A call to this builtin function is introduced
+before entering a @code{strub} context.  It remains as a function call
+if optimization is not enabled.
 @end deftypefn
 
 @deftypefn {Built-in Function} {void} __builtin___strub_update (void **@var{wmptr})
 This function updates a stack @var{watermark} variable with the current
-top of the stack, if it tops the previous watermark.  This builtin
-function should be called within a @code{strub} context whenever
+top of the stack, if it tops the previous watermark.  A call to this
+builtin function is inserted within @code{strub} contexts, whenever
 additional stack space may have been used.  It remains as a function
 call at optimization levels lower than 2.
 @end deftypefn
 
 @deftypefn {Built-in Function} {void} __builtin___strub_leave (void **@var{wmptr})
 This function overwrites the memory area between the current top of the
-stack, and the @var{watermark}ed address.  This builtin function should
-be called after leaving a @code{strub} context.  It remains as a
-function call at optimization levels lower than 3.
+stack, and the @var{watermark}ed address.  A call to this builtin
+function is inserted after leaving a @code{strub} context.  It remains
+as a function call at optimization levels lower than 3, and it is guarded by
+a condition at level 2.
 @end deftypefn
 
 @node Vector Extensions
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 838d9ff1198..afcb37914ac 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,7 +599,8 @@ Objective-C and Objective-C++ Dialects}.
 -fstack-protector-explicit  -fstack-check @gol
 -fstack-limit-register=@var{reg}  -fstack-limit-symbol=@var{sym} @gol
 -fno-stack-limit  -fsplit-stack @gol
--fstrub=default -fstrub=disable -fstrub=at-calls -fstrub=internal -fstrub=all @gol
+-fstrub=disable -fstrub=strict -fstrub=relaxed @gol
+-fstrub=all -fstrub=at-calls -fstrub=internal @gol
 -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
 -fvtv-counts  -fvtv-debug @gol
 -finstrument-functions @gol
@@ -15513,46 +15514,55 @@ without @option{-fsplit-stack} always has a large stack.  Support for
 this is implemented in the gold linker in GNU binutils release 2.21
 and later.
 
-@item -fstrub=default
-@opindex fstrub=default
-Restore the default stack scrub (@code{strub}) setting, namely,
-@code{strub} is only enabled as required by @code{strub} attributes
-associated with function or variable types.  This is only useful to
-override earlier @samp{-fstrub=*} options.
-
 @item -fstrub=disable
 @opindex -fstrub=disable
 Disable stack scrubbing entirely, ignoring any @code{strub} attributes.
+See @xref{Common Type Attributes}.
+
+@item -fstrub=strict
+@opindex fstrub=strict
+Functions default to @code{strub} mode @code{disabled}, and apply
+@option{strict}ly the restriction that only functions associated with
+@code{strub}-@code{callable} modes (@code{at-calls}, @code{callable} and
+@code{always_inline} @code{internal}) are @code{callable} by functions
+with @code{strub}-enabled modes (@code{at-calls} and @code{internal}).
+
+@item -fstrub=relaxed
+@opindex fstrub=relaxed
+Restore the default stack scrub (@code{strub}) setting, namely,
+@code{strub} is only enabled as required by @code{strub} attributes
+associated with function and data types.  @code{Relaxed} means that
+strub contexts are only prevented from calling functions explicitly
+associated with @code{strub} mode @code{disabled}.  This option is only
+useful to override other @option{-fstrub=*} options that precede it in
+the command line.
 
 @item -fstrub=at-calls
 @opindex fstrub=at-calls
-Enable @code{at-calls} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{at-calls} @code{strub} if a different @code{strub}
-mode is explicitly requested, if attribute @code{noipa} is present, or
-if it calls @code{__builtin_apply_args}.  @code{At-calls} @code{strub}
-mode, if not requested through the function type, is only viable for an
-eligible function if the function is not visible to other translation
-units, and it doesn't have its address taken.
+Enable @code{at-calls} @code{strub} mode where viable.  The primary use
+of this option is for testing.  It exercises the @code{strub} machinery
+in scenarios strictly local to a translation unit.  This @code{strub}
+mode modifies function interfaces, so any function that is visible to
+other translation units, or that has its address taken, will @emph{not}
+be affected by this option.  Optimization options may also affect
+viability.  See the @code{strub} attribute documentation for details on
+viability and eligibility requirements.
 
 @item -fstrub=internal
 @opindex fstrub=internal
-Enable @code{internal} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{internal} @code{strub} if a different @code{strub}
-mode is explicitly requested, or if attribute @code{noipa} is present.
-Non-@code{always_inline} functions also become ineligible if attribute
-@code{noclone} is present, if the function uses such features as user
-labels, non-default variable argument interfaces,
-@code{__builtin_next_arg}, or @code{__builtin_return_address}, or if
-they have too many (about 64Ki) arguments.  For @code{internal}
-@code{strub}, all eligible functions are viable.
+Enable @code{internal} @code{strub} mode where viable.  The primary use
+of this option is for testing.  This option is intended to exercise
+thoroughly parts of the @code{strub} machinery that implement the less
+efficient, but interface-preserving @code{strub} mode.  Functions that
+would not be affected by this option are quite uncommon.
 
 @item -fstrub=all
 @opindex fstrub=all
-Enable @code{strub} for all viable functions, and consider non-viable
-functions as @code{callable}.  When both strub modes are viable,
-@code{at-calls} is preferred.
+Enable some @code{strub} mode where viable.  When both strub modes are
+viable, @code{at-calls} is preferred.  @option{-fdump-ipa-strubm} adds
+function attributes that tell which mode was selected for each function.
+The primary use of this option is for testing, to exercise thoroughly
+the @code{strub} machinery.
 
 @item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 @opindex fvtable-verify
@@ -17427,6 +17437,14 @@ and inlining decisions.
 @item inline
 Dump after function inlining.
 
+@item strubm
+Dump after selecting @code{strub} modes, and recording the selections as
+function attributes.
+
+@item strub
+Dump @code{strub} transformations: interface changes, function wrapping,
+and insertion of builtin calls for stack scrubbing and watermarking.
+
 @end table
 
 Additionally, the options @option{-optimized}, @option{-missed},
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 7f4bc44d2bb..932e49dba8e 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -397,7 +397,7 @@ can_inline_edge_p (struct cgraph_edge *e, bool report,
       e->inline_failed = CIF_SANITIZE_ATTRIBUTE_MISMATCH;
       inlinable = false;
     }
-  if (!strub_inlinable_p (callee, caller))
+  if (!strub_inlinable_to_p (callee, caller))
     {
       e->inline_failed = CIF_UNSPECIFIED;
       inlinable = false;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
index cf7d811779e..17d20141fab 100644
--- a/gcc/ipa-strub.c
+++ b/gcc/ipa-strub.c
@@ -159,12 +159,16 @@ enum strub_mode {
 
 };
 
+/* Look up a strub attribute in TYPE, and return it.  */
+
 static tree
 get_strub_attr_from_type (tree type)
 {
   return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
 }
 
+/* Look up a strub attribute in DECL or in its type, and return it.  */
+
 static tree
 get_strub_attr_from_decl (tree decl)
 {
@@ -174,23 +178,161 @@ get_strub_attr_from_decl (tree decl)
   return get_strub_attr_from_type (TREE_TYPE (decl));
 }
 
-tree
+/* Define a function to cache identifier ID, to be used as a strub attribute
+   parameter for a strub mode named after NAME.  */
+#define DEF_STRUB_IDS(NAME, ID)				\
+static inline tree get_strub_mode_id_ ## NAME () {	\
+  static tree identifier = NULL_TREE;			\
+  if (!identifier)					\
+    identifier = get_identifier (ID);			\
+  return identifier;					\
+}
+/* Same as DEF_STRUB_IDS, but use the string expansion of NAME as ID.  */
+#define DEF_STRUB_ID(NAME)				\
+DEF_STRUB_IDS (NAME, #NAME)
+
+/* Define functions for each of the strub mode identifiers.
+   Expose dashes rather than underscores.  */
+DEF_STRUB_ID (disabled)
+DEF_STRUB_IDS (at_calls, "at-calls")
+DEF_STRUB_ID (internal)
+DEF_STRUB_ID (callable)
+DEF_STRUB_ID (wrapped)
+DEF_STRUB_ID (wrapper)
+DEF_STRUB_ID (inlinable)
+DEF_STRUB_IDS (at_calls_opt, "at-calls-opt")
+
+/* Release the temporary macro names.  */
+#undef DEF_STRUB_IDS
+#undef DEF_STRUB_ID
+
+/* Return the identifier corresponding to strub MODE.  */
+
+static tree
+get_strub_mode_attr_parm (enum strub_mode mode)
+{
+  switch (mode)
+    {
+    case STRUB_DISABLED:
+      return get_strub_mode_id_disabled ();
+
+    case STRUB_AT_CALLS:
+      return get_strub_mode_id_at_calls ();
+
+    case STRUB_INTERNAL:
+      return get_strub_mode_id_internal ();
+
+    case STRUB_CALLABLE:
+      return get_strub_mode_id_callable ();
+
+    case STRUB_WRAPPED:
+      return get_strub_mode_id_wrapped ();
+
+    case STRUB_WRAPPER:
+      return get_strub_mode_id_wrapper ();
+
+    case STRUB_INLINABLE:
+      return get_strub_mode_id_inlinable ();
+
+    case STRUB_AT_CALLS_OPT:
+      return get_strub_mode_id_at_calls_opt ();
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+/* Return the parmeters (TREE_VALUE) for a strub attribute of MODE.
+   We know we use a single parameter, so we bypass the creation of a
+   tree list.  */
+
+static tree
 get_strub_mode_attr_value (enum strub_mode mode)
 {
-  return tree_cons (NULL_TREE,
-		    build_int_cst (integer_type_node, (int)mode),
-		    NULL_TREE);
+  return get_strub_mode_attr_parm (mode);
+}
+
+/* Determine whether ID is a well-formed strub mode-specifying attribute
+   parameter for a function (type).  Only user-visible modes are accepted, and
+   ID must be non-NULL.
+
+   For unacceptable parms, return 0, otherwise a nonzero value as below.
 
-#if 0 /* ??? use symbolic mode names with interned strings?  */
-  char *s = NULL;
+   If the parm enables strub, return positive, otherwise negative.
 
-  switch (strub_mode)
+   If the affected type must be a distinct, incompatible type,return an integer
+   of absolute value 2, otherwise 1.  */
+
+int
+strub_validate_fn_attr_parm (tree id)
+{
+  int ret;
+  const char *s = NULL;
+  size_t len = 0;
+
+  /* do NOT test for NULL.  This is only to be called with non-NULL arguments.
+     We assume that the strub parameter applies to a function, because only
+     functions accept an explicit argument.  If we accepted NULL, and we
+     happened to be called to verify the argument for a variable, our return
+     values would be wrong.  */
+  if (TREE_CODE (id) == STRING_CST)
     {
-      
+      s = TREE_STRING_POINTER (id);
+      len = TREE_STRING_LENGTH (id) - 1;
     }
-#endif
+  else if (TREE_CODE (id) == IDENTIFIER_NODE)
+    {
+      s = IDENTIFIER_POINTER (id);
+      len = IDENTIFIER_LENGTH (id);
+    }
+  else
+    return 0;
+
+  enum strub_mode mode;
+
+  if (len != 8)
+    return 0;
+
+  switch (s[0])
+    {
+    case 'd':
+      mode = STRUB_DISABLED;
+      ret = -1;
+      break;
+
+    case 'a':
+      mode = STRUB_AT_CALLS;
+      ret = 2;
+      break;
+
+    case 'i':
+      mode = STRUB_INTERNAL;
+      ret = 1;
+      break;
+
+    case 'c':
+      mode = STRUB_CALLABLE;
+      ret = -2;
+      break;
+
+    default:
+      /* Other parms are for internal use only.  */
+      return 0;
+    }
+
+  tree mode_id = get_strub_mode_attr_parm (mode);
+
+  if (TREE_CODE (id) == IDENTIFIER_NODE
+      ? id != mode_id
+      : strncmp (s, IDENTIFIER_POINTER (mode_id), len) != 0)
+    return 0;
+
+  return ret;
 }
 
+/* Return the strub mode from STRUB_ATTR.  VAR_P should be true if the attribute
+   is taken from a variable, rather than from a function, or a type thereof.  */
+
 static enum strub_mode
 get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
 {
@@ -200,28 +342,102 @@ get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
     {
       if (!TREE_VALUE (strub_attr))
 	mode = !var_p ? STRUB_AT_CALLS : STRUB_INTERNAL;
-      else if (TREE_CODE (TREE_VALUE (TREE_VALUE (strub_attr))) == INTEGER_CST)
-	mode = (enum strub_mode) tree_to_shwi (TREE_VALUE
-					       (TREE_VALUE (strub_attr)));
-      else /* Handlers convert symbolic mode names to INTEGER_CST.  */
-	gcc_unreachable ();
+      else
+	{
+	  gcc_checking_assert (!var_p);
+	  tree id = TREE_VALUE (strub_attr);
+	  if (TREE_CODE (id) == TREE_LIST)
+	    id = TREE_VALUE (id);
+	  const char *s = (TREE_CODE (id) == STRING_CST
+			   ? TREE_STRING_POINTER (id)
+			   : IDENTIFIER_POINTER (id));
+	  size_t len = (TREE_CODE (id) == STRING_CST
+			? TREE_STRING_LENGTH (id) - 1
+			: IDENTIFIER_LENGTH (id));
+
+	  switch (len)
+	    {
+	    case 7:
+	      switch (s[6])
+		{
+		case 'r':
+		  mode = STRUB_WRAPPER;
+		  break;
+
+		case 'd':
+		  mode = STRUB_WRAPPED;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 8:
+	      switch (s[0])
+		{
+		case 'd':
+		  mode = STRUB_DISABLED;
+		  break;
+
+		case 'a':
+		  mode = STRUB_AT_CALLS;
+		  break;
+
+		case 'i':
+		  mode = STRUB_INTERNAL;
+		  break;
+
+		case 'c':
+		  mode = STRUB_CALLABLE;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 9:
+	      mode = STRUB_INLINABLE;
+	      break;
+
+	    case 12:
+	      mode = STRUB_AT_CALLS_OPT;
+	      break;
+
+	    default:
+	      gcc_unreachable ();
+	    }
+
+	  gcc_checking_assert (TREE_CODE (id) == IDENTIFIER_NODE
+			       ? id == get_strub_mode_attr_parm (mode)
+			       : strncmp (IDENTIFIER_POINTER
+					  (get_strub_mode_attr_parm (mode)),
+					  s, len) == 0);
+	}
     }
 
   return mode;
 }
 
+/* Look up, decode and return the strub mode associated with FNDECL.  */
+
 static enum strub_mode
-get_strub_mode_from_decl (tree fndecl)
+get_strub_mode_from_fndecl (tree fndecl)
 {
   return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
 }
 
+/* Look up, decode and return the strub mode associated with NODE.  */
+
 static enum strub_mode
 get_strub_mode (cgraph_node *node)
 {
-  return get_strub_mode_from_decl (node->decl);
+  return get_strub_mode_from_fndecl (node->decl);
 }
 
+/* Look up, decode and return the strub mode associated with TYPE.  */
+
 static enum strub_mode
 get_strub_mode_from_type (tree type)
 {
@@ -231,12 +447,15 @@ get_strub_mode_from_type (tree type)
   if (attr)
     return get_strub_mode_from_attr (attr, var_p);
 
-  if (flag_strub > 0 && !var_p)
+  if (flag_strub >= -1 && !var_p)
     return STRUB_CALLABLE;
 
   return STRUB_DISABLED;
 }
 
+\f
+/* Return true iff NODE calls builtin va_start.  */
+
 static bool
 calls_builtin_va_start_p (cgraph_node *node)
 {
@@ -252,6 +471,8 @@ calls_builtin_va_start_p (cgraph_node *node)
   return result;
 }
 
+/* Return true iff NODE calls builtin apply_args, and optionally REPORT it.  */
+
 static bool
 calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
 {
@@ -276,12 +497,17 @@ calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE carries the always_inline attribute.  */
+
 static inline bool
 strub_always_inline_p (cgraph_node *node)
 {
   return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
 }
 
+/* Return true iff NODE is potentially eligible for any strub-enabled mode, and
+   optionally REPORT the reasons for ineligibility.  */
+
 static inline bool
 can_strub_p (cgraph_node *node, bool report = false)
 {
@@ -321,6 +547,10 @@ can_strub_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE is eligible for at-calls strub, and optionally REPORT
+   the reasons for ineligibility.  Besides general non-eligibility for
+   strub-enabled modes, at-calls rules out calling builtin apply_args.  */
+
 static bool
 can_strub_at_calls_p (cgraph_node *node, bool report = false)
 {
@@ -332,6 +562,9 @@ can_strub_at_calls_p (cgraph_node *node, bool report = false)
   return !calls_builtin_apply_args_p (node, report);
 }
 
+/* Symbolic macro for the max number of arguments that internal strub may add to
+   a function.  */
+
 #define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
 
 /* We can't perform internal strubbing if the function body involves certain
@@ -438,14 +671,12 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 			   || tree_versionable_function_p (node->decl));
 
 
-      /* Label values referenced are not preserved when copying.  If referenced
+      /* Label values references are not preserved when copying.  If referenced
 	 in nested functions, as in 920415-1.c and 920721-4.c their decls get
-	 remapped independently.  That might be too broad, in that we might be
-	 able to support correctly cases in which the labels are only used
-	 internally in a function, but disconnecting user labels from their
-	 original declarations is undesirable in general, and it probably
-	 doesn't matter, since explicitly-requested strub likely uses
-	 STRUB_AT_CALLS mode anyway.  */
+	 remapped independently.  The exclusion below might be too broad, in
+	 that we might be able to support correctly cases in which the labels
+	 are only used internally in a function, but disconnecting forced labels
+	 from their original declarations is undesirable in general.  */
       basic_block bb;
       FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
 	for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -459,8 +690,6 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 
 	    target = gimple_label_label (label_stmt);
 
-	    /* Make an edge to every label block that has been marked as a
-	       potential target for a computed goto or a non-local goto.  */
 	    if (!FORCED_LABEL (target))
 	      continue;
 
@@ -470,7 +699,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 forced labels");
 	  }
     }
 
@@ -491,6 +720,9 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return TRUE iff NODE has any strub-requiring local variable, or accesses (as
+   in reading) any variable through a strub-requiring type.  */
+
 static bool
 strub_from_body_p (cgraph_node *node)
 {
@@ -506,7 +738,9 @@ strub_from_body_p (cgraph_node *node)
 	!= STRUB_DISABLED)
       return true;
 
-  /* Now scan the body for loads with strub types.  */
+  /* Now scan the body for loads with strub-requiring types.
+     ??? Compound types don't propagate the strub requirement to
+     component types.  */
   basic_block bb;
   FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
     for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -528,6 +762,7 @@ strub_from_body_p (cgraph_node *node)
 
 /* Return true iff node is associated with a builtin that should be callable
    from strub contexts.  */
+
 static inline bool
 strub_callable_builtin_p (cgraph_node *node)
 {
@@ -568,17 +803,23 @@ strub_callable_builtin_p (cgraph_node *node)
     }
 }
 
+/* Compute the strub mode to be used for NODE.  STRUB_ATTR should be the strub
+   attribute,found for NODE, if any.  */
+
 static enum strub_mode
 compute_strub_mode (cgraph_node *node, tree strub_attr)
 {
   enum strub_mode req_mode = get_strub_mode_from_attr (strub_attr);
 
-  gcc_checking_assert (flag_strub >= -1 && flag_strub <= 3);
+  gcc_checking_assert (flag_strub >= -2 && flag_strub <= 3);
 
   /* Symbolic encodings of the -fstrub-* flags.  */
   /* Enable strub when explicitly requested through attributes to functions or
      variables, reporting errors if the requests cannot be satisfied.  */
   const bool strub_flag_auto = flag_strub < 0;
+  /* strub_flag_auto with strub call verification; without this, functions are
+     implicitly callable.  */
+  const bool strub_flag_strict = flag_strub < -1;
   /* Disable strub altogether, ignore attributes entirely.  */
   const bool strub_flag_disabled = flag_strub == 0;
   /* On top of _auto, also enable strub implicitly for functions that can
@@ -625,7 +866,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (!strub_flag_disabled
        && (strub_attr
 	   ? req_mode == STRUB_CALLABLE
-	   : (strub_flag_viable
+	   : (!strub_flag_strict
 	      || strub_callable_builtin_p (node))));
 
   /* This is a shorthand for either strub-enabled mode.  */
@@ -674,19 +915,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (at_calls_eligible
        && (strub_attr
 	   || (node->has_gimple_body_p ()
-#if 0 /* We no longer use collect_callers, so we can probably drop it.  */
-	       && node->get_availability () > AVAIL_INTERPOSABLE
-#endif
-	       && ((!node->externally_visible
-#if 0
-		    /* We wish to bypass the test below for functions that are
-		       not externally visible, but that's a little too broad: we
-		       do not wish to skip them for e.g. gnu_inline
-		       functions.  */
-		    && !TREE_PUBLIC (node->decl)
-		    && !DECL_EXTERNAL (node->decl)
-#endif
-		    )
+	       && (!node->externally_visible
 		   || (node->binds_to_current_def_p ()
 		       && node->can_be_local_p ()))
 	       && node->only_called_directly_p ())));
@@ -737,10 +966,6 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
   const enum strub_mode mode
     = ((strub_enable && is_always_inline)
        ? (strub_required ? STRUB_INLINABLE : STRUB_CALLABLE)
-#if 0
-       : (!strub_enable && strub_required && strub_attr)
-       ? req_mode
-#endif
        : (strub_enable && internal_viable
 	  && (strub_flag_internal || !at_calls_viable))
        ? STRUB_INTERNAL
@@ -802,6 +1027,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 /* Set FNDT's strub mode to MODE; FNDT may be a function decl or
    function type.  If OVERRIDE, do not check whether a mode is already
    set.  */
+
 static void
 strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
 {
@@ -828,12 +1054,18 @@ strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
   *attrp = attr;
 }
 
+/* Set FNDT's strub mode to callable.
+   FNDT may be a function decl or a function type.  */
+
 void
 strub_make_callable (tree fndt)
 {
   strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
 }
 
+/* Set NODE to strub MODE.  Report incompatibilities between MODE and the mode
+   requested through explicit attributes, and cases of non-eligibility.  */
+
 static void
 set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
 {
@@ -853,9 +1085,10 @@ 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",
-		    (int) mode, node->decl,
-		    (int) get_strub_mode_from_attr (attr));
+		    "%<strub%> mode %qE selected for %qD, when %qE was requested",
+		    get_strub_mode_attr_parm (mode),
+		    node->decl,
+		    get_strub_mode_attr_parm (req_mode));
 	  if (node->alias)
 	    {
 	      cgraph_node *target = node->ultimate_alias_target ();
@@ -906,6 +1139,8 @@ set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
   strub_set_fndt_mode_to (node->decl, mode, attr);
 }
 
+/* Compute and set NODE's strub mode.  */
+
 static void
 set_strub_mode (cgraph_node *node)
 {
@@ -946,6 +1181,7 @@ set_strub_mode (cgraph_node *node)
   set_strub_mode_to (node, mode);
 }
 
+\f
 /* Non-strub functions shouldn't be called from within strub contexts,
    except through callable ones.  Always inline strub functions can
    only be called from strub functions.  */
@@ -984,7 +1220,7 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_AT_CALLS_OPT:
     case STRUB_INTERNAL:
     case STRUB_WRAPPER:
-      return (flag_strub >= 0);
+      return (flag_strub >= -1);
 
     case STRUB_DISABLED:
       return false;
@@ -999,12 +1235,16 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
   return true;
 }
 
-/* We wish to avoid inlining WRAPPED functions back into their
-   WRAPPERs.  More generally, we wish to avoid inlining
-   strubbed functions into non-strubbed ones.  */
+/* Return true if CALLEE can be inlined into CALLER.  We wish to avoid inlining
+   WRAPPED functions back into their WRAPPERs.  More generally, we wish to avoid
+   inlining strubbed functions into non-strubbed ones.  CALLER doesn't have to
+   be an immediate caller of CALLEE: the immediate caller may have already been
+   cloned for inlining, and then CALLER may be further up the original call
+   chain.  ???  It would be nice if our own caller would retry inlining callee
+   if caller gets inlined.  */
 
 bool
-strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+strub_inlinable_to_p (cgraph_node *callee, cgraph_node *caller)
 {
   strub_mode callee_mode = get_strub_mode (callee);
 
@@ -1020,6 +1260,10 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_WRAPPER:
     case STRUB_DISABLED:
     case STRUB_CALLABLE:
+      /* When we consider inlining, we've already verified callability, so we
+	 can even inline callable and then disabled into a strub context.  That
+	 will get strubbed along with the context, so it's hopefully not a
+	 problem.  */
       return true;
 
     default:
@@ -1049,14 +1293,43 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
   return false;
 }
 
+/* Check that types T1 and T2 are strub-compatible.  Return 1 if the strub modes
+   are the same, 2 if they are interchangeable, and 0 otherwise.  */
+
+int
+strub_comptypes (tree t1, tree t2)
+{
+  enum strub_mode m1 = get_strub_mode_from_type (t1);
+  enum strub_mode m2 = get_strub_mode_from_type (t2);
+
+  if (m1 == m2)
+    return 1;
+
+  /* We're dealing with types, so only strub modes that can be selected by
+     attributes in the front end matter.  If either mode is at-calls, the
+     conversion is not compatible.  */
+  if (m1 == STRUB_AT_CALLS || m2 == STRUB_AT_CALLS)
+    return 0;
+
+  return 2;
+}
+
 /* Check that strub functions don't call non-strub functions, and that
    always_inline strub functions are only called by strub
    functions.  */
+
 static void
 verify_strub ()
 {
   cgraph_node *node;
 
+  /* It's expected that check strub-wise pointer type compatibility of variables
+     and of functions is already taken care of by front-ends, on account of the
+     attribute's being marked as affecting type identity and of the creation of
+     distinct types.  */
+
+  /* Check that call targets in strub contexts have strub-callable types.  */
+
   FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
   {
     enum strub_mode caller_mode = get_strub_mode (node);
@@ -1105,14 +1378,11 @@ verify_strub ()
 	  }
       }
   }
-
-  /* ??? Check strub-wise pointer type compatibility of variables and
-     functions, or is this already taken care of on account of the
-     attribute's being marked as affecting type identity?  */
 }
 
 namespace {
 
+/* Define a pass to compute strub modes.  */
 const pass_data pass_data_ipa_strub_mode = {
   SIMPLE_IPA_PASS,
   "strubm",
@@ -1133,24 +1403,26 @@ public:
   {}
   opt_pass *clone () { return new pass_ipa_strub_mode (m_ctxt); }
   virtual bool gate (function *) {
-    /* In the default setting, the attribute handler changes
-       flag_strub to -1 if any strub-enabling occurence of the
-       attribute is found.  If it remains at -2, nothing that would
-       enable strub was found, so we can disable it and avoid the
+    /* In relaxed (-3) and strict (-4) settings, that only enable strub at a
+       function or variable attribute's request, the attribute handler changes
+       flag_strub to -1 or -2, respectively, if any strub-enabling occurence of
+       the attribute is found.  Therefore, if it remains at -3 or -4, nothing
+       that would enable strub was found, so we can disable it and avoid the
        overhead.  */
-    if (flag_strub == -2)
+    if (flag_strub < -2)
       flag_strub = 0;
     return flag_strub;
   }
   virtual unsigned int execute (function *);
 };
 
+/* Define a pass to introduce strub transformations.  */
 const pass_data pass_data_ipa_strub = {
   SIMPLE_IPA_PASS,
   "strub",
   OPTGROUP_NONE,
   TV_NONE,
-  PROP_cfg, // properties_required
+  PROP_cfg | PROP_ssa, // properties_required
   0,	    // properties_provided
   0,	    // properties_destroyed
   0,	    // properties_start
@@ -1170,6 +1442,7 @@ public:
   virtual bool gate (function *) { return flag_strub; }
   virtual unsigned int execute (function *);
 
+  /* Define on demand and cache some types we use often.  */
 #define DEF_TYPE(NAME, INIT)			\
   static inline tree get_ ## NAME () {		\
     static tree type = NULL_TREE;		\
@@ -1202,6 +1475,7 @@ public:
 
 #undef DEF_TYPE
 
+  /* Define non-strub builtins on demand.  */
 #define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST)			\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1224,6 +1498,7 @@ public:
 
 #undef DEF_NM_BUILTIN
 
+  /* Define strub builtins on demand.  */
 #define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST)		\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1260,6 +1535,7 @@ public:
 
 #undef DEF_SS_BUILTIN
 
+    /* Define strub identifiers on demand.  */
 #define DEF_IDENT(NAME)					\
   static inline tree get_ ## NAME () {			\
     static tree identifier = NULL_TREE;			\
@@ -1278,6 +1554,10 @@ public:
   static inline void adjust_at_calls_call (cgraph_edge *, int);
   static inline void adjust_at_calls_calls (cgraph_node *);
 
+  /* Add to SEQ a call to the strub watermark update builtin, taking NODE's
+     location if given.  Optionally add the corresponding edge from NODE, with
+     execution frequency COUNT.  Return the modified SEQ.  */
+
   static inline gimple_seq
   call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
 			 gimple_seq seq = NULL)
@@ -1300,8 +1580,15 @@ public:
 
 } // anon namespace
 
+/* Gather with this type a collection of parameters that we're turning into
+   explicit references.  */
+
 typedef hash_set<tree> indirect_parms_t;
 
+/* Dereference OP's incoming turned-into-reference parm if it's an
+   INDIRECT_PARMS or an ADDR_EXPR thereof.  Set *REC and return according to
+   gimple-walking expectations.  */
+
 static tree
 maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
 {
@@ -1324,12 +1611,17 @@ maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
     {
       *rec = 0;
       if (indirect_parms.contains (TREE_OPERAND (op, 0)))
-	return TREE_OPERAND (op, 0);
+	{
+	  op = TREE_OPERAND (op, 0);
+	  return op;
+	}
     }
 
   return NULL_TREE;
 }
 
+/* A gimple-walking function that adds dereferencing to indirect parms.  */
+
 static tree
 walk_make_indirect (tree *op, int *rec, void *arg)
 {
@@ -1351,6 +1643,11 @@ walk_make_indirect (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* A gimple-walking function that turns any non-gimple-val ADDR_EXPRs into a
+   separate SSA.  Though addresses of e.g. parameters, and of members thereof,
+   are gimple vals, turning parameters into references, with an extra layer of
+   indirection and thus explicit dereferencing, need to be regimplified.  */
+
 static tree
 walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
 {
@@ -1374,6 +1671,57 @@ walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* Turn STMT's PHI arg defs into separate SSA defs if they've become
+   non-gimple_val.  Return TRUE if any edge insertions need to be committed.  */
+
+static bool
+walk_regimplify_phi (gphi *stmt)
+{
+  bool needs_commit = false;
+
+  for (unsigned i = 0, n = gimple_phi_num_args (stmt); i < n; i++)
+    {
+      tree op = gimple_phi_arg_def (stmt, i);
+      if ((TREE_CODE (op) == ADDR_EXPR
+	   && !is_gimple_val (op))
+	  /* ??? A PARM_DECL that was addressable in the original function and
+	     had its address in PHI nodes, but that became a reference in the
+	     wrapped clone would NOT be updated by update_ssa in PHI nodes.
+	     Alas, if we were to create a default def for it now, update_ssa
+	     would complain that the symbol that needed rewriting already has
+	     SSA names associated with it.  OTOH, leaving the PARM_DECL alone,
+	     it eventually causes errors because it remains unchanged in PHI
+	     nodes, but it gets rewritten as expected if it appears in other
+	     stmts.  So we cheat a little here, and force the PARM_DECL out of
+	     the PHI node and into an assignment.  It's a little expensive,
+	     because we insert it at the edge, which introduces a basic block
+	     that's entirely unnecessary, but it works, and the block will be
+	     removed as the default def gets propagated back into the PHI node,
+	     so the final optimized code looks just as expected.  */
+	  || (TREE_CODE (op) == PARM_DECL
+	      && !TREE_ADDRESSABLE (op)))
+	{
+	  tree temp = make_ssa_name (TREE_TYPE (op), stmt);
+	  if (TREE_CODE (op) == PARM_DECL)
+	    SET_SSA_NAME_VAR_OR_IDENTIFIER (temp, DECL_NAME (op));
+	  SET_PHI_ARG_DEF (stmt, i, temp);
+
+	  gimple *assign = gimple_build_assign (temp, op);
+	  if (gimple_phi_arg_has_location (stmt, i))
+	    gimple_set_location (assign, gimple_phi_arg_location (stmt, i));
+	  gsi_insert_on_edge (gimple_phi_arg_edge (stmt, i), assign);
+	  needs_commit = true;
+	}
+    }
+
+  return needs_commit;
+}
+
+/* Create a reference type to use for PARM when turning it into a reference.
+   NONALIASED causes the reference type to gain its own separate alias set, so
+   that accessing the indirectly-passed parm won'will not add aliasing
+   noise.  */
+
 static tree
 build_ref_type_for (tree parm, bool nonaliased = true)
 {
@@ -1411,6 +1759,7 @@ build_ref_type_for (tree parm, bool nonaliased = true)
 
 /* Add cgraph edges from current_function_decl to callees in SEQ with frequency
    COUNT, assuming all calls in SEQ are direct.  */
+
 static void
 add_call_edges_for_seq (gimple_seq seq, profile_count count)
 {
@@ -1425,16 +1774,22 @@ add_call_edges_for_seq (gimple_seq seq, profile_count count)
     {
       gimple *stmt = gsi_stmt (gsi);
 
-      if (!is_a <gcall *> (stmt))
+      gcall *call = dyn_cast <gcall *> (stmt);
+      if (!call)
 	continue;
 
-      gcall *call = as_a <gcall *> (stmt);
       tree callee = gimple_call_fndecl (call);
       gcc_checking_assert (callee);
       node->create_edge (cgraph_node::get_create (callee), call, count, false);
     }
 }
 
+/* Insert SEQ after the call at GSI, as if the call was in a try block with SEQ
+   as finally, i.e., SEQ will run after the call whether it returns or
+   propagates an exception.  This handles block splitting, EH edge and block
+   creation, noreturn and nothrow optimizations, and even throwing calls without
+   preexisting local handlers.  */
+
 static void
 gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
 {
@@ -1446,7 +1801,7 @@ gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
   if (gimple_has_location (stmt))
     annotate_all_with_location (seq, gimple_location (stmt));
 
-  gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+  gcall *call = dyn_cast <gcall *> (stmt);
   bool noreturn_p = call && gimple_call_noreturn_p (call);
   int eh_lp = lookup_stmt_eh_lp (stmt);
   bool must_not_throw_p = eh_lp < 0;
@@ -1586,8 +1941,12 @@ remove_named_attribute_unsharing (const char *name, tree *attrs)
     }
 }
 
+/* Record the order of the last cgraph entry whose mode we've already set, so
+   that we can perform mode setting incrementally without duplication.  */
 static int last_cgraph_order;
 
+/* Set strub modes for functions introduced since the last call.  */
+
 static void
 ipa_strub_set_mode_for_new_functions ()
 {
@@ -1616,6 +1975,7 @@ ipa_strub_set_mode_for_new_functions ()
 }
 
 /* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
+
 bool
 strub_splittable_p (cgraph_node *node)
 {
@@ -1641,10 +2001,11 @@ strub_splittable_p (cgraph_node *node)
 }
 
 /* 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))
+  switch (get_strub_mode_from_fndecl (fndecl))
     {
     case STRUB_WRAPPED:
     case STRUB_AT_CALLS:
@@ -1676,6 +2037,7 @@ strub_watermark_parm (tree fndecl)
 
 /* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
    hasn't been added yet.  Return the named argument count.  */
+
 int
 pass_ipa_strub::adjust_at_calls_type (tree type)
 {
@@ -1751,6 +2113,12 @@ pass_ipa_strub::adjust_at_calls_type (tree type)
   return named_args;
 }
 
+/* Adjust a call to an at-calls call target.  Create a watermark local variable
+   if needed, initialize it before, pass it to the callee according to the
+   modified at-calls interface, and release the callee's stack space after the
+   call, if not deferred.  If the call is const or pure, arrange for the
+   watermark to not be assumed unused or unchanged.  */
+
 void
 pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 {
@@ -1819,10 +2187,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 
     if (gimple_call_internal_p (stmt))
 #if 0
-      /*
-	new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
-	vargs);
-      */
+      new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
+						 vargs);
 #endif
       gcc_unreachable ();
     else
@@ -1917,6 +2283,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
   gsi_insert_finally_seq_after_call (gsi, seq);
 }
 
+/* Adjust all at-calls calls in NODE. */
+
 void
 pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
 {
@@ -1965,6 +2333,10 @@ pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
     }
 }
 
+/* The strubm (strub mode) pass computes a strub mode for each function in the
+   call graph, and checks, before any inlining, that strub callability
+   requirements in effect are satisfied.  */
+
 unsigned int
 pass_ipa_strub_mode::execute (function *)
 {
@@ -1977,12 +2349,17 @@ pass_ipa_strub_mode::execute (function *)
   return 0;
 }
 
+/* Create a strub mode pass.  */
+
 simple_ipa_opt_pass *
 make_pass_ipa_strub_mode (gcc::context *ctxt)
 {
   return new pass_ipa_strub_mode (ctxt);
 }
 
+/* The strub pass proper adjusts types, signatures, and at-calls calls, and
+   splits internal-strub functions.  */
+
 unsigned int
 pass_ipa_strub::execute (function *)
 {
@@ -2042,17 +2419,6 @@ pass_ipa_strub::execute (function *)
 	if (onode->alias)
 	  continue;
 
-#if 0 /* Calls are now adjusted when examining callers.  */
-	unsigned c;
-	cgraph_edge *e;
-	FOR_EACH_VEC_ELT (onode->collect_callers (), c, e)
-	  {
-	    push_cfun (DECL_STRUCT_FUNCTION (e->caller->decl));
-	    adjust_at_calls_call (e, named_args);
-	    pop_cfun ();
-	  }
-#endif
-
 	cgraph_node *nnode = onode;
 	push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
 
@@ -2071,10 +2437,10 @@ pass_ipa_strub::execute (function *)
 		{
 		  gimple *stmt = gsi_stmt (gsi);
 
-		  if (!is_gimple_call (stmt))
-		    continue;
+		  gcall *call = dyn_cast <gcall *> (stmt);
 
-		  gcall *call = as_a <gcall *> (stmt);
+		  if (!call)
+		    continue;
 
 		  if (gimple_alloca_call_p (call))
 		    {
@@ -2088,10 +2454,6 @@ pass_ipa_strub::execute (function *)
 	  }
 
 	pop_cfun ();
-
-#if 0
-	compute_fn_summary (onode, true);
-#endif
       }
   }
 
@@ -2108,43 +2470,6 @@ pass_ipa_strub::execute (function *)
 	continue;
       }
 
-#if 0
-    /* Hmm, this is an i386-specific attribute.  Do we need machine-specific
-       logic?  */
-    remove_named_attribute_unsharing ("interrupt",
-				      &DECL_ATTRIBUTES (onode->decl));
-#endif
-
-#if 0
-    if (!DECL_STRUCT_FUNCTION (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting struct-less function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    if (!onode->lowered)
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting non-lowered function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    /* 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.  FIXME.  */
-    if (!tree_versionable_function_p (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"%qD cannot be split for %<strub%>",
-		onode->decl);
-	continue;
-      }
-#endif
-
     bool is_stdarg = calls_builtin_va_start_p (onode);;
     bool apply_args = calls_builtin_apply_args_p (onode);
 
@@ -2753,26 +3078,45 @@ pass_ipa_strub::execute (function *)
       if (any_indirect)
 	{
 	  basic_block bb;
+	  bool needs_commit = false;
 	  FOR_EACH_BB_FN (bb, cfun)
-	    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
-		 !gsi_end_p (gsi); gsi_next (&gsi))
-	      {
-		gimple *stmt = gsi_stmt (gsi);
+	    {
+	      for (gphi_iterator gsi = gsi_start_nonvirtual_phis (bb);
+		   !gsi_end_p (gsi);
+		   gsi_next_nonvirtual_phi (&gsi))
+		{
+		  gphi *stmt = gsi.phi ();
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed && !is_gimple_debug (gsi_stmt (gsi)))
+		    if (walk_regimplify_phi (stmt))
+		      needs_commit = true;
+		}
 
-		walk_stmt_info wi = {};
-		wi.info = &indirect_nparms;
-		walk_gimple_op (stmt, walk_make_indirect, &wi);
-		if (wi.changed)
-		  {
-		    if (!is_gimple_debug (gsi_stmt (gsi)))
-		      {
-			wi.info = &gsi;
-			walk_gimple_op (stmt, walk_regimplify_addr_expr,
-					&wi);
-		      }
-		    update_stmt (stmt);
-		  }
-	      }
+	      for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+		   !gsi_end_p (gsi); gsi_next (&gsi))
+		{
+		  gimple *stmt = gsi_stmt (gsi);
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed)
+		    {
+		      if (!is_gimple_debug (stmt))
+			{
+			  wi.info = &gsi;
+			  walk_gimple_op (stmt, walk_regimplify_addr_expr,
+					  &wi);
+			}
+		      update_stmt (stmt);
+		    }
+		}
+	    }
+	  if (needs_commit)
+	    gsi_commit_edge_inserts ();
 	}
 
       if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca
@@ -2842,10 +3186,10 @@ pass_ipa_strub::execute (function *)
       push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
       gimple_stmt_iterator gsi
 	= gsi_after_labels (single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun)));
-      while (!is_gimple_call (gsi_stmt (gsi)))
-	gsi_next (&gsi);
 
-      gcall *wrcall = as_a <gcall *> (gsi_stmt (gsi));
+      gcall *wrcall;
+      while (!(wrcall = dyn_cast <gcall *> (gsi_stmt (gsi))))
+	gsi_next (&gsi);
 
       tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
       TREE_ADDRESSABLE (swm) = true;
@@ -2973,11 +3317,6 @@ pass_ipa_strub::execute (function *)
 
       pop_cfun ();
     }
-
-#if 0
-    compute_fn_summary (onode, true);
-    compute_fn_summary (nnode, true);
-#endif
   }
 
   if (flag_checking)
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
index c335ab42097..5c0b266c3f8 100644
--- a/gcc/ipa-strub.h
+++ b/gcc/ipa-strub.h
@@ -18,11 +18,10 @@ You should have received a copy of the GNU General Public License
 along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
-/* Return TRUE if the first function can be inlined into the second,
-   as far as stack scrubbing constraints are concerned.  CALLEE
-   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 TRUE if CALLEE can be inlined into CALLER, as far as stack scrubbing
+   constraints are concerned.  CALLEE doesn't have to be called directly by
+   CALLER, but the returned value says nothing about intervening functions.  */
+extern bool strub_inlinable_to_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);
@@ -33,3 +32,14 @@ extern tree strub_watermark_parm (tree fndecl);
 
 /* Make a function type or declaration callable.  */
 extern void strub_make_callable (tree fndecl);
+
+/* Return zero iff ID is NOT an acceptable parameter for a user-supplied strub
+   attribute for a function.  Otherwise, return >0 if it enables strub, <0 if it
+   does not.  Return +/-1 if the attribute-modified type is compatible with the
+   type without the attribute, or +/-2 if it is not compatible.  */
+extern int strub_validate_fn_attr_parm (tree id);
+
+/* Like comptypes, return 0 if t1 and t2 are not compatible, 1 if they are
+   compatible, and 2 if they are nearly compatible.  Same strub mode is
+   compatible, interface-compatible strub modes are nearly compatible.  */
+extern int strub_comptypes (tree t1, tree t2);
diff --git a/gcc/testsuite/c-c++-common/strub-O0.c b/gcc/testsuite/c-c++-common/strub-O0.c
index 6afe0fd5de1..c7a79a6ea0d 100644
--- a/gcc/testsuite/c-c++-common/strub-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O0 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O0, none of the strub builtins are expanded inline.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O1.c b/gcc/testsuite/c-c++-common/strub-O1.c
index 1cdeaecaf32..96285c975d9 100644
--- a/gcc/testsuite/c-c++-common/strub-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O1 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O1, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
index 7848c46d179..8edc0d8aa13 100644
--- a/gcc/testsuite/c-c++-common/strub-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O2, without -fno-inline, we fully expand enter and update, and add a test
    around the leave call.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
index 85a8f76785e..c6d900cf3c4 100644
--- a/gcc/testsuite/c-c++-common/strub-O2fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3.c b/gcc/testsuite/c-c++-common/strub-O3.c
index 1fcde345d36..33ee465e51c 100644
--- a/gcc/testsuite/c-c++-common/strub-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand" } */
 
 int __attribute__ ((__strub__)) var;
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
index a2eedfd96b2..2936f82079e 100644
--- a/gcc/testsuite/c-c++-common/strub-O3fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
index e5cb1f60541..479746e57d8 100644
--- a/gcc/testsuite/c-c++-common/strub-Og.c
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Og -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Og -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Og, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
index 194aacc2c05..2241d4ea07f 100644
--- a/gcc/testsuite/c-c++-common/strub-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Os, without -fno-inline, we fully expand enter, and also update.  The
    expanded update might be larger than a call proper, but argument saving and
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
index 46e84bf6560..a322bcc5da6 100644
--- a/gcc/testsuite/c-c++-common/strub-all1.c
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -22,11 +22,11 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
index f377541cff0..db60026d0e0 100644
--- a/gcc/testsuite/c-c++-common/strub-all2.c
+++ b/gcc/testsuite/c-c++-common/strub-all2.c
@@ -17,8 +17,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
index f180b17f30e..2f462adc1ef 100644
--- a/gcc/testsuite/c-c++-common/strub-apply1.c
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -1,13 +1,13 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
-void __attribute__ ((__strub__ (3)))
+void __attribute__ ((__strub__ ("callable")))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0);
 }
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   void *args = __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
index 379a54b73b7..bab2dd10f5b 100644
--- a/gcc/testsuite/c-c++-common/strub-apply2.c
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 extern void __attribute__ ((__strub__))
 apply_function (void *args);
@@ -10,3 +10,9 @@ apply_args (int i, int j, double d) /* { dg-error "selected" } */
   void *args = __builtin_apply_args (); /* { dg-message "does not support" } */
   apply_function (args);
 }
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After the error and sorry messages in strubm, build_ssa_passes are gated out,
+   so we don't get the PROP_ssa property that targetclone requires.  When
+   checking is not enabled at configure time, we admit to being confused and
+   bail out, but when checking is eneabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
index 9b4786be698..85f9d090fda 100644
--- a/gcc/testsuite/c-c++-common/strub-apply3.c
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -1,8 +1,14 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 void __attribute__ ((__strub__))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */
 }
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
index 409f747743e..15ffaa031b8 100644
--- a/gcc/testsuite/c-c++-common/strub-apply4.c
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+/* { dg-options "-O2 -fstrub=strict -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
@@ -18,4 +18,4 @@ void f() {
   apply_args (1, 2, 3);
 }
 
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 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 d964b07ae5d..b70843b4215 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -22,9 +22,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
index 530eee36d06..97a3988a6b9 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls2.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls2.c
@@ -17,7 +17,7 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-default1.c b/gcc/testsuite/c-c++-common/strub-default1.c
deleted file mode 100644
index 79d00cedb9a..00000000000
--- a/gcc/testsuite/c-c++-common/strub-default1.c
+++ /dev/null
@@ -1,42 +0,0 @@
-/* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
-
-static int __attribute__ ((__strub__)) var;
-
-/* 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 inline void
-__attribute__ ((__always_inline__))
-h() {
-  var++;
-}
-
-/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
-   the viability of at-calls strubbing.  Though internally a strub context, its
-   interface is not strub-enabled, so it's not callable from within strub
-   contexts.  */
-static inline void
-g() {
-  var--;
-  h();
-}
-
-/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
-   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
-void
-f() {
-  var++;
-  g();  /* { dg-error "calling non-.strub." } */
-}
-
-/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-4\[)\]" 1 "strubm" } } */
-/* { 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 "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
index 7b04eea35d9..3d73431b3dc 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O1" } */
+/* { dg-options "-fstrub=strict -O1" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O1.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
index 67d96419a5e..fddf3c745e7 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O2" } */
+/* { dg-options "-fstrub=strict -O2" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O2.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
index 34828d2711e..2bc384ee8d1 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O3" } */
+/* { dg-options "-fstrub=strict -O3" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -O3.  */
@@ -10,7 +10,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -18,7 +18,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -32,7 +32,7 @@ leak_string (void)
   return 0;
 }
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 int
 look_for_string (char *e)
 {
@@ -56,14 +56,14 @@ look_for_string (char *e)
   return 0;
 }
 
-static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+static __attribute__ ((__strub__ ("at-calls"), __noinline__, __noclone__))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 deferred_at_calls ()
 {
@@ -73,7 +73,7 @@ deferred_at_calls ()
   return ret;
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 deferred_internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
index b273660aea1..fbaf85fe0fa 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -Os" } */
+/* { dg-options "-fstrub=strict -Os" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -Os.  */
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
index a74658c9ac9..e9d7b7b9ee0 100644
--- a/gcc/testsuite/c-c++-common/strub-internal1.c
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -23,9 +23,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
index a6e69357b23..8b8e15a51c7 100644
--- a/gcc/testsuite/c-c++-common/strub-internal2.c
+++ b/gcc/testsuite/c-c++-common/strub-internal2.c
@@ -14,8 +14,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
index 7e22a266ad9..0a4a7539d34 100644
--- a/gcc/testsuite/c-c++-common/strub-parms1.c
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -16,7 +16,7 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 large_byref_arg (struct large_arg la)
 {
 }
@@ -24,7 +24,7 @@ large_byref_arg (struct large_arg la)
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*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)))
+void __attribute__ ((__strub__ ("internal")))
 std_arg (int i, ...)
 {
   va_list vl;
@@ -38,7 +38,7 @@ std_arg (int i, ...)
 /* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
index 2d8036c0fbc..147171d96d5 100644
--- a/gcc/testsuite/c-c++-common/strub-parms2.c
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -15,14 +15,14 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 large_byref_arg (struct large_arg la)
 {
 }
 
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 std_arg (int i, ...)
 {
   va_list vl;
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
index f64361f1235..4e92682895a 100644
--- a/gcc/testsuite/c-c++-common/strub-parms3.c
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
new file mode 100644
index 00000000000..e2f9d8aebca
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  Without the error,
+   inlining takes place.  */
+
+#include "strub-strict1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
new file mode 100644
index 00000000000..98474435d2e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  */
+
+#include "strub-strict2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
index a4226ce0119..1de15342595 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fexceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fexceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
index 3bab553478b..f9209c81900 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
index c89cc7c2c47..bed1dcfb54a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
index b869fafb691..6bf0071f52b 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
index 1d3dd2f2c2c..4732f515bf7 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
index 4dd4281b03b..8d6424c479a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-strict1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
new file mode 100644
index 00000000000..7f5247cfaf0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-strict1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+static int __attribute__ ((__strub__)) var;
+
+/* 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 inline void
+__attribute__ ((__always_inline__))
+h() {
+  var++;
+}
+
+/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
+   the viability of at-calls strubbing.  Though internally a strub context, its
+   interface is not strub-enabled, so it's not callable from within strub
+   contexts.  */
+static inline void
+g() {
+  var--;
+  h();
+}
+
+/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
+   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
+void
+f() {
+  var++;
+  g();  /* { dg-error "calling non-.strub." } */
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-default2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
similarity index 54%
rename from gcc/testsuite/c-c++-common/strub-default2.c
rename to gcc/testsuite/c-c++-common/strub-strict2.c
index 487253e9227..17463b6e554 100644
--- a/gcc/testsuite/c-c++-common/strub-default2.c
+++ b/gcc/testsuite/c-c++-common/strub-strict2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
 
 static int __attribute__ ((__strub__)) var;
 
@@ -22,8 +22,14 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
index 0840dddd136..e48e0610e07 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 #include "strub-tail-O2.c"
 
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
index 9330d6ff4c1..87cda7ab21b 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.
    Tail calls are short-circuited at -O2+.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
index 5b33ff1f530..b5e45ab0525 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
index 38935e3270b..96aa7fe4b07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -1,39 +1,39 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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);
+extern int __attribute__ ((__strub__ ("callable"))) xcallable (void);
+extern int __attribute__ ((__strub__ ("internal"))) xinternal (void);
+extern int __attribute__ ((__strub__ ("at-calls"))) xat_calls (void);
+extern int __attribute__ ((__strub__ ("disabled"))) 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__ ("callable"))) callable (void);
+int __attribute__ ((__strub__ ("internal"))) internal (void);
+int __attribute__ ((__strub__ ("at-calls"))) at_calls (void);
+int __attribute__ ((__strub__ ("disabled"))) disabled (void);
 
 int __attribute__ ((__strub__)) var;
 int var_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 icallable (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 iinternal (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 idisabled (void);
 static inline int __attribute__ ((__always_inline__))
 ivar_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 i_callable (void) { return 0; }
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 i_internal (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 i_at_calls (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 i_disabled (void) { return 0; }
 static inline int __attribute__ ((__always_inline__))
 i_var_user (void) { return var; }
@@ -70,7 +70,7 @@ i_var_user (void) { return var; }
 
 /* Not a strub context, so it can call anything.
    Explicitly declared as callable even from within strub contexts.  */
-int __attribute__ ((__strub__ (3)))
+int __attribute__ ((__strub__ ("callable")))
 callable (void) {
   int ret = 0;
 
@@ -88,7 +88,7 @@ callable (void) {
 
 /* 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)))
+int __attribute__ ((__strub__ ("internal")))
 internal (void) {
   int ret = var;
 
@@ -109,7 +109,7 @@ internal (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (1)))
+int __attribute__ ((__strub__ ("at-calls")))
 at_calls (void) {
   int ret = var;
 
@@ -130,7 +130,7 @@ at_calls (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (0)))
+int __attribute__ ((__strub__ ("disabled")))
 disabled () {
   int ret = 0;
 
@@ -205,7 +205,7 @@ iinternal (void) {
   return ret;
 }
 
-int
+int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void) {
   int ret = var;
 
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
index 100fb0c59a9..2857195706e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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,
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
index 9e818ac9748..98a92bc9eac 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
index d40e8aa45cb..5511a6e1e71 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __const__))
 f() {
   return 0;
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
index d4cbdaf10f3..47ee927964d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -1,12 +1,12 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__
 __attribute__ ((__const__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data1.c b/gcc/testsuite/c-c++-common/torture/strub-data1.c
index 62a03891ab6..7c27a2a1a6d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointed-to data enables strubbing if accessed.  */
 int __attribute__ ((__strub__)) var;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data2.c b/gcc/testsuite/c-c++-common/torture/strub-data2.c
index 9b7df13a280..e66d903780a 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointer itself is a strub variable, enabling internal strubbing when
    its value is used.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data3.c b/gcc/testsuite/c-c++-common/torture/strub-data3.c
index 515706caa32..5e08e0e58c6 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data4.c b/gcc/testsuite/c-c++-common/torture/strub-data4.c
index 0ec9e35429f..a818e7a38bb 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
index 69f3d65ed44..90e8ad51c1e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -Werror" } */
+/* { dg-options "-fstrub=strict -Werror" } */
 
 typedef int __attribute__ ((__strub__)) strub_int;
 strub_int *ptr;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
index b8adf8009e8..c165f312f16 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype ();
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
index 5b2c35ad6a7..69fcff8d376 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
index 5ee50456dc9..ff006224909 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int, ...);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
new file mode 100644
index 00000000000..b416ba06b59
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+}
+
+void
+bat (void)
+{
+  /* Not allowed, not a strub context.  */
+  inl_int_ali (); /* { dg-error "context" } */
+}
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
new file mode 100644
index 00000000000..f9a6b4a16fa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=all" } */
+
+#include "strub-inlinable1.c"
+
+/* With -fstrub=all, the caller becomes a strub context, so the strub-inlinable
+   callee is not rejected.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
new file mode 100644
index 00000000000..3fc5a5c4c4d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+typedef void ft (void);
+typedef void ft2 (int, int);
+extern ft __attribute__ ((__strub__)) fnac;
+
+ft * f (void) {
+  return fnac; /* { dg-warning "incompatible" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
new file mode 100644
index 00000000000..7f01cb3372f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
@@ -0,0 +1,19 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic" } */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+
+  d_p = bac; /* { dg-warning "not quite compatible" } */
+  c_p = bad; /* { dg-warning "not quite compatible" } */
+  c_p = bar; /* { dg-warning "not quite compatible" } */
+  c_p = bal; /* { 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
index cb223da6efc..a262a086837 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
index 67d1434b1f8..4c4bd50c209 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
index 59f02ea901f..ce195c6b1f1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -1,10 +1,10 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __pure__))
 f() {
   static int i; /* Stop it from being detected as const.  */
   return i;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
index 973e909217d..75cd54ccb5b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
 __attribute__ ((__pure__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
index a4077c35a60..c596066a045 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -14,7 +14,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -59,14 +59,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
index 94e4156ea73..1fdba214a07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
index 0ca74beb59d..afbc2cc9ab4 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
 /* Check that a non-strub function leaves a string behind in the stack, and that
@@ -7,7 +7,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
index 4ab11c0682e..5300f1d330b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -85,14 +85,14 @@ intermediate ()
   return ret;
 }
 
-static inline __attribute__ ((__strub__ (2)))
+static inline __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
   return intermediate ();
 }
 
-int __attribute__ ((strub (0)))
+int __attribute__ ((__strub__ ("disabled")))
 main ()
 {
   if (look_for_string (internal ()))
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4d.c b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
index e4f7445607c..08de3f1c3b1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4d.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -1,7 +1,7 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
-#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ (1)))
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
 
 #include "strub-run4.c"
diff --git a/gcc/testsuite/g++.dg/torture/strub-init1.C b/gcc/testsuite/g++.dg/torture/strub-init1.C
index e51ae802be4..c226ab10ff6 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init1.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init2.C b/gcc/testsuite/g++.dg/torture/strub-init2.C
index edcb7bf8ad2..a7911f1fa72 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init2.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init3.C b/gcc/testsuite/g++.dg/torture/strub-init3.C
index bacf823ca4e..6ebebcd01e8 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init3.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
index 697ac9de764..2d6f6394993 100644
--- a/gcc/testsuite/gnat.dg/strub_attr.adb
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -1,5 +1,5 @@
 --  { dg-do compile }
---  { dg-options "-fstrub=default -fdump-ipa-strubm" }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
 
 package body Strub_Attr is
    E : exception;
diff --git a/libgcc/strub.c b/libgcc/strub.c
index fd6e27556e4..7c58deee1e6 100644
--- a/libgcc/strub.c
+++ b/libgcc/strub.c
@@ -36,7 +36,7 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 # define TOPS <
 #endif
 
-#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ ("callable")))
 
 /* Enter a stack scrubbing context, initializing the watermark to the caller's
    stack address.  */


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

* [gcc(refs/users/aoliva/heads/strub)] -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests
@ 2021-09-05  2:50 Alexandre Oliva
  0 siblings, 0 replies; 13+ messages in thread
From: Alexandre Oliva @ 2021-09-05  2:50 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:1bfb05cec97db89d1a0bb51ff65a1a5484a9d88c

commit 1bfb05cec97db89d1a0bb51ff65a1a5484a9d88c
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Thu Sep 2 23:15:36 2021 -0300

    -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests

Diff:
---
 .../doc/gnat_rm/security_hardening_features.rst    |  44 +-
 gcc/ada/gcc-interface/utils.c                      |  95 ++-
 gcc/attribs.c                                      |  14 +-
 gcc/c-family/c-attribs.c                           |  84 ++-
 gcc/common.opt                                     |  27 +-
 gcc/doc/extend.texi                                | 332 +++++++++--
 gcc/doc/invoke.texi                                |  76 ++-
 gcc/ipa-inline.c                                   |   2 +-
 gcc/ipa-strub.c                                    | 649 ++++++++++++++++-----
 gcc/ipa-strub.h                                    |  20 +-
 gcc/testsuite/c-c++-common/strub-O0.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O1.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2fni.c           |   2 +-
 gcc/testsuite/c-c++-common/strub-O3.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O3fni.c           |   2 +-
 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            |  12 +-
 gcc/testsuite/c-c++-common/strub-all2.c            |   6 +-
 gcc/testsuite/c-c++-common/strub-apply1.c          |   6 +-
 gcc/testsuite/c-c++-common/strub-apply2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-apply3.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-apply4.c          |   4 +-
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-at-calls2.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-default1.c        |  42 --
 gcc/testsuite/c-c++-common/strub-defer-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |  14 +-
 gcc/testsuite/c-c++-common/strub-defer-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-internal1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-internal2.c       |   6 +-
 gcc/testsuite/c-c++-common/strub-parms1.c          |  10 +-
 gcc/testsuite/c-c++-common/strub-parms2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-parms3.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-relaxed1.c        |  18 +
 gcc/testsuite/c-c++-common/strub-relaxed2.c        |  14 +
 gcc/testsuite/c-c++-common/strub-short-O0-exc.c    |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O0.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O3.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-strict1.c         |  48 ++
 .../{strub-default2.c => strub-strict2.c}          |  14 +-
 gcc/testsuite/c-c++-common/strub-tail-O1.c         |   2 +-
 gcc/testsuite/c-c++-common/strub-tail-O2.c         |   2 +-
 .../c-c++-common/torture/strub-callable1.c         |   2 +-
 .../c-c++-common/torture/strub-callable2.c         |  44 +-
 gcc/testsuite/c-c++-common/torture/strub-const1.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const2.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const3.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-const4.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-data1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data3.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data4.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |   2 +-
 .../c-c++-common/torture/strub-indcall1.c          |   2 +-
 .../c-c++-common/torture/strub-indcall2.c          |   2 +-
 .../c-c++-common/torture/strub-indcall3.c          |   2 +-
 .../c-c++-common/torture/strub-inlinable1.c        |  22 +
 .../c-c++-common/torture/strub-inlinable2.c        |   7 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |  10 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |  19 +
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure3.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-pure4.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run1.c    |  10 +-
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run4.c    |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run4d.c   |   4 +-
 gcc/testsuite/g++.dg/torture/strub-init1.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init2.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init3.C         |   2 +-
 gcc/testsuite/gnat.dg/strub_attr.adb               |   2 +-
 libgcc/strub.c                                     |   2 +-
 80 files changed, 1258 insertions(+), 551 deletions(-)

diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 309291f0341..fb5d14fed40 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -19,6 +19,18 @@ It can be enabled with the *-fzero-call-used-regs* command line
 option, to affect all subprograms in a compilation, and with a
 :samp:`Machine_Attribute` pragma, to affect only specific subprograms.
 
+.. code-block:: ada
+
+     procedure Foo;
+     pragma Machine_Attribute (Foo, "zero_call_used_regs", "used");
+     --  Before returning, Foo scrubs only call-clobbered registers
+     --  that it uses itself.
+
+     function Bar return Integer;
+     pragma Machine_Attribute (Bar, "zero_call_used_regs", "all");
+     --  Before returning, Bar scrubs all call-clobbered registers.
+
+
 For usage and more details on the command line option, and on the
 ``zero_call_used_regs`` attribute, see :title:`Using the GNU Compiler
 Collection (GCC)`.
@@ -31,13 +43,31 @@ Stack Scrubbing
 
 GNAT can generate code to zero-out stack frames used by subprograms.
 
-It can be activated with the *-fstrub* command line option, to affect
-all (viable) subprograms in a compilation, and with a
-:samp:`Machine_Attribute` pragma.
+It can be activated with the :samp:`Machine_Attribute` pragma, on
+specific subprograms, variables, and types.
 
-For usage and more details on the command line option, and on the
-``strub`` attribute, see :title:`Using the GNU Compiler Collection
-(GCC)`.
+.. code-block:: ada
+
+     function Foo returns Integer;
+     pragma Machine_Attribute (Foo, "strub");
+     --  Foo and its callers are modified so as to scrub the stack
+     --  space used by Foo after it returns.
+
+     procedure Bar;
+     pragma Machine_Attribute (Bar, "strub", "internal");
+     --  Bar is turned into a wrapper for its original body,
+     --  and they scrub the stack used by the original body.
+
+     Var : Integer;
+     pragma Machine_Attribute (Var, "strub");
+     --  Reading from Var in a subprogram enables stack scrubbing
+     --  of the stack space used by the subprogram.
+
+
+There are also *-fstrub* command line options to control default
+settings.  For usage and more details on the command line option, and
+on the ``strub`` attribute, see :title:`Using the GNU Compiler
+Collection (GCC)`.
 
 Note that Ada secondary stacks are not scrubbed.  The restriction
 ``No_Secondary_Stack`` avoids their use, and thus their accidental
@@ -49,4 +79,4 @@ is not fully reflected in Ada types, ``Access`` attributes, renaming
 and overriding.  Every access type, renaming, and overriding and
 overridden dispatching operations that may refer to an entity with an
 attribute-modified interface must be annotated with the same
-interface-modifying attribute.
+interface-modifying attribute, or with an interface-compatible one.
diff --git a/gcc/ada/gcc-interface/utils.c b/gcc/ada/gcc-interface/utils.c
index 6b20ddc93c9..1abf593d76b 100644
--- a/gcc/ada/gcc-interface/utils.c
+++ b/gcc/ada/gcc-interface/utils.c
@@ -39,6 +39,7 @@
 #include "varasm.h"
 #include "toplev.h"
 #include "opts.h"
+#include "ipa-strub.h"
 #include "output.h"
 #include "debug.h"
 #include "convert.h"
@@ -6611,6 +6612,7 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
+  bool compat_type = false;
   tree orig_fnptr_type = NULL_TREE;
   tree orig_args = args;
 
@@ -6624,68 +6626,36 @@ handle_strub_attribute (tree *node, tree name,
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      /* pragma Machine_Attribute turns string arguments into identifiers.
-	 Reverse it.  */
-      if (TREE_CODE (TREE_VALUE (args)) == IDENTIFIER_NODE)
+      switch (strub_validate_parm (TREE_VALUE (args)))
 	{
-	  gcc_checking_assert (IDENTIFIER_POINTER (TREE_VALUE (args))
-			       [IDENTIFIER_LENGTH (TREE_VALUE (args))] == 0);
-	  TREE_VALUE (args) = build_string
-	    (IDENTIFIER_LENGTH (TREE_VALUE (args)) + 1,
-	     IDENTIFIER_POINTER (TREE_VALUE (args)));
-	}
-
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
-	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
-
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
+	case 1:
+	  compat_type = true;
+	  /* Fall through.  */
 
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
+	  compat_type = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	  compat_type = true;
+	  /* Fall through.  */
+
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
@@ -6699,23 +6669,30 @@ handle_strub_attribute (tree *node, tree name,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   if (!*no_add_attrs)
     {
       *no_add_attrs = true;
       if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
 	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
+	{
+	  if (compat_type)
+	    *node = build_variant_type_copy (*node);
+	  else
+	    *node = build_distinct_type_copy (*node);
+	}
       TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
 					   TYPE_ATTRIBUTES (*node));
 
       if (orig_fnptr_type)
 	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
+	  tree fnptr_type = ((flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
+			     ? orig_fnptr_type
+			     : compat_type
+			     ? build_variant_type_copy (orig_fnptr_type)
+			     : build_distinct_type_copy (orig_fnptr_type));
 	  TREE_TYPE (fnptr_type) = *node;
 	  *node = fnptr_type;
 	}
diff --git a/gcc/attribs.c b/gcc/attribs.c
index 0d22c20a35e..acac634afb5 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "attribs.h"
 #include "fold-const.h"
+#include "ipa-strub.h"
 #include "stor-layout.h"
 #include "langhooks.h"
 #include "plugin.h"
@@ -1353,9 +1354,20 @@ comp_type_attributes (const_tree type1, const_tree type2)
   if ((lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type1)) != NULL)
       ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) != NULL))
     return 0;
+  int strub_ret = strub_comptypes (CONST_CAST_TREE (type1),
+				   CONST_CAST_TREE (type2));
+  if (strub_ret == 0)
+    return strub_ret;
   /* As some type combinations - like default calling-convention - might
      be compatible, we have to call the target hook to get the final result.  */
-  return targetm.comp_type_attributes (type1, type2);
+  int target_ret = targetm.comp_type_attributes (type1, type2);
+  if (target_ret == 0)
+    return target_ret;
+  if (strub_ret == 2 || target_ret == 2)
+    return 2;
+  if (strub_ret == 1 && target_ret == 1)
+    return 1;
+  gcc_unreachable ();
 }
 
 /* PREDICATE acts as a function of type:
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index dc4a4d4d200..2762dcdfcfe 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "common/common-target.h"
 #include "langhooks.h"
 #include "tree-inline.h"
+#include "ipa-strub.h"
 #include "toplev.h"
 #include "tree-iterator.h"
 #include "opts.h"
@@ -1306,6 +1307,7 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
+  bool compat_type = false;
   tree orig_fnptr_type = NULL_TREE;
   tree orig_args = args;
 
@@ -1319,57 +1321,36 @@ handle_strub_attribute (tree *node, tree name,
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
+      switch (strub_validate_parm (TREE_VALUE (args)))
 	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
+	case 1:
+	  compat_type = true;
+	  /* Fall through.  */
 
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
-
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
+	  compat_type = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	  compat_type = true;
+	  /* Fall through.  */
+
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
@@ -1383,23 +1364,30 @@ handle_strub_attribute (tree *node, tree name,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   if (!*no_add_attrs)
     {
       *no_add_attrs = true;
       if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
 	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
+	{
+	  if (compat_type)
+	    *node = build_variant_type_copy (*node);
+	  else
+	    *node = build_distinct_type_copy (*node);
+	}
       TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
 					   TYPE_ATTRIBUTES (*node));
 
       if (orig_fnptr_type)
 	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
+	  tree fnptr_type = ((flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
+			     ? orig_fnptr_type
+			     : compat_type
+			     ? build_variant_type_copy (orig_fnptr_type)
+			     : build_distinct_type_copy (orig_fnptr_type));
 	  TREE_TYPE (fnptr_type) = *node;
 	  *node = fnptr_type;
 	}
diff --git a/gcc/common.opt b/gcc/common.opt
index 71e0c971344..ef018ebf735 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,13 +2687,22 @@ fstrict-overflow
 Common
 Treat signed overflow as undefined.  Negated as -fwrapv -fwrapv-pointer.
 
-; If any strub-enabling attribute is seen when the default value is
-; selected, it's bumped up to -1.  The scrub mode gate function will
-; then bump -2 to 0 if no strub-enabling attribute is seen.  This
-; minimizes the strub overhead.
-fstrub=default
-Common RejectNegative Var(flag_strub, -2) Init(-2)
-Enable stack scrub as requested through attributes.
+fstrub=disable
+Common RejectNegative Var(flag_strub, 0)
+Disable stack scrub entirely, disregarding strub attributes.
+
+fstrub=strict
+Common RejectNegative Var(flag_strub, -4)
+Enable stack scrub as per attributes, with strict call checking.
+
+; If any strub-enabling attribute is seen when the default or strict
+; initializer values are in effect, flag_strub is bumped up by 2.  The
+; scrub mode gate function will then bump these initializer values to
+; 0 if no strub-enabling attribute is seen.  This minimizes the strub
+; overhead.
+fstrub=relaxed
+Common RejectNegative Var(flag_strub, -3) Init(-3)
+Restore default strub mode: as per attributes, with relaxed checking.
 
 fstrub=all
 Common RejectNegative Var(flag_strub, 3)
@@ -2707,10 +2716,6 @@ fstrub=internal
 Common RejectNegative Var(flag_strub, 2)
 Enable internal stack scrubbing for all viable functions.
 
-fstrub=disable
-Common RejectNegative Var(flag_strub, 0)
-Disable stack scrub entirely, disregarding strub attributes.
-
 fsync-libcalls
 Common Var(flag_sync_libcalls) Init(1)
 Implement __atomic operations via libcalls to legacy __sync functions.
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 1fcf60a3875..d85e695d85c 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -77,7 +77,7 @@ extensions, accepted by GCC in C90 mode and in C++.
 * Function Names::      Printable strings which are the name of the current
                         function.
 * Return Address::      Getting the return or frame address of a function.
-* Stack Scrubbing::     Stack scrubbing interfaces.
+* Stack Scrubbing::     Stack scrubbing internal interfaces.
 * Vector Extensions::   Using vector instructions through built-in functions.
 * Offsetof::            Special syntax for implementing @code{offsetof}.
 * __sync Builtins::     Legacy built-in functions for atomic memory access.
@@ -8729,51 +8729,259 @@ pid_t wait (wait_status_ptr_t p)
 @item strub
 @cindex @code{strub} type attribute
 This attribute defines stack-scrubbing properties of functions and
-variables.  When applied to function types, it takes an optional
-argument.  When applied to a pointer-to-function type, it gets
-propagated to the function type if the optional argument is given.
-
-A function whose type is annotated with @code{at-calls} @code{strub}
-mode (@code{strub("at-calls")}, @code{strub(1)}, or @code{strub})
-undergoes interface changes.  Its callers are adjusted to match the
-changes, and to automatically scrub (overwrite with zeros) the stack
-space used by the function.
-
-A function whose type indicates @code{internal} @code{strub} mode
-(@code{strub("internal")} or @code{strub(2)}) retains an unmodified
-interface, but may be turned into a wrapper that calls the wrapped body
-using a custom interface.  The wrapper then scrubs the stack space used
-by the wrapped body.
-
-An automatically-allocated variable whose type carries the @code{strub}
-attribute causes the enclosing function to have @code{strub} enabled.
-
-A statically-allocated variable whose type carries the @code{strub}
-attribute causes functions that @emph{read} it with that type to have
-@code{strub} enabled.  Reading from data referenced by pointers to such
-types has the same effect.  Note: The attribute does not carry over from
-a composite type to the types of its components, so the intended effect
-may not be obtained with non-scalar types.
-
-A @code{strub} context is the body of a function that has strub enabled,
-be it explicitly, by @code{at-calls} or @code{internal} mode, or
-implicitly, by reading from a @code{strub}-marked data type.
+variables.  Being a type attribute, it attaches to types, even when
+specified in function and variable declarations.  When applied to
+function types, it takes an optional string argument.  When applied to a
+pointer-to-function type, if the optional argument is given, it gets
+propagated to the function type.
+
+@smallexample
+/* A strub variable.  */
+int __attribute__ ((strub)) var;
+/* A strub variable that happens to be a pointer.  */
+__attribute__ ((strub)) int *strub_ptr_to_int;
+/* A pointer type that may point to a strub variable.  */
+typedef int __attribute__ ((strub)) *ptr_to_strub_int_type;
+
+/* A declaration of a strub function.  */
+extern int __attribute__ ((strub)) foo (void);
+/* A pointer to that strub function.  */
+int __attribute__ ((strub ("at-calls"))) (*ptr_to_strub_fn)(void) = foo;
+@end smallexample
+
+A function associated with @code{at-calls} @code{strub} mode
+(@code{strub("at-calls")}, or just @code{strub}) undergoes interface
+changes.  Its callers are adjusted to match the changes, and to scrub
+(overwrite with zeros) the stack space used by the called function after
+it returns.  The interface change makes the function type incompatible
+with an unadorned but otherwise equivalent type, so @emph{every}
+declaration and every type that may be used to call the function must be
+associated with this strub mode.
+
+A function associated with @code{internal} @code{strub} mode
+(@code{strub("internal")}) retains an unmodified, type-compatible
+interface, but it may be turned into a wrapper that calls the wrapped
+body using a custom interface.  The wrapper then scrubs the stack space
+used by the wrapped body.  Though the wrapped body has its stack space
+scrubbed, the wrapper does not, so arguments and return values may
+remain unscrubbed even when such a function is called by another
+function that enables @code{strub}.  This is why, when compiling with
+@option{-fstrub=strict}, a @code{strub} context is not allowed to call
+@code{internal} @code{strub} functions.
+
+@smallexample
+/* A declaration of an internal-strub function.  */
+extern int __attribute__ ((strub ("internal"))) bar (void);
+
+int __attribute__ ((strub))
+baz (void)
+@{
+  /* Ok, foo was declared above as an at-calls strub function.  */
+  foo ();
+  /* Not allowed in strict mode, otherwise allowed.  */
+  bar ();
+@}
+@end smallexample
+
+An automatically-allocated variable associated with the @code{strub}
+attribute causes the (immediately) enclosing function to have
+@code{strub} enabled.
+
+A statically-allocated variable associated with the @code{strub}
+attribute causes functions that @emph{read} it, through its @code{strub}
+data type, to have @code{strub} enabled.  Reading data by dereferencing
+a pointer to a @code{strub} data type has the same effect.  Note: The
+attribute does not carry over from a composite type to the types of its
+components, so the intended effect may not be obtained with non-scalar
+types.
+
+When selecting a @code{strub}-enabled mode for a function that is not
+explicitly associated with one, because of @code{strub} variables or
+data pointers, the function must satisfy @code{internal} mode viability
+requirements (see below), even when @code{at-calls} mode is also viable
+and, being more efficient, ends up selected as an optimization.
+
+@smallexample
+/* zapme is implicitly strub-enabled because of strub variables.
+   Optimization may change its strub mode, but not the requirements.  */
+static int
+zapme (int i)
+@{
+  /* A local strub variable enables strub.  */
+  int __attribute__ ((strub)) lvar;
+  /* Reading strub data through a pointer-to-strub enables strub.  */
+  lvar = * (ptr_to_strub_int_type) &i;
+  /* Writing to a global strub variable does not enable strub.  */
+  var = lvar;
+  /* Reading from a global strub variable enables strub.  */
+  return var;
+@}
+@end smallexample
+
+A @code{strub} context is the body (as opposed to the interface) of a
+function that has @code{strub} enabled, be it explicitly, by
+@code{at-calls} or @code{internal} mode, or implicitly, due to
+@code{strub} variables or command-line options.
 
 A function of a type associated with the @code{disabled} @code{strub}
-mode (@code{strub("disabled")}, @code{strub(0)}, or no @code{strub} mode
-specified) will not have its own stack space scrubbed, and it cannot be
-called from @code{strub} contexts.
+mode (@code{strub("disabled")} will not have its own stack space
+scrubbed.  Such functions @emph{cannot} be called from within
+@code{strub} contexts.
+
+In order to enable a function to be called from within @code{strub}
+contexts without having its stack space scrubbed, associate it with the
+@code{callable} @code{strub} mode (@code{strub("callable")}).
+
+When a function is not assigned a @code{strub} mode, explicitly or
+implicitly, the mode defaults to @code{callable}, except when compiling
+with @option{-fstrub=strict}, that causes @code{strub} mode to default
+to @code{disabled}.
+
+@example
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+ /* Implicitly disabled with -fstrub=strict, otherwise callable.  */
+extern int bah (void);
+
+int __attribute__ ((strub))
+bal (void)
+@{
+  /* Not allowed, bad is not strub-callable.  */
+  bad ();
+  /* Ok, bac is strub-callable.  */
+  bac ();
+  /* Not allowed with -fstrub=strict, otherwise allowed.  */
+  bah ();
+@}
+@end example
 
-A function that does not have @code{strub} enabled can only be called
-from within @code{strub} contexts through a function type marked with
-the @code{callable} @code{strub} mode (@code{strub("callable")} or
-@code{strub(3)}).
+Function types marked @code{callable} and @code{disabled} are not
+mutually compatible types, but the underlying interfaces are compatible,
+so it is safe to convert pointers between them, and to use such pointers
+or alternate declarations to call them.  Interfaces are also
+interchangeable between them and @code{internal} (but not
+@code{at-calls}!), but adding @code{internal} to a pointer type will not
+cause the pointed-to function to perform stack scrubbing.
+
+@example
+void __attribute__ ((strub))
+bap (void)
+@{
+  /* Assign a callable function to pointer-to-disabled.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bac;
+  /* Not allowed: calls disabled type in a strub context.  */
+  d_p ();
+
+  /* Assign a disabled function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bad;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an internal function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  c_p = bar;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an at-calls function to pointer-to-callable.
+     Flaggged as incompatible.  */
+  c_p = bal;
+  /* The call through an interface-incompatible type will not use the
+     modified interface expected by the at-calls function, so it is
+     likely to misbehave at runtime.  */
+  c_p ();
+@}
+@end example
 
 @code{Strub} contexts are never inlined into non-@code{strub} contexts.
-When an @code{internal}-strub function is split, the wrapper can often
-be inlined, but the wrapped body never is.  Functions marked as
-@code{always_inline}, even if explicitly assigned @code{internal} strub
-mode, will not undergo wrapping, so their body gets inlined.
+When an @code{internal}-strub function is split up, the wrapper can
+often be inlined, but the wrapped body @emph{never} is.  A function
+marked as @code{always_inline}, even if explicitly assigned
+@code{internal} strub mode, will not undergo wrapping, so its body gets
+inlined as required.
+
+@example
+inline int __attribute__ ((strub ("at-calls")))
+inl_atc (void)
+@{
+  /* This body may get inlined into strub contexts.  */
+@}
+
+inline int __attribute__ ((strub ("internal")))
+inl_int (void)
+@{
+  /* This body NEVER gets inlined, though its wrapper may.  */
+@}
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+@{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+@}
+
+void __attribute__ ((strub ("disabled")))
+bat (void)
+@{
+  /* Not allowed, cannot inline into a non-strub context.  */
+  inl_int_ali ();
+@}
+@end example
+
+@cindex strub eligibility and viability
+Some @option{-fstrub=*} command line options enable @code{strub} modes
+implicitly where viable.  A @code{strub} mode is only viable for a
+function if the function is eligible for that mode, and if other
+conditions, detailed below, are satisfied.  If it's not eligible for a
+mode, attempts to explicitly associate it with that mode are rejected
+with an error message.  If it is eligible, that mode may be assigned
+explicitly through this attribute, but implicit assignment through
+command-line options may involve additional viability requirements.
+
+A function is ineligible for @code{at-calls} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+@code{At-calls} @code{strub} mode, if not requested through the function
+type, is only viable for an eligible function if the function is not
+visible to other translation units, and if it doesn't have its address
+taken.
+
+@smallexample
+/* bar is eligible for at-calls strub mode,
+   but not viable for that mode because it is visible to other units.
+   It is eligible and viable for internal strub mode.  */
+void bav () @{@}
+
+/* setp is eligible for at-calls strub mode,
+   but not viable for that mode because its address is taken.
+   It is eligible and viable for internal strub mode.  */
+void setp (void) @{ static void (*p)(void); = setp; @}
+@end smallexample
+
+A function is ineligible for @code{internal} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, or if attribute
+@code{noipa} is present.  For an @code{always_inline} function, meeting
+these requirements is enough to make it eligible.  Any function that has
+attribute @code{noclone}, that uses such extensions as non-local labels,
+computed gotos, alternate variable argument passing interfaces,
+@code{__builtin_next_arg}, or @code{__builtin_return_address}, or that
+takes too many (about 64Ki) arguments is ineligible, unless it is
+@code{always_inline}.  For @code{internal} @code{strub} mode, all
+eligible functions are viable.
+
+@smallexample
+/* flop is not eligible, thus not viable, for at-calls strub mode.
+   Likewise for internal strub mode.  */
+__attribute__ ((noipa)) void flop (void) @{@}
+
+/* flip is eligible and viable for at-calls strub mode.
+   It would be ineligible for internal strub mode, because of noclone,
+   if it weren't for always_inline.  With always_inline, noclone is not
+   an obstacle, so it is also eligible and viable for internal strub mode.  */
+inline __attribute__ ((noclone, always_inline)) void flip (void) @{@}
+@end smallexample
 
 @item unused
 @cindex @code{unused} type attribute
@@ -11804,40 +12012,48 @@ This function returns the value of the stack pointer register.
 @end deftypefn
 
 @node Stack Scrubbing
-@section Stack scrubbing interfaces
+@section Stack scrubbing internal interfaces
 
 Stack scrubbing involves cooperation between a @code{strub} context,
 i.e., a function whose stack frame is to be zeroed-out, and its callers.
 The caller initializes a stack watermark, the @code{strub} context
-updates the watermark to reflect its stack use, and the caller zeroes it
-out once it regains control.
-
-Each of these steps relies on a different builtin function call.  The
-functions are available in libgcc but, depending on optimization levels,
-they are expanded internally, adjusted to account for inlining, or
-combined/deferred (e.g. passing the caller-supplied watermark on to
-callees, refraining from erasing stack areas that the caller will).
+updates the watermark according to its stack use, and the caller zeroes
+it out once it regains control, whether by the callee's returning or by
+an exception.
+
+Each of these steps is performed by a different builtin function call.
+Calls to these builtins are introduced automatically, in response to
+@code{strub} attributes and command-line options; they are not expected
+to be explicitly called by source code.
+
+The functions that implement the builtins are available in libgcc but,
+depending on optimization levels, they are expanded internally, adjusted
+to account for inlining, and sometimes combined/deferred (e.g. passing
+the caller-supplied watermark on to callees, refraining from erasing
+stack areas that the caller will) to enable tail calls and to optimize
+for code size.
 
 @deftypefn {Built-in Function} {void} __builtin___strub_enter (void **@var{wmptr})
 This function initializes a stack @var{watermark} variable with the
-current top of the stack.  This builtin function should be called before
-entering a @code{strub} context.  It remains as a function call if optimization
-is not enabled.
+current top of the stack.  A call to this builtin function is introduced
+before entering a @code{strub} context.  It remains as a function call
+if optimization is not enabled.
 @end deftypefn
 
 @deftypefn {Built-in Function} {void} __builtin___strub_update (void **@var{wmptr})
 This function updates a stack @var{watermark} variable with the current
-top of the stack, if it tops the previous watermark.  This builtin
-function should be called within a @code{strub} context whenever
+top of the stack, if it tops the previous watermark.  A call to this
+builtin function is inserted within @code{strub} contexts, whenever
 additional stack space may have been used.  It remains as a function
 call at optimization levels lower than 2.
 @end deftypefn
 
 @deftypefn {Built-in Function} {void} __builtin___strub_leave (void **@var{wmptr})
 This function overwrites the memory area between the current top of the
-stack, and the @var{watermark}ed address.  This builtin function should
-be called after leaving a @code{strub} context.  It remains as a
-function call at optimization levels lower than 3.
+stack, and the @var{watermark}ed address.  A call to this builtin
+function is inserted after leaving a @code{strub} context.  It remains
+as a function call at optimization levels lower than 3, and it is guarded by
+a condition at level 2.
 @end deftypefn
 
 @node Vector Extensions
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 838d9ff1198..afcb37914ac 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,7 +599,8 @@ Objective-C and Objective-C++ Dialects}.
 -fstack-protector-explicit  -fstack-check @gol
 -fstack-limit-register=@var{reg}  -fstack-limit-symbol=@var{sym} @gol
 -fno-stack-limit  -fsplit-stack @gol
--fstrub=default -fstrub=disable -fstrub=at-calls -fstrub=internal -fstrub=all @gol
+-fstrub=disable -fstrub=strict -fstrub=relaxed @gol
+-fstrub=all -fstrub=at-calls -fstrub=internal @gol
 -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
 -fvtv-counts  -fvtv-debug @gol
 -finstrument-functions @gol
@@ -15513,46 +15514,55 @@ without @option{-fsplit-stack} always has a large stack.  Support for
 this is implemented in the gold linker in GNU binutils release 2.21
 and later.
 
-@item -fstrub=default
-@opindex fstrub=default
-Restore the default stack scrub (@code{strub}) setting, namely,
-@code{strub} is only enabled as required by @code{strub} attributes
-associated with function or variable types.  This is only useful to
-override earlier @samp{-fstrub=*} options.
-
 @item -fstrub=disable
 @opindex -fstrub=disable
 Disable stack scrubbing entirely, ignoring any @code{strub} attributes.
+See @xref{Common Type Attributes}.
+
+@item -fstrub=strict
+@opindex fstrub=strict
+Functions default to @code{strub} mode @code{disabled}, and apply
+@option{strict}ly the restriction that only functions associated with
+@code{strub}-@code{callable} modes (@code{at-calls}, @code{callable} and
+@code{always_inline} @code{internal}) are @code{callable} by functions
+with @code{strub}-enabled modes (@code{at-calls} and @code{internal}).
+
+@item -fstrub=relaxed
+@opindex fstrub=relaxed
+Restore the default stack scrub (@code{strub}) setting, namely,
+@code{strub} is only enabled as required by @code{strub} attributes
+associated with function and data types.  @code{Relaxed} means that
+strub contexts are only prevented from calling functions explicitly
+associated with @code{strub} mode @code{disabled}.  This option is only
+useful to override other @option{-fstrub=*} options that precede it in
+the command line.
 
 @item -fstrub=at-calls
 @opindex fstrub=at-calls
-Enable @code{at-calls} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{at-calls} @code{strub} if a different @code{strub}
-mode is explicitly requested, if attribute @code{noipa} is present, or
-if it calls @code{__builtin_apply_args}.  @code{At-calls} @code{strub}
-mode, if not requested through the function type, is only viable for an
-eligible function if the function is not visible to other translation
-units, and it doesn't have its address taken.
+Enable @code{at-calls} @code{strub} mode where viable.  The primary use
+of this option is for testing.  It exercises the @code{strub} machinery
+in scenarios strictly local to a translation unit.  This @code{strub}
+mode modifies function interfaces, so any function that is visible to
+other translation units, or that has its address taken, will @emph{not}
+be affected by this option.  Optimization options may also affect
+viability.  See the @code{strub} attribute documentation for details on
+viability and eligibility requirements.
 
 @item -fstrub=internal
 @opindex fstrub=internal
-Enable @code{internal} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{internal} @code{strub} if a different @code{strub}
-mode is explicitly requested, or if attribute @code{noipa} is present.
-Non-@code{always_inline} functions also become ineligible if attribute
-@code{noclone} is present, if the function uses such features as user
-labels, non-default variable argument interfaces,
-@code{__builtin_next_arg}, or @code{__builtin_return_address}, or if
-they have too many (about 64Ki) arguments.  For @code{internal}
-@code{strub}, all eligible functions are viable.
+Enable @code{internal} @code{strub} mode where viable.  The primary use
+of this option is for testing.  This option is intended to exercise
+thoroughly parts of the @code{strub} machinery that implement the less
+efficient, but interface-preserving @code{strub} mode.  Functions that
+would not be affected by this option are quite uncommon.
 
 @item -fstrub=all
 @opindex fstrub=all
-Enable @code{strub} for all viable functions, and consider non-viable
-functions as @code{callable}.  When both strub modes are viable,
-@code{at-calls} is preferred.
+Enable some @code{strub} mode where viable.  When both strub modes are
+viable, @code{at-calls} is preferred.  @option{-fdump-ipa-strubm} adds
+function attributes that tell which mode was selected for each function.
+The primary use of this option is for testing, to exercise thoroughly
+the @code{strub} machinery.
 
 @item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 @opindex fvtable-verify
@@ -17427,6 +17437,14 @@ and inlining decisions.
 @item inline
 Dump after function inlining.
 
+@item strubm
+Dump after selecting @code{strub} modes, and recording the selections as
+function attributes.
+
+@item strub
+Dump @code{strub} transformations: interface changes, function wrapping,
+and insertion of builtin calls for stack scrubbing and watermarking.
+
 @end table
 
 Additionally, the options @option{-optimized}, @option{-missed},
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 7f4bc44d2bb..932e49dba8e 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -397,7 +397,7 @@ can_inline_edge_p (struct cgraph_edge *e, bool report,
       e->inline_failed = CIF_SANITIZE_ATTRIBUTE_MISMATCH;
       inlinable = false;
     }
-  if (!strub_inlinable_p (callee, caller))
+  if (!strub_inlinable_to_p (callee, caller))
     {
       e->inline_failed = CIF_UNSPECIFIED;
       inlinable = false;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
index cf7d811779e..67823433ae8 100644
--- a/gcc/ipa-strub.c
+++ b/gcc/ipa-strub.c
@@ -159,12 +159,16 @@ enum strub_mode {
 
 };
 
+/* Look up a strub attribute in TYPE, and return it.  */
+
 static tree
 get_strub_attr_from_type (tree type)
 {
   return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
 }
 
+/* Look up a strub attribute in DECL or in its type, and return it.  */
+
 static tree
 get_strub_attr_from_decl (tree decl)
 {
@@ -174,23 +178,159 @@ get_strub_attr_from_decl (tree decl)
   return get_strub_attr_from_type (TREE_TYPE (decl));
 }
 
-tree
+/* Define a function to cache identifier ID, to be used as a strub attribute
+   parameter for a strub mode named after NAME.  */
+#define DEF_STRUB_IDS(NAME, ID)				\
+static inline tree get_strub_mode_id_ ## NAME () {	\
+  static tree identifier = NULL_TREE;			\
+  if (!identifier)					\
+    identifier = get_identifier (ID);			\
+  return identifier;					\
+}
+/* Same as DEF_STRUB_IDS, but use the string expansion of NAME as ID.  */
+#define DEF_STRUB_ID(NAME)				\
+DEF_STRUB_IDS (NAME, #NAME)
+
+/* Define functions for each of the strub mode identifiers.
+   Expose dashes rather than underscores.  */
+DEF_STRUB_ID (disabled)
+DEF_STRUB_IDS (at_calls, "at-calls")
+DEF_STRUB_ID (internal)
+DEF_STRUB_ID (callable)
+DEF_STRUB_ID (wrapped)
+DEF_STRUB_ID (wrapper)
+DEF_STRUB_ID (inlinable)
+DEF_STRUB_IDS (at_calls_opt, "at-calls-opt")
+
+/* Release the temporary macro names.  */
+#undef DEF_STRUB_IDS
+#undef DEF_STRUB_ID
+
+/* Return the identifier corresponding to strub MODE.  */
+
+static tree
+get_strub_mode_attr_parm (enum strub_mode mode)
+{
+  switch (mode)
+    {
+    case STRUB_DISABLED:
+      return get_strub_mode_id_disabled ();
+
+    case STRUB_AT_CALLS:
+      return get_strub_mode_id_at_calls ();
+
+    case STRUB_INTERNAL:
+      return get_strub_mode_id_internal ();
+
+    case STRUB_CALLABLE:
+      return get_strub_mode_id_callable ();
+
+    case STRUB_WRAPPED:
+      return get_strub_mode_id_wrapped ();
+
+    case STRUB_WRAPPER:
+      return get_strub_mode_id_wrapper ();
+
+    case STRUB_INLINABLE:
+      return get_strub_mode_id_inlinable ();
+
+    case STRUB_AT_CALLS_OPT:
+      return get_strub_mode_id_at_calls_opt ();
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+/* Return the parmeters (TREE_VALUE) for a strub attribute of MODE.
+   We know we use a single parameter, so we bypass the creation of a
+   tree list.  */
+
+static tree
 get_strub_mode_attr_value (enum strub_mode mode)
 {
-  return tree_cons (NULL_TREE,
-		    build_int_cst (integer_type_node, (int)mode),
-		    NULL_TREE);
+  return get_strub_mode_attr_parm (mode);
+}
 
-#if 0 /* ??? use symbolic mode names with interned strings?  */
-  char *s = NULL;
+/* Determine whether ID is a well-formed strub mode-specifying attribute
+   parameter.  Only user-visible modes are accepted.
+
+   For unacceptable parms, return 0, otherwise a nonzero value as below.
+
+   If the parm enables strub, return positive, otherwise negative.
+
+   If the affected type must be a distinct, incompatible type,return an integer
+   of absolute value 2, otherwise 1.  */
+
+int
+strub_validate_parm (tree id)
+{
+  int ret;
 
-  switch (strub_mode)
+  if (!id)
+    return true;
+
+  const char *s = NULL;
+  size_t len = 0;
+
+  if (TREE_CODE (id) == STRING_CST)
     {
-      
+      s = TREE_STRING_POINTER (id);
+      len = TREE_STRING_LENGTH (id) - 1;
     }
-#endif
+  else if (TREE_CODE (id) == IDENTIFIER_NODE)
+    {
+      s = IDENTIFIER_POINTER (id);
+      len = IDENTIFIER_LENGTH (id);
+    }
+  else
+    return false;
+
+  enum strub_mode mode;
+
+  if (len != 8)
+    return false;
+
+  switch (s[0])
+    {
+    case 'd':
+      mode = STRUB_DISABLED;
+      ret = -1;
+      break;
+
+    case 'a':
+      mode = STRUB_AT_CALLS;
+      ret = 2;
+      break;
+
+    case 'i':
+      mode = STRUB_INTERNAL;
+      ret = 1;
+      break;
+
+    case 'c':
+      mode = STRUB_CALLABLE;
+      ret = -2;
+      break;
+
+    default:
+      /* Other parms are for internal use only.  */
+      return 0;
+    }
+
+  tree mode_id = get_strub_mode_attr_parm (mode);
+
+  if (TREE_CODE (id) == IDENTIFIER_NODE
+      ? id == mode_id
+      : strncmp (s, IDENTIFIER_POINTER (mode_id), len))
+    return 0;
+
+  return ret;
 }
 
+/* Return the strub mode from STRUB_ATTR.  VAR_P should be true if the attribute
+   is taken from a variable, rather than from a function, or a type thereof.  */
+
 static enum strub_mode
 get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
 {
@@ -200,28 +340,102 @@ get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
     {
       if (!TREE_VALUE (strub_attr))
 	mode = !var_p ? STRUB_AT_CALLS : STRUB_INTERNAL;
-      else if (TREE_CODE (TREE_VALUE (TREE_VALUE (strub_attr))) == INTEGER_CST)
-	mode = (enum strub_mode) tree_to_shwi (TREE_VALUE
-					       (TREE_VALUE (strub_attr)));
-      else /* Handlers convert symbolic mode names to INTEGER_CST.  */
-	gcc_unreachable ();
+      else
+	{
+	  gcc_checking_assert (!var_p);
+	  tree id = TREE_VALUE (strub_attr);
+	  if (TREE_CODE (id) == TREE_LIST)
+	    id = TREE_VALUE (id);
+	  const char *s = (TREE_CODE (id) == STRING_CST
+			   ? TREE_STRING_POINTER (id)
+			   : IDENTIFIER_POINTER (id));
+	  size_t len = (TREE_CODE (id) == STRING_CST
+			? TREE_STRING_LENGTH (id) - 1
+			: IDENTIFIER_LENGTH (id));
+
+	  switch (len)
+	    {
+	    case 7:
+	      switch (s[6])
+		{
+		case 'r':
+		  mode = STRUB_WRAPPER;
+		  break;
+
+		case 'd':
+		  mode = STRUB_WRAPPED;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 8:
+	      switch (s[0])
+		{
+		case 'd':
+		  mode = STRUB_DISABLED;
+		  break;
+
+		case 'a':
+		  mode = STRUB_AT_CALLS;
+		  break;
+
+		case 'i':
+		  mode = STRUB_INTERNAL;
+		  break;
+
+		case 'c':
+		  mode = STRUB_CALLABLE;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 9:
+	      mode = STRUB_INLINABLE;
+	      break;
+
+	    case 12:
+	      mode = STRUB_AT_CALLS_OPT;
+	      break;
+
+	    default:
+	      gcc_unreachable ();
+	    }
+
+	  gcc_checking_assert (TREE_CODE (id) == IDENTIFIER_NODE
+			       ? id == get_strub_mode_attr_parm (mode)
+			       : strncmp (IDENTIFIER_POINTER
+					  (get_strub_mode_attr_parm (mode)),
+					  s, len) == 0);
+	}
     }
 
   return mode;
 }
 
+/* Look up, decode and return the strub mode associated with FNDECL.  */
+
 static enum strub_mode
-get_strub_mode_from_decl (tree fndecl)
+get_strub_mode_from_fndecl (tree fndecl)
 {
   return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
 }
 
+/* Look up, decode and return the strub mode associated with NODE.  */
+
 static enum strub_mode
 get_strub_mode (cgraph_node *node)
 {
-  return get_strub_mode_from_decl (node->decl);
+  return get_strub_mode_from_fndecl (node->decl);
 }
 
+/* Look up, decode and return the strub mode associated with TYPE.  */
+
 static enum strub_mode
 get_strub_mode_from_type (tree type)
 {
@@ -231,12 +445,15 @@ get_strub_mode_from_type (tree type)
   if (attr)
     return get_strub_mode_from_attr (attr, var_p);
 
-  if (flag_strub > 0 && !var_p)
+  if (flag_strub >= -1 && !var_p)
     return STRUB_CALLABLE;
 
   return STRUB_DISABLED;
 }
 
+\f
+/* Return true iff NODE calls builtin va_start.  */
+
 static bool
 calls_builtin_va_start_p (cgraph_node *node)
 {
@@ -252,6 +469,8 @@ calls_builtin_va_start_p (cgraph_node *node)
   return result;
 }
 
+/* Return true iff NODE calls builtin apply_args, and optionally REPORT it.  */
+
 static bool
 calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
 {
@@ -276,12 +495,17 @@ calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE carries the always_inline attribute.  */
+
 static inline bool
 strub_always_inline_p (cgraph_node *node)
 {
   return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
 }
 
+/* Return true iff NODE is potentially eligible for any strub-enabled mode, and
+   optionally REPORT the reasons for ineligibility.  */
+
 static inline bool
 can_strub_p (cgraph_node *node, bool report = false)
 {
@@ -321,6 +545,10 @@ can_strub_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE is eligible for at-calls strub, and optionally REPORT
+   the reasons for ineligibility.  Besides general non-eligibility for
+   strub-enabled modes, at-calls rules out calling builtin apply_args.  */
+
 static bool
 can_strub_at_calls_p (cgraph_node *node, bool report = false)
 {
@@ -332,6 +560,9 @@ can_strub_at_calls_p (cgraph_node *node, bool report = false)
   return !calls_builtin_apply_args_p (node, report);
 }
 
+/* Symbolic macro for the max number of arguments that internal strub may add to
+   a function.  */
+
 #define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
 
 /* We can't perform internal strubbing if the function body involves certain
@@ -438,14 +669,12 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 			   || tree_versionable_function_p (node->decl));
 
 
-      /* Label values referenced are not preserved when copying.  If referenced
+      /* Label values references are not preserved when copying.  If referenced
 	 in nested functions, as in 920415-1.c and 920721-4.c their decls get
-	 remapped independently.  That might be too broad, in that we might be
-	 able to support correctly cases in which the labels are only used
-	 internally in a function, but disconnecting user labels from their
-	 original declarations is undesirable in general, and it probably
-	 doesn't matter, since explicitly-requested strub likely uses
-	 STRUB_AT_CALLS mode anyway.  */
+	 remapped independently.  The exclusion below might be too broad, in
+	 that we might be able to support correctly cases in which the labels
+	 are only used internally in a function, but disconnecting forced labels
+	 from their original declarations is undesirable in general.  */
       basic_block bb;
       FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
 	for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -459,8 +688,6 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 
 	    target = gimple_label_label (label_stmt);
 
-	    /* Make an edge to every label block that has been marked as a
-	       potential target for a computed goto or a non-local goto.  */
 	    if (!FORCED_LABEL (target))
 	      continue;
 
@@ -470,7 +697,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 forced labels");
 	  }
     }
 
@@ -491,6 +718,9 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return TRUE iff NODE has any strub-requiring local variable, or accesses (as
+   in reading) any variable through a strub-requiring type.  */
+
 static bool
 strub_from_body_p (cgraph_node *node)
 {
@@ -506,7 +736,9 @@ strub_from_body_p (cgraph_node *node)
 	!= STRUB_DISABLED)
       return true;
 
-  /* Now scan the body for loads with strub types.  */
+  /* Now scan the body for loads with strub-requiring types.
+     ??? Compound types don't propagate the strub requirement to
+     component types.  */
   basic_block bb;
   FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
     for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -528,6 +760,7 @@ strub_from_body_p (cgraph_node *node)
 
 /* Return true iff node is associated with a builtin that should be callable
    from strub contexts.  */
+
 static inline bool
 strub_callable_builtin_p (cgraph_node *node)
 {
@@ -568,17 +801,23 @@ strub_callable_builtin_p (cgraph_node *node)
     }
 }
 
+/* Compute the strub mode to be used for NODE.  STRUB_ATTR should be the strub
+   attribute,found for NODE, if any.  */
+
 static enum strub_mode
 compute_strub_mode (cgraph_node *node, tree strub_attr)
 {
   enum strub_mode req_mode = get_strub_mode_from_attr (strub_attr);
 
-  gcc_checking_assert (flag_strub >= -1 && flag_strub <= 3);
+  gcc_checking_assert (flag_strub >= -2 && flag_strub <= 3);
 
   /* Symbolic encodings of the -fstrub-* flags.  */
   /* Enable strub when explicitly requested through attributes to functions or
      variables, reporting errors if the requests cannot be satisfied.  */
   const bool strub_flag_auto = flag_strub < 0;
+  /* strub_flag_auto with strub call verification; without this, functions are
+     implicitly callable.  */
+  const bool strub_flag_strict = flag_strub < -1;
   /* Disable strub altogether, ignore attributes entirely.  */
   const bool strub_flag_disabled = flag_strub == 0;
   /* On top of _auto, also enable strub implicitly for functions that can
@@ -625,7 +864,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (!strub_flag_disabled
        && (strub_attr
 	   ? req_mode == STRUB_CALLABLE
-	   : (strub_flag_viable
+	   : (!strub_flag_strict
 	      || strub_callable_builtin_p (node))));
 
   /* This is a shorthand for either strub-enabled mode.  */
@@ -674,19 +913,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (at_calls_eligible
        && (strub_attr
 	   || (node->has_gimple_body_p ()
-#if 0 /* We no longer use collect_callers, so we can probably drop it.  */
-	       && node->get_availability () > AVAIL_INTERPOSABLE
-#endif
-	       && ((!node->externally_visible
-#if 0
-		    /* We wish to bypass the test below for functions that are
-		       not externally visible, but that's a little too broad: we
-		       do not wish to skip them for e.g. gnu_inline
-		       functions.  */
-		    && !TREE_PUBLIC (node->decl)
-		    && !DECL_EXTERNAL (node->decl)
-#endif
-		    )
+	       && (!node->externally_visible
 		   || (node->binds_to_current_def_p ()
 		       && node->can_be_local_p ()))
 	       && node->only_called_directly_p ())));
@@ -737,10 +964,6 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
   const enum strub_mode mode
     = ((strub_enable && is_always_inline)
        ? (strub_required ? STRUB_INLINABLE : STRUB_CALLABLE)
-#if 0
-       : (!strub_enable && strub_required && strub_attr)
-       ? req_mode
-#endif
        : (strub_enable && internal_viable
 	  && (strub_flag_internal || !at_calls_viable))
        ? STRUB_INTERNAL
@@ -802,6 +1025,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 /* Set FNDT's strub mode to MODE; FNDT may be a function decl or
    function type.  If OVERRIDE, do not check whether a mode is already
    set.  */
+
 static void
 strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
 {
@@ -828,12 +1052,18 @@ strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
   *attrp = attr;
 }
 
+/* Set FNDT's strub mode to callable.
+   FNDT may be a function decl or a function type.  */
+
 void
 strub_make_callable (tree fndt)
 {
   strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
 }
 
+/* Set NODE to strub MODE.  Report incompatibilities between MODE and the mode
+   requested through explicit attributes, and cases of non-eligibility.  */
+
 static void
 set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
 {
@@ -853,9 +1083,10 @@ 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",
-		    (int) mode, node->decl,
-		    (int) get_strub_mode_from_attr (attr));
+		    "%<strub%> mode %qE selected for %qD, when %qE was requested",
+		    get_strub_mode_attr_parm (mode),
+		    node->decl,
+		    get_strub_mode_attr_parm (req_mode));
 	  if (node->alias)
 	    {
 	      cgraph_node *target = node->ultimate_alias_target ();
@@ -906,6 +1137,8 @@ set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
   strub_set_fndt_mode_to (node->decl, mode, attr);
 }
 
+/* Compute and set NODE's strub mode.  */
+
 static void
 set_strub_mode (cgraph_node *node)
 {
@@ -946,6 +1179,7 @@ set_strub_mode (cgraph_node *node)
   set_strub_mode_to (node, mode);
 }
 
+\f
 /* Non-strub functions shouldn't be called from within strub contexts,
    except through callable ones.  Always inline strub functions can
    only be called from strub functions.  */
@@ -984,7 +1218,7 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_AT_CALLS_OPT:
     case STRUB_INTERNAL:
     case STRUB_WRAPPER:
-      return (flag_strub >= 0);
+      return (flag_strub >= -1);
 
     case STRUB_DISABLED:
       return false;
@@ -999,12 +1233,16 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
   return true;
 }
 
-/* We wish to avoid inlining WRAPPED functions back into their
-   WRAPPERs.  More generally, we wish to avoid inlining
-   strubbed functions into non-strubbed ones.  */
+/* Return true if CALLEE can be inlined into CALLER.  We wish to avoid inlining
+   WRAPPED functions back into their WRAPPERs.  More generally, we wish to avoid
+   inlining strubbed functions into non-strubbed ones.  CALLER doesn't have to
+   be an immediate caller of CALLEE: the immediate caller may have already been
+   cloned for inlining, and then CALLER may be further up the original call
+   chain.  ???  It would be nice if our own caller would retry inlining callee
+   if caller gets inlined.  */
 
 bool
-strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+strub_inlinable_to_p (cgraph_node *callee, cgraph_node *caller)
 {
   strub_mode callee_mode = get_strub_mode (callee);
 
@@ -1020,6 +1258,10 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_WRAPPER:
     case STRUB_DISABLED:
     case STRUB_CALLABLE:
+      /* When we consider inlining, we've already verified callability, so we
+	 can even inline callable and then disabled into a strub context.  That
+	 will get strubbed along with the context, so it's hopefully not a
+	 problem.  */
       return true;
 
     default:
@@ -1049,14 +1291,43 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
   return false;
 }
 
+/* Check that types T1 and T2 are strub-compatible.  Return 1 if the strub modes
+   are the same, 2 if they are interchangeable, and 0 otherwise.  */
+
+int
+strub_comptypes (tree t1, tree t2)
+{
+  enum strub_mode m1 = get_strub_mode_from_type (t1);
+  enum strub_mode m2 = get_strub_mode_from_type (t2);
+
+  if (m1 == m2)
+    return 1;
+
+  /* We're dealing with types, so only strub modes that can be selected by
+     attributes in the front end matter.  If either mode is at-calls, the
+     conversion is not compatible.  */
+  if (m1 == STRUB_AT_CALLS || m2 == STRUB_AT_CALLS)
+    return 0;
+
+  return 2;
+}
+
 /* Check that strub functions don't call non-strub functions, and that
    always_inline strub functions are only called by strub
    functions.  */
+
 static void
 verify_strub ()
 {
   cgraph_node *node;
 
+  /* It's expected that check strub-wise pointer type compatibility of variables
+     and of functions is already taken care of by front-ends, on account of the
+     attribute's being marked as affecting type identity and of the creation of
+     distinct types.  */
+
+  /* Check that call targets in strub contexts have strub-callable types.  */
+
   FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
   {
     enum strub_mode caller_mode = get_strub_mode (node);
@@ -1105,20 +1376,17 @@ verify_strub ()
 	  }
       }
   }
-
-  /* ??? Check strub-wise pointer type compatibility of variables and
-     functions, or is this already taken care of on account of the
-     attribute's being marked as affecting type identity?  */
 }
 
 namespace {
 
+/* Define a pass to compute strub modes.  */
 const pass_data pass_data_ipa_strub_mode = {
   SIMPLE_IPA_PASS,
   "strubm",
   OPTGROUP_NONE,
   TV_NONE,
-  PROP_cfg, // properties_required
+  PROP_cfg | PROP_gimple, // properties_required
   0,	    // properties_provided
   0,	    // properties_destroyed
   0,	    // properties_start
@@ -1133,24 +1401,26 @@ public:
   {}
   opt_pass *clone () { return new pass_ipa_strub_mode (m_ctxt); }
   virtual bool gate (function *) {
-    /* In the default setting, the attribute handler changes
-       flag_strub to -1 if any strub-enabling occurence of the
-       attribute is found.  If it remains at -2, nothing that would
-       enable strub was found, so we can disable it and avoid the
+    /* In relaxed (-3) and strict (-4) settings, that only enable strub at a
+       function or variable attribute's request, the attribute handler changes
+       flag_strub to -1 or -2, respectively, if any strub-enabling occurence of
+       the attribute is found.  Therefore, if it remains at -3 or -4, nothing
+       that would enable strub was found, so we can disable it and avoid the
        overhead.  */
-    if (flag_strub == -2)
+    if (flag_strub < -2)
       flag_strub = 0;
     return flag_strub;
   }
   virtual unsigned int execute (function *);
 };
 
+/* Define a pass to introduce strub transformations.  */
 const pass_data pass_data_ipa_strub = {
   SIMPLE_IPA_PASS,
   "strub",
   OPTGROUP_NONE,
   TV_NONE,
-  PROP_cfg, // properties_required
+  PROP_cfg | PROP_gimple | PROP_ssa, // properties_required
   0,	    // properties_provided
   0,	    // properties_destroyed
   0,	    // properties_start
@@ -1170,6 +1440,7 @@ public:
   virtual bool gate (function *) { return flag_strub; }
   virtual unsigned int execute (function *);
 
+  /* Define on demand and cache some types we use often.  */
 #define DEF_TYPE(NAME, INIT)			\
   static inline tree get_ ## NAME () {		\
     static tree type = NULL_TREE;		\
@@ -1202,6 +1473,7 @@ public:
 
 #undef DEF_TYPE
 
+  /* Define non-strub builtins on demand.  */
 #define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST)			\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1224,6 +1496,7 @@ public:
 
 #undef DEF_NM_BUILTIN
 
+  /* Define strub builtins on demand.  */
 #define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST)		\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1260,6 +1533,7 @@ public:
 
 #undef DEF_SS_BUILTIN
 
+    /* Define strub identifiers on demand.  */
 #define DEF_IDENT(NAME)					\
   static inline tree get_ ## NAME () {			\
     static tree identifier = NULL_TREE;			\
@@ -1278,6 +1552,10 @@ public:
   static inline void adjust_at_calls_call (cgraph_edge *, int);
   static inline void adjust_at_calls_calls (cgraph_node *);
 
+  /* Add to SEQ a call to the strub watermark update builtin, taking NODE's
+     location if given.  Optionally add the corresponding edge from NODE, with
+     execution frequency COUNT.  Return the modified SEQ.  */
+
   static inline gimple_seq
   call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
 			 gimple_seq seq = NULL)
@@ -1300,8 +1578,15 @@ public:
 
 } // anon namespace
 
+/* Gather with this type a collection of parameters that we're turning into
+   explicit references.  */
+
 typedef hash_set<tree> indirect_parms_t;
 
+/* Dereference OP's incoming turned-into-reference parm if it's an
+   INDIRECT_PARMS or an ADDR_EXPR thereof.  Set *REC and return according to
+   gimple-walking expectations.  */
+
 static tree
 maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
 {
@@ -1324,12 +1609,17 @@ maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
     {
       *rec = 0;
       if (indirect_parms.contains (TREE_OPERAND (op, 0)))
-	return TREE_OPERAND (op, 0);
+	{
+	  op = TREE_OPERAND (op, 0);
+	  return op;
+	}
     }
 
   return NULL_TREE;
 }
 
+/* A gimple-walking function that adds dereferencing to indirect parms.  */
+
 static tree
 walk_make_indirect (tree *op, int *rec, void *arg)
 {
@@ -1351,6 +1641,11 @@ walk_make_indirect (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* A gimple-walking function that turns any non-gimple-val ADDR_EXPRs into a
+   separate SSA.  Though addresses of e.g. parameters, and of members thereof,
+   are gimple vals, turning parameters into references, with an extra layer of
+   indirection and thus explicit dereferencing, need to be regimplified.  */
+
 static tree
 walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
 {
@@ -1374,6 +1669,57 @@ walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* Turn STMT's PHI arg defs into separate SSA defs if they've become
+   non-gimple_val.  Return TRUE if any edge insertions need to be committed.  */
+
+static bool
+walk_regimplify_phi (gphi *stmt)
+{
+  bool needs_commit = false;
+
+  for (unsigned i = 0, n = gimple_phi_num_args (stmt); i < n; i++)
+    {
+      tree op = gimple_phi_arg_def (stmt, i);
+      if ((TREE_CODE (op) == ADDR_EXPR
+	   && !is_gimple_val (op))
+	  /* ??? A PARM_DECL that was addressable in the original function and
+	     had its address in PHI nodes, but that became a reference in the
+	     wrapped clone would NOT be updated by update_ssa in PHI nodes.
+	     Alas, if we were to create a default def for it now, update_ssa
+	     would complain that the symbol that needed rewriting already has
+	     SSA names associated with it.  OTOH, leaving the PARM_DECL alone,
+	     it eventually causes errors because it remains unchanged in PHI
+	     nodes, but it gets rewritten as expected if it appears in other
+	     stmts.  So we cheat a little here, and force the PARM_DECL out of
+	     the PHI node and into an assignment.  It's a little expensive,
+	     because we insert it at the edge, which introduces a basic block
+	     that's entirely unnecessary, but it works, and the block will be
+	     removed as the default def gets propagated back into the PHI node,
+	     so the final optimized code looks just as expected.  */
+	  || (TREE_CODE (op) == PARM_DECL
+	      && !TREE_ADDRESSABLE (op)))
+	{
+	  tree temp = make_ssa_name (TREE_TYPE (op), stmt);
+	  if (TREE_CODE (op) == PARM_DECL)
+	    SET_SSA_NAME_VAR_OR_IDENTIFIER (temp, DECL_NAME (op));
+	  SET_PHI_ARG_DEF (stmt, i, temp);
+
+	  gimple *assign = gimple_build_assign (temp, op);
+	  if (gimple_phi_arg_has_location (stmt, i))
+	    gimple_set_location (assign, gimple_phi_arg_location (stmt, i));
+	  gsi_insert_on_edge (gimple_phi_arg_edge (stmt, i), assign);
+	  needs_commit = true;
+	}
+    }
+
+  return needs_commit;
+}
+
+/* Create a reference type to use for PARM when turning it into a reference.
+   NONALIASED causes the reference type to gain its own separate alias set, so
+   that accessing the indirectly-passed parm won'will not add aliasing
+   noise.  */
+
 static tree
 build_ref_type_for (tree parm, bool nonaliased = true)
 {
@@ -1411,6 +1757,7 @@ build_ref_type_for (tree parm, bool nonaliased = true)
 
 /* Add cgraph edges from current_function_decl to callees in SEQ with frequency
    COUNT, assuming all calls in SEQ are direct.  */
+
 static void
 add_call_edges_for_seq (gimple_seq seq, profile_count count)
 {
@@ -1425,16 +1772,22 @@ add_call_edges_for_seq (gimple_seq seq, profile_count count)
     {
       gimple *stmt = gsi_stmt (gsi);
 
-      if (!is_a <gcall *> (stmt))
+      gcall *call = dyn_cast <gcall *> (stmt);
+      if (!call)
 	continue;
 
-      gcall *call = as_a <gcall *> (stmt);
       tree callee = gimple_call_fndecl (call);
       gcc_checking_assert (callee);
       node->create_edge (cgraph_node::get_create (callee), call, count, false);
     }
 }
 
+/* Insert SEQ after the call at GSI, as if the call was in a try block with SEQ
+   as finally, i.e., SEQ will run after the call whether it returns or
+   propagates an exception.  This handles block splitting, EH edge and block
+   creation, noreturn and nothrow optimizations, and even throwing calls without
+   preexisting local handlers.  */
+
 static void
 gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
 {
@@ -1446,7 +1799,7 @@ gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
   if (gimple_has_location (stmt))
     annotate_all_with_location (seq, gimple_location (stmt));
 
-  gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+  gcall *call = dyn_cast <gcall *> (stmt);
   bool noreturn_p = call && gimple_call_noreturn_p (call);
   int eh_lp = lookup_stmt_eh_lp (stmt);
   bool must_not_throw_p = eh_lp < 0;
@@ -1586,8 +1939,12 @@ remove_named_attribute_unsharing (const char *name, tree *attrs)
     }
 }
 
+/* Record the order of the last cgraph entry whose mode we've already set, so
+   that we can perform mode setting incrementally without duplication.  */
 static int last_cgraph_order;
 
+/* Set strub modes for functions introduced since the last call.  */
+
 static void
 ipa_strub_set_mode_for_new_functions ()
 {
@@ -1616,6 +1973,7 @@ ipa_strub_set_mode_for_new_functions ()
 }
 
 /* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
+
 bool
 strub_splittable_p (cgraph_node *node)
 {
@@ -1641,10 +1999,11 @@ strub_splittable_p (cgraph_node *node)
 }
 
 /* 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))
+  switch (get_strub_mode_from_fndecl (fndecl))
     {
     case STRUB_WRAPPED:
     case STRUB_AT_CALLS:
@@ -1676,6 +2035,7 @@ strub_watermark_parm (tree fndecl)
 
 /* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
    hasn't been added yet.  Return the named argument count.  */
+
 int
 pass_ipa_strub::adjust_at_calls_type (tree type)
 {
@@ -1751,6 +2111,12 @@ pass_ipa_strub::adjust_at_calls_type (tree type)
   return named_args;
 }
 
+/* Adjust a call to an at-calls call target.  Create a watermark local variable
+   if needed, initialize it before, pass it to the callee according to the
+   modified at-calls interface, and release the callee's stack space after the
+   call, if not deferred.  If the call is const or pure, arrange for the
+   watermark to not be assumed unused or unchanged.  */
+
 void
 pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 {
@@ -1819,10 +2185,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 
     if (gimple_call_internal_p (stmt))
 #if 0
-      /*
-	new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
-	vargs);
-      */
+      new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
+						 vargs);
 #endif
       gcc_unreachable ();
     else
@@ -1917,6 +2281,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
   gsi_insert_finally_seq_after_call (gsi, seq);
 }
 
+/* Adjust all at-calls calls in NODE. */
+
 void
 pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
 {
@@ -1965,6 +2331,10 @@ pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
     }
 }
 
+/* The strubm (strub mode) pass computes a strub mode for each function in the
+   call graph, and checks, before any inlining, that strub callability
+   requirements in effect are satisfied.  */
+
 unsigned int
 pass_ipa_strub_mode::execute (function *)
 {
@@ -1977,12 +2347,17 @@ pass_ipa_strub_mode::execute (function *)
   return 0;
 }
 
+/* Create a strub mode pass.  */
+
 simple_ipa_opt_pass *
 make_pass_ipa_strub_mode (gcc::context *ctxt)
 {
   return new pass_ipa_strub_mode (ctxt);
 }
 
+/* The strub pass proper adjusts types, signatures, and at-calls calls, and
+   splits internal-strub functions.  */
+
 unsigned int
 pass_ipa_strub::execute (function *)
 {
@@ -2042,17 +2417,6 @@ pass_ipa_strub::execute (function *)
 	if (onode->alias)
 	  continue;
 
-#if 0 /* Calls are now adjusted when examining callers.  */
-	unsigned c;
-	cgraph_edge *e;
-	FOR_EACH_VEC_ELT (onode->collect_callers (), c, e)
-	  {
-	    push_cfun (DECL_STRUCT_FUNCTION (e->caller->decl));
-	    adjust_at_calls_call (e, named_args);
-	    pop_cfun ();
-	  }
-#endif
-
 	cgraph_node *nnode = onode;
 	push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
 
@@ -2071,10 +2435,10 @@ pass_ipa_strub::execute (function *)
 		{
 		  gimple *stmt = gsi_stmt (gsi);
 
-		  if (!is_gimple_call (stmt))
-		    continue;
+		  gcall *call = dyn_cast <gcall *> (stmt);
 
-		  gcall *call = as_a <gcall *> (stmt);
+		  if (!call)
+		    continue;
 
 		  if (gimple_alloca_call_p (call))
 		    {
@@ -2088,10 +2452,6 @@ pass_ipa_strub::execute (function *)
 	  }
 
 	pop_cfun ();
-
-#if 0
-	compute_fn_summary (onode, true);
-#endif
       }
   }
 
@@ -2108,43 +2468,6 @@ pass_ipa_strub::execute (function *)
 	continue;
       }
 
-#if 0
-    /* Hmm, this is an i386-specific attribute.  Do we need machine-specific
-       logic?  */
-    remove_named_attribute_unsharing ("interrupt",
-				      &DECL_ATTRIBUTES (onode->decl));
-#endif
-
-#if 0
-    if (!DECL_STRUCT_FUNCTION (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting struct-less function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    if (!onode->lowered)
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting non-lowered function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    /* 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.  FIXME.  */
-    if (!tree_versionable_function_p (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"%qD cannot be split for %<strub%>",
-		onode->decl);
-	continue;
-      }
-#endif
-
     bool is_stdarg = calls_builtin_va_start_p (onode);;
     bool apply_args = calls_builtin_apply_args_p (onode);
 
@@ -2753,26 +3076,45 @@ pass_ipa_strub::execute (function *)
       if (any_indirect)
 	{
 	  basic_block bb;
+	  bool needs_commit = false;
 	  FOR_EACH_BB_FN (bb, cfun)
-	    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
-		 !gsi_end_p (gsi); gsi_next (&gsi))
-	      {
-		gimple *stmt = gsi_stmt (gsi);
+	    {
+	      for (gphi_iterator gsi = gsi_start_nonvirtual_phis (bb);
+		   !gsi_end_p (gsi);
+		   gsi_next_nonvirtual_phi (&gsi))
+		{
+		  gphi *stmt = gsi.phi ();
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed && !is_gimple_debug (gsi_stmt (gsi)))
+		    if (walk_regimplify_phi (stmt))
+		      needs_commit = true;
+		}
 
-		walk_stmt_info wi = {};
-		wi.info = &indirect_nparms;
-		walk_gimple_op (stmt, walk_make_indirect, &wi);
-		if (wi.changed)
-		  {
-		    if (!is_gimple_debug (gsi_stmt (gsi)))
-		      {
-			wi.info = &gsi;
-			walk_gimple_op (stmt, walk_regimplify_addr_expr,
-					&wi);
-		      }
-		    update_stmt (stmt);
-		  }
-	      }
+	      for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+		   !gsi_end_p (gsi); gsi_next (&gsi))
+		{
+		  gimple *stmt = gsi_stmt (gsi);
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed)
+		    {
+		      if (!is_gimple_debug (stmt))
+			{
+			  wi.info = &gsi;
+			  walk_gimple_op (stmt, walk_regimplify_addr_expr,
+					  &wi);
+			}
+		      update_stmt (stmt);
+		    }
+		}
+	    }
+	  if (needs_commit)
+	    gsi_commit_edge_inserts ();
 	}
 
       if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca
@@ -2842,10 +3184,10 @@ pass_ipa_strub::execute (function *)
       push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
       gimple_stmt_iterator gsi
 	= gsi_after_labels (single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun)));
-      while (!is_gimple_call (gsi_stmt (gsi)))
-	gsi_next (&gsi);
 
-      gcall *wrcall = as_a <gcall *> (gsi_stmt (gsi));
+      gcall *wrcall;
+      while (!(wrcall = dyn_cast <gcall *> (gsi_stmt (gsi))))
+	gsi_next (&gsi);
 
       tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
       TREE_ADDRESSABLE (swm) = true;
@@ -2973,11 +3315,6 @@ pass_ipa_strub::execute (function *)
 
       pop_cfun ();
     }
-
-#if 0
-    compute_fn_summary (onode, true);
-    compute_fn_summary (nnode, true);
-#endif
   }
 
   if (flag_checking)
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
index c335ab42097..036ab3e20d2 100644
--- a/gcc/ipa-strub.h
+++ b/gcc/ipa-strub.h
@@ -18,11 +18,10 @@ You should have received a copy of the GNU General Public License
 along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
-/* Return TRUE if the first function can be inlined into the second,
-   as far as stack scrubbing constraints are concerned.  CALLEE
-   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 TRUE if CALLEE can be inlined into CALLER, as far as stack scrubbing
+   constraints are concerned.  CALLEE doesn't have to be called directly by
+   CALLER, but the returned value says nothing about intervening functions.  */
+extern bool strub_inlinable_to_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);
@@ -33,3 +32,14 @@ extern tree strub_watermark_parm (tree fndecl);
 
 /* Make a function type or declaration callable.  */
 extern void strub_make_callable (tree fndecl);
+
+/* Return zero iff ID is NOT an acceptable parameter for a user-supplied strub
+   attribute.  Otherwise, return >0 if it enables strub, <0 if it does not.
+   Return +/-1 if the attribute-modified type is compatible with the type
+   without the attribute, or +/-2 if it is not compatible.  */
+extern int strub_validate_parm (tree id);
+
+/* Like comptypes, return 0 if t1 and t2 are not compatible, 1 if they are
+   compatible, and 2 if they are nearly compatible.  Same strub mode is
+   compatible, interface-compatible strub modes are nearly compatible.  */
+extern int strub_comptypes (tree t1, tree t2);
diff --git a/gcc/testsuite/c-c++-common/strub-O0.c b/gcc/testsuite/c-c++-common/strub-O0.c
index 6afe0fd5de1..c7a79a6ea0d 100644
--- a/gcc/testsuite/c-c++-common/strub-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O0 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O0, none of the strub builtins are expanded inline.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O1.c b/gcc/testsuite/c-c++-common/strub-O1.c
index 1cdeaecaf32..96285c975d9 100644
--- a/gcc/testsuite/c-c++-common/strub-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O1 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O1, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
index 7848c46d179..8edc0d8aa13 100644
--- a/gcc/testsuite/c-c++-common/strub-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O2, without -fno-inline, we fully expand enter and update, and add a test
    around the leave call.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
index 85a8f76785e..c6d900cf3c4 100644
--- a/gcc/testsuite/c-c++-common/strub-O2fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3.c b/gcc/testsuite/c-c++-common/strub-O3.c
index 1fcde345d36..33ee465e51c 100644
--- a/gcc/testsuite/c-c++-common/strub-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand" } */
 
 int __attribute__ ((__strub__)) var;
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
index a2eedfd96b2..2936f82079e 100644
--- a/gcc/testsuite/c-c++-common/strub-O3fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
index e5cb1f60541..479746e57d8 100644
--- a/gcc/testsuite/c-c++-common/strub-Og.c
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Og -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Og -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Og, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
index 194aacc2c05..2241d4ea07f 100644
--- a/gcc/testsuite/c-c++-common/strub-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Os, without -fno-inline, we fully expand enter, and also update.  The
    expanded update might be larger than a call proper, but argument saving and
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
index 46e84bf6560..a322bcc5da6 100644
--- a/gcc/testsuite/c-c++-common/strub-all1.c
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -22,11 +22,11 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
index f377541cff0..db60026d0e0 100644
--- a/gcc/testsuite/c-c++-common/strub-all2.c
+++ b/gcc/testsuite/c-c++-common/strub-all2.c
@@ -17,8 +17,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
index f180b17f30e..2f462adc1ef 100644
--- a/gcc/testsuite/c-c++-common/strub-apply1.c
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -1,13 +1,13 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
-void __attribute__ ((__strub__ (3)))
+void __attribute__ ((__strub__ ("callable")))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0);
 }
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   void *args = __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
index 379a54b73b7..bab2dd10f5b 100644
--- a/gcc/testsuite/c-c++-common/strub-apply2.c
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 extern void __attribute__ ((__strub__))
 apply_function (void *args);
@@ -10,3 +10,9 @@ apply_args (int i, int j, double d) /* { dg-error "selected" } */
   void *args = __builtin_apply_args (); /* { dg-message "does not support" } */
   apply_function (args);
 }
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After the error and sorry messages in strubm, build_ssa_passes are gated out,
+   so we don't get the PROP_ssa property that targetclone requires.  When
+   checking is not enabled at configure time, we admit to being confused and
+   bail out, but when checking is eneabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
index 9b4786be698..85f9d090fda 100644
--- a/gcc/testsuite/c-c++-common/strub-apply3.c
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -1,8 +1,14 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 void __attribute__ ((__strub__))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */
 }
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
index 409f747743e..15ffaa031b8 100644
--- a/gcc/testsuite/c-c++-common/strub-apply4.c
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+/* { dg-options "-O2 -fstrub=strict -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
@@ -18,4 +18,4 @@ void f() {
   apply_args (1, 2, 3);
 }
 
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 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 d964b07ae5d..b70843b4215 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -22,9 +22,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
index 530eee36d06..97a3988a6b9 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls2.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls2.c
@@ -17,7 +17,7 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-default1.c b/gcc/testsuite/c-c++-common/strub-default1.c
deleted file mode 100644
index 79d00cedb9a..00000000000
--- a/gcc/testsuite/c-c++-common/strub-default1.c
+++ /dev/null
@@ -1,42 +0,0 @@
-/* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
-
-static int __attribute__ ((__strub__)) var;
-
-/* 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 inline void
-__attribute__ ((__always_inline__))
-h() {
-  var++;
-}
-
-/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
-   the viability of at-calls strubbing.  Though internally a strub context, its
-   interface is not strub-enabled, so it's not callable from within strub
-   contexts.  */
-static inline void
-g() {
-  var--;
-  h();
-}
-
-/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
-   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
-void
-f() {
-  var++;
-  g();  /* { dg-error "calling non-.strub." } */
-}
-
-/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-4\[)\]" 1 "strubm" } } */
-/* { 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 "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
index 7b04eea35d9..3d73431b3dc 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O1" } */
+/* { dg-options "-fstrub=strict -O1" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O1.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
index 67d96419a5e..fddf3c745e7 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O2" } */
+/* { dg-options "-fstrub=strict -O2" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O2.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
index 34828d2711e..2bc384ee8d1 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O3" } */
+/* { dg-options "-fstrub=strict -O3" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -O3.  */
@@ -10,7 +10,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -18,7 +18,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -32,7 +32,7 @@ leak_string (void)
   return 0;
 }
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 int
 look_for_string (char *e)
 {
@@ -56,14 +56,14 @@ look_for_string (char *e)
   return 0;
 }
 
-static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+static __attribute__ ((__strub__ ("at-calls"), __noinline__, __noclone__))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 deferred_at_calls ()
 {
@@ -73,7 +73,7 @@ deferred_at_calls ()
   return ret;
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 deferred_internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
index b273660aea1..fbaf85fe0fa 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -Os" } */
+/* { dg-options "-fstrub=strict -Os" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -Os.  */
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
index a74658c9ac9..e9d7b7b9ee0 100644
--- a/gcc/testsuite/c-c++-common/strub-internal1.c
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -23,9 +23,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
index a6e69357b23..8b8e15a51c7 100644
--- a/gcc/testsuite/c-c++-common/strub-internal2.c
+++ b/gcc/testsuite/c-c++-common/strub-internal2.c
@@ -14,8 +14,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
index 7e22a266ad9..0a4a7539d34 100644
--- a/gcc/testsuite/c-c++-common/strub-parms1.c
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -16,7 +16,7 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 large_byref_arg (struct large_arg la)
 {
 }
@@ -24,7 +24,7 @@ large_byref_arg (struct large_arg la)
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*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)))
+void __attribute__ ((__strub__ ("internal")))
 std_arg (int i, ...)
 {
   va_list vl;
@@ -38,7 +38,7 @@ std_arg (int i, ...)
 /* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
index 2d8036c0fbc..147171d96d5 100644
--- a/gcc/testsuite/c-c++-common/strub-parms2.c
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -15,14 +15,14 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 large_byref_arg (struct large_arg la)
 {
 }
 
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 std_arg (int i, ...)
 {
   va_list vl;
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
index f64361f1235..4e92682895a 100644
--- a/gcc/testsuite/c-c++-common/strub-parms3.c
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
new file mode 100644
index 00000000000..e2f9d8aebca
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  Without the error,
+   inlining takes place.  */
+
+#include "strub-strict1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
new file mode 100644
index 00000000000..98474435d2e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  */
+
+#include "strub-strict2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
index a4226ce0119..1de15342595 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fexceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fexceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
index 3bab553478b..f9209c81900 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
index c89cc7c2c47..bed1dcfb54a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
index b869fafb691..6bf0071f52b 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
index 1d3dd2f2c2c..4732f515bf7 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
index 4dd4281b03b..8d6424c479a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-strict1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
new file mode 100644
index 00000000000..7f5247cfaf0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-strict1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+static int __attribute__ ((__strub__)) var;
+
+/* 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 inline void
+__attribute__ ((__always_inline__))
+h() {
+  var++;
+}
+
+/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
+   the viability of at-calls strubbing.  Though internally a strub context, its
+   interface is not strub-enabled, so it's not callable from within strub
+   contexts.  */
+static inline void
+g() {
+  var--;
+  h();
+}
+
+/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
+   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
+void
+f() {
+  var++;
+  g();  /* { dg-error "calling non-.strub." } */
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-default2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
similarity index 54%
rename from gcc/testsuite/c-c++-common/strub-default2.c
rename to gcc/testsuite/c-c++-common/strub-strict2.c
index 487253e9227..17463b6e554 100644
--- a/gcc/testsuite/c-c++-common/strub-default2.c
+++ b/gcc/testsuite/c-c++-common/strub-strict2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
 
 static int __attribute__ ((__strub__)) var;
 
@@ -22,8 +22,14 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
index 0840dddd136..e48e0610e07 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 #include "strub-tail-O2.c"
 
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
index 9330d6ff4c1..87cda7ab21b 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.
    Tail calls are short-circuited at -O2+.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
index 5b33ff1f530..b5e45ab0525 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
index 38935e3270b..96aa7fe4b07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -1,39 +1,39 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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);
+extern int __attribute__ ((__strub__ ("callable"))) xcallable (void);
+extern int __attribute__ ((__strub__ ("internal"))) xinternal (void);
+extern int __attribute__ ((__strub__ ("at-calls"))) xat_calls (void);
+extern int __attribute__ ((__strub__ ("disabled"))) 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__ ("callable"))) callable (void);
+int __attribute__ ((__strub__ ("internal"))) internal (void);
+int __attribute__ ((__strub__ ("at-calls"))) at_calls (void);
+int __attribute__ ((__strub__ ("disabled"))) disabled (void);
 
 int __attribute__ ((__strub__)) var;
 int var_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 icallable (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 iinternal (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 idisabled (void);
 static inline int __attribute__ ((__always_inline__))
 ivar_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 i_callable (void) { return 0; }
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 i_internal (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 i_at_calls (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 i_disabled (void) { return 0; }
 static inline int __attribute__ ((__always_inline__))
 i_var_user (void) { return var; }
@@ -70,7 +70,7 @@ i_var_user (void) { return var; }
 
 /* Not a strub context, so it can call anything.
    Explicitly declared as callable even from within strub contexts.  */
-int __attribute__ ((__strub__ (3)))
+int __attribute__ ((__strub__ ("callable")))
 callable (void) {
   int ret = 0;
 
@@ -88,7 +88,7 @@ callable (void) {
 
 /* 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)))
+int __attribute__ ((__strub__ ("internal")))
 internal (void) {
   int ret = var;
 
@@ -109,7 +109,7 @@ internal (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (1)))
+int __attribute__ ((__strub__ ("at-calls")))
 at_calls (void) {
   int ret = var;
 
@@ -130,7 +130,7 @@ at_calls (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (0)))
+int __attribute__ ((__strub__ ("disabled")))
 disabled () {
   int ret = 0;
 
@@ -205,7 +205,7 @@ iinternal (void) {
   return ret;
 }
 
-int
+int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void) {
   int ret = var;
 
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
index 100fb0c59a9..2857195706e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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,
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
index 9e818ac9748..98a92bc9eac 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
index d40e8aa45cb..5511a6e1e71 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __const__))
 f() {
   return 0;
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
index d4cbdaf10f3..47ee927964d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -1,12 +1,12 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__
 __attribute__ ((__const__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data1.c b/gcc/testsuite/c-c++-common/torture/strub-data1.c
index 62a03891ab6..7c27a2a1a6d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointed-to data enables strubbing if accessed.  */
 int __attribute__ ((__strub__)) var;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data2.c b/gcc/testsuite/c-c++-common/torture/strub-data2.c
index 9b7df13a280..e66d903780a 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointer itself is a strub variable, enabling internal strubbing when
    its value is used.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data3.c b/gcc/testsuite/c-c++-common/torture/strub-data3.c
index 515706caa32..5e08e0e58c6 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data4.c b/gcc/testsuite/c-c++-common/torture/strub-data4.c
index 0ec9e35429f..a818e7a38bb 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
index 69f3d65ed44..90e8ad51c1e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -Werror" } */
+/* { dg-options "-fstrub=strict -Werror" } */
 
 typedef int __attribute__ ((__strub__)) strub_int;
 strub_int *ptr;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
index b8adf8009e8..c165f312f16 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype ();
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
index 5b2c35ad6a7..69fcff8d376 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
index 5ee50456dc9..ff006224909 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int, ...);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
new file mode 100644
index 00000000000..b416ba06b59
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+}
+
+void
+bat (void)
+{
+  /* Not allowed, not a strub context.  */
+  inl_int_ali (); /* { dg-error "context" } */
+}
+
+/* { dg-bogus "internal compiler error" "ICE" { target *-*-* } 0 } */
+/* After an error in strubm, build_ssa_passes are gated out, so we don't get the
+   PROP_ssa property that targetclone requires.  When checking is not enabled at
+   configure time, we admit to being confused and bail out, but when checking is
+   enabled, we go ahead with the ICE.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
new file mode 100644
index 00000000000..f9a6b4a16fa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=all" } */
+
+#include "strub-inlinable1.c"
+
+/* With -fstrub=all, the caller becomes a strub context, so the strub-inlinable
+   callee is not rejected.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
new file mode 100644
index 00000000000..3fc5a5c4c4d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+typedef void ft (void);
+typedef void ft2 (int, int);
+extern ft __attribute__ ((__strub__)) fnac;
+
+ft * f (void) {
+  return fnac; /* { dg-warning "incompatible" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
new file mode 100644
index 00000000000..7f01cb3372f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
@@ -0,0 +1,19 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic" } */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+
+  d_p = bac; /* { dg-warning "not quite compatible" } */
+  c_p = bad; /* { dg-warning "not quite compatible" } */
+  c_p = bar; /* { dg-warning "not quite compatible" } */
+  c_p = bal; /* { 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
index cb223da6efc..a262a086837 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
index 67d1434b1f8..4c4bd50c209 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
index 59f02ea901f..ce195c6b1f1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -1,10 +1,10 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __pure__))
 f() {
   static int i; /* Stop it from being detected as const.  */
   return i;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
index 973e909217d..75cd54ccb5b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
 __attribute__ ((__pure__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
index a4077c35a60..c596066a045 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -14,7 +14,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -59,14 +59,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
index 94e4156ea73..1fdba214a07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
index 0ca74beb59d..afbc2cc9ab4 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
 /* Check that a non-strub function leaves a string behind in the stack, and that
@@ -7,7 +7,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
index 4ab11c0682e..5300f1d330b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -85,14 +85,14 @@ intermediate ()
   return ret;
 }
 
-static inline __attribute__ ((__strub__ (2)))
+static inline __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
   return intermediate ();
 }
 
-int __attribute__ ((strub (0)))
+int __attribute__ ((__strub__ ("disabled")))
 main ()
 {
   if (look_for_string (internal ()))
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4d.c b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
index e4f7445607c..08de3f1c3b1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4d.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -1,7 +1,7 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
-#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ (1)))
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
 
 #include "strub-run4.c"
diff --git a/gcc/testsuite/g++.dg/torture/strub-init1.C b/gcc/testsuite/g++.dg/torture/strub-init1.C
index e51ae802be4..c226ab10ff6 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init1.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init2.C b/gcc/testsuite/g++.dg/torture/strub-init2.C
index edcb7bf8ad2..a7911f1fa72 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init2.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init3.C b/gcc/testsuite/g++.dg/torture/strub-init3.C
index bacf823ca4e..6ebebcd01e8 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init3.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
index 697ac9de764..2d6f6394993 100644
--- a/gcc/testsuite/gnat.dg/strub_attr.adb
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -1,5 +1,5 @@
 --  { dg-do compile }
---  { dg-options "-fstrub=default -fdump-ipa-strubm" }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
 
 package body Strub_Attr is
    E : exception;
diff --git a/libgcc/strub.c b/libgcc/strub.c
index fd6e27556e4..7c58deee1e6 100644
--- a/libgcc/strub.c
+++ b/libgcc/strub.c
@@ -36,7 +36,7 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 # define TOPS <
 #endif
 
-#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ ("callable")))
 
 /* Enter a stack scrubbing context, initializing the watermark to the caller's
    stack address.  */


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

* [gcc(refs/users/aoliva/heads/strub)] -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests
@ 2021-09-04 14:12 Alexandre Oliva
  0 siblings, 0 replies; 13+ messages in thread
From: Alexandre Oliva @ 2021-09-04 14:12 UTC (permalink / raw)
  To: gcc-cvs

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

commit c358786f64ab58b9742f97d6733651536a2c19f0
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Thu Sep 2 23:15:36 2021 -0300

    -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests

Diff:
---
 .../doc/gnat_rm/security_hardening_features.rst    |  30 +
 gcc/ada/gcc-interface/utils.c                      |  95 ++--
 gcc/attribs.c                                      |  14 +-
 gcc/c-family/c-attribs.c                           |  84 ++-
 gcc/common.opt                                     |  27 +-
 gcc/doc/extend.texi                                | 288 ++++++++--
 gcc/doc/invoke.texi                                |  54 +-
 gcc/ipa-inline.c                                   |   2 +-
 gcc/ipa-strub.c                                    | 624 ++++++++++++++++-----
 gcc/ipa-strub.h                                    |  13 +-
 gcc/testsuite/c-c++-common/strub-O0.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O1.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2fni.c           |   2 +-
 gcc/testsuite/c-c++-common/strub-O3.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O3fni.c           |   2 +-
 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            |  12 +-
 gcc/testsuite/c-c++-common/strub-all2.c            |   6 +-
 gcc/testsuite/c-c++-common/strub-apply1.c          |   6 +-
 gcc/testsuite/c-c++-common/strub-apply2.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-apply3.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-apply4.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-at-calls2.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-defer-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |  14 +-
 gcc/testsuite/c-c++-common/strub-defer-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-internal1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-internal2.c       |   6 +-
 gcc/testsuite/c-c++-common/strub-parms1.c          |  10 +-
 gcc/testsuite/c-c++-common/strub-parms2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-parms3.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-relaxed1.c        |  18 +
 gcc/testsuite/c-c++-common/strub-relaxed2.c        |  14 +
 gcc/testsuite/c-c++-common/strub-short-O0-exc.c    |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O0.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O3.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-Os.c        |   2 +-
 .../{strub-default1.c => strub-strict1.c}          |  16 +-
 .../{strub-default2.c => strub-strict2.c}          |   8 +-
 gcc/testsuite/c-c++-common/strub-tail-O1.c         |   2 +-
 gcc/testsuite/c-c++-common/strub-tail-O2.c         |   2 +-
 .../c-c++-common/torture/strub-callable1.c         |   2 +-
 .../c-c++-common/torture/strub-callable2.c         |  42 +-
 gcc/testsuite/c-c++-common/torture/strub-const1.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const2.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const3.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-const4.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-data1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data3.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data4.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |   2 +-
 .../c-c++-common/torture/strub-indcall1.c          |   2 +-
 .../c-c++-common/torture/strub-indcall2.c          |   2 +-
 .../c-c++-common/torture/strub-indcall3.c          |   2 +-
 .../c-c++-common/torture/strub-inlinable1.c        |  16 +
 .../c-c++-common/torture/strub-inlinable2.c        |   7 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |  10 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |  19 +
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure3.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-pure4.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run1.c    |  10 +-
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run4.c    |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run4d.c   |   4 +-
 gcc/testsuite/g++.dg/torture/strub-init1.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init2.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init3.C         |   2 +-
 gcc/testsuite/gnat.dg/strub_attr.adb               |   2 +-
 libgcc/strub.c                                     |   2 +-
 79 files changed, 1117 insertions(+), 478 deletions(-)

diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 309291f0341..90769dff635 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -19,6 +19,18 @@ It can be enabled with the *-fzero-call-used-regs* command line
 option, to affect all subprograms in a compilation, and with a
 :samp:`Machine_Attribute` pragma, to affect only specific subprograms.
 
+.. code-block:: ada
+
+     procedure Foo;
+     pragma Machine_Attribute (Foo, "zero_call_used_regs", "used");
+     --  Before returning, Foo scrubs only call-clobbered registers
+     --  that it uses itself.
+
+     function Bar return Integer;
+     pragma Machine_Attribute (Bar, "zero_call_used_regs", "all");
+     --  Before returning, Bar scrubs all call-clobbered registers.
+
+
 For usage and more details on the command line option, and on the
 ``zero_call_used_regs`` attribute, see :title:`Using the GNU Compiler
 Collection (GCC)`.
@@ -35,6 +47,24 @@ It can be activated with the *-fstrub* command line option, to affect
 all (viable) subprograms in a compilation, and with a
 :samp:`Machine_Attribute` pragma.
 
+.. code-block:: ada
+
+     function Foo returns Integer;
+     pragma Machine_Attribute (Foo, "strub");
+     --  Foo and its callers are modified so as to scrub the stack
+     --  space used by Foo after it returns.
+
+     procedure Bar;
+     pragma Machine_Attribute (Bar, "strub", "internal");
+     --  Bar is turned into a wrapper for its original body,
+     --  and they scrub the stack used by the original body.
+
+     Var : Integer;
+     pragma Machine_Attribute (Var, "strub");
+     --  Reading from Var in a subprogram enables stack scrubbing
+     --  of the stack space used by the subprogram.
+
+
 For usage and more details on the command line option, and on the
 ``strub`` attribute, see :title:`Using the GNU Compiler Collection
 (GCC)`.
diff --git a/gcc/ada/gcc-interface/utils.c b/gcc/ada/gcc-interface/utils.c
index 6b20ddc93c9..1abf593d76b 100644
--- a/gcc/ada/gcc-interface/utils.c
+++ b/gcc/ada/gcc-interface/utils.c
@@ -39,6 +39,7 @@
 #include "varasm.h"
 #include "toplev.h"
 #include "opts.h"
+#include "ipa-strub.h"
 #include "output.h"
 #include "debug.h"
 #include "convert.h"
@@ -6611,6 +6612,7 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
+  bool compat_type = false;
   tree orig_fnptr_type = NULL_TREE;
   tree orig_args = args;
 
@@ -6624,68 +6626,36 @@ handle_strub_attribute (tree *node, tree name,
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      /* pragma Machine_Attribute turns string arguments into identifiers.
-	 Reverse it.  */
-      if (TREE_CODE (TREE_VALUE (args)) == IDENTIFIER_NODE)
+      switch (strub_validate_parm (TREE_VALUE (args)))
 	{
-	  gcc_checking_assert (IDENTIFIER_POINTER (TREE_VALUE (args))
-			       [IDENTIFIER_LENGTH (TREE_VALUE (args))] == 0);
-	  TREE_VALUE (args) = build_string
-	    (IDENTIFIER_LENGTH (TREE_VALUE (args)) + 1,
-	     IDENTIFIER_POINTER (TREE_VALUE (args)));
-	}
-
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
-	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
-
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
+	case 1:
+	  compat_type = true;
+	  /* Fall through.  */
 
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
+	  compat_type = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	  compat_type = true;
+	  /* Fall through.  */
+
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
@@ -6699,23 +6669,30 @@ handle_strub_attribute (tree *node, tree name,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   if (!*no_add_attrs)
     {
       *no_add_attrs = true;
       if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
 	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
+	{
+	  if (compat_type)
+	    *node = build_variant_type_copy (*node);
+	  else
+	    *node = build_distinct_type_copy (*node);
+	}
       TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
 					   TYPE_ATTRIBUTES (*node));
 
       if (orig_fnptr_type)
 	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
+	  tree fnptr_type = ((flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
+			     ? orig_fnptr_type
+			     : compat_type
+			     ? build_variant_type_copy (orig_fnptr_type)
+			     : build_distinct_type_copy (orig_fnptr_type));
 	  TREE_TYPE (fnptr_type) = *node;
 	  *node = fnptr_type;
 	}
diff --git a/gcc/attribs.c b/gcc/attribs.c
index 0d22c20a35e..acac634afb5 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "attribs.h"
 #include "fold-const.h"
+#include "ipa-strub.h"
 #include "stor-layout.h"
 #include "langhooks.h"
 #include "plugin.h"
@@ -1353,9 +1354,20 @@ comp_type_attributes (const_tree type1, const_tree type2)
   if ((lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type1)) != NULL)
       ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) != NULL))
     return 0;
+  int strub_ret = strub_comptypes (CONST_CAST_TREE (type1),
+				   CONST_CAST_TREE (type2));
+  if (strub_ret == 0)
+    return strub_ret;
   /* As some type combinations - like default calling-convention - might
      be compatible, we have to call the target hook to get the final result.  */
-  return targetm.comp_type_attributes (type1, type2);
+  int target_ret = targetm.comp_type_attributes (type1, type2);
+  if (target_ret == 0)
+    return target_ret;
+  if (strub_ret == 2 || target_ret == 2)
+    return 2;
+  if (strub_ret == 1 && target_ret == 1)
+    return 1;
+  gcc_unreachable ();
 }
 
 /* PREDICATE acts as a function of type:
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index dc4a4d4d200..2762dcdfcfe 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "common/common-target.h"
 #include "langhooks.h"
 #include "tree-inline.h"
+#include "ipa-strub.h"
 #include "toplev.h"
 #include "tree-iterator.h"
 #include "opts.h"
@@ -1306,6 +1307,7 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
+  bool compat_type = false;
   tree orig_fnptr_type = NULL_TREE;
   tree orig_args = args;
 
@@ -1319,57 +1321,36 @@ handle_strub_attribute (tree *node, tree name,
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
+      switch (strub_validate_parm (TREE_VALUE (args)))
 	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
+	case 1:
+	  compat_type = true;
+	  /* Fall through.  */
 
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
-
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
+	  compat_type = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	  compat_type = true;
+	  /* Fall through.  */
+
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
@@ -1383,23 +1364,30 @@ handle_strub_attribute (tree *node, tree name,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   if (!*no_add_attrs)
     {
       *no_add_attrs = true;
       if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
 	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
+	{
+	  if (compat_type)
+	    *node = build_variant_type_copy (*node);
+	  else
+	    *node = build_distinct_type_copy (*node);
+	}
       TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
 					   TYPE_ATTRIBUTES (*node));
 
       if (orig_fnptr_type)
 	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
+	  tree fnptr_type = ((flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
+			     ? orig_fnptr_type
+			     : compat_type
+			     ? build_variant_type_copy (orig_fnptr_type)
+			     : build_distinct_type_copy (orig_fnptr_type));
 	  TREE_TYPE (fnptr_type) = *node;
 	  *node = fnptr_type;
 	}
diff --git a/gcc/common.opt b/gcc/common.opt
index 71e0c971344..ef018ebf735 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,13 +2687,22 @@ fstrict-overflow
 Common
 Treat signed overflow as undefined.  Negated as -fwrapv -fwrapv-pointer.
 
-; If any strub-enabling attribute is seen when the default value is
-; selected, it's bumped up to -1.  The scrub mode gate function will
-; then bump -2 to 0 if no strub-enabling attribute is seen.  This
-; minimizes the strub overhead.
-fstrub=default
-Common RejectNegative Var(flag_strub, -2) Init(-2)
-Enable stack scrub as requested through attributes.
+fstrub=disable
+Common RejectNegative Var(flag_strub, 0)
+Disable stack scrub entirely, disregarding strub attributes.
+
+fstrub=strict
+Common RejectNegative Var(flag_strub, -4)
+Enable stack scrub as per attributes, with strict call checking.
+
+; If any strub-enabling attribute is seen when the default or strict
+; initializer values are in effect, flag_strub is bumped up by 2.  The
+; scrub mode gate function will then bump these initializer values to
+; 0 if no strub-enabling attribute is seen.  This minimizes the strub
+; overhead.
+fstrub=relaxed
+Common RejectNegative Var(flag_strub, -3) Init(-3)
+Restore default strub mode: as per attributes, with relaxed checking.
 
 fstrub=all
 Common RejectNegative Var(flag_strub, 3)
@@ -2707,10 +2716,6 @@ fstrub=internal
 Common RejectNegative Var(flag_strub, 2)
 Enable internal stack scrubbing for all viable functions.
 
-fstrub=disable
-Common RejectNegative Var(flag_strub, 0)
-Disable stack scrub entirely, disregarding strub attributes.
-
 fsync-libcalls
 Common Var(flag_sync_libcalls) Init(1)
 Implement __atomic operations via libcalls to legacy __sync functions.
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 1fcf60a3875..46cefe27fa5 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -8729,51 +8729,259 @@ pid_t wait (wait_status_ptr_t p)
 @item strub
 @cindex @code{strub} type attribute
 This attribute defines stack-scrubbing properties of functions and
-variables.  When applied to function types, it takes an optional
-argument.  When applied to a pointer-to-function type, it gets
-propagated to the function type if the optional argument is given.
-
-A function whose type is annotated with @code{at-calls} @code{strub}
-mode (@code{strub("at-calls")}, @code{strub(1)}, or @code{strub})
-undergoes interface changes.  Its callers are adjusted to match the
-changes, and to automatically scrub (overwrite with zeros) the stack
-space used by the function.
-
-A function whose type indicates @code{internal} @code{strub} mode
-(@code{strub("internal")} or @code{strub(2)}) retains an unmodified
-interface, but may be turned into a wrapper that calls the wrapped body
-using a custom interface.  The wrapper then scrubs the stack space used
-by the wrapped body.
-
-An automatically-allocated variable whose type carries the @code{strub}
-attribute causes the enclosing function to have @code{strub} enabled.
-
-A statically-allocated variable whose type carries the @code{strub}
-attribute causes functions that @emph{read} it with that type to have
-@code{strub} enabled.  Reading from data referenced by pointers to such
-types has the same effect.  Note: The attribute does not carry over from
-a composite type to the types of its components, so the intended effect
-may not be obtained with non-scalar types.
-
-A @code{strub} context is the body of a function that has strub enabled,
-be it explicitly, by @code{at-calls} or @code{internal} mode, or
-implicitly, by reading from a @code{strub}-marked data type.
+variables.  Being a type attribute, it attaches to types, even when
+specified in function and variable declarations.  When applied to
+function types, it takes an optional string argument.  When applied to a
+pointer-to-function type, if the optional argument is given, it gets
+propagated to the function type.
+
+@smallexample
+/* A strub variable.  */
+int __attribute__ ((strub)) var;
+/* A strub variable that happens to be a pointer.  */
+__attribute__ ((strub)) int *strub_ptr_to_int;
+/* A pointer type that may point to a strub variable.  */
+typedef int __attribute__ ((strub)) *ptr_to_strub_int_type;
+
+/* A declaration of a strub function.  */
+extern int __attribute__ ((strub)) foo (void);
+/* A pointer to that strub function.  */
+int __attribute__ ((strub ("at-calls"))) (*ptr_to_strub_fn)(void) = foo;
+@end smallexample
+
+A function associated with @code{at-calls} @code{strub} mode
+(@code{strub("at-calls")}, or just @code{strub}) undergoes interface
+changes.  Its callers are adjusted to match the changes, and to scrub
+(overwrite with zeros) the stack space used by the called function after
+it returns.  The interface change makes the function type incompatible
+with an unadorned but otherwise equivalent type, so @emph{every}
+declaration and every type that may be used to call the function must be
+associated with this strub mode.
+
+A function associated with @code{internal} @code{strub} mode
+(@code{strub("internal")}) retains an unmodified, type-compatible
+interface, but it may be turned into a wrapper that calls the wrapped
+body using a custom interface.  The wrapper then scrubs the stack space
+used by the wrapped body.  Though the wrapped body has its stack space
+scrubbed, the wrapper does not, so arguments and return values may
+remain unscrubbed even when such a function is called by another
+function that enables @code{strub}.  This is why, when compiling with
+@samp{-fstrub=strict}, a @code{strub} context is not allowed to call
+@code{internal} @code{strub} functions.
+
+@smallexample
+/* A declaration of an internal-strub function.  */
+extern int __attribute__ ((strub ("internal"))) bar (void);
+
+int __attribute__ ((strub))
+baz (void)
+@{
+  /* Ok, foo was declared above as an at-calls strub function.  */
+  foo ();
+  /* Not allowed in strict mode, otherwise allowed.  */
+  bar ();
+@}
+@end smallexample
+
+An automatically-allocated variable associated with the @code{strub}
+attribute causes the (immediately) enclosing function to have
+@code{strub} enabled.
+
+A statically-allocated variable associated with the @code{strub}
+attribute causes functions that @emph{read} it, through its @code{strub}
+data type, to have @code{strub} enabled.  Reading data by dereferencing
+a pointer to a @code{strub} data type has the same effect.  Note: The
+attribute does not carry over from a composite type to the types of its
+components, so the intended effect may not be obtained with non-scalar
+types.
+
+When selecting a @code{strub}-enabled mode for a function that is not
+explicitly associated with one, because of @code{strub} variables or
+data pointers, the function must satisfy @code{internal} mode viability
+requirements (see below), even when @code{at-calls} mode is also viable
+and, being more efficient, ends up selected as an optimization.
+
+@smallexample
+/* zapme is implicitly strub-enabled because of strub variables.
+   Optimization may change its strub mode, but not the requirements.  */
+static int
+zapme (int i)
+@{
+  /* A local strub variable enables strub.  */
+  int __attribute__ ((strub)) lvar;
+  /* Reading strub data through a pointer-to-strub enables strub.  */
+  lvar = * (ptr_to_strub_int_type) &i;
+  /* Writing to a global strub variable does not enable strub.  */
+  var = lvar;
+  /* Reading from a global strub variable enables strub.  */
+  return var;
+@}
+@end smallexample
+
+A @code{strub} context is the body (as opposed to the interface) of a
+function that has @code{strub} enabled, be it explicitly, by
+@code{at-calls} or @code{internal} mode, or implicitly, due to
+@code{strub} variables or command-line options.
 
 A function of a type associated with the @code{disabled} @code{strub}
-mode (@code{strub("disabled")}, @code{strub(0)}, or no @code{strub} mode
-specified) will not have its own stack space scrubbed, and it cannot be
-called from @code{strub} contexts.
+mode (@code{strub("disabled")} will not have its own stack space
+scrubbed.  Such functions @emph{cannot} be called from within
+@code{strub} contexts.
+
+In order to enable a function to be called from within @code{strub}
+contexts without having its stack space scrubbed, associate it with the
+@code{callable} @code{strub} mode (@code{strub("callable")}).
+
+When a function is not assigned a @code{strub} mode, explicitly or
+implicitly, the mode defaults to @code{callable}, except when compiling
+with @samp{-fstrub=strict}, that causes @code{strub} mode to default to
+@code{disabled}.
+
+@example
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+ /* Implicitly disabled with -fstrub=strict, otherwise callable.  */
+extern int bah (void);
+
+int __attribute__ ((strub))
+bal (void)
+@{
+  /* Not allowed, bad is not strub-callable.  */
+  bad ();
+  /* Ok, bac is strub-callable.  */
+  bac ();
+  /* Not allowed with -fstrub=strict, otherwise allowed.  */
+  bah ();
+@}
+@end example
 
-A function that does not have @code{strub} enabled can only be called
-from within @code{strub} contexts through a function type marked with
-the @code{callable} @code{strub} mode (@code{strub("callable")} or
-@code{strub(3)}).
+Function types marked @code{callable} and @code{disabled} are not
+mutually compatible types, but the underlying interfaces are compatible,
+so it is safe to convert pointers between them, and to use such pointers
+or alternate declarations to call them.  Interfaces are also
+interchangeable between them and @code{internal} (but not
+@code{at-calls}!), but adding @code{internal} to a pointer type will not
+cause the pointed-to function to perform stack scrubbing.
+
+@example
+void __attribute__ ((strub))
+bap (void)
+@{
+  /* Assign a callable function to pointer-to-disabled.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bac;
+  /* Not allowed: calls disabled type in a strub context.  */
+  d_p ();
+
+  /* Assign a disabled function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bad;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an internal function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  c_p = bar;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an at-calls function to pointer-to-callable.
+     Flaggged as incompatible.  */
+  c_p = bal;
+  /* The call through an interface-incompatible type will not use the
+     modified interface expected by the at-calls function, so it is
+     likely to misbehave at runtime.  */
+  c_p ();
+@}
+@end example
 
 @code{Strub} contexts are never inlined into non-@code{strub} contexts.
-When an @code{internal}-strub function is split, the wrapper can often
-be inlined, but the wrapped body never is.  Functions marked as
-@code{always_inline}, even if explicitly assigned @code{internal} strub
-mode, will not undergo wrapping, so their body gets inlined.
+When an @code{internal}-strub function is split up, the wrapper can
+often be inlined, but the wrapped body @emph{never} is.  A function
+marked as @code{always_inline}, even if explicitly assigned
+@code{internal} strub mode, will not undergo wrapping, so its body gets
+inlined as required.
+
+@example
+inline int __attribute__ ((strub ("at-calls")))
+inl_atc (void)
+@{
+  /* This body may get inlined into strub contexts.  */
+@}
+
+inline int __attribute__ ((strub ("internal")))
+inl_int (void)
+@{
+  /* This body NEVER gets inlined, though its wrapper may.  */
+@}
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+@{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+@}
+
+void __attribute__ ((strub ("disabled")))
+bat (void)
+@{
+  /* Not allowed, cannot inline into a non-strub context.  */
+  inl_int_ali ();
+@}
+@end example
+
+@cindex strub eligibility and viability
+Some @samp{-fstrub=*} command line options enable @code{strub} modes
+implicitly where viable.  A @code{strub} mode is only viable for a
+function if the function is eligible for that mode, and if other
+conditions, detailed below, are satisfied.  If it's not eligible for a
+mode, attempts to explicitly associate it with that mode are rejected
+with an error message.  If it is eligible, that mode may be assigned
+explicitly through this attribute, but implicit assignment through
+command-line options may involve additional viability requirements.
+
+A function is ineligible for @code{at-calls} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+@code{At-calls} @code{strub} mode, if not requested through the function
+type, is only viable for an eligible function if the function is not
+visible to other translation units, and if it doesn't have its address
+taken.
+
+@smallexample
+/* bar is eligible for at-calls strub mode,
+   but not viable for that mode because it is visible to other units.
+   It is eligible and viable for internal strub mode.  */
+void bav () @{@}
+
+/* setp is eligible for at-calls strub mode,
+   but not viable for that mode because its address is taken.
+   It is eligible and viable for internal strub mode.  */
+void setp (void) @{ static void (*p)(void); = setp; @}
+@end smallexample
+
+A function is ineligible for @code{internal} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, or if attribute
+@code{noipa} is present.  For an @code{always_inline} function, meeting
+these requirements is enough to make it eligible.  Any function that has
+attribute @code{noclone}, that uses such extensions as non-local labels,
+computed gotos, alternate variable argument passing interfaces,
+@code{__builtin_next_arg}, or @code{__builtin_return_address}, or that
+takes too many (about 64Ki) arguments is ineligible, unless it is
+@code{always_inline}.  For @code{internal} @code{strub} mode, all
+eligible functions are viable.
+
+@smallexample
+/* flop is not eligible, thus not viable, for at-calls strub mode.
+   Likewise for internal strub mode.  */
+__attribute__ ((noipa)) void flop (void) @{@}
+
+/* flip is eligible and viable for at-calls strub mode.
+   It would be ineligible for internal strub mode, because of noclone,
+   if it weren't for always_inline.  With always_inline, noclone is not
+   an obstacle, so it is also eligible and viable for internal strub mode.  */
+inline __attribute__ ((noclone, always_inline)) void flip (void) @{@}
+@end smallexample
 
 @item unused
 @cindex @code{unused} type attribute
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 838d9ff1198..dd529c1a82e 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,7 +599,8 @@ Objective-C and Objective-C++ Dialects}.
 -fstack-protector-explicit  -fstack-check @gol
 -fstack-limit-register=@var{reg}  -fstack-limit-symbol=@var{sym} @gol
 -fno-stack-limit  -fsplit-stack @gol
--fstrub=default -fstrub=disable -fstrub=at-calls -fstrub=internal -fstrub=all @gol
+-fstrub=disable -fstrub=strict -fstrub=relaxed @gol
+-fstrub=all -fstrub=at-calls -fstrub=internal @gol
 -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
 -fvtv-counts  -fvtv-debug @gol
 -finstrument-functions @gol
@@ -15513,46 +15514,41 @@ without @option{-fsplit-stack} always has a large stack.  Support for
 this is implemented in the gold linker in GNU binutils release 2.21
 and later.
 
-@item -fstrub=default
-@opindex fstrub=default
-Restore the default stack scrub (@code{strub}) setting, namely,
-@code{strub} is only enabled as required by @code{strub} attributes
-associated with function or variable types.  This is only useful to
-override earlier @samp{-fstrub=*} options.
-
 @item -fstrub=disable
 @opindex -fstrub=disable
 Disable stack scrubbing entirely, ignoring any @code{strub} attributes.
+See @xref{Common Type Attributes}.
+
+@item -fstrub=strict
+@opindex fstrub=strict
+Functions default to @code{strub} mode @code{disabled}, and apply
+@code{strict}ly the restriction that only functions associated with
+@code{strub}-@code{calalble} modes (@code{at-calls}, @code{callable} and
+@code{always_inline} @code{internal}) are @code{callable} by functions
+with @code{strub}-enabled modes (@code{at-calls} and @code{internal}).
+
+@item -fstrub=relaxed
+@opindex fstrub=relaxed
+Restore the default stack scrub (@code{strub}) setting, namely,
+@code{strub} is only enabled as required by @code{strub} attributes
+associated with function and data types.  @code{Relaxed} means that
+strub contexts are only prevented from calling functions explicitly
+associated with @code{strub} mode @code{disabled}.  This option is only
+useful to override other @samp{-fstrub=*} options that precede it in the
+command line.
 
 @item -fstrub=at-calls
 @opindex fstrub=at-calls
-Enable @code{at-calls} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{at-calls} @code{strub} if a different @code{strub}
-mode is explicitly requested, if attribute @code{noipa} is present, or
-if it calls @code{__builtin_apply_args}.  @code{At-calls} @code{strub}
-mode, if not requested through the function type, is only viable for an
-eligible function if the function is not visible to other translation
-units, and it doesn't have its address taken.
+Enable @code{at-calls} @code{strub} mode where viable.
 
 @item -fstrub=internal
 @opindex fstrub=internal
-Enable @code{internal} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{internal} @code{strub} if a different @code{strub}
-mode is explicitly requested, or if attribute @code{noipa} is present.
-Non-@code{always_inline} functions also become ineligible if attribute
-@code{noclone} is present, if the function uses such features as user
-labels, non-default variable argument interfaces,
-@code{__builtin_next_arg}, or @code{__builtin_return_address}, or if
-they have too many (about 64Ki) arguments.  For @code{internal}
-@code{strub}, all eligible functions are viable.
+Enable @code{internal} @code{strub} mode where viable.
 
 @item -fstrub=all
 @opindex fstrub=all
-Enable @code{strub} for all viable functions, and consider non-viable
-functions as @code{callable}.  When both strub modes are viable,
-@code{at-calls} is preferred.
+Enable some @code{strub} mode where viable.
+When both strub modes are viable, @code{at-calls} is preferred.
 
 @item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 @opindex fvtable-verify
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 7f4bc44d2bb..ee360b6c6ca 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -397,7 +397,7 @@ can_inline_edge_p (struct cgraph_edge *e, bool report,
       e->inline_failed = CIF_SANITIZE_ATTRIBUTE_MISMATCH;
       inlinable = false;
     }
-  if (!strub_inlinable_p (callee, caller))
+  if (!strub_inlinable_from_p (callee, caller))
     {
       e->inline_failed = CIF_UNSPECIFIED;
       inlinable = false;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
index cf7d811779e..aff80eb5f0d 100644
--- a/gcc/ipa-strub.c
+++ b/gcc/ipa-strub.c
@@ -159,12 +159,16 @@ enum strub_mode {
 
 };
 
+/* Look up a strub attribute in TYPE, and return it.  */
+
 static tree
 get_strub_attr_from_type (tree type)
 {
   return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
 }
 
+/* Look up a strub attribute in DECL or in its type, and return it.  */
+
 static tree
 get_strub_attr_from_decl (tree decl)
 {
@@ -174,23 +178,159 @@ get_strub_attr_from_decl (tree decl)
   return get_strub_attr_from_type (TREE_TYPE (decl));
 }
 
-tree
+/* Define a function to cache identifier ID, to be used as a strub attribute
+   parameter for a strub mode named after NAME.  */
+#define DEF_STRUB_IDS(NAME, ID)				\
+static inline tree get_strub_mode_id_ ## NAME () {	\
+  static tree identifier = NULL_TREE;			\
+  if (!identifier)					\
+    identifier = get_identifier (ID);			\
+  return identifier;					\
+}
+/* Same as DEF_STRUB_IDS, but use the string expansion of NAME as ID.  */
+#define DEF_STRUB_ID(NAME)				\
+DEF_STRUB_IDS (NAME, #NAME)
+
+/* Define functions for each of the strub mode identifiers.
+   Expose dashes rather than underscores.  */
+DEF_STRUB_ID (disabled)
+DEF_STRUB_IDS (at_calls, "at-calls")
+DEF_STRUB_ID (internal)
+DEF_STRUB_ID (callable)
+DEF_STRUB_ID (wrapped)
+DEF_STRUB_ID (wrapper)
+DEF_STRUB_ID (inlinable)
+DEF_STRUB_IDS (at_calls_opt, "at-calls-opt")
+
+/* Release the temporary macro names.  */
+#undef DEF_STRUB_IDS
+#undef DEF_STRUB_ID
+
+/* Return the identifier corresponding to strub MODE.  */
+
+static tree
+get_strub_mode_attr_parm (enum strub_mode mode)
+{
+  switch (mode)
+    {
+    case STRUB_DISABLED:
+      return get_strub_mode_id_disabled ();
+
+    case STRUB_AT_CALLS:
+      return get_strub_mode_id_at_calls ();
+
+    case STRUB_INTERNAL:
+      return get_strub_mode_id_internal ();
+
+    case STRUB_CALLABLE:
+      return get_strub_mode_id_callable ();
+
+    case STRUB_WRAPPED:
+      return get_strub_mode_id_wrapped ();
+
+    case STRUB_WRAPPER:
+      return get_strub_mode_id_wrapper ();
+
+    case STRUB_INLINABLE:
+      return get_strub_mode_id_inlinable ();
+
+    case STRUB_AT_CALLS_OPT:
+      return get_strub_mode_id_at_calls_opt ();
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+/* Return the parmeters (TREE_VALUE) for a strub attribute of MODE.
+   We know we use a single parameter, so we bypass the creation of a
+   tree list.  */
+
+static tree
 get_strub_mode_attr_value (enum strub_mode mode)
 {
-  return tree_cons (NULL_TREE,
-		    build_int_cst (integer_type_node, (int)mode),
-		    NULL_TREE);
+  return get_strub_mode_attr_parm (mode);
+}
 
-#if 0 /* ??? use symbolic mode names with interned strings?  */
-  char *s = NULL;
+/* Determine whether ID is a well-formed strub mode-specifying attribute
+   parameter.  Only user-visible modes are accepted.
+
+   For unacceptable parms, return 0, otherwise a nonzero value as below.
+
+   If the parm enables strub, return positive, otherwise negative.
+
+   If the affected type must be a distinct, incompatible type,return an integer
+   of absolute value 2, otherwise 1.  */
+
+int
+strub_validate_parm (tree id)
+{
+  int ret;
 
-  switch (strub_mode)
+  if (!id)
+    return true;
+
+  const char *s = NULL;
+  size_t len = 0;
+
+  if (TREE_CODE (id) == STRING_CST)
     {
-      
+      s = TREE_STRING_POINTER (id);
+      len = TREE_STRING_LENGTH (id) - 1;
     }
-#endif
+  else if (TREE_CODE (id) == IDENTIFIER_NODE)
+    {
+      s = IDENTIFIER_POINTER (id);
+      len = IDENTIFIER_LENGTH (id);
+    }
+  else
+    return false;
+
+  enum strub_mode mode;
+
+  if (len != 8)
+    return false;
+
+  switch (s[0])
+    {
+    case 'd':
+      mode = STRUB_DISABLED;
+      ret = -1;
+      break;
+
+    case 'a':
+      mode = STRUB_AT_CALLS;
+      ret = 2;
+      break;
+
+    case 'i':
+      mode = STRUB_INTERNAL;
+      ret = 1;
+      break;
+
+    case 'c':
+      mode = STRUB_CALLABLE;
+      ret = -2;
+      break;
+
+    default:
+      /* Other parms are for internal use only.  */
+      return 0;
+    }
+
+  tree mode_id = get_strub_mode_attr_parm (mode);
+
+  if (TREE_CODE (id) == IDENTIFIER_NODE
+      ? id == mode_id
+      : strncmp (s, IDENTIFIER_POINTER (mode_id), len))
+    return 0;
+
+  return ret;
 }
 
+/* Return the strub mode from STRUB_ATTR.  VAR_P should be true if the attribute
+   is taken from a variable, rather than from a function, or a type thereof.  */
+
 static enum strub_mode
 get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
 {
@@ -200,28 +340,102 @@ get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
     {
       if (!TREE_VALUE (strub_attr))
 	mode = !var_p ? STRUB_AT_CALLS : STRUB_INTERNAL;
-      else if (TREE_CODE (TREE_VALUE (TREE_VALUE (strub_attr))) == INTEGER_CST)
-	mode = (enum strub_mode) tree_to_shwi (TREE_VALUE
-					       (TREE_VALUE (strub_attr)));
-      else /* Handlers convert symbolic mode names to INTEGER_CST.  */
-	gcc_unreachable ();
+      else
+	{
+	  gcc_checking_assert (!var_p);
+	  tree id = TREE_VALUE (strub_attr);
+	  if (TREE_CODE (id) == TREE_LIST)
+	    id = TREE_VALUE (id);
+	  const char *s = (TREE_CODE (id) == STRING_CST
+			   ? TREE_STRING_POINTER (id)
+			   : IDENTIFIER_POINTER (id));
+	  size_t len = (TREE_CODE (id) == STRING_CST
+			? TREE_STRING_LENGTH (id) - 1
+			: IDENTIFIER_LENGTH (id));
+
+	  switch (len)
+	    {
+	    case 7:
+	      switch (s[6])
+		{
+		case 'r':
+		  mode = STRUB_WRAPPER;
+		  break;
+
+		case 'd':
+		  mode = STRUB_WRAPPED;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 8:
+	      switch (s[0])
+		{
+		case 'd':
+		  mode = STRUB_DISABLED;
+		  break;
+
+		case 'a':
+		  mode = STRUB_AT_CALLS;
+		  break;
+
+		case 'i':
+		  mode = STRUB_INTERNAL;
+		  break;
+
+		case 'c':
+		  mode = STRUB_CALLABLE;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 9:
+	      mode = STRUB_INLINABLE;
+	      break;
+
+	    case 12:
+	      mode = STRUB_AT_CALLS_OPT;
+	      break;
+
+	    default:
+	      gcc_unreachable ();
+	    }
+
+	  gcc_checking_assert (TREE_CODE (id) == IDENTIFIER_NODE
+			       ? id == get_strub_mode_attr_parm (mode)
+			       : strncmp (IDENTIFIER_POINTER
+					  (get_strub_mode_attr_parm (mode)),
+					  s, len) == 0);
+	}
     }
 
   return mode;
 }
 
+/* Look up, decode and return the strub mode associated with FNDECL.  */
+
 static enum strub_mode
-get_strub_mode_from_decl (tree fndecl)
+get_strub_mode_from_fndecl (tree fndecl)
 {
   return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
 }
 
+/* Look up, decode and return the strub mode associated with NODE.  */
+
 static enum strub_mode
 get_strub_mode (cgraph_node *node)
 {
-  return get_strub_mode_from_decl (node->decl);
+  return get_strub_mode_from_fndecl (node->decl);
 }
 
+/* Look up, decode and return the strub mode associated with TYPE.  */
+
 static enum strub_mode
 get_strub_mode_from_type (tree type)
 {
@@ -231,12 +445,15 @@ get_strub_mode_from_type (tree type)
   if (attr)
     return get_strub_mode_from_attr (attr, var_p);
 
-  if (flag_strub > 0 && !var_p)
+  if (flag_strub >= -1 && !var_p)
     return STRUB_CALLABLE;
 
   return STRUB_DISABLED;
 }
 
+\f
+/* Return true iff NODE calls builtin va_start.  */
+
 static bool
 calls_builtin_va_start_p (cgraph_node *node)
 {
@@ -252,6 +469,8 @@ calls_builtin_va_start_p (cgraph_node *node)
   return result;
 }
 
+/* Return true iff NODE calls builtin apply_args, and optionally REPORT it.  */
+
 static bool
 calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
 {
@@ -276,12 +495,17 @@ calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE carries the always_inline attribute.  */
+
 static inline bool
 strub_always_inline_p (cgraph_node *node)
 {
   return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
 }
 
+/* Return true iff NODE is potentially eligible for any strub-enabled mode, and
+   optionally REPORT the reasons for ineligibility.  */
+
 static inline bool
 can_strub_p (cgraph_node *node, bool report = false)
 {
@@ -321,6 +545,10 @@ can_strub_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE is eligible for at-calls strub, and optionally REPORT
+   the reasons for ineligibility.  Besides general non-eligibility for
+   strub-enabled modes, at-calls rules out calling builtin apply_args.  */
+
 static bool
 can_strub_at_calls_p (cgraph_node *node, bool report = false)
 {
@@ -332,6 +560,9 @@ can_strub_at_calls_p (cgraph_node *node, bool report = false)
   return !calls_builtin_apply_args_p (node, report);
 }
 
+/* Symbolic macro for the max number of arguments that internal strub may add to
+   a function.  */
+
 #define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
 
 /* We can't perform internal strubbing if the function body involves certain
@@ -438,14 +669,12 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 			   || tree_versionable_function_p (node->decl));
 
 
-      /* Label values referenced are not preserved when copying.  If referenced
+      /* Label values references are not preserved when copying.  If referenced
 	 in nested functions, as in 920415-1.c and 920721-4.c their decls get
-	 remapped independently.  That might be too broad, in that we might be
-	 able to support correctly cases in which the labels are only used
-	 internally in a function, but disconnecting user labels from their
-	 original declarations is undesirable in general, and it probably
-	 doesn't matter, since explicitly-requested strub likely uses
-	 STRUB_AT_CALLS mode anyway.  */
+	 remapped independently.  The exclusion below might be too broad, in
+	 that we might be able to support correctly cases in which the labels
+	 are only used internally in a function, but disconnecting forced labels
+	 from their original declarations is undesirable in general.  */
       basic_block bb;
       FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
 	for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -459,8 +688,6 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 
 	    target = gimple_label_label (label_stmt);
 
-	    /* Make an edge to every label block that has been marked as a
-	       potential target for a computed goto or a non-local goto.  */
 	    if (!FORCED_LABEL (target))
 	      continue;
 
@@ -470,7 +697,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 forced labels");
 	  }
     }
 
@@ -491,6 +718,9 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return TRUE iff NODE has any strub-requiring local variable, or accesses (as
+   in reading) any variable through a strub-requiring type.  */
+
 static bool
 strub_from_body_p (cgraph_node *node)
 {
@@ -506,7 +736,9 @@ strub_from_body_p (cgraph_node *node)
 	!= STRUB_DISABLED)
       return true;
 
-  /* Now scan the body for loads with strub types.  */
+  /* Now scan the body for loads with strub-requiring types.
+     ??? Compound types don't propagate the strub requirement to
+     component types.  */
   basic_block bb;
   FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
     for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -528,6 +760,7 @@ strub_from_body_p (cgraph_node *node)
 
 /* Return true iff node is associated with a builtin that should be callable
    from strub contexts.  */
+
 static inline bool
 strub_callable_builtin_p (cgraph_node *node)
 {
@@ -568,17 +801,23 @@ strub_callable_builtin_p (cgraph_node *node)
     }
 }
 
+/* Compute the strub mode to be used for NODE.  STRUB_ATTR should be the strub
+   attribute,found for NODE, if any.  */
+
 static enum strub_mode
 compute_strub_mode (cgraph_node *node, tree strub_attr)
 {
   enum strub_mode req_mode = get_strub_mode_from_attr (strub_attr);
 
-  gcc_checking_assert (flag_strub >= -1 && flag_strub <= 3);
+  gcc_checking_assert (flag_strub >= -2 && flag_strub <= 3);
 
   /* Symbolic encodings of the -fstrub-* flags.  */
   /* Enable strub when explicitly requested through attributes to functions or
      variables, reporting errors if the requests cannot be satisfied.  */
   const bool strub_flag_auto = flag_strub < 0;
+  /* strub_flag_auto with strub call verification; without this, functions are
+     implicitly callable.  */
+  const bool strub_flag_strict = flag_strub < -1;
   /* Disable strub altogether, ignore attributes entirely.  */
   const bool strub_flag_disabled = flag_strub == 0;
   /* On top of _auto, also enable strub implicitly for functions that can
@@ -625,7 +864,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (!strub_flag_disabled
        && (strub_attr
 	   ? req_mode == STRUB_CALLABLE
-	   : (strub_flag_viable
+	   : (!strub_flag_strict
 	      || strub_callable_builtin_p (node))));
 
   /* This is a shorthand for either strub-enabled mode.  */
@@ -674,19 +913,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (at_calls_eligible
        && (strub_attr
 	   || (node->has_gimple_body_p ()
-#if 0 /* We no longer use collect_callers, so we can probably drop it.  */
-	       && node->get_availability () > AVAIL_INTERPOSABLE
-#endif
-	       && ((!node->externally_visible
-#if 0
-		    /* We wish to bypass the test below for functions that are
-		       not externally visible, but that's a little too broad: we
-		       do not wish to skip them for e.g. gnu_inline
-		       functions.  */
-		    && !TREE_PUBLIC (node->decl)
-		    && !DECL_EXTERNAL (node->decl)
-#endif
-		    )
+	       && (!node->externally_visible
 		   || (node->binds_to_current_def_p ()
 		       && node->can_be_local_p ()))
 	       && node->only_called_directly_p ())));
@@ -737,10 +964,6 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
   const enum strub_mode mode
     = ((strub_enable && is_always_inline)
        ? (strub_required ? STRUB_INLINABLE : STRUB_CALLABLE)
-#if 0
-       : (!strub_enable && strub_required && strub_attr)
-       ? req_mode
-#endif
        : (strub_enable && internal_viable
 	  && (strub_flag_internal || !at_calls_viable))
        ? STRUB_INTERNAL
@@ -802,6 +1025,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 /* Set FNDT's strub mode to MODE; FNDT may be a function decl or
    function type.  If OVERRIDE, do not check whether a mode is already
    set.  */
+
 static void
 strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
 {
@@ -828,12 +1052,18 @@ strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
   *attrp = attr;
 }
 
+/* Set FNDT's strub mode to callable.
+   FNDT may be a function decl or a function type.  */
+
 void
 strub_make_callable (tree fndt)
 {
   strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
 }
 
+/* Set NODE to strub MODE.  Report incompatibilities between MODE and the mode
+   requested through explicit attributes, and cases of non-eligibility.  */
+
 static void
 set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
 {
@@ -906,6 +1136,8 @@ set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
   strub_set_fndt_mode_to (node->decl, mode, attr);
 }
 
+/* Compute and set NODE's strub mode.  */
+
 static void
 set_strub_mode (cgraph_node *node)
 {
@@ -946,6 +1178,7 @@ set_strub_mode (cgraph_node *node)
   set_strub_mode_to (node, mode);
 }
 
+\f
 /* Non-strub functions shouldn't be called from within strub contexts,
    except through callable ones.  Always inline strub functions can
    only be called from strub functions.  */
@@ -984,7 +1217,7 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_AT_CALLS_OPT:
     case STRUB_INTERNAL:
     case STRUB_WRAPPER:
-      return (flag_strub >= 0);
+      return (flag_strub >= -1);
 
     case STRUB_DISABLED:
       return false;
@@ -1004,7 +1237,7 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
    strubbed functions into non-strubbed ones.  */
 
 bool
-strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+strub_inlinable_from_p (cgraph_node *callee, cgraph_node *caller)
 {
   strub_mode callee_mode = get_strub_mode (callee);
 
@@ -1049,14 +1282,43 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
   return false;
 }
 
+/* Check that types T1 and T2 are strub-compatible.  Return 1 if the strub modes
+   are the same, 2 if they are interchangeable, and 0 otherwise.  */
+
+int
+strub_comptypes (tree t1, tree t2)
+{
+  enum strub_mode m1 = get_strub_mode_from_type (t1);
+  enum strub_mode m2 = get_strub_mode_from_type (t2);
+
+  if (m1 == m2)
+    return 1;
+
+  /* We're dealing with types, so only strub modes that can be selected by
+     attributes in the front end matter.  If either mode is at-calls, the
+     conversion is not compatible.  */
+  if (m1 == STRUB_AT_CALLS || m2 == STRUB_AT_CALLS)
+    return 0;
+
+  return 2;
+}
+
 /* Check that strub functions don't call non-strub functions, and that
    always_inline strub functions are only called by strub
    functions.  */
+
 static void
 verify_strub ()
 {
   cgraph_node *node;
 
+  /* It's expected that check strub-wise pointer type compatibility of variables
+     and of functions is already taken care of by front-ends, on account of the
+     attribute's being marked as affecting type identity and of the creation of
+     distinct types.  */
+
+  /* Check that call targets in strub contexts have strub-callable types.  */
+
   FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
   {
     enum strub_mode caller_mode = get_strub_mode (node);
@@ -1105,14 +1367,11 @@ verify_strub ()
 	  }
       }
   }
-
-  /* ??? Check strub-wise pointer type compatibility of variables and
-     functions, or is this already taken care of on account of the
-     attribute's being marked as affecting type identity?  */
 }
 
 namespace {
 
+/* Define a pass to compute strub modes.  */
 const pass_data pass_data_ipa_strub_mode = {
   SIMPLE_IPA_PASS,
   "strubm",
@@ -1133,18 +1392,20 @@ public:
   {}
   opt_pass *clone () { return new pass_ipa_strub_mode (m_ctxt); }
   virtual bool gate (function *) {
-    /* In the default setting, the attribute handler changes
-       flag_strub to -1 if any strub-enabling occurence of the
-       attribute is found.  If it remains at -2, nothing that would
-       enable strub was found, so we can disable it and avoid the
+    /* In relaxed (-3) and strict (-4) settings, that only enable strub at a
+       function or variable attribute's request, the attribute handler changes
+       flag_strub to -1 or -2, respectively, if any strub-enabling occurence of
+       the attribute is found.  Therefore, if it remains at -3 or -4, nothing
+       that would enable strub was found, so we can disable it and avoid the
        overhead.  */
-    if (flag_strub == -2)
+    if (flag_strub < -2)
       flag_strub = 0;
     return flag_strub;
   }
   virtual unsigned int execute (function *);
 };
 
+/* Define a pass to introduce strub transformations.  */
 const pass_data pass_data_ipa_strub = {
   SIMPLE_IPA_PASS,
   "strub",
@@ -1170,6 +1431,7 @@ public:
   virtual bool gate (function *) { return flag_strub; }
   virtual unsigned int execute (function *);
 
+  /* Define on demand and cache some types we use often.  */
 #define DEF_TYPE(NAME, INIT)			\
   static inline tree get_ ## NAME () {		\
     static tree type = NULL_TREE;		\
@@ -1202,6 +1464,7 @@ public:
 
 #undef DEF_TYPE
 
+  /* Define non-strub builtins on demand.  */
 #define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST)			\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1224,6 +1487,7 @@ public:
 
 #undef DEF_NM_BUILTIN
 
+  /* Define strub builtins on demand.  */
 #define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST)		\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1260,6 +1524,7 @@ public:
 
 #undef DEF_SS_BUILTIN
 
+    /* Define strub identifiers on demand.  */
 #define DEF_IDENT(NAME)					\
   static inline tree get_ ## NAME () {			\
     static tree identifier = NULL_TREE;			\
@@ -1278,6 +1543,10 @@ public:
   static inline void adjust_at_calls_call (cgraph_edge *, int);
   static inline void adjust_at_calls_calls (cgraph_node *);
 
+  /* Add to SEQ a call to the strub watermark update builtin, taking NODE's
+     location if given.  Optionally add the corresponding edge from NODE, with
+     execution frequency COUNT.  Return the modified SEQ.  */
+
   static inline gimple_seq
   call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
 			 gimple_seq seq = NULL)
@@ -1300,8 +1569,15 @@ public:
 
 } // anon namespace
 
+/* Gather with this type a collection of parameters that we're turning into
+   explicit references.  */
+
 typedef hash_set<tree> indirect_parms_t;
 
+/* Dereference OP's incoming turned-into-reference parm if it's an
+   INDIRECT_PARMS or an ADDR_EXPR thereof.  Set *REC and return according to
+   gimple-walking expectations.  */
+
 static tree
 maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
 {
@@ -1324,12 +1600,17 @@ maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
     {
       *rec = 0;
       if (indirect_parms.contains (TREE_OPERAND (op, 0)))
-	return TREE_OPERAND (op, 0);
+	{
+	  op = TREE_OPERAND (op, 0);
+	  return op;
+	}
     }
 
   return NULL_TREE;
 }
 
+/* A gimple-walking function that adds dereferencing to indirect parms.  */
+
 static tree
 walk_make_indirect (tree *op, int *rec, void *arg)
 {
@@ -1351,6 +1632,11 @@ walk_make_indirect (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* A gimple-walking function that turns any non-gimple-val ADDR_EXPRs into a
+   separate SSA.  Though addresses of e.g. parameters, and of members thereof,
+   are gimple vals, turning parameters into references, with an extra layer of
+   indirection and thus explicit dereferencing, need to be regimplified.  */
+
 static tree
 walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
 {
@@ -1374,6 +1660,57 @@ walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* Turn STMT's PHI arg defs into separate SSA defs if they've become
+   non-gimple_val.  Return TRUE if any edge insertions need to be committed.  */
+
+static bool
+walk_regimplify_phi (gphi *stmt)
+{
+  bool needs_commit = false;
+
+  for (unsigned i = 0, n = gimple_phi_num_args (stmt); i < n; i++)
+    {
+      tree op = gimple_phi_arg_def (stmt, i);
+      if ((TREE_CODE (op) == ADDR_EXPR
+	   && !is_gimple_val (op))
+	  /* ??? A PARM_DECL that was addressable in the original function and
+	     had its address in PHI nodes, but that became a reference in the
+	     wrapped clone would NOT be updated by update_ssa in PHI nodes.
+	     Alas, if we were to create a default def for it now, update_ssa
+	     would complain that the symbol that needed rewriting already has
+	     SSA names associated with it.  OTOH, leaving the PARM_DECL alone,
+	     it eventually causes errors because it remains unchanged in PHI
+	     nodes, but it gets rewritten as expected if it appears in other
+	     stmts.  So we cheat a little here, and force the PARM_DECL out of
+	     the PHI node and into an assignment.  It's a little expensive,
+	     because we insert it at the edge, which introduces a basic block
+	     that's entirely unnecessary, but it works, and the block will be
+	     removed as the default def gets propagated back into the PHI node,
+	     so the final optimized code looks just as expected.  */
+	  || (TREE_CODE (op) == PARM_DECL
+	      && !TREE_ADDRESSABLE (op)))
+	{
+	  tree temp = make_ssa_name (TREE_TYPE (op), stmt);
+	  if (TREE_CODE (op) == PARM_DECL)
+	    SET_SSA_NAME_VAR_OR_IDENTIFIER (temp, DECL_NAME (op));
+	  SET_PHI_ARG_DEF (stmt, i, temp);
+
+	  gimple *assign = gimple_build_assign (temp, op);
+	  if (gimple_phi_arg_has_location (stmt, i))
+	    gimple_set_location (assign, gimple_phi_arg_location (stmt, i));
+	  gsi_insert_on_edge (gimple_phi_arg_edge (stmt, i), assign);
+	  needs_commit = true;
+	}
+    }
+
+  return needs_commit;
+}
+
+/* Create a reference type to use for PARM when turning it into a reference.
+   NONALIASED causes the reference type to gain its own separate alias set, so
+   that accessing the indirectly-passed parm won'will not add aliasing
+   noise.  */
+
 static tree
 build_ref_type_for (tree parm, bool nonaliased = true)
 {
@@ -1411,6 +1748,7 @@ build_ref_type_for (tree parm, bool nonaliased = true)
 
 /* Add cgraph edges from current_function_decl to callees in SEQ with frequency
    COUNT, assuming all calls in SEQ are direct.  */
+
 static void
 add_call_edges_for_seq (gimple_seq seq, profile_count count)
 {
@@ -1425,16 +1763,22 @@ add_call_edges_for_seq (gimple_seq seq, profile_count count)
     {
       gimple *stmt = gsi_stmt (gsi);
 
-      if (!is_a <gcall *> (stmt))
+      gcall *call = dyn_cast <gcall *> (stmt);
+      if (!call)
 	continue;
 
-      gcall *call = as_a <gcall *> (stmt);
       tree callee = gimple_call_fndecl (call);
       gcc_checking_assert (callee);
       node->create_edge (cgraph_node::get_create (callee), call, count, false);
     }
 }
 
+/* Insert SEQ after the call at GSI, as if the call was in a try block with SEQ
+   as finally, i.e., SEQ will run after the call whether it returns or
+   propagates an exception.  This handles block splitting, EH edge and block
+   creation, noreturn and nothrow optimizations, and even throwing calls without
+   preexisting local handlers.  */
+
 static void
 gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
 {
@@ -1446,7 +1790,7 @@ gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
   if (gimple_has_location (stmt))
     annotate_all_with_location (seq, gimple_location (stmt));
 
-  gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+  gcall *call = dyn_cast <gcall *> (stmt);
   bool noreturn_p = call && gimple_call_noreturn_p (call);
   int eh_lp = lookup_stmt_eh_lp (stmt);
   bool must_not_throw_p = eh_lp < 0;
@@ -1586,8 +1930,12 @@ remove_named_attribute_unsharing (const char *name, tree *attrs)
     }
 }
 
+/* Record the order of the last cgraph entry whose mode we've already set, so
+   that we can perform mode setting incrementally without duplication.  */
 static int last_cgraph_order;
 
+/* Set strub modes for functions introduced since the last call.  */
+
 static void
 ipa_strub_set_mode_for_new_functions ()
 {
@@ -1616,6 +1964,7 @@ ipa_strub_set_mode_for_new_functions ()
 }
 
 /* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
+
 bool
 strub_splittable_p (cgraph_node *node)
 {
@@ -1641,10 +1990,11 @@ strub_splittable_p (cgraph_node *node)
 }
 
 /* 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))
+  switch (get_strub_mode_from_fndecl (fndecl))
     {
     case STRUB_WRAPPED:
     case STRUB_AT_CALLS:
@@ -1676,6 +2026,7 @@ strub_watermark_parm (tree fndecl)
 
 /* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
    hasn't been added yet.  Return the named argument count.  */
+
 int
 pass_ipa_strub::adjust_at_calls_type (tree type)
 {
@@ -1751,6 +2102,12 @@ pass_ipa_strub::adjust_at_calls_type (tree type)
   return named_args;
 }
 
+/* Adjust a call to an at-calls call target.  Create a watermark local variable
+   if needed, initialize it before, pass it to the callee according to the
+   modified at-calls interface, and release the callee's stack space after the
+   call, if not deferred.  If the call is const or pure, arrange for the
+   watermark to not be assumed unused or unchanged.  */
+
 void
 pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 {
@@ -1819,10 +2176,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 
     if (gimple_call_internal_p (stmt))
 #if 0
-      /*
-	new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
-	vargs);
-      */
+      new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
+						 vargs);
 #endif
       gcc_unreachable ();
     else
@@ -1917,6 +2272,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
   gsi_insert_finally_seq_after_call (gsi, seq);
 }
 
+/* Adjust all at-calls calls in NODE. */
+
 void
 pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
 {
@@ -1965,6 +2322,10 @@ pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
     }
 }
 
+/* The strubm (strub mode) pass computes a strub mode for each function in the
+   call graph, and checks, before any inlining, that strub callability
+   requirements in effect are satisfied.  */
+
 unsigned int
 pass_ipa_strub_mode::execute (function *)
 {
@@ -1977,12 +2338,17 @@ pass_ipa_strub_mode::execute (function *)
   return 0;
 }
 
+/* Create a strub mode pass.  */
+
 simple_ipa_opt_pass *
 make_pass_ipa_strub_mode (gcc::context *ctxt)
 {
   return new pass_ipa_strub_mode (ctxt);
 }
 
+/* The strub pass proper adjusts types, signatures, and at-calls calls, and
+   splits internal-strub functions.  */
+
 unsigned int
 pass_ipa_strub::execute (function *)
 {
@@ -2042,17 +2408,6 @@ pass_ipa_strub::execute (function *)
 	if (onode->alias)
 	  continue;
 
-#if 0 /* Calls are now adjusted when examining callers.  */
-	unsigned c;
-	cgraph_edge *e;
-	FOR_EACH_VEC_ELT (onode->collect_callers (), c, e)
-	  {
-	    push_cfun (DECL_STRUCT_FUNCTION (e->caller->decl));
-	    adjust_at_calls_call (e, named_args);
-	    pop_cfun ();
-	  }
-#endif
-
 	cgraph_node *nnode = onode;
 	push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
 
@@ -2071,10 +2426,10 @@ pass_ipa_strub::execute (function *)
 		{
 		  gimple *stmt = gsi_stmt (gsi);
 
-		  if (!is_gimple_call (stmt))
-		    continue;
+		  gcall *call = dyn_cast <gcall *> (stmt);
 
-		  gcall *call = as_a <gcall *> (stmt);
+		  if (!call)
+		    continue;
 
 		  if (gimple_alloca_call_p (call))
 		    {
@@ -2088,10 +2443,6 @@ pass_ipa_strub::execute (function *)
 	  }
 
 	pop_cfun ();
-
-#if 0
-	compute_fn_summary (onode, true);
-#endif
       }
   }
 
@@ -2108,43 +2459,6 @@ pass_ipa_strub::execute (function *)
 	continue;
       }
 
-#if 0
-    /* Hmm, this is an i386-specific attribute.  Do we need machine-specific
-       logic?  */
-    remove_named_attribute_unsharing ("interrupt",
-				      &DECL_ATTRIBUTES (onode->decl));
-#endif
-
-#if 0
-    if (!DECL_STRUCT_FUNCTION (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting struct-less function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    if (!onode->lowered)
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting non-lowered function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    /* 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.  FIXME.  */
-    if (!tree_versionable_function_p (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"%qD cannot be split for %<strub%>",
-		onode->decl);
-	continue;
-      }
-#endif
-
     bool is_stdarg = calls_builtin_va_start_p (onode);;
     bool apply_args = calls_builtin_apply_args_p (onode);
 
@@ -2753,26 +3067,45 @@ pass_ipa_strub::execute (function *)
       if (any_indirect)
 	{
 	  basic_block bb;
+	  bool needs_commit = false;
 	  FOR_EACH_BB_FN (bb, cfun)
-	    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
-		 !gsi_end_p (gsi); gsi_next (&gsi))
-	      {
-		gimple *stmt = gsi_stmt (gsi);
+	    {
+	      for (gphi_iterator gsi = gsi_start_nonvirtual_phis (bb);
+		   !gsi_end_p (gsi);
+		   gsi_next_nonvirtual_phi (&gsi))
+		{
+		  gphi *stmt = gsi.phi ();
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed && !is_gimple_debug (gsi_stmt (gsi)))
+		    if (walk_regimplify_phi (stmt))
+		      needs_commit = true;
+		}
 
-		walk_stmt_info wi = {};
-		wi.info = &indirect_nparms;
-		walk_gimple_op (stmt, walk_make_indirect, &wi);
-		if (wi.changed)
-		  {
-		    if (!is_gimple_debug (gsi_stmt (gsi)))
-		      {
-			wi.info = &gsi;
-			walk_gimple_op (stmt, walk_regimplify_addr_expr,
-					&wi);
-		      }
-		    update_stmt (stmt);
-		  }
-	      }
+	      for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+		   !gsi_end_p (gsi); gsi_next (&gsi))
+		{
+		  gimple *stmt = gsi_stmt (gsi);
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed)
+		    {
+		      if (!is_gimple_debug (stmt))
+			{
+			  wi.info = &gsi;
+			  walk_gimple_op (stmt, walk_regimplify_addr_expr,
+					  &wi);
+			}
+		      update_stmt (stmt);
+		    }
+		}
+	    }
+	  if (needs_commit)
+	    gsi_commit_edge_inserts ();
 	}
 
       if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca
@@ -2842,10 +3175,10 @@ pass_ipa_strub::execute (function *)
       push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
       gimple_stmt_iterator gsi
 	= gsi_after_labels (single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun)));
-      while (!is_gimple_call (gsi_stmt (gsi)))
-	gsi_next (&gsi);
 
-      gcall *wrcall = as_a <gcall *> (gsi_stmt (gsi));
+      gcall *wrcall;
+      while (!(wrcall = dyn_cast <gcall *> (gsi_stmt (gsi))))
+	gsi_next (&gsi);
 
       tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
       TREE_ADDRESSABLE (swm) = true;
@@ -2973,11 +3306,6 @@ pass_ipa_strub::execute (function *)
 
       pop_cfun ();
     }
-
-#if 0
-    compute_fn_summary (onode, true);
-    compute_fn_summary (nnode, true);
-#endif
   }
 
   if (flag_checking)
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
index c335ab42097..61a47b7ce1e 100644
--- a/gcc/ipa-strub.h
+++ b/gcc/ipa-strub.h
@@ -22,7 +22,7 @@ along with GCC; see the file COPYING3.  If not see
    as far as stack scrubbing constraints are concerned.  CALLEE
    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 bool strub_inlinable_from_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);
@@ -33,3 +33,14 @@ extern tree strub_watermark_parm (tree fndecl);
 
 /* Make a function type or declaration callable.  */
 extern void strub_make_callable (tree fndecl);
+
+/* Return zero iff ID is NOT an acceptable parameter for a user-supplied strub
+   attribute.  Otherwise, return >0 if it enables strub, <0 if it does not.
+   Return +/-1 if the attribute-modified type is compatible with the type
+   without the attribute, or +/-2 if it is not compatible.  */
+extern int strub_validate_parm (tree id);
+
+/* Like comptypes, return 0 if t1 and t2 are not compatible, 1 if they are
+   compatible, and 2 if they are nearly compatible.  Same strub mode is
+   compatible, interface-compatible strub modes are nearly compatible.  */
+extern int strub_comptypes (tree t1, tree t2);
diff --git a/gcc/testsuite/c-c++-common/strub-O0.c b/gcc/testsuite/c-c++-common/strub-O0.c
index 6afe0fd5de1..c7a79a6ea0d 100644
--- a/gcc/testsuite/c-c++-common/strub-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O0 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O0, none of the strub builtins are expanded inline.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O1.c b/gcc/testsuite/c-c++-common/strub-O1.c
index 1cdeaecaf32..96285c975d9 100644
--- a/gcc/testsuite/c-c++-common/strub-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O1 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O1, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
index 7848c46d179..8edc0d8aa13 100644
--- a/gcc/testsuite/c-c++-common/strub-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O2, without -fno-inline, we fully expand enter and update, and add a test
    around the leave call.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
index 85a8f76785e..c6d900cf3c4 100644
--- a/gcc/testsuite/c-c++-common/strub-O2fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3.c b/gcc/testsuite/c-c++-common/strub-O3.c
index 1fcde345d36..33ee465e51c 100644
--- a/gcc/testsuite/c-c++-common/strub-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand" } */
 
 int __attribute__ ((__strub__)) var;
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
index a2eedfd96b2..2936f82079e 100644
--- a/gcc/testsuite/c-c++-common/strub-O3fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
index e5cb1f60541..479746e57d8 100644
--- a/gcc/testsuite/c-c++-common/strub-Og.c
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Og -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Og -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Og, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
index 194aacc2c05..2241d4ea07f 100644
--- a/gcc/testsuite/c-c++-common/strub-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Os, without -fno-inline, we fully expand enter, and also update.  The
    expanded update might be larger than a call proper, but argument saving and
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
index 46e84bf6560..a322bcc5da6 100644
--- a/gcc/testsuite/c-c++-common/strub-all1.c
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -22,11 +22,11 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
index f377541cff0..db60026d0e0 100644
--- a/gcc/testsuite/c-c++-common/strub-all2.c
+++ b/gcc/testsuite/c-c++-common/strub-all2.c
@@ -17,8 +17,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
index f180b17f30e..2f462adc1ef 100644
--- a/gcc/testsuite/c-c++-common/strub-apply1.c
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -1,13 +1,13 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
-void __attribute__ ((__strub__ (3)))
+void __attribute__ ((__strub__ ("callable")))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0);
 }
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   void *args = __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
index 379a54b73b7..a5d7551f5da 100644
--- a/gcc/testsuite/c-c++-common/strub-apply2.c
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 extern void __attribute__ ((__strub__))
 apply_function (void *args);
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
index 9b4786be698..64422a0d1e8 100644
--- a/gcc/testsuite/c-c++-common/strub-apply3.c
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 void __attribute__ ((__strub__))
 apply_function (void *args)
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
index 409f747743e..ea18d0bbd54 100644
--- a/gcc/testsuite/c-c++-common/strub-apply4.c
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+/* { dg-options "-O2 -fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
index d964b07ae5d..b70843b4215 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -22,9 +22,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
index 530eee36d06..97a3988a6b9 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls2.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls2.c
@@ -17,7 +17,7 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
index 7b04eea35d9..3d73431b3dc 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O1" } */
+/* { dg-options "-fstrub=strict -O1" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O1.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
index 67d96419a5e..fddf3c745e7 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O2" } */
+/* { dg-options "-fstrub=strict -O2" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O2.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
index 34828d2711e..2bc384ee8d1 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O3" } */
+/* { dg-options "-fstrub=strict -O3" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -O3.  */
@@ -10,7 +10,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -18,7 +18,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -32,7 +32,7 @@ leak_string (void)
   return 0;
 }
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 int
 look_for_string (char *e)
 {
@@ -56,14 +56,14 @@ look_for_string (char *e)
   return 0;
 }
 
-static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+static __attribute__ ((__strub__ ("at-calls"), __noinline__, __noclone__))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 deferred_at_calls ()
 {
@@ -73,7 +73,7 @@ deferred_at_calls ()
   return ret;
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 deferred_internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
index b273660aea1..fbaf85fe0fa 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -Os" } */
+/* { dg-options "-fstrub=strict -Os" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -Os.  */
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
index a74658c9ac9..e9d7b7b9ee0 100644
--- a/gcc/testsuite/c-c++-common/strub-internal1.c
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -23,9 +23,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
index a6e69357b23..8b8e15a51c7 100644
--- a/gcc/testsuite/c-c++-common/strub-internal2.c
+++ b/gcc/testsuite/c-c++-common/strub-internal2.c
@@ -14,8 +14,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
index 7e22a266ad9..0a4a7539d34 100644
--- a/gcc/testsuite/c-c++-common/strub-parms1.c
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -16,7 +16,7 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 large_byref_arg (struct large_arg la)
 {
 }
@@ -24,7 +24,7 @@ large_byref_arg (struct large_arg la)
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*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)))
+void __attribute__ ((__strub__ ("internal")))
 std_arg (int i, ...)
 {
   va_list vl;
@@ -38,7 +38,7 @@ std_arg (int i, ...)
 /* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
index 2d8036c0fbc..147171d96d5 100644
--- a/gcc/testsuite/c-c++-common/strub-parms2.c
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -15,14 +15,14 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 large_byref_arg (struct large_arg la)
 {
 }
 
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 std_arg (int i, ...)
 {
   va_list vl;
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
index f64361f1235..4e92682895a 100644
--- a/gcc/testsuite/c-c++-common/strub-parms3.c
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
new file mode 100644
index 00000000000..e2f9d8aebca
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  Without the error,
+   inlining takes place.  */
+
+#include "strub-strict1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
new file mode 100644
index 00000000000..98474435d2e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  */
+
+#include "strub-strict2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
index a4226ce0119..1de15342595 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fexceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fexceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
index 3bab553478b..f9209c81900 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
index c89cc7c2c47..bed1dcfb54a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
index b869fafb691..6bf0071f52b 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
index 1d3dd2f2c2c..4732f515bf7 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
index 4dd4281b03b..8d6424c479a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-default1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
similarity index 59%
rename from gcc/testsuite/c-c++-common/strub-default1.c
rename to gcc/testsuite/c-c++-common/strub-strict1.c
index 79d00cedb9a..ea28ba07e6f 100644
--- a/gcc/testsuite/c-c++-common/strub-default1.c
+++ b/gcc/testsuite/c-c++-common/strub-strict1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
 
 static int __attribute__ ((__strub__)) var;
 
@@ -31,12 +31,12 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-4\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { 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" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-default2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
similarity index 71%
rename from gcc/testsuite/c-c++-common/strub-default2.c
rename to gcc/testsuite/c-c++-common/strub-strict2.c
index 487253e9227..36e6f47b957 100644
--- a/gcc/testsuite/c-c++-common/strub-default2.c
+++ b/gcc/testsuite/c-c++-common/strub-strict2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
 
 static int __attribute__ ((__strub__)) var;
 
@@ -22,8 +22,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
index 0840dddd136..e48e0610e07 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 #include "strub-tail-O2.c"
 
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
index 9330d6ff4c1..87cda7ab21b 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.
    Tail calls are short-circuited at -O2+.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
index 5b33ff1f530..b5e45ab0525 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
index 38935e3270b..46b051411fb 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -1,39 +1,39 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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);
+extern int __attribute__ ((__strub__ ("callable"))) xcallable (void);
+extern int __attribute__ ((__strub__ ("internal"))) xinternal (void);
+extern int __attribute__ ((__strub__ ("at-calls"))) xat_calls (void);
+extern int __attribute__ ((__strub__ ("disabled"))) 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__ ("callable"))) callable (void);
+int __attribute__ ((__strub__ ("internal"))) internal (void);
+int __attribute__ ((__strub__ ("at-calls"))) at_calls (void);
+int __attribute__ ((__strub__ ("disabled"))) disabled (void);
 
 int __attribute__ ((__strub__)) var;
 int var_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 icallable (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 iinternal (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 idisabled (void);
 static inline int __attribute__ ((__always_inline__))
 ivar_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 i_callable (void) { return 0; }
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 i_internal (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 i_at_calls (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 i_disabled (void) { return 0; }
 static inline int __attribute__ ((__always_inline__))
 i_var_user (void) { return var; }
@@ -70,7 +70,7 @@ i_var_user (void) { return var; }
 
 /* Not a strub context, so it can call anything.
    Explicitly declared as callable even from within strub contexts.  */
-int __attribute__ ((__strub__ (3)))
+int __attribute__ ((__strub__ ("callable")))
 callable (void) {
   int ret = 0;
 
@@ -88,7 +88,7 @@ callable (void) {
 
 /* 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)))
+int __attribute__ ((__strub__ ("internal")))
 internal (void) {
   int ret = var;
 
@@ -109,7 +109,7 @@ internal (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (1)))
+int __attribute__ ((__strub__ ("at-calls")))
 at_calls (void) {
   int ret = var;
 
@@ -130,7 +130,7 @@ at_calls (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (0)))
+int __attribute__ ((__strub__ ("disabled")))
 disabled () {
   int ret = 0;
 
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
index 100fb0c59a9..2857195706e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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,
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
index 9e818ac9748..98a92bc9eac 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
index d40e8aa45cb..5511a6e1e71 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __const__))
 f() {
   return 0;
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
index d4cbdaf10f3..47ee927964d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -1,12 +1,12 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__
 __attribute__ ((__const__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data1.c b/gcc/testsuite/c-c++-common/torture/strub-data1.c
index 62a03891ab6..7c27a2a1a6d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointed-to data enables strubbing if accessed.  */
 int __attribute__ ((__strub__)) var;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data2.c b/gcc/testsuite/c-c++-common/torture/strub-data2.c
index 9b7df13a280..e66d903780a 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointer itself is a strub variable, enabling internal strubbing when
    its value is used.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data3.c b/gcc/testsuite/c-c++-common/torture/strub-data3.c
index 515706caa32..5e08e0e58c6 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data4.c b/gcc/testsuite/c-c++-common/torture/strub-data4.c
index 0ec9e35429f..a818e7a38bb 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
index 69f3d65ed44..90e8ad51c1e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -Werror" } */
+/* { dg-options "-fstrub=strict -Werror" } */
 
 typedef int __attribute__ ((__strub__)) strub_int;
 strub_int *ptr;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
index b8adf8009e8..c165f312f16 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype ();
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
index 5b2c35ad6a7..69fcff8d376 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
index 5ee50456dc9..ff006224909 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int, ...);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
new file mode 100644
index 00000000000..9a69539db9d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+}
+
+void
+bat (void)
+{
+  /* Not allowed, not a strub context.  */
+  inl_int_ali (); /* { dg-error "context" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
new file mode 100644
index 00000000000..f9a6b4a16fa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=all" } */
+
+#include "strub-inlinable1.c"
+
+/* With -fstrub=all, the caller becomes a strub context, so the strub-inlinable
+   callee is not rejected.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
new file mode 100644
index 00000000000..a7b29a80ff8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -Werror" } */
+
+typedef void ft (void);
+typedef void ft2 (int, int);
+extern ft __attribute__ ((__strub__)) fnac;
+
+ft * f (void) {
+  return fnac; /* { dg-error "incompatible" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
new file mode 100644
index 00000000000..896383078cd
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
@@ -0,0 +1,19 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic -Werror" } */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at_calls"))) bal (void);
+
+void
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+
+  d_p = bac; /* { dg-error "not quite compatible" } */
+  c_p = bad; /* { dg-error "not quite compatible" } */
+  c_p = bar; /* { dg-error "not quite compatible" } */
+  c_p = bal; /* { dg-error "incompatible" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
index cb223da6efc..a262a086837 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
index 67d1434b1f8..4c4bd50c209 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
index 59f02ea901f..ce195c6b1f1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -1,10 +1,10 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __pure__))
 f() {
   static int i; /* Stop it from being detected as const.  */
   return i;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
index 973e909217d..75cd54ccb5b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
 __attribute__ ((__pure__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
index a4077c35a60..c596066a045 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -14,7 +14,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -59,14 +59,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
index 94e4156ea73..1fdba214a07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
index 0ca74beb59d..afbc2cc9ab4 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
 /* Check that a non-strub function leaves a string behind in the stack, and that
@@ -7,7 +7,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
index 4ab11c0682e..5300f1d330b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -85,14 +85,14 @@ intermediate ()
   return ret;
 }
 
-static inline __attribute__ ((__strub__ (2)))
+static inline __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
   return intermediate ();
 }
 
-int __attribute__ ((strub (0)))
+int __attribute__ ((__strub__ ("disabled")))
 main ()
 {
   if (look_for_string (internal ()))
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4d.c b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
index e4f7445607c..08de3f1c3b1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4d.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -1,7 +1,7 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
-#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ (1)))
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
 
 #include "strub-run4.c"
diff --git a/gcc/testsuite/g++.dg/torture/strub-init1.C b/gcc/testsuite/g++.dg/torture/strub-init1.C
index e51ae802be4..c226ab10ff6 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init1.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init2.C b/gcc/testsuite/g++.dg/torture/strub-init2.C
index edcb7bf8ad2..a7911f1fa72 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init2.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init3.C b/gcc/testsuite/g++.dg/torture/strub-init3.C
index bacf823ca4e..6ebebcd01e8 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init3.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
index 697ac9de764..2d6f6394993 100644
--- a/gcc/testsuite/gnat.dg/strub_attr.adb
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -1,5 +1,5 @@
 --  { dg-do compile }
---  { dg-options "-fstrub=default -fdump-ipa-strubm" }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
 
 package body Strub_Attr is
    E : exception;
diff --git a/libgcc/strub.c b/libgcc/strub.c
index fd6e27556e4..7c58deee1e6 100644
--- a/libgcc/strub.c
+++ b/libgcc/strub.c
@@ -36,7 +36,7 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 # define TOPS <
 #endif
 
-#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ ("callable")))
 
 /* Enter a stack scrubbing context, initializing the watermark to the caller's
    stack address.  */


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

* [gcc(refs/users/aoliva/heads/strub)] -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests
@ 2021-09-04 11:10 Alexandre Oliva
  0 siblings, 0 replies; 13+ messages in thread
From: Alexandre Oliva @ 2021-09-04 11:10 UTC (permalink / raw)
  To: gcc-cvs

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

commit af98eb95627ca5ddb322da83ba807ad8c2d484aa
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Thu Sep 2 23:15:36 2021 -0300

    -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests

Diff:
---
 .../doc/gnat_rm/security_hardening_features.rst    |  30 ++
 gcc/ada/gcc-interface/utils.c                      |  95 ++--
 gcc/attribs.c                                      |  14 +-
 gcc/c-family/c-attribs.c                           |  84 ++-
 gcc/common.opt                                     |  27 +-
 gcc/doc/extend.texi                                | 288 +++++++++--
 gcc/doc/invoke.texi                                |  54 +-
 gcc/ipa-inline.c                                   |   2 +-
 gcc/ipa-strub.c                                    | 563 +++++++++++++++------
 gcc/ipa-strub.h                                    |  13 +-
 gcc/testsuite/c-c++-common/strub-O0.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O1.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2fni.c           |   2 +-
 gcc/testsuite/c-c++-common/strub-O3.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O3fni.c           |   2 +-
 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            |  12 +-
 gcc/testsuite/c-c++-common/strub-all2.c            |   6 +-
 gcc/testsuite/c-c++-common/strub-apply1.c          |   6 +-
 gcc/testsuite/c-c++-common/strub-apply2.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-apply3.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-apply4.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-at-calls2.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-defer-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |  14 +-
 gcc/testsuite/c-c++-common/strub-defer-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-internal1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-internal2.c       |   6 +-
 gcc/testsuite/c-c++-common/strub-parms1.c          |  10 +-
 gcc/testsuite/c-c++-common/strub-parms2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-parms3.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-relaxed1.c        |  18 +
 gcc/testsuite/c-c++-common/strub-relaxed2.c        |  14 +
 gcc/testsuite/c-c++-common/strub-short-O0-exc.c    |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O0.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O3.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-Os.c        |   2 +-
 .../{strub-default1.c => strub-strict1.c}          |  16 +-
 .../{strub-default2.c => strub-strict2.c}          |   8 +-
 gcc/testsuite/c-c++-common/strub-tail-O1.c         |   2 +-
 gcc/testsuite/c-c++-common/strub-tail-O2.c         |   2 +-
 .../c-c++-common/torture/strub-callable1.c         |   2 +-
 .../c-c++-common/torture/strub-callable2.c         |  42 +-
 gcc/testsuite/c-c++-common/torture/strub-const1.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const2.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const3.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-const4.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-data1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data3.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data4.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |   2 +-
 .../c-c++-common/torture/strub-indcall1.c          |   2 +-
 .../c-c++-common/torture/strub-indcall2.c          |   2 +-
 .../c-c++-common/torture/strub-indcall3.c          |   2 +-
 .../c-c++-common/torture/strub-inlinable1.c        |  16 +
 .../c-c++-common/torture/strub-inlinable2.c        |   7 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |  10 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |  19 +
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure3.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-pure4.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run1.c    |  10 +-
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run4.c    |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run4d.c   |   4 +-
 gcc/testsuite/g++.dg/torture/strub-init1.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init2.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init3.C         |   2 +-
 gcc/testsuite/gnat.dg/strub_attr.adb               |   2 +-
 libgcc/strub.c                                     |   2 +-
 79 files changed, 1056 insertions(+), 478 deletions(-)

diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 309291f0341..90769dff635 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -19,6 +19,18 @@ It can be enabled with the *-fzero-call-used-regs* command line
 option, to affect all subprograms in a compilation, and with a
 :samp:`Machine_Attribute` pragma, to affect only specific subprograms.
 
+.. code-block:: ada
+
+     procedure Foo;
+     pragma Machine_Attribute (Foo, "zero_call_used_regs", "used");
+     --  Before returning, Foo scrubs only call-clobbered registers
+     --  that it uses itself.
+
+     function Bar return Integer;
+     pragma Machine_Attribute (Bar, "zero_call_used_regs", "all");
+     --  Before returning, Bar scrubs all call-clobbered registers.
+
+
 For usage and more details on the command line option, and on the
 ``zero_call_used_regs`` attribute, see :title:`Using the GNU Compiler
 Collection (GCC)`.
@@ -35,6 +47,24 @@ It can be activated with the *-fstrub* command line option, to affect
 all (viable) subprograms in a compilation, and with a
 :samp:`Machine_Attribute` pragma.
 
+.. code-block:: ada
+
+     function Foo returns Integer;
+     pragma Machine_Attribute (Foo, "strub");
+     --  Foo and its callers are modified so as to scrub the stack
+     --  space used by Foo after it returns.
+
+     procedure Bar;
+     pragma Machine_Attribute (Bar, "strub", "internal");
+     --  Bar is turned into a wrapper for its original body,
+     --  and they scrub the stack used by the original body.
+
+     Var : Integer;
+     pragma Machine_Attribute (Var, "strub");
+     --  Reading from Var in a subprogram enables stack scrubbing
+     --  of the stack space used by the subprogram.
+
+
 For usage and more details on the command line option, and on the
 ``strub`` attribute, see :title:`Using the GNU Compiler Collection
 (GCC)`.
diff --git a/gcc/ada/gcc-interface/utils.c b/gcc/ada/gcc-interface/utils.c
index 6b20ddc93c9..1abf593d76b 100644
--- a/gcc/ada/gcc-interface/utils.c
+++ b/gcc/ada/gcc-interface/utils.c
@@ -39,6 +39,7 @@
 #include "varasm.h"
 #include "toplev.h"
 #include "opts.h"
+#include "ipa-strub.h"
 #include "output.h"
 #include "debug.h"
 #include "convert.h"
@@ -6611,6 +6612,7 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
+  bool compat_type = false;
   tree orig_fnptr_type = NULL_TREE;
   tree orig_args = args;
 
@@ -6624,68 +6626,36 @@ handle_strub_attribute (tree *node, tree name,
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      /* pragma Machine_Attribute turns string arguments into identifiers.
-	 Reverse it.  */
-      if (TREE_CODE (TREE_VALUE (args)) == IDENTIFIER_NODE)
+      switch (strub_validate_parm (TREE_VALUE (args)))
 	{
-	  gcc_checking_assert (IDENTIFIER_POINTER (TREE_VALUE (args))
-			       [IDENTIFIER_LENGTH (TREE_VALUE (args))] == 0);
-	  TREE_VALUE (args) = build_string
-	    (IDENTIFIER_LENGTH (TREE_VALUE (args)) + 1,
-	     IDENTIFIER_POINTER (TREE_VALUE (args)));
-	}
-
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
-	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
-
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
+	case 1:
+	  compat_type = true;
+	  /* Fall through.  */
 
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
+	  compat_type = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	  compat_type = true;
+	  /* Fall through.  */
+
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
@@ -6699,23 +6669,30 @@ handle_strub_attribute (tree *node, tree name,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   if (!*no_add_attrs)
     {
       *no_add_attrs = true;
       if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
 	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
+	{
+	  if (compat_type)
+	    *node = build_variant_type_copy (*node);
+	  else
+	    *node = build_distinct_type_copy (*node);
+	}
       TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
 					   TYPE_ATTRIBUTES (*node));
 
       if (orig_fnptr_type)
 	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
+	  tree fnptr_type = ((flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
+			     ? orig_fnptr_type
+			     : compat_type
+			     ? build_variant_type_copy (orig_fnptr_type)
+			     : build_distinct_type_copy (orig_fnptr_type));
 	  TREE_TYPE (fnptr_type) = *node;
 	  *node = fnptr_type;
 	}
diff --git a/gcc/attribs.c b/gcc/attribs.c
index 0d22c20a35e..acac634afb5 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "attribs.h"
 #include "fold-const.h"
+#include "ipa-strub.h"
 #include "stor-layout.h"
 #include "langhooks.h"
 #include "plugin.h"
@@ -1353,9 +1354,20 @@ comp_type_attributes (const_tree type1, const_tree type2)
   if ((lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type1)) != NULL)
       ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) != NULL))
     return 0;
+  int strub_ret = strub_comptypes (CONST_CAST_TREE (type1),
+				   CONST_CAST_TREE (type2));
+  if (strub_ret == 0)
+    return strub_ret;
   /* As some type combinations - like default calling-convention - might
      be compatible, we have to call the target hook to get the final result.  */
-  return targetm.comp_type_attributes (type1, type2);
+  int target_ret = targetm.comp_type_attributes (type1, type2);
+  if (target_ret == 0)
+    return target_ret;
+  if (strub_ret == 2 || target_ret == 2)
+    return 2;
+  if (strub_ret == 1 && target_ret == 1)
+    return 1;
+  gcc_unreachable ();
 }
 
 /* PREDICATE acts as a function of type:
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index dc4a4d4d200..2762dcdfcfe 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "common/common-target.h"
 #include "langhooks.h"
 #include "tree-inline.h"
+#include "ipa-strub.h"
 #include "toplev.h"
 #include "tree-iterator.h"
 #include "opts.h"
@@ -1306,6 +1307,7 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
+  bool compat_type = false;
   tree orig_fnptr_type = NULL_TREE;
   tree orig_args = args;
 
@@ -1319,57 +1321,36 @@ handle_strub_attribute (tree *node, tree name,
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
+      switch (strub_validate_parm (TREE_VALUE (args)))
 	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
+	case 1:
+	  compat_type = true;
+	  /* Fall through.  */
 
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
-
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
+	  compat_type = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	  compat_type = true;
+	  /* Fall through.  */
+
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
@@ -1383,23 +1364,30 @@ handle_strub_attribute (tree *node, tree name,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   if (!*no_add_attrs)
     {
       *no_add_attrs = true;
       if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
 	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
+	{
+	  if (compat_type)
+	    *node = build_variant_type_copy (*node);
+	  else
+	    *node = build_distinct_type_copy (*node);
+	}
       TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
 					   TYPE_ATTRIBUTES (*node));
 
       if (orig_fnptr_type)
 	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
+	  tree fnptr_type = ((flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
+			     ? orig_fnptr_type
+			     : compat_type
+			     ? build_variant_type_copy (orig_fnptr_type)
+			     : build_distinct_type_copy (orig_fnptr_type));
 	  TREE_TYPE (fnptr_type) = *node;
 	  *node = fnptr_type;
 	}
diff --git a/gcc/common.opt b/gcc/common.opt
index 71e0c971344..ef018ebf735 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,13 +2687,22 @@ fstrict-overflow
 Common
 Treat signed overflow as undefined.  Negated as -fwrapv -fwrapv-pointer.
 
-; If any strub-enabling attribute is seen when the default value is
-; selected, it's bumped up to -1.  The scrub mode gate function will
-; then bump -2 to 0 if no strub-enabling attribute is seen.  This
-; minimizes the strub overhead.
-fstrub=default
-Common RejectNegative Var(flag_strub, -2) Init(-2)
-Enable stack scrub as requested through attributes.
+fstrub=disable
+Common RejectNegative Var(flag_strub, 0)
+Disable stack scrub entirely, disregarding strub attributes.
+
+fstrub=strict
+Common RejectNegative Var(flag_strub, -4)
+Enable stack scrub as per attributes, with strict call checking.
+
+; If any strub-enabling attribute is seen when the default or strict
+; initializer values are in effect, flag_strub is bumped up by 2.  The
+; scrub mode gate function will then bump these initializer values to
+; 0 if no strub-enabling attribute is seen.  This minimizes the strub
+; overhead.
+fstrub=relaxed
+Common RejectNegative Var(flag_strub, -3) Init(-3)
+Restore default strub mode: as per attributes, with relaxed checking.
 
 fstrub=all
 Common RejectNegative Var(flag_strub, 3)
@@ -2707,10 +2716,6 @@ fstrub=internal
 Common RejectNegative Var(flag_strub, 2)
 Enable internal stack scrubbing for all viable functions.
 
-fstrub=disable
-Common RejectNegative Var(flag_strub, 0)
-Disable stack scrub entirely, disregarding strub attributes.
-
 fsync-libcalls
 Common Var(flag_sync_libcalls) Init(1)
 Implement __atomic operations via libcalls to legacy __sync functions.
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 1fcf60a3875..46cefe27fa5 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -8729,51 +8729,259 @@ pid_t wait (wait_status_ptr_t p)
 @item strub
 @cindex @code{strub} type attribute
 This attribute defines stack-scrubbing properties of functions and
-variables.  When applied to function types, it takes an optional
-argument.  When applied to a pointer-to-function type, it gets
-propagated to the function type if the optional argument is given.
-
-A function whose type is annotated with @code{at-calls} @code{strub}
-mode (@code{strub("at-calls")}, @code{strub(1)}, or @code{strub})
-undergoes interface changes.  Its callers are adjusted to match the
-changes, and to automatically scrub (overwrite with zeros) the stack
-space used by the function.
-
-A function whose type indicates @code{internal} @code{strub} mode
-(@code{strub("internal")} or @code{strub(2)}) retains an unmodified
-interface, but may be turned into a wrapper that calls the wrapped body
-using a custom interface.  The wrapper then scrubs the stack space used
-by the wrapped body.
-
-An automatically-allocated variable whose type carries the @code{strub}
-attribute causes the enclosing function to have @code{strub} enabled.
-
-A statically-allocated variable whose type carries the @code{strub}
-attribute causes functions that @emph{read} it with that type to have
-@code{strub} enabled.  Reading from data referenced by pointers to such
-types has the same effect.  Note: The attribute does not carry over from
-a composite type to the types of its components, so the intended effect
-may not be obtained with non-scalar types.
-
-A @code{strub} context is the body of a function that has strub enabled,
-be it explicitly, by @code{at-calls} or @code{internal} mode, or
-implicitly, by reading from a @code{strub}-marked data type.
+variables.  Being a type attribute, it attaches to types, even when
+specified in function and variable declarations.  When applied to
+function types, it takes an optional string argument.  When applied to a
+pointer-to-function type, if the optional argument is given, it gets
+propagated to the function type.
+
+@smallexample
+/* A strub variable.  */
+int __attribute__ ((strub)) var;
+/* A strub variable that happens to be a pointer.  */
+__attribute__ ((strub)) int *strub_ptr_to_int;
+/* A pointer type that may point to a strub variable.  */
+typedef int __attribute__ ((strub)) *ptr_to_strub_int_type;
+
+/* A declaration of a strub function.  */
+extern int __attribute__ ((strub)) foo (void);
+/* A pointer to that strub function.  */
+int __attribute__ ((strub ("at-calls"))) (*ptr_to_strub_fn)(void) = foo;
+@end smallexample
+
+A function associated with @code{at-calls} @code{strub} mode
+(@code{strub("at-calls")}, or just @code{strub}) undergoes interface
+changes.  Its callers are adjusted to match the changes, and to scrub
+(overwrite with zeros) the stack space used by the called function after
+it returns.  The interface change makes the function type incompatible
+with an unadorned but otherwise equivalent type, so @emph{every}
+declaration and every type that may be used to call the function must be
+associated with this strub mode.
+
+A function associated with @code{internal} @code{strub} mode
+(@code{strub("internal")}) retains an unmodified, type-compatible
+interface, but it may be turned into a wrapper that calls the wrapped
+body using a custom interface.  The wrapper then scrubs the stack space
+used by the wrapped body.  Though the wrapped body has its stack space
+scrubbed, the wrapper does not, so arguments and return values may
+remain unscrubbed even when such a function is called by another
+function that enables @code{strub}.  This is why, when compiling with
+@samp{-fstrub=strict}, a @code{strub} context is not allowed to call
+@code{internal} @code{strub} functions.
+
+@smallexample
+/* A declaration of an internal-strub function.  */
+extern int __attribute__ ((strub ("internal"))) bar (void);
+
+int __attribute__ ((strub))
+baz (void)
+@{
+  /* Ok, foo was declared above as an at-calls strub function.  */
+  foo ();
+  /* Not allowed in strict mode, otherwise allowed.  */
+  bar ();
+@}
+@end smallexample
+
+An automatically-allocated variable associated with the @code{strub}
+attribute causes the (immediately) enclosing function to have
+@code{strub} enabled.
+
+A statically-allocated variable associated with the @code{strub}
+attribute causes functions that @emph{read} it, through its @code{strub}
+data type, to have @code{strub} enabled.  Reading data by dereferencing
+a pointer to a @code{strub} data type has the same effect.  Note: The
+attribute does not carry over from a composite type to the types of its
+components, so the intended effect may not be obtained with non-scalar
+types.
+
+When selecting a @code{strub}-enabled mode for a function that is not
+explicitly associated with one, because of @code{strub} variables or
+data pointers, the function must satisfy @code{internal} mode viability
+requirements (see below), even when @code{at-calls} mode is also viable
+and, being more efficient, ends up selected as an optimization.
+
+@smallexample
+/* zapme is implicitly strub-enabled because of strub variables.
+   Optimization may change its strub mode, but not the requirements.  */
+static int
+zapme (int i)
+@{
+  /* A local strub variable enables strub.  */
+  int __attribute__ ((strub)) lvar;
+  /* Reading strub data through a pointer-to-strub enables strub.  */
+  lvar = * (ptr_to_strub_int_type) &i;
+  /* Writing to a global strub variable does not enable strub.  */
+  var = lvar;
+  /* Reading from a global strub variable enables strub.  */
+  return var;
+@}
+@end smallexample
+
+A @code{strub} context is the body (as opposed to the interface) of a
+function that has @code{strub} enabled, be it explicitly, by
+@code{at-calls} or @code{internal} mode, or implicitly, due to
+@code{strub} variables or command-line options.
 
 A function of a type associated with the @code{disabled} @code{strub}
-mode (@code{strub("disabled")}, @code{strub(0)}, or no @code{strub} mode
-specified) will not have its own stack space scrubbed, and it cannot be
-called from @code{strub} contexts.
+mode (@code{strub("disabled")} will not have its own stack space
+scrubbed.  Such functions @emph{cannot} be called from within
+@code{strub} contexts.
+
+In order to enable a function to be called from within @code{strub}
+contexts without having its stack space scrubbed, associate it with the
+@code{callable} @code{strub} mode (@code{strub("callable")}).
+
+When a function is not assigned a @code{strub} mode, explicitly or
+implicitly, the mode defaults to @code{callable}, except when compiling
+with @samp{-fstrub=strict}, that causes @code{strub} mode to default to
+@code{disabled}.
+
+@example
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+ /* Implicitly disabled with -fstrub=strict, otherwise callable.  */
+extern int bah (void);
+
+int __attribute__ ((strub))
+bal (void)
+@{
+  /* Not allowed, bad is not strub-callable.  */
+  bad ();
+  /* Ok, bac is strub-callable.  */
+  bac ();
+  /* Not allowed with -fstrub=strict, otherwise allowed.  */
+  bah ();
+@}
+@end example
 
-A function that does not have @code{strub} enabled can only be called
-from within @code{strub} contexts through a function type marked with
-the @code{callable} @code{strub} mode (@code{strub("callable")} or
-@code{strub(3)}).
+Function types marked @code{callable} and @code{disabled} are not
+mutually compatible types, but the underlying interfaces are compatible,
+so it is safe to convert pointers between them, and to use such pointers
+or alternate declarations to call them.  Interfaces are also
+interchangeable between them and @code{internal} (but not
+@code{at-calls}!), but adding @code{internal} to a pointer type will not
+cause the pointed-to function to perform stack scrubbing.
+
+@example
+void __attribute__ ((strub))
+bap (void)
+@{
+  /* Assign a callable function to pointer-to-disabled.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bac;
+  /* Not allowed: calls disabled type in a strub context.  */
+  d_p ();
+
+  /* Assign a disabled function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bad;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an internal function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  c_p = bar;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an at-calls function to pointer-to-callable.
+     Flaggged as incompatible.  */
+  c_p = bal;
+  /* The call through an interface-incompatible type will not use the
+     modified interface expected by the at-calls function, so it is
+     likely to misbehave at runtime.  */
+  c_p ();
+@}
+@end example
 
 @code{Strub} contexts are never inlined into non-@code{strub} contexts.
-When an @code{internal}-strub function is split, the wrapper can often
-be inlined, but the wrapped body never is.  Functions marked as
-@code{always_inline}, even if explicitly assigned @code{internal} strub
-mode, will not undergo wrapping, so their body gets inlined.
+When an @code{internal}-strub function is split up, the wrapper can
+often be inlined, but the wrapped body @emph{never} is.  A function
+marked as @code{always_inline}, even if explicitly assigned
+@code{internal} strub mode, will not undergo wrapping, so its body gets
+inlined as required.
+
+@example
+inline int __attribute__ ((strub ("at-calls")))
+inl_atc (void)
+@{
+  /* This body may get inlined into strub contexts.  */
+@}
+
+inline int __attribute__ ((strub ("internal")))
+inl_int (void)
+@{
+  /* This body NEVER gets inlined, though its wrapper may.  */
+@}
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+@{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+@}
+
+void __attribute__ ((strub ("disabled")))
+bat (void)
+@{
+  /* Not allowed, cannot inline into a non-strub context.  */
+  inl_int_ali ();
+@}
+@end example
+
+@cindex strub eligibility and viability
+Some @samp{-fstrub=*} command line options enable @code{strub} modes
+implicitly where viable.  A @code{strub} mode is only viable for a
+function if the function is eligible for that mode, and if other
+conditions, detailed below, are satisfied.  If it's not eligible for a
+mode, attempts to explicitly associate it with that mode are rejected
+with an error message.  If it is eligible, that mode may be assigned
+explicitly through this attribute, but implicit assignment through
+command-line options may involve additional viability requirements.
+
+A function is ineligible for @code{at-calls} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+@code{At-calls} @code{strub} mode, if not requested through the function
+type, is only viable for an eligible function if the function is not
+visible to other translation units, and if it doesn't have its address
+taken.
+
+@smallexample
+/* bar is eligible for at-calls strub mode,
+   but not viable for that mode because it is visible to other units.
+   It is eligible and viable for internal strub mode.  */
+void bav () @{@}
+
+/* setp is eligible for at-calls strub mode,
+   but not viable for that mode because its address is taken.
+   It is eligible and viable for internal strub mode.  */
+void setp (void) @{ static void (*p)(void); = setp; @}
+@end smallexample
+
+A function is ineligible for @code{internal} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, or if attribute
+@code{noipa} is present.  For an @code{always_inline} function, meeting
+these requirements is enough to make it eligible.  Any function that has
+attribute @code{noclone}, that uses such extensions as non-local labels,
+computed gotos, alternate variable argument passing interfaces,
+@code{__builtin_next_arg}, or @code{__builtin_return_address}, or that
+takes too many (about 64Ki) arguments is ineligible, unless it is
+@code{always_inline}.  For @code{internal} @code{strub} mode, all
+eligible functions are viable.
+
+@smallexample
+/* flop is not eligible, thus not viable, for at-calls strub mode.
+   Likewise for internal strub mode.  */
+__attribute__ ((noipa)) void flop (void) @{@}
+
+/* flip is eligible and viable for at-calls strub mode.
+   It would be ineligible for internal strub mode, because of noclone,
+   if it weren't for always_inline.  With always_inline, noclone is not
+   an obstacle, so it is also eligible and viable for internal strub mode.  */
+inline __attribute__ ((noclone, always_inline)) void flip (void) @{@}
+@end smallexample
 
 @item unused
 @cindex @code{unused} type attribute
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 838d9ff1198..dd529c1a82e 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,7 +599,8 @@ Objective-C and Objective-C++ Dialects}.
 -fstack-protector-explicit  -fstack-check @gol
 -fstack-limit-register=@var{reg}  -fstack-limit-symbol=@var{sym} @gol
 -fno-stack-limit  -fsplit-stack @gol
--fstrub=default -fstrub=disable -fstrub=at-calls -fstrub=internal -fstrub=all @gol
+-fstrub=disable -fstrub=strict -fstrub=relaxed @gol
+-fstrub=all -fstrub=at-calls -fstrub=internal @gol
 -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
 -fvtv-counts  -fvtv-debug @gol
 -finstrument-functions @gol
@@ -15513,46 +15514,41 @@ without @option{-fsplit-stack} always has a large stack.  Support for
 this is implemented in the gold linker in GNU binutils release 2.21
 and later.
 
-@item -fstrub=default
-@opindex fstrub=default
-Restore the default stack scrub (@code{strub}) setting, namely,
-@code{strub} is only enabled as required by @code{strub} attributes
-associated with function or variable types.  This is only useful to
-override earlier @samp{-fstrub=*} options.
-
 @item -fstrub=disable
 @opindex -fstrub=disable
 Disable stack scrubbing entirely, ignoring any @code{strub} attributes.
+See @xref{Common Type Attributes}.
+
+@item -fstrub=strict
+@opindex fstrub=strict
+Functions default to @code{strub} mode @code{disabled}, and apply
+@code{strict}ly the restriction that only functions associated with
+@code{strub}-@code{calalble} modes (@code{at-calls}, @code{callable} and
+@code{always_inline} @code{internal}) are @code{callable} by functions
+with @code{strub}-enabled modes (@code{at-calls} and @code{internal}).
+
+@item -fstrub=relaxed
+@opindex fstrub=relaxed
+Restore the default stack scrub (@code{strub}) setting, namely,
+@code{strub} is only enabled as required by @code{strub} attributes
+associated with function and data types.  @code{Relaxed} means that
+strub contexts are only prevented from calling functions explicitly
+associated with @code{strub} mode @code{disabled}.  This option is only
+useful to override other @samp{-fstrub=*} options that precede it in the
+command line.
 
 @item -fstrub=at-calls
 @opindex fstrub=at-calls
-Enable @code{at-calls} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{at-calls} @code{strub} if a different @code{strub}
-mode is explicitly requested, if attribute @code{noipa} is present, or
-if it calls @code{__builtin_apply_args}.  @code{At-calls} @code{strub}
-mode, if not requested through the function type, is only viable for an
-eligible function if the function is not visible to other translation
-units, and it doesn't have its address taken.
+Enable @code{at-calls} @code{strub} mode where viable.
 
 @item -fstrub=internal
 @opindex fstrub=internal
-Enable @code{internal} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{internal} @code{strub} if a different @code{strub}
-mode is explicitly requested, or if attribute @code{noipa} is present.
-Non-@code{always_inline} functions also become ineligible if attribute
-@code{noclone} is present, if the function uses such features as user
-labels, non-default variable argument interfaces,
-@code{__builtin_next_arg}, or @code{__builtin_return_address}, or if
-they have too many (about 64Ki) arguments.  For @code{internal}
-@code{strub}, all eligible functions are viable.
+Enable @code{internal} @code{strub} mode where viable.
 
 @item -fstrub=all
 @opindex fstrub=all
-Enable @code{strub} for all viable functions, and consider non-viable
-functions as @code{callable}.  When both strub modes are viable,
-@code{at-calls} is preferred.
+Enable some @code{strub} mode where viable.
+When both strub modes are viable, @code{at-calls} is preferred.
 
 @item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 @opindex fvtable-verify
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 7f4bc44d2bb..ee360b6c6ca 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -397,7 +397,7 @@ can_inline_edge_p (struct cgraph_edge *e, bool report,
       e->inline_failed = CIF_SANITIZE_ATTRIBUTE_MISMATCH;
       inlinable = false;
     }
-  if (!strub_inlinable_p (callee, caller))
+  if (!strub_inlinable_from_p (callee, caller))
     {
       e->inline_failed = CIF_UNSPECIFIED;
       inlinable = false;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
index cf7d811779e..87f55fc9b24 100644
--- a/gcc/ipa-strub.c
+++ b/gcc/ipa-strub.c
@@ -159,12 +159,16 @@ enum strub_mode {
 
 };
 
+/* Look up a strub attribute in TYPE, and return it.  */
+
 static tree
 get_strub_attr_from_type (tree type)
 {
   return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
 }
 
+/* Look up a strub attribute in DECL or in its type, and return it.  */
+
 static tree
 get_strub_attr_from_decl (tree decl)
 {
@@ -174,23 +178,159 @@ get_strub_attr_from_decl (tree decl)
   return get_strub_attr_from_type (TREE_TYPE (decl));
 }
 
-tree
+/* Define a function to cache identifier ID, to be used as a strub attribute
+   parameter for a strub mode named after NAME.  */
+#define DEF_STRUB_IDS(NAME, ID)				\
+static inline tree get_strub_mode_id_ ## NAME () {	\
+  static tree identifier = NULL_TREE;			\
+  if (!identifier)					\
+    identifier = get_identifier (ID);			\
+  return identifier;					\
+}
+/* Same as DEF_STRUB_IDS, but use the string expansion of NAME as ID.  */
+#define DEF_STRUB_ID(NAME)				\
+DEF_STRUB_IDS (NAME, #NAME)
+
+/* Define functions for each of the strub mode identifiers.
+   Expose dashes rather than underscores.  */
+DEF_STRUB_ID (disabled)
+DEF_STRUB_IDS (at_calls, "at-calls")
+DEF_STRUB_ID (internal)
+DEF_STRUB_ID (callable)
+DEF_STRUB_ID (wrapped)
+DEF_STRUB_ID (wrapper)
+DEF_STRUB_ID (inlinable)
+DEF_STRUB_IDS (at_calls_opt, "at-calls-opt")
+
+/* Release the temporary macro names.  */
+#undef DEF_STRUB_IDS
+#undef DEF_STRUB_ID
+
+/* Return the identifier corresponding to strub MODE.  */
+
+static tree
+get_strub_mode_attr_parm (enum strub_mode mode)
+{
+  switch (mode)
+    {
+    case STRUB_DISABLED:
+      return get_strub_mode_id_disabled ();
+
+    case STRUB_AT_CALLS:
+      return get_strub_mode_id_at_calls ();
+
+    case STRUB_INTERNAL:
+      return get_strub_mode_id_internal ();
+
+    case STRUB_CALLABLE:
+      return get_strub_mode_id_callable ();
+
+    case STRUB_WRAPPED:
+      return get_strub_mode_id_wrapped ();
+
+    case STRUB_WRAPPER:
+      return get_strub_mode_id_wrapper ();
+
+    case STRUB_INLINABLE:
+      return get_strub_mode_id_inlinable ();
+
+    case STRUB_AT_CALLS_OPT:
+      return get_strub_mode_id_at_calls_opt ();
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+/* Return the parmeters (TREE_VALUE) for a strub attribute of MODE.
+   We know we use a single parameter, so we bypass the creation of a
+   tree list.  */
+
+static tree
 get_strub_mode_attr_value (enum strub_mode mode)
 {
-  return tree_cons (NULL_TREE,
-		    build_int_cst (integer_type_node, (int)mode),
-		    NULL_TREE);
+  return get_strub_mode_attr_parm (mode);
+}
+
+/* Determine whether ID is a well-formed strub mode-specifying attribute
+   parameter.  Only user-visible modes are accepted.
+
+   For unacceptable parms, return 0, otherwise a nonzero value as below.
+
+   If the parm enables strub, return positive, otherwise negative.
 
-#if 0 /* ??? use symbolic mode names with interned strings?  */
-  char *s = NULL;
+   If the affected type must be a distinct, incompatible type,return an integer
+   of absolute value 2, otherwise 1.  */
 
-  switch (strub_mode)
+int
+strub_validate_parm (tree id)
+{
+  int ret;
+
+  if (!id)
+    return true;
+
+  const char *s = NULL;
+  size_t len = 0;
+
+  if (TREE_CODE (id) == STRING_CST)
     {
-      
+      s = TREE_STRING_POINTER (id);
+      len = TREE_STRING_LENGTH (id) - 1;
     }
-#endif
+  else if (TREE_CODE (id) == IDENTIFIER_NODE)
+    {
+      s = IDENTIFIER_POINTER (id);
+      len = IDENTIFIER_LENGTH (id);
+    }
+  else
+    return false;
+
+  enum strub_mode mode;
+
+  if (len != 8)
+    return false;
+
+  switch (s[0])
+    {
+    case 'd':
+      mode = STRUB_DISABLED;
+      ret = -1;
+      break;
+
+    case 'a':
+      mode = STRUB_AT_CALLS;
+      ret = 2;
+      break;
+
+    case 'i':
+      mode = STRUB_INTERNAL;
+      ret = 1;
+      break;
+
+    case 'c':
+      mode = STRUB_CALLABLE;
+      ret = -2;
+      break;
+
+    default:
+      /* Other parms are for internal use only.  */
+      return 0;
+    }
+
+  tree mode_id = get_strub_mode_attr_parm (mode);
+
+  if (TREE_CODE (id) == IDENTIFIER_NODE
+      ? id == mode_id
+      : strncmp (s, IDENTIFIER_POINTER (mode_id), len))
+    return 0;
+
+  return ret;
 }
 
+/* Return the strub mode from STRUB_ATTR.  VAR_P should be true if the attribute
+   is taken from a variable, rather than from a function, or a type thereof.  */
+
 static enum strub_mode
 get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
 {
@@ -200,28 +340,102 @@ get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
     {
       if (!TREE_VALUE (strub_attr))
 	mode = !var_p ? STRUB_AT_CALLS : STRUB_INTERNAL;
-      else if (TREE_CODE (TREE_VALUE (TREE_VALUE (strub_attr))) == INTEGER_CST)
-	mode = (enum strub_mode) tree_to_shwi (TREE_VALUE
-					       (TREE_VALUE (strub_attr)));
-      else /* Handlers convert symbolic mode names to INTEGER_CST.  */
-	gcc_unreachable ();
+      else
+	{
+	  gcc_checking_assert (!var_p);
+	  tree id = TREE_VALUE (strub_attr);
+	  if (TREE_CODE (id) == TREE_LIST)
+	    id = TREE_VALUE (id);
+	  const char *s = (TREE_CODE (id) == STRING_CST
+			   ? TREE_STRING_POINTER (id)
+			   : IDENTIFIER_POINTER (id));
+	  size_t len = (TREE_CODE (id) == STRING_CST
+			? TREE_STRING_LENGTH (id) - 1
+			: IDENTIFIER_LENGTH (id));
+
+	  switch (len)
+	    {
+	    case 7:
+	      switch (s[6])
+		{
+		case 'r':
+		  mode = STRUB_WRAPPER;
+		  break;
+
+		case 'd':
+		  mode = STRUB_WRAPPED;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 8:
+	      switch (s[0])
+		{
+		case 'd':
+		  mode = STRUB_DISABLED;
+		  break;
+
+		case 'a':
+		  mode = STRUB_AT_CALLS;
+		  break;
+
+		case 'i':
+		  mode = STRUB_INTERNAL;
+		  break;
+
+		case 'c':
+		  mode = STRUB_CALLABLE;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 9:
+	      mode = STRUB_INLINABLE;
+	      break;
+
+	    case 12:
+	      mode = STRUB_AT_CALLS_OPT;
+	      break;
+
+	    default:
+	      gcc_unreachable ();
+	    }
+
+	  gcc_checking_assert (TREE_CODE (id) == IDENTIFIER_NODE
+			       ? id == get_strub_mode_attr_parm (mode)
+			       : strncmp (IDENTIFIER_POINTER
+					  (get_strub_mode_attr_parm (mode)),
+					  s, len) == 0);
+	}
     }
 
   return mode;
 }
 
+/* Look up, decode and return the strub mode associated with FNDECL.  */
+
 static enum strub_mode
-get_strub_mode_from_decl (tree fndecl)
+get_strub_mode_from_fndecl (tree fndecl)
 {
   return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
 }
 
+/* Look up, decode and return the strub mode associated with NODE.  */
+
 static enum strub_mode
 get_strub_mode (cgraph_node *node)
 {
-  return get_strub_mode_from_decl (node->decl);
+  return get_strub_mode_from_fndecl (node->decl);
 }
 
+/* Look up, decode and return the strub mode associated with TYPE.  */
+
 static enum strub_mode
 get_strub_mode_from_type (tree type)
 {
@@ -231,12 +445,15 @@ get_strub_mode_from_type (tree type)
   if (attr)
     return get_strub_mode_from_attr (attr, var_p);
 
-  if (flag_strub > 0 && !var_p)
+  if (flag_strub >= -1 && !var_p)
     return STRUB_CALLABLE;
 
   return STRUB_DISABLED;
 }
 
+\f
+/* Return true iff NODE calls builtin va_start.  */
+
 static bool
 calls_builtin_va_start_p (cgraph_node *node)
 {
@@ -252,6 +469,8 @@ calls_builtin_va_start_p (cgraph_node *node)
   return result;
 }
 
+/* Return true iff NODE calls builtin apply_args, and optionally REPORT it.  */
+
 static bool
 calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
 {
@@ -276,12 +495,17 @@ calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE carries the always_inline attribute.  */
+
 static inline bool
 strub_always_inline_p (cgraph_node *node)
 {
   return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
 }
 
+/* Return true iff NODE is potentially eligible for any strub-enabled mode, and
+   optionally REPORT the reasons for ineligibility.  */
+
 static inline bool
 can_strub_p (cgraph_node *node, bool report = false)
 {
@@ -321,6 +545,10 @@ can_strub_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE is eligible for at-calls strub, and optionally REPORT
+   the reasons for ineligibility.  Besides general non-eligibility for
+   strub-enabled modes, at-calls rules out calling builtin apply_args.  */
+
 static bool
 can_strub_at_calls_p (cgraph_node *node, bool report = false)
 {
@@ -332,6 +560,9 @@ can_strub_at_calls_p (cgraph_node *node, bool report = false)
   return !calls_builtin_apply_args_p (node, report);
 }
 
+/* Symbolic macro for the max number of arguments that internal strub may add to
+   a function.  */
+
 #define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
 
 /* We can't perform internal strubbing if the function body involves certain
@@ -438,14 +669,12 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 			   || tree_versionable_function_p (node->decl));
 
 
-      /* Label values referenced are not preserved when copying.  If referenced
+      /* Label values references are not preserved when copying.  If referenced
 	 in nested functions, as in 920415-1.c and 920721-4.c their decls get
-	 remapped independently.  That might be too broad, in that we might be
-	 able to support correctly cases in which the labels are only used
-	 internally in a function, but disconnecting user labels from their
-	 original declarations is undesirable in general, and it probably
-	 doesn't matter, since explicitly-requested strub likely uses
-	 STRUB_AT_CALLS mode anyway.  */
+	 remapped independently.  The exclusion below might be too broad, in
+	 that we might be able to support correctly cases in which the labels
+	 are only used internally in a function, but disconnecting forced labels
+	 from their original declarations is undesirable in general.  */
       basic_block bb;
       FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
 	for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -459,8 +688,6 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 
 	    target = gimple_label_label (label_stmt);
 
-	    /* Make an edge to every label block that has been marked as a
-	       potential target for a computed goto or a non-local goto.  */
 	    if (!FORCED_LABEL (target))
 	      continue;
 
@@ -470,7 +697,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 forced labels");
 	  }
     }
 
@@ -491,6 +718,9 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return TRUE iff NODE has any strub-requiring local variable, or accesses (as
+   in reading) any variable through a strub-requiring type.  */
+
 static bool
 strub_from_body_p (cgraph_node *node)
 {
@@ -506,7 +736,9 @@ strub_from_body_p (cgraph_node *node)
 	!= STRUB_DISABLED)
       return true;
 
-  /* Now scan the body for loads with strub types.  */
+  /* Now scan the body for loads with strub-requiring types.
+     ??? Compound types don't propagate the strub requirement to
+     component types.  */
   basic_block bb;
   FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
     for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -528,6 +760,7 @@ strub_from_body_p (cgraph_node *node)
 
 /* Return true iff node is associated with a builtin that should be callable
    from strub contexts.  */
+
 static inline bool
 strub_callable_builtin_p (cgraph_node *node)
 {
@@ -568,17 +801,23 @@ strub_callable_builtin_p (cgraph_node *node)
     }
 }
 
+/* Compute the strub mode to be used for NODE.  STRUB_ATTR should be the strub
+   attribute,found for NODE, if any.  */
+
 static enum strub_mode
 compute_strub_mode (cgraph_node *node, tree strub_attr)
 {
   enum strub_mode req_mode = get_strub_mode_from_attr (strub_attr);
 
-  gcc_checking_assert (flag_strub >= -1 && flag_strub <= 3);
+  gcc_checking_assert (flag_strub >= -2 && flag_strub <= 3);
 
   /* Symbolic encodings of the -fstrub-* flags.  */
   /* Enable strub when explicitly requested through attributes to functions or
      variables, reporting errors if the requests cannot be satisfied.  */
   const bool strub_flag_auto = flag_strub < 0;
+  /* strub_flag_auto with strub call verification; without this, functions are
+     implicitly callable.  */
+  const bool strub_flag_strict = flag_strub < -1;
   /* Disable strub altogether, ignore attributes entirely.  */
   const bool strub_flag_disabled = flag_strub == 0;
   /* On top of _auto, also enable strub implicitly for functions that can
@@ -625,7 +864,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (!strub_flag_disabled
        && (strub_attr
 	   ? req_mode == STRUB_CALLABLE
-	   : (strub_flag_viable
+	   : (!strub_flag_strict
 	      || strub_callable_builtin_p (node))));
 
   /* This is a shorthand for either strub-enabled mode.  */
@@ -674,19 +913,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (at_calls_eligible
        && (strub_attr
 	   || (node->has_gimple_body_p ()
-#if 0 /* We no longer use collect_callers, so we can probably drop it.  */
-	       && node->get_availability () > AVAIL_INTERPOSABLE
-#endif
-	       && ((!node->externally_visible
-#if 0
-		    /* We wish to bypass the test below for functions that are
-		       not externally visible, but that's a little too broad: we
-		       do not wish to skip them for e.g. gnu_inline
-		       functions.  */
-		    && !TREE_PUBLIC (node->decl)
-		    && !DECL_EXTERNAL (node->decl)
-#endif
-		    )
+	       && (!node->externally_visible
 		   || (node->binds_to_current_def_p ()
 		       && node->can_be_local_p ()))
 	       && node->only_called_directly_p ())));
@@ -737,10 +964,6 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
   const enum strub_mode mode
     = ((strub_enable && is_always_inline)
        ? (strub_required ? STRUB_INLINABLE : STRUB_CALLABLE)
-#if 0
-       : (!strub_enable && strub_required && strub_attr)
-       ? req_mode
-#endif
        : (strub_enable && internal_viable
 	  && (strub_flag_internal || !at_calls_viable))
        ? STRUB_INTERNAL
@@ -802,6 +1025,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 /* Set FNDT's strub mode to MODE; FNDT may be a function decl or
    function type.  If OVERRIDE, do not check whether a mode is already
    set.  */
+
 static void
 strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
 {
@@ -828,12 +1052,18 @@ strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
   *attrp = attr;
 }
 
+/* Set FNDT's strub mode to callable.
+   FNDT may be a function decl or a function type.  */
+
 void
 strub_make_callable (tree fndt)
 {
   strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
 }
 
+/* Set NODE to strub MODE.  Report incompatibilities between MODE and the mode
+   requested through explicit attributes, and cases of non-eligibility.  */
+
 static void
 set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
 {
@@ -906,6 +1136,8 @@ set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
   strub_set_fndt_mode_to (node->decl, mode, attr);
 }
 
+/* Compute and set NODE's strub mode.  */
+
 static void
 set_strub_mode (cgraph_node *node)
 {
@@ -946,6 +1178,7 @@ set_strub_mode (cgraph_node *node)
   set_strub_mode_to (node, mode);
 }
 
+\f
 /* Non-strub functions shouldn't be called from within strub contexts,
    except through callable ones.  Always inline strub functions can
    only be called from strub functions.  */
@@ -984,7 +1217,7 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_AT_CALLS_OPT:
     case STRUB_INTERNAL:
     case STRUB_WRAPPER:
-      return (flag_strub >= 0);
+      return (flag_strub >= -1);
 
     case STRUB_DISABLED:
       return false;
@@ -1004,7 +1237,7 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
    strubbed functions into non-strubbed ones.  */
 
 bool
-strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+strub_inlinable_from_p (cgraph_node *callee, cgraph_node *caller)
 {
   strub_mode callee_mode = get_strub_mode (callee);
 
@@ -1049,14 +1282,43 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
   return false;
 }
 
+/* Check that types T1 and T2 are strub-compatible.  Return 1 if the strub modes
+   are the same, 2 if they are interchangeable, and 0 otherwise.  */
+
+int
+strub_comptypes (tree t1, tree t2)
+{
+  enum strub_mode m1 = get_strub_mode_from_type (t1);
+  enum strub_mode m2 = get_strub_mode_from_type (t2);
+
+  if (m1 == m2)
+    return 1;
+
+  /* We're dealing with types, so only strub modes that can be selected by
+     attributes in the front end matter.  If either mode is at-calls, the
+     conversion is not compatible.  */
+  if (m1 == STRUB_AT_CALLS || m2 == STRUB_AT_CALLS)
+    return 0;
+
+  return 2;
+}
+
 /* Check that strub functions don't call non-strub functions, and that
    always_inline strub functions are only called by strub
    functions.  */
+
 static void
 verify_strub ()
 {
   cgraph_node *node;
 
+  /* It's expected that check strub-wise pointer type compatibility of variables
+     and of functions is already taken care of by front-ends, on account of the
+     attribute's being marked as affecting type identity and of the creation of
+     distinct types.  */
+
+  /* Check that call targets in strub contexts have strub-callable types.  */
+
   FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
   {
     enum strub_mode caller_mode = get_strub_mode (node);
@@ -1105,14 +1367,11 @@ verify_strub ()
 	  }
       }
   }
-
-  /* ??? Check strub-wise pointer type compatibility of variables and
-     functions, or is this already taken care of on account of the
-     attribute's being marked as affecting type identity?  */
 }
 
 namespace {
 
+/* Define a pass to compute strub modes.  */
 const pass_data pass_data_ipa_strub_mode = {
   SIMPLE_IPA_PASS,
   "strubm",
@@ -1133,18 +1392,20 @@ public:
   {}
   opt_pass *clone () { return new pass_ipa_strub_mode (m_ctxt); }
   virtual bool gate (function *) {
-    /* In the default setting, the attribute handler changes
-       flag_strub to -1 if any strub-enabling occurence of the
-       attribute is found.  If it remains at -2, nothing that would
-       enable strub was found, so we can disable it and avoid the
+    /* In relaxed (-3) and strict (-4) settings, that only enable strub at a
+       function or variable attribute's request, the attribute handler changes
+       flag_strub to -1 or -2, respectively, if any strub-enabling occurence of
+       the attribute is found.  Therefore, if it remains at -3 or -4, nothing
+       that would enable strub was found, so we can disable it and avoid the
        overhead.  */
-    if (flag_strub == -2)
+    if (flag_strub < -2)
       flag_strub = 0;
     return flag_strub;
   }
   virtual unsigned int execute (function *);
 };
 
+/* Define a pass to introduce strub transformations.  */
 const pass_data pass_data_ipa_strub = {
   SIMPLE_IPA_PASS,
   "strub",
@@ -1170,6 +1431,7 @@ public:
   virtual bool gate (function *) { return flag_strub; }
   virtual unsigned int execute (function *);
 
+  /* Define on demand and cache some types we use often.  */
 #define DEF_TYPE(NAME, INIT)			\
   static inline tree get_ ## NAME () {		\
     static tree type = NULL_TREE;		\
@@ -1202,6 +1464,7 @@ public:
 
 #undef DEF_TYPE
 
+  /* Define non-strub builtins on demand.  */
 #define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST)			\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1224,6 +1487,7 @@ public:
 
 #undef DEF_NM_BUILTIN
 
+  /* Define strub builtins on demand.  */
 #define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST)		\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1260,6 +1524,7 @@ public:
 
 #undef DEF_SS_BUILTIN
 
+    /* Define strub identifiers on demand.  */
 #define DEF_IDENT(NAME)					\
   static inline tree get_ ## NAME () {			\
     static tree identifier = NULL_TREE;			\
@@ -1278,6 +1543,10 @@ public:
   static inline void adjust_at_calls_call (cgraph_edge *, int);
   static inline void adjust_at_calls_calls (cgraph_node *);
 
+  /* Add to SEQ a call to the strub watermark update builtin, taking NODE's
+     location if given.  Optionally add the corresponding edge from NODE, with
+     execution frequency COUNT.  Return the modified SEQ.  */
+
   static inline gimple_seq
   call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
 			 gimple_seq seq = NULL)
@@ -1300,8 +1569,15 @@ public:
 
 } // anon namespace
 
+/* Gather with this type a collection of parameters that we're turning into
+   explicit references.  */
+
 typedef hash_set<tree> indirect_parms_t;
 
+/* Dereference OP's incoming turned-into-reference parm if it's an
+   INDIRECT_PARMS or an ADDR_EXPR thereof.  Set *REC and return according to
+   gimple-walking expectations.  */
+
 static tree
 maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
 {
@@ -1324,12 +1600,14 @@ maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
     {
       *rec = 0;
       if (indirect_parms.contains (TREE_OPERAND (op, 0)))
-	return TREE_OPERAND (op, 0);
+	return get_or_create_ssa_default_def (cfun, TREE_OPERAND (op, 0));
     }
 
   return NULL_TREE;
 }
 
+/* A gimple-walking function that adds dereferencing to indirect parms.  */
+
 static tree
 walk_make_indirect (tree *op, int *rec, void *arg)
 {
@@ -1351,6 +1629,11 @@ walk_make_indirect (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* A gimple-walking function that turns any non-gimple-val ADDR_EXPRs into a
+   separate SSA.  Though addresses of e.g. parameters, and of members thereof,
+   are gimple vals, turning parameters into references, with an extra layer of
+   indirection and thus explicit dereferencing, need to be regimplified.  */
+
 static tree
 walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
 {
@@ -1374,6 +1657,11 @@ walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* Create a reference type to use for PARM when turning it into a reference.
+   NONALIASED causes the reference type to gain its own separate alias set, so
+   that accessing the indirectly-passed parm won'will not add aliasing
+   noise.  */
+
 static tree
 build_ref_type_for (tree parm, bool nonaliased = true)
 {
@@ -1411,6 +1699,7 @@ build_ref_type_for (tree parm, bool nonaliased = true)
 
 /* Add cgraph edges from current_function_decl to callees in SEQ with frequency
    COUNT, assuming all calls in SEQ are direct.  */
+
 static void
 add_call_edges_for_seq (gimple_seq seq, profile_count count)
 {
@@ -1425,16 +1714,22 @@ add_call_edges_for_seq (gimple_seq seq, profile_count count)
     {
       gimple *stmt = gsi_stmt (gsi);
 
-      if (!is_a <gcall *> (stmt))
+      gcall *call = dyn_cast <gcall *> (stmt);
+      if (!call)
 	continue;
 
-      gcall *call = as_a <gcall *> (stmt);
       tree callee = gimple_call_fndecl (call);
       gcc_checking_assert (callee);
       node->create_edge (cgraph_node::get_create (callee), call, count, false);
     }
 }
 
+/* Insert SEQ after the call at GSI, as if the call was in a try block with SEQ
+   as finally, i.e., SEQ will run after the call whether it returns or
+   propagates an exception.  This handles block splitting, EH edge and block
+   creation, noreturn and nothrow optimizations, and even throwing calls without
+   preexisting local handlers.  */
+
 static void
 gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
 {
@@ -1446,7 +1741,7 @@ gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
   if (gimple_has_location (stmt))
     annotate_all_with_location (seq, gimple_location (stmt));
 
-  gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+  gcall *call = dyn_cast <gcall *> (stmt);
   bool noreturn_p = call && gimple_call_noreturn_p (call);
   int eh_lp = lookup_stmt_eh_lp (stmt);
   bool must_not_throw_p = eh_lp < 0;
@@ -1586,8 +1881,12 @@ remove_named_attribute_unsharing (const char *name, tree *attrs)
     }
 }
 
+/* Record the order of the last cgraph entry whose mode we've already set, so
+   that we can perform mode setting incrementally without duplication.  */
 static int last_cgraph_order;
 
+/* Set strub modes for functions introduced since the last call.  */
+
 static void
 ipa_strub_set_mode_for_new_functions ()
 {
@@ -1616,6 +1915,7 @@ ipa_strub_set_mode_for_new_functions ()
 }
 
 /* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
+
 bool
 strub_splittable_p (cgraph_node *node)
 {
@@ -1641,10 +1941,11 @@ strub_splittable_p (cgraph_node *node)
 }
 
 /* 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))
+  switch (get_strub_mode_from_fndecl (fndecl))
     {
     case STRUB_WRAPPED:
     case STRUB_AT_CALLS:
@@ -1676,6 +1977,7 @@ strub_watermark_parm (tree fndecl)
 
 /* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
    hasn't been added yet.  Return the named argument count.  */
+
 int
 pass_ipa_strub::adjust_at_calls_type (tree type)
 {
@@ -1751,6 +2053,12 @@ pass_ipa_strub::adjust_at_calls_type (tree type)
   return named_args;
 }
 
+/* Adjust a call to an at-calls call target.  Create a watermark local variable
+   if needed, initialize it before, pass it to the callee according to the
+   modified at-calls interface, and release the callee's stack space after the
+   call, if not deferred.  If the call is const or pure, arrange for the
+   watermark to not be assumed unused or unchanged.  */
+
 void
 pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 {
@@ -1819,10 +2127,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 
     if (gimple_call_internal_p (stmt))
 #if 0
-      /*
-	new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
-	vargs);
-      */
+      new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
+						 vargs);
 #endif
       gcc_unreachable ();
     else
@@ -1917,6 +2223,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
   gsi_insert_finally_seq_after_call (gsi, seq);
 }
 
+/* Adjust all at-calls calls in NODE. */
+
 void
 pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
 {
@@ -1965,6 +2273,10 @@ pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
     }
 }
 
+/* The strubm (strub mode) pass computes a strub mode for each function in the
+   call graph, and checks, before any inlining, that strub callability
+   requirements in effect are satisfied.  */
+
 unsigned int
 pass_ipa_strub_mode::execute (function *)
 {
@@ -1977,12 +2289,17 @@ pass_ipa_strub_mode::execute (function *)
   return 0;
 }
 
+/* Create a strub mode pass.  */
+
 simple_ipa_opt_pass *
 make_pass_ipa_strub_mode (gcc::context *ctxt)
 {
   return new pass_ipa_strub_mode (ctxt);
 }
 
+/* The strub pass proper adjusts types, signatures, and at-calls calls, and
+   splits internal-strub functions.  */
+
 unsigned int
 pass_ipa_strub::execute (function *)
 {
@@ -2042,17 +2359,6 @@ pass_ipa_strub::execute (function *)
 	if (onode->alias)
 	  continue;
 
-#if 0 /* Calls are now adjusted when examining callers.  */
-	unsigned c;
-	cgraph_edge *e;
-	FOR_EACH_VEC_ELT (onode->collect_callers (), c, e)
-	  {
-	    push_cfun (DECL_STRUCT_FUNCTION (e->caller->decl));
-	    adjust_at_calls_call (e, named_args);
-	    pop_cfun ();
-	  }
-#endif
-
 	cgraph_node *nnode = onode;
 	push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
 
@@ -2071,10 +2377,10 @@ pass_ipa_strub::execute (function *)
 		{
 		  gimple *stmt = gsi_stmt (gsi);
 
-		  if (!is_gimple_call (stmt))
-		    continue;
+		  gcall *call = dyn_cast <gcall *> (stmt);
 
-		  gcall *call = as_a <gcall *> (stmt);
+		  if (!call)
+		    continue;
 
 		  if (gimple_alloca_call_p (call))
 		    {
@@ -2088,10 +2394,6 @@ pass_ipa_strub::execute (function *)
 	  }
 
 	pop_cfun ();
-
-#if 0
-	compute_fn_summary (onode, true);
-#endif
       }
   }
 
@@ -2108,43 +2410,6 @@ pass_ipa_strub::execute (function *)
 	continue;
       }
 
-#if 0
-    /* Hmm, this is an i386-specific attribute.  Do we need machine-specific
-       logic?  */
-    remove_named_attribute_unsharing ("interrupt",
-				      &DECL_ATTRIBUTES (onode->decl));
-#endif
-
-#if 0
-    if (!DECL_STRUCT_FUNCTION (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting struct-less function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    if (!onode->lowered)
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting non-lowered function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    /* 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.  FIXME.  */
-    if (!tree_versionable_function_p (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"%qD cannot be split for %<strub%>",
-		onode->decl);
-	continue;
-      }
-#endif
-
     bool is_stdarg = calls_builtin_va_start_p (onode);;
     bool apply_args = calls_builtin_apply_args_p (onode);
 
@@ -2754,25 +3019,32 @@ pass_ipa_strub::execute (function *)
 	{
 	  basic_block bb;
 	  FOR_EACH_BB_FN (bb, cfun)
-	    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
-		 !gsi_end_p (gsi); gsi_next (&gsi))
-	      {
-		gimple *stmt = gsi_stmt (gsi);
+	    for (int phi_p = 1; phi_p >= 0; phi_p--)
+	      for (gimple_stmt_iterator gsi = (phi_p
+					       ? gsi_start_nonvirtual_phis (bb)
+					       : gsi_start_bb (bb));
+		   !gsi_end_p (gsi); gsi_next (&gsi))
+		{
+		  gimple *stmt = gsi_stmt (gsi);
 
-		walk_stmt_info wi = {};
-		wi.info = &indirect_nparms;
-		walk_gimple_op (stmt, walk_make_indirect, &wi);
-		if (wi.changed)
-		  {
-		    if (!is_gimple_debug (gsi_stmt (gsi)))
-		      {
-			wi.info = &gsi;
-			walk_gimple_op (stmt, walk_regimplify_addr_expr,
-					&wi);
-		      }
-		    update_stmt (stmt);
-		  }
-	      }
+		  /* Emulate gsi_next_nonvirtual_phi.  */
+		  if (phi_p && virtual_operand_p (gimple_phi_result (stmt)))
+		    continue;
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed)
+		    {
+		      if (!is_gimple_debug (gsi_stmt (gsi)))
+			{
+			  wi.info = &gsi;
+			  walk_gimple_op (stmt, walk_regimplify_addr_expr,
+					  &wi);
+			}
+		      update_stmt (stmt);
+		    }
+		}
 	}
 
       if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca
@@ -2842,10 +3114,10 @@ pass_ipa_strub::execute (function *)
       push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
       gimple_stmt_iterator gsi
 	= gsi_after_labels (single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun)));
-      while (!is_gimple_call (gsi_stmt (gsi)))
-	gsi_next (&gsi);
 
-      gcall *wrcall = as_a <gcall *> (gsi_stmt (gsi));
+      gcall *wrcall;
+      while (!(wrcall = dyn_cast <gcall *> (gsi_stmt (gsi))))
+	gsi_next (&gsi);
 
       tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
       TREE_ADDRESSABLE (swm) = true;
@@ -2973,11 +3245,6 @@ pass_ipa_strub::execute (function *)
 
       pop_cfun ();
     }
-
-#if 0
-    compute_fn_summary (onode, true);
-    compute_fn_summary (nnode, true);
-#endif
   }
 
   if (flag_checking)
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
index c335ab42097..61a47b7ce1e 100644
--- a/gcc/ipa-strub.h
+++ b/gcc/ipa-strub.h
@@ -22,7 +22,7 @@ along with GCC; see the file COPYING3.  If not see
    as far as stack scrubbing constraints are concerned.  CALLEE
    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 bool strub_inlinable_from_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);
@@ -33,3 +33,14 @@ extern tree strub_watermark_parm (tree fndecl);
 
 /* Make a function type or declaration callable.  */
 extern void strub_make_callable (tree fndecl);
+
+/* Return zero iff ID is NOT an acceptable parameter for a user-supplied strub
+   attribute.  Otherwise, return >0 if it enables strub, <0 if it does not.
+   Return +/-1 if the attribute-modified type is compatible with the type
+   without the attribute, or +/-2 if it is not compatible.  */
+extern int strub_validate_parm (tree id);
+
+/* Like comptypes, return 0 if t1 and t2 are not compatible, 1 if they are
+   compatible, and 2 if they are nearly compatible.  Same strub mode is
+   compatible, interface-compatible strub modes are nearly compatible.  */
+extern int strub_comptypes (tree t1, tree t2);
diff --git a/gcc/testsuite/c-c++-common/strub-O0.c b/gcc/testsuite/c-c++-common/strub-O0.c
index 6afe0fd5de1..c7a79a6ea0d 100644
--- a/gcc/testsuite/c-c++-common/strub-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O0 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O0, none of the strub builtins are expanded inline.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O1.c b/gcc/testsuite/c-c++-common/strub-O1.c
index 1cdeaecaf32..96285c975d9 100644
--- a/gcc/testsuite/c-c++-common/strub-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O1 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O1, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
index 7848c46d179..8edc0d8aa13 100644
--- a/gcc/testsuite/c-c++-common/strub-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O2, without -fno-inline, we fully expand enter and update, and add a test
    around the leave call.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
index 85a8f76785e..c6d900cf3c4 100644
--- a/gcc/testsuite/c-c++-common/strub-O2fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3.c b/gcc/testsuite/c-c++-common/strub-O3.c
index 1fcde345d36..33ee465e51c 100644
--- a/gcc/testsuite/c-c++-common/strub-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand" } */
 
 int __attribute__ ((__strub__)) var;
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
index a2eedfd96b2..2936f82079e 100644
--- a/gcc/testsuite/c-c++-common/strub-O3fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
index e5cb1f60541..479746e57d8 100644
--- a/gcc/testsuite/c-c++-common/strub-Og.c
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Og -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Og -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Og, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
index 194aacc2c05..2241d4ea07f 100644
--- a/gcc/testsuite/c-c++-common/strub-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Os, without -fno-inline, we fully expand enter, and also update.  The
    expanded update might be larger than a call proper, but argument saving and
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
index 46e84bf6560..a322bcc5da6 100644
--- a/gcc/testsuite/c-c++-common/strub-all1.c
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -22,11 +22,11 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
index f377541cff0..db60026d0e0 100644
--- a/gcc/testsuite/c-c++-common/strub-all2.c
+++ b/gcc/testsuite/c-c++-common/strub-all2.c
@@ -17,8 +17,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
index f180b17f30e..2f462adc1ef 100644
--- a/gcc/testsuite/c-c++-common/strub-apply1.c
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -1,13 +1,13 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
-void __attribute__ ((__strub__ (3)))
+void __attribute__ ((__strub__ ("callable")))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0);
 }
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   void *args = __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
index 379a54b73b7..a5d7551f5da 100644
--- a/gcc/testsuite/c-c++-common/strub-apply2.c
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 extern void __attribute__ ((__strub__))
 apply_function (void *args);
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
index 9b4786be698..64422a0d1e8 100644
--- a/gcc/testsuite/c-c++-common/strub-apply3.c
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 void __attribute__ ((__strub__))
 apply_function (void *args)
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
index 409f747743e..ea18d0bbd54 100644
--- a/gcc/testsuite/c-c++-common/strub-apply4.c
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+/* { dg-options "-O2 -fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
index d964b07ae5d..b70843b4215 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -22,9 +22,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
index 530eee36d06..97a3988a6b9 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls2.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls2.c
@@ -17,7 +17,7 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
index 7b04eea35d9..3d73431b3dc 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O1" } */
+/* { dg-options "-fstrub=strict -O1" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O1.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
index 67d96419a5e..fddf3c745e7 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O2" } */
+/* { dg-options "-fstrub=strict -O2" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O2.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
index 34828d2711e..2bc384ee8d1 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O3" } */
+/* { dg-options "-fstrub=strict -O3" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -O3.  */
@@ -10,7 +10,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -18,7 +18,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -32,7 +32,7 @@ leak_string (void)
   return 0;
 }
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 int
 look_for_string (char *e)
 {
@@ -56,14 +56,14 @@ look_for_string (char *e)
   return 0;
 }
 
-static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+static __attribute__ ((__strub__ ("at-calls"), __noinline__, __noclone__))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 deferred_at_calls ()
 {
@@ -73,7 +73,7 @@ deferred_at_calls ()
   return ret;
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 deferred_internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
index b273660aea1..fbaf85fe0fa 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -Os" } */
+/* { dg-options "-fstrub=strict -Os" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -Os.  */
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
index a74658c9ac9..e9d7b7b9ee0 100644
--- a/gcc/testsuite/c-c++-common/strub-internal1.c
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -23,9 +23,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
index a6e69357b23..8b8e15a51c7 100644
--- a/gcc/testsuite/c-c++-common/strub-internal2.c
+++ b/gcc/testsuite/c-c++-common/strub-internal2.c
@@ -14,8 +14,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
index 7e22a266ad9..0a4a7539d34 100644
--- a/gcc/testsuite/c-c++-common/strub-parms1.c
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -16,7 +16,7 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 large_byref_arg (struct large_arg la)
 {
 }
@@ -24,7 +24,7 @@ large_byref_arg (struct large_arg la)
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*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)))
+void __attribute__ ((__strub__ ("internal")))
 std_arg (int i, ...)
 {
   va_list vl;
@@ -38,7 +38,7 @@ std_arg (int i, ...)
 /* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
index 2d8036c0fbc..147171d96d5 100644
--- a/gcc/testsuite/c-c++-common/strub-parms2.c
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -15,14 +15,14 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 large_byref_arg (struct large_arg la)
 {
 }
 
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 std_arg (int i, ...)
 {
   va_list vl;
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
index f64361f1235..4e92682895a 100644
--- a/gcc/testsuite/c-c++-common/strub-parms3.c
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
new file mode 100644
index 00000000000..e2f9d8aebca
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  Without the error,
+   inlining takes place.  */
+
+#include "strub-strict1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
new file mode 100644
index 00000000000..98474435d2e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  */
+
+#include "strub-strict2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
index a4226ce0119..1de15342595 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fexceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fexceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
index 3bab553478b..f9209c81900 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
index c89cc7c2c47..bed1dcfb54a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
index b869fafb691..6bf0071f52b 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
index 1d3dd2f2c2c..4732f515bf7 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
index 4dd4281b03b..8d6424c479a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-default1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
similarity index 59%
rename from gcc/testsuite/c-c++-common/strub-default1.c
rename to gcc/testsuite/c-c++-common/strub-strict1.c
index 79d00cedb9a..ea28ba07e6f 100644
--- a/gcc/testsuite/c-c++-common/strub-default1.c
+++ b/gcc/testsuite/c-c++-common/strub-strict1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
 
 static int __attribute__ ((__strub__)) var;
 
@@ -31,12 +31,12 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-4\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { 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" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-default2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
similarity index 71%
rename from gcc/testsuite/c-c++-common/strub-default2.c
rename to gcc/testsuite/c-c++-common/strub-strict2.c
index 487253e9227..36e6f47b957 100644
--- a/gcc/testsuite/c-c++-common/strub-default2.c
+++ b/gcc/testsuite/c-c++-common/strub-strict2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
 
 static int __attribute__ ((__strub__)) var;
 
@@ -22,8 +22,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
index 0840dddd136..e48e0610e07 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 #include "strub-tail-O2.c"
 
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
index 9330d6ff4c1..87cda7ab21b 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.
    Tail calls are short-circuited at -O2+.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
index 5b33ff1f530..b5e45ab0525 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
index 38935e3270b..46b051411fb 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -1,39 +1,39 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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);
+extern int __attribute__ ((__strub__ ("callable"))) xcallable (void);
+extern int __attribute__ ((__strub__ ("internal"))) xinternal (void);
+extern int __attribute__ ((__strub__ ("at-calls"))) xat_calls (void);
+extern int __attribute__ ((__strub__ ("disabled"))) 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__ ("callable"))) callable (void);
+int __attribute__ ((__strub__ ("internal"))) internal (void);
+int __attribute__ ((__strub__ ("at-calls"))) at_calls (void);
+int __attribute__ ((__strub__ ("disabled"))) disabled (void);
 
 int __attribute__ ((__strub__)) var;
 int var_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 icallable (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 iinternal (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 idisabled (void);
 static inline int __attribute__ ((__always_inline__))
 ivar_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 i_callable (void) { return 0; }
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 i_internal (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 i_at_calls (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 i_disabled (void) { return 0; }
 static inline int __attribute__ ((__always_inline__))
 i_var_user (void) { return var; }
@@ -70,7 +70,7 @@ i_var_user (void) { return var; }
 
 /* Not a strub context, so it can call anything.
    Explicitly declared as callable even from within strub contexts.  */
-int __attribute__ ((__strub__ (3)))
+int __attribute__ ((__strub__ ("callable")))
 callable (void) {
   int ret = 0;
 
@@ -88,7 +88,7 @@ callable (void) {
 
 /* 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)))
+int __attribute__ ((__strub__ ("internal")))
 internal (void) {
   int ret = var;
 
@@ -109,7 +109,7 @@ internal (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (1)))
+int __attribute__ ((__strub__ ("at-calls")))
 at_calls (void) {
   int ret = var;
 
@@ -130,7 +130,7 @@ at_calls (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (0)))
+int __attribute__ ((__strub__ ("disabled")))
 disabled () {
   int ret = 0;
 
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
index 100fb0c59a9..2857195706e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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,
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
index 9e818ac9748..98a92bc9eac 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
index d40e8aa45cb..5511a6e1e71 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __const__))
 f() {
   return 0;
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
index d4cbdaf10f3..47ee927964d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -1,12 +1,12 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__
 __attribute__ ((__const__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data1.c b/gcc/testsuite/c-c++-common/torture/strub-data1.c
index 62a03891ab6..7c27a2a1a6d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointed-to data enables strubbing if accessed.  */
 int __attribute__ ((__strub__)) var;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data2.c b/gcc/testsuite/c-c++-common/torture/strub-data2.c
index 9b7df13a280..e66d903780a 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointer itself is a strub variable, enabling internal strubbing when
    its value is used.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data3.c b/gcc/testsuite/c-c++-common/torture/strub-data3.c
index 515706caa32..5e08e0e58c6 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data4.c b/gcc/testsuite/c-c++-common/torture/strub-data4.c
index 0ec9e35429f..a818e7a38bb 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
index 69f3d65ed44..90e8ad51c1e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -Werror" } */
+/* { dg-options "-fstrub=strict -Werror" } */
 
 typedef int __attribute__ ((__strub__)) strub_int;
 strub_int *ptr;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
index b8adf8009e8..c165f312f16 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype ();
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
index 5b2c35ad6a7..69fcff8d376 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
index 5ee50456dc9..ff006224909 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int, ...);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
new file mode 100644
index 00000000000..9a69539db9d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+}
+
+void
+bat (void)
+{
+  /* Not allowed, not a strub context.  */
+  inl_int_ali (); /* { dg-error "context" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
new file mode 100644
index 00000000000..f9a6b4a16fa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=all" } */
+
+#include "strub-inlinable1.c"
+
+/* With -fstrub=all, the caller becomes a strub context, so the strub-inlinable
+   callee is not rejected.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
new file mode 100644
index 00000000000..a7b29a80ff8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -Werror" } */
+
+typedef void ft (void);
+typedef void ft2 (int, int);
+extern ft __attribute__ ((__strub__)) fnac;
+
+ft * f (void) {
+  return fnac; /* { dg-error "incompatible" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
new file mode 100644
index 00000000000..896383078cd
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
@@ -0,0 +1,19 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic -Werror" } */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at_calls"))) bal (void);
+
+void
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+
+  d_p = bac; /* { dg-error "not quite compatible" } */
+  c_p = bad; /* { dg-error "not quite compatible" } */
+  c_p = bar; /* { dg-error "not quite compatible" } */
+  c_p = bal; /* { dg-error "incompatible" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
index cb223da6efc..a262a086837 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
index 67d1434b1f8..4c4bd50c209 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
index 59f02ea901f..ce195c6b1f1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -1,10 +1,10 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __pure__))
 f() {
   static int i; /* Stop it from being detected as const.  */
   return i;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
index 973e909217d..75cd54ccb5b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
 __attribute__ ((__pure__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
index a4077c35a60..c596066a045 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -14,7 +14,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -59,14 +59,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
index 94e4156ea73..1fdba214a07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
index 0ca74beb59d..afbc2cc9ab4 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
 /* Check that a non-strub function leaves a string behind in the stack, and that
@@ -7,7 +7,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
index 4ab11c0682e..5300f1d330b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -85,14 +85,14 @@ intermediate ()
   return ret;
 }
 
-static inline __attribute__ ((__strub__ (2)))
+static inline __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
   return intermediate ();
 }
 
-int __attribute__ ((strub (0)))
+int __attribute__ ((__strub__ ("disabled")))
 main ()
 {
   if (look_for_string (internal ()))
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4d.c b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
index e4f7445607c..08de3f1c3b1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4d.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -1,7 +1,7 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
-#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ (1)))
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
 
 #include "strub-run4.c"
diff --git a/gcc/testsuite/g++.dg/torture/strub-init1.C b/gcc/testsuite/g++.dg/torture/strub-init1.C
index e51ae802be4..c226ab10ff6 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init1.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init2.C b/gcc/testsuite/g++.dg/torture/strub-init2.C
index edcb7bf8ad2..a7911f1fa72 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init2.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init3.C b/gcc/testsuite/g++.dg/torture/strub-init3.C
index bacf823ca4e..6ebebcd01e8 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init3.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
index 697ac9de764..2d6f6394993 100644
--- a/gcc/testsuite/gnat.dg/strub_attr.adb
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -1,5 +1,5 @@
 --  { dg-do compile }
---  { dg-options "-fstrub=default -fdump-ipa-strubm" }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
 
 package body Strub_Attr is
    E : exception;
diff --git a/libgcc/strub.c b/libgcc/strub.c
index fd6e27556e4..7c58deee1e6 100644
--- a/libgcc/strub.c
+++ b/libgcc/strub.c
@@ -36,7 +36,7 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 # define TOPS <
 #endif
 
-#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ ("callable")))
 
 /* Enter a stack scrubbing context, initializing the watermark to the caller's
    stack address.  */


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

* [gcc(refs/users/aoliva/heads/strub)] -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests
@ 2021-09-03  7:02 Alexandre Oliva
  0 siblings, 0 replies; 13+ messages in thread
From: Alexandre Oliva @ 2021-09-03  7:02 UTC (permalink / raw)
  To: gcc-cvs

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

commit d78b1cadaa166deceb0da9c3ccdde45d52843a8c
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Thu Sep 2 23:15:36 2021 -0300

    -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests

Diff:
---
 .../doc/gnat_rm/security_hardening_features.rst    |  30 ++
 gcc/ada/gcc-interface/utils.c                      |  95 ++--
 gcc/attribs.c                                      |  14 +-
 gcc/c-family/c-attribs.c                           |  84 ++--
 gcc/common.opt                                     |  27 +-
 gcc/doc/extend.texi                                | 288 ++++++++++--
 gcc/doc/invoke.texi                                |  54 ++-
 gcc/ipa-inline.c                                   |   2 +-
 gcc/ipa-strub.c                                    | 499 ++++++++++++++++-----
 gcc/ipa-strub.h                                    |  13 +-
 gcc/testsuite/c-c++-common/strub-O0.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O1.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2fni.c           |   2 +-
 gcc/testsuite/c-c++-common/strub-O3.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O3fni.c           |   2 +-
 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            |  12 +-
 gcc/testsuite/c-c++-common/strub-all2.c            |   6 +-
 gcc/testsuite/c-c++-common/strub-apply1.c          |   6 +-
 gcc/testsuite/c-c++-common/strub-apply2.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-apply3.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-apply4.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-at-calls2.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-defer-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |  14 +-
 gcc/testsuite/c-c++-common/strub-defer-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-internal1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-internal2.c       |   6 +-
 gcc/testsuite/c-c++-common/strub-parms1.c          |  10 +-
 gcc/testsuite/c-c++-common/strub-parms2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-parms3.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-relaxed1.c        |  18 +
 gcc/testsuite/c-c++-common/strub-relaxed2.c        |  14 +
 gcc/testsuite/c-c++-common/strub-short-O0-exc.c    |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O0.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O3.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-Os.c        |   2 +-
 .../{strub-default1.c => strub-strict1.c}          |  16 +-
 .../{strub-default2.c => strub-strict2.c}          |   8 +-
 gcc/testsuite/c-c++-common/strub-tail-O1.c         |   2 +-
 gcc/testsuite/c-c++-common/strub-tail-O2.c         |   2 +-
 .../c-c++-common/torture/strub-callable1.c         |   2 +-
 .../c-c++-common/torture/strub-callable2.c         |  42 +-
 gcc/testsuite/c-c++-common/torture/strub-const1.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const2.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const3.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-const4.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-data1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data3.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data4.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |   2 +-
 .../c-c++-common/torture/strub-indcall1.c          |   2 +-
 .../c-c++-common/torture/strub-indcall2.c          |   2 +-
 .../c-c++-common/torture/strub-indcall3.c          |   2 +-
 .../c-c++-common/torture/strub-inlinable1.c        |  16 +
 .../c-c++-common/torture/strub-inlinable2.c        |   7 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |  10 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |  19 +
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure3.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-pure4.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run1.c    |  10 +-
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run4.c    |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run4d.c   |   4 +-
 gcc/testsuite/g++.dg/torture/strub-init1.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init2.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init3.C         |   2 +-
 gcc/testsuite/gnat.dg/strub_attr.adb               |   2 +-
 libgcc/strub.c                                     |   2 +-
 79 files changed, 1020 insertions(+), 450 deletions(-)

diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 309291f0341..90769dff635 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -19,6 +19,18 @@ It can be enabled with the *-fzero-call-used-regs* command line
 option, to affect all subprograms in a compilation, and with a
 :samp:`Machine_Attribute` pragma, to affect only specific subprograms.
 
+.. code-block:: ada
+
+     procedure Foo;
+     pragma Machine_Attribute (Foo, "zero_call_used_regs", "used");
+     --  Before returning, Foo scrubs only call-clobbered registers
+     --  that it uses itself.
+
+     function Bar return Integer;
+     pragma Machine_Attribute (Bar, "zero_call_used_regs", "all");
+     --  Before returning, Bar scrubs all call-clobbered registers.
+
+
 For usage and more details on the command line option, and on the
 ``zero_call_used_regs`` attribute, see :title:`Using the GNU Compiler
 Collection (GCC)`.
@@ -35,6 +47,24 @@ It can be activated with the *-fstrub* command line option, to affect
 all (viable) subprograms in a compilation, and with a
 :samp:`Machine_Attribute` pragma.
 
+.. code-block:: ada
+
+     function Foo returns Integer;
+     pragma Machine_Attribute (Foo, "strub");
+     --  Foo and its callers are modified so as to scrub the stack
+     --  space used by Foo after it returns.
+
+     procedure Bar;
+     pragma Machine_Attribute (Bar, "strub", "internal");
+     --  Bar is turned into a wrapper for its original body,
+     --  and they scrub the stack used by the original body.
+
+     Var : Integer;
+     pragma Machine_Attribute (Var, "strub");
+     --  Reading from Var in a subprogram enables stack scrubbing
+     --  of the stack space used by the subprogram.
+
+
 For usage and more details on the command line option, and on the
 ``strub`` attribute, see :title:`Using the GNU Compiler Collection
 (GCC)`.
diff --git a/gcc/ada/gcc-interface/utils.c b/gcc/ada/gcc-interface/utils.c
index 6b20ddc93c9..1abf593d76b 100644
--- a/gcc/ada/gcc-interface/utils.c
+++ b/gcc/ada/gcc-interface/utils.c
@@ -39,6 +39,7 @@
 #include "varasm.h"
 #include "toplev.h"
 #include "opts.h"
+#include "ipa-strub.h"
 #include "output.h"
 #include "debug.h"
 #include "convert.h"
@@ -6611,6 +6612,7 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
+  bool compat_type = false;
   tree orig_fnptr_type = NULL_TREE;
   tree orig_args = args;
 
@@ -6624,68 +6626,36 @@ handle_strub_attribute (tree *node, tree name,
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      /* pragma Machine_Attribute turns string arguments into identifiers.
-	 Reverse it.  */
-      if (TREE_CODE (TREE_VALUE (args)) == IDENTIFIER_NODE)
+      switch (strub_validate_parm (TREE_VALUE (args)))
 	{
-	  gcc_checking_assert (IDENTIFIER_POINTER (TREE_VALUE (args))
-			       [IDENTIFIER_LENGTH (TREE_VALUE (args))] == 0);
-	  TREE_VALUE (args) = build_string
-	    (IDENTIFIER_LENGTH (TREE_VALUE (args)) + 1,
-	     IDENTIFIER_POINTER (TREE_VALUE (args)));
-	}
-
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
-	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
-
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
+	case 1:
+	  compat_type = true;
+	  /* Fall through.  */
 
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
+	  compat_type = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	  compat_type = true;
+	  /* Fall through.  */
+
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
@@ -6699,23 +6669,30 @@ handle_strub_attribute (tree *node, tree name,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   if (!*no_add_attrs)
     {
       *no_add_attrs = true;
       if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
 	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
+	{
+	  if (compat_type)
+	    *node = build_variant_type_copy (*node);
+	  else
+	    *node = build_distinct_type_copy (*node);
+	}
       TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
 					   TYPE_ATTRIBUTES (*node));
 
       if (orig_fnptr_type)
 	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
+	  tree fnptr_type = ((flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
+			     ? orig_fnptr_type
+			     : compat_type
+			     ? build_variant_type_copy (orig_fnptr_type)
+			     : build_distinct_type_copy (orig_fnptr_type));
 	  TREE_TYPE (fnptr_type) = *node;
 	  *node = fnptr_type;
 	}
diff --git a/gcc/attribs.c b/gcc/attribs.c
index 0d22c20a35e..acac634afb5 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "attribs.h"
 #include "fold-const.h"
+#include "ipa-strub.h"
 #include "stor-layout.h"
 #include "langhooks.h"
 #include "plugin.h"
@@ -1353,9 +1354,20 @@ comp_type_attributes (const_tree type1, const_tree type2)
   if ((lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type1)) != NULL)
       ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) != NULL))
     return 0;
+  int strub_ret = strub_comptypes (CONST_CAST_TREE (type1),
+				   CONST_CAST_TREE (type2));
+  if (strub_ret == 0)
+    return strub_ret;
   /* As some type combinations - like default calling-convention - might
      be compatible, we have to call the target hook to get the final result.  */
-  return targetm.comp_type_attributes (type1, type2);
+  int target_ret = targetm.comp_type_attributes (type1, type2);
+  if (target_ret == 0)
+    return target_ret;
+  if (strub_ret == 2 || target_ret == 2)
+    return 2;
+  if (strub_ret == 1 && target_ret == 1)
+    return 1;
+  gcc_unreachable ();
 }
 
 /* PREDICATE acts as a function of type:
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index dc4a4d4d200..2762dcdfcfe 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "common/common-target.h"
 #include "langhooks.h"
 #include "tree-inline.h"
+#include "ipa-strub.h"
 #include "toplev.h"
 #include "tree-iterator.h"
 #include "opts.h"
@@ -1306,6 +1307,7 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
+  bool compat_type = false;
   tree orig_fnptr_type = NULL_TREE;
   tree orig_args = args;
 
@@ -1319,57 +1321,36 @@ handle_strub_attribute (tree *node, tree name,
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
+      switch (strub_validate_parm (TREE_VALUE (args)))
 	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
+	case 1:
+	  compat_type = true;
+	  /* Fall through.  */
 
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
-
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
+	  compat_type = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	  compat_type = true;
+	  /* Fall through.  */
+
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
@@ -1383,23 +1364,30 @@ handle_strub_attribute (tree *node, tree name,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   if (!*no_add_attrs)
     {
       *no_add_attrs = true;
       if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
 	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
+	{
+	  if (compat_type)
+	    *node = build_variant_type_copy (*node);
+	  else
+	    *node = build_distinct_type_copy (*node);
+	}
       TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
 					   TYPE_ATTRIBUTES (*node));
 
       if (orig_fnptr_type)
 	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
+	  tree fnptr_type = ((flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
+			     ? orig_fnptr_type
+			     : compat_type
+			     ? build_variant_type_copy (orig_fnptr_type)
+			     : build_distinct_type_copy (orig_fnptr_type));
 	  TREE_TYPE (fnptr_type) = *node;
 	  *node = fnptr_type;
 	}
diff --git a/gcc/common.opt b/gcc/common.opt
index 71e0c971344..ef018ebf735 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,13 +2687,22 @@ fstrict-overflow
 Common
 Treat signed overflow as undefined.  Negated as -fwrapv -fwrapv-pointer.
 
-; If any strub-enabling attribute is seen when the default value is
-; selected, it's bumped up to -1.  The scrub mode gate function will
-; then bump -2 to 0 if no strub-enabling attribute is seen.  This
-; minimizes the strub overhead.
-fstrub=default
-Common RejectNegative Var(flag_strub, -2) Init(-2)
-Enable stack scrub as requested through attributes.
+fstrub=disable
+Common RejectNegative Var(flag_strub, 0)
+Disable stack scrub entirely, disregarding strub attributes.
+
+fstrub=strict
+Common RejectNegative Var(flag_strub, -4)
+Enable stack scrub as per attributes, with strict call checking.
+
+; If any strub-enabling attribute is seen when the default or strict
+; initializer values are in effect, flag_strub is bumped up by 2.  The
+; scrub mode gate function will then bump these initializer values to
+; 0 if no strub-enabling attribute is seen.  This minimizes the strub
+; overhead.
+fstrub=relaxed
+Common RejectNegative Var(flag_strub, -3) Init(-3)
+Restore default strub mode: as per attributes, with relaxed checking.
 
 fstrub=all
 Common RejectNegative Var(flag_strub, 3)
@@ -2707,10 +2716,6 @@ fstrub=internal
 Common RejectNegative Var(flag_strub, 2)
 Enable internal stack scrubbing for all viable functions.
 
-fstrub=disable
-Common RejectNegative Var(flag_strub, 0)
-Disable stack scrub entirely, disregarding strub attributes.
-
 fsync-libcalls
 Common Var(flag_sync_libcalls) Init(1)
 Implement __atomic operations via libcalls to legacy __sync functions.
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 1fcf60a3875..46cefe27fa5 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -8729,51 +8729,259 @@ pid_t wait (wait_status_ptr_t p)
 @item strub
 @cindex @code{strub} type attribute
 This attribute defines stack-scrubbing properties of functions and
-variables.  When applied to function types, it takes an optional
-argument.  When applied to a pointer-to-function type, it gets
-propagated to the function type if the optional argument is given.
-
-A function whose type is annotated with @code{at-calls} @code{strub}
-mode (@code{strub("at-calls")}, @code{strub(1)}, or @code{strub})
-undergoes interface changes.  Its callers are adjusted to match the
-changes, and to automatically scrub (overwrite with zeros) the stack
-space used by the function.
-
-A function whose type indicates @code{internal} @code{strub} mode
-(@code{strub("internal")} or @code{strub(2)}) retains an unmodified
-interface, but may be turned into a wrapper that calls the wrapped body
-using a custom interface.  The wrapper then scrubs the stack space used
-by the wrapped body.
-
-An automatically-allocated variable whose type carries the @code{strub}
-attribute causes the enclosing function to have @code{strub} enabled.
-
-A statically-allocated variable whose type carries the @code{strub}
-attribute causes functions that @emph{read} it with that type to have
-@code{strub} enabled.  Reading from data referenced by pointers to such
-types has the same effect.  Note: The attribute does not carry over from
-a composite type to the types of its components, so the intended effect
-may not be obtained with non-scalar types.
-
-A @code{strub} context is the body of a function that has strub enabled,
-be it explicitly, by @code{at-calls} or @code{internal} mode, or
-implicitly, by reading from a @code{strub}-marked data type.
+variables.  Being a type attribute, it attaches to types, even when
+specified in function and variable declarations.  When applied to
+function types, it takes an optional string argument.  When applied to a
+pointer-to-function type, if the optional argument is given, it gets
+propagated to the function type.
+
+@smallexample
+/* A strub variable.  */
+int __attribute__ ((strub)) var;
+/* A strub variable that happens to be a pointer.  */
+__attribute__ ((strub)) int *strub_ptr_to_int;
+/* A pointer type that may point to a strub variable.  */
+typedef int __attribute__ ((strub)) *ptr_to_strub_int_type;
+
+/* A declaration of a strub function.  */
+extern int __attribute__ ((strub)) foo (void);
+/* A pointer to that strub function.  */
+int __attribute__ ((strub ("at-calls"))) (*ptr_to_strub_fn)(void) = foo;
+@end smallexample
+
+A function associated with @code{at-calls} @code{strub} mode
+(@code{strub("at-calls")}, or just @code{strub}) undergoes interface
+changes.  Its callers are adjusted to match the changes, and to scrub
+(overwrite with zeros) the stack space used by the called function after
+it returns.  The interface change makes the function type incompatible
+with an unadorned but otherwise equivalent type, so @emph{every}
+declaration and every type that may be used to call the function must be
+associated with this strub mode.
+
+A function associated with @code{internal} @code{strub} mode
+(@code{strub("internal")}) retains an unmodified, type-compatible
+interface, but it may be turned into a wrapper that calls the wrapped
+body using a custom interface.  The wrapper then scrubs the stack space
+used by the wrapped body.  Though the wrapped body has its stack space
+scrubbed, the wrapper does not, so arguments and return values may
+remain unscrubbed even when such a function is called by another
+function that enables @code{strub}.  This is why, when compiling with
+@samp{-fstrub=strict}, a @code{strub} context is not allowed to call
+@code{internal} @code{strub} functions.
+
+@smallexample
+/* A declaration of an internal-strub function.  */
+extern int __attribute__ ((strub ("internal"))) bar (void);
+
+int __attribute__ ((strub))
+baz (void)
+@{
+  /* Ok, foo was declared above as an at-calls strub function.  */
+  foo ();
+  /* Not allowed in strict mode, otherwise allowed.  */
+  bar ();
+@}
+@end smallexample
+
+An automatically-allocated variable associated with the @code{strub}
+attribute causes the (immediately) enclosing function to have
+@code{strub} enabled.
+
+A statically-allocated variable associated with the @code{strub}
+attribute causes functions that @emph{read} it, through its @code{strub}
+data type, to have @code{strub} enabled.  Reading data by dereferencing
+a pointer to a @code{strub} data type has the same effect.  Note: The
+attribute does not carry over from a composite type to the types of its
+components, so the intended effect may not be obtained with non-scalar
+types.
+
+When selecting a @code{strub}-enabled mode for a function that is not
+explicitly associated with one, because of @code{strub} variables or
+data pointers, the function must satisfy @code{internal} mode viability
+requirements (see below), even when @code{at-calls} mode is also viable
+and, being more efficient, ends up selected as an optimization.
+
+@smallexample
+/* zapme is implicitly strub-enabled because of strub variables.
+   Optimization may change its strub mode, but not the requirements.  */
+static int
+zapme (int i)
+@{
+  /* A local strub variable enables strub.  */
+  int __attribute__ ((strub)) lvar;
+  /* Reading strub data through a pointer-to-strub enables strub.  */
+  lvar = * (ptr_to_strub_int_type) &i;
+  /* Writing to a global strub variable does not enable strub.  */
+  var = lvar;
+  /* Reading from a global strub variable enables strub.  */
+  return var;
+@}
+@end smallexample
+
+A @code{strub} context is the body (as opposed to the interface) of a
+function that has @code{strub} enabled, be it explicitly, by
+@code{at-calls} or @code{internal} mode, or implicitly, due to
+@code{strub} variables or command-line options.
 
 A function of a type associated with the @code{disabled} @code{strub}
-mode (@code{strub("disabled")}, @code{strub(0)}, or no @code{strub} mode
-specified) will not have its own stack space scrubbed, and it cannot be
-called from @code{strub} contexts.
+mode (@code{strub("disabled")} will not have its own stack space
+scrubbed.  Such functions @emph{cannot} be called from within
+@code{strub} contexts.
+
+In order to enable a function to be called from within @code{strub}
+contexts without having its stack space scrubbed, associate it with the
+@code{callable} @code{strub} mode (@code{strub("callable")}).
+
+When a function is not assigned a @code{strub} mode, explicitly or
+implicitly, the mode defaults to @code{callable}, except when compiling
+with @samp{-fstrub=strict}, that causes @code{strub} mode to default to
+@code{disabled}.
+
+@example
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+ /* Implicitly disabled with -fstrub=strict, otherwise callable.  */
+extern int bah (void);
+
+int __attribute__ ((strub))
+bal (void)
+@{
+  /* Not allowed, bad is not strub-callable.  */
+  bad ();
+  /* Ok, bac is strub-callable.  */
+  bac ();
+  /* Not allowed with -fstrub=strict, otherwise allowed.  */
+  bah ();
+@}
+@end example
 
-A function that does not have @code{strub} enabled can only be called
-from within @code{strub} contexts through a function type marked with
-the @code{callable} @code{strub} mode (@code{strub("callable")} or
-@code{strub(3)}).
+Function types marked @code{callable} and @code{disabled} are not
+mutually compatible types, but the underlying interfaces are compatible,
+so it is safe to convert pointers between them, and to use such pointers
+or alternate declarations to call them.  Interfaces are also
+interchangeable between them and @code{internal} (but not
+@code{at-calls}!), but adding @code{internal} to a pointer type will not
+cause the pointed-to function to perform stack scrubbing.
+
+@example
+void __attribute__ ((strub))
+bap (void)
+@{
+  /* Assign a callable function to pointer-to-disabled.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bac;
+  /* Not allowed: calls disabled type in a strub context.  */
+  d_p ();
+
+  /* Assign a disabled function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bad;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an internal function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  c_p = bar;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an at-calls function to pointer-to-callable.
+     Flaggged as incompatible.  */
+  c_p = bal;
+  /* The call through an interface-incompatible type will not use the
+     modified interface expected by the at-calls function, so it is
+     likely to misbehave at runtime.  */
+  c_p ();
+@}
+@end example
 
 @code{Strub} contexts are never inlined into non-@code{strub} contexts.
-When an @code{internal}-strub function is split, the wrapper can often
-be inlined, but the wrapped body never is.  Functions marked as
-@code{always_inline}, even if explicitly assigned @code{internal} strub
-mode, will not undergo wrapping, so their body gets inlined.
+When an @code{internal}-strub function is split up, the wrapper can
+often be inlined, but the wrapped body @emph{never} is.  A function
+marked as @code{always_inline}, even if explicitly assigned
+@code{internal} strub mode, will not undergo wrapping, so its body gets
+inlined as required.
+
+@example
+inline int __attribute__ ((strub ("at-calls")))
+inl_atc (void)
+@{
+  /* This body may get inlined into strub contexts.  */
+@}
+
+inline int __attribute__ ((strub ("internal")))
+inl_int (void)
+@{
+  /* This body NEVER gets inlined, though its wrapper may.  */
+@}
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+@{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+@}
+
+void __attribute__ ((strub ("disabled")))
+bat (void)
+@{
+  /* Not allowed, cannot inline into a non-strub context.  */
+  inl_int_ali ();
+@}
+@end example
+
+@cindex strub eligibility and viability
+Some @samp{-fstrub=*} command line options enable @code{strub} modes
+implicitly where viable.  A @code{strub} mode is only viable for a
+function if the function is eligible for that mode, and if other
+conditions, detailed below, are satisfied.  If it's not eligible for a
+mode, attempts to explicitly associate it with that mode are rejected
+with an error message.  If it is eligible, that mode may be assigned
+explicitly through this attribute, but implicit assignment through
+command-line options may involve additional viability requirements.
+
+A function is ineligible for @code{at-calls} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+@code{At-calls} @code{strub} mode, if not requested through the function
+type, is only viable for an eligible function if the function is not
+visible to other translation units, and if it doesn't have its address
+taken.
+
+@smallexample
+/* bar is eligible for at-calls strub mode,
+   but not viable for that mode because it is visible to other units.
+   It is eligible and viable for internal strub mode.  */
+void bav () @{@}
+
+/* setp is eligible for at-calls strub mode,
+   but not viable for that mode because its address is taken.
+   It is eligible and viable for internal strub mode.  */
+void setp (void) @{ static void (*p)(void); = setp; @}
+@end smallexample
+
+A function is ineligible for @code{internal} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, or if attribute
+@code{noipa} is present.  For an @code{always_inline} function, meeting
+these requirements is enough to make it eligible.  Any function that has
+attribute @code{noclone}, that uses such extensions as non-local labels,
+computed gotos, alternate variable argument passing interfaces,
+@code{__builtin_next_arg}, or @code{__builtin_return_address}, or that
+takes too many (about 64Ki) arguments is ineligible, unless it is
+@code{always_inline}.  For @code{internal} @code{strub} mode, all
+eligible functions are viable.
+
+@smallexample
+/* flop is not eligible, thus not viable, for at-calls strub mode.
+   Likewise for internal strub mode.  */
+__attribute__ ((noipa)) void flop (void) @{@}
+
+/* flip is eligible and viable for at-calls strub mode.
+   It would be ineligible for internal strub mode, because of noclone,
+   if it weren't for always_inline.  With always_inline, noclone is not
+   an obstacle, so it is also eligible and viable for internal strub mode.  */
+inline __attribute__ ((noclone, always_inline)) void flip (void) @{@}
+@end smallexample
 
 @item unused
 @cindex @code{unused} type attribute
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 838d9ff1198..dd529c1a82e 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,7 +599,8 @@ Objective-C and Objective-C++ Dialects}.
 -fstack-protector-explicit  -fstack-check @gol
 -fstack-limit-register=@var{reg}  -fstack-limit-symbol=@var{sym} @gol
 -fno-stack-limit  -fsplit-stack @gol
--fstrub=default -fstrub=disable -fstrub=at-calls -fstrub=internal -fstrub=all @gol
+-fstrub=disable -fstrub=strict -fstrub=relaxed @gol
+-fstrub=all -fstrub=at-calls -fstrub=internal @gol
 -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
 -fvtv-counts  -fvtv-debug @gol
 -finstrument-functions @gol
@@ -15513,46 +15514,41 @@ without @option{-fsplit-stack} always has a large stack.  Support for
 this is implemented in the gold linker in GNU binutils release 2.21
 and later.
 
-@item -fstrub=default
-@opindex fstrub=default
-Restore the default stack scrub (@code{strub}) setting, namely,
-@code{strub} is only enabled as required by @code{strub} attributes
-associated with function or variable types.  This is only useful to
-override earlier @samp{-fstrub=*} options.
-
 @item -fstrub=disable
 @opindex -fstrub=disable
 Disable stack scrubbing entirely, ignoring any @code{strub} attributes.
+See @xref{Common Type Attributes}.
+
+@item -fstrub=strict
+@opindex fstrub=strict
+Functions default to @code{strub} mode @code{disabled}, and apply
+@code{strict}ly the restriction that only functions associated with
+@code{strub}-@code{calalble} modes (@code{at-calls}, @code{callable} and
+@code{always_inline} @code{internal}) are @code{callable} by functions
+with @code{strub}-enabled modes (@code{at-calls} and @code{internal}).
+
+@item -fstrub=relaxed
+@opindex fstrub=relaxed
+Restore the default stack scrub (@code{strub}) setting, namely,
+@code{strub} is only enabled as required by @code{strub} attributes
+associated with function and data types.  @code{Relaxed} means that
+strub contexts are only prevented from calling functions explicitly
+associated with @code{strub} mode @code{disabled}.  This option is only
+useful to override other @samp{-fstrub=*} options that precede it in the
+command line.
 
 @item -fstrub=at-calls
 @opindex fstrub=at-calls
-Enable @code{at-calls} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{at-calls} @code{strub} if a different @code{strub}
-mode is explicitly requested, if attribute @code{noipa} is present, or
-if it calls @code{__builtin_apply_args}.  @code{At-calls} @code{strub}
-mode, if not requested through the function type, is only viable for an
-eligible function if the function is not visible to other translation
-units, and it doesn't have its address taken.
+Enable @code{at-calls} @code{strub} mode where viable.
 
 @item -fstrub=internal
 @opindex fstrub=internal
-Enable @code{internal} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{internal} @code{strub} if a different @code{strub}
-mode is explicitly requested, or if attribute @code{noipa} is present.
-Non-@code{always_inline} functions also become ineligible if attribute
-@code{noclone} is present, if the function uses such features as user
-labels, non-default variable argument interfaces,
-@code{__builtin_next_arg}, or @code{__builtin_return_address}, or if
-they have too many (about 64Ki) arguments.  For @code{internal}
-@code{strub}, all eligible functions are viable.
+Enable @code{internal} @code{strub} mode where viable.
 
 @item -fstrub=all
 @opindex fstrub=all
-Enable @code{strub} for all viable functions, and consider non-viable
-functions as @code{callable}.  When both strub modes are viable,
-@code{at-calls} is preferred.
+Enable some @code{strub} mode where viable.
+When both strub modes are viable, @code{at-calls} is preferred.
 
 @item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 @opindex fvtable-verify
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 7f4bc44d2bb..ee360b6c6ca 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -397,7 +397,7 @@ can_inline_edge_p (struct cgraph_edge *e, bool report,
       e->inline_failed = CIF_SANITIZE_ATTRIBUTE_MISMATCH;
       inlinable = false;
     }
-  if (!strub_inlinable_p (callee, caller))
+  if (!strub_inlinable_from_p (callee, caller))
     {
       e->inline_failed = CIF_UNSPECIFIED;
       inlinable = false;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
index cf7d811779e..478dd7183fb 100644
--- a/gcc/ipa-strub.c
+++ b/gcc/ipa-strub.c
@@ -159,12 +159,16 @@ enum strub_mode {
 
 };
 
+/* Look up a strub attribute in TYPE, and return it.  */
+
 static tree
 get_strub_attr_from_type (tree type)
 {
   return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
 }
 
+/* Look up a strub attribute in DECL or in its type, and return it.  */
+
 static tree
 get_strub_attr_from_decl (tree decl)
 {
@@ -174,23 +178,159 @@ get_strub_attr_from_decl (tree decl)
   return get_strub_attr_from_type (TREE_TYPE (decl));
 }
 
-tree
+/* Define a function to cache identifier ID, to be used as a strub attribute
+   parameter for a strub mode named after NAME.  */
+#define DEF_STRUB_IDS(NAME, ID)				\
+static inline tree get_strub_mode_id_ ## NAME () {	\
+  static tree identifier = NULL_TREE;			\
+  if (!identifier)					\
+    identifier = get_identifier (ID);			\
+  return identifier;					\
+}
+/* Same as DEF_STRUB_IDS, but use the string expansion of NAME as ID.  */
+#define DEF_STRUB_ID(NAME)				\
+DEF_STRUB_IDS (NAME, #NAME)
+
+/* Define functions for each of the strub mode identifiers.
+   Expose dashes rather than underscores.  */
+DEF_STRUB_ID (disabled)
+DEF_STRUB_IDS (at_calls, "at-calls")
+DEF_STRUB_ID (internal)
+DEF_STRUB_ID (callable)
+DEF_STRUB_ID (wrapped)
+DEF_STRUB_ID (wrapper)
+DEF_STRUB_ID (inlinable)
+DEF_STRUB_IDS (at_calls_opt, "at-calls-opt")
+
+/* Release the temporary macro names.  */
+#undef DEF_STRUB_IDS
+#undef DEF_STRUB_ID
+
+/* Return the identifier corresponding to strub MODE.  */
+
+static tree
+get_strub_mode_attr_parm (enum strub_mode mode)
+{
+  switch (mode)
+    {
+    case STRUB_DISABLED:
+      return get_strub_mode_id_disabled ();
+
+    case STRUB_AT_CALLS:
+      return get_strub_mode_id_at_calls ();
+
+    case STRUB_INTERNAL:
+      return get_strub_mode_id_internal ();
+
+    case STRUB_CALLABLE:
+      return get_strub_mode_id_callable ();
+
+    case STRUB_WRAPPED:
+      return get_strub_mode_id_wrapped ();
+
+    case STRUB_WRAPPER:
+      return get_strub_mode_id_wrapper ();
+
+    case STRUB_INLINABLE:
+      return get_strub_mode_id_inlinable ();
+
+    case STRUB_AT_CALLS_OPT:
+      return get_strub_mode_id_at_calls_opt ();
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+/* Return the parmeters (TREE_VALUE) for a strub attribute of MODE.
+   We know we use a single parameter, so we bypass the creation of a
+   tree list.  */
+
+static tree
 get_strub_mode_attr_value (enum strub_mode mode)
 {
-  return tree_cons (NULL_TREE,
-		    build_int_cst (integer_type_node, (int)mode),
-		    NULL_TREE);
+  return get_strub_mode_attr_parm (mode);
+}
+
+/* Determine whether ID is a well-formed strub mode-specifying attribute
+   parameter.  Only user-visible modes are accepted.
+
+   For unacceptable parms, return 0, otherwise a nonzero value as below.
+
+   If the parm enables strub, return positive, otherwise negative.
+
+   If the affected type must be a distinct, incompatible type,return an integer
+   of absolute value 2, otherwise 1.  */
 
-#if 0 /* ??? use symbolic mode names with interned strings?  */
-  char *s = NULL;
+int
+strub_validate_parm (tree id)
+{
+  int ret;
+
+  if (!id)
+    return true;
 
-  switch (strub_mode)
+  const char *s = NULL;
+  size_t len = 0;
+
+  if (TREE_CODE (id) == STRING_CST)
     {
-      
+      s = TREE_STRING_POINTER (id);
+      len = TREE_STRING_LENGTH (id) - 1;
     }
-#endif
+  else if (TREE_CODE (id) == IDENTIFIER_NODE)
+    {
+      s = IDENTIFIER_POINTER (id);
+      len = IDENTIFIER_LENGTH (id);
+    }
+  else
+    return false;
+
+  enum strub_mode mode;
+
+  if (len != 8)
+    return false;
+
+  switch (s[0])
+    {
+    case 'd':
+      mode = STRUB_DISABLED;
+      ret = -1;
+      break;
+
+    case 'a':
+      mode = STRUB_AT_CALLS;
+      ret = 2;
+      break;
+
+    case 'i':
+      mode = STRUB_INTERNAL;
+      ret = 1;
+      break;
+
+    case 'c':
+      mode = STRUB_CALLABLE;
+      ret = -2;
+      break;
+
+    default:
+      /* Other parms are for internal use only.  */
+      return 0;
+    }
+
+  tree mode_id = get_strub_mode_attr_parm (mode);
+
+  if (TREE_CODE (id) == IDENTIFIER_NODE
+      ? id == mode_id
+      : strncmp (s, IDENTIFIER_POINTER (mode_id), len))
+    return 0;
+
+  return ret;
 }
 
+/* Return the strub mode from STRUB_ATTR.  VAR_P should be true if the attribute
+   is taken from a variable, rather than from a function, or a type thereof.  */
+
 static enum strub_mode
 get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
 {
@@ -200,28 +340,102 @@ get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
     {
       if (!TREE_VALUE (strub_attr))
 	mode = !var_p ? STRUB_AT_CALLS : STRUB_INTERNAL;
-      else if (TREE_CODE (TREE_VALUE (TREE_VALUE (strub_attr))) == INTEGER_CST)
-	mode = (enum strub_mode) tree_to_shwi (TREE_VALUE
-					       (TREE_VALUE (strub_attr)));
-      else /* Handlers convert symbolic mode names to INTEGER_CST.  */
-	gcc_unreachable ();
+      else
+	{
+	  gcc_checking_assert (!var_p);
+	  tree id = TREE_VALUE (strub_attr);
+	  if (TREE_CODE (id) == TREE_LIST)
+	    id = TREE_VALUE (id);
+	  const char *s = (TREE_CODE (id) == STRING_CST
+			   ? TREE_STRING_POINTER (id)
+			   : IDENTIFIER_POINTER (id));
+	  size_t len = (TREE_CODE (id) == STRING_CST
+			? TREE_STRING_LENGTH (id) - 1
+			: IDENTIFIER_LENGTH (id));
+
+	  switch (len)
+	    {
+	    case 7:
+	      switch (s[6])
+		{
+		case 'r':
+		  mode = STRUB_WRAPPER;
+		  break;
+
+		case 'd':
+		  mode = STRUB_WRAPPED;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 8:
+	      switch (s[0])
+		{
+		case 'd':
+		  mode = STRUB_DISABLED;
+		  break;
+
+		case 'a':
+		  mode = STRUB_AT_CALLS;
+		  break;
+
+		case 'i':
+		  mode = STRUB_INTERNAL;
+		  break;
+
+		case 'c':
+		  mode = STRUB_CALLABLE;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 9:
+	      mode = STRUB_INLINABLE;
+	      break;
+
+	    case 12:
+	      mode = STRUB_AT_CALLS_OPT;
+	      break;
+
+	    default:
+	      gcc_unreachable ();
+	    }
+
+	  gcc_checking_assert (TREE_CODE (id) == IDENTIFIER_NODE
+			       ? id == get_strub_mode_attr_parm (mode)
+			       : strncmp (IDENTIFIER_POINTER
+					  (get_strub_mode_attr_parm (mode)),
+					  s, len) == 0);
+	}
     }
 
   return mode;
 }
 
+/* Look up, decode and return the strub mode associated with FNDECL.  */
+
 static enum strub_mode
-get_strub_mode_from_decl (tree fndecl)
+get_strub_mode_from_fndecl (tree fndecl)
 {
   return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
 }
 
+/* Look up, decode and return the strub mode associated with NODE.  */
+
 static enum strub_mode
 get_strub_mode (cgraph_node *node)
 {
-  return get_strub_mode_from_decl (node->decl);
+  return get_strub_mode_from_fndecl (node->decl);
 }
 
+/* Look up, decode and return the strub mode associated with TYPE.  */
+
 static enum strub_mode
 get_strub_mode_from_type (tree type)
 {
@@ -231,12 +445,15 @@ get_strub_mode_from_type (tree type)
   if (attr)
     return get_strub_mode_from_attr (attr, var_p);
 
-  if (flag_strub > 0 && !var_p)
+  if (flag_strub >= -1 && !var_p)
     return STRUB_CALLABLE;
 
   return STRUB_DISABLED;
 }
 
+\f
+/* Return true iff NODE calls builtin va_start.  */
+
 static bool
 calls_builtin_va_start_p (cgraph_node *node)
 {
@@ -252,6 +469,8 @@ calls_builtin_va_start_p (cgraph_node *node)
   return result;
 }
 
+/* Return true iff NODE calls builtin apply_args, and optionally REPORT it.  */
+
 static bool
 calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
 {
@@ -276,12 +495,17 @@ calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE carries the always_inline attribute.  */
+
 static inline bool
 strub_always_inline_p (cgraph_node *node)
 {
   return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
 }
 
+/* Return true iff NODE is potentially eligible for any strub-enabled mode, and
+   optionally REPORT the reasons for ineligibility.  */
+
 static inline bool
 can_strub_p (cgraph_node *node, bool report = false)
 {
@@ -321,6 +545,10 @@ can_strub_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE is eligible for at-calls strub, and optionally REPORT
+   the reasons for ineligibility.  Besides general non-eligibility for
+   strub-enabled modes, at-calls rules out calling builtin apply_args.  */
+
 static bool
 can_strub_at_calls_p (cgraph_node *node, bool report = false)
 {
@@ -332,6 +560,9 @@ can_strub_at_calls_p (cgraph_node *node, bool report = false)
   return !calls_builtin_apply_args_p (node, report);
 }
 
+/* Symbolic macro for the max number of arguments that internal strub may add to
+   a function.  */
+
 #define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
 
 /* We can't perform internal strubbing if the function body involves certain
@@ -438,14 +669,12 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 			   || tree_versionable_function_p (node->decl));
 
 
-      /* Label values referenced are not preserved when copying.  If referenced
+      /* Label values references are not preserved when copying.  If referenced
 	 in nested functions, as in 920415-1.c and 920721-4.c their decls get
-	 remapped independently.  That might be too broad, in that we might be
-	 able to support correctly cases in which the labels are only used
-	 internally in a function, but disconnecting user labels from their
-	 original declarations is undesirable in general, and it probably
-	 doesn't matter, since explicitly-requested strub likely uses
-	 STRUB_AT_CALLS mode anyway.  */
+	 remapped independently.  The exclusion below might be too broad, in
+	 that we might be able to support correctly cases in which the labels
+	 are only used internally in a function, but disconnecting forced labels
+	 from their original declarations is undesirable in general.  */
       basic_block bb;
       FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
 	for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -459,8 +688,6 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 
 	    target = gimple_label_label (label_stmt);
 
-	    /* Make an edge to every label block that has been marked as a
-	       potential target for a computed goto or a non-local goto.  */
 	    if (!FORCED_LABEL (target))
 	      continue;
 
@@ -470,7 +697,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 forced labels");
 	  }
     }
 
@@ -491,6 +718,9 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return TRUE iff NODE has any strub-requiring local variable, or accesses (as
+   in reading) any variable through a strub-requiring type.  */
+
 static bool
 strub_from_body_p (cgraph_node *node)
 {
@@ -506,7 +736,9 @@ strub_from_body_p (cgraph_node *node)
 	!= STRUB_DISABLED)
       return true;
 
-  /* Now scan the body for loads with strub types.  */
+  /* Now scan the body for loads with strub-requiring types.
+     ??? Compound types don't propagate the strub requirement to
+     component types.  */
   basic_block bb;
   FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
     for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -528,6 +760,7 @@ strub_from_body_p (cgraph_node *node)
 
 /* Return true iff node is associated with a builtin that should be callable
    from strub contexts.  */
+
 static inline bool
 strub_callable_builtin_p (cgraph_node *node)
 {
@@ -568,17 +801,23 @@ strub_callable_builtin_p (cgraph_node *node)
     }
 }
 
+/* Compute the strub mode to be used for NODE.  STRUB_ATTR should be the strub
+   attribute,found for NODE, if any.  */
+
 static enum strub_mode
 compute_strub_mode (cgraph_node *node, tree strub_attr)
 {
   enum strub_mode req_mode = get_strub_mode_from_attr (strub_attr);
 
-  gcc_checking_assert (flag_strub >= -1 && flag_strub <= 3);
+  gcc_checking_assert (flag_strub >= -2 && flag_strub <= 3);
 
   /* Symbolic encodings of the -fstrub-* flags.  */
   /* Enable strub when explicitly requested through attributes to functions or
      variables, reporting errors if the requests cannot be satisfied.  */
   const bool strub_flag_auto = flag_strub < 0;
+  /* strub_flag_auto with strub call verification; without this, functions are
+     implicitly callable.  */
+  const bool strub_flag_strict = flag_strub < -1;
   /* Disable strub altogether, ignore attributes entirely.  */
   const bool strub_flag_disabled = flag_strub == 0;
   /* On top of _auto, also enable strub implicitly for functions that can
@@ -625,7 +864,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (!strub_flag_disabled
        && (strub_attr
 	   ? req_mode == STRUB_CALLABLE
-	   : (strub_flag_viable
+	   : (!strub_flag_strict
 	      || strub_callable_builtin_p (node))));
 
   /* This is a shorthand for either strub-enabled mode.  */
@@ -674,19 +913,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (at_calls_eligible
        && (strub_attr
 	   || (node->has_gimple_body_p ()
-#if 0 /* We no longer use collect_callers, so we can probably drop it.  */
-	       && node->get_availability () > AVAIL_INTERPOSABLE
-#endif
-	       && ((!node->externally_visible
-#if 0
-		    /* We wish to bypass the test below for functions that are
-		       not externally visible, but that's a little too broad: we
-		       do not wish to skip them for e.g. gnu_inline
-		       functions.  */
-		    && !TREE_PUBLIC (node->decl)
-		    && !DECL_EXTERNAL (node->decl)
-#endif
-		    )
+	       && (!node->externally_visible
 		   || (node->binds_to_current_def_p ()
 		       && node->can_be_local_p ()))
 	       && node->only_called_directly_p ())));
@@ -737,10 +964,6 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
   const enum strub_mode mode
     = ((strub_enable && is_always_inline)
        ? (strub_required ? STRUB_INLINABLE : STRUB_CALLABLE)
-#if 0
-       : (!strub_enable && strub_required && strub_attr)
-       ? req_mode
-#endif
        : (strub_enable && internal_viable
 	  && (strub_flag_internal || !at_calls_viable))
        ? STRUB_INTERNAL
@@ -802,6 +1025,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 /* Set FNDT's strub mode to MODE; FNDT may be a function decl or
    function type.  If OVERRIDE, do not check whether a mode is already
    set.  */
+
 static void
 strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
 {
@@ -828,12 +1052,18 @@ strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
   *attrp = attr;
 }
 
+/* Set FNDT's strub mode to callable.
+   FNDT may be a function decl or a function type.  */
+
 void
 strub_make_callable (tree fndt)
 {
   strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
 }
 
+/* Set NODE to strub MODE.  Report incompatibilities between MODE and the mode
+   requested through explicit attributes, and cases of non-eligibility.  */
+
 static void
 set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
 {
@@ -906,6 +1136,8 @@ set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
   strub_set_fndt_mode_to (node->decl, mode, attr);
 }
 
+/* Compute and set NODE's strub mode.  */
+
 static void
 set_strub_mode (cgraph_node *node)
 {
@@ -946,6 +1178,7 @@ set_strub_mode (cgraph_node *node)
   set_strub_mode_to (node, mode);
 }
 
+\f
 /* Non-strub functions shouldn't be called from within strub contexts,
    except through callable ones.  Always inline strub functions can
    only be called from strub functions.  */
@@ -984,7 +1217,7 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_AT_CALLS_OPT:
     case STRUB_INTERNAL:
     case STRUB_WRAPPER:
-      return (flag_strub >= 0);
+      return (flag_strub >= -1);
 
     case STRUB_DISABLED:
       return false;
@@ -1004,7 +1237,7 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
    strubbed functions into non-strubbed ones.  */
 
 bool
-strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+strub_inlinable_from_p (cgraph_node *callee, cgraph_node *caller)
 {
   strub_mode callee_mode = get_strub_mode (callee);
 
@@ -1049,14 +1282,43 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
   return false;
 }
 
+/* Check that types T1 and T2 are strub-compatible.  Return 1 if the strub modes
+   are the same, 2 if they are interchangeable, and 0 otherwise.  */
+
+int
+strub_comptypes (tree t1, tree t2)
+{
+  enum strub_mode m1 = get_strub_mode_from_type (t1);
+  enum strub_mode m2 = get_strub_mode_from_type (t2);
+
+  if (m1 == m2)
+    return 1;
+
+  /* We're dealing with types, so only strub modes that can be selected by
+     attributes in the front end matter.  If either mode is at-calls, the
+     conversion is not compatible.  */
+  if (m1 == STRUB_AT_CALLS || m2 == STRUB_AT_CALLS)
+    return 0;
+
+  return 2;
+}
+
 /* Check that strub functions don't call non-strub functions, and that
    always_inline strub functions are only called by strub
    functions.  */
+
 static void
 verify_strub ()
 {
   cgraph_node *node;
 
+  /* It's expected that check strub-wise pointer type compatibility of variables
+     and of functions is already taken care of by front-ends, on account of the
+     attribute's being marked as affecting type identity and of the creation of
+     distinct types.  */
+
+  /* Check that call targets in strub contexts have strub-callable types.  */
+
   FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
   {
     enum strub_mode caller_mode = get_strub_mode (node);
@@ -1105,14 +1367,11 @@ verify_strub ()
 	  }
       }
   }
-
-  /* ??? Check strub-wise pointer type compatibility of variables and
-     functions, or is this already taken care of on account of the
-     attribute's being marked as affecting type identity?  */
 }
 
 namespace {
 
+/* Define a pass to compute strub modes.  */
 const pass_data pass_data_ipa_strub_mode = {
   SIMPLE_IPA_PASS,
   "strubm",
@@ -1133,18 +1392,20 @@ public:
   {}
   opt_pass *clone () { return new pass_ipa_strub_mode (m_ctxt); }
   virtual bool gate (function *) {
-    /* In the default setting, the attribute handler changes
-       flag_strub to -1 if any strub-enabling occurence of the
-       attribute is found.  If it remains at -2, nothing that would
-       enable strub was found, so we can disable it and avoid the
+    /* In relaxed (-3) and strict (-4) settings, that only enable strub at a
+       function or variable attribute's request, the attribute handler changes
+       flag_strub to -1 or -2, respectively, if any strub-enabling occurence of
+       the attribute is found.  Therefore, if it remains at -3 or -4, nothing
+       that would enable strub was found, so we can disable it and avoid the
        overhead.  */
-    if (flag_strub == -2)
+    if (flag_strub < -2)
       flag_strub = 0;
     return flag_strub;
   }
   virtual unsigned int execute (function *);
 };
 
+/* Define a pass to introduce strub transformations.  */
 const pass_data pass_data_ipa_strub = {
   SIMPLE_IPA_PASS,
   "strub",
@@ -1170,6 +1431,7 @@ public:
   virtual bool gate (function *) { return flag_strub; }
   virtual unsigned int execute (function *);
 
+  /* Define on demand and cache some types we use often.  */
 #define DEF_TYPE(NAME, INIT)			\
   static inline tree get_ ## NAME () {		\
     static tree type = NULL_TREE;		\
@@ -1202,6 +1464,7 @@ public:
 
 #undef DEF_TYPE
 
+  /* Define non-strub builtins on demand.  */
 #define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST)			\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1224,6 +1487,7 @@ public:
 
 #undef DEF_NM_BUILTIN
 
+  /* Define strub builtins on demand.  */
 #define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST)		\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1260,6 +1524,7 @@ public:
 
 #undef DEF_SS_BUILTIN
 
+    /* Define strub identifiers on demand.  */
 #define DEF_IDENT(NAME)					\
   static inline tree get_ ## NAME () {			\
     static tree identifier = NULL_TREE;			\
@@ -1278,6 +1543,10 @@ public:
   static inline void adjust_at_calls_call (cgraph_edge *, int);
   static inline void adjust_at_calls_calls (cgraph_node *);
 
+  /* Add to SEQ a call to the strub watermark update builtin, taking NODE's
+     location if given.  Optionally add the corresponding edge from NODE, with
+     execution frequency COUNT.  Return the modified SEQ.  */
+
   static inline gimple_seq
   call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
 			 gimple_seq seq = NULL)
@@ -1300,8 +1569,14 @@ public:
 
 } // anon namespace
 
+/* Gather with this type a collection of parameters that we're turning into
+   explicit references.  */
+
 typedef hash_set<tree> indirect_parms_t;
 
+/* Dereference OP if it's an INDIRECT_PARMS or an ADDR_EXPR thereof.  Set *REC
+   and return according to gimple-walking expectations.  */
+
 static tree
 maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
 {
@@ -1330,6 +1605,8 @@ maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
   return NULL_TREE;
 }
 
+/* A gimple-walking function that adds dereferencing to indirect parms.  */
+
 static tree
 walk_make_indirect (tree *op, int *rec, void *arg)
 {
@@ -1351,6 +1628,11 @@ walk_make_indirect (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* A gimple-walking function that turns any non-gimple-val ADDR_EXPRs into a
+   separate SSA.  Though addresses of e.g. parameters, and of members thereof,
+   are gimple vals, turning parameters into references, with an extra layer of
+   indirection and thus explicit dereferencing, need to be regimplified.  */
+
 static tree
 walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
 {
@@ -1374,6 +1656,11 @@ walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* Create a reference type to use for PARM when turning it into a reference.
+   NONALIASED causes the reference type to gain its own separate alias set, so
+   that accessing the indirectly-passed parm won'will not add aliasing
+   noise.  */
+
 static tree
 build_ref_type_for (tree parm, bool nonaliased = true)
 {
@@ -1411,6 +1698,7 @@ build_ref_type_for (tree parm, bool nonaliased = true)
 
 /* Add cgraph edges from current_function_decl to callees in SEQ with frequency
    COUNT, assuming all calls in SEQ are direct.  */
+
 static void
 add_call_edges_for_seq (gimple_seq seq, profile_count count)
 {
@@ -1435,6 +1723,12 @@ add_call_edges_for_seq (gimple_seq seq, profile_count count)
     }
 }
 
+/* Insert SEQ after the call at GSI, as if the call was in a try block with SEQ
+   as finally, i.e., SEQ will run after the call whether it returns or
+   propagates an exception.  This handles block splitting, EH edge and block
+   creation, noreturn and nothrow optimizations, and even throwing calls without
+   preexisting local handlers.  */
+
 static void
 gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
 {
@@ -1586,8 +1880,12 @@ remove_named_attribute_unsharing (const char *name, tree *attrs)
     }
 }
 
+/* Record the order of the last cgraph entry whose mode we've already set, so
+   that we can perform mode setting incrementally without duplication.  */
 static int last_cgraph_order;
 
+/* Set strub modes for functions introduced since the last call.  */
+
 static void
 ipa_strub_set_mode_for_new_functions ()
 {
@@ -1616,6 +1914,7 @@ ipa_strub_set_mode_for_new_functions ()
 }
 
 /* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
+
 bool
 strub_splittable_p (cgraph_node *node)
 {
@@ -1641,10 +1940,11 @@ strub_splittable_p (cgraph_node *node)
 }
 
 /* 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))
+  switch (get_strub_mode_from_fndecl (fndecl))
     {
     case STRUB_WRAPPED:
     case STRUB_AT_CALLS:
@@ -1676,6 +1976,7 @@ strub_watermark_parm (tree fndecl)
 
 /* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
    hasn't been added yet.  Return the named argument count.  */
+
 int
 pass_ipa_strub::adjust_at_calls_type (tree type)
 {
@@ -1751,6 +2052,12 @@ pass_ipa_strub::adjust_at_calls_type (tree type)
   return named_args;
 }
 
+/* Adjust a call to an at-calls call target.  Create a watermark local variable
+   if needed, initialize it before, pass it to the callee according to the
+   modified at-calls interface, and release the callee's stack space after the
+   call, if not deferred.  If the call is const or pure, arrange for the
+   watermark to not be assumed unused or unchanged.  */
+
 void
 pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 {
@@ -1819,10 +2126,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 
     if (gimple_call_internal_p (stmt))
 #if 0
-      /*
-	new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
-	vargs);
-      */
+      new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
+						 vargs);
 #endif
       gcc_unreachable ();
     else
@@ -1917,6 +2222,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
   gsi_insert_finally_seq_after_call (gsi, seq);
 }
 
+/* Adjust all at-calls calls in NODE. */
+
 void
 pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
 {
@@ -1965,6 +2272,10 @@ pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
     }
 }
 
+/* The strubm (strub mode) pass computes a strub mode for each function in the
+   call graph, and checks, before any inlining, that strub callability
+   requirements in effect are satisfied.  */
+
 unsigned int
 pass_ipa_strub_mode::execute (function *)
 {
@@ -1977,12 +2288,17 @@ pass_ipa_strub_mode::execute (function *)
   return 0;
 }
 
+/* Create a strub mode pass.  */
+
 simple_ipa_opt_pass *
 make_pass_ipa_strub_mode (gcc::context *ctxt)
 {
   return new pass_ipa_strub_mode (ctxt);
 }
 
+/* The strub pass proper adjusts types, signatures, and at-calls calls, and
+   splits internal-strub functions.  */
+
 unsigned int
 pass_ipa_strub::execute (function *)
 {
@@ -2042,17 +2358,6 @@ pass_ipa_strub::execute (function *)
 	if (onode->alias)
 	  continue;
 
-#if 0 /* Calls are now adjusted when examining callers.  */
-	unsigned c;
-	cgraph_edge *e;
-	FOR_EACH_VEC_ELT (onode->collect_callers (), c, e)
-	  {
-	    push_cfun (DECL_STRUCT_FUNCTION (e->caller->decl));
-	    adjust_at_calls_call (e, named_args);
-	    pop_cfun ();
-	  }
-#endif
-
 	cgraph_node *nnode = onode;
 	push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
 
@@ -2088,10 +2393,6 @@ pass_ipa_strub::execute (function *)
 	  }
 
 	pop_cfun ();
-
-#if 0
-	compute_fn_summary (onode, true);
-#endif
       }
   }
 
@@ -2108,43 +2409,6 @@ pass_ipa_strub::execute (function *)
 	continue;
       }
 
-#if 0
-    /* Hmm, this is an i386-specific attribute.  Do we need machine-specific
-       logic?  */
-    remove_named_attribute_unsharing ("interrupt",
-				      &DECL_ATTRIBUTES (onode->decl));
-#endif
-
-#if 0
-    if (!DECL_STRUCT_FUNCTION (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting struct-less function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    if (!onode->lowered)
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting non-lowered function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    /* 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.  FIXME.  */
-    if (!tree_versionable_function_p (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"%qD cannot be split for %<strub%>",
-		onode->decl);
-	continue;
-      }
-#endif
-
     bool is_stdarg = calls_builtin_va_start_p (onode);;
     bool apply_args = calls_builtin_apply_args_p (onode);
 
@@ -2973,11 +3237,6 @@ pass_ipa_strub::execute (function *)
 
       pop_cfun ();
     }
-
-#if 0
-    compute_fn_summary (onode, true);
-    compute_fn_summary (nnode, true);
-#endif
   }
 
   if (flag_checking)
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
index c335ab42097..61a47b7ce1e 100644
--- a/gcc/ipa-strub.h
+++ b/gcc/ipa-strub.h
@@ -22,7 +22,7 @@ along with GCC; see the file COPYING3.  If not see
    as far as stack scrubbing constraints are concerned.  CALLEE
    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 bool strub_inlinable_from_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);
@@ -33,3 +33,14 @@ extern tree strub_watermark_parm (tree fndecl);
 
 /* Make a function type or declaration callable.  */
 extern void strub_make_callable (tree fndecl);
+
+/* Return zero iff ID is NOT an acceptable parameter for a user-supplied strub
+   attribute.  Otherwise, return >0 if it enables strub, <0 if it does not.
+   Return +/-1 if the attribute-modified type is compatible with the type
+   without the attribute, or +/-2 if it is not compatible.  */
+extern int strub_validate_parm (tree id);
+
+/* Like comptypes, return 0 if t1 and t2 are not compatible, 1 if they are
+   compatible, and 2 if they are nearly compatible.  Same strub mode is
+   compatible, interface-compatible strub modes are nearly compatible.  */
+extern int strub_comptypes (tree t1, tree t2);
diff --git a/gcc/testsuite/c-c++-common/strub-O0.c b/gcc/testsuite/c-c++-common/strub-O0.c
index 6afe0fd5de1..c7a79a6ea0d 100644
--- a/gcc/testsuite/c-c++-common/strub-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O0 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O0, none of the strub builtins are expanded inline.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O1.c b/gcc/testsuite/c-c++-common/strub-O1.c
index 1cdeaecaf32..96285c975d9 100644
--- a/gcc/testsuite/c-c++-common/strub-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O1 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O1, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
index 7848c46d179..8edc0d8aa13 100644
--- a/gcc/testsuite/c-c++-common/strub-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O2, without -fno-inline, we fully expand enter and update, and add a test
    around the leave call.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
index 85a8f76785e..c6d900cf3c4 100644
--- a/gcc/testsuite/c-c++-common/strub-O2fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3.c b/gcc/testsuite/c-c++-common/strub-O3.c
index 1fcde345d36..33ee465e51c 100644
--- a/gcc/testsuite/c-c++-common/strub-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand" } */
 
 int __attribute__ ((__strub__)) var;
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
index a2eedfd96b2..2936f82079e 100644
--- a/gcc/testsuite/c-c++-common/strub-O3fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
index e5cb1f60541..479746e57d8 100644
--- a/gcc/testsuite/c-c++-common/strub-Og.c
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Og -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Og -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Og, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
index 194aacc2c05..2241d4ea07f 100644
--- a/gcc/testsuite/c-c++-common/strub-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Os, without -fno-inline, we fully expand enter, and also update.  The
    expanded update might be larger than a call proper, but argument saving and
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
index 46e84bf6560..a322bcc5da6 100644
--- a/gcc/testsuite/c-c++-common/strub-all1.c
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -22,11 +22,11 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
index f377541cff0..db60026d0e0 100644
--- a/gcc/testsuite/c-c++-common/strub-all2.c
+++ b/gcc/testsuite/c-c++-common/strub-all2.c
@@ -17,8 +17,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
index f180b17f30e..2f462adc1ef 100644
--- a/gcc/testsuite/c-c++-common/strub-apply1.c
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -1,13 +1,13 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
-void __attribute__ ((__strub__ (3)))
+void __attribute__ ((__strub__ ("callable")))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0);
 }
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   void *args = __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
index 379a54b73b7..a5d7551f5da 100644
--- a/gcc/testsuite/c-c++-common/strub-apply2.c
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 extern void __attribute__ ((__strub__))
 apply_function (void *args);
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
index 9b4786be698..64422a0d1e8 100644
--- a/gcc/testsuite/c-c++-common/strub-apply3.c
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 void __attribute__ ((__strub__))
 apply_function (void *args)
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
index 409f747743e..ea18d0bbd54 100644
--- a/gcc/testsuite/c-c++-common/strub-apply4.c
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+/* { dg-options "-O2 -fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
index d964b07ae5d..b70843b4215 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -22,9 +22,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
index 530eee36d06..97a3988a6b9 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls2.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls2.c
@@ -17,7 +17,7 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
index 7b04eea35d9..3d73431b3dc 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O1" } */
+/* { dg-options "-fstrub=strict -O1" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O1.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
index 67d96419a5e..fddf3c745e7 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O2" } */
+/* { dg-options "-fstrub=strict -O2" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O2.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
index 34828d2711e..2bc384ee8d1 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O3" } */
+/* { dg-options "-fstrub=strict -O3" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -O3.  */
@@ -10,7 +10,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -18,7 +18,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -32,7 +32,7 @@ leak_string (void)
   return 0;
 }
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 int
 look_for_string (char *e)
 {
@@ -56,14 +56,14 @@ look_for_string (char *e)
   return 0;
 }
 
-static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+static __attribute__ ((__strub__ ("at-calls"), __noinline__, __noclone__))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 deferred_at_calls ()
 {
@@ -73,7 +73,7 @@ deferred_at_calls ()
   return ret;
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 deferred_internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
index b273660aea1..fbaf85fe0fa 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -Os" } */
+/* { dg-options "-fstrub=strict -Os" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -Os.  */
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
index a74658c9ac9..e9d7b7b9ee0 100644
--- a/gcc/testsuite/c-c++-common/strub-internal1.c
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -23,9 +23,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
index a6e69357b23..8b8e15a51c7 100644
--- a/gcc/testsuite/c-c++-common/strub-internal2.c
+++ b/gcc/testsuite/c-c++-common/strub-internal2.c
@@ -14,8 +14,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
index 7e22a266ad9..0a4a7539d34 100644
--- a/gcc/testsuite/c-c++-common/strub-parms1.c
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -16,7 +16,7 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 large_byref_arg (struct large_arg la)
 {
 }
@@ -24,7 +24,7 @@ large_byref_arg (struct large_arg la)
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*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)))
+void __attribute__ ((__strub__ ("internal")))
 std_arg (int i, ...)
 {
   va_list vl;
@@ -38,7 +38,7 @@ std_arg (int i, ...)
 /* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
index 2d8036c0fbc..147171d96d5 100644
--- a/gcc/testsuite/c-c++-common/strub-parms2.c
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -15,14 +15,14 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 large_byref_arg (struct large_arg la)
 {
 }
 
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 std_arg (int i, ...)
 {
   va_list vl;
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
index f64361f1235..4e92682895a 100644
--- a/gcc/testsuite/c-c++-common/strub-parms3.c
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
new file mode 100644
index 00000000000..e2f9d8aebca
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  Without the error,
+   inlining takes place.  */
+
+#include "strub-strict1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
new file mode 100644
index 00000000000..98474435d2e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  */
+
+#include "strub-strict2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
index a4226ce0119..1de15342595 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fexceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fexceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
index 3bab553478b..f9209c81900 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
index c89cc7c2c47..bed1dcfb54a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
index b869fafb691..6bf0071f52b 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
index 1d3dd2f2c2c..4732f515bf7 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
index 4dd4281b03b..8d6424c479a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-default1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
similarity index 59%
rename from gcc/testsuite/c-c++-common/strub-default1.c
rename to gcc/testsuite/c-c++-common/strub-strict1.c
index 79d00cedb9a..ea28ba07e6f 100644
--- a/gcc/testsuite/c-c++-common/strub-default1.c
+++ b/gcc/testsuite/c-c++-common/strub-strict1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
 
 static int __attribute__ ((__strub__)) var;
 
@@ -31,12 +31,12 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-4\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { 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" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-default2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
similarity index 71%
rename from gcc/testsuite/c-c++-common/strub-default2.c
rename to gcc/testsuite/c-c++-common/strub-strict2.c
index 487253e9227..36e6f47b957 100644
--- a/gcc/testsuite/c-c++-common/strub-default2.c
+++ b/gcc/testsuite/c-c++-common/strub-strict2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
 
 static int __attribute__ ((__strub__)) var;
 
@@ -22,8 +22,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
index 0840dddd136..e48e0610e07 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 #include "strub-tail-O2.c"
 
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
index 9330d6ff4c1..87cda7ab21b 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.
    Tail calls are short-circuited at -O2+.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
index 5b33ff1f530..b5e45ab0525 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
index 38935e3270b..46b051411fb 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -1,39 +1,39 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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);
+extern int __attribute__ ((__strub__ ("callable"))) xcallable (void);
+extern int __attribute__ ((__strub__ ("internal"))) xinternal (void);
+extern int __attribute__ ((__strub__ ("at-calls"))) xat_calls (void);
+extern int __attribute__ ((__strub__ ("disabled"))) 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__ ("callable"))) callable (void);
+int __attribute__ ((__strub__ ("internal"))) internal (void);
+int __attribute__ ((__strub__ ("at-calls"))) at_calls (void);
+int __attribute__ ((__strub__ ("disabled"))) disabled (void);
 
 int __attribute__ ((__strub__)) var;
 int var_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 icallable (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 iinternal (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 idisabled (void);
 static inline int __attribute__ ((__always_inline__))
 ivar_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 i_callable (void) { return 0; }
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 i_internal (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 i_at_calls (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 i_disabled (void) { return 0; }
 static inline int __attribute__ ((__always_inline__))
 i_var_user (void) { return var; }
@@ -70,7 +70,7 @@ i_var_user (void) { return var; }
 
 /* Not a strub context, so it can call anything.
    Explicitly declared as callable even from within strub contexts.  */
-int __attribute__ ((__strub__ (3)))
+int __attribute__ ((__strub__ ("callable")))
 callable (void) {
   int ret = 0;
 
@@ -88,7 +88,7 @@ callable (void) {
 
 /* 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)))
+int __attribute__ ((__strub__ ("internal")))
 internal (void) {
   int ret = var;
 
@@ -109,7 +109,7 @@ internal (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (1)))
+int __attribute__ ((__strub__ ("at-calls")))
 at_calls (void) {
   int ret = var;
 
@@ -130,7 +130,7 @@ at_calls (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (0)))
+int __attribute__ ((__strub__ ("disabled")))
 disabled () {
   int ret = 0;
 
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
index 100fb0c59a9..2857195706e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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,
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
index 9e818ac9748..98a92bc9eac 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
index d40e8aa45cb..5511a6e1e71 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __const__))
 f() {
   return 0;
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
index d4cbdaf10f3..47ee927964d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -1,12 +1,12 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__
 __attribute__ ((__const__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data1.c b/gcc/testsuite/c-c++-common/torture/strub-data1.c
index 62a03891ab6..7c27a2a1a6d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointed-to data enables strubbing if accessed.  */
 int __attribute__ ((__strub__)) var;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data2.c b/gcc/testsuite/c-c++-common/torture/strub-data2.c
index 9b7df13a280..e66d903780a 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointer itself is a strub variable, enabling internal strubbing when
    its value is used.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data3.c b/gcc/testsuite/c-c++-common/torture/strub-data3.c
index 515706caa32..5e08e0e58c6 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data4.c b/gcc/testsuite/c-c++-common/torture/strub-data4.c
index 0ec9e35429f..a818e7a38bb 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
index 69f3d65ed44..90e8ad51c1e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -Werror" } */
+/* { dg-options "-fstrub=strict -Werror" } */
 
 typedef int __attribute__ ((__strub__)) strub_int;
 strub_int *ptr;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
index b8adf8009e8..c165f312f16 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype ();
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
index 5b2c35ad6a7..69fcff8d376 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
index 5ee50456dc9..ff006224909 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int, ...);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
new file mode 100644
index 00000000000..9a69539db9d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+}
+
+void
+bat (void)
+{
+  /* Not allowed, not a strub context.  */
+  inl_int_ali (); /* { dg-error "context" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
new file mode 100644
index 00000000000..f9a6b4a16fa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=all" } */
+
+#include "strub-inlinable1.c"
+
+/* With -fstrub=all, the caller becomes a strub context, so the strub-inlinable
+   callee is not rejected.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
new file mode 100644
index 00000000000..a7b29a80ff8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -Werror" } */
+
+typedef void ft (void);
+typedef void ft2 (int, int);
+extern ft __attribute__ ((__strub__)) fnac;
+
+ft * f (void) {
+  return fnac; /* { dg-error "incompatible" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
new file mode 100644
index 00000000000..896383078cd
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
@@ -0,0 +1,19 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic -Werror" } */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at_calls"))) bal (void);
+
+void
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+
+  d_p = bac; /* { dg-error "not quite compatible" } */
+  c_p = bad; /* { dg-error "not quite compatible" } */
+  c_p = bar; /* { dg-error "not quite compatible" } */
+  c_p = bal; /* { dg-error "incompatible" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
index cb223da6efc..a262a086837 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
index 67d1434b1f8..4c4bd50c209 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
index 59f02ea901f..ce195c6b1f1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -1,10 +1,10 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __pure__))
 f() {
   static int i; /* Stop it from being detected as const.  */
   return i;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
index 973e909217d..75cd54ccb5b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
 __attribute__ ((__pure__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
index a4077c35a60..c596066a045 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -14,7 +14,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -59,14 +59,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
index 94e4156ea73..1fdba214a07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
index 0ca74beb59d..afbc2cc9ab4 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
 /* Check that a non-strub function leaves a string behind in the stack, and that
@@ -7,7 +7,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
index 4ab11c0682e..5300f1d330b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -85,14 +85,14 @@ intermediate ()
   return ret;
 }
 
-static inline __attribute__ ((__strub__ (2)))
+static inline __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
   return intermediate ();
 }
 
-int __attribute__ ((strub (0)))
+int __attribute__ ((__strub__ ("disabled")))
 main ()
 {
   if (look_for_string (internal ()))
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4d.c b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
index e4f7445607c..08de3f1c3b1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4d.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -1,7 +1,7 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
-#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ (1)))
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
 
 #include "strub-run4.c"
diff --git a/gcc/testsuite/g++.dg/torture/strub-init1.C b/gcc/testsuite/g++.dg/torture/strub-init1.C
index e51ae802be4..c226ab10ff6 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init1.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init2.C b/gcc/testsuite/g++.dg/torture/strub-init2.C
index edcb7bf8ad2..a7911f1fa72 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init2.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init3.C b/gcc/testsuite/g++.dg/torture/strub-init3.C
index bacf823ca4e..6ebebcd01e8 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init3.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
index 697ac9de764..2d6f6394993 100644
--- a/gcc/testsuite/gnat.dg/strub_attr.adb
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -1,5 +1,5 @@
 --  { dg-do compile }
---  { dg-options "-fstrub=default -fdump-ipa-strubm" }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
 
 package body Strub_Attr is
    E : exception;
diff --git a/libgcc/strub.c b/libgcc/strub.c
index fd6e27556e4..7c58deee1e6 100644
--- a/libgcc/strub.c
+++ b/libgcc/strub.c
@@ -36,7 +36,7 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 # define TOPS <
 #endif
 
-#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ ("callable")))
 
 /* Enter a stack scrubbing context, initializing the watermark to the caller's
    stack address.  */


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

* [gcc(refs/users/aoliva/heads/strub)] -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests
@ 2021-09-03  2:17 Alexandre Oliva
  0 siblings, 0 replies; 13+ messages in thread
From: Alexandre Oliva @ 2021-09-03  2:17 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:57395163d6bfc0255b24e9a8d23903424c697a05

commit 57395163d6bfc0255b24e9a8d23903424c697a05
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Thu Sep 2 23:15:36 2021 -0300

    -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests

Diff:
---
 .../doc/gnat_rm/security_hardening_features.rst    |  30 ++
 gcc/ada/gcc-interface/utils.c                      |  95 ++--
 gcc/attribs.c                                      |  14 +-
 gcc/c-family/c-attribs.c                           |  84 ++--
 gcc/common.opt                                     |  27 +-
 gcc/doc/extend.texi                                | 288 ++++++++++--
 gcc/doc/invoke.texi                                |  54 ++-
 gcc/ipa-inline.c                                   |   2 +-
 gcc/ipa-strub.c                                    | 499 ++++++++++++++++-----
 gcc/ipa-strub.h                                    |  13 +-
 gcc/testsuite/c-c++-common/strub-O0.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O1.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2fni.c           |   2 +-
 gcc/testsuite/c-c++-common/strub-O3.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O3fni.c           |   2 +-
 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            |  12 +-
 gcc/testsuite/c-c++-common/strub-all2.c            |   6 +-
 gcc/testsuite/c-c++-common/strub-apply1.c          |   6 +-
 gcc/testsuite/c-c++-common/strub-apply2.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-apply3.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-apply4.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-at-calls2.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-defer-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |  14 +-
 gcc/testsuite/c-c++-common/strub-defer-Os.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-internal1.c       |   8 +-
 gcc/testsuite/c-c++-common/strub-internal2.c       |   6 +-
 gcc/testsuite/c-c++-common/strub-parms1.c          |  10 +-
 gcc/testsuite/c-c++-common/strub-parms2.c          |   8 +-
 gcc/testsuite/c-c++-common/strub-parms3.c          |   2 +-
 gcc/testsuite/c-c++-common/strub-relaxed1.c        |  18 +
 gcc/testsuite/c-c++-common/strub-relaxed2.c        |  14 +
 gcc/testsuite/c-c++-common/strub-short-O0-exc.c    |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O0.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O1.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O2.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-O3.c        |   2 +-
 gcc/testsuite/c-c++-common/strub-short-Os.c        |   2 +-
 .../{strub-default1.c => strub-strict1.c}          |  16 +-
 .../{strub-default2.c => strub-strict2.c}          |   8 +-
 gcc/testsuite/c-c++-common/strub-tail-O1.c         |   2 +-
 gcc/testsuite/c-c++-common/strub-tail-O2.c         |   2 +-
 .../c-c++-common/torture/strub-callable1.c         |   2 +-
 .../c-c++-common/torture/strub-callable2.c         |  42 +-
 gcc/testsuite/c-c++-common/torture/strub-const1.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const2.c  |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-const3.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-const4.c  |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-data1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data3.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data4.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |   2 +-
 .../c-c++-common/torture/strub-indcall1.c          |   2 +-
 .../c-c++-common/torture/strub-indcall2.c          |   2 +-
 .../c-c++-common/torture/strub-indcall3.c          |   2 +-
 .../c-c++-common/torture/strub-inlinable1.c        |  16 +
 .../c-c++-common/torture/strub-inlinable2.c        |   7 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |  10 +
 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |  19 +
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure2.c   |   2 +-
 gcc/testsuite/c-c++-common/torture/strub-pure3.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-pure4.c   |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run1.c    |  10 +-
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |   8 +-
 gcc/testsuite/c-c++-common/torture/strub-run4.c    |   4 +-
 gcc/testsuite/c-c++-common/torture/strub-run4d.c   |   4 +-
 gcc/testsuite/g++.dg/torture/strub-init1.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init2.C         |   2 +-
 gcc/testsuite/g++.dg/torture/strub-init3.C         |   2 +-
 gcc/testsuite/gnat.dg/strub_attr.adb               |   2 +-
 libgcc/strub.c                                     |   2 +-
 79 files changed, 1020 insertions(+), 450 deletions(-)

diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
index 309291f0341..90769dff635 100644
--- a/gcc/ada/doc/gnat_rm/security_hardening_features.rst
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -19,6 +19,18 @@ It can be enabled with the *-fzero-call-used-regs* command line
 option, to affect all subprograms in a compilation, and with a
 :samp:`Machine_Attribute` pragma, to affect only specific subprograms.
 
+.. code-block:: ada
+
+     procedure Foo;
+     pragma Machine_Attribute (Foo, "zero_call_used_regs", "used");
+     --  Before returning, Foo scrubs only call-clobbered registers
+     --  that it uses itself.
+
+     function Bar return Integer;
+     pragma Machine_Attribute (Bar, "zero_call_used_regs", "all");
+     --  Before returning, Bar scrubs all call-clobbered registers.
+
+
 For usage and more details on the command line option, and on the
 ``zero_call_used_regs`` attribute, see :title:`Using the GNU Compiler
 Collection (GCC)`.
@@ -35,6 +47,24 @@ It can be activated with the *-fstrub* command line option, to affect
 all (viable) subprograms in a compilation, and with a
 :samp:`Machine_Attribute` pragma.
 
+.. code-block:: ada
+
+     function Foo returns Integer;
+     pragma Machine_Attribute (Foo, "strub");
+     --  Foo and its callers are modified so as to scrub the stack
+     --  space used by Foo after it returns.
+
+     procedure Bar;
+     pragma Machine_Attribute (Bar, "strub", "internal");
+     --  Bar is turned into a wrapper for its original body,
+     --  and they scrub the stack used by the original body.
+
+     Var : Integer;
+     pragma Machine_Attribute (Var, "strub");
+     --  Reading from Var in a subprogram enables stack scrubbing
+     --  of the stack space used by the subprogram.
+
+
 For usage and more details on the command line option, and on the
 ``strub`` attribute, see :title:`Using the GNU Compiler Collection
 (GCC)`.
diff --git a/gcc/ada/gcc-interface/utils.c b/gcc/ada/gcc-interface/utils.c
index 6b20ddc93c9..1abf593d76b 100644
--- a/gcc/ada/gcc-interface/utils.c
+++ b/gcc/ada/gcc-interface/utils.c
@@ -39,6 +39,7 @@
 #include "varasm.h"
 #include "toplev.h"
 #include "opts.h"
+#include "ipa-strub.h"
 #include "output.h"
 #include "debug.h"
 #include "convert.h"
@@ -6611,6 +6612,7 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
+  bool compat_type = false;
   tree orig_fnptr_type = NULL_TREE;
   tree orig_args = args;
 
@@ -6624,68 +6626,36 @@ handle_strub_attribute (tree *node, tree name,
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      /* pragma Machine_Attribute turns string arguments into identifiers.
-	 Reverse it.  */
-      if (TREE_CODE (TREE_VALUE (args)) == IDENTIFIER_NODE)
+      switch (strub_validate_parm (TREE_VALUE (args)))
 	{
-	  gcc_checking_assert (IDENTIFIER_POINTER (TREE_VALUE (args))
-			       [IDENTIFIER_LENGTH (TREE_VALUE (args))] == 0);
-	  TREE_VALUE (args) = build_string
-	    (IDENTIFIER_LENGTH (TREE_VALUE (args)) + 1,
-	     IDENTIFIER_POINTER (TREE_VALUE (args)));
-	}
-
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
-	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
-
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
+	case 1:
+	  compat_type = true;
+	  /* Fall through.  */
 
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
+	  compat_type = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	  compat_type = true;
+	  /* Fall through.  */
+
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
@@ -6699,23 +6669,30 @@ handle_strub_attribute (tree *node, tree name,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   if (!*no_add_attrs)
     {
       *no_add_attrs = true;
       if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
 	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
+	{
+	  if (compat_type)
+	    *node = build_variant_type_copy (*node);
+	  else
+	    *node = build_distinct_type_copy (*node);
+	}
       TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
 					   TYPE_ATTRIBUTES (*node));
 
       if (orig_fnptr_type)
 	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
+	  tree fnptr_type = ((flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
+			     ? orig_fnptr_type
+			     : compat_type
+			     ? build_variant_type_copy (orig_fnptr_type)
+			     : build_distinct_type_copy (orig_fnptr_type));
 	  TREE_TYPE (fnptr_type) = *node;
 	  *node = fnptr_type;
 	}
diff --git a/gcc/attribs.c b/gcc/attribs.c
index 0d22c20a35e..acac634afb5 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "attribs.h"
 #include "fold-const.h"
+#include "ipa-strub.h"
 #include "stor-layout.h"
 #include "langhooks.h"
 #include "plugin.h"
@@ -1353,9 +1354,20 @@ comp_type_attributes (const_tree type1, const_tree type2)
   if ((lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type1)) != NULL)
       ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) != NULL))
     return 0;
+  int strub_ret = strub_comptypes (CONST_CAST_TREE (type1),
+				   CONST_CAST_TREE (type2));
+  if (strub_ret == 0)
+    return strub_ret;
   /* As some type combinations - like default calling-convention - might
      be compatible, we have to call the target hook to get the final result.  */
-  return targetm.comp_type_attributes (type1, type2);
+  int target_ret = targetm.comp_type_attributes (type1, type2);
+  if (target_ret == 0)
+    return target_ret;
+  if (strub_ret == 2 || target_ret == 2)
+    return 2;
+  if (strub_ret == 1 && target_ret == 1)
+    return 1;
+  gcc_unreachable ();
 }
 
 /* PREDICATE acts as a function of type:
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index dc4a4d4d200..2762dcdfcfe 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "common/common-target.h"
 #include "langhooks.h"
 #include "tree-inline.h"
+#include "ipa-strub.h"
 #include "toplev.h"
 #include "tree-iterator.h"
 #include "opts.h"
@@ -1306,6 +1307,7 @@ handle_strub_attribute (tree *node, tree name,
 			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
   bool enable = true;
+  bool compat_type = false;
   tree orig_fnptr_type = NULL_TREE;
   tree orig_args = args;
 
@@ -1319,57 +1321,36 @@ handle_strub_attribute (tree *node, tree name,
 
   if (args && FUNC_OR_METHOD_TYPE_P (*node))
     {
-      if (TREE_CODE (TREE_VALUE (args)) == STRING_CST)
+      switch (strub_validate_parm (TREE_VALUE (args)))
 	{
-	  const char *s = TREE_STRING_POINTER (TREE_VALUE (args));
-	  size_t len = TREE_STRING_LENGTH (TREE_VALUE (args));
-	  int val = -1;
-
-	  if (len == 9)
-	    switch (s[0])
-	      {
-	      case 'd':
-		if (strncmp (s, "disabled", len) == 0)
-		  val = 0;
-		break;
-
-	      case 'a':
-		if (strncmp (s, "at_calls", len) == 0)
-		  val = 1;
-		break;
-
-	      case 'i':
-		if (strncmp (s, "internal", len) == 0)
-		  val = 2;
-		break;
+	case 1:
+	  compat_type = true;
+	  /* Fall through.  */
 
-	      case 'c':
-		if (strncmp (s, "callable", len) == 0)
-		  val = 3;
-		break;
-	      }
-
-	  if (val >= 0)
-	    TREE_VALUE (args) = build_int_cst (integer_type_node, val);
-	}
+	case 2:
+	  enable = true;
+	  break;
 
-      /* Check that the supplied arg is acceptable.  */
-      if (TREE_CODE (TREE_VALUE (args)) != INTEGER_CST
-	  || !tree_fits_shwi_p (TREE_VALUE (args))
-	  /* Do not allow explicit -1 (STRUB_WRAPPED).  */
-	  || tree_to_shwi (TREE_VALUE (args)) < 0
-	  || tree_to_shwi (TREE_VALUE (args)) > 3)
-	{
+	case 0:
 	  warning (OPT_Wattributes,
 		   "%qE attribute ignored because of argument %qE",
 		   name, TREE_VALUE (args));
 	  *no_add_attrs = true;
+	  compat_type = true;
 	  enable = false;
+	  break;
+
+	case -1:
+	  compat_type = true;
+	  /* Fall through.  */
+
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
 	}
-      /* STRUB_DISABLED and STRUB_CALLABLE do not cause strub to be enabled.  */
-      else if (integer_zerop (TREE_VALUE (args))
-	       || tree_to_shwi (TREE_VALUE (args)) == 3)
-	enable = false;
 
       args = TREE_CHAIN (args);
     }
@@ -1383,23 +1364,30 @@ handle_strub_attribute (tree *node, tree name,
      implicitly or explicitly, note that the attribute was seen, so that we can
      reduce the compile-time overhead to nearly zero when the strub feature is
      not used.  */
-  if (enable && flag_strub == -2)
-    flag_strub = -1;
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
 
   if (!*no_add_attrs)
     {
       *no_add_attrs = true;
       if (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
 	  && !orig_fnptr_type)
-	*node = build_distinct_type_copy (*node);
+	{
+	  if (compat_type)
+	    *node = build_variant_type_copy (*node);
+	  else
+	    *node = build_distinct_type_copy (*node);
+	}
       TYPE_ATTRIBUTES (*node) = tree_cons (name, orig_args,
 					   TYPE_ATTRIBUTES (*node));
 
       if (orig_fnptr_type)
 	{
-	  tree fnptr_type = (!(flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-			     ? build_distinct_type_copy (orig_fnptr_type)
-			     : orig_fnptr_type);
+	  tree fnptr_type = ((flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
+			     ? orig_fnptr_type
+			     : compat_type
+			     ? build_variant_type_copy (orig_fnptr_type)
+			     : build_distinct_type_copy (orig_fnptr_type));
 	  TREE_TYPE (fnptr_type) = *node;
 	  *node = fnptr_type;
 	}
diff --git a/gcc/common.opt b/gcc/common.opt
index 71e0c971344..ef018ebf735 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,13 +2687,22 @@ fstrict-overflow
 Common
 Treat signed overflow as undefined.  Negated as -fwrapv -fwrapv-pointer.
 
-; If any strub-enabling attribute is seen when the default value is
-; selected, it's bumped up to -1.  The scrub mode gate function will
-; then bump -2 to 0 if no strub-enabling attribute is seen.  This
-; minimizes the strub overhead.
-fstrub=default
-Common RejectNegative Var(flag_strub, -2) Init(-2)
-Enable stack scrub as requested through attributes.
+fstrub=disable
+Common RejectNegative Var(flag_strub, 0)
+Disable stack scrub entirely, disregarding strub attributes.
+
+fstrub=strict
+Common RejectNegative Var(flag_strub, -4)
+Enable stack scrub as per attributes, with strict call checking.
+
+; If any strub-enabling attribute is seen when the default or strict
+; initializer values are in effect, flag_strub is bumped up by 2.  The
+; scrub mode gate function will then bump these initializer values to
+; 0 if no strub-enabling attribute is seen.  This minimizes the strub
+; overhead.
+fstrub=relaxed
+Common RejectNegative Var(flag_strub, -3) Init(-3)
+Restore default strub mode: as per attributes, with relaxed checking.
 
 fstrub=all
 Common RejectNegative Var(flag_strub, 3)
@@ -2707,10 +2716,6 @@ fstrub=internal
 Common RejectNegative Var(flag_strub, 2)
 Enable internal stack scrubbing for all viable functions.
 
-fstrub=disable
-Common RejectNegative Var(flag_strub, 0)
-Disable stack scrub entirely, disregarding strub attributes.
-
 fsync-libcalls
 Common Var(flag_sync_libcalls) Init(1)
 Implement __atomic operations via libcalls to legacy __sync functions.
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 1fcf60a3875..46cefe27fa5 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -8729,51 +8729,259 @@ pid_t wait (wait_status_ptr_t p)
 @item strub
 @cindex @code{strub} type attribute
 This attribute defines stack-scrubbing properties of functions and
-variables.  When applied to function types, it takes an optional
-argument.  When applied to a pointer-to-function type, it gets
-propagated to the function type if the optional argument is given.
-
-A function whose type is annotated with @code{at-calls} @code{strub}
-mode (@code{strub("at-calls")}, @code{strub(1)}, or @code{strub})
-undergoes interface changes.  Its callers are adjusted to match the
-changes, and to automatically scrub (overwrite with zeros) the stack
-space used by the function.
-
-A function whose type indicates @code{internal} @code{strub} mode
-(@code{strub("internal")} or @code{strub(2)}) retains an unmodified
-interface, but may be turned into a wrapper that calls the wrapped body
-using a custom interface.  The wrapper then scrubs the stack space used
-by the wrapped body.
-
-An automatically-allocated variable whose type carries the @code{strub}
-attribute causes the enclosing function to have @code{strub} enabled.
-
-A statically-allocated variable whose type carries the @code{strub}
-attribute causes functions that @emph{read} it with that type to have
-@code{strub} enabled.  Reading from data referenced by pointers to such
-types has the same effect.  Note: The attribute does not carry over from
-a composite type to the types of its components, so the intended effect
-may not be obtained with non-scalar types.
-
-A @code{strub} context is the body of a function that has strub enabled,
-be it explicitly, by @code{at-calls} or @code{internal} mode, or
-implicitly, by reading from a @code{strub}-marked data type.
+variables.  Being a type attribute, it attaches to types, even when
+specified in function and variable declarations.  When applied to
+function types, it takes an optional string argument.  When applied to a
+pointer-to-function type, if the optional argument is given, it gets
+propagated to the function type.
+
+@smallexample
+/* A strub variable.  */
+int __attribute__ ((strub)) var;
+/* A strub variable that happens to be a pointer.  */
+__attribute__ ((strub)) int *strub_ptr_to_int;
+/* A pointer type that may point to a strub variable.  */
+typedef int __attribute__ ((strub)) *ptr_to_strub_int_type;
+
+/* A declaration of a strub function.  */
+extern int __attribute__ ((strub)) foo (void);
+/* A pointer to that strub function.  */
+int __attribute__ ((strub ("at-calls"))) (*ptr_to_strub_fn)(void) = foo;
+@end smallexample
+
+A function associated with @code{at-calls} @code{strub} mode
+(@code{strub("at-calls")}, or just @code{strub}) undergoes interface
+changes.  Its callers are adjusted to match the changes, and to scrub
+(overwrite with zeros) the stack space used by the called function after
+it returns.  The interface change makes the function type incompatible
+with an unadorned but otherwise equivalent type, so @emph{every}
+declaration and every type that may be used to call the function must be
+associated with this strub mode.
+
+A function associated with @code{internal} @code{strub} mode
+(@code{strub("internal")}) retains an unmodified, type-compatible
+interface, but it may be turned into a wrapper that calls the wrapped
+body using a custom interface.  The wrapper then scrubs the stack space
+used by the wrapped body.  Though the wrapped body has its stack space
+scrubbed, the wrapper does not, so arguments and return values may
+remain unscrubbed even when such a function is called by another
+function that enables @code{strub}.  This is why, when compiling with
+@samp{-fstrub=strict}, a @code{strub} context is not allowed to call
+@code{internal} @code{strub} functions.
+
+@smallexample
+/* A declaration of an internal-strub function.  */
+extern int __attribute__ ((strub ("internal"))) bar (void);
+
+int __attribute__ ((strub))
+baz (void)
+@{
+  /* Ok, foo was declared above as an at-calls strub function.  */
+  foo ();
+  /* Not allowed in strict mode, otherwise allowed.  */
+  bar ();
+@}
+@end smallexample
+
+An automatically-allocated variable associated with the @code{strub}
+attribute causes the (immediately) enclosing function to have
+@code{strub} enabled.
+
+A statically-allocated variable associated with the @code{strub}
+attribute causes functions that @emph{read} it, through its @code{strub}
+data type, to have @code{strub} enabled.  Reading data by dereferencing
+a pointer to a @code{strub} data type has the same effect.  Note: The
+attribute does not carry over from a composite type to the types of its
+components, so the intended effect may not be obtained with non-scalar
+types.
+
+When selecting a @code{strub}-enabled mode for a function that is not
+explicitly associated with one, because of @code{strub} variables or
+data pointers, the function must satisfy @code{internal} mode viability
+requirements (see below), even when @code{at-calls} mode is also viable
+and, being more efficient, ends up selected as an optimization.
+
+@smallexample
+/* zapme is implicitly strub-enabled because of strub variables.
+   Optimization may change its strub mode, but not the requirements.  */
+static int
+zapme (int i)
+@{
+  /* A local strub variable enables strub.  */
+  int __attribute__ ((strub)) lvar;
+  /* Reading strub data through a pointer-to-strub enables strub.  */
+  lvar = * (ptr_to_strub_int_type) &i;
+  /* Writing to a global strub variable does not enable strub.  */
+  var = lvar;
+  /* Reading from a global strub variable enables strub.  */
+  return var;
+@}
+@end smallexample
+
+A @code{strub} context is the body (as opposed to the interface) of a
+function that has @code{strub} enabled, be it explicitly, by
+@code{at-calls} or @code{internal} mode, or implicitly, due to
+@code{strub} variables or command-line options.
 
 A function of a type associated with the @code{disabled} @code{strub}
-mode (@code{strub("disabled")}, @code{strub(0)}, or no @code{strub} mode
-specified) will not have its own stack space scrubbed, and it cannot be
-called from @code{strub} contexts.
+mode (@code{strub("disabled")} will not have its own stack space
+scrubbed.  Such functions @emph{cannot} be called from within
+@code{strub} contexts.
+
+In order to enable a function to be called from within @code{strub}
+contexts without having its stack space scrubbed, associate it with the
+@code{callable} @code{strub} mode (@code{strub("callable")}).
+
+When a function is not assigned a @code{strub} mode, explicitly or
+implicitly, the mode defaults to @code{callable}, except when compiling
+with @samp{-fstrub=strict}, that causes @code{strub} mode to default to
+@code{disabled}.
+
+@example
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+ /* Implicitly disabled with -fstrub=strict, otherwise callable.  */
+extern int bah (void);
+
+int __attribute__ ((strub))
+bal (void)
+@{
+  /* Not allowed, bad is not strub-callable.  */
+  bad ();
+  /* Ok, bac is strub-callable.  */
+  bac ();
+  /* Not allowed with -fstrub=strict, otherwise allowed.  */
+  bah ();
+@}
+@end example
 
-A function that does not have @code{strub} enabled can only be called
-from within @code{strub} contexts through a function type marked with
-the @code{callable} @code{strub} mode (@code{strub("callable")} or
-@code{strub(3)}).
+Function types marked @code{callable} and @code{disabled} are not
+mutually compatible types, but the underlying interfaces are compatible,
+so it is safe to convert pointers between them, and to use such pointers
+or alternate declarations to call them.  Interfaces are also
+interchangeable between them and @code{internal} (but not
+@code{at-calls}!), but adding @code{internal} to a pointer type will not
+cause the pointed-to function to perform stack scrubbing.
+
+@example
+void __attribute__ ((strub))
+bap (void)
+@{
+  /* Assign a callable function to pointer-to-disabled.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bac;
+  /* Not allowed: calls disabled type in a strub context.  */
+  d_p ();
+
+  /* Assign a disabled function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bad;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an internal function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  c_p = bar;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an at-calls function to pointer-to-callable.
+     Flaggged as incompatible.  */
+  c_p = bal;
+  /* The call through an interface-incompatible type will not use the
+     modified interface expected by the at-calls function, so it is
+     likely to misbehave at runtime.  */
+  c_p ();
+@}
+@end example
 
 @code{Strub} contexts are never inlined into non-@code{strub} contexts.
-When an @code{internal}-strub function is split, the wrapper can often
-be inlined, but the wrapped body never is.  Functions marked as
-@code{always_inline}, even if explicitly assigned @code{internal} strub
-mode, will not undergo wrapping, so their body gets inlined.
+When an @code{internal}-strub function is split up, the wrapper can
+often be inlined, but the wrapped body @emph{never} is.  A function
+marked as @code{always_inline}, even if explicitly assigned
+@code{internal} strub mode, will not undergo wrapping, so its body gets
+inlined as required.
+
+@example
+inline int __attribute__ ((strub ("at-calls")))
+inl_atc (void)
+@{
+  /* This body may get inlined into strub contexts.  */
+@}
+
+inline int __attribute__ ((strub ("internal")))
+inl_int (void)
+@{
+  /* This body NEVER gets inlined, though its wrapper may.  */
+@}
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+@{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+@}
+
+void __attribute__ ((strub ("disabled")))
+bat (void)
+@{
+  /* Not allowed, cannot inline into a non-strub context.  */
+  inl_int_ali ();
+@}
+@end example
+
+@cindex strub eligibility and viability
+Some @samp{-fstrub=*} command line options enable @code{strub} modes
+implicitly where viable.  A @code{strub} mode is only viable for a
+function if the function is eligible for that mode, and if other
+conditions, detailed below, are satisfied.  If it's not eligible for a
+mode, attempts to explicitly associate it with that mode are rejected
+with an error message.  If it is eligible, that mode may be assigned
+explicitly through this attribute, but implicit assignment through
+command-line options may involve additional viability requirements.
+
+A function is ineligible for @code{at-calls} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+@code{At-calls} @code{strub} mode, if not requested through the function
+type, is only viable for an eligible function if the function is not
+visible to other translation units, and if it doesn't have its address
+taken.
+
+@smallexample
+/* bar is eligible for at-calls strub mode,
+   but not viable for that mode because it is visible to other units.
+   It is eligible and viable for internal strub mode.  */
+void bav () @{@}
+
+/* setp is eligible for at-calls strub mode,
+   but not viable for that mode because its address is taken.
+   It is eligible and viable for internal strub mode.  */
+void setp (void) @{ static void (*p)(void); = setp; @}
+@end smallexample
+
+A function is ineligible for @code{internal} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, or if attribute
+@code{noipa} is present.  For an @code{always_inline} function, meeting
+these requirements is enough to make it eligible.  Any function that has
+attribute @code{noclone}, that uses such extensions as non-local labels,
+computed gotos, alternate variable argument passing interfaces,
+@code{__builtin_next_arg}, or @code{__builtin_return_address}, or that
+takes too many (about 64Ki) arguments is ineligible, unless it is
+@code{always_inline}.  For @code{internal} @code{strub} mode, all
+eligible functions are viable.
+
+@smallexample
+/* flop is not eligible, thus not viable, for at-calls strub mode.
+   Likewise for internal strub mode.  */
+__attribute__ ((noipa)) void flop (void) @{@}
+
+/* flip is eligible and viable for at-calls strub mode.
+   It would be ineligible for internal strub mode, because of noclone,
+   if it weren't for always_inline.  With always_inline, noclone is not
+   an obstacle, so it is also eligible and viable for internal strub mode.  */
+inline __attribute__ ((noclone, always_inline)) void flip (void) @{@}
+@end smallexample
 
 @item unused
 @cindex @code{unused} type attribute
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 838d9ff1198..dd529c1a82e 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,7 +599,8 @@ Objective-C and Objective-C++ Dialects}.
 -fstack-protector-explicit  -fstack-check @gol
 -fstack-limit-register=@var{reg}  -fstack-limit-symbol=@var{sym} @gol
 -fno-stack-limit  -fsplit-stack @gol
--fstrub=default -fstrub=disable -fstrub=at-calls -fstrub=internal -fstrub=all @gol
+-fstrub=disable -fstrub=strict -fstrub=relaxed @gol
+-fstrub=all -fstrub=at-calls -fstrub=internal @gol
 -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
 -fvtv-counts  -fvtv-debug @gol
 -finstrument-functions @gol
@@ -15513,46 +15514,41 @@ without @option{-fsplit-stack} always has a large stack.  Support for
 this is implemented in the gold linker in GNU binutils release 2.21
 and later.
 
-@item -fstrub=default
-@opindex fstrub=default
-Restore the default stack scrub (@code{strub}) setting, namely,
-@code{strub} is only enabled as required by @code{strub} attributes
-associated with function or variable types.  This is only useful to
-override earlier @samp{-fstrub=*} options.
-
 @item -fstrub=disable
 @opindex -fstrub=disable
 Disable stack scrubbing entirely, ignoring any @code{strub} attributes.
+See @xref{Common Type Attributes}.
+
+@item -fstrub=strict
+@opindex fstrub=strict
+Functions default to @code{strub} mode @code{disabled}, and apply
+@code{strict}ly the restriction that only functions associated with
+@code{strub}-@code{calalble} modes (@code{at-calls}, @code{callable} and
+@code{always_inline} @code{internal}) are @code{callable} by functions
+with @code{strub}-enabled modes (@code{at-calls} and @code{internal}).
+
+@item -fstrub=relaxed
+@opindex fstrub=relaxed
+Restore the default stack scrub (@code{strub}) setting, namely,
+@code{strub} is only enabled as required by @code{strub} attributes
+associated with function and data types.  @code{Relaxed} means that
+strub contexts are only prevented from calling functions explicitly
+associated with @code{strub} mode @code{disabled}.  This option is only
+useful to override other @samp{-fstrub=*} options that precede it in the
+command line.
 
 @item -fstrub=at-calls
 @opindex fstrub=at-calls
-Enable @code{at-calls} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{at-calls} @code{strub} if a different @code{strub}
-mode is explicitly requested, if attribute @code{noipa} is present, or
-if it calls @code{__builtin_apply_args}.  @code{At-calls} @code{strub}
-mode, if not requested through the function type, is only viable for an
-eligible function if the function is not visible to other translation
-units, and it doesn't have its address taken.
+Enable @code{at-calls} @code{strub} mode where viable.
 
 @item -fstrub=internal
 @opindex fstrub=internal
-Enable @code{internal} @code{strub} for all viable functions, and
-consider non-viable functions as @code{callable}.  A function is
-ineligible for @code{internal} @code{strub} if a different @code{strub}
-mode is explicitly requested, or if attribute @code{noipa} is present.
-Non-@code{always_inline} functions also become ineligible if attribute
-@code{noclone} is present, if the function uses such features as user
-labels, non-default variable argument interfaces,
-@code{__builtin_next_arg}, or @code{__builtin_return_address}, or if
-they have too many (about 64Ki) arguments.  For @code{internal}
-@code{strub}, all eligible functions are viable.
+Enable @code{internal} @code{strub} mode where viable.
 
 @item -fstrub=all
 @opindex fstrub=all
-Enable @code{strub} for all viable functions, and consider non-viable
-functions as @code{callable}.  When both strub modes are viable,
-@code{at-calls} is preferred.
+Enable some @code{strub} mode where viable.
+When both strub modes are viable, @code{at-calls} is preferred.
 
 @item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 @opindex fvtable-verify
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 7f4bc44d2bb..ee360b6c6ca 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -397,7 +397,7 @@ can_inline_edge_p (struct cgraph_edge *e, bool report,
       e->inline_failed = CIF_SANITIZE_ATTRIBUTE_MISMATCH;
       inlinable = false;
     }
-  if (!strub_inlinable_p (callee, caller))
+  if (!strub_inlinable_from_p (callee, caller))
     {
       e->inline_failed = CIF_UNSPECIFIED;
       inlinable = false;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
index cf7d811779e..7ea54cd1344 100644
--- a/gcc/ipa-strub.c
+++ b/gcc/ipa-strub.c
@@ -159,12 +159,16 @@ enum strub_mode {
 
 };
 
+/* Look up a strub attribute in TYPE, and return it.  */
+
 static tree
 get_strub_attr_from_type (tree type)
 {
   return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
 }
 
+/* Look up a strub attribute in DECL or in its type, and return it.  */
+
 static tree
 get_strub_attr_from_decl (tree decl)
 {
@@ -174,23 +178,159 @@ get_strub_attr_from_decl (tree decl)
   return get_strub_attr_from_type (TREE_TYPE (decl));
 }
 
-tree
+/* Define a function to cache identifier ID, to be used as a strub attribute
+   parameter for a strub mode named after NAME.  */
+#define DEF_STRUB_IDS(NAME, ID)				\
+static inline tree get_strub_mode_id_ ## NAME () {	\
+  static tree identifier = NULL_TREE;			\
+  if (!identifier)					\
+    identifier = get_identifier (ID);			\
+  return identifier;					\
+}
+/* Same as DEF_STRUB_IDS, but use the string expansion of NAME as ID.  */
+#define DEF_STRUB_ID(NAME)				\
+DEF_STRUB_IDS (NAME, #NAME)
+
+/* Define functions for each of the strub mode identifiers.
+   Expose dashes rather than underscores.  */
+DEF_STRUB_ID (disabled)
+DEF_STRUB_IDS (at_calls, "at-calls")
+DEF_STRUB_ID (internal)
+DEF_STRUB_ID (callable)
+DEF_STRUB_ID (wrapped)
+DEF_STRUB_ID (wrapper)
+DEF_STRUB_ID (inlinable)
+DEF_STRUB_IDS (at_calls_opt, "at-calls-opt")
+
+/* Release the temporary macro names.  */
+#undef DEF_STRUB_IDS
+#undef DEF_STRUB_ID
+
+/* Return the identifier corresponding to strub MODE.  */
+
+static tree
+get_strub_mode_attr_parm (enum strub_mode mode)
+{
+  switch (mode)
+    {
+    case STRUB_DISABLED:
+      return get_strub_mode_id_disabled ();
+
+    case STRUB_AT_CALLS:
+      return get_strub_mode_id_at_calls ();
+
+    case STRUB_INTERNAL:
+      return get_strub_mode_id_internal ();
+
+    case STRUB_CALLABLE:
+      return get_strub_mode_id_callable ();
+
+    case STRUB_WRAPPED:
+      return get_strub_mode_id_wrapped ();
+
+    case STRUB_WRAPPER:
+      return get_strub_mode_id_wrapper ();
+
+    case STRUB_INLINABLE:
+      return get_strub_mode_id_inlinable ();
+
+    case STRUB_AT_CALLS_OPT:
+      return get_strub_mode_id_at_calls_opt ();
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+/* Return the parmeters (TREE_VALUE) for a strub attribute of MODE.
+   We know we use a single parameter, so we bypass the creation of a
+   tree list.  */
+
+static tree
 get_strub_mode_attr_value (enum strub_mode mode)
 {
-  return tree_cons (NULL_TREE,
-		    build_int_cst (integer_type_node, (int)mode),
-		    NULL_TREE);
+  return get_strub_mode_attr_parm (mode);
+}
+
+/* Determine whether ID is a well-formed strub mode-specifying attribute
+   parameter.  Only user-visible modes are accepted.
+
+   For unacceptable parms, return 0, otherwise a nonzero value as below.
+
+   If the parm enables strub, return positive, otherwise negative.
+
+   If the affected type must be a distinct, incompatible type,return an integer
+   of absolute value 2, otherwise 1.  */
 
-#if 0 /* ??? use symbolic mode names with interned strings?  */
-  char *s = NULL;
+int
+strub_validate_parm (tree id)
+{
+  int ret;
+
+  if (!id)
+    return true;
 
-  switch (strub_mode)
+  const char *s = NULL;
+  size_t len = 0;
+
+  if (TREE_CODE (id) == STRING_CST)
     {
-      
+      s = TREE_STRING_POINTER (id);
+      len = TREE_STRING_LENGTH (id) - 1;
     }
-#endif
+  else if (TREE_CODE (id) == IDENTIFIER_NODE)
+    {
+      s = IDENTIFIER_POINTER (id);
+      len = IDENTIFIER_LENGTH (id);
+    }
+  else
+    return false;
+
+  enum strub_mode mode;
+
+  if (len != 8)
+    return false;
+
+  switch (s[0])
+    {
+    case 'd':
+      mode = STRUB_DISABLED;
+      ret = -1;
+      break;
+
+    case 'a':
+      mode = STRUB_AT_CALLS;
+      ret = 2;
+      break;
+
+    case 'i':
+      mode = STRUB_INTERNAL;
+      ret = 1;
+      break;
+
+    case 'c':
+      mode = STRUB_CALLABLE;
+      ret = -2;
+      break;
+
+    default:
+      /* Other parms are for internal use only.  */
+      return 0;
+    }
+
+  tree mode_id = get_strub_mode_attr_value (mode);
+
+  if (TREE_CODE (id) == IDENTIFIER_NODE
+      ? id == mode_id
+      : strncmp (s, IDENTIFIER_POINTER (mode_id), len))
+    return 0;
+
+  return ret;
 }
 
+/* Return the strub mode from STRUB_ATTR.  VAR_P should be true if the attribute
+   is taken from a variable, rather than from a function, or a type thereof.  */
+
 static enum strub_mode
 get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
 {
@@ -200,28 +340,102 @@ get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
     {
       if (!TREE_VALUE (strub_attr))
 	mode = !var_p ? STRUB_AT_CALLS : STRUB_INTERNAL;
-      else if (TREE_CODE (TREE_VALUE (TREE_VALUE (strub_attr))) == INTEGER_CST)
-	mode = (enum strub_mode) tree_to_shwi (TREE_VALUE
-					       (TREE_VALUE (strub_attr)));
-      else /* Handlers convert symbolic mode names to INTEGER_CST.  */
-	gcc_unreachable ();
+      else
+	{
+	  gcc_checking_assert (!var_p);
+	  tree id = TREE_VALUE (strub_attr);
+	  if (TREE_CODE (id) == TREE_LIST)
+	    id = TREE_VALUE (id);
+	  const char *s = (TREE_CODE (id) == STRING_CST
+			   ? TREE_STRING_POINTER (id)
+			   : IDENTIFIER_POINTER (id));
+	  size_t len = (TREE_CODE (id) == STRING_CST
+			? TREE_STRING_LENGTH (id) - 1
+			: IDENTIFIER_LENGTH (id));
+
+	  switch (len)
+	    {
+	    case 7:
+	      switch (s[6])
+		{
+		case 'r':
+		  mode = STRUB_WRAPPER;
+		  break;
+
+		case 'd':
+		  mode = STRUB_WRAPPED;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 8:
+	      switch (s[0])
+		{
+		case 'd':
+		  mode = STRUB_DISABLED;
+		  break;
+
+		case 'a':
+		  mode = STRUB_AT_CALLS;
+		  break;
+
+		case 'i':
+		  mode = STRUB_INTERNAL;
+		  break;
+
+		case 'c':
+		  mode = STRUB_CALLABLE;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 9:
+	      mode = STRUB_INLINABLE;
+	      break;
+
+	    case 12:
+	      mode = STRUB_AT_CALLS_OPT;
+	      break;
+
+	    default:
+	      gcc_unreachable ();
+	    }
+
+	  gcc_checking_assert (TREE_CODE (id) == IDENTIFIER_NODE
+			       ? id == get_strub_mode_attr_value (mode)
+			       : strncmp (IDENTIFIER_POINTER
+					  (get_strub_mode_attr_value (mode)),
+					  s, len));
+	}
     }
 
   return mode;
 }
 
+/* Look up, decode and return the strub mode associated with FNDECL.  */
+
 static enum strub_mode
-get_strub_mode_from_decl (tree fndecl)
+get_strub_mode_from_fndecl (tree fndecl)
 {
   return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
 }
 
+/* Look up, decode and return the strub mode associated with NODE.  */
+
 static enum strub_mode
 get_strub_mode (cgraph_node *node)
 {
-  return get_strub_mode_from_decl (node->decl);
+  return get_strub_mode_from_fndecl (node->decl);
 }
 
+/* Look up, decode and return the strub mode associated with TYPE.  */
+
 static enum strub_mode
 get_strub_mode_from_type (tree type)
 {
@@ -231,12 +445,15 @@ get_strub_mode_from_type (tree type)
   if (attr)
     return get_strub_mode_from_attr (attr, var_p);
 
-  if (flag_strub > 0 && !var_p)
+  if (flag_strub >= -1 && !var_p)
     return STRUB_CALLABLE;
 
   return STRUB_DISABLED;
 }
 
+\f
+/* Return true iff NODE calls builtin va_start.  */
+
 static bool
 calls_builtin_va_start_p (cgraph_node *node)
 {
@@ -252,6 +469,8 @@ calls_builtin_va_start_p (cgraph_node *node)
   return result;
 }
 
+/* Return true iff NODE calls builtin apply_args, and optionally REPORT it.  */
+
 static bool
 calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
 {
@@ -276,12 +495,17 @@ calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE carries the always_inline attribute.  */
+
 static inline bool
 strub_always_inline_p (cgraph_node *node)
 {
   return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
 }
 
+/* Return true iff NODE is potentially eligible for any strub-enabled mode, and
+   optionally REPORT the reasons for ineligibility.  */
+
 static inline bool
 can_strub_p (cgraph_node *node, bool report = false)
 {
@@ -321,6 +545,10 @@ can_strub_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return true iff NODE is eligible for at-calls strub, and optionally REPORT
+   the reasons for ineligibility.  Besides general non-eligibility for
+   strub-enabled modes, at-calls rules out calling builtin apply_args.  */
+
 static bool
 can_strub_at_calls_p (cgraph_node *node, bool report = false)
 {
@@ -332,6 +560,9 @@ can_strub_at_calls_p (cgraph_node *node, bool report = false)
   return !calls_builtin_apply_args_p (node, report);
 }
 
+/* Symbolic macro for the max number of arguments that internal strub may add to
+   a function.  */
+
 #define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
 
 /* We can't perform internal strubbing if the function body involves certain
@@ -438,14 +669,12 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 			   || tree_versionable_function_p (node->decl));
 
 
-      /* Label values referenced are not preserved when copying.  If referenced
+      /* Label values references are not preserved when copying.  If referenced
 	 in nested functions, as in 920415-1.c and 920721-4.c their decls get
-	 remapped independently.  That might be too broad, in that we might be
-	 able to support correctly cases in which the labels are only used
-	 internally in a function, but disconnecting user labels from their
-	 original declarations is undesirable in general, and it probably
-	 doesn't matter, since explicitly-requested strub likely uses
-	 STRUB_AT_CALLS mode anyway.  */
+	 remapped independently.  The exclusion below might be too broad, in
+	 that we might be able to support correctly cases in which the labels
+	 are only used internally in a function, but disconnecting forced labels
+	 from their original declarations is undesirable in general.  */
       basic_block bb;
       FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
 	for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -459,8 +688,6 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 
 	    target = gimple_label_label (label_stmt);
 
-	    /* Make an edge to every label block that has been marked as a
-	       potential target for a computed goto or a non-local goto.  */
 	    if (!FORCED_LABEL (target))
 	      continue;
 
@@ -470,7 +697,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 forced labels");
 	  }
     }
 
@@ -491,6 +718,9 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
   return result;
 }
 
+/* Return TRUE iff NODE has any strub-requiring local variable, or accesses (as
+   in reading) any variable through a strub-requiring type.  */
+
 static bool
 strub_from_body_p (cgraph_node *node)
 {
@@ -506,7 +736,9 @@ strub_from_body_p (cgraph_node *node)
 	!= STRUB_DISABLED)
       return true;
 
-  /* Now scan the body for loads with strub types.  */
+  /* Now scan the body for loads with strub-requiring types.
+     ??? Compound types don't propagate the strub requirement to
+     component types.  */
   basic_block bb;
   FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
     for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
@@ -528,6 +760,7 @@ strub_from_body_p (cgraph_node *node)
 
 /* Return true iff node is associated with a builtin that should be callable
    from strub contexts.  */
+
 static inline bool
 strub_callable_builtin_p (cgraph_node *node)
 {
@@ -568,17 +801,23 @@ strub_callable_builtin_p (cgraph_node *node)
     }
 }
 
+/* Compute the strub mode to be used for NODE.  STRUB_ATTR should be the strub
+   attribute,found for NODE, if any.  */
+
 static enum strub_mode
 compute_strub_mode (cgraph_node *node, tree strub_attr)
 {
   enum strub_mode req_mode = get_strub_mode_from_attr (strub_attr);
 
-  gcc_checking_assert (flag_strub >= -1 && flag_strub <= 3);
+  gcc_checking_assert (flag_strub >= -2 && flag_strub <= 3);
 
   /* Symbolic encodings of the -fstrub-* flags.  */
   /* Enable strub when explicitly requested through attributes to functions or
      variables, reporting errors if the requests cannot be satisfied.  */
   const bool strub_flag_auto = flag_strub < 0;
+  /* strub_flag_auto with strub call verification; without this, functions are
+     implicitly callable.  */
+  const bool strub_flag_strict = flag_strub < -1;
   /* Disable strub altogether, ignore attributes entirely.  */
   const bool strub_flag_disabled = flag_strub == 0;
   /* On top of _auto, also enable strub implicitly for functions that can
@@ -625,7 +864,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (!strub_flag_disabled
        && (strub_attr
 	   ? req_mode == STRUB_CALLABLE
-	   : (strub_flag_viable
+	   : (!strub_flag_strict
 	      || strub_callable_builtin_p (node))));
 
   /* This is a shorthand for either strub-enabled mode.  */
@@ -674,19 +913,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
     = (at_calls_eligible
        && (strub_attr
 	   || (node->has_gimple_body_p ()
-#if 0 /* We no longer use collect_callers, so we can probably drop it.  */
-	       && node->get_availability () > AVAIL_INTERPOSABLE
-#endif
-	       && ((!node->externally_visible
-#if 0
-		    /* We wish to bypass the test below for functions that are
-		       not externally visible, but that's a little too broad: we
-		       do not wish to skip them for e.g. gnu_inline
-		       functions.  */
-		    && !TREE_PUBLIC (node->decl)
-		    && !DECL_EXTERNAL (node->decl)
-#endif
-		    )
+	       && (!node->externally_visible
 		   || (node->binds_to_current_def_p ()
 		       && node->can_be_local_p ()))
 	       && node->only_called_directly_p ())));
@@ -737,10 +964,6 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
   const enum strub_mode mode
     = ((strub_enable && is_always_inline)
        ? (strub_required ? STRUB_INLINABLE : STRUB_CALLABLE)
-#if 0
-       : (!strub_enable && strub_required && strub_attr)
-       ? req_mode
-#endif
        : (strub_enable && internal_viable
 	  && (strub_flag_internal || !at_calls_viable))
        ? STRUB_INTERNAL
@@ -802,6 +1025,7 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 /* Set FNDT's strub mode to MODE; FNDT may be a function decl or
    function type.  If OVERRIDE, do not check whether a mode is already
    set.  */
+
 static void
 strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
 {
@@ -828,12 +1052,18 @@ strub_set_fndt_mode_to (tree fndt, enum strub_mode mode, bool override)
   *attrp = attr;
 }
 
+/* Set FNDT's strub mode to callable.
+   FNDT may be a function decl or a function type.  */
+
 void
 strub_make_callable (tree fndt)
 {
   strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
 }
 
+/* Set NODE to strub MODE.  Report incompatibilities between MODE and the mode
+   requested through explicit attributes, and cases of non-eligibility.  */
+
 static void
 set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
 {
@@ -906,6 +1136,8 @@ set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
   strub_set_fndt_mode_to (node->decl, mode, attr);
 }
 
+/* Compute and set NODE's strub mode.  */
+
 static void
 set_strub_mode (cgraph_node *node)
 {
@@ -946,6 +1178,7 @@ set_strub_mode (cgraph_node *node)
   set_strub_mode_to (node, mode);
 }
 
+\f
 /* Non-strub functions shouldn't be called from within strub contexts,
    except through callable ones.  Always inline strub functions can
    only be called from strub functions.  */
@@ -984,7 +1217,7 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_AT_CALLS_OPT:
     case STRUB_INTERNAL:
     case STRUB_WRAPPER:
-      return (flag_strub >= 0);
+      return (flag_strub >= -1);
 
     case STRUB_DISABLED:
       return false;
@@ -1004,7 +1237,7 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
    strubbed functions into non-strubbed ones.  */
 
 bool
-strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+strub_inlinable_from_p (cgraph_node *callee, cgraph_node *caller)
 {
   strub_mode callee_mode = get_strub_mode (callee);
 
@@ -1049,14 +1282,43 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
   return false;
 }
 
+/* Check that types T1 and T2 are strub-compatible.  Return 1 if the strub modes
+   are the same, 2 if they are interchangeable, and 0 otherwise.  */
+
+int
+strub_comptypes (tree t1, tree t2)
+{
+  enum strub_mode m1 = get_strub_mode_from_type (t1);
+  enum strub_mode m2 = get_strub_mode_from_type (t2);
+
+  if (m1 == m2)
+    return 1;
+
+  /* We're dealing with types, so only strub modes that can be selected by
+     attributes in the front end matter.  If either mode is at-calls, the
+     conversion is not compatible.  */
+  if (m1 == STRUB_AT_CALLS || m2 == STRUB_AT_CALLS)
+    return 0;
+
+  return 2;
+}
+
 /* Check that strub functions don't call non-strub functions, and that
    always_inline strub functions are only called by strub
    functions.  */
+
 static void
 verify_strub ()
 {
   cgraph_node *node;
 
+  /* It's expected that check strub-wise pointer type compatibility of variables
+     and of functions is already taken care of by front-ends, on account of the
+     attribute's being marked as affecting type identity and of the creation of
+     distinct types.  */
+
+  /* Check that call targets in strub contexts have strub-callable types.  */
+
   FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
   {
     enum strub_mode caller_mode = get_strub_mode (node);
@@ -1105,14 +1367,11 @@ verify_strub ()
 	  }
       }
   }
-
-  /* ??? Check strub-wise pointer type compatibility of variables and
-     functions, or is this already taken care of on account of the
-     attribute's being marked as affecting type identity?  */
 }
 
 namespace {
 
+/* Define a pass to compute strub modes.  */
 const pass_data pass_data_ipa_strub_mode = {
   SIMPLE_IPA_PASS,
   "strubm",
@@ -1133,18 +1392,20 @@ public:
   {}
   opt_pass *clone () { return new pass_ipa_strub_mode (m_ctxt); }
   virtual bool gate (function *) {
-    /* In the default setting, the attribute handler changes
-       flag_strub to -1 if any strub-enabling occurence of the
-       attribute is found.  If it remains at -2, nothing that would
-       enable strub was found, so we can disable it and avoid the
+    /* In relaxed (-3) and strict (-4) settings, that only enable strub at a
+       function or variable attribute's request, the attribute handler changes
+       flag_strub to -1 or -2, respectively, if any strub-enabling occurence of
+       the attribute is found.  Therefore, if it remains at -3 or -4, nothing
+       that would enable strub was found, so we can disable it and avoid the
        overhead.  */
-    if (flag_strub == -2)
+    if (flag_strub < -2)
       flag_strub = 0;
     return flag_strub;
   }
   virtual unsigned int execute (function *);
 };
 
+/* Define a pass to introduce strub transformations.  */
 const pass_data pass_data_ipa_strub = {
   SIMPLE_IPA_PASS,
   "strub",
@@ -1170,6 +1431,7 @@ public:
   virtual bool gate (function *) { return flag_strub; }
   virtual unsigned int execute (function *);
 
+  /* Define on demand and cache some types we use often.  */
 #define DEF_TYPE(NAME, INIT)			\
   static inline tree get_ ## NAME () {		\
     static tree type = NULL_TREE;		\
@@ -1202,6 +1464,7 @@ public:
 
 #undef DEF_TYPE
 
+  /* Define non-strub builtins on demand.  */
 #define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST)			\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1224,6 +1487,7 @@ public:
 
 #undef DEF_NM_BUILTIN
 
+  /* Define strub builtins on demand.  */
 #define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST)		\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1260,6 +1524,7 @@ public:
 
 #undef DEF_SS_BUILTIN
 
+    /* Define strub identifiers on demand.  */
 #define DEF_IDENT(NAME)					\
   static inline tree get_ ## NAME () {			\
     static tree identifier = NULL_TREE;			\
@@ -1278,6 +1543,10 @@ public:
   static inline void adjust_at_calls_call (cgraph_edge *, int);
   static inline void adjust_at_calls_calls (cgraph_node *);
 
+  /* Add to SEQ a call to the strub watermark update builtin, taking NODE's
+     location if given.  Optionally add the corresponding edge from NODE, with
+     execution frequency COUNT.  Return the modified SEQ.  */
+
   static inline gimple_seq
   call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
 			 gimple_seq seq = NULL)
@@ -1300,8 +1569,14 @@ public:
 
 } // anon namespace
 
+/* Gather with this type a collection of parameters that we're turning into
+   explicit references.  */
+
 typedef hash_set<tree> indirect_parms_t;
 
+/* Dereference OP if it's an INDIRECT_PARMS or an ADDR_EXPR thereof.  Set *REC
+   and return according to gimple-walking expectations.  */
+
 static tree
 maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
 {
@@ -1330,6 +1605,8 @@ maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
   return NULL_TREE;
 }
 
+/* A gimple-walking function that adds dereferencing to indirect parms.  */
+
 static tree
 walk_make_indirect (tree *op, int *rec, void *arg)
 {
@@ -1351,6 +1628,11 @@ walk_make_indirect (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* A gimple-walking function that turns any non-gimple-val ADDR_EXPRs into a
+   separate SSA.  Though addresses of e.g. parameters, and of members thereof,
+   are gimple vals, turning parameters into references, with an extra layer of
+   indirection and thus explicit dereferencing, need to be regimplified.  */
+
 static tree
 walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
 {
@@ -1374,6 +1656,11 @@ walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
   return NULL_TREE;
 }
 
+/* Create a reference type to use for PARM when turning it into a reference.
+   NONALIASED causes the reference type to gain its own separate alias set, so
+   that accessing the indirectly-passed parm won'will not add aliasing
+   noise.  */
+
 static tree
 build_ref_type_for (tree parm, bool nonaliased = true)
 {
@@ -1411,6 +1698,7 @@ build_ref_type_for (tree parm, bool nonaliased = true)
 
 /* Add cgraph edges from current_function_decl to callees in SEQ with frequency
    COUNT, assuming all calls in SEQ are direct.  */
+
 static void
 add_call_edges_for_seq (gimple_seq seq, profile_count count)
 {
@@ -1435,6 +1723,12 @@ add_call_edges_for_seq (gimple_seq seq, profile_count count)
     }
 }
 
+/* Insert SEQ after the call at GSI, as if the call was in a try block with SEQ
+   as finally, i.e., SEQ will run after the call whether it returns or
+   propagates an exception.  This handles block splitting, EH edge and block
+   creation, noreturn and nothrow optimizations, and even throwing calls without
+   preexisting local handlers.  */
+
 static void
 gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
 {
@@ -1586,8 +1880,12 @@ remove_named_attribute_unsharing (const char *name, tree *attrs)
     }
 }
 
+/* Record the order of the last cgraph entry whose mode we've already set, so
+   that we can perform mode setting incrementally without duplication.  */
 static int last_cgraph_order;
 
+/* Set strub modes for functions introduced since the last call.  */
+
 static void
 ipa_strub_set_mode_for_new_functions ()
 {
@@ -1616,6 +1914,7 @@ ipa_strub_set_mode_for_new_functions ()
 }
 
 /* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
+
 bool
 strub_splittable_p (cgraph_node *node)
 {
@@ -1641,10 +1940,11 @@ strub_splittable_p (cgraph_node *node)
 }
 
 /* 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))
+  switch (get_strub_mode_from_fndecl (fndecl))
     {
     case STRUB_WRAPPED:
     case STRUB_AT_CALLS:
@@ -1676,6 +1976,7 @@ strub_watermark_parm (tree fndecl)
 
 /* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
    hasn't been added yet.  Return the named argument count.  */
+
 int
 pass_ipa_strub::adjust_at_calls_type (tree type)
 {
@@ -1751,6 +2052,12 @@ pass_ipa_strub::adjust_at_calls_type (tree type)
   return named_args;
 }
 
+/* Adjust a call to an at-calls call target.  Create a watermark local variable
+   if needed, initialize it before, pass it to the callee according to the
+   modified at-calls interface, and release the callee's stack space after the
+   call, if not deferred.  If the call is const or pure, arrange for the
+   watermark to not be assumed unused or unchanged.  */
+
 void
 pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 {
@@ -1819,10 +2126,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 
     if (gimple_call_internal_p (stmt))
 #if 0
-      /*
-	new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
-	vargs);
-      */
+      new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
+						 vargs);
 #endif
       gcc_unreachable ();
     else
@@ -1917,6 +2222,8 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
   gsi_insert_finally_seq_after_call (gsi, seq);
 }
 
+/* Adjust all at-calls calls in NODE. */
+
 void
 pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
 {
@@ -1965,6 +2272,10 @@ pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
     }
 }
 
+/* The strubm (strub mode) pass computes a strub mode for each function in the
+   call graph, and checks, before any inlining, that strub callability
+   requirements in effect are satisfied.  */
+
 unsigned int
 pass_ipa_strub_mode::execute (function *)
 {
@@ -1977,12 +2288,17 @@ pass_ipa_strub_mode::execute (function *)
   return 0;
 }
 
+/* Create a strub mode pass.  */
+
 simple_ipa_opt_pass *
 make_pass_ipa_strub_mode (gcc::context *ctxt)
 {
   return new pass_ipa_strub_mode (ctxt);
 }
 
+/* The strub pass proper adjusts types, signatures, and at-calls calls, and
+   splits internal-strub functions.  */
+
 unsigned int
 pass_ipa_strub::execute (function *)
 {
@@ -2042,17 +2358,6 @@ pass_ipa_strub::execute (function *)
 	if (onode->alias)
 	  continue;
 
-#if 0 /* Calls are now adjusted when examining callers.  */
-	unsigned c;
-	cgraph_edge *e;
-	FOR_EACH_VEC_ELT (onode->collect_callers (), c, e)
-	  {
-	    push_cfun (DECL_STRUCT_FUNCTION (e->caller->decl));
-	    adjust_at_calls_call (e, named_args);
-	    pop_cfun ();
-	  }
-#endif
-
 	cgraph_node *nnode = onode;
 	push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
 
@@ -2088,10 +2393,6 @@ pass_ipa_strub::execute (function *)
 	  }
 
 	pop_cfun ();
-
-#if 0
-	compute_fn_summary (onode, true);
-#endif
       }
   }
 
@@ -2108,43 +2409,6 @@ pass_ipa_strub::execute (function *)
 	continue;
       }
 
-#if 0
-    /* Hmm, this is an i386-specific attribute.  Do we need machine-specific
-       logic?  */
-    remove_named_attribute_unsharing ("interrupt",
-				      &DECL_ATTRIBUTES (onode->decl));
-#endif
-
-#if 0
-    if (!DECL_STRUCT_FUNCTION (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting struct-less function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    if (!onode->lowered)
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting non-lowered function %qD for %<strub%>",
-		onode->decl);
-	continue;
-      }
-
-    /* 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.  FIXME.  */
-    if (!tree_versionable_function_p (onode->decl))
-      {
-	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"%qD cannot be split for %<strub%>",
-		onode->decl);
-	continue;
-      }
-#endif
-
     bool is_stdarg = calls_builtin_va_start_p (onode);;
     bool apply_args = calls_builtin_apply_args_p (onode);
 
@@ -2973,11 +3237,6 @@ pass_ipa_strub::execute (function *)
 
       pop_cfun ();
     }
-
-#if 0
-    compute_fn_summary (onode, true);
-    compute_fn_summary (nnode, true);
-#endif
   }
 
   if (flag_checking)
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
index c335ab42097..61a47b7ce1e 100644
--- a/gcc/ipa-strub.h
+++ b/gcc/ipa-strub.h
@@ -22,7 +22,7 @@ along with GCC; see the file COPYING3.  If not see
    as far as stack scrubbing constraints are concerned.  CALLEE
    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 bool strub_inlinable_from_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);
@@ -33,3 +33,14 @@ extern tree strub_watermark_parm (tree fndecl);
 
 /* Make a function type or declaration callable.  */
 extern void strub_make_callable (tree fndecl);
+
+/* Return zero iff ID is NOT an acceptable parameter for a user-supplied strub
+   attribute.  Otherwise, return >0 if it enables strub, <0 if it does not.
+   Return +/-1 if the attribute-modified type is compatible with the type
+   without the attribute, or +/-2 if it is not compatible.  */
+extern int strub_validate_parm (tree id);
+
+/* Like comptypes, return 0 if t1 and t2 are not compatible, 1 if they are
+   compatible, and 2 if they are nearly compatible.  Same strub mode is
+   compatible, interface-compatible strub modes are nearly compatible.  */
+extern int strub_comptypes (tree t1, tree t2);
diff --git a/gcc/testsuite/c-c++-common/strub-O0.c b/gcc/testsuite/c-c++-common/strub-O0.c
index 6afe0fd5de1..c7a79a6ea0d 100644
--- a/gcc/testsuite/c-c++-common/strub-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O0 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O0, none of the strub builtins are expanded inline.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O1.c b/gcc/testsuite/c-c++-common/strub-O1.c
index 1cdeaecaf32..96285c975d9 100644
--- a/gcc/testsuite/c-c++-common/strub-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O1 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O1, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
index 7848c46d179..8edc0d8aa13 100644
--- a/gcc/testsuite/c-c++-common/strub-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -O2, without -fno-inline, we fully expand enter and update, and add a test
    around the leave call.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
index 85a8f76785e..c6d900cf3c4 100644
--- a/gcc/testsuite/c-c++-common/strub-O2fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3.c b/gcc/testsuite/c-c++-common/strub-O3.c
index 1fcde345d36..33ee465e51c 100644
--- a/gcc/testsuite/c-c++-common/strub-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand" } */
 
 int __attribute__ ((__strub__)) var;
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
index a2eedfd96b2..2936f82079e 100644
--- a/gcc/testsuite/c-c++-common/strub-O3fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
index e5cb1f60541..479746e57d8 100644
--- a/gcc/testsuite/c-c++-common/strub-Og.c
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Og -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Og -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Og, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
index 194aacc2c05..2241d4ea07f 100644
--- a/gcc/testsuite/c-c++-common/strub-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fdump-rtl-expand" } */
+/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
 
 /* At -Os, without -fno-inline, we fully expand enter, and also update.  The
    expanded update might be larger than a call proper, but argument saving and
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
index 46e84bf6560..a322bcc5da6 100644
--- a/gcc/testsuite/c-c++-common/strub-all1.c
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -22,11 +22,11 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
index f377541cff0..db60026d0e0 100644
--- a/gcc/testsuite/c-c++-common/strub-all2.c
+++ b/gcc/testsuite/c-c++-common/strub-all2.c
@@ -17,8 +17,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
index f180b17f30e..2f462adc1ef 100644
--- a/gcc/testsuite/c-c++-common/strub-apply1.c
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -1,13 +1,13 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
-void __attribute__ ((__strub__ (3)))
+void __attribute__ ((__strub__ ("callable")))
 apply_function (void *args)
 {
   __builtin_apply (0, args, 0);
 }
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   void *args = __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
index 379a54b73b7..a5d7551f5da 100644
--- a/gcc/testsuite/c-c++-common/strub-apply2.c
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 extern void __attribute__ ((__strub__))
 apply_function (void *args);
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
index 9b4786be698..64422a0d1e8 100644
--- a/gcc/testsuite/c-c++-common/strub-apply3.c
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 void __attribute__ ((__strub__))
 apply_function (void *args)
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
index 409f747743e..ea18d0bbd54 100644
--- a/gcc/testsuite/c-c++-common/strub-apply4.c
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+/* { dg-options "-O2 -fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
index d964b07ae5d..b70843b4215 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -22,9 +22,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
index 530eee36d06..97a3988a6b9 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls2.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls2.c
@@ -17,7 +17,7 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
index 7b04eea35d9..3d73431b3dc 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O1" } */
+/* { dg-options "-fstrub=strict -O1" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O1.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
index 67d96419a5e..fddf3c745e7 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O2" } */
+/* { dg-options "-fstrub=strict -O2" } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O2.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
index 34828d2711e..2bc384ee8d1 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -O3" } */
+/* { dg-options "-fstrub=strict -O3" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -O3.  */
@@ -10,7 +10,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -18,7 +18,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -32,7 +32,7 @@ leak_string (void)
   return 0;
 }
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 int
 look_for_string (char *e)
 {
@@ -56,14 +56,14 @@ look_for_string (char *e)
   return 0;
 }
 
-static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+static __attribute__ ((__strub__ ("at-calls"), __noinline__, __noclone__))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 deferred_at_calls ()
 {
@@ -73,7 +73,7 @@ deferred_at_calls ()
   return ret;
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 deferred_internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
index b273660aea1..fbaf85fe0fa 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default -Os" } */
+/* { dg-options "-fstrub=strict -Os" } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -Os.  */
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
index a74658c9ac9..e9d7b7b9ee0 100644
--- a/gcc/testsuite/c-c++-common/strub-internal1.c
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -23,9 +23,9 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
index a6e69357b23..8b8e15a51c7 100644
--- a/gcc/testsuite/c-c++-common/strub-internal2.c
+++ b/gcc/testsuite/c-c++-common/strub-internal2.c
@@ -14,8 +14,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
index 7e22a266ad9..0a4a7539d34 100644
--- a/gcc/testsuite/c-c++-common/strub-parms1.c
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -16,7 +16,7 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 large_byref_arg (struct large_arg la)
 {
 }
@@ -24,7 +24,7 @@ large_byref_arg (struct large_arg la)
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*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)))
+void __attribute__ ((__strub__ ("internal")))
 std_arg (int i, ...)
 {
   va_list vl;
@@ -38,7 +38,7 @@ std_arg (int i, ...)
 /* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
 
-void __attribute__ ((__strub__ (2)))
+void __attribute__ ((__strub__ ("internal")))
 apply_args (int i, int j, double d)
 {
   __builtin_apply_args ();
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
index 2d8036c0fbc..147171d96d5 100644
--- a/gcc/testsuite/c-c++-common/strub-parms2.c
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -1,9 +1,9 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 #include <stdarg.h>
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 small_args (int i, long long l, void *p, void **q, double d, char c)
 {
 }
@@ -15,14 +15,14 @@ struct large_arg {
   int x[128];
 };
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 large_byref_arg (struct large_arg la)
 {
 }
 
 /* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
 
-void __attribute__ ((__strub__ (1)))
+void __attribute__ ((__strub__ ("at-calls")))
 std_arg (int i, ...)
 {
   va_list vl;
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
index f64361f1235..4e92682895a 100644
--- a/gcc/testsuite/c-c++-common/strub-parms3.c
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
new file mode 100644
index 00000000000..e2f9d8aebca
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  Without the error,
+   inlining takes place.  */
+
+#include "strub-strict1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
new file mode 100644
index 00000000000..98474435d2e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  */
+
+#include "strub-strict2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
index a4226ce0119..1de15342595 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fexceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fexceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
index 3bab553478b..f9209c81900 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O0 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O0 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
index c89cc7c2c47..bed1dcfb54a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
index b869fafb691..6bf0071f52b 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
index 1d3dd2f2c2c..4732f515bf7 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
index 4dd4281b03b..8d6424c479a 100644
--- a/gcc/testsuite/c-c++-common/strub-short-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-default1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
similarity index 59%
rename from gcc/testsuite/c-c++-common/strub-default1.c
rename to gcc/testsuite/c-c++-common/strub-strict1.c
index 79d00cedb9a..ea28ba07e6f 100644
--- a/gcc/testsuite/c-c++-common/strub-default1.c
+++ b/gcc/testsuite/c-c++-common/strub-strict1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
 
 static int __attribute__ ((__strub__)) var;
 
@@ -31,12 +31,12 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-4\[)\]" 1 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { 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" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-default2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
similarity index 71%
rename from gcc/testsuite/c-c++-common/strub-default2.c
rename to gcc/testsuite/c-c++-common/strub-strict2.c
index 487253e9227..36e6f47b957 100644
--- a/gcc/testsuite/c-c++-common/strub-default2.c
+++ b/gcc/testsuite/c-c++-common/strub-strict2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } */
 
 static int __attribute__ ((__strub__)) var;
 
@@ -22,8 +22,8 @@ f() {
 }
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
index 0840dddd136..e48e0610e07 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 #include "strub-tail-O2.c"
 
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
index 9330d6ff4c1..87cda7ab21b 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
 
 /* Check that the expected strub calls are issued.
    Tail calls are short-circuited at -O2+.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
index 5b33ff1f530..b5e45ab0525 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
index 38935e3270b..46b051411fb 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -1,39 +1,39 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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);
+extern int __attribute__ ((__strub__ ("callable"))) xcallable (void);
+extern int __attribute__ ((__strub__ ("internal"))) xinternal (void);
+extern int __attribute__ ((__strub__ ("at-calls"))) xat_calls (void);
+extern int __attribute__ ((__strub__ ("disabled"))) 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__ ("callable"))) callable (void);
+int __attribute__ ((__strub__ ("internal"))) internal (void);
+int __attribute__ ((__strub__ ("at-calls"))) at_calls (void);
+int __attribute__ ((__strub__ ("disabled"))) disabled (void);
 
 int __attribute__ ((__strub__)) var;
 int var_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 icallable (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 iinternal (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 iat_calls (void);
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 idisabled (void);
 static inline int __attribute__ ((__always_inline__))
 ivar_user (void);
 
-static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
 i_callable (void) { return 0; }
-static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
 i_internal (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
 i_at_calls (void) { return var; }
-static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
 i_disabled (void) { return 0; }
 static inline int __attribute__ ((__always_inline__))
 i_var_user (void) { return var; }
@@ -70,7 +70,7 @@ i_var_user (void) { return var; }
 
 /* Not a strub context, so it can call anything.
    Explicitly declared as callable even from within strub contexts.  */
-int __attribute__ ((__strub__ (3)))
+int __attribute__ ((__strub__ ("callable")))
 callable (void) {
   int ret = 0;
 
@@ -88,7 +88,7 @@ callable (void) {
 
 /* 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)))
+int __attribute__ ((__strub__ ("internal")))
 internal (void) {
   int ret = var;
 
@@ -109,7 +109,7 @@ internal (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (1)))
+int __attribute__ ((__strub__ ("at-calls")))
 at_calls (void) {
   int ret = var;
 
@@ -130,7 +130,7 @@ at_calls (void) {
   return ret;
 }
 
-int __attribute__ ((__strub__ (0)))
+int __attribute__ ((__strub__ ("disabled")))
 disabled () {
   int ret = 0;
 
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
index 100fb0c59a9..2857195706e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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,
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
index 9e818ac9748..98a92bc9eac 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
index d40e8aa45cb..5511a6e1e71 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __const__))
 f() {
   return 0;
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
index d4cbdaf10f3..47ee927964d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -1,12 +1,12 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__
 __attribute__ ((__const__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data1.c b/gcc/testsuite/c-c++-common/torture/strub-data1.c
index 62a03891ab6..7c27a2a1a6d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointed-to data enables strubbing if accessed.  */
 int __attribute__ ((__strub__)) var;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data2.c b/gcc/testsuite/c-c++-common/torture/strub-data2.c
index 9b7df13a280..e66d903780a 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* The pointer itself is a strub variable, enabling internal strubbing when
    its value is used.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data3.c b/gcc/testsuite/c-c++-common/torture/strub-data3.c
index 515706caa32..5e08e0e58c6 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data4.c b/gcc/testsuite/c-c++-common/torture/strub-data4.c
index 0ec9e35429f..a818e7a38bb 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 /* 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
index 69f3d65ed44..90e8ad51c1e 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -Werror" } */
+/* { dg-options "-fstrub=strict -Werror" } */
 
 typedef int __attribute__ ((__strub__)) strub_int;
 strub_int *ptr;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
index b8adf8009e8..c165f312f16 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype ();
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
index 5b2c35ad6a7..69fcff8d376 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
index 5ee50456dc9..ff006224909 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int, ...);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
new file mode 100644
index 00000000000..9a69539db9d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+}
+
+void
+bat (void)
+{
+  /* Not allowed, not a strub context.  */
+  inl_int_ali (); /* { dg-error "context" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
new file mode 100644
index 00000000000..f9a6b4a16fa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=all" } */
+
+#include "strub-inlinable1.c"
+
+/* With -fstrub=all, the caller becomes a strub context, so the strub-inlinable
+   callee is not rejected.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
new file mode 100644
index 00000000000..a7b29a80ff8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -Werror" } */
+
+typedef void ft (void);
+typedef void ft2 (int, int);
+extern ft __attribute__ ((__strub__)) fnac;
+
+ft * f (void) {
+  return fnac; /* { dg-error "incompatible" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
new file mode 100644
index 00000000000..896383078cd
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
@@ -0,0 +1,19 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic -Werror" } */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at_calls"))) bal (void);
+
+void
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+
+  d_p = bac; /* { dg-error "not quite compatible" } */
+  c_p = bad; /* { dg-error "not quite compatible" } */
+  c_p = bar; /* { dg-error "not quite compatible" } */
+  c_p = bal; /* { dg-error "incompatible" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
index cb223da6efc..a262a086837 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
index 67d1434b1f8..4c4bd50c209 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
index 59f02ea901f..ce195c6b1f1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -1,10 +1,10 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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__))
+int __attribute__ ((__strub__ ("internal"), __pure__))
 f() {
   static int i; /* Stop it from being detected as const.  */
   return i;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
index 973e909217d..75cd54ccb5b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -1,11 +1,11 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -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)))
+int __attribute__ ((__strub__ ("internal")))
 #if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
 __attribute__ ((__pure__))
 #endif
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
index a4077c35a60..c596066a045 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -14,7 +14,7 @@ leak_string (void)
      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 *);
+  typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *);
   callable_t *f = 0;
 
   char s[sizeof (test_string)];
@@ -59,14 +59,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
index 94e4156ea73..1fdba214a07 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -1,12 +1,12 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 
 /* 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)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
index 0ca74beb59d..afbc2cc9ab4 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -1,5 +1,5 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
 /* Check that a non-strub function leaves a string behind in the stack, and that
@@ -7,7 +7,7 @@
 
 const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
 
-static inline __attribute__ ((__always_inline__, __strub__ (3)))
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
 char *
 leak_string (void)
 {
@@ -49,14 +49,14 @@ callable ()
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (1)))
+static __attribute__ ((__strub__ ("at-calls")))
 char *
 at_calls ()
 {
   return leak_string ();
 }
 
-static __attribute__ ((__strub__ (2)))
+static __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
index 4ab11c0682e..5300f1d330b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -85,14 +85,14 @@ intermediate ()
   return ret;
 }
 
-static inline __attribute__ ((__strub__ (2)))
+static inline __attribute__ ((__strub__ ("internal")))
 char *
 internal ()
 {
   return intermediate ();
 }
 
-int __attribute__ ((strub (0)))
+int __attribute__ ((__strub__ ("disabled")))
 main ()
 {
   if (look_for_string (internal ()))
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4d.c b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
index e4f7445607c..08de3f1c3b1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4d.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -1,7 +1,7 @@
 /* { dg-do run } */
-/* { dg-options "-fstrub=default" } */
+/* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
 
-#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ (1)))
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
 
 #include "strub-run4.c"
diff --git a/gcc/testsuite/g++.dg/torture/strub-init1.C b/gcc/testsuite/g++.dg/torture/strub-init1.C
index e51ae802be4..c226ab10ff6 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init1.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init2.C b/gcc/testsuite/g++.dg/torture/strub-init2.C
index edcb7bf8ad2..a7911f1fa72 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init2.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init3.C b/gcc/testsuite/g++.dg/torture/strub-init3.C
index bacf823ca4e..6ebebcd01e8 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init3.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
index 697ac9de764..2d6f6394993 100644
--- a/gcc/testsuite/gnat.dg/strub_attr.adb
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -1,5 +1,5 @@
 --  { dg-do compile }
---  { dg-options "-fstrub=default -fdump-ipa-strubm" }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
 
 package body Strub_Attr is
    E : exception;
diff --git a/libgcc/strub.c b/libgcc/strub.c
index fd6e27556e4..7c58deee1e6 100644
--- a/libgcc/strub.c
+++ b/libgcc/strub.c
@@ -36,7 +36,7 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 # define TOPS <
 #endif
 
-#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ ("callable")))
 
 /* Enter a stack scrubbing context, initializing the watermark to the caller's
    stack address.  */


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

end of thread, other threads:[~2021-09-08 19:25 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-09-05  5:38 [gcc(refs/users/aoliva/heads/strub)] -fstrub=default->strict, +=relaxed, req symbolic attr parms, adj tests Alexandre Oliva
  -- strict thread matches above, loose matches on Subject: below --
2021-09-08 19:25 Alexandre Oliva
2021-09-08 18:43 Alexandre Oliva
2021-09-07 23:42 Alexandre Oliva
2021-09-07 22:44 Alexandre Oliva
2021-09-07 22:35 Alexandre Oliva
2021-09-07 19:08 Alexandre Oliva
2021-09-05  9:50 Alexandre Oliva
2021-09-05  2:50 Alexandre Oliva
2021-09-04 14:12 Alexandre Oliva
2021-09-04 11:10 Alexandre Oliva
2021-09-03  7:02 Alexandre Oliva
2021-09-03  2:17 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).