public inbox for gcc-cvs@sourceware.org
help / color / mirror / Atom feed
* [gcc(refs/users/aoliva/heads/strub)] fix handling of indirect calls
@ 2021-08-04  8:17 Alexandre Oliva
  0 siblings, 0 replies; only message in thread
From: Alexandre Oliva @ 2021-08-04  8:17 UTC (permalink / raw)
  To: gcc-cvs

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

commit a00086813f5ca93baf77276a281daf53803f5c62
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Wed Aug 4 05:14:09 2021 -0300

    fix handling of indirect calls

Diff:
---
 gcc/builtins.c                                     |   6 +-
 gcc/ipa-strub.c                                    | 707 ++++++++++++---------
 gcc/testsuite/c-c++-common/strub-O0.c              |  14 +
 gcc/testsuite/c-c++-common/strub-O1.c              |  15 +
 gcc/testsuite/c-c++-common/strub-O2.c              |  16 +
 gcc/testsuite/c-c++-common/strub-O2fni.c           |  15 +
 gcc/testsuite/c-c++-common/strub-O3.c              |  12 +
 gcc/testsuite/c-c++-common/strub-O3fni.c           |  15 +
 gcc/testsuite/c-c++-common/strub-Og.c              |  16 +
 gcc/testsuite/c-c++-common/strub-Os.c              |  18 +
 gcc/testsuite/c-c++-common/strub-all1.c            |  32 +
 gcc/testsuite/c-c++-common/strub-all2.c            |  24 +
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |  30 +
 gcc/testsuite/c-c++-common/strub-at-calls2.c       |  23 +
 gcc/testsuite/c-c++-common/strub-default1.c        |  40 ++
 gcc/testsuite/c-c++-common/strub-default2.c        |  29 +
 gcc/testsuite/c-c++-common/strub-internal1.c       |  31 +
 gcc/testsuite/c-c++-common/strub-internal2.c       |  21 +
 gcc/testsuite/c-c++-common/torture/strub-data1.c   |  13 +
 gcc/testsuite/c-c++-common/torture/strub-data2.c   |  14 +
 gcc/testsuite/c-c++-common/torture/strub-data3.c   |  14 +
 gcc/testsuite/c-c++-common/torture/strub-data4.c   |  14 +
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |  15 +
 .../c-c++-common/torture/strub-indcall1.c          |  14 +
 .../c-c++-common/torture/strub-indcall2.c          |  14 +
 .../c-c++-common/torture/strub-indcall3.c          |  14 +
 gcc/testsuite/g++.dg/torture/strub-init1.C         |  13 +
 gcc/testsuite/g++.dg/torture/strub-init2.C         |  14 +
 gcc/testsuite/g++.dg/torture/strub-init3.C         |  13 +
 29 files changed, 927 insertions(+), 289 deletions(-)

diff --git a/gcc/builtins.c b/gcc/builtins.c
index 73d80482c32..f387d93974f 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -7923,7 +7923,7 @@ expand_builtin_strub_enter (tree exp)
   if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
     return NULL_RTX;
 
-  if (!optimize || flag_no_inline)
+  if (optimize < 1 || flag_no_inline)
     return NULL_RTX;
 
   rtx stktop = expand_builtin_stack_address ();
@@ -7947,7 +7947,7 @@ expand_builtin_strub_update (tree exp)
   if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
     return NULL_RTX;
 
-  if (optimize < 2 || flag_no_inline)
+  if (optimize < 2 || optimize_size || flag_no_inline)
     return NULL_RTX;
 
   rtx stktop = expand_builtin_stack_address ();
@@ -8005,7 +8005,7 @@ expand_builtin_strub_leave (tree exp)
 			   ptr_mode, NULL_RTX, done, NULL,
 			   profile_probability::very_likely ());
 
-  if (optimize < 3 || !flag_inline_functions)
+  if (optimize < 3)
     expand_call (exp, NULL_RTX, true);
   else
     {
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
index 992ab511b26..e302f0ec1c5 100644
--- a/gcc/ipa-strub.c
+++ b/gcc/ipa-strub.c
@@ -146,6 +146,17 @@ enum strub_mode {
   /* This denotes an always_inline function that requires strubbing.  It can
      only be called from, and inlined into, other strubbing contexts.  */
   STRUB_INLINABLE = -3,
+
+  /* This denotes a function that accesses strub variables, so it would call for
+     internal strubbing (whether or not it's eligible for that), but since
+     at-calls strubbing is viable, that's selected as an optimization.  This
+     mode addresses the inconvenience that such functions may have different
+     modes selected depending on optimization flags, and get a different
+     callable status depending on that choice: if we assigned them
+     STRUB_AT_CALLS mode, they would be callable when optimizing, whereas
+     STRUB_INTERNAL would not be callable.  */
+  STRUB_AT_CALLS_OPT = -4,
+
 };
 
 static tree
@@ -418,6 +429,13 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 		node->decl);
     }
 
+  if (result)
+    /* Since we're not changing the function identity proper, just
+       moving its full implementation, we *could* disable
+       fun->cannot_be_copied_reason and/or temporarily drop a noclone
+       attribute.  */
+    gcc_checking_assert (tree_versionable_function_p (node->decl));
+
   return result;
 }
 
@@ -590,7 +608,9 @@ 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
@@ -659,7 +679,9 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 	  && (strub_flag_internal || !at_calls_viable))
        ? STRUB_INTERNAL
        : (strub_enable && at_calls_viable)
-       ? STRUB_AT_CALLS
+       ? (strub_required && !strub_attr
+	  ? STRUB_AT_CALLS_OPT
+	  : STRUB_AT_CALLS)
        : consider_callable
        ? STRUB_CALLABLE
        : STRUB_DISABLED);
@@ -689,6 +711,18 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 	 reported in set_strub_mode_to.  */
       break;
 
+    case STRUB_AT_CALLS_OPT:
+      /* Functions that select this mode do so because of references to strub
+	 variables.  Even if we choose at-calls as an optimization, the
+	 requirements for internal strub must still be satisfied.  Optimization
+	 options may render implicit at-calls strub not viable (-O0 sets
+	 force_output for static non-inline functions), and it would not be good
+	 if changing optimization options turned a well-formed into an
+	 ill-formed one.  */
+      if (!internal_viable)
+	can_strub_internally_p (node, true);
+      break;
+
     case STRUB_WRAPPED:
     case STRUB_WRAPPER:
     default:
@@ -772,20 +806,21 @@ set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
 					    DECL_ATTRIBUTES (node->decl));
 }
 
-static enum strub_mode
+static void
 set_strub_mode (cgraph_node *node)
 {
   tree attr = get_strub_attr_from_decl (node->decl);
 
   if (attr)
-    switch (enum strub_mode mode = get_strub_mode_from_attr (attr))
+    switch (get_strub_mode_from_attr (attr))
       {
 	/* These can't have been requested through user attributes, so we must
 	   have already gone through them.  */
       case STRUB_WRAPPER:
       case STRUB_WRAPPED:
       case STRUB_INLINABLE:
-	return mode;
+      case STRUB_AT_CALLS_OPT:
+	return;
 
       case STRUB_DISABLED:
       case STRUB_AT_CALLS:
@@ -809,11 +844,9 @@ set_strub_mode (cgraph_node *node)
 			  : compute_strub_mode (node, attr));
 
   set_strub_mode_to (node, mode);
-
-  return mode;
 }
 
-/* Non-strub functions shouldn't be called from strub functions,
+/* 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.  */
 
@@ -826,6 +859,7 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
   switch (caller_mode)
     {
     case STRUB_WRAPPED:
+    case STRUB_AT_CALLS_OPT:
     case STRUB_AT_CALLS:
     case STRUB_INTERNAL:
     case STRUB_INLINABLE:
@@ -844,10 +878,11 @@ strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
     {
     case STRUB_WRAPPED:
     case STRUB_AT_CALLS:
-    case STRUB_INTERNAL:
     case STRUB_INLINABLE:
       break;
 
+    case STRUB_AT_CALLS_OPT:
+    case STRUB_INTERNAL:
     case STRUB_WRAPPER:
       return (flag_strub >= 0);
 
@@ -879,6 +914,7 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_AT_CALLS:
     case STRUB_INTERNAL:
     case STRUB_INLINABLE:
+    case STRUB_AT_CALLS_OPT:
       break;
 
     case STRUB_WRAPPER:
@@ -898,6 +934,7 @@ strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
     case STRUB_AT_CALLS:
     case STRUB_INTERNAL:
     case STRUB_INLINABLE:
+    case STRUB_AT_CALLS_OPT:
       return true;
 
     case STRUB_WRAPPER:
@@ -925,29 +962,35 @@ verify_strub ()
     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_WRAPPED
 	 || caller_mode == STRUB_INLINABLE);
 
+    for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
+      {
+	gcc_checking_assert (e->indirect_unknown_callee);
+	if (!strub_context)
+	  continue;
+
+	tree callee_fntype = gimple_call_fntype (e->call_stmt);
+	enum strub_mode callee_mode
+	  = get_strub_mode_from_type (callee_fntype);
+
+	if (callee_mode == STRUB_DISABLED
+	    || callee_mode == STRUB_INTERNAL)
+	  error_at (gimple_location (e->call_stmt),
+		    "indirect non-strub call in strub context %qD",
+		    node->decl);
+      }
+
     for (cgraph_edge *e = node->callees; e; e = e->next_callee)
-      if (e->indirect_unknown_callee)
-	{
-	  if (!strub_context)
-	    continue;
-
-	  tree callee_fntype = gimple_call_fntype (e->call_stmt);
-	  enum strub_mode callee_mode
-	    = get_strub_mode_from_type (callee_fntype);
-
-	  if (callee_mode == STRUB_DISABLED
-	      || callee_mode == STRUB_INTERNAL)
-	    error_at (gimple_location (e->call_stmt),
-		      "indirect non-strub call in strub context %qD",
-		      node->decl);
-	}
-      else if (!strub_callable_from_p (e->callee, node))
-	error_at (gimple_location (e->call_stmt),
-		  "calling non-strub %qD in strub context %qD",
-		  e->callee->decl, node->decl);
+      {
+	gcc_checking_assert (!e->indirect_unknown_callee);
+	if (!strub_callable_from_p (e->callee, node))
+	  error_at (gimple_location (e->call_stmt),
+		    "calling non-strub %qD in strub context %qD",
+		    e->callee->decl, node->decl);
+      }
   }
 
   /* ??? Check strub-wise pointer type compatibility of variables and
@@ -1014,6 +1057,40 @@ public:
   virtual bool gate (function *) { return flag_strub; }
   virtual unsigned int execute (function *);
 
+#define DEF_TYPE(NAME, INIT)			\
+  static inline tree get_ ## NAME () {		\
+    static tree type = NULL_TREE;		\
+    if (!type)					\
+      type = (INIT);				\
+    return type;				\
+  }
+
+  /* Use a distinct ptr_type_node to denote the watermark, so that we can
+     recognize it in arg lists and avoid modifying types twice.  */
+  DEF_TYPE (wmt, build_distinct_type_copy (ptr_type_node))
+
+  DEF_TYPE (pwmt, build_pointer_type (get_wmt ()))
+
+  DEF_TYPE (qpwmt,
+	    build_qualified_type (get_pwmt (),
+				  TYPE_QUAL_RESTRICT
+				  | TYPE_QUAL_CONST))
+
+  DEF_TYPE (pptr, build_pointer_type (ptr_type_node))
+
+  DEF_TYPE (qpptr,
+	    build_qualified_type (get_pptr (),
+				  TYPE_QUAL_RESTRICT
+				  | TYPE_QUAL_CONST))
+
+  DEF_TYPE (qpvalst,
+	    build_qualified_type (build_pointer_type
+				  (va_list_type_node),
+				  TYPE_QUAL_RESTRICT
+				  | TYPE_QUAL_CONST))
+
+#undef DEF_TYPE
+
 #define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST)			\
   static tree get_ ## NAME () {					\
     tree decl = builtin_decl_explicit (CODE);			\
@@ -1062,13 +1139,13 @@ public:
 
   DEF_SS_BUILTIN (enter, ". Ot",
 		  BUILT_IN___STRUB_ENTER,
-		  (void_type_node, get_pptr (), NULL))
+		  (void_type_node, get_qpwmt (), NULL))
   DEF_SS_BUILTIN (update, ". Wt",
 		  BUILT_IN___STRUB_UPDATE,
-		  (void_type_node, get_pptr (), NULL))
+		  (void_type_node, get_qpwmt (), NULL))
   DEF_SS_BUILTIN (leave, ". w ",
 		  BUILT_IN___STRUB_LEAVE,
-		  (void_type_node, get_pptr (), NULL))
+		  (void_type_node, get_qpwmt (), NULL))
 
 #undef DEF_SS_BUILTIN
 
@@ -1086,28 +1163,8 @@ public:
 
 #undef DEF_IDENT
 
-#define DEF_TYPE(NAME, INIT)			\
-  static inline tree get_ ## NAME () {		\
-    static tree type = NULL_TREE;		\
-    if (!type)					\
-      type = (INIT);				\
-    return type;				\
-  }
-
-  DEF_TYPE (pptr, build_pointer_type (ptr_type_node))
-
-  DEF_TYPE (qpptr,
-	    build_qualified_type (get_pptr (),
-				  TYPE_QUAL_RESTRICT
-				  | TYPE_QUAL_CONST))
-
-  DEF_TYPE (qpvalst,
-	    build_qualified_type (build_pointer_type
-				  (va_list_type_node),
-				  TYPE_QUAL_RESTRICT
-				  | TYPE_QUAL_CONST))
-
-#undef DEF_TYPE
+  static inline int adjust_at_calls_type (tree);
+  static inline void adjust_at_calls_call (cgraph_edge *, int);
 
   static inline gimple_seq
   call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
@@ -1409,18 +1466,14 @@ remove_named_attribute_unsharing (const char *name, tree *attrs)
 
 static int last_cgraph_order;
 
-static bool
+static void
 ipa_strub_set_mode_for_new_functions ()
 {
-  if (last_cgraph_order && symtab->order == last_cgraph_order)
-    /* If we're called again after the first call,
-       then the first call must have returned true.  */
-    return true;
+  if (symtab->order == last_cgraph_order)
+    return;
 
   cgraph_node *node;
 
-  bool any_strub = false;
-
   /* Go through the functions twice, once over non-aliases, and then over
      aliases, so that aliases can reuse the mode computation of their ultimate
      targets.  */
@@ -1434,29 +1487,241 @@ ipa_strub_set_mode_for_new_functions ()
       if (node->order < last_cgraph_order)
 	continue;
 
-      enum strub_mode mode = set_strub_mode (node);
+      set_strub_mode (node);
+    }
+
+  last_cgraph_order = symtab->order;
+}
 
-      if (mode == STRUB_AT_CALLS || mode == STRUB_INTERNAL)
-	any_strub = true;
+/* 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)
+{
+  int named_args = 0;
+
+  if (!TYPE_ARG_TYPES (type))
+    return named_args;
+
+  tree *tlist = &TYPE_ARG_TYPES (type);
+  tree qpwmptrt = get_qpwmt ();
+  while (*tlist && TREE_VALUE (*tlist) != void_type_node)
+    {
+      /* The type has alreayd been adjusted.  */
+      if (TREE_VALUE (*tlist) == qpwmptrt)
+	return named_args;
+      named_args++;
+      *tlist = tree_cons (TREE_PURPOSE (*tlist),
+			  TREE_VALUE (*tlist),
+			  TREE_CHAIN (*tlist));
+      tlist = &TREE_CHAIN (*tlist);
     }
 
-  if (any_strub)
-    last_cgraph_order = symtab->order;
+  /* Add the new argument after all named arguments, so as to not mess with
+     attributes that reference parameters.  */
+  *tlist = tree_cons (NULL_TREE, get_qpwmt (), *tlist);
 
-  return any_strub;
+#if ATTR_FNSPEC_DECONST_WATERMARK
+  if (!type_already_adjusted)
+    {
+      int flags = flags_from_decl_or_type (type);
+      tree fnspec = lookup_attribute ("fn spec", type);
+
+      if ((flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS)) || fnspec)
+	{
+	  size_t xargs = 1;
+	  size_t curlen = 0, tgtlen = 2 + 2 * (named_args + xargs);
+	  auto_vec<char> nspecv (tgtlen);
+	  char *nspec = &nspecv[0]; /* It will *not* be NUL-terminated!  */
+	  if (fnspec)
+	    {
+	      tree fnspecstr = TREE_VALUE (TREE_VALUE (fnspec));
+	      curlen = TREE_STRING_LENGTH (fnspecstr);
+	      memcpy (nspec, TREE_STRING_POINTER (fnspecstr), curlen);
+	    }
+	  if (!curlen)
+	    {
+	      nspec[curlen++] = '.';
+	      nspec[curlen++] = ((flags & ECF_CONST)
+				 ? 'c'
+				 : (flags & ECF_PURE)
+				 ? 'p'
+				 : ' ');
+	    }
+	  while (curlen < tgtlen - 2 * xargs)
+	    {
+	      nspec[curlen++] = '.';
+	      nspec[curlen++] = ' ';
+	    }
+	  nspec[curlen++] = 'W';
+	  nspec[curlen++] = 't';
+
+	  /* The type has already been copied, if needed, before adding
+	     parameters.  */
+	  TYPE_ATTRIBUTES (type)
+	    = tree_cons (get_identifier ("fn spec"),
+			 build_tree_list (NULL_TREE,
+					  build_string (tgtlen, nspec)),
+			 TYPE_ATTRIBUTES (type));
+	}
+    }
+#endif
+
+  return named_args;
+}
+
+void
+pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
+{
+  gcall *ocall = e->call_stmt;
+  gimple_stmt_iterator gsi = gsi_for_stmt (ocall);
+
+  /* Make sure we haven't modified this call yet.  */
+  gcc_checking_assert (!(int (gimple_call_num_args (ocall)) > named_args
+			 && (TREE_TYPE (gimple_call_arg (ocall, named_args))
+			     == get_pwmt ())));
+
+  /* ??? If it's a (tail?) call within a strub context, maybe pass on
+     the strub watermark instead of wrapping the call.  */
+
+  /* Initialize the watermark before the call.  */
+  tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
+  TREE_ADDRESSABLE (swm) = true;
+  tree swmp = build1 (ADDR_EXPR, get_pwmt (), swm);
+
+  tree enter = get_enter ();
+  gcall *stptr = gimple_build_call (enter, 1,
+				    unshare_expr (swmp));
+  gsi_insert_before (&gsi, stptr, GSI_SAME_STMT);
+#if !IMPLICIT_CGRAPH_EDGES
+  e->caller->create_edge (cgraph_node::get_create (enter),
+			  stptr, gsi_bb (gsi)->count, false);
+#endif
+
+  /* Replace the call with one that passes the swmp argument first.  */
+  gcall *wrcall;
+  { gcall *stmt = ocall;
+    // Mostly copied from gimple_call_copy_skip_args.
+    int i = 0;
+    int nargs = gimple_call_num_args (stmt);
+    auto_vec<tree> vargs (MAX (nargs, named_args) + 1);
+    gcall *new_stmt;
+
+    /* pr71109.c calls a prototypeless function, then defines it with
+       additional arguments.  It's ill-formed, but after it's inlined,
+       it somehow works out.  */
+    for (; i < named_args && i < nargs; i++)
+      vargs.quick_push (gimple_call_arg (stmt, i));
+    for (; i < named_args; i++)
+      vargs.quick_push (null_pointer_node);
+
+    vargs.quick_push (unshare_expr (swmp));
+
+    for (; i < nargs; i++)
+#if 0
+      if (!bitmap_bit_p (args_to_skip, i))
+#endif
+	vargs.quick_push (gimple_call_arg (stmt, i));
+
+    if (gimple_call_internal_p (stmt))
+#if 0
+      /*
+	new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
+	vargs);
+      */
+#endif
+      gcc_unreachable ();
+    else
+      new_stmt = gimple_build_call_vec (gimple_call_fn (stmt), vargs);
+
+    if (gimple_call_lhs (stmt))
+      gimple_call_set_lhs (new_stmt, gimple_call_lhs (stmt));
+
+#if 0
+    gimple_set_vuse (new_stmt, gimple_vuse (stmt));
+    gimple_set_vdef (new_stmt, gimple_vdef (stmt));
+#else
+    gimple_move_vops (new_stmt, stmt);
+#endif
+
+    if (gimple_has_location (stmt))
+      gimple_set_location (new_stmt, gimple_location (stmt));
+    gimple_call_copy_flags (new_stmt, stmt);
+    gimple_call_set_chain (new_stmt, gimple_call_chain (stmt));
+
+    gimple_set_modified (new_stmt, true);
+
+    wrcall = new_stmt;
+  }
+
+  update_stmt (wrcall);
+  gsi_replace (&gsi, wrcall, true);
+  cgraph_edge::set_call_stmt (e, wrcall, false);
+
+  /* Insert the strub code after the call.  */
+  gimple_seq seq = NULL;
+
+  {
+#if !ATTR_FNSPEC_DECONST_WATERMARK
+    /* If the call will be assumed to not modify or even read the
+       watermark, make it read and modified ourselves.  */
+    if ((gimple_call_flags (wrcall)
+	 & (ECF_CONST | ECF_PURE | ECF_NOVOPS)))
+      {
+	vec<tree, va_gc> *inputs = NULL;
+	vec<tree, va_gc> *outputs = NULL;
+	vec_safe_push (outputs,
+		       build_tree_list
+		       (build_tree_list
+			(NULL_TREE, build_string (2, "=m")),
+			swm));
+	vec_safe_push (inputs,
+		       build_tree_list
+		       (build_tree_list
+			(NULL_TREE, build_string (1, "m")),
+			swm));
+	gasm *forcemod = gimple_build_asm_vec ("", inputs, outputs,
+					       NULL, NULL);
+	gimple_seq_add_stmt (&seq, forcemod);
+
+	/* If the call will be assumed to not even read the watermark,
+	   make sure it is already in memory before the call.  */
+	if ((gimple_call_flags (wrcall) & ECF_CONST))
+	  {
+	    vec<tree, va_gc> *inputs = NULL;
+	    vec_safe_push (inputs,
+			   build_tree_list
+			   (build_tree_list
+			    (NULL_TREE, build_string (1, "m")),
+			    swm));
+	    gasm *force_store = gimple_build_asm_vec ("", inputs, NULL,
+						      NULL, NULL);
+	    gsi_insert_before (&gsi, force_store, GSI_SAME_STMT);
+	  }
+      }
+#endif
+
+    gcall *sleave = gimple_build_call (get_leave (), 1,
+				       unshare_expr (swmp));
+    gimple_seq_add_stmt (&seq, sleave);
+
+    gassign *clobber = gimple_build_assign (swm,
+					    build_clobber
+					    (TREE_TYPE (swm)));
+    gimple_seq_add_stmt (&seq, clobber);
+  }
+
+  gsi_insert_finally_seq_after_call (gsi, seq);
 }
 
 unsigned int
 pass_ipa_strub_mode::execute (function *)
 {
   last_cgraph_order = 0;
-  bool any_strub = ipa_strub_set_mode_for_new_functions ();
+  ipa_strub_set_mode_for_new_functions ();
 
-  if (!any_strub)
-    flag_strub = 0;
-  else
-    /* Verify before any inlining or other transformations.  */
-    verify_strub ();
+  /* Verify before any inlining or other transformations.  */
+  verify_strub ();
 
   return 0;
 }
@@ -1474,52 +1739,50 @@ pass_ipa_strub::execute (function *)
 
   ipa_strub_set_mode_for_new_functions ();
 
+  /* First, adjust the signature of at-calls functions.  We adjust types of
+     at-calls functions first, so that we don't modify types in place unless
+     strub is explicitly requested.  */
   FOR_EACH_FUNCTION (onode)
   {
     enum strub_mode mode = get_strub_mode (onode);
 
-    if (mode == STRUB_AT_CALLS)
+    if (mode == STRUB_AT_CALLS
+	|| mode == STRUB_AT_CALLS_OPT)
       {
-	int named_args = 0;
-
-	/* Adjust the signature, and all callers.  Add the new
-	   argument after all named arguments, so as to not mess with
-	   attr_fnspec or any other attributes that reference
-	   parameters.  */
-	TREE_TYPE (onode->decl) = build_distinct_type_copy (TREE_TYPE
-							    (onode->decl));
+	/* Create a type variant if strubbing was not explicitly requested in
+	   the function type.  */
+	if (get_strub_mode_from_type (TREE_TYPE (onode->decl)) != mode)
+	  TREE_TYPE (onode->decl) = build_distinct_type_copy (TREE_TYPE
+							      (onode->decl));
+
+	int named_args = adjust_at_calls_type (TREE_TYPE (onode->decl));
+
+	/* An external function explicitly declared with strub won't have a
+	   body.  Even with implicit at-calls strub, a function may have had its
+	   body removed after we selected the mode, and then we have nothing
+	   further to do.  */
+	if (!onode->has_gimple_body_p ())
+	  continue;
 
 	tree *pargs = &DECL_ARGUMENTS (onode->decl);
 
 	/* A noninterposable_alias reuses the same parm decl chain, don't add
-	   the parm twice.  We still have to adjust the type.  */
+	   the parm twice.  */
 	bool aliased_parms = (onode->alias && *pargs
 			      && DECL_CONTEXT (*pargs) != onode->decl);
 
-	if (TYPE_ARG_TYPES (TREE_TYPE (onode->decl)))
-	  {
-	    tree *tlist = &TYPE_ARG_TYPES (TREE_TYPE (onode->decl));
-	    while (*pargs)
-	      {
-		named_args++;
-		*tlist = tree_cons (TREE_PURPOSE (*tlist),
-				    TREE_VALUE (*tlist),
-				    TREE_CHAIN (*tlist));
-		tlist = &TREE_CHAIN (*tlist);
-		pargs = &DECL_CHAIN (*pargs);
-	      }
-	    *tlist = tree_cons (NULL_TREE, get_qpptr (), *tlist);
-	  }
-
 	if (aliased_parms)
 	  continue;
 
+	for (int i = 0; i < named_args; i++)
+	  pargs = &DECL_CHAIN (*pargs);
+
 	tree wmptr = build_decl (DECL_SOURCE_LOCATION (onode->decl),
 				 PARM_DECL,
 				 get_watermark_ptr (),
-				 get_qpptr ());
+				 get_qpwmt ());
 	DECL_ARTIFICIAL (wmptr) = 1;
-	DECL_ARG_TYPE (wmptr) = get_qpptr ();
+	DECL_ARG_TYPE (wmptr) = get_qpwmt ();
 	DECL_CONTEXT (wmptr) = onode->decl;
 	TREE_USED (wmptr) = 1;
 	DECL_CHAIN (wmptr) = *pargs;
@@ -1528,197 +1791,17 @@ 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));
-
-	    gcall *ocall = e->call_stmt;
-	    gimple_stmt_iterator gsi = gsi_for_stmt (ocall);
-
-	    /* ??? If it's a (tail?) call within a strub context, maybe pass on
-	       the strub watermark instead of wrapping the call.  */
-
-	    /* Initialize the watermark before the call.  */
-	    tree swm = create_tmp_var (ptr_type_node, ".strub.watermark");
-	    TREE_ADDRESSABLE (swm) = true;
-	    tree swmp = build1 (ADDR_EXPR, get_pptr (), swm);
-
-	    tree enter = get_enter ();
-	    gcall *stptr = gimple_build_call (enter, 1,
-					      unshare_expr (swmp));
-	    gsi_insert_before (&gsi, stptr, GSI_SAME_STMT);
-#if !IMPLICIT_CGRAPH_EDGES
-	    e->caller->create_edge (cgraph_node::get_create (enter),
-				    stptr, gsi_bb (gsi)->count, false);
-#endif
-
-	    /* Replace the call with one that passes the swmp argument first.  */
-	    gcall *wrcall;
-	    { gcall *stmt = ocall;
-	      // Mostly copied from gimple_call_copy_skip_args.
-	      int i = 0;
-	      int nargs = gimple_call_num_args (stmt);
-	      auto_vec<tree> vargs (MAX (nargs, named_args) + 1);
-	      gcall *new_stmt;
-
-	      /* pr71109.c calls a prototypeless function, then defines it with
-		 additional arguments.  It's ill-formed, but after it's inlined,
-		 it somehow works out.  */
-	      for (; i < named_args && i < nargs; i++)
-		vargs.quick_push (gimple_call_arg (stmt, i));
-	      for (; i < named_args; i++)
-		vargs.quick_push (null_pointer_node);
-
-	      vargs.quick_push (unshare_expr (swmp));
-
-	      for (; i < nargs; i++)
-#if 0
-		if (!bitmap_bit_p (args_to_skip, i))
-#endif
-		  vargs.quick_push (gimple_call_arg (stmt, i));
-
-	      if (gimple_call_internal_p (stmt))
-#if 0
-		/*
-		  new_stmt = gimple_build_call_internal_vec (gimple_call_internal_fn (stmt),
-		  vargs);
-		*/
-#endif
-		gcc_unreachable ();
-	      else
-		new_stmt = gimple_build_call_vec (gimple_call_fn (stmt), vargs);
-
-	      if (gimple_call_lhs (stmt))
-		gimple_call_set_lhs (new_stmt, gimple_call_lhs (stmt));
-
-#if 0
-	      gimple_set_vuse (new_stmt, gimple_vuse (stmt));
-	      gimple_set_vdef (new_stmt, gimple_vdef (stmt));
-#else
-	      gimple_move_vops (new_stmt, stmt);
-#endif
-
-	      if (gimple_has_location (stmt))
-		gimple_set_location (new_stmt, gimple_location (stmt));
-	      gimple_call_copy_flags (new_stmt, stmt);
-	      gimple_call_set_chain (new_stmt, gimple_call_chain (stmt));
-
-	      gimple_set_modified (new_stmt, true);
-
-	      wrcall = new_stmt;
-	    }
-
-	    update_stmt (wrcall);
-	    gsi_replace (&gsi, wrcall, true);
-	    cgraph_edge::set_call_stmt (e, wrcall, false);
-
-	    /* Insert the strub code after the call.  */
-	    gimple_seq seq = NULL;
-
-	    {
-#if !ATTR_FNSPEC_DECONST_WATERMARK
-	      /* If the call will be assumed to not modify or even read the
-		 watermark, make it read and modified ourselves.  */
-	      if ((gimple_call_flags (wrcall)
-		   & (ECF_CONST | ECF_PURE | ECF_NOVOPS)))
-		{
-		  vec<tree, va_gc> *inputs = NULL;
-		  vec<tree, va_gc> *outputs = NULL;
-		  vec_safe_push (outputs,
-				 build_tree_list
-				 (build_tree_list
-				  (NULL_TREE, build_string (2, "=m")),
-				  swm));
-		  vec_safe_push (inputs,
-				 build_tree_list
-				 (build_tree_list
-				  (NULL_TREE, build_string (1, "m")),
-				  swm));
-		  gasm *forcemod = gimple_build_asm_vec ("", inputs, outputs,
-							 NULL, NULL);
-		  gimple_seq_add_stmt (&seq, forcemod);
-
-		  /* If the call will be assumed to not even read the watermark,
-		     make sure it is already in memory before the call.  */
-		  if ((gimple_call_flags (wrcall) & ECF_CONST))
-		    {
-		      vec<tree, va_gc> *inputs = NULL;
-		      vec_safe_push (inputs,
-				     build_tree_list
-				     (build_tree_list
-				      (NULL_TREE, build_string (1, "m")),
-				      swm));
-		      gasm *force_store = gimple_build_asm_vec ("", inputs, NULL,
-								NULL, NULL);
-		      gsi_insert_before (&gsi, force_store, GSI_SAME_STMT);
-		    }
-		}
-#endif
-
-	      gcall *sleave = gimple_build_call (get_leave (), 1,
-						 unshare_expr (swmp));
-	      gimple_seq_add_stmt (&seq, sleave);
-
-	      gassign *clobber = gimple_build_assign (swm,
-						      build_clobber
-						      (TREE_TYPE (swm)));
-	      gimple_seq_add_stmt (&seq, clobber);
-	    }
-
-	    gsi_insert_finally_seq_after_call (gsi, seq);
-
+	    adjust_at_calls_call (e, named_args);
 	    pop_cfun ();
 	  }
-
-#if ATTR_FNSPEC_DECONST_WATERMARK
-	{
-	  int flags = flags_from_decl_or_type (onode->decl);
-	  tree fnspec = lookup_attribute ("fn spec", TREE_TYPE (onode->decl));
-
-	  if ((flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS)) || fnspec)
-	    {
-	      size_t xargs = 1;
-	      size_t curlen = 0, tgtlen = 2 + 2 * (named_args + xargs);
-	      auto_vec<char> nspecv (tgtlen);
-	      char *nspec = &nspecv[0]; /* It will *not* be NUL-terminated!  */
-	      if (fnspec)
-		{
-		  tree fnspecstr = TREE_VALUE (TREE_VALUE (fnspec));
-		  curlen = TREE_STRING_LENGTH (fnspecstr);
-		  memcpy (nspec, TREE_STRING_POINTER (fnspecstr), curlen);
-		}
-	      if (!curlen)
-		{
-		  nspec[curlen++] = '.';
-		  nspec[curlen++] = ((flags & ECF_CONST)
-				     ? 'c'
-				     : (flags & ECF_PURE)
-				     ? 'p'
-				     : ' ');
-		}
-	      while (curlen < tgtlen - 2 * xargs)
-		{
-		  nspec[curlen++] = '.';
-		  nspec[curlen++] = ' ';
-		}
-	      nspec[curlen++] = 'W';
-	      nspec[curlen++] = 't';
-
-	      /* The type has already been copied before adding parameters.  */
-	      TYPE_ATTRIBUTES (TREE_TYPE (onode->decl))
-		= tree_cons (get_identifier ("fn spec"),
-			     build_tree_list (NULL_TREE,
-					      build_string (tgtlen, nspec)),
-			     TYPE_ATTRIBUTES (TREE_TYPE (onode->decl)));
-	    }
-	}
 #endif
 
-	if (!onode->has_gimple_body_p ())
-	  continue;
-
 	cgraph_node *nnode = onode;
 	push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
 
@@ -1758,11 +1841,61 @@ pass_ipa_strub::execute (function *)
 #if 0
 	compute_fn_summary (onode, true);
 #endif
-	continue;
+      }
+  }
+
+  FOR_EACH_FUNCTION (onode)
+  {
+    if (!onode->has_gimple_body_p ())
+      continue;
+
+    /* Adjust unknown-callee indirect calls with STRUB_AT_CALLS types within
+       onode.  */
+    if (onode->indirect_calls)
+      {
+	push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
+	for (cgraph_edge *e = onode->indirect_calls; e; e = e->next_callee)
+	  {
+	    gcc_checking_assert (e->indirect_unknown_callee);
+
+	    tree callee_fntype = gimple_call_fntype (e->call_stmt);
+	    enum strub_mode callee_mode
+	      = get_strub_mode_from_type (callee_fntype);
+
+	    if (callee_mode != STRUB_AT_CALLS
+		&& callee_mode != STRUB_AT_CALLS_OPT)
+	      continue;
+
+	    int named_args = adjust_at_calls_type (callee_fntype);
+
+	    adjust_at_calls_call (e, named_args);
+	  }
+	pop_cfun ();
+      }
+
+    if (onode->callees)
+      {
+	push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
+	for (cgraph_edge *e = onode->callees; e; e = e->next_callee)
+	  {
+	    gcc_checking_assert (!e->indirect_unknown_callee);
+
+	    enum strub_mode callee_mode = get_strub_mode (e->callee);
+
+	    if (callee_mode != STRUB_AT_CALLS
+		&& callee_mode != STRUB_AT_CALLS_OPT)
+	      continue;
+
+	    int named_args = adjust_at_calls_type (TREE_TYPE (e->callee->decl));
+
+	    adjust_at_calls_call (e, named_args);
+	  }
+	pop_cfun ();
       }
 
-    if (mode != STRUB_INTERNAL
-	|| !onode->has_gimple_body_p ())
+    enum strub_mode mode = get_strub_mode (onode);
+
+    if (mode != STRUB_INTERNAL)
       continue;
 
 #if 0
@@ -1772,6 +1905,7 @@ pass_ipa_strub::execute (function *)
 				      &DECL_ATTRIBUTES (onode->decl));
 #endif
 
+#if 0
     if (!DECL_STRUCT_FUNCTION (onode->decl))
       {
 	inform (DECL_SOURCE_LOCATION (onode->decl),
@@ -1799,6 +1933,7 @@ pass_ipa_strub::execute (function *)
 		onode->decl);
 	continue;
       }
+#endif
 
     bool is_stdarg = calls_builtin_va_start_p (onode);;
     bool apply_args = calls_builtin_apply_args_p (onode);
@@ -1835,7 +1970,7 @@ pass_ipa_strub::execute (function *)
 
       ipa_adjusted_param wmadj = {};
       wmadj.op = IPA_PARAM_OP_NEW;
-      wmadj.type = get_qpptr ();
+      wmadj.type = get_qpwmt ();
       vec_safe_push (nparms, wmadj);
     }
     ipa_param_adjustments adj (nparms, -1, false);
@@ -2497,9 +2632,9 @@ pass_ipa_strub::execute (function *)
 
       gcall *wrcall = as_a <gcall *> (gsi_stmt (gsi));
 
-      tree swm = create_tmp_var (ptr_type_node, ".strub.watermark");
+      tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
       TREE_ADDRESSABLE (swm) = true;
-      tree swmp = build1 (ADDR_EXPR, get_pptr (), swm);
+      tree swmp = build1 (ADDR_EXPR, get_pwmt (), swm);
 
       tree enter = get_enter ();
       gcall *stptr = gimple_build_call (enter, 1, unshare_expr (swmp));
diff --git a/gcc/testsuite/c-c++-common/strub-O0.c b/gcc/testsuite/c-c++-common/strub-O0.c
new file mode 100644
index 00000000000..6afe0fd5de1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O0.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=default -fdump-rtl-expand" } */
+
+/* At -O0, none of the strub builtins are expanded inline.  */
+
+int __attribute__ ((__strub__)) var;
+
+int f() {
+  return var;
+}
+
+/* { dg-final { scan-rtl-dump "strub_enter" "expand" } } */
+/* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
+/* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O1.c b/gcc/testsuite/c-c++-common/strub-O1.c
new file mode 100644
index 00000000000..1cdeaecaf32
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=default -fdump-rtl-expand" } */
+
+/* At -O1, without -fno-inline, we fully expand enter, but neither update nor
+   leave.  */
+
+int __attribute__ ((__strub__)) var;
+
+int f() {
+  return var;
+}
+
+/* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
+/* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
+/* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
new file mode 100644
index 00000000000..9cc39e763b1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand" } */
+
+/* At -O2, without -fno-inline, we fully expand enter and update, and add a test
+   around the leave call.  */
+
+int __attribute__ ((__strub__)) var;
+
+int f() {
+  return var;
+}
+
+/* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "strub_update" "expand" } } */
+/* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
+/* { dg-final { scan-rtl-dump "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
new file mode 100644
index 00000000000..51cae845d5f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline-functions" } */
+
+/* With -fno-inline-functions, none of the strub builtins are inlined.  */
+
+int __attribute__ ((__strub__)) var;
+
+int f() {
+  return var;
+}
+
+/* { dg-final { scan-rtl-dump "strub_enter" "expand" } } */
+/* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
+/* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O3.c b/gcc/testsuite/c-c++-common/strub-O3.c
new file mode 100644
index 00000000000..1fcde345d36
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O3.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand" } */
+
+int __attribute__ ((__strub__)) var;
+
+int f() {
+  return var;
+}
+
+/* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "strub_update" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "strub_leave" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
new file mode 100644
index 00000000000..8f67b613be8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=default -fdump-rtl-expand -fno-inline" } */
+
+/* With -fno-inline-functions, none of the strub builtins are inlined.  */
+
+int __attribute__ ((__strub__)) var;
+
+int f() {
+  return var;
+}
+
+/* { dg-final { scan-rtl-dump "strub_enter" "expand" } } */
+/* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
+/* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
new file mode 100644
index 00000000000..6f60349573f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-Og -fstrub=default -fdump-rtl-expand" } */
+
+/* At -Og, without -fno-inline, we fully expand enter, but neither update nor
+   leave.  */
+
+int __attribute__ ((__strub__)) var;
+
+int f() {
+  return var;
+}
+
+/* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
+/* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
+/* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
new file mode 100644
index 00000000000..5d1c08a7528
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-Os -fstrub=default -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
+   restoring required by the call will most often make it larger.  The leave
+   call is left untouched.  */
+
+int __attribute__ ((__strub__)) var;
+
+int f() {
+  return var;
+}
+
+/* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "strub_update" "expand" } } */
+/* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
new file mode 100644
index 00000000000..54daf84656c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -0,0 +1,32 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=all -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
+   strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
+static void
+__attribute__ ((__always_inline__))
+h() {  /* { dg-warning "might not be inlinable" } */
+}
+
+/* g becomes STRUB_AT_CALLS, because of the flag.  */
+static inline void
+g() {
+  h();
+}
+
+/* f becomes STRUB_INTERNAL because of the flag, and gets split into
+   STRUB_WRAPPER and STRUB_WRAPPED.  */
+void
+f() {
+  g();
+}
+
+/* { 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 \[(\]" 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" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
new file mode 100644
index 00000000000..f377541cff0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-all2.c
@@ -0,0 +1,24 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=all -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* g becomes STRUB_INTERNAL, because of the flag.  Without inline, force_output
+   is set for static non-inline functions when not optimizing, and that keeps
+   only_called_directly_p from returning true, which makes STRUB_AT_CALLS
+   non-viable.  */
+static void
+g() {
+}
+
+/* f becomes STRUB_INTERNAL because of the flag, and gets split into
+   STRUB_WRAPPER and STRUB_WRAPPED.  */
+void
+f() {
+  g();
+}
+
+/* { 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 \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
new file mode 100644
index 00000000000..0d1b9fce833
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -0,0 +1,30 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=at-calls -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
+   strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
+static void
+__attribute__ ((__always_inline__))
+h() {  /* { dg-warning "might not be inlinable" } */
+}
+
+/* g becomes STRUB_AT_CALLS, because of the flag.  */
+static inline void
+g() {
+  h();
+}
+
+/* f does NOT become STRUB_AT_CALLS because it is visible; it becomes
+   STRUB_CALLABLE.  */
+void
+f() {
+  g();
+}
+
+/* { 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 \[(\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]1\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
new file mode 100644
index 00000000000..530eee36d06
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-at-calls2.c
@@ -0,0 +1,23 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=at-calls -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* g does NOT become STRUB_AT_CALLS because it's not viable.  Without inline,
+   force_output is set for static non-inline functions when not optimizing, and
+   that keeps only_called_directly_p from returning true, which makes
+   STRUB_AT_CALLS non-viable.  It becomes STRUB_CALLABLE instead.  */
+static void
+g() {
+}
+
+/* f does NOT become STRUB_AT_CALLS because it is visible; it becomes
+   STRUB_CALLABLE.  */
+void
+f() {
+  g();
+}
+
+/* { 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 \[(\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]3\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-default1.c b/gcc/testsuite/c-c++-common/strub-default1.c
new file mode 100644
index 00000000000..a1e1803aadc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-default1.c
@@ -0,0 +1,40 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+static int __attribute__ ((__strub__)) var;
+
+/* h becomes STRUB_STRUB_INLINABLE, because of the use of the strub variable,
+   and the always_inline flag.  It would get inlined before pass_ipa_strub, if
+   it weren't for the error.  */
+static void
+__attribute__ ((__always_inline__))
+h() {  /* { dg-warning "might not be inlinable" } */
+  var++;
+}
+
+/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
+   the viability of at-calls strubbing.  */
+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 "strubm" } } */
+/* { 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-default2.c b/gcc/testsuite/c-c++-common/strub-default2.c
new file mode 100644
index 00000000000..e8be1aeef10
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-default2.c
@@ -0,0 +1,29 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+static int __attribute__ ((__strub__)) var;
+
+/* g becomes STRUB_INTERNAL because of the use of the strub variable, and gets
+   split into STRUB_WRAPPER and STRUB_WRAPPED.  It's not STRUB_AT_CALLS_OPT
+   because force_output is set for static non-inline functions when not
+   optimizing, and that keeps only_called_directly_p from returning true, which
+   makes STRUB_AT_CALLS[_OPT] non-viable.  */
+static void
+g() {
+  var--;
+}
+
+/* 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 \[(\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 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" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
new file mode 100644
index 00000000000..b9bd787df0a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -0,0 +1,31 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=internal -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
+   strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
+static void
+__attribute__ ((__always_inline__))
+h() {  /* { dg-warning "might not be inlinable" } */
+}
+
+/* g becomes STRUB_INTERNAL because of the flag, and gets split into
+   STRUB_WRAPPER and STRUB_WRAPPED.  */
+static inline void
+g() {
+  h();
+}
+
+/* f becomes STRUB_INTERNAL because of the flag, and gets split into
+   STRUB_WRAPPER and STRUB_WRAPPED.  */
+void
+f() {
+  g();
+}
+
+/* { 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 \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
new file mode 100644
index 00000000000..a6e69357b23
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-internal2.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=internal -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* g becomes STRUB_INTERNAL, because of the flag.  */
+static void
+g() {
+}
+
+/* f becomes STRUB_INTERNAL because of the flag, and gets split into
+   STRUB_WRAPPER and STRUB_WRAPPED.  */
+void
+f() {
+  g();
+}
+
+/* { 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 \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data1.c b/gcc/testsuite/c-c++-common/torture/strub-data1.c
new file mode 100644
index 00000000000..62a03891ab6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* The pointed-to data enables strubbing if accessed.  */
+int __attribute__ ((__strub__)) var;
+
+int f() {
+  return var;
+}
+
+/* { dg-final { scan-ipa-dump "strub_enter" "strub" } } */
+/* { dg-final { scan-ipa-dump "strub_leave" "strub" } } */
+/* { dg-final { scan-ipa-dump "strub_update" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data2.c b/gcc/testsuite/c-c++-common/torture/strub-data2.c
new file mode 100644
index 00000000000..9b7df13a280
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* The pointer itself is a strub variable, enabling internal strubbing when
+   its value is used.  */
+int __attribute__ ((__strub__)) *ptr;
+
+int *f() {
+  return ptr;
+}
+
+/* { dg-final { scan-ipa-dump "strub_enter" "strub" } } */
+/* { dg-final { scan-ipa-dump "strub_leave" "strub" } } */
+/* { dg-final { scan-ipa-dump "strub_update" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data3.c b/gcc/testsuite/c-c++-common/torture/strub-data3.c
new file mode 100644
index 00000000000..515706caa32
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -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.  */
+int __attribute__ ((__strub__)) var;
+
+void f() {
+  var = 0;
+}
+
+/* { dg-final { scan-ipa-dump-not "strub_enter" "strub" } } */
+/* { dg-final { scan-ipa-dump-not "strub_leave" "strub" } } */
+/* { dg-final { scan-ipa-dump-not "strub_update" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data4.c b/gcc/testsuite/c-c++-common/torture/strub-data4.c
new file mode 100644
index 00000000000..0ec9e35429f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -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.  */
+int __attribute__ ((__strub__)) *ptr;
+
+void f() {
+  ptr = 0;
+}
+
+/* { dg-final { scan-ipa-dump-not "strub_enter" "strub" } } */
+/* { dg-final { scan-ipa-dump-not "strub_leave" "strub" } } */
+/* { dg-final { scan-ipa-dump-not "strub_update" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
new file mode 100644
index 00000000000..251790d4bbb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -Werror" } */
+
+/* The pointer itself is a strub variable, that would enable internal strubbing
+   if its value was used.  Here, it's only overwritten, so no strub.  */
+typedef int __attribute__ ((__strub__)) strub_int;
+strub_int *ptr;
+
+int *f () {
+  return ptr; /* { dg-warn "incompatible" } */
+}
+
+strub_int *g () {
+  return f (); /* { dg-warn "incompatible" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
new file mode 100644
index 00000000000..b8adf8009e8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+typedef void __attribute__ ((__strub__)) fntype ();
+fntype (*ptr);
+
+void f() {
+  ptr ();
+}
+
+/* { dg-final { scan-ipa-dump "strub_enter" "strub" } } */
+/* { dg-final { scan-ipa-dump "(&\.strub\.watermark\.\[0-9\]\+)" "strub" } } */
+/* { dg-final { scan-ipa-dump "strub_leave" "strub" } } */
+/* { dg-final { scan-ipa-dump-not "strub_update" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
new file mode 100644
index 00000000000..5b2c35ad6a7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+typedef void __attribute__ ((__strub__)) fntype (int, int);
+fntype (*ptr);
+
+void f() {
+  ptr (0, 0);
+}
+
+/* { dg-final { scan-ipa-dump "strub_enter" "strub" } } */
+/* { dg-final { scan-ipa-dump "(0, 0, &\.strub\.watermark\.\[0-9\]\+)" "strub" } } */
+/* { dg-final { scan-ipa-dump "strub_leave" "strub" } } */
+/* { dg-final { scan-ipa-dump-not "strub_update" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
new file mode 100644
index 00000000000..5ee50456dc9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+typedef void __attribute__ ((__strub__)) fntype (int, int, ...);
+fntype (*ptr);
+
+void f() {
+  ptr (0, 0, 1, 1);
+}
+
+/* { dg-final { scan-ipa-dump "strub_enter" "strub" } } */
+/* { dg-final { scan-ipa-dump "(0, 0, &\.strub\.watermark\.\[0-9\]\+, 1, 1)" "strub" } } */
+/* { dg-final { scan-ipa-dump "strub_leave" "strub" } } */
+/* { dg-final { scan-ipa-dump-not "strub_update" "strub" } } */
diff --git a/gcc/testsuite/g++.dg/torture/strub-init1.C b/gcc/testsuite/g++.dg/torture/strub-init1.C
new file mode 100644
index 00000000000..e51ae802be4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+extern int __attribute__((__strub__)) initializer ();
+
+int f() {
+  static int x = initializer ();
+  return x;
+}
+
+/* { dg-final { scan-ipa-dump "strub_enter" "strub" } } */
+/* { dg-final { scan-ipa-dump "strub_leave" "strub" } } */
+/* { dg-final { scan-ipa-dump-not "strub_update" "strub" } } */
diff --git a/gcc/testsuite/g++.dg/torture/strub-init2.C b/gcc/testsuite/g++.dg/torture/strub-init2.C
new file mode 100644
index 00000000000..edcb7bf8ad2
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+extern int __attribute__((__strub__)) initializer ();
+
+static int x = initializer ();
+
+int f() {
+  return x;
+}
+
+/* { dg-final { scan-ipa-dump "strub_enter" "strub" } } */
+/* { dg-final { scan-ipa-dump "strub_leave" "strub" } } */
+/* { dg-final { scan-ipa-dump-not "strub_update" "strub" } } */
diff --git a/gcc/testsuite/g++.dg/torture/strub-init3.C b/gcc/testsuite/g++.dg/torture/strub-init3.C
new file mode 100644
index 00000000000..bacf823ca4e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+extern int __attribute__((__strub__)) initializer ();
+
+int f() {
+  int x = initializer ();
+  return x;
+}
+
+/* { dg-final { scan-ipa-dump "strub_enter" "strub" } } */
+/* { dg-final { scan-ipa-dump "strub_leave" "strub" } } */
+/* { dg-final { scan-ipa-dump-not "strub_update" "strub" } } */


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2021-08-04  8:17 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-08-04  8:17 [gcc(refs/users/aoliva/heads/strub)] fix handling of indirect calls 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).