public inbox for gcc-cvs@sourceware.org
help / color / mirror / Atom feed
* [gcc(refs/users/aoliva/heads/strub)] strub with function and variable attributes
@ 2021-08-28 6:30 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2021-08-28 6:30 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:d1979eb2d84765703c07859b1fa3ea3fc920c39a
commit d1979eb2d84765703c07859b1fa3ea3fc920c39a
Author: Alexandre Oliva <oliva@adacore.com>
Date: Fri Aug 20 02:20:52 2021 -0300
strub with function and variable attributes
still missing tests exercising dispatch/virtual calls
Diff:
---
gcc/Makefile.in | 1 +
gcc/ada/doc/gnat_rm.rst | 1 +
gcc/ada/doc/gnat_rm/about_this_guide.rst | 3 +
.../doc/gnat_rm/security_hardening_features.rst | 52 +
gcc/ada/gcc-interface/trans.c | 5 +
gcc/ada/gcc-interface/utils.c | 124 +
gcc/ada/libgnat/a-except.adb | 90 +
gcc/ada/libgnat/a-except.ads | 8 +
gcc/ada/libgnat/s-arit128.ads | 6 +
gcc/ada/libgnat/s-arit64.ads | 6 +
gcc/ada/libgnat/s-secsta.ads | 10 +
gcc/builtins.c | 273 ++
gcc/builtins.def | 4 +
gcc/c-family/c-attribs.c | 114 +
gcc/common.opt | 24 +
gcc/doc/extend.texi | 91 +
gcc/doc/invoke.texi | 42 +
gcc/ipa-inline.c | 6 +
gcc/ipa-split.c | 7 +
gcc/ipa-strub.c | 2999 ++++++++++++++++++++
gcc/ipa-strub.h | 35 +
gcc/passes.def | 2 +
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-apply1.c | 15 +
gcc/testsuite/c-c++-common/strub-apply2.c | 12 +
gcc/testsuite/c-c++-common/strub-apply3.c | 8 +
gcc/testsuite/c-c++-common/strub-apply4.c | 21 +
gcc/testsuite/c-c++-common/strub-at-calls1.c | 30 +
gcc/testsuite/c-c++-common/strub-at-calls2.c | 23 +
gcc/testsuite/c-c++-common/strub-default1.c | 42 +
gcc/testsuite/c-c++-common/strub-default2.c | 29 +
gcc/testsuite/c-c++-common/strub-defer-O1.c | 7 +
gcc/testsuite/c-c++-common/strub-defer-O2.c | 8 +
gcc/testsuite/c-c++-common/strub-defer-O3.c | 93 +
gcc/testsuite/c-c++-common/strub-defer-Os.c | 7 +
gcc/testsuite/c-c++-common/strub-internal1.c | 31 +
gcc/testsuite/c-c++-common/strub-internal2.c | 21 +
gcc/testsuite/c-c++-common/strub-parms1.c | 48 +
gcc/testsuite/c-c++-common/strub-parms2.c | 36 +
gcc/testsuite/c-c++-common/strub-parms3.c | 58 +
gcc/testsuite/c-c++-common/strub-short-O0-exc.c | 10 +
gcc/testsuite/c-c++-common/strub-short-O0.c | 10 +
gcc/testsuite/c-c++-common/strub-short-O1.c | 10 +
gcc/testsuite/c-c++-common/strub-short-O2.c | 10 +
gcc/testsuite/c-c++-common/strub-short-O3.c | 12 +
gcc/testsuite/c-c++-common/strub-short-Os.c | 12 +
gcc/testsuite/c-c++-common/strub-tail-O1.c | 8 +
gcc/testsuite/c-c++-common/strub-tail-O2.c | 14 +
.../c-c++-common/torture/strub-callable1.c | 9 +
.../c-c++-common/torture/strub-callable2.c | 264 ++
gcc/testsuite/c-c++-common/torture/strub-const1.c | 18 +
gcc/testsuite/c-c++-common/torture/strub-const2.c | 22 +
gcc/testsuite/c-c++-common/torture/strub-const3.c | 13 +
gcc/testsuite/c-c++-common/torture/strub-const4.c | 17 +
gcc/testsuite/c-c++-common/torture/strub-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 | 13 +
.../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/c-c++-common/torture/strub-pure1.c | 18 +
gcc/testsuite/c-c++-common/torture/strub-pure2.c | 22 +
gcc/testsuite/c-c++-common/torture/strub-pure3.c | 13 +
gcc/testsuite/c-c++-common/torture/strub-pure4.c | 17 +
gcc/testsuite/c-c++-common/torture/strub-run1.c | 85 +
gcc/testsuite/c-c++-common/torture/strub-run2.c | 75 +
gcc/testsuite/c-c++-common/torture/strub-run3.c | 75 +
gcc/testsuite/c-c++-common/torture/strub-run4.c | 101 +
gcc/testsuite/c-c++-common/torture/strub-run4c.c | 5 +
gcc/testsuite/c-c++-common/torture/strub-run4d.c | 7 +
gcc/testsuite/c-c++-common/torture/strub-run4i.c | 5 +
gcc/testsuite/g++.dg/strub-run1.C | 19 +
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 +
gcc/testsuite/gnat.dg/strub_attr.adb | 21 +
gcc/testsuite/gnat.dg/strub_attr.ads | 12 +
gcc/tree-cfg.c | 1 +
gcc/tree-pass.h | 5 +-
gcc/tree-ssa-ccp.c | 4 +-
libgcc/Makefile.in | 2 +-
libgcc/libgcc2.h | 4 +
libgcc/strub.c | 112 +
94 files changed, 5747 insertions(+), 4 deletions(-)
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 9714fcaac37..94097ec78d7 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1471,6 +1471,7 @@ OBJS = \
ipa-reference.o \
ipa-ref.o \
ipa-utils.o \
+ ipa-strub.o \
ipa.o \
ira.o \
ira-build.o \
diff --git a/gcc/ada/doc/gnat_rm.rst b/gcc/ada/doc/gnat_rm.rst
index 97f7e4d4570..7743ef8b5f4 100644
--- a/gcc/ada/doc/gnat_rm.rst
+++ b/gcc/ada/doc/gnat_rm.rst
@@ -55,6 +55,7 @@ GNAT Reference Manual
gnat_rm/specialized_needs_annexes
gnat_rm/implementation_of_specific_ada_features
gnat_rm/implementation_of_ada_2012_features
+ gnat_rm/security_hardening_features
gnat_rm/obsolescent_features
gnat_rm/compatibility_and_porting_guide
diff --git a/gcc/ada/doc/gnat_rm/about_this_guide.rst b/gcc/ada/doc/gnat_rm/about_this_guide.rst
index b48785eeed0..9defee818ac 100644
--- a/gcc/ada/doc/gnat_rm/about_this_guide.rst
+++ b/gcc/ada/doc/gnat_rm/about_this_guide.rst
@@ -96,6 +96,9 @@ This reference manual contains the following chapters:
* :ref:`Implementation_of_Ada_2012_Features`, describes the status of the
GNAT implementation of the Ada 2012 language standard.
+* :ref:`Security_Hardening_Features` documents GNAT extensions aimed
+ at security hardening.
+
* :ref:`Obsolescent_Features` documents implementation dependent features,
including pragmas and attributes, which are considered obsolescent, since
there are other preferred ways of achieving the same results. These
diff --git a/gcc/ada/doc/gnat_rm/security_hardening_features.rst b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
new file mode 100644
index 00000000000..309291f0341
--- /dev/null
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -0,0 +1,52 @@
+.. _Security_Hardening_Features:
+
+***************************
+Security Hardening Features
+***************************
+
+This chapter describes Ada extensions aimed at security hardening that
+are provided by GNAT.
+
+.. Register Scrubbing:
+
+Register Scrubbing
+==================
+
+GNAT can generate code to zero-out hardware registers before returning
+from a subprogram.
+
+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.
+
+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)`.
+
+
+.. Stack Scrubbing:
+
+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.
+
+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
+preservation of data that should be scrubbed.
+
+Also note that the machine attribute is not integrated in the Ada type
+system. Though it may modify subprogram and variable interfaces, it
+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.
diff --git a/gcc/ada/gcc-interface/trans.c b/gcc/ada/gcc-interface/trans.c
index 3df56aa0560..f1524a3fb90 100644
--- a/gcc/ada/gcc-interface/trans.c
+++ b/gcc/ada/gcc-interface/trans.c
@@ -43,6 +43,7 @@
#include "output.h"
#include "debug.h"
#include "libfuncs.h" /* For set_stack_check_libfunc. */
+#include "ipa-strub.h" /* For strub_make_callable. */
#include "tree-iterator.h"
#include "gimplify.h"
#include "opts.h"
@@ -444,6 +445,7 @@ gigi (Node_Id gnat_root,
int64_type, NULL_TREE),
NULL_TREE, is_default, true, true, true, false,
false, NULL, Empty);
+ strub_make_callable (mulv64_decl);
if (Enable_128bit_Types)
{
@@ -456,6 +458,7 @@ gigi (Node_Id gnat_root,
NULL_TREE),
NULL_TREE, is_default, true, true, true, false,
false, NULL, Empty);
+ strub_make_callable (mulv128_decl);
}
/* Name of the _Parent field in tagged record types. */
@@ -531,6 +534,7 @@ gigi (Node_Id gnat_root,
= create_subprog_decl
(get_identifier ("__gnat_raise_nodefer_with_msg"), NULL_TREE, ftype,
NULL_TREE, is_default, true, true, true, false, false, NULL, Empty);
+ strub_make_callable (raise_nodefer_decl);
set_exception_parameter_decl
= create_subprog_decl
@@ -779,6 +783,7 @@ build_raise_check (int check, enum exception_info_kind kind)
= create_subprog_decl (get_identifier (Name_Buffer), NULL_TREE, ftype,
NULL_TREE, is_default, true, true, true, false,
false, NULL, Empty);
+ strub_make_callable (result);
return result;
}
diff --git a/gcc/ada/gcc-interface/utils.c b/gcc/ada/gcc-interface/utils.c
index f85373e2f76..6b20ddc93c9 100644
--- a/gcc/ada/gcc-interface/utils.c
+++ b/gcc/ada/gcc-interface/utils.c
@@ -94,6 +94,7 @@ static tree handle_sentinel_attribute (tree *, tree, tree, int, bool *);
static tree handle_noreturn_attribute (tree *, tree, tree, int, bool *);
static tree handle_stack_protect_attribute (tree *, tree, tree, int, bool *);
static tree handle_no_stack_protector_attribute (tree *, tree, tree, int, bool *);
+static tree handle_strub_attribute (tree *, tree, tree, int, bool *);
static tree handle_noinline_attribute (tree *, tree, tree, int, bool *);
static tree handle_noclone_attribute (tree *, tree, tree, int, bool *);
static tree handle_noicf_attribute (tree *, tree, tree, int, bool *);
@@ -157,6 +158,8 @@ const struct attribute_spec gnat_internal_attribute_table[] =
{ "no_stack_protector",0, 0, true, false, false, false,
handle_no_stack_protector_attribute,
attr_stack_protect_exclusions },
+ { "strub", 0, 1, false, true, false, true,
+ handle_strub_attribute, NULL },
{ "noinline", 0, 0, true, false, false, false,
handle_noinline_attribute, NULL },
{ "noclone", 0, 0, true, false, false, false,
@@ -6599,6 +6602,127 @@ handle_no_stack_protector_attribute (tree *node, tree name, tree, int,
return NULL_TREE;
}
+/* Handle a "strub" attribute; arguments as in
+ struct attribute_spec.handler. */
+
+static tree
+handle_strub_attribute (tree *node, tree name,
+ tree args,
+ 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);
+ }
+
+ 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)
+ {
+ 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);
+ }
+
+ /* 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)
+ {
+ warning (OPT_Wattributes,
+ "%qE attribute ignored because of argument %qE",
+ name, TREE_VALUE (args));
+ *no_add_attrs = true;
+ enable = false;
+ }
+ /* 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));
+
+ /* 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;
+ }
+ }
+
+ return NULL_TREE;
+}
/* Handle a "noinline" attribute; arguments as in
struct attribute_spec.handler. */
diff --git a/gcc/ada/libgnat/a-except.adb b/gcc/ada/libgnat/a-except.adb
index c332afad0f8..593392809d9 100644
--- a/gcc/ada/libgnat/a-except.adb
+++ b/gcc/ada/libgnat/a-except.adb
@@ -629,6 +629,96 @@ package body Ada.Exceptions is
pragma No_Return (Rcheck_CE_Invalid_Data_Ext);
pragma No_Return (Rcheck_CE_Range_Check_Ext);
+ -- Make all of these procedures callable from strub contexts.
+ -- These attributes are not visible to callers; they are made
+ -- visible in trans.c:build_raise_check.
+
+ pragma Machine_Attribute (Rcheck_CE_Access_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Null_Access_Parameter,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Discriminant_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Divide_By_Zero,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Explicit_Raise,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Index_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Invalid_Data,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Length_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Null_Exception_Id,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Null_Not_Allowed,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Overflow_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Partition_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Range_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Tag_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Access_Before_Elaboration,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Accessibility_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Address_Of_Intrinsic,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Aliased_Parameters,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_All_Guards_Closed,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Bad_Predicated_Generic_Type,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Build_In_Place_Mismatch,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Current_Task_In_Entry_Body,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Duplicated_Entry_Address,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Explicit_Raise,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Implicit_Return,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Misaligned_Address_Value,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Missing_Return,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Non_Transportable_Actual,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Overlaid_Controlled_Object,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Potentially_Blocking_Operation,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Stream_Operation_Not_Allowed,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Stubbed_Subprogram_Called,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Unchecked_Union_Restriction,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Finalize_Raised_Exception,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_SE_Empty_Storage_Pool,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_SE_Explicit_Raise,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_SE_Infinite_Recursion,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_SE_Object_Too_Large,
+ "strub", "callable");
+
+ pragma Machine_Attribute (Rcheck_CE_Access_Check_Ext,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Index_Check_Ext,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Invalid_Data_Ext,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Range_Check_Ext,
+ "strub", "callable");
+
---------------------------------------------
-- Reason Strings for Run-Time Check Calls --
---------------------------------------------
diff --git a/gcc/ada/libgnat/a-except.ads b/gcc/ada/libgnat/a-except.ads
index 2b27adb6ca1..b5a2e48f96a 100644
--- a/gcc/ada/libgnat/a-except.ads
+++ b/gcc/ada/libgnat/a-except.ads
@@ -81,6 +81,10 @@ package Ada.Exceptions is
pragma No_Return (Raise_Exception);
-- Note: In accordance with AI-466, CE is raised if E = Null_Id
+ -- Make it callable from strub contexts.
+ pragma Machine_Attribute (Raise_Exception,
+ "strub", "callable");
+
function Exception_Message (X : Exception_Occurrence) return String;
procedure Reraise_Occurrence (X : Exception_Occurrence);
@@ -184,6 +188,10 @@ private
-- Raise_Exception_Always if it can determine this is the case. The Export
-- allows this routine to be accessed from Pure units.
+ -- Make it callable from strub contexts.
+ pragma Machine_Attribute (Raise_Exception_Always,
+ "strub", "callable");
+
procedure Raise_From_Controlled_Operation (X : Exception_Occurrence);
pragma No_Return (Raise_From_Controlled_Operation);
pragma Export
diff --git a/gcc/ada/libgnat/s-arit128.ads b/gcc/ada/libgnat/s-arit128.ads
index 6213cfb569a..5d3fff6d8e1 100644
--- a/gcc/ada/libgnat/s-arit128.ads
+++ b/gcc/ada/libgnat/s-arit128.ads
@@ -57,6 +57,12 @@ package System.Arith_128 is
-- bits, otherwise returns the 128-bit signed integer product.
-- Gigi may also call this routine directly.
+ -- Make it callable from strub contexts.
+ -- There is a matching setting in trans.c,
+ -- for calls issued by Gigi.
+ pragma Machine_Attribute (Multiply_With_Ovflo_Check128,
+ "strub", "callable");
+
procedure Scaled_Divide128
(X, Y, Z : Int128;
Q, R : out Int128;
diff --git a/gcc/ada/libgnat/s-arit64.ads b/gcc/ada/libgnat/s-arit64.ads
index c9141f5fe3e..170c2bdf1de 100644
--- a/gcc/ada/libgnat/s-arit64.ads
+++ b/gcc/ada/libgnat/s-arit64.ads
@@ -57,6 +57,12 @@ package System.Arith_64 is
-- bits, otherwise returns the 64-bit signed integer product.
-- Gigi may also call this routine directly.
+ -- Make it callable from strub contexts.
+ -- There is a matching setting in trans.c,
+ -- for calls issued by Gigi.
+ pragma Machine_Attribute (Multiply_With_Ovflo_Check64,
+ "strub", "callable");
+
procedure Scaled_Divide64
(X, Y, Z : Int64;
Q, R : out Int64;
diff --git a/gcc/ada/libgnat/s-secsta.ads b/gcc/ada/libgnat/s-secsta.ads
index 7d6b1b9a90e..63df82c8392 100644
--- a/gcc/ada/libgnat/s-secsta.ads
+++ b/gcc/ada/libgnat/s-secsta.ads
@@ -85,6 +85,9 @@ package System.Secondary_Stack is
--
-- * Create a new chunk that fits the requested Storage_Size.
+ pragma Machine_Attribute (SS_Allocate, "strub", "callable");
+ -- Enable it to be called from within strub contexts.
+
procedure SS_Free (Stack : in out SS_Stack_Ptr);
-- Free all dynamic chunks of secondary stack Stack. If possible, free the
-- stack itself.
@@ -92,9 +95,16 @@ package System.Secondary_Stack is
function SS_Mark return Mark_Id;
-- Capture and return the state of the invoking task's secondary stack
+ pragma Machine_Attribute (SS_Mark, "strub", "callable");
+ -- Enable it to be called from within strub contexts.
+
procedure SS_Release (M : Mark_Id);
-- Restore the state of the invoking task's secondary stack to mark M
+ pragma Machine_Attribute (SS_Release, "strub", "callable");
+ -- Enable it to be called from within strub contexts.
+ -- FIXME: should there be a scrubbing SS_Release?
+
function SS_Get_Max return Long_Long_Integer;
-- Return the high water mark of the invoking task's secondary stack, in
-- bytes.
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 99548627761..7ad91ea6f7a 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -70,6 +70,7 @@ along with GCC; see the file COPYING3. If not see
#include "gimple-fold.h"
#include "intl.h"
#include "file-prefix-map.h" /* remap_macro_filename() */
+#include "ipa-strub.h" /* strub_watermark_parm() */
#include "gomp-constants.h"
#include "omp-general.h"
#include "tree-dfa.h"
@@ -150,6 +151,7 @@ static rtx expand_builtin_strnlen (tree, rtx, machine_mode);
static rtx expand_builtin_alloca (tree);
static rtx expand_builtin_unop (machine_mode, tree, rtx, rtx, optab);
static rtx expand_builtin_frame_address (tree, tree);
+static rtx expand_builtin_stack_address ();
static tree stabilize_va_list_loc (location_t, tree, int);
static rtx expand_builtin_expect (tree, rtx);
static rtx expand_builtin_expect_with_probability (tree, rtx);
@@ -4871,6 +4873,256 @@ expand_builtin_frame_address (tree fndecl, tree exp)
}
}
+#ifndef STACK_GROWS_DOWNWARD
+# define STACK_TOPS GT
+#else
+# define STACK_TOPS LT
+#endif
+
+#ifdef POINTERS_EXTEND_UNSIGNED
+# define STACK_UNSIGNED POINTERS_EXTEND_UNSIGNED
+#else
+# define STACK_UNSIGNED true
+#endif
+
+/* Expand a call to builtin function __builtin_stack_address. */
+
+static rtx
+expand_builtin_stack_address ()
+{
+ return convert_to_mode (ptr_mode, copy_to_reg (stack_pointer_rtx),
+ STACK_UNSIGNED);
+}
+
+/* Expand a call to builtin function __builtin_strub_enter. */
+
+static rtx
+expand_builtin_strub_enter (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 1 || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = NULL_RTX;
+
+#if 1 || defined RED_ZONE_SIZE
+ if (tree wmptr = (optimize
+ ? strub_watermark_parm (current_function_decl)
+ : NULL_TREE))
+ {
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+ stktop = force_reg (ptr_mode, wmark);
+ }
+#endif
+
+ if (!stktop)
+ stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ emit_move_insn (wmark, stktop);
+
+ return const0_rtx;
+}
+
+/* Expand a call to builtin function __builtin_strub_update. */
+
+static rtx
+expand_builtin_strub_update (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 2 || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = expand_builtin_stack_address ();
+
+#ifdef RED_ZONE_SIZE
+ /* Here's how the strub enter, update and leave functions deal with red zones.
+
+ If it weren't for red zones, update, called from within a strub context,
+ would bump the watermark to the top of the stack. Enter and leave, running
+ in the caller, would use the caller's top of stack address both to
+ initialize the watermark passed to the callee, and to start strubbing the
+ stack afterwards.
+
+ Ideally, we'd update the watermark so as to cover the used amount of red
+ zone, and strub starting at the caller's other end of the (presumably
+ unused) red zone. Normally, only leaf functions use the red zone, but at
+ this point we can't tell whether a function is a leaf, nor can we tell how
+ much of the red zone it uses. Furthermore, some strub contexts may have
+ been inlined so that update and leave are called from the same stack frame,
+ and the strub builtins may all have been inlined, turning a strub function
+ into a leaf.
+
+ So cleaning the range from the caller's stack pointer (one end of the red
+ zone) to the (potentially inlined) callee's (other end of the) red zone
+ could scribble over the caller's own red zone.
+
+ We avoid this possibility by arranging for callers that are strub contexts
+ to use their own watermark as the strub starting point. So, if A calls B,
+ and B calls C, B will tell A to strub up to the end of B's red zone, and
+ will strub itself only the part of C's stack frame and red zone that
+ doesn't overlap with B's. With that, we don't need to know who's leaf and
+ who isn't: inlined calls will shrink their strub window to zero, each
+ remaining call will strub some portion of the stack, and eventually the
+ strub context will return to a caller that isn't a strub context itself,
+ that will therefore use its own stack pointer as the strub starting point.
+ It's not a leaf, because strub contexts can't be inlined into non-strub
+ contexts, so it doesn't use the red zone, and it will therefore correctly
+ strub up the callee's stack frame up to the end of the callee's red zone.
+ Neat! */
+ if (true /* (flags_from_decl_or_type (current_function_decl) & ECF_LEAF) */)
+ {
+ poly_int64 red_zone_size = RED_ZONE_SIZE;
+#if STACK_GROWS_DOWNWARD
+ red_zone_size = -red_zone_size;
+#endif
+ stktop = plus_constant (ptr_mode, stktop, red_zone_size);
+ stktop = force_reg (ptr_mode, stktop);
+ }
+#endif
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ rtx wmarkr = force_reg (ptr_mode, wmark);
+
+ rtx_code_label *lab = gen_label_rtx ();
+ do_compare_rtx_and_jump (stktop, wmarkr, STACK_TOPS, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, lab, NULL,
+ profile_probability::very_likely ());
+ emit_move_insn (wmark, stktop);
+
+#if 1 || defined RED_ZONE_SIZE
+ /* If this is an inlined strub function, also bump the watermark for the
+ enclosing function. This avoids a problem with the following scenario: A
+ calls B and B calls C, and both B and C get inlined into A. B allocates
+ temporary stack space before calling C. If we don't update A's watermark,
+ we may use an outdated baseline for the post-C strub_leave, erasing B's
+ temporary stack allocation. We only need this if we're fully expanding
+ strub_leave inline. */
+ tree xwmptr = (optimize > 2
+ ? strub_watermark_parm (current_function_decl)
+ : wmptr);
+ if (wmptr != xwmptr)
+ {
+ wmptr = xwmptr;
+ wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+ wmarkr = force_reg (ptr_mode, wmark);
+
+ do_compare_rtx_and_jump (stktop, wmarkr, STACK_TOPS, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, lab, NULL,
+ profile_probability::very_likely ());
+ emit_move_insn (wmark, stktop);
+ }
+#endif
+
+ emit_label (lab);
+
+ return const0_rtx;
+}
+
+
+/* Expand a call to builtin function __builtin_strub_leave. */
+
+static rtx
+expand_builtin_strub_leave (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 2 || optimize_size || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = NULL_RTX;
+
+#if 1 || defined RED_ZONE_SIZE
+ if (tree wmptr = (optimize
+ ? strub_watermark_parm (current_function_decl)
+ : NULL_TREE))
+ {
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+ stktop = force_reg (ptr_mode, wmark);
+ }
+#endif
+
+ if (!stktop)
+ stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ rtx wmarkr = force_reg (ptr_mode, wmark);
+
+#ifndef STACK_GROWS_DOWNWARD
+ rtx base = stktop;
+ rtx end = wmarkr;
+#else
+ rtx base = wmarkr;
+ rtx end = stktop;
+#endif
+
+ /* We're going to modify it, so make sure it's not e.g. the stack pointer. */
+ base = copy_to_reg (base);
+
+ rtx_code_label *done = gen_label_rtx ();
+ do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, done, NULL,
+ profile_probability::very_likely ());
+
+ if (optimize < 3)
+ expand_call (exp, NULL_RTX, true);
+ else
+ {
+ /* Ok, now we've determined we want to copy the block, so convert the
+ addresses to Pmode, as needed to dereference them to access ptr_mode
+ memory locations, so that we don't have to convert anything within the
+ loop. */
+ base = memory_address (ptr_mode, base);
+ end = memory_address (ptr_mode, end);
+
+ rtx zero = force_operand (const0_rtx, NULL_RTX);
+ int ulen = GET_MODE_SIZE (ptr_mode);
+ rtx incr = plus_constant (Pmode, base, ulen);
+ rtx dstm = gen_rtx_MEM (ptr_mode, base);
+
+ rtx_code_label *loop = gen_label_rtx ();
+ emit_label (loop);
+ emit_move_insn (dstm, zero);
+ emit_move_insn (base, force_operand (incr, NULL_RTX));
+ do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
+ Pmode, NULL_RTX, NULL, loop,
+ profile_probability::very_likely ());
+ }
+
+ emit_label (done);
+
+ return const0_rtx;
+}
+
/* Expand EXP, a call to the alloca builtin. Return NULL_RTX if we
failed and the caller should emit a normal call. */
@@ -7110,6 +7362,27 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
case BUILT_IN_RETURN_ADDRESS:
return expand_builtin_frame_address (fndecl, exp);
+ case BUILT_IN_STACK_ADDRESS:
+ return expand_builtin_stack_address ();
+
+ case BUILT_IN___STRUB_ENTER:
+ target = expand_builtin_strub_enter (exp);
+ if (target)
+ return target;
+ break;
+
+ case BUILT_IN___STRUB_UPDATE:
+ target = expand_builtin_strub_update (exp);
+ if (target)
+ return target;
+ break;
+
+ case BUILT_IN___STRUB_LEAVE:
+ target = expand_builtin_strub_leave (exp);
+ if (target)
+ return target;
+ break;
+
/* Returns the address of the area where the structure is returned.
0 otherwise. */
case BUILT_IN_AGGREGATE_INCOMING_ADDRESS:
diff --git a/gcc/builtins.def b/gcc/builtins.def
index 45a09b4d42d..f83c1f5fcd7 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -874,6 +874,10 @@ DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSL, "ffsl", BT_FN_INT_LONG, ATTR_CONST_NOTHRO
DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSLL, "ffsll", BT_FN_INT_LONGLONG, ATTR_CONST_NOTHROW_LEAF_LIST)
DEF_EXT_LIB_BUILTIN (BUILT_IN_FORK, "fork", BT_FN_PID, ATTR_NOTHROW_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FRAME_ADDRESS, "frame_address", BT_FN_PTR_UINT, ATTR_NULL)
+DEF_GCC_BUILTIN (BUILT_IN_STACK_ADDRESS, "stack_address", BT_FN_PTR, ATTR_NULL)
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_ENTER, "__builtin___strub_enter")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_UPDATE, "__builtin___strub_update")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_LEAVE, "__builtin___strub_leave")
/* [trans-mem]: Adjust BUILT_IN_TM_FREE if BUILT_IN_FREE is changed. */
DEF_LIB_BUILTIN (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_LEAF_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FROB_RETURN_ADDR, "frob_return_addr", BT_FN_PTR_PTR, ATTR_NULL)
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index e60fb31d8c8..913b0239903 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -69,6 +69,7 @@ static tree handle_asan_odr_indicator_attribute (tree *, tree, tree, int,
static tree handle_stack_protect_attribute (tree *, tree, tree, int, bool *);
static tree handle_no_stack_protector_function_attribute (tree *, tree,
tree, int, bool *);
+static tree handle_strub_attribute (tree *, tree, tree, int, bool *);
static tree handle_noinline_attribute (tree *, tree, tree, int, bool *);
static tree handle_noclone_attribute (tree *, tree, tree, int, bool *);
static tree handle_nocf_check_attribute (tree *, tree, tree, int, bool *);
@@ -306,6 +307,8 @@ const struct attribute_spec c_common_attribute_table[] =
{ "no_stack_protector", 0, 0, true, false, false, false,
handle_no_stack_protector_function_attribute,
attr_stack_protect_exclusions },
+ { "strub", 0, 1, false, true, false, true,
+ handle_strub_attribute, NULL },
{ "noinline", 0, 0, true, false, false, false,
handle_noinline_attribute,
attr_noinline_exclusions },
@@ -1290,6 +1293,117 @@ handle_noipa_attribute (tree *node, tree name, tree, int, bool *no_add_attrs)
return NULL_TREE;
}
+/* Handle a "strub" attribute; arguments as in
+ struct attribute_spec.handler. */
+
+static tree
+handle_strub_attribute (tree *node, tree name,
+ tree args,
+ 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);
+ }
+
+ if (args && FUNC_OR_METHOD_TYPE_P (*node))
+ {
+ 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;
+ }
+
+ if (val >= 0)
+ TREE_VALUE (args) = build_int_cst (integer_type_node, val);
+ }
+
+ /* 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)
+ {
+ warning (OPT_Wattributes,
+ "%qE attribute ignored because of argument %qE",
+ name, TREE_VALUE (args));
+ *no_add_attrs = true;
+ enable = false;
+ }
+ /* 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));
+
+ /* 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;
+ }
+ }
+
+ return NULL_TREE;
+}
+
/* Handle a "noinline" attribute; arguments as in
struct attribute_spec.handler. */
diff --git a/gcc/common.opt b/gcc/common.opt
index ed8ab5fbe13..ba0359c3c1d 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,6 +2687,30 @@ 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=all
+Common RejectNegative Var(flag_strub, 3)
+Enable stack scrubbing for all viable functions.
+
+fstrub=at-calls
+Common RejectNegative Var(flag_strub, 1)
+Enable at-calls stack scrubbing for all viable functions.
+
+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 251a10302b4..0a9df98ab57 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -77,6 +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.
* 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.
@@ -8672,6 +8673,55 @@ pid_t wait (wait_status_ptr_t p)
@}
@end smallexample
+@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.
+
+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.
+
+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)}).
+
+@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.
+
@item unused
@cindex @code{unused} type attribute
When attached to a type (including a @code{union} or a @code{struct}),
@@ -11690,6 +11740,47 @@ option is in effect. Such calls should only be made in debugging
situations.
@end deftypefn
+@deftypefn {Built-in Function} {void *} __builtin_stack_address ()
+This function returns the value of the stack pointer register.
+@end deftypefn
+
+@node Stack Scrubbing
+@section Stack scrubbing 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).
+
+@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.
+@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
+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.
+@end deftypefn
+
@node Vector Extensions
@section Using Vector Instructions through Built-in Functions
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index b83bd902cec..9ad191d7da2 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,6 +599,7 @@ 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
-fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
-fvtv-counts -fvtv-debug @gol
-finstrument-functions @gol
@@ -15490,6 +15491,47 @@ 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.
+
+@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.
+
+@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.
+
+@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.
+
@item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
@opindex fvtable-verify
This option is only available when compiling C++ code.
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 413446bcc46..7f4bc44d2bb 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -119,6 +119,7 @@ along with GCC; see the file COPYING3. If not see
#include "stringpool.h"
#include "attribs.h"
#include "asan.h"
+#include "ipa-strub.h"
typedef fibonacci_heap <sreal, cgraph_edge> edge_heap_t;
typedef fibonacci_node <sreal, cgraph_edge> edge_heap_node_t;
@@ -396,6 +397,11 @@ 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))
+ {
+ e->inline_failed = CIF_UNSPECIFIED;
+ inlinable = false;
+ }
if (!inlinable && report)
report_inline_failed_reason (e);
return inlinable;
diff --git a/gcc/ipa-split.c b/gcc/ipa-split.c
index c68577d04a9..8c880747eb7 100644
--- a/gcc/ipa-split.c
+++ b/gcc/ipa-split.c
@@ -104,6 +104,7 @@ along with GCC; see the file COPYING3. If not see
#include "ipa-fnsummary.h"
#include "cfgloop.h"
#include "attribs.h"
+#include "ipa-strub.h"
/* Per basic block info. */
@@ -1792,6 +1793,12 @@ execute_split_functions (void)
"section.\n");
return 0;
}
+ if (!strub_splittable_p (node))
+ {
+ if (dump_file)
+ fprintf (dump_file, "Not splitting: function is a strub context.\n");
+ return 0;
+ }
/* We enforce splitting after loop headers when profile info is not
available. */
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
new file mode 100644
index 00000000000..cf7d811779e
--- /dev/null
+++ b/gcc/ipa-strub.c
@@ -0,0 +1,2999 @@
+/* strub (stack scrubbing) support.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "gimplify.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "gimple-iterator.h"
+#include "gimplify-me.h"
+#include "tree-into-ssa.h"
+#include "tree-ssa.h"
+#include "tree-cfg.h"
+#include "cfghooks.h"
+#include "cfgloop.h"
+#include "cfgcleanup.h"
+#include "tree-eh.h"
+#include "except.h"
+#include "builtins.h"
+#include "attribs.h"
+#include "tree-inline.h"
+#include "cgraph.h"
+#include "alloc-pool.h"
+#include "symbol-summary.h"
+#include "ipa-prop.h"
+#include "ipa-fnsummary.h"
+#include "gimple-fold.h"
+#include "fold-const.h"
+#include "gimple-walk.h"
+#include "tree-dfa.h"
+#include "langhooks.h"
+#include "calls.h"
+#include "vec.h"
+#include "stor-layout.h"
+#include "varasm.h"
+#include "alias.h"
+#include "diagnostic.h"
+#include "intl.h"
+#include "ipa-strub.h"
+
+#if BUILDING_GCC_MAJOR >= 11
+# include "symtab-thunks.h"
+# include "attr-fnspec.h"
+# define HAVE_ATTR_FNSPEC 1
+# define FOR_GCC_11P 1
+#else
+# define HAVE_ATTR_FNSPEC 0
+# define FOR_GCC_11P 0
+#endif
+
+/* Const and pure functions that gain a watermark parameter for strub purposes
+ are still regarded as such, which may cause the inline expansions of the
+ __strub builtins to malfunction. Ideally, attribute "fn spec" would enable
+ us to inform the backend about requirements and side effects of the call, but
+ call_fusage building in calls.c:expand_call does not even look at
+ attr_fnspec, so we resort to asm loads and updates to attain an equivalent
+ effect. Once expand_call gains the ability to issue extra memory uses and
+ clobbers based on pure/const function's fnspec, we can define this to 1. */
+#define ATTR_FNSPEC_DECONST_WATERMARK 0
+
+enum strub_mode {
+ /* This mode denotes a regular function, that does not require stack
+ scrubbing (strubbing). It may call any other functions, but if
+ it calls AT_CALLS (or WRAPPED) ones, strubbing logic is
+ automatically introduced around those calls (the latter, by
+ inlining INTERNAL wrappers). */
+ STRUB_DISABLED = 0,
+
+ /* This denotes a function whose signature is (to be) modified to
+ take an extra parameter, for stack use annotation, and its
+ callers must initialize and pass that argument, and perform the
+ strubbing. Functions that are explicitly marked with attribute
+ strub must have the mark visible wherever the function is,
+ including aliases, and overriders and overriding methods.
+ Functions that are implicitly marked for strubbing, for accessing
+ variables explicitly marked as such, will only select this
+ strubbing method if they are internal to a translation unit. It
+ can only be inlined into other strubbing functions, i.e.,
+ STRUB_AT_CALLS or STRUB_WRAPPED. */
+ STRUB_AT_CALLS = 1,
+
+ /* This denotes a function that is to perform strubbing internally,
+ without any changes to its interface (the function is turned into
+ a strubbing wrapper, and its original body is moved to a separate
+ STRUB_WRAPPED function, with a modified interface). Functions
+ may be explicitly marked with attribute strub(2), and the
+ attribute must be visible at the point of definition. Functions
+ that are explicitly marked for strubbing, for accessing variables
+ explicitly marked as such, may select this strubbing mode if
+ their interface cannot change, e.g. because its interface is
+ visible to other translation units, directly, by indirection
+ (having its address taken), inheritance, etc. Functions that use
+ this method must not have the noclone attribute, nor the noipa
+ one. Functions marked as always_inline may select this mode, but
+ they are NOT wrapped, they remain unchanged, and are only inlined
+ into strubbed contexts. Once non-always_inline functions are
+ wrapped, the wrapper becomes STRUB_WRAPPER, and the wrapped becomes
+ STRUB_WRAPPED. */
+ STRUB_INTERNAL = 2,
+
+ /* This denotes a function whose stack is not strubbed, but that is
+ nevertheless explicitly or implicitly marked as callable from strubbing
+ functions. Normally, only STRUB_AT_CALLS (and STRUB_INTERNAL ->
+ STRUB_WRAPPED) functions can be called from strubbing contexts (bodies of
+ STRUB_AT_CALLS, STRUB_INTERNAL and STRUB_WRAPPED functions), but attribute
+ strub(3) enables other functions to be (indirectly) called from these
+ contexts. Some builtins and internal functions may be implicitly marked as
+ STRUB_CALLABLE. */
+ STRUB_CALLABLE = 3,
+
+ /* This denotes the function that took over the body of a
+ STRUB_INTERNAL function. At first, it's only called by its
+ wrapper, but the wrapper may be inlined. The wrapped function,
+ in turn, can only be inlined into other functions whose stack
+ frames are strubbed, i.e., that are STRUB_WRAPPED or
+ STRUB_AT_CALLS. */
+ STRUB_WRAPPED = -1,
+
+ /* This denotes the wrapper function that replaced the STRUB_INTERNAL
+ function. This mode overrides the STRUB_INTERNAL mode at the time the
+ internal to-be-wrapped function becomes a wrapper, so that inlining logic
+ can tell one from the other. */
+ STRUB_WRAPPER = -2,
+
+ /* 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
+get_strub_attr_from_type (tree type)
+{
+ return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
+}
+
+static tree
+get_strub_attr_from_decl (tree decl)
+{
+ tree ret = lookup_attribute ("strub", DECL_ATTRIBUTES (decl));
+ if (ret)
+ return ret;
+ return get_strub_attr_from_type (TREE_TYPE (decl));
+}
+
+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);
+
+#if 0 /* ??? use symbolic mode names with interned strings? */
+ char *s = NULL;
+
+ switch (strub_mode)
+ {
+
+ }
+#endif
+}
+
+static enum strub_mode
+get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
+{
+ enum strub_mode mode = STRUB_DISABLED;
+
+ if (strub_attr)
+ {
+ 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 ();
+ }
+
+ return mode;
+}
+
+static enum strub_mode
+get_strub_mode_from_decl (tree fndecl)
+{
+ return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
+}
+
+static enum strub_mode
+get_strub_mode (cgraph_node *node)
+{
+ return get_strub_mode_from_decl (node->decl);
+}
+
+static enum strub_mode
+get_strub_mode_from_type (tree type)
+{
+ bool var_p = !FUNC_OR_METHOD_TYPE_P (type);
+ tree attr = get_strub_attr_from_type (type);
+
+ if (attr)
+ return get_strub_mode_from_attr (attr, var_p);
+
+ if (flag_strub > 0 && !var_p)
+ return STRUB_CALLABLE;
+
+ return STRUB_DISABLED;
+}
+
+static bool
+calls_builtin_va_start_p (cgraph_node *node)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (fndecl_built_in_p (cdecl, BUILT_IN_VA_START))
+ return true;
+ }
+
+ return result;
+}
+
+static bool
+calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!fndecl_built_in_p (cdecl, BUILT_IN_APPLY_ARGS))
+ continue;
+
+ result = true;
+
+ if (!report)
+ break;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "at-calls %<strub%> does not support call to %qD",
+ cdecl);
+ }
+
+ return result;
+}
+
+static inline bool
+strub_always_inline_p (cgraph_node *node)
+{
+ return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
+}
+
+static inline bool
+can_strub_p (cgraph_node *node, bool report = false)
+{
+ bool result = true;
+
+ if (!report && strub_always_inline_p (node))
+ return result;
+
+ if (lookup_attribute ("noipa", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for %<strub%>"
+ " because of attribute %<noipa%>",
+ node->decl);
+ }
+
+ /* We can't, and don't want to vectorize the watermark and other
+ strub-introduced parms. */
+ if (lookup_attribute ("simd", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for %<strub%>"
+ " because of attribute %<simd%>",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+can_strub_at_calls_p (cgraph_node *node, bool report = false)
+{
+ bool result = !report || can_strub_p (node, report);
+
+ if (!result && !report)
+ return result;
+
+ return !calls_builtin_apply_args_p (node, report);
+}
+
+#define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
+
+/* We can't perform internal strubbing if the function body involves certain
+ features:
+
+ - a non-default __builtin_va_start (e.g. x86's __builtin_ms_va_start) is
+ currently unsupported because we can't discover the corresponding va_copy and
+ va_end decls in the wrapper, and we don't convey the alternate variable
+ arguments ABI to the modified wrapped function. The default
+ __builtin_va_start is supported by calling va_start/va_end at the wrapper,
+ that takes variable arguments, passing a pointer to the va_list object to the
+ wrapped function, that runs va_copy from it where the original function ran
+ va_start.
+
+ __builtin_next_arg is currently unsupported because the wrapped function
+ won't be a variable argument function. We could process it in the wrapper,
+ that remains a variable argument function, and replace calls in the wrapped
+ body, but we currently don't.
+
+ __builtin_return_address is rejected because it's generally used when the
+ actual caller matters, and introducing a wrapper breaks such uses as those in
+ the unwinder. */
+
+static bool
+can_strub_internally_p (cgraph_node *node, bool report = false)
+{
+ bool result = !report || can_strub_p (node, report);
+
+ if (!result && !report)
+ return result;
+
+ if (!report && strub_always_inline_p (node))
+ return 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, but we'd have to prevent remapping of the labels. */
+ if (lookup_attribute ("noclone", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for internal %<strub%>"
+ " because of attribute %<noclone%>",
+ node->decl);
+ }
+
+ if (node->has_gimple_body_p ())
+ {
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!((fndecl_built_in_p (cdecl, BUILT_IN_VA_START)
+ && cdecl != builtin_decl_explicit (BUILT_IN_VA_START))
+ || fndecl_built_in_p (cdecl, BUILT_IN_NEXT_ARG)
+ || fndecl_built_in_p (cdecl, BUILT_IN_RETURN_ADDRESS)))
+ continue;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "%qD is not eligible for internal %<strub%> "
+ "because it calls %qD",
+ node->decl, cdecl);
+ }
+
+ struct function *fun = DECL_STRUCT_FUNCTION (node->decl);
+ if (fun->has_nonlocal_label)
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for internal %<strub%> "
+ "because it contains a non-local goto target",
+ node->decl);
+ }
+
+ if (fun->has_forced_label_in_static)
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for internal %<strub%> "
+ "because the address of a local label escapes",
+ node->decl);
+ }
+
+ /* Catch any other case that would prevent versioning/cloning
+ so as to also have it covered above. */
+ gcc_checking_assert (!result /* || !node->has_gimple_body_p () */
+ || tree_versionable_function_p (node->decl));
+
+
+ /* Label values referenced 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. */
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ glabel *label_stmt = dyn_cast <glabel *> (gsi_stmt (gsi));
+ tree target;
+
+ if (!label_stmt)
+ break;
+
+ 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;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (label_stmt),
+ "internal %<strub%> does not support user labels");
+ }
+ }
+
+ if (list_length (TYPE_ARG_TYPES (TREE_TYPE (node->decl)))
+ >= (((HOST_WIDE_INT) 1 << IPA_PARAM_MAX_INDEX_BITS)
+ - STRUB_INTERNAL_MAX_EXTRA_ARGS))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD has too many arguments for internal %<strub%>",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+strub_from_body_p (cgraph_node *node)
+{
+ if (!node->has_gimple_body_p ())
+ return false;
+
+ /* If any local variable is marked for strub... */
+ unsigned i;
+ tree var;
+ FOR_EACH_LOCAL_DECL (DECL_STRUCT_FUNCTION (node->decl),
+ i, var)
+ if (get_strub_mode_from_type (TREE_TYPE (var))
+ != STRUB_DISABLED)
+ return true;
+
+ /* Now scan the body for loads with strub types. */
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!gimple_assign_load_p (stmt))
+ continue;
+
+ tree rhs = gimple_assign_rhs1 (stmt);
+ if (get_strub_mode_from_type (TREE_TYPE (rhs))
+ != STRUB_DISABLED)
+ return true;
+ }
+
+ return false;
+}
+
+/* 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)
+{
+ if (DECL_BUILT_IN_CLASS (node->decl) != BUILT_IN_NORMAL)
+ return false;
+
+ enum built_in_function fcode = DECL_FUNCTION_CODE (node->decl);
+
+ switch (fcode)
+ {
+ case BUILT_IN_NONE:
+ gcc_unreachable ();
+
+ /* This temporarily allocates stack for the call, and we can't reasonably
+ update the watermark for that. Besides, we don't check the actual call
+ target, nor its signature, and it seems to be overkill to as much as
+ try to do so. */
+ case BUILT_IN_APPLY:
+ return false;
+
+ /* Conversely, this shouldn't be called from within strub contexts, since
+ the caller may have had its signature modified. STRUB_INTERNAL is ok,
+ the call will remain in the STRUB_WRAPPER, and removed from the
+ STRUB_WRAPPED clone. */
+ case BUILT_IN_APPLY_ARGS:
+ return false;
+
+ /* ??? Make all other builtins callable. We wish to make any builtin call
+ the compiler might introduce on its own callable. Anything that is
+ predictable enough as to be known not to allow stack data that should
+ be strubbed to unintentionally escape to non-strub contexts can be
+ allowed, and pretty much every builtin appears to fit this description.
+ The exceptions to this rule seem to be rare, and only available as
+ explicit __builtin calls, so let's keep it simple and allow all of
+ them... */
+ default:
+ return true;
+ }
+}
+
+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);
+
+ /* 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;
+ /* 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
+ safely undergo at-calls strubbing. Internal mode will still be used in
+ functions that request it explicitly with attribute strub(2), or when the
+ function body requires strubbing and at-calls strubbing is not viable. */
+ const bool strub_flag_at_calls = flag_strub == 1;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo internal strubbing. At-calls mode will still be used in
+ functions that requiest it explicitly with attribute strub() or strub(1),
+ or when the function body requires strubbing and internal strubbing is not
+ viable. */
+ const bool strub_flag_internal = flag_strub == 2;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo strubbing in either mode. When both modes are viable,
+ at-calls is preferred. */
+ const bool strub_flag_either = flag_strub == 3;
+ /* Besides the default behavior, enable strub implicitly for all viable
+ functions. */
+ const bool strub_flag_viable = flag_strub > 0;
+
+ /* The consider_* variables should be true if selecting the corresponding
+ strub modes would be consistent with requests from attributes and command
+ line flags. Attributes associated with functions pretty much mandate a
+ selection, and should report an error if not satisfied; strub_flag_auto
+ implicitly enables some viable strub mode if that's required by references
+ to variables marked for strub; strub_flag_viable enables strub if viable
+ (even when favoring one mode, body-requested strub can still be satisfied
+ by either mode), and falls back to callable, silently unless variables
+ require strubbing. */
+
+ const bool consider_at_calls
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_AT_CALLS
+ : true));
+ const bool consider_internal
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_INTERNAL
+ : true));
+
+ const bool consider_callable
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_CALLABLE
+ : (strub_flag_viable
+ || strub_callable_builtin_p (node))));
+
+ /* This is a shorthand for either strub-enabled mode. */
+ const bool consider_strub
+ = (consider_at_calls || consider_internal);
+
+ /* We can cope with always_inline functions even with noipa and noclone,
+ because we just leave them alone. */
+ const bool is_always_inline
+ = strub_always_inline_p (node);
+
+ /* Strubbing in general, and each specific strub mode, may have its own set of
+ requirements. We require noipa for strubbing, either because of cloning
+ required for internal strub, or because of caller enumeration required for
+ at-calls strub. We don't consider the at-calls mode eligible if it's not
+ even considered, it has no further requirements. Internal mode requires
+ cloning and the absence of certain features in the body and, like at-calls,
+ it's not eligible if it's not even under consideration.
+
+ ??? Do we need target hooks for further constraints? E.g., x86's
+ "interrupt" attribute breaks internal strubbing because the wrapped clone
+ carries the attribute and thus isn't callable; in this case, we could use a
+ target hook to adjust the clone instead. */
+ const bool strub_eligible
+ = (consider_strub
+ && (is_always_inline || can_strub_p (node)));
+ const bool at_calls_eligible
+ = (consider_at_calls && strub_eligible
+ && can_strub_at_calls_p (node));
+ const bool internal_eligible
+ = (consider_internal && strub_eligible
+ && (is_always_inline
+ || can_strub_internally_p (node)));
+
+ /* In addition to the strict eligibility requirements, some additional
+ constraints are placed on implicit selection of certain modes. These do
+ not prevent the selection of a mode if explicitly specified as part of a
+ function interface (the strub attribute), but they may prevent modes from
+ being selected by the command line or by function bodies. The only actual
+ constraint is on at-calls mode: since we change the function's exposed
+ signature, we won't do it implicitly if the function can possibly be used
+ in ways that do not expect the signature change, e.g., if the function is
+ available to or interposable by other units, if its address is taken,
+ etc. */
+ const bool at_calls_viable
+ = (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->binds_to_current_def_p ()
+ && node->can_be_local_p ()))
+ && node->only_called_directly_p ())));
+ const bool internal_viable
+ = (internal_eligible);
+
+ /* Shorthand. */
+ const bool strub_viable
+ = (at_calls_viable || internal_viable);
+
+ /* We wish to analyze the body, to look for implicit requests for strub, both
+ to implicitly enable it when the body calls for it, and to report errors if
+ the body calls for it but neither mode is viable (even if that follows from
+ non-eligibility because of the explicit specification of some non-strubbing
+ mode). We can refrain from scanning the body only in rare circumstances:
+ when strub is enabled by a function attribute (scanning might be redundant
+ in telling us to also enable it), and when we are enabling strub implicitly
+ but there are non-viable modes: we want to know whether strubbing is
+ required, to fallback to another mode, even if we're only enabling a
+ certain mode, or, when either mode would do, to report an error if neither
+ happens to be viable. */
+ const bool analyze_body
+ = (strub_attr
+ ? !consider_strub
+ : (strub_flag_auto
+ || (strub_flag_viable && (!at_calls_viable && !internal_viable))
+ || (strub_flag_either && !strub_viable)));
+
+ /* Cases in which strubbing is enabled or disabled by strub_flag_auto.
+ Unsatisfiable requests ought to be reported. */
+ const bool strub_required
+ = ((strub_attr && consider_strub)
+ || (analyze_body && strub_from_body_p (node)));
+
+ /* Besides the required cases, we want to abide by the requests to enabling on
+ an if-viable basis. */
+ const bool strub_enable
+ = (strub_required
+ || (strub_flag_at_calls && at_calls_viable)
+ || (strub_flag_internal && internal_viable)
+ || (strub_flag_either && strub_viable));
+
+ /* And now we're finally ready to select a mode that abides by the viability
+ and eligibility constraints, and that satisfies the strubbing requirements
+ and requests, subject to the constraints. If both modes are viable and
+ strub is to be enabled, pick STRUB_AT_CALLS unless STRUB_INTERNAL was named
+ as preferred. */
+ 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
+ : (strub_enable && at_calls_viable)
+ ? (strub_required && !strub_attr
+ ? STRUB_AT_CALLS_OPT
+ : STRUB_AT_CALLS)
+ : consider_callable
+ ? STRUB_CALLABLE
+ : STRUB_DISABLED);
+
+ switch (mode)
+ {
+ case STRUB_CALLABLE:
+ if (is_always_inline)
+ break;
+ /* Fall through. */
+
+ case STRUB_DISABLED:
+ if (strub_enable && !strub_attr)
+ {
+ gcc_checking_assert (analyze_body);
+ error_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD requires %<strub%>,"
+ " but no viable %<strub%> mode was found",
+ node->decl);
+ break;
+ }
+ /* Fall through. */
+
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ /* Differences from an mode requested through a function attribute are
+ 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:
+ gcc_unreachable ();
+ }
+
+ return mode;
+}
+
+/* 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)
+{
+ gcc_checking_assert (override
+ || !(DECL_P (fndt)
+ ? get_strub_attr_from_decl (fndt)
+ : get_strub_attr_from_type (fndt)));
+
+ tree attr = tree_cons (get_identifier ("strub"),
+ get_strub_mode_attr_value (mode),
+ NULL_TREE);
+ tree *attrp = NULL;
+ if (DECL_P (fndt))
+ {
+ gcc_checking_assert (FUNC_OR_METHOD_TYPE_P (TREE_TYPE (fndt)));
+ attrp = &DECL_ATTRIBUTES (fndt);
+ }
+ else if (FUNC_OR_METHOD_TYPE_P (fndt))
+ attrp = &TYPE_ATTRIBUTES (fndt);
+ else
+ gcc_unreachable ();
+
+ TREE_CHAIN (attr) = *attrp;
+ *attrp = attr;
+}
+
+void
+strub_make_callable (tree fndt)
+{
+ strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
+}
+
+static void
+set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+ enum strub_mode req_mode = get_strub_mode_from_attr (attr);
+
+ if (attr)
+ {
+ /* Check for and report incompatible mode changes. */
+ if (mode != req_mode
+ && !(req_mode == STRUB_INTERNAL
+ && (mode == STRUB_WRAPPED
+ || mode == STRUB_WRAPPER))
+ && !((req_mode == STRUB_INTERNAL
+ || req_mode == STRUB_AT_CALLS
+ || req_mode == STRUB_CALLABLE)
+ && 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));
+ if (node->alias)
+ {
+ cgraph_node *target = node->ultimate_alias_target ();
+ if (target != node)
+ error_at (DECL_SOURCE_LOCATION (target->decl),
+ "the incompatible selection was determined"
+ " by ultimate alias target %qD",
+ target->decl);
+ }
+
+ /* Report any incompatibilities with explicitly-requested strub. */
+ switch (req_mode)
+ {
+ case STRUB_AT_CALLS:
+ can_strub_at_calls_p (node, true);
+ break;
+
+ case STRUB_INTERNAL:
+ can_strub_internally_p (node, true);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Drop any incompatible strub attributes leading the decl attribute
+ chain. Return if we find one with the mode we need. */
+ for (;;)
+ {
+ if (mode == req_mode)
+ return;
+
+ if (DECL_ATTRIBUTES (node->decl) != attr)
+ break;
+
+ DECL_ATTRIBUTES (node->decl) = TREE_CHAIN (attr);
+ attr = get_strub_attr_from_decl (node->decl);
+ if (!attr)
+ break;
+
+ req_mode = get_strub_mode_from_attr (attr);
+ }
+ }
+ else if (mode == req_mode)
+ return;
+
+ strub_set_fndt_mode_to (node->decl, mode, attr);
+}
+
+static void
+set_strub_mode (cgraph_node *node)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+
+ if (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:
+ case STRUB_AT_CALLS_OPT:
+ return;
+
+ case STRUB_DISABLED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ cgraph_node *xnode = node;
+ if (node->alias)
+ xnode = node->ultimate_alias_target ();
+ /* Weakrefs may remain unresolved (the above will return node) if
+ their targets are not defined, so make sure we compute a strub
+ mode for them, instead of defaulting to STRUB_DISABLED and
+ rendering them uncallable. */
+ enum strub_mode mode = (xnode != node && !xnode->alias
+ ? get_strub_mode (xnode)
+ : compute_strub_mode (node, attr));
+
+ set_strub_mode_to (node, mode);
+}
+
+/* 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. */
+
+static bool
+strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode caller_mode = get_strub_mode (caller);
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS_OPT:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return callee_mode != STRUB_INLINABLE;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_AT_CALLS_OPT:
+ case STRUB_INTERNAL:
+ case STRUB_WRAPPER:
+ return (flag_strub >= 0);
+
+ case STRUB_DISABLED:
+ return false;
+
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ 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. */
+
+bool
+strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ case STRUB_AT_CALLS_OPT:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return true;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ strub_mode caller_mode = get_strub_mode (caller);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ case STRUB_AT_CALLS_OPT:
+ return true;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ return false;
+}
+
+/* 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;
+
+ 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_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)
+ {
+ gcc_checking_assert (!e->indirect_unknown_callee);
+ if (!strub_callable_from_p (e->callee, node))
+ {
+ if (get_strub_mode (e->callee) == STRUB_INLINABLE)
+ error_at (gimple_location (e->call_stmt),
+ "calling %<always_inline%> %<strub%> %qD"
+ " in non-%<strub%> context %qD",
+ e->callee->decl, node->decl);
+ else if (fndecl_built_in_p (e->callee->decl, BUILT_IN_APPLY_ARGS)
+ && get_strub_mode (node) == STRUB_INTERNAL)
+ /* This is ok, it will be kept in the STRUB_WRAPPER, and removed
+ from the STRUB_WRAPPED's strub context. */
+ continue;
+ else
+ error_at (gimple_location (e->call_stmt),
+ "calling non-%<strub%> %qD in %<strub%> context %qD",
+ e->callee->decl, node->decl);
+ }
+ }
+ }
+
+ /* ??? 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 {
+
+const pass_data pass_data_ipa_strub_mode = {
+ SIMPLE_IPA_PASS,
+ "strubm",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ 0, // properties_finish
+};
+
+class pass_ipa_strub_mode : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub_mode (gcc::context *ctxt)
+ : simple_ipa_opt_pass (pass_data_ipa_strub_mode, ctxt)
+ {}
+ 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
+ overhead. */
+ if (flag_strub == -2)
+ flag_strub = 0;
+ return flag_strub;
+ }
+ virtual unsigned int execute (function *);
+};
+
+const pass_data pass_data_ipa_strub = {
+ SIMPLE_IPA_PASS,
+ "strub",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ TODO_update_ssa
+ | TODO_cleanup_cfg
+ | TODO_rebuild_cgraph_edges
+ | TODO_verify_il, // properties_finish
+};
+
+class pass_ipa_strub : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub (gcc::context *ctxt)
+ : 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 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_variant_type_copy (ptr_type_node))
+
+ DEF_TYPE (pwmt, build_reference_type (get_wmt ()))
+
+ DEF_TYPE (qpwmt,
+ build_qualified_type (get_pwmt (),
+ TYPE_QUAL_RESTRICT
+ /* | TYPE_QUAL_CONST */))
+
+ DEF_TYPE (qptr,
+ build_qualified_type (ptr_type_node,
+ TYPE_QUAL_RESTRICT
+ | TYPE_QUAL_CONST))
+
+ DEF_TYPE (qpvalst,
+ build_qualified_type (build_reference_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); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ decl = add_builtin_function \
+ ("__builtin_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ NULL, NULL); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ } \
+ return decl; \
+ }
+
+ DEF_NM_BUILTIN (stack_address,
+ BUILT_IN_STACK_ADDRESS,
+ (ptr_type_node, NULL))
+
+#undef DEF_NM_BUILTIN
+
+#define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST) \
+ static tree get_ ## NAME () { \
+ tree decl = builtin_decl_explicit (CODE); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ tree attrs = NULL; \
+ if (FNSPEC && HAVE_ATTR_FNSPEC) \
+ attrs = tree_cons (get_identifier ("fn spec"), \
+ build_tree_list \
+ (NULL_TREE, \
+ build_string (strlen (FNSPEC), \
+ (FNSPEC))), \
+ attrs); \
+ decl = add_builtin_function_ext_scope \
+ ("__builtin___strub_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ "__strub_" #NAME, attrs); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ } \
+ return decl; \
+ }
+
+ DEF_SS_BUILTIN (enter, ". Ot",
+ BUILT_IN___STRUB_ENTER,
+ (void_type_node, get_qpwmt (), NULL))
+ DEF_SS_BUILTIN (update, ". Wt",
+ BUILT_IN___STRUB_UPDATE,
+ (void_type_node, get_qpwmt (), NULL))
+ DEF_SS_BUILTIN (leave, ". w ",
+ BUILT_IN___STRUB_LEAVE,
+ (void_type_node, get_qpwmt (), NULL))
+
+#undef DEF_SS_BUILTIN
+
+#define DEF_IDENT(NAME) \
+ static inline tree get_ ## NAME () { \
+ static tree identifier = NULL_TREE; \
+ if (!identifier) \
+ identifier = get_identifier (".strub." #NAME); \
+ return identifier; \
+ }
+
+ DEF_IDENT (watermark_ptr)
+ DEF_IDENT (va_list_ptr)
+ DEF_IDENT (apply_args)
+
+#undef DEF_IDENT
+
+ static inline int adjust_at_calls_type (tree);
+ static inline void adjust_at_calls_call (cgraph_edge *, int);
+ static inline void adjust_at_calls_calls (cgraph_node *);
+
+ static inline gimple_seq
+ call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
+ gimple_seq seq = NULL)
+ {
+ tree uwm = get_update ();
+ gcall *update = gimple_build_call (uwm, 1, wmptr);
+ if (node)
+ gimple_set_location (update, DECL_SOURCE_LOCATION (node->decl));
+ gimple_seq_add_stmt (&seq, update);
+ if (node)
+#if !IMPLICIT_CGRAPH_EDGES
+ node->create_edge (cgraph_node::get_create (uwm), update, count, false);
+#else
+ (void)count;
+#endif
+ return seq;
+ }
+
+};
+
+} // anon namespace
+
+typedef hash_set<tree> indirect_parms_t;
+
+static tree
+maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
+{
+ if (DECL_P (op))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (op))
+ {
+ tree ret = gimple_fold_indirect_ref (op);
+ if (!ret)
+ ret = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (op)),
+ op,
+ build_int_cst (TREE_TYPE (op), 0));
+ return ret;
+ }
+ }
+ else if (TREE_CODE (op) == ADDR_EXPR
+ && DECL_P (TREE_OPERAND (op, 0)))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (TREE_OPERAND (op, 0)))
+ return TREE_OPERAND (op, 0);
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_make_indirect (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ indirect_parms_t &indirect_parms = *(indirect_parms_t *)wi->info;
+
+ if (!*op || TYPE_P (*op))
+ {
+ *rec = 0;
+ return NULL_TREE;
+ }
+
+ if (tree repl = maybe_make_indirect (indirect_parms, *op, rec))
+ {
+ *op = repl;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ gimple_stmt_iterator &gsi = *(gimple_stmt_iterator *)wi->info;
+
+ *rec = 0;
+
+ if (!*op || TREE_CODE (*op) != ADDR_EXPR)
+ return NULL_TREE;
+
+ if (!is_gimple_val (*op))
+ {
+ tree ret = force_gimple_operand_gsi (&gsi, *op, true,
+ NULL_TREE, true, GSI_SAME_STMT);
+ gcc_assert (ret != *op);
+ *op = ret;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+build_ref_type_for (tree parm, bool nonaliased = true)
+{
+ gcc_checking_assert (TREE_CODE (parm) == PARM_DECL);
+
+ tree ref_type = build_reference_type (TREE_TYPE (parm));
+
+ if (!nonaliased)
+ return ref_type;
+
+ /* Each PARM turned indirect still points to the distinct memory area at the
+ wrapper, and the reference in unchanging, so we might qualify it, but...
+ const is not really important, since we're only using default defs for the
+ reference parm anyway, and not introducing any defs, and restrict seems to
+ cause trouble. E.g., libgnat/s-concat3.adb:str_concat_3 has memmoves that,
+ if it's wrapped, the memmoves are deleted in dse1. Using a distinct alias
+ set seems to not run afoul of this problem, and it hopefully enables the
+ compiler to tell the pointers do point to objects that are not otherwise
+ aliased. */
+#if 1
+ tree qref_type = build_variant_type_copy (ref_type);
+
+ TYPE_ALIAS_SET (qref_type) = new_alias_set ();
+ record_alias_subset (TYPE_ALIAS_SET (qref_type), get_alias_set (ref_type));
+
+ return qref_type;
+#else
+ tree qref_type = build_qualified_type (ref_type,
+ TYPE_QUAL_RESTRICT
+ | TYPE_QUAL_CONST);
+
+ return qref_type;
+#endif
+}
+
+/* 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)
+{
+#if IMPLICIT_CGRAPH_EDGES
+ return;
+#endif
+
+ cgraph_node *node = cgraph_node::get_create (current_function_decl);
+
+ for (gimple_stmt_iterator gsi = gsi_start (seq);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!is_a <gcall *> (stmt))
+ 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);
+ }
+}
+
+static void
+gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
+{
+ if (!seq)
+ return;
+
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (gimple_has_location (stmt))
+ annotate_all_with_location (seq, gimple_location (stmt));
+
+ gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+ 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;
+ bool nothrow_p = (must_not_throw_p
+ || (call && gimple_call_nothrow_p (call))
+ || (eh_lp <= 0
+ && (TREE_NOTHROW (cfun->decl)
+ || !flag_exceptions)));
+
+ if (noreturn_p && nothrow_p)
+ return;
+
+ /* Don't expect an EH edge if we're not to throw, or if we're not in an EH
+ region yet. */
+ bool no_eh_edge_p = (nothrow_p || !eh_lp);
+ bool must_end_bb = stmt_ends_bb_p (stmt);
+
+ edge eft = NULL, eeh = NULL;
+ if (must_end_bb && !(noreturn_p && no_eh_edge_p))
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+
+ edge e;
+ edge_iterator ei;
+ FOR_EACH_EDGE (e, ei, gsi_bb (gsi)->succs)
+ {
+ if ((e->flags & EDGE_EH))
+ {
+ gcc_checking_assert (!eeh);
+ eeh = e;
+#if !CHECKING_P
+ if (eft || noreturn_p)
+ break;
+#endif
+ }
+ if ((e->flags & EDGE_FALLTHRU))
+ {
+ gcc_checking_assert (!eft);
+ eft = e;
+#if !CHECKING_P
+ if (eeh || no_eh_edge_p)
+ break;
+#endif
+ }
+ }
+
+ gcc_checking_assert (!(eft && (eft->flags & EDGE_FALLTHRU))
+ == noreturn_p);
+ gcc_checking_assert (!(eeh && (eeh->flags & EDGE_EH))
+ == no_eh_edge_p);
+ gcc_checking_assert (eft != eeh);
+ }
+
+ if (!noreturn_p)
+ {
+ gimple_seq nseq = nothrow_p ? seq : gimple_seq_copy (seq);
+
+ if (must_end_bb)
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+ add_call_edges_for_seq (nseq, eft->count ());
+ gsi_insert_seq_on_edge_immediate (eft, nseq);
+ }
+ else
+ {
+ add_call_edges_for_seq (nseq, gsi_bb (gsi)->count);
+ gsi_insert_seq_after (&gsi, nseq, GSI_SAME_STMT);
+ }
+ }
+
+ if (nothrow_p)
+ return;
+
+ if (eh_lp)
+ {
+ add_call_edges_for_seq (seq, eeh->count ());
+ gsi_insert_seq_on_edge_immediate (eeh, seq);
+ return;
+ }
+
+ /* A throwing call may appear within a basic block in a function that doesn't
+ have any EH regions. We're going to add a cleanup if so, therefore the
+ block will have to be split. */
+ basic_block bb = gsi_bb (gsi);
+ if (!gsi_one_before_end_p (gsi))
+ split_block (bb, stmt);
+
+ /* Create a new block for the EH cleanup. */
+ basic_block bb_eh_cleanup = create_empty_bb (bb);
+ if (dom_info_available_p (CDI_DOMINATORS))
+ set_immediate_dominator (CDI_DOMINATORS, bb_eh_cleanup, bb);
+ if (current_loops)
+ add_bb_to_loop (bb_eh_cleanup, current_loops->tree_root);
+
+ /* Make the new block an EH cleanup for the call. */
+ eh_region new_r = gen_eh_region_cleanup (NULL);
+ eh_landing_pad lp = gen_eh_landing_pad (new_r);
+ tree label = gimple_block_label (bb_eh_cleanup);
+ lp->post_landing_pad = label;
+ EH_LANDING_PAD_NR (label) = lp->index;
+ add_stmt_to_eh_lp (stmt, lp->index);
+
+ /* Add the cleanup code to the EH cleanup block. */
+ gsi = gsi_after_labels (bb_eh_cleanup);
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
+
+ /* And then propagate the exception further. */
+ gresx *resx = gimple_build_resx (new_r->index);
+ if (gimple_has_location (stmt))
+ gimple_set_location (resx, gimple_location (stmt));
+ gsi_insert_before (&gsi, resx, GSI_SAME_STMT);
+
+ /* Finally, wire the EH cleanup block into the CFG. */
+ make_eh_edges (stmt);
+ add_call_edges_for_seq (seq, single_pred_edge (bb_eh_cleanup)->count ());
+}
+
+/* Copy the attribute list at *ATTRS, minus any NAME attributes, leaving
+ shareable trailing nodes alone. */
+
+static inline void
+remove_named_attribute_unsharing (const char *name, tree *attrs)
+{
+ while (tree found = lookup_attribute (name, *attrs))
+ {
+ /* Copy nodes up to the next NAME attribute. */
+ while (*attrs != found)
+ {
+ *attrs = tree_cons (TREE_PURPOSE (*attrs),
+ TREE_VALUE (*attrs),
+ TREE_CHAIN (*attrs));
+ attrs = &TREE_CHAIN (*attrs);
+ }
+ /* Then drop it. */
+ gcc_checking_assert (*attrs == found);
+ *attrs = TREE_CHAIN (*attrs);
+ }
+}
+
+static int last_cgraph_order;
+
+static void
+ipa_strub_set_mode_for_new_functions ()
+{
+ if (symtab->order == last_cgraph_order)
+ return;
+
+ cgraph_node *node;
+
+ /* 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. */
+ for (int aliases = 0; aliases <= 1; aliases++)
+ FOR_EACH_FUNCTION (node)
+ {
+ if (!node->alias != !aliases)
+ continue;
+
+ /* Already done. */
+ if (node->order < last_cgraph_order)
+ continue;
+
+ set_strub_mode (node);
+ }
+
+ last_cgraph_order = symtab->order;
+}
+
+/* Return FALSE if NODE is a strub context, and TRUE otherwise. */
+bool
+strub_splittable_p (cgraph_node *node)
+{
+ switch (get_strub_mode (node))
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_AT_CALLS_OPT:
+ case STRUB_INLINABLE:
+ case STRUB_INTERNAL:
+ case STRUB_WRAPPER:
+ return false;
+
+ case STRUB_CALLABLE:
+ case STRUB_DISABLED:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ return true;
+}
+
+/* Return the PARM_DECL of the incoming watermark pointer, if there is one. */
+tree
+strub_watermark_parm (tree fndecl)
+{
+ switch (get_strub_mode_from_decl (fndecl))
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_AT_CALLS_OPT:
+ break;
+
+ case STRUB_INTERNAL:
+ case STRUB_WRAPPER:
+ case STRUB_CALLABLE:
+ case STRUB_DISABLED:
+ case STRUB_INLINABLE:
+ return NULL_TREE;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ for (tree parm = DECL_ARGUMENTS (fndecl); parm; parm = DECL_CHAIN (parm))
+ /* The type (variant) compare finds the parameter even in a just-created
+ clone, before we set its name, but the type-based compare doesn't work
+ during builtin expansion within the lto compiler, because we'll have
+ created a separate variant in that run. */
+ if (TREE_TYPE (parm) == pass_ipa_strub::get_qpwmt ()
+ || DECL_NAME (parm) == pass_ipa_strub::get_watermark_ptr ())
+ return parm;
+
+ gcc_unreachable ();
+}
+
+/* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
+ hasn't been added yet. Return the named argument count. */
+int
+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 already 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);
+ }
+
+ /* 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);
+
+#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 we're already within a strub context, pass on the incoming watermark
+ pointer, and omit the enter and leave calls around the modified call, as an
+ optimization, or as a means to satisfy a tail-call requirement. */
+ tree swmp = ((optimize_size || optimize > 2
+ || gimple_call_must_tail_p (ocall)
+ || (optimize == 2 && gimple_call_tail_p (ocall)))
+ ? strub_watermark_parm (e->caller->decl)
+ : NULL_TREE);
+ bool omit_own_watermark = swmp;
+ tree swm = NULL_TREE;
+ if (!omit_own_watermark)
+ {
+ swm = create_tmp_var (get_wmt (), ".strub.watermark");
+ TREE_ADDRESSABLE (swm) = true;
+ swmp = build1 (ADDR_EXPR, get_pwmt (), swm);
+
+ /* Initialize the watermark before the call. */
+ tree enter = get_enter ();
+ gcall *stptr = gimple_build_call (enter, 1,
+ unshare_expr (swmp));
+ if (gimple_has_location (ocall))
+ gimple_set_location (stptr, gimple_location (ocall));
+ 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)))
+ {
+ if (!swm)
+ swm = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (swmp)),
+ swmp,
+ build_int_cst (TREE_TYPE (swmp), 0));
+
+ vec<tree, va_gc> *inputs = NULL;
+ vec<tree, va_gc> *outputs = NULL;
+ vec_safe_push (outputs,
+ build_tree_list
+ (build_tree_list
+ (NULL_TREE, build_string (2, "=m")),
+ unshare_expr (swm)));
+ vec_safe_push (inputs,
+ build_tree_list
+ (build_tree_list
+ (NULL_TREE, build_string (1, "m")),
+ unshare_expr (swm)));
+ gasm *forcemod = gimple_build_asm_vec ("", inputs, outputs,
+ NULL, NULL);
+ gimple_seq_add_stmt (&seq, forcemod);
+
+ /* If the call will be assumed to not even read the watermark,
+ make sure it is already in memory before the call. */
+ if ((gimple_call_flags (wrcall) & ECF_CONST))
+ {
+ vec<tree, va_gc> *inputs = NULL;
+ vec_safe_push (inputs,
+ build_tree_list
+ (build_tree_list
+ (NULL_TREE, build_string (1, "m")),
+ unshare_expr (swm)));
+ gasm *force_store = gimple_build_asm_vec ("", inputs, NULL,
+ NULL, NULL);
+ if (gimple_has_location (wrcall))
+ gimple_set_location (force_store, gimple_location (wrcall));
+ gsi_insert_before (&gsi, force_store, GSI_SAME_STMT);
+ }
+ }
+#endif
+
+ if (!omit_own_watermark)
+ {
+ gcall *sleave = gimple_build_call (get_leave (), 1,
+ unshare_expr (swmp));
+ gimple_seq_add_stmt (&seq, sleave);
+
+ gassign *clobber = gimple_build_assign (swm,
+ build_clobber
+ (TREE_TYPE (swm)));
+ gimple_seq_add_stmt (&seq, clobber);
+ }
+
+ gsi_insert_finally_seq_after_call (gsi, seq);
+}
+
+void
+pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
+{
+ /* Adjust unknown-callee indirect calls with STRUB_AT_CALLS types within
+ onode. */
+ if (node->indirect_calls)
+ {
+ push_cfun (DECL_STRUCT_FUNCTION (node->decl));
+ for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
+ {
+ gcc_checking_assert (e->indirect_unknown_callee);
+
+ tree callee_fntype = gimple_call_fntype (e->call_stmt);
+ enum strub_mode callee_mode
+ = get_strub_mode_from_type (callee_fntype);
+
+ if (callee_mode != STRUB_AT_CALLS
+ && callee_mode != STRUB_AT_CALLS_OPT)
+ continue;
+
+ int named_args = adjust_at_calls_type (callee_fntype);
+
+ adjust_at_calls_call (e, named_args);
+ }
+ pop_cfun ();
+ }
+
+ if (node->callees)
+ {
+ push_cfun (DECL_STRUCT_FUNCTION (node->decl));
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ gcc_checking_assert (!e->indirect_unknown_callee);
+
+ enum strub_mode callee_mode = get_strub_mode (e->callee);
+
+ if (callee_mode != STRUB_AT_CALLS
+ && callee_mode != STRUB_AT_CALLS_OPT)
+ continue;
+
+ int named_args = adjust_at_calls_type (TREE_TYPE (e->callee->decl));
+
+ adjust_at_calls_call (e, named_args);
+ }
+ pop_cfun ();
+ }
+}
+
+unsigned int
+pass_ipa_strub_mode::execute (function *)
+{
+ last_cgraph_order = 0;
+ ipa_strub_set_mode_for_new_functions ();
+
+ /* Verify before any inlining or other transformations. */
+ verify_strub ();
+
+ return 0;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub_mode (gcc::context *ctxt)
+{
+ return new pass_ipa_strub_mode (ctxt);
+}
+
+unsigned int
+pass_ipa_strub::execute (function *)
+{
+ cgraph_node *onode;
+
+ 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
+ || mode == STRUB_AT_CALLS_OPT)
+ {
+ /* 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. */
+ bool aliased_parms = (onode->alias && *pargs
+ && DECL_CONTEXT (*pargs) != onode->decl);
+
+ 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_qpwmt ());
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_ARG_TYPE (wmptr) = get_qpwmt ();
+ DECL_CONTEXT (wmptr) = onode->decl;
+ TREE_USED (wmptr) = 1;
+ DECL_CHAIN (wmptr) = *pargs;
+ *pargs = wmptr;
+
+ 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));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca)
+ {
+ 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);
+
+ if (!is_gimple_call (stmt))
+ continue;
+
+ gcall *call = as_a <gcall *> (stmt);
+
+ if (gimple_alloca_call_p (call))
+ {
+ /* Capture stack growth. */
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)
+ ->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ }
+ }
+
+ pop_cfun ();
+
+#if 0
+ compute_fn_summary (onode, true);
+#endif
+ }
+ }
+
+ FOR_EACH_FUNCTION (onode)
+ {
+ if (!onode->has_gimple_body_p ())
+ continue;
+
+ enum strub_mode mode = get_strub_mode (onode);
+
+ if (mode != STRUB_INTERNAL)
+ {
+ adjust_at_calls_calls (onode);
+ 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);
+
+ vec<ipa_adjusted_param, va_gc> *nparms = NULL;
+ unsigned j = 0;
+ {
+ // The following loop copied from ipa-split.c:split_function.
+ for (tree parm = DECL_ARGUMENTS (onode->decl);
+ parm; parm = DECL_CHAIN (parm), j++)
+ {
+ ipa_adjusted_param adj = {};
+ adj.op = IPA_PARAM_OP_COPY;
+ adj.base_index = j;
+ adj.prev_clone_index = j;
+ vec_safe_push (nparms, adj);
+ }
+
+ if (apply_args)
+ {
+ ipa_adjusted_param aaadj = {};
+ aaadj.op = IPA_PARAM_OP_NEW;
+ aaadj.type = get_qptr ();
+ vec_safe_push (nparms, aaadj);
+ }
+
+ if (is_stdarg)
+ {
+ ipa_adjusted_param vladj = {};
+ vladj.op = IPA_PARAM_OP_NEW;
+ vladj.type = get_qpvalst ();
+ vec_safe_push (nparms, vladj);
+ }
+
+ ipa_adjusted_param wmadj = {};
+ wmadj.op = IPA_PARAM_OP_NEW;
+ wmadj.type = get_qpwmt ();
+ vec_safe_push (nparms, wmadj);
+ }
+ ipa_param_adjustments adj (nparms, -1, false);
+
+ cgraph_node *nnode = onode->create_version_clone_with_body
+ (auto_vec<cgraph_edge *> (0),
+ NULL, &adj, NULL, NULL, "strub", NULL);
+
+ if (!nnode)
+ {
+ error_at (DECL_SOURCE_LOCATION (onode->decl),
+ "failed to split %qD for %<strub%>",
+ onode->decl);
+ continue;
+ }
+
+ onode->split_part = true;
+ if (onode->calls_comdat_local)
+ nnode->add_to_same_comdat_group (onode);
+
+ gcc_checking_assert (!DECL_STRUCT_FUNCTION (nnode->decl)->stdarg);
+
+ set_strub_mode_to (onode, STRUB_WRAPPER);
+ set_strub_mode_to (nnode, STRUB_WRAPPED);
+
+ adjust_at_calls_calls (nnode);
+
+ /* Decide which of the wrapped function's parms we want to turn into
+ references to the argument passed to the wrapper. In general, we want to
+ copy small arguments, and avoid copying large ones. Variable-sized array
+ lengths given by other arguments, as in 20020210-1.c, would lead to
+ problems if passed by value, after resetting the original function and
+ dropping the length computation; passing them by reference works.
+ DECL_BY_REFERENCE is *not* a substitute for this: it involves copying
+ anyway, but performed at the caller. */
+ indirect_parms_t indirect_nparms (3, false);
+ unsigned adjust_ftype = 0;
+ unsigned named_args = 0;
+ for (tree parm = DECL_ARGUMENTS (onode->decl),
+ nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (TREE_TYPE (nnode->decl));
+ parm;
+ named_args++,
+ parm = DECL_CHAIN (parm),
+ nparm = DECL_CHAIN (nparm),
+ nparmt = nparmt ? TREE_CHAIN (nparmt) : NULL_TREE)
+ if (!(0 /* DECL_BY_REFERENCE (narg) */
+ || is_gimple_reg_type (TREE_TYPE (nparm))
+ || VECTOR_TYPE_P (TREE_TYPE (nparm))
+ || TREE_CODE (TREE_TYPE (nparm)) == COMPLEX_TYPE
+ || (tree_fits_uhwi_p (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ && (tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ <= 4 * UNITS_PER_WORD))))
+ {
+ indirect_nparms.add (nparm);
+
+ /* ??? Is there any case in which it is not safe to suggest the parms
+ turned indirect don't alias anything else? They are distinct,
+ unaliased memory in the wrapper, and the wrapped can't possibly
+ take pointers into them because none of the pointers passed to the
+ wrapper can alias other incoming parameters passed by value, even
+ if with transparent reference, and the wrapper doesn't take any
+ extra parms that could point into wrapper's parms. So we can
+ probably drop the TREE_ADDRESSABLE and keep the true. */
+ tree ref_type = build_ref_type_for (nparm,
+ true
+ || !TREE_ADDRESSABLE (parm));
+
+ DECL_ARG_TYPE (nparm) = TREE_TYPE (nparm) = ref_type;
+ relayout_decl (nparm);
+ TREE_ADDRESSABLE (nparm) = 0;
+ DECL_BY_REFERENCE (nparm) = 0;
+#if FOR_GCC_11P
+ DECL_NOT_GIMPLE_REG_P (nparm) = 0;
+#else
+ DECL_GIMPLE_REG_P (nparm) = 1;
+#endif
+ /* ??? This avoids mismatches in debug info bind stmts in
+ e.g. a-chahan . */
+ DECL_ABSTRACT_ORIGIN (nparm) = NULL;
+
+ if (nparmt)
+ adjust_ftype++;
+ }
+
+ /* Also adjust the wrapped function type, if needed. */
+ if (adjust_ftype)
+ {
+ tree nftype = TREE_TYPE (nnode->decl);
+
+ /* We always add at least one argument at the end of the signature, when
+ cloning the function, so we don't expect to need to duplicate the
+ type here. */
+ gcc_checking_assert (TYPE_ARG_TYPES (nftype)
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+
+#if HAVE_ATTR_FNSPEC
+ /* Check that fnspec still works for the modified function signature,
+ and drop it otherwise. */
+ bool drop_fnspec = false;
+ tree fnspec = lookup_attribute ("fn spec", TYPE_ATTRIBUTES (nftype));
+ attr_fnspec spec = fnspec ? attr_fnspec (fnspec) : attr_fnspec ("");
+
+ unsigned retcopy;
+ if (!(fnspec && spec.returns_arg (&retcopy)))
+ retcopy = (unsigned) -1;
+
+ unsigned i = 0;
+#endif
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (nftype);
+ adjust_ftype > 0;
+#if HAVE_ATTR_FNSPEC
+ i++,
+#endif
+ nparm = DECL_CHAIN (nparm), nparmt = TREE_CHAIN (nparmt))
+ if (indirect_nparms.contains (nparm))
+ {
+ TREE_VALUE (nparmt) = TREE_TYPE (nparm);
+ adjust_ftype--;
+
+#if HAVE_ATTR_FNSPEC
+ if (fnspec && !drop_fnspec)
+ {
+ if (i == retcopy)
+ drop_fnspec = true;
+ else if (spec.arg_specified_p (i))
+ {
+ /* Properties that apply to pointers only must not be
+ present, because we don't make pointers further
+ indirect. */
+ gcc_checking_assert
+ (!spec.arg_max_access_size_given_by_arg_p (i, NULL));
+ gcc_checking_assert (!spec.arg_copied_to_arg_p (i, NULL));
+
+ /* Any claim of direct access only is invalidated by
+ adding an indirection level. */
+ if (spec.arg_direct_p (i))
+ drop_fnspec = true;
+
+ /* If there's a claim the argument is not read from, the
+ added indirection invalidates it: if the argument is
+ used at all, then the pointer will necessarily be
+ read. */
+ if (!spec.arg_maybe_read_p (i)
+ && spec.arg_used_p (i))
+ drop_fnspec = true;
+ }
+ }
+#endif
+ }
+
+#if HAVE_ATTR_FNSPEC
+ /* ??? Maybe we could adjust it instead. */
+ if (drop_fnspec)
+ remove_named_attribute_unsharing ("fn spec",
+ &TYPE_ATTRIBUTES (nftype));
+#endif
+
+ TREE_TYPE (nnode->decl) = nftype;
+ }
+
+#if ATTR_FNSPEC_DECONST_WATERMARK
+ {
+ int flags = flags_from_decl_or_type (nnode->decl);
+ tree fnspec = lookup_attribute ("fn spec", TREE_TYPE (nnode->decl));
+
+ if ((flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS)) || fnspec)
+ {
+ size_t xargs = 1 + int (is_stdarg) + int (apply_args);
+ 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! */
+ bool no_writes_p = true;
+ if (fnspec)
+ {
+ tree fnspecstr = TREE_VALUE (TREE_VALUE (fnspec));
+ curlen = TREE_STRING_LENGTH (fnspecstr);
+ memcpy (nspec, TREE_STRING_POINTER (fnspecstr), curlen);
+ if (!(flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS))
+ && curlen >= 2
+ && nspec[1] != 'c' && nspec[1] != 'C'
+ && nspec[1] != 'p' && nspec[1] != 'P')
+ no_writes_p = false;
+ }
+ if (!curlen)
+ {
+ nspec[curlen++] = '.';
+ nspec[curlen++] = ((flags & ECF_CONST)
+ ? 'c'
+ : (flags & ECF_PURE)
+ ? 'p'
+ : ' ');
+ }
+ while (curlen < tgtlen - 2 * xargs)
+ {
+ nspec[curlen++] = '.';
+ nspec[curlen++] = ' ';
+ }
+
+ /* These extra args are unlikely to be present in const or pure
+ functions. It's conceivable that a function that takes variable
+ arguments, or that passes its arguments on to another function,
+ could be const or pure, but it would not modify the arguments, and,
+ being pure or const, it couldn't possibly modify or even access
+ memory referenced by them. But it can read from these internal
+ data structures created by the wrapper, and from any
+ argument-passing memory referenced by them, so we denote the
+ possibility of reading from multiple levels of indirection, but
+ only of reading because const/pure. */
+ if (apply_args)
+ {
+ nspec[curlen++] = 'r';
+ nspec[curlen++] = ' ';
+ }
+ if (is_stdarg)
+ {
+ nspec[curlen++] = (no_writes_p ? 'r' : '.');
+ nspec[curlen++] = (no_writes_p ? 't' : ' ');
+ }
+
+ nspec[curlen++] = 'W';
+ nspec[curlen++] = 't';
+
+ /* The type has already been copied before adding parameters. */
+ gcc_checking_assert (TYPE_ARG_TYPES (TREE_TYPE (nnode->decl))
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+ TYPE_ATTRIBUTES (TREE_TYPE (nnode->decl))
+ = tree_cons (get_identifier ("fn spec"),
+ build_tree_list (NULL_TREE,
+ build_string (tgtlen, nspec)),
+ TYPE_ATTRIBUTES (TREE_TYPE (nnode->decl)));
+ }
+ }
+#endif
+
+ {
+ tree decl = onode->decl;
+ cgraph_node *target = nnode;
+
+ { // copied from create_wrapper
+
+ /* Preserve DECL_RESULT so we get right by reference flag. */
+ tree decl_result = DECL_RESULT (decl);
+
+ /* Remove the function's body but keep arguments to be reused
+ for thunk. */
+ onode->release_body (true);
+ onode->reset ();
+
+ DECL_UNINLINABLE (decl) = false;
+ DECL_RESULT (decl) = decl_result;
+ DECL_INITIAL (decl) = NULL;
+ allocate_struct_function (decl, false);
+ set_cfun (NULL);
+
+ /* Turn alias into thunk and expand it into GIMPLE representation. */
+ onode->definition = true;
+
+#if FOR_GCC_11P
+ thunk_info::get_create (onode);
+ onode->thunk = true;
+#else
+ memset (&onode->thunk, 0, sizeof (cgraph_thunk_info));
+ onode->thunk.thunk_p = true;
+ onode->thunk.alias = target->decl;
+#endif
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (target, NULL, onode->count);
+#endif
+ onode->callees->can_throw_external = !TREE_NOTHROW (target->decl);
+
+ tree arguments = DECL_ARGUMENTS (decl);
+
+ while (arguments)
+ {
+ TREE_ADDRESSABLE (arguments) = false;
+ arguments = TREE_CHAIN (arguments);
+ }
+
+ {
+ tree alias = onode->callees->callee->decl;
+ tree thunk_fndecl = decl;
+ tree a;
+
+ int nxargs = 1 + is_stdarg + apply_args;
+
+ { // Simplified from expand_thunk.
+ tree restype;
+ basic_block bb, then_bb, else_bb, return_bb;
+ gimple_stmt_iterator bsi;
+ int nargs = 0;
+ tree arg;
+ int i;
+ tree resdecl;
+ tree restmp = NULL;
+
+ gcall *call;
+ greturn *ret;
+ bool alias_is_noreturn = TREE_THIS_VOLATILE (alias);
+
+ a = DECL_ARGUMENTS (thunk_fndecl);
+
+ current_function_decl = thunk_fndecl;
+
+#if FOR_GCC_11P
+ /* Ensure thunks are emitted in their correct sections. */
+ resolve_unique_section (thunk_fndecl, 0,
+ flag_function_sections);
+#endif
+
+ bitmap_obstack_initialize (NULL);
+
+ /* Build the return declaration for the function. */
+ restype = TREE_TYPE (TREE_TYPE (thunk_fndecl));
+ if (DECL_RESULT (thunk_fndecl) == NULL_TREE)
+ {
+ resdecl = build_decl (input_location, RESULT_DECL, 0, restype);
+ DECL_ARTIFICIAL (resdecl) = 1;
+ DECL_IGNORED_P (resdecl) = 1;
+ DECL_CONTEXT (resdecl) = thunk_fndecl;
+ DECL_RESULT (thunk_fndecl) = resdecl;
+ }
+ else
+ resdecl = DECL_RESULT (thunk_fndecl);
+
+ profile_count cfg_count = onode->count;
+ if (!cfg_count.initialized_p ())
+ cfg_count = profile_count::from_gcov_type (BB_FREQ_MAX).guessed_local ();
+
+ bb = then_bb = else_bb = return_bb
+ = init_lowered_empty_function (thunk_fndecl, true, cfg_count);
+
+ bsi = gsi_start_bb (bb);
+
+ /* Build call to the function being thunked. */
+ if (!VOID_TYPE_P (restype)
+ && (!alias_is_noreturn
+ || TREE_ADDRESSABLE (restype)
+ || TREE_CODE (TYPE_SIZE_UNIT (restype)) != INTEGER_CST))
+ {
+ if (DECL_BY_REFERENCE (resdecl))
+ {
+ restmp = gimple_fold_indirect_ref (resdecl);
+ if (!restmp)
+ restmp = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (resdecl)),
+ resdecl,
+ build_int_cst (TREE_TYPE (resdecl), 0));
+ }
+ else if (!is_gimple_reg_type (restype))
+ {
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl)))
+ {
+ restmp = resdecl;
+
+ if (VAR_P (restmp))
+ {
+ add_local_decl (cfun, restmp);
+ BLOCK_VARS (DECL_INITIAL (current_function_decl))
+ = restmp;
+ }
+ }
+ else
+ restmp = create_tmp_var (restype, "retval");
+ }
+ else
+ restmp = create_tmp_reg (restype, "retval");
+ }
+
+ for (arg = a; arg; arg = DECL_CHAIN (arg))
+ nargs++;
+ auto_vec<tree> vargs (nargs + nxargs);
+ i = 0;
+ arg = a;
+
+ if (nargs)
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl);
+ i < nargs;
+ i++, arg = DECL_CHAIN (arg), nparm = DECL_CHAIN (nparm))
+ {
+ tree save_arg = arg;
+ tree tmp = arg;
+
+ /* Arrange to pass indirectly the parms, if we decided to do
+ so, and revert its type in the wrapper. */
+ if (indirect_nparms.contains (nparm))
+ {
+ tree ref_type = TREE_TYPE (nparm);
+ TREE_ADDRESSABLE (arg) = true;
+ tree addr = build1 (ADDR_EXPR, ref_type, arg);
+ tmp = arg = addr;
+ }
+#if ! FOR_GCC_11P
+ else if (VECTOR_TYPE_P (TREE_TYPE (arg))
+ || TREE_CODE (TREE_TYPE (arg)) == COMPLEX_TYPE)
+ DECL_GIMPLE_REG_P (arg) = 1;
+#else
+ else
+ DECL_NOT_GIMPLE_REG_P (arg) = 0;
+#endif
+
+ /* Convert the argument back to the type used by the calling
+ conventions, e.g. a non-prototyped float type is passed as
+ double, as in 930603-1.c, and needs to be converted back to
+ double to be passed on unchanged to the wrapped
+ function. */
+ if (TREE_TYPE (nparm) != DECL_ARG_TYPE (nparm))
+ arg = fold_convert (DECL_ARG_TYPE (nparm), arg);
+
+ if (!is_gimple_val (arg))
+ {
+ tmp = create_tmp_reg (TYPE_MAIN_VARIANT
+ (TREE_TYPE (arg)), "arg");
+ gimple *stmt = gimple_build_assign (tmp, arg);
+ gsi_insert_after (&bsi, stmt, GSI_NEW_STMT);
+ }
+ vargs.quick_push (tmp);
+ arg = save_arg;
+ }
+ /* These strub arguments are adjusted later. */
+ if (apply_args)
+ vargs.quick_push (null_pointer_node);
+ if (is_stdarg)
+ vargs.quick_push (null_pointer_node);
+ vargs.quick_push (null_pointer_node);
+ call = gimple_build_call_vec (build_fold_addr_expr_loc (0, alias),
+ vargs);
+ onode->callees->call_stmt = call;
+ // gimple_call_set_from_thunk (call, true);
+ if (DECL_STATIC_CHAIN (alias))
+ {
+ tree p = DECL_STRUCT_FUNCTION (alias)->static_chain_decl;
+ tree type = TREE_TYPE (p);
+ tree decl = build_decl (DECL_SOURCE_LOCATION (thunk_fndecl),
+ PARM_DECL, create_tmp_var_name ("CHAIN"),
+ type);
+ DECL_ARTIFICIAL (decl) = 1;
+ DECL_IGNORED_P (decl) = 1;
+ TREE_USED (decl) = 1;
+ DECL_CONTEXT (decl) = thunk_fndecl;
+ DECL_ARG_TYPE (decl) = type;
+ TREE_READONLY (decl) = 1;
+
+ struct function *sf = DECL_STRUCT_FUNCTION (thunk_fndecl);
+ sf->static_chain_decl = decl;
+
+ gimple_call_set_chain (call, decl);
+ }
+
+ /* Return slot optimization is always possible and in fact required to
+ return values with DECL_BY_REFERENCE. */
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl))
+ && (!is_gimple_reg_type (TREE_TYPE (resdecl))
+ || DECL_BY_REFERENCE (resdecl)))
+ gimple_call_set_return_slot_opt (call, true);
+
+ if (restmp)
+ {
+ gimple_call_set_lhs (call, restmp);
+ gcc_assert (useless_type_conversion_p (TREE_TYPE (restmp),
+ TREE_TYPE (TREE_TYPE (alias))));
+ }
+ gsi_insert_after (&bsi, call, GSI_NEW_STMT);
+ if (!alias_is_noreturn)
+ {
+ /* Build return value. */
+ if (!DECL_BY_REFERENCE (resdecl))
+ ret = gimple_build_return (restmp);
+ else
+ ret = gimple_build_return (resdecl);
+
+ gsi_insert_after (&bsi, ret, GSI_NEW_STMT);
+ }
+ else
+ {
+ remove_edge (single_succ_edge (bb));
+ }
+
+ cfun->gimple_df->in_ssa_p = true;
+ update_max_bb_count ();
+ profile_status_for_fn (cfun)
+ = cfg_count.initialized_p () && cfg_count.ipa_p ()
+ ? PROFILE_READ : PROFILE_GUESSED;
+#if FOR_GCC_11P
+ /* FIXME: C++ FE should stop setting TREE_ASM_WRITTEN on thunks. */
+ // TREE_ASM_WRITTEN (thunk_fndecl) = false;
+#endif
+ delete_unreachable_blocks ();
+ update_ssa (TODO_update_ssa);
+ checking_verify_flow_info ();
+ free_dominance_info (CDI_DOMINATORS);
+
+ /* Since we want to emit the thunk, we explicitly mark its name as
+ referenced. */
+#if FOR_GCC_11P
+ onode->thunk = false;
+#else
+ onode->thunk.thunk_p = false;
+#endif
+ onode->lowered = true;
+ bitmap_obstack_release (NULL);
+ }
+ current_function_decl = NULL;
+ set_cfun (NULL);
+ }
+
+#if FOR_GCC_11P
+ thunk_info::remove (onode);
+#endif
+
+ // some more of create_wrapper at the end of the next block.
+ }
+ }
+
+ {
+ tree aaval = NULL_TREE;
+ tree vaptr = NULL_TREE;
+ tree wmptr = NULL_TREE;
+ for (tree arg = DECL_ARGUMENTS (nnode->decl); arg; arg = DECL_CHAIN (arg))
+ {
+ aaval = vaptr;
+ vaptr = wmptr;
+ wmptr = arg;
+ }
+
+ if (!apply_args)
+ aaval = NULL_TREE;
+ /* The trailing args are [apply_args], [va_list_ptr], and
+ watermark. If we don't have a va_list_ptr, the penultimate
+ argument is apply_args.
+ */
+ else if (!is_stdarg)
+ aaval = vaptr;
+
+ if (!is_stdarg)
+ vaptr = NULL_TREE;
+
+ DECL_NAME (wmptr) = get_watermark_ptr ();
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_IGNORED_P (wmptr) = 1;
+ TREE_USED (wmptr) = 1;
+
+ if (is_stdarg)
+ {
+ DECL_NAME (vaptr) = get_va_list_ptr ();
+ DECL_ARTIFICIAL (vaptr) = 1;
+ DECL_IGNORED_P (vaptr) = 1;
+ TREE_USED (vaptr) = 1;
+ }
+
+ if (apply_args)
+ {
+ DECL_NAME (aaval) = get_apply_args ();
+ DECL_ARTIFICIAL (aaval) = 1;
+ DECL_IGNORED_P (aaval) = 1;
+ TREE_USED (aaval) = 1;
+ }
+
+ push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ bool any_indirect = !indirect_nparms.is_empty ();
+
+ if (any_indirect)
+ {
+ 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);
+
+ 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
+ || is_stdarg || apply_args)
+ for (cgraph_edge *e = nnode->callees, *enext; e; e = enext)
+ {
+ gcall *call = e->call_stmt;
+ gimple_stmt_iterator gsi = gsi_for_stmt (call);
+ tree fndecl = e->callee->decl;
+
+ enext = e->next_callee;
+
+ if (gimple_alloca_call_p (call))
+ {
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ else if (fndecl && is_stdarg
+ && fndecl_built_in_p (fndecl, BUILT_IN_VA_START))
+ {
+ /* Using a non-default stdarg ABI makes the function ineligible
+ for internal strub. */
+ gcc_checking_assert (builtin_decl_explicit (BUILT_IN_VA_START)
+ == fndecl);
+ tree bvacopy = builtin_decl_explicit (BUILT_IN_VA_COPY);
+ gimple_call_set_fndecl (call, bvacopy);
+ tree arg = vaptr;
+ /* The va_copy source must be dereferenced, unless it's an array
+ type, that would have decayed to a pointer. */
+ if (TREE_CODE (TREE_TYPE (TREE_TYPE (vaptr))) != ARRAY_TYPE)
+ {
+ arg = gimple_fold_indirect_ref (vaptr);
+ if (!arg)
+ arg = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (vaptr)),
+ vaptr,
+ build_int_cst (TREE_TYPE (vaptr), 0));
+ }
+ gimple_call_set_arg (call, 1, arg);
+ update_stmt (call);
+ e->redirect_callee (cgraph_node::get_create (bvacopy));
+ }
+ else if (fndecl && apply_args
+ && fndecl_built_in_p (fndecl, BUILT_IN_APPLY_ARGS))
+ {
+ tree lhs = gimple_call_lhs (call);
+ gimple *assign = (lhs
+ ? gimple_build_assign (lhs, aaval)
+ : gimple_build_nop ());
+ gsi_replace (&gsi, assign, true);
+ cgraph_edge::remove (e);
+ }
+ }
+
+ { // a little more copied from create_wrapper
+
+ /* Inline summary set-up. */
+ nnode->analyze ();
+ // inline_analyze_function (nnode);
+ }
+
+ pop_cfun ();
+ }
+
+ {
+ 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));
+
+ 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));
+ gimple_set_location (stptr, gimple_location (wrcall));
+ gsi_insert_before (&gsi, stptr, GSI_SAME_STMT);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (enter),
+ stptr, gsi_bb (gsi)->count, false);
+#endif
+
+ int nargs = gimple_call_num_args (wrcall);
+
+ gimple_seq seq = NULL;
+
+ if (apply_args)
+ {
+ tree aalst = create_tmp_var (ptr_type_node, ".strub.apply_args");
+ tree bappargs = builtin_decl_explicit (BUILT_IN_APPLY_ARGS);
+ gcall *appargs = gimple_build_call (bappargs, 0);
+ gimple_call_set_lhs (appargs, aalst);
+ gimple_set_location (appargs, gimple_location (wrcall));
+ gsi_insert_before (&gsi, appargs, GSI_SAME_STMT);
+ gimple_call_set_arg (wrcall, nargs - 2 - is_stdarg, aalst);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bappargs),
+ appargs, gsi_bb (gsi)->count, false);
+#endif
+ }
+
+ if (is_stdarg)
+ {
+ tree valst = create_tmp_var (va_list_type_node, ".strub.va_list");
+ TREE_ADDRESSABLE (valst) = true;
+ tree vaptr = build1 (ADDR_EXPR,
+ build_pointer_type (va_list_type_node),
+ valst);
+ gimple_call_set_arg (wrcall, nargs - 2, unshare_expr (vaptr));
+
+ tree bvastart = builtin_decl_explicit (BUILT_IN_VA_START);
+ gcall *vastart = gimple_build_call (bvastart, 2,
+ unshare_expr (vaptr),
+ integer_zero_node);
+ gimple_set_location (vastart, gimple_location (wrcall));
+ gsi_insert_before (&gsi, vastart, GSI_SAME_STMT);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bvastart),
+ vastart, gsi_bb (gsi)->count, false);
+#endif
+
+ tree bvaend = builtin_decl_explicit (BUILT_IN_VA_END);
+ gcall *vaend = gimple_build_call (bvaend, 1, unshare_expr (vaptr));
+ gimple_set_location (vaend, gimple_location (wrcall));
+ gimple_seq_add_stmt (&seq, vaend);
+ }
+
+ gimple_call_set_arg (wrcall, nargs - 1, unshare_expr (swmp));
+ // gimple_call_set_tail (wrcall, false);
+ update_stmt (wrcall);
+
+ {
+#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);
+ gimple_set_location (force_store, gimple_location (wrcall));
+ 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);
+
+ /* For nnode, we don't rebuild edges because we wish to retain
+ any redirections copied to it from earlier passes, so we add
+ call graph edges explicitly there, but for onode, we create a
+ fresh function, so we may as well just issue the calls and
+ then rebuild all cgraph edges. */
+ // cgraph_edge::rebuild_edges ();
+ onode->analyze ();
+ // inline_analyze_function (onode);
+
+ 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;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub (gcc::context *ctxt)
+{
+ return new pass_ipa_strub (ctxt);
+}
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
new file mode 100644
index 00000000000..c335ab42097
--- /dev/null
+++ b/gcc/ipa-strub.h
@@ -0,0 +1,35 @@
+/* strub (stack scrubbing) infrastructure.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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 FALSE if NODE is a strub context, and TRUE otherwise. */
+extern bool strub_splittable_p (cgraph_node *node);
+
+/* Locate and return the watermark_ptr parameter for FNDECL. If FNDECL is not a
+ strub context, return NULL. */
+extern tree strub_watermark_parm (tree fndecl);
+
+/* Make a function type or declaration callable. */
+extern void strub_make_callable (tree fndecl);
diff --git a/gcc/passes.def b/gcc/passes.def
index d7a1f8c97a6..8e4638d20ed 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -53,6 +53,7 @@ along with GCC; see the file COPYING3. If not see
INSERT_PASSES_AFTER (all_small_ipa_passes)
NEXT_PASS (pass_ipa_free_lang_data);
NEXT_PASS (pass_ipa_function_and_variable_visibility);
+ NEXT_PASS (pass_ipa_strub_mode);
NEXT_PASS (pass_build_ssa_passes);
PUSH_INSERT_PASSES_WITHIN (pass_build_ssa_passes)
NEXT_PASS (pass_fixup_cfg);
@@ -111,6 +112,7 @@ along with GCC; see the file COPYING3. If not see
POP_INSERT_PASSES ()
NEXT_PASS (pass_ipa_remove_symbols);
+ NEXT_PASS (pass_ipa_strub);
NEXT_PASS (pass_ipa_oacc);
PUSH_INSERT_PASSES_WITHIN (pass_ipa_oacc)
NEXT_PASS (pass_ipa_pta);
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..7848c46d179
--- /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 "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
new file mode 100644
index 00000000000..85a8f76785e
--- /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" } */
+
+/* With -fno-inline, 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 "\[(\]call\[^\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..a2eedfd96b2
--- /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, 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 "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
new file mode 100644
index 00000000000..e5cb1f60541
--- /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 "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
new file mode 100644
index 00000000000..194aacc2c05
--- /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 "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
new file mode 100644
index 00000000000..46e84bf6560
--- /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 inline void
+__attribute__ ((__always_inline__))
+h() {
+}
+
+/* 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-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
new file mode 100644
index 00000000000..f180b17f30e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__ (3)))
+apply_function (void *args)
+{
+ __builtin_apply (0, args, 0);
+}
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+ void *args = __builtin_apply_args ();
+ apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
new file mode 100644
index 00000000000..379a54b73b7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+extern void __attribute__ ((__strub__))
+apply_function (void *args);
+
+void __attribute__ ((__strub__))
+apply_args (int i, int j, double d) /* { dg-error "selected" } */
+{
+ void *args = __builtin_apply_args (); /* { dg-message "does not support" } */
+ apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
new file mode 100644
index 00000000000..9b4786be698
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__))
+apply_function (void *args)
+{
+ __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
new file mode 100644
index 00000000000..409f747743e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+
+/* Check that implicit enabling of strub mode selects internal strub when the
+ function uses __builtin_apply_args, that prevents the optimization to
+ at-calls mode. */
+
+int __attribute__ ((__strub__)) var;
+
+static inline void
+apply_args (int i, int j, double d)
+{
+ var++;
+ __builtin_apply_args ();
+}
+
+void f() {
+ apply_args (1, 2, 3);
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
new file mode 100644
index 00000000000..d964b07ae5d
--- /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 inline void
+__attribute__ ((__always_inline__))
+h() {
+}
+
+/* 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..79d00cedb9a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-default1.c
@@ -0,0 +1,42 @@
+/* { 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-default2.c b/gcc/testsuite/c-c++-common/strub-default2.c
new file mode 100644
index 00000000000..487253e9227
--- /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-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
new file mode 100644
index 00000000000..7b04eea35d9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O1" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+ the strubbing to its caller at -O1. */
+
+#include "strub-defer-O2.c"
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
new file mode 100644
index 00000000000..67d96419a5e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -0,0 +1,8 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O2" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+ the strubbing to its caller at -O2. */
+
+#define EXPECT_DEFERRAL !
+#include "strub-defer-O3.c"
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
new file mode 100644
index 00000000000..34828d2711e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -0,0 +1,93 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O3" } */
+
+/* Check that a strub function called by another strub function defers the
+ strubbing to its caller at -O3. */
+
+#ifndef EXPECT_DEFERRAL
+# define EXPECT_DEFERRAL
+#endif
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+ /* We use this variable to avoid any stack red zone. Stack scrubbing covers
+ it, but __builtin_stack_address, that we take as a reference, doesn't, so
+ if e.g. callable() were to store the string in the red zone, we wouldn't
+ find it because it would be outside the range we searched. */
+ typedef void __attribute__ ((__strub__ (3))) callable_t (char *);
+ callable_t *f = 0;
+
+ char s[sizeof (test_string)];
+ __builtin_strcpy (s, test_string);
+ asm ("" : "+m" (s), "+r" (f));
+
+ if (__builtin_expect (!f, 1))
+ return (char*)__builtin_stack_address ();
+
+ f (s);
+ return 0;
+}
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+int
+look_for_string (char *e)
+{
+ char *p = (char*)__builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+char *
+at_calls ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+deferred_at_calls ()
+{
+ char *ret = at_calls ();
+ if (EXPECT_DEFERRAL !look_for_string (ret))
+ __builtin_abort ();
+ return ret;
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+deferred_internal ()
+{
+ char *ret = at_calls ();
+ if (EXPECT_DEFERRAL !look_for_string (ret))
+ __builtin_abort ();
+ return ret;
+}
+
+int main ()
+{
+ if (look_for_string (deferred_at_calls ()))
+ __builtin_abort ();
+ if (look_for_string (deferred_internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
new file mode 100644
index 00000000000..b273660aea1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -Os" } */
+
+/* Check that a strub function called by another strub function defers the
+ strubbing to its caller at -Os. */
+
+#include "strub-defer-O3.c"
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
new file mode 100644
index 00000000000..a74658c9ac9
--- /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 inline void
+__attribute__ ((__always_inline__))
+h() {
+}
+
+/* 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/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
new file mode 100644
index 00000000000..7e22a266ad9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (2)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+ int x[128];
+};
+
+void __attribute__ ((__strub__ (2)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\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)))
+std_arg (int i, ...)
+{
+ va_list vl;
+ va_start (vl, i);
+ va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+ __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
new file mode 100644
index 00000000000..2d8036c0fbc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (1)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*small_args\[^ \]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+
+struct large_arg {
+ int x[128];
+};
+
+void __attribute__ ((__strub__ (1)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ (1)))
+std_arg (int i, ...)
+{
+ va_list vl;
+ va_start (vl, i);
+ va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*std_arg\[^ \]* \[(\]int i, void \\* &\[^&,\]*.strub.watermark_ptr\[, .]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-not "va_copy \\(" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
new file mode 100644
index 00000000000..f64361f1235
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -0,0 +1,58 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that uses of a strub variable implicitly enables internal strub for
+ publicly-visible functions, and causes the same transformations to their
+ signatures as those in strub-parms1.c. */
+
+#include <stdarg.h>
+
+int __attribute__ ((__strub__)) var;
+
+void
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+ var++;
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+ int x[128];
+};
+
+void
+large_byref_arg (struct large_arg la)
+{
+ var++;
+}
+
+/* { dg-final { scan-ipa-dump "\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
+std_arg (int i, ...)
+{
+ va_list vl;
+ va_start (vl, i);
+ var++;
+ va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void
+apply_args (int i, int j, double d)
+{
+ var++;
+ __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
new file mode 100644
index 00000000000..a4226ce0119
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=default -fexceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 89 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
new file mode 100644
index 00000000000..3bab553478b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
new file mode 100644
index 00000000000..c89cc7c2c47
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
new file mode 100644
index 00000000000..b869fafb691
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
new file mode 100644
index 00000000000..1d3dd2f2c2c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=default -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
+ watermark. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 15 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 15 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
new file mode 100644
index 00000000000..4dd4281b03b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-Os -fstrub=default -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
+ watermark. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 15 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 15 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
new file mode 100644
index 00000000000..0840dddd136
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+#include "strub-tail-O2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
new file mode 100644
index 00000000000..9330d6ff4c1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued.
+ Tail calls are short-circuited at -O2+. */
+
+int __attribute__ ((__strub__))
+g (int i, int j) {
+ return g (i, j);
+}
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 0 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 0 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
new file mode 100644
index 00000000000..5b33ff1f530
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -0,0 +1,9 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that strub and non-strub functions can be called from non-strub
+ contexts, and that strub and callable functions can be called from strub
+ contexts. */
+
+#define OMIT_IMPERMISSIBLE_CALLS 1
+#include "strub-callable2.c"
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
new file mode 100644
index 00000000000..38935e3270b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -0,0 +1,264 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that impermissible (cross-strub-context) calls are reported. */
+
+extern int __attribute__ ((__strub__ (3))) xcallable (void);
+extern int __attribute__ ((__strub__ (2))) xinternal (void);
+extern int __attribute__ ((__strub__ (1))) xat_calls (void);
+extern int __attribute__ ((__strub__ (0))) xdisabled (void);
+
+int __attribute__ ((__strub__ (3))) callable (void);
+int __attribute__ ((__strub__ (2))) internal (void);
+int __attribute__ ((__strub__ (1))) at_calls (void);
+int __attribute__ ((__strub__ (0))) disabled (void);
+
+int __attribute__ ((__strub__)) var;
+int var_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+icallable (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+iinternal (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+iat_calls (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+idisabled (void);
+static inline int __attribute__ ((__always_inline__))
+ivar_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+i_callable (void) { return 0; }
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+i_internal (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+i_at_calls (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+i_disabled (void) { return 0; }
+static inline int __attribute__ ((__always_inline__))
+i_var_user (void) { return var; }
+
+#define CALLS_GOOD_FOR_STRUB_CONTEXT(ISEP) \
+ do { \
+ ret += i ## ISEP ## at_calls (); \
+ ret += i ## ISEP ## internal (); \
+ ret += i ## ISEP ## var_user (); \
+ } while (0)
+
+#define CALLS_GOOD_FOR_NONSTRUB_CONTEXT(ISEP) \
+ do { \
+ ret += internal (); \
+ ret += disabled (); \
+ ret += var_user (); \
+ \
+ ret += i ## ISEP ## disabled (); \
+ \
+ ret += xinternal (); \
+ ret += xdisabled (); \
+ } while (0)
+
+#define CALLS_GOOD_FOR_EITHER_CONTEXT(ISEP) \
+ do { \
+ ret += i ## ISEP ## callable (); \
+ \
+ ret += callable (); \
+ ret += at_calls (); \
+ \
+ ret += xat_calls (); \
+ ret += xcallable (); \
+ } while (0)
+
+/* Not a strub context, so it can call anything.
+ Explicitly declared as callable even from within strub contexts. */
+int __attribute__ ((__strub__ (3)))
+callable (void) {
+ int ret = 0;
+
+ /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+ ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+ ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+ return ret;
+}
+
+/* Internal strubbing means the body is a strub context, so it can only call
+ strub functions, and it's not itself callable from strub functions. */
+int __attribute__ ((__strub__ (2)))
+internal (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT();
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int __attribute__ ((__strub__ (1)))
+at_calls (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT();
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int __attribute__ ((__strub__ (0)))
+disabled () {
+ int ret = 0;
+
+ /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+ ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+ ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+ return ret;
+}
+
+int
+var_user (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT();
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int
+icallable (void)
+{
+ int ret = 0;
+
+ /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+ ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+ ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+ return ret;
+}
+
+int
+iinternal (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int
+iat_calls (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int
+idisabled () {
+ int ret = 0;
+
+ /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+ ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+ ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+ return ret;
+}
+
+int
+ivar_user (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
new file mode 100644
index 00000000000..100fb0c59a9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const function call, we issue an asm statement
+ to make sure the watermark passed to it is held in memory before the call,
+ and another to make sure it is not assumed to be unchanged. */
+
+int __attribute__ ((__strub__, __const__))
+f() {
+ return 0;
+}
+
+int
+g() {
+ return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
new file mode 100644
index 00000000000..9e818ac9748
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const function call, we issue an
+ asm statement to make sure the watermark passed to it is held in memory
+ before the call, and another to make sure it is not assumed to be
+ unchanged. */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+ return 0;
+}
+
+int
+g() {
+ return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
new file mode 100644
index 00000000000..d40e8aa45cb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const wrapping call, we issue an asm statement
+ to make sure the watermark passed to it is held in memory before the call,
+ and another to make sure it is not assumed to be unchanged. */
+
+int __attribute__ ((__strub__ (2), __const__))
+f() {
+ return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
new file mode 100644
index 00000000000..d4cbdaf10f3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const wrapping call, we issue an
+ asm statement to make sure the watermark passed to it is held in memory
+ before the call, and another to make sure it is not assumed to be
+ unchanged. */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+ return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-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..69f3d65ed44
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -Werror" } */
+
+typedef int __attribute__ ((__strub__)) strub_int;
+strub_int *ptr;
+
+int *f () {
+ return ptr; /* { dg-error "incompatible" } */
+}
+
+strub_int *g () {
+ return f (); /* { dg-error "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/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
new file mode 100644
index 00000000000..cb223da6efc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure function call, we issue an asm statement
+ to make sure the watermark passed to it is not assumed to be unchanged. */
+
+int __attribute__ ((__strub__, __pure__))
+f() {
+ static int i; /* Stop it from being detected as const. */
+ return i;
+}
+
+int
+g() {
+ return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
new file mode 100644
index 00000000000..67d1434b1f8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure function call, we issue an asm
+ statement to make sure the watermark passed to it is not assumed to be
+ unchanged. */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run. */
+__attribute__ ((__pure__))
+#endif
+f() {
+ static int i; /* Stop it from being detected as const. */
+ return i;
+}
+
+int
+g() {
+ return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
new file mode 100644
index 00000000000..59f02ea901f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure wrapping call, we issue an asm statement
+ to make sure the watermark passed to it is not assumed to be unchanged. */
+
+int __attribute__ ((__strub__ (2), __pure__))
+f() {
+ static int i; /* Stop it from being detected as const. */
+ return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
new file mode 100644
index 00000000000..973e909217d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure wrapping call, we issue an asm
+ statement to make sure the watermark passed to it is not assumed to be
+ unchanged. */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run. */
+__attribute__ ((__pure__))
+#endif
+f() {
+ static int i; /* Stop it from being detected as const. */
+ return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
new file mode 100644
index 00000000000..a4077c35a60
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -0,0 +1,85 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+ equivalent strub functions don't. */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+ /* We use this variable to avoid any stack red zone. Stack scrubbing covers
+ it, but __builtin_stack_address, that we take as a reference, doesn't, so
+ if e.g. callable() were to store the string in the red zone, we wouldn't
+ find it because it would be outside the range we searched. */
+ typedef void __attribute__ ((__strub__ (3))) callable_t (char *);
+ callable_t *f = 0;
+
+ char s[sizeof (test_string)];
+ __builtin_strcpy (s, test_string);
+ asm ("" : "+m" (s), "+r" (f));
+
+ if (__builtin_expect (!f, 1))
+ return (char *) __builtin_stack_address ();
+
+ f (s);
+ return 0;
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+ char *p = (char *) __builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+ return leak_string ();
+}
+
+int main ()
+{
+ if (!look_for_string (callable ()))
+ __builtin_abort ();
+ if (look_for_string (at_calls ()))
+ __builtin_abort ();
+ if (look_for_string (internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
new file mode 100644
index 00000000000..94e4156ea73
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+ equivalent strub functions don't. */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+ int len = sizeof (test_string);
+ asm ("" : "+rm" (len));
+ char s[len];
+ __builtin_strcpy (s, test_string);
+ asm ("" : "+m" (s));
+ return (char *) __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+ char *p = (char *) __builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+ return leak_string ();
+}
+
+int main ()
+{
+ if (!look_for_string (callable ()))
+ __builtin_abort ();
+ if (look_for_string (at_calls ()))
+ __builtin_abort ();
+ if (look_for_string (internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
new file mode 100644
index 00000000000..0ca74beb59d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+/* { dg-require-effective-target alloca } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+ equivalent strub functions don't. */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+ int len = sizeof (test_string);
+ char *s = (char *) __builtin_alloca (len);
+ __builtin_strcpy (s, test_string);
+ asm ("" : "+m" (s));
+ return (char *) __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+ char *p = (char *) __builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+ return leak_string ();
+}
+
+int main ()
+{
+ if (!look_for_string (callable ()))
+ __builtin_abort ();
+ if (look_for_string (at_calls ()))
+ __builtin_abort ();
+ if (look_for_string (internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
new file mode 100644
index 00000000000..4ab11c0682e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -0,0 +1,101 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=all" } */
+/* { dg-require-effective-target alloca } */
+
+/* Check that multi-level, multi-inlined functions still get cleaned up as
+ expected, without overwriting temporary stack allocations while they should
+ still be available. */
+
+#ifndef ATTR_STRUB_AT_CALLS
+# define ATTR_STRUB_AT_CALLS /* Defined in strub-run4d.c. */
+#endif
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__))
+char *
+leak_string (void)
+{
+ int __attribute__ ((__strub__)) len = 512;
+ asm ("" : "+r" (len));
+ char s[len];
+ __builtin_strcpy (s, test_string);
+ __builtin_strcpy (s + len - sizeof (test_string), test_string);
+ asm ("" : "+m" (s));
+ return (char *) __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+ char *p = (char *) __builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static inline ATTR_STRUB_AT_CALLS
+char *
+innermost ()
+{
+ int __attribute__ ((__strub__)) len = 512;
+ asm ("" : "+r" (len));
+ char s[len];
+ __builtin_strcpy (s, test_string);
+ __builtin_strcpy (s + len - sizeof (test_string), test_string);
+ asm ("" : "+m" (s));
+ char *ret = leak_string ();
+ if (__builtin_strcmp (s, test_string) != 0)
+ __builtin_abort ();
+ if (__builtin_strcmp (s + len - sizeof (test_string), test_string) != 0)
+ __builtin_abort ();
+ return ret;
+}
+
+static inline ATTR_STRUB_AT_CALLS
+char *
+intermediate ()
+{
+ int __attribute__ ((__strub__)) len = 512;
+ asm ("" : "+r" (len));
+ char s[len];
+ __builtin_strcpy (s, test_string);
+ __builtin_strcpy (s + len - sizeof (test_string), test_string);
+ asm ("" : "+m" (s));
+ char *ret = innermost ();
+ if (__builtin_strcmp (s, test_string) != 0)
+ __builtin_abort ();
+ if (__builtin_strcmp (s + len - sizeof (test_string), test_string) != 0)
+ __builtin_abort ();
+ return ret;
+}
+
+static inline __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+ return intermediate ();
+}
+
+int __attribute__ ((strub (0)))
+main ()
+{
+ if (look_for_string (internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4c.c b/gcc/testsuite/c-c++-common/torture/strub-run4c.c
new file mode 100644
index 00000000000..57f9baf758d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4c.c
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=at-calls" } */
+/* { dg-require-effective-target alloca } */
+
+#include "strub-run4.c"
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4d.c b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
new file mode 100644
index 00000000000..e4f7445607c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+/* { dg-require-effective-target alloca } */
+
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ (1)))
+
+#include "strub-run4.c"
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4i.c b/gcc/testsuite/c-c++-common/torture/strub-run4i.c
new file mode 100644
index 00000000000..459f6886c54
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4i.c
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=internal" } */
+/* { dg-require-effective-target alloca } */
+
+#include "strub-run4.c"
diff --git a/gcc/testsuite/g++.dg/strub-run1.C b/gcc/testsuite/g++.dg/strub-run1.C
new file mode 100644
index 00000000000..0d367fb83d0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/strub-run1.C
@@ -0,0 +1,19 @@
+// { dg-do run }
+// { dg-options "-fstrub=internal" }
+
+// Check that we don't get extra copies.
+
+struct T {
+ T &self;
+ void check () const { if (&self != this) __builtin_abort (); }
+ T() : self (*this) { check (); }
+ T(const T& ck) : self (*this) { ck.check (); check (); }
+ ~T() { check (); }
+};
+
+T foo (T q) { q.check (); return T(); }
+T bar (T p) { p.check (); return foo (p); }
+
+int main () {
+ bar (T()).check ();
+}
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" } } */
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
new file mode 100644
index 00000000000..697ac9de764
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -0,0 +1,21 @@
+-- { dg-do compile }
+-- { dg-options "-fstrub=default -fdump-ipa-strubm" }
+
+package body Strub_Attr 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 (X);
+end Strub_Attr;
+
+-- { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]2\[)\]\[)\]" 2 "strubm" } }
+-- { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
diff --git a/gcc/testsuite/gnat.dg/strub_attr.ads b/gcc/testsuite/gnat.dg/strub_attr.ads
new file mode 100644
index 00000000000..a94c23bf418
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_attr.ads
@@ -0,0 +1,12 @@
+package Strub_Attr 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;
+end Strub_Attr;
diff --git a/gcc/tree-cfg.c b/gcc/tree-cfg.c
index 61b53913e0b..9b01f2bb01b 100644
--- a/gcc/tree-cfg.c
+++ b/gcc/tree-cfg.c
@@ -5620,6 +5620,7 @@ gimple_verify_flow_info (void)
{
gimple *stmt = gsi_stmt (gsi);
+ /* Do NOT disregard debug stmts after found_ctrl_stmt. */
if (found_ctrl_stmt)
{
error ("control flow in the middle of basic block %d",
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index 83941bc0cee..df4abdf1449 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -497,8 +497,9 @@ extern gimple_opt_pass *make_pass_adjust_alignment (gcc::context *ctxt);
/* IPA Passes */
extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt);
-extern simple_ipa_opt_pass
- *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub_mode (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_tree_profile (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_auto_profile (gcc::context *ctxt);
diff --git a/gcc/tree-ssa-ccp.c b/gcc/tree-ssa-ccp.c
index f4a99aca4b4..84815c2977c 100644
--- a/gcc/tree-ssa-ccp.c
+++ b/gcc/tree-ssa-ccp.c
@@ -3049,7 +3049,9 @@ optimize_stack_restore (gimple_stmt_iterator i)
if (!callee
|| !fndecl_built_in_p (callee, BUILT_IN_NORMAL)
/* All regular builtins are ok, just obviously not alloca. */
- || ALLOCA_FUNCTION_CODE_P (DECL_FUNCTION_CODE (callee)))
+ || ALLOCA_FUNCTION_CODE_P (DECL_FUNCTION_CODE (callee))
+ /* Do not remove stack updates before strub leave. */
+ || fndecl_built_in_p (callee, BUILT_IN___STRUB_LEAVE))
return NULL_TREE;
if (fndecl_built_in_p (callee, BUILT_IN_STACK_RESTORE))
diff --git a/libgcc/Makefile.in b/libgcc/Makefile.in
index 7ec97584554..c4ee6701cb3 100644
--- a/libgcc/Makefile.in
+++ b/libgcc/Makefile.in
@@ -428,7 +428,7 @@ ifneq ($(enable_shared),yes)
iterator = $(patsubst %,$(srcdir)/static-object.mk,$(iter-items))
endif
-LIB2ADD += enable-execute-stack.c
+LIB2ADD += enable-execute-stack.c $(srcdir)/strub.c
# While emutls.c has nothing to do with EH, it is in LIB2ADDEH*
# instead of LIB2ADD because that's the way to be sure on some targets
diff --git a/libgcc/libgcc2.h b/libgcc/libgcc2.h
index 1819ff3ac3d..857091e65c8 100644
--- a/libgcc/libgcc2.h
+++ b/libgcc/libgcc2.h
@@ -532,6 +532,10 @@ extern int __parityDI2 (UDWtype);
extern void __enable_execute_stack (void *);
+extern void __strub_enter (void **);
+extern void __strub_update (void**);
+extern void __strub_leave (void **);
+
#ifndef HIDE_EXPORTS
#pragma GCC visibility pop
#endif
diff --git a/libgcc/strub.c b/libgcc/strub.c
new file mode 100644
index 00000000000..fd6e27556e4
--- /dev/null
+++ b/libgcc/strub.c
@@ -0,0 +1,112 @@
+/* Stack scrubbing infrastructure
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+Under Section 7 of GPL version 3, you are granted additional
+permissions described in the GCC Runtime Library Exception, version
+3.1, as published by the Free Software Foundation.
+
+You should have received a copy of the GNU General Public License and
+a copy of the GCC Runtime Library Exception along with this program;
+see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
+<http://www.gnu.org/licenses/>. */
+
+#include "tconfig.h"
+#include "tsystem.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "libgcc_tm.h"
+#include "libgcc2.h"
+
+#ifndef STACK_GROWS_DOWNWARD
+# define TOPS >
+#else
+# define TOPS <
+#endif
+
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+
+/* Enter a stack scrubbing context, initializing the watermark to the caller's
+ stack address. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_enter (void **watermark)
+{
+ *watermark = __builtin_frame_address (0);
+}
+
+/* Update the watermark within a stack scrubbing context with the current stack
+ pointer. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_update (void **watermark)
+{
+ void *sp = __builtin_frame_address (0);
+
+ if (sp TOPS *watermark)
+ *watermark = sp;
+}
+
+#ifndef TARGET_STRUB_USE_DYNAMIC_ARRAY
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+#endif
+
+#ifndef TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+# ifdef TARGET_STRUB_MAY_USE_MEMSET
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+# else
+# define TARGET_STRUB_MAY_USE_MEMSET 1
+# endif
+#endif
+
+/* Leave a stack scrubbing context, restoring and updating SAVED, and
+ clearing the stack between top and watermark. */
+void ATTRIBUTE_STRUB_CALLABLE
+#if ! TARGET_STRUB_MAY_USE_MEMSET
+__attribute__ ((__optimize__ ("-fno-tree-loop-distribute-patterns")))
+#endif
+__strub_leave (void **mark)
+{
+ void *sp = __builtin_stack_address ();
+
+ void **base, **end;
+#ifndef STACK_GROWS_DOWNWARD
+ base = sp;
+ end = *mark;
+#else
+ base = *mark;
+ end = sp;
+#endif
+
+ ptrdiff_t len = end - base;
+ if (len <= 0)
+ return;
+
+#if ! TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+ /* Allocate a dynamically-sized array covering the desired range, so that we
+ can safely call memset on it. */
+ void *ptr[len];
+ base = &ptr[0];
+ end = &ptr[len];
+#else
+ void **ptr = end;
+#endif /* TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY */
+
+ /* ldist turns this into a memset. Without the dynamic array above, that call
+ is likely unsafe: possibly tail-called, and likely scribbling over its own
+ stack frame. */
+ while (base < end)
+ *base++ = 0;
+
+ asm ("" : : "m" (ptr));
+}
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/strub)] strub with function and variable attributes
@ 2021-08-28 4:13 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2021-08-28 4:13 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:b9b1ef7258853012fc4935ed75be1646250245ce
commit b9b1ef7258853012fc4935ed75be1646250245ce
Author: Alexandre Oliva <oliva@adacore.com>
Date: Fri Aug 20 02:20:52 2021 -0300
strub with function and variable attributes
still missing tests exercising dispatch/virtual calls
Diff:
---
gcc/Makefile.in | 1 +
gcc/ada/gcc-interface/trans.c | 5 +
gcc/ada/gcc-interface/utils.c | 124 +
gcc/ada/libgnat/a-except.adb | 90 +
gcc/ada/libgnat/a-except.ads | 8 +
gcc/ada/libgnat/s-arit128.ads | 6 +
gcc/ada/libgnat/s-arit64.ads | 6 +
gcc/ada/libgnat/s-secsta.ads | 10 +
gcc/builtins.c | 273 ++
gcc/builtins.def | 4 +
gcc/c-family/c-attribs.c | 114 +
gcc/common.opt | 24 +
gcc/doc/extend.texi | 86 +
gcc/doc/invoke.texi | 42 +
gcc/ipa-inline.c | 6 +
gcc/ipa-split.c | 7 +
gcc/ipa-strub.c | 2999 ++++++++++++++++++++
gcc/ipa-strub.h | 35 +
gcc/passes.def | 2 +
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-apply1.c | 15 +
gcc/testsuite/c-c++-common/strub-apply2.c | 12 +
gcc/testsuite/c-c++-common/strub-apply3.c | 8 +
gcc/testsuite/c-c++-common/strub-apply4.c | 21 +
gcc/testsuite/c-c++-common/strub-at-calls1.c | 30 +
gcc/testsuite/c-c++-common/strub-at-calls2.c | 23 +
gcc/testsuite/c-c++-common/strub-default1.c | 42 +
gcc/testsuite/c-c++-common/strub-default2.c | 29 +
gcc/testsuite/c-c++-common/strub-defer-O1.c | 7 +
gcc/testsuite/c-c++-common/strub-defer-O2.c | 8 +
gcc/testsuite/c-c++-common/strub-defer-O3.c | 93 +
gcc/testsuite/c-c++-common/strub-defer-Os.c | 7 +
gcc/testsuite/c-c++-common/strub-internal1.c | 31 +
gcc/testsuite/c-c++-common/strub-internal2.c | 21 +
gcc/testsuite/c-c++-common/strub-parms1.c | 48 +
gcc/testsuite/c-c++-common/strub-parms2.c | 36 +
gcc/testsuite/c-c++-common/strub-parms3.c | 58 +
gcc/testsuite/c-c++-common/strub-short-O0-exc.c | 10 +
gcc/testsuite/c-c++-common/strub-short-O0.c | 10 +
gcc/testsuite/c-c++-common/strub-short-O1.c | 10 +
gcc/testsuite/c-c++-common/strub-short-O2.c | 10 +
gcc/testsuite/c-c++-common/strub-short-O3.c | 12 +
gcc/testsuite/c-c++-common/strub-short-Os.c | 12 +
gcc/testsuite/c-c++-common/strub-tail-O1.c | 8 +
gcc/testsuite/c-c++-common/strub-tail-O2.c | 14 +
.../c-c++-common/torture/strub-callable1.c | 9 +
.../c-c++-common/torture/strub-callable2.c | 264 ++
gcc/testsuite/c-c++-common/torture/strub-const1.c | 18 +
gcc/testsuite/c-c++-common/torture/strub-const2.c | 22 +
gcc/testsuite/c-c++-common/torture/strub-const3.c | 13 +
gcc/testsuite/c-c++-common/torture/strub-const4.c | 17 +
gcc/testsuite/c-c++-common/torture/strub-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 | 13 +
.../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/c-c++-common/torture/strub-pure1.c | 18 +
gcc/testsuite/c-c++-common/torture/strub-pure2.c | 22 +
gcc/testsuite/c-c++-common/torture/strub-pure3.c | 13 +
gcc/testsuite/c-c++-common/torture/strub-pure4.c | 17 +
gcc/testsuite/c-c++-common/torture/strub-run1.c | 85 +
gcc/testsuite/c-c++-common/torture/strub-run2.c | 75 +
gcc/testsuite/c-c++-common/torture/strub-run3.c | 75 +
gcc/testsuite/c-c++-common/torture/strub-run4.c | 101 +
gcc/testsuite/c-c++-common/torture/strub-run4c.c | 5 +
gcc/testsuite/c-c++-common/torture/strub-run4d.c | 7 +
gcc/testsuite/c-c++-common/torture/strub-run4i.c | 5 +
gcc/testsuite/g++.dg/strub-run1.C | 19 +
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 +
gcc/testsuite/gnat.dg/strub_attr.adb | 21 +
gcc/testsuite/gnat.dg/strub_attr.ads | 12 +
gcc/tree-cfg.c | 1 +
gcc/tree-pass.h | 5 +-
gcc/tree-ssa-ccp.c | 4 +-
libgcc/Makefile.in | 2 +-
libgcc/libgcc2.h | 4 +
libgcc/strub.c | 112 +
91 files changed, 5686 insertions(+), 4 deletions(-)
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 9714fcaac37..94097ec78d7 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1471,6 +1471,7 @@ OBJS = \
ipa-reference.o \
ipa-ref.o \
ipa-utils.o \
+ ipa-strub.o \
ipa.o \
ira.o \
ira-build.o \
diff --git a/gcc/ada/gcc-interface/trans.c b/gcc/ada/gcc-interface/trans.c
index 3df56aa0560..f1524a3fb90 100644
--- a/gcc/ada/gcc-interface/trans.c
+++ b/gcc/ada/gcc-interface/trans.c
@@ -43,6 +43,7 @@
#include "output.h"
#include "debug.h"
#include "libfuncs.h" /* For set_stack_check_libfunc. */
+#include "ipa-strub.h" /* For strub_make_callable. */
#include "tree-iterator.h"
#include "gimplify.h"
#include "opts.h"
@@ -444,6 +445,7 @@ gigi (Node_Id gnat_root,
int64_type, NULL_TREE),
NULL_TREE, is_default, true, true, true, false,
false, NULL, Empty);
+ strub_make_callable (mulv64_decl);
if (Enable_128bit_Types)
{
@@ -456,6 +458,7 @@ gigi (Node_Id gnat_root,
NULL_TREE),
NULL_TREE, is_default, true, true, true, false,
false, NULL, Empty);
+ strub_make_callable (mulv128_decl);
}
/* Name of the _Parent field in tagged record types. */
@@ -531,6 +534,7 @@ gigi (Node_Id gnat_root,
= create_subprog_decl
(get_identifier ("__gnat_raise_nodefer_with_msg"), NULL_TREE, ftype,
NULL_TREE, is_default, true, true, true, false, false, NULL, Empty);
+ strub_make_callable (raise_nodefer_decl);
set_exception_parameter_decl
= create_subprog_decl
@@ -779,6 +783,7 @@ build_raise_check (int check, enum exception_info_kind kind)
= create_subprog_decl (get_identifier (Name_Buffer), NULL_TREE, ftype,
NULL_TREE, is_default, true, true, true, false,
false, NULL, Empty);
+ strub_make_callable (result);
return result;
}
diff --git a/gcc/ada/gcc-interface/utils.c b/gcc/ada/gcc-interface/utils.c
index f85373e2f76..6b20ddc93c9 100644
--- a/gcc/ada/gcc-interface/utils.c
+++ b/gcc/ada/gcc-interface/utils.c
@@ -94,6 +94,7 @@ static tree handle_sentinel_attribute (tree *, tree, tree, int, bool *);
static tree handle_noreturn_attribute (tree *, tree, tree, int, bool *);
static tree handle_stack_protect_attribute (tree *, tree, tree, int, bool *);
static tree handle_no_stack_protector_attribute (tree *, tree, tree, int, bool *);
+static tree handle_strub_attribute (tree *, tree, tree, int, bool *);
static tree handle_noinline_attribute (tree *, tree, tree, int, bool *);
static tree handle_noclone_attribute (tree *, tree, tree, int, bool *);
static tree handle_noicf_attribute (tree *, tree, tree, int, bool *);
@@ -157,6 +158,8 @@ const struct attribute_spec gnat_internal_attribute_table[] =
{ "no_stack_protector",0, 0, true, false, false, false,
handle_no_stack_protector_attribute,
attr_stack_protect_exclusions },
+ { "strub", 0, 1, false, true, false, true,
+ handle_strub_attribute, NULL },
{ "noinline", 0, 0, true, false, false, false,
handle_noinline_attribute, NULL },
{ "noclone", 0, 0, true, false, false, false,
@@ -6599,6 +6602,127 @@ handle_no_stack_protector_attribute (tree *node, tree name, tree, int,
return NULL_TREE;
}
+/* Handle a "strub" attribute; arguments as in
+ struct attribute_spec.handler. */
+
+static tree
+handle_strub_attribute (tree *node, tree name,
+ tree args,
+ 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);
+ }
+
+ 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)
+ {
+ 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);
+ }
+
+ /* 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)
+ {
+ warning (OPT_Wattributes,
+ "%qE attribute ignored because of argument %qE",
+ name, TREE_VALUE (args));
+ *no_add_attrs = true;
+ enable = false;
+ }
+ /* 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));
+
+ /* 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;
+ }
+ }
+
+ return NULL_TREE;
+}
/* Handle a "noinline" attribute; arguments as in
struct attribute_spec.handler. */
diff --git a/gcc/ada/libgnat/a-except.adb b/gcc/ada/libgnat/a-except.adb
index c332afad0f8..593392809d9 100644
--- a/gcc/ada/libgnat/a-except.adb
+++ b/gcc/ada/libgnat/a-except.adb
@@ -629,6 +629,96 @@ package body Ada.Exceptions is
pragma No_Return (Rcheck_CE_Invalid_Data_Ext);
pragma No_Return (Rcheck_CE_Range_Check_Ext);
+ -- Make all of these procedures callable from strub contexts.
+ -- These attributes are not visible to callers; they are made
+ -- visible in trans.c:build_raise_check.
+
+ pragma Machine_Attribute (Rcheck_CE_Access_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Null_Access_Parameter,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Discriminant_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Divide_By_Zero,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Explicit_Raise,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Index_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Invalid_Data,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Length_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Null_Exception_Id,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Null_Not_Allowed,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Overflow_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Partition_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Range_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Tag_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Access_Before_Elaboration,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Accessibility_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Address_Of_Intrinsic,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Aliased_Parameters,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_All_Guards_Closed,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Bad_Predicated_Generic_Type,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Build_In_Place_Mismatch,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Current_Task_In_Entry_Body,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Duplicated_Entry_Address,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Explicit_Raise,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Implicit_Return,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Misaligned_Address_Value,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Missing_Return,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Non_Transportable_Actual,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Overlaid_Controlled_Object,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Potentially_Blocking_Operation,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Stream_Operation_Not_Allowed,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Stubbed_Subprogram_Called,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Unchecked_Union_Restriction,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Finalize_Raised_Exception,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_SE_Empty_Storage_Pool,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_SE_Explicit_Raise,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_SE_Infinite_Recursion,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_SE_Object_Too_Large,
+ "strub", "callable");
+
+ pragma Machine_Attribute (Rcheck_CE_Access_Check_Ext,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Index_Check_Ext,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Invalid_Data_Ext,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Range_Check_Ext,
+ "strub", "callable");
+
---------------------------------------------
-- Reason Strings for Run-Time Check Calls --
---------------------------------------------
diff --git a/gcc/ada/libgnat/a-except.ads b/gcc/ada/libgnat/a-except.ads
index 2b27adb6ca1..b5a2e48f96a 100644
--- a/gcc/ada/libgnat/a-except.ads
+++ b/gcc/ada/libgnat/a-except.ads
@@ -81,6 +81,10 @@ package Ada.Exceptions is
pragma No_Return (Raise_Exception);
-- Note: In accordance with AI-466, CE is raised if E = Null_Id
+ -- Make it callable from strub contexts.
+ pragma Machine_Attribute (Raise_Exception,
+ "strub", "callable");
+
function Exception_Message (X : Exception_Occurrence) return String;
procedure Reraise_Occurrence (X : Exception_Occurrence);
@@ -184,6 +188,10 @@ private
-- Raise_Exception_Always if it can determine this is the case. The Export
-- allows this routine to be accessed from Pure units.
+ -- Make it callable from strub contexts.
+ pragma Machine_Attribute (Raise_Exception_Always,
+ "strub", "callable");
+
procedure Raise_From_Controlled_Operation (X : Exception_Occurrence);
pragma No_Return (Raise_From_Controlled_Operation);
pragma Export
diff --git a/gcc/ada/libgnat/s-arit128.ads b/gcc/ada/libgnat/s-arit128.ads
index 6213cfb569a..5d3fff6d8e1 100644
--- a/gcc/ada/libgnat/s-arit128.ads
+++ b/gcc/ada/libgnat/s-arit128.ads
@@ -57,6 +57,12 @@ package System.Arith_128 is
-- bits, otherwise returns the 128-bit signed integer product.
-- Gigi may also call this routine directly.
+ -- Make it callable from strub contexts.
+ -- There is a matching setting in trans.c,
+ -- for calls issued by Gigi.
+ pragma Machine_Attribute (Multiply_With_Ovflo_Check128,
+ "strub", "callable");
+
procedure Scaled_Divide128
(X, Y, Z : Int128;
Q, R : out Int128;
diff --git a/gcc/ada/libgnat/s-arit64.ads b/gcc/ada/libgnat/s-arit64.ads
index c9141f5fe3e..170c2bdf1de 100644
--- a/gcc/ada/libgnat/s-arit64.ads
+++ b/gcc/ada/libgnat/s-arit64.ads
@@ -57,6 +57,12 @@ package System.Arith_64 is
-- bits, otherwise returns the 64-bit signed integer product.
-- Gigi may also call this routine directly.
+ -- Make it callable from strub contexts.
+ -- There is a matching setting in trans.c,
+ -- for calls issued by Gigi.
+ pragma Machine_Attribute (Multiply_With_Ovflo_Check64,
+ "strub", "callable");
+
procedure Scaled_Divide64
(X, Y, Z : Int64;
Q, R : out Int64;
diff --git a/gcc/ada/libgnat/s-secsta.ads b/gcc/ada/libgnat/s-secsta.ads
index 7d6b1b9a90e..63df82c8392 100644
--- a/gcc/ada/libgnat/s-secsta.ads
+++ b/gcc/ada/libgnat/s-secsta.ads
@@ -85,6 +85,9 @@ package System.Secondary_Stack is
--
-- * Create a new chunk that fits the requested Storage_Size.
+ pragma Machine_Attribute (SS_Allocate, "strub", "callable");
+ -- Enable it to be called from within strub contexts.
+
procedure SS_Free (Stack : in out SS_Stack_Ptr);
-- Free all dynamic chunks of secondary stack Stack. If possible, free the
-- stack itself.
@@ -92,9 +95,16 @@ package System.Secondary_Stack is
function SS_Mark return Mark_Id;
-- Capture and return the state of the invoking task's secondary stack
+ pragma Machine_Attribute (SS_Mark, "strub", "callable");
+ -- Enable it to be called from within strub contexts.
+
procedure SS_Release (M : Mark_Id);
-- Restore the state of the invoking task's secondary stack to mark M
+ pragma Machine_Attribute (SS_Release, "strub", "callable");
+ -- Enable it to be called from within strub contexts.
+ -- FIXME: should there be a scrubbing SS_Release?
+
function SS_Get_Max return Long_Long_Integer;
-- Return the high water mark of the invoking task's secondary stack, in
-- bytes.
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 99548627761..7ad91ea6f7a 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -70,6 +70,7 @@ along with GCC; see the file COPYING3. If not see
#include "gimple-fold.h"
#include "intl.h"
#include "file-prefix-map.h" /* remap_macro_filename() */
+#include "ipa-strub.h" /* strub_watermark_parm() */
#include "gomp-constants.h"
#include "omp-general.h"
#include "tree-dfa.h"
@@ -150,6 +151,7 @@ static rtx expand_builtin_strnlen (tree, rtx, machine_mode);
static rtx expand_builtin_alloca (tree);
static rtx expand_builtin_unop (machine_mode, tree, rtx, rtx, optab);
static rtx expand_builtin_frame_address (tree, tree);
+static rtx expand_builtin_stack_address ();
static tree stabilize_va_list_loc (location_t, tree, int);
static rtx expand_builtin_expect (tree, rtx);
static rtx expand_builtin_expect_with_probability (tree, rtx);
@@ -4871,6 +4873,256 @@ expand_builtin_frame_address (tree fndecl, tree exp)
}
}
+#ifndef STACK_GROWS_DOWNWARD
+# define STACK_TOPS GT
+#else
+# define STACK_TOPS LT
+#endif
+
+#ifdef POINTERS_EXTEND_UNSIGNED
+# define STACK_UNSIGNED POINTERS_EXTEND_UNSIGNED
+#else
+# define STACK_UNSIGNED true
+#endif
+
+/* Expand a call to builtin function __builtin_stack_address. */
+
+static rtx
+expand_builtin_stack_address ()
+{
+ return convert_to_mode (ptr_mode, copy_to_reg (stack_pointer_rtx),
+ STACK_UNSIGNED);
+}
+
+/* Expand a call to builtin function __builtin_strub_enter. */
+
+static rtx
+expand_builtin_strub_enter (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 1 || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = NULL_RTX;
+
+#if 1 || defined RED_ZONE_SIZE
+ if (tree wmptr = (optimize
+ ? strub_watermark_parm (current_function_decl)
+ : NULL_TREE))
+ {
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+ stktop = force_reg (ptr_mode, wmark);
+ }
+#endif
+
+ if (!stktop)
+ stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ emit_move_insn (wmark, stktop);
+
+ return const0_rtx;
+}
+
+/* Expand a call to builtin function __builtin_strub_update. */
+
+static rtx
+expand_builtin_strub_update (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 2 || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = expand_builtin_stack_address ();
+
+#ifdef RED_ZONE_SIZE
+ /* Here's how the strub enter, update and leave functions deal with red zones.
+
+ If it weren't for red zones, update, called from within a strub context,
+ would bump the watermark to the top of the stack. Enter and leave, running
+ in the caller, would use the caller's top of stack address both to
+ initialize the watermark passed to the callee, and to start strubbing the
+ stack afterwards.
+
+ Ideally, we'd update the watermark so as to cover the used amount of red
+ zone, and strub starting at the caller's other end of the (presumably
+ unused) red zone. Normally, only leaf functions use the red zone, but at
+ this point we can't tell whether a function is a leaf, nor can we tell how
+ much of the red zone it uses. Furthermore, some strub contexts may have
+ been inlined so that update and leave are called from the same stack frame,
+ and the strub builtins may all have been inlined, turning a strub function
+ into a leaf.
+
+ So cleaning the range from the caller's stack pointer (one end of the red
+ zone) to the (potentially inlined) callee's (other end of the) red zone
+ could scribble over the caller's own red zone.
+
+ We avoid this possibility by arranging for callers that are strub contexts
+ to use their own watermark as the strub starting point. So, if A calls B,
+ and B calls C, B will tell A to strub up to the end of B's red zone, and
+ will strub itself only the part of C's stack frame and red zone that
+ doesn't overlap with B's. With that, we don't need to know who's leaf and
+ who isn't: inlined calls will shrink their strub window to zero, each
+ remaining call will strub some portion of the stack, and eventually the
+ strub context will return to a caller that isn't a strub context itself,
+ that will therefore use its own stack pointer as the strub starting point.
+ It's not a leaf, because strub contexts can't be inlined into non-strub
+ contexts, so it doesn't use the red zone, and it will therefore correctly
+ strub up the callee's stack frame up to the end of the callee's red zone.
+ Neat! */
+ if (true /* (flags_from_decl_or_type (current_function_decl) & ECF_LEAF) */)
+ {
+ poly_int64 red_zone_size = RED_ZONE_SIZE;
+#if STACK_GROWS_DOWNWARD
+ red_zone_size = -red_zone_size;
+#endif
+ stktop = plus_constant (ptr_mode, stktop, red_zone_size);
+ stktop = force_reg (ptr_mode, stktop);
+ }
+#endif
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ rtx wmarkr = force_reg (ptr_mode, wmark);
+
+ rtx_code_label *lab = gen_label_rtx ();
+ do_compare_rtx_and_jump (stktop, wmarkr, STACK_TOPS, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, lab, NULL,
+ profile_probability::very_likely ());
+ emit_move_insn (wmark, stktop);
+
+#if 1 || defined RED_ZONE_SIZE
+ /* If this is an inlined strub function, also bump the watermark for the
+ enclosing function. This avoids a problem with the following scenario: A
+ calls B and B calls C, and both B and C get inlined into A. B allocates
+ temporary stack space before calling C. If we don't update A's watermark,
+ we may use an outdated baseline for the post-C strub_leave, erasing B's
+ temporary stack allocation. We only need this if we're fully expanding
+ strub_leave inline. */
+ tree xwmptr = (optimize > 2
+ ? strub_watermark_parm (current_function_decl)
+ : wmptr);
+ if (wmptr != xwmptr)
+ {
+ wmptr = xwmptr;
+ wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+ wmarkr = force_reg (ptr_mode, wmark);
+
+ do_compare_rtx_and_jump (stktop, wmarkr, STACK_TOPS, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, lab, NULL,
+ profile_probability::very_likely ());
+ emit_move_insn (wmark, stktop);
+ }
+#endif
+
+ emit_label (lab);
+
+ return const0_rtx;
+}
+
+
+/* Expand a call to builtin function __builtin_strub_leave. */
+
+static rtx
+expand_builtin_strub_leave (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 2 || optimize_size || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = NULL_RTX;
+
+#if 1 || defined RED_ZONE_SIZE
+ if (tree wmptr = (optimize
+ ? strub_watermark_parm (current_function_decl)
+ : NULL_TREE))
+ {
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+ stktop = force_reg (ptr_mode, wmark);
+ }
+#endif
+
+ if (!stktop)
+ stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ rtx wmarkr = force_reg (ptr_mode, wmark);
+
+#ifndef STACK_GROWS_DOWNWARD
+ rtx base = stktop;
+ rtx end = wmarkr;
+#else
+ rtx base = wmarkr;
+ rtx end = stktop;
+#endif
+
+ /* We're going to modify it, so make sure it's not e.g. the stack pointer. */
+ base = copy_to_reg (base);
+
+ rtx_code_label *done = gen_label_rtx ();
+ do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, done, NULL,
+ profile_probability::very_likely ());
+
+ if (optimize < 3)
+ expand_call (exp, NULL_RTX, true);
+ else
+ {
+ /* Ok, now we've determined we want to copy the block, so convert the
+ addresses to Pmode, as needed to dereference them to access ptr_mode
+ memory locations, so that we don't have to convert anything within the
+ loop. */
+ base = memory_address (ptr_mode, base);
+ end = memory_address (ptr_mode, end);
+
+ rtx zero = force_operand (const0_rtx, NULL_RTX);
+ int ulen = GET_MODE_SIZE (ptr_mode);
+ rtx incr = plus_constant (Pmode, base, ulen);
+ rtx dstm = gen_rtx_MEM (ptr_mode, base);
+
+ rtx_code_label *loop = gen_label_rtx ();
+ emit_label (loop);
+ emit_move_insn (dstm, zero);
+ emit_move_insn (base, force_operand (incr, NULL_RTX));
+ do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
+ Pmode, NULL_RTX, NULL, loop,
+ profile_probability::very_likely ());
+ }
+
+ emit_label (done);
+
+ return const0_rtx;
+}
+
/* Expand EXP, a call to the alloca builtin. Return NULL_RTX if we
failed and the caller should emit a normal call. */
@@ -7110,6 +7362,27 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
case BUILT_IN_RETURN_ADDRESS:
return expand_builtin_frame_address (fndecl, exp);
+ case BUILT_IN_STACK_ADDRESS:
+ return expand_builtin_stack_address ();
+
+ case BUILT_IN___STRUB_ENTER:
+ target = expand_builtin_strub_enter (exp);
+ if (target)
+ return target;
+ break;
+
+ case BUILT_IN___STRUB_UPDATE:
+ target = expand_builtin_strub_update (exp);
+ if (target)
+ return target;
+ break;
+
+ case BUILT_IN___STRUB_LEAVE:
+ target = expand_builtin_strub_leave (exp);
+ if (target)
+ return target;
+ break;
+
/* Returns the address of the area where the structure is returned.
0 otherwise. */
case BUILT_IN_AGGREGATE_INCOMING_ADDRESS:
diff --git a/gcc/builtins.def b/gcc/builtins.def
index 45a09b4d42d..f83c1f5fcd7 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -874,6 +874,10 @@ DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSL, "ffsl", BT_FN_INT_LONG, ATTR_CONST_NOTHRO
DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSLL, "ffsll", BT_FN_INT_LONGLONG, ATTR_CONST_NOTHROW_LEAF_LIST)
DEF_EXT_LIB_BUILTIN (BUILT_IN_FORK, "fork", BT_FN_PID, ATTR_NOTHROW_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FRAME_ADDRESS, "frame_address", BT_FN_PTR_UINT, ATTR_NULL)
+DEF_GCC_BUILTIN (BUILT_IN_STACK_ADDRESS, "stack_address", BT_FN_PTR, ATTR_NULL)
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_ENTER, "__builtin___strub_enter")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_UPDATE, "__builtin___strub_update")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_LEAVE, "__builtin___strub_leave")
/* [trans-mem]: Adjust BUILT_IN_TM_FREE if BUILT_IN_FREE is changed. */
DEF_LIB_BUILTIN (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_LEAF_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FROB_RETURN_ADDR, "frob_return_addr", BT_FN_PTR_PTR, ATTR_NULL)
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index e60fb31d8c8..913b0239903 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -69,6 +69,7 @@ static tree handle_asan_odr_indicator_attribute (tree *, tree, tree, int,
static tree handle_stack_protect_attribute (tree *, tree, tree, int, bool *);
static tree handle_no_stack_protector_function_attribute (tree *, tree,
tree, int, bool *);
+static tree handle_strub_attribute (tree *, tree, tree, int, bool *);
static tree handle_noinline_attribute (tree *, tree, tree, int, bool *);
static tree handle_noclone_attribute (tree *, tree, tree, int, bool *);
static tree handle_nocf_check_attribute (tree *, tree, tree, int, bool *);
@@ -306,6 +307,8 @@ const struct attribute_spec c_common_attribute_table[] =
{ "no_stack_protector", 0, 0, true, false, false, false,
handle_no_stack_protector_function_attribute,
attr_stack_protect_exclusions },
+ { "strub", 0, 1, false, true, false, true,
+ handle_strub_attribute, NULL },
{ "noinline", 0, 0, true, false, false, false,
handle_noinline_attribute,
attr_noinline_exclusions },
@@ -1290,6 +1293,117 @@ handle_noipa_attribute (tree *node, tree name, tree, int, bool *no_add_attrs)
return NULL_TREE;
}
+/* Handle a "strub" attribute; arguments as in
+ struct attribute_spec.handler. */
+
+static tree
+handle_strub_attribute (tree *node, tree name,
+ tree args,
+ 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);
+ }
+
+ if (args && FUNC_OR_METHOD_TYPE_P (*node))
+ {
+ 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;
+ }
+
+ if (val >= 0)
+ TREE_VALUE (args) = build_int_cst (integer_type_node, val);
+ }
+
+ /* 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)
+ {
+ warning (OPT_Wattributes,
+ "%qE attribute ignored because of argument %qE",
+ name, TREE_VALUE (args));
+ *no_add_attrs = true;
+ enable = false;
+ }
+ /* 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));
+
+ /* 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;
+ }
+ }
+
+ return NULL_TREE;
+}
+
/* Handle a "noinline" attribute; arguments as in
struct attribute_spec.handler. */
diff --git a/gcc/common.opt b/gcc/common.opt
index ed8ab5fbe13..ba0359c3c1d 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,6 +2687,30 @@ 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=all
+Common RejectNegative Var(flag_strub, 3)
+Enable stack scrubbing for all viable functions.
+
+fstrub=at-calls
+Common RejectNegative Var(flag_strub, 1)
+Enable at-calls stack scrubbing for all viable functions.
+
+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 251a10302b4..1d28d587c8a 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -77,6 +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.
* 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.
@@ -8672,6 +8673,50 @@ pid_t wait (wait_status_ptr_t p)
@}
@end smallexample
+@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.
+
+A variable whose type carries the @code{strub} attribute causes
+functions that @emph{read} from it to have @code{strub} enabled.
+Reading from data referenced by pointers to such types has the same
+effect.
+
+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.
+
+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.
+
+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)}).
+
+@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.
+
@item unused
@cindex @code{unused} type attribute
When attached to a type (including a @code{union} or a @code{struct}),
@@ -11690,6 +11735,47 @@ option is in effect. Such calls should only be made in debugging
situations.
@end deftypefn
+@deftypefn {Built-in Function} {void *} __builtin_stack_address ()
+This function returns the value of the stack pointer register.
+@end deftypefn
+
+@node Stack Scrubbing
+@section Stack scrubbing 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).
+
+@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.
+@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
+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.
+@end deftypefn
+
@node Vector Extensions
@section Using Vector Instructions through Built-in Functions
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index b83bd902cec..9ad191d7da2 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,6 +599,7 @@ 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
-fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
-fvtv-counts -fvtv-debug @gol
-finstrument-functions @gol
@@ -15490,6 +15491,47 @@ 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.
+
+@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.
+
+@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.
+
+@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.
+
@item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
@opindex fvtable-verify
This option is only available when compiling C++ code.
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 413446bcc46..7f4bc44d2bb 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -119,6 +119,7 @@ along with GCC; see the file COPYING3. If not see
#include "stringpool.h"
#include "attribs.h"
#include "asan.h"
+#include "ipa-strub.h"
typedef fibonacci_heap <sreal, cgraph_edge> edge_heap_t;
typedef fibonacci_node <sreal, cgraph_edge> edge_heap_node_t;
@@ -396,6 +397,11 @@ 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))
+ {
+ e->inline_failed = CIF_UNSPECIFIED;
+ inlinable = false;
+ }
if (!inlinable && report)
report_inline_failed_reason (e);
return inlinable;
diff --git a/gcc/ipa-split.c b/gcc/ipa-split.c
index c68577d04a9..8c880747eb7 100644
--- a/gcc/ipa-split.c
+++ b/gcc/ipa-split.c
@@ -104,6 +104,7 @@ along with GCC; see the file COPYING3. If not see
#include "ipa-fnsummary.h"
#include "cfgloop.h"
#include "attribs.h"
+#include "ipa-strub.h"
/* Per basic block info. */
@@ -1792,6 +1793,12 @@ execute_split_functions (void)
"section.\n");
return 0;
}
+ if (!strub_splittable_p (node))
+ {
+ if (dump_file)
+ fprintf (dump_file, "Not splitting: function is a strub context.\n");
+ return 0;
+ }
/* We enforce splitting after loop headers when profile info is not
available. */
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
new file mode 100644
index 00000000000..cf7d811779e
--- /dev/null
+++ b/gcc/ipa-strub.c
@@ -0,0 +1,2999 @@
+/* strub (stack scrubbing) support.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "gimplify.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "gimple-iterator.h"
+#include "gimplify-me.h"
+#include "tree-into-ssa.h"
+#include "tree-ssa.h"
+#include "tree-cfg.h"
+#include "cfghooks.h"
+#include "cfgloop.h"
+#include "cfgcleanup.h"
+#include "tree-eh.h"
+#include "except.h"
+#include "builtins.h"
+#include "attribs.h"
+#include "tree-inline.h"
+#include "cgraph.h"
+#include "alloc-pool.h"
+#include "symbol-summary.h"
+#include "ipa-prop.h"
+#include "ipa-fnsummary.h"
+#include "gimple-fold.h"
+#include "fold-const.h"
+#include "gimple-walk.h"
+#include "tree-dfa.h"
+#include "langhooks.h"
+#include "calls.h"
+#include "vec.h"
+#include "stor-layout.h"
+#include "varasm.h"
+#include "alias.h"
+#include "diagnostic.h"
+#include "intl.h"
+#include "ipa-strub.h"
+
+#if BUILDING_GCC_MAJOR >= 11
+# include "symtab-thunks.h"
+# include "attr-fnspec.h"
+# define HAVE_ATTR_FNSPEC 1
+# define FOR_GCC_11P 1
+#else
+# define HAVE_ATTR_FNSPEC 0
+# define FOR_GCC_11P 0
+#endif
+
+/* Const and pure functions that gain a watermark parameter for strub purposes
+ are still regarded as such, which may cause the inline expansions of the
+ __strub builtins to malfunction. Ideally, attribute "fn spec" would enable
+ us to inform the backend about requirements and side effects of the call, but
+ call_fusage building in calls.c:expand_call does not even look at
+ attr_fnspec, so we resort to asm loads and updates to attain an equivalent
+ effect. Once expand_call gains the ability to issue extra memory uses and
+ clobbers based on pure/const function's fnspec, we can define this to 1. */
+#define ATTR_FNSPEC_DECONST_WATERMARK 0
+
+enum strub_mode {
+ /* This mode denotes a regular function, that does not require stack
+ scrubbing (strubbing). It may call any other functions, but if
+ it calls AT_CALLS (or WRAPPED) ones, strubbing logic is
+ automatically introduced around those calls (the latter, by
+ inlining INTERNAL wrappers). */
+ STRUB_DISABLED = 0,
+
+ /* This denotes a function whose signature is (to be) modified to
+ take an extra parameter, for stack use annotation, and its
+ callers must initialize and pass that argument, and perform the
+ strubbing. Functions that are explicitly marked with attribute
+ strub must have the mark visible wherever the function is,
+ including aliases, and overriders and overriding methods.
+ Functions that are implicitly marked for strubbing, for accessing
+ variables explicitly marked as such, will only select this
+ strubbing method if they are internal to a translation unit. It
+ can only be inlined into other strubbing functions, i.e.,
+ STRUB_AT_CALLS or STRUB_WRAPPED. */
+ STRUB_AT_CALLS = 1,
+
+ /* This denotes a function that is to perform strubbing internally,
+ without any changes to its interface (the function is turned into
+ a strubbing wrapper, and its original body is moved to a separate
+ STRUB_WRAPPED function, with a modified interface). Functions
+ may be explicitly marked with attribute strub(2), and the
+ attribute must be visible at the point of definition. Functions
+ that are explicitly marked for strubbing, for accessing variables
+ explicitly marked as such, may select this strubbing mode if
+ their interface cannot change, e.g. because its interface is
+ visible to other translation units, directly, by indirection
+ (having its address taken), inheritance, etc. Functions that use
+ this method must not have the noclone attribute, nor the noipa
+ one. Functions marked as always_inline may select this mode, but
+ they are NOT wrapped, they remain unchanged, and are only inlined
+ into strubbed contexts. Once non-always_inline functions are
+ wrapped, the wrapper becomes STRUB_WRAPPER, and the wrapped becomes
+ STRUB_WRAPPED. */
+ STRUB_INTERNAL = 2,
+
+ /* This denotes a function whose stack is not strubbed, but that is
+ nevertheless explicitly or implicitly marked as callable from strubbing
+ functions. Normally, only STRUB_AT_CALLS (and STRUB_INTERNAL ->
+ STRUB_WRAPPED) functions can be called from strubbing contexts (bodies of
+ STRUB_AT_CALLS, STRUB_INTERNAL and STRUB_WRAPPED functions), but attribute
+ strub(3) enables other functions to be (indirectly) called from these
+ contexts. Some builtins and internal functions may be implicitly marked as
+ STRUB_CALLABLE. */
+ STRUB_CALLABLE = 3,
+
+ /* This denotes the function that took over the body of a
+ STRUB_INTERNAL function. At first, it's only called by its
+ wrapper, but the wrapper may be inlined. The wrapped function,
+ in turn, can only be inlined into other functions whose stack
+ frames are strubbed, i.e., that are STRUB_WRAPPED or
+ STRUB_AT_CALLS. */
+ STRUB_WRAPPED = -1,
+
+ /* This denotes the wrapper function that replaced the STRUB_INTERNAL
+ function. This mode overrides the STRUB_INTERNAL mode at the time the
+ internal to-be-wrapped function becomes a wrapper, so that inlining logic
+ can tell one from the other. */
+ STRUB_WRAPPER = -2,
+
+ /* 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
+get_strub_attr_from_type (tree type)
+{
+ return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
+}
+
+static tree
+get_strub_attr_from_decl (tree decl)
+{
+ tree ret = lookup_attribute ("strub", DECL_ATTRIBUTES (decl));
+ if (ret)
+ return ret;
+ return get_strub_attr_from_type (TREE_TYPE (decl));
+}
+
+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);
+
+#if 0 /* ??? use symbolic mode names with interned strings? */
+ char *s = NULL;
+
+ switch (strub_mode)
+ {
+
+ }
+#endif
+}
+
+static enum strub_mode
+get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
+{
+ enum strub_mode mode = STRUB_DISABLED;
+
+ if (strub_attr)
+ {
+ 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 ();
+ }
+
+ return mode;
+}
+
+static enum strub_mode
+get_strub_mode_from_decl (tree fndecl)
+{
+ return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
+}
+
+static enum strub_mode
+get_strub_mode (cgraph_node *node)
+{
+ return get_strub_mode_from_decl (node->decl);
+}
+
+static enum strub_mode
+get_strub_mode_from_type (tree type)
+{
+ bool var_p = !FUNC_OR_METHOD_TYPE_P (type);
+ tree attr = get_strub_attr_from_type (type);
+
+ if (attr)
+ return get_strub_mode_from_attr (attr, var_p);
+
+ if (flag_strub > 0 && !var_p)
+ return STRUB_CALLABLE;
+
+ return STRUB_DISABLED;
+}
+
+static bool
+calls_builtin_va_start_p (cgraph_node *node)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (fndecl_built_in_p (cdecl, BUILT_IN_VA_START))
+ return true;
+ }
+
+ return result;
+}
+
+static bool
+calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!fndecl_built_in_p (cdecl, BUILT_IN_APPLY_ARGS))
+ continue;
+
+ result = true;
+
+ if (!report)
+ break;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "at-calls %<strub%> does not support call to %qD",
+ cdecl);
+ }
+
+ return result;
+}
+
+static inline bool
+strub_always_inline_p (cgraph_node *node)
+{
+ return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
+}
+
+static inline bool
+can_strub_p (cgraph_node *node, bool report = false)
+{
+ bool result = true;
+
+ if (!report && strub_always_inline_p (node))
+ return result;
+
+ if (lookup_attribute ("noipa", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for %<strub%>"
+ " because of attribute %<noipa%>",
+ node->decl);
+ }
+
+ /* We can't, and don't want to vectorize the watermark and other
+ strub-introduced parms. */
+ if (lookup_attribute ("simd", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for %<strub%>"
+ " because of attribute %<simd%>",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+can_strub_at_calls_p (cgraph_node *node, bool report = false)
+{
+ bool result = !report || can_strub_p (node, report);
+
+ if (!result && !report)
+ return result;
+
+ return !calls_builtin_apply_args_p (node, report);
+}
+
+#define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
+
+/* We can't perform internal strubbing if the function body involves certain
+ features:
+
+ - a non-default __builtin_va_start (e.g. x86's __builtin_ms_va_start) is
+ currently unsupported because we can't discover the corresponding va_copy and
+ va_end decls in the wrapper, and we don't convey the alternate variable
+ arguments ABI to the modified wrapped function. The default
+ __builtin_va_start is supported by calling va_start/va_end at the wrapper,
+ that takes variable arguments, passing a pointer to the va_list object to the
+ wrapped function, that runs va_copy from it where the original function ran
+ va_start.
+
+ __builtin_next_arg is currently unsupported because the wrapped function
+ won't be a variable argument function. We could process it in the wrapper,
+ that remains a variable argument function, and replace calls in the wrapped
+ body, but we currently don't.
+
+ __builtin_return_address is rejected because it's generally used when the
+ actual caller matters, and introducing a wrapper breaks such uses as those in
+ the unwinder. */
+
+static bool
+can_strub_internally_p (cgraph_node *node, bool report = false)
+{
+ bool result = !report || can_strub_p (node, report);
+
+ if (!result && !report)
+ return result;
+
+ if (!report && strub_always_inline_p (node))
+ return 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, but we'd have to prevent remapping of the labels. */
+ if (lookup_attribute ("noclone", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for internal %<strub%>"
+ " because of attribute %<noclone%>",
+ node->decl);
+ }
+
+ if (node->has_gimple_body_p ())
+ {
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!((fndecl_built_in_p (cdecl, BUILT_IN_VA_START)
+ && cdecl != builtin_decl_explicit (BUILT_IN_VA_START))
+ || fndecl_built_in_p (cdecl, BUILT_IN_NEXT_ARG)
+ || fndecl_built_in_p (cdecl, BUILT_IN_RETURN_ADDRESS)))
+ continue;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "%qD is not eligible for internal %<strub%> "
+ "because it calls %qD",
+ node->decl, cdecl);
+ }
+
+ struct function *fun = DECL_STRUCT_FUNCTION (node->decl);
+ if (fun->has_nonlocal_label)
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for internal %<strub%> "
+ "because it contains a non-local goto target",
+ node->decl);
+ }
+
+ if (fun->has_forced_label_in_static)
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for internal %<strub%> "
+ "because the address of a local label escapes",
+ node->decl);
+ }
+
+ /* Catch any other case that would prevent versioning/cloning
+ so as to also have it covered above. */
+ gcc_checking_assert (!result /* || !node->has_gimple_body_p () */
+ || tree_versionable_function_p (node->decl));
+
+
+ /* Label values referenced 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. */
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ glabel *label_stmt = dyn_cast <glabel *> (gsi_stmt (gsi));
+ tree target;
+
+ if (!label_stmt)
+ break;
+
+ 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;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (label_stmt),
+ "internal %<strub%> does not support user labels");
+ }
+ }
+
+ if (list_length (TYPE_ARG_TYPES (TREE_TYPE (node->decl)))
+ >= (((HOST_WIDE_INT) 1 << IPA_PARAM_MAX_INDEX_BITS)
+ - STRUB_INTERNAL_MAX_EXTRA_ARGS))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD has too many arguments for internal %<strub%>",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+strub_from_body_p (cgraph_node *node)
+{
+ if (!node->has_gimple_body_p ())
+ return false;
+
+ /* If any local variable is marked for strub... */
+ unsigned i;
+ tree var;
+ FOR_EACH_LOCAL_DECL (DECL_STRUCT_FUNCTION (node->decl),
+ i, var)
+ if (get_strub_mode_from_type (TREE_TYPE (var))
+ != STRUB_DISABLED)
+ return true;
+
+ /* Now scan the body for loads with strub types. */
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!gimple_assign_load_p (stmt))
+ continue;
+
+ tree rhs = gimple_assign_rhs1 (stmt);
+ if (get_strub_mode_from_type (TREE_TYPE (rhs))
+ != STRUB_DISABLED)
+ return true;
+ }
+
+ return false;
+}
+
+/* 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)
+{
+ if (DECL_BUILT_IN_CLASS (node->decl) != BUILT_IN_NORMAL)
+ return false;
+
+ enum built_in_function fcode = DECL_FUNCTION_CODE (node->decl);
+
+ switch (fcode)
+ {
+ case BUILT_IN_NONE:
+ gcc_unreachable ();
+
+ /* This temporarily allocates stack for the call, and we can't reasonably
+ update the watermark for that. Besides, we don't check the actual call
+ target, nor its signature, and it seems to be overkill to as much as
+ try to do so. */
+ case BUILT_IN_APPLY:
+ return false;
+
+ /* Conversely, this shouldn't be called from within strub contexts, since
+ the caller may have had its signature modified. STRUB_INTERNAL is ok,
+ the call will remain in the STRUB_WRAPPER, and removed from the
+ STRUB_WRAPPED clone. */
+ case BUILT_IN_APPLY_ARGS:
+ return false;
+
+ /* ??? Make all other builtins callable. We wish to make any builtin call
+ the compiler might introduce on its own callable. Anything that is
+ predictable enough as to be known not to allow stack data that should
+ be strubbed to unintentionally escape to non-strub contexts can be
+ allowed, and pretty much every builtin appears to fit this description.
+ The exceptions to this rule seem to be rare, and only available as
+ explicit __builtin calls, so let's keep it simple and allow all of
+ them... */
+ default:
+ return true;
+ }
+}
+
+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);
+
+ /* 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;
+ /* 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
+ safely undergo at-calls strubbing. Internal mode will still be used in
+ functions that request it explicitly with attribute strub(2), or when the
+ function body requires strubbing and at-calls strubbing is not viable. */
+ const bool strub_flag_at_calls = flag_strub == 1;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo internal strubbing. At-calls mode will still be used in
+ functions that requiest it explicitly with attribute strub() or strub(1),
+ or when the function body requires strubbing and internal strubbing is not
+ viable. */
+ const bool strub_flag_internal = flag_strub == 2;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo strubbing in either mode. When both modes are viable,
+ at-calls is preferred. */
+ const bool strub_flag_either = flag_strub == 3;
+ /* Besides the default behavior, enable strub implicitly for all viable
+ functions. */
+ const bool strub_flag_viable = flag_strub > 0;
+
+ /* The consider_* variables should be true if selecting the corresponding
+ strub modes would be consistent with requests from attributes and command
+ line flags. Attributes associated with functions pretty much mandate a
+ selection, and should report an error if not satisfied; strub_flag_auto
+ implicitly enables some viable strub mode if that's required by references
+ to variables marked for strub; strub_flag_viable enables strub if viable
+ (even when favoring one mode, body-requested strub can still be satisfied
+ by either mode), and falls back to callable, silently unless variables
+ require strubbing. */
+
+ const bool consider_at_calls
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_AT_CALLS
+ : true));
+ const bool consider_internal
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_INTERNAL
+ : true));
+
+ const bool consider_callable
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_CALLABLE
+ : (strub_flag_viable
+ || strub_callable_builtin_p (node))));
+
+ /* This is a shorthand for either strub-enabled mode. */
+ const bool consider_strub
+ = (consider_at_calls || consider_internal);
+
+ /* We can cope with always_inline functions even with noipa and noclone,
+ because we just leave them alone. */
+ const bool is_always_inline
+ = strub_always_inline_p (node);
+
+ /* Strubbing in general, and each specific strub mode, may have its own set of
+ requirements. We require noipa for strubbing, either because of cloning
+ required for internal strub, or because of caller enumeration required for
+ at-calls strub. We don't consider the at-calls mode eligible if it's not
+ even considered, it has no further requirements. Internal mode requires
+ cloning and the absence of certain features in the body and, like at-calls,
+ it's not eligible if it's not even under consideration.
+
+ ??? Do we need target hooks for further constraints? E.g., x86's
+ "interrupt" attribute breaks internal strubbing because the wrapped clone
+ carries the attribute and thus isn't callable; in this case, we could use a
+ target hook to adjust the clone instead. */
+ const bool strub_eligible
+ = (consider_strub
+ && (is_always_inline || can_strub_p (node)));
+ const bool at_calls_eligible
+ = (consider_at_calls && strub_eligible
+ && can_strub_at_calls_p (node));
+ const bool internal_eligible
+ = (consider_internal && strub_eligible
+ && (is_always_inline
+ || can_strub_internally_p (node)));
+
+ /* In addition to the strict eligibility requirements, some additional
+ constraints are placed on implicit selection of certain modes. These do
+ not prevent the selection of a mode if explicitly specified as part of a
+ function interface (the strub attribute), but they may prevent modes from
+ being selected by the command line or by function bodies. The only actual
+ constraint is on at-calls mode: since we change the function's exposed
+ signature, we won't do it implicitly if the function can possibly be used
+ in ways that do not expect the signature change, e.g., if the function is
+ available to or interposable by other units, if its address is taken,
+ etc. */
+ const bool at_calls_viable
+ = (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->binds_to_current_def_p ()
+ && node->can_be_local_p ()))
+ && node->only_called_directly_p ())));
+ const bool internal_viable
+ = (internal_eligible);
+
+ /* Shorthand. */
+ const bool strub_viable
+ = (at_calls_viable || internal_viable);
+
+ /* We wish to analyze the body, to look for implicit requests for strub, both
+ to implicitly enable it when the body calls for it, and to report errors if
+ the body calls for it but neither mode is viable (even if that follows from
+ non-eligibility because of the explicit specification of some non-strubbing
+ mode). We can refrain from scanning the body only in rare circumstances:
+ when strub is enabled by a function attribute (scanning might be redundant
+ in telling us to also enable it), and when we are enabling strub implicitly
+ but there are non-viable modes: we want to know whether strubbing is
+ required, to fallback to another mode, even if we're only enabling a
+ certain mode, or, when either mode would do, to report an error if neither
+ happens to be viable. */
+ const bool analyze_body
+ = (strub_attr
+ ? !consider_strub
+ : (strub_flag_auto
+ || (strub_flag_viable && (!at_calls_viable && !internal_viable))
+ || (strub_flag_either && !strub_viable)));
+
+ /* Cases in which strubbing is enabled or disabled by strub_flag_auto.
+ Unsatisfiable requests ought to be reported. */
+ const bool strub_required
+ = ((strub_attr && consider_strub)
+ || (analyze_body && strub_from_body_p (node)));
+
+ /* Besides the required cases, we want to abide by the requests to enabling on
+ an if-viable basis. */
+ const bool strub_enable
+ = (strub_required
+ || (strub_flag_at_calls && at_calls_viable)
+ || (strub_flag_internal && internal_viable)
+ || (strub_flag_either && strub_viable));
+
+ /* And now we're finally ready to select a mode that abides by the viability
+ and eligibility constraints, and that satisfies the strubbing requirements
+ and requests, subject to the constraints. If both modes are viable and
+ strub is to be enabled, pick STRUB_AT_CALLS unless STRUB_INTERNAL was named
+ as preferred. */
+ 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
+ : (strub_enable && at_calls_viable)
+ ? (strub_required && !strub_attr
+ ? STRUB_AT_CALLS_OPT
+ : STRUB_AT_CALLS)
+ : consider_callable
+ ? STRUB_CALLABLE
+ : STRUB_DISABLED);
+
+ switch (mode)
+ {
+ case STRUB_CALLABLE:
+ if (is_always_inline)
+ break;
+ /* Fall through. */
+
+ case STRUB_DISABLED:
+ if (strub_enable && !strub_attr)
+ {
+ gcc_checking_assert (analyze_body);
+ error_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD requires %<strub%>,"
+ " but no viable %<strub%> mode was found",
+ node->decl);
+ break;
+ }
+ /* Fall through. */
+
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ /* Differences from an mode requested through a function attribute are
+ 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:
+ gcc_unreachable ();
+ }
+
+ return mode;
+}
+
+/* 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)
+{
+ gcc_checking_assert (override
+ || !(DECL_P (fndt)
+ ? get_strub_attr_from_decl (fndt)
+ : get_strub_attr_from_type (fndt)));
+
+ tree attr = tree_cons (get_identifier ("strub"),
+ get_strub_mode_attr_value (mode),
+ NULL_TREE);
+ tree *attrp = NULL;
+ if (DECL_P (fndt))
+ {
+ gcc_checking_assert (FUNC_OR_METHOD_TYPE_P (TREE_TYPE (fndt)));
+ attrp = &DECL_ATTRIBUTES (fndt);
+ }
+ else if (FUNC_OR_METHOD_TYPE_P (fndt))
+ attrp = &TYPE_ATTRIBUTES (fndt);
+ else
+ gcc_unreachable ();
+
+ TREE_CHAIN (attr) = *attrp;
+ *attrp = attr;
+}
+
+void
+strub_make_callable (tree fndt)
+{
+ strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
+}
+
+static void
+set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+ enum strub_mode req_mode = get_strub_mode_from_attr (attr);
+
+ if (attr)
+ {
+ /* Check for and report incompatible mode changes. */
+ if (mode != req_mode
+ && !(req_mode == STRUB_INTERNAL
+ && (mode == STRUB_WRAPPED
+ || mode == STRUB_WRAPPER))
+ && !((req_mode == STRUB_INTERNAL
+ || req_mode == STRUB_AT_CALLS
+ || req_mode == STRUB_CALLABLE)
+ && 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));
+ if (node->alias)
+ {
+ cgraph_node *target = node->ultimate_alias_target ();
+ if (target != node)
+ error_at (DECL_SOURCE_LOCATION (target->decl),
+ "the incompatible selection was determined"
+ " by ultimate alias target %qD",
+ target->decl);
+ }
+
+ /* Report any incompatibilities with explicitly-requested strub. */
+ switch (req_mode)
+ {
+ case STRUB_AT_CALLS:
+ can_strub_at_calls_p (node, true);
+ break;
+
+ case STRUB_INTERNAL:
+ can_strub_internally_p (node, true);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Drop any incompatible strub attributes leading the decl attribute
+ chain. Return if we find one with the mode we need. */
+ for (;;)
+ {
+ if (mode == req_mode)
+ return;
+
+ if (DECL_ATTRIBUTES (node->decl) != attr)
+ break;
+
+ DECL_ATTRIBUTES (node->decl) = TREE_CHAIN (attr);
+ attr = get_strub_attr_from_decl (node->decl);
+ if (!attr)
+ break;
+
+ req_mode = get_strub_mode_from_attr (attr);
+ }
+ }
+ else if (mode == req_mode)
+ return;
+
+ strub_set_fndt_mode_to (node->decl, mode, attr);
+}
+
+static void
+set_strub_mode (cgraph_node *node)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+
+ if (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:
+ case STRUB_AT_CALLS_OPT:
+ return;
+
+ case STRUB_DISABLED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ cgraph_node *xnode = node;
+ if (node->alias)
+ xnode = node->ultimate_alias_target ();
+ /* Weakrefs may remain unresolved (the above will return node) if
+ their targets are not defined, so make sure we compute a strub
+ mode for them, instead of defaulting to STRUB_DISABLED and
+ rendering them uncallable. */
+ enum strub_mode mode = (xnode != node && !xnode->alias
+ ? get_strub_mode (xnode)
+ : compute_strub_mode (node, attr));
+
+ set_strub_mode_to (node, mode);
+}
+
+/* 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. */
+
+static bool
+strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode caller_mode = get_strub_mode (caller);
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS_OPT:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return callee_mode != STRUB_INLINABLE;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_AT_CALLS_OPT:
+ case STRUB_INTERNAL:
+ case STRUB_WRAPPER:
+ return (flag_strub >= 0);
+
+ case STRUB_DISABLED:
+ return false;
+
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ 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. */
+
+bool
+strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ case STRUB_AT_CALLS_OPT:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return true;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ strub_mode caller_mode = get_strub_mode (caller);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ case STRUB_AT_CALLS_OPT:
+ return true;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ return false;
+}
+
+/* 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;
+
+ 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_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)
+ {
+ gcc_checking_assert (!e->indirect_unknown_callee);
+ if (!strub_callable_from_p (e->callee, node))
+ {
+ if (get_strub_mode (e->callee) == STRUB_INLINABLE)
+ error_at (gimple_location (e->call_stmt),
+ "calling %<always_inline%> %<strub%> %qD"
+ " in non-%<strub%> context %qD",
+ e->callee->decl, node->decl);
+ else if (fndecl_built_in_p (e->callee->decl, BUILT_IN_APPLY_ARGS)
+ && get_strub_mode (node) == STRUB_INTERNAL)
+ /* This is ok, it will be kept in the STRUB_WRAPPER, and removed
+ from the STRUB_WRAPPED's strub context. */
+ continue;
+ else
+ error_at (gimple_location (e->call_stmt),
+ "calling non-%<strub%> %qD in %<strub%> context %qD",
+ e->callee->decl, node->decl);
+ }
+ }
+ }
+
+ /* ??? 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 {
+
+const pass_data pass_data_ipa_strub_mode = {
+ SIMPLE_IPA_PASS,
+ "strubm",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ 0, // properties_finish
+};
+
+class pass_ipa_strub_mode : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub_mode (gcc::context *ctxt)
+ : simple_ipa_opt_pass (pass_data_ipa_strub_mode, ctxt)
+ {}
+ 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
+ overhead. */
+ if (flag_strub == -2)
+ flag_strub = 0;
+ return flag_strub;
+ }
+ virtual unsigned int execute (function *);
+};
+
+const pass_data pass_data_ipa_strub = {
+ SIMPLE_IPA_PASS,
+ "strub",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ TODO_update_ssa
+ | TODO_cleanup_cfg
+ | TODO_rebuild_cgraph_edges
+ | TODO_verify_il, // properties_finish
+};
+
+class pass_ipa_strub : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub (gcc::context *ctxt)
+ : 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 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_variant_type_copy (ptr_type_node))
+
+ DEF_TYPE (pwmt, build_reference_type (get_wmt ()))
+
+ DEF_TYPE (qpwmt,
+ build_qualified_type (get_pwmt (),
+ TYPE_QUAL_RESTRICT
+ /* | TYPE_QUAL_CONST */))
+
+ DEF_TYPE (qptr,
+ build_qualified_type (ptr_type_node,
+ TYPE_QUAL_RESTRICT
+ | TYPE_QUAL_CONST))
+
+ DEF_TYPE (qpvalst,
+ build_qualified_type (build_reference_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); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ decl = add_builtin_function \
+ ("__builtin_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ NULL, NULL); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ } \
+ return decl; \
+ }
+
+ DEF_NM_BUILTIN (stack_address,
+ BUILT_IN_STACK_ADDRESS,
+ (ptr_type_node, NULL))
+
+#undef DEF_NM_BUILTIN
+
+#define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST) \
+ static tree get_ ## NAME () { \
+ tree decl = builtin_decl_explicit (CODE); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ tree attrs = NULL; \
+ if (FNSPEC && HAVE_ATTR_FNSPEC) \
+ attrs = tree_cons (get_identifier ("fn spec"), \
+ build_tree_list \
+ (NULL_TREE, \
+ build_string (strlen (FNSPEC), \
+ (FNSPEC))), \
+ attrs); \
+ decl = add_builtin_function_ext_scope \
+ ("__builtin___strub_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ "__strub_" #NAME, attrs); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ } \
+ return decl; \
+ }
+
+ DEF_SS_BUILTIN (enter, ". Ot",
+ BUILT_IN___STRUB_ENTER,
+ (void_type_node, get_qpwmt (), NULL))
+ DEF_SS_BUILTIN (update, ". Wt",
+ BUILT_IN___STRUB_UPDATE,
+ (void_type_node, get_qpwmt (), NULL))
+ DEF_SS_BUILTIN (leave, ". w ",
+ BUILT_IN___STRUB_LEAVE,
+ (void_type_node, get_qpwmt (), NULL))
+
+#undef DEF_SS_BUILTIN
+
+#define DEF_IDENT(NAME) \
+ static inline tree get_ ## NAME () { \
+ static tree identifier = NULL_TREE; \
+ if (!identifier) \
+ identifier = get_identifier (".strub." #NAME); \
+ return identifier; \
+ }
+
+ DEF_IDENT (watermark_ptr)
+ DEF_IDENT (va_list_ptr)
+ DEF_IDENT (apply_args)
+
+#undef DEF_IDENT
+
+ static inline int adjust_at_calls_type (tree);
+ static inline void adjust_at_calls_call (cgraph_edge *, int);
+ static inline void adjust_at_calls_calls (cgraph_node *);
+
+ static inline gimple_seq
+ call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
+ gimple_seq seq = NULL)
+ {
+ tree uwm = get_update ();
+ gcall *update = gimple_build_call (uwm, 1, wmptr);
+ if (node)
+ gimple_set_location (update, DECL_SOURCE_LOCATION (node->decl));
+ gimple_seq_add_stmt (&seq, update);
+ if (node)
+#if !IMPLICIT_CGRAPH_EDGES
+ node->create_edge (cgraph_node::get_create (uwm), update, count, false);
+#else
+ (void)count;
+#endif
+ return seq;
+ }
+
+};
+
+} // anon namespace
+
+typedef hash_set<tree> indirect_parms_t;
+
+static tree
+maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
+{
+ if (DECL_P (op))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (op))
+ {
+ tree ret = gimple_fold_indirect_ref (op);
+ if (!ret)
+ ret = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (op)),
+ op,
+ build_int_cst (TREE_TYPE (op), 0));
+ return ret;
+ }
+ }
+ else if (TREE_CODE (op) == ADDR_EXPR
+ && DECL_P (TREE_OPERAND (op, 0)))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (TREE_OPERAND (op, 0)))
+ return TREE_OPERAND (op, 0);
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_make_indirect (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ indirect_parms_t &indirect_parms = *(indirect_parms_t *)wi->info;
+
+ if (!*op || TYPE_P (*op))
+ {
+ *rec = 0;
+ return NULL_TREE;
+ }
+
+ if (tree repl = maybe_make_indirect (indirect_parms, *op, rec))
+ {
+ *op = repl;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ gimple_stmt_iterator &gsi = *(gimple_stmt_iterator *)wi->info;
+
+ *rec = 0;
+
+ if (!*op || TREE_CODE (*op) != ADDR_EXPR)
+ return NULL_TREE;
+
+ if (!is_gimple_val (*op))
+ {
+ tree ret = force_gimple_operand_gsi (&gsi, *op, true,
+ NULL_TREE, true, GSI_SAME_STMT);
+ gcc_assert (ret != *op);
+ *op = ret;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+build_ref_type_for (tree parm, bool nonaliased = true)
+{
+ gcc_checking_assert (TREE_CODE (parm) == PARM_DECL);
+
+ tree ref_type = build_reference_type (TREE_TYPE (parm));
+
+ if (!nonaliased)
+ return ref_type;
+
+ /* Each PARM turned indirect still points to the distinct memory area at the
+ wrapper, and the reference in unchanging, so we might qualify it, but...
+ const is not really important, since we're only using default defs for the
+ reference parm anyway, and not introducing any defs, and restrict seems to
+ cause trouble. E.g., libgnat/s-concat3.adb:str_concat_3 has memmoves that,
+ if it's wrapped, the memmoves are deleted in dse1. Using a distinct alias
+ set seems to not run afoul of this problem, and it hopefully enables the
+ compiler to tell the pointers do point to objects that are not otherwise
+ aliased. */
+#if 1
+ tree qref_type = build_variant_type_copy (ref_type);
+
+ TYPE_ALIAS_SET (qref_type) = new_alias_set ();
+ record_alias_subset (TYPE_ALIAS_SET (qref_type), get_alias_set (ref_type));
+
+ return qref_type;
+#else
+ tree qref_type = build_qualified_type (ref_type,
+ TYPE_QUAL_RESTRICT
+ | TYPE_QUAL_CONST);
+
+ return qref_type;
+#endif
+}
+
+/* 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)
+{
+#if IMPLICIT_CGRAPH_EDGES
+ return;
+#endif
+
+ cgraph_node *node = cgraph_node::get_create (current_function_decl);
+
+ for (gimple_stmt_iterator gsi = gsi_start (seq);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!is_a <gcall *> (stmt))
+ 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);
+ }
+}
+
+static void
+gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
+{
+ if (!seq)
+ return;
+
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (gimple_has_location (stmt))
+ annotate_all_with_location (seq, gimple_location (stmt));
+
+ gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+ 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;
+ bool nothrow_p = (must_not_throw_p
+ || (call && gimple_call_nothrow_p (call))
+ || (eh_lp <= 0
+ && (TREE_NOTHROW (cfun->decl)
+ || !flag_exceptions)));
+
+ if (noreturn_p && nothrow_p)
+ return;
+
+ /* Don't expect an EH edge if we're not to throw, or if we're not in an EH
+ region yet. */
+ bool no_eh_edge_p = (nothrow_p || !eh_lp);
+ bool must_end_bb = stmt_ends_bb_p (stmt);
+
+ edge eft = NULL, eeh = NULL;
+ if (must_end_bb && !(noreturn_p && no_eh_edge_p))
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+
+ edge e;
+ edge_iterator ei;
+ FOR_EACH_EDGE (e, ei, gsi_bb (gsi)->succs)
+ {
+ if ((e->flags & EDGE_EH))
+ {
+ gcc_checking_assert (!eeh);
+ eeh = e;
+#if !CHECKING_P
+ if (eft || noreturn_p)
+ break;
+#endif
+ }
+ if ((e->flags & EDGE_FALLTHRU))
+ {
+ gcc_checking_assert (!eft);
+ eft = e;
+#if !CHECKING_P
+ if (eeh || no_eh_edge_p)
+ break;
+#endif
+ }
+ }
+
+ gcc_checking_assert (!(eft && (eft->flags & EDGE_FALLTHRU))
+ == noreturn_p);
+ gcc_checking_assert (!(eeh && (eeh->flags & EDGE_EH))
+ == no_eh_edge_p);
+ gcc_checking_assert (eft != eeh);
+ }
+
+ if (!noreturn_p)
+ {
+ gimple_seq nseq = nothrow_p ? seq : gimple_seq_copy (seq);
+
+ if (must_end_bb)
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+ add_call_edges_for_seq (nseq, eft->count ());
+ gsi_insert_seq_on_edge_immediate (eft, nseq);
+ }
+ else
+ {
+ add_call_edges_for_seq (nseq, gsi_bb (gsi)->count);
+ gsi_insert_seq_after (&gsi, nseq, GSI_SAME_STMT);
+ }
+ }
+
+ if (nothrow_p)
+ return;
+
+ if (eh_lp)
+ {
+ add_call_edges_for_seq (seq, eeh->count ());
+ gsi_insert_seq_on_edge_immediate (eeh, seq);
+ return;
+ }
+
+ /* A throwing call may appear within a basic block in a function that doesn't
+ have any EH regions. We're going to add a cleanup if so, therefore the
+ block will have to be split. */
+ basic_block bb = gsi_bb (gsi);
+ if (!gsi_one_before_end_p (gsi))
+ split_block (bb, stmt);
+
+ /* Create a new block for the EH cleanup. */
+ basic_block bb_eh_cleanup = create_empty_bb (bb);
+ if (dom_info_available_p (CDI_DOMINATORS))
+ set_immediate_dominator (CDI_DOMINATORS, bb_eh_cleanup, bb);
+ if (current_loops)
+ add_bb_to_loop (bb_eh_cleanup, current_loops->tree_root);
+
+ /* Make the new block an EH cleanup for the call. */
+ eh_region new_r = gen_eh_region_cleanup (NULL);
+ eh_landing_pad lp = gen_eh_landing_pad (new_r);
+ tree label = gimple_block_label (bb_eh_cleanup);
+ lp->post_landing_pad = label;
+ EH_LANDING_PAD_NR (label) = lp->index;
+ add_stmt_to_eh_lp (stmt, lp->index);
+
+ /* Add the cleanup code to the EH cleanup block. */
+ gsi = gsi_after_labels (bb_eh_cleanup);
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
+
+ /* And then propagate the exception further. */
+ gresx *resx = gimple_build_resx (new_r->index);
+ if (gimple_has_location (stmt))
+ gimple_set_location (resx, gimple_location (stmt));
+ gsi_insert_before (&gsi, resx, GSI_SAME_STMT);
+
+ /* Finally, wire the EH cleanup block into the CFG. */
+ make_eh_edges (stmt);
+ add_call_edges_for_seq (seq, single_pred_edge (bb_eh_cleanup)->count ());
+}
+
+/* Copy the attribute list at *ATTRS, minus any NAME attributes, leaving
+ shareable trailing nodes alone. */
+
+static inline void
+remove_named_attribute_unsharing (const char *name, tree *attrs)
+{
+ while (tree found = lookup_attribute (name, *attrs))
+ {
+ /* Copy nodes up to the next NAME attribute. */
+ while (*attrs != found)
+ {
+ *attrs = tree_cons (TREE_PURPOSE (*attrs),
+ TREE_VALUE (*attrs),
+ TREE_CHAIN (*attrs));
+ attrs = &TREE_CHAIN (*attrs);
+ }
+ /* Then drop it. */
+ gcc_checking_assert (*attrs == found);
+ *attrs = TREE_CHAIN (*attrs);
+ }
+}
+
+static int last_cgraph_order;
+
+static void
+ipa_strub_set_mode_for_new_functions ()
+{
+ if (symtab->order == last_cgraph_order)
+ return;
+
+ cgraph_node *node;
+
+ /* 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. */
+ for (int aliases = 0; aliases <= 1; aliases++)
+ FOR_EACH_FUNCTION (node)
+ {
+ if (!node->alias != !aliases)
+ continue;
+
+ /* Already done. */
+ if (node->order < last_cgraph_order)
+ continue;
+
+ set_strub_mode (node);
+ }
+
+ last_cgraph_order = symtab->order;
+}
+
+/* Return FALSE if NODE is a strub context, and TRUE otherwise. */
+bool
+strub_splittable_p (cgraph_node *node)
+{
+ switch (get_strub_mode (node))
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_AT_CALLS_OPT:
+ case STRUB_INLINABLE:
+ case STRUB_INTERNAL:
+ case STRUB_WRAPPER:
+ return false;
+
+ case STRUB_CALLABLE:
+ case STRUB_DISABLED:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ return true;
+}
+
+/* Return the PARM_DECL of the incoming watermark pointer, if there is one. */
+tree
+strub_watermark_parm (tree fndecl)
+{
+ switch (get_strub_mode_from_decl (fndecl))
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_AT_CALLS_OPT:
+ break;
+
+ case STRUB_INTERNAL:
+ case STRUB_WRAPPER:
+ case STRUB_CALLABLE:
+ case STRUB_DISABLED:
+ case STRUB_INLINABLE:
+ return NULL_TREE;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ for (tree parm = DECL_ARGUMENTS (fndecl); parm; parm = DECL_CHAIN (parm))
+ /* The type (variant) compare finds the parameter even in a just-created
+ clone, before we set its name, but the type-based compare doesn't work
+ during builtin expansion within the lto compiler, because we'll have
+ created a separate variant in that run. */
+ if (TREE_TYPE (parm) == pass_ipa_strub::get_qpwmt ()
+ || DECL_NAME (parm) == pass_ipa_strub::get_watermark_ptr ())
+ return parm;
+
+ gcc_unreachable ();
+}
+
+/* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
+ hasn't been added yet. Return the named argument count. */
+int
+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 already 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);
+ }
+
+ /* 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);
+
+#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 we're already within a strub context, pass on the incoming watermark
+ pointer, and omit the enter and leave calls around the modified call, as an
+ optimization, or as a means to satisfy a tail-call requirement. */
+ tree swmp = ((optimize_size || optimize > 2
+ || gimple_call_must_tail_p (ocall)
+ || (optimize == 2 && gimple_call_tail_p (ocall)))
+ ? strub_watermark_parm (e->caller->decl)
+ : NULL_TREE);
+ bool omit_own_watermark = swmp;
+ tree swm = NULL_TREE;
+ if (!omit_own_watermark)
+ {
+ swm = create_tmp_var (get_wmt (), ".strub.watermark");
+ TREE_ADDRESSABLE (swm) = true;
+ swmp = build1 (ADDR_EXPR, get_pwmt (), swm);
+
+ /* Initialize the watermark before the call. */
+ tree enter = get_enter ();
+ gcall *stptr = gimple_build_call (enter, 1,
+ unshare_expr (swmp));
+ if (gimple_has_location (ocall))
+ gimple_set_location (stptr, gimple_location (ocall));
+ 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)))
+ {
+ if (!swm)
+ swm = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (swmp)),
+ swmp,
+ build_int_cst (TREE_TYPE (swmp), 0));
+
+ vec<tree, va_gc> *inputs = NULL;
+ vec<tree, va_gc> *outputs = NULL;
+ vec_safe_push (outputs,
+ build_tree_list
+ (build_tree_list
+ (NULL_TREE, build_string (2, "=m")),
+ unshare_expr (swm)));
+ vec_safe_push (inputs,
+ build_tree_list
+ (build_tree_list
+ (NULL_TREE, build_string (1, "m")),
+ unshare_expr (swm)));
+ gasm *forcemod = gimple_build_asm_vec ("", inputs, outputs,
+ NULL, NULL);
+ gimple_seq_add_stmt (&seq, forcemod);
+
+ /* If the call will be assumed to not even read the watermark,
+ make sure it is already in memory before the call. */
+ if ((gimple_call_flags (wrcall) & ECF_CONST))
+ {
+ vec<tree, va_gc> *inputs = NULL;
+ vec_safe_push (inputs,
+ build_tree_list
+ (build_tree_list
+ (NULL_TREE, build_string (1, "m")),
+ unshare_expr (swm)));
+ gasm *force_store = gimple_build_asm_vec ("", inputs, NULL,
+ NULL, NULL);
+ if (gimple_has_location (wrcall))
+ gimple_set_location (force_store, gimple_location (wrcall));
+ gsi_insert_before (&gsi, force_store, GSI_SAME_STMT);
+ }
+ }
+#endif
+
+ if (!omit_own_watermark)
+ {
+ gcall *sleave = gimple_build_call (get_leave (), 1,
+ unshare_expr (swmp));
+ gimple_seq_add_stmt (&seq, sleave);
+
+ gassign *clobber = gimple_build_assign (swm,
+ build_clobber
+ (TREE_TYPE (swm)));
+ gimple_seq_add_stmt (&seq, clobber);
+ }
+
+ gsi_insert_finally_seq_after_call (gsi, seq);
+}
+
+void
+pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
+{
+ /* Adjust unknown-callee indirect calls with STRUB_AT_CALLS types within
+ onode. */
+ if (node->indirect_calls)
+ {
+ push_cfun (DECL_STRUCT_FUNCTION (node->decl));
+ for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
+ {
+ gcc_checking_assert (e->indirect_unknown_callee);
+
+ tree callee_fntype = gimple_call_fntype (e->call_stmt);
+ enum strub_mode callee_mode
+ = get_strub_mode_from_type (callee_fntype);
+
+ if (callee_mode != STRUB_AT_CALLS
+ && callee_mode != STRUB_AT_CALLS_OPT)
+ continue;
+
+ int named_args = adjust_at_calls_type (callee_fntype);
+
+ adjust_at_calls_call (e, named_args);
+ }
+ pop_cfun ();
+ }
+
+ if (node->callees)
+ {
+ push_cfun (DECL_STRUCT_FUNCTION (node->decl));
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ gcc_checking_assert (!e->indirect_unknown_callee);
+
+ enum strub_mode callee_mode = get_strub_mode (e->callee);
+
+ if (callee_mode != STRUB_AT_CALLS
+ && callee_mode != STRUB_AT_CALLS_OPT)
+ continue;
+
+ int named_args = adjust_at_calls_type (TREE_TYPE (e->callee->decl));
+
+ adjust_at_calls_call (e, named_args);
+ }
+ pop_cfun ();
+ }
+}
+
+unsigned int
+pass_ipa_strub_mode::execute (function *)
+{
+ last_cgraph_order = 0;
+ ipa_strub_set_mode_for_new_functions ();
+
+ /* Verify before any inlining or other transformations. */
+ verify_strub ();
+
+ return 0;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub_mode (gcc::context *ctxt)
+{
+ return new pass_ipa_strub_mode (ctxt);
+}
+
+unsigned int
+pass_ipa_strub::execute (function *)
+{
+ cgraph_node *onode;
+
+ 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
+ || mode == STRUB_AT_CALLS_OPT)
+ {
+ /* 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. */
+ bool aliased_parms = (onode->alias && *pargs
+ && DECL_CONTEXT (*pargs) != onode->decl);
+
+ 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_qpwmt ());
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_ARG_TYPE (wmptr) = get_qpwmt ();
+ DECL_CONTEXT (wmptr) = onode->decl;
+ TREE_USED (wmptr) = 1;
+ DECL_CHAIN (wmptr) = *pargs;
+ *pargs = wmptr;
+
+ 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));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca)
+ {
+ 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);
+
+ if (!is_gimple_call (stmt))
+ continue;
+
+ gcall *call = as_a <gcall *> (stmt);
+
+ if (gimple_alloca_call_p (call))
+ {
+ /* Capture stack growth. */
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)
+ ->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ }
+ }
+
+ pop_cfun ();
+
+#if 0
+ compute_fn_summary (onode, true);
+#endif
+ }
+ }
+
+ FOR_EACH_FUNCTION (onode)
+ {
+ if (!onode->has_gimple_body_p ())
+ continue;
+
+ enum strub_mode mode = get_strub_mode (onode);
+
+ if (mode != STRUB_INTERNAL)
+ {
+ adjust_at_calls_calls (onode);
+ 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);
+
+ vec<ipa_adjusted_param, va_gc> *nparms = NULL;
+ unsigned j = 0;
+ {
+ // The following loop copied from ipa-split.c:split_function.
+ for (tree parm = DECL_ARGUMENTS (onode->decl);
+ parm; parm = DECL_CHAIN (parm), j++)
+ {
+ ipa_adjusted_param adj = {};
+ adj.op = IPA_PARAM_OP_COPY;
+ adj.base_index = j;
+ adj.prev_clone_index = j;
+ vec_safe_push (nparms, adj);
+ }
+
+ if (apply_args)
+ {
+ ipa_adjusted_param aaadj = {};
+ aaadj.op = IPA_PARAM_OP_NEW;
+ aaadj.type = get_qptr ();
+ vec_safe_push (nparms, aaadj);
+ }
+
+ if (is_stdarg)
+ {
+ ipa_adjusted_param vladj = {};
+ vladj.op = IPA_PARAM_OP_NEW;
+ vladj.type = get_qpvalst ();
+ vec_safe_push (nparms, vladj);
+ }
+
+ ipa_adjusted_param wmadj = {};
+ wmadj.op = IPA_PARAM_OP_NEW;
+ wmadj.type = get_qpwmt ();
+ vec_safe_push (nparms, wmadj);
+ }
+ ipa_param_adjustments adj (nparms, -1, false);
+
+ cgraph_node *nnode = onode->create_version_clone_with_body
+ (auto_vec<cgraph_edge *> (0),
+ NULL, &adj, NULL, NULL, "strub", NULL);
+
+ if (!nnode)
+ {
+ error_at (DECL_SOURCE_LOCATION (onode->decl),
+ "failed to split %qD for %<strub%>",
+ onode->decl);
+ continue;
+ }
+
+ onode->split_part = true;
+ if (onode->calls_comdat_local)
+ nnode->add_to_same_comdat_group (onode);
+
+ gcc_checking_assert (!DECL_STRUCT_FUNCTION (nnode->decl)->stdarg);
+
+ set_strub_mode_to (onode, STRUB_WRAPPER);
+ set_strub_mode_to (nnode, STRUB_WRAPPED);
+
+ adjust_at_calls_calls (nnode);
+
+ /* Decide which of the wrapped function's parms we want to turn into
+ references to the argument passed to the wrapper. In general, we want to
+ copy small arguments, and avoid copying large ones. Variable-sized array
+ lengths given by other arguments, as in 20020210-1.c, would lead to
+ problems if passed by value, after resetting the original function and
+ dropping the length computation; passing them by reference works.
+ DECL_BY_REFERENCE is *not* a substitute for this: it involves copying
+ anyway, but performed at the caller. */
+ indirect_parms_t indirect_nparms (3, false);
+ unsigned adjust_ftype = 0;
+ unsigned named_args = 0;
+ for (tree parm = DECL_ARGUMENTS (onode->decl),
+ nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (TREE_TYPE (nnode->decl));
+ parm;
+ named_args++,
+ parm = DECL_CHAIN (parm),
+ nparm = DECL_CHAIN (nparm),
+ nparmt = nparmt ? TREE_CHAIN (nparmt) : NULL_TREE)
+ if (!(0 /* DECL_BY_REFERENCE (narg) */
+ || is_gimple_reg_type (TREE_TYPE (nparm))
+ || VECTOR_TYPE_P (TREE_TYPE (nparm))
+ || TREE_CODE (TREE_TYPE (nparm)) == COMPLEX_TYPE
+ || (tree_fits_uhwi_p (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ && (tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ <= 4 * UNITS_PER_WORD))))
+ {
+ indirect_nparms.add (nparm);
+
+ /* ??? Is there any case in which it is not safe to suggest the parms
+ turned indirect don't alias anything else? They are distinct,
+ unaliased memory in the wrapper, and the wrapped can't possibly
+ take pointers into them because none of the pointers passed to the
+ wrapper can alias other incoming parameters passed by value, even
+ if with transparent reference, and the wrapper doesn't take any
+ extra parms that could point into wrapper's parms. So we can
+ probably drop the TREE_ADDRESSABLE and keep the true. */
+ tree ref_type = build_ref_type_for (nparm,
+ true
+ || !TREE_ADDRESSABLE (parm));
+
+ DECL_ARG_TYPE (nparm) = TREE_TYPE (nparm) = ref_type;
+ relayout_decl (nparm);
+ TREE_ADDRESSABLE (nparm) = 0;
+ DECL_BY_REFERENCE (nparm) = 0;
+#if FOR_GCC_11P
+ DECL_NOT_GIMPLE_REG_P (nparm) = 0;
+#else
+ DECL_GIMPLE_REG_P (nparm) = 1;
+#endif
+ /* ??? This avoids mismatches in debug info bind stmts in
+ e.g. a-chahan . */
+ DECL_ABSTRACT_ORIGIN (nparm) = NULL;
+
+ if (nparmt)
+ adjust_ftype++;
+ }
+
+ /* Also adjust the wrapped function type, if needed. */
+ if (adjust_ftype)
+ {
+ tree nftype = TREE_TYPE (nnode->decl);
+
+ /* We always add at least one argument at the end of the signature, when
+ cloning the function, so we don't expect to need to duplicate the
+ type here. */
+ gcc_checking_assert (TYPE_ARG_TYPES (nftype)
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+
+#if HAVE_ATTR_FNSPEC
+ /* Check that fnspec still works for the modified function signature,
+ and drop it otherwise. */
+ bool drop_fnspec = false;
+ tree fnspec = lookup_attribute ("fn spec", TYPE_ATTRIBUTES (nftype));
+ attr_fnspec spec = fnspec ? attr_fnspec (fnspec) : attr_fnspec ("");
+
+ unsigned retcopy;
+ if (!(fnspec && spec.returns_arg (&retcopy)))
+ retcopy = (unsigned) -1;
+
+ unsigned i = 0;
+#endif
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (nftype);
+ adjust_ftype > 0;
+#if HAVE_ATTR_FNSPEC
+ i++,
+#endif
+ nparm = DECL_CHAIN (nparm), nparmt = TREE_CHAIN (nparmt))
+ if (indirect_nparms.contains (nparm))
+ {
+ TREE_VALUE (nparmt) = TREE_TYPE (nparm);
+ adjust_ftype--;
+
+#if HAVE_ATTR_FNSPEC
+ if (fnspec && !drop_fnspec)
+ {
+ if (i == retcopy)
+ drop_fnspec = true;
+ else if (spec.arg_specified_p (i))
+ {
+ /* Properties that apply to pointers only must not be
+ present, because we don't make pointers further
+ indirect. */
+ gcc_checking_assert
+ (!spec.arg_max_access_size_given_by_arg_p (i, NULL));
+ gcc_checking_assert (!spec.arg_copied_to_arg_p (i, NULL));
+
+ /* Any claim of direct access only is invalidated by
+ adding an indirection level. */
+ if (spec.arg_direct_p (i))
+ drop_fnspec = true;
+
+ /* If there's a claim the argument is not read from, the
+ added indirection invalidates it: if the argument is
+ used at all, then the pointer will necessarily be
+ read. */
+ if (!spec.arg_maybe_read_p (i)
+ && spec.arg_used_p (i))
+ drop_fnspec = true;
+ }
+ }
+#endif
+ }
+
+#if HAVE_ATTR_FNSPEC
+ /* ??? Maybe we could adjust it instead. */
+ if (drop_fnspec)
+ remove_named_attribute_unsharing ("fn spec",
+ &TYPE_ATTRIBUTES (nftype));
+#endif
+
+ TREE_TYPE (nnode->decl) = nftype;
+ }
+
+#if ATTR_FNSPEC_DECONST_WATERMARK
+ {
+ int flags = flags_from_decl_or_type (nnode->decl);
+ tree fnspec = lookup_attribute ("fn spec", TREE_TYPE (nnode->decl));
+
+ if ((flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS)) || fnspec)
+ {
+ size_t xargs = 1 + int (is_stdarg) + int (apply_args);
+ 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! */
+ bool no_writes_p = true;
+ if (fnspec)
+ {
+ tree fnspecstr = TREE_VALUE (TREE_VALUE (fnspec));
+ curlen = TREE_STRING_LENGTH (fnspecstr);
+ memcpy (nspec, TREE_STRING_POINTER (fnspecstr), curlen);
+ if (!(flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS))
+ && curlen >= 2
+ && nspec[1] != 'c' && nspec[1] != 'C'
+ && nspec[1] != 'p' && nspec[1] != 'P')
+ no_writes_p = false;
+ }
+ if (!curlen)
+ {
+ nspec[curlen++] = '.';
+ nspec[curlen++] = ((flags & ECF_CONST)
+ ? 'c'
+ : (flags & ECF_PURE)
+ ? 'p'
+ : ' ');
+ }
+ while (curlen < tgtlen - 2 * xargs)
+ {
+ nspec[curlen++] = '.';
+ nspec[curlen++] = ' ';
+ }
+
+ /* These extra args are unlikely to be present in const or pure
+ functions. It's conceivable that a function that takes variable
+ arguments, or that passes its arguments on to another function,
+ could be const or pure, but it would not modify the arguments, and,
+ being pure or const, it couldn't possibly modify or even access
+ memory referenced by them. But it can read from these internal
+ data structures created by the wrapper, and from any
+ argument-passing memory referenced by them, so we denote the
+ possibility of reading from multiple levels of indirection, but
+ only of reading because const/pure. */
+ if (apply_args)
+ {
+ nspec[curlen++] = 'r';
+ nspec[curlen++] = ' ';
+ }
+ if (is_stdarg)
+ {
+ nspec[curlen++] = (no_writes_p ? 'r' : '.');
+ nspec[curlen++] = (no_writes_p ? 't' : ' ');
+ }
+
+ nspec[curlen++] = 'W';
+ nspec[curlen++] = 't';
+
+ /* The type has already been copied before adding parameters. */
+ gcc_checking_assert (TYPE_ARG_TYPES (TREE_TYPE (nnode->decl))
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+ TYPE_ATTRIBUTES (TREE_TYPE (nnode->decl))
+ = tree_cons (get_identifier ("fn spec"),
+ build_tree_list (NULL_TREE,
+ build_string (tgtlen, nspec)),
+ TYPE_ATTRIBUTES (TREE_TYPE (nnode->decl)));
+ }
+ }
+#endif
+
+ {
+ tree decl = onode->decl;
+ cgraph_node *target = nnode;
+
+ { // copied from create_wrapper
+
+ /* Preserve DECL_RESULT so we get right by reference flag. */
+ tree decl_result = DECL_RESULT (decl);
+
+ /* Remove the function's body but keep arguments to be reused
+ for thunk. */
+ onode->release_body (true);
+ onode->reset ();
+
+ DECL_UNINLINABLE (decl) = false;
+ DECL_RESULT (decl) = decl_result;
+ DECL_INITIAL (decl) = NULL;
+ allocate_struct_function (decl, false);
+ set_cfun (NULL);
+
+ /* Turn alias into thunk and expand it into GIMPLE representation. */
+ onode->definition = true;
+
+#if FOR_GCC_11P
+ thunk_info::get_create (onode);
+ onode->thunk = true;
+#else
+ memset (&onode->thunk, 0, sizeof (cgraph_thunk_info));
+ onode->thunk.thunk_p = true;
+ onode->thunk.alias = target->decl;
+#endif
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (target, NULL, onode->count);
+#endif
+ onode->callees->can_throw_external = !TREE_NOTHROW (target->decl);
+
+ tree arguments = DECL_ARGUMENTS (decl);
+
+ while (arguments)
+ {
+ TREE_ADDRESSABLE (arguments) = false;
+ arguments = TREE_CHAIN (arguments);
+ }
+
+ {
+ tree alias = onode->callees->callee->decl;
+ tree thunk_fndecl = decl;
+ tree a;
+
+ int nxargs = 1 + is_stdarg + apply_args;
+
+ { // Simplified from expand_thunk.
+ tree restype;
+ basic_block bb, then_bb, else_bb, return_bb;
+ gimple_stmt_iterator bsi;
+ int nargs = 0;
+ tree arg;
+ int i;
+ tree resdecl;
+ tree restmp = NULL;
+
+ gcall *call;
+ greturn *ret;
+ bool alias_is_noreturn = TREE_THIS_VOLATILE (alias);
+
+ a = DECL_ARGUMENTS (thunk_fndecl);
+
+ current_function_decl = thunk_fndecl;
+
+#if FOR_GCC_11P
+ /* Ensure thunks are emitted in their correct sections. */
+ resolve_unique_section (thunk_fndecl, 0,
+ flag_function_sections);
+#endif
+
+ bitmap_obstack_initialize (NULL);
+
+ /* Build the return declaration for the function. */
+ restype = TREE_TYPE (TREE_TYPE (thunk_fndecl));
+ if (DECL_RESULT (thunk_fndecl) == NULL_TREE)
+ {
+ resdecl = build_decl (input_location, RESULT_DECL, 0, restype);
+ DECL_ARTIFICIAL (resdecl) = 1;
+ DECL_IGNORED_P (resdecl) = 1;
+ DECL_CONTEXT (resdecl) = thunk_fndecl;
+ DECL_RESULT (thunk_fndecl) = resdecl;
+ }
+ else
+ resdecl = DECL_RESULT (thunk_fndecl);
+
+ profile_count cfg_count = onode->count;
+ if (!cfg_count.initialized_p ())
+ cfg_count = profile_count::from_gcov_type (BB_FREQ_MAX).guessed_local ();
+
+ bb = then_bb = else_bb = return_bb
+ = init_lowered_empty_function (thunk_fndecl, true, cfg_count);
+
+ bsi = gsi_start_bb (bb);
+
+ /* Build call to the function being thunked. */
+ if (!VOID_TYPE_P (restype)
+ && (!alias_is_noreturn
+ || TREE_ADDRESSABLE (restype)
+ || TREE_CODE (TYPE_SIZE_UNIT (restype)) != INTEGER_CST))
+ {
+ if (DECL_BY_REFERENCE (resdecl))
+ {
+ restmp = gimple_fold_indirect_ref (resdecl);
+ if (!restmp)
+ restmp = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (resdecl)),
+ resdecl,
+ build_int_cst (TREE_TYPE (resdecl), 0));
+ }
+ else if (!is_gimple_reg_type (restype))
+ {
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl)))
+ {
+ restmp = resdecl;
+
+ if (VAR_P (restmp))
+ {
+ add_local_decl (cfun, restmp);
+ BLOCK_VARS (DECL_INITIAL (current_function_decl))
+ = restmp;
+ }
+ }
+ else
+ restmp = create_tmp_var (restype, "retval");
+ }
+ else
+ restmp = create_tmp_reg (restype, "retval");
+ }
+
+ for (arg = a; arg; arg = DECL_CHAIN (arg))
+ nargs++;
+ auto_vec<tree> vargs (nargs + nxargs);
+ i = 0;
+ arg = a;
+
+ if (nargs)
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl);
+ i < nargs;
+ i++, arg = DECL_CHAIN (arg), nparm = DECL_CHAIN (nparm))
+ {
+ tree save_arg = arg;
+ tree tmp = arg;
+
+ /* Arrange to pass indirectly the parms, if we decided to do
+ so, and revert its type in the wrapper. */
+ if (indirect_nparms.contains (nparm))
+ {
+ tree ref_type = TREE_TYPE (nparm);
+ TREE_ADDRESSABLE (arg) = true;
+ tree addr = build1 (ADDR_EXPR, ref_type, arg);
+ tmp = arg = addr;
+ }
+#if ! FOR_GCC_11P
+ else if (VECTOR_TYPE_P (TREE_TYPE (arg))
+ || TREE_CODE (TREE_TYPE (arg)) == COMPLEX_TYPE)
+ DECL_GIMPLE_REG_P (arg) = 1;
+#else
+ else
+ DECL_NOT_GIMPLE_REG_P (arg) = 0;
+#endif
+
+ /* Convert the argument back to the type used by the calling
+ conventions, e.g. a non-prototyped float type is passed as
+ double, as in 930603-1.c, and needs to be converted back to
+ double to be passed on unchanged to the wrapped
+ function. */
+ if (TREE_TYPE (nparm) != DECL_ARG_TYPE (nparm))
+ arg = fold_convert (DECL_ARG_TYPE (nparm), arg);
+
+ if (!is_gimple_val (arg))
+ {
+ tmp = create_tmp_reg (TYPE_MAIN_VARIANT
+ (TREE_TYPE (arg)), "arg");
+ gimple *stmt = gimple_build_assign (tmp, arg);
+ gsi_insert_after (&bsi, stmt, GSI_NEW_STMT);
+ }
+ vargs.quick_push (tmp);
+ arg = save_arg;
+ }
+ /* These strub arguments are adjusted later. */
+ if (apply_args)
+ vargs.quick_push (null_pointer_node);
+ if (is_stdarg)
+ vargs.quick_push (null_pointer_node);
+ vargs.quick_push (null_pointer_node);
+ call = gimple_build_call_vec (build_fold_addr_expr_loc (0, alias),
+ vargs);
+ onode->callees->call_stmt = call;
+ // gimple_call_set_from_thunk (call, true);
+ if (DECL_STATIC_CHAIN (alias))
+ {
+ tree p = DECL_STRUCT_FUNCTION (alias)->static_chain_decl;
+ tree type = TREE_TYPE (p);
+ tree decl = build_decl (DECL_SOURCE_LOCATION (thunk_fndecl),
+ PARM_DECL, create_tmp_var_name ("CHAIN"),
+ type);
+ DECL_ARTIFICIAL (decl) = 1;
+ DECL_IGNORED_P (decl) = 1;
+ TREE_USED (decl) = 1;
+ DECL_CONTEXT (decl) = thunk_fndecl;
+ DECL_ARG_TYPE (decl) = type;
+ TREE_READONLY (decl) = 1;
+
+ struct function *sf = DECL_STRUCT_FUNCTION (thunk_fndecl);
+ sf->static_chain_decl = decl;
+
+ gimple_call_set_chain (call, decl);
+ }
+
+ /* Return slot optimization is always possible and in fact required to
+ return values with DECL_BY_REFERENCE. */
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl))
+ && (!is_gimple_reg_type (TREE_TYPE (resdecl))
+ || DECL_BY_REFERENCE (resdecl)))
+ gimple_call_set_return_slot_opt (call, true);
+
+ if (restmp)
+ {
+ gimple_call_set_lhs (call, restmp);
+ gcc_assert (useless_type_conversion_p (TREE_TYPE (restmp),
+ TREE_TYPE (TREE_TYPE (alias))));
+ }
+ gsi_insert_after (&bsi, call, GSI_NEW_STMT);
+ if (!alias_is_noreturn)
+ {
+ /* Build return value. */
+ if (!DECL_BY_REFERENCE (resdecl))
+ ret = gimple_build_return (restmp);
+ else
+ ret = gimple_build_return (resdecl);
+
+ gsi_insert_after (&bsi, ret, GSI_NEW_STMT);
+ }
+ else
+ {
+ remove_edge (single_succ_edge (bb));
+ }
+
+ cfun->gimple_df->in_ssa_p = true;
+ update_max_bb_count ();
+ profile_status_for_fn (cfun)
+ = cfg_count.initialized_p () && cfg_count.ipa_p ()
+ ? PROFILE_READ : PROFILE_GUESSED;
+#if FOR_GCC_11P
+ /* FIXME: C++ FE should stop setting TREE_ASM_WRITTEN on thunks. */
+ // TREE_ASM_WRITTEN (thunk_fndecl) = false;
+#endif
+ delete_unreachable_blocks ();
+ update_ssa (TODO_update_ssa);
+ checking_verify_flow_info ();
+ free_dominance_info (CDI_DOMINATORS);
+
+ /* Since we want to emit the thunk, we explicitly mark its name as
+ referenced. */
+#if FOR_GCC_11P
+ onode->thunk = false;
+#else
+ onode->thunk.thunk_p = false;
+#endif
+ onode->lowered = true;
+ bitmap_obstack_release (NULL);
+ }
+ current_function_decl = NULL;
+ set_cfun (NULL);
+ }
+
+#if FOR_GCC_11P
+ thunk_info::remove (onode);
+#endif
+
+ // some more of create_wrapper at the end of the next block.
+ }
+ }
+
+ {
+ tree aaval = NULL_TREE;
+ tree vaptr = NULL_TREE;
+ tree wmptr = NULL_TREE;
+ for (tree arg = DECL_ARGUMENTS (nnode->decl); arg; arg = DECL_CHAIN (arg))
+ {
+ aaval = vaptr;
+ vaptr = wmptr;
+ wmptr = arg;
+ }
+
+ if (!apply_args)
+ aaval = NULL_TREE;
+ /* The trailing args are [apply_args], [va_list_ptr], and
+ watermark. If we don't have a va_list_ptr, the penultimate
+ argument is apply_args.
+ */
+ else if (!is_stdarg)
+ aaval = vaptr;
+
+ if (!is_stdarg)
+ vaptr = NULL_TREE;
+
+ DECL_NAME (wmptr) = get_watermark_ptr ();
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_IGNORED_P (wmptr) = 1;
+ TREE_USED (wmptr) = 1;
+
+ if (is_stdarg)
+ {
+ DECL_NAME (vaptr) = get_va_list_ptr ();
+ DECL_ARTIFICIAL (vaptr) = 1;
+ DECL_IGNORED_P (vaptr) = 1;
+ TREE_USED (vaptr) = 1;
+ }
+
+ if (apply_args)
+ {
+ DECL_NAME (aaval) = get_apply_args ();
+ DECL_ARTIFICIAL (aaval) = 1;
+ DECL_IGNORED_P (aaval) = 1;
+ TREE_USED (aaval) = 1;
+ }
+
+ push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ bool any_indirect = !indirect_nparms.is_empty ();
+
+ if (any_indirect)
+ {
+ 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);
+
+ 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
+ || is_stdarg || apply_args)
+ for (cgraph_edge *e = nnode->callees, *enext; e; e = enext)
+ {
+ gcall *call = e->call_stmt;
+ gimple_stmt_iterator gsi = gsi_for_stmt (call);
+ tree fndecl = e->callee->decl;
+
+ enext = e->next_callee;
+
+ if (gimple_alloca_call_p (call))
+ {
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ else if (fndecl && is_stdarg
+ && fndecl_built_in_p (fndecl, BUILT_IN_VA_START))
+ {
+ /* Using a non-default stdarg ABI makes the function ineligible
+ for internal strub. */
+ gcc_checking_assert (builtin_decl_explicit (BUILT_IN_VA_START)
+ == fndecl);
+ tree bvacopy = builtin_decl_explicit (BUILT_IN_VA_COPY);
+ gimple_call_set_fndecl (call, bvacopy);
+ tree arg = vaptr;
+ /* The va_copy source must be dereferenced, unless it's an array
+ type, that would have decayed to a pointer. */
+ if (TREE_CODE (TREE_TYPE (TREE_TYPE (vaptr))) != ARRAY_TYPE)
+ {
+ arg = gimple_fold_indirect_ref (vaptr);
+ if (!arg)
+ arg = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (vaptr)),
+ vaptr,
+ build_int_cst (TREE_TYPE (vaptr), 0));
+ }
+ gimple_call_set_arg (call, 1, arg);
+ update_stmt (call);
+ e->redirect_callee (cgraph_node::get_create (bvacopy));
+ }
+ else if (fndecl && apply_args
+ && fndecl_built_in_p (fndecl, BUILT_IN_APPLY_ARGS))
+ {
+ tree lhs = gimple_call_lhs (call);
+ gimple *assign = (lhs
+ ? gimple_build_assign (lhs, aaval)
+ : gimple_build_nop ());
+ gsi_replace (&gsi, assign, true);
+ cgraph_edge::remove (e);
+ }
+ }
+
+ { // a little more copied from create_wrapper
+
+ /* Inline summary set-up. */
+ nnode->analyze ();
+ // inline_analyze_function (nnode);
+ }
+
+ pop_cfun ();
+ }
+
+ {
+ 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));
+
+ 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));
+ gimple_set_location (stptr, gimple_location (wrcall));
+ gsi_insert_before (&gsi, stptr, GSI_SAME_STMT);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (enter),
+ stptr, gsi_bb (gsi)->count, false);
+#endif
+
+ int nargs = gimple_call_num_args (wrcall);
+
+ gimple_seq seq = NULL;
+
+ if (apply_args)
+ {
+ tree aalst = create_tmp_var (ptr_type_node, ".strub.apply_args");
+ tree bappargs = builtin_decl_explicit (BUILT_IN_APPLY_ARGS);
+ gcall *appargs = gimple_build_call (bappargs, 0);
+ gimple_call_set_lhs (appargs, aalst);
+ gimple_set_location (appargs, gimple_location (wrcall));
+ gsi_insert_before (&gsi, appargs, GSI_SAME_STMT);
+ gimple_call_set_arg (wrcall, nargs - 2 - is_stdarg, aalst);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bappargs),
+ appargs, gsi_bb (gsi)->count, false);
+#endif
+ }
+
+ if (is_stdarg)
+ {
+ tree valst = create_tmp_var (va_list_type_node, ".strub.va_list");
+ TREE_ADDRESSABLE (valst) = true;
+ tree vaptr = build1 (ADDR_EXPR,
+ build_pointer_type (va_list_type_node),
+ valst);
+ gimple_call_set_arg (wrcall, nargs - 2, unshare_expr (vaptr));
+
+ tree bvastart = builtin_decl_explicit (BUILT_IN_VA_START);
+ gcall *vastart = gimple_build_call (bvastart, 2,
+ unshare_expr (vaptr),
+ integer_zero_node);
+ gimple_set_location (vastart, gimple_location (wrcall));
+ gsi_insert_before (&gsi, vastart, GSI_SAME_STMT);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bvastart),
+ vastart, gsi_bb (gsi)->count, false);
+#endif
+
+ tree bvaend = builtin_decl_explicit (BUILT_IN_VA_END);
+ gcall *vaend = gimple_build_call (bvaend, 1, unshare_expr (vaptr));
+ gimple_set_location (vaend, gimple_location (wrcall));
+ gimple_seq_add_stmt (&seq, vaend);
+ }
+
+ gimple_call_set_arg (wrcall, nargs - 1, unshare_expr (swmp));
+ // gimple_call_set_tail (wrcall, false);
+ update_stmt (wrcall);
+
+ {
+#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);
+ gimple_set_location (force_store, gimple_location (wrcall));
+ 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);
+
+ /* For nnode, we don't rebuild edges because we wish to retain
+ any redirections copied to it from earlier passes, so we add
+ call graph edges explicitly there, but for onode, we create a
+ fresh function, so we may as well just issue the calls and
+ then rebuild all cgraph edges. */
+ // cgraph_edge::rebuild_edges ();
+ onode->analyze ();
+ // inline_analyze_function (onode);
+
+ 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;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub (gcc::context *ctxt)
+{
+ return new pass_ipa_strub (ctxt);
+}
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
new file mode 100644
index 00000000000..c335ab42097
--- /dev/null
+++ b/gcc/ipa-strub.h
@@ -0,0 +1,35 @@
+/* strub (stack scrubbing) infrastructure.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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 FALSE if NODE is a strub context, and TRUE otherwise. */
+extern bool strub_splittable_p (cgraph_node *node);
+
+/* Locate and return the watermark_ptr parameter for FNDECL. If FNDECL is not a
+ strub context, return NULL. */
+extern tree strub_watermark_parm (tree fndecl);
+
+/* Make a function type or declaration callable. */
+extern void strub_make_callable (tree fndecl);
diff --git a/gcc/passes.def b/gcc/passes.def
index d7a1f8c97a6..8e4638d20ed 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -53,6 +53,7 @@ along with GCC; see the file COPYING3. If not see
INSERT_PASSES_AFTER (all_small_ipa_passes)
NEXT_PASS (pass_ipa_free_lang_data);
NEXT_PASS (pass_ipa_function_and_variable_visibility);
+ NEXT_PASS (pass_ipa_strub_mode);
NEXT_PASS (pass_build_ssa_passes);
PUSH_INSERT_PASSES_WITHIN (pass_build_ssa_passes)
NEXT_PASS (pass_fixup_cfg);
@@ -111,6 +112,7 @@ along with GCC; see the file COPYING3. If not see
POP_INSERT_PASSES ()
NEXT_PASS (pass_ipa_remove_symbols);
+ NEXT_PASS (pass_ipa_strub);
NEXT_PASS (pass_ipa_oacc);
PUSH_INSERT_PASSES_WITHIN (pass_ipa_oacc)
NEXT_PASS (pass_ipa_pta);
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..7848c46d179
--- /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 "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
new file mode 100644
index 00000000000..85a8f76785e
--- /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" } */
+
+/* With -fno-inline, 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 "\[(\]call\[^\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..a2eedfd96b2
--- /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, 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 "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
new file mode 100644
index 00000000000..e5cb1f60541
--- /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 "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
new file mode 100644
index 00000000000..194aacc2c05
--- /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 "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
new file mode 100644
index 00000000000..46e84bf6560
--- /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 inline void
+__attribute__ ((__always_inline__))
+h() {
+}
+
+/* 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-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
new file mode 100644
index 00000000000..f180b17f30e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__ (3)))
+apply_function (void *args)
+{
+ __builtin_apply (0, args, 0);
+}
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+ void *args = __builtin_apply_args ();
+ apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
new file mode 100644
index 00000000000..379a54b73b7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+extern void __attribute__ ((__strub__))
+apply_function (void *args);
+
+void __attribute__ ((__strub__))
+apply_args (int i, int j, double d) /* { dg-error "selected" } */
+{
+ void *args = __builtin_apply_args (); /* { dg-message "does not support" } */
+ apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
new file mode 100644
index 00000000000..9b4786be698
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__))
+apply_function (void *args)
+{
+ __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
new file mode 100644
index 00000000000..409f747743e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+
+/* Check that implicit enabling of strub mode selects internal strub when the
+ function uses __builtin_apply_args, that prevents the optimization to
+ at-calls mode. */
+
+int __attribute__ ((__strub__)) var;
+
+static inline void
+apply_args (int i, int j, double d)
+{
+ var++;
+ __builtin_apply_args ();
+}
+
+void f() {
+ apply_args (1, 2, 3);
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
new file mode 100644
index 00000000000..d964b07ae5d
--- /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 inline void
+__attribute__ ((__always_inline__))
+h() {
+}
+
+/* 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..79d00cedb9a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-default1.c
@@ -0,0 +1,42 @@
+/* { 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-default2.c b/gcc/testsuite/c-c++-common/strub-default2.c
new file mode 100644
index 00000000000..487253e9227
--- /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-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
new file mode 100644
index 00000000000..7b04eea35d9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O1" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+ the strubbing to its caller at -O1. */
+
+#include "strub-defer-O2.c"
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
new file mode 100644
index 00000000000..67d96419a5e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -0,0 +1,8 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O2" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+ the strubbing to its caller at -O2. */
+
+#define EXPECT_DEFERRAL !
+#include "strub-defer-O3.c"
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
new file mode 100644
index 00000000000..34828d2711e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -0,0 +1,93 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O3" } */
+
+/* Check that a strub function called by another strub function defers the
+ strubbing to its caller at -O3. */
+
+#ifndef EXPECT_DEFERRAL
+# define EXPECT_DEFERRAL
+#endif
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+ /* We use this variable to avoid any stack red zone. Stack scrubbing covers
+ it, but __builtin_stack_address, that we take as a reference, doesn't, so
+ if e.g. callable() were to store the string in the red zone, we wouldn't
+ find it because it would be outside the range we searched. */
+ typedef void __attribute__ ((__strub__ (3))) callable_t (char *);
+ callable_t *f = 0;
+
+ char s[sizeof (test_string)];
+ __builtin_strcpy (s, test_string);
+ asm ("" : "+m" (s), "+r" (f));
+
+ if (__builtin_expect (!f, 1))
+ return (char*)__builtin_stack_address ();
+
+ f (s);
+ return 0;
+}
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+int
+look_for_string (char *e)
+{
+ char *p = (char*)__builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+char *
+at_calls ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+deferred_at_calls ()
+{
+ char *ret = at_calls ();
+ if (EXPECT_DEFERRAL !look_for_string (ret))
+ __builtin_abort ();
+ return ret;
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+deferred_internal ()
+{
+ char *ret = at_calls ();
+ if (EXPECT_DEFERRAL !look_for_string (ret))
+ __builtin_abort ();
+ return ret;
+}
+
+int main ()
+{
+ if (look_for_string (deferred_at_calls ()))
+ __builtin_abort ();
+ if (look_for_string (deferred_internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
new file mode 100644
index 00000000000..b273660aea1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -Os" } */
+
+/* Check that a strub function called by another strub function defers the
+ strubbing to its caller at -Os. */
+
+#include "strub-defer-O3.c"
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
new file mode 100644
index 00000000000..a74658c9ac9
--- /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 inline void
+__attribute__ ((__always_inline__))
+h() {
+}
+
+/* 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/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
new file mode 100644
index 00000000000..7e22a266ad9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (2)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+ int x[128];
+};
+
+void __attribute__ ((__strub__ (2)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\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)))
+std_arg (int i, ...)
+{
+ va_list vl;
+ va_start (vl, i);
+ va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+ __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
new file mode 100644
index 00000000000..2d8036c0fbc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (1)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*small_args\[^ \]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+
+struct large_arg {
+ int x[128];
+};
+
+void __attribute__ ((__strub__ (1)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ (1)))
+std_arg (int i, ...)
+{
+ va_list vl;
+ va_start (vl, i);
+ va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*std_arg\[^ \]* \[(\]int i, void \\* &\[^&,\]*.strub.watermark_ptr\[, .]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-not "va_copy \\(" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
new file mode 100644
index 00000000000..f64361f1235
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -0,0 +1,58 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that uses of a strub variable implicitly enables internal strub for
+ publicly-visible functions, and causes the same transformations to their
+ signatures as those in strub-parms1.c. */
+
+#include <stdarg.h>
+
+int __attribute__ ((__strub__)) var;
+
+void
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+ var++;
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+ int x[128];
+};
+
+void
+large_byref_arg (struct large_arg la)
+{
+ var++;
+}
+
+/* { dg-final { scan-ipa-dump "\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
+std_arg (int i, ...)
+{
+ va_list vl;
+ va_start (vl, i);
+ var++;
+ va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void
+apply_args (int i, int j, double d)
+{
+ var++;
+ __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
new file mode 100644
index 00000000000..a4226ce0119
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=default -fexceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 89 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
new file mode 100644
index 00000000000..3bab553478b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
new file mode 100644
index 00000000000..c89cc7c2c47
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
new file mode 100644
index 00000000000..b869fafb691
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
new file mode 100644
index 00000000000..1d3dd2f2c2c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=default -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
+ watermark. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 15 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 15 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
new file mode 100644
index 00000000000..4dd4281b03b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-Os -fstrub=default -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
+ watermark. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 15 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 15 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
new file mode 100644
index 00000000000..0840dddd136
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+#include "strub-tail-O2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
new file mode 100644
index 00000000000..9330d6ff4c1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued.
+ Tail calls are short-circuited at -O2+. */
+
+int __attribute__ ((__strub__))
+g (int i, int j) {
+ return g (i, j);
+}
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 0 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 0 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
new file mode 100644
index 00000000000..5b33ff1f530
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -0,0 +1,9 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that strub and non-strub functions can be called from non-strub
+ contexts, and that strub and callable functions can be called from strub
+ contexts. */
+
+#define OMIT_IMPERMISSIBLE_CALLS 1
+#include "strub-callable2.c"
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
new file mode 100644
index 00000000000..38935e3270b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -0,0 +1,264 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that impermissible (cross-strub-context) calls are reported. */
+
+extern int __attribute__ ((__strub__ (3))) xcallable (void);
+extern int __attribute__ ((__strub__ (2))) xinternal (void);
+extern int __attribute__ ((__strub__ (1))) xat_calls (void);
+extern int __attribute__ ((__strub__ (0))) xdisabled (void);
+
+int __attribute__ ((__strub__ (3))) callable (void);
+int __attribute__ ((__strub__ (2))) internal (void);
+int __attribute__ ((__strub__ (1))) at_calls (void);
+int __attribute__ ((__strub__ (0))) disabled (void);
+
+int __attribute__ ((__strub__)) var;
+int var_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+icallable (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+iinternal (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+iat_calls (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+idisabled (void);
+static inline int __attribute__ ((__always_inline__))
+ivar_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+i_callable (void) { return 0; }
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+i_internal (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+i_at_calls (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+i_disabled (void) { return 0; }
+static inline int __attribute__ ((__always_inline__))
+i_var_user (void) { return var; }
+
+#define CALLS_GOOD_FOR_STRUB_CONTEXT(ISEP) \
+ do { \
+ ret += i ## ISEP ## at_calls (); \
+ ret += i ## ISEP ## internal (); \
+ ret += i ## ISEP ## var_user (); \
+ } while (0)
+
+#define CALLS_GOOD_FOR_NONSTRUB_CONTEXT(ISEP) \
+ do { \
+ ret += internal (); \
+ ret += disabled (); \
+ ret += var_user (); \
+ \
+ ret += i ## ISEP ## disabled (); \
+ \
+ ret += xinternal (); \
+ ret += xdisabled (); \
+ } while (0)
+
+#define CALLS_GOOD_FOR_EITHER_CONTEXT(ISEP) \
+ do { \
+ ret += i ## ISEP ## callable (); \
+ \
+ ret += callable (); \
+ ret += at_calls (); \
+ \
+ ret += xat_calls (); \
+ ret += xcallable (); \
+ } while (0)
+
+/* Not a strub context, so it can call anything.
+ Explicitly declared as callable even from within strub contexts. */
+int __attribute__ ((__strub__ (3)))
+callable (void) {
+ int ret = 0;
+
+ /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+ ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+ ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+ return ret;
+}
+
+/* Internal strubbing means the body is a strub context, so it can only call
+ strub functions, and it's not itself callable from strub functions. */
+int __attribute__ ((__strub__ (2)))
+internal (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT();
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int __attribute__ ((__strub__ (1)))
+at_calls (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT();
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int __attribute__ ((__strub__ (0)))
+disabled () {
+ int ret = 0;
+
+ /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+ ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+ ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+ return ret;
+}
+
+int
+var_user (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT();
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int
+icallable (void)
+{
+ int ret = 0;
+
+ /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+ ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+ ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+ return ret;
+}
+
+int
+iinternal (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int
+iat_calls (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int
+idisabled () {
+ int ret = 0;
+
+ /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+ ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+ ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+ return ret;
+}
+
+int
+ivar_user (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
new file mode 100644
index 00000000000..100fb0c59a9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const function call, we issue an asm statement
+ to make sure the watermark passed to it is held in memory before the call,
+ and another to make sure it is not assumed to be unchanged. */
+
+int __attribute__ ((__strub__, __const__))
+f() {
+ return 0;
+}
+
+int
+g() {
+ return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
new file mode 100644
index 00000000000..9e818ac9748
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const function call, we issue an
+ asm statement to make sure the watermark passed to it is held in memory
+ before the call, and another to make sure it is not assumed to be
+ unchanged. */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+ return 0;
+}
+
+int
+g() {
+ return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
new file mode 100644
index 00000000000..d40e8aa45cb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const wrapping call, we issue an asm statement
+ to make sure the watermark passed to it is held in memory before the call,
+ and another to make sure it is not assumed to be unchanged. */
+
+int __attribute__ ((__strub__ (2), __const__))
+f() {
+ return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
new file mode 100644
index 00000000000..d4cbdaf10f3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const wrapping call, we issue an
+ asm statement to make sure the watermark passed to it is held in memory
+ before the call, and another to make sure it is not assumed to be
+ unchanged. */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+ return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-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..69f3d65ed44
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -Werror" } */
+
+typedef int __attribute__ ((__strub__)) strub_int;
+strub_int *ptr;
+
+int *f () {
+ return ptr; /* { dg-error "incompatible" } */
+}
+
+strub_int *g () {
+ return f (); /* { dg-error "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/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
new file mode 100644
index 00000000000..cb223da6efc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure function call, we issue an asm statement
+ to make sure the watermark passed to it is not assumed to be unchanged. */
+
+int __attribute__ ((__strub__, __pure__))
+f() {
+ static int i; /* Stop it from being detected as const. */
+ return i;
+}
+
+int
+g() {
+ return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
new file mode 100644
index 00000000000..67d1434b1f8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure function call, we issue an asm
+ statement to make sure the watermark passed to it is not assumed to be
+ unchanged. */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run. */
+__attribute__ ((__pure__))
+#endif
+f() {
+ static int i; /* Stop it from being detected as const. */
+ return i;
+}
+
+int
+g() {
+ return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
new file mode 100644
index 00000000000..59f02ea901f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure wrapping call, we issue an asm statement
+ to make sure the watermark passed to it is not assumed to be unchanged. */
+
+int __attribute__ ((__strub__ (2), __pure__))
+f() {
+ static int i; /* Stop it from being detected as const. */
+ return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
new file mode 100644
index 00000000000..973e909217d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure wrapping call, we issue an asm
+ statement to make sure the watermark passed to it is not assumed to be
+ unchanged. */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run. */
+__attribute__ ((__pure__))
+#endif
+f() {
+ static int i; /* Stop it from being detected as const. */
+ return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
new file mode 100644
index 00000000000..a4077c35a60
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -0,0 +1,85 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+ equivalent strub functions don't. */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+ /* We use this variable to avoid any stack red zone. Stack scrubbing covers
+ it, but __builtin_stack_address, that we take as a reference, doesn't, so
+ if e.g. callable() were to store the string in the red zone, we wouldn't
+ find it because it would be outside the range we searched. */
+ typedef void __attribute__ ((__strub__ (3))) callable_t (char *);
+ callable_t *f = 0;
+
+ char s[sizeof (test_string)];
+ __builtin_strcpy (s, test_string);
+ asm ("" : "+m" (s), "+r" (f));
+
+ if (__builtin_expect (!f, 1))
+ return (char *) __builtin_stack_address ();
+
+ f (s);
+ return 0;
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+ char *p = (char *) __builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+ return leak_string ();
+}
+
+int main ()
+{
+ if (!look_for_string (callable ()))
+ __builtin_abort ();
+ if (look_for_string (at_calls ()))
+ __builtin_abort ();
+ if (look_for_string (internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
new file mode 100644
index 00000000000..94e4156ea73
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+ equivalent strub functions don't. */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+ int len = sizeof (test_string);
+ asm ("" : "+rm" (len));
+ char s[len];
+ __builtin_strcpy (s, test_string);
+ asm ("" : "+m" (s));
+ return (char *) __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+ char *p = (char *) __builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+ return leak_string ();
+}
+
+int main ()
+{
+ if (!look_for_string (callable ()))
+ __builtin_abort ();
+ if (look_for_string (at_calls ()))
+ __builtin_abort ();
+ if (look_for_string (internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
new file mode 100644
index 00000000000..0ca74beb59d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+/* { dg-require-effective-target alloca } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+ equivalent strub functions don't. */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+ int len = sizeof (test_string);
+ char *s = (char *) __builtin_alloca (len);
+ __builtin_strcpy (s, test_string);
+ asm ("" : "+m" (s));
+ return (char *) __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+ char *p = (char *) __builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+ return leak_string ();
+}
+
+int main ()
+{
+ if (!look_for_string (callable ()))
+ __builtin_abort ();
+ if (look_for_string (at_calls ()))
+ __builtin_abort ();
+ if (look_for_string (internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
new file mode 100644
index 00000000000..4ab11c0682e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -0,0 +1,101 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=all" } */
+/* { dg-require-effective-target alloca } */
+
+/* Check that multi-level, multi-inlined functions still get cleaned up as
+ expected, without overwriting temporary stack allocations while they should
+ still be available. */
+
+#ifndef ATTR_STRUB_AT_CALLS
+# define ATTR_STRUB_AT_CALLS /* Defined in strub-run4d.c. */
+#endif
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__))
+char *
+leak_string (void)
+{
+ int __attribute__ ((__strub__)) len = 512;
+ asm ("" : "+r" (len));
+ char s[len];
+ __builtin_strcpy (s, test_string);
+ __builtin_strcpy (s + len - sizeof (test_string), test_string);
+ asm ("" : "+m" (s));
+ return (char *) __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+ char *p = (char *) __builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static inline ATTR_STRUB_AT_CALLS
+char *
+innermost ()
+{
+ int __attribute__ ((__strub__)) len = 512;
+ asm ("" : "+r" (len));
+ char s[len];
+ __builtin_strcpy (s, test_string);
+ __builtin_strcpy (s + len - sizeof (test_string), test_string);
+ asm ("" : "+m" (s));
+ char *ret = leak_string ();
+ if (__builtin_strcmp (s, test_string) != 0)
+ __builtin_abort ();
+ if (__builtin_strcmp (s + len - sizeof (test_string), test_string) != 0)
+ __builtin_abort ();
+ return ret;
+}
+
+static inline ATTR_STRUB_AT_CALLS
+char *
+intermediate ()
+{
+ int __attribute__ ((__strub__)) len = 512;
+ asm ("" : "+r" (len));
+ char s[len];
+ __builtin_strcpy (s, test_string);
+ __builtin_strcpy (s + len - sizeof (test_string), test_string);
+ asm ("" : "+m" (s));
+ char *ret = innermost ();
+ if (__builtin_strcmp (s, test_string) != 0)
+ __builtin_abort ();
+ if (__builtin_strcmp (s + len - sizeof (test_string), test_string) != 0)
+ __builtin_abort ();
+ return ret;
+}
+
+static inline __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+ return intermediate ();
+}
+
+int __attribute__ ((strub (0)))
+main ()
+{
+ if (look_for_string (internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4c.c b/gcc/testsuite/c-c++-common/torture/strub-run4c.c
new file mode 100644
index 00000000000..57f9baf758d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4c.c
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=at-calls" } */
+/* { dg-require-effective-target alloca } */
+
+#include "strub-run4.c"
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4d.c b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
new file mode 100644
index 00000000000..e4f7445607c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+/* { dg-require-effective-target alloca } */
+
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ (1)))
+
+#include "strub-run4.c"
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4i.c b/gcc/testsuite/c-c++-common/torture/strub-run4i.c
new file mode 100644
index 00000000000..459f6886c54
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4i.c
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=internal" } */
+/* { dg-require-effective-target alloca } */
+
+#include "strub-run4.c"
diff --git a/gcc/testsuite/g++.dg/strub-run1.C b/gcc/testsuite/g++.dg/strub-run1.C
new file mode 100644
index 00000000000..0d367fb83d0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/strub-run1.C
@@ -0,0 +1,19 @@
+// { dg-do run }
+// { dg-options "-fstrub=internal" }
+
+// Check that we don't get extra copies.
+
+struct T {
+ T &self;
+ void check () const { if (&self != this) __builtin_abort (); }
+ T() : self (*this) { check (); }
+ T(const T& ck) : self (*this) { ck.check (); check (); }
+ ~T() { check (); }
+};
+
+T foo (T q) { q.check (); return T(); }
+T bar (T p) { p.check (); return foo (p); }
+
+int main () {
+ bar (T()).check ();
+}
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" } } */
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
new file mode 100644
index 00000000000..697ac9de764
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -0,0 +1,21 @@
+-- { dg-do compile }
+-- { dg-options "-fstrub=default -fdump-ipa-strubm" }
+
+package body Strub_Attr 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 (X);
+end Strub_Attr;
+
+-- { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]2\[)\]\[)\]" 2 "strubm" } }
+-- { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
diff --git a/gcc/testsuite/gnat.dg/strub_attr.ads b/gcc/testsuite/gnat.dg/strub_attr.ads
new file mode 100644
index 00000000000..a94c23bf418
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_attr.ads
@@ -0,0 +1,12 @@
+package Strub_Attr 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;
+end Strub_Attr;
diff --git a/gcc/tree-cfg.c b/gcc/tree-cfg.c
index 61b53913e0b..9b01f2bb01b 100644
--- a/gcc/tree-cfg.c
+++ b/gcc/tree-cfg.c
@@ -5620,6 +5620,7 @@ gimple_verify_flow_info (void)
{
gimple *stmt = gsi_stmt (gsi);
+ /* Do NOT disregard debug stmts after found_ctrl_stmt. */
if (found_ctrl_stmt)
{
error ("control flow in the middle of basic block %d",
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index 83941bc0cee..df4abdf1449 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -497,8 +497,9 @@ extern gimple_opt_pass *make_pass_adjust_alignment (gcc::context *ctxt);
/* IPA Passes */
extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt);
-extern simple_ipa_opt_pass
- *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub_mode (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_tree_profile (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_auto_profile (gcc::context *ctxt);
diff --git a/gcc/tree-ssa-ccp.c b/gcc/tree-ssa-ccp.c
index f4a99aca4b4..84815c2977c 100644
--- a/gcc/tree-ssa-ccp.c
+++ b/gcc/tree-ssa-ccp.c
@@ -3049,7 +3049,9 @@ optimize_stack_restore (gimple_stmt_iterator i)
if (!callee
|| !fndecl_built_in_p (callee, BUILT_IN_NORMAL)
/* All regular builtins are ok, just obviously not alloca. */
- || ALLOCA_FUNCTION_CODE_P (DECL_FUNCTION_CODE (callee)))
+ || ALLOCA_FUNCTION_CODE_P (DECL_FUNCTION_CODE (callee))
+ /* Do not remove stack updates before strub leave. */
+ || fndecl_built_in_p (callee, BUILT_IN___STRUB_LEAVE))
return NULL_TREE;
if (fndecl_built_in_p (callee, BUILT_IN_STACK_RESTORE))
diff --git a/libgcc/Makefile.in b/libgcc/Makefile.in
index 7ec97584554..c4ee6701cb3 100644
--- a/libgcc/Makefile.in
+++ b/libgcc/Makefile.in
@@ -428,7 +428,7 @@ ifneq ($(enable_shared),yes)
iterator = $(patsubst %,$(srcdir)/static-object.mk,$(iter-items))
endif
-LIB2ADD += enable-execute-stack.c
+LIB2ADD += enable-execute-stack.c $(srcdir)/strub.c
# While emutls.c has nothing to do with EH, it is in LIB2ADDEH*
# instead of LIB2ADD because that's the way to be sure on some targets
diff --git a/libgcc/libgcc2.h b/libgcc/libgcc2.h
index 1819ff3ac3d..857091e65c8 100644
--- a/libgcc/libgcc2.h
+++ b/libgcc/libgcc2.h
@@ -532,6 +532,10 @@ extern int __parityDI2 (UDWtype);
extern void __enable_execute_stack (void *);
+extern void __strub_enter (void **);
+extern void __strub_update (void**);
+extern void __strub_leave (void **);
+
#ifndef HIDE_EXPORTS
#pragma GCC visibility pop
#endif
diff --git a/libgcc/strub.c b/libgcc/strub.c
new file mode 100644
index 00000000000..fd6e27556e4
--- /dev/null
+++ b/libgcc/strub.c
@@ -0,0 +1,112 @@
+/* Stack scrubbing infrastructure
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+Under Section 7 of GPL version 3, you are granted additional
+permissions described in the GCC Runtime Library Exception, version
+3.1, as published by the Free Software Foundation.
+
+You should have received a copy of the GNU General Public License and
+a copy of the GCC Runtime Library Exception along with this program;
+see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
+<http://www.gnu.org/licenses/>. */
+
+#include "tconfig.h"
+#include "tsystem.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "libgcc_tm.h"
+#include "libgcc2.h"
+
+#ifndef STACK_GROWS_DOWNWARD
+# define TOPS >
+#else
+# define TOPS <
+#endif
+
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+
+/* Enter a stack scrubbing context, initializing the watermark to the caller's
+ stack address. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_enter (void **watermark)
+{
+ *watermark = __builtin_frame_address (0);
+}
+
+/* Update the watermark within a stack scrubbing context with the current stack
+ pointer. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_update (void **watermark)
+{
+ void *sp = __builtin_frame_address (0);
+
+ if (sp TOPS *watermark)
+ *watermark = sp;
+}
+
+#ifndef TARGET_STRUB_USE_DYNAMIC_ARRAY
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+#endif
+
+#ifndef TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+# ifdef TARGET_STRUB_MAY_USE_MEMSET
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+# else
+# define TARGET_STRUB_MAY_USE_MEMSET 1
+# endif
+#endif
+
+/* Leave a stack scrubbing context, restoring and updating SAVED, and
+ clearing the stack between top and watermark. */
+void ATTRIBUTE_STRUB_CALLABLE
+#if ! TARGET_STRUB_MAY_USE_MEMSET
+__attribute__ ((__optimize__ ("-fno-tree-loop-distribute-patterns")))
+#endif
+__strub_leave (void **mark)
+{
+ void *sp = __builtin_stack_address ();
+
+ void **base, **end;
+#ifndef STACK_GROWS_DOWNWARD
+ base = sp;
+ end = *mark;
+#else
+ base = *mark;
+ end = sp;
+#endif
+
+ ptrdiff_t len = end - base;
+ if (len <= 0)
+ return;
+
+#if ! TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+ /* Allocate a dynamically-sized array covering the desired range, so that we
+ can safely call memset on it. */
+ void *ptr[len];
+ base = &ptr[0];
+ end = &ptr[len];
+#else
+ void **ptr = end;
+#endif /* TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY */
+
+ /* ldist turns this into a memset. Without the dynamic array above, that call
+ is likely unsafe: possibly tail-called, and likely scribbling over its own
+ stack frame. */
+ while (base < end)
+ *base++ = 0;
+
+ asm ("" : : "m" (ptr));
+}
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/strub)] strub with function and variable attributes
@ 2021-08-20 4:21 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2021-08-20 4:21 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:412b80893cc0cf65f4e68a072f8a6c1b451588f7
commit 412b80893cc0cf65f4e68a072f8a6c1b451588f7
Author: Alexandre Oliva <oliva@adacore.com>
Date: Wed Aug 18 23:57:13 2021 -0300
strub with function and variable attributes
still missing strub-aware conversion checking,
tests exercising dispatch calls
Diff:
---
gcc/Makefile.in | 1 +
gcc/ada/gcc-interface/trans.c | 5 +
gcc/ada/gcc-interface/utils.c | 97 +
gcc/ada/libgnat/a-except.adb | 90 +
gcc/ada/libgnat/a-except.ads | 8 +
gcc/ada/libgnat/s-arit128.ads | 6 +
gcc/ada/libgnat/s-arit64.ads | 6 +
gcc/builtins.c | 273 ++
gcc/builtins.def | 4 +
gcc/c-family/c-attribs.c | 87 +
gcc/common.opt | 24 +
gcc/doc/extend.texi | 85 +
gcc/doc/invoke.texi | 42 +
gcc/ipa-inline.c | 6 +
gcc/ipa-split.c | 7 +
gcc/ipa-strub.c | 2999 ++++++++++++++++++++
gcc/ipa-strub.h | 35 +
gcc/passes.def | 2 +
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-apply1.c | 15 +
gcc/testsuite/c-c++-common/strub-apply2.c | 12 +
gcc/testsuite/c-c++-common/strub-apply3.c | 8 +
gcc/testsuite/c-c++-common/strub-apply4.c | 21 +
gcc/testsuite/c-c++-common/strub-at-calls1.c | 30 +
gcc/testsuite/c-c++-common/strub-at-calls2.c | 23 +
gcc/testsuite/c-c++-common/strub-default1.c | 42 +
gcc/testsuite/c-c++-common/strub-default2.c | 29 +
gcc/testsuite/c-c++-common/strub-defer-O1.c | 7 +
gcc/testsuite/c-c++-common/strub-defer-O2.c | 8 +
gcc/testsuite/c-c++-common/strub-defer-O3.c | 93 +
gcc/testsuite/c-c++-common/strub-defer-Os.c | 7 +
gcc/testsuite/c-c++-common/strub-internal1.c | 31 +
gcc/testsuite/c-c++-common/strub-internal2.c | 21 +
gcc/testsuite/c-c++-common/strub-parms1.c | 48 +
gcc/testsuite/c-c++-common/strub-parms2.c | 36 +
gcc/testsuite/c-c++-common/strub-parms3.c | 58 +
gcc/testsuite/c-c++-common/strub-short-O0-exc.c | 10 +
gcc/testsuite/c-c++-common/strub-short-O0.c | 10 +
gcc/testsuite/c-c++-common/strub-short-O1.c | 10 +
gcc/testsuite/c-c++-common/strub-short-O2.c | 10 +
gcc/testsuite/c-c++-common/strub-short-O3.c | 12 +
gcc/testsuite/c-c++-common/strub-short-Os.c | 12 +
gcc/testsuite/c-c++-common/strub-tail-O1.c | 8 +
gcc/testsuite/c-c++-common/strub-tail-O2.c | 14 +
.../c-c++-common/torture/strub-callable1.c | 9 +
.../c-c++-common/torture/strub-callable2.c | 264 ++
gcc/testsuite/c-c++-common/torture/strub-const1.c | 18 +
gcc/testsuite/c-c++-common/torture/strub-const2.c | 22 +
gcc/testsuite/c-c++-common/torture/strub-const3.c | 13 +
gcc/testsuite/c-c++-common/torture/strub-const4.c | 17 +
gcc/testsuite/c-c++-common/torture/strub-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 | 13 +
.../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/c-c++-common/torture/strub-pure1.c | 18 +
gcc/testsuite/c-c++-common/torture/strub-pure2.c | 22 +
gcc/testsuite/c-c++-common/torture/strub-pure3.c | 13 +
gcc/testsuite/c-c++-common/torture/strub-pure4.c | 17 +
gcc/testsuite/c-c++-common/torture/strub-run1.c | 85 +
gcc/testsuite/c-c++-common/torture/strub-run2.c | 75 +
gcc/testsuite/c-c++-common/torture/strub-run3.c | 75 +
gcc/testsuite/c-c++-common/torture/strub-run4.c | 101 +
gcc/testsuite/c-c++-common/torture/strub-run4c.c | 5 +
gcc/testsuite/c-c++-common/torture/strub-run4d.c | 7 +
gcc/testsuite/c-c++-common/torture/strub-run4i.c | 5 +
gcc/testsuite/g++.dg/strub-run1.C | 19 +
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 +
gcc/testsuite/gnat.dg/strub_attr.adb | 21 +
gcc/testsuite/gnat.dg/strub_attr.ads | 12 +
gcc/tree-cfg.c | 1 +
gcc/tree-pass.h | 5 +-
gcc/tree-ssa-ccp.c | 4 +-
libgcc/Makefile.in | 2 +-
libgcc/libgcc2.h | 4 +
libgcc/strub.c | 112 +
90 files changed, 5621 insertions(+), 4 deletions(-)
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 9714fcaac37..94097ec78d7 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1471,6 +1471,7 @@ OBJS = \
ipa-reference.o \
ipa-ref.o \
ipa-utils.o \
+ ipa-strub.o \
ipa.o \
ira.o \
ira-build.o \
diff --git a/gcc/ada/gcc-interface/trans.c b/gcc/ada/gcc-interface/trans.c
index 3df56aa0560..f1524a3fb90 100644
--- a/gcc/ada/gcc-interface/trans.c
+++ b/gcc/ada/gcc-interface/trans.c
@@ -43,6 +43,7 @@
#include "output.h"
#include "debug.h"
#include "libfuncs.h" /* For set_stack_check_libfunc. */
+#include "ipa-strub.h" /* For strub_make_callable. */
#include "tree-iterator.h"
#include "gimplify.h"
#include "opts.h"
@@ -444,6 +445,7 @@ gigi (Node_Id gnat_root,
int64_type, NULL_TREE),
NULL_TREE, is_default, true, true, true, false,
false, NULL, Empty);
+ strub_make_callable (mulv64_decl);
if (Enable_128bit_Types)
{
@@ -456,6 +458,7 @@ gigi (Node_Id gnat_root,
NULL_TREE),
NULL_TREE, is_default, true, true, true, false,
false, NULL, Empty);
+ strub_make_callable (mulv128_decl);
}
/* Name of the _Parent field in tagged record types. */
@@ -531,6 +534,7 @@ gigi (Node_Id gnat_root,
= create_subprog_decl
(get_identifier ("__gnat_raise_nodefer_with_msg"), NULL_TREE, ftype,
NULL_TREE, is_default, true, true, true, false, false, NULL, Empty);
+ strub_make_callable (raise_nodefer_decl);
set_exception_parameter_decl
= create_subprog_decl
@@ -779,6 +783,7 @@ build_raise_check (int check, enum exception_info_kind kind)
= create_subprog_decl (get_identifier (Name_Buffer), NULL_TREE, ftype,
NULL_TREE, is_default, true, true, true, false,
false, NULL, Empty);
+ strub_make_callable (result);
return result;
}
diff --git a/gcc/ada/gcc-interface/utils.c b/gcc/ada/gcc-interface/utils.c
index f85373e2f76..d9fb78416ea 100644
--- a/gcc/ada/gcc-interface/utils.c
+++ b/gcc/ada/gcc-interface/utils.c
@@ -94,6 +94,7 @@ static tree handle_sentinel_attribute (tree *, tree, tree, int, bool *);
static tree handle_noreturn_attribute (tree *, tree, tree, int, bool *);
static tree handle_stack_protect_attribute (tree *, tree, tree, int, bool *);
static tree handle_no_stack_protector_attribute (tree *, tree, tree, int, bool *);
+static tree handle_strub_attribute (tree *, tree, tree, int, bool *);
static tree handle_noinline_attribute (tree *, tree, tree, int, bool *);
static tree handle_noclone_attribute (tree *, tree, tree, int, bool *);
static tree handle_noicf_attribute (tree *, tree, tree, int, bool *);
@@ -157,6 +158,8 @@ const struct attribute_spec gnat_internal_attribute_table[] =
{ "no_stack_protector",0, 0, true, false, false, false,
handle_no_stack_protector_attribute,
attr_stack_protect_exclusions },
+ { "strub", 0, 1, false, true, false, true,
+ handle_strub_attribute, NULL },
{ "noinline", 0, 0, true, false, false, false,
handle_noinline_attribute, NULL },
{ "noclone", 0, 0, true, false, false, false,
@@ -6599,6 +6602,100 @@ handle_no_stack_protector_attribute (tree *node, tree name, tree, int,
return NULL_TREE;
}
+/* Handle a "strub" attribute; arguments as in
+ struct attribute_spec.handler. */
+
+static tree
+handle_strub_attribute (tree *node, tree name,
+ tree args,
+ int ARG_UNUSED (flags), bool *no_add_attrs)
+{
+ bool enable = true;
+
+ if (args
+ && (TREE_CODE (*node) == FUNCTION_TYPE
+ || TREE_CODE (*node) == METHOD_TYPE))
+ {
+ /* 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)
+ {
+ 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);
+ }
+
+ /* 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)
+ {
+ warning (OPT_Wattributes,
+ "%qE attribute ignored because of argument %qE",
+ name, TREE_VALUE (args));
+ *no_add_attrs = true;
+ enable = false;
+ }
+ /* 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));
+
+ /* 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;
+
+ return NULL_TREE;
+}
/* Handle a "noinline" attribute; arguments as in
struct attribute_spec.handler. */
diff --git a/gcc/ada/libgnat/a-except.adb b/gcc/ada/libgnat/a-except.adb
index c332afad0f8..593392809d9 100644
--- a/gcc/ada/libgnat/a-except.adb
+++ b/gcc/ada/libgnat/a-except.adb
@@ -629,6 +629,96 @@ package body Ada.Exceptions is
pragma No_Return (Rcheck_CE_Invalid_Data_Ext);
pragma No_Return (Rcheck_CE_Range_Check_Ext);
+ -- Make all of these procedures callable from strub contexts.
+ -- These attributes are not visible to callers; they are made
+ -- visible in trans.c:build_raise_check.
+
+ pragma Machine_Attribute (Rcheck_CE_Access_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Null_Access_Parameter,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Discriminant_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Divide_By_Zero,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Explicit_Raise,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Index_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Invalid_Data,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Length_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Null_Exception_Id,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Null_Not_Allowed,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Overflow_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Partition_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Range_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Tag_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Access_Before_Elaboration,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Accessibility_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Address_Of_Intrinsic,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Aliased_Parameters,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_All_Guards_Closed,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Bad_Predicated_Generic_Type,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Build_In_Place_Mismatch,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Current_Task_In_Entry_Body,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Duplicated_Entry_Address,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Explicit_Raise,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Implicit_Return,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Misaligned_Address_Value,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Missing_Return,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Non_Transportable_Actual,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Overlaid_Controlled_Object,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Potentially_Blocking_Operation,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Stream_Operation_Not_Allowed,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Stubbed_Subprogram_Called,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Unchecked_Union_Restriction,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Finalize_Raised_Exception,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_SE_Empty_Storage_Pool,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_SE_Explicit_Raise,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_SE_Infinite_Recursion,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_SE_Object_Too_Large,
+ "strub", "callable");
+
+ pragma Machine_Attribute (Rcheck_CE_Access_Check_Ext,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Index_Check_Ext,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Invalid_Data_Ext,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Range_Check_Ext,
+ "strub", "callable");
+
---------------------------------------------
-- Reason Strings for Run-Time Check Calls --
---------------------------------------------
diff --git a/gcc/ada/libgnat/a-except.ads b/gcc/ada/libgnat/a-except.ads
index 2b27adb6ca1..b5a2e48f96a 100644
--- a/gcc/ada/libgnat/a-except.ads
+++ b/gcc/ada/libgnat/a-except.ads
@@ -81,6 +81,10 @@ package Ada.Exceptions is
pragma No_Return (Raise_Exception);
-- Note: In accordance with AI-466, CE is raised if E = Null_Id
+ -- Make it callable from strub contexts.
+ pragma Machine_Attribute (Raise_Exception,
+ "strub", "callable");
+
function Exception_Message (X : Exception_Occurrence) return String;
procedure Reraise_Occurrence (X : Exception_Occurrence);
@@ -184,6 +188,10 @@ private
-- Raise_Exception_Always if it can determine this is the case. The Export
-- allows this routine to be accessed from Pure units.
+ -- Make it callable from strub contexts.
+ pragma Machine_Attribute (Raise_Exception_Always,
+ "strub", "callable");
+
procedure Raise_From_Controlled_Operation (X : Exception_Occurrence);
pragma No_Return (Raise_From_Controlled_Operation);
pragma Export
diff --git a/gcc/ada/libgnat/s-arit128.ads b/gcc/ada/libgnat/s-arit128.ads
index 6213cfb569a..5d3fff6d8e1 100644
--- a/gcc/ada/libgnat/s-arit128.ads
+++ b/gcc/ada/libgnat/s-arit128.ads
@@ -57,6 +57,12 @@ package System.Arith_128 is
-- bits, otherwise returns the 128-bit signed integer product.
-- Gigi may also call this routine directly.
+ -- Make it callable from strub contexts.
+ -- There is a matching setting in trans.c,
+ -- for calls issued by Gigi.
+ pragma Machine_Attribute (Multiply_With_Ovflo_Check128,
+ "strub", "callable");
+
procedure Scaled_Divide128
(X, Y, Z : Int128;
Q, R : out Int128;
diff --git a/gcc/ada/libgnat/s-arit64.ads b/gcc/ada/libgnat/s-arit64.ads
index c9141f5fe3e..170c2bdf1de 100644
--- a/gcc/ada/libgnat/s-arit64.ads
+++ b/gcc/ada/libgnat/s-arit64.ads
@@ -57,6 +57,12 @@ package System.Arith_64 is
-- bits, otherwise returns the 64-bit signed integer product.
-- Gigi may also call this routine directly.
+ -- Make it callable from strub contexts.
+ -- There is a matching setting in trans.c,
+ -- for calls issued by Gigi.
+ pragma Machine_Attribute (Multiply_With_Ovflo_Check64,
+ "strub", "callable");
+
procedure Scaled_Divide64
(X, Y, Z : Int64;
Q, R : out Int64;
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 99548627761..7ad91ea6f7a 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -70,6 +70,7 @@ along with GCC; see the file COPYING3. If not see
#include "gimple-fold.h"
#include "intl.h"
#include "file-prefix-map.h" /* remap_macro_filename() */
+#include "ipa-strub.h" /* strub_watermark_parm() */
#include "gomp-constants.h"
#include "omp-general.h"
#include "tree-dfa.h"
@@ -150,6 +151,7 @@ static rtx expand_builtin_strnlen (tree, rtx, machine_mode);
static rtx expand_builtin_alloca (tree);
static rtx expand_builtin_unop (machine_mode, tree, rtx, rtx, optab);
static rtx expand_builtin_frame_address (tree, tree);
+static rtx expand_builtin_stack_address ();
static tree stabilize_va_list_loc (location_t, tree, int);
static rtx expand_builtin_expect (tree, rtx);
static rtx expand_builtin_expect_with_probability (tree, rtx);
@@ -4871,6 +4873,256 @@ expand_builtin_frame_address (tree fndecl, tree exp)
}
}
+#ifndef STACK_GROWS_DOWNWARD
+# define STACK_TOPS GT
+#else
+# define STACK_TOPS LT
+#endif
+
+#ifdef POINTERS_EXTEND_UNSIGNED
+# define STACK_UNSIGNED POINTERS_EXTEND_UNSIGNED
+#else
+# define STACK_UNSIGNED true
+#endif
+
+/* Expand a call to builtin function __builtin_stack_address. */
+
+static rtx
+expand_builtin_stack_address ()
+{
+ return convert_to_mode (ptr_mode, copy_to_reg (stack_pointer_rtx),
+ STACK_UNSIGNED);
+}
+
+/* Expand a call to builtin function __builtin_strub_enter. */
+
+static rtx
+expand_builtin_strub_enter (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 1 || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = NULL_RTX;
+
+#if 1 || defined RED_ZONE_SIZE
+ if (tree wmptr = (optimize
+ ? strub_watermark_parm (current_function_decl)
+ : NULL_TREE))
+ {
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+ stktop = force_reg (ptr_mode, wmark);
+ }
+#endif
+
+ if (!stktop)
+ stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ emit_move_insn (wmark, stktop);
+
+ return const0_rtx;
+}
+
+/* Expand a call to builtin function __builtin_strub_update. */
+
+static rtx
+expand_builtin_strub_update (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 2 || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = expand_builtin_stack_address ();
+
+#ifdef RED_ZONE_SIZE
+ /* Here's how the strub enter, update and leave functions deal with red zones.
+
+ If it weren't for red zones, update, called from within a strub context,
+ would bump the watermark to the top of the stack. Enter and leave, running
+ in the caller, would use the caller's top of stack address both to
+ initialize the watermark passed to the callee, and to start strubbing the
+ stack afterwards.
+
+ Ideally, we'd update the watermark so as to cover the used amount of red
+ zone, and strub starting at the caller's other end of the (presumably
+ unused) red zone. Normally, only leaf functions use the red zone, but at
+ this point we can't tell whether a function is a leaf, nor can we tell how
+ much of the red zone it uses. Furthermore, some strub contexts may have
+ been inlined so that update and leave are called from the same stack frame,
+ and the strub builtins may all have been inlined, turning a strub function
+ into a leaf.
+
+ So cleaning the range from the caller's stack pointer (one end of the red
+ zone) to the (potentially inlined) callee's (other end of the) red zone
+ could scribble over the caller's own red zone.
+
+ We avoid this possibility by arranging for callers that are strub contexts
+ to use their own watermark as the strub starting point. So, if A calls B,
+ and B calls C, B will tell A to strub up to the end of B's red zone, and
+ will strub itself only the part of C's stack frame and red zone that
+ doesn't overlap with B's. With that, we don't need to know who's leaf and
+ who isn't: inlined calls will shrink their strub window to zero, each
+ remaining call will strub some portion of the stack, and eventually the
+ strub context will return to a caller that isn't a strub context itself,
+ that will therefore use its own stack pointer as the strub starting point.
+ It's not a leaf, because strub contexts can't be inlined into non-strub
+ contexts, so it doesn't use the red zone, and it will therefore correctly
+ strub up the callee's stack frame up to the end of the callee's red zone.
+ Neat! */
+ if (true /* (flags_from_decl_or_type (current_function_decl) & ECF_LEAF) */)
+ {
+ poly_int64 red_zone_size = RED_ZONE_SIZE;
+#if STACK_GROWS_DOWNWARD
+ red_zone_size = -red_zone_size;
+#endif
+ stktop = plus_constant (ptr_mode, stktop, red_zone_size);
+ stktop = force_reg (ptr_mode, stktop);
+ }
+#endif
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ rtx wmarkr = force_reg (ptr_mode, wmark);
+
+ rtx_code_label *lab = gen_label_rtx ();
+ do_compare_rtx_and_jump (stktop, wmarkr, STACK_TOPS, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, lab, NULL,
+ profile_probability::very_likely ());
+ emit_move_insn (wmark, stktop);
+
+#if 1 || defined RED_ZONE_SIZE
+ /* If this is an inlined strub function, also bump the watermark for the
+ enclosing function. This avoids a problem with the following scenario: A
+ calls B and B calls C, and both B and C get inlined into A. B allocates
+ temporary stack space before calling C. If we don't update A's watermark,
+ we may use an outdated baseline for the post-C strub_leave, erasing B's
+ temporary stack allocation. We only need this if we're fully expanding
+ strub_leave inline. */
+ tree xwmptr = (optimize > 2
+ ? strub_watermark_parm (current_function_decl)
+ : wmptr);
+ if (wmptr != xwmptr)
+ {
+ wmptr = xwmptr;
+ wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+ wmarkr = force_reg (ptr_mode, wmark);
+
+ do_compare_rtx_and_jump (stktop, wmarkr, STACK_TOPS, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, lab, NULL,
+ profile_probability::very_likely ());
+ emit_move_insn (wmark, stktop);
+ }
+#endif
+
+ emit_label (lab);
+
+ return const0_rtx;
+}
+
+
+/* Expand a call to builtin function __builtin_strub_leave. */
+
+static rtx
+expand_builtin_strub_leave (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 2 || optimize_size || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = NULL_RTX;
+
+#if 1 || defined RED_ZONE_SIZE
+ if (tree wmptr = (optimize
+ ? strub_watermark_parm (current_function_decl)
+ : NULL_TREE))
+ {
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+ stktop = force_reg (ptr_mode, wmark);
+ }
+#endif
+
+ if (!stktop)
+ stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ rtx wmarkr = force_reg (ptr_mode, wmark);
+
+#ifndef STACK_GROWS_DOWNWARD
+ rtx base = stktop;
+ rtx end = wmarkr;
+#else
+ rtx base = wmarkr;
+ rtx end = stktop;
+#endif
+
+ /* We're going to modify it, so make sure it's not e.g. the stack pointer. */
+ base = copy_to_reg (base);
+
+ rtx_code_label *done = gen_label_rtx ();
+ do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, done, NULL,
+ profile_probability::very_likely ());
+
+ if (optimize < 3)
+ expand_call (exp, NULL_RTX, true);
+ else
+ {
+ /* Ok, now we've determined we want to copy the block, so convert the
+ addresses to Pmode, as needed to dereference them to access ptr_mode
+ memory locations, so that we don't have to convert anything within the
+ loop. */
+ base = memory_address (ptr_mode, base);
+ end = memory_address (ptr_mode, end);
+
+ rtx zero = force_operand (const0_rtx, NULL_RTX);
+ int ulen = GET_MODE_SIZE (ptr_mode);
+ rtx incr = plus_constant (Pmode, base, ulen);
+ rtx dstm = gen_rtx_MEM (ptr_mode, base);
+
+ rtx_code_label *loop = gen_label_rtx ();
+ emit_label (loop);
+ emit_move_insn (dstm, zero);
+ emit_move_insn (base, force_operand (incr, NULL_RTX));
+ do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
+ Pmode, NULL_RTX, NULL, loop,
+ profile_probability::very_likely ());
+ }
+
+ emit_label (done);
+
+ return const0_rtx;
+}
+
/* Expand EXP, a call to the alloca builtin. Return NULL_RTX if we
failed and the caller should emit a normal call. */
@@ -7110,6 +7362,27 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
case BUILT_IN_RETURN_ADDRESS:
return expand_builtin_frame_address (fndecl, exp);
+ case BUILT_IN_STACK_ADDRESS:
+ return expand_builtin_stack_address ();
+
+ case BUILT_IN___STRUB_ENTER:
+ target = expand_builtin_strub_enter (exp);
+ if (target)
+ return target;
+ break;
+
+ case BUILT_IN___STRUB_UPDATE:
+ target = expand_builtin_strub_update (exp);
+ if (target)
+ return target;
+ break;
+
+ case BUILT_IN___STRUB_LEAVE:
+ target = expand_builtin_strub_leave (exp);
+ if (target)
+ return target;
+ break;
+
/* Returns the address of the area where the structure is returned.
0 otherwise. */
case BUILT_IN_AGGREGATE_INCOMING_ADDRESS:
diff --git a/gcc/builtins.def b/gcc/builtins.def
index 45a09b4d42d..f83c1f5fcd7 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -874,6 +874,10 @@ DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSL, "ffsl", BT_FN_INT_LONG, ATTR_CONST_NOTHRO
DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSLL, "ffsll", BT_FN_INT_LONGLONG, ATTR_CONST_NOTHROW_LEAF_LIST)
DEF_EXT_LIB_BUILTIN (BUILT_IN_FORK, "fork", BT_FN_PID, ATTR_NOTHROW_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FRAME_ADDRESS, "frame_address", BT_FN_PTR_UINT, ATTR_NULL)
+DEF_GCC_BUILTIN (BUILT_IN_STACK_ADDRESS, "stack_address", BT_FN_PTR, ATTR_NULL)
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_ENTER, "__builtin___strub_enter")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_UPDATE, "__builtin___strub_update")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_LEAVE, "__builtin___strub_leave")
/* [trans-mem]: Adjust BUILT_IN_TM_FREE if BUILT_IN_FREE is changed. */
DEF_LIB_BUILTIN (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_LEAF_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FROB_RETURN_ADDR, "frob_return_addr", BT_FN_PTR_PTR, ATTR_NULL)
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index e60fb31d8c8..edec2afc251 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -69,6 +69,7 @@ static tree handle_asan_odr_indicator_attribute (tree *, tree, tree, int,
static tree handle_stack_protect_attribute (tree *, tree, tree, int, bool *);
static tree handle_no_stack_protector_function_attribute (tree *, tree,
tree, int, bool *);
+static tree handle_strub_attribute (tree *, tree, tree, int, bool *);
static tree handle_noinline_attribute (tree *, tree, tree, int, bool *);
static tree handle_noclone_attribute (tree *, tree, tree, int, bool *);
static tree handle_nocf_check_attribute (tree *, tree, tree, int, bool *);
@@ -306,6 +307,8 @@ const struct attribute_spec c_common_attribute_table[] =
{ "no_stack_protector", 0, 0, true, false, false, false,
handle_no_stack_protector_function_attribute,
attr_stack_protect_exclusions },
+ { "strub", 0, 1, false, true, false, true,
+ handle_strub_attribute, NULL },
{ "noinline", 0, 0, true, false, false, false,
handle_noinline_attribute,
attr_noinline_exclusions },
@@ -1290,6 +1293,90 @@ handle_noipa_attribute (tree *node, tree name, tree, int, bool *no_add_attrs)
return NULL_TREE;
}
+/* Handle a "strub" attribute; arguments as in
+ struct attribute_spec.handler. */
+
+static tree
+handle_strub_attribute (tree *node, tree name,
+ tree args,
+ int ARG_UNUSED (flags), bool *no_add_attrs)
+{
+ bool enable = true;
+
+ if (args
+ && (TREE_CODE (*node) == FUNCTION_TYPE
+ || TREE_CODE (*node) == METHOD_TYPE))
+ {
+ 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;
+ }
+
+ if (val >= 0)
+ TREE_VALUE (args) = build_int_cst (integer_type_node, val);
+ }
+
+ /* 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)
+ {
+ warning (OPT_Wattributes,
+ "%qE attribute ignored because of argument %qE",
+ name, TREE_VALUE (args));
+ *no_add_attrs = true;
+ enable = false;
+ }
+ /* 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));
+
+ /* 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;
+
+ return NULL_TREE;
+}
+
/* Handle a "noinline" attribute; arguments as in
struct attribute_spec.handler. */
diff --git a/gcc/common.opt b/gcc/common.opt
index ed8ab5fbe13..ba0359c3c1d 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,6 +2687,30 @@ 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=all
+Common RejectNegative Var(flag_strub, 3)
+Enable stack scrubbing for all viable functions.
+
+fstrub=at-calls
+Common RejectNegative Var(flag_strub, 1)
+Enable at-calls stack scrubbing for all viable functions.
+
+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 49df8e6dc38..e256db23862 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -77,6 +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.
* 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.
@@ -8672,6 +8673,49 @@ pid_t wait (wait_status_ptr_t p)
@}
@end smallexample
+@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.
+
+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.
+
+A variable whose type carries the @code{strub} attribute causes
+functions that @emph{read} from it to have @code{strub} enabled.
+Reading from data referenced by pointers to such types has the same
+effect.
+
+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.
+
+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.
+
+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)}).
+
+@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.
+
@item unused
@cindex @code{unused} type attribute
When attached to a type (including a @code{union} or a @code{struct}),
@@ -11690,6 +11734,47 @@ option is in effect. Such calls should only be made in debugging
situations.
@end deftypefn
+@deftypefn {Built-in Function} {void *} __builtin_stack_address ()
+This function returns the value of the stack pointer register.
+@end deftypefn
+
+@node Stack Scrubbing
+@section Stack scrubbing 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).
+
+@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.
+@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
+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.
+@end deftypefn
+
@node Vector Extensions
@section Using Vector Instructions through Built-in Functions
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index d8a6b0b60c9..684d87bceae 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,6 +599,7 @@ 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
-fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
-fvtv-counts -fvtv-debug @gol
-finstrument-functions @gol
@@ -15484,6 +15485,47 @@ 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.
+
+@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.
+
+@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.
+
+@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.
+
@item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
@opindex fvtable-verify
This option is only available when compiling C++ code.
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 413446bcc46..7f4bc44d2bb 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -119,6 +119,7 @@ along with GCC; see the file COPYING3. If not see
#include "stringpool.h"
#include "attribs.h"
#include "asan.h"
+#include "ipa-strub.h"
typedef fibonacci_heap <sreal, cgraph_edge> edge_heap_t;
typedef fibonacci_node <sreal, cgraph_edge> edge_heap_node_t;
@@ -396,6 +397,11 @@ 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))
+ {
+ e->inline_failed = CIF_UNSPECIFIED;
+ inlinable = false;
+ }
if (!inlinable && report)
report_inline_failed_reason (e);
return inlinable;
diff --git a/gcc/ipa-split.c b/gcc/ipa-split.c
index c68577d04a9..8c880747eb7 100644
--- a/gcc/ipa-split.c
+++ b/gcc/ipa-split.c
@@ -104,6 +104,7 @@ along with GCC; see the file COPYING3. If not see
#include "ipa-fnsummary.h"
#include "cfgloop.h"
#include "attribs.h"
+#include "ipa-strub.h"
/* Per basic block info. */
@@ -1792,6 +1793,12 @@ execute_split_functions (void)
"section.\n");
return 0;
}
+ if (!strub_splittable_p (node))
+ {
+ if (dump_file)
+ fprintf (dump_file, "Not splitting: function is a strub context.\n");
+ return 0;
+ }
/* We enforce splitting after loop headers when profile info is not
available. */
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
new file mode 100644
index 00000000000..cf7d811779e
--- /dev/null
+++ b/gcc/ipa-strub.c
@@ -0,0 +1,2999 @@
+/* strub (stack scrubbing) support.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "gimplify.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "gimple-iterator.h"
+#include "gimplify-me.h"
+#include "tree-into-ssa.h"
+#include "tree-ssa.h"
+#include "tree-cfg.h"
+#include "cfghooks.h"
+#include "cfgloop.h"
+#include "cfgcleanup.h"
+#include "tree-eh.h"
+#include "except.h"
+#include "builtins.h"
+#include "attribs.h"
+#include "tree-inline.h"
+#include "cgraph.h"
+#include "alloc-pool.h"
+#include "symbol-summary.h"
+#include "ipa-prop.h"
+#include "ipa-fnsummary.h"
+#include "gimple-fold.h"
+#include "fold-const.h"
+#include "gimple-walk.h"
+#include "tree-dfa.h"
+#include "langhooks.h"
+#include "calls.h"
+#include "vec.h"
+#include "stor-layout.h"
+#include "varasm.h"
+#include "alias.h"
+#include "diagnostic.h"
+#include "intl.h"
+#include "ipa-strub.h"
+
+#if BUILDING_GCC_MAJOR >= 11
+# include "symtab-thunks.h"
+# include "attr-fnspec.h"
+# define HAVE_ATTR_FNSPEC 1
+# define FOR_GCC_11P 1
+#else
+# define HAVE_ATTR_FNSPEC 0
+# define FOR_GCC_11P 0
+#endif
+
+/* Const and pure functions that gain a watermark parameter for strub purposes
+ are still regarded as such, which may cause the inline expansions of the
+ __strub builtins to malfunction. Ideally, attribute "fn spec" would enable
+ us to inform the backend about requirements and side effects of the call, but
+ call_fusage building in calls.c:expand_call does not even look at
+ attr_fnspec, so we resort to asm loads and updates to attain an equivalent
+ effect. Once expand_call gains the ability to issue extra memory uses and
+ clobbers based on pure/const function's fnspec, we can define this to 1. */
+#define ATTR_FNSPEC_DECONST_WATERMARK 0
+
+enum strub_mode {
+ /* This mode denotes a regular function, that does not require stack
+ scrubbing (strubbing). It may call any other functions, but if
+ it calls AT_CALLS (or WRAPPED) ones, strubbing logic is
+ automatically introduced around those calls (the latter, by
+ inlining INTERNAL wrappers). */
+ STRUB_DISABLED = 0,
+
+ /* This denotes a function whose signature is (to be) modified to
+ take an extra parameter, for stack use annotation, and its
+ callers must initialize and pass that argument, and perform the
+ strubbing. Functions that are explicitly marked with attribute
+ strub must have the mark visible wherever the function is,
+ including aliases, and overriders and overriding methods.
+ Functions that are implicitly marked for strubbing, for accessing
+ variables explicitly marked as such, will only select this
+ strubbing method if they are internal to a translation unit. It
+ can only be inlined into other strubbing functions, i.e.,
+ STRUB_AT_CALLS or STRUB_WRAPPED. */
+ STRUB_AT_CALLS = 1,
+
+ /* This denotes a function that is to perform strubbing internally,
+ without any changes to its interface (the function is turned into
+ a strubbing wrapper, and its original body is moved to a separate
+ STRUB_WRAPPED function, with a modified interface). Functions
+ may be explicitly marked with attribute strub(2), and the
+ attribute must be visible at the point of definition. Functions
+ that are explicitly marked for strubbing, for accessing variables
+ explicitly marked as such, may select this strubbing mode if
+ their interface cannot change, e.g. because its interface is
+ visible to other translation units, directly, by indirection
+ (having its address taken), inheritance, etc. Functions that use
+ this method must not have the noclone attribute, nor the noipa
+ one. Functions marked as always_inline may select this mode, but
+ they are NOT wrapped, they remain unchanged, and are only inlined
+ into strubbed contexts. Once non-always_inline functions are
+ wrapped, the wrapper becomes STRUB_WRAPPER, and the wrapped becomes
+ STRUB_WRAPPED. */
+ STRUB_INTERNAL = 2,
+
+ /* This denotes a function whose stack is not strubbed, but that is
+ nevertheless explicitly or implicitly marked as callable from strubbing
+ functions. Normally, only STRUB_AT_CALLS (and STRUB_INTERNAL ->
+ STRUB_WRAPPED) functions can be called from strubbing contexts (bodies of
+ STRUB_AT_CALLS, STRUB_INTERNAL and STRUB_WRAPPED functions), but attribute
+ strub(3) enables other functions to be (indirectly) called from these
+ contexts. Some builtins and internal functions may be implicitly marked as
+ STRUB_CALLABLE. */
+ STRUB_CALLABLE = 3,
+
+ /* This denotes the function that took over the body of a
+ STRUB_INTERNAL function. At first, it's only called by its
+ wrapper, but the wrapper may be inlined. The wrapped function,
+ in turn, can only be inlined into other functions whose stack
+ frames are strubbed, i.e., that are STRUB_WRAPPED or
+ STRUB_AT_CALLS. */
+ STRUB_WRAPPED = -1,
+
+ /* This denotes the wrapper function that replaced the STRUB_INTERNAL
+ function. This mode overrides the STRUB_INTERNAL mode at the time the
+ internal to-be-wrapped function becomes a wrapper, so that inlining logic
+ can tell one from the other. */
+ STRUB_WRAPPER = -2,
+
+ /* 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
+get_strub_attr_from_type (tree type)
+{
+ return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
+}
+
+static tree
+get_strub_attr_from_decl (tree decl)
+{
+ tree ret = lookup_attribute ("strub", DECL_ATTRIBUTES (decl));
+ if (ret)
+ return ret;
+ return get_strub_attr_from_type (TREE_TYPE (decl));
+}
+
+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);
+
+#if 0 /* ??? use symbolic mode names with interned strings? */
+ char *s = NULL;
+
+ switch (strub_mode)
+ {
+
+ }
+#endif
+}
+
+static enum strub_mode
+get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
+{
+ enum strub_mode mode = STRUB_DISABLED;
+
+ if (strub_attr)
+ {
+ 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 ();
+ }
+
+ return mode;
+}
+
+static enum strub_mode
+get_strub_mode_from_decl (tree fndecl)
+{
+ return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
+}
+
+static enum strub_mode
+get_strub_mode (cgraph_node *node)
+{
+ return get_strub_mode_from_decl (node->decl);
+}
+
+static enum strub_mode
+get_strub_mode_from_type (tree type)
+{
+ bool var_p = !FUNC_OR_METHOD_TYPE_P (type);
+ tree attr = get_strub_attr_from_type (type);
+
+ if (attr)
+ return get_strub_mode_from_attr (attr, var_p);
+
+ if (flag_strub > 0 && !var_p)
+ return STRUB_CALLABLE;
+
+ return STRUB_DISABLED;
+}
+
+static bool
+calls_builtin_va_start_p (cgraph_node *node)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (fndecl_built_in_p (cdecl, BUILT_IN_VA_START))
+ return true;
+ }
+
+ return result;
+}
+
+static bool
+calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!fndecl_built_in_p (cdecl, BUILT_IN_APPLY_ARGS))
+ continue;
+
+ result = true;
+
+ if (!report)
+ break;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "at-calls %<strub%> does not support call to %qD",
+ cdecl);
+ }
+
+ return result;
+}
+
+static inline bool
+strub_always_inline_p (cgraph_node *node)
+{
+ return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
+}
+
+static inline bool
+can_strub_p (cgraph_node *node, bool report = false)
+{
+ bool result = true;
+
+ if (!report && strub_always_inline_p (node))
+ return result;
+
+ if (lookup_attribute ("noipa", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for %<strub%>"
+ " because of attribute %<noipa%>",
+ node->decl);
+ }
+
+ /* We can't, and don't want to vectorize the watermark and other
+ strub-introduced parms. */
+ if (lookup_attribute ("simd", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for %<strub%>"
+ " because of attribute %<simd%>",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+can_strub_at_calls_p (cgraph_node *node, bool report = false)
+{
+ bool result = !report || can_strub_p (node, report);
+
+ if (!result && !report)
+ return result;
+
+ return !calls_builtin_apply_args_p (node, report);
+}
+
+#define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
+
+/* We can't perform internal strubbing if the function body involves certain
+ features:
+
+ - a non-default __builtin_va_start (e.g. x86's __builtin_ms_va_start) is
+ currently unsupported because we can't discover the corresponding va_copy and
+ va_end decls in the wrapper, and we don't convey the alternate variable
+ arguments ABI to the modified wrapped function. The default
+ __builtin_va_start is supported by calling va_start/va_end at the wrapper,
+ that takes variable arguments, passing a pointer to the va_list object to the
+ wrapped function, that runs va_copy from it where the original function ran
+ va_start.
+
+ __builtin_next_arg is currently unsupported because the wrapped function
+ won't be a variable argument function. We could process it in the wrapper,
+ that remains a variable argument function, and replace calls in the wrapped
+ body, but we currently don't.
+
+ __builtin_return_address is rejected because it's generally used when the
+ actual caller matters, and introducing a wrapper breaks such uses as those in
+ the unwinder. */
+
+static bool
+can_strub_internally_p (cgraph_node *node, bool report = false)
+{
+ bool result = !report || can_strub_p (node, report);
+
+ if (!result && !report)
+ return result;
+
+ if (!report && strub_always_inline_p (node))
+ return 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, but we'd have to prevent remapping of the labels. */
+ if (lookup_attribute ("noclone", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for internal %<strub%>"
+ " because of attribute %<noclone%>",
+ node->decl);
+ }
+
+ if (node->has_gimple_body_p ())
+ {
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!((fndecl_built_in_p (cdecl, BUILT_IN_VA_START)
+ && cdecl != builtin_decl_explicit (BUILT_IN_VA_START))
+ || fndecl_built_in_p (cdecl, BUILT_IN_NEXT_ARG)
+ || fndecl_built_in_p (cdecl, BUILT_IN_RETURN_ADDRESS)))
+ continue;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "%qD is not eligible for internal %<strub%> "
+ "because it calls %qD",
+ node->decl, cdecl);
+ }
+
+ struct function *fun = DECL_STRUCT_FUNCTION (node->decl);
+ if (fun->has_nonlocal_label)
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for internal %<strub%> "
+ "because it contains a non-local goto target",
+ node->decl);
+ }
+
+ if (fun->has_forced_label_in_static)
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for internal %<strub%> "
+ "because the address of a local label escapes",
+ node->decl);
+ }
+
+ /* Catch any other case that would prevent versioning/cloning
+ so as to also have it covered above. */
+ gcc_checking_assert (!result /* || !node->has_gimple_body_p () */
+ || tree_versionable_function_p (node->decl));
+
+
+ /* Label values referenced 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. */
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ glabel *label_stmt = dyn_cast <glabel *> (gsi_stmt (gsi));
+ tree target;
+
+ if (!label_stmt)
+ break;
+
+ 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;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (label_stmt),
+ "internal %<strub%> does not support user labels");
+ }
+ }
+
+ if (list_length (TYPE_ARG_TYPES (TREE_TYPE (node->decl)))
+ >= (((HOST_WIDE_INT) 1 << IPA_PARAM_MAX_INDEX_BITS)
+ - STRUB_INTERNAL_MAX_EXTRA_ARGS))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD has too many arguments for internal %<strub%>",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+strub_from_body_p (cgraph_node *node)
+{
+ if (!node->has_gimple_body_p ())
+ return false;
+
+ /* If any local variable is marked for strub... */
+ unsigned i;
+ tree var;
+ FOR_EACH_LOCAL_DECL (DECL_STRUCT_FUNCTION (node->decl),
+ i, var)
+ if (get_strub_mode_from_type (TREE_TYPE (var))
+ != STRUB_DISABLED)
+ return true;
+
+ /* Now scan the body for loads with strub types. */
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!gimple_assign_load_p (stmt))
+ continue;
+
+ tree rhs = gimple_assign_rhs1 (stmt);
+ if (get_strub_mode_from_type (TREE_TYPE (rhs))
+ != STRUB_DISABLED)
+ return true;
+ }
+
+ return false;
+}
+
+/* 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)
+{
+ if (DECL_BUILT_IN_CLASS (node->decl) != BUILT_IN_NORMAL)
+ return false;
+
+ enum built_in_function fcode = DECL_FUNCTION_CODE (node->decl);
+
+ switch (fcode)
+ {
+ case BUILT_IN_NONE:
+ gcc_unreachable ();
+
+ /* This temporarily allocates stack for the call, and we can't reasonably
+ update the watermark for that. Besides, we don't check the actual call
+ target, nor its signature, and it seems to be overkill to as much as
+ try to do so. */
+ case BUILT_IN_APPLY:
+ return false;
+
+ /* Conversely, this shouldn't be called from within strub contexts, since
+ the caller may have had its signature modified. STRUB_INTERNAL is ok,
+ the call will remain in the STRUB_WRAPPER, and removed from the
+ STRUB_WRAPPED clone. */
+ case BUILT_IN_APPLY_ARGS:
+ return false;
+
+ /* ??? Make all other builtins callable. We wish to make any builtin call
+ the compiler might introduce on its own callable. Anything that is
+ predictable enough as to be known not to allow stack data that should
+ be strubbed to unintentionally escape to non-strub contexts can be
+ allowed, and pretty much every builtin appears to fit this description.
+ The exceptions to this rule seem to be rare, and only available as
+ explicit __builtin calls, so let's keep it simple and allow all of
+ them... */
+ default:
+ return true;
+ }
+}
+
+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);
+
+ /* 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;
+ /* 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
+ safely undergo at-calls strubbing. Internal mode will still be used in
+ functions that request it explicitly with attribute strub(2), or when the
+ function body requires strubbing and at-calls strubbing is not viable. */
+ const bool strub_flag_at_calls = flag_strub == 1;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo internal strubbing. At-calls mode will still be used in
+ functions that requiest it explicitly with attribute strub() or strub(1),
+ or when the function body requires strubbing and internal strubbing is not
+ viable. */
+ const bool strub_flag_internal = flag_strub == 2;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo strubbing in either mode. When both modes are viable,
+ at-calls is preferred. */
+ const bool strub_flag_either = flag_strub == 3;
+ /* Besides the default behavior, enable strub implicitly for all viable
+ functions. */
+ const bool strub_flag_viable = flag_strub > 0;
+
+ /* The consider_* variables should be true if selecting the corresponding
+ strub modes would be consistent with requests from attributes and command
+ line flags. Attributes associated with functions pretty much mandate a
+ selection, and should report an error if not satisfied; strub_flag_auto
+ implicitly enables some viable strub mode if that's required by references
+ to variables marked for strub; strub_flag_viable enables strub if viable
+ (even when favoring one mode, body-requested strub can still be satisfied
+ by either mode), and falls back to callable, silently unless variables
+ require strubbing. */
+
+ const bool consider_at_calls
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_AT_CALLS
+ : true));
+ const bool consider_internal
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_INTERNAL
+ : true));
+
+ const bool consider_callable
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_CALLABLE
+ : (strub_flag_viable
+ || strub_callable_builtin_p (node))));
+
+ /* This is a shorthand for either strub-enabled mode. */
+ const bool consider_strub
+ = (consider_at_calls || consider_internal);
+
+ /* We can cope with always_inline functions even with noipa and noclone,
+ because we just leave them alone. */
+ const bool is_always_inline
+ = strub_always_inline_p (node);
+
+ /* Strubbing in general, and each specific strub mode, may have its own set of
+ requirements. We require noipa for strubbing, either because of cloning
+ required for internal strub, or because of caller enumeration required for
+ at-calls strub. We don't consider the at-calls mode eligible if it's not
+ even considered, it has no further requirements. Internal mode requires
+ cloning and the absence of certain features in the body and, like at-calls,
+ it's not eligible if it's not even under consideration.
+
+ ??? Do we need target hooks for further constraints? E.g., x86's
+ "interrupt" attribute breaks internal strubbing because the wrapped clone
+ carries the attribute and thus isn't callable; in this case, we could use a
+ target hook to adjust the clone instead. */
+ const bool strub_eligible
+ = (consider_strub
+ && (is_always_inline || can_strub_p (node)));
+ const bool at_calls_eligible
+ = (consider_at_calls && strub_eligible
+ && can_strub_at_calls_p (node));
+ const bool internal_eligible
+ = (consider_internal && strub_eligible
+ && (is_always_inline
+ || can_strub_internally_p (node)));
+
+ /* In addition to the strict eligibility requirements, some additional
+ constraints are placed on implicit selection of certain modes. These do
+ not prevent the selection of a mode if explicitly specified as part of a
+ function interface (the strub attribute), but they may prevent modes from
+ being selected by the command line or by function bodies. The only actual
+ constraint is on at-calls mode: since we change the function's exposed
+ signature, we won't do it implicitly if the function can possibly be used
+ in ways that do not expect the signature change, e.g., if the function is
+ available to or interposable by other units, if its address is taken,
+ etc. */
+ const bool at_calls_viable
+ = (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->binds_to_current_def_p ()
+ && node->can_be_local_p ()))
+ && node->only_called_directly_p ())));
+ const bool internal_viable
+ = (internal_eligible);
+
+ /* Shorthand. */
+ const bool strub_viable
+ = (at_calls_viable || internal_viable);
+
+ /* We wish to analyze the body, to look for implicit requests for strub, both
+ to implicitly enable it when the body calls for it, and to report errors if
+ the body calls for it but neither mode is viable (even if that follows from
+ non-eligibility because of the explicit specification of some non-strubbing
+ mode). We can refrain from scanning the body only in rare circumstances:
+ when strub is enabled by a function attribute (scanning might be redundant
+ in telling us to also enable it), and when we are enabling strub implicitly
+ but there are non-viable modes: we want to know whether strubbing is
+ required, to fallback to another mode, even if we're only enabling a
+ certain mode, or, when either mode would do, to report an error if neither
+ happens to be viable. */
+ const bool analyze_body
+ = (strub_attr
+ ? !consider_strub
+ : (strub_flag_auto
+ || (strub_flag_viable && (!at_calls_viable && !internal_viable))
+ || (strub_flag_either && !strub_viable)));
+
+ /* Cases in which strubbing is enabled or disabled by strub_flag_auto.
+ Unsatisfiable requests ought to be reported. */
+ const bool strub_required
+ = ((strub_attr && consider_strub)
+ || (analyze_body && strub_from_body_p (node)));
+
+ /* Besides the required cases, we want to abide by the requests to enabling on
+ an if-viable basis. */
+ const bool strub_enable
+ = (strub_required
+ || (strub_flag_at_calls && at_calls_viable)
+ || (strub_flag_internal && internal_viable)
+ || (strub_flag_either && strub_viable));
+
+ /* And now we're finally ready to select a mode that abides by the viability
+ and eligibility constraints, and that satisfies the strubbing requirements
+ and requests, subject to the constraints. If both modes are viable and
+ strub is to be enabled, pick STRUB_AT_CALLS unless STRUB_INTERNAL was named
+ as preferred. */
+ 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
+ : (strub_enable && at_calls_viable)
+ ? (strub_required && !strub_attr
+ ? STRUB_AT_CALLS_OPT
+ : STRUB_AT_CALLS)
+ : consider_callable
+ ? STRUB_CALLABLE
+ : STRUB_DISABLED);
+
+ switch (mode)
+ {
+ case STRUB_CALLABLE:
+ if (is_always_inline)
+ break;
+ /* Fall through. */
+
+ case STRUB_DISABLED:
+ if (strub_enable && !strub_attr)
+ {
+ gcc_checking_assert (analyze_body);
+ error_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD requires %<strub%>,"
+ " but no viable %<strub%> mode was found",
+ node->decl);
+ break;
+ }
+ /* Fall through. */
+
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ /* Differences from an mode requested through a function attribute are
+ 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:
+ gcc_unreachable ();
+ }
+
+ return mode;
+}
+
+/* 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)
+{
+ gcc_checking_assert (override
+ || !(DECL_P (fndt)
+ ? get_strub_attr_from_decl (fndt)
+ : get_strub_attr_from_type (fndt)));
+
+ tree attr = tree_cons (get_identifier ("strub"),
+ get_strub_mode_attr_value (mode),
+ NULL_TREE);
+ tree *attrp = NULL;
+ if (DECL_P (fndt))
+ {
+ gcc_checking_assert (FUNC_OR_METHOD_TYPE_P (TREE_TYPE (fndt)));
+ attrp = &DECL_ATTRIBUTES (fndt);
+ }
+ else if (FUNC_OR_METHOD_TYPE_P (fndt))
+ attrp = &TYPE_ATTRIBUTES (fndt);
+ else
+ gcc_unreachable ();
+
+ TREE_CHAIN (attr) = *attrp;
+ *attrp = attr;
+}
+
+void
+strub_make_callable (tree fndt)
+{
+ strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
+}
+
+static void
+set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+ enum strub_mode req_mode = get_strub_mode_from_attr (attr);
+
+ if (attr)
+ {
+ /* Check for and report incompatible mode changes. */
+ if (mode != req_mode
+ && !(req_mode == STRUB_INTERNAL
+ && (mode == STRUB_WRAPPED
+ || mode == STRUB_WRAPPER))
+ && !((req_mode == STRUB_INTERNAL
+ || req_mode == STRUB_AT_CALLS
+ || req_mode == STRUB_CALLABLE)
+ && 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));
+ if (node->alias)
+ {
+ cgraph_node *target = node->ultimate_alias_target ();
+ if (target != node)
+ error_at (DECL_SOURCE_LOCATION (target->decl),
+ "the incompatible selection was determined"
+ " by ultimate alias target %qD",
+ target->decl);
+ }
+
+ /* Report any incompatibilities with explicitly-requested strub. */
+ switch (req_mode)
+ {
+ case STRUB_AT_CALLS:
+ can_strub_at_calls_p (node, true);
+ break;
+
+ case STRUB_INTERNAL:
+ can_strub_internally_p (node, true);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Drop any incompatible strub attributes leading the decl attribute
+ chain. Return if we find one with the mode we need. */
+ for (;;)
+ {
+ if (mode == req_mode)
+ return;
+
+ if (DECL_ATTRIBUTES (node->decl) != attr)
+ break;
+
+ DECL_ATTRIBUTES (node->decl) = TREE_CHAIN (attr);
+ attr = get_strub_attr_from_decl (node->decl);
+ if (!attr)
+ break;
+
+ req_mode = get_strub_mode_from_attr (attr);
+ }
+ }
+ else if (mode == req_mode)
+ return;
+
+ strub_set_fndt_mode_to (node->decl, mode, attr);
+}
+
+static void
+set_strub_mode (cgraph_node *node)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+
+ if (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:
+ case STRUB_AT_CALLS_OPT:
+ return;
+
+ case STRUB_DISABLED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ cgraph_node *xnode = node;
+ if (node->alias)
+ xnode = node->ultimate_alias_target ();
+ /* Weakrefs may remain unresolved (the above will return node) if
+ their targets are not defined, so make sure we compute a strub
+ mode for them, instead of defaulting to STRUB_DISABLED and
+ rendering them uncallable. */
+ enum strub_mode mode = (xnode != node && !xnode->alias
+ ? get_strub_mode (xnode)
+ : compute_strub_mode (node, attr));
+
+ set_strub_mode_to (node, mode);
+}
+
+/* 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. */
+
+static bool
+strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode caller_mode = get_strub_mode (caller);
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS_OPT:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return callee_mode != STRUB_INLINABLE;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_AT_CALLS_OPT:
+ case STRUB_INTERNAL:
+ case STRUB_WRAPPER:
+ return (flag_strub >= 0);
+
+ case STRUB_DISABLED:
+ return false;
+
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ 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. */
+
+bool
+strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ case STRUB_AT_CALLS_OPT:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return true;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ strub_mode caller_mode = get_strub_mode (caller);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ case STRUB_AT_CALLS_OPT:
+ return true;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ return false;
+}
+
+/* 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;
+
+ 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_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)
+ {
+ gcc_checking_assert (!e->indirect_unknown_callee);
+ if (!strub_callable_from_p (e->callee, node))
+ {
+ if (get_strub_mode (e->callee) == STRUB_INLINABLE)
+ error_at (gimple_location (e->call_stmt),
+ "calling %<always_inline%> %<strub%> %qD"
+ " in non-%<strub%> context %qD",
+ e->callee->decl, node->decl);
+ else if (fndecl_built_in_p (e->callee->decl, BUILT_IN_APPLY_ARGS)
+ && get_strub_mode (node) == STRUB_INTERNAL)
+ /* This is ok, it will be kept in the STRUB_WRAPPER, and removed
+ from the STRUB_WRAPPED's strub context. */
+ continue;
+ else
+ error_at (gimple_location (e->call_stmt),
+ "calling non-%<strub%> %qD in %<strub%> context %qD",
+ e->callee->decl, node->decl);
+ }
+ }
+ }
+
+ /* ??? 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 {
+
+const pass_data pass_data_ipa_strub_mode = {
+ SIMPLE_IPA_PASS,
+ "strubm",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ 0, // properties_finish
+};
+
+class pass_ipa_strub_mode : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub_mode (gcc::context *ctxt)
+ : simple_ipa_opt_pass (pass_data_ipa_strub_mode, ctxt)
+ {}
+ 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
+ overhead. */
+ if (flag_strub == -2)
+ flag_strub = 0;
+ return flag_strub;
+ }
+ virtual unsigned int execute (function *);
+};
+
+const pass_data pass_data_ipa_strub = {
+ SIMPLE_IPA_PASS,
+ "strub",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ TODO_update_ssa
+ | TODO_cleanup_cfg
+ | TODO_rebuild_cgraph_edges
+ | TODO_verify_il, // properties_finish
+};
+
+class pass_ipa_strub : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub (gcc::context *ctxt)
+ : 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 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_variant_type_copy (ptr_type_node))
+
+ DEF_TYPE (pwmt, build_reference_type (get_wmt ()))
+
+ DEF_TYPE (qpwmt,
+ build_qualified_type (get_pwmt (),
+ TYPE_QUAL_RESTRICT
+ /* | TYPE_QUAL_CONST */))
+
+ DEF_TYPE (qptr,
+ build_qualified_type (ptr_type_node,
+ TYPE_QUAL_RESTRICT
+ | TYPE_QUAL_CONST))
+
+ DEF_TYPE (qpvalst,
+ build_qualified_type (build_reference_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); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ decl = add_builtin_function \
+ ("__builtin_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ NULL, NULL); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ } \
+ return decl; \
+ }
+
+ DEF_NM_BUILTIN (stack_address,
+ BUILT_IN_STACK_ADDRESS,
+ (ptr_type_node, NULL))
+
+#undef DEF_NM_BUILTIN
+
+#define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST) \
+ static tree get_ ## NAME () { \
+ tree decl = builtin_decl_explicit (CODE); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ tree attrs = NULL; \
+ if (FNSPEC && HAVE_ATTR_FNSPEC) \
+ attrs = tree_cons (get_identifier ("fn spec"), \
+ build_tree_list \
+ (NULL_TREE, \
+ build_string (strlen (FNSPEC), \
+ (FNSPEC))), \
+ attrs); \
+ decl = add_builtin_function_ext_scope \
+ ("__builtin___strub_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ "__strub_" #NAME, attrs); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ } \
+ return decl; \
+ }
+
+ DEF_SS_BUILTIN (enter, ". Ot",
+ BUILT_IN___STRUB_ENTER,
+ (void_type_node, get_qpwmt (), NULL))
+ DEF_SS_BUILTIN (update, ". Wt",
+ BUILT_IN___STRUB_UPDATE,
+ (void_type_node, get_qpwmt (), NULL))
+ DEF_SS_BUILTIN (leave, ". w ",
+ BUILT_IN___STRUB_LEAVE,
+ (void_type_node, get_qpwmt (), NULL))
+
+#undef DEF_SS_BUILTIN
+
+#define DEF_IDENT(NAME) \
+ static inline tree get_ ## NAME () { \
+ static tree identifier = NULL_TREE; \
+ if (!identifier) \
+ identifier = get_identifier (".strub." #NAME); \
+ return identifier; \
+ }
+
+ DEF_IDENT (watermark_ptr)
+ DEF_IDENT (va_list_ptr)
+ DEF_IDENT (apply_args)
+
+#undef DEF_IDENT
+
+ static inline int adjust_at_calls_type (tree);
+ static inline void adjust_at_calls_call (cgraph_edge *, int);
+ static inline void adjust_at_calls_calls (cgraph_node *);
+
+ static inline gimple_seq
+ call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
+ gimple_seq seq = NULL)
+ {
+ tree uwm = get_update ();
+ gcall *update = gimple_build_call (uwm, 1, wmptr);
+ if (node)
+ gimple_set_location (update, DECL_SOURCE_LOCATION (node->decl));
+ gimple_seq_add_stmt (&seq, update);
+ if (node)
+#if !IMPLICIT_CGRAPH_EDGES
+ node->create_edge (cgraph_node::get_create (uwm), update, count, false);
+#else
+ (void)count;
+#endif
+ return seq;
+ }
+
+};
+
+} // anon namespace
+
+typedef hash_set<tree> indirect_parms_t;
+
+static tree
+maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
+{
+ if (DECL_P (op))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (op))
+ {
+ tree ret = gimple_fold_indirect_ref (op);
+ if (!ret)
+ ret = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (op)),
+ op,
+ build_int_cst (TREE_TYPE (op), 0));
+ return ret;
+ }
+ }
+ else if (TREE_CODE (op) == ADDR_EXPR
+ && DECL_P (TREE_OPERAND (op, 0)))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (TREE_OPERAND (op, 0)))
+ return TREE_OPERAND (op, 0);
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_make_indirect (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ indirect_parms_t &indirect_parms = *(indirect_parms_t *)wi->info;
+
+ if (!*op || TYPE_P (*op))
+ {
+ *rec = 0;
+ return NULL_TREE;
+ }
+
+ if (tree repl = maybe_make_indirect (indirect_parms, *op, rec))
+ {
+ *op = repl;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ gimple_stmt_iterator &gsi = *(gimple_stmt_iterator *)wi->info;
+
+ *rec = 0;
+
+ if (!*op || TREE_CODE (*op) != ADDR_EXPR)
+ return NULL_TREE;
+
+ if (!is_gimple_val (*op))
+ {
+ tree ret = force_gimple_operand_gsi (&gsi, *op, true,
+ NULL_TREE, true, GSI_SAME_STMT);
+ gcc_assert (ret != *op);
+ *op = ret;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+build_ref_type_for (tree parm, bool nonaliased = true)
+{
+ gcc_checking_assert (TREE_CODE (parm) == PARM_DECL);
+
+ tree ref_type = build_reference_type (TREE_TYPE (parm));
+
+ if (!nonaliased)
+ return ref_type;
+
+ /* Each PARM turned indirect still points to the distinct memory area at the
+ wrapper, and the reference in unchanging, so we might qualify it, but...
+ const is not really important, since we're only using default defs for the
+ reference parm anyway, and not introducing any defs, and restrict seems to
+ cause trouble. E.g., libgnat/s-concat3.adb:str_concat_3 has memmoves that,
+ if it's wrapped, the memmoves are deleted in dse1. Using a distinct alias
+ set seems to not run afoul of this problem, and it hopefully enables the
+ compiler to tell the pointers do point to objects that are not otherwise
+ aliased. */
+#if 1
+ tree qref_type = build_variant_type_copy (ref_type);
+
+ TYPE_ALIAS_SET (qref_type) = new_alias_set ();
+ record_alias_subset (TYPE_ALIAS_SET (qref_type), get_alias_set (ref_type));
+
+ return qref_type;
+#else
+ tree qref_type = build_qualified_type (ref_type,
+ TYPE_QUAL_RESTRICT
+ | TYPE_QUAL_CONST);
+
+ return qref_type;
+#endif
+}
+
+/* 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)
+{
+#if IMPLICIT_CGRAPH_EDGES
+ return;
+#endif
+
+ cgraph_node *node = cgraph_node::get_create (current_function_decl);
+
+ for (gimple_stmt_iterator gsi = gsi_start (seq);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!is_a <gcall *> (stmt))
+ 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);
+ }
+}
+
+static void
+gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
+{
+ if (!seq)
+ return;
+
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (gimple_has_location (stmt))
+ annotate_all_with_location (seq, gimple_location (stmt));
+
+ gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+ 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;
+ bool nothrow_p = (must_not_throw_p
+ || (call && gimple_call_nothrow_p (call))
+ || (eh_lp <= 0
+ && (TREE_NOTHROW (cfun->decl)
+ || !flag_exceptions)));
+
+ if (noreturn_p && nothrow_p)
+ return;
+
+ /* Don't expect an EH edge if we're not to throw, or if we're not in an EH
+ region yet. */
+ bool no_eh_edge_p = (nothrow_p || !eh_lp);
+ bool must_end_bb = stmt_ends_bb_p (stmt);
+
+ edge eft = NULL, eeh = NULL;
+ if (must_end_bb && !(noreturn_p && no_eh_edge_p))
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+
+ edge e;
+ edge_iterator ei;
+ FOR_EACH_EDGE (e, ei, gsi_bb (gsi)->succs)
+ {
+ if ((e->flags & EDGE_EH))
+ {
+ gcc_checking_assert (!eeh);
+ eeh = e;
+#if !CHECKING_P
+ if (eft || noreturn_p)
+ break;
+#endif
+ }
+ if ((e->flags & EDGE_FALLTHRU))
+ {
+ gcc_checking_assert (!eft);
+ eft = e;
+#if !CHECKING_P
+ if (eeh || no_eh_edge_p)
+ break;
+#endif
+ }
+ }
+
+ gcc_checking_assert (!(eft && (eft->flags & EDGE_FALLTHRU))
+ == noreturn_p);
+ gcc_checking_assert (!(eeh && (eeh->flags & EDGE_EH))
+ == no_eh_edge_p);
+ gcc_checking_assert (eft != eeh);
+ }
+
+ if (!noreturn_p)
+ {
+ gimple_seq nseq = nothrow_p ? seq : gimple_seq_copy (seq);
+
+ if (must_end_bb)
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+ add_call_edges_for_seq (nseq, eft->count ());
+ gsi_insert_seq_on_edge_immediate (eft, nseq);
+ }
+ else
+ {
+ add_call_edges_for_seq (nseq, gsi_bb (gsi)->count);
+ gsi_insert_seq_after (&gsi, nseq, GSI_SAME_STMT);
+ }
+ }
+
+ if (nothrow_p)
+ return;
+
+ if (eh_lp)
+ {
+ add_call_edges_for_seq (seq, eeh->count ());
+ gsi_insert_seq_on_edge_immediate (eeh, seq);
+ return;
+ }
+
+ /* A throwing call may appear within a basic block in a function that doesn't
+ have any EH regions. We're going to add a cleanup if so, therefore the
+ block will have to be split. */
+ basic_block bb = gsi_bb (gsi);
+ if (!gsi_one_before_end_p (gsi))
+ split_block (bb, stmt);
+
+ /* Create a new block for the EH cleanup. */
+ basic_block bb_eh_cleanup = create_empty_bb (bb);
+ if (dom_info_available_p (CDI_DOMINATORS))
+ set_immediate_dominator (CDI_DOMINATORS, bb_eh_cleanup, bb);
+ if (current_loops)
+ add_bb_to_loop (bb_eh_cleanup, current_loops->tree_root);
+
+ /* Make the new block an EH cleanup for the call. */
+ eh_region new_r = gen_eh_region_cleanup (NULL);
+ eh_landing_pad lp = gen_eh_landing_pad (new_r);
+ tree label = gimple_block_label (bb_eh_cleanup);
+ lp->post_landing_pad = label;
+ EH_LANDING_PAD_NR (label) = lp->index;
+ add_stmt_to_eh_lp (stmt, lp->index);
+
+ /* Add the cleanup code to the EH cleanup block. */
+ gsi = gsi_after_labels (bb_eh_cleanup);
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
+
+ /* And then propagate the exception further. */
+ gresx *resx = gimple_build_resx (new_r->index);
+ if (gimple_has_location (stmt))
+ gimple_set_location (resx, gimple_location (stmt));
+ gsi_insert_before (&gsi, resx, GSI_SAME_STMT);
+
+ /* Finally, wire the EH cleanup block into the CFG. */
+ make_eh_edges (stmt);
+ add_call_edges_for_seq (seq, single_pred_edge (bb_eh_cleanup)->count ());
+}
+
+/* Copy the attribute list at *ATTRS, minus any NAME attributes, leaving
+ shareable trailing nodes alone. */
+
+static inline void
+remove_named_attribute_unsharing (const char *name, tree *attrs)
+{
+ while (tree found = lookup_attribute (name, *attrs))
+ {
+ /* Copy nodes up to the next NAME attribute. */
+ while (*attrs != found)
+ {
+ *attrs = tree_cons (TREE_PURPOSE (*attrs),
+ TREE_VALUE (*attrs),
+ TREE_CHAIN (*attrs));
+ attrs = &TREE_CHAIN (*attrs);
+ }
+ /* Then drop it. */
+ gcc_checking_assert (*attrs == found);
+ *attrs = TREE_CHAIN (*attrs);
+ }
+}
+
+static int last_cgraph_order;
+
+static void
+ipa_strub_set_mode_for_new_functions ()
+{
+ if (symtab->order == last_cgraph_order)
+ return;
+
+ cgraph_node *node;
+
+ /* 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. */
+ for (int aliases = 0; aliases <= 1; aliases++)
+ FOR_EACH_FUNCTION (node)
+ {
+ if (!node->alias != !aliases)
+ continue;
+
+ /* Already done. */
+ if (node->order < last_cgraph_order)
+ continue;
+
+ set_strub_mode (node);
+ }
+
+ last_cgraph_order = symtab->order;
+}
+
+/* Return FALSE if NODE is a strub context, and TRUE otherwise. */
+bool
+strub_splittable_p (cgraph_node *node)
+{
+ switch (get_strub_mode (node))
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_AT_CALLS_OPT:
+ case STRUB_INLINABLE:
+ case STRUB_INTERNAL:
+ case STRUB_WRAPPER:
+ return false;
+
+ case STRUB_CALLABLE:
+ case STRUB_DISABLED:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ return true;
+}
+
+/* Return the PARM_DECL of the incoming watermark pointer, if there is one. */
+tree
+strub_watermark_parm (tree fndecl)
+{
+ switch (get_strub_mode_from_decl (fndecl))
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_AT_CALLS_OPT:
+ break;
+
+ case STRUB_INTERNAL:
+ case STRUB_WRAPPER:
+ case STRUB_CALLABLE:
+ case STRUB_DISABLED:
+ case STRUB_INLINABLE:
+ return NULL_TREE;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ for (tree parm = DECL_ARGUMENTS (fndecl); parm; parm = DECL_CHAIN (parm))
+ /* The type (variant) compare finds the parameter even in a just-created
+ clone, before we set its name, but the type-based compare doesn't work
+ during builtin expansion within the lto compiler, because we'll have
+ created a separate variant in that run. */
+ if (TREE_TYPE (parm) == pass_ipa_strub::get_qpwmt ()
+ || DECL_NAME (parm) == pass_ipa_strub::get_watermark_ptr ())
+ return parm;
+
+ gcc_unreachable ();
+}
+
+/* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
+ hasn't been added yet. Return the named argument count. */
+int
+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 already 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);
+ }
+
+ /* 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);
+
+#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 we're already within a strub context, pass on the incoming watermark
+ pointer, and omit the enter and leave calls around the modified call, as an
+ optimization, or as a means to satisfy a tail-call requirement. */
+ tree swmp = ((optimize_size || optimize > 2
+ || gimple_call_must_tail_p (ocall)
+ || (optimize == 2 && gimple_call_tail_p (ocall)))
+ ? strub_watermark_parm (e->caller->decl)
+ : NULL_TREE);
+ bool omit_own_watermark = swmp;
+ tree swm = NULL_TREE;
+ if (!omit_own_watermark)
+ {
+ swm = create_tmp_var (get_wmt (), ".strub.watermark");
+ TREE_ADDRESSABLE (swm) = true;
+ swmp = build1 (ADDR_EXPR, get_pwmt (), swm);
+
+ /* Initialize the watermark before the call. */
+ tree enter = get_enter ();
+ gcall *stptr = gimple_build_call (enter, 1,
+ unshare_expr (swmp));
+ if (gimple_has_location (ocall))
+ gimple_set_location (stptr, gimple_location (ocall));
+ 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)))
+ {
+ if (!swm)
+ swm = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (swmp)),
+ swmp,
+ build_int_cst (TREE_TYPE (swmp), 0));
+
+ vec<tree, va_gc> *inputs = NULL;
+ vec<tree, va_gc> *outputs = NULL;
+ vec_safe_push (outputs,
+ build_tree_list
+ (build_tree_list
+ (NULL_TREE, build_string (2, "=m")),
+ unshare_expr (swm)));
+ vec_safe_push (inputs,
+ build_tree_list
+ (build_tree_list
+ (NULL_TREE, build_string (1, "m")),
+ unshare_expr (swm)));
+ gasm *forcemod = gimple_build_asm_vec ("", inputs, outputs,
+ NULL, NULL);
+ gimple_seq_add_stmt (&seq, forcemod);
+
+ /* If the call will be assumed to not even read the watermark,
+ make sure it is already in memory before the call. */
+ if ((gimple_call_flags (wrcall) & ECF_CONST))
+ {
+ vec<tree, va_gc> *inputs = NULL;
+ vec_safe_push (inputs,
+ build_tree_list
+ (build_tree_list
+ (NULL_TREE, build_string (1, "m")),
+ unshare_expr (swm)));
+ gasm *force_store = gimple_build_asm_vec ("", inputs, NULL,
+ NULL, NULL);
+ if (gimple_has_location (wrcall))
+ gimple_set_location (force_store, gimple_location (wrcall));
+ gsi_insert_before (&gsi, force_store, GSI_SAME_STMT);
+ }
+ }
+#endif
+
+ if (!omit_own_watermark)
+ {
+ gcall *sleave = gimple_build_call (get_leave (), 1,
+ unshare_expr (swmp));
+ gimple_seq_add_stmt (&seq, sleave);
+
+ gassign *clobber = gimple_build_assign (swm,
+ build_clobber
+ (TREE_TYPE (swm)));
+ gimple_seq_add_stmt (&seq, clobber);
+ }
+
+ gsi_insert_finally_seq_after_call (gsi, seq);
+}
+
+void
+pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
+{
+ /* Adjust unknown-callee indirect calls with STRUB_AT_CALLS types within
+ onode. */
+ if (node->indirect_calls)
+ {
+ push_cfun (DECL_STRUCT_FUNCTION (node->decl));
+ for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
+ {
+ gcc_checking_assert (e->indirect_unknown_callee);
+
+ tree callee_fntype = gimple_call_fntype (e->call_stmt);
+ enum strub_mode callee_mode
+ = get_strub_mode_from_type (callee_fntype);
+
+ if (callee_mode != STRUB_AT_CALLS
+ && callee_mode != STRUB_AT_CALLS_OPT)
+ continue;
+
+ int named_args = adjust_at_calls_type (callee_fntype);
+
+ adjust_at_calls_call (e, named_args);
+ }
+ pop_cfun ();
+ }
+
+ if (node->callees)
+ {
+ push_cfun (DECL_STRUCT_FUNCTION (node->decl));
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ gcc_checking_assert (!e->indirect_unknown_callee);
+
+ enum strub_mode callee_mode = get_strub_mode (e->callee);
+
+ if (callee_mode != STRUB_AT_CALLS
+ && callee_mode != STRUB_AT_CALLS_OPT)
+ continue;
+
+ int named_args = adjust_at_calls_type (TREE_TYPE (e->callee->decl));
+
+ adjust_at_calls_call (e, named_args);
+ }
+ pop_cfun ();
+ }
+}
+
+unsigned int
+pass_ipa_strub_mode::execute (function *)
+{
+ last_cgraph_order = 0;
+ ipa_strub_set_mode_for_new_functions ();
+
+ /* Verify before any inlining or other transformations. */
+ verify_strub ();
+
+ return 0;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub_mode (gcc::context *ctxt)
+{
+ return new pass_ipa_strub_mode (ctxt);
+}
+
+unsigned int
+pass_ipa_strub::execute (function *)
+{
+ cgraph_node *onode;
+
+ 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
+ || mode == STRUB_AT_CALLS_OPT)
+ {
+ /* 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. */
+ bool aliased_parms = (onode->alias && *pargs
+ && DECL_CONTEXT (*pargs) != onode->decl);
+
+ 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_qpwmt ());
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_ARG_TYPE (wmptr) = get_qpwmt ();
+ DECL_CONTEXT (wmptr) = onode->decl;
+ TREE_USED (wmptr) = 1;
+ DECL_CHAIN (wmptr) = *pargs;
+ *pargs = wmptr;
+
+ 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));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca)
+ {
+ 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);
+
+ if (!is_gimple_call (stmt))
+ continue;
+
+ gcall *call = as_a <gcall *> (stmt);
+
+ if (gimple_alloca_call_p (call))
+ {
+ /* Capture stack growth. */
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)
+ ->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ }
+ }
+
+ pop_cfun ();
+
+#if 0
+ compute_fn_summary (onode, true);
+#endif
+ }
+ }
+
+ FOR_EACH_FUNCTION (onode)
+ {
+ if (!onode->has_gimple_body_p ())
+ continue;
+
+ enum strub_mode mode = get_strub_mode (onode);
+
+ if (mode != STRUB_INTERNAL)
+ {
+ adjust_at_calls_calls (onode);
+ 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);
+
+ vec<ipa_adjusted_param, va_gc> *nparms = NULL;
+ unsigned j = 0;
+ {
+ // The following loop copied from ipa-split.c:split_function.
+ for (tree parm = DECL_ARGUMENTS (onode->decl);
+ parm; parm = DECL_CHAIN (parm), j++)
+ {
+ ipa_adjusted_param adj = {};
+ adj.op = IPA_PARAM_OP_COPY;
+ adj.base_index = j;
+ adj.prev_clone_index = j;
+ vec_safe_push (nparms, adj);
+ }
+
+ if (apply_args)
+ {
+ ipa_adjusted_param aaadj = {};
+ aaadj.op = IPA_PARAM_OP_NEW;
+ aaadj.type = get_qptr ();
+ vec_safe_push (nparms, aaadj);
+ }
+
+ if (is_stdarg)
+ {
+ ipa_adjusted_param vladj = {};
+ vladj.op = IPA_PARAM_OP_NEW;
+ vladj.type = get_qpvalst ();
+ vec_safe_push (nparms, vladj);
+ }
+
+ ipa_adjusted_param wmadj = {};
+ wmadj.op = IPA_PARAM_OP_NEW;
+ wmadj.type = get_qpwmt ();
+ vec_safe_push (nparms, wmadj);
+ }
+ ipa_param_adjustments adj (nparms, -1, false);
+
+ cgraph_node *nnode = onode->create_version_clone_with_body
+ (auto_vec<cgraph_edge *> (0),
+ NULL, &adj, NULL, NULL, "strub", NULL);
+
+ if (!nnode)
+ {
+ error_at (DECL_SOURCE_LOCATION (onode->decl),
+ "failed to split %qD for %<strub%>",
+ onode->decl);
+ continue;
+ }
+
+ onode->split_part = true;
+ if (onode->calls_comdat_local)
+ nnode->add_to_same_comdat_group (onode);
+
+ gcc_checking_assert (!DECL_STRUCT_FUNCTION (nnode->decl)->stdarg);
+
+ set_strub_mode_to (onode, STRUB_WRAPPER);
+ set_strub_mode_to (nnode, STRUB_WRAPPED);
+
+ adjust_at_calls_calls (nnode);
+
+ /* Decide which of the wrapped function's parms we want to turn into
+ references to the argument passed to the wrapper. In general, we want to
+ copy small arguments, and avoid copying large ones. Variable-sized array
+ lengths given by other arguments, as in 20020210-1.c, would lead to
+ problems if passed by value, after resetting the original function and
+ dropping the length computation; passing them by reference works.
+ DECL_BY_REFERENCE is *not* a substitute for this: it involves copying
+ anyway, but performed at the caller. */
+ indirect_parms_t indirect_nparms (3, false);
+ unsigned adjust_ftype = 0;
+ unsigned named_args = 0;
+ for (tree parm = DECL_ARGUMENTS (onode->decl),
+ nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (TREE_TYPE (nnode->decl));
+ parm;
+ named_args++,
+ parm = DECL_CHAIN (parm),
+ nparm = DECL_CHAIN (nparm),
+ nparmt = nparmt ? TREE_CHAIN (nparmt) : NULL_TREE)
+ if (!(0 /* DECL_BY_REFERENCE (narg) */
+ || is_gimple_reg_type (TREE_TYPE (nparm))
+ || VECTOR_TYPE_P (TREE_TYPE (nparm))
+ || TREE_CODE (TREE_TYPE (nparm)) == COMPLEX_TYPE
+ || (tree_fits_uhwi_p (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ && (tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ <= 4 * UNITS_PER_WORD))))
+ {
+ indirect_nparms.add (nparm);
+
+ /* ??? Is there any case in which it is not safe to suggest the parms
+ turned indirect don't alias anything else? They are distinct,
+ unaliased memory in the wrapper, and the wrapped can't possibly
+ take pointers into them because none of the pointers passed to the
+ wrapper can alias other incoming parameters passed by value, even
+ if with transparent reference, and the wrapper doesn't take any
+ extra parms that could point into wrapper's parms. So we can
+ probably drop the TREE_ADDRESSABLE and keep the true. */
+ tree ref_type = build_ref_type_for (nparm,
+ true
+ || !TREE_ADDRESSABLE (parm));
+
+ DECL_ARG_TYPE (nparm) = TREE_TYPE (nparm) = ref_type;
+ relayout_decl (nparm);
+ TREE_ADDRESSABLE (nparm) = 0;
+ DECL_BY_REFERENCE (nparm) = 0;
+#if FOR_GCC_11P
+ DECL_NOT_GIMPLE_REG_P (nparm) = 0;
+#else
+ DECL_GIMPLE_REG_P (nparm) = 1;
+#endif
+ /* ??? This avoids mismatches in debug info bind stmts in
+ e.g. a-chahan . */
+ DECL_ABSTRACT_ORIGIN (nparm) = NULL;
+
+ if (nparmt)
+ adjust_ftype++;
+ }
+
+ /* Also adjust the wrapped function type, if needed. */
+ if (adjust_ftype)
+ {
+ tree nftype = TREE_TYPE (nnode->decl);
+
+ /* We always add at least one argument at the end of the signature, when
+ cloning the function, so we don't expect to need to duplicate the
+ type here. */
+ gcc_checking_assert (TYPE_ARG_TYPES (nftype)
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+
+#if HAVE_ATTR_FNSPEC
+ /* Check that fnspec still works for the modified function signature,
+ and drop it otherwise. */
+ bool drop_fnspec = false;
+ tree fnspec = lookup_attribute ("fn spec", TYPE_ATTRIBUTES (nftype));
+ attr_fnspec spec = fnspec ? attr_fnspec (fnspec) : attr_fnspec ("");
+
+ unsigned retcopy;
+ if (!(fnspec && spec.returns_arg (&retcopy)))
+ retcopy = (unsigned) -1;
+
+ unsigned i = 0;
+#endif
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (nftype);
+ adjust_ftype > 0;
+#if HAVE_ATTR_FNSPEC
+ i++,
+#endif
+ nparm = DECL_CHAIN (nparm), nparmt = TREE_CHAIN (nparmt))
+ if (indirect_nparms.contains (nparm))
+ {
+ TREE_VALUE (nparmt) = TREE_TYPE (nparm);
+ adjust_ftype--;
+
+#if HAVE_ATTR_FNSPEC
+ if (fnspec && !drop_fnspec)
+ {
+ if (i == retcopy)
+ drop_fnspec = true;
+ else if (spec.arg_specified_p (i))
+ {
+ /* Properties that apply to pointers only must not be
+ present, because we don't make pointers further
+ indirect. */
+ gcc_checking_assert
+ (!spec.arg_max_access_size_given_by_arg_p (i, NULL));
+ gcc_checking_assert (!spec.arg_copied_to_arg_p (i, NULL));
+
+ /* Any claim of direct access only is invalidated by
+ adding an indirection level. */
+ if (spec.arg_direct_p (i))
+ drop_fnspec = true;
+
+ /* If there's a claim the argument is not read from, the
+ added indirection invalidates it: if the argument is
+ used at all, then the pointer will necessarily be
+ read. */
+ if (!spec.arg_maybe_read_p (i)
+ && spec.arg_used_p (i))
+ drop_fnspec = true;
+ }
+ }
+#endif
+ }
+
+#if HAVE_ATTR_FNSPEC
+ /* ??? Maybe we could adjust it instead. */
+ if (drop_fnspec)
+ remove_named_attribute_unsharing ("fn spec",
+ &TYPE_ATTRIBUTES (nftype));
+#endif
+
+ TREE_TYPE (nnode->decl) = nftype;
+ }
+
+#if ATTR_FNSPEC_DECONST_WATERMARK
+ {
+ int flags = flags_from_decl_or_type (nnode->decl);
+ tree fnspec = lookup_attribute ("fn spec", TREE_TYPE (nnode->decl));
+
+ if ((flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS)) || fnspec)
+ {
+ size_t xargs = 1 + int (is_stdarg) + int (apply_args);
+ 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! */
+ bool no_writes_p = true;
+ if (fnspec)
+ {
+ tree fnspecstr = TREE_VALUE (TREE_VALUE (fnspec));
+ curlen = TREE_STRING_LENGTH (fnspecstr);
+ memcpy (nspec, TREE_STRING_POINTER (fnspecstr), curlen);
+ if (!(flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS))
+ && curlen >= 2
+ && nspec[1] != 'c' && nspec[1] != 'C'
+ && nspec[1] != 'p' && nspec[1] != 'P')
+ no_writes_p = false;
+ }
+ if (!curlen)
+ {
+ nspec[curlen++] = '.';
+ nspec[curlen++] = ((flags & ECF_CONST)
+ ? 'c'
+ : (flags & ECF_PURE)
+ ? 'p'
+ : ' ');
+ }
+ while (curlen < tgtlen - 2 * xargs)
+ {
+ nspec[curlen++] = '.';
+ nspec[curlen++] = ' ';
+ }
+
+ /* These extra args are unlikely to be present in const or pure
+ functions. It's conceivable that a function that takes variable
+ arguments, or that passes its arguments on to another function,
+ could be const or pure, but it would not modify the arguments, and,
+ being pure or const, it couldn't possibly modify or even access
+ memory referenced by them. But it can read from these internal
+ data structures created by the wrapper, and from any
+ argument-passing memory referenced by them, so we denote the
+ possibility of reading from multiple levels of indirection, but
+ only of reading because const/pure. */
+ if (apply_args)
+ {
+ nspec[curlen++] = 'r';
+ nspec[curlen++] = ' ';
+ }
+ if (is_stdarg)
+ {
+ nspec[curlen++] = (no_writes_p ? 'r' : '.');
+ nspec[curlen++] = (no_writes_p ? 't' : ' ');
+ }
+
+ nspec[curlen++] = 'W';
+ nspec[curlen++] = 't';
+
+ /* The type has already been copied before adding parameters. */
+ gcc_checking_assert (TYPE_ARG_TYPES (TREE_TYPE (nnode->decl))
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+ TYPE_ATTRIBUTES (TREE_TYPE (nnode->decl))
+ = tree_cons (get_identifier ("fn spec"),
+ build_tree_list (NULL_TREE,
+ build_string (tgtlen, nspec)),
+ TYPE_ATTRIBUTES (TREE_TYPE (nnode->decl)));
+ }
+ }
+#endif
+
+ {
+ tree decl = onode->decl;
+ cgraph_node *target = nnode;
+
+ { // copied from create_wrapper
+
+ /* Preserve DECL_RESULT so we get right by reference flag. */
+ tree decl_result = DECL_RESULT (decl);
+
+ /* Remove the function's body but keep arguments to be reused
+ for thunk. */
+ onode->release_body (true);
+ onode->reset ();
+
+ DECL_UNINLINABLE (decl) = false;
+ DECL_RESULT (decl) = decl_result;
+ DECL_INITIAL (decl) = NULL;
+ allocate_struct_function (decl, false);
+ set_cfun (NULL);
+
+ /* Turn alias into thunk and expand it into GIMPLE representation. */
+ onode->definition = true;
+
+#if FOR_GCC_11P
+ thunk_info::get_create (onode);
+ onode->thunk = true;
+#else
+ memset (&onode->thunk, 0, sizeof (cgraph_thunk_info));
+ onode->thunk.thunk_p = true;
+ onode->thunk.alias = target->decl;
+#endif
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (target, NULL, onode->count);
+#endif
+ onode->callees->can_throw_external = !TREE_NOTHROW (target->decl);
+
+ tree arguments = DECL_ARGUMENTS (decl);
+
+ while (arguments)
+ {
+ TREE_ADDRESSABLE (arguments) = false;
+ arguments = TREE_CHAIN (arguments);
+ }
+
+ {
+ tree alias = onode->callees->callee->decl;
+ tree thunk_fndecl = decl;
+ tree a;
+
+ int nxargs = 1 + is_stdarg + apply_args;
+
+ { // Simplified from expand_thunk.
+ tree restype;
+ basic_block bb, then_bb, else_bb, return_bb;
+ gimple_stmt_iterator bsi;
+ int nargs = 0;
+ tree arg;
+ int i;
+ tree resdecl;
+ tree restmp = NULL;
+
+ gcall *call;
+ greturn *ret;
+ bool alias_is_noreturn = TREE_THIS_VOLATILE (alias);
+
+ a = DECL_ARGUMENTS (thunk_fndecl);
+
+ current_function_decl = thunk_fndecl;
+
+#if FOR_GCC_11P
+ /* Ensure thunks are emitted in their correct sections. */
+ resolve_unique_section (thunk_fndecl, 0,
+ flag_function_sections);
+#endif
+
+ bitmap_obstack_initialize (NULL);
+
+ /* Build the return declaration for the function. */
+ restype = TREE_TYPE (TREE_TYPE (thunk_fndecl));
+ if (DECL_RESULT (thunk_fndecl) == NULL_TREE)
+ {
+ resdecl = build_decl (input_location, RESULT_DECL, 0, restype);
+ DECL_ARTIFICIAL (resdecl) = 1;
+ DECL_IGNORED_P (resdecl) = 1;
+ DECL_CONTEXT (resdecl) = thunk_fndecl;
+ DECL_RESULT (thunk_fndecl) = resdecl;
+ }
+ else
+ resdecl = DECL_RESULT (thunk_fndecl);
+
+ profile_count cfg_count = onode->count;
+ if (!cfg_count.initialized_p ())
+ cfg_count = profile_count::from_gcov_type (BB_FREQ_MAX).guessed_local ();
+
+ bb = then_bb = else_bb = return_bb
+ = init_lowered_empty_function (thunk_fndecl, true, cfg_count);
+
+ bsi = gsi_start_bb (bb);
+
+ /* Build call to the function being thunked. */
+ if (!VOID_TYPE_P (restype)
+ && (!alias_is_noreturn
+ || TREE_ADDRESSABLE (restype)
+ || TREE_CODE (TYPE_SIZE_UNIT (restype)) != INTEGER_CST))
+ {
+ if (DECL_BY_REFERENCE (resdecl))
+ {
+ restmp = gimple_fold_indirect_ref (resdecl);
+ if (!restmp)
+ restmp = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (resdecl)),
+ resdecl,
+ build_int_cst (TREE_TYPE (resdecl), 0));
+ }
+ else if (!is_gimple_reg_type (restype))
+ {
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl)))
+ {
+ restmp = resdecl;
+
+ if (VAR_P (restmp))
+ {
+ add_local_decl (cfun, restmp);
+ BLOCK_VARS (DECL_INITIAL (current_function_decl))
+ = restmp;
+ }
+ }
+ else
+ restmp = create_tmp_var (restype, "retval");
+ }
+ else
+ restmp = create_tmp_reg (restype, "retval");
+ }
+
+ for (arg = a; arg; arg = DECL_CHAIN (arg))
+ nargs++;
+ auto_vec<tree> vargs (nargs + nxargs);
+ i = 0;
+ arg = a;
+
+ if (nargs)
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl);
+ i < nargs;
+ i++, arg = DECL_CHAIN (arg), nparm = DECL_CHAIN (nparm))
+ {
+ tree save_arg = arg;
+ tree tmp = arg;
+
+ /* Arrange to pass indirectly the parms, if we decided to do
+ so, and revert its type in the wrapper. */
+ if (indirect_nparms.contains (nparm))
+ {
+ tree ref_type = TREE_TYPE (nparm);
+ TREE_ADDRESSABLE (arg) = true;
+ tree addr = build1 (ADDR_EXPR, ref_type, arg);
+ tmp = arg = addr;
+ }
+#if ! FOR_GCC_11P
+ else if (VECTOR_TYPE_P (TREE_TYPE (arg))
+ || TREE_CODE (TREE_TYPE (arg)) == COMPLEX_TYPE)
+ DECL_GIMPLE_REG_P (arg) = 1;
+#else
+ else
+ DECL_NOT_GIMPLE_REG_P (arg) = 0;
+#endif
+
+ /* Convert the argument back to the type used by the calling
+ conventions, e.g. a non-prototyped float type is passed as
+ double, as in 930603-1.c, and needs to be converted back to
+ double to be passed on unchanged to the wrapped
+ function. */
+ if (TREE_TYPE (nparm) != DECL_ARG_TYPE (nparm))
+ arg = fold_convert (DECL_ARG_TYPE (nparm), arg);
+
+ if (!is_gimple_val (arg))
+ {
+ tmp = create_tmp_reg (TYPE_MAIN_VARIANT
+ (TREE_TYPE (arg)), "arg");
+ gimple *stmt = gimple_build_assign (tmp, arg);
+ gsi_insert_after (&bsi, stmt, GSI_NEW_STMT);
+ }
+ vargs.quick_push (tmp);
+ arg = save_arg;
+ }
+ /* These strub arguments are adjusted later. */
+ if (apply_args)
+ vargs.quick_push (null_pointer_node);
+ if (is_stdarg)
+ vargs.quick_push (null_pointer_node);
+ vargs.quick_push (null_pointer_node);
+ call = gimple_build_call_vec (build_fold_addr_expr_loc (0, alias),
+ vargs);
+ onode->callees->call_stmt = call;
+ // gimple_call_set_from_thunk (call, true);
+ if (DECL_STATIC_CHAIN (alias))
+ {
+ tree p = DECL_STRUCT_FUNCTION (alias)->static_chain_decl;
+ tree type = TREE_TYPE (p);
+ tree decl = build_decl (DECL_SOURCE_LOCATION (thunk_fndecl),
+ PARM_DECL, create_tmp_var_name ("CHAIN"),
+ type);
+ DECL_ARTIFICIAL (decl) = 1;
+ DECL_IGNORED_P (decl) = 1;
+ TREE_USED (decl) = 1;
+ DECL_CONTEXT (decl) = thunk_fndecl;
+ DECL_ARG_TYPE (decl) = type;
+ TREE_READONLY (decl) = 1;
+
+ struct function *sf = DECL_STRUCT_FUNCTION (thunk_fndecl);
+ sf->static_chain_decl = decl;
+
+ gimple_call_set_chain (call, decl);
+ }
+
+ /* Return slot optimization is always possible and in fact required to
+ return values with DECL_BY_REFERENCE. */
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl))
+ && (!is_gimple_reg_type (TREE_TYPE (resdecl))
+ || DECL_BY_REFERENCE (resdecl)))
+ gimple_call_set_return_slot_opt (call, true);
+
+ if (restmp)
+ {
+ gimple_call_set_lhs (call, restmp);
+ gcc_assert (useless_type_conversion_p (TREE_TYPE (restmp),
+ TREE_TYPE (TREE_TYPE (alias))));
+ }
+ gsi_insert_after (&bsi, call, GSI_NEW_STMT);
+ if (!alias_is_noreturn)
+ {
+ /* Build return value. */
+ if (!DECL_BY_REFERENCE (resdecl))
+ ret = gimple_build_return (restmp);
+ else
+ ret = gimple_build_return (resdecl);
+
+ gsi_insert_after (&bsi, ret, GSI_NEW_STMT);
+ }
+ else
+ {
+ remove_edge (single_succ_edge (bb));
+ }
+
+ cfun->gimple_df->in_ssa_p = true;
+ update_max_bb_count ();
+ profile_status_for_fn (cfun)
+ = cfg_count.initialized_p () && cfg_count.ipa_p ()
+ ? PROFILE_READ : PROFILE_GUESSED;
+#if FOR_GCC_11P
+ /* FIXME: C++ FE should stop setting TREE_ASM_WRITTEN on thunks. */
+ // TREE_ASM_WRITTEN (thunk_fndecl) = false;
+#endif
+ delete_unreachable_blocks ();
+ update_ssa (TODO_update_ssa);
+ checking_verify_flow_info ();
+ free_dominance_info (CDI_DOMINATORS);
+
+ /* Since we want to emit the thunk, we explicitly mark its name as
+ referenced. */
+#if FOR_GCC_11P
+ onode->thunk = false;
+#else
+ onode->thunk.thunk_p = false;
+#endif
+ onode->lowered = true;
+ bitmap_obstack_release (NULL);
+ }
+ current_function_decl = NULL;
+ set_cfun (NULL);
+ }
+
+#if FOR_GCC_11P
+ thunk_info::remove (onode);
+#endif
+
+ // some more of create_wrapper at the end of the next block.
+ }
+ }
+
+ {
+ tree aaval = NULL_TREE;
+ tree vaptr = NULL_TREE;
+ tree wmptr = NULL_TREE;
+ for (tree arg = DECL_ARGUMENTS (nnode->decl); arg; arg = DECL_CHAIN (arg))
+ {
+ aaval = vaptr;
+ vaptr = wmptr;
+ wmptr = arg;
+ }
+
+ if (!apply_args)
+ aaval = NULL_TREE;
+ /* The trailing args are [apply_args], [va_list_ptr], and
+ watermark. If we don't have a va_list_ptr, the penultimate
+ argument is apply_args.
+ */
+ else if (!is_stdarg)
+ aaval = vaptr;
+
+ if (!is_stdarg)
+ vaptr = NULL_TREE;
+
+ DECL_NAME (wmptr) = get_watermark_ptr ();
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_IGNORED_P (wmptr) = 1;
+ TREE_USED (wmptr) = 1;
+
+ if (is_stdarg)
+ {
+ DECL_NAME (vaptr) = get_va_list_ptr ();
+ DECL_ARTIFICIAL (vaptr) = 1;
+ DECL_IGNORED_P (vaptr) = 1;
+ TREE_USED (vaptr) = 1;
+ }
+
+ if (apply_args)
+ {
+ DECL_NAME (aaval) = get_apply_args ();
+ DECL_ARTIFICIAL (aaval) = 1;
+ DECL_IGNORED_P (aaval) = 1;
+ TREE_USED (aaval) = 1;
+ }
+
+ push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ bool any_indirect = !indirect_nparms.is_empty ();
+
+ if (any_indirect)
+ {
+ 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);
+
+ 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
+ || is_stdarg || apply_args)
+ for (cgraph_edge *e = nnode->callees, *enext; e; e = enext)
+ {
+ gcall *call = e->call_stmt;
+ gimple_stmt_iterator gsi = gsi_for_stmt (call);
+ tree fndecl = e->callee->decl;
+
+ enext = e->next_callee;
+
+ if (gimple_alloca_call_p (call))
+ {
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ else if (fndecl && is_stdarg
+ && fndecl_built_in_p (fndecl, BUILT_IN_VA_START))
+ {
+ /* Using a non-default stdarg ABI makes the function ineligible
+ for internal strub. */
+ gcc_checking_assert (builtin_decl_explicit (BUILT_IN_VA_START)
+ == fndecl);
+ tree bvacopy = builtin_decl_explicit (BUILT_IN_VA_COPY);
+ gimple_call_set_fndecl (call, bvacopy);
+ tree arg = vaptr;
+ /* The va_copy source must be dereferenced, unless it's an array
+ type, that would have decayed to a pointer. */
+ if (TREE_CODE (TREE_TYPE (TREE_TYPE (vaptr))) != ARRAY_TYPE)
+ {
+ arg = gimple_fold_indirect_ref (vaptr);
+ if (!arg)
+ arg = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (vaptr)),
+ vaptr,
+ build_int_cst (TREE_TYPE (vaptr), 0));
+ }
+ gimple_call_set_arg (call, 1, arg);
+ update_stmt (call);
+ e->redirect_callee (cgraph_node::get_create (bvacopy));
+ }
+ else if (fndecl && apply_args
+ && fndecl_built_in_p (fndecl, BUILT_IN_APPLY_ARGS))
+ {
+ tree lhs = gimple_call_lhs (call);
+ gimple *assign = (lhs
+ ? gimple_build_assign (lhs, aaval)
+ : gimple_build_nop ());
+ gsi_replace (&gsi, assign, true);
+ cgraph_edge::remove (e);
+ }
+ }
+
+ { // a little more copied from create_wrapper
+
+ /* Inline summary set-up. */
+ nnode->analyze ();
+ // inline_analyze_function (nnode);
+ }
+
+ pop_cfun ();
+ }
+
+ {
+ 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));
+
+ 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));
+ gimple_set_location (stptr, gimple_location (wrcall));
+ gsi_insert_before (&gsi, stptr, GSI_SAME_STMT);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (enter),
+ stptr, gsi_bb (gsi)->count, false);
+#endif
+
+ int nargs = gimple_call_num_args (wrcall);
+
+ gimple_seq seq = NULL;
+
+ if (apply_args)
+ {
+ tree aalst = create_tmp_var (ptr_type_node, ".strub.apply_args");
+ tree bappargs = builtin_decl_explicit (BUILT_IN_APPLY_ARGS);
+ gcall *appargs = gimple_build_call (bappargs, 0);
+ gimple_call_set_lhs (appargs, aalst);
+ gimple_set_location (appargs, gimple_location (wrcall));
+ gsi_insert_before (&gsi, appargs, GSI_SAME_STMT);
+ gimple_call_set_arg (wrcall, nargs - 2 - is_stdarg, aalst);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bappargs),
+ appargs, gsi_bb (gsi)->count, false);
+#endif
+ }
+
+ if (is_stdarg)
+ {
+ tree valst = create_tmp_var (va_list_type_node, ".strub.va_list");
+ TREE_ADDRESSABLE (valst) = true;
+ tree vaptr = build1 (ADDR_EXPR,
+ build_pointer_type (va_list_type_node),
+ valst);
+ gimple_call_set_arg (wrcall, nargs - 2, unshare_expr (vaptr));
+
+ tree bvastart = builtin_decl_explicit (BUILT_IN_VA_START);
+ gcall *vastart = gimple_build_call (bvastart, 2,
+ unshare_expr (vaptr),
+ integer_zero_node);
+ gimple_set_location (vastart, gimple_location (wrcall));
+ gsi_insert_before (&gsi, vastart, GSI_SAME_STMT);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bvastart),
+ vastart, gsi_bb (gsi)->count, false);
+#endif
+
+ tree bvaend = builtin_decl_explicit (BUILT_IN_VA_END);
+ gcall *vaend = gimple_build_call (bvaend, 1, unshare_expr (vaptr));
+ gimple_set_location (vaend, gimple_location (wrcall));
+ gimple_seq_add_stmt (&seq, vaend);
+ }
+
+ gimple_call_set_arg (wrcall, nargs - 1, unshare_expr (swmp));
+ // gimple_call_set_tail (wrcall, false);
+ update_stmt (wrcall);
+
+ {
+#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);
+ gimple_set_location (force_store, gimple_location (wrcall));
+ 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);
+
+ /* For nnode, we don't rebuild edges because we wish to retain
+ any redirections copied to it from earlier passes, so we add
+ call graph edges explicitly there, but for onode, we create a
+ fresh function, so we may as well just issue the calls and
+ then rebuild all cgraph edges. */
+ // cgraph_edge::rebuild_edges ();
+ onode->analyze ();
+ // inline_analyze_function (onode);
+
+ 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;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub (gcc::context *ctxt)
+{
+ return new pass_ipa_strub (ctxt);
+}
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
new file mode 100644
index 00000000000..c335ab42097
--- /dev/null
+++ b/gcc/ipa-strub.h
@@ -0,0 +1,35 @@
+/* strub (stack scrubbing) infrastructure.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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 FALSE if NODE is a strub context, and TRUE otherwise. */
+extern bool strub_splittable_p (cgraph_node *node);
+
+/* Locate and return the watermark_ptr parameter for FNDECL. If FNDECL is not a
+ strub context, return NULL. */
+extern tree strub_watermark_parm (tree fndecl);
+
+/* Make a function type or declaration callable. */
+extern void strub_make_callable (tree fndecl);
diff --git a/gcc/passes.def b/gcc/passes.def
index d7a1f8c97a6..8e4638d20ed 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -53,6 +53,7 @@ along with GCC; see the file COPYING3. If not see
INSERT_PASSES_AFTER (all_small_ipa_passes)
NEXT_PASS (pass_ipa_free_lang_data);
NEXT_PASS (pass_ipa_function_and_variable_visibility);
+ NEXT_PASS (pass_ipa_strub_mode);
NEXT_PASS (pass_build_ssa_passes);
PUSH_INSERT_PASSES_WITHIN (pass_build_ssa_passes)
NEXT_PASS (pass_fixup_cfg);
@@ -111,6 +112,7 @@ along with GCC; see the file COPYING3. If not see
POP_INSERT_PASSES ()
NEXT_PASS (pass_ipa_remove_symbols);
+ NEXT_PASS (pass_ipa_strub);
NEXT_PASS (pass_ipa_oacc);
PUSH_INSERT_PASSES_WITHIN (pass_ipa_oacc)
NEXT_PASS (pass_ipa_pta);
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..7848c46d179
--- /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 "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
new file mode 100644
index 00000000000..85a8f76785e
--- /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" } */
+
+/* With -fno-inline, 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 "\[(\]call\[^\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..a2eedfd96b2
--- /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, 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 "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
new file mode 100644
index 00000000000..e5cb1f60541
--- /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 "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
new file mode 100644
index 00000000000..194aacc2c05
--- /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 "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
new file mode 100644
index 00000000000..46e84bf6560
--- /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 inline void
+__attribute__ ((__always_inline__))
+h() {
+}
+
+/* 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-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
new file mode 100644
index 00000000000..f180b17f30e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__ (3)))
+apply_function (void *args)
+{
+ __builtin_apply (0, args, 0);
+}
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+ void *args = __builtin_apply_args ();
+ apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
new file mode 100644
index 00000000000..379a54b73b7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+extern void __attribute__ ((__strub__))
+apply_function (void *args);
+
+void __attribute__ ((__strub__))
+apply_args (int i, int j, double d) /* { dg-error "selected" } */
+{
+ void *args = __builtin_apply_args (); /* { dg-message "does not support" } */
+ apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
new file mode 100644
index 00000000000..9b4786be698
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__))
+apply_function (void *args)
+{
+ __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
new file mode 100644
index 00000000000..409f747743e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+
+/* Check that implicit enabling of strub mode selects internal strub when the
+ function uses __builtin_apply_args, that prevents the optimization to
+ at-calls mode. */
+
+int __attribute__ ((__strub__)) var;
+
+static inline void
+apply_args (int i, int j, double d)
+{
+ var++;
+ __builtin_apply_args ();
+}
+
+void f() {
+ apply_args (1, 2, 3);
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
new file mode 100644
index 00000000000..d964b07ae5d
--- /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 inline void
+__attribute__ ((__always_inline__))
+h() {
+}
+
+/* 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..79d00cedb9a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-default1.c
@@ -0,0 +1,42 @@
+/* { 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-default2.c b/gcc/testsuite/c-c++-common/strub-default2.c
new file mode 100644
index 00000000000..487253e9227
--- /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-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
new file mode 100644
index 00000000000..7b04eea35d9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O1" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+ the strubbing to its caller at -O1. */
+
+#include "strub-defer-O2.c"
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
new file mode 100644
index 00000000000..67d96419a5e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -0,0 +1,8 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O2" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+ the strubbing to its caller at -O2. */
+
+#define EXPECT_DEFERRAL !
+#include "strub-defer-O3.c"
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
new file mode 100644
index 00000000000..34828d2711e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -0,0 +1,93 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O3" } */
+
+/* Check that a strub function called by another strub function defers the
+ strubbing to its caller at -O3. */
+
+#ifndef EXPECT_DEFERRAL
+# define EXPECT_DEFERRAL
+#endif
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+ /* We use this variable to avoid any stack red zone. Stack scrubbing covers
+ it, but __builtin_stack_address, that we take as a reference, doesn't, so
+ if e.g. callable() were to store the string in the red zone, we wouldn't
+ find it because it would be outside the range we searched. */
+ typedef void __attribute__ ((__strub__ (3))) callable_t (char *);
+ callable_t *f = 0;
+
+ char s[sizeof (test_string)];
+ __builtin_strcpy (s, test_string);
+ asm ("" : "+m" (s), "+r" (f));
+
+ if (__builtin_expect (!f, 1))
+ return (char*)__builtin_stack_address ();
+
+ f (s);
+ return 0;
+}
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+int
+look_for_string (char *e)
+{
+ char *p = (char*)__builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+char *
+at_calls ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+deferred_at_calls ()
+{
+ char *ret = at_calls ();
+ if (EXPECT_DEFERRAL !look_for_string (ret))
+ __builtin_abort ();
+ return ret;
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+deferred_internal ()
+{
+ char *ret = at_calls ();
+ if (EXPECT_DEFERRAL !look_for_string (ret))
+ __builtin_abort ();
+ return ret;
+}
+
+int main ()
+{
+ if (look_for_string (deferred_at_calls ()))
+ __builtin_abort ();
+ if (look_for_string (deferred_internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
new file mode 100644
index 00000000000..b273660aea1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -Os" } */
+
+/* Check that a strub function called by another strub function defers the
+ strubbing to its caller at -Os. */
+
+#include "strub-defer-O3.c"
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
new file mode 100644
index 00000000000..a74658c9ac9
--- /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 inline void
+__attribute__ ((__always_inline__))
+h() {
+}
+
+/* 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/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
new file mode 100644
index 00000000000..7e22a266ad9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (2)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+ int x[128];
+};
+
+void __attribute__ ((__strub__ (2)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\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)))
+std_arg (int i, ...)
+{
+ va_list vl;
+ va_start (vl, i);
+ va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+ __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
new file mode 100644
index 00000000000..2d8036c0fbc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (1)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*small_args\[^ \]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+
+struct large_arg {
+ int x[128];
+};
+
+void __attribute__ ((__strub__ (1)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ (1)))
+std_arg (int i, ...)
+{
+ va_list vl;
+ va_start (vl, i);
+ va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*std_arg\[^ \]* \[(\]int i, void \\* &\[^&,\]*.strub.watermark_ptr\[, .]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-not "va_copy \\(" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
new file mode 100644
index 00000000000..f64361f1235
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -0,0 +1,58 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that uses of a strub variable implicitly enables internal strub for
+ publicly-visible functions, and causes the same transformations to their
+ signatures as those in strub-parms1.c. */
+
+#include <stdarg.h>
+
+int __attribute__ ((__strub__)) var;
+
+void
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+ var++;
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+ int x[128];
+};
+
+void
+large_byref_arg (struct large_arg la)
+{
+ var++;
+}
+
+/* { dg-final { scan-ipa-dump "\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
+std_arg (int i, ...)
+{
+ va_list vl;
+ va_start (vl, i);
+ var++;
+ va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void
+apply_args (int i, int j, double d)
+{
+ var++;
+ __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
new file mode 100644
index 00000000000..a4226ce0119
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=default -fexceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 89 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
new file mode 100644
index 00000000000..3bab553478b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
new file mode 100644
index 00000000000..c89cc7c2c47
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
new file mode 100644
index 00000000000..b869fafb691
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
new file mode 100644
index 00000000000..1d3dd2f2c2c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=default -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
+ watermark. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 15 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 15 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
new file mode 100644
index 00000000000..4dd4281b03b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-Os -fstrub=default -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
+ watermark. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 15 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 15 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
new file mode 100644
index 00000000000..0840dddd136
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+#include "strub-tail-O2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
new file mode 100644
index 00000000000..9330d6ff4c1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued.
+ Tail calls are short-circuited at -O2+. */
+
+int __attribute__ ((__strub__))
+g (int i, int j) {
+ return g (i, j);
+}
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 0 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 0 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
new file mode 100644
index 00000000000..5b33ff1f530
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -0,0 +1,9 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that strub and non-strub functions can be called from non-strub
+ contexts, and that strub and callable functions can be called from strub
+ contexts. */
+
+#define OMIT_IMPERMISSIBLE_CALLS 1
+#include "strub-callable2.c"
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
new file mode 100644
index 00000000000..38935e3270b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -0,0 +1,264 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that impermissible (cross-strub-context) calls are reported. */
+
+extern int __attribute__ ((__strub__ (3))) xcallable (void);
+extern int __attribute__ ((__strub__ (2))) xinternal (void);
+extern int __attribute__ ((__strub__ (1))) xat_calls (void);
+extern int __attribute__ ((__strub__ (0))) xdisabled (void);
+
+int __attribute__ ((__strub__ (3))) callable (void);
+int __attribute__ ((__strub__ (2))) internal (void);
+int __attribute__ ((__strub__ (1))) at_calls (void);
+int __attribute__ ((__strub__ (0))) disabled (void);
+
+int __attribute__ ((__strub__)) var;
+int var_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+icallable (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+iinternal (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+iat_calls (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+idisabled (void);
+static inline int __attribute__ ((__always_inline__))
+ivar_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+i_callable (void) { return 0; }
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+i_internal (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+i_at_calls (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+i_disabled (void) { return 0; }
+static inline int __attribute__ ((__always_inline__))
+i_var_user (void) { return var; }
+
+#define CALLS_GOOD_FOR_STRUB_CONTEXT(ISEP) \
+ do { \
+ ret += i ## ISEP ## at_calls (); \
+ ret += i ## ISEP ## internal (); \
+ ret += i ## ISEP ## var_user (); \
+ } while (0)
+
+#define CALLS_GOOD_FOR_NONSTRUB_CONTEXT(ISEP) \
+ do { \
+ ret += internal (); \
+ ret += disabled (); \
+ ret += var_user (); \
+ \
+ ret += i ## ISEP ## disabled (); \
+ \
+ ret += xinternal (); \
+ ret += xdisabled (); \
+ } while (0)
+
+#define CALLS_GOOD_FOR_EITHER_CONTEXT(ISEP) \
+ do { \
+ ret += i ## ISEP ## callable (); \
+ \
+ ret += callable (); \
+ ret += at_calls (); \
+ \
+ ret += xat_calls (); \
+ ret += xcallable (); \
+ } while (0)
+
+/* Not a strub context, so it can call anything.
+ Explicitly declared as callable even from within strub contexts. */
+int __attribute__ ((__strub__ (3)))
+callable (void) {
+ int ret = 0;
+
+ /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+ ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+ ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+ return ret;
+}
+
+/* Internal strubbing means the body is a strub context, so it can only call
+ strub functions, and it's not itself callable from strub functions. */
+int __attribute__ ((__strub__ (2)))
+internal (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT();
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int __attribute__ ((__strub__ (1)))
+at_calls (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT();
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int __attribute__ ((__strub__ (0)))
+disabled () {
+ int ret = 0;
+
+ /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+ ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+ ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+ return ret;
+}
+
+int
+var_user (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT();
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int
+icallable (void)
+{
+ int ret = 0;
+
+ /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+ ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+ ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+ return ret;
+}
+
+int
+iinternal (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int
+iat_calls (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int
+idisabled () {
+ int ret = 0;
+
+ /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+ ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+ ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+ return ret;
+}
+
+int
+ivar_user (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
new file mode 100644
index 00000000000..100fb0c59a9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const function call, we issue an asm statement
+ to make sure the watermark passed to it is held in memory before the call,
+ and another to make sure it is not assumed to be unchanged. */
+
+int __attribute__ ((__strub__, __const__))
+f() {
+ return 0;
+}
+
+int
+g() {
+ return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
new file mode 100644
index 00000000000..9e818ac9748
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const function call, we issue an
+ asm statement to make sure the watermark passed to it is held in memory
+ before the call, and another to make sure it is not assumed to be
+ unchanged. */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+ return 0;
+}
+
+int
+g() {
+ return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
new file mode 100644
index 00000000000..d40e8aa45cb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const wrapping call, we issue an asm statement
+ to make sure the watermark passed to it is held in memory before the call,
+ and another to make sure it is not assumed to be unchanged. */
+
+int __attribute__ ((__strub__ (2), __const__))
+f() {
+ return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
new file mode 100644
index 00000000000..d4cbdaf10f3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const wrapping call, we issue an
+ asm statement to make sure the watermark passed to it is held in memory
+ before the call, and another to make sure it is not assumed to be
+ unchanged. */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+ return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-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..07e25af9c53
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -Werror" } */
+
+typedef int __attribute__ ((__strub__)) strub_int;
+strub_int *ptr;
+
+int *f () {
+ return ptr; /* { dg-warning "incompatible" } */
+}
+
+strub_int *g () {
+ return f (); /* { dg-warning "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/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
new file mode 100644
index 00000000000..cb223da6efc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure function call, we issue an asm statement
+ to make sure the watermark passed to it is not assumed to be unchanged. */
+
+int __attribute__ ((__strub__, __pure__))
+f() {
+ static int i; /* Stop it from being detected as const. */
+ return i;
+}
+
+int
+g() {
+ return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
new file mode 100644
index 00000000000..67d1434b1f8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure function call, we issue an asm
+ statement to make sure the watermark passed to it is not assumed to be
+ unchanged. */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run. */
+__attribute__ ((__pure__))
+#endif
+f() {
+ static int i; /* Stop it from being detected as const. */
+ return i;
+}
+
+int
+g() {
+ return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
new file mode 100644
index 00000000000..59f02ea901f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure wrapping call, we issue an asm statement
+ to make sure the watermark passed to it is not assumed to be unchanged. */
+
+int __attribute__ ((__strub__ (2), __pure__))
+f() {
+ static int i; /* Stop it from being detected as const. */
+ return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
new file mode 100644
index 00000000000..973e909217d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure wrapping call, we issue an asm
+ statement to make sure the watermark passed to it is not assumed to be
+ unchanged. */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run. */
+__attribute__ ((__pure__))
+#endif
+f() {
+ static int i; /* Stop it from being detected as const. */
+ return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
new file mode 100644
index 00000000000..a4077c35a60
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -0,0 +1,85 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+ equivalent strub functions don't. */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+ /* We use this variable to avoid any stack red zone. Stack scrubbing covers
+ it, but __builtin_stack_address, that we take as a reference, doesn't, so
+ if e.g. callable() were to store the string in the red zone, we wouldn't
+ find it because it would be outside the range we searched. */
+ typedef void __attribute__ ((__strub__ (3))) callable_t (char *);
+ callable_t *f = 0;
+
+ char s[sizeof (test_string)];
+ __builtin_strcpy (s, test_string);
+ asm ("" : "+m" (s), "+r" (f));
+
+ if (__builtin_expect (!f, 1))
+ return (char *) __builtin_stack_address ();
+
+ f (s);
+ return 0;
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+ char *p = (char *) __builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+ return leak_string ();
+}
+
+int main ()
+{
+ if (!look_for_string (callable ()))
+ __builtin_abort ();
+ if (look_for_string (at_calls ()))
+ __builtin_abort ();
+ if (look_for_string (internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
new file mode 100644
index 00000000000..94e4156ea73
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+ equivalent strub functions don't. */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+ int len = sizeof (test_string);
+ asm ("" : "+rm" (len));
+ char s[len];
+ __builtin_strcpy (s, test_string);
+ asm ("" : "+m" (s));
+ return (char *) __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+ char *p = (char *) __builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+ return leak_string ();
+}
+
+int main ()
+{
+ if (!look_for_string (callable ()))
+ __builtin_abort ();
+ if (look_for_string (at_calls ()))
+ __builtin_abort ();
+ if (look_for_string (internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
new file mode 100644
index 00000000000..0ca74beb59d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+/* { dg-require-effective-target alloca } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+ equivalent strub functions don't. */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+ int len = sizeof (test_string);
+ char *s = (char *) __builtin_alloca (len);
+ __builtin_strcpy (s, test_string);
+ asm ("" : "+m" (s));
+ return (char *) __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+ char *p = (char *) __builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+ return leak_string ();
+}
+
+int main ()
+{
+ if (!look_for_string (callable ()))
+ __builtin_abort ();
+ if (look_for_string (at_calls ()))
+ __builtin_abort ();
+ if (look_for_string (internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
new file mode 100644
index 00000000000..4ab11c0682e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -0,0 +1,101 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=all" } */
+/* { dg-require-effective-target alloca } */
+
+/* Check that multi-level, multi-inlined functions still get cleaned up as
+ expected, without overwriting temporary stack allocations while they should
+ still be available. */
+
+#ifndef ATTR_STRUB_AT_CALLS
+# define ATTR_STRUB_AT_CALLS /* Defined in strub-run4d.c. */
+#endif
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__))
+char *
+leak_string (void)
+{
+ int __attribute__ ((__strub__)) len = 512;
+ asm ("" : "+r" (len));
+ char s[len];
+ __builtin_strcpy (s, test_string);
+ __builtin_strcpy (s + len - sizeof (test_string), test_string);
+ asm ("" : "+m" (s));
+ return (char *) __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+ char *p = (char *) __builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static inline ATTR_STRUB_AT_CALLS
+char *
+innermost ()
+{
+ int __attribute__ ((__strub__)) len = 512;
+ asm ("" : "+r" (len));
+ char s[len];
+ __builtin_strcpy (s, test_string);
+ __builtin_strcpy (s + len - sizeof (test_string), test_string);
+ asm ("" : "+m" (s));
+ char *ret = leak_string ();
+ if (__builtin_strcmp (s, test_string) != 0)
+ __builtin_abort ();
+ if (__builtin_strcmp (s + len - sizeof (test_string), test_string) != 0)
+ __builtin_abort ();
+ return ret;
+}
+
+static inline ATTR_STRUB_AT_CALLS
+char *
+intermediate ()
+{
+ int __attribute__ ((__strub__)) len = 512;
+ asm ("" : "+r" (len));
+ char s[len];
+ __builtin_strcpy (s, test_string);
+ __builtin_strcpy (s + len - sizeof (test_string), test_string);
+ asm ("" : "+m" (s));
+ char *ret = innermost ();
+ if (__builtin_strcmp (s, test_string) != 0)
+ __builtin_abort ();
+ if (__builtin_strcmp (s + len - sizeof (test_string), test_string) != 0)
+ __builtin_abort ();
+ return ret;
+}
+
+static inline __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+ return intermediate ();
+}
+
+int __attribute__ ((strub (0)))
+main ()
+{
+ if (look_for_string (internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4c.c b/gcc/testsuite/c-c++-common/torture/strub-run4c.c
new file mode 100644
index 00000000000..57f9baf758d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4c.c
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=at-calls" } */
+/* { dg-require-effective-target alloca } */
+
+#include "strub-run4.c"
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4d.c b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
new file mode 100644
index 00000000000..e4f7445607c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+/* { dg-require-effective-target alloca } */
+
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ (1)))
+
+#include "strub-run4.c"
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4i.c b/gcc/testsuite/c-c++-common/torture/strub-run4i.c
new file mode 100644
index 00000000000..459f6886c54
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4i.c
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=internal" } */
+/* { dg-require-effective-target alloca } */
+
+#include "strub-run4.c"
diff --git a/gcc/testsuite/g++.dg/strub-run1.C b/gcc/testsuite/g++.dg/strub-run1.C
new file mode 100644
index 00000000000..0d367fb83d0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/strub-run1.C
@@ -0,0 +1,19 @@
+// { dg-do run }
+// { dg-options "-fstrub=internal" }
+
+// Check that we don't get extra copies.
+
+struct T {
+ T &self;
+ void check () const { if (&self != this) __builtin_abort (); }
+ T() : self (*this) { check (); }
+ T(const T& ck) : self (*this) { ck.check (); check (); }
+ ~T() { check (); }
+};
+
+T foo (T q) { q.check (); return T(); }
+T bar (T p) { p.check (); return foo (p); }
+
+int main () {
+ bar (T()).check ();
+}
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" } } */
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
new file mode 100644
index 00000000000..697ac9de764
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -0,0 +1,21 @@
+-- { dg-do compile }
+-- { dg-options "-fstrub=default -fdump-ipa-strubm" }
+
+package body Strub_Attr 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 (X);
+end Strub_Attr;
+
+-- { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]2\[)\]\[)\]" 2 "strubm" } }
+-- { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
diff --git a/gcc/testsuite/gnat.dg/strub_attr.ads b/gcc/testsuite/gnat.dg/strub_attr.ads
new file mode 100644
index 00000000000..a94c23bf418
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_attr.ads
@@ -0,0 +1,12 @@
+package Strub_Attr 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;
+end Strub_Attr;
diff --git a/gcc/tree-cfg.c b/gcc/tree-cfg.c
index 61b53913e0b..9b01f2bb01b 100644
--- a/gcc/tree-cfg.c
+++ b/gcc/tree-cfg.c
@@ -5620,6 +5620,7 @@ gimple_verify_flow_info (void)
{
gimple *stmt = gsi_stmt (gsi);
+ /* Do NOT disregard debug stmts after found_ctrl_stmt. */
if (found_ctrl_stmt)
{
error ("control flow in the middle of basic block %d",
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index 83941bc0cee..df4abdf1449 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -497,8 +497,9 @@ extern gimple_opt_pass *make_pass_adjust_alignment (gcc::context *ctxt);
/* IPA Passes */
extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt);
-extern simple_ipa_opt_pass
- *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub_mode (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_tree_profile (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_auto_profile (gcc::context *ctxt);
diff --git a/gcc/tree-ssa-ccp.c b/gcc/tree-ssa-ccp.c
index 1a63ae5f104..2f2fffac4df 100644
--- a/gcc/tree-ssa-ccp.c
+++ b/gcc/tree-ssa-ccp.c
@@ -2827,7 +2827,9 @@ optimize_stack_restore (gimple_stmt_iterator i)
if (!callee
|| !fndecl_built_in_p (callee, BUILT_IN_NORMAL)
/* All regular builtins are ok, just obviously not alloca. */
- || ALLOCA_FUNCTION_CODE_P (DECL_FUNCTION_CODE (callee)))
+ || ALLOCA_FUNCTION_CODE_P (DECL_FUNCTION_CODE (callee))
+ /* Do not remove stack updates before strub leave. */
+ || fndecl_built_in_p (callee, BUILT_IN___STRUB_LEAVE))
return NULL_TREE;
if (fndecl_built_in_p (callee, BUILT_IN_STACK_RESTORE))
diff --git a/libgcc/Makefile.in b/libgcc/Makefile.in
index 7ec97584554..c4ee6701cb3 100644
--- a/libgcc/Makefile.in
+++ b/libgcc/Makefile.in
@@ -428,7 +428,7 @@ ifneq ($(enable_shared),yes)
iterator = $(patsubst %,$(srcdir)/static-object.mk,$(iter-items))
endif
-LIB2ADD += enable-execute-stack.c
+LIB2ADD += enable-execute-stack.c $(srcdir)/strub.c
# While emutls.c has nothing to do with EH, it is in LIB2ADDEH*
# instead of LIB2ADD because that's the way to be sure on some targets
diff --git a/libgcc/libgcc2.h b/libgcc/libgcc2.h
index 1819ff3ac3d..857091e65c8 100644
--- a/libgcc/libgcc2.h
+++ b/libgcc/libgcc2.h
@@ -532,6 +532,10 @@ extern int __parityDI2 (UDWtype);
extern void __enable_execute_stack (void *);
+extern void __strub_enter (void **);
+extern void __strub_update (void**);
+extern void __strub_leave (void **);
+
#ifndef HIDE_EXPORTS
#pragma GCC visibility pop
#endif
diff --git a/libgcc/strub.c b/libgcc/strub.c
new file mode 100644
index 00000000000..fd6e27556e4
--- /dev/null
+++ b/libgcc/strub.c
@@ -0,0 +1,112 @@
+/* Stack scrubbing infrastructure
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+Under Section 7 of GPL version 3, you are granted additional
+permissions described in the GCC Runtime Library Exception, version
+3.1, as published by the Free Software Foundation.
+
+You should have received a copy of the GNU General Public License and
+a copy of the GCC Runtime Library Exception along with this program;
+see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
+<http://www.gnu.org/licenses/>. */
+
+#include "tconfig.h"
+#include "tsystem.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "libgcc_tm.h"
+#include "libgcc2.h"
+
+#ifndef STACK_GROWS_DOWNWARD
+# define TOPS >
+#else
+# define TOPS <
+#endif
+
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+
+/* Enter a stack scrubbing context, initializing the watermark to the caller's
+ stack address. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_enter (void **watermark)
+{
+ *watermark = __builtin_frame_address (0);
+}
+
+/* Update the watermark within a stack scrubbing context with the current stack
+ pointer. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_update (void **watermark)
+{
+ void *sp = __builtin_frame_address (0);
+
+ if (sp TOPS *watermark)
+ *watermark = sp;
+}
+
+#ifndef TARGET_STRUB_USE_DYNAMIC_ARRAY
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+#endif
+
+#ifndef TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+# ifdef TARGET_STRUB_MAY_USE_MEMSET
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+# else
+# define TARGET_STRUB_MAY_USE_MEMSET 1
+# endif
+#endif
+
+/* Leave a stack scrubbing context, restoring and updating SAVED, and
+ clearing the stack between top and watermark. */
+void ATTRIBUTE_STRUB_CALLABLE
+#if ! TARGET_STRUB_MAY_USE_MEMSET
+__attribute__ ((__optimize__ ("-fno-tree-loop-distribute-patterns")))
+#endif
+__strub_leave (void **mark)
+{
+ void *sp = __builtin_stack_address ();
+
+ void **base, **end;
+#ifndef STACK_GROWS_DOWNWARD
+ base = sp;
+ end = *mark;
+#else
+ base = *mark;
+ end = sp;
+#endif
+
+ ptrdiff_t len = end - base;
+ if (len <= 0)
+ return;
+
+#if ! TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+ /* Allocate a dynamically-sized array covering the desired range, so that we
+ can safely call memset on it. */
+ void *ptr[len];
+ base = &ptr[0];
+ end = &ptr[len];
+#else
+ void **ptr = end;
+#endif /* TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY */
+
+ /* ldist turns this into a memset. Without the dynamic array above, that call
+ is likely unsafe: possibly tail-called, and likely scribbling over its own
+ stack frame. */
+ while (base < end)
+ *base++ = 0;
+
+ asm ("" : : "m" (ptr));
+}
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/strub)] strub with function and variable attributes
@ 2021-08-20 4:20 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2021-08-20 4:20 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:cdd5c89e694518955b4b4e08342009256a77ab41
commit cdd5c89e694518955b4b4e08342009256a77ab41
Author: Alexandre Oliva <oliva@adacore.com>
Date: Wed Aug 18 23:57:13 2021 -0300
strub with function and variable attributes
still missing strub-aware conversion checking,
tests exercising dispatch calls
Diff:
---
gcc/Makefile.in | 1 +
gcc/ada/gcc-interface/trans.c | 5 +
gcc/ada/gcc-interface/utils.c | 97 +
gcc/ada/libgnat/a-except.adb | 90 +
gcc/ada/libgnat/a-except.ads | 8 +
gcc/ada/libgnat/s-arit128.ads | 6 +
gcc/ada/libgnat/s-arit64.ads | 6 +
gcc/builtins.c | 273 ++
gcc/builtins.def | 4 +
gcc/c-family/c-attribs.c | 87 +
gcc/common.opt | 24 +
gcc/doc/extend.texi | 85 +
gcc/doc/invoke.texi | 42 +
gcc/ipa-inline.c | 6 +
gcc/ipa-split.c | 7 +
gcc/ipa-strub.c | 2999 ++++++++++++++++++++
gcc/ipa-strub.h | 35 +
gcc/passes.def | 2 +
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-apply1.c | 15 +
gcc/testsuite/c-c++-common/strub-apply2.c | 12 +
gcc/testsuite/c-c++-common/strub-apply3.c | 8 +
gcc/testsuite/c-c++-common/strub-apply4.c | 21 +
gcc/testsuite/c-c++-common/strub-at-calls1.c | 30 +
gcc/testsuite/c-c++-common/strub-at-calls2.c | 23 +
gcc/testsuite/c-c++-common/strub-default1.c | 42 +
gcc/testsuite/c-c++-common/strub-default2.c | 29 +
gcc/testsuite/c-c++-common/strub-defer-O1.c | 7 +
gcc/testsuite/c-c++-common/strub-defer-O2.c | 8 +
gcc/testsuite/c-c++-common/strub-defer-O3.c | 93 +
gcc/testsuite/c-c++-common/strub-defer-Os.c | 7 +
gcc/testsuite/c-c++-common/strub-internal1.c | 31 +
gcc/testsuite/c-c++-common/strub-internal2.c | 21 +
gcc/testsuite/c-c++-common/strub-parms1.c | 48 +
gcc/testsuite/c-c++-common/strub-parms2.c | 36 +
gcc/testsuite/c-c++-common/strub-parms3.c | 58 +
gcc/testsuite/c-c++-common/strub-short-O0-exc.c | 10 +
gcc/testsuite/c-c++-common/strub-short-O0.c | 10 +
gcc/testsuite/c-c++-common/strub-short-O1.c | 10 +
gcc/testsuite/c-c++-common/strub-short-O2.c | 10 +
gcc/testsuite/c-c++-common/strub-short-O3.c | 12 +
gcc/testsuite/c-c++-common/strub-short-Os.c | 12 +
gcc/testsuite/c-c++-common/strub-tail-O1.c | 8 +
gcc/testsuite/c-c++-common/strub-tail-O2.c | 14 +
.../c-c++-common/torture/strub-callable1.c | 9 +
.../c-c++-common/torture/strub-callable2.c | 264 ++
gcc/testsuite/c-c++-common/torture/strub-const1.c | 18 +
gcc/testsuite/c-c++-common/torture/strub-const2.c | 22 +
gcc/testsuite/c-c++-common/torture/strub-const3.c | 13 +
gcc/testsuite/c-c++-common/torture/strub-const4.c | 17 +
gcc/testsuite/c-c++-common/torture/strub-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 | 13 +
.../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/c-c++-common/torture/strub-pure1.c | 18 +
gcc/testsuite/c-c++-common/torture/strub-pure2.c | 22 +
gcc/testsuite/c-c++-common/torture/strub-pure3.c | 13 +
gcc/testsuite/c-c++-common/torture/strub-pure4.c | 17 +
gcc/testsuite/c-c++-common/torture/strub-run1.c | 85 +
gcc/testsuite/c-c++-common/torture/strub-run2.c | 75 +
gcc/testsuite/c-c++-common/torture/strub-run3.c | 75 +
gcc/testsuite/c-c++-common/torture/strub-run4.c | 101 +
gcc/testsuite/c-c++-common/torture/strub-run4c.c | 5 +
gcc/testsuite/c-c++-common/torture/strub-run4d.c | 7 +
gcc/testsuite/c-c++-common/torture/strub-run4i.c | 5 +
gcc/testsuite/g++.dg/strub-run1.C | 19 +
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 +
gcc/testsuite/gnat.dg/strub_attr.adb | 21 +
gcc/testsuite/gnat.dg/strub_attr.ads | 12 +
gcc/tree-cfg.c | 1 +
gcc/tree-pass.h | 5 +-
gcc/tree-ssa-ccp.c | 4 +-
libgcc/Makefile.in | 2 +-
libgcc/libgcc2.h | 4 +
libgcc/strub.c | 112 +
90 files changed, 5621 insertions(+), 4 deletions(-)
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 9714fcaac37..94097ec78d7 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1471,6 +1471,7 @@ OBJS = \
ipa-reference.o \
ipa-ref.o \
ipa-utils.o \
+ ipa-strub.o \
ipa.o \
ira.o \
ira-build.o \
diff --git a/gcc/ada/gcc-interface/trans.c b/gcc/ada/gcc-interface/trans.c
index 3df56aa0560..f1524a3fb90 100644
--- a/gcc/ada/gcc-interface/trans.c
+++ b/gcc/ada/gcc-interface/trans.c
@@ -43,6 +43,7 @@
#include "output.h"
#include "debug.h"
#include "libfuncs.h" /* For set_stack_check_libfunc. */
+#include "ipa-strub.h" /* For strub_make_callable. */
#include "tree-iterator.h"
#include "gimplify.h"
#include "opts.h"
@@ -444,6 +445,7 @@ gigi (Node_Id gnat_root,
int64_type, NULL_TREE),
NULL_TREE, is_default, true, true, true, false,
false, NULL, Empty);
+ strub_make_callable (mulv64_decl);
if (Enable_128bit_Types)
{
@@ -456,6 +458,7 @@ gigi (Node_Id gnat_root,
NULL_TREE),
NULL_TREE, is_default, true, true, true, false,
false, NULL, Empty);
+ strub_make_callable (mulv128_decl);
}
/* Name of the _Parent field in tagged record types. */
@@ -531,6 +534,7 @@ gigi (Node_Id gnat_root,
= create_subprog_decl
(get_identifier ("__gnat_raise_nodefer_with_msg"), NULL_TREE, ftype,
NULL_TREE, is_default, true, true, true, false, false, NULL, Empty);
+ strub_make_callable (raise_nodefer_decl);
set_exception_parameter_decl
= create_subprog_decl
@@ -779,6 +783,7 @@ build_raise_check (int check, enum exception_info_kind kind)
= create_subprog_decl (get_identifier (Name_Buffer), NULL_TREE, ftype,
NULL_TREE, is_default, true, true, true, false,
false, NULL, Empty);
+ strub_make_callable (result);
return result;
}
diff --git a/gcc/ada/gcc-interface/utils.c b/gcc/ada/gcc-interface/utils.c
index f85373e2f76..d9fb78416ea 100644
--- a/gcc/ada/gcc-interface/utils.c
+++ b/gcc/ada/gcc-interface/utils.c
@@ -94,6 +94,7 @@ static tree handle_sentinel_attribute (tree *, tree, tree, int, bool *);
static tree handle_noreturn_attribute (tree *, tree, tree, int, bool *);
static tree handle_stack_protect_attribute (tree *, tree, tree, int, bool *);
static tree handle_no_stack_protector_attribute (tree *, tree, tree, int, bool *);
+static tree handle_strub_attribute (tree *, tree, tree, int, bool *);
static tree handle_noinline_attribute (tree *, tree, tree, int, bool *);
static tree handle_noclone_attribute (tree *, tree, tree, int, bool *);
static tree handle_noicf_attribute (tree *, tree, tree, int, bool *);
@@ -157,6 +158,8 @@ const struct attribute_spec gnat_internal_attribute_table[] =
{ "no_stack_protector",0, 0, true, false, false, false,
handle_no_stack_protector_attribute,
attr_stack_protect_exclusions },
+ { "strub", 0, 1, false, true, false, true,
+ handle_strub_attribute, NULL },
{ "noinline", 0, 0, true, false, false, false,
handle_noinline_attribute, NULL },
{ "noclone", 0, 0, true, false, false, false,
@@ -6599,6 +6602,100 @@ handle_no_stack_protector_attribute (tree *node, tree name, tree, int,
return NULL_TREE;
}
+/* Handle a "strub" attribute; arguments as in
+ struct attribute_spec.handler. */
+
+static tree
+handle_strub_attribute (tree *node, tree name,
+ tree args,
+ int ARG_UNUSED (flags), bool *no_add_attrs)
+{
+ bool enable = true;
+
+ if (args
+ && (TREE_CODE (*node) == FUNCTION_TYPE
+ || TREE_CODE (*node) == METHOD_TYPE))
+ {
+ /* 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)
+ {
+ 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);
+ }
+
+ /* 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)
+ {
+ warning (OPT_Wattributes,
+ "%qE attribute ignored because of argument %qE",
+ name, TREE_VALUE (args));
+ *no_add_attrs = true;
+ enable = false;
+ }
+ /* 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));
+
+ /* 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;
+
+ return NULL_TREE;
+}
/* Handle a "noinline" attribute; arguments as in
struct attribute_spec.handler. */
diff --git a/gcc/ada/libgnat/a-except.adb b/gcc/ada/libgnat/a-except.adb
index c332afad0f8..593392809d9 100644
--- a/gcc/ada/libgnat/a-except.adb
+++ b/gcc/ada/libgnat/a-except.adb
@@ -629,6 +629,96 @@ package body Ada.Exceptions is
pragma No_Return (Rcheck_CE_Invalid_Data_Ext);
pragma No_Return (Rcheck_CE_Range_Check_Ext);
+ -- Make all of these procedures callable from strub contexts.
+ -- These attributes are not visible to callers; they are made
+ -- visible in trans.c:build_raise_check.
+
+ pragma Machine_Attribute (Rcheck_CE_Access_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Null_Access_Parameter,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Discriminant_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Divide_By_Zero,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Explicit_Raise,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Index_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Invalid_Data,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Length_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Null_Exception_Id,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Null_Not_Allowed,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Overflow_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Partition_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Range_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Tag_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Access_Before_Elaboration,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Accessibility_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Address_Of_Intrinsic,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Aliased_Parameters,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_All_Guards_Closed,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Bad_Predicated_Generic_Type,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Build_In_Place_Mismatch,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Current_Task_In_Entry_Body,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Duplicated_Entry_Address,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Explicit_Raise,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Implicit_Return,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Misaligned_Address_Value,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Missing_Return,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Non_Transportable_Actual,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Overlaid_Controlled_Object,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Potentially_Blocking_Operation,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Stream_Operation_Not_Allowed,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Stubbed_Subprogram_Called,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Unchecked_Union_Restriction,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Finalize_Raised_Exception,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_SE_Empty_Storage_Pool,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_SE_Explicit_Raise,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_SE_Infinite_Recursion,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_SE_Object_Too_Large,
+ "strub", "callable");
+
+ pragma Machine_Attribute (Rcheck_CE_Access_Check_Ext,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Index_Check_Ext,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Invalid_Data_Ext,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Range_Check_Ext,
+ "strub", "callable");
+
---------------------------------------------
-- Reason Strings for Run-Time Check Calls --
---------------------------------------------
diff --git a/gcc/ada/libgnat/a-except.ads b/gcc/ada/libgnat/a-except.ads
index 2b27adb6ca1..b5a2e48f96a 100644
--- a/gcc/ada/libgnat/a-except.ads
+++ b/gcc/ada/libgnat/a-except.ads
@@ -81,6 +81,10 @@ package Ada.Exceptions is
pragma No_Return (Raise_Exception);
-- Note: In accordance with AI-466, CE is raised if E = Null_Id
+ -- Make it callable from strub contexts.
+ pragma Machine_Attribute (Raise_Exception,
+ "strub", "callable");
+
function Exception_Message (X : Exception_Occurrence) return String;
procedure Reraise_Occurrence (X : Exception_Occurrence);
@@ -184,6 +188,10 @@ private
-- Raise_Exception_Always if it can determine this is the case. The Export
-- allows this routine to be accessed from Pure units.
+ -- Make it callable from strub contexts.
+ pragma Machine_Attribute (Raise_Exception_Always,
+ "strub", "callable");
+
procedure Raise_From_Controlled_Operation (X : Exception_Occurrence);
pragma No_Return (Raise_From_Controlled_Operation);
pragma Export
diff --git a/gcc/ada/libgnat/s-arit128.ads b/gcc/ada/libgnat/s-arit128.ads
index 6213cfb569a..5d3fff6d8e1 100644
--- a/gcc/ada/libgnat/s-arit128.ads
+++ b/gcc/ada/libgnat/s-arit128.ads
@@ -57,6 +57,12 @@ package System.Arith_128 is
-- bits, otherwise returns the 128-bit signed integer product.
-- Gigi may also call this routine directly.
+ -- Make it callable from strub contexts.
+ -- There is a matching setting in trans.c,
+ -- for calls issued by Gigi.
+ pragma Machine_Attribute (Multiply_With_Ovflo_Check128,
+ "strub", "callable");
+
procedure Scaled_Divide128
(X, Y, Z : Int128;
Q, R : out Int128;
diff --git a/gcc/ada/libgnat/s-arit64.ads b/gcc/ada/libgnat/s-arit64.ads
index c9141f5fe3e..170c2bdf1de 100644
--- a/gcc/ada/libgnat/s-arit64.ads
+++ b/gcc/ada/libgnat/s-arit64.ads
@@ -57,6 +57,12 @@ package System.Arith_64 is
-- bits, otherwise returns the 64-bit signed integer product.
-- Gigi may also call this routine directly.
+ -- Make it callable from strub contexts.
+ -- There is a matching setting in trans.c,
+ -- for calls issued by Gigi.
+ pragma Machine_Attribute (Multiply_With_Ovflo_Check64,
+ "strub", "callable");
+
procedure Scaled_Divide64
(X, Y, Z : Int64;
Q, R : out Int64;
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 99548627761..7ad91ea6f7a 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -70,6 +70,7 @@ along with GCC; see the file COPYING3. If not see
#include "gimple-fold.h"
#include "intl.h"
#include "file-prefix-map.h" /* remap_macro_filename() */
+#include "ipa-strub.h" /* strub_watermark_parm() */
#include "gomp-constants.h"
#include "omp-general.h"
#include "tree-dfa.h"
@@ -150,6 +151,7 @@ static rtx expand_builtin_strnlen (tree, rtx, machine_mode);
static rtx expand_builtin_alloca (tree);
static rtx expand_builtin_unop (machine_mode, tree, rtx, rtx, optab);
static rtx expand_builtin_frame_address (tree, tree);
+static rtx expand_builtin_stack_address ();
static tree stabilize_va_list_loc (location_t, tree, int);
static rtx expand_builtin_expect (tree, rtx);
static rtx expand_builtin_expect_with_probability (tree, rtx);
@@ -4871,6 +4873,256 @@ expand_builtin_frame_address (tree fndecl, tree exp)
}
}
+#ifndef STACK_GROWS_DOWNWARD
+# define STACK_TOPS GT
+#else
+# define STACK_TOPS LT
+#endif
+
+#ifdef POINTERS_EXTEND_UNSIGNED
+# define STACK_UNSIGNED POINTERS_EXTEND_UNSIGNED
+#else
+# define STACK_UNSIGNED true
+#endif
+
+/* Expand a call to builtin function __builtin_stack_address. */
+
+static rtx
+expand_builtin_stack_address ()
+{
+ return convert_to_mode (ptr_mode, copy_to_reg (stack_pointer_rtx),
+ STACK_UNSIGNED);
+}
+
+/* Expand a call to builtin function __builtin_strub_enter. */
+
+static rtx
+expand_builtin_strub_enter (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 1 || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = NULL_RTX;
+
+#if 1 || defined RED_ZONE_SIZE
+ if (tree wmptr = (optimize
+ ? strub_watermark_parm (current_function_decl)
+ : NULL_TREE))
+ {
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+ stktop = force_reg (ptr_mode, wmark);
+ }
+#endif
+
+ if (!stktop)
+ stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ emit_move_insn (wmark, stktop);
+
+ return const0_rtx;
+}
+
+/* Expand a call to builtin function __builtin_strub_update. */
+
+static rtx
+expand_builtin_strub_update (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 2 || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = expand_builtin_stack_address ();
+
+#ifdef RED_ZONE_SIZE
+ /* Here's how the strub enter, update and leave functions deal with red zones.
+
+ If it weren't for red zones, update, called from within a strub context,
+ would bump the watermark to the top of the stack. Enter and leave, running
+ in the caller, would use the caller's top of stack address both to
+ initialize the watermark passed to the callee, and to start strubbing the
+ stack afterwards.
+
+ Ideally, we'd update the watermark so as to cover the used amount of red
+ zone, and strub starting at the caller's other end of the (presumably
+ unused) red zone. Normally, only leaf functions use the red zone, but at
+ this point we can't tell whether a function is a leaf, nor can we tell how
+ much of the red zone it uses. Furthermore, some strub contexts may have
+ been inlined so that update and leave are called from the same stack frame,
+ and the strub builtins may all have been inlined, turning a strub function
+ into a leaf.
+
+ So cleaning the range from the caller's stack pointer (one end of the red
+ zone) to the (potentially inlined) callee's (other end of the) red zone
+ could scribble over the caller's own red zone.
+
+ We avoid this possibility by arranging for callers that are strub contexts
+ to use their own watermark as the strub starting point. So, if A calls B,
+ and B calls C, B will tell A to strub up to the end of B's red zone, and
+ will strub itself only the part of C's stack frame and red zone that
+ doesn't overlap with B's. With that, we don't need to know who's leaf and
+ who isn't: inlined calls will shrink their strub window to zero, each
+ remaining call will strub some portion of the stack, and eventually the
+ strub context will return to a caller that isn't a strub context itself,
+ that will therefore use its own stack pointer as the strub starting point.
+ It's not a leaf, because strub contexts can't be inlined into non-strub
+ contexts, so it doesn't use the red zone, and it will therefore correctly
+ strub up the callee's stack frame up to the end of the callee's red zone.
+ Neat! */
+ if (true /* (flags_from_decl_or_type (current_function_decl) & ECF_LEAF) */)
+ {
+ poly_int64 red_zone_size = RED_ZONE_SIZE;
+#if STACK_GROWS_DOWNWARD
+ red_zone_size = -red_zone_size;
+#endif
+ stktop = plus_constant (ptr_mode, stktop, red_zone_size);
+ stktop = force_reg (ptr_mode, stktop);
+ }
+#endif
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ rtx wmarkr = force_reg (ptr_mode, wmark);
+
+ rtx_code_label *lab = gen_label_rtx ();
+ do_compare_rtx_and_jump (stktop, wmarkr, STACK_TOPS, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, lab, NULL,
+ profile_probability::very_likely ());
+ emit_move_insn (wmark, stktop);
+
+#if 1 || defined RED_ZONE_SIZE
+ /* If this is an inlined strub function, also bump the watermark for the
+ enclosing function. This avoids a problem with the following scenario: A
+ calls B and B calls C, and both B and C get inlined into A. B allocates
+ temporary stack space before calling C. If we don't update A's watermark,
+ we may use an outdated baseline for the post-C strub_leave, erasing B's
+ temporary stack allocation. We only need this if we're fully expanding
+ strub_leave inline. */
+ tree xwmptr = (optimize > 2
+ ? strub_watermark_parm (current_function_decl)
+ : wmptr);
+ if (wmptr != xwmptr)
+ {
+ wmptr = xwmptr;
+ wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+ wmarkr = force_reg (ptr_mode, wmark);
+
+ do_compare_rtx_and_jump (stktop, wmarkr, STACK_TOPS, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, lab, NULL,
+ profile_probability::very_likely ());
+ emit_move_insn (wmark, stktop);
+ }
+#endif
+
+ emit_label (lab);
+
+ return const0_rtx;
+}
+
+
+/* Expand a call to builtin function __builtin_strub_leave. */
+
+static rtx
+expand_builtin_strub_leave (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 2 || optimize_size || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = NULL_RTX;
+
+#if 1 || defined RED_ZONE_SIZE
+ if (tree wmptr = (optimize
+ ? strub_watermark_parm (current_function_decl)
+ : NULL_TREE))
+ {
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+ stktop = force_reg (ptr_mode, wmark);
+ }
+#endif
+
+ if (!stktop)
+ stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ rtx wmarkr = force_reg (ptr_mode, wmark);
+
+#ifndef STACK_GROWS_DOWNWARD
+ rtx base = stktop;
+ rtx end = wmarkr;
+#else
+ rtx base = wmarkr;
+ rtx end = stktop;
+#endif
+
+ /* We're going to modify it, so make sure it's not e.g. the stack pointer. */
+ base = copy_to_reg (base);
+
+ rtx_code_label *done = gen_label_rtx ();
+ do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, done, NULL,
+ profile_probability::very_likely ());
+
+ if (optimize < 3)
+ expand_call (exp, NULL_RTX, true);
+ else
+ {
+ /* Ok, now we've determined we want to copy the block, so convert the
+ addresses to Pmode, as needed to dereference them to access ptr_mode
+ memory locations, so that we don't have to convert anything within the
+ loop. */
+ base = memory_address (ptr_mode, base);
+ end = memory_address (ptr_mode, end);
+
+ rtx zero = force_operand (const0_rtx, NULL_RTX);
+ int ulen = GET_MODE_SIZE (ptr_mode);
+ rtx incr = plus_constant (Pmode, base, ulen);
+ rtx dstm = gen_rtx_MEM (ptr_mode, base);
+
+ rtx_code_label *loop = gen_label_rtx ();
+ emit_label (loop);
+ emit_move_insn (dstm, zero);
+ emit_move_insn (base, force_operand (incr, NULL_RTX));
+ do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
+ Pmode, NULL_RTX, NULL, loop,
+ profile_probability::very_likely ());
+ }
+
+ emit_label (done);
+
+ return const0_rtx;
+}
+
/* Expand EXP, a call to the alloca builtin. Return NULL_RTX if we
failed and the caller should emit a normal call. */
@@ -7110,6 +7362,27 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
case BUILT_IN_RETURN_ADDRESS:
return expand_builtin_frame_address (fndecl, exp);
+ case BUILT_IN_STACK_ADDRESS:
+ return expand_builtin_stack_address ();
+
+ case BUILT_IN___STRUB_ENTER:
+ target = expand_builtin_strub_enter (exp);
+ if (target)
+ return target;
+ break;
+
+ case BUILT_IN___STRUB_UPDATE:
+ target = expand_builtin_strub_update (exp);
+ if (target)
+ return target;
+ break;
+
+ case BUILT_IN___STRUB_LEAVE:
+ target = expand_builtin_strub_leave (exp);
+ if (target)
+ return target;
+ break;
+
/* Returns the address of the area where the structure is returned.
0 otherwise. */
case BUILT_IN_AGGREGATE_INCOMING_ADDRESS:
diff --git a/gcc/builtins.def b/gcc/builtins.def
index 45a09b4d42d..f83c1f5fcd7 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -874,6 +874,10 @@ DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSL, "ffsl", BT_FN_INT_LONG, ATTR_CONST_NOTHRO
DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSLL, "ffsll", BT_FN_INT_LONGLONG, ATTR_CONST_NOTHROW_LEAF_LIST)
DEF_EXT_LIB_BUILTIN (BUILT_IN_FORK, "fork", BT_FN_PID, ATTR_NOTHROW_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FRAME_ADDRESS, "frame_address", BT_FN_PTR_UINT, ATTR_NULL)
+DEF_GCC_BUILTIN (BUILT_IN_STACK_ADDRESS, "stack_address", BT_FN_PTR, ATTR_NULL)
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_ENTER, "__builtin___strub_enter")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_UPDATE, "__builtin___strub_update")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_LEAVE, "__builtin___strub_leave")
/* [trans-mem]: Adjust BUILT_IN_TM_FREE if BUILT_IN_FREE is changed. */
DEF_LIB_BUILTIN (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_LEAF_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FROB_RETURN_ADDR, "frob_return_addr", BT_FN_PTR_PTR, ATTR_NULL)
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index e60fb31d8c8..edec2afc251 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -69,6 +69,7 @@ static tree handle_asan_odr_indicator_attribute (tree *, tree, tree, int,
static tree handle_stack_protect_attribute (tree *, tree, tree, int, bool *);
static tree handle_no_stack_protector_function_attribute (tree *, tree,
tree, int, bool *);
+static tree handle_strub_attribute (tree *, tree, tree, int, bool *);
static tree handle_noinline_attribute (tree *, tree, tree, int, bool *);
static tree handle_noclone_attribute (tree *, tree, tree, int, bool *);
static tree handle_nocf_check_attribute (tree *, tree, tree, int, bool *);
@@ -306,6 +307,8 @@ const struct attribute_spec c_common_attribute_table[] =
{ "no_stack_protector", 0, 0, true, false, false, false,
handle_no_stack_protector_function_attribute,
attr_stack_protect_exclusions },
+ { "strub", 0, 1, false, true, false, true,
+ handle_strub_attribute, NULL },
{ "noinline", 0, 0, true, false, false, false,
handle_noinline_attribute,
attr_noinline_exclusions },
@@ -1290,6 +1293,90 @@ handle_noipa_attribute (tree *node, tree name, tree, int, bool *no_add_attrs)
return NULL_TREE;
}
+/* Handle a "strub" attribute; arguments as in
+ struct attribute_spec.handler. */
+
+static tree
+handle_strub_attribute (tree *node, tree name,
+ tree args,
+ int ARG_UNUSED (flags), bool *no_add_attrs)
+{
+ bool enable = true;
+
+ if (args
+ && (TREE_CODE (*node) == FUNCTION_TYPE
+ || TREE_CODE (*node) == METHOD_TYPE))
+ {
+ 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;
+ }
+
+ if (val >= 0)
+ TREE_VALUE (args) = build_int_cst (integer_type_node, val);
+ }
+
+ /* 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)
+ {
+ warning (OPT_Wattributes,
+ "%qE attribute ignored because of argument %qE",
+ name, TREE_VALUE (args));
+ *no_add_attrs = true;
+ enable = false;
+ }
+ /* 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));
+
+ /* 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;
+
+ return NULL_TREE;
+}
+
/* Handle a "noinline" attribute; arguments as in
struct attribute_spec.handler. */
diff --git a/gcc/common.opt b/gcc/common.opt
index ed8ab5fbe13..ba0359c3c1d 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,6 +2687,30 @@ 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=all
+Common RejectNegative Var(flag_strub, 3)
+Enable stack scrubbing for all viable functions.
+
+fstrub=at-calls
+Common RejectNegative Var(flag_strub, 1)
+Enable at-calls stack scrubbing for all viable functions.
+
+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 49df8e6dc38..e256db23862 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -77,6 +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.
* 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.
@@ -8672,6 +8673,49 @@ pid_t wait (wait_status_ptr_t p)
@}
@end smallexample
+@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.
+
+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.
+
+A variable whose type carries the @code{strub} attribute causes
+functions that @emph{read} from it to have @code{strub} enabled.
+Reading from data referenced by pointers to such types has the same
+effect.
+
+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.
+
+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.
+
+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)}).
+
+@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.
+
@item unused
@cindex @code{unused} type attribute
When attached to a type (including a @code{union} or a @code{struct}),
@@ -11690,6 +11734,47 @@ option is in effect. Such calls should only be made in debugging
situations.
@end deftypefn
+@deftypefn {Built-in Function} {void *} __builtin_stack_address ()
+This function returns the value of the stack pointer register.
+@end deftypefn
+
+@node Stack Scrubbing
+@section Stack scrubbing 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).
+
+@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.
+@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
+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.
+@end deftypefn
+
@node Vector Extensions
@section Using Vector Instructions through Built-in Functions
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index d8a6b0b60c9..684d87bceae 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,6 +599,7 @@ 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
-fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
-fvtv-counts -fvtv-debug @gol
-finstrument-functions @gol
@@ -15484,6 +15485,47 @@ 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.
+
+@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.
+
+@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.
+
+@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.
+
@item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
@opindex fvtable-verify
This option is only available when compiling C++ code.
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 413446bcc46..7f4bc44d2bb 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -119,6 +119,7 @@ along with GCC; see the file COPYING3. If not see
#include "stringpool.h"
#include "attribs.h"
#include "asan.h"
+#include "ipa-strub.h"
typedef fibonacci_heap <sreal, cgraph_edge> edge_heap_t;
typedef fibonacci_node <sreal, cgraph_edge> edge_heap_node_t;
@@ -396,6 +397,11 @@ 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))
+ {
+ e->inline_failed = CIF_UNSPECIFIED;
+ inlinable = false;
+ }
if (!inlinable && report)
report_inline_failed_reason (e);
return inlinable;
diff --git a/gcc/ipa-split.c b/gcc/ipa-split.c
index c68577d04a9..8c880747eb7 100644
--- a/gcc/ipa-split.c
+++ b/gcc/ipa-split.c
@@ -104,6 +104,7 @@ along with GCC; see the file COPYING3. If not see
#include "ipa-fnsummary.h"
#include "cfgloop.h"
#include "attribs.h"
+#include "ipa-strub.h"
/* Per basic block info. */
@@ -1792,6 +1793,12 @@ execute_split_functions (void)
"section.\n");
return 0;
}
+ if (!strub_splittable_p (node))
+ {
+ if (dump_file)
+ fprintf (dump_file, "Not splitting: function is a strub context.\n");
+ return 0;
+ }
/* We enforce splitting after loop headers when profile info is not
available. */
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
new file mode 100644
index 00000000000..cf7d811779e
--- /dev/null
+++ b/gcc/ipa-strub.c
@@ -0,0 +1,2999 @@
+/* strub (stack scrubbing) support.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "gimplify.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "gimple-iterator.h"
+#include "gimplify-me.h"
+#include "tree-into-ssa.h"
+#include "tree-ssa.h"
+#include "tree-cfg.h"
+#include "cfghooks.h"
+#include "cfgloop.h"
+#include "cfgcleanup.h"
+#include "tree-eh.h"
+#include "except.h"
+#include "builtins.h"
+#include "attribs.h"
+#include "tree-inline.h"
+#include "cgraph.h"
+#include "alloc-pool.h"
+#include "symbol-summary.h"
+#include "ipa-prop.h"
+#include "ipa-fnsummary.h"
+#include "gimple-fold.h"
+#include "fold-const.h"
+#include "gimple-walk.h"
+#include "tree-dfa.h"
+#include "langhooks.h"
+#include "calls.h"
+#include "vec.h"
+#include "stor-layout.h"
+#include "varasm.h"
+#include "alias.h"
+#include "diagnostic.h"
+#include "intl.h"
+#include "ipa-strub.h"
+
+#if BUILDING_GCC_MAJOR >= 11
+# include "symtab-thunks.h"
+# include "attr-fnspec.h"
+# define HAVE_ATTR_FNSPEC 1
+# define FOR_GCC_11P 1
+#else
+# define HAVE_ATTR_FNSPEC 0
+# define FOR_GCC_11P 0
+#endif
+
+/* Const and pure functions that gain a watermark parameter for strub purposes
+ are still regarded as such, which may cause the inline expansions of the
+ __strub builtins to malfunction. Ideally, attribute "fn spec" would enable
+ us to inform the backend about requirements and side effects of the call, but
+ call_fusage building in calls.c:expand_call does not even look at
+ attr_fnspec, so we resort to asm loads and updates to attain an equivalent
+ effect. Once expand_call gains the ability to issue extra memory uses and
+ clobbers based on pure/const function's fnspec, we can define this to 1. */
+#define ATTR_FNSPEC_DECONST_WATERMARK 0
+
+enum strub_mode {
+ /* This mode denotes a regular function, that does not require stack
+ scrubbing (strubbing). It may call any other functions, but if
+ it calls AT_CALLS (or WRAPPED) ones, strubbing logic is
+ automatically introduced around those calls (the latter, by
+ inlining INTERNAL wrappers). */
+ STRUB_DISABLED = 0,
+
+ /* This denotes a function whose signature is (to be) modified to
+ take an extra parameter, for stack use annotation, and its
+ callers must initialize and pass that argument, and perform the
+ strubbing. Functions that are explicitly marked with attribute
+ strub must have the mark visible wherever the function is,
+ including aliases, and overriders and overriding methods.
+ Functions that are implicitly marked for strubbing, for accessing
+ variables explicitly marked as such, will only select this
+ strubbing method if they are internal to a translation unit. It
+ can only be inlined into other strubbing functions, i.e.,
+ STRUB_AT_CALLS or STRUB_WRAPPED. */
+ STRUB_AT_CALLS = 1,
+
+ /* This denotes a function that is to perform strubbing internally,
+ without any changes to its interface (the function is turned into
+ a strubbing wrapper, and its original body is moved to a separate
+ STRUB_WRAPPED function, with a modified interface). Functions
+ may be explicitly marked with attribute strub(2), and the
+ attribute must be visible at the point of definition. Functions
+ that are explicitly marked for strubbing, for accessing variables
+ explicitly marked as such, may select this strubbing mode if
+ their interface cannot change, e.g. because its interface is
+ visible to other translation units, directly, by indirection
+ (having its address taken), inheritance, etc. Functions that use
+ this method must not have the noclone attribute, nor the noipa
+ one. Functions marked as always_inline may select this mode, but
+ they are NOT wrapped, they remain unchanged, and are only inlined
+ into strubbed contexts. Once non-always_inline functions are
+ wrapped, the wrapper becomes STRUB_WRAPPER, and the wrapped becomes
+ STRUB_WRAPPED. */
+ STRUB_INTERNAL = 2,
+
+ /* This denotes a function whose stack is not strubbed, but that is
+ nevertheless explicitly or implicitly marked as callable from strubbing
+ functions. Normally, only STRUB_AT_CALLS (and STRUB_INTERNAL ->
+ STRUB_WRAPPED) functions can be called from strubbing contexts (bodies of
+ STRUB_AT_CALLS, STRUB_INTERNAL and STRUB_WRAPPED functions), but attribute
+ strub(3) enables other functions to be (indirectly) called from these
+ contexts. Some builtins and internal functions may be implicitly marked as
+ STRUB_CALLABLE. */
+ STRUB_CALLABLE = 3,
+
+ /* This denotes the function that took over the body of a
+ STRUB_INTERNAL function. At first, it's only called by its
+ wrapper, but the wrapper may be inlined. The wrapped function,
+ in turn, can only be inlined into other functions whose stack
+ frames are strubbed, i.e., that are STRUB_WRAPPED or
+ STRUB_AT_CALLS. */
+ STRUB_WRAPPED = -1,
+
+ /* This denotes the wrapper function that replaced the STRUB_INTERNAL
+ function. This mode overrides the STRUB_INTERNAL mode at the time the
+ internal to-be-wrapped function becomes a wrapper, so that inlining logic
+ can tell one from the other. */
+ STRUB_WRAPPER = -2,
+
+ /* 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
+get_strub_attr_from_type (tree type)
+{
+ return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
+}
+
+static tree
+get_strub_attr_from_decl (tree decl)
+{
+ tree ret = lookup_attribute ("strub", DECL_ATTRIBUTES (decl));
+ if (ret)
+ return ret;
+ return get_strub_attr_from_type (TREE_TYPE (decl));
+}
+
+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);
+
+#if 0 /* ??? use symbolic mode names with interned strings? */
+ char *s = NULL;
+
+ switch (strub_mode)
+ {
+
+ }
+#endif
+}
+
+static enum strub_mode
+get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
+{
+ enum strub_mode mode = STRUB_DISABLED;
+
+ if (strub_attr)
+ {
+ 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 ();
+ }
+
+ return mode;
+}
+
+static enum strub_mode
+get_strub_mode_from_decl (tree fndecl)
+{
+ return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
+}
+
+static enum strub_mode
+get_strub_mode (cgraph_node *node)
+{
+ return get_strub_mode_from_decl (node->decl);
+}
+
+static enum strub_mode
+get_strub_mode_from_type (tree type)
+{
+ bool var_p = !FUNC_OR_METHOD_TYPE_P (type);
+ tree attr = get_strub_attr_from_type (type);
+
+ if (attr)
+ return get_strub_mode_from_attr (attr, var_p);
+
+ if (flag_strub > 0 && !var_p)
+ return STRUB_CALLABLE;
+
+ return STRUB_DISABLED;
+}
+
+static bool
+calls_builtin_va_start_p (cgraph_node *node)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (fndecl_built_in_p (cdecl, BUILT_IN_VA_START))
+ return true;
+ }
+
+ return result;
+}
+
+static bool
+calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!fndecl_built_in_p (cdecl, BUILT_IN_APPLY_ARGS))
+ continue;
+
+ result = true;
+
+ if (!report)
+ break;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "at-calls %<strub%> does not support call to %qD",
+ cdecl);
+ }
+
+ return result;
+}
+
+static inline bool
+strub_always_inline_p (cgraph_node *node)
+{
+ return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
+}
+
+static inline bool
+can_strub_p (cgraph_node *node, bool report = false)
+{
+ bool result = true;
+
+ if (!report && strub_always_inline_p (node))
+ return result;
+
+ if (lookup_attribute ("noipa", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for %<strub%>"
+ " because of attribute %<noipa%>",
+ node->decl);
+ }
+
+ /* We can't, and don't want to vectorize the watermark and other
+ strub-introduced parms. */
+ if (lookup_attribute ("simd", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for %<strub%>"
+ " because of attribute %<simd%>",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+can_strub_at_calls_p (cgraph_node *node, bool report = false)
+{
+ bool result = !report || can_strub_p (node, report);
+
+ if (!result && !report)
+ return result;
+
+ return !calls_builtin_apply_args_p (node, report);
+}
+
+#define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
+
+/* We can't perform internal strubbing if the function body involves certain
+ features:
+
+ - a non-default __builtin_va_start (e.g. x86's __builtin_ms_va_start) is
+ currently unsupported because we can't discover the corresponding va_copy and
+ va_end decls in the wrapper, and we don't convey the alternate variable
+ arguments ABI to the modified wrapped function. The default
+ __builtin_va_start is supported by calling va_start/va_end at the wrapper,
+ that takes variable arguments, passing a pointer to the va_list object to the
+ wrapped function, that runs va_copy from it where the original function ran
+ va_start.
+
+ __builtin_next_arg is currently unsupported because the wrapped function
+ won't be a variable argument function. We could process it in the wrapper,
+ that remains a variable argument function, and replace calls in the wrapped
+ body, but we currently don't.
+
+ __builtin_return_address is rejected because it's generally used when the
+ actual caller matters, and introducing a wrapper breaks such uses as those in
+ the unwinder. */
+
+static bool
+can_strub_internally_p (cgraph_node *node, bool report = false)
+{
+ bool result = !report || can_strub_p (node, report);
+
+ if (!result && !report)
+ return result;
+
+ if (!report && strub_always_inline_p (node))
+ return 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, but we'd have to prevent remapping of the labels. */
+ if (lookup_attribute ("noclone", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for internal %<strub%>"
+ " because of attribute %<noclone%>",
+ node->decl);
+ }
+
+ if (node->has_gimple_body_p ())
+ {
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!((fndecl_built_in_p (cdecl, BUILT_IN_VA_START)
+ && cdecl != builtin_decl_explicit (BUILT_IN_VA_START))
+ || fndecl_built_in_p (cdecl, BUILT_IN_NEXT_ARG)
+ || fndecl_built_in_p (cdecl, BUILT_IN_RETURN_ADDRESS)))
+ continue;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "%qD is not eligible for internal %<strub%> "
+ "because it calls %qD",
+ node->decl, cdecl);
+ }
+
+ struct function *fun = DECL_STRUCT_FUNCTION (node->decl);
+ if (fun->has_nonlocal_label)
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for internal %<strub%> "
+ "because it contains a non-local goto target",
+ node->decl);
+ }
+
+ if (fun->has_forced_label_in_static)
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for internal %<strub%> "
+ "because the address of a local label escapes",
+ node->decl);
+ }
+
+ /* Catch any other case that would prevent versioning/cloning
+ so as to also have it covered above. */
+ gcc_checking_assert (!result /* || !node->has_gimple_body_p () */
+ || tree_versionable_function_p (node->decl));
+
+
+ /* Label values referenced 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. */
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ glabel *label_stmt = dyn_cast <glabel *> (gsi_stmt (gsi));
+ tree target;
+
+ if (!label_stmt)
+ break;
+
+ 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;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (label_stmt),
+ "internal %<strub%> does not support user labels");
+ }
+ }
+
+ if (list_length (TYPE_ARG_TYPES (TREE_TYPE (node->decl)))
+ >= (((HOST_WIDE_INT) 1 << IPA_PARAM_MAX_INDEX_BITS)
+ - STRUB_INTERNAL_MAX_EXTRA_ARGS))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD has too many arguments for internal %<strub%>",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+strub_from_body_p (cgraph_node *node)
+{
+ if (!node->has_gimple_body_p ())
+ return false;
+
+ /* If any local variable is marked for strub... */
+ unsigned i;
+ tree var;
+ FOR_EACH_LOCAL_DECL (DECL_STRUCT_FUNCTION (node->decl),
+ i, var)
+ if (get_strub_mode_from_type (TREE_TYPE (var))
+ != STRUB_DISABLED)
+ return true;
+
+ /* Now scan the body for loads with strub types. */
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!gimple_assign_load_p (stmt))
+ continue;
+
+ tree rhs = gimple_assign_rhs1 (stmt);
+ if (get_strub_mode_from_type (TREE_TYPE (rhs))
+ != STRUB_DISABLED)
+ return true;
+ }
+
+ return false;
+}
+
+/* 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)
+{
+ if (DECL_BUILT_IN_CLASS (node->decl) != BUILT_IN_NORMAL)
+ return false;
+
+ enum built_in_function fcode = DECL_FUNCTION_CODE (node->decl);
+
+ switch (fcode)
+ {
+ case BUILT_IN_NONE:
+ gcc_unreachable ();
+
+ /* This temporarily allocates stack for the call, and we can't reasonably
+ update the watermark for that. Besides, we don't check the actual call
+ target, nor its signature, and it seems to be overkill to as much as
+ try to do so. */
+ case BUILT_IN_APPLY:
+ return false;
+
+ /* Conversely, this shouldn't be called from within strub contexts, since
+ the caller may have had its signature modified. STRUB_INTERNAL is ok,
+ the call will remain in the STRUB_WRAPPER, and removed from the
+ STRUB_WRAPPED clone. */
+ case BUILT_IN_APPLY_ARGS:
+ return false;
+
+ /* ??? Make all other builtins callable. We wish to make any builtin call
+ the compiler might introduce on its own callable. Anything that is
+ predictable enough as to be known not to allow stack data that should
+ be strubbed to unintentionally escape to non-strub contexts can be
+ allowed, and pretty much every builtin appears to fit this description.
+ The exceptions to this rule seem to be rare, and only available as
+ explicit __builtin calls, so let's keep it simple and allow all of
+ them... */
+ default:
+ return true;
+ }
+}
+
+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);
+
+ /* 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;
+ /* 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
+ safely undergo at-calls strubbing. Internal mode will still be used in
+ functions that request it explicitly with attribute strub(2), or when the
+ function body requires strubbing and at-calls strubbing is not viable. */
+ const bool strub_flag_at_calls = flag_strub == 1;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo internal strubbing. At-calls mode will still be used in
+ functions that requiest it explicitly with attribute strub() or strub(1),
+ or when the function body requires strubbing and internal strubbing is not
+ viable. */
+ const bool strub_flag_internal = flag_strub == 2;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo strubbing in either mode. When both modes are viable,
+ at-calls is preferred. */
+ const bool strub_flag_either = flag_strub == 3;
+ /* Besides the default behavior, enable strub implicitly for all viable
+ functions. */
+ const bool strub_flag_viable = flag_strub > 0;
+
+ /* The consider_* variables should be true if selecting the corresponding
+ strub modes would be consistent with requests from attributes and command
+ line flags. Attributes associated with functions pretty much mandate a
+ selection, and should report an error if not satisfied; strub_flag_auto
+ implicitly enables some viable strub mode if that's required by references
+ to variables marked for strub; strub_flag_viable enables strub if viable
+ (even when favoring one mode, body-requested strub can still be satisfied
+ by either mode), and falls back to callable, silently unless variables
+ require strubbing. */
+
+ const bool consider_at_calls
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_AT_CALLS
+ : true));
+ const bool consider_internal
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_INTERNAL
+ : true));
+
+ const bool consider_callable
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_CALLABLE
+ : (strub_flag_viable
+ || strub_callable_builtin_p (node))));
+
+ /* This is a shorthand for either strub-enabled mode. */
+ const bool consider_strub
+ = (consider_at_calls || consider_internal);
+
+ /* We can cope with always_inline functions even with noipa and noclone,
+ because we just leave them alone. */
+ const bool is_always_inline
+ = strub_always_inline_p (node);
+
+ /* Strubbing in general, and each specific strub mode, may have its own set of
+ requirements. We require noipa for strubbing, either because of cloning
+ required for internal strub, or because of caller enumeration required for
+ at-calls strub. We don't consider the at-calls mode eligible if it's not
+ even considered, it has no further requirements. Internal mode requires
+ cloning and the absence of certain features in the body and, like at-calls,
+ it's not eligible if it's not even under consideration.
+
+ ??? Do we need target hooks for further constraints? E.g., x86's
+ "interrupt" attribute breaks internal strubbing because the wrapped clone
+ carries the attribute and thus isn't callable; in this case, we could use a
+ target hook to adjust the clone instead. */
+ const bool strub_eligible
+ = (consider_strub
+ && (is_always_inline || can_strub_p (node)));
+ const bool at_calls_eligible
+ = (consider_at_calls && strub_eligible
+ && can_strub_at_calls_p (node));
+ const bool internal_eligible
+ = (consider_internal && strub_eligible
+ && (is_always_inline
+ || can_strub_internally_p (node)));
+
+ /* In addition to the strict eligibility requirements, some additional
+ constraints are placed on implicit selection of certain modes. These do
+ not prevent the selection of a mode if explicitly specified as part of a
+ function interface (the strub attribute), but they may prevent modes from
+ being selected by the command line or by function bodies. The only actual
+ constraint is on at-calls mode: since we change the function's exposed
+ signature, we won't do it implicitly if the function can possibly be used
+ in ways that do not expect the signature change, e.g., if the function is
+ available to or interposable by other units, if its address is taken,
+ etc. */
+ const bool at_calls_viable
+ = (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->binds_to_current_def_p ()
+ && node->can_be_local_p ()))
+ && node->only_called_directly_p ())));
+ const bool internal_viable
+ = (internal_eligible);
+
+ /* Shorthand. */
+ const bool strub_viable
+ = (at_calls_viable || internal_viable);
+
+ /* We wish to analyze the body, to look for implicit requests for strub, both
+ to implicitly enable it when the body calls for it, and to report errors if
+ the body calls for it but neither mode is viable (even if that follows from
+ non-eligibility because of the explicit specification of some non-strubbing
+ mode). We can refrain from scanning the body only in rare circumstances:
+ when strub is enabled by a function attribute (scanning might be redundant
+ in telling us to also enable it), and when we are enabling strub implicitly
+ but there are non-viable modes: we want to know whether strubbing is
+ required, to fallback to another mode, even if we're only enabling a
+ certain mode, or, when either mode would do, to report an error if neither
+ happens to be viable. */
+ const bool analyze_body
+ = (strub_attr
+ ? !consider_strub
+ : (strub_flag_auto
+ || (strub_flag_viable && (!at_calls_viable && !internal_viable))
+ || (strub_flag_either && !strub_viable)));
+
+ /* Cases in which strubbing is enabled or disabled by strub_flag_auto.
+ Unsatisfiable requests ought to be reported. */
+ const bool strub_required
+ = ((strub_attr && consider_strub)
+ || (analyze_body && strub_from_body_p (node)));
+
+ /* Besides the required cases, we want to abide by the requests to enabling on
+ an if-viable basis. */
+ const bool strub_enable
+ = (strub_required
+ || (strub_flag_at_calls && at_calls_viable)
+ || (strub_flag_internal && internal_viable)
+ || (strub_flag_either && strub_viable));
+
+ /* And now we're finally ready to select a mode that abides by the viability
+ and eligibility constraints, and that satisfies the strubbing requirements
+ and requests, subject to the constraints. If both modes are viable and
+ strub is to be enabled, pick STRUB_AT_CALLS unless STRUB_INTERNAL was named
+ as preferred. */
+ 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
+ : (strub_enable && at_calls_viable)
+ ? (strub_required && !strub_attr
+ ? STRUB_AT_CALLS_OPT
+ : STRUB_AT_CALLS)
+ : consider_callable
+ ? STRUB_CALLABLE
+ : STRUB_DISABLED);
+
+ switch (mode)
+ {
+ case STRUB_CALLABLE:
+ if (is_always_inline)
+ break;
+ /* Fall through. */
+
+ case STRUB_DISABLED:
+ if (strub_enable && !strub_attr)
+ {
+ gcc_checking_assert (analyze_body);
+ error_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD requires %<strub%>,"
+ " but no viable %<strub%> mode was found",
+ node->decl);
+ break;
+ }
+ /* Fall through. */
+
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ /* Differences from an mode requested through a function attribute are
+ 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:
+ gcc_unreachable ();
+ }
+
+ return mode;
+}
+
+/* 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)
+{
+ gcc_checking_assert (override
+ || !(DECL_P (fndt)
+ ? get_strub_attr_from_decl (fndt)
+ : get_strub_attr_from_type (fndt)));
+
+ tree attr = tree_cons (get_identifier ("strub"),
+ get_strub_mode_attr_value (mode),
+ NULL_TREE);
+ tree *attrp = NULL;
+ if (DECL_P (fndt))
+ {
+ gcc_checking_assert (FUNC_OR_METHOD_TYPE_P (TREE_TYPE (fndt)));
+ attrp = &DECL_ATTRIBUTES (fndt);
+ }
+ else if (FUNC_OR_METHOD_TYPE_P (fndt))
+ attrp = &TYPE_ATTRIBUTES (fndt);
+ else
+ gcc_unreachable ();
+
+ TREE_CHAIN (attr) = *attrp;
+ *attrp = attr;
+}
+
+void
+strub_make_callable (tree fndt)
+{
+ strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
+}
+
+static void
+set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+ enum strub_mode req_mode = get_strub_mode_from_attr (attr);
+
+ if (attr)
+ {
+ /* Check for and report incompatible mode changes. */
+ if (mode != req_mode
+ && !(req_mode == STRUB_INTERNAL
+ && (mode == STRUB_WRAPPED
+ || mode == STRUB_WRAPPER))
+ && !((req_mode == STRUB_INTERNAL
+ || req_mode == STRUB_AT_CALLS
+ || req_mode == STRUB_CALLABLE)
+ && 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));
+ if (node->alias)
+ {
+ cgraph_node *target = node->ultimate_alias_target ();
+ if (target != node)
+ error_at (DECL_SOURCE_LOCATION (target->decl),
+ "the incompatible selection was determined"
+ " by ultimate alias target %qD",
+ target->decl);
+ }
+
+ /* Report any incompatibilities with explicitly-requested strub. */
+ switch (req_mode)
+ {
+ case STRUB_AT_CALLS:
+ can_strub_at_calls_p (node, true);
+ break;
+
+ case STRUB_INTERNAL:
+ can_strub_internally_p (node, true);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Drop any incompatible strub attributes leading the decl attribute
+ chain. Return if we find one with the mode we need. */
+ for (;;)
+ {
+ if (mode == req_mode)
+ return;
+
+ if (DECL_ATTRIBUTES (node->decl) != attr)
+ break;
+
+ DECL_ATTRIBUTES (node->decl) = TREE_CHAIN (attr);
+ attr = get_strub_attr_from_decl (node->decl);
+ if (!attr)
+ break;
+
+ req_mode = get_strub_mode_from_attr (attr);
+ }
+ }
+ else if (mode == req_mode)
+ return;
+
+ strub_set_fndt_mode_to (node->decl, mode, attr);
+}
+
+static void
+set_strub_mode (cgraph_node *node)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+
+ if (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:
+ case STRUB_AT_CALLS_OPT:
+ return;
+
+ case STRUB_DISABLED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ cgraph_node *xnode = node;
+ if (node->alias)
+ xnode = node->ultimate_alias_target ();
+ /* Weakrefs may remain unresolved (the above will return node) if
+ their targets are not defined, so make sure we compute a strub
+ mode for them, instead of defaulting to STRUB_DISABLED and
+ rendering them uncallable. */
+ enum strub_mode mode = (xnode != node && !xnode->alias
+ ? get_strub_mode (xnode)
+ : compute_strub_mode (node, attr));
+
+ set_strub_mode_to (node, mode);
+}
+
+/* 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. */
+
+static bool
+strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode caller_mode = get_strub_mode (caller);
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS_OPT:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return callee_mode != STRUB_INLINABLE;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_AT_CALLS_OPT:
+ case STRUB_INTERNAL:
+ case STRUB_WRAPPER:
+ return (flag_strub >= 0);
+
+ case STRUB_DISABLED:
+ return false;
+
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ 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. */
+
+bool
+strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ case STRUB_AT_CALLS_OPT:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return true;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ strub_mode caller_mode = get_strub_mode (caller);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ case STRUB_AT_CALLS_OPT:
+ return true;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ return false;
+}
+
+/* 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;
+
+ 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_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)
+ {
+ gcc_checking_assert (!e->indirect_unknown_callee);
+ if (!strub_callable_from_p (e->callee, node))
+ {
+ if (get_strub_mode (e->callee) == STRUB_INLINABLE)
+ error_at (gimple_location (e->call_stmt),
+ "calling %<always_inline%> %<strub%> %qD"
+ " in non-%<strub%> context %qD",
+ e->callee->decl, node->decl);
+ else if (fndecl_built_in_p (e->callee->decl, BUILT_IN_APPLY_ARGS)
+ && get_strub_mode (node) == STRUB_INTERNAL)
+ /* This is ok, it will be kept in the STRUB_WRAPPER, and removed
+ from the STRUB_WRAPPED's strub context. */
+ continue;
+ else
+ error_at (gimple_location (e->call_stmt),
+ "calling non-%<strub%> %qD in %<strub%> context %qD",
+ e->callee->decl, node->decl);
+ }
+ }
+ }
+
+ /* ??? 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 {
+
+const pass_data pass_data_ipa_strub_mode = {
+ SIMPLE_IPA_PASS,
+ "strubm",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ 0, // properties_finish
+};
+
+class pass_ipa_strub_mode : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub_mode (gcc::context *ctxt)
+ : simple_ipa_opt_pass (pass_data_ipa_strub_mode, ctxt)
+ {}
+ 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
+ overhead. */
+ if (flag_strub == -2)
+ flag_strub = 0;
+ return flag_strub;
+ }
+ virtual unsigned int execute (function *);
+};
+
+const pass_data pass_data_ipa_strub = {
+ SIMPLE_IPA_PASS,
+ "strub",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ TODO_update_ssa
+ | TODO_cleanup_cfg
+ | TODO_rebuild_cgraph_edges
+ | TODO_verify_il, // properties_finish
+};
+
+class pass_ipa_strub : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub (gcc::context *ctxt)
+ : 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 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_variant_type_copy (ptr_type_node))
+
+ DEF_TYPE (pwmt, build_reference_type (get_wmt ()))
+
+ DEF_TYPE (qpwmt,
+ build_qualified_type (get_pwmt (),
+ TYPE_QUAL_RESTRICT
+ /* | TYPE_QUAL_CONST */))
+
+ DEF_TYPE (qptr,
+ build_qualified_type (ptr_type_node,
+ TYPE_QUAL_RESTRICT
+ | TYPE_QUAL_CONST))
+
+ DEF_TYPE (qpvalst,
+ build_qualified_type (build_reference_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); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ decl = add_builtin_function \
+ ("__builtin_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ NULL, NULL); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ } \
+ return decl; \
+ }
+
+ DEF_NM_BUILTIN (stack_address,
+ BUILT_IN_STACK_ADDRESS,
+ (ptr_type_node, NULL))
+
+#undef DEF_NM_BUILTIN
+
+#define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST) \
+ static tree get_ ## NAME () { \
+ tree decl = builtin_decl_explicit (CODE); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ tree attrs = NULL; \
+ if (FNSPEC && HAVE_ATTR_FNSPEC) \
+ attrs = tree_cons (get_identifier ("fn spec"), \
+ build_tree_list \
+ (NULL_TREE, \
+ build_string (strlen (FNSPEC), \
+ (FNSPEC))), \
+ attrs); \
+ decl = add_builtin_function_ext_scope \
+ ("__builtin___strub_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ "__strub_" #NAME, attrs); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ } \
+ return decl; \
+ }
+
+ DEF_SS_BUILTIN (enter, ". Ot",
+ BUILT_IN___STRUB_ENTER,
+ (void_type_node, get_qpwmt (), NULL))
+ DEF_SS_BUILTIN (update, ". Wt",
+ BUILT_IN___STRUB_UPDATE,
+ (void_type_node, get_qpwmt (), NULL))
+ DEF_SS_BUILTIN (leave, ". w ",
+ BUILT_IN___STRUB_LEAVE,
+ (void_type_node, get_qpwmt (), NULL))
+
+#undef DEF_SS_BUILTIN
+
+#define DEF_IDENT(NAME) \
+ static inline tree get_ ## NAME () { \
+ static tree identifier = NULL_TREE; \
+ if (!identifier) \
+ identifier = get_identifier (".strub." #NAME); \
+ return identifier; \
+ }
+
+ DEF_IDENT (watermark_ptr)
+ DEF_IDENT (va_list_ptr)
+ DEF_IDENT (apply_args)
+
+#undef DEF_IDENT
+
+ static inline int adjust_at_calls_type (tree);
+ static inline void adjust_at_calls_call (cgraph_edge *, int);
+ static inline void adjust_at_calls_calls (cgraph_node *);
+
+ static inline gimple_seq
+ call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
+ gimple_seq seq = NULL)
+ {
+ tree uwm = get_update ();
+ gcall *update = gimple_build_call (uwm, 1, wmptr);
+ if (node)
+ gimple_set_location (update, DECL_SOURCE_LOCATION (node->decl));
+ gimple_seq_add_stmt (&seq, update);
+ if (node)
+#if !IMPLICIT_CGRAPH_EDGES
+ node->create_edge (cgraph_node::get_create (uwm), update, count, false);
+#else
+ (void)count;
+#endif
+ return seq;
+ }
+
+};
+
+} // anon namespace
+
+typedef hash_set<tree> indirect_parms_t;
+
+static tree
+maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
+{
+ if (DECL_P (op))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (op))
+ {
+ tree ret = gimple_fold_indirect_ref (op);
+ if (!ret)
+ ret = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (op)),
+ op,
+ build_int_cst (TREE_TYPE (op), 0));
+ return ret;
+ }
+ }
+ else if (TREE_CODE (op) == ADDR_EXPR
+ && DECL_P (TREE_OPERAND (op, 0)))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (TREE_OPERAND (op, 0)))
+ return TREE_OPERAND (op, 0);
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_make_indirect (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ indirect_parms_t &indirect_parms = *(indirect_parms_t *)wi->info;
+
+ if (!*op || TYPE_P (*op))
+ {
+ *rec = 0;
+ return NULL_TREE;
+ }
+
+ if (tree repl = maybe_make_indirect (indirect_parms, *op, rec))
+ {
+ *op = repl;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ gimple_stmt_iterator &gsi = *(gimple_stmt_iterator *)wi->info;
+
+ *rec = 0;
+
+ if (!*op || TREE_CODE (*op) != ADDR_EXPR)
+ return NULL_TREE;
+
+ if (!is_gimple_val (*op))
+ {
+ tree ret = force_gimple_operand_gsi (&gsi, *op, true,
+ NULL_TREE, true, GSI_SAME_STMT);
+ gcc_assert (ret != *op);
+ *op = ret;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+build_ref_type_for (tree parm, bool nonaliased = true)
+{
+ gcc_checking_assert (TREE_CODE (parm) == PARM_DECL);
+
+ tree ref_type = build_reference_type (TREE_TYPE (parm));
+
+ if (!nonaliased)
+ return ref_type;
+
+ /* Each PARM turned indirect still points to the distinct memory area at the
+ wrapper, and the reference in unchanging, so we might qualify it, but...
+ const is not really important, since we're only using default defs for the
+ reference parm anyway, and not introducing any defs, and restrict seems to
+ cause trouble. E.g., libgnat/s-concat3.adb:str_concat_3 has memmoves that,
+ if it's wrapped, the memmoves are deleted in dse1. Using a distinct alias
+ set seems to not run afoul of this problem, and it hopefully enables the
+ compiler to tell the pointers do point to objects that are not otherwise
+ aliased. */
+#if 1
+ tree qref_type = build_variant_type_copy (ref_type);
+
+ TYPE_ALIAS_SET (qref_type) = new_alias_set ();
+ record_alias_subset (TYPE_ALIAS_SET (qref_type), get_alias_set (ref_type));
+
+ return qref_type;
+#else
+ tree qref_type = build_qualified_type (ref_type,
+ TYPE_QUAL_RESTRICT
+ | TYPE_QUAL_CONST);
+
+ return qref_type;
+#endif
+}
+
+/* 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)
+{
+#if IMPLICIT_CGRAPH_EDGES
+ return;
+#endif
+
+ cgraph_node *node = cgraph_node::get_create (current_function_decl);
+
+ for (gimple_stmt_iterator gsi = gsi_start (seq);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!is_a <gcall *> (stmt))
+ 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);
+ }
+}
+
+static void
+gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
+{
+ if (!seq)
+ return;
+
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (gimple_has_location (stmt))
+ annotate_all_with_location (seq, gimple_location (stmt));
+
+ gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+ 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;
+ bool nothrow_p = (must_not_throw_p
+ || (call && gimple_call_nothrow_p (call))
+ || (eh_lp <= 0
+ && (TREE_NOTHROW (cfun->decl)
+ || !flag_exceptions)));
+
+ if (noreturn_p && nothrow_p)
+ return;
+
+ /* Don't expect an EH edge if we're not to throw, or if we're not in an EH
+ region yet. */
+ bool no_eh_edge_p = (nothrow_p || !eh_lp);
+ bool must_end_bb = stmt_ends_bb_p (stmt);
+
+ edge eft = NULL, eeh = NULL;
+ if (must_end_bb && !(noreturn_p && no_eh_edge_p))
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+
+ edge e;
+ edge_iterator ei;
+ FOR_EACH_EDGE (e, ei, gsi_bb (gsi)->succs)
+ {
+ if ((e->flags & EDGE_EH))
+ {
+ gcc_checking_assert (!eeh);
+ eeh = e;
+#if !CHECKING_P
+ if (eft || noreturn_p)
+ break;
+#endif
+ }
+ if ((e->flags & EDGE_FALLTHRU))
+ {
+ gcc_checking_assert (!eft);
+ eft = e;
+#if !CHECKING_P
+ if (eeh || no_eh_edge_p)
+ break;
+#endif
+ }
+ }
+
+ gcc_checking_assert (!(eft && (eft->flags & EDGE_FALLTHRU))
+ == noreturn_p);
+ gcc_checking_assert (!(eeh && (eeh->flags & EDGE_EH))
+ == no_eh_edge_p);
+ gcc_checking_assert (eft != eeh);
+ }
+
+ if (!noreturn_p)
+ {
+ gimple_seq nseq = nothrow_p ? seq : gimple_seq_copy (seq);
+
+ if (must_end_bb)
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+ add_call_edges_for_seq (nseq, eft->count ());
+ gsi_insert_seq_on_edge_immediate (eft, nseq);
+ }
+ else
+ {
+ add_call_edges_for_seq (nseq, gsi_bb (gsi)->count);
+ gsi_insert_seq_after (&gsi, nseq, GSI_SAME_STMT);
+ }
+ }
+
+ if (nothrow_p)
+ return;
+
+ if (eh_lp)
+ {
+ add_call_edges_for_seq (seq, eeh->count ());
+ gsi_insert_seq_on_edge_immediate (eeh, seq);
+ return;
+ }
+
+ /* A throwing call may appear within a basic block in a function that doesn't
+ have any EH regions. We're going to add a cleanup if so, therefore the
+ block will have to be split. */
+ basic_block bb = gsi_bb (gsi);
+ if (!gsi_one_before_end_p (gsi))
+ split_block (bb, stmt);
+
+ /* Create a new block for the EH cleanup. */
+ basic_block bb_eh_cleanup = create_empty_bb (bb);
+ if (dom_info_available_p (CDI_DOMINATORS))
+ set_immediate_dominator (CDI_DOMINATORS, bb_eh_cleanup, bb);
+ if (current_loops)
+ add_bb_to_loop (bb_eh_cleanup, current_loops->tree_root);
+
+ /* Make the new block an EH cleanup for the call. */
+ eh_region new_r = gen_eh_region_cleanup (NULL);
+ eh_landing_pad lp = gen_eh_landing_pad (new_r);
+ tree label = gimple_block_label (bb_eh_cleanup);
+ lp->post_landing_pad = label;
+ EH_LANDING_PAD_NR (label) = lp->index;
+ add_stmt_to_eh_lp (stmt, lp->index);
+
+ /* Add the cleanup code to the EH cleanup block. */
+ gsi = gsi_after_labels (bb_eh_cleanup);
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
+
+ /* And then propagate the exception further. */
+ gresx *resx = gimple_build_resx (new_r->index);
+ if (gimple_has_location (stmt))
+ gimple_set_location (resx, gimple_location (stmt));
+ gsi_insert_before (&gsi, resx, GSI_SAME_STMT);
+
+ /* Finally, wire the EH cleanup block into the CFG. */
+ make_eh_edges (stmt);
+ add_call_edges_for_seq (seq, single_pred_edge (bb_eh_cleanup)->count ());
+}
+
+/* Copy the attribute list at *ATTRS, minus any NAME attributes, leaving
+ shareable trailing nodes alone. */
+
+static inline void
+remove_named_attribute_unsharing (const char *name, tree *attrs)
+{
+ while (tree found = lookup_attribute (name, *attrs))
+ {
+ /* Copy nodes up to the next NAME attribute. */
+ while (*attrs != found)
+ {
+ *attrs = tree_cons (TREE_PURPOSE (*attrs),
+ TREE_VALUE (*attrs),
+ TREE_CHAIN (*attrs));
+ attrs = &TREE_CHAIN (*attrs);
+ }
+ /* Then drop it. */
+ gcc_checking_assert (*attrs == found);
+ *attrs = TREE_CHAIN (*attrs);
+ }
+}
+
+static int last_cgraph_order;
+
+static void
+ipa_strub_set_mode_for_new_functions ()
+{
+ if (symtab->order == last_cgraph_order)
+ return;
+
+ cgraph_node *node;
+
+ /* 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. */
+ for (int aliases = 0; aliases <= 1; aliases++)
+ FOR_EACH_FUNCTION (node)
+ {
+ if (!node->alias != !aliases)
+ continue;
+
+ /* Already done. */
+ if (node->order < last_cgraph_order)
+ continue;
+
+ set_strub_mode (node);
+ }
+
+ last_cgraph_order = symtab->order;
+}
+
+/* Return FALSE if NODE is a strub context, and TRUE otherwise. */
+bool
+strub_splittable_p (cgraph_node *node)
+{
+ switch (get_strub_mode (node))
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_AT_CALLS_OPT:
+ case STRUB_INLINABLE:
+ case STRUB_INTERNAL:
+ case STRUB_WRAPPER:
+ return false;
+
+ case STRUB_CALLABLE:
+ case STRUB_DISABLED:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ return true;
+}
+
+/* Return the PARM_DECL of the incoming watermark pointer, if there is one. */
+tree
+strub_watermark_parm (tree fndecl)
+{
+ switch (get_strub_mode_from_decl (fndecl))
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_AT_CALLS_OPT:
+ break;
+
+ case STRUB_INTERNAL:
+ case STRUB_WRAPPER:
+ case STRUB_CALLABLE:
+ case STRUB_DISABLED:
+ case STRUB_INLINABLE:
+ return NULL_TREE;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ for (tree parm = DECL_ARGUMENTS (fndecl); parm; parm = DECL_CHAIN (parm))
+ /* The type (variant) compare finds the parameter even in a just-created
+ clone, before we set its name, but the type-based compare doesn't work
+ during builtin expansion within the lto compiler, because we'll have
+ created a separate variant in that run. */
+ if (TREE_TYPE (parm) == pass_ipa_strub::get_qpwmt ()
+ || DECL_NAME (parm) == pass_ipa_strub::get_watermark_ptr ())
+ return parm;
+
+ gcc_unreachable ();
+}
+
+/* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
+ hasn't been added yet. Return the named argument count. */
+int
+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 already 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);
+ }
+
+ /* 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);
+
+#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 we're already within a strub context, pass on the incoming watermark
+ pointer, and omit the enter and leave calls around the modified call, as an
+ optimization, or as a means to satisfy a tail-call requirement. */
+ tree swmp = ((optimize_size || optimize > 2
+ || gimple_call_must_tail_p (ocall)
+ || (optimize == 2 && gimple_call_tail_p (ocall)))
+ ? strub_watermark_parm (e->caller->decl)
+ : NULL_TREE);
+ bool omit_own_watermark = swmp;
+ tree swm = NULL_TREE;
+ if (!omit_own_watermark)
+ {
+ swm = create_tmp_var (get_wmt (), ".strub.watermark");
+ TREE_ADDRESSABLE (swm) = true;
+ swmp = build1 (ADDR_EXPR, get_pwmt (), swm);
+
+ /* Initialize the watermark before the call. */
+ tree enter = get_enter ();
+ gcall *stptr = gimple_build_call (enter, 1,
+ unshare_expr (swmp));
+ if (gimple_has_location (ocall))
+ gimple_set_location (stptr, gimple_location (ocall));
+ 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)))
+ {
+ if (!swm)
+ swm = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (swmp)),
+ swmp,
+ build_int_cst (TREE_TYPE (swmp), 0));
+
+ vec<tree, va_gc> *inputs = NULL;
+ vec<tree, va_gc> *outputs = NULL;
+ vec_safe_push (outputs,
+ build_tree_list
+ (build_tree_list
+ (NULL_TREE, build_string (2, "=m")),
+ unshare_expr (swm)));
+ vec_safe_push (inputs,
+ build_tree_list
+ (build_tree_list
+ (NULL_TREE, build_string (1, "m")),
+ unshare_expr (swm)));
+ gasm *forcemod = gimple_build_asm_vec ("", inputs, outputs,
+ NULL, NULL);
+ gimple_seq_add_stmt (&seq, forcemod);
+
+ /* If the call will be assumed to not even read the watermark,
+ make sure it is already in memory before the call. */
+ if ((gimple_call_flags (wrcall) & ECF_CONST))
+ {
+ vec<tree, va_gc> *inputs = NULL;
+ vec_safe_push (inputs,
+ build_tree_list
+ (build_tree_list
+ (NULL_TREE, build_string (1, "m")),
+ unshare_expr (swm)));
+ gasm *force_store = gimple_build_asm_vec ("", inputs, NULL,
+ NULL, NULL);
+ if (gimple_has_location (wrcall))
+ gimple_set_location (force_store, gimple_location (wrcall));
+ gsi_insert_before (&gsi, force_store, GSI_SAME_STMT);
+ }
+ }
+#endif
+
+ if (!omit_own_watermark)
+ {
+ gcall *sleave = gimple_build_call (get_leave (), 1,
+ unshare_expr (swmp));
+ gimple_seq_add_stmt (&seq, sleave);
+
+ gassign *clobber = gimple_build_assign (swm,
+ build_clobber
+ (TREE_TYPE (swm)));
+ gimple_seq_add_stmt (&seq, clobber);
+ }
+
+ gsi_insert_finally_seq_after_call (gsi, seq);
+}
+
+void
+pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
+{
+ /* Adjust unknown-callee indirect calls with STRUB_AT_CALLS types within
+ onode. */
+ if (node->indirect_calls)
+ {
+ push_cfun (DECL_STRUCT_FUNCTION (node->decl));
+ for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
+ {
+ gcc_checking_assert (e->indirect_unknown_callee);
+
+ tree callee_fntype = gimple_call_fntype (e->call_stmt);
+ enum strub_mode callee_mode
+ = get_strub_mode_from_type (callee_fntype);
+
+ if (callee_mode != STRUB_AT_CALLS
+ && callee_mode != STRUB_AT_CALLS_OPT)
+ continue;
+
+ int named_args = adjust_at_calls_type (callee_fntype);
+
+ adjust_at_calls_call (e, named_args);
+ }
+ pop_cfun ();
+ }
+
+ if (node->callees)
+ {
+ push_cfun (DECL_STRUCT_FUNCTION (node->decl));
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ gcc_checking_assert (!e->indirect_unknown_callee);
+
+ enum strub_mode callee_mode = get_strub_mode (e->callee);
+
+ if (callee_mode != STRUB_AT_CALLS
+ && callee_mode != STRUB_AT_CALLS_OPT)
+ continue;
+
+ int named_args = adjust_at_calls_type (TREE_TYPE (e->callee->decl));
+
+ adjust_at_calls_call (e, named_args);
+ }
+ pop_cfun ();
+ }
+}
+
+unsigned int
+pass_ipa_strub_mode::execute (function *)
+{
+ last_cgraph_order = 0;
+ ipa_strub_set_mode_for_new_functions ();
+
+ /* Verify before any inlining or other transformations. */
+ verify_strub ();
+
+ return 0;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub_mode (gcc::context *ctxt)
+{
+ return new pass_ipa_strub_mode (ctxt);
+}
+
+unsigned int
+pass_ipa_strub::execute (function *)
+{
+ cgraph_node *onode;
+
+ 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
+ || mode == STRUB_AT_CALLS_OPT)
+ {
+ /* 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. */
+ bool aliased_parms = (onode->alias && *pargs
+ && DECL_CONTEXT (*pargs) != onode->decl);
+
+ 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_qpwmt ());
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_ARG_TYPE (wmptr) = get_qpwmt ();
+ DECL_CONTEXT (wmptr) = onode->decl;
+ TREE_USED (wmptr) = 1;
+ DECL_CHAIN (wmptr) = *pargs;
+ *pargs = wmptr;
+
+ 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));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca)
+ {
+ 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);
+
+ if (!is_gimple_call (stmt))
+ continue;
+
+ gcall *call = as_a <gcall *> (stmt);
+
+ if (gimple_alloca_call_p (call))
+ {
+ /* Capture stack growth. */
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)
+ ->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ }
+ }
+
+ pop_cfun ();
+
+#if 0
+ compute_fn_summary (onode, true);
+#endif
+ }
+ }
+
+ FOR_EACH_FUNCTION (onode)
+ {
+ if (!onode->has_gimple_body_p ())
+ continue;
+
+ enum strub_mode mode = get_strub_mode (onode);
+
+ if (mode != STRUB_INTERNAL)
+ {
+ adjust_at_calls_calls (onode);
+ 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);
+
+ vec<ipa_adjusted_param, va_gc> *nparms = NULL;
+ unsigned j = 0;
+ {
+ // The following loop copied from ipa-split.c:split_function.
+ for (tree parm = DECL_ARGUMENTS (onode->decl);
+ parm; parm = DECL_CHAIN (parm), j++)
+ {
+ ipa_adjusted_param adj = {};
+ adj.op = IPA_PARAM_OP_COPY;
+ adj.base_index = j;
+ adj.prev_clone_index = j;
+ vec_safe_push (nparms, adj);
+ }
+
+ if (apply_args)
+ {
+ ipa_adjusted_param aaadj = {};
+ aaadj.op = IPA_PARAM_OP_NEW;
+ aaadj.type = get_qptr ();
+ vec_safe_push (nparms, aaadj);
+ }
+
+ if (is_stdarg)
+ {
+ ipa_adjusted_param vladj = {};
+ vladj.op = IPA_PARAM_OP_NEW;
+ vladj.type = get_qpvalst ();
+ vec_safe_push (nparms, vladj);
+ }
+
+ ipa_adjusted_param wmadj = {};
+ wmadj.op = IPA_PARAM_OP_NEW;
+ wmadj.type = get_qpwmt ();
+ vec_safe_push (nparms, wmadj);
+ }
+ ipa_param_adjustments adj (nparms, -1, false);
+
+ cgraph_node *nnode = onode->create_version_clone_with_body
+ (auto_vec<cgraph_edge *> (0),
+ NULL, &adj, NULL, NULL, "strub", NULL);
+
+ if (!nnode)
+ {
+ error_at (DECL_SOURCE_LOCATION (onode->decl),
+ "failed to split %qD for %<strub%>",
+ onode->decl);
+ continue;
+ }
+
+ onode->split_part = true;
+ if (onode->calls_comdat_local)
+ nnode->add_to_same_comdat_group (onode);
+
+ gcc_checking_assert (!DECL_STRUCT_FUNCTION (nnode->decl)->stdarg);
+
+ set_strub_mode_to (onode, STRUB_WRAPPER);
+ set_strub_mode_to (nnode, STRUB_WRAPPED);
+
+ adjust_at_calls_calls (nnode);
+
+ /* Decide which of the wrapped function's parms we want to turn into
+ references to the argument passed to the wrapper. In general, we want to
+ copy small arguments, and avoid copying large ones. Variable-sized array
+ lengths given by other arguments, as in 20020210-1.c, would lead to
+ problems if passed by value, after resetting the original function and
+ dropping the length computation; passing them by reference works.
+ DECL_BY_REFERENCE is *not* a substitute for this: it involves copying
+ anyway, but performed at the caller. */
+ indirect_parms_t indirect_nparms (3, false);
+ unsigned adjust_ftype = 0;
+ unsigned named_args = 0;
+ for (tree parm = DECL_ARGUMENTS (onode->decl),
+ nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (TREE_TYPE (nnode->decl));
+ parm;
+ named_args++,
+ parm = DECL_CHAIN (parm),
+ nparm = DECL_CHAIN (nparm),
+ nparmt = nparmt ? TREE_CHAIN (nparmt) : NULL_TREE)
+ if (!(0 /* DECL_BY_REFERENCE (narg) */
+ || is_gimple_reg_type (TREE_TYPE (nparm))
+ || VECTOR_TYPE_P (TREE_TYPE (nparm))
+ || TREE_CODE (TREE_TYPE (nparm)) == COMPLEX_TYPE
+ || (tree_fits_uhwi_p (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ && (tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ <= 4 * UNITS_PER_WORD))))
+ {
+ indirect_nparms.add (nparm);
+
+ /* ??? Is there any case in which it is not safe to suggest the parms
+ turned indirect don't alias anything else? They are distinct,
+ unaliased memory in the wrapper, and the wrapped can't possibly
+ take pointers into them because none of the pointers passed to the
+ wrapper can alias other incoming parameters passed by value, even
+ if with transparent reference, and the wrapper doesn't take any
+ extra parms that could point into wrapper's parms. So we can
+ probably drop the TREE_ADDRESSABLE and keep the true. */
+ tree ref_type = build_ref_type_for (nparm,
+ true
+ || !TREE_ADDRESSABLE (parm));
+
+ DECL_ARG_TYPE (nparm) = TREE_TYPE (nparm) = ref_type;
+ relayout_decl (nparm);
+ TREE_ADDRESSABLE (nparm) = 0;
+ DECL_BY_REFERENCE (nparm) = 0;
+#if FOR_GCC_11P
+ DECL_NOT_GIMPLE_REG_P (nparm) = 0;
+#else
+ DECL_GIMPLE_REG_P (nparm) = 1;
+#endif
+ /* ??? This avoids mismatches in debug info bind stmts in
+ e.g. a-chahan . */
+ DECL_ABSTRACT_ORIGIN (nparm) = NULL;
+
+ if (nparmt)
+ adjust_ftype++;
+ }
+
+ /* Also adjust the wrapped function type, if needed. */
+ if (adjust_ftype)
+ {
+ tree nftype = TREE_TYPE (nnode->decl);
+
+ /* We always add at least one argument at the end of the signature, when
+ cloning the function, so we don't expect to need to duplicate the
+ type here. */
+ gcc_checking_assert (TYPE_ARG_TYPES (nftype)
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+
+#if HAVE_ATTR_FNSPEC
+ /* Check that fnspec still works for the modified function signature,
+ and drop it otherwise. */
+ bool drop_fnspec = false;
+ tree fnspec = lookup_attribute ("fn spec", TYPE_ATTRIBUTES (nftype));
+ attr_fnspec spec = fnspec ? attr_fnspec (fnspec) : attr_fnspec ("");
+
+ unsigned retcopy;
+ if (!(fnspec && spec.returns_arg (&retcopy)))
+ retcopy = (unsigned) -1;
+
+ unsigned i = 0;
+#endif
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (nftype);
+ adjust_ftype > 0;
+#if HAVE_ATTR_FNSPEC
+ i++,
+#endif
+ nparm = DECL_CHAIN (nparm), nparmt = TREE_CHAIN (nparmt))
+ if (indirect_nparms.contains (nparm))
+ {
+ TREE_VALUE (nparmt) = TREE_TYPE (nparm);
+ adjust_ftype--;
+
+#if HAVE_ATTR_FNSPEC
+ if (fnspec && !drop_fnspec)
+ {
+ if (i == retcopy)
+ drop_fnspec = true;
+ else if (spec.arg_specified_p (i))
+ {
+ /* Properties that apply to pointers only must not be
+ present, because we don't make pointers further
+ indirect. */
+ gcc_checking_assert
+ (!spec.arg_max_access_size_given_by_arg_p (i, NULL));
+ gcc_checking_assert (!spec.arg_copied_to_arg_p (i, NULL));
+
+ /* Any claim of direct access only is invalidated by
+ adding an indirection level. */
+ if (spec.arg_direct_p (i))
+ drop_fnspec = true;
+
+ /* If there's a claim the argument is not read from, the
+ added indirection invalidates it: if the argument is
+ used at all, then the pointer will necessarily be
+ read. */
+ if (!spec.arg_maybe_read_p (i)
+ && spec.arg_used_p (i))
+ drop_fnspec = true;
+ }
+ }
+#endif
+ }
+
+#if HAVE_ATTR_FNSPEC
+ /* ??? Maybe we could adjust it instead. */
+ if (drop_fnspec)
+ remove_named_attribute_unsharing ("fn spec",
+ &TYPE_ATTRIBUTES (nftype));
+#endif
+
+ TREE_TYPE (nnode->decl) = nftype;
+ }
+
+#if ATTR_FNSPEC_DECONST_WATERMARK
+ {
+ int flags = flags_from_decl_or_type (nnode->decl);
+ tree fnspec = lookup_attribute ("fn spec", TREE_TYPE (nnode->decl));
+
+ if ((flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS)) || fnspec)
+ {
+ size_t xargs = 1 + int (is_stdarg) + int (apply_args);
+ 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! */
+ bool no_writes_p = true;
+ if (fnspec)
+ {
+ tree fnspecstr = TREE_VALUE (TREE_VALUE (fnspec));
+ curlen = TREE_STRING_LENGTH (fnspecstr);
+ memcpy (nspec, TREE_STRING_POINTER (fnspecstr), curlen);
+ if (!(flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS))
+ && curlen >= 2
+ && nspec[1] != 'c' && nspec[1] != 'C'
+ && nspec[1] != 'p' && nspec[1] != 'P')
+ no_writes_p = false;
+ }
+ if (!curlen)
+ {
+ nspec[curlen++] = '.';
+ nspec[curlen++] = ((flags & ECF_CONST)
+ ? 'c'
+ : (flags & ECF_PURE)
+ ? 'p'
+ : ' ');
+ }
+ while (curlen < tgtlen - 2 * xargs)
+ {
+ nspec[curlen++] = '.';
+ nspec[curlen++] = ' ';
+ }
+
+ /* These extra args are unlikely to be present in const or pure
+ functions. It's conceivable that a function that takes variable
+ arguments, or that passes its arguments on to another function,
+ could be const or pure, but it would not modify the arguments, and,
+ being pure or const, it couldn't possibly modify or even access
+ memory referenced by them. But it can read from these internal
+ data structures created by the wrapper, and from any
+ argument-passing memory referenced by them, so we denote the
+ possibility of reading from multiple levels of indirection, but
+ only of reading because const/pure. */
+ if (apply_args)
+ {
+ nspec[curlen++] = 'r';
+ nspec[curlen++] = ' ';
+ }
+ if (is_stdarg)
+ {
+ nspec[curlen++] = (no_writes_p ? 'r' : '.');
+ nspec[curlen++] = (no_writes_p ? 't' : ' ');
+ }
+
+ nspec[curlen++] = 'W';
+ nspec[curlen++] = 't';
+
+ /* The type has already been copied before adding parameters. */
+ gcc_checking_assert (TYPE_ARG_TYPES (TREE_TYPE (nnode->decl))
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+ TYPE_ATTRIBUTES (TREE_TYPE (nnode->decl))
+ = tree_cons (get_identifier ("fn spec"),
+ build_tree_list (NULL_TREE,
+ build_string (tgtlen, nspec)),
+ TYPE_ATTRIBUTES (TREE_TYPE (nnode->decl)));
+ }
+ }
+#endif
+
+ {
+ tree decl = onode->decl;
+ cgraph_node *target = nnode;
+
+ { // copied from create_wrapper
+
+ /* Preserve DECL_RESULT so we get right by reference flag. */
+ tree decl_result = DECL_RESULT (decl);
+
+ /* Remove the function's body but keep arguments to be reused
+ for thunk. */
+ onode->release_body (true);
+ onode->reset ();
+
+ DECL_UNINLINABLE (decl) = false;
+ DECL_RESULT (decl) = decl_result;
+ DECL_INITIAL (decl) = NULL;
+ allocate_struct_function (decl, false);
+ set_cfun (NULL);
+
+ /* Turn alias into thunk and expand it into GIMPLE representation. */
+ onode->definition = true;
+
+#if FOR_GCC_11P
+ thunk_info::get_create (onode);
+ onode->thunk = true;
+#else
+ memset (&onode->thunk, 0, sizeof (cgraph_thunk_info));
+ onode->thunk.thunk_p = true;
+ onode->thunk.alias = target->decl;
+#endif
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (target, NULL, onode->count);
+#endif
+ onode->callees->can_throw_external = !TREE_NOTHROW (target->decl);
+
+ tree arguments = DECL_ARGUMENTS (decl);
+
+ while (arguments)
+ {
+ TREE_ADDRESSABLE (arguments) = false;
+ arguments = TREE_CHAIN (arguments);
+ }
+
+ {
+ tree alias = onode->callees->callee->decl;
+ tree thunk_fndecl = decl;
+ tree a;
+
+ int nxargs = 1 + is_stdarg + apply_args;
+
+ { // Simplified from expand_thunk.
+ tree restype;
+ basic_block bb, then_bb, else_bb, return_bb;
+ gimple_stmt_iterator bsi;
+ int nargs = 0;
+ tree arg;
+ int i;
+ tree resdecl;
+ tree restmp = NULL;
+
+ gcall *call;
+ greturn *ret;
+ bool alias_is_noreturn = TREE_THIS_VOLATILE (alias);
+
+ a = DECL_ARGUMENTS (thunk_fndecl);
+
+ current_function_decl = thunk_fndecl;
+
+#if FOR_GCC_11P
+ /* Ensure thunks are emitted in their correct sections. */
+ resolve_unique_section (thunk_fndecl, 0,
+ flag_function_sections);
+#endif
+
+ bitmap_obstack_initialize (NULL);
+
+ /* Build the return declaration for the function. */
+ restype = TREE_TYPE (TREE_TYPE (thunk_fndecl));
+ if (DECL_RESULT (thunk_fndecl) == NULL_TREE)
+ {
+ resdecl = build_decl (input_location, RESULT_DECL, 0, restype);
+ DECL_ARTIFICIAL (resdecl) = 1;
+ DECL_IGNORED_P (resdecl) = 1;
+ DECL_CONTEXT (resdecl) = thunk_fndecl;
+ DECL_RESULT (thunk_fndecl) = resdecl;
+ }
+ else
+ resdecl = DECL_RESULT (thunk_fndecl);
+
+ profile_count cfg_count = onode->count;
+ if (!cfg_count.initialized_p ())
+ cfg_count = profile_count::from_gcov_type (BB_FREQ_MAX).guessed_local ();
+
+ bb = then_bb = else_bb = return_bb
+ = init_lowered_empty_function (thunk_fndecl, true, cfg_count);
+
+ bsi = gsi_start_bb (bb);
+
+ /* Build call to the function being thunked. */
+ if (!VOID_TYPE_P (restype)
+ && (!alias_is_noreturn
+ || TREE_ADDRESSABLE (restype)
+ || TREE_CODE (TYPE_SIZE_UNIT (restype)) != INTEGER_CST))
+ {
+ if (DECL_BY_REFERENCE (resdecl))
+ {
+ restmp = gimple_fold_indirect_ref (resdecl);
+ if (!restmp)
+ restmp = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (resdecl)),
+ resdecl,
+ build_int_cst (TREE_TYPE (resdecl), 0));
+ }
+ else if (!is_gimple_reg_type (restype))
+ {
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl)))
+ {
+ restmp = resdecl;
+
+ if (VAR_P (restmp))
+ {
+ add_local_decl (cfun, restmp);
+ BLOCK_VARS (DECL_INITIAL (current_function_decl))
+ = restmp;
+ }
+ }
+ else
+ restmp = create_tmp_var (restype, "retval");
+ }
+ else
+ restmp = create_tmp_reg (restype, "retval");
+ }
+
+ for (arg = a; arg; arg = DECL_CHAIN (arg))
+ nargs++;
+ auto_vec<tree> vargs (nargs + nxargs);
+ i = 0;
+ arg = a;
+
+ if (nargs)
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl);
+ i < nargs;
+ i++, arg = DECL_CHAIN (arg), nparm = DECL_CHAIN (nparm))
+ {
+ tree save_arg = arg;
+ tree tmp = arg;
+
+ /* Arrange to pass indirectly the parms, if we decided to do
+ so, and revert its type in the wrapper. */
+ if (indirect_nparms.contains (nparm))
+ {
+ tree ref_type = TREE_TYPE (nparm);
+ TREE_ADDRESSABLE (arg) = true;
+ tree addr = build1 (ADDR_EXPR, ref_type, arg);
+ tmp = arg = addr;
+ }
+#if ! FOR_GCC_11P
+ else if (VECTOR_TYPE_P (TREE_TYPE (arg))
+ || TREE_CODE (TREE_TYPE (arg)) == COMPLEX_TYPE)
+ DECL_GIMPLE_REG_P (arg) = 1;
+#else
+ else
+ DECL_NOT_GIMPLE_REG_P (arg) = 0;
+#endif
+
+ /* Convert the argument back to the type used by the calling
+ conventions, e.g. a non-prototyped float type is passed as
+ double, as in 930603-1.c, and needs to be converted back to
+ double to be passed on unchanged to the wrapped
+ function. */
+ if (TREE_TYPE (nparm) != DECL_ARG_TYPE (nparm))
+ arg = fold_convert (DECL_ARG_TYPE (nparm), arg);
+
+ if (!is_gimple_val (arg))
+ {
+ tmp = create_tmp_reg (TYPE_MAIN_VARIANT
+ (TREE_TYPE (arg)), "arg");
+ gimple *stmt = gimple_build_assign (tmp, arg);
+ gsi_insert_after (&bsi, stmt, GSI_NEW_STMT);
+ }
+ vargs.quick_push (tmp);
+ arg = save_arg;
+ }
+ /* These strub arguments are adjusted later. */
+ if (apply_args)
+ vargs.quick_push (null_pointer_node);
+ if (is_stdarg)
+ vargs.quick_push (null_pointer_node);
+ vargs.quick_push (null_pointer_node);
+ call = gimple_build_call_vec (build_fold_addr_expr_loc (0, alias),
+ vargs);
+ onode->callees->call_stmt = call;
+ // gimple_call_set_from_thunk (call, true);
+ if (DECL_STATIC_CHAIN (alias))
+ {
+ tree p = DECL_STRUCT_FUNCTION (alias)->static_chain_decl;
+ tree type = TREE_TYPE (p);
+ tree decl = build_decl (DECL_SOURCE_LOCATION (thunk_fndecl),
+ PARM_DECL, create_tmp_var_name ("CHAIN"),
+ type);
+ DECL_ARTIFICIAL (decl) = 1;
+ DECL_IGNORED_P (decl) = 1;
+ TREE_USED (decl) = 1;
+ DECL_CONTEXT (decl) = thunk_fndecl;
+ DECL_ARG_TYPE (decl) = type;
+ TREE_READONLY (decl) = 1;
+
+ struct function *sf = DECL_STRUCT_FUNCTION (thunk_fndecl);
+ sf->static_chain_decl = decl;
+
+ gimple_call_set_chain (call, decl);
+ }
+
+ /* Return slot optimization is always possible and in fact required to
+ return values with DECL_BY_REFERENCE. */
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl))
+ && (!is_gimple_reg_type (TREE_TYPE (resdecl))
+ || DECL_BY_REFERENCE (resdecl)))
+ gimple_call_set_return_slot_opt (call, true);
+
+ if (restmp)
+ {
+ gimple_call_set_lhs (call, restmp);
+ gcc_assert (useless_type_conversion_p (TREE_TYPE (restmp),
+ TREE_TYPE (TREE_TYPE (alias))));
+ }
+ gsi_insert_after (&bsi, call, GSI_NEW_STMT);
+ if (!alias_is_noreturn)
+ {
+ /* Build return value. */
+ if (!DECL_BY_REFERENCE (resdecl))
+ ret = gimple_build_return (restmp);
+ else
+ ret = gimple_build_return (resdecl);
+
+ gsi_insert_after (&bsi, ret, GSI_NEW_STMT);
+ }
+ else
+ {
+ remove_edge (single_succ_edge (bb));
+ }
+
+ cfun->gimple_df->in_ssa_p = true;
+ update_max_bb_count ();
+ profile_status_for_fn (cfun)
+ = cfg_count.initialized_p () && cfg_count.ipa_p ()
+ ? PROFILE_READ : PROFILE_GUESSED;
+#if FOR_GCC_11P
+ /* FIXME: C++ FE should stop setting TREE_ASM_WRITTEN on thunks. */
+ // TREE_ASM_WRITTEN (thunk_fndecl) = false;
+#endif
+ delete_unreachable_blocks ();
+ update_ssa (TODO_update_ssa);
+ checking_verify_flow_info ();
+ free_dominance_info (CDI_DOMINATORS);
+
+ /* Since we want to emit the thunk, we explicitly mark its name as
+ referenced. */
+#if FOR_GCC_11P
+ onode->thunk = false;
+#else
+ onode->thunk.thunk_p = false;
+#endif
+ onode->lowered = true;
+ bitmap_obstack_release (NULL);
+ }
+ current_function_decl = NULL;
+ set_cfun (NULL);
+ }
+
+#if FOR_GCC_11P
+ thunk_info::remove (onode);
+#endif
+
+ // some more of create_wrapper at the end of the next block.
+ }
+ }
+
+ {
+ tree aaval = NULL_TREE;
+ tree vaptr = NULL_TREE;
+ tree wmptr = NULL_TREE;
+ for (tree arg = DECL_ARGUMENTS (nnode->decl); arg; arg = DECL_CHAIN (arg))
+ {
+ aaval = vaptr;
+ vaptr = wmptr;
+ wmptr = arg;
+ }
+
+ if (!apply_args)
+ aaval = NULL_TREE;
+ /* The trailing args are [apply_args], [va_list_ptr], and
+ watermark. If we don't have a va_list_ptr, the penultimate
+ argument is apply_args.
+ */
+ else if (!is_stdarg)
+ aaval = vaptr;
+
+ if (!is_stdarg)
+ vaptr = NULL_TREE;
+
+ DECL_NAME (wmptr) = get_watermark_ptr ();
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_IGNORED_P (wmptr) = 1;
+ TREE_USED (wmptr) = 1;
+
+ if (is_stdarg)
+ {
+ DECL_NAME (vaptr) = get_va_list_ptr ();
+ DECL_ARTIFICIAL (vaptr) = 1;
+ DECL_IGNORED_P (vaptr) = 1;
+ TREE_USED (vaptr) = 1;
+ }
+
+ if (apply_args)
+ {
+ DECL_NAME (aaval) = get_apply_args ();
+ DECL_ARTIFICIAL (aaval) = 1;
+ DECL_IGNORED_P (aaval) = 1;
+ TREE_USED (aaval) = 1;
+ }
+
+ push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ bool any_indirect = !indirect_nparms.is_empty ();
+
+ if (any_indirect)
+ {
+ 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);
+
+ 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
+ || is_stdarg || apply_args)
+ for (cgraph_edge *e = nnode->callees, *enext; e; e = enext)
+ {
+ gcall *call = e->call_stmt;
+ gimple_stmt_iterator gsi = gsi_for_stmt (call);
+ tree fndecl = e->callee->decl;
+
+ enext = e->next_callee;
+
+ if (gimple_alloca_call_p (call))
+ {
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ else if (fndecl && is_stdarg
+ && fndecl_built_in_p (fndecl, BUILT_IN_VA_START))
+ {
+ /* Using a non-default stdarg ABI makes the function ineligible
+ for internal strub. */
+ gcc_checking_assert (builtin_decl_explicit (BUILT_IN_VA_START)
+ == fndecl);
+ tree bvacopy = builtin_decl_explicit (BUILT_IN_VA_COPY);
+ gimple_call_set_fndecl (call, bvacopy);
+ tree arg = vaptr;
+ /* The va_copy source must be dereferenced, unless it's an array
+ type, that would have decayed to a pointer. */
+ if (TREE_CODE (TREE_TYPE (TREE_TYPE (vaptr))) != ARRAY_TYPE)
+ {
+ arg = gimple_fold_indirect_ref (vaptr);
+ if (!arg)
+ arg = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (vaptr)),
+ vaptr,
+ build_int_cst (TREE_TYPE (vaptr), 0));
+ }
+ gimple_call_set_arg (call, 1, arg);
+ update_stmt (call);
+ e->redirect_callee (cgraph_node::get_create (bvacopy));
+ }
+ else if (fndecl && apply_args
+ && fndecl_built_in_p (fndecl, BUILT_IN_APPLY_ARGS))
+ {
+ tree lhs = gimple_call_lhs (call);
+ gimple *assign = (lhs
+ ? gimple_build_assign (lhs, aaval)
+ : gimple_build_nop ());
+ gsi_replace (&gsi, assign, true);
+ cgraph_edge::remove (e);
+ }
+ }
+
+ { // a little more copied from create_wrapper
+
+ /* Inline summary set-up. */
+ nnode->analyze ();
+ // inline_analyze_function (nnode);
+ }
+
+ pop_cfun ();
+ }
+
+ {
+ 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));
+
+ 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));
+ gimple_set_location (stptr, gimple_location (wrcall));
+ gsi_insert_before (&gsi, stptr, GSI_SAME_STMT);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (enter),
+ stptr, gsi_bb (gsi)->count, false);
+#endif
+
+ int nargs = gimple_call_num_args (wrcall);
+
+ gimple_seq seq = NULL;
+
+ if (apply_args)
+ {
+ tree aalst = create_tmp_var (ptr_type_node, ".strub.apply_args");
+ tree bappargs = builtin_decl_explicit (BUILT_IN_APPLY_ARGS);
+ gcall *appargs = gimple_build_call (bappargs, 0);
+ gimple_call_set_lhs (appargs, aalst);
+ gimple_set_location (appargs, gimple_location (wrcall));
+ gsi_insert_before (&gsi, appargs, GSI_SAME_STMT);
+ gimple_call_set_arg (wrcall, nargs - 2 - is_stdarg, aalst);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bappargs),
+ appargs, gsi_bb (gsi)->count, false);
+#endif
+ }
+
+ if (is_stdarg)
+ {
+ tree valst = create_tmp_var (va_list_type_node, ".strub.va_list");
+ TREE_ADDRESSABLE (valst) = true;
+ tree vaptr = build1 (ADDR_EXPR,
+ build_pointer_type (va_list_type_node),
+ valst);
+ gimple_call_set_arg (wrcall, nargs - 2, unshare_expr (vaptr));
+
+ tree bvastart = builtin_decl_explicit (BUILT_IN_VA_START);
+ gcall *vastart = gimple_build_call (bvastart, 2,
+ unshare_expr (vaptr),
+ integer_zero_node);
+ gimple_set_location (vastart, gimple_location (wrcall));
+ gsi_insert_before (&gsi, vastart, GSI_SAME_STMT);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bvastart),
+ vastart, gsi_bb (gsi)->count, false);
+#endif
+
+ tree bvaend = builtin_decl_explicit (BUILT_IN_VA_END);
+ gcall *vaend = gimple_build_call (bvaend, 1, unshare_expr (vaptr));
+ gimple_set_location (vaend, gimple_location (wrcall));
+ gimple_seq_add_stmt (&seq, vaend);
+ }
+
+ gimple_call_set_arg (wrcall, nargs - 1, unshare_expr (swmp));
+ // gimple_call_set_tail (wrcall, false);
+ update_stmt (wrcall);
+
+ {
+#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);
+ gimple_set_location (force_store, gimple_location (wrcall));
+ 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);
+
+ /* For nnode, we don't rebuild edges because we wish to retain
+ any redirections copied to it from earlier passes, so we add
+ call graph edges explicitly there, but for onode, we create a
+ fresh function, so we may as well just issue the calls and
+ then rebuild all cgraph edges. */
+ // cgraph_edge::rebuild_edges ();
+ onode->analyze ();
+ // inline_analyze_function (onode);
+
+ 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;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub (gcc::context *ctxt)
+{
+ return new pass_ipa_strub (ctxt);
+}
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
new file mode 100644
index 00000000000..c335ab42097
--- /dev/null
+++ b/gcc/ipa-strub.h
@@ -0,0 +1,35 @@
+/* strub (stack scrubbing) infrastructure.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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 FALSE if NODE is a strub context, and TRUE otherwise. */
+extern bool strub_splittable_p (cgraph_node *node);
+
+/* Locate and return the watermark_ptr parameter for FNDECL. If FNDECL is not a
+ strub context, return NULL. */
+extern tree strub_watermark_parm (tree fndecl);
+
+/* Make a function type or declaration callable. */
+extern void strub_make_callable (tree fndecl);
diff --git a/gcc/passes.def b/gcc/passes.def
index d7a1f8c97a6..8e4638d20ed 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -53,6 +53,7 @@ along with GCC; see the file COPYING3. If not see
INSERT_PASSES_AFTER (all_small_ipa_passes)
NEXT_PASS (pass_ipa_free_lang_data);
NEXT_PASS (pass_ipa_function_and_variable_visibility);
+ NEXT_PASS (pass_ipa_strub_mode);
NEXT_PASS (pass_build_ssa_passes);
PUSH_INSERT_PASSES_WITHIN (pass_build_ssa_passes)
NEXT_PASS (pass_fixup_cfg);
@@ -111,6 +112,7 @@ along with GCC; see the file COPYING3. If not see
POP_INSERT_PASSES ()
NEXT_PASS (pass_ipa_remove_symbols);
+ NEXT_PASS (pass_ipa_strub);
NEXT_PASS (pass_ipa_oacc);
PUSH_INSERT_PASSES_WITHIN (pass_ipa_oacc)
NEXT_PASS (pass_ipa_pta);
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..7848c46d179
--- /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 "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
new file mode 100644
index 00000000000..85a8f76785e
--- /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" } */
+
+/* With -fno-inline, 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 "\[(\]call\[^\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..a2eedfd96b2
--- /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, 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 "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
new file mode 100644
index 00000000000..e5cb1f60541
--- /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 "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
new file mode 100644
index 00000000000..194aacc2c05
--- /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 "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
new file mode 100644
index 00000000000..46e84bf6560
--- /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 inline void
+__attribute__ ((__always_inline__))
+h() {
+}
+
+/* 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-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
new file mode 100644
index 00000000000..f180b17f30e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__ (3)))
+apply_function (void *args)
+{
+ __builtin_apply (0, args, 0);
+}
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+ void *args = __builtin_apply_args ();
+ apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
new file mode 100644
index 00000000000..379a54b73b7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+extern void __attribute__ ((__strub__))
+apply_function (void *args);
+
+void __attribute__ ((__strub__))
+apply_args (int i, int j, double d) /* { dg-error "selected" } */
+{
+ void *args = __builtin_apply_args (); /* { dg-message "does not support" } */
+ apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
new file mode 100644
index 00000000000..9b4786be698
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__))
+apply_function (void *args)
+{
+ __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
new file mode 100644
index 00000000000..409f747743e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+
+/* Check that implicit enabling of strub mode selects internal strub when the
+ function uses __builtin_apply_args, that prevents the optimization to
+ at-calls mode. */
+
+int __attribute__ ((__strub__)) var;
+
+static inline void
+apply_args (int i, int j, double d)
+{
+ var++;
+ __builtin_apply_args ();
+}
+
+void f() {
+ apply_args (1, 2, 3);
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
new file mode 100644
index 00000000000..d964b07ae5d
--- /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 inline void
+__attribute__ ((__always_inline__))
+h() {
+}
+
+/* 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..79d00cedb9a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-default1.c
@@ -0,0 +1,42 @@
+/* { 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-default2.c b/gcc/testsuite/c-c++-common/strub-default2.c
new file mode 100644
index 00000000000..487253e9227
--- /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-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
new file mode 100644
index 00000000000..7b04eea35d9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O1" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+ the strubbing to its caller at -O1. */
+
+#include "strub-defer-O2.c"
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
new file mode 100644
index 00000000000..67d96419a5e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -0,0 +1,8 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O2" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+ the strubbing to its caller at -O2. */
+
+#define EXPECT_DEFERRAL !
+#include "strub-defer-O3.c"
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
new file mode 100644
index 00000000000..34828d2711e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -0,0 +1,93 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O3" } */
+
+/* Check that a strub function called by another strub function defers the
+ strubbing to its caller at -O3. */
+
+#ifndef EXPECT_DEFERRAL
+# define EXPECT_DEFERRAL
+#endif
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+ /* We use this variable to avoid any stack red zone. Stack scrubbing covers
+ it, but __builtin_stack_address, that we take as a reference, doesn't, so
+ if e.g. callable() were to store the string in the red zone, we wouldn't
+ find it because it would be outside the range we searched. */
+ typedef void __attribute__ ((__strub__ (3))) callable_t (char *);
+ callable_t *f = 0;
+
+ char s[sizeof (test_string)];
+ __builtin_strcpy (s, test_string);
+ asm ("" : "+m" (s), "+r" (f));
+
+ if (__builtin_expect (!f, 1))
+ return (char*)__builtin_stack_address ();
+
+ f (s);
+ return 0;
+}
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+int
+look_for_string (char *e)
+{
+ char *p = (char*)__builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+char *
+at_calls ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+deferred_at_calls ()
+{
+ char *ret = at_calls ();
+ if (EXPECT_DEFERRAL !look_for_string (ret))
+ __builtin_abort ();
+ return ret;
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+deferred_internal ()
+{
+ char *ret = at_calls ();
+ if (EXPECT_DEFERRAL !look_for_string (ret))
+ __builtin_abort ();
+ return ret;
+}
+
+int main ()
+{
+ if (look_for_string (deferred_at_calls ()))
+ __builtin_abort ();
+ if (look_for_string (deferred_internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
new file mode 100644
index 00000000000..b273660aea1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -Os" } */
+
+/* Check that a strub function called by another strub function defers the
+ strubbing to its caller at -Os. */
+
+#include "strub-defer-O3.c"
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
new file mode 100644
index 00000000000..a74658c9ac9
--- /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 inline void
+__attribute__ ((__always_inline__))
+h() {
+}
+
+/* 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/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
new file mode 100644
index 00000000000..7e22a266ad9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (2)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+ int x[128];
+};
+
+void __attribute__ ((__strub__ (2)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\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)))
+std_arg (int i, ...)
+{
+ va_list vl;
+ va_start (vl, i);
+ va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+ __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
new file mode 100644
index 00000000000..2d8036c0fbc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (1)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*small_args\[^ \]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+
+struct large_arg {
+ int x[128];
+};
+
+void __attribute__ ((__strub__ (1)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ (1)))
+std_arg (int i, ...)
+{
+ va_list vl;
+ va_start (vl, i);
+ va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*std_arg\[^ \]* \[(\]int i, void \\* &\[^&,\]*.strub.watermark_ptr\[, .]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-not "va_copy \\(" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
new file mode 100644
index 00000000000..f64361f1235
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -0,0 +1,58 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that uses of a strub variable implicitly enables internal strub for
+ publicly-visible functions, and causes the same transformations to their
+ signatures as those in strub-parms1.c. */
+
+#include <stdarg.h>
+
+int __attribute__ ((__strub__)) var;
+
+void
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+ var++;
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+ int x[128];
+};
+
+void
+large_byref_arg (struct large_arg la)
+{
+ var++;
+}
+
+/* { dg-final { scan-ipa-dump "\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
+std_arg (int i, ...)
+{
+ va_list vl;
+ va_start (vl, i);
+ var++;
+ va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void
+apply_args (int i, int j, double d)
+{
+ var++;
+ __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
new file mode 100644
index 00000000000..a4226ce0119
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=default -fexceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 89 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
new file mode 100644
index 00000000000..3bab553478b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
new file mode 100644
index 00000000000..c89cc7c2c47
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
new file mode 100644
index 00000000000..b869fafb691
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
new file mode 100644
index 00000000000..1d3dd2f2c2c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=default -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
+ watermark. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 15 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 15 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
new file mode 100644
index 00000000000..4dd4281b03b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-Os -fstrub=default -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
+ watermark. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 15 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 15 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
new file mode 100644
index 00000000000..0840dddd136
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+#include "strub-tail-O2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
new file mode 100644
index 00000000000..9330d6ff4c1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued.
+ Tail calls are short-circuited at -O2+. */
+
+int __attribute__ ((__strub__))
+g (int i, int j) {
+ return g (i, j);
+}
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 0 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 0 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
new file mode 100644
index 00000000000..5b33ff1f530
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -0,0 +1,9 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that strub and non-strub functions can be called from non-strub
+ contexts, and that strub and callable functions can be called from strub
+ contexts. */
+
+#define OMIT_IMPERMISSIBLE_CALLS 1
+#include "strub-callable2.c"
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
new file mode 100644
index 00000000000..38935e3270b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -0,0 +1,264 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that impermissible (cross-strub-context) calls are reported. */
+
+extern int __attribute__ ((__strub__ (3))) xcallable (void);
+extern int __attribute__ ((__strub__ (2))) xinternal (void);
+extern int __attribute__ ((__strub__ (1))) xat_calls (void);
+extern int __attribute__ ((__strub__ (0))) xdisabled (void);
+
+int __attribute__ ((__strub__ (3))) callable (void);
+int __attribute__ ((__strub__ (2))) internal (void);
+int __attribute__ ((__strub__ (1))) at_calls (void);
+int __attribute__ ((__strub__ (0))) disabled (void);
+
+int __attribute__ ((__strub__)) var;
+int var_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+icallable (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+iinternal (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+iat_calls (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+idisabled (void);
+static inline int __attribute__ ((__always_inline__))
+ivar_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+i_callable (void) { return 0; }
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+i_internal (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+i_at_calls (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+i_disabled (void) { return 0; }
+static inline int __attribute__ ((__always_inline__))
+i_var_user (void) { return var; }
+
+#define CALLS_GOOD_FOR_STRUB_CONTEXT(ISEP) \
+ do { \
+ ret += i ## ISEP ## at_calls (); \
+ ret += i ## ISEP ## internal (); \
+ ret += i ## ISEP ## var_user (); \
+ } while (0)
+
+#define CALLS_GOOD_FOR_NONSTRUB_CONTEXT(ISEP) \
+ do { \
+ ret += internal (); \
+ ret += disabled (); \
+ ret += var_user (); \
+ \
+ ret += i ## ISEP ## disabled (); \
+ \
+ ret += xinternal (); \
+ ret += xdisabled (); \
+ } while (0)
+
+#define CALLS_GOOD_FOR_EITHER_CONTEXT(ISEP) \
+ do { \
+ ret += i ## ISEP ## callable (); \
+ \
+ ret += callable (); \
+ ret += at_calls (); \
+ \
+ ret += xat_calls (); \
+ ret += xcallable (); \
+ } while (0)
+
+/* Not a strub context, so it can call anything.
+ Explicitly declared as callable even from within strub contexts. */
+int __attribute__ ((__strub__ (3)))
+callable (void) {
+ int ret = 0;
+
+ /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+ ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+ ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+ return ret;
+}
+
+/* Internal strubbing means the body is a strub context, so it can only call
+ strub functions, and it's not itself callable from strub functions. */
+int __attribute__ ((__strub__ (2)))
+internal (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT();
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int __attribute__ ((__strub__ (1)))
+at_calls (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT();
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int __attribute__ ((__strub__ (0)))
+disabled () {
+ int ret = 0;
+
+ /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+ ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+ ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+ return ret;
+}
+
+int
+var_user (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT();
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int
+icallable (void)
+{
+ int ret = 0;
+
+ /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+ ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+ ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+ return ret;
+}
+
+int
+iinternal (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int
+iat_calls (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int
+idisabled () {
+ int ret = 0;
+
+ /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+ ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+ ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+ return ret;
+}
+
+int
+ivar_user (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
new file mode 100644
index 00000000000..100fb0c59a9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const function call, we issue an asm statement
+ to make sure the watermark passed to it is held in memory before the call,
+ and another to make sure it is not assumed to be unchanged. */
+
+int __attribute__ ((__strub__, __const__))
+f() {
+ return 0;
+}
+
+int
+g() {
+ return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
new file mode 100644
index 00000000000..9e818ac9748
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const function call, we issue an
+ asm statement to make sure the watermark passed to it is held in memory
+ before the call, and another to make sure it is not assumed to be
+ unchanged. */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+ return 0;
+}
+
+int
+g() {
+ return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
new file mode 100644
index 00000000000..d40e8aa45cb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const wrapping call, we issue an asm statement
+ to make sure the watermark passed to it is held in memory before the call,
+ and another to make sure it is not assumed to be unchanged. */
+
+int __attribute__ ((__strub__ (2), __const__))
+f() {
+ return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
new file mode 100644
index 00000000000..d4cbdaf10f3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const wrapping call, we issue an
+ asm statement to make sure the watermark passed to it is held in memory
+ before the call, and another to make sure it is not assumed to be
+ unchanged. */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+ return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-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..07e25af9c53
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -Werror" } */
+
+typedef int __attribute__ ((__strub__)) strub_int;
+strub_int *ptr;
+
+int *f () {
+ return ptr; /* { dg-warning "incompatible" } */
+}
+
+strub_int *g () {
+ return f (); /* { dg-warning "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/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
new file mode 100644
index 00000000000..cb223da6efc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure function call, we issue an asm statement
+ to make sure the watermark passed to it is not assumed to be unchanged. */
+
+int __attribute__ ((__strub__, __pure__))
+f() {
+ static int i; /* Stop it from being detected as const. */
+ return i;
+}
+
+int
+g() {
+ return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
new file mode 100644
index 00000000000..67d1434b1f8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure function call, we issue an asm
+ statement to make sure the watermark passed to it is not assumed to be
+ unchanged. */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run. */
+__attribute__ ((__pure__))
+#endif
+f() {
+ static int i; /* Stop it from being detected as const. */
+ return i;
+}
+
+int
+g() {
+ return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
new file mode 100644
index 00000000000..59f02ea901f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure wrapping call, we issue an asm statement
+ to make sure the watermark passed to it is not assumed to be unchanged. */
+
+int __attribute__ ((__strub__ (2), __pure__))
+f() {
+ static int i; /* Stop it from being detected as const. */
+ return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
new file mode 100644
index 00000000000..973e909217d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure wrapping call, we issue an asm
+ statement to make sure the watermark passed to it is not assumed to be
+ unchanged. */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run. */
+__attribute__ ((__pure__))
+#endif
+f() {
+ static int i; /* Stop it from being detected as const. */
+ return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
new file mode 100644
index 00000000000..a4077c35a60
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -0,0 +1,85 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+ equivalent strub functions don't. */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+ /* We use this variable to avoid any stack red zone. Stack scrubbing covers
+ it, but __builtin_stack_address, that we take as a reference, doesn't, so
+ if e.g. callable() were to store the string in the red zone, we wouldn't
+ find it because it would be outside the range we searched. */
+ typedef void __attribute__ ((__strub__ (3))) callable_t (char *);
+ callable_t *f = 0;
+
+ char s[sizeof (test_string)];
+ __builtin_strcpy (s, test_string);
+ asm ("" : "+m" (s), "+r" (f));
+
+ if (__builtin_expect (!f, 1))
+ return (char *) __builtin_stack_address ();
+
+ f (s);
+ return 0;
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+ char *p = (char *) __builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+ return leak_string ();
+}
+
+int main ()
+{
+ if (!look_for_string (callable ()))
+ __builtin_abort ();
+ if (look_for_string (at_calls ()))
+ __builtin_abort ();
+ if (look_for_string (internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
new file mode 100644
index 00000000000..94e4156ea73
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+ equivalent strub functions don't. */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+ int len = sizeof (test_string);
+ asm ("" : "+rm" (len));
+ char s[len];
+ __builtin_strcpy (s, test_string);
+ asm ("" : "+m" (s));
+ return (char *) __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+ char *p = (char *) __builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+ return leak_string ();
+}
+
+int main ()
+{
+ if (!look_for_string (callable ()))
+ __builtin_abort ();
+ if (look_for_string (at_calls ()))
+ __builtin_abort ();
+ if (look_for_string (internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
new file mode 100644
index 00000000000..0ca74beb59d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+/* { dg-require-effective-target alloca } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+ equivalent strub functions don't. */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+ int len = sizeof (test_string);
+ char *s = (char *) __builtin_alloca (len);
+ __builtin_strcpy (s, test_string);
+ asm ("" : "+m" (s));
+ return (char *) __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+ char *p = (char *) __builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+ return leak_string ();
+}
+
+int main ()
+{
+ if (!look_for_string (callable ()))
+ __builtin_abort ();
+ if (look_for_string (at_calls ()))
+ __builtin_abort ();
+ if (look_for_string (internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
new file mode 100644
index 00000000000..4ab11c0682e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -0,0 +1,101 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=all" } */
+/* { dg-require-effective-target alloca } */
+
+/* Check that multi-level, multi-inlined functions still get cleaned up as
+ expected, without overwriting temporary stack allocations while they should
+ still be available. */
+
+#ifndef ATTR_STRUB_AT_CALLS
+# define ATTR_STRUB_AT_CALLS /* Defined in strub-run4d.c. */
+#endif
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__))
+char *
+leak_string (void)
+{
+ int __attribute__ ((__strub__)) len = 512;
+ asm ("" : "+r" (len));
+ char s[len];
+ __builtin_strcpy (s, test_string);
+ __builtin_strcpy (s + len - sizeof (test_string), test_string);
+ asm ("" : "+m" (s));
+ return (char *) __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+ char *p = (char *) __builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static inline ATTR_STRUB_AT_CALLS
+char *
+innermost ()
+{
+ int __attribute__ ((__strub__)) len = 512;
+ asm ("" : "+r" (len));
+ char s[len];
+ __builtin_strcpy (s, test_string);
+ __builtin_strcpy (s + len - sizeof (test_string), test_string);
+ asm ("" : "+m" (s));
+ char *ret = leak_string ();
+ if (__builtin_strcmp (s, test_string) != 0)
+ __builtin_abort ();
+ if (__builtin_strcmp (s + len - sizeof (test_string), test_string) != 0)
+ __builtin_abort ();
+ return ret;
+}
+
+static inline ATTR_STRUB_AT_CALLS
+char *
+intermediate ()
+{
+ int __attribute__ ((__strub__)) len = 512;
+ asm ("" : "+r" (len));
+ char s[len];
+ __builtin_strcpy (s, test_string);
+ __builtin_strcpy (s + len - sizeof (test_string), test_string);
+ asm ("" : "+m" (s));
+ char *ret = innermost ();
+ if (__builtin_strcmp (s, test_string) != 0)
+ __builtin_abort ();
+ if (__builtin_strcmp (s + len - sizeof (test_string), test_string) != 0)
+ __builtin_abort ();
+ return ret;
+}
+
+static inline __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+ return intermediate ();
+}
+
+int __attribute__ ((strub (0)))
+main ()
+{
+ if (look_for_string (internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4c.c b/gcc/testsuite/c-c++-common/torture/strub-run4c.c
new file mode 100644
index 00000000000..57f9baf758d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4c.c
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=at-calls" } */
+/* { dg-require-effective-target alloca } */
+
+#include "strub-run4.c"
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4d.c b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
new file mode 100644
index 00000000000..e4f7445607c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+/* { dg-require-effective-target alloca } */
+
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ (1)))
+
+#include "strub-run4.c"
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4i.c b/gcc/testsuite/c-c++-common/torture/strub-run4i.c
new file mode 100644
index 00000000000..459f6886c54
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4i.c
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=internal" } */
+/* { dg-require-effective-target alloca } */
+
+#include "strub-run4.c"
diff --git a/gcc/testsuite/g++.dg/strub-run1.C b/gcc/testsuite/g++.dg/strub-run1.C
new file mode 100644
index 00000000000..0d367fb83d0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/strub-run1.C
@@ -0,0 +1,19 @@
+// { dg-do run }
+// { dg-options "-fstrub=internal" }
+
+// Check that we don't get extra copies.
+
+struct T {
+ T &self;
+ void check () const { if (&self != this) __builtin_abort (); }
+ T() : self (*this) { check (); }
+ T(const T& ck) : self (*this) { ck.check (); check (); }
+ ~T() { check (); }
+};
+
+T foo (T q) { q.check (); return T(); }
+T bar (T p) { p.check (); return foo (p); }
+
+int main () {
+ bar (T()).check ();
+}
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" } } */
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
new file mode 100644
index 00000000000..697ac9de764
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -0,0 +1,21 @@
+-- { dg-do compile }
+-- { dg-options "-fstrub=default -fdump-ipa-strubm" }
+
+package body Strub_Attr 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 (X);
+end Strub_Attr;
+
+-- { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]2\[)\]\[)\]" 2 "strubm" } }
+-- { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
diff --git a/gcc/testsuite/gnat.dg/strub_attr.ads b/gcc/testsuite/gnat.dg/strub_attr.ads
new file mode 100644
index 00000000000..a94c23bf418
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_attr.ads
@@ -0,0 +1,12 @@
+package Strub_Attr 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;
+end Strub_Attr;
diff --git a/gcc/tree-cfg.c b/gcc/tree-cfg.c
index 61b53913e0b..9b01f2bb01b 100644
--- a/gcc/tree-cfg.c
+++ b/gcc/tree-cfg.c
@@ -5620,6 +5620,7 @@ gimple_verify_flow_info (void)
{
gimple *stmt = gsi_stmt (gsi);
+ /* Do NOT disregard debug stmts after found_ctrl_stmt. */
if (found_ctrl_stmt)
{
error ("control flow in the middle of basic block %d",
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index 83941bc0cee..df4abdf1449 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -497,8 +497,9 @@ extern gimple_opt_pass *make_pass_adjust_alignment (gcc::context *ctxt);
/* IPA Passes */
extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt);
-extern simple_ipa_opt_pass
- *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub_mode (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_tree_profile (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_auto_profile (gcc::context *ctxt);
diff --git a/gcc/tree-ssa-ccp.c b/gcc/tree-ssa-ccp.c
index 1a63ae5f104..2f2fffac4df 100644
--- a/gcc/tree-ssa-ccp.c
+++ b/gcc/tree-ssa-ccp.c
@@ -2827,7 +2827,9 @@ optimize_stack_restore (gimple_stmt_iterator i)
if (!callee
|| !fndecl_built_in_p (callee, BUILT_IN_NORMAL)
/* All regular builtins are ok, just obviously not alloca. */
- || ALLOCA_FUNCTION_CODE_P (DECL_FUNCTION_CODE (callee)))
+ || ALLOCA_FUNCTION_CODE_P (DECL_FUNCTION_CODE (callee))
+ /* Do not remove stack updates before strub leave. */
+ || fndecl_built_in_p (callee, BUILT_IN___STRUB_LEAVE))
return NULL_TREE;
if (fndecl_built_in_p (callee, BUILT_IN_STACK_RESTORE))
diff --git a/libgcc/Makefile.in b/libgcc/Makefile.in
index 7ec97584554..c4ee6701cb3 100644
--- a/libgcc/Makefile.in
+++ b/libgcc/Makefile.in
@@ -428,7 +428,7 @@ ifneq ($(enable_shared),yes)
iterator = $(patsubst %,$(srcdir)/static-object.mk,$(iter-items))
endif
-LIB2ADD += enable-execute-stack.c
+LIB2ADD += enable-execute-stack.c $(srcdir)/strub.c
# While emutls.c has nothing to do with EH, it is in LIB2ADDEH*
# instead of LIB2ADD because that's the way to be sure on some targets
diff --git a/libgcc/libgcc2.h b/libgcc/libgcc2.h
index 1819ff3ac3d..857091e65c8 100644
--- a/libgcc/libgcc2.h
+++ b/libgcc/libgcc2.h
@@ -532,6 +532,10 @@ extern int __parityDI2 (UDWtype);
extern void __enable_execute_stack (void *);
+extern void __strub_enter (void **);
+extern void __strub_update (void**);
+extern void __strub_leave (void **);
+
#ifndef HIDE_EXPORTS
#pragma GCC visibility pop
#endif
diff --git a/libgcc/strub.c b/libgcc/strub.c
new file mode 100644
index 00000000000..fd6e27556e4
--- /dev/null
+++ b/libgcc/strub.c
@@ -0,0 +1,112 @@
+/* Stack scrubbing infrastructure
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+Under Section 7 of GPL version 3, you are granted additional
+permissions described in the GCC Runtime Library Exception, version
+3.1, as published by the Free Software Foundation.
+
+You should have received a copy of the GNU General Public License and
+a copy of the GCC Runtime Library Exception along with this program;
+see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
+<http://www.gnu.org/licenses/>. */
+
+#include "tconfig.h"
+#include "tsystem.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "libgcc_tm.h"
+#include "libgcc2.h"
+
+#ifndef STACK_GROWS_DOWNWARD
+# define TOPS >
+#else
+# define TOPS <
+#endif
+
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+
+/* Enter a stack scrubbing context, initializing the watermark to the caller's
+ stack address. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_enter (void **watermark)
+{
+ *watermark = __builtin_frame_address (0);
+}
+
+/* Update the watermark within a stack scrubbing context with the current stack
+ pointer. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_update (void **watermark)
+{
+ void *sp = __builtin_frame_address (0);
+
+ if (sp TOPS *watermark)
+ *watermark = sp;
+}
+
+#ifndef TARGET_STRUB_USE_DYNAMIC_ARRAY
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+#endif
+
+#ifndef TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+# ifdef TARGET_STRUB_MAY_USE_MEMSET
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+# else
+# define TARGET_STRUB_MAY_USE_MEMSET 1
+# endif
+#endif
+
+/* Leave a stack scrubbing context, restoring and updating SAVED, and
+ clearing the stack between top and watermark. */
+void ATTRIBUTE_STRUB_CALLABLE
+#if ! TARGET_STRUB_MAY_USE_MEMSET
+__attribute__ ((__optimize__ ("-fno-tree-loop-distribute-patterns")))
+#endif
+__strub_leave (void **mark)
+{
+ void *sp = __builtin_stack_address ();
+
+ void **base, **end;
+#ifndef STACK_GROWS_DOWNWARD
+ base = sp;
+ end = *mark;
+#else
+ base = *mark;
+ end = sp;
+#endif
+
+ ptrdiff_t len = end - base;
+ if (len <= 0)
+ return;
+
+#if ! TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+ /* Allocate a dynamically-sized array covering the desired range, so that we
+ can safely call memset on it. */
+ void *ptr[len];
+ base = &ptr[0];
+ end = &ptr[len];
+#else
+ void **ptr = end;
+#endif /* TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY */
+
+ /* ldist turns this into a memset. Without the dynamic array above, that call
+ is likely unsafe: possibly tail-called, and likely scribbling over its own
+ stack frame. */
+ while (base < end)
+ *base++ = 0;
+
+ asm ("" : : "m" (ptr));
+}
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/strub)] strub with function and variable attributes
@ 2021-08-19 2:58 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2021-08-19 2:58 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:4efe342d5092a31dc6a9d2f7bf7f44894357170c
commit 4efe342d5092a31dc6a9d2f7bf7f44894357170c
Author: Alexandre Oliva <oliva@adacore.com>
Date: Wed Aug 18 23:57:13 2021 -0300
strub with function and variable attributes
still missing verification of calls, documentation, tests,
builtin expansion
Diff:
---
gcc/Makefile.in | 1 +
gcc/ada/gcc-interface/trans.c | 5 +
gcc/ada/gcc-interface/utils.c | 97 +
gcc/ada/libgnat/a-except.adb | 90 +
gcc/ada/libgnat/a-except.ads | 8 +
gcc/ada/libgnat/s-arit128.ads | 6 +
gcc/ada/libgnat/s-arit64.ads | 6 +
gcc/builtins.c | 273 ++
gcc/builtins.def | 4 +
gcc/c-family/c-attribs.c | 87 +
gcc/common.opt | 24 +
gcc/doc/extend.texi | 85 +
gcc/doc/invoke.texi | 42 +
gcc/ipa-inline.c | 6 +
gcc/ipa-split.c | 7 +
gcc/ipa-strub.c | 2999 ++++++++++++++++++++
gcc/ipa-strub.h | 35 +
gcc/passes.def | 2 +
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-apply1.c | 15 +
gcc/testsuite/c-c++-common/strub-apply2.c | 12 +
gcc/testsuite/c-c++-common/strub-apply3.c | 8 +
gcc/testsuite/c-c++-common/strub-apply4.c | 21 +
gcc/testsuite/c-c++-common/strub-at-calls1.c | 30 +
gcc/testsuite/c-c++-common/strub-at-calls2.c | 23 +
gcc/testsuite/c-c++-common/strub-default1.c | 42 +
gcc/testsuite/c-c++-common/strub-default2.c | 29 +
gcc/testsuite/c-c++-common/strub-defer-O1.c | 7 +
gcc/testsuite/c-c++-common/strub-defer-O2.c | 8 +
gcc/testsuite/c-c++-common/strub-defer-O3.c | 93 +
gcc/testsuite/c-c++-common/strub-defer-Os.c | 7 +
gcc/testsuite/c-c++-common/strub-internal1.c | 31 +
gcc/testsuite/c-c++-common/strub-internal2.c | 21 +
gcc/testsuite/c-c++-common/strub-parms1.c | 48 +
gcc/testsuite/c-c++-common/strub-parms2.c | 36 +
gcc/testsuite/c-c++-common/strub-parms3.c | 58 +
gcc/testsuite/c-c++-common/strub-short-O0-exc.c | 10 +
gcc/testsuite/c-c++-common/strub-short-O0.c | 10 +
gcc/testsuite/c-c++-common/strub-short-O1.c | 10 +
gcc/testsuite/c-c++-common/strub-short-O2.c | 10 +
gcc/testsuite/c-c++-common/strub-short-O3.c | 12 +
gcc/testsuite/c-c++-common/strub-short-Os.c | 12 +
gcc/testsuite/c-c++-common/strub-tail-O1.c | 8 +
gcc/testsuite/c-c++-common/strub-tail-O2.c | 14 +
.../c-c++-common/torture/strub-callable1.c | 9 +
.../c-c++-common/torture/strub-callable2.c | 264 ++
gcc/testsuite/c-c++-common/torture/strub-const1.c | 18 +
gcc/testsuite/c-c++-common/torture/strub-const2.c | 22 +
gcc/testsuite/c-c++-common/torture/strub-const3.c | 13 +
gcc/testsuite/c-c++-common/torture/strub-const4.c | 17 +
gcc/testsuite/c-c++-common/torture/strub-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 | 13 +
.../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/c-c++-common/torture/strub-pure1.c | 18 +
gcc/testsuite/c-c++-common/torture/strub-pure2.c | 22 +
gcc/testsuite/c-c++-common/torture/strub-pure3.c | 13 +
gcc/testsuite/c-c++-common/torture/strub-pure4.c | 17 +
gcc/testsuite/c-c++-common/torture/strub-run1.c | 85 +
gcc/testsuite/c-c++-common/torture/strub-run2.c | 75 +
gcc/testsuite/c-c++-common/torture/strub-run3.c | 75 +
gcc/testsuite/c-c++-common/torture/strub-run4.c | 101 +
gcc/testsuite/c-c++-common/torture/strub-run4c.c | 5 +
gcc/testsuite/c-c++-common/torture/strub-run4d.c | 7 +
gcc/testsuite/c-c++-common/torture/strub-run4i.c | 5 +
gcc/testsuite/g++.dg/strub-run1.C | 19 +
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 +
gcc/testsuite/gnat.dg/strub_attr.adb | 21 +
gcc/testsuite/gnat.dg/strub_attr.ads | 12 +
gcc/tree-cfg.c | 1 +
gcc/tree-pass.h | 5 +-
gcc/tree-ssa-ccp.c | 4 +-
libgcc/Makefile.in | 2 +-
libgcc/libgcc2.h | 4 +
libgcc/strub.c | 112 +
90 files changed, 5621 insertions(+), 4 deletions(-)
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 9714fcaac37..94097ec78d7 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1471,6 +1471,7 @@ OBJS = \
ipa-reference.o \
ipa-ref.o \
ipa-utils.o \
+ ipa-strub.o \
ipa.o \
ira.o \
ira-build.o \
diff --git a/gcc/ada/gcc-interface/trans.c b/gcc/ada/gcc-interface/trans.c
index 3df56aa0560..f1524a3fb90 100644
--- a/gcc/ada/gcc-interface/trans.c
+++ b/gcc/ada/gcc-interface/trans.c
@@ -43,6 +43,7 @@
#include "output.h"
#include "debug.h"
#include "libfuncs.h" /* For set_stack_check_libfunc. */
+#include "ipa-strub.h" /* For strub_make_callable. */
#include "tree-iterator.h"
#include "gimplify.h"
#include "opts.h"
@@ -444,6 +445,7 @@ gigi (Node_Id gnat_root,
int64_type, NULL_TREE),
NULL_TREE, is_default, true, true, true, false,
false, NULL, Empty);
+ strub_make_callable (mulv64_decl);
if (Enable_128bit_Types)
{
@@ -456,6 +458,7 @@ gigi (Node_Id gnat_root,
NULL_TREE),
NULL_TREE, is_default, true, true, true, false,
false, NULL, Empty);
+ strub_make_callable (mulv128_decl);
}
/* Name of the _Parent field in tagged record types. */
@@ -531,6 +534,7 @@ gigi (Node_Id gnat_root,
= create_subprog_decl
(get_identifier ("__gnat_raise_nodefer_with_msg"), NULL_TREE, ftype,
NULL_TREE, is_default, true, true, true, false, false, NULL, Empty);
+ strub_make_callable (raise_nodefer_decl);
set_exception_parameter_decl
= create_subprog_decl
@@ -779,6 +783,7 @@ build_raise_check (int check, enum exception_info_kind kind)
= create_subprog_decl (get_identifier (Name_Buffer), NULL_TREE, ftype,
NULL_TREE, is_default, true, true, true, false,
false, NULL, Empty);
+ strub_make_callable (result);
return result;
}
diff --git a/gcc/ada/gcc-interface/utils.c b/gcc/ada/gcc-interface/utils.c
index f85373e2f76..d9fb78416ea 100644
--- a/gcc/ada/gcc-interface/utils.c
+++ b/gcc/ada/gcc-interface/utils.c
@@ -94,6 +94,7 @@ static tree handle_sentinel_attribute (tree *, tree, tree, int, bool *);
static tree handle_noreturn_attribute (tree *, tree, tree, int, bool *);
static tree handle_stack_protect_attribute (tree *, tree, tree, int, bool *);
static tree handle_no_stack_protector_attribute (tree *, tree, tree, int, bool *);
+static tree handle_strub_attribute (tree *, tree, tree, int, bool *);
static tree handle_noinline_attribute (tree *, tree, tree, int, bool *);
static tree handle_noclone_attribute (tree *, tree, tree, int, bool *);
static tree handle_noicf_attribute (tree *, tree, tree, int, bool *);
@@ -157,6 +158,8 @@ const struct attribute_spec gnat_internal_attribute_table[] =
{ "no_stack_protector",0, 0, true, false, false, false,
handle_no_stack_protector_attribute,
attr_stack_protect_exclusions },
+ { "strub", 0, 1, false, true, false, true,
+ handle_strub_attribute, NULL },
{ "noinline", 0, 0, true, false, false, false,
handle_noinline_attribute, NULL },
{ "noclone", 0, 0, true, false, false, false,
@@ -6599,6 +6602,100 @@ handle_no_stack_protector_attribute (tree *node, tree name, tree, int,
return NULL_TREE;
}
+/* Handle a "strub" attribute; arguments as in
+ struct attribute_spec.handler. */
+
+static tree
+handle_strub_attribute (tree *node, tree name,
+ tree args,
+ int ARG_UNUSED (flags), bool *no_add_attrs)
+{
+ bool enable = true;
+
+ if (args
+ && (TREE_CODE (*node) == FUNCTION_TYPE
+ || TREE_CODE (*node) == METHOD_TYPE))
+ {
+ /* 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)
+ {
+ 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);
+ }
+
+ /* 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)
+ {
+ warning (OPT_Wattributes,
+ "%qE attribute ignored because of argument %qE",
+ name, TREE_VALUE (args));
+ *no_add_attrs = true;
+ enable = false;
+ }
+ /* 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));
+
+ /* 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;
+
+ return NULL_TREE;
+}
/* Handle a "noinline" attribute; arguments as in
struct attribute_spec.handler. */
diff --git a/gcc/ada/libgnat/a-except.adb b/gcc/ada/libgnat/a-except.adb
index c332afad0f8..593392809d9 100644
--- a/gcc/ada/libgnat/a-except.adb
+++ b/gcc/ada/libgnat/a-except.adb
@@ -629,6 +629,96 @@ package body Ada.Exceptions is
pragma No_Return (Rcheck_CE_Invalid_Data_Ext);
pragma No_Return (Rcheck_CE_Range_Check_Ext);
+ -- Make all of these procedures callable from strub contexts.
+ -- These attributes are not visible to callers; they are made
+ -- visible in trans.c:build_raise_check.
+
+ pragma Machine_Attribute (Rcheck_CE_Access_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Null_Access_Parameter,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Discriminant_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Divide_By_Zero,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Explicit_Raise,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Index_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Invalid_Data,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Length_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Null_Exception_Id,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Null_Not_Allowed,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Overflow_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Partition_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Range_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Tag_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Access_Before_Elaboration,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Accessibility_Check,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Address_Of_Intrinsic,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Aliased_Parameters,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_All_Guards_Closed,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Bad_Predicated_Generic_Type,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Build_In_Place_Mismatch,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Current_Task_In_Entry_Body,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Duplicated_Entry_Address,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Explicit_Raise,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Implicit_Return,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Misaligned_Address_Value,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Missing_Return,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Non_Transportable_Actual,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Overlaid_Controlled_Object,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Potentially_Blocking_Operation,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Stream_Operation_Not_Allowed,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Stubbed_Subprogram_Called,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Unchecked_Union_Restriction,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_PE_Finalize_Raised_Exception,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_SE_Empty_Storage_Pool,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_SE_Explicit_Raise,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_SE_Infinite_Recursion,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_SE_Object_Too_Large,
+ "strub", "callable");
+
+ pragma Machine_Attribute (Rcheck_CE_Access_Check_Ext,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Index_Check_Ext,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Invalid_Data_Ext,
+ "strub", "callable");
+ pragma Machine_Attribute (Rcheck_CE_Range_Check_Ext,
+ "strub", "callable");
+
---------------------------------------------
-- Reason Strings for Run-Time Check Calls --
---------------------------------------------
diff --git a/gcc/ada/libgnat/a-except.ads b/gcc/ada/libgnat/a-except.ads
index 2b27adb6ca1..b5a2e48f96a 100644
--- a/gcc/ada/libgnat/a-except.ads
+++ b/gcc/ada/libgnat/a-except.ads
@@ -81,6 +81,10 @@ package Ada.Exceptions is
pragma No_Return (Raise_Exception);
-- Note: In accordance with AI-466, CE is raised if E = Null_Id
+ -- Make it callable from strub contexts.
+ pragma Machine_Attribute (Raise_Exception,
+ "strub", "callable");
+
function Exception_Message (X : Exception_Occurrence) return String;
procedure Reraise_Occurrence (X : Exception_Occurrence);
@@ -184,6 +188,10 @@ private
-- Raise_Exception_Always if it can determine this is the case. The Export
-- allows this routine to be accessed from Pure units.
+ -- Make it callable from strub contexts.
+ pragma Machine_Attribute (Raise_Exception_Always,
+ "strub", "callable");
+
procedure Raise_From_Controlled_Operation (X : Exception_Occurrence);
pragma No_Return (Raise_From_Controlled_Operation);
pragma Export
diff --git a/gcc/ada/libgnat/s-arit128.ads b/gcc/ada/libgnat/s-arit128.ads
index 6213cfb569a..5d3fff6d8e1 100644
--- a/gcc/ada/libgnat/s-arit128.ads
+++ b/gcc/ada/libgnat/s-arit128.ads
@@ -57,6 +57,12 @@ package System.Arith_128 is
-- bits, otherwise returns the 128-bit signed integer product.
-- Gigi may also call this routine directly.
+ -- Make it callable from strub contexts.
+ -- There is a matching setting in trans.c,
+ -- for calls issued by Gigi.
+ pragma Machine_Attribute (Multiply_With_Ovflo_Check128,
+ "strub", "callable");
+
procedure Scaled_Divide128
(X, Y, Z : Int128;
Q, R : out Int128;
diff --git a/gcc/ada/libgnat/s-arit64.ads b/gcc/ada/libgnat/s-arit64.ads
index c9141f5fe3e..170c2bdf1de 100644
--- a/gcc/ada/libgnat/s-arit64.ads
+++ b/gcc/ada/libgnat/s-arit64.ads
@@ -57,6 +57,12 @@ package System.Arith_64 is
-- bits, otherwise returns the 64-bit signed integer product.
-- Gigi may also call this routine directly.
+ -- Make it callable from strub contexts.
+ -- There is a matching setting in trans.c,
+ -- for calls issued by Gigi.
+ pragma Machine_Attribute (Multiply_With_Ovflo_Check64,
+ "strub", "callable");
+
procedure Scaled_Divide64
(X, Y, Z : Int64;
Q, R : out Int64;
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 99548627761..7ad91ea6f7a 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -70,6 +70,7 @@ along with GCC; see the file COPYING3. If not see
#include "gimple-fold.h"
#include "intl.h"
#include "file-prefix-map.h" /* remap_macro_filename() */
+#include "ipa-strub.h" /* strub_watermark_parm() */
#include "gomp-constants.h"
#include "omp-general.h"
#include "tree-dfa.h"
@@ -150,6 +151,7 @@ static rtx expand_builtin_strnlen (tree, rtx, machine_mode);
static rtx expand_builtin_alloca (tree);
static rtx expand_builtin_unop (machine_mode, tree, rtx, rtx, optab);
static rtx expand_builtin_frame_address (tree, tree);
+static rtx expand_builtin_stack_address ();
static tree stabilize_va_list_loc (location_t, tree, int);
static rtx expand_builtin_expect (tree, rtx);
static rtx expand_builtin_expect_with_probability (tree, rtx);
@@ -4871,6 +4873,256 @@ expand_builtin_frame_address (tree fndecl, tree exp)
}
}
+#ifndef STACK_GROWS_DOWNWARD
+# define STACK_TOPS GT
+#else
+# define STACK_TOPS LT
+#endif
+
+#ifdef POINTERS_EXTEND_UNSIGNED
+# define STACK_UNSIGNED POINTERS_EXTEND_UNSIGNED
+#else
+# define STACK_UNSIGNED true
+#endif
+
+/* Expand a call to builtin function __builtin_stack_address. */
+
+static rtx
+expand_builtin_stack_address ()
+{
+ return convert_to_mode (ptr_mode, copy_to_reg (stack_pointer_rtx),
+ STACK_UNSIGNED);
+}
+
+/* Expand a call to builtin function __builtin_strub_enter. */
+
+static rtx
+expand_builtin_strub_enter (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 1 || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = NULL_RTX;
+
+#if 1 || defined RED_ZONE_SIZE
+ if (tree wmptr = (optimize
+ ? strub_watermark_parm (current_function_decl)
+ : NULL_TREE))
+ {
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+ stktop = force_reg (ptr_mode, wmark);
+ }
+#endif
+
+ if (!stktop)
+ stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ emit_move_insn (wmark, stktop);
+
+ return const0_rtx;
+}
+
+/* Expand a call to builtin function __builtin_strub_update. */
+
+static rtx
+expand_builtin_strub_update (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 2 || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = expand_builtin_stack_address ();
+
+#ifdef RED_ZONE_SIZE
+ /* Here's how the strub enter, update and leave functions deal with red zones.
+
+ If it weren't for red zones, update, called from within a strub context,
+ would bump the watermark to the top of the stack. Enter and leave, running
+ in the caller, would use the caller's top of stack address both to
+ initialize the watermark passed to the callee, and to start strubbing the
+ stack afterwards.
+
+ Ideally, we'd update the watermark so as to cover the used amount of red
+ zone, and strub starting at the caller's other end of the (presumably
+ unused) red zone. Normally, only leaf functions use the red zone, but at
+ this point we can't tell whether a function is a leaf, nor can we tell how
+ much of the red zone it uses. Furthermore, some strub contexts may have
+ been inlined so that update and leave are called from the same stack frame,
+ and the strub builtins may all have been inlined, turning a strub function
+ into a leaf.
+
+ So cleaning the range from the caller's stack pointer (one end of the red
+ zone) to the (potentially inlined) callee's (other end of the) red zone
+ could scribble over the caller's own red zone.
+
+ We avoid this possibility by arranging for callers that are strub contexts
+ to use their own watermark as the strub starting point. So, if A calls B,
+ and B calls C, B will tell A to strub up to the end of B's red zone, and
+ will strub itself only the part of C's stack frame and red zone that
+ doesn't overlap with B's. With that, we don't need to know who's leaf and
+ who isn't: inlined calls will shrink their strub window to zero, each
+ remaining call will strub some portion of the stack, and eventually the
+ strub context will return to a caller that isn't a strub context itself,
+ that will therefore use its own stack pointer as the strub starting point.
+ It's not a leaf, because strub contexts can't be inlined into non-strub
+ contexts, so it doesn't use the red zone, and it will therefore correctly
+ strub up the callee's stack frame up to the end of the callee's red zone.
+ Neat! */
+ if (true /* (flags_from_decl_or_type (current_function_decl) & ECF_LEAF) */)
+ {
+ poly_int64 red_zone_size = RED_ZONE_SIZE;
+#if STACK_GROWS_DOWNWARD
+ red_zone_size = -red_zone_size;
+#endif
+ stktop = plus_constant (ptr_mode, stktop, red_zone_size);
+ stktop = force_reg (ptr_mode, stktop);
+ }
+#endif
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ rtx wmarkr = force_reg (ptr_mode, wmark);
+
+ rtx_code_label *lab = gen_label_rtx ();
+ do_compare_rtx_and_jump (stktop, wmarkr, STACK_TOPS, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, lab, NULL,
+ profile_probability::very_likely ());
+ emit_move_insn (wmark, stktop);
+
+#if 1 || defined RED_ZONE_SIZE
+ /* If this is an inlined strub function, also bump the watermark for the
+ enclosing function. This avoids a problem with the following scenario: A
+ calls B and B calls C, and both B and C get inlined into A. B allocates
+ temporary stack space before calling C. If we don't update A's watermark,
+ we may use an outdated baseline for the post-C strub_leave, erasing B's
+ temporary stack allocation. We only need this if we're fully expanding
+ strub_leave inline. */
+ tree xwmptr = (optimize > 2
+ ? strub_watermark_parm (current_function_decl)
+ : wmptr);
+ if (wmptr != xwmptr)
+ {
+ wmptr = xwmptr;
+ wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+ wmarkr = force_reg (ptr_mode, wmark);
+
+ do_compare_rtx_and_jump (stktop, wmarkr, STACK_TOPS, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, lab, NULL,
+ profile_probability::very_likely ());
+ emit_move_insn (wmark, stktop);
+ }
+#endif
+
+ emit_label (lab);
+
+ return const0_rtx;
+}
+
+
+/* Expand a call to builtin function __builtin_strub_leave. */
+
+static rtx
+expand_builtin_strub_leave (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 2 || optimize_size || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = NULL_RTX;
+
+#if 1 || defined RED_ZONE_SIZE
+ if (tree wmptr = (optimize
+ ? strub_watermark_parm (current_function_decl)
+ : NULL_TREE))
+ {
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+ stktop = force_reg (ptr_mode, wmark);
+ }
+#endif
+
+ if (!stktop)
+ stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ rtx wmarkr = force_reg (ptr_mode, wmark);
+
+#ifndef STACK_GROWS_DOWNWARD
+ rtx base = stktop;
+ rtx end = wmarkr;
+#else
+ rtx base = wmarkr;
+ rtx end = stktop;
+#endif
+
+ /* We're going to modify it, so make sure it's not e.g. the stack pointer. */
+ base = copy_to_reg (base);
+
+ rtx_code_label *done = gen_label_rtx ();
+ do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, done, NULL,
+ profile_probability::very_likely ());
+
+ if (optimize < 3)
+ expand_call (exp, NULL_RTX, true);
+ else
+ {
+ /* Ok, now we've determined we want to copy the block, so convert the
+ addresses to Pmode, as needed to dereference them to access ptr_mode
+ memory locations, so that we don't have to convert anything within the
+ loop. */
+ base = memory_address (ptr_mode, base);
+ end = memory_address (ptr_mode, end);
+
+ rtx zero = force_operand (const0_rtx, NULL_RTX);
+ int ulen = GET_MODE_SIZE (ptr_mode);
+ rtx incr = plus_constant (Pmode, base, ulen);
+ rtx dstm = gen_rtx_MEM (ptr_mode, base);
+
+ rtx_code_label *loop = gen_label_rtx ();
+ emit_label (loop);
+ emit_move_insn (dstm, zero);
+ emit_move_insn (base, force_operand (incr, NULL_RTX));
+ do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
+ Pmode, NULL_RTX, NULL, loop,
+ profile_probability::very_likely ());
+ }
+
+ emit_label (done);
+
+ return const0_rtx;
+}
+
/* Expand EXP, a call to the alloca builtin. Return NULL_RTX if we
failed and the caller should emit a normal call. */
@@ -7110,6 +7362,27 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
case BUILT_IN_RETURN_ADDRESS:
return expand_builtin_frame_address (fndecl, exp);
+ case BUILT_IN_STACK_ADDRESS:
+ return expand_builtin_stack_address ();
+
+ case BUILT_IN___STRUB_ENTER:
+ target = expand_builtin_strub_enter (exp);
+ if (target)
+ return target;
+ break;
+
+ case BUILT_IN___STRUB_UPDATE:
+ target = expand_builtin_strub_update (exp);
+ if (target)
+ return target;
+ break;
+
+ case BUILT_IN___STRUB_LEAVE:
+ target = expand_builtin_strub_leave (exp);
+ if (target)
+ return target;
+ break;
+
/* Returns the address of the area where the structure is returned.
0 otherwise. */
case BUILT_IN_AGGREGATE_INCOMING_ADDRESS:
diff --git a/gcc/builtins.def b/gcc/builtins.def
index 45a09b4d42d..f83c1f5fcd7 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -874,6 +874,10 @@ DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSL, "ffsl", BT_FN_INT_LONG, ATTR_CONST_NOTHRO
DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSLL, "ffsll", BT_FN_INT_LONGLONG, ATTR_CONST_NOTHROW_LEAF_LIST)
DEF_EXT_LIB_BUILTIN (BUILT_IN_FORK, "fork", BT_FN_PID, ATTR_NOTHROW_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FRAME_ADDRESS, "frame_address", BT_FN_PTR_UINT, ATTR_NULL)
+DEF_GCC_BUILTIN (BUILT_IN_STACK_ADDRESS, "stack_address", BT_FN_PTR, ATTR_NULL)
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_ENTER, "__builtin___strub_enter")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_UPDATE, "__builtin___strub_update")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_LEAVE, "__builtin___strub_leave")
/* [trans-mem]: Adjust BUILT_IN_TM_FREE if BUILT_IN_FREE is changed. */
DEF_LIB_BUILTIN (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_LEAF_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FROB_RETURN_ADDR, "frob_return_addr", BT_FN_PTR_PTR, ATTR_NULL)
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index e60fb31d8c8..edec2afc251 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -69,6 +69,7 @@ static tree handle_asan_odr_indicator_attribute (tree *, tree, tree, int,
static tree handle_stack_protect_attribute (tree *, tree, tree, int, bool *);
static tree handle_no_stack_protector_function_attribute (tree *, tree,
tree, int, bool *);
+static tree handle_strub_attribute (tree *, tree, tree, int, bool *);
static tree handle_noinline_attribute (tree *, tree, tree, int, bool *);
static tree handle_noclone_attribute (tree *, tree, tree, int, bool *);
static tree handle_nocf_check_attribute (tree *, tree, tree, int, bool *);
@@ -306,6 +307,8 @@ const struct attribute_spec c_common_attribute_table[] =
{ "no_stack_protector", 0, 0, true, false, false, false,
handle_no_stack_protector_function_attribute,
attr_stack_protect_exclusions },
+ { "strub", 0, 1, false, true, false, true,
+ handle_strub_attribute, NULL },
{ "noinline", 0, 0, true, false, false, false,
handle_noinline_attribute,
attr_noinline_exclusions },
@@ -1290,6 +1293,90 @@ handle_noipa_attribute (tree *node, tree name, tree, int, bool *no_add_attrs)
return NULL_TREE;
}
+/* Handle a "strub" attribute; arguments as in
+ struct attribute_spec.handler. */
+
+static tree
+handle_strub_attribute (tree *node, tree name,
+ tree args,
+ int ARG_UNUSED (flags), bool *no_add_attrs)
+{
+ bool enable = true;
+
+ if (args
+ && (TREE_CODE (*node) == FUNCTION_TYPE
+ || TREE_CODE (*node) == METHOD_TYPE))
+ {
+ 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;
+ }
+
+ if (val >= 0)
+ TREE_VALUE (args) = build_int_cst (integer_type_node, val);
+ }
+
+ /* 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)
+ {
+ warning (OPT_Wattributes,
+ "%qE attribute ignored because of argument %qE",
+ name, TREE_VALUE (args));
+ *no_add_attrs = true;
+ enable = false;
+ }
+ /* 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));
+
+ /* 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;
+
+ return NULL_TREE;
+}
+
/* Handle a "noinline" attribute; arguments as in
struct attribute_spec.handler. */
diff --git a/gcc/common.opt b/gcc/common.opt
index ed8ab5fbe13..ba0359c3c1d 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,6 +2687,30 @@ 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=all
+Common RejectNegative Var(flag_strub, 3)
+Enable stack scrubbing for all viable functions.
+
+fstrub=at-calls
+Common RejectNegative Var(flag_strub, 1)
+Enable at-calls stack scrubbing for all viable functions.
+
+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 49df8e6dc38..e256db23862 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -77,6 +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.
* 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.
@@ -8672,6 +8673,49 @@ pid_t wait (wait_status_ptr_t p)
@}
@end smallexample
+@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.
+
+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.
+
+A variable whose type carries the @code{strub} attribute causes
+functions that @emph{read} from it to have @code{strub} enabled.
+Reading from data referenced by pointers to such types has the same
+effect.
+
+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.
+
+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.
+
+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)}).
+
+@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.
+
@item unused
@cindex @code{unused} type attribute
When attached to a type (including a @code{union} or a @code{struct}),
@@ -11690,6 +11734,47 @@ option is in effect. Such calls should only be made in debugging
situations.
@end deftypefn
+@deftypefn {Built-in Function} {void *} __builtin_stack_address ()
+This function returns the value of the stack pointer register.
+@end deftypefn
+
+@node Stack Scrubbing
+@section Stack scrubbing 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).
+
+@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.
+@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
+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.
+@end deftypefn
+
@node Vector Extensions
@section Using Vector Instructions through Built-in Functions
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index d8a6b0b60c9..684d87bceae 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,6 +599,7 @@ 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
-fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
-fvtv-counts -fvtv-debug @gol
-finstrument-functions @gol
@@ -15484,6 +15485,47 @@ 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.
+
+@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.
+
+@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.
+
+@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.
+
@item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
@opindex fvtable-verify
This option is only available when compiling C++ code.
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 413446bcc46..7f4bc44d2bb 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -119,6 +119,7 @@ along with GCC; see the file COPYING3. If not see
#include "stringpool.h"
#include "attribs.h"
#include "asan.h"
+#include "ipa-strub.h"
typedef fibonacci_heap <sreal, cgraph_edge> edge_heap_t;
typedef fibonacci_node <sreal, cgraph_edge> edge_heap_node_t;
@@ -396,6 +397,11 @@ 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))
+ {
+ e->inline_failed = CIF_UNSPECIFIED;
+ inlinable = false;
+ }
if (!inlinable && report)
report_inline_failed_reason (e);
return inlinable;
diff --git a/gcc/ipa-split.c b/gcc/ipa-split.c
index c68577d04a9..8c880747eb7 100644
--- a/gcc/ipa-split.c
+++ b/gcc/ipa-split.c
@@ -104,6 +104,7 @@ along with GCC; see the file COPYING3. If not see
#include "ipa-fnsummary.h"
#include "cfgloop.h"
#include "attribs.h"
+#include "ipa-strub.h"
/* Per basic block info. */
@@ -1792,6 +1793,12 @@ execute_split_functions (void)
"section.\n");
return 0;
}
+ if (!strub_splittable_p (node))
+ {
+ if (dump_file)
+ fprintf (dump_file, "Not splitting: function is a strub context.\n");
+ return 0;
+ }
/* We enforce splitting after loop headers when profile info is not
available. */
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
new file mode 100644
index 00000000000..cf7d811779e
--- /dev/null
+++ b/gcc/ipa-strub.c
@@ -0,0 +1,2999 @@
+/* strub (stack scrubbing) support.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "gimplify.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "gimple-iterator.h"
+#include "gimplify-me.h"
+#include "tree-into-ssa.h"
+#include "tree-ssa.h"
+#include "tree-cfg.h"
+#include "cfghooks.h"
+#include "cfgloop.h"
+#include "cfgcleanup.h"
+#include "tree-eh.h"
+#include "except.h"
+#include "builtins.h"
+#include "attribs.h"
+#include "tree-inline.h"
+#include "cgraph.h"
+#include "alloc-pool.h"
+#include "symbol-summary.h"
+#include "ipa-prop.h"
+#include "ipa-fnsummary.h"
+#include "gimple-fold.h"
+#include "fold-const.h"
+#include "gimple-walk.h"
+#include "tree-dfa.h"
+#include "langhooks.h"
+#include "calls.h"
+#include "vec.h"
+#include "stor-layout.h"
+#include "varasm.h"
+#include "alias.h"
+#include "diagnostic.h"
+#include "intl.h"
+#include "ipa-strub.h"
+
+#if BUILDING_GCC_MAJOR >= 11
+# include "symtab-thunks.h"
+# include "attr-fnspec.h"
+# define HAVE_ATTR_FNSPEC 1
+# define FOR_GCC_11P 1
+#else
+# define HAVE_ATTR_FNSPEC 0
+# define FOR_GCC_11P 0
+#endif
+
+/* Const and pure functions that gain a watermark parameter for strub purposes
+ are still regarded as such, which may cause the inline expansions of the
+ __strub builtins to malfunction. Ideally, attribute "fn spec" would enable
+ us to inform the backend about requirements and side effects of the call, but
+ call_fusage building in calls.c:expand_call does not even look at
+ attr_fnspec, so we resort to asm loads and updates to attain an equivalent
+ effect. Once expand_call gains the ability to issue extra memory uses and
+ clobbers based on pure/const function's fnspec, we can define this to 1. */
+#define ATTR_FNSPEC_DECONST_WATERMARK 0
+
+enum strub_mode {
+ /* This mode denotes a regular function, that does not require stack
+ scrubbing (strubbing). It may call any other functions, but if
+ it calls AT_CALLS (or WRAPPED) ones, strubbing logic is
+ automatically introduced around those calls (the latter, by
+ inlining INTERNAL wrappers). */
+ STRUB_DISABLED = 0,
+
+ /* This denotes a function whose signature is (to be) modified to
+ take an extra parameter, for stack use annotation, and its
+ callers must initialize and pass that argument, and perform the
+ strubbing. Functions that are explicitly marked with attribute
+ strub must have the mark visible wherever the function is,
+ including aliases, and overriders and overriding methods.
+ Functions that are implicitly marked for strubbing, for accessing
+ variables explicitly marked as such, will only select this
+ strubbing method if they are internal to a translation unit. It
+ can only be inlined into other strubbing functions, i.e.,
+ STRUB_AT_CALLS or STRUB_WRAPPED. */
+ STRUB_AT_CALLS = 1,
+
+ /* This denotes a function that is to perform strubbing internally,
+ without any changes to its interface (the function is turned into
+ a strubbing wrapper, and its original body is moved to a separate
+ STRUB_WRAPPED function, with a modified interface). Functions
+ may be explicitly marked with attribute strub(2), and the
+ attribute must be visible at the point of definition. Functions
+ that are explicitly marked for strubbing, for accessing variables
+ explicitly marked as such, may select this strubbing mode if
+ their interface cannot change, e.g. because its interface is
+ visible to other translation units, directly, by indirection
+ (having its address taken), inheritance, etc. Functions that use
+ this method must not have the noclone attribute, nor the noipa
+ one. Functions marked as always_inline may select this mode, but
+ they are NOT wrapped, they remain unchanged, and are only inlined
+ into strubbed contexts. Once non-always_inline functions are
+ wrapped, the wrapper becomes STRUB_WRAPPER, and the wrapped becomes
+ STRUB_WRAPPED. */
+ STRUB_INTERNAL = 2,
+
+ /* This denotes a function whose stack is not strubbed, but that is
+ nevertheless explicitly or implicitly marked as callable from strubbing
+ functions. Normally, only STRUB_AT_CALLS (and STRUB_INTERNAL ->
+ STRUB_WRAPPED) functions can be called from strubbing contexts (bodies of
+ STRUB_AT_CALLS, STRUB_INTERNAL and STRUB_WRAPPED functions), but attribute
+ strub(3) enables other functions to be (indirectly) called from these
+ contexts. Some builtins and internal functions may be implicitly marked as
+ STRUB_CALLABLE. */
+ STRUB_CALLABLE = 3,
+
+ /* This denotes the function that took over the body of a
+ STRUB_INTERNAL function. At first, it's only called by its
+ wrapper, but the wrapper may be inlined. The wrapped function,
+ in turn, can only be inlined into other functions whose stack
+ frames are strubbed, i.e., that are STRUB_WRAPPED or
+ STRUB_AT_CALLS. */
+ STRUB_WRAPPED = -1,
+
+ /* This denotes the wrapper function that replaced the STRUB_INTERNAL
+ function. This mode overrides the STRUB_INTERNAL mode at the time the
+ internal to-be-wrapped function becomes a wrapper, so that inlining logic
+ can tell one from the other. */
+ STRUB_WRAPPER = -2,
+
+ /* 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
+get_strub_attr_from_type (tree type)
+{
+ return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
+}
+
+static tree
+get_strub_attr_from_decl (tree decl)
+{
+ tree ret = lookup_attribute ("strub", DECL_ATTRIBUTES (decl));
+ if (ret)
+ return ret;
+ return get_strub_attr_from_type (TREE_TYPE (decl));
+}
+
+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);
+
+#if 0 /* ??? use symbolic mode names with interned strings? */
+ char *s = NULL;
+
+ switch (strub_mode)
+ {
+
+ }
+#endif
+}
+
+static enum strub_mode
+get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
+{
+ enum strub_mode mode = STRUB_DISABLED;
+
+ if (strub_attr)
+ {
+ 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 ();
+ }
+
+ return mode;
+}
+
+static enum strub_mode
+get_strub_mode_from_decl (tree fndecl)
+{
+ return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
+}
+
+static enum strub_mode
+get_strub_mode (cgraph_node *node)
+{
+ return get_strub_mode_from_decl (node->decl);
+}
+
+static enum strub_mode
+get_strub_mode_from_type (tree type)
+{
+ bool var_p = !FUNC_OR_METHOD_TYPE_P (type);
+ tree attr = get_strub_attr_from_type (type);
+
+ if (attr)
+ return get_strub_mode_from_attr (attr, var_p);
+
+ if (flag_strub > 0 && !var_p)
+ return STRUB_CALLABLE;
+
+ return STRUB_DISABLED;
+}
+
+static bool
+calls_builtin_va_start_p (cgraph_node *node)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (fndecl_built_in_p (cdecl, BUILT_IN_VA_START))
+ return true;
+ }
+
+ return result;
+}
+
+static bool
+calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!fndecl_built_in_p (cdecl, BUILT_IN_APPLY_ARGS))
+ continue;
+
+ result = true;
+
+ if (!report)
+ break;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "at-calls %<strub%> does not support call to %qD",
+ cdecl);
+ }
+
+ return result;
+}
+
+static inline bool
+strub_always_inline_p (cgraph_node *node)
+{
+ return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
+}
+
+static inline bool
+can_strub_p (cgraph_node *node, bool report = false)
+{
+ bool result = true;
+
+ if (!report && strub_always_inline_p (node))
+ return result;
+
+ if (lookup_attribute ("noipa", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for %<strub%>"
+ " because of attribute %<noipa%>",
+ node->decl);
+ }
+
+ /* We can't, and don't want to vectorize the watermark and other
+ strub-introduced parms. */
+ if (lookup_attribute ("simd", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for %<strub%>"
+ " because of attribute %<simd%>",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+can_strub_at_calls_p (cgraph_node *node, bool report = false)
+{
+ bool result = !report || can_strub_p (node, report);
+
+ if (!result && !report)
+ return result;
+
+ return !calls_builtin_apply_args_p (node, report);
+}
+
+#define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
+
+/* We can't perform internal strubbing if the function body involves certain
+ features:
+
+ - a non-default __builtin_va_start (e.g. x86's __builtin_ms_va_start) is
+ currently unsupported because we can't discover the corresponding va_copy and
+ va_end decls in the wrapper, and we don't convey the alternate variable
+ arguments ABI to the modified wrapped function. The default
+ __builtin_va_start is supported by calling va_start/va_end at the wrapper,
+ that takes variable arguments, passing a pointer to the va_list object to the
+ wrapped function, that runs va_copy from it where the original function ran
+ va_start.
+
+ __builtin_next_arg is currently unsupported because the wrapped function
+ won't be a variable argument function. We could process it in the wrapper,
+ that remains a variable argument function, and replace calls in the wrapped
+ body, but we currently don't.
+
+ __builtin_return_address is rejected because it's generally used when the
+ actual caller matters, and introducing a wrapper breaks such uses as those in
+ the unwinder. */
+
+static bool
+can_strub_internally_p (cgraph_node *node, bool report = false)
+{
+ bool result = !report || can_strub_p (node, report);
+
+ if (!result && !report)
+ return result;
+
+ if (!report && strub_always_inline_p (node))
+ return 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, but we'd have to prevent remapping of the labels. */
+ if (lookup_attribute ("noclone", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for internal %<strub%>"
+ " because of attribute %<noclone%>",
+ node->decl);
+ }
+
+ if (node->has_gimple_body_p ())
+ {
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!((fndecl_built_in_p (cdecl, BUILT_IN_VA_START)
+ && cdecl != builtin_decl_explicit (BUILT_IN_VA_START))
+ || fndecl_built_in_p (cdecl, BUILT_IN_NEXT_ARG)
+ || fndecl_built_in_p (cdecl, BUILT_IN_RETURN_ADDRESS)))
+ continue;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "%qD is not eligible for internal %<strub%> "
+ "because it calls %qD",
+ node->decl, cdecl);
+ }
+
+ struct function *fun = DECL_STRUCT_FUNCTION (node->decl);
+ if (fun->has_nonlocal_label)
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for internal %<strub%> "
+ "because it contains a non-local goto target",
+ node->decl);
+ }
+
+ if (fun->has_forced_label_in_static)
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for internal %<strub%> "
+ "because the address of a local label escapes",
+ node->decl);
+ }
+
+ /* Catch any other case that would prevent versioning/cloning
+ so as to also have it covered above. */
+ gcc_checking_assert (!result /* || !node->has_gimple_body_p () */
+ || tree_versionable_function_p (node->decl));
+
+
+ /* Label values referenced 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. */
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ glabel *label_stmt = dyn_cast <glabel *> (gsi_stmt (gsi));
+ tree target;
+
+ if (!label_stmt)
+ break;
+
+ 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;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (label_stmt),
+ "internal %<strub%> does not support user labels");
+ }
+ }
+
+ if (list_length (TYPE_ARG_TYPES (TREE_TYPE (node->decl)))
+ >= (((HOST_WIDE_INT) 1 << IPA_PARAM_MAX_INDEX_BITS)
+ - STRUB_INTERNAL_MAX_EXTRA_ARGS))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD has too many arguments for internal %<strub%>",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+strub_from_body_p (cgraph_node *node)
+{
+ if (!node->has_gimple_body_p ())
+ return false;
+
+ /* If any local variable is marked for strub... */
+ unsigned i;
+ tree var;
+ FOR_EACH_LOCAL_DECL (DECL_STRUCT_FUNCTION (node->decl),
+ i, var)
+ if (get_strub_mode_from_type (TREE_TYPE (var))
+ != STRUB_DISABLED)
+ return true;
+
+ /* Now scan the body for loads with strub types. */
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!gimple_assign_load_p (stmt))
+ continue;
+
+ tree rhs = gimple_assign_rhs1 (stmt);
+ if (get_strub_mode_from_type (TREE_TYPE (rhs))
+ != STRUB_DISABLED)
+ return true;
+ }
+
+ return false;
+}
+
+/* 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)
+{
+ if (DECL_BUILT_IN_CLASS (node->decl) != BUILT_IN_NORMAL)
+ return false;
+
+ enum built_in_function fcode = DECL_FUNCTION_CODE (node->decl);
+
+ switch (fcode)
+ {
+ case BUILT_IN_NONE:
+ gcc_unreachable ();
+
+ /* This temporarily allocates stack for the call, and we can't reasonably
+ update the watermark for that. Besides, we don't check the actual call
+ target, nor its signature, and it seems to be overkill to as much as
+ try to do so. */
+ case BUILT_IN_APPLY:
+ return false;
+
+ /* Conversely, this shouldn't be called from within strub contexts, since
+ the caller may have had its signature modified. STRUB_INTERNAL is ok,
+ the call will remain in the STRUB_WRAPPER, and removed from the
+ STRUB_WRAPPED clone. */
+ case BUILT_IN_APPLY_ARGS:
+ return false;
+
+ /* ??? Make all other builtins callable. We wish to make any builtin call
+ the compiler might introduce on its own callable. Anything that is
+ predictable enough as to be known not to allow stack data that should
+ be strubbed to unintentionally escape to non-strub contexts can be
+ allowed, and pretty much every builtin appears to fit this description.
+ The exceptions to this rule seem to be rare, and only available as
+ explicit __builtin calls, so let's keep it simple and allow all of
+ them... */
+ default:
+ return true;
+ }
+}
+
+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);
+
+ /* 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;
+ /* 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
+ safely undergo at-calls strubbing. Internal mode will still be used in
+ functions that request it explicitly with attribute strub(2), or when the
+ function body requires strubbing and at-calls strubbing is not viable. */
+ const bool strub_flag_at_calls = flag_strub == 1;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo internal strubbing. At-calls mode will still be used in
+ functions that requiest it explicitly with attribute strub() or strub(1),
+ or when the function body requires strubbing and internal strubbing is not
+ viable. */
+ const bool strub_flag_internal = flag_strub == 2;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo strubbing in either mode. When both modes are viable,
+ at-calls is preferred. */
+ const bool strub_flag_either = flag_strub == 3;
+ /* Besides the default behavior, enable strub implicitly for all viable
+ functions. */
+ const bool strub_flag_viable = flag_strub > 0;
+
+ /* The consider_* variables should be true if selecting the corresponding
+ strub modes would be consistent with requests from attributes and command
+ line flags. Attributes associated with functions pretty much mandate a
+ selection, and should report an error if not satisfied; strub_flag_auto
+ implicitly enables some viable strub mode if that's required by references
+ to variables marked for strub; strub_flag_viable enables strub if viable
+ (even when favoring one mode, body-requested strub can still be satisfied
+ by either mode), and falls back to callable, silently unless variables
+ require strubbing. */
+
+ const bool consider_at_calls
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_AT_CALLS
+ : true));
+ const bool consider_internal
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_INTERNAL
+ : true));
+
+ const bool consider_callable
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_CALLABLE
+ : (strub_flag_viable
+ || strub_callable_builtin_p (node))));
+
+ /* This is a shorthand for either strub-enabled mode. */
+ const bool consider_strub
+ = (consider_at_calls || consider_internal);
+
+ /* We can cope with always_inline functions even with noipa and noclone,
+ because we just leave them alone. */
+ const bool is_always_inline
+ = strub_always_inline_p (node);
+
+ /* Strubbing in general, and each specific strub mode, may have its own set of
+ requirements. We require noipa for strubbing, either because of cloning
+ required for internal strub, or because of caller enumeration required for
+ at-calls strub. We don't consider the at-calls mode eligible if it's not
+ even considered, it has no further requirements. Internal mode requires
+ cloning and the absence of certain features in the body and, like at-calls,
+ it's not eligible if it's not even under consideration.
+
+ ??? Do we need target hooks for further constraints? E.g., x86's
+ "interrupt" attribute breaks internal strubbing because the wrapped clone
+ carries the attribute and thus isn't callable; in this case, we could use a
+ target hook to adjust the clone instead. */
+ const bool strub_eligible
+ = (consider_strub
+ && (is_always_inline || can_strub_p (node)));
+ const bool at_calls_eligible
+ = (consider_at_calls && strub_eligible
+ && can_strub_at_calls_p (node));
+ const bool internal_eligible
+ = (consider_internal && strub_eligible
+ && (is_always_inline
+ || can_strub_internally_p (node)));
+
+ /* In addition to the strict eligibility requirements, some additional
+ constraints are placed on implicit selection of certain modes. These do
+ not prevent the selection of a mode if explicitly specified as part of a
+ function interface (the strub attribute), but they may prevent modes from
+ being selected by the command line or by function bodies. The only actual
+ constraint is on at-calls mode: since we change the function's exposed
+ signature, we won't do it implicitly if the function can possibly be used
+ in ways that do not expect the signature change, e.g., if the function is
+ available to or interposable by other units, if its address is taken,
+ etc. */
+ const bool at_calls_viable
+ = (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->binds_to_current_def_p ()
+ && node->can_be_local_p ()))
+ && node->only_called_directly_p ())));
+ const bool internal_viable
+ = (internal_eligible);
+
+ /* Shorthand. */
+ const bool strub_viable
+ = (at_calls_viable || internal_viable);
+
+ /* We wish to analyze the body, to look for implicit requests for strub, both
+ to implicitly enable it when the body calls for it, and to report errors if
+ the body calls for it but neither mode is viable (even if that follows from
+ non-eligibility because of the explicit specification of some non-strubbing
+ mode). We can refrain from scanning the body only in rare circumstances:
+ when strub is enabled by a function attribute (scanning might be redundant
+ in telling us to also enable it), and when we are enabling strub implicitly
+ but there are non-viable modes: we want to know whether strubbing is
+ required, to fallback to another mode, even if we're only enabling a
+ certain mode, or, when either mode would do, to report an error if neither
+ happens to be viable. */
+ const bool analyze_body
+ = (strub_attr
+ ? !consider_strub
+ : (strub_flag_auto
+ || (strub_flag_viable && (!at_calls_viable && !internal_viable))
+ || (strub_flag_either && !strub_viable)));
+
+ /* Cases in which strubbing is enabled or disabled by strub_flag_auto.
+ Unsatisfiable requests ought to be reported. */
+ const bool strub_required
+ = ((strub_attr && consider_strub)
+ || (analyze_body && strub_from_body_p (node)));
+
+ /* Besides the required cases, we want to abide by the requests to enabling on
+ an if-viable basis. */
+ const bool strub_enable
+ = (strub_required
+ || (strub_flag_at_calls && at_calls_viable)
+ || (strub_flag_internal && internal_viable)
+ || (strub_flag_either && strub_viable));
+
+ /* And now we're finally ready to select a mode that abides by the viability
+ and eligibility constraints, and that satisfies the strubbing requirements
+ and requests, subject to the constraints. If both modes are viable and
+ strub is to be enabled, pick STRUB_AT_CALLS unless STRUB_INTERNAL was named
+ as preferred. */
+ 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
+ : (strub_enable && at_calls_viable)
+ ? (strub_required && !strub_attr
+ ? STRUB_AT_CALLS_OPT
+ : STRUB_AT_CALLS)
+ : consider_callable
+ ? STRUB_CALLABLE
+ : STRUB_DISABLED);
+
+ switch (mode)
+ {
+ case STRUB_CALLABLE:
+ if (is_always_inline)
+ break;
+ /* Fall through. */
+
+ case STRUB_DISABLED:
+ if (strub_enable && !strub_attr)
+ {
+ gcc_checking_assert (analyze_body);
+ error_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD requires %<strub%>,"
+ " but no viable %<strub%> mode was found",
+ node->decl);
+ break;
+ }
+ /* Fall through. */
+
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ /* Differences from an mode requested through a function attribute are
+ 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:
+ gcc_unreachable ();
+ }
+
+ return mode;
+}
+
+/* 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)
+{
+ gcc_checking_assert (override
+ || !(DECL_P (fndt)
+ ? get_strub_attr_from_decl (fndt)
+ : get_strub_attr_from_type (fndt)));
+
+ tree attr = tree_cons (get_identifier ("strub"),
+ get_strub_mode_attr_value (mode),
+ NULL_TREE);
+ tree *attrp = NULL;
+ if (DECL_P (fndt))
+ {
+ gcc_checking_assert (FUNC_OR_METHOD_TYPE_P (TREE_TYPE (fndt)));
+ attrp = &DECL_ATTRIBUTES (fndt);
+ }
+ else if (FUNC_OR_METHOD_TYPE_P (fndt))
+ attrp = &TYPE_ATTRIBUTES (fndt);
+ else
+ gcc_unreachable ();
+
+ TREE_CHAIN (attr) = *attrp;
+ *attrp = attr;
+}
+
+void
+strub_make_callable (tree fndt)
+{
+ strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
+}
+
+static void
+set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+ enum strub_mode req_mode = get_strub_mode_from_attr (attr);
+
+ if (attr)
+ {
+ /* Check for and report incompatible mode changes. */
+ if (mode != req_mode
+ && !(req_mode == STRUB_INTERNAL
+ && (mode == STRUB_WRAPPED
+ || mode == STRUB_WRAPPER))
+ && !((req_mode == STRUB_INTERNAL
+ || req_mode == STRUB_AT_CALLS
+ || req_mode == STRUB_CALLABLE)
+ && 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));
+ if (node->alias)
+ {
+ cgraph_node *target = node->ultimate_alias_target ();
+ if (target != node)
+ error_at (DECL_SOURCE_LOCATION (target->decl),
+ "the incompatible selection was determined"
+ " by ultimate alias target %qD",
+ target->decl);
+ }
+
+ /* Report any incompatibilities with explicitly-requested strub. */
+ switch (req_mode)
+ {
+ case STRUB_AT_CALLS:
+ can_strub_at_calls_p (node, true);
+ break;
+
+ case STRUB_INTERNAL:
+ can_strub_internally_p (node, true);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Drop any incompatible strub attributes leading the decl attribute
+ chain. Return if we find one with the mode we need. */
+ for (;;)
+ {
+ if (mode == req_mode)
+ return;
+
+ if (DECL_ATTRIBUTES (node->decl) != attr)
+ break;
+
+ DECL_ATTRIBUTES (node->decl) = TREE_CHAIN (attr);
+ attr = get_strub_attr_from_decl (node->decl);
+ if (!attr)
+ break;
+
+ req_mode = get_strub_mode_from_attr (attr);
+ }
+ }
+ else if (mode == req_mode)
+ return;
+
+ strub_set_fndt_mode_to (node->decl, mode, attr);
+}
+
+static void
+set_strub_mode (cgraph_node *node)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+
+ if (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:
+ case STRUB_AT_CALLS_OPT:
+ return;
+
+ case STRUB_DISABLED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ cgraph_node *xnode = node;
+ if (node->alias)
+ xnode = node->ultimate_alias_target ();
+ /* Weakrefs may remain unresolved (the above will return node) if
+ their targets are not defined, so make sure we compute a strub
+ mode for them, instead of defaulting to STRUB_DISABLED and
+ rendering them uncallable. */
+ enum strub_mode mode = (xnode != node && !xnode->alias
+ ? get_strub_mode (xnode)
+ : compute_strub_mode (node, attr));
+
+ set_strub_mode_to (node, mode);
+}
+
+/* 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. */
+
+static bool
+strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode caller_mode = get_strub_mode (caller);
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS_OPT:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return callee_mode != STRUB_INLINABLE;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_AT_CALLS_OPT:
+ case STRUB_INTERNAL:
+ case STRUB_WRAPPER:
+ return (flag_strub >= 0);
+
+ case STRUB_DISABLED:
+ return false;
+
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ 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. */
+
+bool
+strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ case STRUB_AT_CALLS_OPT:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return true;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ strub_mode caller_mode = get_strub_mode (caller);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ case STRUB_AT_CALLS_OPT:
+ return true;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ return false;
+}
+
+/* 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;
+
+ 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_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)
+ {
+ gcc_checking_assert (!e->indirect_unknown_callee);
+ if (!strub_callable_from_p (e->callee, node))
+ {
+ if (get_strub_mode (e->callee) == STRUB_INLINABLE)
+ error_at (gimple_location (e->call_stmt),
+ "calling %<always_inline%> %<strub%> %qD"
+ " in non-%<strub%> context %qD",
+ e->callee->decl, node->decl);
+ else if (fndecl_built_in_p (e->callee->decl, BUILT_IN_APPLY_ARGS)
+ && get_strub_mode (node) == STRUB_INTERNAL)
+ /* This is ok, it will be kept in the STRUB_WRAPPER, and removed
+ from the STRUB_WRAPPED's strub context. */
+ continue;
+ else
+ error_at (gimple_location (e->call_stmt),
+ "calling non-%<strub%> %qD in %<strub%> context %qD",
+ e->callee->decl, node->decl);
+ }
+ }
+ }
+
+ /* ??? 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 {
+
+const pass_data pass_data_ipa_strub_mode = {
+ SIMPLE_IPA_PASS,
+ "strubm",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ 0, // properties_finish
+};
+
+class pass_ipa_strub_mode : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub_mode (gcc::context *ctxt)
+ : simple_ipa_opt_pass (pass_data_ipa_strub_mode, ctxt)
+ {}
+ 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
+ overhead. */
+ if (flag_strub == -2)
+ flag_strub = 0;
+ return flag_strub;
+ }
+ virtual unsigned int execute (function *);
+};
+
+const pass_data pass_data_ipa_strub = {
+ SIMPLE_IPA_PASS,
+ "strub",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ TODO_update_ssa
+ | TODO_cleanup_cfg
+ | TODO_rebuild_cgraph_edges
+ | TODO_verify_il, // properties_finish
+};
+
+class pass_ipa_strub : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub (gcc::context *ctxt)
+ : 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 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_variant_type_copy (ptr_type_node))
+
+ DEF_TYPE (pwmt, build_reference_type (get_wmt ()))
+
+ DEF_TYPE (qpwmt,
+ build_qualified_type (get_pwmt (),
+ TYPE_QUAL_RESTRICT
+ /* | TYPE_QUAL_CONST */))
+
+ DEF_TYPE (qptr,
+ build_qualified_type (ptr_type_node,
+ TYPE_QUAL_RESTRICT
+ | TYPE_QUAL_CONST))
+
+ DEF_TYPE (qpvalst,
+ build_qualified_type (build_reference_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); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ decl = add_builtin_function \
+ ("__builtin_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ NULL, NULL); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ } \
+ return decl; \
+ }
+
+ DEF_NM_BUILTIN (stack_address,
+ BUILT_IN_STACK_ADDRESS,
+ (ptr_type_node, NULL))
+
+#undef DEF_NM_BUILTIN
+
+#define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST) \
+ static tree get_ ## NAME () { \
+ tree decl = builtin_decl_explicit (CODE); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ tree attrs = NULL; \
+ if (FNSPEC && HAVE_ATTR_FNSPEC) \
+ attrs = tree_cons (get_identifier ("fn spec"), \
+ build_tree_list \
+ (NULL_TREE, \
+ build_string (strlen (FNSPEC), \
+ (FNSPEC))), \
+ attrs); \
+ decl = add_builtin_function_ext_scope \
+ ("__builtin___strub_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ "__strub_" #NAME, attrs); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ } \
+ return decl; \
+ }
+
+ DEF_SS_BUILTIN (enter, ". Ot",
+ BUILT_IN___STRUB_ENTER,
+ (void_type_node, get_qpwmt (), NULL))
+ DEF_SS_BUILTIN (update, ". Wt",
+ BUILT_IN___STRUB_UPDATE,
+ (void_type_node, get_qpwmt (), NULL))
+ DEF_SS_BUILTIN (leave, ". w ",
+ BUILT_IN___STRUB_LEAVE,
+ (void_type_node, get_qpwmt (), NULL))
+
+#undef DEF_SS_BUILTIN
+
+#define DEF_IDENT(NAME) \
+ static inline tree get_ ## NAME () { \
+ static tree identifier = NULL_TREE; \
+ if (!identifier) \
+ identifier = get_identifier (".strub." #NAME); \
+ return identifier; \
+ }
+
+ DEF_IDENT (watermark_ptr)
+ DEF_IDENT (va_list_ptr)
+ DEF_IDENT (apply_args)
+
+#undef DEF_IDENT
+
+ static inline int adjust_at_calls_type (tree);
+ static inline void adjust_at_calls_call (cgraph_edge *, int);
+ static inline void adjust_at_calls_calls (cgraph_node *);
+
+ static inline gimple_seq
+ call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
+ gimple_seq seq = NULL)
+ {
+ tree uwm = get_update ();
+ gcall *update = gimple_build_call (uwm, 1, wmptr);
+ if (node)
+ gimple_set_location (update, DECL_SOURCE_LOCATION (node->decl));
+ gimple_seq_add_stmt (&seq, update);
+ if (node)
+#if !IMPLICIT_CGRAPH_EDGES
+ node->create_edge (cgraph_node::get_create (uwm), update, count, false);
+#else
+ (void)count;
+#endif
+ return seq;
+ }
+
+};
+
+} // anon namespace
+
+typedef hash_set<tree> indirect_parms_t;
+
+static tree
+maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
+{
+ if (DECL_P (op))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (op))
+ {
+ tree ret = gimple_fold_indirect_ref (op);
+ if (!ret)
+ ret = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (op)),
+ op,
+ build_int_cst (TREE_TYPE (op), 0));
+ return ret;
+ }
+ }
+ else if (TREE_CODE (op) == ADDR_EXPR
+ && DECL_P (TREE_OPERAND (op, 0)))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (TREE_OPERAND (op, 0)))
+ return TREE_OPERAND (op, 0);
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_make_indirect (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ indirect_parms_t &indirect_parms = *(indirect_parms_t *)wi->info;
+
+ if (!*op || TYPE_P (*op))
+ {
+ *rec = 0;
+ return NULL_TREE;
+ }
+
+ if (tree repl = maybe_make_indirect (indirect_parms, *op, rec))
+ {
+ *op = repl;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ gimple_stmt_iterator &gsi = *(gimple_stmt_iterator *)wi->info;
+
+ *rec = 0;
+
+ if (!*op || TREE_CODE (*op) != ADDR_EXPR)
+ return NULL_TREE;
+
+ if (!is_gimple_val (*op))
+ {
+ tree ret = force_gimple_operand_gsi (&gsi, *op, true,
+ NULL_TREE, true, GSI_SAME_STMT);
+ gcc_assert (ret != *op);
+ *op = ret;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+build_ref_type_for (tree parm, bool nonaliased = true)
+{
+ gcc_checking_assert (TREE_CODE (parm) == PARM_DECL);
+
+ tree ref_type = build_reference_type (TREE_TYPE (parm));
+
+ if (!nonaliased)
+ return ref_type;
+
+ /* Each PARM turned indirect still points to the distinct memory area at the
+ wrapper, and the reference in unchanging, so we might qualify it, but...
+ const is not really important, since we're only using default defs for the
+ reference parm anyway, and not introducing any defs, and restrict seems to
+ cause trouble. E.g., libgnat/s-concat3.adb:str_concat_3 has memmoves that,
+ if it's wrapped, the memmoves are deleted in dse1. Using a distinct alias
+ set seems to not run afoul of this problem, and it hopefully enables the
+ compiler to tell the pointers do point to objects that are not otherwise
+ aliased. */
+#if 1
+ tree qref_type = build_variant_type_copy (ref_type);
+
+ TYPE_ALIAS_SET (qref_type) = new_alias_set ();
+ record_alias_subset (TYPE_ALIAS_SET (qref_type), get_alias_set (ref_type));
+
+ return qref_type;
+#else
+ tree qref_type = build_qualified_type (ref_type,
+ TYPE_QUAL_RESTRICT
+ | TYPE_QUAL_CONST);
+
+ return qref_type;
+#endif
+}
+
+/* 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)
+{
+#if IMPLICIT_CGRAPH_EDGES
+ return;
+#endif
+
+ cgraph_node *node = cgraph_node::get_create (current_function_decl);
+
+ for (gimple_stmt_iterator gsi = gsi_start (seq);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!is_a <gcall *> (stmt))
+ 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);
+ }
+}
+
+static void
+gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
+{
+ if (!seq)
+ return;
+
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (gimple_has_location (stmt))
+ annotate_all_with_location (seq, gimple_location (stmt));
+
+ gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+ 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;
+ bool nothrow_p = (must_not_throw_p
+ || (call && gimple_call_nothrow_p (call))
+ || (eh_lp <= 0
+ && (TREE_NOTHROW (cfun->decl)
+ || !flag_exceptions)));
+
+ if (noreturn_p && nothrow_p)
+ return;
+
+ /* Don't expect an EH edge if we're not to throw, or if we're not in an EH
+ region yet. */
+ bool no_eh_edge_p = (nothrow_p || !eh_lp);
+ bool must_end_bb = stmt_ends_bb_p (stmt);
+
+ edge eft = NULL, eeh = NULL;
+ if (must_end_bb && !(noreturn_p && no_eh_edge_p))
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+
+ edge e;
+ edge_iterator ei;
+ FOR_EACH_EDGE (e, ei, gsi_bb (gsi)->succs)
+ {
+ if ((e->flags & EDGE_EH))
+ {
+ gcc_checking_assert (!eeh);
+ eeh = e;
+#if !CHECKING_P
+ if (eft || noreturn_p)
+ break;
+#endif
+ }
+ if ((e->flags & EDGE_FALLTHRU))
+ {
+ gcc_checking_assert (!eft);
+ eft = e;
+#if !CHECKING_P
+ if (eeh || no_eh_edge_p)
+ break;
+#endif
+ }
+ }
+
+ gcc_checking_assert (!(eft && (eft->flags & EDGE_FALLTHRU))
+ == noreturn_p);
+ gcc_checking_assert (!(eeh && (eeh->flags & EDGE_EH))
+ == no_eh_edge_p);
+ gcc_checking_assert (eft != eeh);
+ }
+
+ if (!noreturn_p)
+ {
+ gimple_seq nseq = nothrow_p ? seq : gimple_seq_copy (seq);
+
+ if (must_end_bb)
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+ add_call_edges_for_seq (nseq, eft->count ());
+ gsi_insert_seq_on_edge_immediate (eft, nseq);
+ }
+ else
+ {
+ add_call_edges_for_seq (nseq, gsi_bb (gsi)->count);
+ gsi_insert_seq_after (&gsi, nseq, GSI_SAME_STMT);
+ }
+ }
+
+ if (nothrow_p)
+ return;
+
+ if (eh_lp)
+ {
+ add_call_edges_for_seq (seq, eeh->count ());
+ gsi_insert_seq_on_edge_immediate (eeh, seq);
+ return;
+ }
+
+ /* A throwing call may appear within a basic block in a function that doesn't
+ have any EH regions. We're going to add a cleanup if so, therefore the
+ block will have to be split. */
+ basic_block bb = gsi_bb (gsi);
+ if (!gsi_one_before_end_p (gsi))
+ split_block (bb, stmt);
+
+ /* Create a new block for the EH cleanup. */
+ basic_block bb_eh_cleanup = create_empty_bb (bb);
+ if (dom_info_available_p (CDI_DOMINATORS))
+ set_immediate_dominator (CDI_DOMINATORS, bb_eh_cleanup, bb);
+ if (current_loops)
+ add_bb_to_loop (bb_eh_cleanup, current_loops->tree_root);
+
+ /* Make the new block an EH cleanup for the call. */
+ eh_region new_r = gen_eh_region_cleanup (NULL);
+ eh_landing_pad lp = gen_eh_landing_pad (new_r);
+ tree label = gimple_block_label (bb_eh_cleanup);
+ lp->post_landing_pad = label;
+ EH_LANDING_PAD_NR (label) = lp->index;
+ add_stmt_to_eh_lp (stmt, lp->index);
+
+ /* Add the cleanup code to the EH cleanup block. */
+ gsi = gsi_after_labels (bb_eh_cleanup);
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
+
+ /* And then propagate the exception further. */
+ gresx *resx = gimple_build_resx (new_r->index);
+ if (gimple_has_location (stmt))
+ gimple_set_location (resx, gimple_location (stmt));
+ gsi_insert_before (&gsi, resx, GSI_SAME_STMT);
+
+ /* Finally, wire the EH cleanup block into the CFG. */
+ make_eh_edges (stmt);
+ add_call_edges_for_seq (seq, single_pred_edge (bb_eh_cleanup)->count ());
+}
+
+/* Copy the attribute list at *ATTRS, minus any NAME attributes, leaving
+ shareable trailing nodes alone. */
+
+static inline void
+remove_named_attribute_unsharing (const char *name, tree *attrs)
+{
+ while (tree found = lookup_attribute (name, *attrs))
+ {
+ /* Copy nodes up to the next NAME attribute. */
+ while (*attrs != found)
+ {
+ *attrs = tree_cons (TREE_PURPOSE (*attrs),
+ TREE_VALUE (*attrs),
+ TREE_CHAIN (*attrs));
+ attrs = &TREE_CHAIN (*attrs);
+ }
+ /* Then drop it. */
+ gcc_checking_assert (*attrs == found);
+ *attrs = TREE_CHAIN (*attrs);
+ }
+}
+
+static int last_cgraph_order;
+
+static void
+ipa_strub_set_mode_for_new_functions ()
+{
+ if (symtab->order == last_cgraph_order)
+ return;
+
+ cgraph_node *node;
+
+ /* 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. */
+ for (int aliases = 0; aliases <= 1; aliases++)
+ FOR_EACH_FUNCTION (node)
+ {
+ if (!node->alias != !aliases)
+ continue;
+
+ /* Already done. */
+ if (node->order < last_cgraph_order)
+ continue;
+
+ set_strub_mode (node);
+ }
+
+ last_cgraph_order = symtab->order;
+}
+
+/* Return FALSE if NODE is a strub context, and TRUE otherwise. */
+bool
+strub_splittable_p (cgraph_node *node)
+{
+ switch (get_strub_mode (node))
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_AT_CALLS_OPT:
+ case STRUB_INLINABLE:
+ case STRUB_INTERNAL:
+ case STRUB_WRAPPER:
+ return false;
+
+ case STRUB_CALLABLE:
+ case STRUB_DISABLED:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ return true;
+}
+
+/* Return the PARM_DECL of the incoming watermark pointer, if there is one. */
+tree
+strub_watermark_parm (tree fndecl)
+{
+ switch (get_strub_mode_from_decl (fndecl))
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_AT_CALLS_OPT:
+ break;
+
+ case STRUB_INTERNAL:
+ case STRUB_WRAPPER:
+ case STRUB_CALLABLE:
+ case STRUB_DISABLED:
+ case STRUB_INLINABLE:
+ return NULL_TREE;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ for (tree parm = DECL_ARGUMENTS (fndecl); parm; parm = DECL_CHAIN (parm))
+ /* The type (variant) compare finds the parameter even in a just-created
+ clone, before we set its name, but the type-based compare doesn't work
+ during builtin expansion within the lto compiler, because we'll have
+ created a separate variant in that run. */
+ if (TREE_TYPE (parm) == pass_ipa_strub::get_qpwmt ()
+ || DECL_NAME (parm) == pass_ipa_strub::get_watermark_ptr ())
+ return parm;
+
+ gcc_unreachable ();
+}
+
+/* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
+ hasn't been added yet. Return the named argument count. */
+int
+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 already 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);
+ }
+
+ /* 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);
+
+#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 we're already within a strub context, pass on the incoming watermark
+ pointer, and omit the enter and leave calls around the modified call, as an
+ optimization, or as a means to satisfy a tail-call requirement. */
+ tree swmp = ((optimize_size || optimize > 2
+ || gimple_call_must_tail_p (ocall)
+ || (optimize == 2 && gimple_call_tail_p (ocall)))
+ ? strub_watermark_parm (e->caller->decl)
+ : NULL_TREE);
+ bool omit_own_watermark = swmp;
+ tree swm = NULL_TREE;
+ if (!omit_own_watermark)
+ {
+ swm = create_tmp_var (get_wmt (), ".strub.watermark");
+ TREE_ADDRESSABLE (swm) = true;
+ swmp = build1 (ADDR_EXPR, get_pwmt (), swm);
+
+ /* Initialize the watermark before the call. */
+ tree enter = get_enter ();
+ gcall *stptr = gimple_build_call (enter, 1,
+ unshare_expr (swmp));
+ if (gimple_has_location (ocall))
+ gimple_set_location (stptr, gimple_location (ocall));
+ 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)))
+ {
+ if (!swm)
+ swm = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (swmp)),
+ swmp,
+ build_int_cst (TREE_TYPE (swmp), 0));
+
+ vec<tree, va_gc> *inputs = NULL;
+ vec<tree, va_gc> *outputs = NULL;
+ vec_safe_push (outputs,
+ build_tree_list
+ (build_tree_list
+ (NULL_TREE, build_string (2, "=m")),
+ unshare_expr (swm)));
+ vec_safe_push (inputs,
+ build_tree_list
+ (build_tree_list
+ (NULL_TREE, build_string (1, "m")),
+ unshare_expr (swm)));
+ gasm *forcemod = gimple_build_asm_vec ("", inputs, outputs,
+ NULL, NULL);
+ gimple_seq_add_stmt (&seq, forcemod);
+
+ /* If the call will be assumed to not even read the watermark,
+ make sure it is already in memory before the call. */
+ if ((gimple_call_flags (wrcall) & ECF_CONST))
+ {
+ vec<tree, va_gc> *inputs = NULL;
+ vec_safe_push (inputs,
+ build_tree_list
+ (build_tree_list
+ (NULL_TREE, build_string (1, "m")),
+ unshare_expr (swm)));
+ gasm *force_store = gimple_build_asm_vec ("", inputs, NULL,
+ NULL, NULL);
+ if (gimple_has_location (wrcall))
+ gimple_set_location (force_store, gimple_location (wrcall));
+ gsi_insert_before (&gsi, force_store, GSI_SAME_STMT);
+ }
+ }
+#endif
+
+ if (!omit_own_watermark)
+ {
+ gcall *sleave = gimple_build_call (get_leave (), 1,
+ unshare_expr (swmp));
+ gimple_seq_add_stmt (&seq, sleave);
+
+ gassign *clobber = gimple_build_assign (swm,
+ build_clobber
+ (TREE_TYPE (swm)));
+ gimple_seq_add_stmt (&seq, clobber);
+ }
+
+ gsi_insert_finally_seq_after_call (gsi, seq);
+}
+
+void
+pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
+{
+ /* Adjust unknown-callee indirect calls with STRUB_AT_CALLS types within
+ onode. */
+ if (node->indirect_calls)
+ {
+ push_cfun (DECL_STRUCT_FUNCTION (node->decl));
+ for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
+ {
+ gcc_checking_assert (e->indirect_unknown_callee);
+
+ tree callee_fntype = gimple_call_fntype (e->call_stmt);
+ enum strub_mode callee_mode
+ = get_strub_mode_from_type (callee_fntype);
+
+ if (callee_mode != STRUB_AT_CALLS
+ && callee_mode != STRUB_AT_CALLS_OPT)
+ continue;
+
+ int named_args = adjust_at_calls_type (callee_fntype);
+
+ adjust_at_calls_call (e, named_args);
+ }
+ pop_cfun ();
+ }
+
+ if (node->callees)
+ {
+ push_cfun (DECL_STRUCT_FUNCTION (node->decl));
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ gcc_checking_assert (!e->indirect_unknown_callee);
+
+ enum strub_mode callee_mode = get_strub_mode (e->callee);
+
+ if (callee_mode != STRUB_AT_CALLS
+ && callee_mode != STRUB_AT_CALLS_OPT)
+ continue;
+
+ int named_args = adjust_at_calls_type (TREE_TYPE (e->callee->decl));
+
+ adjust_at_calls_call (e, named_args);
+ }
+ pop_cfun ();
+ }
+}
+
+unsigned int
+pass_ipa_strub_mode::execute (function *)
+{
+ last_cgraph_order = 0;
+ ipa_strub_set_mode_for_new_functions ();
+
+ /* Verify before any inlining or other transformations. */
+ verify_strub ();
+
+ return 0;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub_mode (gcc::context *ctxt)
+{
+ return new pass_ipa_strub_mode (ctxt);
+}
+
+unsigned int
+pass_ipa_strub::execute (function *)
+{
+ cgraph_node *onode;
+
+ 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
+ || mode == STRUB_AT_CALLS_OPT)
+ {
+ /* 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. */
+ bool aliased_parms = (onode->alias && *pargs
+ && DECL_CONTEXT (*pargs) != onode->decl);
+
+ 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_qpwmt ());
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_ARG_TYPE (wmptr) = get_qpwmt ();
+ DECL_CONTEXT (wmptr) = onode->decl;
+ TREE_USED (wmptr) = 1;
+ DECL_CHAIN (wmptr) = *pargs;
+ *pargs = wmptr;
+
+ 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));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca)
+ {
+ 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);
+
+ if (!is_gimple_call (stmt))
+ continue;
+
+ gcall *call = as_a <gcall *> (stmt);
+
+ if (gimple_alloca_call_p (call))
+ {
+ /* Capture stack growth. */
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)
+ ->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ }
+ }
+
+ pop_cfun ();
+
+#if 0
+ compute_fn_summary (onode, true);
+#endif
+ }
+ }
+
+ FOR_EACH_FUNCTION (onode)
+ {
+ if (!onode->has_gimple_body_p ())
+ continue;
+
+ enum strub_mode mode = get_strub_mode (onode);
+
+ if (mode != STRUB_INTERNAL)
+ {
+ adjust_at_calls_calls (onode);
+ 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);
+
+ vec<ipa_adjusted_param, va_gc> *nparms = NULL;
+ unsigned j = 0;
+ {
+ // The following loop copied from ipa-split.c:split_function.
+ for (tree parm = DECL_ARGUMENTS (onode->decl);
+ parm; parm = DECL_CHAIN (parm), j++)
+ {
+ ipa_adjusted_param adj = {};
+ adj.op = IPA_PARAM_OP_COPY;
+ adj.base_index = j;
+ adj.prev_clone_index = j;
+ vec_safe_push (nparms, adj);
+ }
+
+ if (apply_args)
+ {
+ ipa_adjusted_param aaadj = {};
+ aaadj.op = IPA_PARAM_OP_NEW;
+ aaadj.type = get_qptr ();
+ vec_safe_push (nparms, aaadj);
+ }
+
+ if (is_stdarg)
+ {
+ ipa_adjusted_param vladj = {};
+ vladj.op = IPA_PARAM_OP_NEW;
+ vladj.type = get_qpvalst ();
+ vec_safe_push (nparms, vladj);
+ }
+
+ ipa_adjusted_param wmadj = {};
+ wmadj.op = IPA_PARAM_OP_NEW;
+ wmadj.type = get_qpwmt ();
+ vec_safe_push (nparms, wmadj);
+ }
+ ipa_param_adjustments adj (nparms, -1, false);
+
+ cgraph_node *nnode = onode->create_version_clone_with_body
+ (auto_vec<cgraph_edge *> (0),
+ NULL, &adj, NULL, NULL, "strub", NULL);
+
+ if (!nnode)
+ {
+ error_at (DECL_SOURCE_LOCATION (onode->decl),
+ "failed to split %qD for %<strub%>",
+ onode->decl);
+ continue;
+ }
+
+ onode->split_part = true;
+ if (onode->calls_comdat_local)
+ nnode->add_to_same_comdat_group (onode);
+
+ gcc_checking_assert (!DECL_STRUCT_FUNCTION (nnode->decl)->stdarg);
+
+ set_strub_mode_to (onode, STRUB_WRAPPER);
+ set_strub_mode_to (nnode, STRUB_WRAPPED);
+
+ adjust_at_calls_calls (nnode);
+
+ /* Decide which of the wrapped function's parms we want to turn into
+ references to the argument passed to the wrapper. In general, we want to
+ copy small arguments, and avoid copying large ones. Variable-sized array
+ lengths given by other arguments, as in 20020210-1.c, would lead to
+ problems if passed by value, after resetting the original function and
+ dropping the length computation; passing them by reference works.
+ DECL_BY_REFERENCE is *not* a substitute for this: it involves copying
+ anyway, but performed at the caller. */
+ indirect_parms_t indirect_nparms (3, false);
+ unsigned adjust_ftype = 0;
+ unsigned named_args = 0;
+ for (tree parm = DECL_ARGUMENTS (onode->decl),
+ nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (TREE_TYPE (nnode->decl));
+ parm;
+ named_args++,
+ parm = DECL_CHAIN (parm),
+ nparm = DECL_CHAIN (nparm),
+ nparmt = nparmt ? TREE_CHAIN (nparmt) : NULL_TREE)
+ if (!(0 /* DECL_BY_REFERENCE (narg) */
+ || is_gimple_reg_type (TREE_TYPE (nparm))
+ || VECTOR_TYPE_P (TREE_TYPE (nparm))
+ || TREE_CODE (TREE_TYPE (nparm)) == COMPLEX_TYPE
+ || (tree_fits_uhwi_p (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ && (tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ <= 4 * UNITS_PER_WORD))))
+ {
+ indirect_nparms.add (nparm);
+
+ /* ??? Is there any case in which it is not safe to suggest the parms
+ turned indirect don't alias anything else? They are distinct,
+ unaliased memory in the wrapper, and the wrapped can't possibly
+ take pointers into them because none of the pointers passed to the
+ wrapper can alias other incoming parameters passed by value, even
+ if with transparent reference, and the wrapper doesn't take any
+ extra parms that could point into wrapper's parms. So we can
+ probably drop the TREE_ADDRESSABLE and keep the true. */
+ tree ref_type = build_ref_type_for (nparm,
+ true
+ || !TREE_ADDRESSABLE (parm));
+
+ DECL_ARG_TYPE (nparm) = TREE_TYPE (nparm) = ref_type;
+ relayout_decl (nparm);
+ TREE_ADDRESSABLE (nparm) = 0;
+ DECL_BY_REFERENCE (nparm) = 0;
+#if FOR_GCC_11P
+ DECL_NOT_GIMPLE_REG_P (nparm) = 0;
+#else
+ DECL_GIMPLE_REG_P (nparm) = 1;
+#endif
+ /* ??? This avoids mismatches in debug info bind stmts in
+ e.g. a-chahan . */
+ DECL_ABSTRACT_ORIGIN (nparm) = NULL;
+
+ if (nparmt)
+ adjust_ftype++;
+ }
+
+ /* Also adjust the wrapped function type, if needed. */
+ if (adjust_ftype)
+ {
+ tree nftype = TREE_TYPE (nnode->decl);
+
+ /* We always add at least one argument at the end of the signature, when
+ cloning the function, so we don't expect to need to duplicate the
+ type here. */
+ gcc_checking_assert (TYPE_ARG_TYPES (nftype)
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+
+#if HAVE_ATTR_FNSPEC
+ /* Check that fnspec still works for the modified function signature,
+ and drop it otherwise. */
+ bool drop_fnspec = false;
+ tree fnspec = lookup_attribute ("fn spec", TYPE_ATTRIBUTES (nftype));
+ attr_fnspec spec = fnspec ? attr_fnspec (fnspec) : attr_fnspec ("");
+
+ unsigned retcopy;
+ if (!(fnspec && spec.returns_arg (&retcopy)))
+ retcopy = (unsigned) -1;
+
+ unsigned i = 0;
+#endif
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (nftype);
+ adjust_ftype > 0;
+#if HAVE_ATTR_FNSPEC
+ i++,
+#endif
+ nparm = DECL_CHAIN (nparm), nparmt = TREE_CHAIN (nparmt))
+ if (indirect_nparms.contains (nparm))
+ {
+ TREE_VALUE (nparmt) = TREE_TYPE (nparm);
+ adjust_ftype--;
+
+#if HAVE_ATTR_FNSPEC
+ if (fnspec && !drop_fnspec)
+ {
+ if (i == retcopy)
+ drop_fnspec = true;
+ else if (spec.arg_specified_p (i))
+ {
+ /* Properties that apply to pointers only must not be
+ present, because we don't make pointers further
+ indirect. */
+ gcc_checking_assert
+ (!spec.arg_max_access_size_given_by_arg_p (i, NULL));
+ gcc_checking_assert (!spec.arg_copied_to_arg_p (i, NULL));
+
+ /* Any claim of direct access only is invalidated by
+ adding an indirection level. */
+ if (spec.arg_direct_p (i))
+ drop_fnspec = true;
+
+ /* If there's a claim the argument is not read from, the
+ added indirection invalidates it: if the argument is
+ used at all, then the pointer will necessarily be
+ read. */
+ if (!spec.arg_maybe_read_p (i)
+ && spec.arg_used_p (i))
+ drop_fnspec = true;
+ }
+ }
+#endif
+ }
+
+#if HAVE_ATTR_FNSPEC
+ /* ??? Maybe we could adjust it instead. */
+ if (drop_fnspec)
+ remove_named_attribute_unsharing ("fn spec",
+ &TYPE_ATTRIBUTES (nftype));
+#endif
+
+ TREE_TYPE (nnode->decl) = nftype;
+ }
+
+#if ATTR_FNSPEC_DECONST_WATERMARK
+ {
+ int flags = flags_from_decl_or_type (nnode->decl);
+ tree fnspec = lookup_attribute ("fn spec", TREE_TYPE (nnode->decl));
+
+ if ((flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS)) || fnspec)
+ {
+ size_t xargs = 1 + int (is_stdarg) + int (apply_args);
+ 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! */
+ bool no_writes_p = true;
+ if (fnspec)
+ {
+ tree fnspecstr = TREE_VALUE (TREE_VALUE (fnspec));
+ curlen = TREE_STRING_LENGTH (fnspecstr);
+ memcpy (nspec, TREE_STRING_POINTER (fnspecstr), curlen);
+ if (!(flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS))
+ && curlen >= 2
+ && nspec[1] != 'c' && nspec[1] != 'C'
+ && nspec[1] != 'p' && nspec[1] != 'P')
+ no_writes_p = false;
+ }
+ if (!curlen)
+ {
+ nspec[curlen++] = '.';
+ nspec[curlen++] = ((flags & ECF_CONST)
+ ? 'c'
+ : (flags & ECF_PURE)
+ ? 'p'
+ : ' ');
+ }
+ while (curlen < tgtlen - 2 * xargs)
+ {
+ nspec[curlen++] = '.';
+ nspec[curlen++] = ' ';
+ }
+
+ /* These extra args are unlikely to be present in const or pure
+ functions. It's conceivable that a function that takes variable
+ arguments, or that passes its arguments on to another function,
+ could be const or pure, but it would not modify the arguments, and,
+ being pure or const, it couldn't possibly modify or even access
+ memory referenced by them. But it can read from these internal
+ data structures created by the wrapper, and from any
+ argument-passing memory referenced by them, so we denote the
+ possibility of reading from multiple levels of indirection, but
+ only of reading because const/pure. */
+ if (apply_args)
+ {
+ nspec[curlen++] = 'r';
+ nspec[curlen++] = ' ';
+ }
+ if (is_stdarg)
+ {
+ nspec[curlen++] = (no_writes_p ? 'r' : '.');
+ nspec[curlen++] = (no_writes_p ? 't' : ' ');
+ }
+
+ nspec[curlen++] = 'W';
+ nspec[curlen++] = 't';
+
+ /* The type has already been copied before adding parameters. */
+ gcc_checking_assert (TYPE_ARG_TYPES (TREE_TYPE (nnode->decl))
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+ TYPE_ATTRIBUTES (TREE_TYPE (nnode->decl))
+ = tree_cons (get_identifier ("fn spec"),
+ build_tree_list (NULL_TREE,
+ build_string (tgtlen, nspec)),
+ TYPE_ATTRIBUTES (TREE_TYPE (nnode->decl)));
+ }
+ }
+#endif
+
+ {
+ tree decl = onode->decl;
+ cgraph_node *target = nnode;
+
+ { // copied from create_wrapper
+
+ /* Preserve DECL_RESULT so we get right by reference flag. */
+ tree decl_result = DECL_RESULT (decl);
+
+ /* Remove the function's body but keep arguments to be reused
+ for thunk. */
+ onode->release_body (true);
+ onode->reset ();
+
+ DECL_UNINLINABLE (decl) = false;
+ DECL_RESULT (decl) = decl_result;
+ DECL_INITIAL (decl) = NULL;
+ allocate_struct_function (decl, false);
+ set_cfun (NULL);
+
+ /* Turn alias into thunk and expand it into GIMPLE representation. */
+ onode->definition = true;
+
+#if FOR_GCC_11P
+ thunk_info::get_create (onode);
+ onode->thunk = true;
+#else
+ memset (&onode->thunk, 0, sizeof (cgraph_thunk_info));
+ onode->thunk.thunk_p = true;
+ onode->thunk.alias = target->decl;
+#endif
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (target, NULL, onode->count);
+#endif
+ onode->callees->can_throw_external = !TREE_NOTHROW (target->decl);
+
+ tree arguments = DECL_ARGUMENTS (decl);
+
+ while (arguments)
+ {
+ TREE_ADDRESSABLE (arguments) = false;
+ arguments = TREE_CHAIN (arguments);
+ }
+
+ {
+ tree alias = onode->callees->callee->decl;
+ tree thunk_fndecl = decl;
+ tree a;
+
+ int nxargs = 1 + is_stdarg + apply_args;
+
+ { // Simplified from expand_thunk.
+ tree restype;
+ basic_block bb, then_bb, else_bb, return_bb;
+ gimple_stmt_iterator bsi;
+ int nargs = 0;
+ tree arg;
+ int i;
+ tree resdecl;
+ tree restmp = NULL;
+
+ gcall *call;
+ greturn *ret;
+ bool alias_is_noreturn = TREE_THIS_VOLATILE (alias);
+
+ a = DECL_ARGUMENTS (thunk_fndecl);
+
+ current_function_decl = thunk_fndecl;
+
+#if FOR_GCC_11P
+ /* Ensure thunks are emitted in their correct sections. */
+ resolve_unique_section (thunk_fndecl, 0,
+ flag_function_sections);
+#endif
+
+ bitmap_obstack_initialize (NULL);
+
+ /* Build the return declaration for the function. */
+ restype = TREE_TYPE (TREE_TYPE (thunk_fndecl));
+ if (DECL_RESULT (thunk_fndecl) == NULL_TREE)
+ {
+ resdecl = build_decl (input_location, RESULT_DECL, 0, restype);
+ DECL_ARTIFICIAL (resdecl) = 1;
+ DECL_IGNORED_P (resdecl) = 1;
+ DECL_CONTEXT (resdecl) = thunk_fndecl;
+ DECL_RESULT (thunk_fndecl) = resdecl;
+ }
+ else
+ resdecl = DECL_RESULT (thunk_fndecl);
+
+ profile_count cfg_count = onode->count;
+ if (!cfg_count.initialized_p ())
+ cfg_count = profile_count::from_gcov_type (BB_FREQ_MAX).guessed_local ();
+
+ bb = then_bb = else_bb = return_bb
+ = init_lowered_empty_function (thunk_fndecl, true, cfg_count);
+
+ bsi = gsi_start_bb (bb);
+
+ /* Build call to the function being thunked. */
+ if (!VOID_TYPE_P (restype)
+ && (!alias_is_noreturn
+ || TREE_ADDRESSABLE (restype)
+ || TREE_CODE (TYPE_SIZE_UNIT (restype)) != INTEGER_CST))
+ {
+ if (DECL_BY_REFERENCE (resdecl))
+ {
+ restmp = gimple_fold_indirect_ref (resdecl);
+ if (!restmp)
+ restmp = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (resdecl)),
+ resdecl,
+ build_int_cst (TREE_TYPE (resdecl), 0));
+ }
+ else if (!is_gimple_reg_type (restype))
+ {
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl)))
+ {
+ restmp = resdecl;
+
+ if (VAR_P (restmp))
+ {
+ add_local_decl (cfun, restmp);
+ BLOCK_VARS (DECL_INITIAL (current_function_decl))
+ = restmp;
+ }
+ }
+ else
+ restmp = create_tmp_var (restype, "retval");
+ }
+ else
+ restmp = create_tmp_reg (restype, "retval");
+ }
+
+ for (arg = a; arg; arg = DECL_CHAIN (arg))
+ nargs++;
+ auto_vec<tree> vargs (nargs + nxargs);
+ i = 0;
+ arg = a;
+
+ if (nargs)
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl);
+ i < nargs;
+ i++, arg = DECL_CHAIN (arg), nparm = DECL_CHAIN (nparm))
+ {
+ tree save_arg = arg;
+ tree tmp = arg;
+
+ /* Arrange to pass indirectly the parms, if we decided to do
+ so, and revert its type in the wrapper. */
+ if (indirect_nparms.contains (nparm))
+ {
+ tree ref_type = TREE_TYPE (nparm);
+ TREE_ADDRESSABLE (arg) = true;
+ tree addr = build1 (ADDR_EXPR, ref_type, arg);
+ tmp = arg = addr;
+ }
+#if ! FOR_GCC_11P
+ else if (VECTOR_TYPE_P (TREE_TYPE (arg))
+ || TREE_CODE (TREE_TYPE (arg)) == COMPLEX_TYPE)
+ DECL_GIMPLE_REG_P (arg) = 1;
+#else
+ else
+ DECL_NOT_GIMPLE_REG_P (arg) = 0;
+#endif
+
+ /* Convert the argument back to the type used by the calling
+ conventions, e.g. a non-prototyped float type is passed as
+ double, as in 930603-1.c, and needs to be converted back to
+ double to be passed on unchanged to the wrapped
+ function. */
+ if (TREE_TYPE (nparm) != DECL_ARG_TYPE (nparm))
+ arg = fold_convert (DECL_ARG_TYPE (nparm), arg);
+
+ if (!is_gimple_val (arg))
+ {
+ tmp = create_tmp_reg (TYPE_MAIN_VARIANT
+ (TREE_TYPE (arg)), "arg");
+ gimple *stmt = gimple_build_assign (tmp, arg);
+ gsi_insert_after (&bsi, stmt, GSI_NEW_STMT);
+ }
+ vargs.quick_push (tmp);
+ arg = save_arg;
+ }
+ /* These strub arguments are adjusted later. */
+ if (apply_args)
+ vargs.quick_push (null_pointer_node);
+ if (is_stdarg)
+ vargs.quick_push (null_pointer_node);
+ vargs.quick_push (null_pointer_node);
+ call = gimple_build_call_vec (build_fold_addr_expr_loc (0, alias),
+ vargs);
+ onode->callees->call_stmt = call;
+ // gimple_call_set_from_thunk (call, true);
+ if (DECL_STATIC_CHAIN (alias))
+ {
+ tree p = DECL_STRUCT_FUNCTION (alias)->static_chain_decl;
+ tree type = TREE_TYPE (p);
+ tree decl = build_decl (DECL_SOURCE_LOCATION (thunk_fndecl),
+ PARM_DECL, create_tmp_var_name ("CHAIN"),
+ type);
+ DECL_ARTIFICIAL (decl) = 1;
+ DECL_IGNORED_P (decl) = 1;
+ TREE_USED (decl) = 1;
+ DECL_CONTEXT (decl) = thunk_fndecl;
+ DECL_ARG_TYPE (decl) = type;
+ TREE_READONLY (decl) = 1;
+
+ struct function *sf = DECL_STRUCT_FUNCTION (thunk_fndecl);
+ sf->static_chain_decl = decl;
+
+ gimple_call_set_chain (call, decl);
+ }
+
+ /* Return slot optimization is always possible and in fact required to
+ return values with DECL_BY_REFERENCE. */
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl))
+ && (!is_gimple_reg_type (TREE_TYPE (resdecl))
+ || DECL_BY_REFERENCE (resdecl)))
+ gimple_call_set_return_slot_opt (call, true);
+
+ if (restmp)
+ {
+ gimple_call_set_lhs (call, restmp);
+ gcc_assert (useless_type_conversion_p (TREE_TYPE (restmp),
+ TREE_TYPE (TREE_TYPE (alias))));
+ }
+ gsi_insert_after (&bsi, call, GSI_NEW_STMT);
+ if (!alias_is_noreturn)
+ {
+ /* Build return value. */
+ if (!DECL_BY_REFERENCE (resdecl))
+ ret = gimple_build_return (restmp);
+ else
+ ret = gimple_build_return (resdecl);
+
+ gsi_insert_after (&bsi, ret, GSI_NEW_STMT);
+ }
+ else
+ {
+ remove_edge (single_succ_edge (bb));
+ }
+
+ cfun->gimple_df->in_ssa_p = true;
+ update_max_bb_count ();
+ profile_status_for_fn (cfun)
+ = cfg_count.initialized_p () && cfg_count.ipa_p ()
+ ? PROFILE_READ : PROFILE_GUESSED;
+#if FOR_GCC_11P
+ /* FIXME: C++ FE should stop setting TREE_ASM_WRITTEN on thunks. */
+ // TREE_ASM_WRITTEN (thunk_fndecl) = false;
+#endif
+ delete_unreachable_blocks ();
+ update_ssa (TODO_update_ssa);
+ checking_verify_flow_info ();
+ free_dominance_info (CDI_DOMINATORS);
+
+ /* Since we want to emit the thunk, we explicitly mark its name as
+ referenced. */
+#if FOR_GCC_11P
+ onode->thunk = false;
+#else
+ onode->thunk.thunk_p = false;
+#endif
+ onode->lowered = true;
+ bitmap_obstack_release (NULL);
+ }
+ current_function_decl = NULL;
+ set_cfun (NULL);
+ }
+
+#if FOR_GCC_11P
+ thunk_info::remove (onode);
+#endif
+
+ // some more of create_wrapper at the end of the next block.
+ }
+ }
+
+ {
+ tree aaval = NULL_TREE;
+ tree vaptr = NULL_TREE;
+ tree wmptr = NULL_TREE;
+ for (tree arg = DECL_ARGUMENTS (nnode->decl); arg; arg = DECL_CHAIN (arg))
+ {
+ aaval = vaptr;
+ vaptr = wmptr;
+ wmptr = arg;
+ }
+
+ if (!apply_args)
+ aaval = NULL_TREE;
+ /* The trailing args are [apply_args], [va_list_ptr], and
+ watermark. If we don't have a va_list_ptr, the penultimate
+ argument is apply_args.
+ */
+ else if (!is_stdarg)
+ aaval = vaptr;
+
+ if (!is_stdarg)
+ vaptr = NULL_TREE;
+
+ DECL_NAME (wmptr) = get_watermark_ptr ();
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_IGNORED_P (wmptr) = 1;
+ TREE_USED (wmptr) = 1;
+
+ if (is_stdarg)
+ {
+ DECL_NAME (vaptr) = get_va_list_ptr ();
+ DECL_ARTIFICIAL (vaptr) = 1;
+ DECL_IGNORED_P (vaptr) = 1;
+ TREE_USED (vaptr) = 1;
+ }
+
+ if (apply_args)
+ {
+ DECL_NAME (aaval) = get_apply_args ();
+ DECL_ARTIFICIAL (aaval) = 1;
+ DECL_IGNORED_P (aaval) = 1;
+ TREE_USED (aaval) = 1;
+ }
+
+ push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ bool any_indirect = !indirect_nparms.is_empty ();
+
+ if (any_indirect)
+ {
+ 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);
+
+ 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
+ || is_stdarg || apply_args)
+ for (cgraph_edge *e = nnode->callees, *enext; e; e = enext)
+ {
+ gcall *call = e->call_stmt;
+ gimple_stmt_iterator gsi = gsi_for_stmt (call);
+ tree fndecl = e->callee->decl;
+
+ enext = e->next_callee;
+
+ if (gimple_alloca_call_p (call))
+ {
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ else if (fndecl && is_stdarg
+ && fndecl_built_in_p (fndecl, BUILT_IN_VA_START))
+ {
+ /* Using a non-default stdarg ABI makes the function ineligible
+ for internal strub. */
+ gcc_checking_assert (builtin_decl_explicit (BUILT_IN_VA_START)
+ == fndecl);
+ tree bvacopy = builtin_decl_explicit (BUILT_IN_VA_COPY);
+ gimple_call_set_fndecl (call, bvacopy);
+ tree arg = vaptr;
+ /* The va_copy source must be dereferenced, unless it's an array
+ type, that would have decayed to a pointer. */
+ if (TREE_CODE (TREE_TYPE (TREE_TYPE (vaptr))) != ARRAY_TYPE)
+ {
+ arg = gimple_fold_indirect_ref (vaptr);
+ if (!arg)
+ arg = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (vaptr)),
+ vaptr,
+ build_int_cst (TREE_TYPE (vaptr), 0));
+ }
+ gimple_call_set_arg (call, 1, arg);
+ update_stmt (call);
+ e->redirect_callee (cgraph_node::get_create (bvacopy));
+ }
+ else if (fndecl && apply_args
+ && fndecl_built_in_p (fndecl, BUILT_IN_APPLY_ARGS))
+ {
+ tree lhs = gimple_call_lhs (call);
+ gimple *assign = (lhs
+ ? gimple_build_assign (lhs, aaval)
+ : gimple_build_nop ());
+ gsi_replace (&gsi, assign, true);
+ cgraph_edge::remove (e);
+ }
+ }
+
+ { // a little more copied from create_wrapper
+
+ /* Inline summary set-up. */
+ nnode->analyze ();
+ // inline_analyze_function (nnode);
+ }
+
+ pop_cfun ();
+ }
+
+ {
+ 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));
+
+ 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));
+ gimple_set_location (stptr, gimple_location (wrcall));
+ gsi_insert_before (&gsi, stptr, GSI_SAME_STMT);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (enter),
+ stptr, gsi_bb (gsi)->count, false);
+#endif
+
+ int nargs = gimple_call_num_args (wrcall);
+
+ gimple_seq seq = NULL;
+
+ if (apply_args)
+ {
+ tree aalst = create_tmp_var (ptr_type_node, ".strub.apply_args");
+ tree bappargs = builtin_decl_explicit (BUILT_IN_APPLY_ARGS);
+ gcall *appargs = gimple_build_call (bappargs, 0);
+ gimple_call_set_lhs (appargs, aalst);
+ gimple_set_location (appargs, gimple_location (wrcall));
+ gsi_insert_before (&gsi, appargs, GSI_SAME_STMT);
+ gimple_call_set_arg (wrcall, nargs - 2 - is_stdarg, aalst);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bappargs),
+ appargs, gsi_bb (gsi)->count, false);
+#endif
+ }
+
+ if (is_stdarg)
+ {
+ tree valst = create_tmp_var (va_list_type_node, ".strub.va_list");
+ TREE_ADDRESSABLE (valst) = true;
+ tree vaptr = build1 (ADDR_EXPR,
+ build_pointer_type (va_list_type_node),
+ valst);
+ gimple_call_set_arg (wrcall, nargs - 2, unshare_expr (vaptr));
+
+ tree bvastart = builtin_decl_explicit (BUILT_IN_VA_START);
+ gcall *vastart = gimple_build_call (bvastart, 2,
+ unshare_expr (vaptr),
+ integer_zero_node);
+ gimple_set_location (vastart, gimple_location (wrcall));
+ gsi_insert_before (&gsi, vastart, GSI_SAME_STMT);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bvastart),
+ vastart, gsi_bb (gsi)->count, false);
+#endif
+
+ tree bvaend = builtin_decl_explicit (BUILT_IN_VA_END);
+ gcall *vaend = gimple_build_call (bvaend, 1, unshare_expr (vaptr));
+ gimple_set_location (vaend, gimple_location (wrcall));
+ gimple_seq_add_stmt (&seq, vaend);
+ }
+
+ gimple_call_set_arg (wrcall, nargs - 1, unshare_expr (swmp));
+ // gimple_call_set_tail (wrcall, false);
+ update_stmt (wrcall);
+
+ {
+#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);
+ gimple_set_location (force_store, gimple_location (wrcall));
+ 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);
+
+ /* For nnode, we don't rebuild edges because we wish to retain
+ any redirections copied to it from earlier passes, so we add
+ call graph edges explicitly there, but for onode, we create a
+ fresh function, so we may as well just issue the calls and
+ then rebuild all cgraph edges. */
+ // cgraph_edge::rebuild_edges ();
+ onode->analyze ();
+ // inline_analyze_function (onode);
+
+ 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;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub (gcc::context *ctxt)
+{
+ return new pass_ipa_strub (ctxt);
+}
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
new file mode 100644
index 00000000000..c335ab42097
--- /dev/null
+++ b/gcc/ipa-strub.h
@@ -0,0 +1,35 @@
+/* strub (stack scrubbing) infrastructure.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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 FALSE if NODE is a strub context, and TRUE otherwise. */
+extern bool strub_splittable_p (cgraph_node *node);
+
+/* Locate and return the watermark_ptr parameter for FNDECL. If FNDECL is not a
+ strub context, return NULL. */
+extern tree strub_watermark_parm (tree fndecl);
+
+/* Make a function type or declaration callable. */
+extern void strub_make_callable (tree fndecl);
diff --git a/gcc/passes.def b/gcc/passes.def
index d7a1f8c97a6..8e4638d20ed 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -53,6 +53,7 @@ along with GCC; see the file COPYING3. If not see
INSERT_PASSES_AFTER (all_small_ipa_passes)
NEXT_PASS (pass_ipa_free_lang_data);
NEXT_PASS (pass_ipa_function_and_variable_visibility);
+ NEXT_PASS (pass_ipa_strub_mode);
NEXT_PASS (pass_build_ssa_passes);
PUSH_INSERT_PASSES_WITHIN (pass_build_ssa_passes)
NEXT_PASS (pass_fixup_cfg);
@@ -111,6 +112,7 @@ along with GCC; see the file COPYING3. If not see
POP_INSERT_PASSES ()
NEXT_PASS (pass_ipa_remove_symbols);
+ NEXT_PASS (pass_ipa_strub);
NEXT_PASS (pass_ipa_oacc);
PUSH_INSERT_PASSES_WITHIN (pass_ipa_oacc)
NEXT_PASS (pass_ipa_pta);
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..7848c46d179
--- /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 "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
new file mode 100644
index 00000000000..85a8f76785e
--- /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" } */
+
+/* With -fno-inline, 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 "\[(\]call\[^\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..a2eedfd96b2
--- /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, 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 "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
new file mode 100644
index 00000000000..e5cb1f60541
--- /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 "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
new file mode 100644
index 00000000000..194aacc2c05
--- /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 "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
new file mode 100644
index 00000000000..46e84bf6560
--- /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 inline void
+__attribute__ ((__always_inline__))
+h() {
+}
+
+/* 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-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
new file mode 100644
index 00000000000..f180b17f30e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__ (3)))
+apply_function (void *args)
+{
+ __builtin_apply (0, args, 0);
+}
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+ void *args = __builtin_apply_args ();
+ apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
new file mode 100644
index 00000000000..379a54b73b7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+extern void __attribute__ ((__strub__))
+apply_function (void *args);
+
+void __attribute__ ((__strub__))
+apply_args (int i, int j, double d) /* { dg-error "selected" } */
+{
+ void *args = __builtin_apply_args (); /* { dg-message "does not support" } */
+ apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
new file mode 100644
index 00000000000..9b4786be698
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__))
+apply_function (void *args)
+{
+ __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
new file mode 100644
index 00000000000..409f747743e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+
+/* Check that implicit enabling of strub mode selects internal strub when the
+ function uses __builtin_apply_args, that prevents the optimization to
+ at-calls mode. */
+
+int __attribute__ ((__strub__)) var;
+
+static inline void
+apply_args (int i, int j, double d)
+{
+ var++;
+ __builtin_apply_args ();
+}
+
+void f() {
+ apply_args (1, 2, 3);
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
new file mode 100644
index 00000000000..d964b07ae5d
--- /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 inline void
+__attribute__ ((__always_inline__))
+h() {
+}
+
+/* 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..79d00cedb9a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-default1.c
@@ -0,0 +1,42 @@
+/* { 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-default2.c b/gcc/testsuite/c-c++-common/strub-default2.c
new file mode 100644
index 00000000000..487253e9227
--- /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-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
new file mode 100644
index 00000000000..7b04eea35d9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O1" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+ the strubbing to its caller at -O1. */
+
+#include "strub-defer-O2.c"
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
new file mode 100644
index 00000000000..67d96419a5e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -0,0 +1,8 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O2" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+ the strubbing to its caller at -O2. */
+
+#define EXPECT_DEFERRAL !
+#include "strub-defer-O3.c"
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
new file mode 100644
index 00000000000..34828d2711e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -0,0 +1,93 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O3" } */
+
+/* Check that a strub function called by another strub function defers the
+ strubbing to its caller at -O3. */
+
+#ifndef EXPECT_DEFERRAL
+# define EXPECT_DEFERRAL
+#endif
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+ /* We use this variable to avoid any stack red zone. Stack scrubbing covers
+ it, but __builtin_stack_address, that we take as a reference, doesn't, so
+ if e.g. callable() were to store the string in the red zone, we wouldn't
+ find it because it would be outside the range we searched. */
+ typedef void __attribute__ ((__strub__ (3))) callable_t (char *);
+ callable_t *f = 0;
+
+ char s[sizeof (test_string)];
+ __builtin_strcpy (s, test_string);
+ asm ("" : "+m" (s), "+r" (f));
+
+ if (__builtin_expect (!f, 1))
+ return (char*)__builtin_stack_address ();
+
+ f (s);
+ return 0;
+}
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+int
+look_for_string (char *e)
+{
+ char *p = (char*)__builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+char *
+at_calls ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+deferred_at_calls ()
+{
+ char *ret = at_calls ();
+ if (EXPECT_DEFERRAL !look_for_string (ret))
+ __builtin_abort ();
+ return ret;
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+deferred_internal ()
+{
+ char *ret = at_calls ();
+ if (EXPECT_DEFERRAL !look_for_string (ret))
+ __builtin_abort ();
+ return ret;
+}
+
+int main ()
+{
+ if (look_for_string (deferred_at_calls ()))
+ __builtin_abort ();
+ if (look_for_string (deferred_internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
new file mode 100644
index 00000000000..b273660aea1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -Os" } */
+
+/* Check that a strub function called by another strub function defers the
+ strubbing to its caller at -Os. */
+
+#include "strub-defer-O3.c"
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
new file mode 100644
index 00000000000..a74658c9ac9
--- /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 inline void
+__attribute__ ((__always_inline__))
+h() {
+}
+
+/* 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/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
new file mode 100644
index 00000000000..7e22a266ad9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (2)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+ int x[128];
+};
+
+void __attribute__ ((__strub__ (2)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\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)))
+std_arg (int i, ...)
+{
+ va_list vl;
+ va_start (vl, i);
+ va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+ __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
new file mode 100644
index 00000000000..2d8036c0fbc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (1)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*small_args\[^ \]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+
+struct large_arg {
+ int x[128];
+};
+
+void __attribute__ ((__strub__ (1)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ (1)))
+std_arg (int i, ...)
+{
+ va_list vl;
+ va_start (vl, i);
+ va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*std_arg\[^ \]* \[(\]int i, void \\* &\[^&,\]*.strub.watermark_ptr\[, .]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-not "va_copy \\(" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
new file mode 100644
index 00000000000..f64361f1235
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -0,0 +1,58 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that uses of a strub variable implicitly enables internal strub for
+ publicly-visible functions, and causes the same transformations to their
+ signatures as those in strub-parms1.c. */
+
+#include <stdarg.h>
+
+int __attribute__ ((__strub__)) var;
+
+void
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+ var++;
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+ int x[128];
+};
+
+void
+large_byref_arg (struct large_arg la)
+{
+ var++;
+}
+
+/* { dg-final { scan-ipa-dump "\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
+std_arg (int i, ...)
+{
+ va_list vl;
+ va_start (vl, i);
+ var++;
+ va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void
+apply_args (int i, int j, double d)
+{
+ var++;
+ __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " \[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
new file mode 100644
index 00000000000..a4226ce0119
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=default -fexceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 89 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
new file mode 100644
index 00000000000..3bab553478b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
new file mode 100644
index 00000000000..c89cc7c2c47
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
new file mode 100644
index 00000000000..b869fafb691
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
new file mode 100644
index 00000000000..1d3dd2f2c2c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=default -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
+ watermark. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 15 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 15 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
new file mode 100644
index 00000000000..4dd4281b03b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-Os -fstrub=default -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
+ watermark. */
+
+#include "torture/strub-callable1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 15 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 15 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
new file mode 100644
index 00000000000..0840dddd136
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+#include "strub-tail-O2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
new file mode 100644
index 00000000000..9330d6ff4c1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued.
+ Tail calls are short-circuited at -O2+. */
+
+int __attribute__ ((__strub__))
+g (int i, int j) {
+ return g (i, j);
+}
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 0 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 0 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
new file mode 100644
index 00000000000..5b33ff1f530
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -0,0 +1,9 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that strub and non-strub functions can be called from non-strub
+ contexts, and that strub and callable functions can be called from strub
+ contexts. */
+
+#define OMIT_IMPERMISSIBLE_CALLS 1
+#include "strub-callable2.c"
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
new file mode 100644
index 00000000000..38935e3270b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -0,0 +1,264 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that impermissible (cross-strub-context) calls are reported. */
+
+extern int __attribute__ ((__strub__ (3))) xcallable (void);
+extern int __attribute__ ((__strub__ (2))) xinternal (void);
+extern int __attribute__ ((__strub__ (1))) xat_calls (void);
+extern int __attribute__ ((__strub__ (0))) xdisabled (void);
+
+int __attribute__ ((__strub__ (3))) callable (void);
+int __attribute__ ((__strub__ (2))) internal (void);
+int __attribute__ ((__strub__ (1))) at_calls (void);
+int __attribute__ ((__strub__ (0))) disabled (void);
+
+int __attribute__ ((__strub__)) var;
+int var_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+icallable (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+iinternal (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+iat_calls (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+idisabled (void);
+static inline int __attribute__ ((__always_inline__))
+ivar_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+i_callable (void) { return 0; }
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+i_internal (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+i_at_calls (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+i_disabled (void) { return 0; }
+static inline int __attribute__ ((__always_inline__))
+i_var_user (void) { return var; }
+
+#define CALLS_GOOD_FOR_STRUB_CONTEXT(ISEP) \
+ do { \
+ ret += i ## ISEP ## at_calls (); \
+ ret += i ## ISEP ## internal (); \
+ ret += i ## ISEP ## var_user (); \
+ } while (0)
+
+#define CALLS_GOOD_FOR_NONSTRUB_CONTEXT(ISEP) \
+ do { \
+ ret += internal (); \
+ ret += disabled (); \
+ ret += var_user (); \
+ \
+ ret += i ## ISEP ## disabled (); \
+ \
+ ret += xinternal (); \
+ ret += xdisabled (); \
+ } while (0)
+
+#define CALLS_GOOD_FOR_EITHER_CONTEXT(ISEP) \
+ do { \
+ ret += i ## ISEP ## callable (); \
+ \
+ ret += callable (); \
+ ret += at_calls (); \
+ \
+ ret += xat_calls (); \
+ ret += xcallable (); \
+ } while (0)
+
+/* Not a strub context, so it can call anything.
+ Explicitly declared as callable even from within strub contexts. */
+int __attribute__ ((__strub__ (3)))
+callable (void) {
+ int ret = 0;
+
+ /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+ ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+ ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+ return ret;
+}
+
+/* Internal strubbing means the body is a strub context, so it can only call
+ strub functions, and it's not itself callable from strub functions. */
+int __attribute__ ((__strub__ (2)))
+internal (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT();
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int __attribute__ ((__strub__ (1)))
+at_calls (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT();
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int __attribute__ ((__strub__ (0)))
+disabled () {
+ int ret = 0;
+
+ /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+ ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+ ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+ return ret;
+}
+
+int
+var_user (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT();
+ CALLS_GOOD_FOR_EITHER_CONTEXT();
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int
+icallable (void)
+{
+ int ret = 0;
+
+ /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+ ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+ ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+ return ret;
+}
+
+int
+iinternal (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int
+iat_calls (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
+
+int
+idisabled () {
+ int ret = 0;
+
+ /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+ ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+ ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+ return ret;
+}
+
+int
+ivar_user (void) {
+ int ret = var;
+
+ CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+ CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+ /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+ ret += internal (); /* { dg-error "in .strub. context" } */
+ ret += disabled (); /* { dg-error "in .strub. context" } */
+ ret += var_user (); /* { dg-error "in .strub. context" } */
+
+ ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+ ret += xinternal (); /* { dg-error "in .strub. context" } */
+ ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+ return ret;
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
new file mode 100644
index 00000000000..100fb0c59a9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const function call, we issue an asm statement
+ to make sure the watermark passed to it is held in memory before the call,
+ and another to make sure it is not assumed to be unchanged. */
+
+int __attribute__ ((__strub__, __const__))
+f() {
+ return 0;
+}
+
+int
+g() {
+ return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
new file mode 100644
index 00000000000..9e818ac9748
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const function call, we issue an
+ asm statement to make sure the watermark passed to it is held in memory
+ before the call, and another to make sure it is not assumed to be
+ unchanged. */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+ return 0;
+}
+
+int
+g() {
+ return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
new file mode 100644
index 00000000000..d40e8aa45cb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const wrapping call, we issue an asm statement
+ to make sure the watermark passed to it is held in memory before the call,
+ and another to make sure it is not assumed to be unchanged. */
+
+int __attribute__ ((__strub__ (2), __const__))
+f() {
+ return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
new file mode 100644
index 00000000000..d4cbdaf10f3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const wrapping call, we issue an
+ asm statement to make sure the watermark passed to it is held in memory
+ before the call, and another to make sure it is not assumed to be
+ unchanged. */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+ return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-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..07e25af9c53
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -Werror" } */
+
+typedef int __attribute__ ((__strub__)) strub_int;
+strub_int *ptr;
+
+int *f () {
+ return ptr; /* { dg-warning "incompatible" } */
+}
+
+strub_int *g () {
+ return f (); /* { dg-warning "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/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
new file mode 100644
index 00000000000..cb223da6efc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure function call, we issue an asm statement
+ to make sure the watermark passed to it is not assumed to be unchanged. */
+
+int __attribute__ ((__strub__, __pure__))
+f() {
+ static int i; /* Stop it from being detected as const. */
+ return i;
+}
+
+int
+g() {
+ return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
new file mode 100644
index 00000000000..67d1434b1f8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure function call, we issue an asm
+ statement to make sure the watermark passed to it is not assumed to be
+ unchanged. */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run. */
+__attribute__ ((__pure__))
+#endif
+f() {
+ static int i; /* Stop it from being detected as const. */
+ return i;
+}
+
+int
+g() {
+ return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
new file mode 100644
index 00000000000..59f02ea901f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure wrapping call, we issue an asm statement
+ to make sure the watermark passed to it is not assumed to be unchanged. */
+
+int __attribute__ ((__strub__ (2), __pure__))
+f() {
+ static int i; /* Stop it from being detected as const. */
+ return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
new file mode 100644
index 00000000000..973e909217d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure wrapping call, we issue an asm
+ statement to make sure the watermark passed to it is not assumed to be
+ unchanged. */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run. */
+__attribute__ ((__pure__))
+#endif
+f() {
+ static int i; /* Stop it from being detected as const. */
+ return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
new file mode 100644
index 00000000000..a4077c35a60
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -0,0 +1,85 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+ equivalent strub functions don't. */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+ /* We use this variable to avoid any stack red zone. Stack scrubbing covers
+ it, but __builtin_stack_address, that we take as a reference, doesn't, so
+ if e.g. callable() were to store the string in the red zone, we wouldn't
+ find it because it would be outside the range we searched. */
+ typedef void __attribute__ ((__strub__ (3))) callable_t (char *);
+ callable_t *f = 0;
+
+ char s[sizeof (test_string)];
+ __builtin_strcpy (s, test_string);
+ asm ("" : "+m" (s), "+r" (f));
+
+ if (__builtin_expect (!f, 1))
+ return (char *) __builtin_stack_address ();
+
+ f (s);
+ return 0;
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+ char *p = (char *) __builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+ return leak_string ();
+}
+
+int main ()
+{
+ if (!look_for_string (callable ()))
+ __builtin_abort ();
+ if (look_for_string (at_calls ()))
+ __builtin_abort ();
+ if (look_for_string (internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
new file mode 100644
index 00000000000..94e4156ea73
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+ equivalent strub functions don't. */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+ int len = sizeof (test_string);
+ asm ("" : "+rm" (len));
+ char s[len];
+ __builtin_strcpy (s, test_string);
+ asm ("" : "+m" (s));
+ return (char *) __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+ char *p = (char *) __builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+ return leak_string ();
+}
+
+int main ()
+{
+ if (!look_for_string (callable ()))
+ __builtin_abort ();
+ if (look_for_string (at_calls ()))
+ __builtin_abort ();
+ if (look_for_string (internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
new file mode 100644
index 00000000000..0ca74beb59d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+/* { dg-require-effective-target alloca } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+ equivalent strub functions don't. */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+ int len = sizeof (test_string);
+ char *s = (char *) __builtin_alloca (len);
+ __builtin_strcpy (s, test_string);
+ asm ("" : "+m" (s));
+ return (char *) __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+ char *p = (char *) __builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+ return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+ return leak_string ();
+}
+
+int main ()
+{
+ if (!look_for_string (callable ()))
+ __builtin_abort ();
+ if (look_for_string (at_calls ()))
+ __builtin_abort ();
+ if (look_for_string (internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
new file mode 100644
index 00000000000..4ab11c0682e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -0,0 +1,101 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=all" } */
+/* { dg-require-effective-target alloca } */
+
+/* Check that multi-level, multi-inlined functions still get cleaned up as
+ expected, without overwriting temporary stack allocations while they should
+ still be available. */
+
+#ifndef ATTR_STRUB_AT_CALLS
+# define ATTR_STRUB_AT_CALLS /* Defined in strub-run4d.c. */
+#endif
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__))
+char *
+leak_string (void)
+{
+ int __attribute__ ((__strub__)) len = 512;
+ asm ("" : "+r" (len));
+ char s[len];
+ __builtin_strcpy (s, test_string);
+ __builtin_strcpy (s + len - sizeof (test_string), test_string);
+ asm ("" : "+m" (s));
+ return (char *) __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+ char *p = (char *) __builtin_stack_address ();
+
+ if (p == e)
+ __builtin_abort ();
+
+ if (p > e)
+ {
+ char *q = p;
+ p = e;
+ e = q;
+ }
+
+ for (char *re = e - sizeof (test_string); p < re; p++)
+ for (int i = 0; p[i] == test_string[i]; i++)
+ if (i == sizeof (test_string) - 1)
+ return i;
+
+ return 0;
+}
+
+static inline ATTR_STRUB_AT_CALLS
+char *
+innermost ()
+{
+ int __attribute__ ((__strub__)) len = 512;
+ asm ("" : "+r" (len));
+ char s[len];
+ __builtin_strcpy (s, test_string);
+ __builtin_strcpy (s + len - sizeof (test_string), test_string);
+ asm ("" : "+m" (s));
+ char *ret = leak_string ();
+ if (__builtin_strcmp (s, test_string) != 0)
+ __builtin_abort ();
+ if (__builtin_strcmp (s + len - sizeof (test_string), test_string) != 0)
+ __builtin_abort ();
+ return ret;
+}
+
+static inline ATTR_STRUB_AT_CALLS
+char *
+intermediate ()
+{
+ int __attribute__ ((__strub__)) len = 512;
+ asm ("" : "+r" (len));
+ char s[len];
+ __builtin_strcpy (s, test_string);
+ __builtin_strcpy (s + len - sizeof (test_string), test_string);
+ asm ("" : "+m" (s));
+ char *ret = innermost ();
+ if (__builtin_strcmp (s, test_string) != 0)
+ __builtin_abort ();
+ if (__builtin_strcmp (s + len - sizeof (test_string), test_string) != 0)
+ __builtin_abort ();
+ return ret;
+}
+
+static inline __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+ return intermediate ();
+}
+
+int __attribute__ ((strub (0)))
+main ()
+{
+ if (look_for_string (internal ()))
+ __builtin_abort ();
+ __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4c.c b/gcc/testsuite/c-c++-common/torture/strub-run4c.c
new file mode 100644
index 00000000000..57f9baf758d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4c.c
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=at-calls" } */
+/* { dg-require-effective-target alloca } */
+
+#include "strub-run4.c"
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4d.c b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
new file mode 100644
index 00000000000..e4f7445607c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+/* { dg-require-effective-target alloca } */
+
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ (1)))
+
+#include "strub-run4.c"
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4i.c b/gcc/testsuite/c-c++-common/torture/strub-run4i.c
new file mode 100644
index 00000000000..459f6886c54
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4i.c
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=internal" } */
+/* { dg-require-effective-target alloca } */
+
+#include "strub-run4.c"
diff --git a/gcc/testsuite/g++.dg/strub-run1.C b/gcc/testsuite/g++.dg/strub-run1.C
new file mode 100644
index 00000000000..0d367fb83d0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/strub-run1.C
@@ -0,0 +1,19 @@
+// { dg-do run }
+// { dg-options "-fstrub=internal" }
+
+// Check that we don't get extra copies.
+
+struct T {
+ T &self;
+ void check () const { if (&self != this) __builtin_abort (); }
+ T() : self (*this) { check (); }
+ T(const T& ck) : self (*this) { ck.check (); check (); }
+ ~T() { check (); }
+};
+
+T foo (T q) { q.check (); return T(); }
+T bar (T p) { p.check (); return foo (p); }
+
+int main () {
+ bar (T()).check ();
+}
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" } } */
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
new file mode 100644
index 00000000000..697ac9de764
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -0,0 +1,21 @@
+-- { dg-do compile }
+-- { dg-options "-fstrub=default -fdump-ipa-strubm" }
+
+package body Strub_Attr 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 (X);
+end Strub_Attr;
+
+-- { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]2\[)\]\[)\]" 2 "strubm" } }
+-- { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
diff --git a/gcc/testsuite/gnat.dg/strub_attr.ads b/gcc/testsuite/gnat.dg/strub_attr.ads
new file mode 100644
index 00000000000..a94c23bf418
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_attr.ads
@@ -0,0 +1,12 @@
+package Strub_Attr 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;
+end Strub_Attr;
diff --git a/gcc/tree-cfg.c b/gcc/tree-cfg.c
index 61b53913e0b..9b01f2bb01b 100644
--- a/gcc/tree-cfg.c
+++ b/gcc/tree-cfg.c
@@ -5620,6 +5620,7 @@ gimple_verify_flow_info (void)
{
gimple *stmt = gsi_stmt (gsi);
+ /* Do NOT disregard debug stmts after found_ctrl_stmt. */
if (found_ctrl_stmt)
{
error ("control flow in the middle of basic block %d",
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index 83941bc0cee..df4abdf1449 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -497,8 +497,9 @@ extern gimple_opt_pass *make_pass_adjust_alignment (gcc::context *ctxt);
/* IPA Passes */
extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt);
-extern simple_ipa_opt_pass
- *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub_mode (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_tree_profile (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_auto_profile (gcc::context *ctxt);
diff --git a/gcc/tree-ssa-ccp.c b/gcc/tree-ssa-ccp.c
index 1a63ae5f104..2f2fffac4df 100644
--- a/gcc/tree-ssa-ccp.c
+++ b/gcc/tree-ssa-ccp.c
@@ -2827,7 +2827,9 @@ optimize_stack_restore (gimple_stmt_iterator i)
if (!callee
|| !fndecl_built_in_p (callee, BUILT_IN_NORMAL)
/* All regular builtins are ok, just obviously not alloca. */
- || ALLOCA_FUNCTION_CODE_P (DECL_FUNCTION_CODE (callee)))
+ || ALLOCA_FUNCTION_CODE_P (DECL_FUNCTION_CODE (callee))
+ /* Do not remove stack updates before strub leave. */
+ || fndecl_built_in_p (callee, BUILT_IN___STRUB_LEAVE))
return NULL_TREE;
if (fndecl_built_in_p (callee, BUILT_IN_STACK_RESTORE))
diff --git a/libgcc/Makefile.in b/libgcc/Makefile.in
index 7ec97584554..c4ee6701cb3 100644
--- a/libgcc/Makefile.in
+++ b/libgcc/Makefile.in
@@ -428,7 +428,7 @@ ifneq ($(enable_shared),yes)
iterator = $(patsubst %,$(srcdir)/static-object.mk,$(iter-items))
endif
-LIB2ADD += enable-execute-stack.c
+LIB2ADD += enable-execute-stack.c $(srcdir)/strub.c
# While emutls.c has nothing to do with EH, it is in LIB2ADDEH*
# instead of LIB2ADD because that's the way to be sure on some targets
diff --git a/libgcc/libgcc2.h b/libgcc/libgcc2.h
index 1819ff3ac3d..857091e65c8 100644
--- a/libgcc/libgcc2.h
+++ b/libgcc/libgcc2.h
@@ -532,6 +532,10 @@ extern int __parityDI2 (UDWtype);
extern void __enable_execute_stack (void *);
+extern void __strub_enter (void **);
+extern void __strub_update (void**);
+extern void __strub_leave (void **);
+
#ifndef HIDE_EXPORTS
#pragma GCC visibility pop
#endif
diff --git a/libgcc/strub.c b/libgcc/strub.c
new file mode 100644
index 00000000000..fd6e27556e4
--- /dev/null
+++ b/libgcc/strub.c
@@ -0,0 +1,112 @@
+/* Stack scrubbing infrastructure
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+Under Section 7 of GPL version 3, you are granted additional
+permissions described in the GCC Runtime Library Exception, version
+3.1, as published by the Free Software Foundation.
+
+You should have received a copy of the GNU General Public License and
+a copy of the GCC Runtime Library Exception along with this program;
+see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
+<http://www.gnu.org/licenses/>. */
+
+#include "tconfig.h"
+#include "tsystem.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "libgcc_tm.h"
+#include "libgcc2.h"
+
+#ifndef STACK_GROWS_DOWNWARD
+# define TOPS >
+#else
+# define TOPS <
+#endif
+
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+
+/* Enter a stack scrubbing context, initializing the watermark to the caller's
+ stack address. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_enter (void **watermark)
+{
+ *watermark = __builtin_frame_address (0);
+}
+
+/* Update the watermark within a stack scrubbing context with the current stack
+ pointer. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_update (void **watermark)
+{
+ void *sp = __builtin_frame_address (0);
+
+ if (sp TOPS *watermark)
+ *watermark = sp;
+}
+
+#ifndef TARGET_STRUB_USE_DYNAMIC_ARRAY
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+#endif
+
+#ifndef TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+# ifdef TARGET_STRUB_MAY_USE_MEMSET
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+# else
+# define TARGET_STRUB_MAY_USE_MEMSET 1
+# endif
+#endif
+
+/* Leave a stack scrubbing context, restoring and updating SAVED, and
+ clearing the stack between top and watermark. */
+void ATTRIBUTE_STRUB_CALLABLE
+#if ! TARGET_STRUB_MAY_USE_MEMSET
+__attribute__ ((__optimize__ ("-fno-tree-loop-distribute-patterns")))
+#endif
+__strub_leave (void **mark)
+{
+ void *sp = __builtin_stack_address ();
+
+ void **base, **end;
+#ifndef STACK_GROWS_DOWNWARD
+ base = sp;
+ end = *mark;
+#else
+ base = *mark;
+ end = sp;
+#endif
+
+ ptrdiff_t len = end - base;
+ if (len <= 0)
+ return;
+
+#if ! TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+ /* Allocate a dynamically-sized array covering the desired range, so that we
+ can safely call memset on it. */
+ void *ptr[len];
+ base = &ptr[0];
+ end = &ptr[len];
+#else
+ void **ptr = end;
+#endif /* TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY */
+
+ /* ldist turns this into a memset. Without the dynamic array above, that call
+ is likely unsafe: possibly tail-called, and likely scribbling over its own
+ stack frame. */
+ while (base < end)
+ *base++ = 0;
+
+ asm ("" : : "m" (ptr));
+}
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/strub)] strub with function and variable attributes
@ 2021-08-04 8:55 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2021-08-04 8:55 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:4b6eef5d36e2c2978a5e81cb2b99e3aeb18e27a8
commit 4b6eef5d36e2c2978a5e81cb2b99e3aeb18e27a8
Author: Alexandre Oliva <oliva@adacore.com>
Date: Sat Jul 24 23:05:42 2021 -0300
strub with function and variable attributes
still missing verification of calls, documentation, tests,
builtin expansion
Diff:
---
gcc/Makefile.in | 1 +
gcc/builtins.c | 163 ++
gcc/builtins.def | 4 +
gcc/c-family/c-attribs.c | 53 +
gcc/common.opt | 24 +
gcc/doc/extend.texi | 72 +
gcc/doc/invoke.texi | 41 +
gcc/ipa-inline.c | 6 +
gcc/ipa-strub.c | 2779 ++++++++++++++++++++
gcc/ipa-strub.h | 25 +
gcc/passes.def | 2 +
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 +
gcc/testsuite/g++.dg/wrappers/strub1.C | 18 +
gcc/testsuite/g++.dg/wrappers/strub2.C | 22 +
gcc/testsuite/g++.dg/wrappers/strub3.C | 22 +
gcc/testsuite/g++.dg/wrappers/strub4.C | 18 +
gcc/tree-cfg.c | 1 +
gcc/tree-pass.h | 5 +-
libgcc/Makefile.in | 2 +-
libgcc/libgcc2.h | 4 +
libgcc/strub.c | 112 +
47 files changed, 3874 insertions(+), 3 deletions(-)
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 1666ef84d6a..163fd1a856c 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1468,6 +1468,7 @@ OBJS = \
ipa-reference.o \
ipa-ref.o \
ipa-utils.o \
+ ipa-strub.o \
ipa.o \
ira.o \
ira-build.o \
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 170d776c410..f387d93974f 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -153,6 +153,7 @@ static rtx expand_builtin_strnlen (tree, rtx, machine_mode);
static rtx expand_builtin_alloca (tree);
static rtx expand_builtin_unop (machine_mode, tree, rtx, rtx, optab);
static rtx expand_builtin_frame_address (tree, tree);
+static rtx expand_builtin_stack_address ();
static tree stabilize_va_list_loc (location_t, tree, int);
static rtx expand_builtin_expect (tree, rtx);
static rtx expand_builtin_expect_with_probability (tree, rtx);
@@ -7893,6 +7894,147 @@ expand_builtin_frame_address (tree fndecl, tree exp)
}
}
+#ifndef STACK_GROWS_DOWNWARD
+# define STACK_TOPS GT
+#else
+# define STACK_TOPS LT
+#endif
+
+#ifdef POINTERS_EXTEND_UNSIGNED
+# define STACK_UNSIGNED POINTERS_EXTEND_UNSIGNED
+#else
+# define STACK_UNSIGNED true
+#endif
+
+/* Expand a call to builtin function __builtin_stack_address. */
+
+static rtx
+expand_builtin_stack_address ()
+{
+ return convert_to_mode (ptr_mode, copy_to_reg (stack_pointer_rtx),
+ STACK_UNSIGNED);
+}
+
+/* Expand a call to builtin function __builtin_strub_enter. */
+
+static rtx
+expand_builtin_strub_enter (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 1 || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ emit_move_insn (wmark, stktop);
+
+ return const0_rtx;
+}
+
+/* Expand a call to builtin function __builtin_strub_update. */
+
+static rtx
+expand_builtin_strub_update (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 2 || optimize_size || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ rtx wmarkr = force_reg (ptr_mode, wmark);
+
+ rtx_code_label *lab = gen_label_rtx ();
+ do_compare_rtx_and_jump (stktop, wmarkr, STACK_TOPS, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, lab, NULL,
+ profile_probability::very_likely ());
+ emit_move_insn (wmark, stktop);
+ emit_label (lab);
+
+ return const0_rtx;
+}
+
+
+/* Expand a call to builtin function __builtin_strub_leave. */
+
+static rtx
+expand_builtin_strub_leave (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 2 || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ rtx wmarkr = force_reg (ptr_mode, wmark);
+
+#ifndef STACK_GROWS_DOWNWARD
+ rtx base = stktop;
+ rtx end = wmarkr;
+#else
+ rtx base = wmarkr;
+ rtx end = stktop;
+#endif
+
+ rtx_code_label *done = gen_label_rtx ();
+ do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, done, NULL,
+ profile_probability::very_likely ());
+
+ if (optimize < 3)
+ expand_call (exp, NULL_RTX, true);
+ else
+ {
+ /* Ok, now we've determined we want to copy the block, so convert the
+ addresses to Pmode, as needed to dereference them to access ptr_mode
+ memory locations, so that we don't have to convert anything within the
+ loop. */
+ base = memory_address (ptr_mode, base);
+ end = memory_address (ptr_mode, end);
+
+ rtx zero = force_operand (const0_rtx, NULL_RTX);
+ int ulen = GET_MODE_SIZE (ptr_mode);
+ rtx incr = plus_constant (Pmode, base, ulen);
+ rtx dstm = gen_rtx_MEM (ptr_mode, base);
+
+ rtx_code_label *loop = gen_label_rtx ();
+ emit_label (loop);
+ emit_move_insn (dstm, zero);
+ emit_move_insn (base, force_operand (incr, NULL_RTX));
+ do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
+ Pmode, NULL_RTX, NULL, loop,
+ profile_probability::very_likely ());
+ }
+
+ emit_label (done);
+
+ return const0_rtx;
+}
+
/* Expand EXP, a call to the alloca builtin. Return NULL_RTX if we
failed and the caller should emit a normal call. */
@@ -10151,6 +10293,27 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
case BUILT_IN_RETURN_ADDRESS:
return expand_builtin_frame_address (fndecl, exp);
+ case BUILT_IN_STACK_ADDRESS:
+ return expand_builtin_stack_address ();
+
+ case BUILT_IN___STRUB_ENTER:
+ target = expand_builtin_strub_enter (exp);
+ if (target)
+ return target;
+ break;
+
+ case BUILT_IN___STRUB_UPDATE:
+ target = expand_builtin_strub_update (exp);
+ if (target)
+ return target;
+ break;
+
+ case BUILT_IN___STRUB_LEAVE:
+ target = expand_builtin_strub_leave (exp);
+ if (target)
+ return target;
+ break;
+
/* Returns the address of the area where the structure is returned.
0 otherwise. */
case BUILT_IN_AGGREGATE_INCOMING_ADDRESS:
diff --git a/gcc/builtins.def b/gcc/builtins.def
index ec556df4f66..4545033e664 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -878,6 +878,10 @@ DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSL, "ffsl", BT_FN_INT_LONG, ATTR_CONST_NOTHRO
DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSLL, "ffsll", BT_FN_INT_LONGLONG, ATTR_CONST_NOTHROW_LEAF_LIST)
DEF_EXT_LIB_BUILTIN (BUILT_IN_FORK, "fork", BT_FN_PID, ATTR_NOTHROW_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FRAME_ADDRESS, "frame_address", BT_FN_PTR_UINT, ATTR_NULL)
+DEF_GCC_BUILTIN (BUILT_IN_STACK_ADDRESS, "stack_address", BT_FN_PTR, ATTR_NULL)
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_ENTER, "__builtin___strub_enter")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_UPDATE, "__builtin___strub_update")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_LEAVE, "__builtin___strub_leave")
/* [trans-mem]: Adjust BUILT_IN_TM_FREE if BUILT_IN_FREE is changed. */
DEF_LIB_BUILTIN (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_LEAF_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FROB_RETURN_ADDR, "frob_return_addr", BT_FN_PTR_PTR, ATTR_NULL)
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index e60fb31d8c8..89ef83f6fac 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -69,6 +69,7 @@ static tree handle_asan_odr_indicator_attribute (tree *, tree, tree, int,
static tree handle_stack_protect_attribute (tree *, tree, tree, int, bool *);
static tree handle_no_stack_protector_function_attribute (tree *, tree,
tree, int, bool *);
+static tree handle_strub_attribute (tree *, tree, tree, int, bool *);
static tree handle_noinline_attribute (tree *, tree, tree, int, bool *);
static tree handle_noclone_attribute (tree *, tree, tree, int, bool *);
static tree handle_nocf_check_attribute (tree *, tree, tree, int, bool *);
@@ -306,6 +307,8 @@ const struct attribute_spec c_common_attribute_table[] =
{ "no_stack_protector", 0, 0, true, false, false, false,
handle_no_stack_protector_function_attribute,
attr_stack_protect_exclusions },
+ { "strub", 0, 1, false, true, false, true,
+ handle_strub_attribute, NULL },
{ "noinline", 0, 0, true, false, false, false,
handle_noinline_attribute,
attr_noinline_exclusions },
@@ -1290,6 +1293,56 @@ handle_noipa_attribute (tree *node, tree name, tree, int, bool *no_add_attrs)
return NULL_TREE;
}
+/* Handle a "strub" attribute; arguments as in
+ struct attribute_spec.handler. */
+
+static tree
+handle_strub_attribute (tree *node, tree name,
+ tree args,
+ int ARG_UNUSED (flags), bool *no_add_attrs)
+{
+ bool enable = true;
+
+ if (args
+ && (TREE_CODE (*node) == FUNCTION_TYPE
+ || TREE_CODE (*node) == METHOD_TYPE))
+ {
+ /* 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)
+ {
+ warning (OPT_Wattributes,
+ "%qE attribute ignored because of argument %qE",
+ name, TREE_VALUE (args));
+ *no_add_attrs = true;
+ enable = false;
+ }
+ /* 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));
+
+ /* 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;
+
+ return NULL_TREE;
+}
+
/* Handle a "noinline" attribute; arguments as in
struct attribute_spec.handler. */
diff --git a/gcc/common.opt b/gcc/common.opt
index d9da1131eda..c0408cb0f4c 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,6 +2687,30 @@ 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=all
+Common RejectNegative Var(flag_strub, 3)
+Enable stack scrubbing for all viable functions.
+
+fstrub=at-calls
+Common RejectNegative Var(flag_strub, 1)
+Enable at-calls stack scrubbing for all viable functions.
+
+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 b83cd4919bb..020bb1aa808 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -77,6 +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.
* 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.
@@ -8652,6 +8653,40 @@ pid_t wait (wait_status_ptr_t p)
@}
@end smallexample
+@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 numeric
+argument.
+
+A function associated with at-calls strub mode (@code{strub(1)})
+undergoes interface changes, and callers automatically gain code to
+scrub the stack space it uses. If the operational numeric argument is
+omitted from the @code{strub} attribute in a function type, the at-calls
+mode is selected.
+
+A function with internal strub mode @code{strub(2)} retains an
+unmodified interface, but it may be turned into a wrapper that calls the
+wrapped body, with a custom interface, and then scrubs the stack space
+used by it.
+
+A variable whose type carries the strub attribute causes functions that
+use it to have strub enabled.
+
+A function associated with the disabled strub mode @code{strub(0)} will
+not have its stack scrubbed. A function that has strub mode disabled
+cannot be called from strub contexts, namely from bodies of functions
+with strub modes @code{1} or @code{2}.
+
+A function whose body is not a strub context, but that should be
+callable from within strub contexts, should be marked as callable with
+@code{strub(3)}.
+
+Strub contexts are not inlined into non-strub contexts. When an
+internal-strub function is split, the wrapper can often be inlined, but
+its wrapped body cannot. Functions marked as @code{always_inline}, even
+if explicitly assigned internal strub mode, will not undergo wrapping.
+
@item unused
@cindex @code{unused} type attribute
When attached to a type (including a @code{union} or a @code{struct}),
@@ -11670,6 +11705,43 @@ option is in effect. Such calls should only be made in debugging
situations.
@end deftypefn
+@deftypefn {Built-in Function} {void *} __builtin_stack_address ()
+This function returns the value of the stack pointer register.
+@end deftypefn
+
+@node Stack Scrubbing
+@section Stack scrubbing interfaces
+
+Stack scrubbing involves cooperation between a strub context, i.e., a
+function whose stack frame is to be zeroed out, and its callers. The
+caller initializes a stack watermark, the 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, each one defined in libgcc, but expanded internally
+depending on optimization levels.
+
+@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 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 strub context 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 strub context. It remains as a function call
+at optimization levels lower than 3.
+@end deftypefn
+
@node Vector Extensions
@section Using Vector Instructions through Built-in Functions
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 32697e6117c..cca3e9b18c6 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,6 +599,7 @@ 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
-fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
-fvtv-counts -fvtv-debug @gol
-finstrument-functions @gol
@@ -15425,6 +15426,46 @@ 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 (strub) setting, namely, 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, even when the @code{strub} attribute
+is present in function and variable types.
+
+@item -fstrub=at-calls
+@opindex fstrub=at-calls
+Enable at-calls strub for all viable functions, and consider non-viable
+functions as callable. A function is non-eligible for at-calls strub if
+a different strub mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+At-calls strub mode, if not requested with the function type, is only
+viable for an eligible function if it is not visible to other
+translation units, through its declaration or by having its address
+taken.
+
+@item -fstrub=internal
+@opindex fstrub=internal
+Enable internal strub for all viable functions, and consider non-viable
+functions as callable. A function is not eligible for internal strub if
+another strub mode is explicitly requested, attributes @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 internal strub, all
+eligible functions are viable.
+
+@item -fstrub=all
+@opindex fstrub=all
+Enable strub for all viable functions, and consider non-viable functions
+as callable. When both strub modes are viable, at-calls is preferred.
+
@item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
@opindex fvtable-verify
This option is only available when compiling C++ code.
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 413446bcc46..7f4bc44d2bb 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -119,6 +119,7 @@ along with GCC; see the file COPYING3. If not see
#include "stringpool.h"
#include "attribs.h"
#include "asan.h"
+#include "ipa-strub.h"
typedef fibonacci_heap <sreal, cgraph_edge> edge_heap_t;
typedef fibonacci_node <sreal, cgraph_edge> edge_heap_node_t;
@@ -396,6 +397,11 @@ 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))
+ {
+ e->inline_failed = CIF_UNSPECIFIED;
+ inlinable = false;
+ }
if (!inlinable && report)
report_inline_failed_reason (e);
return inlinable;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
new file mode 100644
index 00000000000..e302f0ec1c5
--- /dev/null
+++ b/gcc/ipa-strub.c
@@ -0,0 +1,2779 @@
+/* strub (stack scrubbing) support.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "gimplify.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "gimple-iterator.h"
+#include "gimplify-me.h"
+#include "tree-into-ssa.h"
+#include "tree-ssa.h"
+#include "tree-cfg.h"
+#include "cfghooks.h"
+#include "cfgloop.h"
+#include "cfgcleanup.h"
+#include "tree-eh.h"
+#include "except.h"
+#include "builtins.h"
+#include "attribs.h"
+#include "tree-inline.h"
+#include "cgraph.h"
+#include "alloc-pool.h"
+#include "symbol-summary.h"
+#include "ipa-prop.h"
+#include "ipa-fnsummary.h"
+#include "gimple-fold.h"
+#include "fold-const.h"
+#include "gimple-walk.h"
+#include "tree-dfa.h"
+#include "langhooks.h"
+#include "calls.h"
+#include "vec.h"
+#include "stor-layout.h"
+#include "varasm.h"
+#include "alias.h"
+#include "diagnostic.h"
+#include "intl.h"
+#include "ipa-strub.h"
+
+#if BUILDING_GCC_MAJOR >= 11
+# include "symtab-thunks.h"
+# include "attr-fnspec.h"
+# define HAVE_ATTR_FNSPEC 1
+# define FOR_GCC_11P 1
+#else
+# define HAVE_ATTR_FNSPEC 0
+# define FOR_GCC_11P 0
+#endif
+
+/* Const and pure functions that gain a watermark parameter for strub purposes
+ are still regarded as such, which may cause the inline expansions of the
+ __strub builtins to malfunction. Ideally, attribute "fn spec" would enable
+ us to inform the backend about requirements and side effects of the call, but
+ call_fusage building in calls.c:expand_call does not even look at
+ attr_fnspec, so we resort to asm loads and updates to attain an equivalent
+ effect. Once expand_call gains the ability to issue extra memory uses and
+ clobbers based on pure/const function's fnspec, we can define this to 1. */
+#define ATTR_FNSPEC_DECONST_WATERMARK 0
+
+enum strub_mode {
+ /* This mode denotes a regular function, that does not require stack
+ scrubbing (strubbing). It may call any other functions, but if
+ it calls AT_CALLS (or WRAPPED) ones, strubbing logic is
+ automatically introduced around those calls (the latter, by
+ inlining INTERNAL wrappers). */
+ STRUB_DISABLED = 0,
+
+ /* This denotes a function whose signature is (to be) modified to
+ take an extra parameter, for stack use annotation, and its
+ callers must initialize and pass that argument, and perform the
+ strubbing. Functions that are explicitly marked with attribute
+ strub must have the mark visible wherever the function is,
+ including aliases, and overriders and overriding methods.
+ Functions that are implicitly marked for strubbing, for accessing
+ variables explicitly marked as such, will only select this
+ strubbing method if they are internal to a translation unit. It
+ can only be inlined into other strubbing functions, i.e.,
+ STRUB_AT_CALLS or STRUB_WRAPPED. */
+ STRUB_AT_CALLS = 1,
+
+ /* This denotes a function that is to perform strubbing internally,
+ without any changes to its interface (the function is turned into
+ a strubbing wrapper, and its original body is moved to a separate
+ STRUB_WRAPPED function, with a modified interface). Functions
+ may be explicitly marked with attribute strub(2), and the
+ attribute must be visible at the point of definition. Functions
+ that are explicitly marked for strubbing, for accessing variables
+ explicitly marked as such, may select this strubbing mode if
+ their interface cannot change, e.g. because its interface is
+ visible to other translation units, directly, by indirection
+ (having its address taken), inheritance, etc. Functions that use
+ this method must not have the noclone attribute, nor the noipa
+ one. Functions marked as always_inline may select this mode, but
+ they are NOT wrapped, they remain unchanged, and are only inlined
+ into strubbed contexts. Once non-always_inline functions are
+ wrapped, the wrapper becomes STRUB_WRAPPER, and the wrapped becomes
+ STRUB_WRAPPED. */
+ STRUB_INTERNAL = 2,
+
+ /* This denotes a function whose stack is not strubbed, but that is
+ nevertheless explicitly or implicitly marked as callable from strubbing
+ functions. Normally, only STRUB_AT_CALLS (and STRUB_INTERNAL ->
+ STRUB_WRAPPED) functions can be called from strubbing contexts (bodies of
+ STRUB_AT_CALLS, STRUB_INTERNAL and STRUB_WRAPPED functions), but attribute
+ strub(3) enables other functions to be (indirectly) called from these
+ contexts. Some builtins and internal functions may be implicitly marked as
+ STRUB_CALLABLE. */
+ STRUB_CALLABLE = 3,
+
+ /* This denotes the function that took over the body of a
+ STRUB_INTERNAL function. At first, it's only called by its
+ wrapper, but the wrapper may be inlined. The wrapped function,
+ in turn, can only be inlined into other functions whose stack
+ frames are strubbed, i.e., that are STRUB_WRAPPED or
+ STRUB_AT_CALLS. */
+ STRUB_WRAPPED = -1,
+
+ /* This denotes the wrapper function that replaced the STRUB_INTERNAL
+ function. This mode overrides the STRUB_INTERNAL mode at the time the
+ internal to-be-wrapped function becomes a wrapper, so that inlining logic
+ can tell one from the other. */
+ STRUB_WRAPPER = -2,
+
+ /* 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
+get_strub_attr_from_type (tree type)
+{
+ return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
+}
+
+static tree
+get_strub_attr_from_decl (tree decl)
+{
+ tree ret = lookup_attribute ("strub", DECL_ATTRIBUTES (decl));
+ if (ret)
+ return ret;
+ return get_strub_attr_from_type (TREE_TYPE (decl));
+}
+
+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);
+
+#if 0 /* ??? use symbolic mode names with interned strings? */
+ char *s = NULL;
+
+ switch (strub_mode)
+ {
+
+ }
+#endif
+}
+
+static enum strub_mode
+get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
+{
+ enum strub_mode mode = STRUB_DISABLED;
+
+ if (strub_attr)
+ {
+ 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 /* ??? Support symbolic mode names? */
+ gcc_unreachable ();
+ }
+
+ return mode;
+}
+
+static enum strub_mode
+get_strub_mode (cgraph_node *node)
+{
+ return get_strub_mode_from_attr (get_strub_attr_from_decl (node->decl));
+}
+
+static enum strub_mode
+get_strub_mode_from_type (tree type)
+{
+ bool var_p = !FUNC_OR_METHOD_TYPE_P (type);
+ tree attr = get_strub_attr_from_type (type);
+
+ if (attr)
+ return get_strub_mode_from_attr (attr, var_p);
+
+ if (flag_strub > 0 && !var_p)
+ return STRUB_CALLABLE;
+
+ return STRUB_DISABLED;
+}
+
+static bool
+calls_builtin_va_start_p (cgraph_node *node)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (fndecl_built_in_p (cdecl, BUILT_IN_VA_START))
+ return true;
+ }
+
+ return result;
+}
+
+static bool
+calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!fndecl_built_in_p (cdecl, BUILT_IN_APPLY_ARGS))
+ continue;
+
+ result = true;
+
+ if (!report)
+ break;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "at-calls strub does not support call to %qD",
+ cdecl);
+ }
+
+ return result;
+}
+
+static inline bool
+strub_always_inline_p (cgraph_node *node)
+{
+ return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
+}
+
+static inline bool
+can_strub_p (cgraph_node *node, bool report = false)
+{
+ bool result = true;
+
+ if (!report && strub_always_inline_p (node))
+ return result;
+
+ if (lookup_attribute ("noipa", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for strub because of attribute %<noipa%>",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+can_strub_at_calls_p (cgraph_node *node, bool report = false)
+{
+ bool result = !report || can_strub_p (node, report);
+
+ if (!result && !report)
+ return result;
+
+ return !calls_builtin_apply_args_p (node, report);
+}
+
+#define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
+
+/* We can't perform internal strubbing if the function body involves certain
+ features:
+
+ - a non-default __builtin_va_start (e.g. x86's __builtin_ms_va_start) is
+ currently unsupported because we can't discover the corresponding va_copy and
+ va_end decls in the wrapper, and we don't convey the alternate variable
+ arguments ABI to the modified wrapped function. The default
+ __builtin_va_start is supported by calling va_start/va_end at the wrapper,
+ that takes variable arguments, passing a pointer to the va_list object to the
+ wrapped function, that runs va_copy from it where the original function ran
+ va_start.
+
+ __builtin_next_arg is currently unsupported because the wrapped function
+ won't be a variable argument function. We could process it in the wrapper,
+ that remains a variable argument function, and replace calls in the wrapped
+ body, but we currently don't.
+
+ __builtin_return_address is rejected because it's generally used when the
+ actual caller matters, and introducing a wrapper breaks such uses as those in
+ the unwinder. */
+
+static bool
+can_strub_internally_p (cgraph_node *node, bool report = false)
+{
+ bool result = !report || can_strub_p (node, report);
+
+ if (!result && !report)
+ return result;
+
+ if (!report && strub_always_inline_p (node))
+ return result;
+
+ if (lookup_attribute ("noclone", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for internal strub"
+ " because of attribute %<noclone%>",
+ node->decl);
+ }
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!((fndecl_built_in_p (cdecl, BUILT_IN_VA_START)
+ && cdecl != builtin_decl_explicit (BUILT_IN_VA_START))
+ || fndecl_built_in_p (cdecl, BUILT_IN_NEXT_ARG)
+ || fndecl_built_in_p (cdecl, BUILT_IN_RETURN_ADDRESS)))
+ continue;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "internal strub does not support call to %qD",
+ cdecl);
+ }
+
+ basic_block bb;
+ if (node->has_gimple_body_p ())
+ {
+ /* Label values referenced 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. */
+
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ glabel *label_stmt = dyn_cast <glabel *> (gsi_stmt (gsi));
+ tree target;
+
+ if (!label_stmt)
+ break;
+
+ 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;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (label_stmt),
+ "internal strub does not support user labels");
+ }
+ }
+
+ if (list_length (TYPE_ARG_TYPES (TREE_TYPE (node->decl)))
+ >= (((HOST_WIDE_INT) 1 << IPA_PARAM_MAX_INDEX_BITS)
+ - STRUB_INTERNAL_MAX_EXTRA_ARGS))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD has too many arguments for internal strub",
+ node->decl);
+ }
+
+ if (result)
+ /* Since we're not changing the function identity proper, just
+ moving its full implementation, we *could* disable
+ fun->cannot_be_copied_reason and/or temporarily drop a noclone
+ attribute. */
+ gcc_checking_assert (tree_versionable_function_p (node->decl));
+
+ return result;
+}
+
+static bool
+strub_from_body_p (cgraph_node *node)
+{
+ if (!node->has_gimple_body_p ())
+ return false;
+
+ /* If any local variable is marked for strub... */
+ unsigned i;
+ tree var;
+ FOR_EACH_LOCAL_DECL (DECL_STRUCT_FUNCTION (node->decl),
+ i, var)
+ if (get_strub_mode_from_type (TREE_TYPE (var))
+ != STRUB_DISABLED)
+ return true;
+
+ /* Now scan the body for loads with strub types. */
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!gimple_assign_load_p (stmt))
+ continue;
+
+ tree rhs = gimple_assign_rhs1 (stmt);
+ if (get_strub_mode_from_type (TREE_TYPE (rhs))
+ != STRUB_DISABLED)
+ return true;
+ }
+
+ return false;
+}
+
+/* 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)
+{
+ if (DECL_BUILT_IN_CLASS (node->decl) != BUILT_IN_NORMAL)
+ return false;
+
+ enum built_in_function fcode = DECL_FUNCTION_CODE (node->decl);
+
+ switch (fcode)
+ {
+ case BUILT_IN_NONE:
+ gcc_unreachable ();
+
+ /* ??? Make all builtins callable. We wish to make any builtin call the
+ compiler might introduce on its own callable. Anything that is
+ predictable enough as to be known not to allow stack data that should
+ be strubbed to unintentionally escape to non-strub contexts can be
+ allowed, and pretty much every builtin appears to fit this description.
+ The exceptions to this rule seem to be rare, and only available as
+ explicit __builtin calls, so let's keep it simple and allow all of
+ them... */
+ default:
+ return true;
+ }
+}
+
+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);
+
+ /* 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;
+ /* 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
+ safely undergo at-calls strubbing. Internal mode will still be used in
+ functions that request it explicitly with attribute strub(2), or when the
+ function body requires strubbing and at-calls strubbing is not viable. */
+ const bool strub_flag_at_calls = flag_strub == 1;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo internal strubbing. At-calls mode will still be used in
+ functions that requiest it explicitly with attribute strub() or strub(1),
+ or when the function body requires strubbing and internal strubbing is not
+ viable. */
+ const bool strub_flag_internal = flag_strub == 2;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo strubbing in either mode. When both modes are viable,
+ at-calls is preferred. */
+ const bool strub_flag_either = flag_strub == 3;
+ /* Besides the default behavior, enable strub implicitly for all viable
+ functions. */
+ const bool strub_flag_viable = flag_strub > 0;
+
+ /* The consider_* variables should be true if selecting the corresponding
+ strub modes would be consistent with requests from attributes and command
+ line flags. Attributes associated with functions pretty much mandate a
+ selection, and should report an error if not satisfied; strub_flag_auto
+ implicitly enables some viable strub mode if that's required by references
+ to variables marked for strub; strub_flag_viable enables strub if viable
+ (even when favoring one mode, body-requested strub can still be satisfied
+ by either mode), and falls back to callable, silently unless variables
+ require strubbing. */
+
+ const bool consider_at_calls
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_AT_CALLS
+ : true));
+ const bool consider_internal
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_INTERNAL
+ : true));
+
+ const bool consider_callable
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_CALLABLE
+ : (strub_flag_viable
+ || strub_callable_builtin_p (node))));
+
+ /* This is a shorthand for either strub-enabled mode. */
+ const bool consider_strub
+ = (consider_at_calls || consider_internal);
+
+ /* We can cope with always_inline functions even with noipa and noclone,
+ because we just leave them alone. */
+ const bool is_always_inline
+ = strub_always_inline_p (node);
+
+ /* Strubbing in general, and each specific strub mode, may have its own set of
+ requirements. We require noipa for strubbing, either because of cloning
+ required for internal strub, or because of caller enumeration required for
+ at-calls strub. We don't consider the at-calls mode eligible if it's not
+ even considered, it has no further requirements. Internal mode requires
+ cloning and the absence of certain features in the body and, like at-calls,
+ it's not eligible if it's not even under consideration.
+
+ ??? Do we need target hooks for further constraints? E.g., x86's
+ "interrupt" attribute breaks internal strubbing because the wrapped clone
+ carries the attribute and thus isn't callable; in this case, we could use a
+ target hook to adjust the clone instead. */
+ const bool strub_eligible
+ = (consider_strub
+ && (is_always_inline || can_strub_p (node)));
+ const bool at_calls_eligible
+ = (consider_at_calls && strub_eligible
+ && can_strub_at_calls_p (node));
+ const bool internal_eligible
+ = (consider_internal && strub_eligible
+ && (is_always_inline
+ || can_strub_internally_p (node)));
+
+ /* In addition to the strict eligibility requirements, some additional
+ constraints are placed on implicit selection of certain modes. These do
+ not prevent the selection of a mode if explicitly specified as part of a
+ function interface (the strub attribute), but they may prevent modes from
+ being selected by the command line or by function bodies. The only actual
+ constraint is on at-calls mode: since we change the function's exposed
+ signature, we won't do it implicitly if the function can possibly be used
+ in ways that do not expect the signature change, e.g., if the function is
+ available to or interposable by other units, if its address is taken,
+ etc. */
+ const bool at_calls_viable
+ = (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->binds_to_current_def_p ()
+ && node->can_be_local_p ()))
+ && node->only_called_directly_p ())));
+ const bool internal_viable
+ = (internal_eligible);
+
+ /* Shorthand. */
+ const bool strub_viable
+ = (at_calls_viable || internal_viable);
+
+ /* We wish to analyze the body, to look for implicit requests for strub, both
+ to implicitly enable it when the body calls for it, and to report errors if
+ the body calls for it but neither mode is viable (even if that follows from
+ non-eligibility because of the explicit specification of some non-strubbing
+ mode). We can refrain from scanning the body only in rare circumstances:
+ when strub is enabled by a function attribute (scanning might be redundant
+ in telling us to also enable it), and when we are enabling strub implicitly
+ but there are non-viable modes: we want to know whether strubbing is
+ required, to fallback to another mode, even if we're only enabling a
+ certain mode, or, when either mode would do, to report an error if neither
+ happens to be viable. */
+ const bool analyze_body
+ = (strub_attr
+ ? !consider_strub
+ : (strub_flag_auto
+ || (strub_flag_viable && (!at_calls_viable && !internal_viable))
+ || (strub_flag_either && !strub_viable)));
+
+ /* Cases in which strubbing is enabled or disabled by strub_flag_auto.
+ Unsatisfiable requests ought to be reported. */
+ const bool strub_required
+ = ((strub_attr && consider_strub)
+ || (analyze_body && strub_from_body_p (node)));
+
+ /* Besides the required cases, we want to abide by the requests to enabling on
+ an if-viable basis. */
+ const bool strub_enable
+ = (strub_required
+ || (strub_flag_at_calls && at_calls_viable)
+ || (strub_flag_internal && internal_viable)
+ || (strub_flag_either && strub_viable));
+
+ /* And now we're finally ready to select a mode that abides by the viability
+ and eligibility constraints, and that satisfies the strubbing requirements
+ and requests, subject to the constraints. If both modes are viable and
+ strub is to be enabled, pick STRUB_AT_CALLS unless STRUB_INTERNAL was named
+ as preferred. */
+ 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
+ : (strub_enable && at_calls_viable)
+ ? (strub_required && !strub_attr
+ ? STRUB_AT_CALLS_OPT
+ : STRUB_AT_CALLS)
+ : consider_callable
+ ? STRUB_CALLABLE
+ : STRUB_DISABLED);
+
+ switch (mode)
+ {
+ case STRUB_CALLABLE:
+ if (is_always_inline)
+ break;
+ /* Fall through. */
+
+ case STRUB_DISABLED:
+ if (strub_enable && !strub_attr)
+ {
+ gcc_checking_assert (analyze_body);
+ error_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD requires strub, but no viable strub mode was found",
+ node->decl);
+ break;
+ }
+ /* Fall through. */
+
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ /* Differences from an mode requested through a function attribute are
+ 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:
+ gcc_unreachable ();
+ }
+
+ return mode;
+}
+
+static void
+set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+ enum strub_mode req_mode = get_strub_mode_from_attr (attr);
+
+ if (attr)
+ {
+ /* Check for and report incompatible mode changes. */
+ if (mode != req_mode
+ && !(req_mode == STRUB_INTERNAL
+ && (mode == STRUB_WRAPPED
+ || mode == STRUB_WRAPPER))
+ && !((req_mode == STRUB_INTERNAL
+ || req_mode == STRUB_AT_CALLS
+ || req_mode == STRUB_CALLABLE)
+ && 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));
+ if (node->alias)
+ {
+ cgraph_node *target = node->ultimate_alias_target ();
+ if (target != node)
+ error_at (DECL_SOURCE_LOCATION (target->decl),
+ "the incompatible selection was determined"
+ " by ultimate alias target %qD",
+ target->decl);
+ }
+
+ /* Report any incompatibilities with explicitly-requested strub. */
+ switch (req_mode)
+ {
+ case STRUB_AT_CALLS:
+ can_strub_at_calls_p (node, true);
+ break;
+
+ case STRUB_INTERNAL:
+ can_strub_internally_p (node, true);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Drop any incompatible strub attributes leading the decl attribute
+ chain. Return if we find one with the mode we need. */
+ for (;;)
+ {
+ if (mode == req_mode)
+ return;
+
+ if (DECL_ATTRIBUTES (node->decl) != attr)
+ break;
+
+ DECL_ATTRIBUTES (node->decl) = TREE_CHAIN (attr);
+ attr = get_strub_attr_from_decl (node->decl);
+ if (!attr)
+ break;
+
+ req_mode = get_strub_mode_from_attr (attr);
+ }
+ }
+ else if (mode == req_mode)
+ return;
+
+ DECL_ATTRIBUTES (node->decl) = tree_cons (get_identifier ("strub"),
+ get_strub_mode_attr_value (mode),
+ DECL_ATTRIBUTES (node->decl));
+}
+
+static void
+set_strub_mode (cgraph_node *node)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+
+ if (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:
+ case STRUB_AT_CALLS_OPT:
+ return;
+
+ case STRUB_DISABLED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ cgraph_node *xnode = node;
+ if (node->alias)
+ xnode = node->ultimate_alias_target ();
+ /* Weakrefs may remain unresolved (the above will return node) if
+ their targets are not defined, so make sure we compute a strub
+ mode for them, instead of defaulting to STRUB_DISABLED and
+ rendering them uncallable. */
+ enum strub_mode mode = (xnode != node && !xnode->alias
+ ? get_strub_mode (xnode)
+ : compute_strub_mode (node, attr));
+
+ set_strub_mode_to (node, mode);
+}
+
+/* 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. */
+
+static bool
+strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode caller_mode = get_strub_mode (caller);
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS_OPT:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return callee_mode != STRUB_INLINABLE;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_AT_CALLS_OPT:
+ case STRUB_INTERNAL:
+ case STRUB_WRAPPER:
+ return (flag_strub >= 0);
+
+ case STRUB_DISABLED:
+ return false;
+
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ 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. */
+
+bool
+strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ case STRUB_AT_CALLS_OPT:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return true;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ strub_mode caller_mode = get_strub_mode (caller);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ case STRUB_AT_CALLS_OPT:
+ return true;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ return false;
+}
+
+/* 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;
+
+ 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_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)
+ {
+ 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
+ functions, or is this already taken care of on account of the
+ attribute's being marked as affecting type identity? */
+}
+
+namespace {
+
+const pass_data pass_data_ipa_strub_mode = {
+ SIMPLE_IPA_PASS,
+ "strubm",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ 0, // properties_finish
+};
+
+class pass_ipa_strub_mode : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub_mode (gcc::context *ctxt)
+ : simple_ipa_opt_pass (pass_data_ipa_strub_mode, ctxt)
+ {}
+ 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
+ overhead. */
+ if (flag_strub == -2)
+ flag_strub = 0;
+ return flag_strub;
+ }
+ virtual unsigned int execute (function *);
+};
+
+const pass_data pass_data_ipa_strub = {
+ SIMPLE_IPA_PASS,
+ "strub",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ TODO_update_ssa
+ | TODO_cleanup_cfg
+ | TODO_rebuild_cgraph_edges
+ | TODO_verify_il, // properties_finish
+};
+
+class pass_ipa_strub : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub (gcc::context *ctxt)
+ : 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 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); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ decl = add_builtin_function \
+ ("__builtin_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ NULL, NULL); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ } \
+ return decl; \
+ }
+
+ DEF_NM_BUILTIN (stack_address,
+ BUILT_IN_STACK_ADDRESS,
+ (ptr_type_node, NULL))
+
+#undef DEF_NM_BUILTIN
+
+#define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST) \
+ static tree get_ ## NAME () { \
+ tree decl = builtin_decl_explicit (CODE); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ tree attrs = NULL; \
+ if (FNSPEC && HAVE_ATTR_FNSPEC) \
+ attrs = tree_cons (get_identifier ("fn spec"), \
+ build_tree_list \
+ (NULL_TREE, \
+ build_string (strlen (FNSPEC), \
+ (FNSPEC))), \
+ attrs); \
+ decl = add_builtin_function_ext_scope \
+ ("__builtin___strub_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ "__strub_" #NAME, attrs); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ } \
+ return decl; \
+ }
+
+ DEF_SS_BUILTIN (enter, ". Ot",
+ BUILT_IN___STRUB_ENTER,
+ (void_type_node, get_qpwmt (), NULL))
+ DEF_SS_BUILTIN (update, ". Wt",
+ BUILT_IN___STRUB_UPDATE,
+ (void_type_node, get_qpwmt (), NULL))
+ DEF_SS_BUILTIN (leave, ". w ",
+ BUILT_IN___STRUB_LEAVE,
+ (void_type_node, get_qpwmt (), NULL))
+
+#undef DEF_SS_BUILTIN
+
+#define DEF_IDENT(NAME) \
+ static inline tree get_ ## NAME () { \
+ static tree identifier = NULL_TREE; \
+ if (!identifier) \
+ identifier = get_identifier (".strub." #NAME); \
+ return identifier; \
+ }
+
+ DEF_IDENT (watermark_ptr)
+ DEF_IDENT (va_list_ptr)
+ DEF_IDENT (apply_args_ptr)
+
+#undef DEF_IDENT
+
+ 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,
+ gimple_seq seq = NULL)
+ {
+ tree uwm = get_update ();
+ gcall *update = gimple_build_call (uwm, 1, wmptr);
+ gimple_seq_add_stmt (&seq, update);
+ if (node)
+#if !IMPLICIT_CGRAPH_EDGES
+ node->create_edge (cgraph_node::get_create (uwm), update, count, false);
+#else
+ (void)count;
+#endif
+ return seq;
+ }
+
+};
+
+} // anon namespace
+
+typedef hash_set<tree> indirect_parms_t;
+
+static tree
+maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
+{
+ if (DECL_P (op))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (op))
+ {
+ tree ret = gimple_fold_indirect_ref (op);
+ if (!ret)
+ ret = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (op)),
+ op,
+ build_int_cst (TREE_TYPE (op), 0));
+ return ret;
+ }
+ }
+ else if (TREE_CODE (op) == ADDR_EXPR
+ && DECL_P (TREE_OPERAND (op, 0)))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (TREE_OPERAND (op, 0)))
+ return TREE_OPERAND (op, 0);
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_make_indirect (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ indirect_parms_t &indirect_parms = *(indirect_parms_t *)wi->info;
+
+ if (!*op || TYPE_P (*op))
+ {
+ *rec = 0;
+ return NULL_TREE;
+ }
+
+ if (tree repl = maybe_make_indirect (indirect_parms, *op, rec))
+ {
+ *op = repl;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ gimple_stmt_iterator &gsi = *(gimple_stmt_iterator *)wi->info;
+
+ *rec = 0;
+
+ if (!*op || TREE_CODE (*op) != ADDR_EXPR)
+ return NULL_TREE;
+
+ if (!is_gimple_val (*op))
+ {
+ tree ret = force_gimple_operand_gsi (&gsi, *op, true,
+ NULL_TREE, true, GSI_SAME_STMT);
+ gcc_assert (ret != *op);
+ *op = ret;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+build_ref_type_for (tree parm, bool nonaliased = true)
+{
+ gcc_checking_assert (TREE_CODE (parm) == PARM_DECL);
+
+ tree ref_type = build_reference_type (TREE_TYPE (parm));
+
+ if (!nonaliased)
+ return ref_type;
+
+ /* Each PARM turned indirect still points to the distinct memory area at the
+ wrapper, and the reference in unchanging, so we might qualify it, but...
+ const is not really important, since we're only using default defs for the
+ reference parm anyway, and not introducing any defs, and restrict seems to
+ cause trouble. E.g., libgnat/s-concat3.adb:str_concat_3 has memmoves that,
+ if it's wrapped, the memmoves are deleted in dse1. Using a distinct alias
+ set seems to not run afoul of this problem, and it hopefully enables the
+ compiler to tell the pointers do point to objects that are not otherwise
+ aliased. */
+#if 1
+ tree qref_type = build_variant_type_copy (ref_type);
+
+ TYPE_ALIAS_SET (qref_type) = new_alias_set ();
+ record_alias_subset (TYPE_ALIAS_SET (qref_type), get_alias_set (ref_type));
+
+ return qref_type;
+#else
+ tree qref_type = build_qualified_type (ref_type,
+ TYPE_QUAL_RESTRICT
+ | TYPE_QUAL_CONST);
+
+ return qref_type;
+#endif
+}
+
+/* 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)
+{
+#if IMPLICIT_CGRAPH_EDGES
+ return;
+#endif
+
+ cgraph_node *node = cgraph_node::get_create (current_function_decl);
+
+ for (gimple_stmt_iterator gsi = gsi_start (seq);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!is_a <gcall *> (stmt))
+ 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);
+ }
+}
+
+static void
+gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
+{
+ gimple *stmt = gsi_stmt (gsi);
+
+ gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+ 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;
+ bool nothrow_p = (must_not_throw_p
+ || (call && gimple_call_nothrow_p (call))
+ || (eh_lp <= 0
+ && (TREE_NOTHROW (cfun->decl)
+ || !flag_exceptions)));
+
+ if (noreturn_p && nothrow_p)
+ return;
+
+ /* Don't expect an EH edge if we're not to throw, or if we're not in an EH
+ region yet. */
+ bool no_eh_edge_p = (nothrow_p || !eh_lp);
+ bool must_end_bb = stmt_ends_bb_p (stmt);
+
+ edge eft = NULL, eeh = NULL;
+ if (must_end_bb && !(noreturn_p && no_eh_edge_p))
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+
+ edge e;
+ edge_iterator ei;
+ FOR_EACH_EDGE (e, ei, gsi_bb (gsi)->succs)
+ {
+ if ((e->flags & EDGE_EH))
+ {
+ gcc_checking_assert (!eeh);
+ eeh = e;
+#if !CHECKING_P
+ if (eft || noreturn_p)
+ break;
+#endif
+ }
+ if ((e->flags & EDGE_FALLTHRU))
+ {
+ gcc_checking_assert (!eft);
+ eft = e;
+#if !CHECKING_P
+ if (eeh || no_eh_edge_p)
+ break;
+#endif
+ }
+ }
+
+ gcc_checking_assert (!(eft && (eft->flags & EDGE_FALLTHRU))
+ == noreturn_p);
+ gcc_checking_assert (!(eeh && (eeh->flags & EDGE_EH))
+ == no_eh_edge_p);
+ gcc_checking_assert (eft != eeh);
+ }
+
+ if (!noreturn_p)
+ {
+ gimple_seq nseq = nothrow_p ? seq : gimple_seq_copy (seq);
+
+ if (must_end_bb)
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+ add_call_edges_for_seq (nseq, eft->count ());
+ gsi_insert_seq_on_edge_immediate (eft, nseq);
+ }
+ else
+ {
+ add_call_edges_for_seq (nseq, gsi_bb (gsi)->count);
+ gsi_insert_seq_after (&gsi, nseq, GSI_SAME_STMT);
+ }
+ }
+
+ if (nothrow_p)
+ return;
+
+ if (eh_lp)
+ {
+ add_call_edges_for_seq (seq, eeh->count ());
+ gsi_insert_seq_on_edge_immediate (eeh, seq);
+ return;
+ }
+
+ /* A throwing call may appear within a basic block in a function that doesn't
+ have any EH regions. We're going to add a cleanup if so, therefore the
+ block will have to be split. */
+ basic_block bb = gsi_bb (gsi);
+ if (!gsi_one_before_end_p (gsi))
+ split_block (bb, stmt);
+
+ /* Create a new block for the EH cleanup. */
+ basic_block bb_eh_cleanup = create_empty_bb (bb);
+ if (dom_info_available_p (CDI_DOMINATORS))
+ set_immediate_dominator (CDI_DOMINATORS, bb_eh_cleanup, bb);
+ if (current_loops)
+ add_bb_to_loop (bb_eh_cleanup, current_loops->tree_root);
+
+ /* Make the new block an EH cleanup for the call. */
+ eh_region new_r = gen_eh_region_cleanup (NULL);
+ eh_landing_pad lp = gen_eh_landing_pad (new_r);
+ tree label = gimple_block_label (bb_eh_cleanup);
+ lp->post_landing_pad = label;
+ EH_LANDING_PAD_NR (label) = lp->index;
+ add_stmt_to_eh_lp (stmt, lp->index);
+
+ /* Add the cleanup code to the EH cleanup block. */
+ gsi = gsi_after_labels (bb_eh_cleanup);
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
+
+ /* And then propagate the exception further. */
+ gresx *resx = gimple_build_resx (new_r->index);
+ gsi_insert_before (&gsi, resx, GSI_SAME_STMT);
+
+ /* Finally, wire the EH cleanup block into the CFG. */
+ make_eh_edges (stmt);
+ add_call_edges_for_seq (seq, single_pred_edge (bb_eh_cleanup)->count ());
+}
+
+/* Copy the attribute list at *ATTRS, minus any NAME attributes, leaving
+ shareable trailing nodes alone. */
+
+static inline void
+remove_named_attribute_unsharing (const char *name, tree *attrs)
+{
+ while (tree found = lookup_attribute (name, *attrs))
+ {
+ /* Copy nodes up to the next NAME attribute. */
+ while (*attrs != found)
+ {
+ *attrs = tree_cons (TREE_PURPOSE (*attrs),
+ TREE_VALUE (*attrs),
+ TREE_CHAIN (*attrs));
+ attrs = &TREE_CHAIN (*attrs);
+ }
+ /* Then drop it. */
+ gcc_checking_assert (*attrs == found);
+ *attrs = TREE_CHAIN (*attrs);
+ }
+}
+
+static int last_cgraph_order;
+
+static void
+ipa_strub_set_mode_for_new_functions ()
+{
+ if (symtab->order == last_cgraph_order)
+ return;
+
+ cgraph_node *node;
+
+ /* 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. */
+ for (int aliases = 0; aliases <= 1; aliases++)
+ FOR_EACH_FUNCTION (node)
+ {
+ if (!node->alias != !aliases)
+ continue;
+
+ /* Already done. */
+ if (node->order < last_cgraph_order)
+ continue;
+
+ set_strub_mode (node);
+ }
+
+ last_cgraph_order = symtab->order;
+}
+
+/* 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);
+ }
+
+ /* 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);
+
+#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;
+ ipa_strub_set_mode_for_new_functions ();
+
+ /* Verify before any inlining or other transformations. */
+ verify_strub ();
+
+ return 0;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub_mode (gcc::context *ctxt)
+{
+ return new pass_ipa_strub_mode (ctxt);
+}
+
+unsigned int
+pass_ipa_strub::execute (function *)
+{
+ cgraph_node *onode;
+
+ 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
+ || mode == STRUB_AT_CALLS_OPT)
+ {
+ /* 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. */
+ bool aliased_parms = (onode->alias && *pargs
+ && DECL_CONTEXT (*pargs) != onode->decl);
+
+ 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_qpwmt ());
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_ARG_TYPE (wmptr) = get_qpwmt ();
+ DECL_CONTEXT (wmptr) = onode->decl;
+ TREE_USED (wmptr) = 1;
+ DECL_CHAIN (wmptr) = *pargs;
+ *pargs = wmptr;
+
+ 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));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca)
+ {
+ 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);
+
+ if (!is_gimple_call (stmt))
+ continue;
+
+ gcall *call = as_a <gcall *> (stmt);
+
+ if (gimple_alloca_call_p (call))
+ {
+ /* Capture stack growth. */
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)
+ ->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ }
+ }
+
+ pop_cfun ();
+
+#if 0
+ compute_fn_summary (onode, true);
+#endif
+ }
+ }
+
+ 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 ();
+ }
+
+ enum strub_mode mode = get_strub_mode (onode);
+
+ if (mode != STRUB_INTERNAL)
+ 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 stack scrubbing",
+ onode->decl);
+ continue;
+ }
+
+ if (!onode->lowered)
+ {
+ inform (DECL_SOURCE_LOCATION (onode->decl),
+ "not splitting non-lowered function %qD for stack scrubbing",
+ 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 stack scrubbing",
+ onode->decl);
+ continue;
+ }
+#endif
+
+ bool is_stdarg = calls_builtin_va_start_p (onode);;
+ bool apply_args = calls_builtin_apply_args_p (onode);
+
+ vec<ipa_adjusted_param, va_gc> *nparms = NULL;
+ unsigned j = 0;
+ {
+ // The following loop copied from ipa-split.c:split_function.
+ for (tree parm = DECL_ARGUMENTS (onode->decl);
+ parm; parm = DECL_CHAIN (parm), j++)
+ {
+ ipa_adjusted_param adj = {};
+ adj.op = IPA_PARAM_OP_COPY;
+ adj.base_index = j;
+ adj.prev_clone_index = j;
+ vec_safe_push (nparms, adj);
+ }
+
+ if (apply_args)
+ {
+ ipa_adjusted_param aaadj = {};
+ aaadj.op = IPA_PARAM_OP_NEW;
+ aaadj.type = get_qpptr ();
+ vec_safe_push (nparms, aaadj);
+ }
+
+ if (is_stdarg)
+ {
+ ipa_adjusted_param vladj = {};
+ vladj.op = IPA_PARAM_OP_NEW;
+ vladj.type = get_qpvalst ();
+ vec_safe_push (nparms, vladj);
+ }
+
+ ipa_adjusted_param wmadj = {};
+ wmadj.op = IPA_PARAM_OP_NEW;
+ wmadj.type = get_qpwmt ();
+ vec_safe_push (nparms, wmadj);
+ }
+ ipa_param_adjustments adj (nparms, -1, false);
+
+ cgraph_node *nnode = onode->create_version_clone_with_body
+ (auto_vec<cgraph_edge *> (0),
+ NULL, &adj, NULL, NULL, "strub", NULL);
+
+ if (!nnode)
+ {
+ error_at (DECL_SOURCE_LOCATION (onode->decl),
+ "failed to split %qD for stack scrubbing",
+ onode->decl);
+ continue;
+ }
+
+ onode->split_part = true;
+ if (onode->calls_comdat_local)
+ nnode->add_to_same_comdat_group (onode);
+
+ gcc_checking_assert (!DECL_STRUCT_FUNCTION (nnode->decl)->stdarg);
+
+ set_strub_mode_to (onode, STRUB_WRAPPER);
+ set_strub_mode_to (nnode, STRUB_WRAPPED);
+
+ /* Decide which of the wrapped function's parms we want to turn into
+ references to the argument passed to the wrapper. In general, we want to
+ copy small arguments, and avoid copying large ones. Variable-sized array
+ lengths given by other arguments, as in 20020210-1.c, would lead to
+ problems if passed by value, after resetting the original function and
+ dropping the length computation; passing them by reference works.
+ DECL_BY_REFERENCE is *not* a substitute for this: it involves copying
+ anyway, but performed at the caller. */
+ indirect_parms_t indirect_nparms (3, false);
+ unsigned adjust_ftype = 0;
+ unsigned named_args = 0;
+ for (tree parm = DECL_ARGUMENTS (onode->decl),
+ nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (TREE_TYPE (nnode->decl));
+ parm;
+ named_args++,
+ parm = DECL_CHAIN (parm),
+ nparm = DECL_CHAIN (nparm),
+ nparmt = nparmt ? TREE_CHAIN (nparmt) : NULL_TREE)
+ if (!(0 /* DECL_BY_REFERENCE (narg) */
+ || is_gimple_reg_type (TREE_TYPE (nparm))
+ || VECTOR_TYPE_P (TREE_TYPE (nparm))
+ || TREE_CODE (TREE_TYPE (nparm)) == COMPLEX_TYPE
+ || (tree_fits_uhwi_p (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ && (tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ <= 4 * UNITS_PER_WORD))))
+ {
+ indirect_nparms.add (nparm);
+
+ /* ??? Is there any case in which it is not safe to suggest the parms
+ turned indirect don't alias anything else? They are distinct,
+ unaliased memory in the wrapper, and the wrapped can't possibly
+ take pointers into them because none of the pointers passed to the
+ wrapper can alias other incoming parameters passed by value, even
+ if with transparent reference, and the wrapper doesn't take any
+ extra parms that could point into wrapper's parms. So we can
+ probably drop the TREE_ADDRESSABLE and keep the true. */
+ tree ref_type = build_ref_type_for (nparm,
+ true
+ || !TREE_ADDRESSABLE (parm));
+
+ DECL_ARG_TYPE (nparm) = TREE_TYPE (nparm) = ref_type;
+ relayout_decl (nparm);
+ TREE_ADDRESSABLE (nparm) = 0;
+ DECL_BY_REFERENCE (nparm) = 0;
+#if FOR_GCC_11P
+ DECL_NOT_GIMPLE_REG_P (nparm) = 0;
+#else
+ DECL_GIMPLE_REG_P (nparm) = 1;
+#endif
+ /* ??? This avoids mismatches in debug info bind stmts in
+ e.g. a-chahan . */
+ DECL_ABSTRACT_ORIGIN (nparm) = NULL;
+
+ if (nparmt)
+ adjust_ftype++;
+ }
+
+ /* Also adjust the wrapped function type, if needed. */
+ if (adjust_ftype)
+ {
+ tree nftype = TREE_TYPE (nnode->decl);
+
+ /* We always add at least one argument at the end of the signature, when
+ cloning the function, so we don't expect to need to duplicate the
+ type here. */
+ gcc_checking_assert (TYPE_ARG_TYPES (nftype)
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+
+#if HAVE_ATTR_FNSPEC
+ /* Check that fnspec still works for the modified function signature,
+ and drop it otherwise. */
+ bool drop_fnspec = false;
+ tree fnspec = lookup_attribute ("fn spec", TYPE_ATTRIBUTES (nftype));
+ attr_fnspec spec = fnspec ? attr_fnspec (fnspec) : attr_fnspec ("");
+
+ unsigned retcopy;
+ if (!(fnspec && spec.returns_arg (&retcopy)))
+ retcopy = (unsigned) -1;
+
+ unsigned i = 0;
+#endif
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (nftype);
+ adjust_ftype > 0;
+#if HAVE_ATTR_FNSPEC
+ i++,
+#endif
+ nparm = DECL_CHAIN (nparm), nparmt = TREE_CHAIN (nparmt))
+ if (indirect_nparms.contains (nparm))
+ {
+ TREE_VALUE (nparmt) = TREE_TYPE (nparm);
+ adjust_ftype--;
+
+#if HAVE_ATTR_FNSPEC
+ if (fnspec && !drop_fnspec)
+ {
+ if (i == retcopy)
+ drop_fnspec = true;
+ else if (spec.arg_specified_p (i))
+ {
+ /* Properties that apply to pointers only must not be
+ present, because we don't make pointers further
+ indirect. */
+ gcc_checking_assert
+ (!spec.arg_max_access_size_given_by_arg_p (i, NULL));
+ gcc_checking_assert (!spec.arg_copied_to_arg_p (i, NULL));
+
+ /* Any claim of direct access only is invalidated by
+ adding an indirection level. */
+ if (spec.arg_direct_p (i))
+ drop_fnspec = true;
+
+ /* If there's a claim the argument is not read from, the
+ added indirection invalidates it: if the argument is
+ used at all, then the pointer will necessarily be
+ read. */
+ if (!spec.arg_maybe_read_p (i)
+ && spec.arg_used_p (i))
+ drop_fnspec = true;
+ }
+ }
+#endif
+ }
+
+#if HAVE_ATTR_FNSPEC
+ /* ??? Maybe we could adjust it instead. */
+ if (drop_fnspec)
+ remove_named_attribute_unsharing ("fn spec",
+ &TYPE_ATTRIBUTES (nftype));
+#endif
+
+ TREE_TYPE (nnode->decl) = nftype;
+ }
+
+#if ATTR_FNSPEC_DECONST_WATERMARK
+ {
+ int flags = flags_from_decl_or_type (nnode->decl);
+ tree fnspec = lookup_attribute ("fn spec", TREE_TYPE (nnode->decl));
+
+ if ((flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS)) || fnspec)
+ {
+ size_t xargs = 1 + int (is_stdarg) + int (apply_args);
+ 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! */
+ bool no_writes_p = true;
+ if (fnspec)
+ {
+ tree fnspecstr = TREE_VALUE (TREE_VALUE (fnspec));
+ curlen = TREE_STRING_LENGTH (fnspecstr);
+ memcpy (nspec, TREE_STRING_POINTER (fnspecstr), curlen);
+ if (!(flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS))
+ && curlen >= 2
+ && nspec[1] != 'c' && nspec[1] != 'C'
+ && nspec[1] != 'p' && nspec[1] != 'P')
+ no_writes_p = false;
+ }
+ if (!curlen)
+ {
+ nspec[curlen++] = '.';
+ nspec[curlen++] = ((flags & ECF_CONST)
+ ? 'c'
+ : (flags & ECF_PURE)
+ ? 'p'
+ : ' ');
+ }
+ while (curlen < tgtlen - 2 * xargs)
+ {
+ nspec[curlen++] = '.';
+ nspec[curlen++] = ' ';
+ }
+
+ /* These extra args are unlikely to be present in const or pure
+ functions. It's conceivable that a function that takes variable
+ arguments, or that passes its arguments on to another function,
+ could be const or pure, but it would not modify the arguments, and,
+ being pure or const, it couldn't possibly modify or even access
+ memory referenced by them. But it can read from these internal
+ data structures created by the wrapper, and from any
+ argument-passing memory referenced by them, so we denote the
+ possibility of reading from multiple levels of indirection, but
+ only of reading because const/pure. */
+ if (apply_args)
+ {
+ nspec[curlen++] = (no_writes_p ? 'r' : '.');
+ nspec[curlen++] = (no_writes_p ? 't' : ' ');
+ }
+ if (is_stdarg)
+ {
+ nspec[curlen++] = (no_writes_p ? 'r' : '.');
+ nspec[curlen++] = (no_writes_p ? 't' : ' ');
+ }
+
+ nspec[curlen++] = 'W';
+ nspec[curlen++] = 't';
+
+ /* The type has already been copied before adding parameters. */
+ gcc_checking_assert (TYPE_ARG_TYPES (TREE_TYPE (nnode->decl))
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+ TYPE_ATTRIBUTES (TREE_TYPE (nnode->decl))
+ = tree_cons (get_identifier ("fn spec"),
+ build_tree_list (NULL_TREE,
+ build_string (tgtlen, nspec)),
+ TYPE_ATTRIBUTES (TREE_TYPE (nnode->decl)));
+ }
+ }
+#endif
+
+ {
+ tree decl = onode->decl;
+ cgraph_node *target = nnode;
+
+ { // copied from create_wrapper
+
+ /* Preserve DECL_RESULT so we get right by reference flag. */
+ tree decl_result = DECL_RESULT (decl);
+
+ /* Remove the function's body but keep arguments to be reused
+ for thunk. */
+ onode->release_body (true);
+ onode->reset ();
+
+ DECL_UNINLINABLE (decl) = false;
+ DECL_RESULT (decl) = decl_result;
+ DECL_INITIAL (decl) = NULL;
+ allocate_struct_function (decl, false);
+ set_cfun (NULL);
+
+ /* Turn alias into thunk and expand it into GIMPLE representation. */
+ onode->definition = true;
+
+#if FOR_GCC_11P
+ thunk_info::get_create (onode);
+ onode->thunk = true;
+#else
+ memset (&onode->thunk, 0, sizeof (cgraph_thunk_info));
+ onode->thunk.thunk_p = true;
+ onode->thunk.alias = target->decl;
+#endif
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (target, NULL, onode->count);
+#endif
+ onode->callees->can_throw_external = !TREE_NOTHROW (target->decl);
+
+ tree arguments = DECL_ARGUMENTS (decl);
+
+ while (arguments)
+ {
+ TREE_ADDRESSABLE (arguments) = false;
+ arguments = TREE_CHAIN (arguments);
+ }
+
+ {
+ tree alias = onode->callees->callee->decl;
+ tree thunk_fndecl = decl;
+ tree a;
+
+ int nxargs = 1 + is_stdarg + apply_args;
+
+ { // Simplified from expand_thunk.
+ tree restype;
+ basic_block bb, then_bb, else_bb, return_bb;
+ gimple_stmt_iterator bsi;
+ int nargs = 0;
+ tree arg;
+ int i;
+ tree resdecl;
+ tree restmp = NULL;
+
+ gcall *call;
+ greturn *ret;
+ bool alias_is_noreturn = TREE_THIS_VOLATILE (alias);
+
+ a = DECL_ARGUMENTS (thunk_fndecl);
+
+ current_function_decl = thunk_fndecl;
+
+#if FOR_GCC_11P
+ /* Ensure thunks are emitted in their correct sections. */
+ resolve_unique_section (thunk_fndecl, 0,
+ flag_function_sections);
+#endif
+
+ bitmap_obstack_initialize (NULL);
+
+ /* Build the return declaration for the function. */
+ restype = TREE_TYPE (TREE_TYPE (thunk_fndecl));
+ if (DECL_RESULT (thunk_fndecl) == NULL_TREE)
+ {
+ resdecl = build_decl (input_location, RESULT_DECL, 0, restype);
+ DECL_ARTIFICIAL (resdecl) = 1;
+ DECL_IGNORED_P (resdecl) = 1;
+ DECL_CONTEXT (resdecl) = thunk_fndecl;
+ DECL_RESULT (thunk_fndecl) = resdecl;
+ }
+ else
+ resdecl = DECL_RESULT (thunk_fndecl);
+
+ profile_count cfg_count = onode->count;
+ if (!cfg_count.initialized_p ())
+ cfg_count = profile_count::from_gcov_type (BB_FREQ_MAX).guessed_local ();
+
+ bb = then_bb = else_bb = return_bb
+ = init_lowered_empty_function (thunk_fndecl, true, cfg_count);
+
+ bsi = gsi_start_bb (bb);
+
+ /* Build call to the function being thunked. */
+ if (!VOID_TYPE_P (restype)
+ && (!alias_is_noreturn
+ || TREE_ADDRESSABLE (restype)
+ || TREE_CODE (TYPE_SIZE_UNIT (restype)) != INTEGER_CST))
+ {
+ if (DECL_BY_REFERENCE (resdecl))
+ {
+ restmp = gimple_fold_indirect_ref (resdecl);
+ if (!restmp)
+ restmp = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (resdecl)),
+ resdecl,
+ build_int_cst (TREE_TYPE (resdecl), 0));
+ }
+ else if (!is_gimple_reg_type (restype))
+ {
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl)))
+ {
+ restmp = resdecl;
+
+ if (VAR_P (restmp))
+ {
+ add_local_decl (cfun, restmp);
+ BLOCK_VARS (DECL_INITIAL (current_function_decl))
+ = restmp;
+ }
+ }
+ else
+ restmp = create_tmp_var (restype, "retval");
+ }
+ else
+ restmp = create_tmp_reg (restype, "retval");
+ }
+
+ for (arg = a; arg; arg = DECL_CHAIN (arg))
+ nargs++;
+ auto_vec<tree> vargs (nargs + nxargs);
+ i = 0;
+ arg = a;
+
+ if (nargs)
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl);
+ i < nargs;
+ i++, arg = DECL_CHAIN (arg), nparm = DECL_CHAIN (nparm))
+ {
+ tree save_arg = arg;
+ tree tmp = arg;
+
+ /* Arrange to pass indirectly the parms, if we decided to do
+ so, and revert its type in the wrapper. */
+ if (indirect_nparms.contains (nparm))
+ {
+ tree ref_type = TREE_TYPE (nparm);
+ TREE_ADDRESSABLE (arg) = true;
+ tree addr = build1 (ADDR_EXPR, ref_type, arg);
+ tmp = arg = addr;
+ }
+#if ! FOR_GCC_11P
+ else if (VECTOR_TYPE_P (TREE_TYPE (arg))
+ || TREE_CODE (TREE_TYPE (arg)) == COMPLEX_TYPE)
+ DECL_GIMPLE_REG_P (arg) = 1;
+#else
+ else
+ DECL_NOT_GIMPLE_REG_P (arg) = 0;
+#endif
+
+ /* Convert the argument back to the type used by the calling
+ conventions, e.g. a non-prototyped float type is passed as
+ double, as in 930603-1.c, and needs to be converted back to
+ double to be passed on unchanged to the wrapped
+ function. */
+ if (TREE_TYPE (nparm) != DECL_ARG_TYPE (nparm))
+ arg = fold_convert (DECL_ARG_TYPE (nparm), arg);
+
+ if (!is_gimple_val (arg))
+ {
+ tmp = create_tmp_reg (TYPE_MAIN_VARIANT
+ (TREE_TYPE (arg)), "arg");
+ gimple *stmt = gimple_build_assign (tmp, arg);
+ gsi_insert_after (&bsi, stmt, GSI_NEW_STMT);
+ }
+ vargs.quick_push (tmp);
+ arg = save_arg;
+ }
+ /* These strub arguments are adjusted later. */
+ if (apply_args)
+ vargs.quick_push (null_pointer_node);
+ if (is_stdarg)
+ vargs.quick_push (null_pointer_node);
+ vargs.quick_push (null_pointer_node);
+ call = gimple_build_call_vec (build_fold_addr_expr_loc (0, alias),
+ vargs);
+ onode->callees->call_stmt = call;
+ // gimple_call_set_from_thunk (call, true);
+ if (DECL_STATIC_CHAIN (alias))
+ {
+ tree p = DECL_STRUCT_FUNCTION (alias)->static_chain_decl;
+ tree type = TREE_TYPE (p);
+ tree decl = build_decl (DECL_SOURCE_LOCATION (thunk_fndecl),
+ PARM_DECL, create_tmp_var_name ("CHAIN"),
+ type);
+ DECL_ARTIFICIAL (decl) = 1;
+ DECL_IGNORED_P (decl) = 1;
+ TREE_USED (decl) = 1;
+ DECL_CONTEXT (decl) = thunk_fndecl;
+ DECL_ARG_TYPE (decl) = type;
+ TREE_READONLY (decl) = 1;
+
+ struct function *sf = DECL_STRUCT_FUNCTION (thunk_fndecl);
+ sf->static_chain_decl = decl;
+
+ gimple_call_set_chain (call, decl);
+ }
+
+ /* Return slot optimization is always possible and in fact required to
+ return values with DECL_BY_REFERENCE. */
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl))
+ && (!is_gimple_reg_type (TREE_TYPE (resdecl))
+ || DECL_BY_REFERENCE (resdecl)))
+ gimple_call_set_return_slot_opt (call, true);
+
+ if (restmp)
+ {
+ gimple_call_set_lhs (call, restmp);
+ gcc_assert (useless_type_conversion_p (TREE_TYPE (restmp),
+ TREE_TYPE (TREE_TYPE (alias))));
+ }
+ gsi_insert_after (&bsi, call, GSI_NEW_STMT);
+ if (!alias_is_noreturn)
+ {
+ /* Build return value. */
+ if (!DECL_BY_REFERENCE (resdecl))
+ ret = gimple_build_return (restmp);
+ else
+ ret = gimple_build_return (resdecl);
+
+ gsi_insert_after (&bsi, ret, GSI_NEW_STMT);
+ }
+ else
+ {
+ remove_edge (single_succ_edge (bb));
+ }
+
+ cfun->gimple_df->in_ssa_p = true;
+ update_max_bb_count ();
+ profile_status_for_fn (cfun)
+ = cfg_count.initialized_p () && cfg_count.ipa_p ()
+ ? PROFILE_READ : PROFILE_GUESSED;
+#if FOR_GCC_11P
+ /* FIXME: C++ FE should stop setting TREE_ASM_WRITTEN on thunks. */
+ // TREE_ASM_WRITTEN (thunk_fndecl) = false;
+#endif
+ delete_unreachable_blocks ();
+ update_ssa (TODO_update_ssa);
+ checking_verify_flow_info ();
+ free_dominance_info (CDI_DOMINATORS);
+
+ /* Since we want to emit the thunk, we explicitly mark its name as
+ referenced. */
+#if FOR_GCC_11P
+ onode->thunk = false;
+#else
+ onode->thunk.thunk_p = false;
+#endif
+ onode->lowered = true;
+ bitmap_obstack_release (NULL);
+ }
+ current_function_decl = NULL;
+ set_cfun (NULL);
+ }
+
+#if FOR_GCC_11P
+ thunk_info::remove (onode);
+#endif
+
+ // some more of create_wrapper at the end of the next block.
+ }
+ }
+
+ {
+ tree aaptr = NULL_TREE;
+ tree vaptr = NULL_TREE;
+ tree wmptr = NULL_TREE;
+ for (tree arg = DECL_ARGUMENTS (nnode->decl); arg; arg = DECL_CHAIN (arg))
+ {
+ aaptr = vaptr;
+ vaptr = wmptr;
+ wmptr = arg;
+ }
+
+ if (!apply_args)
+ aaptr = NULL_TREE;
+ /* The trailing args are [apply_args], [va_list_ptr], and
+ watermark. If we don't have a va_list_ptr, the penultimate
+ argument is apply_args.
+ */
+ else if (!is_stdarg)
+ aaptr = vaptr;
+
+ if (!is_stdarg)
+ vaptr = NULL_TREE;
+
+ DECL_NAME (wmptr) = get_watermark_ptr ();
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_IGNORED_P (wmptr) = 1;
+ TREE_USED (wmptr) = 1;
+
+ if (is_stdarg)
+ {
+ DECL_NAME (vaptr) = get_va_list_ptr ();
+ DECL_ARTIFICIAL (vaptr) = 1;
+ DECL_IGNORED_P (vaptr) = 1;
+ TREE_USED (vaptr) = 1;
+ }
+
+ if (apply_args)
+ {
+ DECL_NAME (aaptr) = get_apply_args_ptr ();
+ DECL_ARTIFICIAL (aaptr) = 1;
+ DECL_IGNORED_P (aaptr) = 1;
+ TREE_USED (aaptr) = 1;
+ }
+
+ push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ bool any_indirect = !indirect_nparms.is_empty ();
+
+ if (any_indirect)
+ {
+ 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);
+
+ 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
+ || is_stdarg || apply_args)
+ for (cgraph_edge *e = nnode->callees, *enext; e; e = enext)
+ {
+ gcall *call = e->call_stmt;
+ gimple_stmt_iterator gsi = gsi_for_stmt (call);
+ tree fndecl = e->callee->decl;
+
+ enext = e->next_callee;
+
+ if (gimple_alloca_call_p (call))
+ {
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ else if (fndecl && is_stdarg
+ && fndecl_built_in_p (fndecl, BUILT_IN_VA_START))
+ {
+ if (builtin_decl_explicit (BUILT_IN_VA_START) != fndecl)
+ sorry_at (gimple_location (call),
+ "nonstandard stdarg conventions");
+ tree bvacopy = builtin_decl_explicit (BUILT_IN_VA_COPY);
+ gimple_call_set_fndecl (call, bvacopy);
+ tree arg = vaptr;
+ /* The va_copy source must be dereferenced, unless it's an array
+ type, that would have decayed to a pointer. */
+ if (TREE_CODE (TREE_TYPE (TREE_TYPE (vaptr))) != ARRAY_TYPE)
+ {
+ arg = gimple_fold_indirect_ref (vaptr);
+ if (!arg)
+ arg = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (vaptr)),
+ vaptr,
+ build_int_cst (TREE_TYPE (vaptr), 0));
+ }
+ gimple_call_set_arg (call, 1, arg);
+ update_stmt (call);
+ e->redirect_callee (cgraph_node::get_create (bvacopy));
+ }
+ else if (fndecl && apply_args
+ && fndecl_built_in_p (fndecl, BUILT_IN_APPLY_ARGS))
+ {
+ tree lhs = gimple_call_lhs (call);
+ gassign *assign = gimple_build_assign (lhs, aaptr);
+ gsi_replace (&gsi, assign, true);
+ cgraph_edge::remove (e);
+ }
+ }
+
+ { // a little more copied from create_wrapper
+
+ /* Inline summary set-up. */
+ nnode->analyze ();
+ // inline_analyze_function (nnode);
+ }
+
+ pop_cfun ();
+ }
+
+ {
+ 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));
+
+ 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
+ onode->create_edge (cgraph_node::get_create (enter),
+ stptr, gsi_bb (gsi)->count, false);
+#endif
+
+ int nargs = gimple_call_num_args (wrcall);
+
+ gimple_seq seq = NULL;
+
+ if (apply_args)
+ {
+ tree aalst = create_tmp_var (ptr_type_node, ".strub.appargs");
+ tree bappargs = builtin_decl_explicit (BUILT_IN_APPLY_ARGS);
+ gcall *appargs = gimple_build_call (bappargs, 0);
+ gimple_call_set_lhs (appargs, aalst);
+ gsi_insert_before (&gsi, appargs, GSI_SAME_STMT);
+ gimple_call_set_arg (wrcall, nargs - 2 - is_stdarg, aalst);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bappargs),
+ appargs, gsi_bb (gsi)->count, false);
+#endif
+ }
+
+ if (is_stdarg)
+ {
+ tree valst = create_tmp_var (va_list_type_node, ".strub.va_list");
+ TREE_ADDRESSABLE (valst) = true;
+ tree vaptr = build1 (ADDR_EXPR,
+ build_pointer_type (va_list_type_node),
+ valst);
+ gimple_call_set_arg (wrcall, nargs - 2, unshare_expr (vaptr));
+
+ tree bvastart = builtin_decl_explicit (BUILT_IN_VA_START);
+ gcall *vastart = gimple_build_call (bvastart, 2,
+ unshare_expr (vaptr),
+ integer_zero_node);
+ gsi_insert_before (&gsi, vastart, GSI_SAME_STMT);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bvastart),
+ vastart, gsi_bb (gsi)->count, false);
+#endif
+
+ tree bvaend = builtin_decl_explicit (BUILT_IN_VA_END);
+ gcall *vaend = gimple_build_call (bvaend, 1, unshare_expr (vaptr));
+ gimple_seq_add_stmt (&seq, vaend);
+ }
+
+ gimple_call_set_arg (wrcall, nargs - 1, unshare_expr (swmp));
+ // gimple_call_set_tail (wrcall, false);
+ update_stmt (wrcall);
+
+ {
+#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);
+
+ /* For nnode, we don't rebuild edges because we wish to retain
+ any redirections copied to it from earlier passes, so we add
+ call graph edges explicitly there, but for onode, we create a
+ fresh function, so we may as well just issue the calls and
+ then rebuild all cgraph edges. */
+ // cgraph_edge::rebuild_edges ();
+ onode->analyze ();
+ // inline_analyze_function (onode);
+
+ 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;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub (gcc::context *ctxt)
+{
+ return new pass_ipa_strub (ctxt);
+}
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
new file mode 100644
index 00000000000..cadbca5002a
--- /dev/null
+++ b/gcc/ipa-strub.h
@@ -0,0 +1,25 @@
+/* strub (stack scrubbing) infrastructure.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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);
diff --git a/gcc/passes.def b/gcc/passes.def
index f5d88a61b0e..677c000e80b 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -53,6 +53,7 @@ along with GCC; see the file COPYING3. If not see
INSERT_PASSES_AFTER (all_small_ipa_passes)
NEXT_PASS (pass_ipa_free_lang_data);
NEXT_PASS (pass_ipa_function_and_variable_visibility);
+ NEXT_PASS (pass_ipa_strub_mode);
NEXT_PASS (pass_build_ssa_passes);
PUSH_INSERT_PASSES_WITHIN (pass_build_ssa_passes)
NEXT_PASS (pass_fixup_cfg);
@@ -111,6 +112,7 @@ along with GCC; see the file COPYING3. If not see
POP_INSERT_PASSES ()
NEXT_PASS (pass_ipa_remove_symbols);
+ NEXT_PASS (pass_ipa_strub);
NEXT_PASS (pass_ipa_oacc);
PUSH_INSERT_PASSES_WITHIN (pass_ipa_oacc)
NEXT_PASS (pass_ipa_pta);
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" } } */
diff --git a/gcc/testsuite/g++.dg/wrappers/strub1.C b/gcc/testsuite/g++.dg/wrappers/strub1.C
new file mode 100644
index 00000000000..a474a929649
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub1.C
@@ -0,0 +1,18 @@
+// { dg-do run }
+
+// Check that we don't get extra copies.
+
+struct T {
+ T &self;
+ void check () const { if (&self != this) __builtin_abort (); }
+ T() : self (*this) { check (); }
+ T(const T& ck) : self (*this) { ck.check (); check (); }
+ ~T() { check (); }
+};
+
+T foo (T) { return T(); }
+T bar (T p) { return foo (p); }
+
+int main () {
+ bar (T{});
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub2.C b/gcc/testsuite/g++.dg/wrappers/strub2.C
new file mode 100644
index 00000000000..25a62166448
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub2.C
@@ -0,0 +1,22 @@
+// { dg-do run }
+
+// This doesn't really test anything yet. We should mark the
+// variables as requiring strubbing, and somehow check that the
+// wrapped functions take the parameter by reference.
+
+struct T {
+ char d[32 * sizeof(void*)];
+};
+
+T foo (T q) { asm ("" : : "m"(q)); return q; }
+T bar (T p) { return foo (p); }
+
+T tmp;
+T tmp2;
+
+int main () {
+ __builtin_memset (&tmp, 0x55, sizeof (tmp));
+ tmp2 = bar (tmp);
+ if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
+ __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub3.C b/gcc/testsuite/g++.dg/wrappers/strub3.C
new file mode 100644
index 00000000000..e1b51cd0399
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub3.C
@@ -0,0 +1,22 @@
+// { dg-do run }
+
+// This doesn't really test anything yet. We should mark the
+// variables as requiring strubbing, and somehow check that the
+// wrapped functions take the parameter by reference.
+
+struct T {
+ char d[32 * sizeof(void*)];
+};
+
+static T foo (T q) { asm ("" : : "m"(q)); return q; }
+static T bar (T p) { return foo (p); }
+
+T tmp;
+T tmp2;
+
+int main () {
+ __builtin_memset (&tmp, 0x55, sizeof (tmp));
+ tmp2 = bar (tmp);
+ if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
+ __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub4.C b/gcc/testsuite/g++.dg/wrappers/strub4.C
new file mode 100644
index 00000000000..d021fca88e4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub4.C
@@ -0,0 +1,18 @@
+// { dg-do run }
+
+namespace
+{
+ class foo
+ {
+ public:
+ foo();
+ };
+
+ foo::foo() {}
+
+ foo bar;
+}
+
+int main()
+{
+}
diff --git a/gcc/tree-cfg.c b/gcc/tree-cfg.c
index 38269a27b79..b5e0af8b3a1 100644
--- a/gcc/tree-cfg.c
+++ b/gcc/tree-cfg.c
@@ -5617,6 +5617,7 @@ gimple_verify_flow_info (void)
{
gimple *stmt = gsi_stmt (gsi);
+ /* Do NOT disregard debug stmts after found_ctrl_stmt. */
if (found_ctrl_stmt)
{
error ("control flow in the middle of basic block %d",
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index aa9757a2fe9..0210f84bc0c 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -494,8 +494,9 @@ extern gimple_opt_pass *make_pass_adjust_alignment (gcc::context *ctxt);
/* IPA Passes */
extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt);
-extern simple_ipa_opt_pass
- *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub_mode (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_tree_profile (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_auto_profile (gcc::context *ctxt);
diff --git a/libgcc/Makefile.in b/libgcc/Makefile.in
index 2c8be561eb5..6b7b12493f5 100644
--- a/libgcc/Makefile.in
+++ b/libgcc/Makefile.in
@@ -428,7 +428,7 @@ ifneq ($(enable_shared),yes)
iterator = $(patsubst %,$(srcdir)/static-object.mk,$(iter-items))
endif
-LIB2ADD += enable-execute-stack.c
+LIB2ADD += enable-execute-stack.c $(srcdir)/strub.c
# While emutls.c has nothing to do with EH, it is in LIB2ADDEH*
# instead of LIB2ADD because that's the way to be sure on some targets
diff --git a/libgcc/libgcc2.h b/libgcc/libgcc2.h
index 1819ff3ac3d..857091e65c8 100644
--- a/libgcc/libgcc2.h
+++ b/libgcc/libgcc2.h
@@ -532,6 +532,10 @@ extern int __parityDI2 (UDWtype);
extern void __enable_execute_stack (void *);
+extern void __strub_enter (void **);
+extern void __strub_update (void**);
+extern void __strub_leave (void **);
+
#ifndef HIDE_EXPORTS
#pragma GCC visibility pop
#endif
diff --git a/libgcc/strub.c b/libgcc/strub.c
new file mode 100644
index 00000000000..fd6e27556e4
--- /dev/null
+++ b/libgcc/strub.c
@@ -0,0 +1,112 @@
+/* Stack scrubbing infrastructure
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+Under Section 7 of GPL version 3, you are granted additional
+permissions described in the GCC Runtime Library Exception, version
+3.1, as published by the Free Software Foundation.
+
+You should have received a copy of the GNU General Public License and
+a copy of the GCC Runtime Library Exception along with this program;
+see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
+<http://www.gnu.org/licenses/>. */
+
+#include "tconfig.h"
+#include "tsystem.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "libgcc_tm.h"
+#include "libgcc2.h"
+
+#ifndef STACK_GROWS_DOWNWARD
+# define TOPS >
+#else
+# define TOPS <
+#endif
+
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+
+/* Enter a stack scrubbing context, initializing the watermark to the caller's
+ stack address. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_enter (void **watermark)
+{
+ *watermark = __builtin_frame_address (0);
+}
+
+/* Update the watermark within a stack scrubbing context with the current stack
+ pointer. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_update (void **watermark)
+{
+ void *sp = __builtin_frame_address (0);
+
+ if (sp TOPS *watermark)
+ *watermark = sp;
+}
+
+#ifndef TARGET_STRUB_USE_DYNAMIC_ARRAY
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+#endif
+
+#ifndef TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+# ifdef TARGET_STRUB_MAY_USE_MEMSET
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+# else
+# define TARGET_STRUB_MAY_USE_MEMSET 1
+# endif
+#endif
+
+/* Leave a stack scrubbing context, restoring and updating SAVED, and
+ clearing the stack between top and watermark. */
+void ATTRIBUTE_STRUB_CALLABLE
+#if ! TARGET_STRUB_MAY_USE_MEMSET
+__attribute__ ((__optimize__ ("-fno-tree-loop-distribute-patterns")))
+#endif
+__strub_leave (void **mark)
+{
+ void *sp = __builtin_stack_address ();
+
+ void **base, **end;
+#ifndef STACK_GROWS_DOWNWARD
+ base = sp;
+ end = *mark;
+#else
+ base = *mark;
+ end = sp;
+#endif
+
+ ptrdiff_t len = end - base;
+ if (len <= 0)
+ return;
+
+#if ! TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+ /* Allocate a dynamically-sized array covering the desired range, so that we
+ can safely call memset on it. */
+ void *ptr[len];
+ base = &ptr[0];
+ end = &ptr[len];
+#else
+ void **ptr = end;
+#endif /* TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY */
+
+ /* ldist turns this into a memset. Without the dynamic array above, that call
+ is likely unsafe: possibly tail-called, and likely scribbling over its own
+ stack frame. */
+ while (base < end)
+ *base++ = 0;
+
+ asm ("" : : "m" (ptr));
+}
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/strub)] strub with function and variable attributes
@ 2021-07-28 9:35 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2021-07-28 9:35 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:bd8bcabe3d7959425e36834c0d5666b871648678
commit bd8bcabe3d7959425e36834c0d5666b871648678
Author: Alexandre Oliva <oliva@adacore.com>
Date: Sat Jul 24 23:05:42 2021 -0300
strub with function and variable attributes
still missing verification of calls, documentation, tests,
builtin expansion
Diff:
---
gcc/Makefile.in | 1 +
gcc/builtins.c | 163 ++
gcc/builtins.def | 4 +
gcc/c-family/c-attribs.c | 53 +
gcc/common.opt | 24 +
gcc/doc/extend.texi | 72 +
gcc/doc/invoke.texi | 41 +
gcc/ipa-inline.c | 6 +
gcc/ipa-strub.c | 2644 ++++++++++++++++++++++++++++++++
gcc/ipa-strub.h | 25 +
gcc/passes.def | 2 +
gcc/testsuite/g++.dg/wrappers/strub1.C | 18 +
gcc/testsuite/g++.dg/wrappers/strub2.C | 22 +
gcc/testsuite/g++.dg/wrappers/strub3.C | 22 +
gcc/testsuite/g++.dg/wrappers/strub4.C | 18 +
gcc/tree-cfg.c | 1 +
gcc/tree-pass.h | 5 +-
libgcc/Makefile.in | 2 +-
libgcc/libgcc2.h | 4 +
libgcc/strub.c | 112 ++
20 files changed, 3236 insertions(+), 3 deletions(-)
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 1666ef84d6a..163fd1a856c 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1468,6 +1468,7 @@ OBJS = \
ipa-reference.o \
ipa-ref.o \
ipa-utils.o \
+ ipa-strub.o \
ipa.o \
ira.o \
ira-build.o \
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 170d776c410..73d80482c32 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -153,6 +153,7 @@ static rtx expand_builtin_strnlen (tree, rtx, machine_mode);
static rtx expand_builtin_alloca (tree);
static rtx expand_builtin_unop (machine_mode, tree, rtx, rtx, optab);
static rtx expand_builtin_frame_address (tree, tree);
+static rtx expand_builtin_stack_address ();
static tree stabilize_va_list_loc (location_t, tree, int);
static rtx expand_builtin_expect (tree, rtx);
static rtx expand_builtin_expect_with_probability (tree, rtx);
@@ -7893,6 +7894,147 @@ expand_builtin_frame_address (tree fndecl, tree exp)
}
}
+#ifndef STACK_GROWS_DOWNWARD
+# define STACK_TOPS GT
+#else
+# define STACK_TOPS LT
+#endif
+
+#ifdef POINTERS_EXTEND_UNSIGNED
+# define STACK_UNSIGNED POINTERS_EXTEND_UNSIGNED
+#else
+# define STACK_UNSIGNED true
+#endif
+
+/* Expand a call to builtin function __builtin_stack_address. */
+
+static rtx
+expand_builtin_stack_address ()
+{
+ return convert_to_mode (ptr_mode, copy_to_reg (stack_pointer_rtx),
+ STACK_UNSIGNED);
+}
+
+/* Expand a call to builtin function __builtin_strub_enter. */
+
+static rtx
+expand_builtin_strub_enter (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (!optimize || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ emit_move_insn (wmark, stktop);
+
+ return const0_rtx;
+}
+
+/* Expand a call to builtin function __builtin_strub_update. */
+
+static rtx
+expand_builtin_strub_update (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 2 || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ rtx wmarkr = force_reg (ptr_mode, wmark);
+
+ rtx_code_label *lab = gen_label_rtx ();
+ do_compare_rtx_and_jump (stktop, wmarkr, STACK_TOPS, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, lab, NULL,
+ profile_probability::very_likely ());
+ emit_move_insn (wmark, stktop);
+ emit_label (lab);
+
+ return const0_rtx;
+}
+
+
+/* Expand a call to builtin function __builtin_strub_leave. */
+
+static rtx
+expand_builtin_strub_leave (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 2 || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ rtx wmarkr = force_reg (ptr_mode, wmark);
+
+#ifndef STACK_GROWS_DOWNWARD
+ rtx base = stktop;
+ rtx end = wmarkr;
+#else
+ rtx base = wmarkr;
+ rtx end = stktop;
+#endif
+
+ rtx_code_label *done = gen_label_rtx ();
+ do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, done, NULL,
+ profile_probability::very_likely ());
+
+ if (optimize < 3 || !flag_inline_functions)
+ expand_call (exp, NULL_RTX, true);
+ else
+ {
+ /* Ok, now we've determined we want to copy the block, so convert the
+ addresses to Pmode, as needed to dereference them to access ptr_mode
+ memory locations, so that we don't have to convert anything within the
+ loop. */
+ base = memory_address (ptr_mode, base);
+ end = memory_address (ptr_mode, end);
+
+ rtx zero = force_operand (const0_rtx, NULL_RTX);
+ int ulen = GET_MODE_SIZE (ptr_mode);
+ rtx incr = plus_constant (Pmode, base, ulen);
+ rtx dstm = gen_rtx_MEM (ptr_mode, base);
+
+ rtx_code_label *loop = gen_label_rtx ();
+ emit_label (loop);
+ emit_move_insn (dstm, zero);
+ emit_move_insn (base, force_operand (incr, NULL_RTX));
+ do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
+ Pmode, NULL_RTX, NULL, loop,
+ profile_probability::very_likely ());
+ }
+
+ emit_label (done);
+
+ return const0_rtx;
+}
+
/* Expand EXP, a call to the alloca builtin. Return NULL_RTX if we
failed and the caller should emit a normal call. */
@@ -10151,6 +10293,27 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
case BUILT_IN_RETURN_ADDRESS:
return expand_builtin_frame_address (fndecl, exp);
+ case BUILT_IN_STACK_ADDRESS:
+ return expand_builtin_stack_address ();
+
+ case BUILT_IN___STRUB_ENTER:
+ target = expand_builtin_strub_enter (exp);
+ if (target)
+ return target;
+ break;
+
+ case BUILT_IN___STRUB_UPDATE:
+ target = expand_builtin_strub_update (exp);
+ if (target)
+ return target;
+ break;
+
+ case BUILT_IN___STRUB_LEAVE:
+ target = expand_builtin_strub_leave (exp);
+ if (target)
+ return target;
+ break;
+
/* Returns the address of the area where the structure is returned.
0 otherwise. */
case BUILT_IN_AGGREGATE_INCOMING_ADDRESS:
diff --git a/gcc/builtins.def b/gcc/builtins.def
index ec556df4f66..4545033e664 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -878,6 +878,10 @@ DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSL, "ffsl", BT_FN_INT_LONG, ATTR_CONST_NOTHRO
DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSLL, "ffsll", BT_FN_INT_LONGLONG, ATTR_CONST_NOTHROW_LEAF_LIST)
DEF_EXT_LIB_BUILTIN (BUILT_IN_FORK, "fork", BT_FN_PID, ATTR_NOTHROW_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FRAME_ADDRESS, "frame_address", BT_FN_PTR_UINT, ATTR_NULL)
+DEF_GCC_BUILTIN (BUILT_IN_STACK_ADDRESS, "stack_address", BT_FN_PTR, ATTR_NULL)
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_ENTER, "__builtin___strub_enter")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_UPDATE, "__builtin___strub_update")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_LEAVE, "__builtin___strub_leave")
/* [trans-mem]: Adjust BUILT_IN_TM_FREE if BUILT_IN_FREE is changed. */
DEF_LIB_BUILTIN (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_LEAF_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FROB_RETURN_ADDR, "frob_return_addr", BT_FN_PTR_PTR, ATTR_NULL)
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index e60fb31d8c8..89ef83f6fac 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -69,6 +69,7 @@ static tree handle_asan_odr_indicator_attribute (tree *, tree, tree, int,
static tree handle_stack_protect_attribute (tree *, tree, tree, int, bool *);
static tree handle_no_stack_protector_function_attribute (tree *, tree,
tree, int, bool *);
+static tree handle_strub_attribute (tree *, tree, tree, int, bool *);
static tree handle_noinline_attribute (tree *, tree, tree, int, bool *);
static tree handle_noclone_attribute (tree *, tree, tree, int, bool *);
static tree handle_nocf_check_attribute (tree *, tree, tree, int, bool *);
@@ -306,6 +307,8 @@ const struct attribute_spec c_common_attribute_table[] =
{ "no_stack_protector", 0, 0, true, false, false, false,
handle_no_stack_protector_function_attribute,
attr_stack_protect_exclusions },
+ { "strub", 0, 1, false, true, false, true,
+ handle_strub_attribute, NULL },
{ "noinline", 0, 0, true, false, false, false,
handle_noinline_attribute,
attr_noinline_exclusions },
@@ -1290,6 +1293,56 @@ handle_noipa_attribute (tree *node, tree name, tree, int, bool *no_add_attrs)
return NULL_TREE;
}
+/* Handle a "strub" attribute; arguments as in
+ struct attribute_spec.handler. */
+
+static tree
+handle_strub_attribute (tree *node, tree name,
+ tree args,
+ int ARG_UNUSED (flags), bool *no_add_attrs)
+{
+ bool enable = true;
+
+ if (args
+ && (TREE_CODE (*node) == FUNCTION_TYPE
+ || TREE_CODE (*node) == METHOD_TYPE))
+ {
+ /* 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)
+ {
+ warning (OPT_Wattributes,
+ "%qE attribute ignored because of argument %qE",
+ name, TREE_VALUE (args));
+ *no_add_attrs = true;
+ enable = false;
+ }
+ /* 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));
+
+ /* 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;
+
+ return NULL_TREE;
+}
+
/* Handle a "noinline" attribute; arguments as in
struct attribute_spec.handler. */
diff --git a/gcc/common.opt b/gcc/common.opt
index d9da1131eda..c0408cb0f4c 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,6 +2687,30 @@ 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=all
+Common RejectNegative Var(flag_strub, 3)
+Enable stack scrubbing for all viable functions.
+
+fstrub=at-calls
+Common RejectNegative Var(flag_strub, 1)
+Enable at-calls stack scrubbing for all viable functions.
+
+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 b83cd4919bb..020bb1aa808 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -77,6 +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.
* 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.
@@ -8652,6 +8653,40 @@ pid_t wait (wait_status_ptr_t p)
@}
@end smallexample
+@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 numeric
+argument.
+
+A function associated with at-calls strub mode (@code{strub(1)})
+undergoes interface changes, and callers automatically gain code to
+scrub the stack space it uses. If the operational numeric argument is
+omitted from the @code{strub} attribute in a function type, the at-calls
+mode is selected.
+
+A function with internal strub mode @code{strub(2)} retains an
+unmodified interface, but it may be turned into a wrapper that calls the
+wrapped body, with a custom interface, and then scrubs the stack space
+used by it.
+
+A variable whose type carries the strub attribute causes functions that
+use it to have strub enabled.
+
+A function associated with the disabled strub mode @code{strub(0)} will
+not have its stack scrubbed. A function that has strub mode disabled
+cannot be called from strub contexts, namely from bodies of functions
+with strub modes @code{1} or @code{2}.
+
+A function whose body is not a strub context, but that should be
+callable from within strub contexts, should be marked as callable with
+@code{strub(3)}.
+
+Strub contexts are not inlined into non-strub contexts. When an
+internal-strub function is split, the wrapper can often be inlined, but
+its wrapped body cannot. Functions marked as @code{always_inline}, even
+if explicitly assigned internal strub mode, will not undergo wrapping.
+
@item unused
@cindex @code{unused} type attribute
When attached to a type (including a @code{union} or a @code{struct}),
@@ -11670,6 +11705,43 @@ option is in effect. Such calls should only be made in debugging
situations.
@end deftypefn
+@deftypefn {Built-in Function} {void *} __builtin_stack_address ()
+This function returns the value of the stack pointer register.
+@end deftypefn
+
+@node Stack Scrubbing
+@section Stack scrubbing interfaces
+
+Stack scrubbing involves cooperation between a strub context, i.e., a
+function whose stack frame is to be zeroed out, and its callers. The
+caller initializes a stack watermark, the 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, each one defined in libgcc, but expanded internally
+depending on optimization levels.
+
+@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 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 strub context 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 strub context. It remains as a function call
+at optimization levels lower than 3.
+@end deftypefn
+
@node Vector Extensions
@section Using Vector Instructions through Built-in Functions
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 32697e6117c..cca3e9b18c6 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,6 +599,7 @@ 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
-fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
-fvtv-counts -fvtv-debug @gol
-finstrument-functions @gol
@@ -15425,6 +15426,46 @@ 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 (strub) setting, namely, 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, even when the @code{strub} attribute
+is present in function and variable types.
+
+@item -fstrub=at-calls
+@opindex fstrub=at-calls
+Enable at-calls strub for all viable functions, and consider non-viable
+functions as callable. A function is non-eligible for at-calls strub if
+a different strub mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+At-calls strub mode, if not requested with the function type, is only
+viable for an eligible function if it is not visible to other
+translation units, through its declaration or by having its address
+taken.
+
+@item -fstrub=internal
+@opindex fstrub=internal
+Enable internal strub for all viable functions, and consider non-viable
+functions as callable. A function is not eligible for internal strub if
+another strub mode is explicitly requested, attributes @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 internal strub, all
+eligible functions are viable.
+
+@item -fstrub=all
+@opindex fstrub=all
+Enable strub for all viable functions, and consider non-viable functions
+as callable. When both strub modes are viable, at-calls is preferred.
+
@item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
@opindex fvtable-verify
This option is only available when compiling C++ code.
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 413446bcc46..7f4bc44d2bb 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -119,6 +119,7 @@ along with GCC; see the file COPYING3. If not see
#include "stringpool.h"
#include "attribs.h"
#include "asan.h"
+#include "ipa-strub.h"
typedef fibonacci_heap <sreal, cgraph_edge> edge_heap_t;
typedef fibonacci_node <sreal, cgraph_edge> edge_heap_node_t;
@@ -396,6 +397,11 @@ 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))
+ {
+ e->inline_failed = CIF_UNSPECIFIED;
+ inlinable = false;
+ }
if (!inlinable && report)
report_inline_failed_reason (e);
return inlinable;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
new file mode 100644
index 00000000000..992ab511b26
--- /dev/null
+++ b/gcc/ipa-strub.c
@@ -0,0 +1,2644 @@
+/* strub (stack scrubbing) support.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "gimplify.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "gimple-iterator.h"
+#include "gimplify-me.h"
+#include "tree-into-ssa.h"
+#include "tree-ssa.h"
+#include "tree-cfg.h"
+#include "cfghooks.h"
+#include "cfgloop.h"
+#include "cfgcleanup.h"
+#include "tree-eh.h"
+#include "except.h"
+#include "builtins.h"
+#include "attribs.h"
+#include "tree-inline.h"
+#include "cgraph.h"
+#include "alloc-pool.h"
+#include "symbol-summary.h"
+#include "ipa-prop.h"
+#include "ipa-fnsummary.h"
+#include "gimple-fold.h"
+#include "fold-const.h"
+#include "gimple-walk.h"
+#include "tree-dfa.h"
+#include "langhooks.h"
+#include "calls.h"
+#include "vec.h"
+#include "stor-layout.h"
+#include "varasm.h"
+#include "alias.h"
+#include "diagnostic.h"
+#include "intl.h"
+#include "ipa-strub.h"
+
+#if BUILDING_GCC_MAJOR >= 11
+# include "symtab-thunks.h"
+# include "attr-fnspec.h"
+# define HAVE_ATTR_FNSPEC 1
+# define FOR_GCC_11P 1
+#else
+# define HAVE_ATTR_FNSPEC 0
+# define FOR_GCC_11P 0
+#endif
+
+/* Const and pure functions that gain a watermark parameter for strub purposes
+ are still regarded as such, which may cause the inline expansions of the
+ __strub builtins to malfunction. Ideally, attribute "fn spec" would enable
+ us to inform the backend about requirements and side effects of the call, but
+ call_fusage building in calls.c:expand_call does not even look at
+ attr_fnspec, so we resort to asm loads and updates to attain an equivalent
+ effect. Once expand_call gains the ability to issue extra memory uses and
+ clobbers based on pure/const function's fnspec, we can define this to 1. */
+#define ATTR_FNSPEC_DECONST_WATERMARK 0
+
+enum strub_mode {
+ /* This mode denotes a regular function, that does not require stack
+ scrubbing (strubbing). It may call any other functions, but if
+ it calls AT_CALLS (or WRAPPED) ones, strubbing logic is
+ automatically introduced around those calls (the latter, by
+ inlining INTERNAL wrappers). */
+ STRUB_DISABLED = 0,
+
+ /* This denotes a function whose signature is (to be) modified to
+ take an extra parameter, for stack use annotation, and its
+ callers must initialize and pass that argument, and perform the
+ strubbing. Functions that are explicitly marked with attribute
+ strub must have the mark visible wherever the function is,
+ including aliases, and overriders and overriding methods.
+ Functions that are implicitly marked for strubbing, for accessing
+ variables explicitly marked as such, will only select this
+ strubbing method if they are internal to a translation unit. It
+ can only be inlined into other strubbing functions, i.e.,
+ STRUB_AT_CALLS or STRUB_WRAPPED. */
+ STRUB_AT_CALLS = 1,
+
+ /* This denotes a function that is to perform strubbing internally,
+ without any changes to its interface (the function is turned into
+ a strubbing wrapper, and its original body is moved to a separate
+ STRUB_WRAPPED function, with a modified interface). Functions
+ may be explicitly marked with attribute strub(2), and the
+ attribute must be visible at the point of definition. Functions
+ that are explicitly marked for strubbing, for accessing variables
+ explicitly marked as such, may select this strubbing mode if
+ their interface cannot change, e.g. because its interface is
+ visible to other translation units, directly, by indirection
+ (having its address taken), inheritance, etc. Functions that use
+ this method must not have the noclone attribute, nor the noipa
+ one. Functions marked as always_inline may select this mode, but
+ they are NOT wrapped, they remain unchanged, and are only inlined
+ into strubbed contexts. Once non-always_inline functions are
+ wrapped, the wrapper becomes STRUB_WRAPPER, and the wrapped becomes
+ STRUB_WRAPPED. */
+ STRUB_INTERNAL = 2,
+
+ /* This denotes a function whose stack is not strubbed, but that is
+ nevertheless explicitly or implicitly marked as callable from strubbing
+ functions. Normally, only STRUB_AT_CALLS (and STRUB_INTERNAL ->
+ STRUB_WRAPPED) functions can be called from strubbing contexts (bodies of
+ STRUB_AT_CALLS, STRUB_INTERNAL and STRUB_WRAPPED functions), but attribute
+ strub(3) enables other functions to be (indirectly) called from these
+ contexts. Some builtins and internal functions may be implicitly marked as
+ STRUB_CALLABLE. */
+ STRUB_CALLABLE = 3,
+
+ /* This denotes the function that took over the body of a
+ STRUB_INTERNAL function. At first, it's only called by its
+ wrapper, but the wrapper may be inlined. The wrapped function,
+ in turn, can only be inlined into other functions whose stack
+ frames are strubbed, i.e., that are STRUB_WRAPPED or
+ STRUB_AT_CALLS. */
+ STRUB_WRAPPED = -1,
+
+ /* This denotes the wrapper function that replaced the STRUB_INTERNAL
+ function. This mode overrides the STRUB_INTERNAL mode at the time the
+ internal to-be-wrapped function becomes a wrapper, so that inlining logic
+ can tell one from the other. */
+ STRUB_WRAPPER = -2,
+
+ /* This denotes an always_inline function that requires strubbing. It can
+ only be called from, and inlined into, other strubbing contexts. */
+ STRUB_INLINABLE = -3,
+};
+
+static tree
+get_strub_attr_from_type (tree type)
+{
+ return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
+}
+
+static tree
+get_strub_attr_from_decl (tree decl)
+{
+ tree ret = lookup_attribute ("strub", DECL_ATTRIBUTES (decl));
+ if (ret)
+ return ret;
+ return get_strub_attr_from_type (TREE_TYPE (decl));
+}
+
+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);
+
+#if 0 /* ??? use symbolic mode names with interned strings? */
+ char *s = NULL;
+
+ switch (strub_mode)
+ {
+
+ }
+#endif
+}
+
+static enum strub_mode
+get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
+{
+ enum strub_mode mode = STRUB_DISABLED;
+
+ if (strub_attr)
+ {
+ 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 /* ??? Support symbolic mode names? */
+ gcc_unreachable ();
+ }
+
+ return mode;
+}
+
+static enum strub_mode
+get_strub_mode (cgraph_node *node)
+{
+ return get_strub_mode_from_attr (get_strub_attr_from_decl (node->decl));
+}
+
+static enum strub_mode
+get_strub_mode_from_type (tree type)
+{
+ bool var_p = !FUNC_OR_METHOD_TYPE_P (type);
+ tree attr = get_strub_attr_from_type (type);
+
+ if (attr)
+ return get_strub_mode_from_attr (attr, var_p);
+
+ if (flag_strub > 0 && !var_p)
+ return STRUB_CALLABLE;
+
+ return STRUB_DISABLED;
+}
+
+static bool
+calls_builtin_va_start_p (cgraph_node *node)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (fndecl_built_in_p (cdecl, BUILT_IN_VA_START))
+ return true;
+ }
+
+ return result;
+}
+
+static bool
+calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!fndecl_built_in_p (cdecl, BUILT_IN_APPLY_ARGS))
+ continue;
+
+ result = true;
+
+ if (!report)
+ break;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "at-calls strub does not support call to %qD",
+ cdecl);
+ }
+
+ return result;
+}
+
+static inline bool
+strub_always_inline_p (cgraph_node *node)
+{
+ return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
+}
+
+static inline bool
+can_strub_p (cgraph_node *node, bool report = false)
+{
+ bool result = true;
+
+ if (!report && strub_always_inline_p (node))
+ return result;
+
+ if (lookup_attribute ("noipa", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for strub because of attribute %<noipa%>",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+can_strub_at_calls_p (cgraph_node *node, bool report = false)
+{
+ bool result = !report || can_strub_p (node, report);
+
+ if (!result && !report)
+ return result;
+
+ return !calls_builtin_apply_args_p (node, report);
+}
+
+#define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
+
+/* We can't perform internal strubbing if the function body involves certain
+ features:
+
+ - a non-default __builtin_va_start (e.g. x86's __builtin_ms_va_start) is
+ currently unsupported because we can't discover the corresponding va_copy and
+ va_end decls in the wrapper, and we don't convey the alternate variable
+ arguments ABI to the modified wrapped function. The default
+ __builtin_va_start is supported by calling va_start/va_end at the wrapper,
+ that takes variable arguments, passing a pointer to the va_list object to the
+ wrapped function, that runs va_copy from it where the original function ran
+ va_start.
+
+ __builtin_next_arg is currently unsupported because the wrapped function
+ won't be a variable argument function. We could process it in the wrapper,
+ that remains a variable argument function, and replace calls in the wrapped
+ body, but we currently don't.
+
+ __builtin_return_address is rejected because it's generally used when the
+ actual caller matters, and introducing a wrapper breaks such uses as those in
+ the unwinder. */
+
+static bool
+can_strub_internally_p (cgraph_node *node, bool report = false)
+{
+ bool result = !report || can_strub_p (node, report);
+
+ if (!result && !report)
+ return result;
+
+ if (!report && strub_always_inline_p (node))
+ return result;
+
+ if (lookup_attribute ("noclone", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for internal strub"
+ " because of attribute %<noclone%>",
+ node->decl);
+ }
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!((fndecl_built_in_p (cdecl, BUILT_IN_VA_START)
+ && cdecl != builtin_decl_explicit (BUILT_IN_VA_START))
+ || fndecl_built_in_p (cdecl, BUILT_IN_NEXT_ARG)
+ || fndecl_built_in_p (cdecl, BUILT_IN_RETURN_ADDRESS)))
+ continue;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "internal strub does not support call to %qD",
+ cdecl);
+ }
+
+ basic_block bb;
+ if (node->has_gimple_body_p ())
+ {
+ /* Label values referenced 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. */
+
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ glabel *label_stmt = dyn_cast <glabel *> (gsi_stmt (gsi));
+ tree target;
+
+ if (!label_stmt)
+ break;
+
+ 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;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (label_stmt),
+ "internal strub does not support user labels");
+ }
+ }
+
+ if (list_length (TYPE_ARG_TYPES (TREE_TYPE (node->decl)))
+ >= (((HOST_WIDE_INT) 1 << IPA_PARAM_MAX_INDEX_BITS)
+ - STRUB_INTERNAL_MAX_EXTRA_ARGS))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD has too many arguments for internal strub",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+strub_from_body_p (cgraph_node *node)
+{
+ if (!node->has_gimple_body_p ())
+ return false;
+
+ /* If any local variable is marked for strub... */
+ unsigned i;
+ tree var;
+ FOR_EACH_LOCAL_DECL (DECL_STRUCT_FUNCTION (node->decl),
+ i, var)
+ if (get_strub_mode_from_type (TREE_TYPE (var))
+ != STRUB_DISABLED)
+ return true;
+
+ /* Now scan the body for loads with strub types. */
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!gimple_assign_load_p (stmt))
+ continue;
+
+ tree rhs = gimple_assign_rhs1 (stmt);
+ if (get_strub_mode_from_type (TREE_TYPE (rhs))
+ != STRUB_DISABLED)
+ return true;
+ }
+
+ return false;
+}
+
+/* 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)
+{
+ if (DECL_BUILT_IN_CLASS (node->decl) != BUILT_IN_NORMAL)
+ return false;
+
+ enum built_in_function fcode = DECL_FUNCTION_CODE (node->decl);
+
+ switch (fcode)
+ {
+ case BUILT_IN_NONE:
+ gcc_unreachable ();
+
+ /* ??? Make all builtins callable. We wish to make any builtin call the
+ compiler might introduce on its own callable. Anything that is
+ predictable enough as to be known not to allow stack data that should
+ be strubbed to unintentionally escape to non-strub contexts can be
+ allowed, and pretty much every builtin appears to fit this description.
+ The exceptions to this rule seem to be rare, and only available as
+ explicit __builtin calls, so let's keep it simple and allow all of
+ them... */
+ default:
+ return true;
+ }
+}
+
+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);
+
+ /* 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;
+ /* 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
+ safely undergo at-calls strubbing. Internal mode will still be used in
+ functions that request it explicitly with attribute strub(2), or when the
+ function body requires strubbing and at-calls strubbing is not viable. */
+ const bool strub_flag_at_calls = flag_strub == 1;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo internal strubbing. At-calls mode will still be used in
+ functions that requiest it explicitly with attribute strub() or strub(1),
+ or when the function body requires strubbing and internal strubbing is not
+ viable. */
+ const bool strub_flag_internal = flag_strub == 2;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo strubbing in either mode. When both modes are viable,
+ at-calls is preferred. */
+ const bool strub_flag_either = flag_strub == 3;
+ /* Besides the default behavior, enable strub implicitly for all viable
+ functions. */
+ const bool strub_flag_viable = flag_strub > 0;
+
+ /* The consider_* variables should be true if selecting the corresponding
+ strub modes would be consistent with requests from attributes and command
+ line flags. Attributes associated with functions pretty much mandate a
+ selection, and should report an error if not satisfied; strub_flag_auto
+ implicitly enables some viable strub mode if that's required by references
+ to variables marked for strub; strub_flag_viable enables strub if viable
+ (even when favoring one mode, body-requested strub can still be satisfied
+ by either mode), and falls back to callable, silently unless variables
+ require strubbing. */
+
+ const bool consider_at_calls
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_AT_CALLS
+ : true));
+ const bool consider_internal
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_INTERNAL
+ : true));
+
+ const bool consider_callable
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_CALLABLE
+ : (strub_flag_viable
+ || strub_callable_builtin_p (node))));
+
+ /* This is a shorthand for either strub-enabled mode. */
+ const bool consider_strub
+ = (consider_at_calls || consider_internal);
+
+ /* We can cope with always_inline functions even with noipa and noclone,
+ because we just leave them alone. */
+ const bool is_always_inline
+ = strub_always_inline_p (node);
+
+ /* Strubbing in general, and each specific strub mode, may have its own set of
+ requirements. We require noipa for strubbing, either because of cloning
+ required for internal strub, or because of caller enumeration required for
+ at-calls strub. We don't consider the at-calls mode eligible if it's not
+ even considered, it has no further requirements. Internal mode requires
+ cloning and the absence of certain features in the body and, like at-calls,
+ it's not eligible if it's not even under consideration.
+
+ ??? Do we need target hooks for further constraints? E.g., x86's
+ "interrupt" attribute breaks internal strubbing because the wrapped clone
+ carries the attribute and thus isn't callable; in this case, we could use a
+ target hook to adjust the clone instead. */
+ const bool strub_eligible
+ = (consider_strub
+ && (is_always_inline || can_strub_p (node)));
+ const bool at_calls_eligible
+ = (consider_at_calls && strub_eligible
+ && can_strub_at_calls_p (node));
+ const bool internal_eligible
+ = (consider_internal && strub_eligible
+ && (is_always_inline
+ || can_strub_internally_p (node)));
+
+ /* In addition to the strict eligibility requirements, some additional
+ constraints are placed on implicit selection of certain modes. These do
+ not prevent the selection of a mode if explicitly specified as part of a
+ function interface (the strub attribute), but they may prevent modes from
+ being selected by the command line or by function bodies. The only actual
+ constraint is on at-calls mode: since we change the function's exposed
+ signature, we won't do it implicitly if the function can possibly be used
+ in ways that do not expect the signature change, e.g., if the function is
+ available to or interposable by other units, if its address is taken,
+ etc. */
+ const bool at_calls_viable
+ = (at_calls_eligible
+ && (strub_attr
+ || (node->has_gimple_body_p ()
+ && node->get_availability () > AVAIL_INTERPOSABLE
+ && ((!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->binds_to_current_def_p ()
+ && node->can_be_local_p ()))
+ && node->only_called_directly_p ())));
+ const bool internal_viable
+ = (internal_eligible);
+
+ /* Shorthand. */
+ const bool strub_viable
+ = (at_calls_viable || internal_viable);
+
+ /* We wish to analyze the body, to look for implicit requests for strub, both
+ to implicitly enable it when the body calls for it, and to report errors if
+ the body calls for it but neither mode is viable (even if that follows from
+ non-eligibility because of the explicit specification of some non-strubbing
+ mode). We can refrain from scanning the body only in rare circumstances:
+ when strub is enabled by a function attribute (scanning might be redundant
+ in telling us to also enable it), and when we are enabling strub implicitly
+ but there are non-viable modes: we want to know whether strubbing is
+ required, to fallback to another mode, even if we're only enabling a
+ certain mode, or, when either mode would do, to report an error if neither
+ happens to be viable. */
+ const bool analyze_body
+ = (strub_attr
+ ? !consider_strub
+ : (strub_flag_auto
+ || (strub_flag_viable && (!at_calls_viable && !internal_viable))
+ || (strub_flag_either && !strub_viable)));
+
+ /* Cases in which strubbing is enabled or disabled by strub_flag_auto.
+ Unsatisfiable requests ought to be reported. */
+ const bool strub_required
+ = ((strub_attr && consider_strub)
+ || (analyze_body && strub_from_body_p (node)));
+
+ /* Besides the required cases, we want to abide by the requests to enabling on
+ an if-viable basis. */
+ const bool strub_enable
+ = (strub_required
+ || (strub_flag_at_calls && at_calls_viable)
+ || (strub_flag_internal && internal_viable)
+ || (strub_flag_either && strub_viable));
+
+ /* And now we're finally ready to select a mode that abides by the viability
+ and eligibility constraints, and that satisfies the strubbing requirements
+ and requests, subject to the constraints. If both modes are viable and
+ strub is to be enabled, pick STRUB_AT_CALLS unless STRUB_INTERNAL was named
+ as preferred. */
+ 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
+ : (strub_enable && at_calls_viable)
+ ? STRUB_AT_CALLS
+ : consider_callable
+ ? STRUB_CALLABLE
+ : STRUB_DISABLED);
+
+ switch (mode)
+ {
+ case STRUB_CALLABLE:
+ if (is_always_inline)
+ break;
+ /* Fall through. */
+
+ case STRUB_DISABLED:
+ if (strub_enable && !strub_attr)
+ {
+ gcc_checking_assert (analyze_body);
+ error_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD requires strub, but no viable strub mode was found",
+ node->decl);
+ break;
+ }
+ /* Fall through. */
+
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ /* Differences from an mode requested through a function attribute are
+ reported in set_strub_mode_to. */
+ break;
+
+ case STRUB_WRAPPED:
+ case STRUB_WRAPPER:
+ default:
+ gcc_unreachable ();
+ }
+
+ return mode;
+}
+
+static void
+set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+ enum strub_mode req_mode = get_strub_mode_from_attr (attr);
+
+ if (attr)
+ {
+ /* Check for and report incompatible mode changes. */
+ if (mode != req_mode
+ && !(req_mode == STRUB_INTERNAL
+ && (mode == STRUB_WRAPPED
+ || mode == STRUB_WRAPPER))
+ && !((req_mode == STRUB_INTERNAL
+ || req_mode == STRUB_AT_CALLS
+ || req_mode == STRUB_CALLABLE)
+ && 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));
+ if (node->alias)
+ {
+ cgraph_node *target = node->ultimate_alias_target ();
+ if (target != node)
+ error_at (DECL_SOURCE_LOCATION (target->decl),
+ "the incompatible selection was determined"
+ " by ultimate alias target %qD",
+ target->decl);
+ }
+
+ /* Report any incompatibilities with explicitly-requested strub. */
+ switch (req_mode)
+ {
+ case STRUB_AT_CALLS:
+ can_strub_at_calls_p (node, true);
+ break;
+
+ case STRUB_INTERNAL:
+ can_strub_internally_p (node, true);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Drop any incompatible strub attributes leading the decl attribute
+ chain. Return if we find one with the mode we need. */
+ for (;;)
+ {
+ if (mode == req_mode)
+ return;
+
+ if (DECL_ATTRIBUTES (node->decl) != attr)
+ break;
+
+ DECL_ATTRIBUTES (node->decl) = TREE_CHAIN (attr);
+ attr = get_strub_attr_from_decl (node->decl);
+ if (!attr)
+ break;
+
+ req_mode = get_strub_mode_from_attr (attr);
+ }
+ }
+ else if (mode == req_mode)
+ return;
+
+ DECL_ATTRIBUTES (node->decl) = tree_cons (get_identifier ("strub"),
+ get_strub_mode_attr_value (mode),
+ DECL_ATTRIBUTES (node->decl));
+}
+
+static enum strub_mode
+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))
+ {
+ /* 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_DISABLED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ cgraph_node *xnode = node;
+ if (node->alias)
+ xnode = node->ultimate_alias_target ();
+ /* Weakrefs may remain unresolved (the above will return node) if
+ their targets are not defined, so make sure we compute a strub
+ mode for them, instead of defaulting to STRUB_DISABLED and
+ rendering them uncallable. */
+ enum strub_mode mode = (xnode != node && !xnode->alias
+ ? get_strub_mode (xnode)
+ : compute_strub_mode (node, attr));
+
+ set_strub_mode_to (node, mode);
+
+ return mode;
+}
+
+/* Non-strub functions shouldn't be called from strub functions,
+ except through callable ones. Always inline strub functions can
+ only be called from strub functions. */
+
+static bool
+strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode caller_mode = get_strub_mode (caller);
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return callee_mode != STRUB_INLINABLE;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ return (flag_strub >= 0);
+
+ case STRUB_DISABLED:
+ return false;
+
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ 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. */
+
+bool
+strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return true;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ strub_mode caller_mode = get_strub_mode (caller);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ return true;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ return false;
+}
+
+/* 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;
+
+ 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_WRAPPED
+ || caller_mode == STRUB_INLINABLE);
+
+ 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);
+ }
+
+ /* ??? 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 {
+
+const pass_data pass_data_ipa_strub_mode = {
+ SIMPLE_IPA_PASS,
+ "strubm",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ 0, // properties_finish
+};
+
+class pass_ipa_strub_mode : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub_mode (gcc::context *ctxt)
+ : simple_ipa_opt_pass (pass_data_ipa_strub_mode, ctxt)
+ {}
+ 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
+ overhead. */
+ if (flag_strub == -2)
+ flag_strub = 0;
+ return flag_strub;
+ }
+ virtual unsigned int execute (function *);
+};
+
+const pass_data pass_data_ipa_strub = {
+ SIMPLE_IPA_PASS,
+ "strub",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ TODO_update_ssa
+ | TODO_cleanup_cfg
+ | TODO_rebuild_cgraph_edges
+ | TODO_verify_il, // properties_finish
+};
+
+class pass_ipa_strub : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub (gcc::context *ctxt)
+ : 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 unsigned int execute (function *);
+
+#define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST) \
+ static tree get_ ## NAME () { \
+ tree decl = builtin_decl_explicit (CODE); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ decl = add_builtin_function \
+ ("__builtin_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ NULL, NULL); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ } \
+ return decl; \
+ }
+
+ DEF_NM_BUILTIN (stack_address,
+ BUILT_IN_STACK_ADDRESS,
+ (ptr_type_node, NULL))
+
+#undef DEF_NM_BUILTIN
+
+#define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST) \
+ static tree get_ ## NAME () { \
+ tree decl = builtin_decl_explicit (CODE); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ tree attrs = NULL; \
+ if (FNSPEC && HAVE_ATTR_FNSPEC) \
+ attrs = tree_cons (get_identifier ("fn spec"), \
+ build_tree_list \
+ (NULL_TREE, \
+ build_string (strlen (FNSPEC), \
+ (FNSPEC))), \
+ attrs); \
+ decl = add_builtin_function_ext_scope \
+ ("__builtin___strub_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ "__strub_" #NAME, attrs); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ } \
+ return decl; \
+ }
+
+ DEF_SS_BUILTIN (enter, ". Ot",
+ BUILT_IN___STRUB_ENTER,
+ (void_type_node, get_pptr (), NULL))
+ DEF_SS_BUILTIN (update, ". Wt",
+ BUILT_IN___STRUB_UPDATE,
+ (void_type_node, get_pptr (), NULL))
+ DEF_SS_BUILTIN (leave, ". w ",
+ BUILT_IN___STRUB_LEAVE,
+ (void_type_node, get_pptr (), NULL))
+
+#undef DEF_SS_BUILTIN
+
+#define DEF_IDENT(NAME) \
+ static inline tree get_ ## NAME () { \
+ static tree identifier = NULL_TREE; \
+ if (!identifier) \
+ identifier = get_identifier (".strub." #NAME); \
+ return identifier; \
+ }
+
+ DEF_IDENT (watermark_ptr)
+ DEF_IDENT (va_list_ptr)
+ DEF_IDENT (apply_args_ptr)
+
+#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 gimple_seq
+ call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
+ gimple_seq seq = NULL)
+ {
+ tree uwm = get_update ();
+ gcall *update = gimple_build_call (uwm, 1, wmptr);
+ gimple_seq_add_stmt (&seq, update);
+ if (node)
+#if !IMPLICIT_CGRAPH_EDGES
+ node->create_edge (cgraph_node::get_create (uwm), update, count, false);
+#else
+ (void)count;
+#endif
+ return seq;
+ }
+
+};
+
+} // anon namespace
+
+typedef hash_set<tree> indirect_parms_t;
+
+static tree
+maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
+{
+ if (DECL_P (op))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (op))
+ {
+ tree ret = gimple_fold_indirect_ref (op);
+ if (!ret)
+ ret = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (op)),
+ op,
+ build_int_cst (TREE_TYPE (op), 0));
+ return ret;
+ }
+ }
+ else if (TREE_CODE (op) == ADDR_EXPR
+ && DECL_P (TREE_OPERAND (op, 0)))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (TREE_OPERAND (op, 0)))
+ return TREE_OPERAND (op, 0);
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_make_indirect (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ indirect_parms_t &indirect_parms = *(indirect_parms_t *)wi->info;
+
+ if (!*op || TYPE_P (*op))
+ {
+ *rec = 0;
+ return NULL_TREE;
+ }
+
+ if (tree repl = maybe_make_indirect (indirect_parms, *op, rec))
+ {
+ *op = repl;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ gimple_stmt_iterator &gsi = *(gimple_stmt_iterator *)wi->info;
+
+ *rec = 0;
+
+ if (!*op || TREE_CODE (*op) != ADDR_EXPR)
+ return NULL_TREE;
+
+ if (!is_gimple_val (*op))
+ {
+ tree ret = force_gimple_operand_gsi (&gsi, *op, true,
+ NULL_TREE, true, GSI_SAME_STMT);
+ gcc_assert (ret != *op);
+ *op = ret;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+build_ref_type_for (tree parm, bool nonaliased = true)
+{
+ gcc_checking_assert (TREE_CODE (parm) == PARM_DECL);
+
+ tree ref_type = build_reference_type (TREE_TYPE (parm));
+
+ if (!nonaliased)
+ return ref_type;
+
+ /* Each PARM turned indirect still points to the distinct memory area at the
+ wrapper, and the reference in unchanging, so we might qualify it, but...
+ const is not really important, since we're only using default defs for the
+ reference parm anyway, and not introducing any defs, and restrict seems to
+ cause trouble. E.g., libgnat/s-concat3.adb:str_concat_3 has memmoves that,
+ if it's wrapped, the memmoves are deleted in dse1. Using a distinct alias
+ set seems to not run afoul of this problem, and it hopefully enables the
+ compiler to tell the pointers do point to objects that are not otherwise
+ aliased. */
+#if 1
+ tree qref_type = build_variant_type_copy (ref_type);
+
+ TYPE_ALIAS_SET (qref_type) = new_alias_set ();
+ record_alias_subset (TYPE_ALIAS_SET (qref_type), get_alias_set (ref_type));
+
+ return qref_type;
+#else
+ tree qref_type = build_qualified_type (ref_type,
+ TYPE_QUAL_RESTRICT
+ | TYPE_QUAL_CONST);
+
+ return qref_type;
+#endif
+}
+
+/* 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)
+{
+#if IMPLICIT_CGRAPH_EDGES
+ return;
+#endif
+
+ cgraph_node *node = cgraph_node::get_create (current_function_decl);
+
+ for (gimple_stmt_iterator gsi = gsi_start (seq);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!is_a <gcall *> (stmt))
+ 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);
+ }
+}
+
+static void
+gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
+{
+ gimple *stmt = gsi_stmt (gsi);
+
+ gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+ 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;
+ bool nothrow_p = (must_not_throw_p
+ || (call && gimple_call_nothrow_p (call))
+ || (eh_lp <= 0
+ && (TREE_NOTHROW (cfun->decl)
+ || !flag_exceptions)));
+
+ if (noreturn_p && nothrow_p)
+ return;
+
+ /* Don't expect an EH edge if we're not to throw, or if we're not in an EH
+ region yet. */
+ bool no_eh_edge_p = (nothrow_p || !eh_lp);
+ bool must_end_bb = stmt_ends_bb_p (stmt);
+
+ edge eft = NULL, eeh = NULL;
+ if (must_end_bb && !(noreturn_p && no_eh_edge_p))
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+
+ edge e;
+ edge_iterator ei;
+ FOR_EACH_EDGE (e, ei, gsi_bb (gsi)->succs)
+ {
+ if ((e->flags & EDGE_EH))
+ {
+ gcc_checking_assert (!eeh);
+ eeh = e;
+#if !CHECKING_P
+ if (eft || noreturn_p)
+ break;
+#endif
+ }
+ if ((e->flags & EDGE_FALLTHRU))
+ {
+ gcc_checking_assert (!eft);
+ eft = e;
+#if !CHECKING_P
+ if (eeh || no_eh_edge_p)
+ break;
+#endif
+ }
+ }
+
+ gcc_checking_assert (!(eft && (eft->flags & EDGE_FALLTHRU))
+ == noreturn_p);
+ gcc_checking_assert (!(eeh && (eeh->flags & EDGE_EH))
+ == no_eh_edge_p);
+ gcc_checking_assert (eft != eeh);
+ }
+
+ if (!noreturn_p)
+ {
+ gimple_seq nseq = nothrow_p ? seq : gimple_seq_copy (seq);
+
+ if (must_end_bb)
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+ add_call_edges_for_seq (nseq, eft->count ());
+ gsi_insert_seq_on_edge_immediate (eft, nseq);
+ }
+ else
+ {
+ add_call_edges_for_seq (nseq, gsi_bb (gsi)->count);
+ gsi_insert_seq_after (&gsi, nseq, GSI_SAME_STMT);
+ }
+ }
+
+ if (nothrow_p)
+ return;
+
+ if (eh_lp)
+ {
+ add_call_edges_for_seq (seq, eeh->count ());
+ gsi_insert_seq_on_edge_immediate (eeh, seq);
+ return;
+ }
+
+ /* A throwing call may appear within a basic block in a function that doesn't
+ have any EH regions. We're going to add a cleanup if so, therefore the
+ block will have to be split. */
+ basic_block bb = gsi_bb (gsi);
+ if (!gsi_one_before_end_p (gsi))
+ split_block (bb, stmt);
+
+ /* Create a new block for the EH cleanup. */
+ basic_block bb_eh_cleanup = create_empty_bb (bb);
+ if (dom_info_available_p (CDI_DOMINATORS))
+ set_immediate_dominator (CDI_DOMINATORS, bb_eh_cleanup, bb);
+ if (current_loops)
+ add_bb_to_loop (bb_eh_cleanup, current_loops->tree_root);
+
+ /* Make the new block an EH cleanup for the call. */
+ eh_region new_r = gen_eh_region_cleanup (NULL);
+ eh_landing_pad lp = gen_eh_landing_pad (new_r);
+ tree label = gimple_block_label (bb_eh_cleanup);
+ lp->post_landing_pad = label;
+ EH_LANDING_PAD_NR (label) = lp->index;
+ add_stmt_to_eh_lp (stmt, lp->index);
+
+ /* Add the cleanup code to the EH cleanup block. */
+ gsi = gsi_after_labels (bb_eh_cleanup);
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
+
+ /* And then propagate the exception further. */
+ gresx *resx = gimple_build_resx (new_r->index);
+ gsi_insert_before (&gsi, resx, GSI_SAME_STMT);
+
+ /* Finally, wire the EH cleanup block into the CFG. */
+ make_eh_edges (stmt);
+ add_call_edges_for_seq (seq, single_pred_edge (bb_eh_cleanup)->count ());
+}
+
+/* Copy the attribute list at *ATTRS, minus any NAME attributes, leaving
+ shareable trailing nodes alone. */
+
+static inline void
+remove_named_attribute_unsharing (const char *name, tree *attrs)
+{
+ while (tree found = lookup_attribute (name, *attrs))
+ {
+ /* Copy nodes up to the next NAME attribute. */
+ while (*attrs != found)
+ {
+ *attrs = tree_cons (TREE_PURPOSE (*attrs),
+ TREE_VALUE (*attrs),
+ TREE_CHAIN (*attrs));
+ attrs = &TREE_CHAIN (*attrs);
+ }
+ /* Then drop it. */
+ gcc_checking_assert (*attrs == found);
+ *attrs = TREE_CHAIN (*attrs);
+ }
+}
+
+static int last_cgraph_order;
+
+static bool
+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;
+
+ 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. */
+ for (int aliases = 0; aliases <= 1; aliases++)
+ FOR_EACH_FUNCTION (node)
+ {
+ if (!node->alias != !aliases)
+ continue;
+
+ /* Already done. */
+ if (node->order < last_cgraph_order)
+ continue;
+
+ enum strub_mode mode = set_strub_mode (node);
+
+ if (mode == STRUB_AT_CALLS || mode == STRUB_INTERNAL)
+ any_strub = true;
+ }
+
+ if (any_strub)
+ last_cgraph_order = symtab->order;
+
+ return any_strub;
+}
+
+unsigned int
+pass_ipa_strub_mode::execute (function *)
+{
+ last_cgraph_order = 0;
+ bool any_strub = ipa_strub_set_mode_for_new_functions ();
+
+ if (!any_strub)
+ flag_strub = 0;
+ else
+ /* Verify before any inlining or other transformations. */
+ verify_strub ();
+
+ return 0;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub_mode (gcc::context *ctxt)
+{
+ return new pass_ipa_strub_mode (ctxt);
+}
+
+unsigned int
+pass_ipa_strub::execute (function *)
+{
+ cgraph_node *onode;
+
+ ipa_strub_set_mode_for_new_functions ();
+
+ FOR_EACH_FUNCTION (onode)
+ {
+ enum strub_mode mode = get_strub_mode (onode);
+
+ if (mode == STRUB_AT_CALLS)
+ {
+ 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));
+
+ 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. */
+ 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;
+
+ tree wmptr = build_decl (DECL_SOURCE_LOCATION (onode->decl),
+ PARM_DECL,
+ get_watermark_ptr (),
+ get_qpptr ());
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_ARG_TYPE (wmptr) = get_qpptr ();
+ DECL_CONTEXT (wmptr) = onode->decl;
+ TREE_USED (wmptr) = 1;
+ DECL_CHAIN (wmptr) = *pargs;
+ *pargs = wmptr;
+
+ if (onode->alias)
+ continue;
+
+ 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);
+
+ 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));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca)
+ {
+ 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);
+
+ if (!is_gimple_call (stmt))
+ continue;
+
+ gcall *call = as_a <gcall *> (stmt);
+
+ if (gimple_alloca_call_p (call))
+ {
+ /* Capture stack growth. */
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)
+ ->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ }
+ }
+
+ pop_cfun ();
+
+#if 0
+ compute_fn_summary (onode, true);
+#endif
+ continue;
+ }
+
+ if (mode != STRUB_INTERNAL
+ || !onode->has_gimple_body_p ())
+ 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 (!DECL_STRUCT_FUNCTION (onode->decl))
+ {
+ inform (DECL_SOURCE_LOCATION (onode->decl),
+ "not splitting struct-less function %qD for stack scrubbing",
+ onode->decl);
+ continue;
+ }
+
+ if (!onode->lowered)
+ {
+ inform (DECL_SOURCE_LOCATION (onode->decl),
+ "not splitting non-lowered function %qD for stack scrubbing",
+ 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 stack scrubbing",
+ onode->decl);
+ continue;
+ }
+
+ bool is_stdarg = calls_builtin_va_start_p (onode);;
+ bool apply_args = calls_builtin_apply_args_p (onode);
+
+ vec<ipa_adjusted_param, va_gc> *nparms = NULL;
+ unsigned j = 0;
+ {
+ // The following loop copied from ipa-split.c:split_function.
+ for (tree parm = DECL_ARGUMENTS (onode->decl);
+ parm; parm = DECL_CHAIN (parm), j++)
+ {
+ ipa_adjusted_param adj = {};
+ adj.op = IPA_PARAM_OP_COPY;
+ adj.base_index = j;
+ adj.prev_clone_index = j;
+ vec_safe_push (nparms, adj);
+ }
+
+ if (apply_args)
+ {
+ ipa_adjusted_param aaadj = {};
+ aaadj.op = IPA_PARAM_OP_NEW;
+ aaadj.type = get_qpptr ();
+ vec_safe_push (nparms, aaadj);
+ }
+
+ if (is_stdarg)
+ {
+ ipa_adjusted_param vladj = {};
+ vladj.op = IPA_PARAM_OP_NEW;
+ vladj.type = get_qpvalst ();
+ vec_safe_push (nparms, vladj);
+ }
+
+ ipa_adjusted_param wmadj = {};
+ wmadj.op = IPA_PARAM_OP_NEW;
+ wmadj.type = get_qpptr ();
+ vec_safe_push (nparms, wmadj);
+ }
+ ipa_param_adjustments adj (nparms, -1, false);
+
+ cgraph_node *nnode = onode->create_version_clone_with_body
+ (auto_vec<cgraph_edge *> (0),
+ NULL, &adj, NULL, NULL, "strub", NULL);
+
+ if (!nnode)
+ {
+ error_at (DECL_SOURCE_LOCATION (onode->decl),
+ "failed to split %qD for stack scrubbing",
+ onode->decl);
+ continue;
+ }
+
+ onode->split_part = true;
+ if (onode->calls_comdat_local)
+ nnode->add_to_same_comdat_group (onode);
+
+ gcc_checking_assert (!DECL_STRUCT_FUNCTION (nnode->decl)->stdarg);
+
+ set_strub_mode_to (onode, STRUB_WRAPPER);
+ set_strub_mode_to (nnode, STRUB_WRAPPED);
+
+ /* Decide which of the wrapped function's parms we want to turn into
+ references to the argument passed to the wrapper. In general, we want to
+ copy small arguments, and avoid copying large ones. Variable-sized array
+ lengths given by other arguments, as in 20020210-1.c, would lead to
+ problems if passed by value, after resetting the original function and
+ dropping the length computation; passing them by reference works.
+ DECL_BY_REFERENCE is *not* a substitute for this: it involves copying
+ anyway, but performed at the caller. */
+ indirect_parms_t indirect_nparms (3, false);
+ unsigned adjust_ftype = 0;
+ unsigned named_args = 0;
+ for (tree parm = DECL_ARGUMENTS (onode->decl),
+ nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (TREE_TYPE (nnode->decl));
+ parm;
+ named_args++,
+ parm = DECL_CHAIN (parm),
+ nparm = DECL_CHAIN (nparm),
+ nparmt = nparmt ? TREE_CHAIN (nparmt) : NULL_TREE)
+ if (!(0 /* DECL_BY_REFERENCE (narg) */
+ || is_gimple_reg_type (TREE_TYPE (nparm))
+ || VECTOR_TYPE_P (TREE_TYPE (nparm))
+ || TREE_CODE (TREE_TYPE (nparm)) == COMPLEX_TYPE
+ || (tree_fits_uhwi_p (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ && (tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ <= 4 * UNITS_PER_WORD))))
+ {
+ indirect_nparms.add (nparm);
+
+ /* ??? Is there any case in which it is not safe to suggest the parms
+ turned indirect don't alias anything else? They are distinct,
+ unaliased memory in the wrapper, and the wrapped can't possibly
+ take pointers into them because none of the pointers passed to the
+ wrapper can alias other incoming parameters passed by value, even
+ if with transparent reference, and the wrapper doesn't take any
+ extra parms that could point into wrapper's parms. So we can
+ probably drop the TREE_ADDRESSABLE and keep the true. */
+ tree ref_type = build_ref_type_for (nparm,
+ true
+ || !TREE_ADDRESSABLE (parm));
+
+ DECL_ARG_TYPE (nparm) = TREE_TYPE (nparm) = ref_type;
+ relayout_decl (nparm);
+ TREE_ADDRESSABLE (nparm) = 0;
+ DECL_BY_REFERENCE (nparm) = 0;
+#if FOR_GCC_11P
+ DECL_NOT_GIMPLE_REG_P (nparm) = 0;
+#else
+ DECL_GIMPLE_REG_P (nparm) = 1;
+#endif
+ /* ??? This avoids mismatches in debug info bind stmts in
+ e.g. a-chahan . */
+ DECL_ABSTRACT_ORIGIN (nparm) = NULL;
+
+ if (nparmt)
+ adjust_ftype++;
+ }
+
+ /* Also adjust the wrapped function type, if needed. */
+ if (adjust_ftype)
+ {
+ tree nftype = TREE_TYPE (nnode->decl);
+
+ /* We always add at least one argument at the end of the signature, when
+ cloning the function, so we don't expect to need to duplicate the
+ type here. */
+ gcc_checking_assert (TYPE_ARG_TYPES (nftype)
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+
+#if HAVE_ATTR_FNSPEC
+ /* Check that fnspec still works for the modified function signature,
+ and drop it otherwise. */
+ bool drop_fnspec = false;
+ tree fnspec = lookup_attribute ("fn spec", TYPE_ATTRIBUTES (nftype));
+ attr_fnspec spec = fnspec ? attr_fnspec (fnspec) : attr_fnspec ("");
+
+ unsigned retcopy;
+ if (!(fnspec && spec.returns_arg (&retcopy)))
+ retcopy = (unsigned) -1;
+
+ unsigned i = 0;
+#endif
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (nftype);
+ adjust_ftype > 0;
+#if HAVE_ATTR_FNSPEC
+ i++,
+#endif
+ nparm = DECL_CHAIN (nparm), nparmt = TREE_CHAIN (nparmt))
+ if (indirect_nparms.contains (nparm))
+ {
+ TREE_VALUE (nparmt) = TREE_TYPE (nparm);
+ adjust_ftype--;
+
+#if HAVE_ATTR_FNSPEC
+ if (fnspec && !drop_fnspec)
+ {
+ if (i == retcopy)
+ drop_fnspec = true;
+ else if (spec.arg_specified_p (i))
+ {
+ /* Properties that apply to pointers only must not be
+ present, because we don't make pointers further
+ indirect. */
+ gcc_checking_assert
+ (!spec.arg_max_access_size_given_by_arg_p (i, NULL));
+ gcc_checking_assert (!spec.arg_copied_to_arg_p (i, NULL));
+
+ /* Any claim of direct access only is invalidated by
+ adding an indirection level. */
+ if (spec.arg_direct_p (i))
+ drop_fnspec = true;
+
+ /* If there's a claim the argument is not read from, the
+ added indirection invalidates it: if the argument is
+ used at all, then the pointer will necessarily be
+ read. */
+ if (!spec.arg_maybe_read_p (i)
+ && spec.arg_used_p (i))
+ drop_fnspec = true;
+ }
+ }
+#endif
+ }
+
+#if HAVE_ATTR_FNSPEC
+ /* ??? Maybe we could adjust it instead. */
+ if (drop_fnspec)
+ remove_named_attribute_unsharing ("fn spec",
+ &TYPE_ATTRIBUTES (nftype));
+#endif
+
+ TREE_TYPE (nnode->decl) = nftype;
+ }
+
+#if ATTR_FNSPEC_DECONST_WATERMARK
+ {
+ int flags = flags_from_decl_or_type (nnode->decl);
+ tree fnspec = lookup_attribute ("fn spec", TREE_TYPE (nnode->decl));
+
+ if ((flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS)) || fnspec)
+ {
+ size_t xargs = 1 + int (is_stdarg) + int (apply_args);
+ 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! */
+ bool no_writes_p = true;
+ if (fnspec)
+ {
+ tree fnspecstr = TREE_VALUE (TREE_VALUE (fnspec));
+ curlen = TREE_STRING_LENGTH (fnspecstr);
+ memcpy (nspec, TREE_STRING_POINTER (fnspecstr), curlen);
+ if (!(flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS))
+ && curlen >= 2
+ && nspec[1] != 'c' && nspec[1] != 'C'
+ && nspec[1] != 'p' && nspec[1] != 'P')
+ no_writes_p = false;
+ }
+ if (!curlen)
+ {
+ nspec[curlen++] = '.';
+ nspec[curlen++] = ((flags & ECF_CONST)
+ ? 'c'
+ : (flags & ECF_PURE)
+ ? 'p'
+ : ' ');
+ }
+ while (curlen < tgtlen - 2 * xargs)
+ {
+ nspec[curlen++] = '.';
+ nspec[curlen++] = ' ';
+ }
+
+ /* These extra args are unlikely to be present in const or pure
+ functions. It's conceivable that a function that takes variable
+ arguments, or that passes its arguments on to another function,
+ could be const or pure, but it would not modify the arguments, and,
+ being pure or const, it couldn't possibly modify or even access
+ memory referenced by them. But it can read from these internal
+ data structures created by the wrapper, and from any
+ argument-passing memory referenced by them, so we denote the
+ possibility of reading from multiple levels of indirection, but
+ only of reading because const/pure. */
+ if (apply_args)
+ {
+ nspec[curlen++] = (no_writes_p ? 'r' : '.');
+ nspec[curlen++] = (no_writes_p ? 't' : ' ');
+ }
+ if (is_stdarg)
+ {
+ nspec[curlen++] = (no_writes_p ? 'r' : '.');
+ nspec[curlen++] = (no_writes_p ? 't' : ' ');
+ }
+
+ nspec[curlen++] = 'W';
+ nspec[curlen++] = 't';
+
+ /* The type has already been copied before adding parameters. */
+ gcc_checking_assert (TYPE_ARG_TYPES (TREE_TYPE (nnode->decl))
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+ TYPE_ATTRIBUTES (TREE_TYPE (nnode->decl))
+ = tree_cons (get_identifier ("fn spec"),
+ build_tree_list (NULL_TREE,
+ build_string (tgtlen, nspec)),
+ TYPE_ATTRIBUTES (TREE_TYPE (nnode->decl)));
+ }
+ }
+#endif
+
+ {
+ tree decl = onode->decl;
+ cgraph_node *target = nnode;
+
+ { // copied from create_wrapper
+
+ /* Preserve DECL_RESULT so we get right by reference flag. */
+ tree decl_result = DECL_RESULT (decl);
+
+ /* Remove the function's body but keep arguments to be reused
+ for thunk. */
+ onode->release_body (true);
+ onode->reset ();
+
+ DECL_UNINLINABLE (decl) = false;
+ DECL_RESULT (decl) = decl_result;
+ DECL_INITIAL (decl) = NULL;
+ allocate_struct_function (decl, false);
+ set_cfun (NULL);
+
+ /* Turn alias into thunk and expand it into GIMPLE representation. */
+ onode->definition = true;
+
+#if FOR_GCC_11P
+ thunk_info::get_create (onode);
+ onode->thunk = true;
+#else
+ memset (&onode->thunk, 0, sizeof (cgraph_thunk_info));
+ onode->thunk.thunk_p = true;
+ onode->thunk.alias = target->decl;
+#endif
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (target, NULL, onode->count);
+#endif
+ onode->callees->can_throw_external = !TREE_NOTHROW (target->decl);
+
+ tree arguments = DECL_ARGUMENTS (decl);
+
+ while (arguments)
+ {
+ TREE_ADDRESSABLE (arguments) = false;
+ arguments = TREE_CHAIN (arguments);
+ }
+
+ {
+ tree alias = onode->callees->callee->decl;
+ tree thunk_fndecl = decl;
+ tree a;
+
+ int nxargs = 1 + is_stdarg + apply_args;
+
+ { // Simplified from expand_thunk.
+ tree restype;
+ basic_block bb, then_bb, else_bb, return_bb;
+ gimple_stmt_iterator bsi;
+ int nargs = 0;
+ tree arg;
+ int i;
+ tree resdecl;
+ tree restmp = NULL;
+
+ gcall *call;
+ greturn *ret;
+ bool alias_is_noreturn = TREE_THIS_VOLATILE (alias);
+
+ a = DECL_ARGUMENTS (thunk_fndecl);
+
+ current_function_decl = thunk_fndecl;
+
+#if FOR_GCC_11P
+ /* Ensure thunks are emitted in their correct sections. */
+ resolve_unique_section (thunk_fndecl, 0,
+ flag_function_sections);
+#endif
+
+ bitmap_obstack_initialize (NULL);
+
+ /* Build the return declaration for the function. */
+ restype = TREE_TYPE (TREE_TYPE (thunk_fndecl));
+ if (DECL_RESULT (thunk_fndecl) == NULL_TREE)
+ {
+ resdecl = build_decl (input_location, RESULT_DECL, 0, restype);
+ DECL_ARTIFICIAL (resdecl) = 1;
+ DECL_IGNORED_P (resdecl) = 1;
+ DECL_CONTEXT (resdecl) = thunk_fndecl;
+ DECL_RESULT (thunk_fndecl) = resdecl;
+ }
+ else
+ resdecl = DECL_RESULT (thunk_fndecl);
+
+ profile_count cfg_count = onode->count;
+ if (!cfg_count.initialized_p ())
+ cfg_count = profile_count::from_gcov_type (BB_FREQ_MAX).guessed_local ();
+
+ bb = then_bb = else_bb = return_bb
+ = init_lowered_empty_function (thunk_fndecl, true, cfg_count);
+
+ bsi = gsi_start_bb (bb);
+
+ /* Build call to the function being thunked. */
+ if (!VOID_TYPE_P (restype)
+ && (!alias_is_noreturn
+ || TREE_ADDRESSABLE (restype)
+ || TREE_CODE (TYPE_SIZE_UNIT (restype)) != INTEGER_CST))
+ {
+ if (DECL_BY_REFERENCE (resdecl))
+ {
+ restmp = gimple_fold_indirect_ref (resdecl);
+ if (!restmp)
+ restmp = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (resdecl)),
+ resdecl,
+ build_int_cst (TREE_TYPE (resdecl), 0));
+ }
+ else if (!is_gimple_reg_type (restype))
+ {
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl)))
+ {
+ restmp = resdecl;
+
+ if (VAR_P (restmp))
+ {
+ add_local_decl (cfun, restmp);
+ BLOCK_VARS (DECL_INITIAL (current_function_decl))
+ = restmp;
+ }
+ }
+ else
+ restmp = create_tmp_var (restype, "retval");
+ }
+ else
+ restmp = create_tmp_reg (restype, "retval");
+ }
+
+ for (arg = a; arg; arg = DECL_CHAIN (arg))
+ nargs++;
+ auto_vec<tree> vargs (nargs + nxargs);
+ i = 0;
+ arg = a;
+
+ if (nargs)
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl);
+ i < nargs;
+ i++, arg = DECL_CHAIN (arg), nparm = DECL_CHAIN (nparm))
+ {
+ tree save_arg = arg;
+ tree tmp = arg;
+
+ /* Arrange to pass indirectly the parms, if we decided to do
+ so, and revert its type in the wrapper. */
+ if (indirect_nparms.contains (nparm))
+ {
+ tree ref_type = TREE_TYPE (nparm);
+ TREE_ADDRESSABLE (arg) = true;
+ tree addr = build1 (ADDR_EXPR, ref_type, arg);
+ tmp = arg = addr;
+ }
+#if ! FOR_GCC_11P
+ else if (VECTOR_TYPE_P (TREE_TYPE (arg))
+ || TREE_CODE (TREE_TYPE (arg)) == COMPLEX_TYPE)
+ DECL_GIMPLE_REG_P (arg) = 1;
+#else
+ else
+ DECL_NOT_GIMPLE_REG_P (arg) = 0;
+#endif
+
+ /* Convert the argument back to the type used by the calling
+ conventions, e.g. a non-prototyped float type is passed as
+ double, as in 930603-1.c, and needs to be converted back to
+ double to be passed on unchanged to the wrapped
+ function. */
+ if (TREE_TYPE (nparm) != DECL_ARG_TYPE (nparm))
+ arg = fold_convert (DECL_ARG_TYPE (nparm), arg);
+
+ if (!is_gimple_val (arg))
+ {
+ tmp = create_tmp_reg (TYPE_MAIN_VARIANT
+ (TREE_TYPE (arg)), "arg");
+ gimple *stmt = gimple_build_assign (tmp, arg);
+ gsi_insert_after (&bsi, stmt, GSI_NEW_STMT);
+ }
+ vargs.quick_push (tmp);
+ arg = save_arg;
+ }
+ /* These strub arguments are adjusted later. */
+ if (apply_args)
+ vargs.quick_push (null_pointer_node);
+ if (is_stdarg)
+ vargs.quick_push (null_pointer_node);
+ vargs.quick_push (null_pointer_node);
+ call = gimple_build_call_vec (build_fold_addr_expr_loc (0, alias),
+ vargs);
+ onode->callees->call_stmt = call;
+ // gimple_call_set_from_thunk (call, true);
+ if (DECL_STATIC_CHAIN (alias))
+ {
+ tree p = DECL_STRUCT_FUNCTION (alias)->static_chain_decl;
+ tree type = TREE_TYPE (p);
+ tree decl = build_decl (DECL_SOURCE_LOCATION (thunk_fndecl),
+ PARM_DECL, create_tmp_var_name ("CHAIN"),
+ type);
+ DECL_ARTIFICIAL (decl) = 1;
+ DECL_IGNORED_P (decl) = 1;
+ TREE_USED (decl) = 1;
+ DECL_CONTEXT (decl) = thunk_fndecl;
+ DECL_ARG_TYPE (decl) = type;
+ TREE_READONLY (decl) = 1;
+
+ struct function *sf = DECL_STRUCT_FUNCTION (thunk_fndecl);
+ sf->static_chain_decl = decl;
+
+ gimple_call_set_chain (call, decl);
+ }
+
+ /* Return slot optimization is always possible and in fact required to
+ return values with DECL_BY_REFERENCE. */
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl))
+ && (!is_gimple_reg_type (TREE_TYPE (resdecl))
+ || DECL_BY_REFERENCE (resdecl)))
+ gimple_call_set_return_slot_opt (call, true);
+
+ if (restmp)
+ {
+ gimple_call_set_lhs (call, restmp);
+ gcc_assert (useless_type_conversion_p (TREE_TYPE (restmp),
+ TREE_TYPE (TREE_TYPE (alias))));
+ }
+ gsi_insert_after (&bsi, call, GSI_NEW_STMT);
+ if (!alias_is_noreturn)
+ {
+ /* Build return value. */
+ if (!DECL_BY_REFERENCE (resdecl))
+ ret = gimple_build_return (restmp);
+ else
+ ret = gimple_build_return (resdecl);
+
+ gsi_insert_after (&bsi, ret, GSI_NEW_STMT);
+ }
+ else
+ {
+ remove_edge (single_succ_edge (bb));
+ }
+
+ cfun->gimple_df->in_ssa_p = true;
+ update_max_bb_count ();
+ profile_status_for_fn (cfun)
+ = cfg_count.initialized_p () && cfg_count.ipa_p ()
+ ? PROFILE_READ : PROFILE_GUESSED;
+#if FOR_GCC_11P
+ /* FIXME: C++ FE should stop setting TREE_ASM_WRITTEN on thunks. */
+ // TREE_ASM_WRITTEN (thunk_fndecl) = false;
+#endif
+ delete_unreachable_blocks ();
+ update_ssa (TODO_update_ssa);
+ checking_verify_flow_info ();
+ free_dominance_info (CDI_DOMINATORS);
+
+ /* Since we want to emit the thunk, we explicitly mark its name as
+ referenced. */
+#if FOR_GCC_11P
+ onode->thunk = false;
+#else
+ onode->thunk.thunk_p = false;
+#endif
+ onode->lowered = true;
+ bitmap_obstack_release (NULL);
+ }
+ current_function_decl = NULL;
+ set_cfun (NULL);
+ }
+
+#if FOR_GCC_11P
+ thunk_info::remove (onode);
+#endif
+
+ // some more of create_wrapper at the end of the next block.
+ }
+ }
+
+ {
+ tree aaptr = NULL_TREE;
+ tree vaptr = NULL_TREE;
+ tree wmptr = NULL_TREE;
+ for (tree arg = DECL_ARGUMENTS (nnode->decl); arg; arg = DECL_CHAIN (arg))
+ {
+ aaptr = vaptr;
+ vaptr = wmptr;
+ wmptr = arg;
+ }
+
+ if (!apply_args)
+ aaptr = NULL_TREE;
+ /* The trailing args are [apply_args], [va_list_ptr], and
+ watermark. If we don't have a va_list_ptr, the penultimate
+ argument is apply_args.
+ */
+ else if (!is_stdarg)
+ aaptr = vaptr;
+
+ if (!is_stdarg)
+ vaptr = NULL_TREE;
+
+ DECL_NAME (wmptr) = get_watermark_ptr ();
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_IGNORED_P (wmptr) = 1;
+ TREE_USED (wmptr) = 1;
+
+ if (is_stdarg)
+ {
+ DECL_NAME (vaptr) = get_va_list_ptr ();
+ DECL_ARTIFICIAL (vaptr) = 1;
+ DECL_IGNORED_P (vaptr) = 1;
+ TREE_USED (vaptr) = 1;
+ }
+
+ if (apply_args)
+ {
+ DECL_NAME (aaptr) = get_apply_args_ptr ();
+ DECL_ARTIFICIAL (aaptr) = 1;
+ DECL_IGNORED_P (aaptr) = 1;
+ TREE_USED (aaptr) = 1;
+ }
+
+ push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ bool any_indirect = !indirect_nparms.is_empty ();
+
+ if (any_indirect)
+ {
+ 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);
+
+ 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
+ || is_stdarg || apply_args)
+ for (cgraph_edge *e = nnode->callees, *enext; e; e = enext)
+ {
+ gcall *call = e->call_stmt;
+ gimple_stmt_iterator gsi = gsi_for_stmt (call);
+ tree fndecl = e->callee->decl;
+
+ enext = e->next_callee;
+
+ if (gimple_alloca_call_p (call))
+ {
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ else if (fndecl && is_stdarg
+ && fndecl_built_in_p (fndecl, BUILT_IN_VA_START))
+ {
+ if (builtin_decl_explicit (BUILT_IN_VA_START) != fndecl)
+ sorry_at (gimple_location (call),
+ "nonstandard stdarg conventions");
+ tree bvacopy = builtin_decl_explicit (BUILT_IN_VA_COPY);
+ gimple_call_set_fndecl (call, bvacopy);
+ tree arg = vaptr;
+ /* The va_copy source must be dereferenced, unless it's an array
+ type, that would have decayed to a pointer. */
+ if (TREE_CODE (TREE_TYPE (TREE_TYPE (vaptr))) != ARRAY_TYPE)
+ {
+ arg = gimple_fold_indirect_ref (vaptr);
+ if (!arg)
+ arg = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (vaptr)),
+ vaptr,
+ build_int_cst (TREE_TYPE (vaptr), 0));
+ }
+ gimple_call_set_arg (call, 1, arg);
+ update_stmt (call);
+ e->redirect_callee (cgraph_node::get_create (bvacopy));
+ }
+ else if (fndecl && apply_args
+ && fndecl_built_in_p (fndecl, BUILT_IN_APPLY_ARGS))
+ {
+ tree lhs = gimple_call_lhs (call);
+ gassign *assign = gimple_build_assign (lhs, aaptr);
+ gsi_replace (&gsi, assign, true);
+ cgraph_edge::remove (e);
+ }
+ }
+
+ { // a little more copied from create_wrapper
+
+ /* Inline summary set-up. */
+ nnode->analyze ();
+ // inline_analyze_function (nnode);
+ }
+
+ pop_cfun ();
+ }
+
+ {
+ 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));
+
+ 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
+ onode->create_edge (cgraph_node::get_create (enter),
+ stptr, gsi_bb (gsi)->count, false);
+#endif
+
+ int nargs = gimple_call_num_args (wrcall);
+
+ gimple_seq seq = NULL;
+
+ if (apply_args)
+ {
+ tree aalst = create_tmp_var (ptr_type_node, ".strub.appargs");
+ tree bappargs = builtin_decl_explicit (BUILT_IN_APPLY_ARGS);
+ gcall *appargs = gimple_build_call (bappargs, 0);
+ gimple_call_set_lhs (appargs, aalst);
+ gsi_insert_before (&gsi, appargs, GSI_SAME_STMT);
+ gimple_call_set_arg (wrcall, nargs - 2 - is_stdarg, aalst);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bappargs),
+ appargs, gsi_bb (gsi)->count, false);
+#endif
+ }
+
+ if (is_stdarg)
+ {
+ tree valst = create_tmp_var (va_list_type_node, ".strub.va_list");
+ TREE_ADDRESSABLE (valst) = true;
+ tree vaptr = build1 (ADDR_EXPR,
+ build_pointer_type (va_list_type_node),
+ valst);
+ gimple_call_set_arg (wrcall, nargs - 2, unshare_expr (vaptr));
+
+ tree bvastart = builtin_decl_explicit (BUILT_IN_VA_START);
+ gcall *vastart = gimple_build_call (bvastart, 2,
+ unshare_expr (vaptr),
+ integer_zero_node);
+ gsi_insert_before (&gsi, vastart, GSI_SAME_STMT);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bvastart),
+ vastart, gsi_bb (gsi)->count, false);
+#endif
+
+ tree bvaend = builtin_decl_explicit (BUILT_IN_VA_END);
+ gcall *vaend = gimple_build_call (bvaend, 1, unshare_expr (vaptr));
+ gimple_seq_add_stmt (&seq, vaend);
+ }
+
+ gimple_call_set_arg (wrcall, nargs - 1, unshare_expr (swmp));
+ // gimple_call_set_tail (wrcall, false);
+ update_stmt (wrcall);
+
+ {
+#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);
+
+ /* For nnode, we don't rebuild edges because we wish to retain
+ any redirections copied to it from earlier passes, so we add
+ call graph edges explicitly there, but for onode, we create a
+ fresh function, so we may as well just issue the calls and
+ then rebuild all cgraph edges. */
+ // cgraph_edge::rebuild_edges ();
+ onode->analyze ();
+ // inline_analyze_function (onode);
+
+ 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;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub (gcc::context *ctxt)
+{
+ return new pass_ipa_strub (ctxt);
+}
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
new file mode 100644
index 00000000000..cadbca5002a
--- /dev/null
+++ b/gcc/ipa-strub.h
@@ -0,0 +1,25 @@
+/* strub (stack scrubbing) infrastructure.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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);
diff --git a/gcc/passes.def b/gcc/passes.def
index f5d88a61b0e..677c000e80b 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -53,6 +53,7 @@ along with GCC; see the file COPYING3. If not see
INSERT_PASSES_AFTER (all_small_ipa_passes)
NEXT_PASS (pass_ipa_free_lang_data);
NEXT_PASS (pass_ipa_function_and_variable_visibility);
+ NEXT_PASS (pass_ipa_strub_mode);
NEXT_PASS (pass_build_ssa_passes);
PUSH_INSERT_PASSES_WITHIN (pass_build_ssa_passes)
NEXT_PASS (pass_fixup_cfg);
@@ -111,6 +112,7 @@ along with GCC; see the file COPYING3. If not see
POP_INSERT_PASSES ()
NEXT_PASS (pass_ipa_remove_symbols);
+ NEXT_PASS (pass_ipa_strub);
NEXT_PASS (pass_ipa_oacc);
PUSH_INSERT_PASSES_WITHIN (pass_ipa_oacc)
NEXT_PASS (pass_ipa_pta);
diff --git a/gcc/testsuite/g++.dg/wrappers/strub1.C b/gcc/testsuite/g++.dg/wrappers/strub1.C
new file mode 100644
index 00000000000..a474a929649
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub1.C
@@ -0,0 +1,18 @@
+// { dg-do run }
+
+// Check that we don't get extra copies.
+
+struct T {
+ T &self;
+ void check () const { if (&self != this) __builtin_abort (); }
+ T() : self (*this) { check (); }
+ T(const T& ck) : self (*this) { ck.check (); check (); }
+ ~T() { check (); }
+};
+
+T foo (T) { return T(); }
+T bar (T p) { return foo (p); }
+
+int main () {
+ bar (T{});
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub2.C b/gcc/testsuite/g++.dg/wrappers/strub2.C
new file mode 100644
index 00000000000..25a62166448
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub2.C
@@ -0,0 +1,22 @@
+// { dg-do run }
+
+// This doesn't really test anything yet. We should mark the
+// variables as requiring strubbing, and somehow check that the
+// wrapped functions take the parameter by reference.
+
+struct T {
+ char d[32 * sizeof(void*)];
+};
+
+T foo (T q) { asm ("" : : "m"(q)); return q; }
+T bar (T p) { return foo (p); }
+
+T tmp;
+T tmp2;
+
+int main () {
+ __builtin_memset (&tmp, 0x55, sizeof (tmp));
+ tmp2 = bar (tmp);
+ if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
+ __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub3.C b/gcc/testsuite/g++.dg/wrappers/strub3.C
new file mode 100644
index 00000000000..e1b51cd0399
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub3.C
@@ -0,0 +1,22 @@
+// { dg-do run }
+
+// This doesn't really test anything yet. We should mark the
+// variables as requiring strubbing, and somehow check that the
+// wrapped functions take the parameter by reference.
+
+struct T {
+ char d[32 * sizeof(void*)];
+};
+
+static T foo (T q) { asm ("" : : "m"(q)); return q; }
+static T bar (T p) { return foo (p); }
+
+T tmp;
+T tmp2;
+
+int main () {
+ __builtin_memset (&tmp, 0x55, sizeof (tmp));
+ tmp2 = bar (tmp);
+ if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
+ __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub4.C b/gcc/testsuite/g++.dg/wrappers/strub4.C
new file mode 100644
index 00000000000..d021fca88e4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub4.C
@@ -0,0 +1,18 @@
+// { dg-do run }
+
+namespace
+{
+ class foo
+ {
+ public:
+ foo();
+ };
+
+ foo::foo() {}
+
+ foo bar;
+}
+
+int main()
+{
+}
diff --git a/gcc/tree-cfg.c b/gcc/tree-cfg.c
index 38269a27b79..b5e0af8b3a1 100644
--- a/gcc/tree-cfg.c
+++ b/gcc/tree-cfg.c
@@ -5617,6 +5617,7 @@ gimple_verify_flow_info (void)
{
gimple *stmt = gsi_stmt (gsi);
+ /* Do NOT disregard debug stmts after found_ctrl_stmt. */
if (found_ctrl_stmt)
{
error ("control flow in the middle of basic block %d",
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index aa9757a2fe9..0210f84bc0c 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -494,8 +494,9 @@ extern gimple_opt_pass *make_pass_adjust_alignment (gcc::context *ctxt);
/* IPA Passes */
extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt);
-extern simple_ipa_opt_pass
- *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub_mode (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_tree_profile (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_auto_profile (gcc::context *ctxt);
diff --git a/libgcc/Makefile.in b/libgcc/Makefile.in
index 2c8be561eb5..6b7b12493f5 100644
--- a/libgcc/Makefile.in
+++ b/libgcc/Makefile.in
@@ -428,7 +428,7 @@ ifneq ($(enable_shared),yes)
iterator = $(patsubst %,$(srcdir)/static-object.mk,$(iter-items))
endif
-LIB2ADD += enable-execute-stack.c
+LIB2ADD += enable-execute-stack.c $(srcdir)/strub.c
# While emutls.c has nothing to do with EH, it is in LIB2ADDEH*
# instead of LIB2ADD because that's the way to be sure on some targets
diff --git a/libgcc/libgcc2.h b/libgcc/libgcc2.h
index 1819ff3ac3d..857091e65c8 100644
--- a/libgcc/libgcc2.h
+++ b/libgcc/libgcc2.h
@@ -532,6 +532,10 @@ extern int __parityDI2 (UDWtype);
extern void __enable_execute_stack (void *);
+extern void __strub_enter (void **);
+extern void __strub_update (void**);
+extern void __strub_leave (void **);
+
#ifndef HIDE_EXPORTS
#pragma GCC visibility pop
#endif
diff --git a/libgcc/strub.c b/libgcc/strub.c
new file mode 100644
index 00000000000..fd6e27556e4
--- /dev/null
+++ b/libgcc/strub.c
@@ -0,0 +1,112 @@
+/* Stack scrubbing infrastructure
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+Under Section 7 of GPL version 3, you are granted additional
+permissions described in the GCC Runtime Library Exception, version
+3.1, as published by the Free Software Foundation.
+
+You should have received a copy of the GNU General Public License and
+a copy of the GCC Runtime Library Exception along with this program;
+see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
+<http://www.gnu.org/licenses/>. */
+
+#include "tconfig.h"
+#include "tsystem.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "libgcc_tm.h"
+#include "libgcc2.h"
+
+#ifndef STACK_GROWS_DOWNWARD
+# define TOPS >
+#else
+# define TOPS <
+#endif
+
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+
+/* Enter a stack scrubbing context, initializing the watermark to the caller's
+ stack address. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_enter (void **watermark)
+{
+ *watermark = __builtin_frame_address (0);
+}
+
+/* Update the watermark within a stack scrubbing context with the current stack
+ pointer. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_update (void **watermark)
+{
+ void *sp = __builtin_frame_address (0);
+
+ if (sp TOPS *watermark)
+ *watermark = sp;
+}
+
+#ifndef TARGET_STRUB_USE_DYNAMIC_ARRAY
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+#endif
+
+#ifndef TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+# ifdef TARGET_STRUB_MAY_USE_MEMSET
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+# else
+# define TARGET_STRUB_MAY_USE_MEMSET 1
+# endif
+#endif
+
+/* Leave a stack scrubbing context, restoring and updating SAVED, and
+ clearing the stack between top and watermark. */
+void ATTRIBUTE_STRUB_CALLABLE
+#if ! TARGET_STRUB_MAY_USE_MEMSET
+__attribute__ ((__optimize__ ("-fno-tree-loop-distribute-patterns")))
+#endif
+__strub_leave (void **mark)
+{
+ void *sp = __builtin_stack_address ();
+
+ void **base, **end;
+#ifndef STACK_GROWS_DOWNWARD
+ base = sp;
+ end = *mark;
+#else
+ base = *mark;
+ end = sp;
+#endif
+
+ ptrdiff_t len = end - base;
+ if (len <= 0)
+ return;
+
+#if ! TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+ /* Allocate a dynamically-sized array covering the desired range, so that we
+ can safely call memset on it. */
+ void *ptr[len];
+ base = &ptr[0];
+ end = &ptr[len];
+#else
+ void **ptr = end;
+#endif /* TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY */
+
+ /* ldist turns this into a memset. Without the dynamic array above, that call
+ is likely unsafe: possibly tail-called, and likely scribbling over its own
+ stack frame. */
+ while (base < end)
+ *base++ = 0;
+
+ asm ("" : : "m" (ptr));
+}
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/strub)] strub with function and variable attributes
@ 2021-07-28 7:06 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2021-07-28 7:06 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:50b83a03cac3a51fe3adba3849b9207bc7b0f4ff
commit 50b83a03cac3a51fe3adba3849b9207bc7b0f4ff
Author: Alexandre Oliva <oliva@adacore.com>
Date: Sat Jul 24 23:05:42 2021 -0300
strub with function and variable attributes
still missing verification of calls, documentation, tests,
builtin expansion
Diff:
---
gcc/Makefile.in | 1 +
gcc/builtins.c | 153 ++
gcc/builtins.def | 4 +
gcc/c-family/c-attribs.c | 53 +
gcc/common.opt | 24 +
gcc/doc/extend.texi | 72 +
gcc/doc/invoke.texi | 41 +
gcc/ipa-inline.c | 6 +
gcc/ipa-strub.c | 2644 ++++++++++++++++++++++++++++++++
gcc/ipa-strub.h | 25 +
gcc/passes.def | 2 +
gcc/testsuite/g++.dg/wrappers/strub1.C | 18 +
gcc/testsuite/g++.dg/wrappers/strub2.C | 22 +
gcc/testsuite/g++.dg/wrappers/strub3.C | 22 +
gcc/testsuite/g++.dg/wrappers/strub4.C | 18 +
gcc/tree-cfg.c | 1 +
gcc/tree-pass.h | 5 +-
libgcc/Makefile.in | 2 +-
libgcc/libgcc2.h | 4 +
libgcc/strub.c | 112 ++
20 files changed, 3226 insertions(+), 3 deletions(-)
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 1666ef84d6a..163fd1a856c 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1468,6 +1468,7 @@ OBJS = \
ipa-reference.o \
ipa-ref.o \
ipa-utils.o \
+ ipa-strub.o \
ipa.o \
ira.o \
ira-build.o \
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 170d776c410..90d6cce4a84 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -153,6 +153,7 @@ static rtx expand_builtin_strnlen (tree, rtx, machine_mode);
static rtx expand_builtin_alloca (tree);
static rtx expand_builtin_unop (machine_mode, tree, rtx, rtx, optab);
static rtx expand_builtin_frame_address (tree, tree);
+static rtx expand_builtin_stack_address ();
static tree stabilize_va_list_loc (location_t, tree, int);
static rtx expand_builtin_expect (tree, rtx);
static rtx expand_builtin_expect_with_probability (tree, rtx);
@@ -7893,6 +7894,137 @@ expand_builtin_frame_address (tree fndecl, tree exp)
}
}
+/* Expand a call to builtin function __builtin_stack_address. */
+
+static rtx
+expand_builtin_stack_address ()
+{
+ return copy_addr_to_reg (stack_pointer_rtx);
+}
+
+/* Expand a call to builtin function __builtin_strub_enter. */
+
+static rtx
+expand_builtin_strub_enter (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (!optimize || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, Pmode, EXPAND_MEMORY);
+
+ emit_move_insn (wmark, stktop);
+
+ return const0_rtx;
+}
+
+#ifndef STACK_GROWS_DOWNWARD
+# define STACK_TOPS GT
+#else
+# define STACK_TOPS LT
+#endif
+
+#ifdef POINTERS_EXTEND_UNSIGNED
+# define STACK_UNSIGNED POINTERS_EXTEND_UNSIGNED
+#else
+# define STACK_UNSIGNED true
+#endif
+
+/* Expand a call to builtin function __builtin_strub_update. */
+
+static rtx
+expand_builtin_strub_update (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 2 || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, Pmode, EXPAND_MEMORY);
+ rtx wmarkr = force_reg (Pmode, wmark);
+
+ rtx_code_label *lab = gen_label_rtx ();
+ do_compare_rtx_and_jump (stktop, wmarkr, STACK_TOPS, STACK_UNSIGNED,
+ Pmode, NULL_RTX, lab, NULL,
+ profile_probability::very_likely ());
+ emit_move_insn (wmark, stktop);
+ emit_label (lab);
+
+ return const0_rtx;
+}
+
+
+/* Expand a call to builtin function __builtin_strub_leave. */
+
+static rtx
+expand_builtin_strub_leave (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 2 || flag_no_inline)
+ return NULL_RTX;
+
+ rtx stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, Pmode, EXPAND_MEMORY);
+ rtx wmarkr = force_reg (Pmode, wmark);
+
+#ifndef STACK_GROWS_DOWNWARD
+ rtx base = stktop;
+ rtx end = wmarkr;
+#else
+ rtx base = wmarkr;
+ rtx end = stktop;
+#endif
+
+ rtx_code_label *done = gen_label_rtx ();
+ do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
+ Pmode, NULL_RTX, done, NULL,
+ profile_probability::very_likely ());
+
+ if (optimize < 3 || !flag_inline_functions)
+ expand_call (exp, NULL_RTX, true);
+ else
+ {
+ rtx zero = force_operand (const0_rtx, NULL_RTX);
+ int ulen = GET_MODE_SIZE (Pmode);
+ rtx incr = plus_constant (Pmode, base, ulen);
+ rtx dstm = gen_rtx_MEM (Pmode, base);
+
+ rtx_code_label *loop = gen_label_rtx ();
+ emit_label (loop);
+ emit_move_insn (dstm, zero);
+ emit_move_insn (base, force_operand (incr, NULL_RTX));
+ do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
+ Pmode, NULL_RTX, NULL, loop,
+ profile_probability::very_likely ());
+ }
+
+ emit_label (done);
+
+ return const0_rtx;
+}
+
/* Expand EXP, a call to the alloca builtin. Return NULL_RTX if we
failed and the caller should emit a normal call. */
@@ -10151,6 +10283,27 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
case BUILT_IN_RETURN_ADDRESS:
return expand_builtin_frame_address (fndecl, exp);
+ case BUILT_IN_STACK_ADDRESS:
+ return expand_builtin_stack_address ();
+
+ case BUILT_IN___STRUB_ENTER:
+ target = expand_builtin_strub_enter (exp);
+ if (target)
+ return target;
+ break;
+
+ case BUILT_IN___STRUB_UPDATE:
+ target = expand_builtin_strub_update (exp);
+ if (target)
+ return target;
+ break;
+
+ case BUILT_IN___STRUB_LEAVE:
+ target = expand_builtin_strub_leave (exp);
+ if (target)
+ return target;
+ break;
+
/* Returns the address of the area where the structure is returned.
0 otherwise. */
case BUILT_IN_AGGREGATE_INCOMING_ADDRESS:
diff --git a/gcc/builtins.def b/gcc/builtins.def
index ec556df4f66..4545033e664 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -878,6 +878,10 @@ DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSL, "ffsl", BT_FN_INT_LONG, ATTR_CONST_NOTHRO
DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSLL, "ffsll", BT_FN_INT_LONGLONG, ATTR_CONST_NOTHROW_LEAF_LIST)
DEF_EXT_LIB_BUILTIN (BUILT_IN_FORK, "fork", BT_FN_PID, ATTR_NOTHROW_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FRAME_ADDRESS, "frame_address", BT_FN_PTR_UINT, ATTR_NULL)
+DEF_GCC_BUILTIN (BUILT_IN_STACK_ADDRESS, "stack_address", BT_FN_PTR, ATTR_NULL)
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_ENTER, "__builtin___strub_enter")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_UPDATE, "__builtin___strub_update")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_LEAVE, "__builtin___strub_leave")
/* [trans-mem]: Adjust BUILT_IN_TM_FREE if BUILT_IN_FREE is changed. */
DEF_LIB_BUILTIN (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_LEAF_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FROB_RETURN_ADDR, "frob_return_addr", BT_FN_PTR_PTR, ATTR_NULL)
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index e60fb31d8c8..89ef83f6fac 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -69,6 +69,7 @@ static tree handle_asan_odr_indicator_attribute (tree *, tree, tree, int,
static tree handle_stack_protect_attribute (tree *, tree, tree, int, bool *);
static tree handle_no_stack_protector_function_attribute (tree *, tree,
tree, int, bool *);
+static tree handle_strub_attribute (tree *, tree, tree, int, bool *);
static tree handle_noinline_attribute (tree *, tree, tree, int, bool *);
static tree handle_noclone_attribute (tree *, tree, tree, int, bool *);
static tree handle_nocf_check_attribute (tree *, tree, tree, int, bool *);
@@ -306,6 +307,8 @@ const struct attribute_spec c_common_attribute_table[] =
{ "no_stack_protector", 0, 0, true, false, false, false,
handle_no_stack_protector_function_attribute,
attr_stack_protect_exclusions },
+ { "strub", 0, 1, false, true, false, true,
+ handle_strub_attribute, NULL },
{ "noinline", 0, 0, true, false, false, false,
handle_noinline_attribute,
attr_noinline_exclusions },
@@ -1290,6 +1293,56 @@ handle_noipa_attribute (tree *node, tree name, tree, int, bool *no_add_attrs)
return NULL_TREE;
}
+/* Handle a "strub" attribute; arguments as in
+ struct attribute_spec.handler. */
+
+static tree
+handle_strub_attribute (tree *node, tree name,
+ tree args,
+ int ARG_UNUSED (flags), bool *no_add_attrs)
+{
+ bool enable = true;
+
+ if (args
+ && (TREE_CODE (*node) == FUNCTION_TYPE
+ || TREE_CODE (*node) == METHOD_TYPE))
+ {
+ /* 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)
+ {
+ warning (OPT_Wattributes,
+ "%qE attribute ignored because of argument %qE",
+ name, TREE_VALUE (args));
+ *no_add_attrs = true;
+ enable = false;
+ }
+ /* 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));
+
+ /* 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;
+
+ return NULL_TREE;
+}
+
/* Handle a "noinline" attribute; arguments as in
struct attribute_spec.handler. */
diff --git a/gcc/common.opt b/gcc/common.opt
index d9da1131eda..c0408cb0f4c 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,6 +2687,30 @@ 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=all
+Common RejectNegative Var(flag_strub, 3)
+Enable stack scrubbing for all viable functions.
+
+fstrub=at-calls
+Common RejectNegative Var(flag_strub, 1)
+Enable at-calls stack scrubbing for all viable functions.
+
+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 b83cd4919bb..020bb1aa808 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -77,6 +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.
* 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.
@@ -8652,6 +8653,40 @@ pid_t wait (wait_status_ptr_t p)
@}
@end smallexample
+@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 numeric
+argument.
+
+A function associated with at-calls strub mode (@code{strub(1)})
+undergoes interface changes, and callers automatically gain code to
+scrub the stack space it uses. If the operational numeric argument is
+omitted from the @code{strub} attribute in a function type, the at-calls
+mode is selected.
+
+A function with internal strub mode @code{strub(2)} retains an
+unmodified interface, but it may be turned into a wrapper that calls the
+wrapped body, with a custom interface, and then scrubs the stack space
+used by it.
+
+A variable whose type carries the strub attribute causes functions that
+use it to have strub enabled.
+
+A function associated with the disabled strub mode @code{strub(0)} will
+not have its stack scrubbed. A function that has strub mode disabled
+cannot be called from strub contexts, namely from bodies of functions
+with strub modes @code{1} or @code{2}.
+
+A function whose body is not a strub context, but that should be
+callable from within strub contexts, should be marked as callable with
+@code{strub(3)}.
+
+Strub contexts are not inlined into non-strub contexts. When an
+internal-strub function is split, the wrapper can often be inlined, but
+its wrapped body cannot. Functions marked as @code{always_inline}, even
+if explicitly assigned internal strub mode, will not undergo wrapping.
+
@item unused
@cindex @code{unused} type attribute
When attached to a type (including a @code{union} or a @code{struct}),
@@ -11670,6 +11705,43 @@ option is in effect. Such calls should only be made in debugging
situations.
@end deftypefn
+@deftypefn {Built-in Function} {void *} __builtin_stack_address ()
+This function returns the value of the stack pointer register.
+@end deftypefn
+
+@node Stack Scrubbing
+@section Stack scrubbing interfaces
+
+Stack scrubbing involves cooperation between a strub context, i.e., a
+function whose stack frame is to be zeroed out, and its callers. The
+caller initializes a stack watermark, the 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, each one defined in libgcc, but expanded internally
+depending on optimization levels.
+
+@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 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 strub context 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 strub context. It remains as a function call
+at optimization levels lower than 3.
+@end deftypefn
+
@node Vector Extensions
@section Using Vector Instructions through Built-in Functions
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 32697e6117c..cca3e9b18c6 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,6 +599,7 @@ 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
-fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
-fvtv-counts -fvtv-debug @gol
-finstrument-functions @gol
@@ -15425,6 +15426,46 @@ 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 (strub) setting, namely, 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, even when the @code{strub} attribute
+is present in function and variable types.
+
+@item -fstrub=at-calls
+@opindex fstrub=at-calls
+Enable at-calls strub for all viable functions, and consider non-viable
+functions as callable. A function is non-eligible for at-calls strub if
+a different strub mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+At-calls strub mode, if not requested with the function type, is only
+viable for an eligible function if it is not visible to other
+translation units, through its declaration or by having its address
+taken.
+
+@item -fstrub=internal
+@opindex fstrub=internal
+Enable internal strub for all viable functions, and consider non-viable
+functions as callable. A function is not eligible for internal strub if
+another strub mode is explicitly requested, attributes @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 internal strub, all
+eligible functions are viable.
+
+@item -fstrub=all
+@opindex fstrub=all
+Enable strub for all viable functions, and consider non-viable functions
+as callable. When both strub modes are viable, at-calls is preferred.
+
@item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
@opindex fvtable-verify
This option is only available when compiling C++ code.
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 413446bcc46..7f4bc44d2bb 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -119,6 +119,7 @@ along with GCC; see the file COPYING3. If not see
#include "stringpool.h"
#include "attribs.h"
#include "asan.h"
+#include "ipa-strub.h"
typedef fibonacci_heap <sreal, cgraph_edge> edge_heap_t;
typedef fibonacci_node <sreal, cgraph_edge> edge_heap_node_t;
@@ -396,6 +397,11 @@ 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))
+ {
+ e->inline_failed = CIF_UNSPECIFIED;
+ inlinable = false;
+ }
if (!inlinable && report)
report_inline_failed_reason (e);
return inlinable;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
new file mode 100644
index 00000000000..992ab511b26
--- /dev/null
+++ b/gcc/ipa-strub.c
@@ -0,0 +1,2644 @@
+/* strub (stack scrubbing) support.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "gimplify.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "gimple-iterator.h"
+#include "gimplify-me.h"
+#include "tree-into-ssa.h"
+#include "tree-ssa.h"
+#include "tree-cfg.h"
+#include "cfghooks.h"
+#include "cfgloop.h"
+#include "cfgcleanup.h"
+#include "tree-eh.h"
+#include "except.h"
+#include "builtins.h"
+#include "attribs.h"
+#include "tree-inline.h"
+#include "cgraph.h"
+#include "alloc-pool.h"
+#include "symbol-summary.h"
+#include "ipa-prop.h"
+#include "ipa-fnsummary.h"
+#include "gimple-fold.h"
+#include "fold-const.h"
+#include "gimple-walk.h"
+#include "tree-dfa.h"
+#include "langhooks.h"
+#include "calls.h"
+#include "vec.h"
+#include "stor-layout.h"
+#include "varasm.h"
+#include "alias.h"
+#include "diagnostic.h"
+#include "intl.h"
+#include "ipa-strub.h"
+
+#if BUILDING_GCC_MAJOR >= 11
+# include "symtab-thunks.h"
+# include "attr-fnspec.h"
+# define HAVE_ATTR_FNSPEC 1
+# define FOR_GCC_11P 1
+#else
+# define HAVE_ATTR_FNSPEC 0
+# define FOR_GCC_11P 0
+#endif
+
+/* Const and pure functions that gain a watermark parameter for strub purposes
+ are still regarded as such, which may cause the inline expansions of the
+ __strub builtins to malfunction. Ideally, attribute "fn spec" would enable
+ us to inform the backend about requirements and side effects of the call, but
+ call_fusage building in calls.c:expand_call does not even look at
+ attr_fnspec, so we resort to asm loads and updates to attain an equivalent
+ effect. Once expand_call gains the ability to issue extra memory uses and
+ clobbers based on pure/const function's fnspec, we can define this to 1. */
+#define ATTR_FNSPEC_DECONST_WATERMARK 0
+
+enum strub_mode {
+ /* This mode denotes a regular function, that does not require stack
+ scrubbing (strubbing). It may call any other functions, but if
+ it calls AT_CALLS (or WRAPPED) ones, strubbing logic is
+ automatically introduced around those calls (the latter, by
+ inlining INTERNAL wrappers). */
+ STRUB_DISABLED = 0,
+
+ /* This denotes a function whose signature is (to be) modified to
+ take an extra parameter, for stack use annotation, and its
+ callers must initialize and pass that argument, and perform the
+ strubbing. Functions that are explicitly marked with attribute
+ strub must have the mark visible wherever the function is,
+ including aliases, and overriders and overriding methods.
+ Functions that are implicitly marked for strubbing, for accessing
+ variables explicitly marked as such, will only select this
+ strubbing method if they are internal to a translation unit. It
+ can only be inlined into other strubbing functions, i.e.,
+ STRUB_AT_CALLS or STRUB_WRAPPED. */
+ STRUB_AT_CALLS = 1,
+
+ /* This denotes a function that is to perform strubbing internally,
+ without any changes to its interface (the function is turned into
+ a strubbing wrapper, and its original body is moved to a separate
+ STRUB_WRAPPED function, with a modified interface). Functions
+ may be explicitly marked with attribute strub(2), and the
+ attribute must be visible at the point of definition. Functions
+ that are explicitly marked for strubbing, for accessing variables
+ explicitly marked as such, may select this strubbing mode if
+ their interface cannot change, e.g. because its interface is
+ visible to other translation units, directly, by indirection
+ (having its address taken), inheritance, etc. Functions that use
+ this method must not have the noclone attribute, nor the noipa
+ one. Functions marked as always_inline may select this mode, but
+ they are NOT wrapped, they remain unchanged, and are only inlined
+ into strubbed contexts. Once non-always_inline functions are
+ wrapped, the wrapper becomes STRUB_WRAPPER, and the wrapped becomes
+ STRUB_WRAPPED. */
+ STRUB_INTERNAL = 2,
+
+ /* This denotes a function whose stack is not strubbed, but that is
+ nevertheless explicitly or implicitly marked as callable from strubbing
+ functions. Normally, only STRUB_AT_CALLS (and STRUB_INTERNAL ->
+ STRUB_WRAPPED) functions can be called from strubbing contexts (bodies of
+ STRUB_AT_CALLS, STRUB_INTERNAL and STRUB_WRAPPED functions), but attribute
+ strub(3) enables other functions to be (indirectly) called from these
+ contexts. Some builtins and internal functions may be implicitly marked as
+ STRUB_CALLABLE. */
+ STRUB_CALLABLE = 3,
+
+ /* This denotes the function that took over the body of a
+ STRUB_INTERNAL function. At first, it's only called by its
+ wrapper, but the wrapper may be inlined. The wrapped function,
+ in turn, can only be inlined into other functions whose stack
+ frames are strubbed, i.e., that are STRUB_WRAPPED or
+ STRUB_AT_CALLS. */
+ STRUB_WRAPPED = -1,
+
+ /* This denotes the wrapper function that replaced the STRUB_INTERNAL
+ function. This mode overrides the STRUB_INTERNAL mode at the time the
+ internal to-be-wrapped function becomes a wrapper, so that inlining logic
+ can tell one from the other. */
+ STRUB_WRAPPER = -2,
+
+ /* This denotes an always_inline function that requires strubbing. It can
+ only be called from, and inlined into, other strubbing contexts. */
+ STRUB_INLINABLE = -3,
+};
+
+static tree
+get_strub_attr_from_type (tree type)
+{
+ return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
+}
+
+static tree
+get_strub_attr_from_decl (tree decl)
+{
+ tree ret = lookup_attribute ("strub", DECL_ATTRIBUTES (decl));
+ if (ret)
+ return ret;
+ return get_strub_attr_from_type (TREE_TYPE (decl));
+}
+
+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);
+
+#if 0 /* ??? use symbolic mode names with interned strings? */
+ char *s = NULL;
+
+ switch (strub_mode)
+ {
+
+ }
+#endif
+}
+
+static enum strub_mode
+get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
+{
+ enum strub_mode mode = STRUB_DISABLED;
+
+ if (strub_attr)
+ {
+ 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 /* ??? Support symbolic mode names? */
+ gcc_unreachable ();
+ }
+
+ return mode;
+}
+
+static enum strub_mode
+get_strub_mode (cgraph_node *node)
+{
+ return get_strub_mode_from_attr (get_strub_attr_from_decl (node->decl));
+}
+
+static enum strub_mode
+get_strub_mode_from_type (tree type)
+{
+ bool var_p = !FUNC_OR_METHOD_TYPE_P (type);
+ tree attr = get_strub_attr_from_type (type);
+
+ if (attr)
+ return get_strub_mode_from_attr (attr, var_p);
+
+ if (flag_strub > 0 && !var_p)
+ return STRUB_CALLABLE;
+
+ return STRUB_DISABLED;
+}
+
+static bool
+calls_builtin_va_start_p (cgraph_node *node)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (fndecl_built_in_p (cdecl, BUILT_IN_VA_START))
+ return true;
+ }
+
+ return result;
+}
+
+static bool
+calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!fndecl_built_in_p (cdecl, BUILT_IN_APPLY_ARGS))
+ continue;
+
+ result = true;
+
+ if (!report)
+ break;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "at-calls strub does not support call to %qD",
+ cdecl);
+ }
+
+ return result;
+}
+
+static inline bool
+strub_always_inline_p (cgraph_node *node)
+{
+ return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
+}
+
+static inline bool
+can_strub_p (cgraph_node *node, bool report = false)
+{
+ bool result = true;
+
+ if (!report && strub_always_inline_p (node))
+ return result;
+
+ if (lookup_attribute ("noipa", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for strub because of attribute %<noipa%>",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+can_strub_at_calls_p (cgraph_node *node, bool report = false)
+{
+ bool result = !report || can_strub_p (node, report);
+
+ if (!result && !report)
+ return result;
+
+ return !calls_builtin_apply_args_p (node, report);
+}
+
+#define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
+
+/* We can't perform internal strubbing if the function body involves certain
+ features:
+
+ - a non-default __builtin_va_start (e.g. x86's __builtin_ms_va_start) is
+ currently unsupported because we can't discover the corresponding va_copy and
+ va_end decls in the wrapper, and we don't convey the alternate variable
+ arguments ABI to the modified wrapped function. The default
+ __builtin_va_start is supported by calling va_start/va_end at the wrapper,
+ that takes variable arguments, passing a pointer to the va_list object to the
+ wrapped function, that runs va_copy from it where the original function ran
+ va_start.
+
+ __builtin_next_arg is currently unsupported because the wrapped function
+ won't be a variable argument function. We could process it in the wrapper,
+ that remains a variable argument function, and replace calls in the wrapped
+ body, but we currently don't.
+
+ __builtin_return_address is rejected because it's generally used when the
+ actual caller matters, and introducing a wrapper breaks such uses as those in
+ the unwinder. */
+
+static bool
+can_strub_internally_p (cgraph_node *node, bool report = false)
+{
+ bool result = !report || can_strub_p (node, report);
+
+ if (!result && !report)
+ return result;
+
+ if (!report && strub_always_inline_p (node))
+ return result;
+
+ if (lookup_attribute ("noclone", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for internal strub"
+ " because of attribute %<noclone%>",
+ node->decl);
+ }
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!((fndecl_built_in_p (cdecl, BUILT_IN_VA_START)
+ && cdecl != builtin_decl_explicit (BUILT_IN_VA_START))
+ || fndecl_built_in_p (cdecl, BUILT_IN_NEXT_ARG)
+ || fndecl_built_in_p (cdecl, BUILT_IN_RETURN_ADDRESS)))
+ continue;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "internal strub does not support call to %qD",
+ cdecl);
+ }
+
+ basic_block bb;
+ if (node->has_gimple_body_p ())
+ {
+ /* Label values referenced 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. */
+
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ glabel *label_stmt = dyn_cast <glabel *> (gsi_stmt (gsi));
+ tree target;
+
+ if (!label_stmt)
+ break;
+
+ 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;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (label_stmt),
+ "internal strub does not support user labels");
+ }
+ }
+
+ if (list_length (TYPE_ARG_TYPES (TREE_TYPE (node->decl)))
+ >= (((HOST_WIDE_INT) 1 << IPA_PARAM_MAX_INDEX_BITS)
+ - STRUB_INTERNAL_MAX_EXTRA_ARGS))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD has too many arguments for internal strub",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+strub_from_body_p (cgraph_node *node)
+{
+ if (!node->has_gimple_body_p ())
+ return false;
+
+ /* If any local variable is marked for strub... */
+ unsigned i;
+ tree var;
+ FOR_EACH_LOCAL_DECL (DECL_STRUCT_FUNCTION (node->decl),
+ i, var)
+ if (get_strub_mode_from_type (TREE_TYPE (var))
+ != STRUB_DISABLED)
+ return true;
+
+ /* Now scan the body for loads with strub types. */
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!gimple_assign_load_p (stmt))
+ continue;
+
+ tree rhs = gimple_assign_rhs1 (stmt);
+ if (get_strub_mode_from_type (TREE_TYPE (rhs))
+ != STRUB_DISABLED)
+ return true;
+ }
+
+ return false;
+}
+
+/* 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)
+{
+ if (DECL_BUILT_IN_CLASS (node->decl) != BUILT_IN_NORMAL)
+ return false;
+
+ enum built_in_function fcode = DECL_FUNCTION_CODE (node->decl);
+
+ switch (fcode)
+ {
+ case BUILT_IN_NONE:
+ gcc_unreachable ();
+
+ /* ??? Make all builtins callable. We wish to make any builtin call the
+ compiler might introduce on its own callable. Anything that is
+ predictable enough as to be known not to allow stack data that should
+ be strubbed to unintentionally escape to non-strub contexts can be
+ allowed, and pretty much every builtin appears to fit this description.
+ The exceptions to this rule seem to be rare, and only available as
+ explicit __builtin calls, so let's keep it simple and allow all of
+ them... */
+ default:
+ return true;
+ }
+}
+
+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);
+
+ /* 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;
+ /* 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
+ safely undergo at-calls strubbing. Internal mode will still be used in
+ functions that request it explicitly with attribute strub(2), or when the
+ function body requires strubbing and at-calls strubbing is not viable. */
+ const bool strub_flag_at_calls = flag_strub == 1;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo internal strubbing. At-calls mode will still be used in
+ functions that requiest it explicitly with attribute strub() or strub(1),
+ or when the function body requires strubbing and internal strubbing is not
+ viable. */
+ const bool strub_flag_internal = flag_strub == 2;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo strubbing in either mode. When both modes are viable,
+ at-calls is preferred. */
+ const bool strub_flag_either = flag_strub == 3;
+ /* Besides the default behavior, enable strub implicitly for all viable
+ functions. */
+ const bool strub_flag_viable = flag_strub > 0;
+
+ /* The consider_* variables should be true if selecting the corresponding
+ strub modes would be consistent with requests from attributes and command
+ line flags. Attributes associated with functions pretty much mandate a
+ selection, and should report an error if not satisfied; strub_flag_auto
+ implicitly enables some viable strub mode if that's required by references
+ to variables marked for strub; strub_flag_viable enables strub if viable
+ (even when favoring one mode, body-requested strub can still be satisfied
+ by either mode), and falls back to callable, silently unless variables
+ require strubbing. */
+
+ const bool consider_at_calls
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_AT_CALLS
+ : true));
+ const bool consider_internal
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_INTERNAL
+ : true));
+
+ const bool consider_callable
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_CALLABLE
+ : (strub_flag_viable
+ || strub_callable_builtin_p (node))));
+
+ /* This is a shorthand for either strub-enabled mode. */
+ const bool consider_strub
+ = (consider_at_calls || consider_internal);
+
+ /* We can cope with always_inline functions even with noipa and noclone,
+ because we just leave them alone. */
+ const bool is_always_inline
+ = strub_always_inline_p (node);
+
+ /* Strubbing in general, and each specific strub mode, may have its own set of
+ requirements. We require noipa for strubbing, either because of cloning
+ required for internal strub, or because of caller enumeration required for
+ at-calls strub. We don't consider the at-calls mode eligible if it's not
+ even considered, it has no further requirements. Internal mode requires
+ cloning and the absence of certain features in the body and, like at-calls,
+ it's not eligible if it's not even under consideration.
+
+ ??? Do we need target hooks for further constraints? E.g., x86's
+ "interrupt" attribute breaks internal strubbing because the wrapped clone
+ carries the attribute and thus isn't callable; in this case, we could use a
+ target hook to adjust the clone instead. */
+ const bool strub_eligible
+ = (consider_strub
+ && (is_always_inline || can_strub_p (node)));
+ const bool at_calls_eligible
+ = (consider_at_calls && strub_eligible
+ && can_strub_at_calls_p (node));
+ const bool internal_eligible
+ = (consider_internal && strub_eligible
+ && (is_always_inline
+ || can_strub_internally_p (node)));
+
+ /* In addition to the strict eligibility requirements, some additional
+ constraints are placed on implicit selection of certain modes. These do
+ not prevent the selection of a mode if explicitly specified as part of a
+ function interface (the strub attribute), but they may prevent modes from
+ being selected by the command line or by function bodies. The only actual
+ constraint is on at-calls mode: since we change the function's exposed
+ signature, we won't do it implicitly if the function can possibly be used
+ in ways that do not expect the signature change, e.g., if the function is
+ available to or interposable by other units, if its address is taken,
+ etc. */
+ const bool at_calls_viable
+ = (at_calls_eligible
+ && (strub_attr
+ || (node->has_gimple_body_p ()
+ && node->get_availability () > AVAIL_INTERPOSABLE
+ && ((!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->binds_to_current_def_p ()
+ && node->can_be_local_p ()))
+ && node->only_called_directly_p ())));
+ const bool internal_viable
+ = (internal_eligible);
+
+ /* Shorthand. */
+ const bool strub_viable
+ = (at_calls_viable || internal_viable);
+
+ /* We wish to analyze the body, to look for implicit requests for strub, both
+ to implicitly enable it when the body calls for it, and to report errors if
+ the body calls for it but neither mode is viable (even if that follows from
+ non-eligibility because of the explicit specification of some non-strubbing
+ mode). We can refrain from scanning the body only in rare circumstances:
+ when strub is enabled by a function attribute (scanning might be redundant
+ in telling us to also enable it), and when we are enabling strub implicitly
+ but there are non-viable modes: we want to know whether strubbing is
+ required, to fallback to another mode, even if we're only enabling a
+ certain mode, or, when either mode would do, to report an error if neither
+ happens to be viable. */
+ const bool analyze_body
+ = (strub_attr
+ ? !consider_strub
+ : (strub_flag_auto
+ || (strub_flag_viable && (!at_calls_viable && !internal_viable))
+ || (strub_flag_either && !strub_viable)));
+
+ /* Cases in which strubbing is enabled or disabled by strub_flag_auto.
+ Unsatisfiable requests ought to be reported. */
+ const bool strub_required
+ = ((strub_attr && consider_strub)
+ || (analyze_body && strub_from_body_p (node)));
+
+ /* Besides the required cases, we want to abide by the requests to enabling on
+ an if-viable basis. */
+ const bool strub_enable
+ = (strub_required
+ || (strub_flag_at_calls && at_calls_viable)
+ || (strub_flag_internal && internal_viable)
+ || (strub_flag_either && strub_viable));
+
+ /* And now we're finally ready to select a mode that abides by the viability
+ and eligibility constraints, and that satisfies the strubbing requirements
+ and requests, subject to the constraints. If both modes are viable and
+ strub is to be enabled, pick STRUB_AT_CALLS unless STRUB_INTERNAL was named
+ as preferred. */
+ 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
+ : (strub_enable && at_calls_viable)
+ ? STRUB_AT_CALLS
+ : consider_callable
+ ? STRUB_CALLABLE
+ : STRUB_DISABLED);
+
+ switch (mode)
+ {
+ case STRUB_CALLABLE:
+ if (is_always_inline)
+ break;
+ /* Fall through. */
+
+ case STRUB_DISABLED:
+ if (strub_enable && !strub_attr)
+ {
+ gcc_checking_assert (analyze_body);
+ error_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD requires strub, but no viable strub mode was found",
+ node->decl);
+ break;
+ }
+ /* Fall through. */
+
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ /* Differences from an mode requested through a function attribute are
+ reported in set_strub_mode_to. */
+ break;
+
+ case STRUB_WRAPPED:
+ case STRUB_WRAPPER:
+ default:
+ gcc_unreachable ();
+ }
+
+ return mode;
+}
+
+static void
+set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+ enum strub_mode req_mode = get_strub_mode_from_attr (attr);
+
+ if (attr)
+ {
+ /* Check for and report incompatible mode changes. */
+ if (mode != req_mode
+ && !(req_mode == STRUB_INTERNAL
+ && (mode == STRUB_WRAPPED
+ || mode == STRUB_WRAPPER))
+ && !((req_mode == STRUB_INTERNAL
+ || req_mode == STRUB_AT_CALLS
+ || req_mode == STRUB_CALLABLE)
+ && 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));
+ if (node->alias)
+ {
+ cgraph_node *target = node->ultimate_alias_target ();
+ if (target != node)
+ error_at (DECL_SOURCE_LOCATION (target->decl),
+ "the incompatible selection was determined"
+ " by ultimate alias target %qD",
+ target->decl);
+ }
+
+ /* Report any incompatibilities with explicitly-requested strub. */
+ switch (req_mode)
+ {
+ case STRUB_AT_CALLS:
+ can_strub_at_calls_p (node, true);
+ break;
+
+ case STRUB_INTERNAL:
+ can_strub_internally_p (node, true);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Drop any incompatible strub attributes leading the decl attribute
+ chain. Return if we find one with the mode we need. */
+ for (;;)
+ {
+ if (mode == req_mode)
+ return;
+
+ if (DECL_ATTRIBUTES (node->decl) != attr)
+ break;
+
+ DECL_ATTRIBUTES (node->decl) = TREE_CHAIN (attr);
+ attr = get_strub_attr_from_decl (node->decl);
+ if (!attr)
+ break;
+
+ req_mode = get_strub_mode_from_attr (attr);
+ }
+ }
+ else if (mode == req_mode)
+ return;
+
+ DECL_ATTRIBUTES (node->decl) = tree_cons (get_identifier ("strub"),
+ get_strub_mode_attr_value (mode),
+ DECL_ATTRIBUTES (node->decl));
+}
+
+static enum strub_mode
+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))
+ {
+ /* 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_DISABLED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ cgraph_node *xnode = node;
+ if (node->alias)
+ xnode = node->ultimate_alias_target ();
+ /* Weakrefs may remain unresolved (the above will return node) if
+ their targets are not defined, so make sure we compute a strub
+ mode for them, instead of defaulting to STRUB_DISABLED and
+ rendering them uncallable. */
+ enum strub_mode mode = (xnode != node && !xnode->alias
+ ? get_strub_mode (xnode)
+ : compute_strub_mode (node, attr));
+
+ set_strub_mode_to (node, mode);
+
+ return mode;
+}
+
+/* Non-strub functions shouldn't be called from strub functions,
+ except through callable ones. Always inline strub functions can
+ only be called from strub functions. */
+
+static bool
+strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode caller_mode = get_strub_mode (caller);
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return callee_mode != STRUB_INLINABLE;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ return (flag_strub >= 0);
+
+ case STRUB_DISABLED:
+ return false;
+
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ 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. */
+
+bool
+strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return true;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ strub_mode caller_mode = get_strub_mode (caller);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ return true;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ return false;
+}
+
+/* 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;
+
+ 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_WRAPPED
+ || caller_mode == STRUB_INLINABLE);
+
+ 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);
+ }
+
+ /* ??? 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 {
+
+const pass_data pass_data_ipa_strub_mode = {
+ SIMPLE_IPA_PASS,
+ "strubm",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ 0, // properties_finish
+};
+
+class pass_ipa_strub_mode : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub_mode (gcc::context *ctxt)
+ : simple_ipa_opt_pass (pass_data_ipa_strub_mode, ctxt)
+ {}
+ 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
+ overhead. */
+ if (flag_strub == -2)
+ flag_strub = 0;
+ return flag_strub;
+ }
+ virtual unsigned int execute (function *);
+};
+
+const pass_data pass_data_ipa_strub = {
+ SIMPLE_IPA_PASS,
+ "strub",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ TODO_update_ssa
+ | TODO_cleanup_cfg
+ | TODO_rebuild_cgraph_edges
+ | TODO_verify_il, // properties_finish
+};
+
+class pass_ipa_strub : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub (gcc::context *ctxt)
+ : 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 unsigned int execute (function *);
+
+#define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST) \
+ static tree get_ ## NAME () { \
+ tree decl = builtin_decl_explicit (CODE); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ decl = add_builtin_function \
+ ("__builtin_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ NULL, NULL); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ } \
+ return decl; \
+ }
+
+ DEF_NM_BUILTIN (stack_address,
+ BUILT_IN_STACK_ADDRESS,
+ (ptr_type_node, NULL))
+
+#undef DEF_NM_BUILTIN
+
+#define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST) \
+ static tree get_ ## NAME () { \
+ tree decl = builtin_decl_explicit (CODE); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ tree attrs = NULL; \
+ if (FNSPEC && HAVE_ATTR_FNSPEC) \
+ attrs = tree_cons (get_identifier ("fn spec"), \
+ build_tree_list \
+ (NULL_TREE, \
+ build_string (strlen (FNSPEC), \
+ (FNSPEC))), \
+ attrs); \
+ decl = add_builtin_function_ext_scope \
+ ("__builtin___strub_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ "__strub_" #NAME, attrs); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ } \
+ return decl; \
+ }
+
+ DEF_SS_BUILTIN (enter, ". Ot",
+ BUILT_IN___STRUB_ENTER,
+ (void_type_node, get_pptr (), NULL))
+ DEF_SS_BUILTIN (update, ". Wt",
+ BUILT_IN___STRUB_UPDATE,
+ (void_type_node, get_pptr (), NULL))
+ DEF_SS_BUILTIN (leave, ". w ",
+ BUILT_IN___STRUB_LEAVE,
+ (void_type_node, get_pptr (), NULL))
+
+#undef DEF_SS_BUILTIN
+
+#define DEF_IDENT(NAME) \
+ static inline tree get_ ## NAME () { \
+ static tree identifier = NULL_TREE; \
+ if (!identifier) \
+ identifier = get_identifier (".strub." #NAME); \
+ return identifier; \
+ }
+
+ DEF_IDENT (watermark_ptr)
+ DEF_IDENT (va_list_ptr)
+ DEF_IDENT (apply_args_ptr)
+
+#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 gimple_seq
+ call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
+ gimple_seq seq = NULL)
+ {
+ tree uwm = get_update ();
+ gcall *update = gimple_build_call (uwm, 1, wmptr);
+ gimple_seq_add_stmt (&seq, update);
+ if (node)
+#if !IMPLICIT_CGRAPH_EDGES
+ node->create_edge (cgraph_node::get_create (uwm), update, count, false);
+#else
+ (void)count;
+#endif
+ return seq;
+ }
+
+};
+
+} // anon namespace
+
+typedef hash_set<tree> indirect_parms_t;
+
+static tree
+maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
+{
+ if (DECL_P (op))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (op))
+ {
+ tree ret = gimple_fold_indirect_ref (op);
+ if (!ret)
+ ret = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (op)),
+ op,
+ build_int_cst (TREE_TYPE (op), 0));
+ return ret;
+ }
+ }
+ else if (TREE_CODE (op) == ADDR_EXPR
+ && DECL_P (TREE_OPERAND (op, 0)))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (TREE_OPERAND (op, 0)))
+ return TREE_OPERAND (op, 0);
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_make_indirect (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ indirect_parms_t &indirect_parms = *(indirect_parms_t *)wi->info;
+
+ if (!*op || TYPE_P (*op))
+ {
+ *rec = 0;
+ return NULL_TREE;
+ }
+
+ if (tree repl = maybe_make_indirect (indirect_parms, *op, rec))
+ {
+ *op = repl;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ gimple_stmt_iterator &gsi = *(gimple_stmt_iterator *)wi->info;
+
+ *rec = 0;
+
+ if (!*op || TREE_CODE (*op) != ADDR_EXPR)
+ return NULL_TREE;
+
+ if (!is_gimple_val (*op))
+ {
+ tree ret = force_gimple_operand_gsi (&gsi, *op, true,
+ NULL_TREE, true, GSI_SAME_STMT);
+ gcc_assert (ret != *op);
+ *op = ret;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+build_ref_type_for (tree parm, bool nonaliased = true)
+{
+ gcc_checking_assert (TREE_CODE (parm) == PARM_DECL);
+
+ tree ref_type = build_reference_type (TREE_TYPE (parm));
+
+ if (!nonaliased)
+ return ref_type;
+
+ /* Each PARM turned indirect still points to the distinct memory area at the
+ wrapper, and the reference in unchanging, so we might qualify it, but...
+ const is not really important, since we're only using default defs for the
+ reference parm anyway, and not introducing any defs, and restrict seems to
+ cause trouble. E.g., libgnat/s-concat3.adb:str_concat_3 has memmoves that,
+ if it's wrapped, the memmoves are deleted in dse1. Using a distinct alias
+ set seems to not run afoul of this problem, and it hopefully enables the
+ compiler to tell the pointers do point to objects that are not otherwise
+ aliased. */
+#if 1
+ tree qref_type = build_variant_type_copy (ref_type);
+
+ TYPE_ALIAS_SET (qref_type) = new_alias_set ();
+ record_alias_subset (TYPE_ALIAS_SET (qref_type), get_alias_set (ref_type));
+
+ return qref_type;
+#else
+ tree qref_type = build_qualified_type (ref_type,
+ TYPE_QUAL_RESTRICT
+ | TYPE_QUAL_CONST);
+
+ return qref_type;
+#endif
+}
+
+/* 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)
+{
+#if IMPLICIT_CGRAPH_EDGES
+ return;
+#endif
+
+ cgraph_node *node = cgraph_node::get_create (current_function_decl);
+
+ for (gimple_stmt_iterator gsi = gsi_start (seq);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!is_a <gcall *> (stmt))
+ 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);
+ }
+}
+
+static void
+gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
+{
+ gimple *stmt = gsi_stmt (gsi);
+
+ gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+ 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;
+ bool nothrow_p = (must_not_throw_p
+ || (call && gimple_call_nothrow_p (call))
+ || (eh_lp <= 0
+ && (TREE_NOTHROW (cfun->decl)
+ || !flag_exceptions)));
+
+ if (noreturn_p && nothrow_p)
+ return;
+
+ /* Don't expect an EH edge if we're not to throw, or if we're not in an EH
+ region yet. */
+ bool no_eh_edge_p = (nothrow_p || !eh_lp);
+ bool must_end_bb = stmt_ends_bb_p (stmt);
+
+ edge eft = NULL, eeh = NULL;
+ if (must_end_bb && !(noreturn_p && no_eh_edge_p))
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+
+ edge e;
+ edge_iterator ei;
+ FOR_EACH_EDGE (e, ei, gsi_bb (gsi)->succs)
+ {
+ if ((e->flags & EDGE_EH))
+ {
+ gcc_checking_assert (!eeh);
+ eeh = e;
+#if !CHECKING_P
+ if (eft || noreturn_p)
+ break;
+#endif
+ }
+ if ((e->flags & EDGE_FALLTHRU))
+ {
+ gcc_checking_assert (!eft);
+ eft = e;
+#if !CHECKING_P
+ if (eeh || no_eh_edge_p)
+ break;
+#endif
+ }
+ }
+
+ gcc_checking_assert (!(eft && (eft->flags & EDGE_FALLTHRU))
+ == noreturn_p);
+ gcc_checking_assert (!(eeh && (eeh->flags & EDGE_EH))
+ == no_eh_edge_p);
+ gcc_checking_assert (eft != eeh);
+ }
+
+ if (!noreturn_p)
+ {
+ gimple_seq nseq = nothrow_p ? seq : gimple_seq_copy (seq);
+
+ if (must_end_bb)
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+ add_call_edges_for_seq (nseq, eft->count ());
+ gsi_insert_seq_on_edge_immediate (eft, nseq);
+ }
+ else
+ {
+ add_call_edges_for_seq (nseq, gsi_bb (gsi)->count);
+ gsi_insert_seq_after (&gsi, nseq, GSI_SAME_STMT);
+ }
+ }
+
+ if (nothrow_p)
+ return;
+
+ if (eh_lp)
+ {
+ add_call_edges_for_seq (seq, eeh->count ());
+ gsi_insert_seq_on_edge_immediate (eeh, seq);
+ return;
+ }
+
+ /* A throwing call may appear within a basic block in a function that doesn't
+ have any EH regions. We're going to add a cleanup if so, therefore the
+ block will have to be split. */
+ basic_block bb = gsi_bb (gsi);
+ if (!gsi_one_before_end_p (gsi))
+ split_block (bb, stmt);
+
+ /* Create a new block for the EH cleanup. */
+ basic_block bb_eh_cleanup = create_empty_bb (bb);
+ if (dom_info_available_p (CDI_DOMINATORS))
+ set_immediate_dominator (CDI_DOMINATORS, bb_eh_cleanup, bb);
+ if (current_loops)
+ add_bb_to_loop (bb_eh_cleanup, current_loops->tree_root);
+
+ /* Make the new block an EH cleanup for the call. */
+ eh_region new_r = gen_eh_region_cleanup (NULL);
+ eh_landing_pad lp = gen_eh_landing_pad (new_r);
+ tree label = gimple_block_label (bb_eh_cleanup);
+ lp->post_landing_pad = label;
+ EH_LANDING_PAD_NR (label) = lp->index;
+ add_stmt_to_eh_lp (stmt, lp->index);
+
+ /* Add the cleanup code to the EH cleanup block. */
+ gsi = gsi_after_labels (bb_eh_cleanup);
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
+
+ /* And then propagate the exception further. */
+ gresx *resx = gimple_build_resx (new_r->index);
+ gsi_insert_before (&gsi, resx, GSI_SAME_STMT);
+
+ /* Finally, wire the EH cleanup block into the CFG. */
+ make_eh_edges (stmt);
+ add_call_edges_for_seq (seq, single_pred_edge (bb_eh_cleanup)->count ());
+}
+
+/* Copy the attribute list at *ATTRS, minus any NAME attributes, leaving
+ shareable trailing nodes alone. */
+
+static inline void
+remove_named_attribute_unsharing (const char *name, tree *attrs)
+{
+ while (tree found = lookup_attribute (name, *attrs))
+ {
+ /* Copy nodes up to the next NAME attribute. */
+ while (*attrs != found)
+ {
+ *attrs = tree_cons (TREE_PURPOSE (*attrs),
+ TREE_VALUE (*attrs),
+ TREE_CHAIN (*attrs));
+ attrs = &TREE_CHAIN (*attrs);
+ }
+ /* Then drop it. */
+ gcc_checking_assert (*attrs == found);
+ *attrs = TREE_CHAIN (*attrs);
+ }
+}
+
+static int last_cgraph_order;
+
+static bool
+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;
+
+ 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. */
+ for (int aliases = 0; aliases <= 1; aliases++)
+ FOR_EACH_FUNCTION (node)
+ {
+ if (!node->alias != !aliases)
+ continue;
+
+ /* Already done. */
+ if (node->order < last_cgraph_order)
+ continue;
+
+ enum strub_mode mode = set_strub_mode (node);
+
+ if (mode == STRUB_AT_CALLS || mode == STRUB_INTERNAL)
+ any_strub = true;
+ }
+
+ if (any_strub)
+ last_cgraph_order = symtab->order;
+
+ return any_strub;
+}
+
+unsigned int
+pass_ipa_strub_mode::execute (function *)
+{
+ last_cgraph_order = 0;
+ bool any_strub = ipa_strub_set_mode_for_new_functions ();
+
+ if (!any_strub)
+ flag_strub = 0;
+ else
+ /* Verify before any inlining or other transformations. */
+ verify_strub ();
+
+ return 0;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub_mode (gcc::context *ctxt)
+{
+ return new pass_ipa_strub_mode (ctxt);
+}
+
+unsigned int
+pass_ipa_strub::execute (function *)
+{
+ cgraph_node *onode;
+
+ ipa_strub_set_mode_for_new_functions ();
+
+ FOR_EACH_FUNCTION (onode)
+ {
+ enum strub_mode mode = get_strub_mode (onode);
+
+ if (mode == STRUB_AT_CALLS)
+ {
+ 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));
+
+ 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. */
+ 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;
+
+ tree wmptr = build_decl (DECL_SOURCE_LOCATION (onode->decl),
+ PARM_DECL,
+ get_watermark_ptr (),
+ get_qpptr ());
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_ARG_TYPE (wmptr) = get_qpptr ();
+ DECL_CONTEXT (wmptr) = onode->decl;
+ TREE_USED (wmptr) = 1;
+ DECL_CHAIN (wmptr) = *pargs;
+ *pargs = wmptr;
+
+ if (onode->alias)
+ continue;
+
+ 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);
+
+ 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));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca)
+ {
+ 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);
+
+ if (!is_gimple_call (stmt))
+ continue;
+
+ gcall *call = as_a <gcall *> (stmt);
+
+ if (gimple_alloca_call_p (call))
+ {
+ /* Capture stack growth. */
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)
+ ->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ }
+ }
+
+ pop_cfun ();
+
+#if 0
+ compute_fn_summary (onode, true);
+#endif
+ continue;
+ }
+
+ if (mode != STRUB_INTERNAL
+ || !onode->has_gimple_body_p ())
+ 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 (!DECL_STRUCT_FUNCTION (onode->decl))
+ {
+ inform (DECL_SOURCE_LOCATION (onode->decl),
+ "not splitting struct-less function %qD for stack scrubbing",
+ onode->decl);
+ continue;
+ }
+
+ if (!onode->lowered)
+ {
+ inform (DECL_SOURCE_LOCATION (onode->decl),
+ "not splitting non-lowered function %qD for stack scrubbing",
+ 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 stack scrubbing",
+ onode->decl);
+ continue;
+ }
+
+ bool is_stdarg = calls_builtin_va_start_p (onode);;
+ bool apply_args = calls_builtin_apply_args_p (onode);
+
+ vec<ipa_adjusted_param, va_gc> *nparms = NULL;
+ unsigned j = 0;
+ {
+ // The following loop copied from ipa-split.c:split_function.
+ for (tree parm = DECL_ARGUMENTS (onode->decl);
+ parm; parm = DECL_CHAIN (parm), j++)
+ {
+ ipa_adjusted_param adj = {};
+ adj.op = IPA_PARAM_OP_COPY;
+ adj.base_index = j;
+ adj.prev_clone_index = j;
+ vec_safe_push (nparms, adj);
+ }
+
+ if (apply_args)
+ {
+ ipa_adjusted_param aaadj = {};
+ aaadj.op = IPA_PARAM_OP_NEW;
+ aaadj.type = get_qpptr ();
+ vec_safe_push (nparms, aaadj);
+ }
+
+ if (is_stdarg)
+ {
+ ipa_adjusted_param vladj = {};
+ vladj.op = IPA_PARAM_OP_NEW;
+ vladj.type = get_qpvalst ();
+ vec_safe_push (nparms, vladj);
+ }
+
+ ipa_adjusted_param wmadj = {};
+ wmadj.op = IPA_PARAM_OP_NEW;
+ wmadj.type = get_qpptr ();
+ vec_safe_push (nparms, wmadj);
+ }
+ ipa_param_adjustments adj (nparms, -1, false);
+
+ cgraph_node *nnode = onode->create_version_clone_with_body
+ (auto_vec<cgraph_edge *> (0),
+ NULL, &adj, NULL, NULL, "strub", NULL);
+
+ if (!nnode)
+ {
+ error_at (DECL_SOURCE_LOCATION (onode->decl),
+ "failed to split %qD for stack scrubbing",
+ onode->decl);
+ continue;
+ }
+
+ onode->split_part = true;
+ if (onode->calls_comdat_local)
+ nnode->add_to_same_comdat_group (onode);
+
+ gcc_checking_assert (!DECL_STRUCT_FUNCTION (nnode->decl)->stdarg);
+
+ set_strub_mode_to (onode, STRUB_WRAPPER);
+ set_strub_mode_to (nnode, STRUB_WRAPPED);
+
+ /* Decide which of the wrapped function's parms we want to turn into
+ references to the argument passed to the wrapper. In general, we want to
+ copy small arguments, and avoid copying large ones. Variable-sized array
+ lengths given by other arguments, as in 20020210-1.c, would lead to
+ problems if passed by value, after resetting the original function and
+ dropping the length computation; passing them by reference works.
+ DECL_BY_REFERENCE is *not* a substitute for this: it involves copying
+ anyway, but performed at the caller. */
+ indirect_parms_t indirect_nparms (3, false);
+ unsigned adjust_ftype = 0;
+ unsigned named_args = 0;
+ for (tree parm = DECL_ARGUMENTS (onode->decl),
+ nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (TREE_TYPE (nnode->decl));
+ parm;
+ named_args++,
+ parm = DECL_CHAIN (parm),
+ nparm = DECL_CHAIN (nparm),
+ nparmt = nparmt ? TREE_CHAIN (nparmt) : NULL_TREE)
+ if (!(0 /* DECL_BY_REFERENCE (narg) */
+ || is_gimple_reg_type (TREE_TYPE (nparm))
+ || VECTOR_TYPE_P (TREE_TYPE (nparm))
+ || TREE_CODE (TREE_TYPE (nparm)) == COMPLEX_TYPE
+ || (tree_fits_uhwi_p (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ && (tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ <= 4 * UNITS_PER_WORD))))
+ {
+ indirect_nparms.add (nparm);
+
+ /* ??? Is there any case in which it is not safe to suggest the parms
+ turned indirect don't alias anything else? They are distinct,
+ unaliased memory in the wrapper, and the wrapped can't possibly
+ take pointers into them because none of the pointers passed to the
+ wrapper can alias other incoming parameters passed by value, even
+ if with transparent reference, and the wrapper doesn't take any
+ extra parms that could point into wrapper's parms. So we can
+ probably drop the TREE_ADDRESSABLE and keep the true. */
+ tree ref_type = build_ref_type_for (nparm,
+ true
+ || !TREE_ADDRESSABLE (parm));
+
+ DECL_ARG_TYPE (nparm) = TREE_TYPE (nparm) = ref_type;
+ relayout_decl (nparm);
+ TREE_ADDRESSABLE (nparm) = 0;
+ DECL_BY_REFERENCE (nparm) = 0;
+#if FOR_GCC_11P
+ DECL_NOT_GIMPLE_REG_P (nparm) = 0;
+#else
+ DECL_GIMPLE_REG_P (nparm) = 1;
+#endif
+ /* ??? This avoids mismatches in debug info bind stmts in
+ e.g. a-chahan . */
+ DECL_ABSTRACT_ORIGIN (nparm) = NULL;
+
+ if (nparmt)
+ adjust_ftype++;
+ }
+
+ /* Also adjust the wrapped function type, if needed. */
+ if (adjust_ftype)
+ {
+ tree nftype = TREE_TYPE (nnode->decl);
+
+ /* We always add at least one argument at the end of the signature, when
+ cloning the function, so we don't expect to need to duplicate the
+ type here. */
+ gcc_checking_assert (TYPE_ARG_TYPES (nftype)
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+
+#if HAVE_ATTR_FNSPEC
+ /* Check that fnspec still works for the modified function signature,
+ and drop it otherwise. */
+ bool drop_fnspec = false;
+ tree fnspec = lookup_attribute ("fn spec", TYPE_ATTRIBUTES (nftype));
+ attr_fnspec spec = fnspec ? attr_fnspec (fnspec) : attr_fnspec ("");
+
+ unsigned retcopy;
+ if (!(fnspec && spec.returns_arg (&retcopy)))
+ retcopy = (unsigned) -1;
+
+ unsigned i = 0;
+#endif
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (nftype);
+ adjust_ftype > 0;
+#if HAVE_ATTR_FNSPEC
+ i++,
+#endif
+ nparm = DECL_CHAIN (nparm), nparmt = TREE_CHAIN (nparmt))
+ if (indirect_nparms.contains (nparm))
+ {
+ TREE_VALUE (nparmt) = TREE_TYPE (nparm);
+ adjust_ftype--;
+
+#if HAVE_ATTR_FNSPEC
+ if (fnspec && !drop_fnspec)
+ {
+ if (i == retcopy)
+ drop_fnspec = true;
+ else if (spec.arg_specified_p (i))
+ {
+ /* Properties that apply to pointers only must not be
+ present, because we don't make pointers further
+ indirect. */
+ gcc_checking_assert
+ (!spec.arg_max_access_size_given_by_arg_p (i, NULL));
+ gcc_checking_assert (!spec.arg_copied_to_arg_p (i, NULL));
+
+ /* Any claim of direct access only is invalidated by
+ adding an indirection level. */
+ if (spec.arg_direct_p (i))
+ drop_fnspec = true;
+
+ /* If there's a claim the argument is not read from, the
+ added indirection invalidates it: if the argument is
+ used at all, then the pointer will necessarily be
+ read. */
+ if (!spec.arg_maybe_read_p (i)
+ && spec.arg_used_p (i))
+ drop_fnspec = true;
+ }
+ }
+#endif
+ }
+
+#if HAVE_ATTR_FNSPEC
+ /* ??? Maybe we could adjust it instead. */
+ if (drop_fnspec)
+ remove_named_attribute_unsharing ("fn spec",
+ &TYPE_ATTRIBUTES (nftype));
+#endif
+
+ TREE_TYPE (nnode->decl) = nftype;
+ }
+
+#if ATTR_FNSPEC_DECONST_WATERMARK
+ {
+ int flags = flags_from_decl_or_type (nnode->decl);
+ tree fnspec = lookup_attribute ("fn spec", TREE_TYPE (nnode->decl));
+
+ if ((flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS)) || fnspec)
+ {
+ size_t xargs = 1 + int (is_stdarg) + int (apply_args);
+ 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! */
+ bool no_writes_p = true;
+ if (fnspec)
+ {
+ tree fnspecstr = TREE_VALUE (TREE_VALUE (fnspec));
+ curlen = TREE_STRING_LENGTH (fnspecstr);
+ memcpy (nspec, TREE_STRING_POINTER (fnspecstr), curlen);
+ if (!(flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS))
+ && curlen >= 2
+ && nspec[1] != 'c' && nspec[1] != 'C'
+ && nspec[1] != 'p' && nspec[1] != 'P')
+ no_writes_p = false;
+ }
+ if (!curlen)
+ {
+ nspec[curlen++] = '.';
+ nspec[curlen++] = ((flags & ECF_CONST)
+ ? 'c'
+ : (flags & ECF_PURE)
+ ? 'p'
+ : ' ');
+ }
+ while (curlen < tgtlen - 2 * xargs)
+ {
+ nspec[curlen++] = '.';
+ nspec[curlen++] = ' ';
+ }
+
+ /* These extra args are unlikely to be present in const or pure
+ functions. It's conceivable that a function that takes variable
+ arguments, or that passes its arguments on to another function,
+ could be const or pure, but it would not modify the arguments, and,
+ being pure or const, it couldn't possibly modify or even access
+ memory referenced by them. But it can read from these internal
+ data structures created by the wrapper, and from any
+ argument-passing memory referenced by them, so we denote the
+ possibility of reading from multiple levels of indirection, but
+ only of reading because const/pure. */
+ if (apply_args)
+ {
+ nspec[curlen++] = (no_writes_p ? 'r' : '.');
+ nspec[curlen++] = (no_writes_p ? 't' : ' ');
+ }
+ if (is_stdarg)
+ {
+ nspec[curlen++] = (no_writes_p ? 'r' : '.');
+ nspec[curlen++] = (no_writes_p ? 't' : ' ');
+ }
+
+ nspec[curlen++] = 'W';
+ nspec[curlen++] = 't';
+
+ /* The type has already been copied before adding parameters. */
+ gcc_checking_assert (TYPE_ARG_TYPES (TREE_TYPE (nnode->decl))
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+ TYPE_ATTRIBUTES (TREE_TYPE (nnode->decl))
+ = tree_cons (get_identifier ("fn spec"),
+ build_tree_list (NULL_TREE,
+ build_string (tgtlen, nspec)),
+ TYPE_ATTRIBUTES (TREE_TYPE (nnode->decl)));
+ }
+ }
+#endif
+
+ {
+ tree decl = onode->decl;
+ cgraph_node *target = nnode;
+
+ { // copied from create_wrapper
+
+ /* Preserve DECL_RESULT so we get right by reference flag. */
+ tree decl_result = DECL_RESULT (decl);
+
+ /* Remove the function's body but keep arguments to be reused
+ for thunk. */
+ onode->release_body (true);
+ onode->reset ();
+
+ DECL_UNINLINABLE (decl) = false;
+ DECL_RESULT (decl) = decl_result;
+ DECL_INITIAL (decl) = NULL;
+ allocate_struct_function (decl, false);
+ set_cfun (NULL);
+
+ /* Turn alias into thunk and expand it into GIMPLE representation. */
+ onode->definition = true;
+
+#if FOR_GCC_11P
+ thunk_info::get_create (onode);
+ onode->thunk = true;
+#else
+ memset (&onode->thunk, 0, sizeof (cgraph_thunk_info));
+ onode->thunk.thunk_p = true;
+ onode->thunk.alias = target->decl;
+#endif
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (target, NULL, onode->count);
+#endif
+ onode->callees->can_throw_external = !TREE_NOTHROW (target->decl);
+
+ tree arguments = DECL_ARGUMENTS (decl);
+
+ while (arguments)
+ {
+ TREE_ADDRESSABLE (arguments) = false;
+ arguments = TREE_CHAIN (arguments);
+ }
+
+ {
+ tree alias = onode->callees->callee->decl;
+ tree thunk_fndecl = decl;
+ tree a;
+
+ int nxargs = 1 + is_stdarg + apply_args;
+
+ { // Simplified from expand_thunk.
+ tree restype;
+ basic_block bb, then_bb, else_bb, return_bb;
+ gimple_stmt_iterator bsi;
+ int nargs = 0;
+ tree arg;
+ int i;
+ tree resdecl;
+ tree restmp = NULL;
+
+ gcall *call;
+ greturn *ret;
+ bool alias_is_noreturn = TREE_THIS_VOLATILE (alias);
+
+ a = DECL_ARGUMENTS (thunk_fndecl);
+
+ current_function_decl = thunk_fndecl;
+
+#if FOR_GCC_11P
+ /* Ensure thunks are emitted in their correct sections. */
+ resolve_unique_section (thunk_fndecl, 0,
+ flag_function_sections);
+#endif
+
+ bitmap_obstack_initialize (NULL);
+
+ /* Build the return declaration for the function. */
+ restype = TREE_TYPE (TREE_TYPE (thunk_fndecl));
+ if (DECL_RESULT (thunk_fndecl) == NULL_TREE)
+ {
+ resdecl = build_decl (input_location, RESULT_DECL, 0, restype);
+ DECL_ARTIFICIAL (resdecl) = 1;
+ DECL_IGNORED_P (resdecl) = 1;
+ DECL_CONTEXT (resdecl) = thunk_fndecl;
+ DECL_RESULT (thunk_fndecl) = resdecl;
+ }
+ else
+ resdecl = DECL_RESULT (thunk_fndecl);
+
+ profile_count cfg_count = onode->count;
+ if (!cfg_count.initialized_p ())
+ cfg_count = profile_count::from_gcov_type (BB_FREQ_MAX).guessed_local ();
+
+ bb = then_bb = else_bb = return_bb
+ = init_lowered_empty_function (thunk_fndecl, true, cfg_count);
+
+ bsi = gsi_start_bb (bb);
+
+ /* Build call to the function being thunked. */
+ if (!VOID_TYPE_P (restype)
+ && (!alias_is_noreturn
+ || TREE_ADDRESSABLE (restype)
+ || TREE_CODE (TYPE_SIZE_UNIT (restype)) != INTEGER_CST))
+ {
+ if (DECL_BY_REFERENCE (resdecl))
+ {
+ restmp = gimple_fold_indirect_ref (resdecl);
+ if (!restmp)
+ restmp = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (resdecl)),
+ resdecl,
+ build_int_cst (TREE_TYPE (resdecl), 0));
+ }
+ else if (!is_gimple_reg_type (restype))
+ {
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl)))
+ {
+ restmp = resdecl;
+
+ if (VAR_P (restmp))
+ {
+ add_local_decl (cfun, restmp);
+ BLOCK_VARS (DECL_INITIAL (current_function_decl))
+ = restmp;
+ }
+ }
+ else
+ restmp = create_tmp_var (restype, "retval");
+ }
+ else
+ restmp = create_tmp_reg (restype, "retval");
+ }
+
+ for (arg = a; arg; arg = DECL_CHAIN (arg))
+ nargs++;
+ auto_vec<tree> vargs (nargs + nxargs);
+ i = 0;
+ arg = a;
+
+ if (nargs)
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl);
+ i < nargs;
+ i++, arg = DECL_CHAIN (arg), nparm = DECL_CHAIN (nparm))
+ {
+ tree save_arg = arg;
+ tree tmp = arg;
+
+ /* Arrange to pass indirectly the parms, if we decided to do
+ so, and revert its type in the wrapper. */
+ if (indirect_nparms.contains (nparm))
+ {
+ tree ref_type = TREE_TYPE (nparm);
+ TREE_ADDRESSABLE (arg) = true;
+ tree addr = build1 (ADDR_EXPR, ref_type, arg);
+ tmp = arg = addr;
+ }
+#if ! FOR_GCC_11P
+ else if (VECTOR_TYPE_P (TREE_TYPE (arg))
+ || TREE_CODE (TREE_TYPE (arg)) == COMPLEX_TYPE)
+ DECL_GIMPLE_REG_P (arg) = 1;
+#else
+ else
+ DECL_NOT_GIMPLE_REG_P (arg) = 0;
+#endif
+
+ /* Convert the argument back to the type used by the calling
+ conventions, e.g. a non-prototyped float type is passed as
+ double, as in 930603-1.c, and needs to be converted back to
+ double to be passed on unchanged to the wrapped
+ function. */
+ if (TREE_TYPE (nparm) != DECL_ARG_TYPE (nparm))
+ arg = fold_convert (DECL_ARG_TYPE (nparm), arg);
+
+ if (!is_gimple_val (arg))
+ {
+ tmp = create_tmp_reg (TYPE_MAIN_VARIANT
+ (TREE_TYPE (arg)), "arg");
+ gimple *stmt = gimple_build_assign (tmp, arg);
+ gsi_insert_after (&bsi, stmt, GSI_NEW_STMT);
+ }
+ vargs.quick_push (tmp);
+ arg = save_arg;
+ }
+ /* These strub arguments are adjusted later. */
+ if (apply_args)
+ vargs.quick_push (null_pointer_node);
+ if (is_stdarg)
+ vargs.quick_push (null_pointer_node);
+ vargs.quick_push (null_pointer_node);
+ call = gimple_build_call_vec (build_fold_addr_expr_loc (0, alias),
+ vargs);
+ onode->callees->call_stmt = call;
+ // gimple_call_set_from_thunk (call, true);
+ if (DECL_STATIC_CHAIN (alias))
+ {
+ tree p = DECL_STRUCT_FUNCTION (alias)->static_chain_decl;
+ tree type = TREE_TYPE (p);
+ tree decl = build_decl (DECL_SOURCE_LOCATION (thunk_fndecl),
+ PARM_DECL, create_tmp_var_name ("CHAIN"),
+ type);
+ DECL_ARTIFICIAL (decl) = 1;
+ DECL_IGNORED_P (decl) = 1;
+ TREE_USED (decl) = 1;
+ DECL_CONTEXT (decl) = thunk_fndecl;
+ DECL_ARG_TYPE (decl) = type;
+ TREE_READONLY (decl) = 1;
+
+ struct function *sf = DECL_STRUCT_FUNCTION (thunk_fndecl);
+ sf->static_chain_decl = decl;
+
+ gimple_call_set_chain (call, decl);
+ }
+
+ /* Return slot optimization is always possible and in fact required to
+ return values with DECL_BY_REFERENCE. */
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl))
+ && (!is_gimple_reg_type (TREE_TYPE (resdecl))
+ || DECL_BY_REFERENCE (resdecl)))
+ gimple_call_set_return_slot_opt (call, true);
+
+ if (restmp)
+ {
+ gimple_call_set_lhs (call, restmp);
+ gcc_assert (useless_type_conversion_p (TREE_TYPE (restmp),
+ TREE_TYPE (TREE_TYPE (alias))));
+ }
+ gsi_insert_after (&bsi, call, GSI_NEW_STMT);
+ if (!alias_is_noreturn)
+ {
+ /* Build return value. */
+ if (!DECL_BY_REFERENCE (resdecl))
+ ret = gimple_build_return (restmp);
+ else
+ ret = gimple_build_return (resdecl);
+
+ gsi_insert_after (&bsi, ret, GSI_NEW_STMT);
+ }
+ else
+ {
+ remove_edge (single_succ_edge (bb));
+ }
+
+ cfun->gimple_df->in_ssa_p = true;
+ update_max_bb_count ();
+ profile_status_for_fn (cfun)
+ = cfg_count.initialized_p () && cfg_count.ipa_p ()
+ ? PROFILE_READ : PROFILE_GUESSED;
+#if FOR_GCC_11P
+ /* FIXME: C++ FE should stop setting TREE_ASM_WRITTEN on thunks. */
+ // TREE_ASM_WRITTEN (thunk_fndecl) = false;
+#endif
+ delete_unreachable_blocks ();
+ update_ssa (TODO_update_ssa);
+ checking_verify_flow_info ();
+ free_dominance_info (CDI_DOMINATORS);
+
+ /* Since we want to emit the thunk, we explicitly mark its name as
+ referenced. */
+#if FOR_GCC_11P
+ onode->thunk = false;
+#else
+ onode->thunk.thunk_p = false;
+#endif
+ onode->lowered = true;
+ bitmap_obstack_release (NULL);
+ }
+ current_function_decl = NULL;
+ set_cfun (NULL);
+ }
+
+#if FOR_GCC_11P
+ thunk_info::remove (onode);
+#endif
+
+ // some more of create_wrapper at the end of the next block.
+ }
+ }
+
+ {
+ tree aaptr = NULL_TREE;
+ tree vaptr = NULL_TREE;
+ tree wmptr = NULL_TREE;
+ for (tree arg = DECL_ARGUMENTS (nnode->decl); arg; arg = DECL_CHAIN (arg))
+ {
+ aaptr = vaptr;
+ vaptr = wmptr;
+ wmptr = arg;
+ }
+
+ if (!apply_args)
+ aaptr = NULL_TREE;
+ /* The trailing args are [apply_args], [va_list_ptr], and
+ watermark. If we don't have a va_list_ptr, the penultimate
+ argument is apply_args.
+ */
+ else if (!is_stdarg)
+ aaptr = vaptr;
+
+ if (!is_stdarg)
+ vaptr = NULL_TREE;
+
+ DECL_NAME (wmptr) = get_watermark_ptr ();
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_IGNORED_P (wmptr) = 1;
+ TREE_USED (wmptr) = 1;
+
+ if (is_stdarg)
+ {
+ DECL_NAME (vaptr) = get_va_list_ptr ();
+ DECL_ARTIFICIAL (vaptr) = 1;
+ DECL_IGNORED_P (vaptr) = 1;
+ TREE_USED (vaptr) = 1;
+ }
+
+ if (apply_args)
+ {
+ DECL_NAME (aaptr) = get_apply_args_ptr ();
+ DECL_ARTIFICIAL (aaptr) = 1;
+ DECL_IGNORED_P (aaptr) = 1;
+ TREE_USED (aaptr) = 1;
+ }
+
+ push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ bool any_indirect = !indirect_nparms.is_empty ();
+
+ if (any_indirect)
+ {
+ 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);
+
+ 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
+ || is_stdarg || apply_args)
+ for (cgraph_edge *e = nnode->callees, *enext; e; e = enext)
+ {
+ gcall *call = e->call_stmt;
+ gimple_stmt_iterator gsi = gsi_for_stmt (call);
+ tree fndecl = e->callee->decl;
+
+ enext = e->next_callee;
+
+ if (gimple_alloca_call_p (call))
+ {
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ else if (fndecl && is_stdarg
+ && fndecl_built_in_p (fndecl, BUILT_IN_VA_START))
+ {
+ if (builtin_decl_explicit (BUILT_IN_VA_START) != fndecl)
+ sorry_at (gimple_location (call),
+ "nonstandard stdarg conventions");
+ tree bvacopy = builtin_decl_explicit (BUILT_IN_VA_COPY);
+ gimple_call_set_fndecl (call, bvacopy);
+ tree arg = vaptr;
+ /* The va_copy source must be dereferenced, unless it's an array
+ type, that would have decayed to a pointer. */
+ if (TREE_CODE (TREE_TYPE (TREE_TYPE (vaptr))) != ARRAY_TYPE)
+ {
+ arg = gimple_fold_indirect_ref (vaptr);
+ if (!arg)
+ arg = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (vaptr)),
+ vaptr,
+ build_int_cst (TREE_TYPE (vaptr), 0));
+ }
+ gimple_call_set_arg (call, 1, arg);
+ update_stmt (call);
+ e->redirect_callee (cgraph_node::get_create (bvacopy));
+ }
+ else if (fndecl && apply_args
+ && fndecl_built_in_p (fndecl, BUILT_IN_APPLY_ARGS))
+ {
+ tree lhs = gimple_call_lhs (call);
+ gassign *assign = gimple_build_assign (lhs, aaptr);
+ gsi_replace (&gsi, assign, true);
+ cgraph_edge::remove (e);
+ }
+ }
+
+ { // a little more copied from create_wrapper
+
+ /* Inline summary set-up. */
+ nnode->analyze ();
+ // inline_analyze_function (nnode);
+ }
+
+ pop_cfun ();
+ }
+
+ {
+ 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));
+
+ 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
+ onode->create_edge (cgraph_node::get_create (enter),
+ stptr, gsi_bb (gsi)->count, false);
+#endif
+
+ int nargs = gimple_call_num_args (wrcall);
+
+ gimple_seq seq = NULL;
+
+ if (apply_args)
+ {
+ tree aalst = create_tmp_var (ptr_type_node, ".strub.appargs");
+ tree bappargs = builtin_decl_explicit (BUILT_IN_APPLY_ARGS);
+ gcall *appargs = gimple_build_call (bappargs, 0);
+ gimple_call_set_lhs (appargs, aalst);
+ gsi_insert_before (&gsi, appargs, GSI_SAME_STMT);
+ gimple_call_set_arg (wrcall, nargs - 2 - is_stdarg, aalst);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bappargs),
+ appargs, gsi_bb (gsi)->count, false);
+#endif
+ }
+
+ if (is_stdarg)
+ {
+ tree valst = create_tmp_var (va_list_type_node, ".strub.va_list");
+ TREE_ADDRESSABLE (valst) = true;
+ tree vaptr = build1 (ADDR_EXPR,
+ build_pointer_type (va_list_type_node),
+ valst);
+ gimple_call_set_arg (wrcall, nargs - 2, unshare_expr (vaptr));
+
+ tree bvastart = builtin_decl_explicit (BUILT_IN_VA_START);
+ gcall *vastart = gimple_build_call (bvastart, 2,
+ unshare_expr (vaptr),
+ integer_zero_node);
+ gsi_insert_before (&gsi, vastart, GSI_SAME_STMT);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bvastart),
+ vastart, gsi_bb (gsi)->count, false);
+#endif
+
+ tree bvaend = builtin_decl_explicit (BUILT_IN_VA_END);
+ gcall *vaend = gimple_build_call (bvaend, 1, unshare_expr (vaptr));
+ gimple_seq_add_stmt (&seq, vaend);
+ }
+
+ gimple_call_set_arg (wrcall, nargs - 1, unshare_expr (swmp));
+ // gimple_call_set_tail (wrcall, false);
+ update_stmt (wrcall);
+
+ {
+#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);
+
+ /* For nnode, we don't rebuild edges because we wish to retain
+ any redirections copied to it from earlier passes, so we add
+ call graph edges explicitly there, but for onode, we create a
+ fresh function, so we may as well just issue the calls and
+ then rebuild all cgraph edges. */
+ // cgraph_edge::rebuild_edges ();
+ onode->analyze ();
+ // inline_analyze_function (onode);
+
+ 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;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub (gcc::context *ctxt)
+{
+ return new pass_ipa_strub (ctxt);
+}
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
new file mode 100644
index 00000000000..cadbca5002a
--- /dev/null
+++ b/gcc/ipa-strub.h
@@ -0,0 +1,25 @@
+/* strub (stack scrubbing) infrastructure.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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);
diff --git a/gcc/passes.def b/gcc/passes.def
index f5d88a61b0e..677c000e80b 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -53,6 +53,7 @@ along with GCC; see the file COPYING3. If not see
INSERT_PASSES_AFTER (all_small_ipa_passes)
NEXT_PASS (pass_ipa_free_lang_data);
NEXT_PASS (pass_ipa_function_and_variable_visibility);
+ NEXT_PASS (pass_ipa_strub_mode);
NEXT_PASS (pass_build_ssa_passes);
PUSH_INSERT_PASSES_WITHIN (pass_build_ssa_passes)
NEXT_PASS (pass_fixup_cfg);
@@ -111,6 +112,7 @@ along with GCC; see the file COPYING3. If not see
POP_INSERT_PASSES ()
NEXT_PASS (pass_ipa_remove_symbols);
+ NEXT_PASS (pass_ipa_strub);
NEXT_PASS (pass_ipa_oacc);
PUSH_INSERT_PASSES_WITHIN (pass_ipa_oacc)
NEXT_PASS (pass_ipa_pta);
diff --git a/gcc/testsuite/g++.dg/wrappers/strub1.C b/gcc/testsuite/g++.dg/wrappers/strub1.C
new file mode 100644
index 00000000000..a474a929649
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub1.C
@@ -0,0 +1,18 @@
+// { dg-do run }
+
+// Check that we don't get extra copies.
+
+struct T {
+ T &self;
+ void check () const { if (&self != this) __builtin_abort (); }
+ T() : self (*this) { check (); }
+ T(const T& ck) : self (*this) { ck.check (); check (); }
+ ~T() { check (); }
+};
+
+T foo (T) { return T(); }
+T bar (T p) { return foo (p); }
+
+int main () {
+ bar (T{});
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub2.C b/gcc/testsuite/g++.dg/wrappers/strub2.C
new file mode 100644
index 00000000000..25a62166448
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub2.C
@@ -0,0 +1,22 @@
+// { dg-do run }
+
+// This doesn't really test anything yet. We should mark the
+// variables as requiring strubbing, and somehow check that the
+// wrapped functions take the parameter by reference.
+
+struct T {
+ char d[32 * sizeof(void*)];
+};
+
+T foo (T q) { asm ("" : : "m"(q)); return q; }
+T bar (T p) { return foo (p); }
+
+T tmp;
+T tmp2;
+
+int main () {
+ __builtin_memset (&tmp, 0x55, sizeof (tmp));
+ tmp2 = bar (tmp);
+ if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
+ __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub3.C b/gcc/testsuite/g++.dg/wrappers/strub3.C
new file mode 100644
index 00000000000..e1b51cd0399
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub3.C
@@ -0,0 +1,22 @@
+// { dg-do run }
+
+// This doesn't really test anything yet. We should mark the
+// variables as requiring strubbing, and somehow check that the
+// wrapped functions take the parameter by reference.
+
+struct T {
+ char d[32 * sizeof(void*)];
+};
+
+static T foo (T q) { asm ("" : : "m"(q)); return q; }
+static T bar (T p) { return foo (p); }
+
+T tmp;
+T tmp2;
+
+int main () {
+ __builtin_memset (&tmp, 0x55, sizeof (tmp));
+ tmp2 = bar (tmp);
+ if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
+ __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub4.C b/gcc/testsuite/g++.dg/wrappers/strub4.C
new file mode 100644
index 00000000000..d021fca88e4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub4.C
@@ -0,0 +1,18 @@
+// { dg-do run }
+
+namespace
+{
+ class foo
+ {
+ public:
+ foo();
+ };
+
+ foo::foo() {}
+
+ foo bar;
+}
+
+int main()
+{
+}
diff --git a/gcc/tree-cfg.c b/gcc/tree-cfg.c
index 38269a27b79..b5e0af8b3a1 100644
--- a/gcc/tree-cfg.c
+++ b/gcc/tree-cfg.c
@@ -5617,6 +5617,7 @@ gimple_verify_flow_info (void)
{
gimple *stmt = gsi_stmt (gsi);
+ /* Do NOT disregard debug stmts after found_ctrl_stmt. */
if (found_ctrl_stmt)
{
error ("control flow in the middle of basic block %d",
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index aa9757a2fe9..0210f84bc0c 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -494,8 +494,9 @@ extern gimple_opt_pass *make_pass_adjust_alignment (gcc::context *ctxt);
/* IPA Passes */
extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt);
-extern simple_ipa_opt_pass
- *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub_mode (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_tree_profile (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_auto_profile (gcc::context *ctxt);
diff --git a/libgcc/Makefile.in b/libgcc/Makefile.in
index 2c8be561eb5..6b7b12493f5 100644
--- a/libgcc/Makefile.in
+++ b/libgcc/Makefile.in
@@ -428,7 +428,7 @@ ifneq ($(enable_shared),yes)
iterator = $(patsubst %,$(srcdir)/static-object.mk,$(iter-items))
endif
-LIB2ADD += enable-execute-stack.c
+LIB2ADD += enable-execute-stack.c $(srcdir)/strub.c
# While emutls.c has nothing to do with EH, it is in LIB2ADDEH*
# instead of LIB2ADD because that's the way to be sure on some targets
diff --git a/libgcc/libgcc2.h b/libgcc/libgcc2.h
index 1819ff3ac3d..857091e65c8 100644
--- a/libgcc/libgcc2.h
+++ b/libgcc/libgcc2.h
@@ -532,6 +532,10 @@ extern int __parityDI2 (UDWtype);
extern void __enable_execute_stack (void *);
+extern void __strub_enter (void **);
+extern void __strub_update (void**);
+extern void __strub_leave (void **);
+
#ifndef HIDE_EXPORTS
#pragma GCC visibility pop
#endif
diff --git a/libgcc/strub.c b/libgcc/strub.c
new file mode 100644
index 00000000000..fd6e27556e4
--- /dev/null
+++ b/libgcc/strub.c
@@ -0,0 +1,112 @@
+/* Stack scrubbing infrastructure
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+Under Section 7 of GPL version 3, you are granted additional
+permissions described in the GCC Runtime Library Exception, version
+3.1, as published by the Free Software Foundation.
+
+You should have received a copy of the GNU General Public License and
+a copy of the GCC Runtime Library Exception along with this program;
+see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
+<http://www.gnu.org/licenses/>. */
+
+#include "tconfig.h"
+#include "tsystem.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "libgcc_tm.h"
+#include "libgcc2.h"
+
+#ifndef STACK_GROWS_DOWNWARD
+# define TOPS >
+#else
+# define TOPS <
+#endif
+
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+
+/* Enter a stack scrubbing context, initializing the watermark to the caller's
+ stack address. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_enter (void **watermark)
+{
+ *watermark = __builtin_frame_address (0);
+}
+
+/* Update the watermark within a stack scrubbing context with the current stack
+ pointer. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_update (void **watermark)
+{
+ void *sp = __builtin_frame_address (0);
+
+ if (sp TOPS *watermark)
+ *watermark = sp;
+}
+
+#ifndef TARGET_STRUB_USE_DYNAMIC_ARRAY
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+#endif
+
+#ifndef TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+# ifdef TARGET_STRUB_MAY_USE_MEMSET
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+# else
+# define TARGET_STRUB_MAY_USE_MEMSET 1
+# endif
+#endif
+
+/* Leave a stack scrubbing context, restoring and updating SAVED, and
+ clearing the stack between top and watermark. */
+void ATTRIBUTE_STRUB_CALLABLE
+#if ! TARGET_STRUB_MAY_USE_MEMSET
+__attribute__ ((__optimize__ ("-fno-tree-loop-distribute-patterns")))
+#endif
+__strub_leave (void **mark)
+{
+ void *sp = __builtin_stack_address ();
+
+ void **base, **end;
+#ifndef STACK_GROWS_DOWNWARD
+ base = sp;
+ end = *mark;
+#else
+ base = *mark;
+ end = sp;
+#endif
+
+ ptrdiff_t len = end - base;
+ if (len <= 0)
+ return;
+
+#if ! TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+ /* Allocate a dynamically-sized array covering the desired range, so that we
+ can safely call memset on it. */
+ void *ptr[len];
+ base = &ptr[0];
+ end = &ptr[len];
+#else
+ void **ptr = end;
+#endif /* TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY */
+
+ /* ldist turns this into a memset. Without the dynamic array above, that call
+ is likely unsafe: possibly tail-called, and likely scribbling over its own
+ stack frame. */
+ while (base < end)
+ *base++ = 0;
+
+ asm ("" : : "m" (ptr));
+}
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/strub)] strub with function and variable attributes
@ 2021-07-28 6:33 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2021-07-28 6:33 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:24f9049690d8942208632bdd2e53411f52e42351
commit 24f9049690d8942208632bdd2e53411f52e42351
Author: Alexandre Oliva <oliva@adacore.com>
Date: Sat Jul 24 23:05:42 2021 -0300
strub with function and variable attributes
still missing verification of calls, documentation, tests,
builtin expansion
Diff:
---
gcc/Makefile.in | 1 +
gcc/builtins.c | 148 ++
gcc/builtins.def | 4 +
gcc/c-family/c-attribs.c | 53 +
gcc/common.opt | 24 +
gcc/doc/extend.texi | 72 +
gcc/doc/invoke.texi | 41 +
gcc/ipa-inline.c | 6 +
gcc/ipa-strub.c | 2644 ++++++++++++++++++++++++++++++++
gcc/ipa-strub.h | 25 +
gcc/passes.def | 2 +
gcc/testsuite/g++.dg/wrappers/strub1.C | 18 +
gcc/testsuite/g++.dg/wrappers/strub2.C | 22 +
gcc/testsuite/g++.dg/wrappers/strub3.C | 22 +
gcc/testsuite/g++.dg/wrappers/strub4.C | 18 +
gcc/tree-cfg.c | 1 +
gcc/tree-pass.h | 5 +-
libgcc/Makefile.in | 2 +-
libgcc/libgcc2.h | 4 +
libgcc/strub.c | 112 ++
20 files changed, 3221 insertions(+), 3 deletions(-)
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 1666ef84d6a..163fd1a856c 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1468,6 +1468,7 @@ OBJS = \
ipa-reference.o \
ipa-ref.o \
ipa-utils.o \
+ ipa-strub.o \
ipa.o \
ira.o \
ira-build.o \
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 170d776c410..4d4dfcce631 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -153,6 +153,7 @@ static rtx expand_builtin_strnlen (tree, rtx, machine_mode);
static rtx expand_builtin_alloca (tree);
static rtx expand_builtin_unop (machine_mode, tree, rtx, rtx, optab);
static rtx expand_builtin_frame_address (tree, tree);
+static rtx expand_builtin_stack_address ();
static tree stabilize_va_list_loc (location_t, tree, int);
static rtx expand_builtin_expect (tree, rtx);
static rtx expand_builtin_expect_with_probability (tree, rtx);
@@ -7893,6 +7894,132 @@ expand_builtin_frame_address (tree fndecl, tree exp)
}
}
+/* Expand a call to builtin function __builtin_stack_address. */
+
+static rtx
+expand_builtin_stack_address ()
+{
+ return copy_addr_to_reg (stack_pointer_rtx);
+}
+
+/* Expand a call to builtin function __builtin_strub_enter. */
+
+static rtx
+expand_builtin_strub_enter (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (!optimize)
+ return NULL_RTX;
+
+ rtx stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, Pmode, EXPAND_MEMORY);
+
+ emit_move_insn (wmark, stktop);
+
+ return const0_rtx;
+}
+
+#ifndef STACK_GROWS_DOWNWARD
+# define STACK_TOPS GT
+#else
+# define STACK_TOPS LT
+#endif
+
+#ifdef POINTERS_EXTEND_UNSIGNED
+# define STACK_UNSIGNED POINTERS_EXTEND_UNSIGNED
+#else
+# define STACK_UNSIGNED true
+#endif
+
+/* Expand a call to builtin function __builtin_strub_update. */
+
+static rtx
+expand_builtin_strub_update (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 2)
+ return NULL_RTX;
+
+ rtx stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, Pmode, EXPAND_MEMORY);
+ rtx wmarkr = force_reg (Pmode, wmark);
+
+ rtx_code_label *lab = gen_label_rtx ();
+ do_compare_rtx_and_jump (stktop, wmarkr, STACK_TOPS, STACK_UNSIGNED,
+ Pmode, NULL_RTX, lab, NULL,
+ profile_probability::very_likely ());
+ emit_move_insn (wmark, stktop);
+ emit_label (lab);
+
+ return const0_rtx;
+}
+
+
+/* Expand a call to builtin function __builtin_strub_leave. */
+
+static rtx
+expand_builtin_strub_leave (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 3)
+ return NULL_RTX;
+
+ rtx stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, Pmode, EXPAND_MEMORY);
+ rtx wmarkr = force_reg (Pmode, wmark);
+
+#ifndef STACK_GROWS_DOWNWARD
+ rtx base = stktop;
+ rtx end = wmarkr;
+#else
+ rtx base = wmarkr;
+ rtx end = stktop;
+#endif
+
+ rtx_code_label *done = gen_label_rtx ();
+ do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
+ Pmode, NULL_RTX, done, NULL,
+ profile_probability::very_likely ());
+
+ rtx zero = force_operand (const0_rtx, NULL_RTX);
+ int ulen = GET_MODE_SIZE (Pmode);
+ rtx incr = plus_constant (Pmode, base, ulen);
+ rtx dstm = gen_rtx_MEM (Pmode, base);
+
+ rtx_code_label *loop = gen_label_rtx ();
+ emit_label (loop);
+ emit_move_insn (dstm, zero);
+ emit_move_insn (base, force_operand (incr, NULL_RTX));
+ do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
+ Pmode, NULL_RTX, NULL, loop,
+ profile_probability::very_likely ());
+
+ emit_label (done);
+
+ return const0_rtx;
+}
+
/* Expand EXP, a call to the alloca builtin. Return NULL_RTX if we
failed and the caller should emit a normal call. */
@@ -10151,6 +10278,27 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
case BUILT_IN_RETURN_ADDRESS:
return expand_builtin_frame_address (fndecl, exp);
+ case BUILT_IN_STACK_ADDRESS:
+ return expand_builtin_stack_address ();
+
+ case BUILT_IN___STRUB_ENTER:
+ target = expand_builtin_strub_enter (exp);
+ if (target)
+ return target;
+ break;
+
+ case BUILT_IN___STRUB_UPDATE:
+ target = expand_builtin_strub_update (exp);
+ if (target)
+ return target;
+ break;
+
+ case BUILT_IN___STRUB_LEAVE:
+ target = expand_builtin_strub_leave (exp);
+ if (target)
+ return target;
+ break;
+
/* Returns the address of the area where the structure is returned.
0 otherwise. */
case BUILT_IN_AGGREGATE_INCOMING_ADDRESS:
diff --git a/gcc/builtins.def b/gcc/builtins.def
index ec556df4f66..4545033e664 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -878,6 +878,10 @@ DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSL, "ffsl", BT_FN_INT_LONG, ATTR_CONST_NOTHRO
DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSLL, "ffsll", BT_FN_INT_LONGLONG, ATTR_CONST_NOTHROW_LEAF_LIST)
DEF_EXT_LIB_BUILTIN (BUILT_IN_FORK, "fork", BT_FN_PID, ATTR_NOTHROW_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FRAME_ADDRESS, "frame_address", BT_FN_PTR_UINT, ATTR_NULL)
+DEF_GCC_BUILTIN (BUILT_IN_STACK_ADDRESS, "stack_address", BT_FN_PTR, ATTR_NULL)
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_ENTER, "__builtin___strub_enter")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_UPDATE, "__builtin___strub_update")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_LEAVE, "__builtin___strub_leave")
/* [trans-mem]: Adjust BUILT_IN_TM_FREE if BUILT_IN_FREE is changed. */
DEF_LIB_BUILTIN (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_LEAF_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FROB_RETURN_ADDR, "frob_return_addr", BT_FN_PTR_PTR, ATTR_NULL)
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index e60fb31d8c8..89ef83f6fac 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -69,6 +69,7 @@ static tree handle_asan_odr_indicator_attribute (tree *, tree, tree, int,
static tree handle_stack_protect_attribute (tree *, tree, tree, int, bool *);
static tree handle_no_stack_protector_function_attribute (tree *, tree,
tree, int, bool *);
+static tree handle_strub_attribute (tree *, tree, tree, int, bool *);
static tree handle_noinline_attribute (tree *, tree, tree, int, bool *);
static tree handle_noclone_attribute (tree *, tree, tree, int, bool *);
static tree handle_nocf_check_attribute (tree *, tree, tree, int, bool *);
@@ -306,6 +307,8 @@ const struct attribute_spec c_common_attribute_table[] =
{ "no_stack_protector", 0, 0, true, false, false, false,
handle_no_stack_protector_function_attribute,
attr_stack_protect_exclusions },
+ { "strub", 0, 1, false, true, false, true,
+ handle_strub_attribute, NULL },
{ "noinline", 0, 0, true, false, false, false,
handle_noinline_attribute,
attr_noinline_exclusions },
@@ -1290,6 +1293,56 @@ handle_noipa_attribute (tree *node, tree name, tree, int, bool *no_add_attrs)
return NULL_TREE;
}
+/* Handle a "strub" attribute; arguments as in
+ struct attribute_spec.handler. */
+
+static tree
+handle_strub_attribute (tree *node, tree name,
+ tree args,
+ int ARG_UNUSED (flags), bool *no_add_attrs)
+{
+ bool enable = true;
+
+ if (args
+ && (TREE_CODE (*node) == FUNCTION_TYPE
+ || TREE_CODE (*node) == METHOD_TYPE))
+ {
+ /* 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)
+ {
+ warning (OPT_Wattributes,
+ "%qE attribute ignored because of argument %qE",
+ name, TREE_VALUE (args));
+ *no_add_attrs = true;
+ enable = false;
+ }
+ /* 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));
+
+ /* 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;
+
+ return NULL_TREE;
+}
+
/* Handle a "noinline" attribute; arguments as in
struct attribute_spec.handler. */
diff --git a/gcc/common.opt b/gcc/common.opt
index d9da1131eda..c0408cb0f4c 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,6 +2687,30 @@ 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=all
+Common RejectNegative Var(flag_strub, 3)
+Enable stack scrubbing for all viable functions.
+
+fstrub=at-calls
+Common RejectNegative Var(flag_strub, 1)
+Enable at-calls stack scrubbing for all viable functions.
+
+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 b83cd4919bb..020bb1aa808 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -77,6 +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.
* 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.
@@ -8652,6 +8653,40 @@ pid_t wait (wait_status_ptr_t p)
@}
@end smallexample
+@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 numeric
+argument.
+
+A function associated with at-calls strub mode (@code{strub(1)})
+undergoes interface changes, and callers automatically gain code to
+scrub the stack space it uses. If the operational numeric argument is
+omitted from the @code{strub} attribute in a function type, the at-calls
+mode is selected.
+
+A function with internal strub mode @code{strub(2)} retains an
+unmodified interface, but it may be turned into a wrapper that calls the
+wrapped body, with a custom interface, and then scrubs the stack space
+used by it.
+
+A variable whose type carries the strub attribute causes functions that
+use it to have strub enabled.
+
+A function associated with the disabled strub mode @code{strub(0)} will
+not have its stack scrubbed. A function that has strub mode disabled
+cannot be called from strub contexts, namely from bodies of functions
+with strub modes @code{1} or @code{2}.
+
+A function whose body is not a strub context, but that should be
+callable from within strub contexts, should be marked as callable with
+@code{strub(3)}.
+
+Strub contexts are not inlined into non-strub contexts. When an
+internal-strub function is split, the wrapper can often be inlined, but
+its wrapped body cannot. Functions marked as @code{always_inline}, even
+if explicitly assigned internal strub mode, will not undergo wrapping.
+
@item unused
@cindex @code{unused} type attribute
When attached to a type (including a @code{union} or a @code{struct}),
@@ -11670,6 +11705,43 @@ option is in effect. Such calls should only be made in debugging
situations.
@end deftypefn
+@deftypefn {Built-in Function} {void *} __builtin_stack_address ()
+This function returns the value of the stack pointer register.
+@end deftypefn
+
+@node Stack Scrubbing
+@section Stack scrubbing interfaces
+
+Stack scrubbing involves cooperation between a strub context, i.e., a
+function whose stack frame is to be zeroed out, and its callers. The
+caller initializes a stack watermark, the 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, each one defined in libgcc, but expanded internally
+depending on optimization levels.
+
+@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 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 strub context 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 strub context. It remains as a function call
+at optimization levels lower than 3.
+@end deftypefn
+
@node Vector Extensions
@section Using Vector Instructions through Built-in Functions
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 32697e6117c..cca3e9b18c6 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,6 +599,7 @@ 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
-fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
-fvtv-counts -fvtv-debug @gol
-finstrument-functions @gol
@@ -15425,6 +15426,46 @@ 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 (strub) setting, namely, 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, even when the @code{strub} attribute
+is present in function and variable types.
+
+@item -fstrub=at-calls
+@opindex fstrub=at-calls
+Enable at-calls strub for all viable functions, and consider non-viable
+functions as callable. A function is non-eligible for at-calls strub if
+a different strub mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+At-calls strub mode, if not requested with the function type, is only
+viable for an eligible function if it is not visible to other
+translation units, through its declaration or by having its address
+taken.
+
+@item -fstrub=internal
+@opindex fstrub=internal
+Enable internal strub for all viable functions, and consider non-viable
+functions as callable. A function is not eligible for internal strub if
+another strub mode is explicitly requested, attributes @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 internal strub, all
+eligible functions are viable.
+
+@item -fstrub=all
+@opindex fstrub=all
+Enable strub for all viable functions, and consider non-viable functions
+as callable. When both strub modes are viable, at-calls is preferred.
+
@item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
@opindex fvtable-verify
This option is only available when compiling C++ code.
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 413446bcc46..7f4bc44d2bb 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -119,6 +119,7 @@ along with GCC; see the file COPYING3. If not see
#include "stringpool.h"
#include "attribs.h"
#include "asan.h"
+#include "ipa-strub.h"
typedef fibonacci_heap <sreal, cgraph_edge> edge_heap_t;
typedef fibonacci_node <sreal, cgraph_edge> edge_heap_node_t;
@@ -396,6 +397,11 @@ 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))
+ {
+ e->inline_failed = CIF_UNSPECIFIED;
+ inlinable = false;
+ }
if (!inlinable && report)
report_inline_failed_reason (e);
return inlinable;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
new file mode 100644
index 00000000000..992ab511b26
--- /dev/null
+++ b/gcc/ipa-strub.c
@@ -0,0 +1,2644 @@
+/* strub (stack scrubbing) support.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "gimplify.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "gimple-iterator.h"
+#include "gimplify-me.h"
+#include "tree-into-ssa.h"
+#include "tree-ssa.h"
+#include "tree-cfg.h"
+#include "cfghooks.h"
+#include "cfgloop.h"
+#include "cfgcleanup.h"
+#include "tree-eh.h"
+#include "except.h"
+#include "builtins.h"
+#include "attribs.h"
+#include "tree-inline.h"
+#include "cgraph.h"
+#include "alloc-pool.h"
+#include "symbol-summary.h"
+#include "ipa-prop.h"
+#include "ipa-fnsummary.h"
+#include "gimple-fold.h"
+#include "fold-const.h"
+#include "gimple-walk.h"
+#include "tree-dfa.h"
+#include "langhooks.h"
+#include "calls.h"
+#include "vec.h"
+#include "stor-layout.h"
+#include "varasm.h"
+#include "alias.h"
+#include "diagnostic.h"
+#include "intl.h"
+#include "ipa-strub.h"
+
+#if BUILDING_GCC_MAJOR >= 11
+# include "symtab-thunks.h"
+# include "attr-fnspec.h"
+# define HAVE_ATTR_FNSPEC 1
+# define FOR_GCC_11P 1
+#else
+# define HAVE_ATTR_FNSPEC 0
+# define FOR_GCC_11P 0
+#endif
+
+/* Const and pure functions that gain a watermark parameter for strub purposes
+ are still regarded as such, which may cause the inline expansions of the
+ __strub builtins to malfunction. Ideally, attribute "fn spec" would enable
+ us to inform the backend about requirements and side effects of the call, but
+ call_fusage building in calls.c:expand_call does not even look at
+ attr_fnspec, so we resort to asm loads and updates to attain an equivalent
+ effect. Once expand_call gains the ability to issue extra memory uses and
+ clobbers based on pure/const function's fnspec, we can define this to 1. */
+#define ATTR_FNSPEC_DECONST_WATERMARK 0
+
+enum strub_mode {
+ /* This mode denotes a regular function, that does not require stack
+ scrubbing (strubbing). It may call any other functions, but if
+ it calls AT_CALLS (or WRAPPED) ones, strubbing logic is
+ automatically introduced around those calls (the latter, by
+ inlining INTERNAL wrappers). */
+ STRUB_DISABLED = 0,
+
+ /* This denotes a function whose signature is (to be) modified to
+ take an extra parameter, for stack use annotation, and its
+ callers must initialize and pass that argument, and perform the
+ strubbing. Functions that are explicitly marked with attribute
+ strub must have the mark visible wherever the function is,
+ including aliases, and overriders and overriding methods.
+ Functions that are implicitly marked for strubbing, for accessing
+ variables explicitly marked as such, will only select this
+ strubbing method if they are internal to a translation unit. It
+ can only be inlined into other strubbing functions, i.e.,
+ STRUB_AT_CALLS or STRUB_WRAPPED. */
+ STRUB_AT_CALLS = 1,
+
+ /* This denotes a function that is to perform strubbing internally,
+ without any changes to its interface (the function is turned into
+ a strubbing wrapper, and its original body is moved to a separate
+ STRUB_WRAPPED function, with a modified interface). Functions
+ may be explicitly marked with attribute strub(2), and the
+ attribute must be visible at the point of definition. Functions
+ that are explicitly marked for strubbing, for accessing variables
+ explicitly marked as such, may select this strubbing mode if
+ their interface cannot change, e.g. because its interface is
+ visible to other translation units, directly, by indirection
+ (having its address taken), inheritance, etc. Functions that use
+ this method must not have the noclone attribute, nor the noipa
+ one. Functions marked as always_inline may select this mode, but
+ they are NOT wrapped, they remain unchanged, and are only inlined
+ into strubbed contexts. Once non-always_inline functions are
+ wrapped, the wrapper becomes STRUB_WRAPPER, and the wrapped becomes
+ STRUB_WRAPPED. */
+ STRUB_INTERNAL = 2,
+
+ /* This denotes a function whose stack is not strubbed, but that is
+ nevertheless explicitly or implicitly marked as callable from strubbing
+ functions. Normally, only STRUB_AT_CALLS (and STRUB_INTERNAL ->
+ STRUB_WRAPPED) functions can be called from strubbing contexts (bodies of
+ STRUB_AT_CALLS, STRUB_INTERNAL and STRUB_WRAPPED functions), but attribute
+ strub(3) enables other functions to be (indirectly) called from these
+ contexts. Some builtins and internal functions may be implicitly marked as
+ STRUB_CALLABLE. */
+ STRUB_CALLABLE = 3,
+
+ /* This denotes the function that took over the body of a
+ STRUB_INTERNAL function. At first, it's only called by its
+ wrapper, but the wrapper may be inlined. The wrapped function,
+ in turn, can only be inlined into other functions whose stack
+ frames are strubbed, i.e., that are STRUB_WRAPPED or
+ STRUB_AT_CALLS. */
+ STRUB_WRAPPED = -1,
+
+ /* This denotes the wrapper function that replaced the STRUB_INTERNAL
+ function. This mode overrides the STRUB_INTERNAL mode at the time the
+ internal to-be-wrapped function becomes a wrapper, so that inlining logic
+ can tell one from the other. */
+ STRUB_WRAPPER = -2,
+
+ /* This denotes an always_inline function that requires strubbing. It can
+ only be called from, and inlined into, other strubbing contexts. */
+ STRUB_INLINABLE = -3,
+};
+
+static tree
+get_strub_attr_from_type (tree type)
+{
+ return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
+}
+
+static tree
+get_strub_attr_from_decl (tree decl)
+{
+ tree ret = lookup_attribute ("strub", DECL_ATTRIBUTES (decl));
+ if (ret)
+ return ret;
+ return get_strub_attr_from_type (TREE_TYPE (decl));
+}
+
+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);
+
+#if 0 /* ??? use symbolic mode names with interned strings? */
+ char *s = NULL;
+
+ switch (strub_mode)
+ {
+
+ }
+#endif
+}
+
+static enum strub_mode
+get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
+{
+ enum strub_mode mode = STRUB_DISABLED;
+
+ if (strub_attr)
+ {
+ 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 /* ??? Support symbolic mode names? */
+ gcc_unreachable ();
+ }
+
+ return mode;
+}
+
+static enum strub_mode
+get_strub_mode (cgraph_node *node)
+{
+ return get_strub_mode_from_attr (get_strub_attr_from_decl (node->decl));
+}
+
+static enum strub_mode
+get_strub_mode_from_type (tree type)
+{
+ bool var_p = !FUNC_OR_METHOD_TYPE_P (type);
+ tree attr = get_strub_attr_from_type (type);
+
+ if (attr)
+ return get_strub_mode_from_attr (attr, var_p);
+
+ if (flag_strub > 0 && !var_p)
+ return STRUB_CALLABLE;
+
+ return STRUB_DISABLED;
+}
+
+static bool
+calls_builtin_va_start_p (cgraph_node *node)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (fndecl_built_in_p (cdecl, BUILT_IN_VA_START))
+ return true;
+ }
+
+ return result;
+}
+
+static bool
+calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!fndecl_built_in_p (cdecl, BUILT_IN_APPLY_ARGS))
+ continue;
+
+ result = true;
+
+ if (!report)
+ break;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "at-calls strub does not support call to %qD",
+ cdecl);
+ }
+
+ return result;
+}
+
+static inline bool
+strub_always_inline_p (cgraph_node *node)
+{
+ return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
+}
+
+static inline bool
+can_strub_p (cgraph_node *node, bool report = false)
+{
+ bool result = true;
+
+ if (!report && strub_always_inline_p (node))
+ return result;
+
+ if (lookup_attribute ("noipa", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for strub because of attribute %<noipa%>",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+can_strub_at_calls_p (cgraph_node *node, bool report = false)
+{
+ bool result = !report || can_strub_p (node, report);
+
+ if (!result && !report)
+ return result;
+
+ return !calls_builtin_apply_args_p (node, report);
+}
+
+#define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
+
+/* We can't perform internal strubbing if the function body involves certain
+ features:
+
+ - a non-default __builtin_va_start (e.g. x86's __builtin_ms_va_start) is
+ currently unsupported because we can't discover the corresponding va_copy and
+ va_end decls in the wrapper, and we don't convey the alternate variable
+ arguments ABI to the modified wrapped function. The default
+ __builtin_va_start is supported by calling va_start/va_end at the wrapper,
+ that takes variable arguments, passing a pointer to the va_list object to the
+ wrapped function, that runs va_copy from it where the original function ran
+ va_start.
+
+ __builtin_next_arg is currently unsupported because the wrapped function
+ won't be a variable argument function. We could process it in the wrapper,
+ that remains a variable argument function, and replace calls in the wrapped
+ body, but we currently don't.
+
+ __builtin_return_address is rejected because it's generally used when the
+ actual caller matters, and introducing a wrapper breaks such uses as those in
+ the unwinder. */
+
+static bool
+can_strub_internally_p (cgraph_node *node, bool report = false)
+{
+ bool result = !report || can_strub_p (node, report);
+
+ if (!result && !report)
+ return result;
+
+ if (!report && strub_always_inline_p (node))
+ return result;
+
+ if (lookup_attribute ("noclone", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for internal strub"
+ " because of attribute %<noclone%>",
+ node->decl);
+ }
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!((fndecl_built_in_p (cdecl, BUILT_IN_VA_START)
+ && cdecl != builtin_decl_explicit (BUILT_IN_VA_START))
+ || fndecl_built_in_p (cdecl, BUILT_IN_NEXT_ARG)
+ || fndecl_built_in_p (cdecl, BUILT_IN_RETURN_ADDRESS)))
+ continue;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "internal strub does not support call to %qD",
+ cdecl);
+ }
+
+ basic_block bb;
+ if (node->has_gimple_body_p ())
+ {
+ /* Label values referenced 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. */
+
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ glabel *label_stmt = dyn_cast <glabel *> (gsi_stmt (gsi));
+ tree target;
+
+ if (!label_stmt)
+ break;
+
+ 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;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (label_stmt),
+ "internal strub does not support user labels");
+ }
+ }
+
+ if (list_length (TYPE_ARG_TYPES (TREE_TYPE (node->decl)))
+ >= (((HOST_WIDE_INT) 1 << IPA_PARAM_MAX_INDEX_BITS)
+ - STRUB_INTERNAL_MAX_EXTRA_ARGS))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD has too many arguments for internal strub",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+strub_from_body_p (cgraph_node *node)
+{
+ if (!node->has_gimple_body_p ())
+ return false;
+
+ /* If any local variable is marked for strub... */
+ unsigned i;
+ tree var;
+ FOR_EACH_LOCAL_DECL (DECL_STRUCT_FUNCTION (node->decl),
+ i, var)
+ if (get_strub_mode_from_type (TREE_TYPE (var))
+ != STRUB_DISABLED)
+ return true;
+
+ /* Now scan the body for loads with strub types. */
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!gimple_assign_load_p (stmt))
+ continue;
+
+ tree rhs = gimple_assign_rhs1 (stmt);
+ if (get_strub_mode_from_type (TREE_TYPE (rhs))
+ != STRUB_DISABLED)
+ return true;
+ }
+
+ return false;
+}
+
+/* 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)
+{
+ if (DECL_BUILT_IN_CLASS (node->decl) != BUILT_IN_NORMAL)
+ return false;
+
+ enum built_in_function fcode = DECL_FUNCTION_CODE (node->decl);
+
+ switch (fcode)
+ {
+ case BUILT_IN_NONE:
+ gcc_unreachable ();
+
+ /* ??? Make all builtins callable. We wish to make any builtin call the
+ compiler might introduce on its own callable. Anything that is
+ predictable enough as to be known not to allow stack data that should
+ be strubbed to unintentionally escape to non-strub contexts can be
+ allowed, and pretty much every builtin appears to fit this description.
+ The exceptions to this rule seem to be rare, and only available as
+ explicit __builtin calls, so let's keep it simple and allow all of
+ them... */
+ default:
+ return true;
+ }
+}
+
+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);
+
+ /* 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;
+ /* 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
+ safely undergo at-calls strubbing. Internal mode will still be used in
+ functions that request it explicitly with attribute strub(2), or when the
+ function body requires strubbing and at-calls strubbing is not viable. */
+ const bool strub_flag_at_calls = flag_strub == 1;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo internal strubbing. At-calls mode will still be used in
+ functions that requiest it explicitly with attribute strub() or strub(1),
+ or when the function body requires strubbing and internal strubbing is not
+ viable. */
+ const bool strub_flag_internal = flag_strub == 2;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo strubbing in either mode. When both modes are viable,
+ at-calls is preferred. */
+ const bool strub_flag_either = flag_strub == 3;
+ /* Besides the default behavior, enable strub implicitly for all viable
+ functions. */
+ const bool strub_flag_viable = flag_strub > 0;
+
+ /* The consider_* variables should be true if selecting the corresponding
+ strub modes would be consistent with requests from attributes and command
+ line flags. Attributes associated with functions pretty much mandate a
+ selection, and should report an error if not satisfied; strub_flag_auto
+ implicitly enables some viable strub mode if that's required by references
+ to variables marked for strub; strub_flag_viable enables strub if viable
+ (even when favoring one mode, body-requested strub can still be satisfied
+ by either mode), and falls back to callable, silently unless variables
+ require strubbing. */
+
+ const bool consider_at_calls
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_AT_CALLS
+ : true));
+ const bool consider_internal
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_INTERNAL
+ : true));
+
+ const bool consider_callable
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_CALLABLE
+ : (strub_flag_viable
+ || strub_callable_builtin_p (node))));
+
+ /* This is a shorthand for either strub-enabled mode. */
+ const bool consider_strub
+ = (consider_at_calls || consider_internal);
+
+ /* We can cope with always_inline functions even with noipa and noclone,
+ because we just leave them alone. */
+ const bool is_always_inline
+ = strub_always_inline_p (node);
+
+ /* Strubbing in general, and each specific strub mode, may have its own set of
+ requirements. We require noipa for strubbing, either because of cloning
+ required for internal strub, or because of caller enumeration required for
+ at-calls strub. We don't consider the at-calls mode eligible if it's not
+ even considered, it has no further requirements. Internal mode requires
+ cloning and the absence of certain features in the body and, like at-calls,
+ it's not eligible if it's not even under consideration.
+
+ ??? Do we need target hooks for further constraints? E.g., x86's
+ "interrupt" attribute breaks internal strubbing because the wrapped clone
+ carries the attribute and thus isn't callable; in this case, we could use a
+ target hook to adjust the clone instead. */
+ const bool strub_eligible
+ = (consider_strub
+ && (is_always_inline || can_strub_p (node)));
+ const bool at_calls_eligible
+ = (consider_at_calls && strub_eligible
+ && can_strub_at_calls_p (node));
+ const bool internal_eligible
+ = (consider_internal && strub_eligible
+ && (is_always_inline
+ || can_strub_internally_p (node)));
+
+ /* In addition to the strict eligibility requirements, some additional
+ constraints are placed on implicit selection of certain modes. These do
+ not prevent the selection of a mode if explicitly specified as part of a
+ function interface (the strub attribute), but they may prevent modes from
+ being selected by the command line or by function bodies. The only actual
+ constraint is on at-calls mode: since we change the function's exposed
+ signature, we won't do it implicitly if the function can possibly be used
+ in ways that do not expect the signature change, e.g., if the function is
+ available to or interposable by other units, if its address is taken,
+ etc. */
+ const bool at_calls_viable
+ = (at_calls_eligible
+ && (strub_attr
+ || (node->has_gimple_body_p ()
+ && node->get_availability () > AVAIL_INTERPOSABLE
+ && ((!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->binds_to_current_def_p ()
+ && node->can_be_local_p ()))
+ && node->only_called_directly_p ())));
+ const bool internal_viable
+ = (internal_eligible);
+
+ /* Shorthand. */
+ const bool strub_viable
+ = (at_calls_viable || internal_viable);
+
+ /* We wish to analyze the body, to look for implicit requests for strub, both
+ to implicitly enable it when the body calls for it, and to report errors if
+ the body calls for it but neither mode is viable (even if that follows from
+ non-eligibility because of the explicit specification of some non-strubbing
+ mode). We can refrain from scanning the body only in rare circumstances:
+ when strub is enabled by a function attribute (scanning might be redundant
+ in telling us to also enable it), and when we are enabling strub implicitly
+ but there are non-viable modes: we want to know whether strubbing is
+ required, to fallback to another mode, even if we're only enabling a
+ certain mode, or, when either mode would do, to report an error if neither
+ happens to be viable. */
+ const bool analyze_body
+ = (strub_attr
+ ? !consider_strub
+ : (strub_flag_auto
+ || (strub_flag_viable && (!at_calls_viable && !internal_viable))
+ || (strub_flag_either && !strub_viable)));
+
+ /* Cases in which strubbing is enabled or disabled by strub_flag_auto.
+ Unsatisfiable requests ought to be reported. */
+ const bool strub_required
+ = ((strub_attr && consider_strub)
+ || (analyze_body && strub_from_body_p (node)));
+
+ /* Besides the required cases, we want to abide by the requests to enabling on
+ an if-viable basis. */
+ const bool strub_enable
+ = (strub_required
+ || (strub_flag_at_calls && at_calls_viable)
+ || (strub_flag_internal && internal_viable)
+ || (strub_flag_either && strub_viable));
+
+ /* And now we're finally ready to select a mode that abides by the viability
+ and eligibility constraints, and that satisfies the strubbing requirements
+ and requests, subject to the constraints. If both modes are viable and
+ strub is to be enabled, pick STRUB_AT_CALLS unless STRUB_INTERNAL was named
+ as preferred. */
+ 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
+ : (strub_enable && at_calls_viable)
+ ? STRUB_AT_CALLS
+ : consider_callable
+ ? STRUB_CALLABLE
+ : STRUB_DISABLED);
+
+ switch (mode)
+ {
+ case STRUB_CALLABLE:
+ if (is_always_inline)
+ break;
+ /* Fall through. */
+
+ case STRUB_DISABLED:
+ if (strub_enable && !strub_attr)
+ {
+ gcc_checking_assert (analyze_body);
+ error_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD requires strub, but no viable strub mode was found",
+ node->decl);
+ break;
+ }
+ /* Fall through. */
+
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ /* Differences from an mode requested through a function attribute are
+ reported in set_strub_mode_to. */
+ break;
+
+ case STRUB_WRAPPED:
+ case STRUB_WRAPPER:
+ default:
+ gcc_unreachable ();
+ }
+
+ return mode;
+}
+
+static void
+set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+ enum strub_mode req_mode = get_strub_mode_from_attr (attr);
+
+ if (attr)
+ {
+ /* Check for and report incompatible mode changes. */
+ if (mode != req_mode
+ && !(req_mode == STRUB_INTERNAL
+ && (mode == STRUB_WRAPPED
+ || mode == STRUB_WRAPPER))
+ && !((req_mode == STRUB_INTERNAL
+ || req_mode == STRUB_AT_CALLS
+ || req_mode == STRUB_CALLABLE)
+ && 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));
+ if (node->alias)
+ {
+ cgraph_node *target = node->ultimate_alias_target ();
+ if (target != node)
+ error_at (DECL_SOURCE_LOCATION (target->decl),
+ "the incompatible selection was determined"
+ " by ultimate alias target %qD",
+ target->decl);
+ }
+
+ /* Report any incompatibilities with explicitly-requested strub. */
+ switch (req_mode)
+ {
+ case STRUB_AT_CALLS:
+ can_strub_at_calls_p (node, true);
+ break;
+
+ case STRUB_INTERNAL:
+ can_strub_internally_p (node, true);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Drop any incompatible strub attributes leading the decl attribute
+ chain. Return if we find one with the mode we need. */
+ for (;;)
+ {
+ if (mode == req_mode)
+ return;
+
+ if (DECL_ATTRIBUTES (node->decl) != attr)
+ break;
+
+ DECL_ATTRIBUTES (node->decl) = TREE_CHAIN (attr);
+ attr = get_strub_attr_from_decl (node->decl);
+ if (!attr)
+ break;
+
+ req_mode = get_strub_mode_from_attr (attr);
+ }
+ }
+ else if (mode == req_mode)
+ return;
+
+ DECL_ATTRIBUTES (node->decl) = tree_cons (get_identifier ("strub"),
+ get_strub_mode_attr_value (mode),
+ DECL_ATTRIBUTES (node->decl));
+}
+
+static enum strub_mode
+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))
+ {
+ /* 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_DISABLED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ cgraph_node *xnode = node;
+ if (node->alias)
+ xnode = node->ultimate_alias_target ();
+ /* Weakrefs may remain unresolved (the above will return node) if
+ their targets are not defined, so make sure we compute a strub
+ mode for them, instead of defaulting to STRUB_DISABLED and
+ rendering them uncallable. */
+ enum strub_mode mode = (xnode != node && !xnode->alias
+ ? get_strub_mode (xnode)
+ : compute_strub_mode (node, attr));
+
+ set_strub_mode_to (node, mode);
+
+ return mode;
+}
+
+/* Non-strub functions shouldn't be called from strub functions,
+ except through callable ones. Always inline strub functions can
+ only be called from strub functions. */
+
+static bool
+strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode caller_mode = get_strub_mode (caller);
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return callee_mode != STRUB_INLINABLE;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ return (flag_strub >= 0);
+
+ case STRUB_DISABLED:
+ return false;
+
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ 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. */
+
+bool
+strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return true;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ strub_mode caller_mode = get_strub_mode (caller);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ return true;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ return false;
+}
+
+/* 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;
+
+ 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_WRAPPED
+ || caller_mode == STRUB_INLINABLE);
+
+ 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);
+ }
+
+ /* ??? 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 {
+
+const pass_data pass_data_ipa_strub_mode = {
+ SIMPLE_IPA_PASS,
+ "strubm",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ 0, // properties_finish
+};
+
+class pass_ipa_strub_mode : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub_mode (gcc::context *ctxt)
+ : simple_ipa_opt_pass (pass_data_ipa_strub_mode, ctxt)
+ {}
+ 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
+ overhead. */
+ if (flag_strub == -2)
+ flag_strub = 0;
+ return flag_strub;
+ }
+ virtual unsigned int execute (function *);
+};
+
+const pass_data pass_data_ipa_strub = {
+ SIMPLE_IPA_PASS,
+ "strub",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ TODO_update_ssa
+ | TODO_cleanup_cfg
+ | TODO_rebuild_cgraph_edges
+ | TODO_verify_il, // properties_finish
+};
+
+class pass_ipa_strub : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub (gcc::context *ctxt)
+ : 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 unsigned int execute (function *);
+
+#define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST) \
+ static tree get_ ## NAME () { \
+ tree decl = builtin_decl_explicit (CODE); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ decl = add_builtin_function \
+ ("__builtin_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ NULL, NULL); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ } \
+ return decl; \
+ }
+
+ DEF_NM_BUILTIN (stack_address,
+ BUILT_IN_STACK_ADDRESS,
+ (ptr_type_node, NULL))
+
+#undef DEF_NM_BUILTIN
+
+#define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST) \
+ static tree get_ ## NAME () { \
+ tree decl = builtin_decl_explicit (CODE); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ tree attrs = NULL; \
+ if (FNSPEC && HAVE_ATTR_FNSPEC) \
+ attrs = tree_cons (get_identifier ("fn spec"), \
+ build_tree_list \
+ (NULL_TREE, \
+ build_string (strlen (FNSPEC), \
+ (FNSPEC))), \
+ attrs); \
+ decl = add_builtin_function_ext_scope \
+ ("__builtin___strub_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ "__strub_" #NAME, attrs); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ } \
+ return decl; \
+ }
+
+ DEF_SS_BUILTIN (enter, ". Ot",
+ BUILT_IN___STRUB_ENTER,
+ (void_type_node, get_pptr (), NULL))
+ DEF_SS_BUILTIN (update, ". Wt",
+ BUILT_IN___STRUB_UPDATE,
+ (void_type_node, get_pptr (), NULL))
+ DEF_SS_BUILTIN (leave, ". w ",
+ BUILT_IN___STRUB_LEAVE,
+ (void_type_node, get_pptr (), NULL))
+
+#undef DEF_SS_BUILTIN
+
+#define DEF_IDENT(NAME) \
+ static inline tree get_ ## NAME () { \
+ static tree identifier = NULL_TREE; \
+ if (!identifier) \
+ identifier = get_identifier (".strub." #NAME); \
+ return identifier; \
+ }
+
+ DEF_IDENT (watermark_ptr)
+ DEF_IDENT (va_list_ptr)
+ DEF_IDENT (apply_args_ptr)
+
+#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 gimple_seq
+ call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
+ gimple_seq seq = NULL)
+ {
+ tree uwm = get_update ();
+ gcall *update = gimple_build_call (uwm, 1, wmptr);
+ gimple_seq_add_stmt (&seq, update);
+ if (node)
+#if !IMPLICIT_CGRAPH_EDGES
+ node->create_edge (cgraph_node::get_create (uwm), update, count, false);
+#else
+ (void)count;
+#endif
+ return seq;
+ }
+
+};
+
+} // anon namespace
+
+typedef hash_set<tree> indirect_parms_t;
+
+static tree
+maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
+{
+ if (DECL_P (op))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (op))
+ {
+ tree ret = gimple_fold_indirect_ref (op);
+ if (!ret)
+ ret = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (op)),
+ op,
+ build_int_cst (TREE_TYPE (op), 0));
+ return ret;
+ }
+ }
+ else if (TREE_CODE (op) == ADDR_EXPR
+ && DECL_P (TREE_OPERAND (op, 0)))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (TREE_OPERAND (op, 0)))
+ return TREE_OPERAND (op, 0);
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_make_indirect (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ indirect_parms_t &indirect_parms = *(indirect_parms_t *)wi->info;
+
+ if (!*op || TYPE_P (*op))
+ {
+ *rec = 0;
+ return NULL_TREE;
+ }
+
+ if (tree repl = maybe_make_indirect (indirect_parms, *op, rec))
+ {
+ *op = repl;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ gimple_stmt_iterator &gsi = *(gimple_stmt_iterator *)wi->info;
+
+ *rec = 0;
+
+ if (!*op || TREE_CODE (*op) != ADDR_EXPR)
+ return NULL_TREE;
+
+ if (!is_gimple_val (*op))
+ {
+ tree ret = force_gimple_operand_gsi (&gsi, *op, true,
+ NULL_TREE, true, GSI_SAME_STMT);
+ gcc_assert (ret != *op);
+ *op = ret;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+build_ref_type_for (tree parm, bool nonaliased = true)
+{
+ gcc_checking_assert (TREE_CODE (parm) == PARM_DECL);
+
+ tree ref_type = build_reference_type (TREE_TYPE (parm));
+
+ if (!nonaliased)
+ return ref_type;
+
+ /* Each PARM turned indirect still points to the distinct memory area at the
+ wrapper, and the reference in unchanging, so we might qualify it, but...
+ const is not really important, since we're only using default defs for the
+ reference parm anyway, and not introducing any defs, and restrict seems to
+ cause trouble. E.g., libgnat/s-concat3.adb:str_concat_3 has memmoves that,
+ if it's wrapped, the memmoves are deleted in dse1. Using a distinct alias
+ set seems to not run afoul of this problem, and it hopefully enables the
+ compiler to tell the pointers do point to objects that are not otherwise
+ aliased. */
+#if 1
+ tree qref_type = build_variant_type_copy (ref_type);
+
+ TYPE_ALIAS_SET (qref_type) = new_alias_set ();
+ record_alias_subset (TYPE_ALIAS_SET (qref_type), get_alias_set (ref_type));
+
+ return qref_type;
+#else
+ tree qref_type = build_qualified_type (ref_type,
+ TYPE_QUAL_RESTRICT
+ | TYPE_QUAL_CONST);
+
+ return qref_type;
+#endif
+}
+
+/* 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)
+{
+#if IMPLICIT_CGRAPH_EDGES
+ return;
+#endif
+
+ cgraph_node *node = cgraph_node::get_create (current_function_decl);
+
+ for (gimple_stmt_iterator gsi = gsi_start (seq);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!is_a <gcall *> (stmt))
+ 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);
+ }
+}
+
+static void
+gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
+{
+ gimple *stmt = gsi_stmt (gsi);
+
+ gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+ 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;
+ bool nothrow_p = (must_not_throw_p
+ || (call && gimple_call_nothrow_p (call))
+ || (eh_lp <= 0
+ && (TREE_NOTHROW (cfun->decl)
+ || !flag_exceptions)));
+
+ if (noreturn_p && nothrow_p)
+ return;
+
+ /* Don't expect an EH edge if we're not to throw, or if we're not in an EH
+ region yet. */
+ bool no_eh_edge_p = (nothrow_p || !eh_lp);
+ bool must_end_bb = stmt_ends_bb_p (stmt);
+
+ edge eft = NULL, eeh = NULL;
+ if (must_end_bb && !(noreturn_p && no_eh_edge_p))
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+
+ edge e;
+ edge_iterator ei;
+ FOR_EACH_EDGE (e, ei, gsi_bb (gsi)->succs)
+ {
+ if ((e->flags & EDGE_EH))
+ {
+ gcc_checking_assert (!eeh);
+ eeh = e;
+#if !CHECKING_P
+ if (eft || noreturn_p)
+ break;
+#endif
+ }
+ if ((e->flags & EDGE_FALLTHRU))
+ {
+ gcc_checking_assert (!eft);
+ eft = e;
+#if !CHECKING_P
+ if (eeh || no_eh_edge_p)
+ break;
+#endif
+ }
+ }
+
+ gcc_checking_assert (!(eft && (eft->flags & EDGE_FALLTHRU))
+ == noreturn_p);
+ gcc_checking_assert (!(eeh && (eeh->flags & EDGE_EH))
+ == no_eh_edge_p);
+ gcc_checking_assert (eft != eeh);
+ }
+
+ if (!noreturn_p)
+ {
+ gimple_seq nseq = nothrow_p ? seq : gimple_seq_copy (seq);
+
+ if (must_end_bb)
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+ add_call_edges_for_seq (nseq, eft->count ());
+ gsi_insert_seq_on_edge_immediate (eft, nseq);
+ }
+ else
+ {
+ add_call_edges_for_seq (nseq, gsi_bb (gsi)->count);
+ gsi_insert_seq_after (&gsi, nseq, GSI_SAME_STMT);
+ }
+ }
+
+ if (nothrow_p)
+ return;
+
+ if (eh_lp)
+ {
+ add_call_edges_for_seq (seq, eeh->count ());
+ gsi_insert_seq_on_edge_immediate (eeh, seq);
+ return;
+ }
+
+ /* A throwing call may appear within a basic block in a function that doesn't
+ have any EH regions. We're going to add a cleanup if so, therefore the
+ block will have to be split. */
+ basic_block bb = gsi_bb (gsi);
+ if (!gsi_one_before_end_p (gsi))
+ split_block (bb, stmt);
+
+ /* Create a new block for the EH cleanup. */
+ basic_block bb_eh_cleanup = create_empty_bb (bb);
+ if (dom_info_available_p (CDI_DOMINATORS))
+ set_immediate_dominator (CDI_DOMINATORS, bb_eh_cleanup, bb);
+ if (current_loops)
+ add_bb_to_loop (bb_eh_cleanup, current_loops->tree_root);
+
+ /* Make the new block an EH cleanup for the call. */
+ eh_region new_r = gen_eh_region_cleanup (NULL);
+ eh_landing_pad lp = gen_eh_landing_pad (new_r);
+ tree label = gimple_block_label (bb_eh_cleanup);
+ lp->post_landing_pad = label;
+ EH_LANDING_PAD_NR (label) = lp->index;
+ add_stmt_to_eh_lp (stmt, lp->index);
+
+ /* Add the cleanup code to the EH cleanup block. */
+ gsi = gsi_after_labels (bb_eh_cleanup);
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
+
+ /* And then propagate the exception further. */
+ gresx *resx = gimple_build_resx (new_r->index);
+ gsi_insert_before (&gsi, resx, GSI_SAME_STMT);
+
+ /* Finally, wire the EH cleanup block into the CFG. */
+ make_eh_edges (stmt);
+ add_call_edges_for_seq (seq, single_pred_edge (bb_eh_cleanup)->count ());
+}
+
+/* Copy the attribute list at *ATTRS, minus any NAME attributes, leaving
+ shareable trailing nodes alone. */
+
+static inline void
+remove_named_attribute_unsharing (const char *name, tree *attrs)
+{
+ while (tree found = lookup_attribute (name, *attrs))
+ {
+ /* Copy nodes up to the next NAME attribute. */
+ while (*attrs != found)
+ {
+ *attrs = tree_cons (TREE_PURPOSE (*attrs),
+ TREE_VALUE (*attrs),
+ TREE_CHAIN (*attrs));
+ attrs = &TREE_CHAIN (*attrs);
+ }
+ /* Then drop it. */
+ gcc_checking_assert (*attrs == found);
+ *attrs = TREE_CHAIN (*attrs);
+ }
+}
+
+static int last_cgraph_order;
+
+static bool
+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;
+
+ 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. */
+ for (int aliases = 0; aliases <= 1; aliases++)
+ FOR_EACH_FUNCTION (node)
+ {
+ if (!node->alias != !aliases)
+ continue;
+
+ /* Already done. */
+ if (node->order < last_cgraph_order)
+ continue;
+
+ enum strub_mode mode = set_strub_mode (node);
+
+ if (mode == STRUB_AT_CALLS || mode == STRUB_INTERNAL)
+ any_strub = true;
+ }
+
+ if (any_strub)
+ last_cgraph_order = symtab->order;
+
+ return any_strub;
+}
+
+unsigned int
+pass_ipa_strub_mode::execute (function *)
+{
+ last_cgraph_order = 0;
+ bool any_strub = ipa_strub_set_mode_for_new_functions ();
+
+ if (!any_strub)
+ flag_strub = 0;
+ else
+ /* Verify before any inlining or other transformations. */
+ verify_strub ();
+
+ return 0;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub_mode (gcc::context *ctxt)
+{
+ return new pass_ipa_strub_mode (ctxt);
+}
+
+unsigned int
+pass_ipa_strub::execute (function *)
+{
+ cgraph_node *onode;
+
+ ipa_strub_set_mode_for_new_functions ();
+
+ FOR_EACH_FUNCTION (onode)
+ {
+ enum strub_mode mode = get_strub_mode (onode);
+
+ if (mode == STRUB_AT_CALLS)
+ {
+ 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));
+
+ 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. */
+ 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;
+
+ tree wmptr = build_decl (DECL_SOURCE_LOCATION (onode->decl),
+ PARM_DECL,
+ get_watermark_ptr (),
+ get_qpptr ());
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_ARG_TYPE (wmptr) = get_qpptr ();
+ DECL_CONTEXT (wmptr) = onode->decl;
+ TREE_USED (wmptr) = 1;
+ DECL_CHAIN (wmptr) = *pargs;
+ *pargs = wmptr;
+
+ if (onode->alias)
+ continue;
+
+ 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);
+
+ 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));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca)
+ {
+ 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);
+
+ if (!is_gimple_call (stmt))
+ continue;
+
+ gcall *call = as_a <gcall *> (stmt);
+
+ if (gimple_alloca_call_p (call))
+ {
+ /* Capture stack growth. */
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)
+ ->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ }
+ }
+
+ pop_cfun ();
+
+#if 0
+ compute_fn_summary (onode, true);
+#endif
+ continue;
+ }
+
+ if (mode != STRUB_INTERNAL
+ || !onode->has_gimple_body_p ())
+ 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 (!DECL_STRUCT_FUNCTION (onode->decl))
+ {
+ inform (DECL_SOURCE_LOCATION (onode->decl),
+ "not splitting struct-less function %qD for stack scrubbing",
+ onode->decl);
+ continue;
+ }
+
+ if (!onode->lowered)
+ {
+ inform (DECL_SOURCE_LOCATION (onode->decl),
+ "not splitting non-lowered function %qD for stack scrubbing",
+ 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 stack scrubbing",
+ onode->decl);
+ continue;
+ }
+
+ bool is_stdarg = calls_builtin_va_start_p (onode);;
+ bool apply_args = calls_builtin_apply_args_p (onode);
+
+ vec<ipa_adjusted_param, va_gc> *nparms = NULL;
+ unsigned j = 0;
+ {
+ // The following loop copied from ipa-split.c:split_function.
+ for (tree parm = DECL_ARGUMENTS (onode->decl);
+ parm; parm = DECL_CHAIN (parm), j++)
+ {
+ ipa_adjusted_param adj = {};
+ adj.op = IPA_PARAM_OP_COPY;
+ adj.base_index = j;
+ adj.prev_clone_index = j;
+ vec_safe_push (nparms, adj);
+ }
+
+ if (apply_args)
+ {
+ ipa_adjusted_param aaadj = {};
+ aaadj.op = IPA_PARAM_OP_NEW;
+ aaadj.type = get_qpptr ();
+ vec_safe_push (nparms, aaadj);
+ }
+
+ if (is_stdarg)
+ {
+ ipa_adjusted_param vladj = {};
+ vladj.op = IPA_PARAM_OP_NEW;
+ vladj.type = get_qpvalst ();
+ vec_safe_push (nparms, vladj);
+ }
+
+ ipa_adjusted_param wmadj = {};
+ wmadj.op = IPA_PARAM_OP_NEW;
+ wmadj.type = get_qpptr ();
+ vec_safe_push (nparms, wmadj);
+ }
+ ipa_param_adjustments adj (nparms, -1, false);
+
+ cgraph_node *nnode = onode->create_version_clone_with_body
+ (auto_vec<cgraph_edge *> (0),
+ NULL, &adj, NULL, NULL, "strub", NULL);
+
+ if (!nnode)
+ {
+ error_at (DECL_SOURCE_LOCATION (onode->decl),
+ "failed to split %qD for stack scrubbing",
+ onode->decl);
+ continue;
+ }
+
+ onode->split_part = true;
+ if (onode->calls_comdat_local)
+ nnode->add_to_same_comdat_group (onode);
+
+ gcc_checking_assert (!DECL_STRUCT_FUNCTION (nnode->decl)->stdarg);
+
+ set_strub_mode_to (onode, STRUB_WRAPPER);
+ set_strub_mode_to (nnode, STRUB_WRAPPED);
+
+ /* Decide which of the wrapped function's parms we want to turn into
+ references to the argument passed to the wrapper. In general, we want to
+ copy small arguments, and avoid copying large ones. Variable-sized array
+ lengths given by other arguments, as in 20020210-1.c, would lead to
+ problems if passed by value, after resetting the original function and
+ dropping the length computation; passing them by reference works.
+ DECL_BY_REFERENCE is *not* a substitute for this: it involves copying
+ anyway, but performed at the caller. */
+ indirect_parms_t indirect_nparms (3, false);
+ unsigned adjust_ftype = 0;
+ unsigned named_args = 0;
+ for (tree parm = DECL_ARGUMENTS (onode->decl),
+ nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (TREE_TYPE (nnode->decl));
+ parm;
+ named_args++,
+ parm = DECL_CHAIN (parm),
+ nparm = DECL_CHAIN (nparm),
+ nparmt = nparmt ? TREE_CHAIN (nparmt) : NULL_TREE)
+ if (!(0 /* DECL_BY_REFERENCE (narg) */
+ || is_gimple_reg_type (TREE_TYPE (nparm))
+ || VECTOR_TYPE_P (TREE_TYPE (nparm))
+ || TREE_CODE (TREE_TYPE (nparm)) == COMPLEX_TYPE
+ || (tree_fits_uhwi_p (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ && (tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ <= 4 * UNITS_PER_WORD))))
+ {
+ indirect_nparms.add (nparm);
+
+ /* ??? Is there any case in which it is not safe to suggest the parms
+ turned indirect don't alias anything else? They are distinct,
+ unaliased memory in the wrapper, and the wrapped can't possibly
+ take pointers into them because none of the pointers passed to the
+ wrapper can alias other incoming parameters passed by value, even
+ if with transparent reference, and the wrapper doesn't take any
+ extra parms that could point into wrapper's parms. So we can
+ probably drop the TREE_ADDRESSABLE and keep the true. */
+ tree ref_type = build_ref_type_for (nparm,
+ true
+ || !TREE_ADDRESSABLE (parm));
+
+ DECL_ARG_TYPE (nparm) = TREE_TYPE (nparm) = ref_type;
+ relayout_decl (nparm);
+ TREE_ADDRESSABLE (nparm) = 0;
+ DECL_BY_REFERENCE (nparm) = 0;
+#if FOR_GCC_11P
+ DECL_NOT_GIMPLE_REG_P (nparm) = 0;
+#else
+ DECL_GIMPLE_REG_P (nparm) = 1;
+#endif
+ /* ??? This avoids mismatches in debug info bind stmts in
+ e.g. a-chahan . */
+ DECL_ABSTRACT_ORIGIN (nparm) = NULL;
+
+ if (nparmt)
+ adjust_ftype++;
+ }
+
+ /* Also adjust the wrapped function type, if needed. */
+ if (adjust_ftype)
+ {
+ tree nftype = TREE_TYPE (nnode->decl);
+
+ /* We always add at least one argument at the end of the signature, when
+ cloning the function, so we don't expect to need to duplicate the
+ type here. */
+ gcc_checking_assert (TYPE_ARG_TYPES (nftype)
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+
+#if HAVE_ATTR_FNSPEC
+ /* Check that fnspec still works for the modified function signature,
+ and drop it otherwise. */
+ bool drop_fnspec = false;
+ tree fnspec = lookup_attribute ("fn spec", TYPE_ATTRIBUTES (nftype));
+ attr_fnspec spec = fnspec ? attr_fnspec (fnspec) : attr_fnspec ("");
+
+ unsigned retcopy;
+ if (!(fnspec && spec.returns_arg (&retcopy)))
+ retcopy = (unsigned) -1;
+
+ unsigned i = 0;
+#endif
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (nftype);
+ adjust_ftype > 0;
+#if HAVE_ATTR_FNSPEC
+ i++,
+#endif
+ nparm = DECL_CHAIN (nparm), nparmt = TREE_CHAIN (nparmt))
+ if (indirect_nparms.contains (nparm))
+ {
+ TREE_VALUE (nparmt) = TREE_TYPE (nparm);
+ adjust_ftype--;
+
+#if HAVE_ATTR_FNSPEC
+ if (fnspec && !drop_fnspec)
+ {
+ if (i == retcopy)
+ drop_fnspec = true;
+ else if (spec.arg_specified_p (i))
+ {
+ /* Properties that apply to pointers only must not be
+ present, because we don't make pointers further
+ indirect. */
+ gcc_checking_assert
+ (!spec.arg_max_access_size_given_by_arg_p (i, NULL));
+ gcc_checking_assert (!spec.arg_copied_to_arg_p (i, NULL));
+
+ /* Any claim of direct access only is invalidated by
+ adding an indirection level. */
+ if (spec.arg_direct_p (i))
+ drop_fnspec = true;
+
+ /* If there's a claim the argument is not read from, the
+ added indirection invalidates it: if the argument is
+ used at all, then the pointer will necessarily be
+ read. */
+ if (!spec.arg_maybe_read_p (i)
+ && spec.arg_used_p (i))
+ drop_fnspec = true;
+ }
+ }
+#endif
+ }
+
+#if HAVE_ATTR_FNSPEC
+ /* ??? Maybe we could adjust it instead. */
+ if (drop_fnspec)
+ remove_named_attribute_unsharing ("fn spec",
+ &TYPE_ATTRIBUTES (nftype));
+#endif
+
+ TREE_TYPE (nnode->decl) = nftype;
+ }
+
+#if ATTR_FNSPEC_DECONST_WATERMARK
+ {
+ int flags = flags_from_decl_or_type (nnode->decl);
+ tree fnspec = lookup_attribute ("fn spec", TREE_TYPE (nnode->decl));
+
+ if ((flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS)) || fnspec)
+ {
+ size_t xargs = 1 + int (is_stdarg) + int (apply_args);
+ 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! */
+ bool no_writes_p = true;
+ if (fnspec)
+ {
+ tree fnspecstr = TREE_VALUE (TREE_VALUE (fnspec));
+ curlen = TREE_STRING_LENGTH (fnspecstr);
+ memcpy (nspec, TREE_STRING_POINTER (fnspecstr), curlen);
+ if (!(flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS))
+ && curlen >= 2
+ && nspec[1] != 'c' && nspec[1] != 'C'
+ && nspec[1] != 'p' && nspec[1] != 'P')
+ no_writes_p = false;
+ }
+ if (!curlen)
+ {
+ nspec[curlen++] = '.';
+ nspec[curlen++] = ((flags & ECF_CONST)
+ ? 'c'
+ : (flags & ECF_PURE)
+ ? 'p'
+ : ' ');
+ }
+ while (curlen < tgtlen - 2 * xargs)
+ {
+ nspec[curlen++] = '.';
+ nspec[curlen++] = ' ';
+ }
+
+ /* These extra args are unlikely to be present in const or pure
+ functions. It's conceivable that a function that takes variable
+ arguments, or that passes its arguments on to another function,
+ could be const or pure, but it would not modify the arguments, and,
+ being pure or const, it couldn't possibly modify or even access
+ memory referenced by them. But it can read from these internal
+ data structures created by the wrapper, and from any
+ argument-passing memory referenced by them, so we denote the
+ possibility of reading from multiple levels of indirection, but
+ only of reading because const/pure. */
+ if (apply_args)
+ {
+ nspec[curlen++] = (no_writes_p ? 'r' : '.');
+ nspec[curlen++] = (no_writes_p ? 't' : ' ');
+ }
+ if (is_stdarg)
+ {
+ nspec[curlen++] = (no_writes_p ? 'r' : '.');
+ nspec[curlen++] = (no_writes_p ? 't' : ' ');
+ }
+
+ nspec[curlen++] = 'W';
+ nspec[curlen++] = 't';
+
+ /* The type has already been copied before adding parameters. */
+ gcc_checking_assert (TYPE_ARG_TYPES (TREE_TYPE (nnode->decl))
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+ TYPE_ATTRIBUTES (TREE_TYPE (nnode->decl))
+ = tree_cons (get_identifier ("fn spec"),
+ build_tree_list (NULL_TREE,
+ build_string (tgtlen, nspec)),
+ TYPE_ATTRIBUTES (TREE_TYPE (nnode->decl)));
+ }
+ }
+#endif
+
+ {
+ tree decl = onode->decl;
+ cgraph_node *target = nnode;
+
+ { // copied from create_wrapper
+
+ /* Preserve DECL_RESULT so we get right by reference flag. */
+ tree decl_result = DECL_RESULT (decl);
+
+ /* Remove the function's body but keep arguments to be reused
+ for thunk. */
+ onode->release_body (true);
+ onode->reset ();
+
+ DECL_UNINLINABLE (decl) = false;
+ DECL_RESULT (decl) = decl_result;
+ DECL_INITIAL (decl) = NULL;
+ allocate_struct_function (decl, false);
+ set_cfun (NULL);
+
+ /* Turn alias into thunk and expand it into GIMPLE representation. */
+ onode->definition = true;
+
+#if FOR_GCC_11P
+ thunk_info::get_create (onode);
+ onode->thunk = true;
+#else
+ memset (&onode->thunk, 0, sizeof (cgraph_thunk_info));
+ onode->thunk.thunk_p = true;
+ onode->thunk.alias = target->decl;
+#endif
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (target, NULL, onode->count);
+#endif
+ onode->callees->can_throw_external = !TREE_NOTHROW (target->decl);
+
+ tree arguments = DECL_ARGUMENTS (decl);
+
+ while (arguments)
+ {
+ TREE_ADDRESSABLE (arguments) = false;
+ arguments = TREE_CHAIN (arguments);
+ }
+
+ {
+ tree alias = onode->callees->callee->decl;
+ tree thunk_fndecl = decl;
+ tree a;
+
+ int nxargs = 1 + is_stdarg + apply_args;
+
+ { // Simplified from expand_thunk.
+ tree restype;
+ basic_block bb, then_bb, else_bb, return_bb;
+ gimple_stmt_iterator bsi;
+ int nargs = 0;
+ tree arg;
+ int i;
+ tree resdecl;
+ tree restmp = NULL;
+
+ gcall *call;
+ greturn *ret;
+ bool alias_is_noreturn = TREE_THIS_VOLATILE (alias);
+
+ a = DECL_ARGUMENTS (thunk_fndecl);
+
+ current_function_decl = thunk_fndecl;
+
+#if FOR_GCC_11P
+ /* Ensure thunks are emitted in their correct sections. */
+ resolve_unique_section (thunk_fndecl, 0,
+ flag_function_sections);
+#endif
+
+ bitmap_obstack_initialize (NULL);
+
+ /* Build the return declaration for the function. */
+ restype = TREE_TYPE (TREE_TYPE (thunk_fndecl));
+ if (DECL_RESULT (thunk_fndecl) == NULL_TREE)
+ {
+ resdecl = build_decl (input_location, RESULT_DECL, 0, restype);
+ DECL_ARTIFICIAL (resdecl) = 1;
+ DECL_IGNORED_P (resdecl) = 1;
+ DECL_CONTEXT (resdecl) = thunk_fndecl;
+ DECL_RESULT (thunk_fndecl) = resdecl;
+ }
+ else
+ resdecl = DECL_RESULT (thunk_fndecl);
+
+ profile_count cfg_count = onode->count;
+ if (!cfg_count.initialized_p ())
+ cfg_count = profile_count::from_gcov_type (BB_FREQ_MAX).guessed_local ();
+
+ bb = then_bb = else_bb = return_bb
+ = init_lowered_empty_function (thunk_fndecl, true, cfg_count);
+
+ bsi = gsi_start_bb (bb);
+
+ /* Build call to the function being thunked. */
+ if (!VOID_TYPE_P (restype)
+ && (!alias_is_noreturn
+ || TREE_ADDRESSABLE (restype)
+ || TREE_CODE (TYPE_SIZE_UNIT (restype)) != INTEGER_CST))
+ {
+ if (DECL_BY_REFERENCE (resdecl))
+ {
+ restmp = gimple_fold_indirect_ref (resdecl);
+ if (!restmp)
+ restmp = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (resdecl)),
+ resdecl,
+ build_int_cst (TREE_TYPE (resdecl), 0));
+ }
+ else if (!is_gimple_reg_type (restype))
+ {
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl)))
+ {
+ restmp = resdecl;
+
+ if (VAR_P (restmp))
+ {
+ add_local_decl (cfun, restmp);
+ BLOCK_VARS (DECL_INITIAL (current_function_decl))
+ = restmp;
+ }
+ }
+ else
+ restmp = create_tmp_var (restype, "retval");
+ }
+ else
+ restmp = create_tmp_reg (restype, "retval");
+ }
+
+ for (arg = a; arg; arg = DECL_CHAIN (arg))
+ nargs++;
+ auto_vec<tree> vargs (nargs + nxargs);
+ i = 0;
+ arg = a;
+
+ if (nargs)
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl);
+ i < nargs;
+ i++, arg = DECL_CHAIN (arg), nparm = DECL_CHAIN (nparm))
+ {
+ tree save_arg = arg;
+ tree tmp = arg;
+
+ /* Arrange to pass indirectly the parms, if we decided to do
+ so, and revert its type in the wrapper. */
+ if (indirect_nparms.contains (nparm))
+ {
+ tree ref_type = TREE_TYPE (nparm);
+ TREE_ADDRESSABLE (arg) = true;
+ tree addr = build1 (ADDR_EXPR, ref_type, arg);
+ tmp = arg = addr;
+ }
+#if ! FOR_GCC_11P
+ else if (VECTOR_TYPE_P (TREE_TYPE (arg))
+ || TREE_CODE (TREE_TYPE (arg)) == COMPLEX_TYPE)
+ DECL_GIMPLE_REG_P (arg) = 1;
+#else
+ else
+ DECL_NOT_GIMPLE_REG_P (arg) = 0;
+#endif
+
+ /* Convert the argument back to the type used by the calling
+ conventions, e.g. a non-prototyped float type is passed as
+ double, as in 930603-1.c, and needs to be converted back to
+ double to be passed on unchanged to the wrapped
+ function. */
+ if (TREE_TYPE (nparm) != DECL_ARG_TYPE (nparm))
+ arg = fold_convert (DECL_ARG_TYPE (nparm), arg);
+
+ if (!is_gimple_val (arg))
+ {
+ tmp = create_tmp_reg (TYPE_MAIN_VARIANT
+ (TREE_TYPE (arg)), "arg");
+ gimple *stmt = gimple_build_assign (tmp, arg);
+ gsi_insert_after (&bsi, stmt, GSI_NEW_STMT);
+ }
+ vargs.quick_push (tmp);
+ arg = save_arg;
+ }
+ /* These strub arguments are adjusted later. */
+ if (apply_args)
+ vargs.quick_push (null_pointer_node);
+ if (is_stdarg)
+ vargs.quick_push (null_pointer_node);
+ vargs.quick_push (null_pointer_node);
+ call = gimple_build_call_vec (build_fold_addr_expr_loc (0, alias),
+ vargs);
+ onode->callees->call_stmt = call;
+ // gimple_call_set_from_thunk (call, true);
+ if (DECL_STATIC_CHAIN (alias))
+ {
+ tree p = DECL_STRUCT_FUNCTION (alias)->static_chain_decl;
+ tree type = TREE_TYPE (p);
+ tree decl = build_decl (DECL_SOURCE_LOCATION (thunk_fndecl),
+ PARM_DECL, create_tmp_var_name ("CHAIN"),
+ type);
+ DECL_ARTIFICIAL (decl) = 1;
+ DECL_IGNORED_P (decl) = 1;
+ TREE_USED (decl) = 1;
+ DECL_CONTEXT (decl) = thunk_fndecl;
+ DECL_ARG_TYPE (decl) = type;
+ TREE_READONLY (decl) = 1;
+
+ struct function *sf = DECL_STRUCT_FUNCTION (thunk_fndecl);
+ sf->static_chain_decl = decl;
+
+ gimple_call_set_chain (call, decl);
+ }
+
+ /* Return slot optimization is always possible and in fact required to
+ return values with DECL_BY_REFERENCE. */
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl))
+ && (!is_gimple_reg_type (TREE_TYPE (resdecl))
+ || DECL_BY_REFERENCE (resdecl)))
+ gimple_call_set_return_slot_opt (call, true);
+
+ if (restmp)
+ {
+ gimple_call_set_lhs (call, restmp);
+ gcc_assert (useless_type_conversion_p (TREE_TYPE (restmp),
+ TREE_TYPE (TREE_TYPE (alias))));
+ }
+ gsi_insert_after (&bsi, call, GSI_NEW_STMT);
+ if (!alias_is_noreturn)
+ {
+ /* Build return value. */
+ if (!DECL_BY_REFERENCE (resdecl))
+ ret = gimple_build_return (restmp);
+ else
+ ret = gimple_build_return (resdecl);
+
+ gsi_insert_after (&bsi, ret, GSI_NEW_STMT);
+ }
+ else
+ {
+ remove_edge (single_succ_edge (bb));
+ }
+
+ cfun->gimple_df->in_ssa_p = true;
+ update_max_bb_count ();
+ profile_status_for_fn (cfun)
+ = cfg_count.initialized_p () && cfg_count.ipa_p ()
+ ? PROFILE_READ : PROFILE_GUESSED;
+#if FOR_GCC_11P
+ /* FIXME: C++ FE should stop setting TREE_ASM_WRITTEN on thunks. */
+ // TREE_ASM_WRITTEN (thunk_fndecl) = false;
+#endif
+ delete_unreachable_blocks ();
+ update_ssa (TODO_update_ssa);
+ checking_verify_flow_info ();
+ free_dominance_info (CDI_DOMINATORS);
+
+ /* Since we want to emit the thunk, we explicitly mark its name as
+ referenced. */
+#if FOR_GCC_11P
+ onode->thunk = false;
+#else
+ onode->thunk.thunk_p = false;
+#endif
+ onode->lowered = true;
+ bitmap_obstack_release (NULL);
+ }
+ current_function_decl = NULL;
+ set_cfun (NULL);
+ }
+
+#if FOR_GCC_11P
+ thunk_info::remove (onode);
+#endif
+
+ // some more of create_wrapper at the end of the next block.
+ }
+ }
+
+ {
+ tree aaptr = NULL_TREE;
+ tree vaptr = NULL_TREE;
+ tree wmptr = NULL_TREE;
+ for (tree arg = DECL_ARGUMENTS (nnode->decl); arg; arg = DECL_CHAIN (arg))
+ {
+ aaptr = vaptr;
+ vaptr = wmptr;
+ wmptr = arg;
+ }
+
+ if (!apply_args)
+ aaptr = NULL_TREE;
+ /* The trailing args are [apply_args], [va_list_ptr], and
+ watermark. If we don't have a va_list_ptr, the penultimate
+ argument is apply_args.
+ */
+ else if (!is_stdarg)
+ aaptr = vaptr;
+
+ if (!is_stdarg)
+ vaptr = NULL_TREE;
+
+ DECL_NAME (wmptr) = get_watermark_ptr ();
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_IGNORED_P (wmptr) = 1;
+ TREE_USED (wmptr) = 1;
+
+ if (is_stdarg)
+ {
+ DECL_NAME (vaptr) = get_va_list_ptr ();
+ DECL_ARTIFICIAL (vaptr) = 1;
+ DECL_IGNORED_P (vaptr) = 1;
+ TREE_USED (vaptr) = 1;
+ }
+
+ if (apply_args)
+ {
+ DECL_NAME (aaptr) = get_apply_args_ptr ();
+ DECL_ARTIFICIAL (aaptr) = 1;
+ DECL_IGNORED_P (aaptr) = 1;
+ TREE_USED (aaptr) = 1;
+ }
+
+ push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ bool any_indirect = !indirect_nparms.is_empty ();
+
+ if (any_indirect)
+ {
+ 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);
+
+ 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
+ || is_stdarg || apply_args)
+ for (cgraph_edge *e = nnode->callees, *enext; e; e = enext)
+ {
+ gcall *call = e->call_stmt;
+ gimple_stmt_iterator gsi = gsi_for_stmt (call);
+ tree fndecl = e->callee->decl;
+
+ enext = e->next_callee;
+
+ if (gimple_alloca_call_p (call))
+ {
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ else if (fndecl && is_stdarg
+ && fndecl_built_in_p (fndecl, BUILT_IN_VA_START))
+ {
+ if (builtin_decl_explicit (BUILT_IN_VA_START) != fndecl)
+ sorry_at (gimple_location (call),
+ "nonstandard stdarg conventions");
+ tree bvacopy = builtin_decl_explicit (BUILT_IN_VA_COPY);
+ gimple_call_set_fndecl (call, bvacopy);
+ tree arg = vaptr;
+ /* The va_copy source must be dereferenced, unless it's an array
+ type, that would have decayed to a pointer. */
+ if (TREE_CODE (TREE_TYPE (TREE_TYPE (vaptr))) != ARRAY_TYPE)
+ {
+ arg = gimple_fold_indirect_ref (vaptr);
+ if (!arg)
+ arg = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (vaptr)),
+ vaptr,
+ build_int_cst (TREE_TYPE (vaptr), 0));
+ }
+ gimple_call_set_arg (call, 1, arg);
+ update_stmt (call);
+ e->redirect_callee (cgraph_node::get_create (bvacopy));
+ }
+ else if (fndecl && apply_args
+ && fndecl_built_in_p (fndecl, BUILT_IN_APPLY_ARGS))
+ {
+ tree lhs = gimple_call_lhs (call);
+ gassign *assign = gimple_build_assign (lhs, aaptr);
+ gsi_replace (&gsi, assign, true);
+ cgraph_edge::remove (e);
+ }
+ }
+
+ { // a little more copied from create_wrapper
+
+ /* Inline summary set-up. */
+ nnode->analyze ();
+ // inline_analyze_function (nnode);
+ }
+
+ pop_cfun ();
+ }
+
+ {
+ 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));
+
+ 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
+ onode->create_edge (cgraph_node::get_create (enter),
+ stptr, gsi_bb (gsi)->count, false);
+#endif
+
+ int nargs = gimple_call_num_args (wrcall);
+
+ gimple_seq seq = NULL;
+
+ if (apply_args)
+ {
+ tree aalst = create_tmp_var (ptr_type_node, ".strub.appargs");
+ tree bappargs = builtin_decl_explicit (BUILT_IN_APPLY_ARGS);
+ gcall *appargs = gimple_build_call (bappargs, 0);
+ gimple_call_set_lhs (appargs, aalst);
+ gsi_insert_before (&gsi, appargs, GSI_SAME_STMT);
+ gimple_call_set_arg (wrcall, nargs - 2 - is_stdarg, aalst);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bappargs),
+ appargs, gsi_bb (gsi)->count, false);
+#endif
+ }
+
+ if (is_stdarg)
+ {
+ tree valst = create_tmp_var (va_list_type_node, ".strub.va_list");
+ TREE_ADDRESSABLE (valst) = true;
+ tree vaptr = build1 (ADDR_EXPR,
+ build_pointer_type (va_list_type_node),
+ valst);
+ gimple_call_set_arg (wrcall, nargs - 2, unshare_expr (vaptr));
+
+ tree bvastart = builtin_decl_explicit (BUILT_IN_VA_START);
+ gcall *vastart = gimple_build_call (bvastart, 2,
+ unshare_expr (vaptr),
+ integer_zero_node);
+ gsi_insert_before (&gsi, vastart, GSI_SAME_STMT);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bvastart),
+ vastart, gsi_bb (gsi)->count, false);
+#endif
+
+ tree bvaend = builtin_decl_explicit (BUILT_IN_VA_END);
+ gcall *vaend = gimple_build_call (bvaend, 1, unshare_expr (vaptr));
+ gimple_seq_add_stmt (&seq, vaend);
+ }
+
+ gimple_call_set_arg (wrcall, nargs - 1, unshare_expr (swmp));
+ // gimple_call_set_tail (wrcall, false);
+ update_stmt (wrcall);
+
+ {
+#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);
+
+ /* For nnode, we don't rebuild edges because we wish to retain
+ any redirections copied to it from earlier passes, so we add
+ call graph edges explicitly there, but for onode, we create a
+ fresh function, so we may as well just issue the calls and
+ then rebuild all cgraph edges. */
+ // cgraph_edge::rebuild_edges ();
+ onode->analyze ();
+ // inline_analyze_function (onode);
+
+ 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;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub (gcc::context *ctxt)
+{
+ return new pass_ipa_strub (ctxt);
+}
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
new file mode 100644
index 00000000000..cadbca5002a
--- /dev/null
+++ b/gcc/ipa-strub.h
@@ -0,0 +1,25 @@
+/* strub (stack scrubbing) infrastructure.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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);
diff --git a/gcc/passes.def b/gcc/passes.def
index f5d88a61b0e..677c000e80b 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -53,6 +53,7 @@ along with GCC; see the file COPYING3. If not see
INSERT_PASSES_AFTER (all_small_ipa_passes)
NEXT_PASS (pass_ipa_free_lang_data);
NEXT_PASS (pass_ipa_function_and_variable_visibility);
+ NEXT_PASS (pass_ipa_strub_mode);
NEXT_PASS (pass_build_ssa_passes);
PUSH_INSERT_PASSES_WITHIN (pass_build_ssa_passes)
NEXT_PASS (pass_fixup_cfg);
@@ -111,6 +112,7 @@ along with GCC; see the file COPYING3. If not see
POP_INSERT_PASSES ()
NEXT_PASS (pass_ipa_remove_symbols);
+ NEXT_PASS (pass_ipa_strub);
NEXT_PASS (pass_ipa_oacc);
PUSH_INSERT_PASSES_WITHIN (pass_ipa_oacc)
NEXT_PASS (pass_ipa_pta);
diff --git a/gcc/testsuite/g++.dg/wrappers/strub1.C b/gcc/testsuite/g++.dg/wrappers/strub1.C
new file mode 100644
index 00000000000..a474a929649
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub1.C
@@ -0,0 +1,18 @@
+// { dg-do run }
+
+// Check that we don't get extra copies.
+
+struct T {
+ T &self;
+ void check () const { if (&self != this) __builtin_abort (); }
+ T() : self (*this) { check (); }
+ T(const T& ck) : self (*this) { ck.check (); check (); }
+ ~T() { check (); }
+};
+
+T foo (T) { return T(); }
+T bar (T p) { return foo (p); }
+
+int main () {
+ bar (T{});
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub2.C b/gcc/testsuite/g++.dg/wrappers/strub2.C
new file mode 100644
index 00000000000..25a62166448
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub2.C
@@ -0,0 +1,22 @@
+// { dg-do run }
+
+// This doesn't really test anything yet. We should mark the
+// variables as requiring strubbing, and somehow check that the
+// wrapped functions take the parameter by reference.
+
+struct T {
+ char d[32 * sizeof(void*)];
+};
+
+T foo (T q) { asm ("" : : "m"(q)); return q; }
+T bar (T p) { return foo (p); }
+
+T tmp;
+T tmp2;
+
+int main () {
+ __builtin_memset (&tmp, 0x55, sizeof (tmp));
+ tmp2 = bar (tmp);
+ if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
+ __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub3.C b/gcc/testsuite/g++.dg/wrappers/strub3.C
new file mode 100644
index 00000000000..e1b51cd0399
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub3.C
@@ -0,0 +1,22 @@
+// { dg-do run }
+
+// This doesn't really test anything yet. We should mark the
+// variables as requiring strubbing, and somehow check that the
+// wrapped functions take the parameter by reference.
+
+struct T {
+ char d[32 * sizeof(void*)];
+};
+
+static T foo (T q) { asm ("" : : "m"(q)); return q; }
+static T bar (T p) { return foo (p); }
+
+T tmp;
+T tmp2;
+
+int main () {
+ __builtin_memset (&tmp, 0x55, sizeof (tmp));
+ tmp2 = bar (tmp);
+ if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
+ __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub4.C b/gcc/testsuite/g++.dg/wrappers/strub4.C
new file mode 100644
index 00000000000..d021fca88e4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub4.C
@@ -0,0 +1,18 @@
+// { dg-do run }
+
+namespace
+{
+ class foo
+ {
+ public:
+ foo();
+ };
+
+ foo::foo() {}
+
+ foo bar;
+}
+
+int main()
+{
+}
diff --git a/gcc/tree-cfg.c b/gcc/tree-cfg.c
index 38269a27b79..b5e0af8b3a1 100644
--- a/gcc/tree-cfg.c
+++ b/gcc/tree-cfg.c
@@ -5617,6 +5617,7 @@ gimple_verify_flow_info (void)
{
gimple *stmt = gsi_stmt (gsi);
+ /* Do NOT disregard debug stmts after found_ctrl_stmt. */
if (found_ctrl_stmt)
{
error ("control flow in the middle of basic block %d",
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index aa9757a2fe9..0210f84bc0c 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -494,8 +494,9 @@ extern gimple_opt_pass *make_pass_adjust_alignment (gcc::context *ctxt);
/* IPA Passes */
extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt);
-extern simple_ipa_opt_pass
- *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub_mode (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_tree_profile (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_auto_profile (gcc::context *ctxt);
diff --git a/libgcc/Makefile.in b/libgcc/Makefile.in
index 2c8be561eb5..6b7b12493f5 100644
--- a/libgcc/Makefile.in
+++ b/libgcc/Makefile.in
@@ -428,7 +428,7 @@ ifneq ($(enable_shared),yes)
iterator = $(patsubst %,$(srcdir)/static-object.mk,$(iter-items))
endif
-LIB2ADD += enable-execute-stack.c
+LIB2ADD += enable-execute-stack.c $(srcdir)/strub.c
# While emutls.c has nothing to do with EH, it is in LIB2ADDEH*
# instead of LIB2ADD because that's the way to be sure on some targets
diff --git a/libgcc/libgcc2.h b/libgcc/libgcc2.h
index 1819ff3ac3d..857091e65c8 100644
--- a/libgcc/libgcc2.h
+++ b/libgcc/libgcc2.h
@@ -532,6 +532,10 @@ extern int __parityDI2 (UDWtype);
extern void __enable_execute_stack (void *);
+extern void __strub_enter (void **);
+extern void __strub_update (void**);
+extern void __strub_leave (void **);
+
#ifndef HIDE_EXPORTS
#pragma GCC visibility pop
#endif
diff --git a/libgcc/strub.c b/libgcc/strub.c
new file mode 100644
index 00000000000..fd6e27556e4
--- /dev/null
+++ b/libgcc/strub.c
@@ -0,0 +1,112 @@
+/* Stack scrubbing infrastructure
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+Under Section 7 of GPL version 3, you are granted additional
+permissions described in the GCC Runtime Library Exception, version
+3.1, as published by the Free Software Foundation.
+
+You should have received a copy of the GNU General Public License and
+a copy of the GCC Runtime Library Exception along with this program;
+see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
+<http://www.gnu.org/licenses/>. */
+
+#include "tconfig.h"
+#include "tsystem.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "libgcc_tm.h"
+#include "libgcc2.h"
+
+#ifndef STACK_GROWS_DOWNWARD
+# define TOPS >
+#else
+# define TOPS <
+#endif
+
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+
+/* Enter a stack scrubbing context, initializing the watermark to the caller's
+ stack address. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_enter (void **watermark)
+{
+ *watermark = __builtin_frame_address (0);
+}
+
+/* Update the watermark within a stack scrubbing context with the current stack
+ pointer. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_update (void **watermark)
+{
+ void *sp = __builtin_frame_address (0);
+
+ if (sp TOPS *watermark)
+ *watermark = sp;
+}
+
+#ifndef TARGET_STRUB_USE_DYNAMIC_ARRAY
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+#endif
+
+#ifndef TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+# ifdef TARGET_STRUB_MAY_USE_MEMSET
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+# else
+# define TARGET_STRUB_MAY_USE_MEMSET 1
+# endif
+#endif
+
+/* Leave a stack scrubbing context, restoring and updating SAVED, and
+ clearing the stack between top and watermark. */
+void ATTRIBUTE_STRUB_CALLABLE
+#if ! TARGET_STRUB_MAY_USE_MEMSET
+__attribute__ ((__optimize__ ("-fno-tree-loop-distribute-patterns")))
+#endif
+__strub_leave (void **mark)
+{
+ void *sp = __builtin_stack_address ();
+
+ void **base, **end;
+#ifndef STACK_GROWS_DOWNWARD
+ base = sp;
+ end = *mark;
+#else
+ base = *mark;
+ end = sp;
+#endif
+
+ ptrdiff_t len = end - base;
+ if (len <= 0)
+ return;
+
+#if ! TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+ /* Allocate a dynamically-sized array covering the desired range, so that we
+ can safely call memset on it. */
+ void *ptr[len];
+ base = &ptr[0];
+ end = &ptr[len];
+#else
+ void **ptr = end;
+#endif /* TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY */
+
+ /* ldist turns this into a memset. Without the dynamic array above, that call
+ is likely unsafe: possibly tail-called, and likely scribbling over its own
+ stack frame. */
+ while (base < end)
+ *base++ = 0;
+
+ asm ("" : : "m" (ptr));
+}
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/strub)] strub with function and variable attributes
@ 2021-07-26 13:31 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2021-07-26 13:31 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:29a0dcf367237b844691f1092d7368cf608d8b30
commit 29a0dcf367237b844691f1092d7368cf608d8b30
Author: Alexandre Oliva <oliva@adacore.com>
Date: Sat Jul 24 23:05:42 2021 -0300
strub with function and variable attributes
still missing verification of calls, documentation, tests,
builtin expansion
Diff:
---
gcc/Makefile.in | 1 +
gcc/builtins.c | 163 +++
gcc/builtins.def | 4 +
gcc/c-family/c-attribs.c | 39 +
gcc/common.opt | 20 +
gcc/doc/extend.texi | 72 +
gcc/doc/invoke.texi | 41 +
gcc/ipa-inline.c | 6 +
gcc/ipa-strub.c | 2430 ++++++++++++++++++++++++++++++++
gcc/ipa-strub.h | 25 +
gcc/passes.def | 2 +
gcc/testsuite/g++.dg/wrappers/strub1.C | 18 +
gcc/testsuite/g++.dg/wrappers/strub2.C | 22 +
gcc/testsuite/g++.dg/wrappers/strub3.C | 22 +
gcc/testsuite/g++.dg/wrappers/strub4.C | 18 +
gcc/tree-cfg.c | 1 +
gcc/tree-pass.h | 5 +-
libgcc/Makefile.in | 2 +-
libgcc/libgcc2.h | 4 +
libgcc/strub.c | 112 ++
20 files changed, 3004 insertions(+), 3 deletions(-)
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 1666ef84d6a..163fd1a856c 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1468,6 +1468,7 @@ OBJS = \
ipa-reference.o \
ipa-ref.o \
ipa-utils.o \
+ ipa-strub.o \
ipa.o \
ira.o \
ira-build.o \
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 170d776c410..373b4e90f70 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -153,6 +153,7 @@ static rtx expand_builtin_strnlen (tree, rtx, machine_mode);
static rtx expand_builtin_alloca (tree);
static rtx expand_builtin_unop (machine_mode, tree, rtx, rtx, optab);
static rtx expand_builtin_frame_address (tree, tree);
+static rtx expand_builtin_stack_address ();
static tree stabilize_va_list_loc (location_t, tree, int);
static rtx expand_builtin_expect (tree, rtx);
static rtx expand_builtin_expect_with_probability (tree, rtx);
@@ -7893,6 +7894,147 @@ expand_builtin_frame_address (tree fndecl, tree exp)
}
}
+/* Expand a call to builtin function __builtin_stack_address. */
+
+static rtx
+expand_builtin_stack_address ()
+{
+ return copy_addr_to_reg (stack_pointer_rtx);
+}
+
+/* Expand a call to builtin function __builtin_strub_enter. */
+
+static rtx
+expand_builtin_strub_enter (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (!optimize)
+ return NULL_RTX;
+
+ rtx stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ emit_move_insn (wmark, stktop);
+
+ return const0_rtx;
+}
+
+#ifndef STACK_GROWS_DOWNWARD
+# define STACK_TOPS GT
+#else
+# define STACK_TOPS LT
+#endif
+
+#ifdef POINTERS_EXTEND_UNSIGNED
+# define STACK_UNSIGNED POINTERS_EXTEND_UNSIGNED
+#else
+# define STACK_UNSIGNED true
+#endif
+
+/* Expand a call to builtin function __builtin_strub_update. */
+
+static rtx
+expand_builtin_strub_update (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 2)
+ return NULL_RTX;
+
+ rtx stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+ rtx wmarkr = force_reg (ptr_mode, wmark);
+
+ rtx_code_label *lab = gen_label_rtx ();
+ do_compare_rtx_and_jump (stktop, wmarkr, STACK_TOPS, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, lab, NULL,
+ profile_probability::very_likely ());
+ emit_move_insn (wmark, stktop);
+ emit_label (lab);
+
+ return const0_rtx;
+}
+
+
+/* Expand a call to builtin function __builtin_strub_leave. */
+
+static rtx
+expand_builtin_strub_leave (tree exp)
+{
+ if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
+ return NULL_RTX;
+
+ if (optimize < 3)
+ return NULL_RTX;
+
+ rtx stktop = expand_builtin_stack_address ();
+
+ tree wmptr = CALL_EXPR_ARG (exp, 0);
+ tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+ tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+ build_int_cst (TREE_TYPE (wmptr), 0));
+ rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+
+ /* If the function called between enter and leave is const or pure,
+ we may assume it won't change the watermark passed indirectly to
+ it. These ensure the value is neither dropped nor assumed
+ unchanged. */
+ emit_insn (gen_rtx_USE (VOIDmode, wmark));
+ emit_insn (gen_rtx_CLOBBER (VOIDmode, wmark));
+
+ rtx wmarkr = force_reg (ptr_mode, wmark);
+
+#ifndef STACK_GROWS_DOWNWARD
+ rtx base = stktop;
+ rtx end = wmarkr;
+#else
+ rtx base = wmarkr;
+ rtx end = stktop;
+#endif
+
+ rtx_code_label *done = gen_label_rtx ();
+ do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, done, NULL,
+ profile_probability::very_likely ());
+
+ rtx zero = force_operand (const0_rtx, NULL_RTX);
+ rtx ulen = GEN_INT (GET_MODE_SIZE (ptr_mode));
+ rtx incr = gen_rtx_PLUS (ptr_mode, base, ulen);
+ rtx dstm = gen_rtx_MEM (ptr_mode, base);
+
+ rtx_code_label *loop = gen_label_rtx ();
+ emit_label (loop);
+ emit_move_insn (dstm, zero);
+ emit_move_insn (base, force_operand (incr, NULL_RTX));
+ do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
+ ptr_mode, NULL_RTX, NULL, loop,
+ profile_probability::very_likely ());
+
+#if 0
+ emit_insn (gen_rtx_USE (VOIDmode,
+ gen_rtx_MEM (BLKmode,
+ gen_rtx_PLUS (ptr_mode, base,
+ GEN_INT (-GET_MODE_SIZE (ptr_mode))))));
+#endif
+
+ emit_label (done);
+
+ return const0_rtx;
+}
+
/* Expand EXP, a call to the alloca builtin. Return NULL_RTX if we
failed and the caller should emit a normal call. */
@@ -10151,6 +10293,27 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
case BUILT_IN_RETURN_ADDRESS:
return expand_builtin_frame_address (fndecl, exp);
+ case BUILT_IN_STACK_ADDRESS:
+ return expand_builtin_stack_address ();
+
+ case BUILT_IN___STRUB_ENTER:
+ target = expand_builtin_strub_enter (exp);
+ if (target)
+ return target;
+ break;
+
+ case BUILT_IN___STRUB_UPDATE:
+ target = expand_builtin_strub_update (exp);
+ if (target)
+ return target;
+ break;
+
+ case BUILT_IN___STRUB_LEAVE:
+ target = expand_builtin_strub_leave (exp);
+ if (target)
+ return target;
+ break;
+
/* Returns the address of the area where the structure is returned.
0 otherwise. */
case BUILT_IN_AGGREGATE_INCOMING_ADDRESS:
diff --git a/gcc/builtins.def b/gcc/builtins.def
index ec556df4f66..4545033e664 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -878,6 +878,10 @@ DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSL, "ffsl", BT_FN_INT_LONG, ATTR_CONST_NOTHRO
DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSLL, "ffsll", BT_FN_INT_LONGLONG, ATTR_CONST_NOTHROW_LEAF_LIST)
DEF_EXT_LIB_BUILTIN (BUILT_IN_FORK, "fork", BT_FN_PID, ATTR_NOTHROW_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FRAME_ADDRESS, "frame_address", BT_FN_PTR_UINT, ATTR_NULL)
+DEF_GCC_BUILTIN (BUILT_IN_STACK_ADDRESS, "stack_address", BT_FN_PTR, ATTR_NULL)
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_ENTER, "__builtin___strub_enter")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_UPDATE, "__builtin___strub_update")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_LEAVE, "__builtin___strub_leave")
/* [trans-mem]: Adjust BUILT_IN_TM_FREE if BUILT_IN_FREE is changed. */
DEF_LIB_BUILTIN (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_LEAF_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FROB_RETURN_ADDR, "frob_return_addr", BT_FN_PTR_PTR, ATTR_NULL)
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index e60fb31d8c8..f596e3f7237 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -69,6 +69,7 @@ static tree handle_asan_odr_indicator_attribute (tree *, tree, tree, int,
static tree handle_stack_protect_attribute (tree *, tree, tree, int, bool *);
static tree handle_no_stack_protector_function_attribute (tree *, tree,
tree, int, bool *);
+static tree handle_strub_attribute (tree *, tree, tree, int, bool *);
static tree handle_noinline_attribute (tree *, tree, tree, int, bool *);
static tree handle_noclone_attribute (tree *, tree, tree, int, bool *);
static tree handle_nocf_check_attribute (tree *, tree, tree, int, bool *);
@@ -306,6 +307,8 @@ const struct attribute_spec c_common_attribute_table[] =
{ "no_stack_protector", 0, 0, true, false, false, false,
handle_no_stack_protector_function_attribute,
attr_stack_protect_exclusions },
+ { "strub", 0, 1, false, true, false, true,
+ handle_strub_attribute, NULL },
{ "noinline", 0, 0, true, false, false, false,
handle_noinline_attribute,
attr_noinline_exclusions },
@@ -1290,6 +1293,42 @@ handle_noipa_attribute (tree *node, tree name, tree, int, bool *no_add_attrs)
return NULL_TREE;
}
+/* Handle a "strub" attribute; arguments as in
+ struct attribute_spec.handler. */
+
+static tree
+handle_strub_attribute (tree *node, tree name,
+ tree args,
+ int ARG_UNUSED (flags), bool *no_add_attrs)
+{
+ if (args
+ && (TREE_CODE (*node) == FUNCTION_TYPE
+ || TREE_CODE (*node) == METHOD_TYPE))
+ {
+ /* 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)
+ {
+ warning (OPT_Wattributes,
+ "%qE attribute ignored because of argument %qE",
+ name, TREE_VALUE (args));
+ *no_add_attrs = true;
+ }
+
+ args = TREE_CHAIN (args);
+ }
+
+ if (args)
+ warning (OPT_Wattributes,
+ "ignoring excess %qE attribute arguments starting at %qE",
+ name, TREE_VALUE (args));
+
+ return NULL_TREE;
+}
+
/* Handle a "noinline" attribute; arguments as in
struct attribute_spec.handler. */
diff --git a/gcc/common.opt b/gcc/common.opt
index d9da1131eda..8c05bfe4306 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,6 +2687,26 @@ fstrict-overflow
Common
Treat signed overflow as undefined. Negated as -fwrapv -fwrapv-pointer.
+fstrub=all
+Common RejectNegative Var(flag_strub, 3) Init(-2)
+Enable stack scrubbing for all viable functions.
+
+fstrub=at-calls
+Common RejectNegative Var(flag_strub, 1)
+Enable at-calls stack scrubbing for all viable functions.
+
+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.
+
+fstrub=default
+Common RejectNegative Var(flag_strub, -1)
+Enable stack scrub as requested through 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 b83cd4919bb..020bb1aa808 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -77,6 +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.
* 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.
@@ -8652,6 +8653,40 @@ pid_t wait (wait_status_ptr_t p)
@}
@end smallexample
+@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 numeric
+argument.
+
+A function associated with at-calls strub mode (@code{strub(1)})
+undergoes interface changes, and callers automatically gain code to
+scrub the stack space it uses. If the operational numeric argument is
+omitted from the @code{strub} attribute in a function type, the at-calls
+mode is selected.
+
+A function with internal strub mode @code{strub(2)} retains an
+unmodified interface, but it may be turned into a wrapper that calls the
+wrapped body, with a custom interface, and then scrubs the stack space
+used by it.
+
+A variable whose type carries the strub attribute causes functions that
+use it to have strub enabled.
+
+A function associated with the disabled strub mode @code{strub(0)} will
+not have its stack scrubbed. A function that has strub mode disabled
+cannot be called from strub contexts, namely from bodies of functions
+with strub modes @code{1} or @code{2}.
+
+A function whose body is not a strub context, but that should be
+callable from within strub contexts, should be marked as callable with
+@code{strub(3)}.
+
+Strub contexts are not inlined into non-strub contexts. When an
+internal-strub function is split, the wrapper can often be inlined, but
+its wrapped body cannot. Functions marked as @code{always_inline}, even
+if explicitly assigned internal strub mode, will not undergo wrapping.
+
@item unused
@cindex @code{unused} type attribute
When attached to a type (including a @code{union} or a @code{struct}),
@@ -11670,6 +11705,43 @@ option is in effect. Such calls should only be made in debugging
situations.
@end deftypefn
+@deftypefn {Built-in Function} {void *} __builtin_stack_address ()
+This function returns the value of the stack pointer register.
+@end deftypefn
+
+@node Stack Scrubbing
+@section Stack scrubbing interfaces
+
+Stack scrubbing involves cooperation between a strub context, i.e., a
+function whose stack frame is to be zeroed out, and its callers. The
+caller initializes a stack watermark, the 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, each one defined in libgcc, but expanded internally
+depending on optimization levels.
+
+@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 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 strub context 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 strub context. It remains as a function call
+at optimization levels lower than 3.
+@end deftypefn
+
@node Vector Extensions
@section Using Vector Instructions through Built-in Functions
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 32697e6117c..ee9adf44686 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,6 +599,7 @@ 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=all -fstrub=at-calls -fstrub=internal -fstrub=default -fstrub=none @gol
-fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
-fvtv-counts -fvtv-debug @gol
-finstrument-functions @gol
@@ -15425,6 +15426,46 @@ 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=all
+@opindex fstrub=all
+Enable stack scrub (strub) for all viable functions, and consider
+non-viable functions as callable. When both strub modes are viable,
+at-calls is preferred.
+
+@item -fstrub=at-calls
+@opindex fstrub=at-calls
+Enable at-calls strub for all viable functions, and consider non-viable
+functions as callable. A function is non-eligible for at-calls strub if
+a different strub mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+At-calls strub mode, if not requested with the function type, is only
+viable for an eligible function if it is not visible to other
+translation units, through its declaration or by having its address
+taken.
+
+@item -fstrub=internal
+@opindex fstrub=internal
+Enable internal strub for all viable functions, and consider non-viable
+functions as callable. A function is not eligible for internal strub if
+another strub mode is explicitly requested, attributes @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 internal strub, all
+eligible functions are viable.
+
+@item -fstrub=disable
+@opindex -fstrub=disable
+Disable stack scrubbing entirely, even when the @code{strub} attribute
+is present in function and variable types.
+
+@item -fstrub=default
+@opindex fstrub=default
+Restore the default strub behavior, namely, 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 -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
@opindex fvtable-verify
This option is only available when compiling C++ code.
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 413446bcc46..7f4bc44d2bb 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -119,6 +119,7 @@ along with GCC; see the file COPYING3. If not see
#include "stringpool.h"
#include "attribs.h"
#include "asan.h"
+#include "ipa-strub.h"
typedef fibonacci_heap <sreal, cgraph_edge> edge_heap_t;
typedef fibonacci_node <sreal, cgraph_edge> edge_heap_node_t;
@@ -396,6 +397,11 @@ 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))
+ {
+ e->inline_failed = CIF_UNSPECIFIED;
+ inlinable = false;
+ }
if (!inlinable && report)
report_inline_failed_reason (e);
return inlinable;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
new file mode 100644
index 00000000000..a3f4abf8b24
--- /dev/null
+++ b/gcc/ipa-strub.c
@@ -0,0 +1,2430 @@
+/* strub (stack scrubbing) support.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "gimplify.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "gimple-iterator.h"
+#include "gimplify-me.h"
+#include "tree-into-ssa.h"
+#include "tree-ssa.h"
+#include "tree-cfg.h"
+#include "cfghooks.h"
+#include "cfgloop.h"
+#include "cfgcleanup.h"
+#include "tree-eh.h"
+#include "except.h"
+#include "builtins.h"
+#include "attribs.h"
+#include "tree-inline.h"
+#include "cgraph.h"
+#include "alloc-pool.h"
+#include "symbol-summary.h"
+#include "ipa-prop.h"
+#include "ipa-fnsummary.h"
+#include "symtab-thunks.h"
+#include "gimple-fold.h"
+#include "fold-const.h"
+#include "gimple-walk.h"
+#include "tree-dfa.h"
+#include "langhooks.h"
+#include "calls.h"
+#include "vec.h"
+#include "stor-layout.h"
+#include "varasm.h"
+#include "alias.h"
+#include "diagnostic.h"
+#include "intl.h"
+#include "ipa-strub.h"
+#include "attr-fnspec.h"
+
+enum strub_mode {
+ /* This mode denotes a regular function, that does not require stack
+ scrubbing (strubbing). It may call any other functions, but if
+ it calls AT_CALLS (or WRAPPED) ones, strubbing logic is
+ automatically introduced around those calls (the latter, by
+ inlining INTERNAL wrappers). */
+ STRUB_DISABLED = 0,
+
+ /* This denotes a function whose signature is (to be) modified to
+ take an extra parameter, for stack use annotation, and its
+ callers must initialize and pass that argument, and perform the
+ strubbing. Functions that are explicitly marked with attribute
+ strub must have the mark visible wherever the function is,
+ including aliases, and overriders and overriding methods.
+ Functions that are implicitly marked for strubbing, for accessing
+ variables explicitly marked as such, will only select this
+ strubbing method if they are internal to a translation unit. It
+ can only be inlined into other strubbing functions, i.e.,
+ STRUB_AT_CALLS or STRUB_WRAPPED. */
+ STRUB_AT_CALLS = 1,
+
+ /* This denotes a function that is to perform strubbing internally,
+ without any changes to its interface (the function is turned into
+ a strubbing wrapper, and its original body is moved to a separate
+ STRUB_WRAPPED function, with a modified interface). Functions
+ may be explicitly marked with attribute strub(2), and the
+ attribute must be visible at the point of definition. Functions
+ that are explicitly marked for strubbing, for accessing variables
+ explicitly marked as such, may select this strubbing mode if
+ their interface cannot change, e.g. because its interface is
+ visible to other translation units, directly, by indirection
+ (having its address taken), inheritance, etc. Functions that use
+ this method must not have the noclone attribute, nor the noipa
+ one. Functions marked as always_inline may select this mode, but
+ they are NOT wrapped, they remain unchanged, and are only inlined
+ into strubbed contexts. Once non-always_inline functions are
+ wrapped, the wrapper becomes STRUB_WRAPPER, and the wrapped becomes
+ STRUB_WRAPPED. */
+ STRUB_INTERNAL = 2,
+
+ /* This denotes a function whose stack is not strubbed, but that is
+ nevertheless explicitly or implicitly marked as callable from strubbing
+ functions. Normally, only STRUB_AT_CALLS (and STRUB_INTERNAL ->
+ STRUB_WRAPPED) functions can be called from strubbing contexts (bodies of
+ STRUB_AT_CALLS, STRUB_INTERNAL and STRUB_WRAPPED functions), but attribute
+ strub(3) enables other functions to be (indirectly) called from these
+ contexts. Some builtins and internal functions may be implicitly marked as
+ STRUB_CALLABLE. */
+ STRUB_CALLABLE = 3,
+
+ /* This denotes the function that took over the body of a
+ STRUB_INTERNAL function. At first, it's only called by its
+ wrapper, but the wrapper may be inlined. The wrapped function,
+ in turn, can only be inlined into other functions whose stack
+ frames are strubbed, i.e., that are STRUB_WRAPPED or
+ STRUB_AT_CALLS. */
+ STRUB_WRAPPED = -1,
+
+ /* This denotes the wrapper function that replaced the STRUB_INTERNAL
+ function. This mode overrides the STRUB_INTERNAL mode at the time the
+ internal to-be-wrapped function becomes a wrapper, so that inlining logic
+ can tell one from the other. */
+ STRUB_WRAPPER = -2,
+
+ /* This denotes an always_inline function that requires strubbing. It can
+ only be called from, and inlined into, other strubbing contexts. */
+ STRUB_INLINABLE = -3,
+};
+
+static tree
+get_strub_attr_from_type (tree type)
+{
+ return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
+}
+
+static tree
+get_strub_attr_from_decl (tree decl)
+{
+ tree ret = lookup_attribute ("strub", DECL_ATTRIBUTES (decl));
+ if (ret)
+ return ret;
+ return get_strub_attr_from_type (TREE_TYPE (decl));
+}
+
+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);
+
+#if 0 /* ??? use symbolic mode names with interned strings? */
+ char *s = NULL;
+
+ switch (strub_mode)
+ {
+
+ }
+#endif
+}
+
+static enum strub_mode
+get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
+{
+ enum strub_mode mode = STRUB_DISABLED;
+
+ if (strub_attr)
+ {
+ 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 /* ??? Support symbolic mode names? */
+ gcc_unreachable ();
+ }
+
+ return mode;
+}
+
+static enum strub_mode
+get_strub_mode (cgraph_node *node)
+{
+ return get_strub_mode_from_attr (get_strub_attr_from_decl (node->decl));
+}
+
+static enum strub_mode
+get_strub_mode_from_type (tree type)
+{
+ bool var_p = !FUNC_OR_METHOD_TYPE_P (type);
+ tree attr = get_strub_attr_from_type (type);
+
+ if (attr)
+ return get_strub_mode_from_attr (attr, var_p);
+
+ if (flag_strub > 0 && !var_p)
+ return STRUB_CALLABLE;
+
+ return STRUB_DISABLED;
+}
+
+static bool
+calls_builtin_va_start_p (cgraph_node *node)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (fndecl_built_in_p (cdecl, BUILT_IN_VA_START))
+ return true;
+ }
+
+ return result;
+}
+
+static bool
+calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!fndecl_built_in_p (cdecl, BUILT_IN_APPLY_ARGS))
+ continue;
+
+ result = true;
+
+ if (!report)
+ break;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "at-calls strub does not support call to %qD",
+ cdecl);
+ }
+
+ return result;
+}
+
+static inline bool
+strub_always_inline_p (cgraph_node *node)
+{
+ return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
+}
+
+static inline bool
+can_strub_p (cgraph_node *node, bool report = false)
+{
+ bool result = true;
+
+ if (!report && strub_always_inline_p (node))
+ return result;
+
+ if (lookup_attribute ("noipa", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for strub because of attribute %<noipa%>",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+can_strub_at_calls_p (cgraph_node *node, bool report = false)
+{
+ bool result = !report || can_strub_p (node, report);
+
+ if (!result && !report)
+ return result;
+
+ return !calls_builtin_apply_args_p (node, report);
+}
+
+#define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
+
+/* We can't perform internal strubbing if the function body involves certain
+ features:
+
+ - a non-default __builtin_va_start (e.g. x86's __builtin_ms_va_start) is
+ currently unsupported because we can't discover the corresponding va_copy and
+ va_end decls in the wrapper, and we don't convey the alternate variable
+ arguments ABI to the modified wrapped function. The default
+ __builtin_va_start is supported by calling va_start/va_end at the wrapper,
+ that takes variable arguments, passing a pointer to the va_list object to the
+ wrapped function, that runs va_copy from it where the original function ran
+ va_start.
+
+ __builtin_next_arg is currently unsupported because the wrapped function
+ won't be a variable argument function. We could process it in the wrapper,
+ that remains a variable argument function, and replace calls in the wrapped
+ body, but we currently don't.
+
+ __builtin_return_address is rejected because it's generally used when the
+ actual caller matters, and introducing a wrapper breaks such uses as those in
+ the unwinder. */
+
+static bool
+can_strub_internally_p (cgraph_node *node, bool report = false)
+{
+ bool result = !report || can_strub_p (node, report);
+
+ if (!result && !report)
+ return result;
+
+ if (!report && strub_always_inline_p (node))
+ return result;
+
+ if (lookup_attribute ("noclone", DECL_ATTRIBUTES (node->decl)))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD is not eligible for internal strub"
+ " because of attribute %<noclone%>",
+ node->decl);
+ }
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!((fndecl_built_in_p (cdecl, BUILT_IN_VA_START)
+ && cdecl != builtin_decl_explicit (BUILT_IN_VA_START))
+ || fndecl_built_in_p (cdecl, BUILT_IN_NEXT_ARG)
+ || fndecl_built_in_p (cdecl, BUILT_IN_RETURN_ADDRESS)))
+ continue;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "internal strub does not support call to %qD",
+ cdecl);
+ }
+
+ basic_block bb;
+ if (node->has_gimple_body_p ())
+ {
+ /* Label values referenced 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. */
+
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ glabel *label_stmt = dyn_cast <glabel *> (gsi_stmt (gsi));
+ tree target;
+
+ if (!label_stmt)
+ break;
+
+ 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;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (label_stmt),
+ "internal strub does not support user labels");
+ }
+ }
+
+ if (list_length (TYPE_ARG_TYPES (TREE_TYPE (node->decl)))
+ >= (((HOST_WIDE_INT) 1 << IPA_PARAM_MAX_INDEX_BITS)
+ - STRUB_INTERNAL_MAX_EXTRA_ARGS))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD has too many arguments for internal strub",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+strub_from_body_p (cgraph_node *node)
+{
+ if (!node->has_gimple_body_p ())
+ return false;
+
+ /* If any local variable is marked for strub... */
+ unsigned i;
+ tree var;
+ FOR_EACH_LOCAL_DECL (DECL_STRUCT_FUNCTION (node->decl),
+ i, var)
+ if (get_strub_mode_from_type (TREE_TYPE (var))
+ != STRUB_DISABLED)
+ return true;
+
+ /* Now scan the body for loads with strub types. */
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!gimple_assign_load_p (stmt))
+ continue;
+
+ tree rhs = gimple_assign_rhs1 (stmt);
+ if (get_strub_mode_from_type (TREE_TYPE (rhs))
+ != STRUB_DISABLED)
+ return true;
+ }
+
+ return false;
+}
+
+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);
+
+ /* 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;
+ /* 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
+ safely undergo at-calls strubbing. Internal mode will still be used in
+ functions that request it explicitly with attribute strub(2), or when the
+ function body requires strubbing and at-calls strubbing is not viable. */
+ const bool strub_flag_at_calls = flag_strub == 1;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo internal strubbing. At-calls mode will still be used in
+ functions that requiest it explicitly with attribute strub() or strub(1),
+ or when the function body requires strubbing and internal strubbing is not
+ viable. */
+ const bool strub_flag_internal = flag_strub == 2;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo strubbing in either mode. When both modes are viable,
+ at-calls is preferred. */
+ const bool strub_flag_either = flag_strub == 3;
+ /* Besides the default behavior, enable strub implicitly for all viable
+ functions. */
+ const bool strub_flag_viable = flag_strub > 0;
+
+ /* The consider_* variables should be true if selecting the corresponding
+ strub modes would be consistent with requests from attributes and command
+ line flags. Attributes associated with functions pretty much mandate a
+ selection, and should report an error if not satisfied; strub_flag_auto
+ implicitly enables some viable strub mode if that's required by references
+ to variables marked for strub; strub_flag_viable enables strub if viable
+ (even when favoring one mode, body-requested strub can still be satisfied
+ by either mode), and falls back to callable, silently unless variables
+ require strubbing. */
+
+ const bool consider_at_calls
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_AT_CALLS
+ : true));
+ const bool consider_internal
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_INTERNAL
+ : true));
+
+ const bool consider_callable
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_CALLABLE
+ : strub_flag_viable));
+
+ /* This is a shorthand for either strub-enabled mode. */
+ const bool consider_strub
+ = (consider_at_calls || consider_internal);
+
+ /* We can cope with always_inline functions even with noipa and noclone,
+ because we just leave them alone. */
+ const bool is_always_inline
+ = strub_always_inline_p (node);
+
+ /* Strubbing in general, and each specific strub mode, may have its own set of
+ requirements. We require noipa for strubbing, either because of cloning
+ required for internal strub, or because of caller enumeration required for
+ at-calls strub. We don't consider the at-calls mode eligible if it's not
+ even considered, it has no further requirements. Internal mode requires
+ cloning and the absence of certain features in the body and, like at-calls,
+ it's not eligible if it's not even under consideration.
+
+ ??? Do we need target hooks for further constraints? E.g., x86's
+ "interrupt" attribute breaks internal strubbing because the wrapped clone
+ carries the attribute and thus isn't callable; in this case, we could use a
+ target hook to adjust the clone instead. */
+ const bool strub_eligible
+ = (consider_strub
+ && (is_always_inline || can_strub_p (node)));
+ const bool at_calls_eligible
+ = (consider_at_calls && strub_eligible
+ && can_strub_at_calls_p (node));
+ const bool internal_eligible
+ = (consider_internal && strub_eligible
+ && (is_always_inline
+ || can_strub_internally_p (node)));
+
+ /* In addition to the strict eligibility requirements, some additional
+ constraints are placed on implicit selection of certain modes. These do
+ not prevent the selection of a mode if explicitly specified as part of a
+ function interface (the strub attribute), but they may prevent modes from
+ being selected by the command line or by function bodies. The only actual
+ constraint is on at-calls mode: since we change the function's exposed
+ signature, we won't do it implicitly if the function can possibly be used
+ in ways that do not expect the signature change, e.g., if the function is
+ available to or interposable by other units, if its address is taken,
+ etc. */
+ const bool at_calls_viable
+ = (at_calls_eligible
+ && (strub_attr
+ || (node->has_gimple_body_p ()
+ && node->get_availability () > AVAIL_INTERPOSABLE
+ && ((!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->binds_to_current_def_p ()
+ && node->can_be_local_p ()))
+ && node->only_called_directly_p ())));
+ const bool internal_viable
+ = (internal_eligible);
+
+ /* Shorthand. */
+ const bool strub_viable
+ = (at_calls_viable || internal_viable);
+
+ /* We wish to analyze the body, to look for implicit requests for strub, both
+ to implicitly enable it when the body calls for it, and to report errors if
+ the body calls for it but neither mode is viable (even if that follows from
+ non-eligibility because of the explicit specification of some non-strubbing
+ mode). We can refrain from scanning the body only in rare circumstances:
+ when strub is enabled by a function attribute (scanning might be redundant
+ in telling us to also enable it), and when we are enabling strub implicitly
+ but there are non-viable modes: we want to know whether strubbing is
+ required, to fallback to another mode, even if we're only enabling a
+ certain mode, or, when either mode would do, to report an error if neither
+ happens to be viable. */
+ const bool analyze_body
+ = (strub_attr
+ ? !consider_strub
+ : (strub_flag_auto
+ || (strub_flag_viable && (!at_calls_viable && !internal_viable))
+ || (strub_flag_either && !strub_viable)));
+
+ /* Cases in which strubbing is enabled or disabled by strub_flag_auto.
+ Unsatisfiable requests ought to be reported. */
+ const bool strub_required
+ = ((strub_attr && consider_strub)
+ || (analyze_body && strub_from_body_p (node)));
+
+ /* Besides the required cases, we want to abide by the requests to enabling on
+ an if-viable basis. */
+ const bool strub_enable
+ = (strub_required
+ || (strub_flag_at_calls && at_calls_viable)
+ || (strub_flag_internal && internal_viable)
+ || (strub_flag_either && strub_viable));
+
+ /* And now we're finally ready to select a mode that abides by the viability
+ and eligibility constraints, and that satisfies the strubbing requirements
+ and requests, subject to the constraints. If both modes are viable and
+ strub is to be enabled, pick STRUB_AT_CALLS unless STRUB_INTERNAL was named
+ as preferred. */
+ 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
+ : (strub_enable && at_calls_viable)
+ ? STRUB_AT_CALLS
+ : consider_callable
+ ? STRUB_CALLABLE
+ : STRUB_DISABLED);
+
+ switch (mode)
+ {
+ case STRUB_CALLABLE:
+ if (is_always_inline)
+ break;
+ /* Fall through. */
+
+ case STRUB_DISABLED:
+ if (strub_enable && !strub_attr)
+ {
+ gcc_checking_assert (analyze_body);
+ error_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD requires strub, but no viable strub mode was found",
+ node->decl);
+ break;
+ }
+ /* Fall through. */
+
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ /* Differences from an mode requested through a function attribute are
+ reported in set_strub_mode_to. */
+ break;
+
+ case STRUB_WRAPPED:
+ case STRUB_WRAPPER:
+ default:
+ gcc_unreachable ();
+ }
+
+ return mode;
+}
+
+static void
+set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+ enum strub_mode req_mode = get_strub_mode_from_attr (attr);
+
+ if (attr)
+ {
+ /* Check for and report incompatible mode changes. */
+ if (mode != req_mode
+ && !(req_mode == STRUB_INTERNAL
+ && (mode == STRUB_WRAPPED
+ || mode == STRUB_WRAPPER))
+ && !((req_mode == STRUB_INTERNAL
+ || req_mode == STRUB_AT_CALLS
+ || req_mode == STRUB_CALLABLE)
+ && 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));
+ if (node->alias)
+ {
+ cgraph_node *target = node->ultimate_alias_target ();
+ if (target != node)
+ error_at (DECL_SOURCE_LOCATION (target->decl),
+ "the incompatible selection was determined"
+ " by ultimate alias target %qD",
+ target->decl);
+ }
+
+ /* Report any incompatibilities with explicitly-requested strub. */
+ switch (req_mode)
+ {
+ case STRUB_AT_CALLS:
+ can_strub_at_calls_p (node, true);
+ break;
+
+ case STRUB_INTERNAL:
+ can_strub_internally_p (node, true);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Drop any incompatible strub attributes leading the decl attribute
+ chain. Return if we find one with the mode we need. */
+ for (;;)
+ {
+ if (mode == req_mode)
+ return;
+
+ if (DECL_ATTRIBUTES (node->decl) != attr)
+ break;
+
+ DECL_ATTRIBUTES (node->decl) = TREE_CHAIN (attr);
+ attr = get_strub_attr_from_decl (node->decl);
+ if (!attr)
+ break;
+
+ req_mode = get_strub_mode_from_attr (attr);
+ }
+ }
+ else if (mode == req_mode)
+ return;
+
+ DECL_ATTRIBUTES (node->decl) = tree_cons (get_identifier ("strub"),
+ get_strub_mode_attr_value (mode),
+ DECL_ATTRIBUTES (node->decl));
+}
+
+static enum strub_mode
+set_strub_mode (cgraph_node *node)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+
+ cgraph_node *xnode = node;
+ if (node->alias)
+ xnode = node->ultimate_alias_target ();
+ /* Weakrefs may remain unresolved (the above will return node) if
+ their targets are not defined, so make sure we compute a strub
+ mode for them, instead of defaulting to STRUB_DISABLED and
+ rendering them uncallable. */
+ enum strub_mode mode = (xnode != node && !xnode->alias
+ ? get_strub_mode (xnode)
+ : compute_strub_mode (node, attr));
+
+ set_strub_mode_to (node, mode);
+
+ return mode;
+}
+
+/* Non-strub functions shouldn't be called from strub functions,
+ except through callable ones. Always inline strub functions can
+ only be called from strub functions. */
+
+static bool
+strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode caller_mode = get_strub_mode (caller);
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return callee_mode != STRUB_INLINABLE;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ return (flag_strub >= 0);
+
+ case STRUB_DISABLED:
+ return false;
+
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ 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. */
+
+bool
+strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return true;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ strub_mode caller_mode = get_strub_mode (caller);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ return true;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ return false;
+}
+
+namespace {
+
+const pass_data pass_data_ipa_strub_mode = {
+ SIMPLE_IPA_PASS,
+ "strubm",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ 0, // properties_finish
+};
+
+class pass_ipa_strub_mode : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub_mode (gcc::context *ctxt)
+ : simple_ipa_opt_pass (pass_data_ipa_strub_mode, ctxt)
+ {}
+ opt_pass *clone () { return new pass_ipa_strub_mode (m_ctxt); }
+ virtual bool gate (function *) { return flag_strub; }
+ virtual unsigned int execute (function *);
+};
+
+const pass_data pass_data_ipa_strub = {
+ SIMPLE_IPA_PASS,
+ "strub",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ TODO_update_ssa
+ | TODO_cleanup_cfg
+ | TODO_rebuild_cgraph_edges
+ | TODO_verify_il, // properties_finish
+};
+
+class pass_ipa_strub : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub (gcc::context *ctxt)
+ : 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 unsigned int execute (function *);
+
+#define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST) \
+ static tree get_ ## NAME () { \
+ tree decl = builtin_decl_explicit (CODE); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ decl = add_builtin_function \
+ ("__builtin_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ NULL, NULL); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ set_strub_mode_to (cgraph_node::get_create (decl), \
+ STRUB_CALLABLE); \
+ } \
+ return decl; \
+ }
+
+ DEF_NM_BUILTIN (stack_address,
+ BUILT_IN_STACK_ADDRESS,
+ (ptr_type_node, NULL))
+
+#undef DEF_NM_BUILTIN
+
+#define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST) \
+ static tree get_ ## NAME () { \
+ tree decl = builtin_decl_explicit (CODE); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ tree attrs = NULL; \
+ if (FNSPEC) \
+ attrs = tree_cons (get_identifier ("fn spec"), \
+ build_tree_list \
+ (NULL_TREE, \
+ build_string (strlen (FNSPEC), \
+ (FNSPEC))), \
+ attrs); \
+ decl = add_builtin_function_ext_scope \
+ ("__builtin___strub_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ "__strub_" #NAME, attrs); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ set_strub_mode_to (cgraph_node::get_create (decl), \
+ STRUB_CALLABLE); \
+ } \
+ return decl; \
+ }
+
+ DEF_SS_BUILTIN (enter, ". Ot",
+ BUILT_IN___STRUB_ENTER,
+ (void_type_node, get_pptr (), NULL))
+ DEF_SS_BUILTIN (update, ". Wt",
+ BUILT_IN___STRUB_UPDATE,
+ (void_type_node, get_pptr (), NULL))
+ DEF_SS_BUILTIN (leave, ". w ",
+ BUILT_IN___STRUB_LEAVE,
+ (void_type_node, get_pptr (), NULL))
+
+#undef DEF_SS_BUILTIN
+
+#define DEF_IDENT(NAME) \
+ static inline tree get_ ## NAME () { \
+ static tree identifier = NULL_TREE; \
+ if (!identifier) \
+ identifier = get_identifier (".strub." #NAME); \
+ return identifier; \
+ }
+
+ DEF_IDENT (watermark_ptr)
+ DEF_IDENT (va_list_ptr)
+ DEF_IDENT (apply_args_ptr)
+
+#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 gimple_seq
+ call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
+ gimple_seq seq = NULL)
+ {
+ tree uwm = get_update ();
+ gcall *update = gimple_build_call (uwm, 1, wmptr);
+ gimple_seq_add_stmt (&seq, update);
+ if (node)
+#if !IMPLICIT_CGRAPH_EDGES
+ node->create_edge (cgraph_node::get_create (uwm), update, count, false);
+#else
+ (void)count;
+#endif
+ return seq;
+ }
+
+};
+
+} // anon namespace
+
+#if 0
+static bool
+may_throw_p (gcall *stmt)
+{
+ return flag_exceptions && !gimple_call_nothrow_p (stmt);
+}
+
+static bool
+strub_this_call_p (gcall *stmt)
+{
+ if (gimple_call_internal_p (stmt))
+ return false;
+
+ /* If there's no outgoing path in which to do the scrubbing, don't
+ bother. */
+ if (gimple_call_noreturn_p (stmt) && !may_throw_p (stmt))
+ return false;
+
+ /* ??? Maybe non-mandatory tail calls should be disabled for
+ scrubbing. Or maybe it won't matter, as long as both tail-caller
+ and callee are scrubbing-capable. */
+ if (gimple_call_must_tail_p (stmt) || gimple_call_tail_p (stmt))
+ return false;
+
+ if (gimple_alloca_call_p (stmt))
+ return true;
+
+ tree fndecl = gimple_call_fndecl (stmt);
+ if (!fndecl)
+ return true;
+
+ if (DECL_BUILT_IN_CLASS (fndecl) != BUILT_IN_NORMAL)
+ return true;
+
+ enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl);
+
+ switch (fcode)
+ {
+ CASE_BUILT_IN_ALLOCA:
+ return true;
+
+ case BUILT_IN_NONE:
+ return true;
+
+ case BUILT_IN___STRUB_ENTER:
+ case BUILT_IN___STRUB_UPDATE:
+ case BUILT_IN___STRUB_LEAVE:
+ return false;
+
+ case BUILT_IN_CLASSIFY_TYPE:
+ case BUILT_IN_CONSTANT_P:
+ return false;
+
+ case BUILT_IN_RETURN_ADDRESS:
+ case BUILT_IN_FRAME_ADDRESS:
+ case BUILT_IN_STACK_ADDRESS:
+ case BUILT_IN_AGGREGATE_INCOMING_ADDRESS:
+ return false;
+
+ case BUILT_IN_STACK_SAVE:
+ case BUILT_IN_STACK_RESTORE:
+ case BUILT_IN_ASAN_ALLOCAS_UNPOISON:
+ return false;
+
+ case BUILT_IN_SETJMP_SETUP:
+ case BUILT_IN_SETJMP_RECEIVER:
+ case BUILT_IN_LONGJMP:
+ case BUILT_IN_NONLOCAL_GOTO:
+ case BUILT_IN_UPDATE_SETJMP_BUF:
+ case BUILT_IN_TRAP:
+ case BUILT_IN_UNREACHABLE:
+ return false;
+
+ case BUILT_IN_UNWIND_INIT:
+ case BUILT_IN_DWARF_CFA:
+#ifdef DWARF2_UNWIND_INFO
+ case BUILT_IN_DWARF_SP_COLUMN:
+ case BUILT_IN_INIT_DWARF_REG_SIZES:
+#endif
+ case BUILT_IN_FROB_RETURN_ADDR:
+ case BUILT_IN_EXTRACT_RETURN_ADDR:
+ case BUILT_IN_EH_RETURN:
+ case BUILT_IN_EH_RETURN_DATA_REGNO:
+ case BUILT_IN_EXTEND_POINTER:
+ case BUILT_IN_EH_POINTER:
+ case BUILT_IN_EH_FILTER:
+ case BUILT_IN_EH_COPY_VALUES:
+ return false;
+
+ case BUILT_IN_VA_START:
+ case BUILT_IN_VA_END:
+ case BUILT_IN_VA_COPY:
+ case BUILT_IN_EXPECT:
+ case BUILT_IN_EXPECT_WITH_PROBABILITY:
+ case BUILT_IN_ASSUME_ALIGNED:
+ case BUILT_IN_PREFETCH:
+ return false;
+
+ case BUILT_IN_ATOMIC_ALWAYS_LOCK_FREE:
+ case BUILT_IN_OBJECT_SIZE:
+ case BUILT_IN_THREAD_POINTER:
+ case BUILT_IN_SET_THREAD_POINTER:
+ return false;
+
+ default:
+ return true;
+ }
+}
+#endif
+
+typedef hash_set<tree> indirect_parms_t;
+
+static tree
+maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
+{
+ if (DECL_P (op))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (op))
+ {
+ tree ret = gimple_fold_indirect_ref (op);
+ if (!ret)
+ ret = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (op)),
+ op,
+ build_int_cst (TREE_TYPE (op), 0));
+ return ret;
+ }
+ }
+ else if (TREE_CODE (op) == ADDR_EXPR
+ && DECL_P (TREE_OPERAND (op, 0)))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (TREE_OPERAND (op, 0)))
+ return TREE_OPERAND (op, 0);
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_make_indirect (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ indirect_parms_t &indirect_parms = *(indirect_parms_t *)wi->info;
+
+ if (!*op || TYPE_P (*op))
+ {
+ *rec = 0;
+ return NULL_TREE;
+ }
+
+ if (tree repl = maybe_make_indirect (indirect_parms, *op, rec))
+ {
+ *op = repl;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ gimple_stmt_iterator &gsi = *(gimple_stmt_iterator *)wi->info;
+
+ *rec = 0;
+
+ if (!*op || TREE_CODE (*op) != ADDR_EXPR)
+ return NULL_TREE;
+
+ if (!is_gimple_val (*op))
+ {
+ tree ret = force_gimple_operand_gsi (&gsi, *op, true,
+ NULL_TREE, true, GSI_SAME_STMT);
+ gcc_assert (ret != *op);
+ *op = ret;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+build_ref_type_for (tree parm, bool nonaliased = true)
+{
+ gcc_checking_assert (TREE_CODE (parm) == PARM_DECL);
+
+ tree ref_type = build_reference_type (TREE_TYPE (parm));
+
+ if (!nonaliased)
+ return ref_type;
+
+ /* Each PARM turned indirect still points to the distinct memory area at the
+ wrapper, and the reference in unchanging, so we might qualify it, but...
+ const is not really important, since we're only using default defs for the
+ reference parm anyway, and not introducing any defs, and restrict seems to
+ cause trouble. E.g., libgnat/s-concat3.adb:str_concat_3 has memmoves that,
+ if it's wrapped, the memmoves are deleted in dse1. Using a distinct alias
+ set seems to not run afoul of this problem, and it hopefully enables the
+ compiler to tell the pointers do point to objects that are not otherwise
+ aliased. */
+#if 1
+ tree qref_type = build_variant_type_copy (ref_type);
+
+ TYPE_ALIAS_SET (qref_type) = new_alias_set ();
+ record_alias_subset (TYPE_ALIAS_SET (qref_type), get_alias_set (ref_type));
+
+ return qref_type;
+#else
+ tree qref_type = build_qualified_type (ref_type,
+ TYPE_QUAL_RESTRICT
+ | TYPE_QUAL_CONST);
+
+ return qref_type;
+#endif
+}
+
+/* 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)
+{
+#if IMPLICIT_CGRAPH_EDGES
+ return;
+#endif
+
+ cgraph_node *node = cgraph_node::get_create (current_function_decl);
+
+ for (gimple_stmt_iterator gsi = gsi_start (seq);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!is_a <gcall *> (stmt))
+ 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);
+ }
+}
+
+static void
+gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
+{
+ gimple *stmt = gsi_stmt (gsi);
+
+ gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+ 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;
+ bool nothrow_p = (must_not_throw_p
+ || (call && gimple_call_nothrow_p (call))
+ || (eh_lp <= 0
+ && (TREE_NOTHROW (cfun->decl)
+ || !flag_exceptions)));
+
+ if (noreturn_p && nothrow_p)
+ return;
+
+ /* Don't expect an EH edge if we're not to throw, or if we're not in an EH
+ region yet. */
+ bool no_eh_edge_p = (nothrow_p || !eh_lp);
+ bool must_end_bb = stmt_ends_bb_p (stmt);
+
+ edge eft = NULL, eeh = NULL;
+ if (must_end_bb && !(noreturn_p && no_eh_edge_p))
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+
+ edge e;
+ edge_iterator ei;
+ FOR_EACH_EDGE (e, ei, gsi_bb (gsi)->succs)
+ {
+ if ((e->flags & EDGE_EH))
+ {
+ gcc_checking_assert (!eeh);
+ eeh = e;
+#if !CHECKING_P
+ if (eft || noreturn_p)
+ break;
+#endif
+ }
+ if ((e->flags & EDGE_FALLTHRU))
+ {
+ gcc_checking_assert (!eft);
+ eft = e;
+#if !CHECKING_P
+ if (eeh || no_eh_edge_p)
+ break;
+#endif
+ }
+ }
+
+ gcc_checking_assert (!(eft && (eft->flags & EDGE_FALLTHRU))
+ == noreturn_p);
+ gcc_checking_assert (!(eeh && (eeh->flags & EDGE_EH))
+ == no_eh_edge_p);
+ gcc_checking_assert (eft != eeh);
+ }
+
+ if (!noreturn_p)
+ {
+ gimple_seq nseq = nothrow_p ? seq : gimple_seq_copy (seq);
+
+ if (must_end_bb)
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+ add_call_edges_for_seq (nseq, eft->count ());
+ gsi_insert_seq_on_edge_immediate (eft, nseq);
+ }
+ else
+ {
+ add_call_edges_for_seq (nseq, gsi_bb (gsi)->count);
+ gsi_insert_seq_after (&gsi, nseq, GSI_SAME_STMT);
+ }
+ }
+
+ if (nothrow_p)
+ return;
+
+ if (eh_lp)
+ {
+ add_call_edges_for_seq (seq, eeh->count ());
+ gsi_insert_seq_on_edge_immediate (eeh, seq);
+ return;
+ }
+
+ /* A throwing call may appear within a basic block in a function that doesn't
+ have any EH regions. We're going to add a cleanup if so, therefore the
+ block will have to be split. */
+ basic_block bb = gsi_bb (gsi);
+ if (!gsi_one_before_end_p (gsi))
+ split_block (bb, stmt);
+
+ /* Create a new block for the EH cleanup. */
+ basic_block bb_eh_cleanup = create_empty_bb (bb);
+ if (dom_info_available_p (CDI_DOMINATORS))
+ set_immediate_dominator (CDI_DOMINATORS, bb_eh_cleanup, bb);
+ if (current_loops)
+ add_bb_to_loop (bb_eh_cleanup, current_loops->tree_root);
+
+ /* Make the new block an EH cleanup for the call. */
+ eh_region new_r = gen_eh_region_cleanup (NULL);
+ eh_landing_pad lp = gen_eh_landing_pad (new_r);
+ tree label = gimple_block_label (bb_eh_cleanup);
+ lp->post_landing_pad = label;
+ EH_LANDING_PAD_NR (label) = lp->index;
+ add_stmt_to_eh_lp (stmt, lp->index);
+
+ /* Add the cleanup code to the EH cleanup block. */
+ gsi = gsi_after_labels (bb_eh_cleanup);
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
+
+ /* And then propagate the exception further. */
+ gresx *resx = gimple_build_resx (new_r->index);
+ gsi_insert_before (&gsi, resx, GSI_SAME_STMT);
+
+ /* Finally, wire the EH cleanup block into the CFG. */
+ make_eh_edges (stmt);
+ add_call_edges_for_seq (seq, single_pred_edge (bb_eh_cleanup)->count ());
+}
+
+/* Copy the attribute list at *ATTRS, minus any NAME attributes, leaving
+ shareable trailing nodes alone. */
+
+static inline void
+remove_named_attribute_unsharing (const char *name, tree *attrs)
+{
+ while (tree found = lookup_attribute (name, *attrs))
+ {
+ /* Copy nodes up to the next NAME attribute. */
+ while (*attrs != found)
+ {
+ *attrs = tree_cons (TREE_PURPOSE (*attrs),
+ TREE_VALUE (*attrs),
+ TREE_CHAIN (*attrs));
+ attrs = &TREE_CHAIN (*attrs);
+ }
+ /* Then drop it. */
+ gcc_checking_assert (*attrs == found);
+ *attrs = TREE_CHAIN (*attrs);
+ }
+}
+
+unsigned int
+pass_ipa_strub_mode::execute (function *)
+{
+ cgraph_node *onode;
+
+ /* If no strub flag was given in the command line,
+ set the actual default. */
+ if (flag_strub == -2)
+ flag_strub = -1;
+
+ bool any_strub = false;
+
+ for (int aliases = 0; aliases <= 1; aliases++)
+ FOR_EACH_FUNCTION (onode)
+ {
+ if (!onode->alias != !aliases)
+ continue;
+
+ enum strub_mode mode = set_strub_mode (onode);
+
+ if (mode == STRUB_AT_CALLS || mode == STRUB_INTERNAL)
+ any_strub = true;
+ }
+
+ if (!any_strub)
+ flag_strub = 0;
+
+ return 0;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub_mode (gcc::context *ctxt)
+{
+ return new pass_ipa_strub_mode (ctxt);
+}
+
+/* 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;
+
+ 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_WRAPPED
+ || caller_mode == STRUB_INLINABLE);
+
+ 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);
+ }
+
+ /* ??? 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? */
+}
+
+unsigned int
+pass_ipa_strub::execute (function *)
+{
+ cgraph_node *onode;
+
+ FOR_EACH_FUNCTION (onode)
+ {
+ enum strub_mode mode = get_strub_mode (onode);
+
+ if (mode == STRUB_AT_CALLS)
+ {
+ 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));
+
+ 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. */
+ 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;
+
+ tree wmptr = build_decl (DECL_SOURCE_LOCATION (onode->decl),
+ PARM_DECL,
+ get_watermark_ptr (),
+ get_qpptr ());
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_ARG_TYPE (wmptr) = get_qpptr ();
+ DECL_CONTEXT (wmptr) = onode->decl;
+ TREE_USED (wmptr) = 1;
+ DECL_CHAIN (wmptr) = *pargs;
+ *pargs = wmptr;
+
+ if (onode->alias)
+ continue;
+
+ 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 (nargs + 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 0
+ tree lswm = create_tmp_var (ptr_type_node, ".L.strub.watermark");
+ gassign *load = gimple_build_assign (lswm, swm);
+ gimple_seq_add_stmt (&seq, load);
+#else
+ tree lswm = unshare_expr (swmp);
+#endif
+
+ gcall *sleave = gimple_build_call (get_leave (), 1, lswm);
+ 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);
+
+ pop_cfun ();
+ }
+
+ if (!onode->has_gimple_body_p ())
+ continue;
+
+ cgraph_node *nnode = onode;
+ push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca)
+ {
+ 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);
+
+ if (!is_gimple_call (stmt))
+ continue;
+
+ gcall *call = as_a <gcall *> (stmt);
+
+ if (gimple_alloca_call_p (call))
+ {
+ /* Capture stack growth. */
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)
+ ->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ }
+ }
+
+ pop_cfun ();
+
+#if 0
+ compute_fn_summary (onode, true);
+#endif
+ continue;
+ }
+
+ if (mode != STRUB_INTERNAL
+ || !onode->has_gimple_body_p ())
+ 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 (!DECL_STRUCT_FUNCTION (onode->decl))
+ {
+ inform (DECL_SOURCE_LOCATION (onode->decl),
+ "not splitting struct-less function %qD for stack scrubbing",
+ onode->decl);
+ continue;
+ }
+
+ if (!onode->lowered)
+ {
+ inform (DECL_SOURCE_LOCATION (onode->decl),
+ "not splitting non-lowered function %qD for stack scrubbing",
+ 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 stack scrubbing",
+ onode->decl);
+ continue;
+ }
+
+ bool is_stdarg = calls_builtin_va_start_p (onode);;
+ bool apply_args = calls_builtin_apply_args_p (onode);
+
+ vec<ipa_adjusted_param, va_gc> *nparms = NULL;
+ unsigned j = 0;
+ {
+ // The following loop copied from ipa-split.c:split_function.
+ for (tree parm = DECL_ARGUMENTS (onode->decl);
+ parm; parm = DECL_CHAIN (parm), j++)
+ {
+ ipa_adjusted_param adj = {};
+ adj.op = IPA_PARAM_OP_COPY;
+ adj.base_index = j;
+ adj.prev_clone_index = j;
+ vec_safe_push (nparms, adj);
+ }
+
+ if (apply_args)
+ {
+ ipa_adjusted_param aaadj = {};
+ aaadj.op = IPA_PARAM_OP_NEW;
+ aaadj.type = get_qpptr ();
+ vec_safe_push (nparms, aaadj);
+ }
+
+ if (is_stdarg)
+ {
+ ipa_adjusted_param vladj = {};
+ vladj.op = IPA_PARAM_OP_NEW;
+ vladj.type = get_qpvalst ();
+ vec_safe_push (nparms, vladj);
+ }
+
+ ipa_adjusted_param wmadj = {};
+ wmadj.op = IPA_PARAM_OP_NEW;
+ wmadj.type = get_qpptr ();
+ vec_safe_push (nparms, wmadj);
+ }
+ ipa_param_adjustments adj (nparms, -1, false);
+
+ cgraph_node *nnode = onode->create_version_clone_with_body
+ (auto_vec<cgraph_edge *> (0),
+ NULL, &adj, NULL, NULL, "strub", NULL);
+
+ if (!nnode)
+ {
+ error_at (DECL_SOURCE_LOCATION (onode->decl),
+ "failed to split %qD for stack scrubbing",
+ onode->decl);
+ continue;
+ }
+
+ onode->split_part = true;
+ if (onode->calls_comdat_local)
+ nnode->add_to_same_comdat_group (onode);
+
+ gcc_checking_assert (!DECL_STRUCT_FUNCTION (nnode->decl)->stdarg);
+
+ set_strub_mode_to (onode, STRUB_WRAPPER);
+ set_strub_mode_to (nnode, STRUB_WRAPPED);
+
+ /* Decide which of the wrapped function's parms we want to turn into
+ references to the argument passed to the wrapper. In general, we want to
+ copy small arguments, and avoid copying large ones. Variable-sized array
+ lengths given by other arguments, as in 20020210-1.c, would lead to
+ problems if passed by value, after resetting the original function and
+ dropping the length computation; passing them by reference works.
+ DECL_BY_REFERENCE is *not* a substitute for this: it involves copying
+ anyway, but performed at the caller. */
+ indirect_parms_t indirect_nparms (3, false);
+ unsigned adjust_ftype = 0;
+ for (tree parm = DECL_ARGUMENTS (onode->decl),
+ nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (TREE_TYPE (nnode->decl));
+ parm;
+ parm = DECL_CHAIN (parm),
+ nparm = DECL_CHAIN (nparm),
+ nparmt = nparmt ? TREE_CHAIN (nparmt) : NULL_TREE)
+ if (!(0 /* DECL_BY_REFERENCE (narg) */
+ || is_gimple_reg_type (TREE_TYPE (nparm))
+ || VECTOR_TYPE_P (TREE_TYPE (nparm))
+ || TREE_CODE (TREE_TYPE (nparm)) == COMPLEX_TYPE
+ || (tree_fits_uhwi_p (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ && (tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ <= 4 * UNITS_PER_WORD))))
+ {
+ indirect_nparms.add (nparm);
+
+ /* ??? Is there any case in which it is not safe to suggest the parms
+ turned indirect don't alias anything else? They are distinct,
+ unaliased memory in the wrapper, and the wrapped can't possibly
+ take pointers into them because none of the pointers passed to the
+ wrapper can alias other incoming parameters passed by value, even
+ if with transparent reference, and the wrapper doesn't take any
+ extra parms that could point into wrapper's parms. So we can
+ probably drop the TREE_ADDRESSABLE and keep the true. */
+ tree ref_type = build_ref_type_for (nparm,
+ true
+ || !TREE_ADDRESSABLE (parm));
+
+ DECL_ARG_TYPE (nparm) = TREE_TYPE (nparm) = ref_type;
+ relayout_decl (nparm);
+ TREE_ADDRESSABLE (nparm) = 0;
+ DECL_BY_REFERENCE (nparm) = 0;
+ DECL_NOT_GIMPLE_REG_P (nparm) = 0;
+ /* ??? This avoids mismatches in debug info bind stmts in
+ e.g. a-chahan . */
+ DECL_ABSTRACT_ORIGIN (nparm) = NULL;
+
+ if (nparmt)
+ adjust_ftype++;
+ }
+
+ /* Also adjust the wrapped function type, if needed. */
+ if (adjust_ftype)
+ {
+ tree nftype = TREE_TYPE (nnode->decl);
+
+ /* We always add at least one argument at the end of the signature, when
+ cloning the function, so we don't expect to need to duplicate the
+ type here. */
+ gcc_checking_assert (TYPE_ARG_TYPES (nftype)
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+
+ /* Check that fnspec still works for the modified function signature,
+ and drop it otherwise. */
+ bool drop_fnspec = false;
+ tree fnspec = lookup_attribute ("fn spec", TYPE_ATTRIBUTES (nftype));
+ attr_fnspec spec = fnspec ? attr_fnspec (fnspec) : attr_fnspec ("");
+
+ unsigned retcopy;
+ if (!(fnspec && spec.returns_arg (&retcopy)))
+ retcopy = (unsigned) -1;
+
+ unsigned i = 0;
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (nftype);
+ adjust_ftype > 0;
+ nparm = DECL_CHAIN (nparm), nparmt = TREE_CHAIN (nparmt), i++)
+ if (indirect_nparms.contains (nparm))
+ {
+ TREE_VALUE (nparmt) = TREE_TYPE (nparm);
+ adjust_ftype--;
+
+ if (fnspec && !drop_fnspec)
+ {
+ if (i == retcopy)
+ drop_fnspec = true;
+ else if (spec.arg_specified_p (i))
+ {
+ /* Properties that apply to pointers only must not be
+ present, because we don't make pointers further
+ indirect. */
+ gcc_checking_assert
+ (!spec.arg_max_access_size_given_by_arg_p (i, NULL));
+ gcc_checking_assert (!spec.arg_copied_to_arg_p (i, NULL));
+
+ /* Any claim of direct access only is invalidated by
+ adding an indirection level. */
+ if (spec.arg_direct_p (i))
+ drop_fnspec = true;
+
+ /* If there's a claim the argument is not read from, the
+ added indirection invalidates it: if the argument is
+ used at all, then the pointer will necessarily be
+ read. */
+ if (!spec.arg_maybe_read_p (i)
+ && spec.arg_used_p (i))
+ drop_fnspec = true;
+ }
+ }
+ }
+
+ /* ??? Maybe we could adjust it instead. */
+ if (drop_fnspec)
+ remove_named_attribute_unsharing ("fn spec",
+ &TYPE_ATTRIBUTES (nftype));
+
+ TREE_TYPE (nnode->decl) = nftype;
+ }
+
+ {
+ tree decl = onode->decl;
+ cgraph_node *target = nnode;
+
+ { // copied from create_wrapper
+
+ /* Preserve DECL_RESULT so we get right by reference flag. */
+ tree decl_result = DECL_RESULT (decl);
+
+ /* Remove the function's body but keep arguments to be reused
+ for thunk. */
+ onode->release_body (true);
+ onode->reset ();
+
+ DECL_UNINLINABLE (decl) = false;
+ DECL_RESULT (decl) = decl_result;
+ DECL_INITIAL (decl) = NULL;
+ allocate_struct_function (decl, false);
+ set_cfun (NULL);
+
+ /* Turn alias into thunk and expand it into GIMPLE representation. */
+ onode->definition = true;
+
+ thunk_info::get_create (onode);
+ onode->thunk = true;
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (target, NULL, onode->count);
+#endif
+ onode->callees->can_throw_external = !TREE_NOTHROW (target->decl);
+
+ tree arguments = DECL_ARGUMENTS (decl);
+
+ while (arguments)
+ {
+ TREE_ADDRESSABLE (arguments) = false;
+ arguments = TREE_CHAIN (arguments);
+ }
+
+ {
+ tree alias = onode->callees->callee->decl;
+ tree thunk_fndecl = decl;
+ tree a;
+
+ int nxargs = 1 + is_stdarg + apply_args;
+
+ { // Simplified from expand_thunk.
+ tree restype;
+ basic_block bb, then_bb, else_bb, return_bb;
+ gimple_stmt_iterator bsi;
+ int nargs = 0;
+ tree arg;
+ int i;
+ tree resdecl;
+ tree restmp = NULL;
+
+ gcall *call;
+ greturn *ret;
+ bool alias_is_noreturn = TREE_THIS_VOLATILE (alias);
+
+ a = DECL_ARGUMENTS (thunk_fndecl);
+
+ current_function_decl = thunk_fndecl;
+
+ /* Ensure thunks are emitted in their correct sections. */
+ resolve_unique_section (thunk_fndecl, 0,
+ flag_function_sections);
+
+ bitmap_obstack_initialize (NULL);
+
+ /* Build the return declaration for the function. */
+ restype = TREE_TYPE (TREE_TYPE (thunk_fndecl));
+ if (DECL_RESULT (thunk_fndecl) == NULL_TREE)
+ {
+ resdecl = build_decl (input_location, RESULT_DECL, 0, restype);
+ DECL_ARTIFICIAL (resdecl) = 1;
+ DECL_IGNORED_P (resdecl) = 1;
+ DECL_CONTEXT (resdecl) = thunk_fndecl;
+ DECL_RESULT (thunk_fndecl) = resdecl;
+ }
+ else
+ resdecl = DECL_RESULT (thunk_fndecl);
+
+ profile_count cfg_count = onode->count;
+ if (!cfg_count.initialized_p ())
+ cfg_count = profile_count::from_gcov_type (BB_FREQ_MAX).guessed_local ();
+
+ bb = then_bb = else_bb = return_bb
+ = init_lowered_empty_function (thunk_fndecl, true, cfg_count);
+
+ bsi = gsi_start_bb (bb);
+
+ /* Build call to the function being thunked. */
+ if (!VOID_TYPE_P (restype)
+ && (!alias_is_noreturn
+ || TREE_ADDRESSABLE (restype)
+ || TREE_CODE (TYPE_SIZE_UNIT (restype)) != INTEGER_CST))
+ {
+ if (DECL_BY_REFERENCE (resdecl))
+ {
+ restmp = gimple_fold_indirect_ref (resdecl);
+ if (!restmp)
+ restmp = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (resdecl)),
+ resdecl,
+ build_int_cst (TREE_TYPE (resdecl), 0));
+ }
+ else if (!is_gimple_reg_type (restype))
+ {
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl)))
+ {
+ restmp = resdecl;
+
+ if (VAR_P (restmp))
+ {
+ add_local_decl (cfun, restmp);
+ BLOCK_VARS (DECL_INITIAL (current_function_decl))
+ = restmp;
+ }
+ }
+ else
+ restmp = create_tmp_var (restype, "retval");
+ }
+ else
+ restmp = create_tmp_reg (restype, "retval");
+ }
+
+ for (arg = a; arg; arg = DECL_CHAIN (arg))
+ nargs++;
+ auto_vec<tree> vargs (nargs + nxargs);
+ i = 0;
+ arg = a;
+
+ if (nargs)
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl);
+ i < nargs;
+ i++, arg = DECL_CHAIN (arg), nparm = DECL_CHAIN (nparm))
+ {
+ tree save_arg = arg;
+ tree tmp = arg;
+
+ /* Arrange to pass indirectly the parms, if we decided to do
+ so, and revert its type in the wrapper. */
+ if (indirect_nparms.contains (nparm))
+ {
+ tree ref_type = TREE_TYPE (nparm);
+ TREE_ADDRESSABLE (arg) = true;
+ tree addr = build1 (ADDR_EXPR, ref_type, arg);
+ tmp = arg = addr;
+ }
+ else
+ DECL_NOT_GIMPLE_REG_P (arg) = 0;
+
+ /* Convert the argument back to the type used by the calling
+ conventions, e.g. a non-prototyped float type is passed as
+ double, as in 930603-1.c, and needs to be converted back to
+ double to be passed on unchanged to the wrapped
+ function. */
+ if (TREE_TYPE (nparm) != DECL_ARG_TYPE (nparm))
+ arg = fold_convert (DECL_ARG_TYPE (nparm), arg);
+
+ if (!is_gimple_val (arg))
+ {
+ tmp = create_tmp_reg (TYPE_MAIN_VARIANT
+ (TREE_TYPE (arg)), "arg");
+ gimple *stmt = gimple_build_assign (tmp, arg);
+ gsi_insert_after (&bsi, stmt, GSI_NEW_STMT);
+ }
+ vargs.quick_push (tmp);
+ arg = save_arg;
+ }
+ /* These strub arguments are adjusted later. */
+ if (apply_args)
+ vargs.quick_push (null_pointer_node);
+ if (is_stdarg)
+ vargs.quick_push (null_pointer_node);
+ vargs.quick_push (null_pointer_node);
+ call = gimple_build_call_vec (build_fold_addr_expr_loc (0, alias),
+ vargs);
+ onode->callees->call_stmt = call;
+ // gimple_call_set_from_thunk (call, true);
+ if (DECL_STATIC_CHAIN (alias))
+ {
+ tree p = DECL_STRUCT_FUNCTION (alias)->static_chain_decl;
+ tree type = TREE_TYPE (p);
+ tree decl = build_decl (DECL_SOURCE_LOCATION (thunk_fndecl),
+ PARM_DECL, create_tmp_var_name ("CHAIN"),
+ type);
+ DECL_ARTIFICIAL (decl) = 1;
+ DECL_IGNORED_P (decl) = 1;
+ TREE_USED (decl) = 1;
+ DECL_CONTEXT (decl) = thunk_fndecl;
+ DECL_ARG_TYPE (decl) = type;
+ TREE_READONLY (decl) = 1;
+
+ struct function *sf = DECL_STRUCT_FUNCTION (thunk_fndecl);
+ sf->static_chain_decl = decl;
+
+ gimple_call_set_chain (call, decl);
+ }
+
+ /* Return slot optimization is always possible and in fact required to
+ return values with DECL_BY_REFERENCE. */
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl))
+ && (!is_gimple_reg_type (TREE_TYPE (resdecl))
+ || DECL_BY_REFERENCE (resdecl)))
+ gimple_call_set_return_slot_opt (call, true);
+
+ if (restmp)
+ {
+ gimple_call_set_lhs (call, restmp);
+ gcc_assert (useless_type_conversion_p (TREE_TYPE (restmp),
+ TREE_TYPE (TREE_TYPE (alias))));
+ }
+ gsi_insert_after (&bsi, call, GSI_NEW_STMT);
+ if (!alias_is_noreturn)
+ {
+ /* Build return value. */
+ if (!DECL_BY_REFERENCE (resdecl))
+ ret = gimple_build_return (restmp);
+ else
+ ret = gimple_build_return (resdecl);
+
+ gsi_insert_after (&bsi, ret, GSI_NEW_STMT);
+ }
+ else
+ {
+ remove_edge (single_succ_edge (bb));
+ }
+
+ cfun->gimple_df->in_ssa_p = true;
+ update_max_bb_count ();
+ profile_status_for_fn (cfun)
+ = cfg_count.initialized_p () && cfg_count.ipa_p ()
+ ? PROFILE_READ : PROFILE_GUESSED;
+ /* FIXME: C++ FE should stop setting TREE_ASM_WRITTEN on thunks. */
+ // TREE_ASM_WRITTEN (thunk_fndecl) = false;
+ delete_unreachable_blocks ();
+ update_ssa (TODO_update_ssa);
+ checking_verify_flow_info ();
+ free_dominance_info (CDI_DOMINATORS);
+
+ /* Since we want to emit the thunk, we explicitly mark its name as
+ referenced. */
+ onode->thunk = false;
+ onode->lowered = true;
+ bitmap_obstack_release (NULL);
+ }
+ current_function_decl = NULL;
+ set_cfun (NULL);
+ }
+
+ thunk_info::remove (onode);
+
+ // some more of create_wrapper at the end of the next block.
+ }
+ }
+
+ {
+ tree aaptr = NULL_TREE;
+ tree vaptr = NULL_TREE;
+ tree wmptr = NULL_TREE;
+ for (tree arg = DECL_ARGUMENTS (nnode->decl); arg; arg = DECL_CHAIN (arg))
+ {
+ aaptr = vaptr;
+ vaptr = wmptr;
+ wmptr = arg;
+ }
+
+ if (!apply_args)
+ aaptr = NULL_TREE;
+ /* The trailing args are [apply_args], [va_list_ptr], and
+ watermark. If we don't have a va_list_ptr, the penultimate
+ argument is apply_args.
+ */
+ else if (!is_stdarg)
+ aaptr = vaptr;
+
+ if (!is_stdarg)
+ vaptr = NULL_TREE;
+
+ DECL_NAME (wmptr) = get_watermark_ptr ();
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_IGNORED_P (wmptr) = 1;
+ TREE_USED (wmptr) = 1;
+
+ if (is_stdarg)
+ {
+ DECL_NAME (vaptr) = get_va_list_ptr ();
+ DECL_ARTIFICIAL (vaptr) = 1;
+ DECL_IGNORED_P (vaptr) = 1;
+ TREE_USED (vaptr) = 1;
+ }
+
+ if (apply_args)
+ {
+ DECL_NAME (aaptr) = get_apply_args_ptr ();
+ DECL_ARTIFICIAL (aaptr) = 1;
+ DECL_IGNORED_P (aaptr) = 1;
+ TREE_USED (aaptr) = 1;
+ }
+
+ push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ bool any_indirect = !indirect_nparms.is_empty ();
+
+ if (any_indirect)
+ {
+ 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);
+
+ 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
+ || is_stdarg || apply_args)
+ for (cgraph_edge *e = nnode->callees, *enext; e; e = enext)
+ {
+ gcall *call = e->call_stmt;
+ gimple_stmt_iterator gsi = gsi_for_stmt (call);
+ tree fndecl = e->callee->decl;
+
+ enext = e->next_callee;
+
+ if (gimple_alloca_call_p (call))
+ {
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ else if (fndecl && is_stdarg
+ && fndecl_built_in_p (fndecl, BUILT_IN_VA_START))
+ {
+ if (builtin_decl_explicit (BUILT_IN_VA_START) != fndecl)
+ sorry_at (gimple_location (call),
+ "nonstandard stdarg conventions");
+ tree bvacopy = builtin_decl_explicit (BUILT_IN_VA_COPY);
+ gimple_call_set_fndecl (call, bvacopy);
+ tree arg = vaptr;
+ /* The va_copy source must be dereferenced, unless it's an array
+ type, that would have decayed to a pointer. */
+ if (TREE_CODE (TREE_TYPE (TREE_TYPE (vaptr))) != ARRAY_TYPE)
+ {
+ arg = gimple_fold_indirect_ref (vaptr);
+ if (!arg)
+ arg = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (vaptr)),
+ vaptr,
+ build_int_cst (TREE_TYPE (vaptr), 0));
+ }
+ gimple_call_set_arg (call, 1, arg);
+ update_stmt (call);
+ e->redirect_callee (cgraph_node::get_create (bvacopy));
+ }
+ else if (fndecl && apply_args
+ && fndecl_built_in_p (fndecl, BUILT_IN_APPLY_ARGS))
+ {
+ tree lhs = gimple_call_lhs (call);
+ gassign *assign = gimple_build_assign (lhs, aaptr);
+ gsi_replace (&gsi, assign, true);
+ cgraph_edge::remove (e);
+ }
+ }
+
+ { // a little more copied from create_wrapper
+
+ /* Inline summary set-up. */
+ nnode->analyze ();
+ // inline_analyze_function (nnode);
+ }
+
+ pop_cfun ();
+ }
+
+ {
+ 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));
+
+ 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
+ onode->create_edge (cgraph_node::get_create (enter),
+ stptr, gsi_bb (gsi)->count, false);
+#endif
+
+ int nargs = gimple_call_num_args (wrcall);
+
+ gimple_seq seq = NULL;
+
+ if (apply_args)
+ {
+ tree aalst = create_tmp_var (ptr_type_node, ".strub.appargs");
+ tree bappargs = builtin_decl_explicit (BUILT_IN_APPLY_ARGS);
+ gcall *appargs = gimple_build_call (bappargs, 0);
+ gimple_call_set_lhs (appargs, aalst);
+ gsi_insert_before (&gsi, appargs, GSI_SAME_STMT);
+ gimple_call_set_arg (wrcall, nargs - 2 - is_stdarg, aalst);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bappargs),
+ appargs, gsi_bb (gsi)->count, false);
+#endif
+ }
+
+ if (is_stdarg)
+ {
+ tree valst = create_tmp_var (va_list_type_node, ".strub.va_list");
+ TREE_ADDRESSABLE (valst) = true;
+ tree vaptr = build1 (ADDR_EXPR,
+ build_pointer_type (va_list_type_node),
+ valst);
+ gimple_call_set_arg (wrcall, nargs - 2, unshare_expr (vaptr));
+
+ tree bvastart = builtin_decl_explicit (BUILT_IN_VA_START);
+ gcall *vastart = gimple_build_call (bvastart, 2,
+ unshare_expr (vaptr),
+ integer_zero_node);
+ gsi_insert_before (&gsi, vastart, GSI_SAME_STMT);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bvastart),
+ vastart, gsi_bb (gsi)->count, false);
+#endif
+
+ tree bvaend = builtin_decl_explicit (BUILT_IN_VA_END);
+ gcall *vaend = gimple_build_call (bvaend, 1, unshare_expr (vaptr));
+ gimple_seq_add_stmt (&seq, vaend);
+ }
+
+ gimple_call_set_arg (wrcall, nargs - 1, unshare_expr (swmp));
+ // gimple_call_set_tail (wrcall, false);
+ update_stmt (wrcall);
+
+ {
+#if 0
+ tree lswm = create_tmp_var (ptr_type_node, ".L.strub.watermark");
+ gassign *load = gimple_build_assign (lswm, swm);
+ gimple_seq_add_stmt (&seq, load);
+#else
+ tree lswm = unshare_expr (swmp);
+#endif
+
+ gcall *sleave = gimple_build_call (get_leave (), 1, lswm);
+ 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);
+
+ /* For nnode, we don't rebuild edges because we wish to retain
+ any redirections copied to it from earlier passes, so we add
+ call graph edges explicitly there, but for onode, we create a
+ fresh function, so we may as well just issue the calls and
+ then rebuild all cgraph edges. */
+ // cgraph_edge::rebuild_edges ();
+ onode->analyze ();
+ // inline_analyze_function (onode);
+
+ pop_cfun ();
+ }
+
+#if 0
+ compute_fn_summary (onode, true);
+ compute_fn_summary (nnode, true);
+#endif
+ }
+
+ verify_strub ();
+
+ return 0;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub (gcc::context *ctxt)
+{
+ return new pass_ipa_strub (ctxt);
+}
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
new file mode 100644
index 00000000000..cadbca5002a
--- /dev/null
+++ b/gcc/ipa-strub.h
@@ -0,0 +1,25 @@
+/* strub (stack scrubbing) infrastructure.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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);
diff --git a/gcc/passes.def b/gcc/passes.def
index f5d88a61b0e..677c000e80b 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -53,6 +53,7 @@ along with GCC; see the file COPYING3. If not see
INSERT_PASSES_AFTER (all_small_ipa_passes)
NEXT_PASS (pass_ipa_free_lang_data);
NEXT_PASS (pass_ipa_function_and_variable_visibility);
+ NEXT_PASS (pass_ipa_strub_mode);
NEXT_PASS (pass_build_ssa_passes);
PUSH_INSERT_PASSES_WITHIN (pass_build_ssa_passes)
NEXT_PASS (pass_fixup_cfg);
@@ -111,6 +112,7 @@ along with GCC; see the file COPYING3. If not see
POP_INSERT_PASSES ()
NEXT_PASS (pass_ipa_remove_symbols);
+ NEXT_PASS (pass_ipa_strub);
NEXT_PASS (pass_ipa_oacc);
PUSH_INSERT_PASSES_WITHIN (pass_ipa_oacc)
NEXT_PASS (pass_ipa_pta);
diff --git a/gcc/testsuite/g++.dg/wrappers/strub1.C b/gcc/testsuite/g++.dg/wrappers/strub1.C
new file mode 100644
index 00000000000..a474a929649
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub1.C
@@ -0,0 +1,18 @@
+// { dg-do run }
+
+// Check that we don't get extra copies.
+
+struct T {
+ T &self;
+ void check () const { if (&self != this) __builtin_abort (); }
+ T() : self (*this) { check (); }
+ T(const T& ck) : self (*this) { ck.check (); check (); }
+ ~T() { check (); }
+};
+
+T foo (T) { return T(); }
+T bar (T p) { return foo (p); }
+
+int main () {
+ bar (T{});
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub2.C b/gcc/testsuite/g++.dg/wrappers/strub2.C
new file mode 100644
index 00000000000..25a62166448
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub2.C
@@ -0,0 +1,22 @@
+// { dg-do run }
+
+// This doesn't really test anything yet. We should mark the
+// variables as requiring strubbing, and somehow check that the
+// wrapped functions take the parameter by reference.
+
+struct T {
+ char d[32 * sizeof(void*)];
+};
+
+T foo (T q) { asm ("" : : "m"(q)); return q; }
+T bar (T p) { return foo (p); }
+
+T tmp;
+T tmp2;
+
+int main () {
+ __builtin_memset (&tmp, 0x55, sizeof (tmp));
+ tmp2 = bar (tmp);
+ if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
+ __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub3.C b/gcc/testsuite/g++.dg/wrappers/strub3.C
new file mode 100644
index 00000000000..e1b51cd0399
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub3.C
@@ -0,0 +1,22 @@
+// { dg-do run }
+
+// This doesn't really test anything yet. We should mark the
+// variables as requiring strubbing, and somehow check that the
+// wrapped functions take the parameter by reference.
+
+struct T {
+ char d[32 * sizeof(void*)];
+};
+
+static T foo (T q) { asm ("" : : "m"(q)); return q; }
+static T bar (T p) { return foo (p); }
+
+T tmp;
+T tmp2;
+
+int main () {
+ __builtin_memset (&tmp, 0x55, sizeof (tmp));
+ tmp2 = bar (tmp);
+ if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
+ __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub4.C b/gcc/testsuite/g++.dg/wrappers/strub4.C
new file mode 100644
index 00000000000..d021fca88e4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub4.C
@@ -0,0 +1,18 @@
+// { dg-do run }
+
+namespace
+{
+ class foo
+ {
+ public:
+ foo();
+ };
+
+ foo::foo() {}
+
+ foo bar;
+}
+
+int main()
+{
+}
diff --git a/gcc/tree-cfg.c b/gcc/tree-cfg.c
index 38269a27b79..b5e0af8b3a1 100644
--- a/gcc/tree-cfg.c
+++ b/gcc/tree-cfg.c
@@ -5617,6 +5617,7 @@ gimple_verify_flow_info (void)
{
gimple *stmt = gsi_stmt (gsi);
+ /* Do NOT disregard debug stmts after found_ctrl_stmt. */
if (found_ctrl_stmt)
{
error ("control flow in the middle of basic block %d",
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index aa9757a2fe9..0210f84bc0c 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -494,8 +494,9 @@ extern gimple_opt_pass *make_pass_adjust_alignment (gcc::context *ctxt);
/* IPA Passes */
extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt);
-extern simple_ipa_opt_pass
- *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub_mode (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_tree_profile (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_auto_profile (gcc::context *ctxt);
diff --git a/libgcc/Makefile.in b/libgcc/Makefile.in
index 2c8be561eb5..6b7b12493f5 100644
--- a/libgcc/Makefile.in
+++ b/libgcc/Makefile.in
@@ -428,7 +428,7 @@ ifneq ($(enable_shared),yes)
iterator = $(patsubst %,$(srcdir)/static-object.mk,$(iter-items))
endif
-LIB2ADD += enable-execute-stack.c
+LIB2ADD += enable-execute-stack.c $(srcdir)/strub.c
# While emutls.c has nothing to do with EH, it is in LIB2ADDEH*
# instead of LIB2ADD because that's the way to be sure on some targets
diff --git a/libgcc/libgcc2.h b/libgcc/libgcc2.h
index 1819ff3ac3d..857091e65c8 100644
--- a/libgcc/libgcc2.h
+++ b/libgcc/libgcc2.h
@@ -532,6 +532,10 @@ extern int __parityDI2 (UDWtype);
extern void __enable_execute_stack (void *);
+extern void __strub_enter (void **);
+extern void __strub_update (void**);
+extern void __strub_leave (void **);
+
#ifndef HIDE_EXPORTS
#pragma GCC visibility pop
#endif
diff --git a/libgcc/strub.c b/libgcc/strub.c
new file mode 100644
index 00000000000..fd6e27556e4
--- /dev/null
+++ b/libgcc/strub.c
@@ -0,0 +1,112 @@
+/* Stack scrubbing infrastructure
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+Under Section 7 of GPL version 3, you are granted additional
+permissions described in the GCC Runtime Library Exception, version
+3.1, as published by the Free Software Foundation.
+
+You should have received a copy of the GNU General Public License and
+a copy of the GCC Runtime Library Exception along with this program;
+see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
+<http://www.gnu.org/licenses/>. */
+
+#include "tconfig.h"
+#include "tsystem.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "libgcc_tm.h"
+#include "libgcc2.h"
+
+#ifndef STACK_GROWS_DOWNWARD
+# define TOPS >
+#else
+# define TOPS <
+#endif
+
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+
+/* Enter a stack scrubbing context, initializing the watermark to the caller's
+ stack address. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_enter (void **watermark)
+{
+ *watermark = __builtin_frame_address (0);
+}
+
+/* Update the watermark within a stack scrubbing context with the current stack
+ pointer. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_update (void **watermark)
+{
+ void *sp = __builtin_frame_address (0);
+
+ if (sp TOPS *watermark)
+ *watermark = sp;
+}
+
+#ifndef TARGET_STRUB_USE_DYNAMIC_ARRAY
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+#endif
+
+#ifndef TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+# ifdef TARGET_STRUB_MAY_USE_MEMSET
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+# else
+# define TARGET_STRUB_MAY_USE_MEMSET 1
+# endif
+#endif
+
+/* Leave a stack scrubbing context, restoring and updating SAVED, and
+ clearing the stack between top and watermark. */
+void ATTRIBUTE_STRUB_CALLABLE
+#if ! TARGET_STRUB_MAY_USE_MEMSET
+__attribute__ ((__optimize__ ("-fno-tree-loop-distribute-patterns")))
+#endif
+__strub_leave (void **mark)
+{
+ void *sp = __builtin_stack_address ();
+
+ void **base, **end;
+#ifndef STACK_GROWS_DOWNWARD
+ base = sp;
+ end = *mark;
+#else
+ base = *mark;
+ end = sp;
+#endif
+
+ ptrdiff_t len = end - base;
+ if (len <= 0)
+ return;
+
+#if ! TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+ /* Allocate a dynamically-sized array covering the desired range, so that we
+ can safely call memset on it. */
+ void *ptr[len];
+ base = &ptr[0];
+ end = &ptr[len];
+#else
+ void **ptr = end;
+#endif /* TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY */
+
+ /* ldist turns this into a memset. Without the dynamic array above, that call
+ is likely unsafe: possibly tail-called, and likely scribbling over its own
+ stack frame. */
+ while (base < end)
+ *base++ = 0;
+
+ asm ("" : : "m" (ptr));
+}
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/strub)] strub with function and variable attributes
@ 2021-07-25 15:58 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2021-07-25 15:58 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:4e63e9c18cb7bdefe9d7b17a1df8b57eea5a8164
commit 4e63e9c18cb7bdefe9d7b17a1df8b57eea5a8164
Author: Alexandre Oliva <oliva@adacore.com>
Date: Sat Jul 24 23:05:42 2021 -0300
strub with function and variable attributes
still missing verification of calls, documentation, tests,
builtin expansion
Diff:
---
gcc/Makefile.in | 1 +
gcc/builtins.c | 12 +
gcc/builtins.def | 4 +
gcc/c-family/c-attribs.c | 39 +
gcc/common.opt | 16 +
gcc/doc/extend.texi | 4 +
gcc/ipa-inline.c | 6 +
gcc/ipa-strub.c | 2308 ++++++++++++++++++++++++++++++++
gcc/ipa-strub.h | 25 +
gcc/passes.def | 2 +
gcc/testsuite/g++.dg/wrappers/strub1.C | 18 +
gcc/testsuite/g++.dg/wrappers/strub2.C | 22 +
gcc/testsuite/g++.dg/wrappers/strub3.C | 22 +
gcc/testsuite/g++.dg/wrappers/strub4.C | 18 +
gcc/tree-cfg.c | 1 +
gcc/tree-pass.h | 5 +-
libgcc/Makefile.in | 2 +-
libgcc/libgcc2.h | 4 +
libgcc/strub.c | 112 ++
19 files changed, 2618 insertions(+), 3 deletions(-)
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 1666ef84d6a..163fd1a856c 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1468,6 +1468,7 @@ OBJS = \
ipa-reference.o \
ipa-ref.o \
ipa-utils.o \
+ ipa-strub.o \
ipa.o \
ira.o \
ira-build.o \
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 170d776c410..84539d73c8c 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -153,6 +153,7 @@ static rtx expand_builtin_strnlen (tree, rtx, machine_mode);
static rtx expand_builtin_alloca (tree);
static rtx expand_builtin_unop (machine_mode, tree, rtx, rtx, optab);
static rtx expand_builtin_frame_address (tree, tree);
+static rtx expand_builtin_stack_address ();
static tree stabilize_va_list_loc (location_t, tree, int);
static rtx expand_builtin_expect (tree, rtx);
static rtx expand_builtin_expect_with_probability (tree, rtx);
@@ -7893,6 +7894,14 @@ expand_builtin_frame_address (tree fndecl, tree exp)
}
}
+/* Expand a call to builtin functions __builtin_stack_address. */
+
+static rtx
+expand_builtin_stack_address ()
+{
+ return copy_addr_to_reg (stack_pointer_rtx);
+}
+
/* Expand EXP, a call to the alloca builtin. Return NULL_RTX if we
failed and the caller should emit a normal call. */
@@ -10151,6 +10160,9 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
case BUILT_IN_RETURN_ADDRESS:
return expand_builtin_frame_address (fndecl, exp);
+ case BUILT_IN_STACK_ADDRESS:
+ return expand_builtin_stack_address ();
+
/* Returns the address of the area where the structure is returned.
0 otherwise. */
case BUILT_IN_AGGREGATE_INCOMING_ADDRESS:
diff --git a/gcc/builtins.def b/gcc/builtins.def
index ec556df4f66..4545033e664 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -878,6 +878,10 @@ DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSL, "ffsl", BT_FN_INT_LONG, ATTR_CONST_NOTHRO
DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSLL, "ffsll", BT_FN_INT_LONGLONG, ATTR_CONST_NOTHROW_LEAF_LIST)
DEF_EXT_LIB_BUILTIN (BUILT_IN_FORK, "fork", BT_FN_PID, ATTR_NOTHROW_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FRAME_ADDRESS, "frame_address", BT_FN_PTR_UINT, ATTR_NULL)
+DEF_GCC_BUILTIN (BUILT_IN_STACK_ADDRESS, "stack_address", BT_FN_PTR, ATTR_NULL)
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_ENTER, "__builtin___strub_enter")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_UPDATE, "__builtin___strub_update")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_LEAVE, "__builtin___strub_leave")
/* [trans-mem]: Adjust BUILT_IN_TM_FREE if BUILT_IN_FREE is changed. */
DEF_LIB_BUILTIN (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_LEAF_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FROB_RETURN_ADDR, "frob_return_addr", BT_FN_PTR_PTR, ATTR_NULL)
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index e60fb31d8c8..f596e3f7237 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -69,6 +69,7 @@ static tree handle_asan_odr_indicator_attribute (tree *, tree, tree, int,
static tree handle_stack_protect_attribute (tree *, tree, tree, int, bool *);
static tree handle_no_stack_protector_function_attribute (tree *, tree,
tree, int, bool *);
+static tree handle_strub_attribute (tree *, tree, tree, int, bool *);
static tree handle_noinline_attribute (tree *, tree, tree, int, bool *);
static tree handle_noclone_attribute (tree *, tree, tree, int, bool *);
static tree handle_nocf_check_attribute (tree *, tree, tree, int, bool *);
@@ -306,6 +307,8 @@ const struct attribute_spec c_common_attribute_table[] =
{ "no_stack_protector", 0, 0, true, false, false, false,
handle_no_stack_protector_function_attribute,
attr_stack_protect_exclusions },
+ { "strub", 0, 1, false, true, false, true,
+ handle_strub_attribute, NULL },
{ "noinline", 0, 0, true, false, false, false,
handle_noinline_attribute,
attr_noinline_exclusions },
@@ -1290,6 +1293,42 @@ handle_noipa_attribute (tree *node, tree name, tree, int, bool *no_add_attrs)
return NULL_TREE;
}
+/* Handle a "strub" attribute; arguments as in
+ struct attribute_spec.handler. */
+
+static tree
+handle_strub_attribute (tree *node, tree name,
+ tree args,
+ int ARG_UNUSED (flags), bool *no_add_attrs)
+{
+ if (args
+ && (TREE_CODE (*node) == FUNCTION_TYPE
+ || TREE_CODE (*node) == METHOD_TYPE))
+ {
+ /* 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)
+ {
+ warning (OPT_Wattributes,
+ "%qE attribute ignored because of argument %qE",
+ name, TREE_VALUE (args));
+ *no_add_attrs = true;
+ }
+
+ args = TREE_CHAIN (args);
+ }
+
+ if (args)
+ warning (OPT_Wattributes,
+ "ignoring excess %qE attribute arguments starting at %qE",
+ name, TREE_VALUE (args));
+
+ return NULL_TREE;
+}
+
/* Handle a "noinline" attribute; arguments as in
struct attribute_spec.handler. */
diff --git a/gcc/common.opt b/gcc/common.opt
index d9da1131eda..72a0b9e7a0c 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,6 +2687,22 @@ fstrict-overflow
Common
Treat signed overflow as undefined. Negated as -fwrapv -fwrapv-pointer.
+fstrub
+Common Var(flag_strub, 3) Init(-2)
+Enable (or disable) stack scrubbing for all viable functions
+
+fstrub-default
+Common Var(flag_strub, -1)
+Enable stack scrub as requested through attributes
+
+fstrub-at-calls
+Common RejectNegative Var(flag_strub, 1)
+Enable at-calls stack scrubbing for all viable functions
+
+fstrub-internal
+Common RejectNegative Var(flag_strub, 2)
+Enable internal stack scrubbing for all viable functions
+
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 b83cd4919bb..4e4206b2f35 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -11670,6 +11670,10 @@ option is in effect. Such calls should only be made in debugging
situations.
@end deftypefn
+@deftypefn {Built-in Function} {void *} __builtin_stack_address ()
+This function returns the value of the stack pointer register.
+@end deftypefn
+
@node Vector Extensions
@section Using Vector Instructions through Built-in Functions
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 413446bcc46..7f4bc44d2bb 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -119,6 +119,7 @@ along with GCC; see the file COPYING3. If not see
#include "stringpool.h"
#include "attribs.h"
#include "asan.h"
+#include "ipa-strub.h"
typedef fibonacci_heap <sreal, cgraph_edge> edge_heap_t;
typedef fibonacci_node <sreal, cgraph_edge> edge_heap_node_t;
@@ -396,6 +397,11 @@ 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))
+ {
+ e->inline_failed = CIF_UNSPECIFIED;
+ inlinable = false;
+ }
if (!inlinable && report)
report_inline_failed_reason (e);
return inlinable;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
new file mode 100644
index 00000000000..f9a3c92a115
--- /dev/null
+++ b/gcc/ipa-strub.c
@@ -0,0 +1,2308 @@
+/* strub (stack scrubbing) support.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "gimplify.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "gimple-iterator.h"
+#include "gimplify-me.h"
+#include "tree-into-ssa.h"
+#include "tree-ssa.h"
+#include "tree-cfg.h"
+#include "cfghooks.h"
+#include "cfgloop.h"
+#include "cfgcleanup.h"
+#include "tree-eh.h"
+#include "except.h"
+#include "builtins.h"
+#include "attribs.h"
+#include "tree-inline.h"
+#include "cgraph.h"
+#include "alloc-pool.h"
+#include "symbol-summary.h"
+#include "ipa-prop.h"
+#include "ipa-fnsummary.h"
+#include "symtab-thunks.h"
+#include "gimple-fold.h"
+#include "fold-const.h"
+#include "gimple-walk.h"
+#include "tree-dfa.h"
+#include "langhooks.h"
+#include "calls.h"
+#include "vec.h"
+#include "stor-layout.h"
+#include "varasm.h"
+#include "alias.h"
+#include "diagnostic.h"
+#include "intl.h"
+#include "ipa-strub.h"
+#include "attr-fnspec.h"
+
+enum strub_mode {
+ /* This mode denotes a regular function, that does not require stack
+ scrubbing (strubbing). It may call any other functions, but if
+ it calls AT_CALLS (or WRAPPED) ones, strubbing logic is
+ automatically introduced around those calls (the latter, by
+ inlining INTERNAL wrappers). */
+ STRUB_DISABLED = 0,
+
+ /* This denotes a function whose signature is (to be) modified to
+ take an extra parameter, for stack use annotation, and its
+ callers must initialize and pass that argument, and perform the
+ strubbing. Functions that are explicitly marked with attribute
+ strub must have the mark visible wherever the function is,
+ including aliases, and overriders and overriding methods.
+ Functions that are implicitly marked for strubbing, for accessing
+ variables explicitly marked as such, will only select this
+ strubbing method if they are internal to a translation unit. It
+ can only be inlined into other strubbing functions, i.e.,
+ STRUB_AT_CALLS or STRUB_WRAPPED. */
+ STRUB_AT_CALLS = 1,
+
+ /* This denotes a function that is to perform strubbing internally,
+ without any changes to its interface (the function is turned into
+ a strubbing wrapper, and its original body is moved to a separate
+ STRUB_WRAPPED function, with a modified interface). Functions
+ may be explicitly marked with attribute strub(2), and the
+ attribute must be visible at the point of definition. Functions
+ that are explicitly marked for strubbing, for accessing variables
+ explicitly marked as such, may select this strubbing mode if
+ their interface cannot change, e.g. because its interface is
+ visible to other translation units, directly, by indirection
+ (having its address taken), inheritance, etc. Functions that use
+ this method must not have the noclone attribute, nor the noipa
+ one. Functions marked as always_inline may select this mode, but
+ they are NOT wrapped, they remain unchanged, and are only inlined
+ into strubbed contexts. Once non-always_inline functions are
+ wrapped, the wrapper becomes STRUB_WRAPPER, and the wrapped becomes
+ STRUB_WRAPPED. */
+ STRUB_INTERNAL = 2,
+
+ /* This denotes a function whose stack is not strubbed, but that is
+ nevertheless explicitly or implicitly marked as callable from strubbing
+ functions. Normally, only STRUB_AT_CALLS (and STRUB_INTERNAL ->
+ STRUB_WRAPPED) functions can be called from strubbing contexts (bodies of
+ STRUB_AT_CALLS, STRUB_INTERNAL and STRUB_WRAPPED functions), but attribute
+ strub(3) enables other functions to be (indirectly) called from these
+ contexts. Some builtins and internal functions may be implicitly marked as
+ STRUB_CALLABLE. */
+ STRUB_CALLABLE = 3,
+
+ /* This denotes the function that took over the body of a
+ STRUB_INTERNAL function. At first, it's only called by its
+ wrapper, but the wrapper may be inlined. The wrapped function,
+ in turn, can only be inlined into other functions whose stack
+ frames are strubbed, i.e., that are STRUB_WRAPPED or
+ STRUB_AT_CALLS. */
+ STRUB_WRAPPED = -1,
+
+ /* This denotes the wrapper function that replaced the STRUB_INTERNAL
+ function. This mode overrides the STRUB_INTERNAL mode at the time the
+ internal to-be-wrapped function becomes a wrapper, so that inlining logic
+ can tell one from the other. */
+ STRUB_WRAPPER = -2,
+
+ /* This denotes an always_inline function that requires strubbing. It can
+ only be called from, and inlined into, other strubbing contexts. */
+ STRUB_INLINABLE = -3,
+};
+
+static tree
+get_strub_attr_from_type (tree type)
+{
+ return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
+}
+
+static tree
+get_strub_attr_from_decl (tree decl)
+{
+ tree ret = lookup_attribute ("strub", DECL_ATTRIBUTES (decl));
+ if (ret)
+ return ret;
+ return get_strub_attr_from_type (TREE_TYPE (decl));
+}
+
+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);
+
+#if 0 /* ??? use symbolic mode names with interned strings? */
+ char *s = NULL;
+
+ switch (strub_mode)
+ {
+
+ }
+#endif
+}
+
+static enum strub_mode
+get_strub_mode_from_attr (tree strub_attr)
+{
+ enum strub_mode mode = STRUB_DISABLED;
+
+ if (strub_attr)
+ {
+ if (!TREE_VALUE (strub_attr))
+ mode = STRUB_AT_CALLS;
+ 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 /* ??? Support symbolic mode names? */
+ gcc_unreachable ();
+ }
+
+ return mode;
+}
+
+static enum strub_mode
+get_strub_mode (cgraph_node *node)
+{
+ return get_strub_mode_from_attr (get_strub_attr_from_decl (node->decl));
+}
+
+static bool
+calls_builtin_va_start_p (cgraph_node *node)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (fndecl_built_in_p (cdecl, BUILT_IN_VA_START))
+ return true;
+ }
+
+ return result;
+}
+
+static bool
+calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!fndecl_built_in_p (cdecl, BUILT_IN_APPLY_ARGS))
+ continue;
+
+ result = true;
+
+ if (!report)
+ break;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "at-calls strub does not support call to %qD",
+ cdecl);
+ }
+
+ return result;
+}
+
+static bool
+can_strub_at_calls_p (cgraph_node *node, bool report = false)
+{
+ return !calls_builtin_apply_args_p (node, report);
+}
+
+#define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
+
+/* We can't perform internal strubbing if the function body involves certain
+ features:
+
+ - a non-default __builtin_va_start (e.g. x86's __builtin_ms_va_start) is
+ currently unsupported because we can't discover the corresponding va_copy and
+ va_end decls in the wrapper, and we don't convey the alternate variable
+ arguments ABI to the modified wrapped function. The default
+ __builtin_va_start is supported by calling va_start/va_end at the wrapper,
+ that takes variable arguments, passing a pointer to the va_list object to the
+ wrapped function, that runs va_copy from it where the original function ran
+ va_start.
+
+ __builtin_next_arg is currently unsupported because the wrapped function
+ won't be a variable argument function. We could process it in the wrapper,
+ that remains a variable argument function, and replace calls in the wrapped
+ body, but we currently don't.
+
+ __builtin_return_address is rejected because it's generally used when the
+ actual caller matters, and introducing a wrapper breaks such uses as those in
+ the unwinder. */
+
+static bool
+can_strub_internally_p (cgraph_node *node, bool report = false)
+{
+ bool result = true;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!((fndecl_built_in_p (cdecl, BUILT_IN_VA_START)
+ && cdecl != builtin_decl_explicit (BUILT_IN_VA_START))
+ || fndecl_built_in_p (cdecl, BUILT_IN_NEXT_ARG)
+ || fndecl_built_in_p (cdecl, BUILT_IN_RETURN_ADDRESS)))
+ continue;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "internal strub does not support call to %qD",
+ cdecl);
+ }
+
+ basic_block bb;
+ if (node->has_gimple_body_p ())
+ {
+ /* Label values referenced 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. */
+
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ glabel *label_stmt = dyn_cast <glabel *> (gsi_stmt (gsi));
+ tree target;
+
+ if (!label_stmt)
+ break;
+
+ 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;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (label_stmt),
+ "internal strub does not support user labels");
+ }
+ }
+
+ if (list_length (TYPE_ARG_TYPES (TREE_TYPE (node->decl)))
+ >= (((HOST_WIDE_INT) 1 << IPA_PARAM_MAX_INDEX_BITS)
+ - STRUB_INTERNAL_MAX_EXTRA_ARGS))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD has too many arguments for internal strub",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+strub_from_body_p (cgraph_node *node)
+{
+ if (!node->has_gimple_body_p ())
+ return false;
+
+ /* If any local variable is marked for strub... */
+ unsigned i;
+ tree var;
+ FOR_EACH_LOCAL_DECL (DECL_STRUCT_FUNCTION (node->decl),
+ i, var)
+ if (get_strub_attr_from_decl (var))
+ return true;
+
+ /* Now scan the body for loads with strub types. */
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!gimple_assign_load_p (stmt))
+ continue;
+
+ tree rhs = gimple_assign_rhs1 (stmt);
+ if (get_strub_attr_from_type (TREE_TYPE (rhs)))
+ return true;
+ }
+
+ return false;
+}
+
+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);
+
+ /* 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;
+ /* 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
+ safely undergo at-calls strubbing. Internal mode will still be used in
+ functions that request it explicitly with attribute strub(2), or when the
+ function body requires strubbing and at-calls strubbing is not viable. */
+ const bool strub_flag_at_calls = flag_strub == 1;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo internal strubbing. At-calls mode will still be used in
+ functions that requiest it explicitly with attribute strub() or strub(1),
+ or when the function body requires strubbing and internal strubbing is not
+ viable. */
+ const bool strub_flag_internal = flag_strub == 2;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo strubbing in either mode. When both modes are viable,
+ at-calls is preferred. */
+ const bool strub_flag_either = flag_strub == 3;
+ /* Besides the default behavior, enable strub implicitly for all viable
+ functions. */
+ const bool strub_flag_viable = flag_strub > 0;
+
+ /* The consider_* variables should be true if selecting the corresponding
+ strub modes would be consistent with requests from attributes and command
+ line flags. Attributes associated with functions pretty much mandate a
+ selection, and should report an error if not satisfied; strub_flag_auto
+ implicitly enables some viable strub mode if that's required by references
+ to variables marked for strub; strub_flag_viable enables strub if viable
+ (even when favoring one mode, body-requested strub can still be satisfied
+ by either mode), and falls back to callable, silently unless variables
+ require strubbing. */
+
+ const bool consider_at_calls
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_AT_CALLS
+ : true));
+ const bool consider_internal
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_INTERNAL
+ : true));
+
+ const bool consider_callable
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_CALLABLE
+ : strub_flag_viable));
+
+ /* This is a shorthand for either strub-enabled mode. */
+ const bool consider_strub
+ = (consider_at_calls || consider_internal);
+
+ /* We can cope with always_inline functions even with noipa and noclone,
+ because we just leave them alone. */
+ const bool is_always_inline
+ = (lookup_attribute ("always_inline",
+ DECL_ATTRIBUTES (node->decl)));
+
+ /* Strubbing in general, and each specific strub mode, may have its own set of
+ requirements. We require noipa for strubbing, either because of cloning
+ required for internal strub, or because of caller enumeration required for
+ at-calls strub. We don't consider the at-calls mode eligible if it's not
+ even considered, it has no further requirements. Internal mode requires
+ cloning and the absence of certain features in the body and, like at-calls,
+ it's not eligible if it's not even under consideration.
+
+ ??? Do we need target hooks for further constraints? E.g., x86's
+ "interrupt" attribute breaks internal strubbing because the wrapped clone
+ carries the attribute and thus isn't callable; in this case, we could use a
+ target hook to adjust the clone instead. */
+ const bool strub_eligible
+ = (consider_strub
+ && (is_always_inline
+ || !lookup_attribute ("noipa",
+ DECL_ATTRIBUTES (node->decl))));
+ const bool at_calls_eligible
+ = (consider_at_calls && strub_eligible
+ && can_strub_at_calls_p (node));
+ const bool internal_eligible
+ = (consider_internal && strub_eligible
+ && (is_always_inline
+ || (!lookup_attribute ("noclone", DECL_ATTRIBUTES (node->decl))
+ && can_strub_internally_p (node))));
+
+ /* In addition to the strict eligibility requirements, some additional
+ constraints are placed on implicit selection of certain modes. These do
+ not prevent the selection of a mode if explicitly specified as part of a
+ function interface (the strub attribute), but they may prevent modes from
+ being selected by the command line or by function bodies. The only actual
+ constraint is on at-calls mode: since we change the function's exposed
+ signature, we won't do it implicitly if the function can possibly be used
+ in ways that do not expect the signature change, e.g., if the function is
+ available to or interposable by other units, if its address is taken,
+ etc. */
+ const bool at_calls_viable
+ = (at_calls_eligible
+ && (strub_attr
+ || (node->has_gimple_body_p ()
+ && node->get_availability () > AVAIL_INTERPOSABLE
+ && ((!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->binds_to_current_def_p ()
+ && node->can_be_local_p ()))
+ && node->only_called_directly_p ())));
+ const bool internal_viable
+ = (internal_eligible);
+
+ /* Shorthand. */
+ const bool strub_viable
+ = (at_calls_viable || internal_viable);
+
+ /* We wish to analyze the body, to look for implicit requests for strub, both
+ to implicitly enable it when the body calls for it, and to report errors if
+ the body calls for it but neither mode is viable (even if that follows from
+ non-eligibility because of the explicit specification of some non-strubbing
+ mode). We can refrain from scanning the body only in rare circumstances:
+ when strub is enabled by a function attribute (scanning might be redundant
+ in telling us to also enable it), and when we are enabling strub implicitly
+ but there are non-viable modes: we want to know whether strubbing is
+ required, to fallback to another mode, even if we're only enabling a
+ certain mode, or, when either mode would do, to report an error if neither
+ happens to be viable. */
+ const bool analyze_body
+ = (strub_attr
+ ? !consider_strub
+ : (strub_flag_auto
+ || (strub_flag_viable && (!at_calls_viable && !internal_viable))
+ || (strub_flag_either && !strub_viable)));
+
+ /* Cases in which strubbing is enabled or disabled by strub_flag_auto.
+ Unsatisfiable requests ought to be reported. */
+ const bool strub_required
+ = ((strub_attr && consider_strub)
+ || (analyze_body && strub_from_body_p (node)));
+
+ /* Besides the required cases, we want to abide by the requests to enabling on
+ an if-viable basis. */
+ const bool strub_enable
+ = (strub_required
+ || (strub_flag_at_calls && at_calls_viable)
+ || (strub_flag_internal && internal_viable)
+ || (strub_flag_either && strub_viable));
+
+ /* And now we're finally ready to select a mode that abides by the viability
+ and eligibility constraints, and that satisfies the strubbing requirements
+ and requests, subject to the constraints. If both modes are viable and
+ strub is to be enabled, pick STRUB_AT_CALLS unless STRUB_INTERNAL was named
+ as preferred. */
+ 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
+ : (strub_enable && at_calls_viable)
+ ? STRUB_AT_CALLS
+ : consider_callable
+ ? STRUB_CALLABLE
+ : STRUB_DISABLED);
+
+ switch (mode)
+ {
+ case STRUB_CALLABLE:
+ if (is_always_inline)
+ break;
+ /* Fall through. */
+
+ case STRUB_DISABLED:
+ if (strub_enable && !strub_attr)
+ {
+ gcc_checking_assert (analyze_body);
+ error_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD requires strub, but no viable strub mode was found",
+ node->decl);
+ break;
+ }
+ /* Fall through. */
+
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ /* Differences from an mode requested through a function attribute are
+ reported in set_strub_mode_to. */
+ break;
+
+ case STRUB_WRAPPED:
+ case STRUB_WRAPPER:
+ default:
+ gcc_unreachable ();
+ }
+
+ return mode;
+}
+
+static void
+set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+ enum strub_mode req_mode = get_strub_mode_from_attr (attr);
+
+ if (attr)
+ {
+ /* Check for and report incompatible mode changes. */
+ if (mode != req_mode
+ && !(req_mode == STRUB_INTERNAL
+ && (mode == STRUB_WRAPPED
+ || mode == STRUB_WRAPPER))
+ && !((req_mode == STRUB_INTERNAL
+ || req_mode == STRUB_AT_CALLS
+ || req_mode == STRUB_CALLABLE)
+ && 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));
+ if (node->alias)
+ {
+ cgraph_node *target = node->ultimate_alias_target ();
+ error_at (DECL_SOURCE_LOCATION (target->decl),
+ "the incompatible selection was determined"
+ " by ultimate alias target %qD",
+ target->decl);
+ }
+
+ /* Report any incompatibilities with explicitly-requested strub. */
+ switch (req_mode)
+ {
+ case STRUB_AT_CALLS:
+ can_strub_at_calls_p (node, true);
+ break;
+
+ case STRUB_INTERNAL:
+ can_strub_internally_p (node, true);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Drop any incompatible strub attributes leading the decl attribute
+ chain. Return if we find one with the mode we need. */
+ for (;;)
+ {
+ if (mode == req_mode)
+ return;
+
+ if (DECL_ATTRIBUTES (node->decl) != attr)
+ break;
+
+ DECL_ATTRIBUTES (node->decl) = TREE_CHAIN (attr);
+ attr = get_strub_attr_from_decl (node->decl);
+ if (!attr)
+ break;
+
+ req_mode = get_strub_mode_from_attr (attr);
+ }
+ }
+ else if (mode == req_mode)
+ return;
+
+ DECL_ATTRIBUTES (node->decl) = tree_cons (get_identifier ("strub"),
+ get_strub_mode_attr_value (mode),
+ DECL_ATTRIBUTES (node->decl));
+}
+
+static enum strub_mode
+set_strub_mode (cgraph_node *node)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+
+ enum strub_mode mode = (node->alias
+ ? get_strub_mode (node->ultimate_alias_target ())
+ : compute_strub_mode (node, attr));
+
+ set_strub_mode_to (node, mode);
+
+ return mode;
+}
+
+#if 0
+/* Non-strub functions shouldn't be called from strub functions,
+ except through callable ones. */
+
+static bool
+strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode caller_mode = get_strub_mode (caller);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return true;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return false;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ return true;
+}
+#endif
+
+/* We wish to avoid inlining WRAPPED functions back into their
+ WRAPPERs. More generally, we wish to avoid inlining
+ strubbed functions into non-strubbed ones. */
+
+bool
+strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return true;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ strub_mode caller_mode = get_strub_mode (caller);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ return true;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ return false;
+}
+
+namespace {
+
+const pass_data pass_data_ipa_strub_mode = {
+ SIMPLE_IPA_PASS,
+ "strubm",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ 0, // properties_finish
+};
+
+class pass_ipa_strub_mode : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub_mode (gcc::context *ctxt)
+ : simple_ipa_opt_pass (pass_data_ipa_strub_mode, ctxt)
+ {}
+ opt_pass *clone () { return new pass_ipa_strub_mode (m_ctxt); }
+ virtual bool gate (function *) { return flag_strub; }
+ virtual unsigned int execute (function *);
+};
+
+const pass_data pass_data_ipa_strub = {
+ SIMPLE_IPA_PASS,
+ "strub",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ TODO_update_ssa
+ | TODO_cleanup_cfg
+ | TODO_rebuild_cgraph_edges
+ | TODO_verify_il, // properties_finish
+};
+
+class pass_ipa_strub : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub (gcc::context *ctxt)
+ : 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 unsigned int execute (function *);
+
+#define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST) \
+ static tree get_ ## NAME () { \
+ tree decl = builtin_decl_explicit (CODE); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ decl = add_builtin_function \
+ ("__builtin_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ NULL, NULL); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ set_strub_mode_to (cgraph_node::get_create (decl), \
+ STRUB_CALLABLE); \
+ } \
+ return decl; \
+ }
+
+ DEF_NM_BUILTIN (stack_address,
+ BUILT_IN_STACK_ADDRESS,
+ (ptr_type_node, NULL))
+
+#undef DEF_NM_BUILTIN
+
+#define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST) \
+ static tree get_ ## NAME () { \
+ tree decl = builtin_decl_explicit (CODE); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ tree attrs = NULL; \
+ if (FNSPEC) \
+ attrs = tree_cons (get_identifier ("fn spec"), \
+ build_tree_list \
+ (NULL_TREE, \
+ build_string (strlen (FNSPEC), \
+ (FNSPEC))), \
+ attrs); \
+ decl = add_builtin_function_ext_scope \
+ ("__builtin___strub_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ "__strub_" #NAME, attrs); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ set_strub_mode_to (cgraph_node::get_create (decl), \
+ STRUB_CALLABLE); \
+ } \
+ return decl; \
+ }
+
+ DEF_SS_BUILTIN (enter, ". Ot",
+ BUILT_IN___STRUB_ENTER,
+ (void_type_node, get_pptr (), NULL))
+ DEF_SS_BUILTIN (update, ". Wt",
+ BUILT_IN___STRUB_UPDATE,
+ (void_type_node, get_pptr (), NULL))
+ DEF_SS_BUILTIN (leave, ". w ",
+ BUILT_IN___STRUB_LEAVE,
+ (void_type_node, get_pptr (), NULL))
+
+#undef DEF_SS_BUILTIN
+
+#define DEF_IDENT(NAME) \
+ static inline tree get_ ## NAME () { \
+ static tree identifier = NULL_TREE; \
+ if (!identifier) \
+ identifier = get_identifier (".strub." #NAME); \
+ return identifier; \
+ }
+
+ DEF_IDENT (watermark_ptr)
+ DEF_IDENT (va_list_ptr)
+ DEF_IDENT (apply_args_ptr)
+
+#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 gimple_seq
+ call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
+ gimple_seq seq = NULL)
+ {
+ tree uwm = get_update ();
+ gcall *update = gimple_build_call (uwm, 1, wmptr);
+ gimple_seq_add_stmt (&seq, update);
+ if (node)
+#if !IMPLICIT_CGRAPH_EDGES
+ node->create_edge (cgraph_node::get_create (uwm), update, count, false);
+#else
+ (void)count;
+#endif
+ return seq;
+ }
+
+};
+
+} // anon namespace
+
+#if 0
+static bool
+may_throw_p (gcall *stmt)
+{
+ return flag_exceptions && !gimple_call_nothrow_p (stmt);
+}
+
+static bool
+strub_this_call_p (gcall *stmt)
+{
+ if (gimple_call_internal_p (stmt))
+ return false;
+
+ /* If there's no outgoing path in which to do the scrubbing, don't
+ bother. */
+ if (gimple_call_noreturn_p (stmt) && !may_throw_p (stmt))
+ return false;
+
+ /* ??? Maybe non-mandatory tail calls should be disabled for
+ scrubbing. Or maybe it won't matter, as long as both tail-caller
+ and callee are scrubbing-capable. */
+ if (gimple_call_must_tail_p (stmt) || gimple_call_tail_p (stmt))
+ return false;
+
+ if (gimple_alloca_call_p (stmt))
+ return true;
+
+ tree fndecl = gimple_call_fndecl (stmt);
+ if (!fndecl)
+ return true;
+
+ if (DECL_BUILT_IN_CLASS (fndecl) != BUILT_IN_NORMAL)
+ return true;
+
+ enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl);
+
+ switch (fcode)
+ {
+ CASE_BUILT_IN_ALLOCA:
+ return true;
+
+ case BUILT_IN_NONE:
+ return true;
+
+ case BUILT_IN___STRUB_ENTER:
+ case BUILT_IN___STRUB_UPDATE:
+ case BUILT_IN___STRUB_LEAVE:
+ return false;
+
+ case BUILT_IN_CLASSIFY_TYPE:
+ case BUILT_IN_CONSTANT_P:
+ return false;
+
+ case BUILT_IN_RETURN_ADDRESS:
+ case BUILT_IN_FRAME_ADDRESS:
+ case BUILT_IN_STACK_ADDRESS:
+ case BUILT_IN_AGGREGATE_INCOMING_ADDRESS:
+ return false;
+
+ case BUILT_IN_STACK_SAVE:
+ case BUILT_IN_STACK_RESTORE:
+ case BUILT_IN_ASAN_ALLOCAS_UNPOISON:
+ return false;
+
+ case BUILT_IN_SETJMP_SETUP:
+ case BUILT_IN_SETJMP_RECEIVER:
+ case BUILT_IN_LONGJMP:
+ case BUILT_IN_NONLOCAL_GOTO:
+ case BUILT_IN_UPDATE_SETJMP_BUF:
+ case BUILT_IN_TRAP:
+ case BUILT_IN_UNREACHABLE:
+ return false;
+
+ case BUILT_IN_UNWIND_INIT:
+ case BUILT_IN_DWARF_CFA:
+#ifdef DWARF2_UNWIND_INFO
+ case BUILT_IN_DWARF_SP_COLUMN:
+ case BUILT_IN_INIT_DWARF_REG_SIZES:
+#endif
+ case BUILT_IN_FROB_RETURN_ADDR:
+ case BUILT_IN_EXTRACT_RETURN_ADDR:
+ case BUILT_IN_EH_RETURN:
+ case BUILT_IN_EH_RETURN_DATA_REGNO:
+ case BUILT_IN_EXTEND_POINTER:
+ case BUILT_IN_EH_POINTER:
+ case BUILT_IN_EH_FILTER:
+ case BUILT_IN_EH_COPY_VALUES:
+ return false;
+
+ case BUILT_IN_VA_START:
+ case BUILT_IN_VA_END:
+ case BUILT_IN_VA_COPY:
+ case BUILT_IN_EXPECT:
+ case BUILT_IN_EXPECT_WITH_PROBABILITY:
+ case BUILT_IN_ASSUME_ALIGNED:
+ case BUILT_IN_PREFETCH:
+ return false;
+
+ case BUILT_IN_ATOMIC_ALWAYS_LOCK_FREE:
+ case BUILT_IN_OBJECT_SIZE:
+ case BUILT_IN_THREAD_POINTER:
+ case BUILT_IN_SET_THREAD_POINTER:
+ return false;
+
+ default:
+ return true;
+ }
+}
+#endif
+
+typedef hash_set<tree> indirect_parms_t;
+
+static tree
+maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
+{
+ if (DECL_P (op))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (op))
+ {
+ tree ret = gimple_fold_indirect_ref (op);
+ if (!ret)
+ ret = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (op)),
+ op,
+ build_int_cst (TREE_TYPE (op), 0));
+ return ret;
+ }
+ }
+ else if (TREE_CODE (op) == ADDR_EXPR
+ && DECL_P (TREE_OPERAND (op, 0)))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (TREE_OPERAND (op, 0)))
+ return TREE_OPERAND (op, 0);
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_make_indirect (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ indirect_parms_t &indirect_parms = *(indirect_parms_t *)wi->info;
+
+ if (!*op || TYPE_P (*op))
+ {
+ *rec = 0;
+ return NULL_TREE;
+ }
+
+ if (tree repl = maybe_make_indirect (indirect_parms, *op, rec))
+ {
+ *op = repl;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ gimple_stmt_iterator &gsi = *(gimple_stmt_iterator *)wi->info;
+
+ *rec = 0;
+
+ if (!*op || TREE_CODE (*op) != ADDR_EXPR)
+ return NULL_TREE;
+
+ if (!is_gimple_val (*op))
+ {
+ tree ret = force_gimple_operand_gsi (&gsi, *op, true,
+ NULL_TREE, true, GSI_SAME_STMT);
+ gcc_assert (ret != *op);
+ *op = ret;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+build_ref_type_for (tree parm, bool nonaliased = true)
+{
+ gcc_checking_assert (TREE_CODE (parm) == PARM_DECL);
+
+ tree ref_type = build_reference_type (TREE_TYPE (parm));
+
+ if (!nonaliased)
+ return ref_type;
+
+ /* Each PARM turned indirect still points to the distinct memory area at the
+ wrapper, and the reference in unchanging, so we might qualify it, but...
+ const is not really important, since we're only using default defs for the
+ reference parm anyway, and not introducing any defs, and restrict seems to
+ cause trouble. E.g., libgnat/s-concat3.adb:str_concat_3 has memmoves that,
+ if it's wrapped, the memmoves are deleted in dse1. Using a distinct alias
+ set seems to not run afoul of this problem, and it hopefully enables the
+ compiler to tell the pointers do point to objects that are not otherwise
+ aliased. */
+#if 1
+ tree qref_type = build_variant_type_copy (ref_type);
+
+ TYPE_ALIAS_SET (qref_type) = new_alias_set ();
+ record_alias_subset (TYPE_ALIAS_SET (qref_type), get_alias_set (ref_type));
+
+ return qref_type;
+#else
+ tree qref_type = build_qualified_type (ref_type,
+ TYPE_QUAL_RESTRICT
+ | TYPE_QUAL_CONST);
+
+ return qref_type;
+#endif
+}
+
+/* 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)
+{
+#if IMPLICIT_CGRAPH_EDGES
+ return;
+#endif
+
+ cgraph_node *node = cgraph_node::get_create (current_function_decl);
+
+ for (gimple_stmt_iterator gsi = gsi_start (seq);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!is_a <gcall *> (stmt))
+ 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);
+ }
+}
+
+static void
+gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
+{
+ gimple *stmt = gsi_stmt (gsi);
+
+ gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+ 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;
+ bool nothrow_p = (must_not_throw_p
+ || (call && gimple_call_nothrow_p (call))
+ || (eh_lp <= 0
+ && (TREE_NOTHROW (cfun->decl)
+ || !flag_exceptions)));
+
+ if (noreturn_p && nothrow_p)
+ return;
+
+ /* Don't expect an EH edge if we're not to throw, or if we're not in an EH
+ region yet. */
+ bool no_eh_edge_p = (nothrow_p || !eh_lp);
+ bool must_end_bb = stmt_ends_bb_p (stmt);
+
+ edge eft = NULL, eeh = NULL;
+ if (must_end_bb && !(noreturn_p && no_eh_edge_p))
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+
+ edge e;
+ edge_iterator ei;
+ FOR_EACH_EDGE (e, ei, gsi_bb (gsi)->succs)
+ {
+ if ((e->flags & EDGE_EH))
+ {
+ gcc_checking_assert (!eeh);
+ eeh = e;
+#if !CHECKING_P
+ if (eft || noreturn_p)
+ break;
+#endif
+ }
+ if ((e->flags & EDGE_FALLTHRU))
+ {
+ gcc_checking_assert (!eft);
+ eft = e;
+#if !CHECKING_P
+ if (eeh || no_eh_edge_p)
+ break;
+#endif
+ }
+ }
+
+ gcc_checking_assert (!(eft && (eft->flags & EDGE_FALLTHRU))
+ == noreturn_p);
+ gcc_checking_assert (!(eeh && (eeh->flags & EDGE_EH))
+ == no_eh_edge_p);
+ gcc_checking_assert (eft != eeh);
+ }
+
+ if (!noreturn_p)
+ {
+ gimple_seq nseq = nothrow_p ? seq : gimple_seq_copy (seq);
+
+ if (must_end_bb)
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+ add_call_edges_for_seq (nseq, eft->count ());
+ gsi_insert_seq_on_edge_immediate (eft, nseq);
+ }
+ else
+ {
+ add_call_edges_for_seq (nseq, gsi_bb (gsi)->count);
+ gsi_insert_seq_after (&gsi, nseq, GSI_SAME_STMT);
+ }
+ }
+
+ if (nothrow_p)
+ return;
+
+ if (eh_lp)
+ {
+ add_call_edges_for_seq (seq, eeh->count ());
+ gsi_insert_seq_on_edge_immediate (eeh, seq);
+ return;
+ }
+
+ /* A throwing call may appear within a basic block in a function that doesn't
+ have any EH regions. We're going to add a cleanup if so, therefore the
+ block will have to be split. */
+ basic_block bb = gsi_bb (gsi);
+ if (!gsi_one_before_end_p (gsi))
+ split_block (bb, stmt);
+
+ /* Create a new block for the EH cleanup. */
+ basic_block bb_eh_cleanup = create_empty_bb (bb);
+ if (dom_info_available_p (CDI_DOMINATORS))
+ set_immediate_dominator (CDI_DOMINATORS, bb_eh_cleanup, bb);
+ if (current_loops)
+ add_bb_to_loop (bb_eh_cleanup, current_loops->tree_root);
+
+ /* Make the new block an EH cleanup for the call. */
+ eh_region new_r = gen_eh_region_cleanup (NULL);
+ eh_landing_pad lp = gen_eh_landing_pad (new_r);
+ tree label = gimple_block_label (bb_eh_cleanup);
+ lp->post_landing_pad = label;
+ EH_LANDING_PAD_NR (label) = lp->index;
+ add_stmt_to_eh_lp (stmt, lp->index);
+
+ /* Add the cleanup code to the EH cleanup block. */
+ gsi = gsi_after_labels (bb_eh_cleanup);
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
+
+ /* And then propagate the exception further. */
+ gresx *resx = gimple_build_resx (new_r->index);
+ gsi_insert_before (&gsi, resx, GSI_SAME_STMT);
+
+ /* Finally, wire the EH cleanup block into the CFG. */
+ make_eh_edges (stmt);
+ add_call_edges_for_seq (seq, single_pred_edge (bb_eh_cleanup)->count ());
+}
+
+/* Copy the attribute list at *ATTRS, minus any NAME attributes, leaving
+ shareable trailing nodes alone. */
+
+static inline void
+remove_named_attribute_unsharing (const char *name, tree *attrs)
+{
+ while (tree found = lookup_attribute (name, *attrs))
+ {
+ /* Copy nodes up to the next NAME attribute. */
+ while (*attrs != found)
+ {
+ *attrs = tree_cons (TREE_PURPOSE (*attrs),
+ TREE_VALUE (*attrs),
+ TREE_CHAIN (*attrs));
+ attrs = &TREE_CHAIN (*attrs);
+ }
+ /* Then drop it. */
+ gcc_checking_assert (*attrs == found);
+ *attrs = TREE_CHAIN (*attrs);
+ }
+}
+
+unsigned int
+pass_ipa_strub_mode::execute (function *)
+{
+ cgraph_node *onode;
+
+ /* If no strub flag was given in the command line,
+ set the actual default. */
+ if (flag_strub == -2)
+ flag_strub = -1;
+
+ bool any_strub = false;
+
+ for (int aliases = 0; aliases <= 1; aliases++)
+ FOR_EACH_FUNCTION (onode)
+ {
+ if (!onode->alias != !aliases)
+ continue;
+
+ enum strub_mode mode = set_strub_mode (onode);
+
+ if (mode == STRUB_AT_CALLS || mode == STRUB_INTERNAL)
+ any_strub = true;
+ }
+
+ if (!any_strub)
+ flag_strub = 0;
+
+ return 0;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub_mode (gcc::context *ctxt)
+{
+ return new pass_ipa_strub_mode (ctxt);
+}
+
+unsigned int
+pass_ipa_strub::execute (function *)
+{
+ cgraph_node *onode;
+
+ FOR_EACH_FUNCTION (onode)
+ {
+ enum strub_mode mode = get_strub_mode (onode);
+
+ if (mode == STRUB_AT_CALLS)
+ {
+ 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));
+
+ 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. */
+ 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;
+
+ tree wmptr = build_decl (DECL_SOURCE_LOCATION (onode->decl),
+ PARM_DECL,
+ get_watermark_ptr (),
+ get_qpptr ());
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_ARG_TYPE (wmptr) = get_qpptr ();
+ DECL_CONTEXT (wmptr) = onode->decl;
+ TREE_USED (wmptr) = 1;
+ DECL_CHAIN (wmptr) = *pargs;
+ *pargs = wmptr;
+
+ if (onode->alias)
+ continue;
+
+ 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);
+
+ /* 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 (nargs + 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 0
+ tree lswm = create_tmp_var (ptr_type_node, ".L.strub.watermark");
+ gassign *load = gimple_build_assign (lswm, swm);
+ gimple_seq_add_stmt (&seq, load);
+#else
+ tree lswm = unshare_expr (swmp);
+#endif
+
+ gcall *sleave = gimple_build_call (get_leave (), 1, lswm);
+ 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);
+
+ pop_cfun ();
+ }
+
+ if (!onode->has_gimple_body_p ())
+ continue;
+
+ cgraph_node *nnode = onode;
+ push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca)
+ {
+ 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);
+
+ if (!is_gimple_call (stmt))
+ continue;
+
+ gcall *call = as_a <gcall *> (stmt);
+
+ if (gimple_alloca_call_p (call))
+ {
+ /* Capture stack growth. */
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)
+ ->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ }
+ }
+
+ pop_cfun ();
+
+#if 0
+ compute_fn_summary (onode, true);
+#endif
+ continue;
+ }
+
+ if (mode != STRUB_INTERNAL
+ || !onode->has_gimple_body_p ())
+ 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 (!DECL_STRUCT_FUNCTION (onode->decl))
+ {
+ inform (DECL_SOURCE_LOCATION (onode->decl),
+ "not splitting struct-less function %qD for stack scrubbing",
+ onode->decl);
+ continue;
+ }
+
+ if (!onode->lowered)
+ {
+ inform (DECL_SOURCE_LOCATION (onode->decl),
+ "not splitting non-lowered function %qD for stack scrubbing",
+ 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 stack scrubbing",
+ onode->decl);
+ continue;
+ }
+
+ bool is_stdarg = calls_builtin_va_start_p (onode);;
+ bool apply_args = calls_builtin_apply_args_p (onode);
+
+ vec<ipa_adjusted_param, va_gc> *nparms = NULL;
+ unsigned j = 0;
+ {
+ // The following loop copied from ipa-split.c:split_function.
+ for (tree parm = DECL_ARGUMENTS (onode->decl);
+ parm; parm = DECL_CHAIN (parm), j++)
+ {
+ ipa_adjusted_param adj = {};
+ adj.op = IPA_PARAM_OP_COPY;
+ adj.base_index = j;
+ adj.prev_clone_index = j;
+ vec_safe_push (nparms, adj);
+ }
+
+ if (apply_args)
+ {
+ ipa_adjusted_param aaadj = {};
+ aaadj.op = IPA_PARAM_OP_NEW;
+ aaadj.type = get_qpptr ();
+ vec_safe_push (nparms, aaadj);
+ }
+
+ if (is_stdarg)
+ {
+ ipa_adjusted_param vladj = {};
+ vladj.op = IPA_PARAM_OP_NEW;
+ vladj.type = get_qpvalst ();
+ vec_safe_push (nparms, vladj);
+ }
+
+ ipa_adjusted_param wmadj = {};
+ wmadj.op = IPA_PARAM_OP_NEW;
+ wmadj.type = get_qpptr ();
+ vec_safe_push (nparms, wmadj);
+ }
+ ipa_param_adjustments adj (nparms, -1, false);
+
+ cgraph_node *nnode = onode->create_version_clone_with_body
+ (auto_vec<cgraph_edge *> (0),
+ NULL, &adj, NULL, NULL, "strub", NULL);
+
+ if (!nnode)
+ {
+ error_at (DECL_SOURCE_LOCATION (onode->decl),
+ "failed to split %qD for stack scrubbing",
+ onode->decl);
+ continue;
+ }
+
+ onode->split_part = true;
+ if (onode->calls_comdat_local)
+ nnode->add_to_same_comdat_group (onode);
+
+ gcc_checking_assert (!DECL_STRUCT_FUNCTION (nnode->decl)->stdarg);
+
+ set_strub_mode_to (onode, STRUB_WRAPPER);
+ set_strub_mode_to (nnode, STRUB_WRAPPED);
+
+ /* Decide which of the wrapped function's parms we want to turn into
+ references to the argument passed to the wrapper. In general, we want to
+ copy small arguments, and avoid copying large ones. Variable-sized array
+ lengths given by other arguments, as in 20020210-1.c, would lead to
+ problems if passed by value, after resetting the original function and
+ dropping the length computation; passing them by reference works.
+ DECL_BY_REFERENCE is *not* a substitute for this: it involves copying
+ anyway, but performed at the caller. */
+ indirect_parms_t indirect_nparms (3, false);
+ unsigned adjust_ftype = 0;
+ for (tree parm = DECL_ARGUMENTS (onode->decl),
+ nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (TREE_TYPE (nnode->decl));
+ parm;
+ parm = DECL_CHAIN (parm),
+ nparm = DECL_CHAIN (nparm),
+ nparmt = nparmt ? TREE_CHAIN (nparmt) : NULL_TREE)
+ if (!(0 /* DECL_BY_REFERENCE (narg) */
+ || is_gimple_reg_type (TREE_TYPE (nparm))
+ || VECTOR_TYPE_P (TREE_TYPE (nparm))
+ || TREE_CODE (TREE_TYPE (nparm)) == COMPLEX_TYPE
+ || (tree_fits_uhwi_p (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ && (tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ <= 4 * UNITS_PER_WORD))))
+ {
+ indirect_nparms.add (nparm);
+
+ /* ??? Is there any case in which it is not safe to suggest the parms
+ turned indirect don't alias anything else? They are distinct,
+ unaliased memory in the wrapper, and the wrapped can't possibly
+ take pointers into them because none of the pointers passed to the
+ wrapper can alias other incoming parameters passed by value, even
+ if with transparent reference, and the wrapper doesn't take any
+ extra parms that could point into wrapper's parms. So we can
+ probably drop the TREE_ADDRESSABLE and keep the true. */
+ tree ref_type = build_ref_type_for (nparm,
+ true
+ || !TREE_ADDRESSABLE (parm));
+
+ DECL_ARG_TYPE (nparm) = TREE_TYPE (nparm) = ref_type;
+ relayout_decl (nparm);
+ TREE_ADDRESSABLE (nparm) = 0;
+ DECL_BY_REFERENCE (nparm) = 0;
+ DECL_NOT_GIMPLE_REG_P (nparm) = 0;
+ /* ??? This avoids mismatches in debug info bind stmts in
+ e.g. a-chahan . */
+ DECL_ABSTRACT_ORIGIN (nparm) = NULL;
+
+ if (nparmt)
+ adjust_ftype++;
+ }
+
+ /* Also adjust the wrapped function type, if needed. */
+ if (adjust_ftype)
+ {
+ tree nftype = TREE_TYPE (nnode->decl);
+
+ /* We always add at least one argument at the end of the signature, when
+ cloning the function, so we don't expect to need to duplicate the
+ type here. */
+ gcc_checking_assert (TYPE_ARG_TYPES (nftype)
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+
+ /* Check that fnspec still works for the modified function signature,
+ and drop it otherwise. */
+ bool drop_fnspec = false;
+ tree fnspec = lookup_attribute ("fn spec", TYPE_ATTRIBUTES (nftype));
+ attr_fnspec spec = fnspec ? attr_fnspec (fnspec) : attr_fnspec ("");
+
+ unsigned retcopy;
+ if (!(fnspec && spec.returns_arg (&retcopy)))
+ retcopy = (unsigned) -1;
+
+ unsigned i = 0;
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (nftype);
+ adjust_ftype > 0;
+ nparm = DECL_CHAIN (nparm), nparmt = TREE_CHAIN (nparmt), i++)
+ if (indirect_nparms.contains (nparm))
+ {
+ TREE_VALUE (nparmt) = TREE_TYPE (nparm);
+ adjust_ftype--;
+
+ if (fnspec && !drop_fnspec)
+ {
+ if (i == retcopy)
+ drop_fnspec = true;
+ else if (spec.arg_specified_p (i))
+ {
+ /* Properties that apply to pointers only must not be
+ present, because we don't make pointers further
+ indirect. */
+ gcc_checking_assert
+ (!spec.arg_max_access_size_given_by_arg_p (i, NULL));
+ gcc_checking_assert (!spec.arg_copied_to_arg_p (i, NULL));
+
+ /* Any claim of direct access only is invalidated by
+ adding an indirection level. */
+ if (spec.arg_direct_p (i))
+ drop_fnspec = true;
+
+ /* If there's a claim the argument is not read from, the
+ added indirection invalidates it: if the argument is
+ used at all, then the pointer will necessarily be
+ read. */
+ if (!spec.arg_maybe_read_p (i)
+ && spec.arg_used_p (i))
+ drop_fnspec = true;
+ }
+ }
+ }
+
+ /* ??? Maybe we could adjust it instead. */
+ if (drop_fnspec)
+ remove_named_attribute_unsharing ("fn spec",
+ &TYPE_ATTRIBUTES (nftype));
+
+ TREE_TYPE (nnode->decl) = nftype;
+ }
+
+ {
+ tree decl = onode->decl;
+ cgraph_node *target = nnode;
+
+ { // copied from create_wrapper
+
+ /* Preserve DECL_RESULT so we get right by reference flag. */
+ tree decl_result = DECL_RESULT (decl);
+
+ /* Remove the function's body but keep arguments to be reused
+ for thunk. */
+ onode->release_body (true);
+ onode->reset ();
+
+ DECL_UNINLINABLE (decl) = false;
+ DECL_RESULT (decl) = decl_result;
+ DECL_INITIAL (decl) = NULL;
+ allocate_struct_function (decl, false);
+ set_cfun (NULL);
+
+ /* Turn alias into thunk and expand it into GIMPLE representation. */
+ onode->definition = true;
+
+ thunk_info::get_create (onode);
+ onode->thunk = true;
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (target, NULL, onode->count);
+#endif
+ onode->callees->can_throw_external = !TREE_NOTHROW (target->decl);
+
+ tree arguments = DECL_ARGUMENTS (decl);
+
+ while (arguments)
+ {
+ TREE_ADDRESSABLE (arguments) = false;
+ arguments = TREE_CHAIN (arguments);
+ }
+
+ {
+ tree alias = onode->callees->callee->decl;
+ tree thunk_fndecl = decl;
+ tree a;
+
+ int nxargs = 1 + is_stdarg + apply_args;
+
+ { // Simplified from expand_thunk.
+ tree restype;
+ basic_block bb, then_bb, else_bb, return_bb;
+ gimple_stmt_iterator bsi;
+ int nargs = 0;
+ tree arg;
+ int i;
+ tree resdecl;
+ tree restmp = NULL;
+
+ gcall *call;
+ greturn *ret;
+ bool alias_is_noreturn = TREE_THIS_VOLATILE (alias);
+
+ a = DECL_ARGUMENTS (thunk_fndecl);
+
+ current_function_decl = thunk_fndecl;
+
+ /* Ensure thunks are emitted in their correct sections. */
+ resolve_unique_section (thunk_fndecl, 0,
+ flag_function_sections);
+
+ bitmap_obstack_initialize (NULL);
+
+ /* Build the return declaration for the function. */
+ restype = TREE_TYPE (TREE_TYPE (thunk_fndecl));
+ if (DECL_RESULT (thunk_fndecl) == NULL_TREE)
+ {
+ resdecl = build_decl (input_location, RESULT_DECL, 0, restype);
+ DECL_ARTIFICIAL (resdecl) = 1;
+ DECL_IGNORED_P (resdecl) = 1;
+ DECL_CONTEXT (resdecl) = thunk_fndecl;
+ DECL_RESULT (thunk_fndecl) = resdecl;
+ }
+ else
+ resdecl = DECL_RESULT (thunk_fndecl);
+
+ profile_count cfg_count = onode->count;
+ if (!cfg_count.initialized_p ())
+ cfg_count = profile_count::from_gcov_type (BB_FREQ_MAX).guessed_local ();
+
+ bb = then_bb = else_bb = return_bb
+ = init_lowered_empty_function (thunk_fndecl, true, cfg_count);
+
+ bsi = gsi_start_bb (bb);
+
+ /* Build call to the function being thunked. */
+ if (!VOID_TYPE_P (restype)
+ && (!alias_is_noreturn
+ || TREE_ADDRESSABLE (restype)
+ || TREE_CODE (TYPE_SIZE_UNIT (restype)) != INTEGER_CST))
+ {
+ if (DECL_BY_REFERENCE (resdecl))
+ {
+ restmp = gimple_fold_indirect_ref (resdecl);
+ if (!restmp)
+ restmp = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (resdecl)),
+ resdecl,
+ build_int_cst (TREE_TYPE (resdecl), 0));
+ }
+ else if (!is_gimple_reg_type (restype))
+ {
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl)))
+ {
+ restmp = resdecl;
+
+ if (VAR_P (restmp))
+ {
+ add_local_decl (cfun, restmp);
+ BLOCK_VARS (DECL_INITIAL (current_function_decl))
+ = restmp;
+ }
+ }
+ else
+ restmp = create_tmp_var (restype, "retval");
+ }
+ else
+ restmp = create_tmp_reg (restype, "retval");
+ }
+
+ for (arg = a; arg; arg = DECL_CHAIN (arg))
+ nargs++;
+ auto_vec<tree> vargs (nargs + nxargs);
+ i = 0;
+ arg = a;
+
+ if (nargs)
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl);
+ i < nargs;
+ i++, arg = DECL_CHAIN (arg), nparm = DECL_CHAIN (nparm))
+ {
+ tree save_arg = arg;
+ tree tmp = arg;
+
+ /* Arrange to pass indirectly the parms, if we decided to do
+ so, and revert its type in the wrapper. */
+ if (indirect_nparms.contains (nparm))
+ {
+ tree ref_type = TREE_TYPE (nparm);
+ TREE_ADDRESSABLE (arg) = true;
+ tree addr = build1 (ADDR_EXPR, ref_type, arg);
+ tmp = arg = addr;
+ }
+ else
+ DECL_NOT_GIMPLE_REG_P (arg) = 0;
+
+ /* Convert the argument back to the type used by the calling
+ conventions, e.g. a non-prototyped float type is passed as
+ double, as in 930603-1.c, and needs to be converted back to
+ double to be passed on unchanged to the wrapped
+ function. */
+ if (TREE_TYPE (nparm) != DECL_ARG_TYPE (nparm))
+ arg = fold_convert (DECL_ARG_TYPE (nparm), arg);
+
+ if (!is_gimple_val (arg))
+ {
+ tmp = create_tmp_reg (TYPE_MAIN_VARIANT
+ (TREE_TYPE (arg)), "arg");
+ gimple *stmt = gimple_build_assign (tmp, arg);
+ gsi_insert_after (&bsi, stmt, GSI_NEW_STMT);
+ }
+ vargs.quick_push (tmp);
+ arg = save_arg;
+ }
+ /* These strub arguments are adjusted later. */
+ if (apply_args)
+ vargs.quick_push (null_pointer_node);
+ if (is_stdarg)
+ vargs.quick_push (null_pointer_node);
+ vargs.quick_push (null_pointer_node);
+ call = gimple_build_call_vec (build_fold_addr_expr_loc (0, alias),
+ vargs);
+ onode->callees->call_stmt = call;
+ // gimple_call_set_from_thunk (call, true);
+ if (DECL_STATIC_CHAIN (alias))
+ {
+ tree p = DECL_STRUCT_FUNCTION (alias)->static_chain_decl;
+ tree type = TREE_TYPE (p);
+ tree decl = build_decl (DECL_SOURCE_LOCATION (thunk_fndecl),
+ PARM_DECL, create_tmp_var_name ("CHAIN"),
+ type);
+ DECL_ARTIFICIAL (decl) = 1;
+ DECL_IGNORED_P (decl) = 1;
+ TREE_USED (decl) = 1;
+ DECL_CONTEXT (decl) = thunk_fndecl;
+ DECL_ARG_TYPE (decl) = type;
+ TREE_READONLY (decl) = 1;
+
+ struct function *sf = DECL_STRUCT_FUNCTION (thunk_fndecl);
+ sf->static_chain_decl = decl;
+
+ gimple_call_set_chain (call, decl);
+ }
+
+ /* Return slot optimization is always possible and in fact required to
+ return values with DECL_BY_REFERENCE. */
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl))
+ && (!is_gimple_reg_type (TREE_TYPE (resdecl))
+ || DECL_BY_REFERENCE (resdecl)))
+ gimple_call_set_return_slot_opt (call, true);
+
+ if (restmp)
+ {
+ gimple_call_set_lhs (call, restmp);
+ gcc_assert (useless_type_conversion_p (TREE_TYPE (restmp),
+ TREE_TYPE (TREE_TYPE (alias))));
+ }
+ gsi_insert_after (&bsi, call, GSI_NEW_STMT);
+ if (!alias_is_noreturn)
+ {
+ /* Build return value. */
+ if (!DECL_BY_REFERENCE (resdecl))
+ ret = gimple_build_return (restmp);
+ else
+ ret = gimple_build_return (resdecl);
+
+ gsi_insert_after (&bsi, ret, GSI_NEW_STMT);
+ }
+ else
+ {
+ remove_edge (single_succ_edge (bb));
+ }
+
+ cfun->gimple_df->in_ssa_p = true;
+ update_max_bb_count ();
+ profile_status_for_fn (cfun)
+ = cfg_count.initialized_p () && cfg_count.ipa_p ()
+ ? PROFILE_READ : PROFILE_GUESSED;
+ /* FIXME: C++ FE should stop setting TREE_ASM_WRITTEN on thunks. */
+ // TREE_ASM_WRITTEN (thunk_fndecl) = false;
+ delete_unreachable_blocks ();
+ update_ssa (TODO_update_ssa);
+ checking_verify_flow_info ();
+ free_dominance_info (CDI_DOMINATORS);
+
+ /* Since we want to emit the thunk, we explicitly mark its name as
+ referenced. */
+ onode->thunk = false;
+ onode->lowered = true;
+ bitmap_obstack_release (NULL);
+ }
+ current_function_decl = NULL;
+ set_cfun (NULL);
+ }
+
+ thunk_info::remove (onode);
+
+ // some more of create_wrapper at the end of the next block.
+ }
+ }
+
+ {
+ tree aaptr = NULL_TREE;
+ tree vaptr = NULL_TREE;
+ tree wmptr = NULL_TREE;
+ for (tree arg = DECL_ARGUMENTS (nnode->decl); arg; arg = DECL_CHAIN (arg))
+ {
+ aaptr = vaptr;
+ vaptr = wmptr;
+ wmptr = arg;
+ }
+
+ if (!apply_args)
+ aaptr = NULL_TREE;
+ /* The trailing args are [apply_args], [va_list_ptr], and
+ watermark. If we don't have a va_list_ptr, the penultimate
+ argument is apply_args.
+ */
+ else if (!is_stdarg)
+ aaptr = vaptr;
+
+ if (!is_stdarg)
+ vaptr = NULL_TREE;
+
+ DECL_NAME (wmptr) = get_watermark_ptr ();
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_IGNORED_P (wmptr) = 1;
+ TREE_USED (wmptr) = 1;
+
+ if (is_stdarg)
+ {
+ DECL_NAME (vaptr) = get_va_list_ptr ();
+ DECL_ARTIFICIAL (vaptr) = 1;
+ DECL_IGNORED_P (vaptr) = 1;
+ TREE_USED (vaptr) = 1;
+ }
+
+ if (apply_args)
+ {
+ DECL_NAME (aaptr) = get_apply_args_ptr ();
+ DECL_ARTIFICIAL (aaptr) = 1;
+ DECL_IGNORED_P (aaptr) = 1;
+ TREE_USED (aaptr) = 1;
+ }
+
+ push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ bool any_indirect = !indirect_nparms.is_empty ();
+
+ if (any_indirect)
+ {
+ 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);
+
+ 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
+ || is_stdarg || apply_args)
+ for (cgraph_edge *e = nnode->callees, *enext; e; e = enext)
+ {
+ gcall *call = e->call_stmt;
+ gimple_stmt_iterator gsi = gsi_for_stmt (call);
+ tree fndecl = e->callee->decl;
+
+ enext = e->next_callee;
+
+ if (gimple_alloca_call_p (call))
+ {
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ else if (fndecl && is_stdarg
+ && fndecl_built_in_p (fndecl, BUILT_IN_VA_START))
+ {
+ if (builtin_decl_explicit (BUILT_IN_VA_START) != fndecl)
+ sorry_at (gimple_location (call),
+ "nonstandard stdarg conventions");
+ tree bvacopy = builtin_decl_explicit (BUILT_IN_VA_COPY);
+ gimple_call_set_fndecl (call, bvacopy);
+ tree arg = vaptr;
+ /* The va_copy source must be dereferenced, unless it's an array
+ type, that would have decayed to a pointer. */
+ if (TREE_CODE (TREE_TYPE (TREE_TYPE (vaptr))) != ARRAY_TYPE)
+ {
+ arg = gimple_fold_indirect_ref (vaptr);
+ if (!arg)
+ arg = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (vaptr)),
+ vaptr,
+ build_int_cst (TREE_TYPE (vaptr), 0));
+ }
+ gimple_call_set_arg (call, 1, arg);
+ update_stmt (call);
+ e->redirect_callee (cgraph_node::get_create (bvacopy));
+ }
+ else if (fndecl && apply_args
+ && fndecl_built_in_p (fndecl, BUILT_IN_APPLY_ARGS))
+ {
+ tree lhs = gimple_call_lhs (call);
+ gassign *assign = gimple_build_assign (lhs, aaptr);
+ gsi_replace (&gsi, assign, true);
+ cgraph_edge::remove (e);
+ }
+ }
+
+ { // a little more copied from create_wrapper
+
+ /* Inline summary set-up. */
+ nnode->analyze ();
+ // inline_analyze_function (nnode);
+ }
+
+ pop_cfun ();
+ }
+
+ {
+ 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));
+
+ 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
+ onode->create_edge (cgraph_node::get_create (enter),
+ stptr, gsi_bb (gsi)->count, false);
+#endif
+
+ int nargs = gimple_call_num_args (wrcall);
+
+ gimple_seq seq = NULL;
+
+ if (apply_args)
+ {
+ tree aalst = create_tmp_var (ptr_type_node, ".strub.appargs");
+ tree bappargs = builtin_decl_explicit (BUILT_IN_APPLY_ARGS);
+ gcall *appargs = gimple_build_call (bappargs, 0);
+ gimple_call_set_lhs (appargs, aalst);
+ gsi_insert_before (&gsi, appargs, GSI_SAME_STMT);
+ gimple_call_set_arg (wrcall, nargs - 2 - is_stdarg, aalst);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bappargs),
+ appargs, gsi_bb (gsi)->count, false);
+#endif
+ }
+
+ if (is_stdarg)
+ {
+ tree valst = create_tmp_var (va_list_type_node, ".strub.va_list");
+ TREE_ADDRESSABLE (valst) = true;
+ tree vaptr = build1 (ADDR_EXPR,
+ build_pointer_type (va_list_type_node),
+ valst);
+ gimple_call_set_arg (wrcall, nargs - 2, unshare_expr (vaptr));
+
+ tree bvastart = builtin_decl_explicit (BUILT_IN_VA_START);
+ gcall *vastart = gimple_build_call (bvastart, 2,
+ unshare_expr (vaptr),
+ integer_zero_node);
+ gsi_insert_before (&gsi, vastart, GSI_SAME_STMT);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bvastart),
+ vastart, gsi_bb (gsi)->count, false);
+#endif
+
+ tree bvaend = builtin_decl_explicit (BUILT_IN_VA_END);
+ gcall *vaend = gimple_build_call (bvaend, 1, unshare_expr (vaptr));
+ gimple_seq_add_stmt (&seq, vaend);
+ }
+
+ gimple_call_set_arg (wrcall, nargs - 1, unshare_expr (swmp));
+ // gimple_call_set_tail (wrcall, false);
+ update_stmt (wrcall);
+
+ {
+#if 0
+ tree lswm = create_tmp_var (ptr_type_node, ".L.strub.watermark");
+ gassign *load = gimple_build_assign (lswm, swm);
+ gimple_seq_add_stmt (&seq, load);
+#else
+ tree lswm = unshare_expr (swmp);
+#endif
+
+ gcall *sleave = gimple_build_call (get_leave (), 1, lswm);
+ 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);
+
+ /* For nnode, we don't rebuild edges because we wish to retain
+ any redirections copied to it from earlier passes, so we add
+ call graph edges explicitly there, but for onode, we create a
+ fresh function, so we may as well just issue the calls and
+ then rebuild all cgraph edges. */
+ // cgraph_edge::rebuild_edges ();
+ onode->analyze ();
+ // inline_analyze_function (onode);
+
+ pop_cfun ();
+ }
+
+#if 0
+ compute_fn_summary (onode, true);
+ compute_fn_summary (nnode, true);
+#endif
+ }
+
+ /* ??? Check that strub functions don't call non-strub functions, and that
+ always_inline strub functions are only called by strub functions. */
+
+ return 0;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub (gcc::context *ctxt)
+{
+ return new pass_ipa_strub (ctxt);
+}
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
new file mode 100644
index 00000000000..cadbca5002a
--- /dev/null
+++ b/gcc/ipa-strub.h
@@ -0,0 +1,25 @@
+/* strub (stack scrubbing) infrastructure.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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);
diff --git a/gcc/passes.def b/gcc/passes.def
index f5d88a61b0e..677c000e80b 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -53,6 +53,7 @@ along with GCC; see the file COPYING3. If not see
INSERT_PASSES_AFTER (all_small_ipa_passes)
NEXT_PASS (pass_ipa_free_lang_data);
NEXT_PASS (pass_ipa_function_and_variable_visibility);
+ NEXT_PASS (pass_ipa_strub_mode);
NEXT_PASS (pass_build_ssa_passes);
PUSH_INSERT_PASSES_WITHIN (pass_build_ssa_passes)
NEXT_PASS (pass_fixup_cfg);
@@ -111,6 +112,7 @@ along with GCC; see the file COPYING3. If not see
POP_INSERT_PASSES ()
NEXT_PASS (pass_ipa_remove_symbols);
+ NEXT_PASS (pass_ipa_strub);
NEXT_PASS (pass_ipa_oacc);
PUSH_INSERT_PASSES_WITHIN (pass_ipa_oacc)
NEXT_PASS (pass_ipa_pta);
diff --git a/gcc/testsuite/g++.dg/wrappers/strub1.C b/gcc/testsuite/g++.dg/wrappers/strub1.C
new file mode 100644
index 00000000000..a474a929649
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub1.C
@@ -0,0 +1,18 @@
+// { dg-do run }
+
+// Check that we don't get extra copies.
+
+struct T {
+ T &self;
+ void check () const { if (&self != this) __builtin_abort (); }
+ T() : self (*this) { check (); }
+ T(const T& ck) : self (*this) { ck.check (); check (); }
+ ~T() { check (); }
+};
+
+T foo (T) { return T(); }
+T bar (T p) { return foo (p); }
+
+int main () {
+ bar (T{});
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub2.C b/gcc/testsuite/g++.dg/wrappers/strub2.C
new file mode 100644
index 00000000000..25a62166448
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub2.C
@@ -0,0 +1,22 @@
+// { dg-do run }
+
+// This doesn't really test anything yet. We should mark the
+// variables as requiring strubbing, and somehow check that the
+// wrapped functions take the parameter by reference.
+
+struct T {
+ char d[32 * sizeof(void*)];
+};
+
+T foo (T q) { asm ("" : : "m"(q)); return q; }
+T bar (T p) { return foo (p); }
+
+T tmp;
+T tmp2;
+
+int main () {
+ __builtin_memset (&tmp, 0x55, sizeof (tmp));
+ tmp2 = bar (tmp);
+ if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
+ __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub3.C b/gcc/testsuite/g++.dg/wrappers/strub3.C
new file mode 100644
index 00000000000..e1b51cd0399
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub3.C
@@ -0,0 +1,22 @@
+// { dg-do run }
+
+// This doesn't really test anything yet. We should mark the
+// variables as requiring strubbing, and somehow check that the
+// wrapped functions take the parameter by reference.
+
+struct T {
+ char d[32 * sizeof(void*)];
+};
+
+static T foo (T q) { asm ("" : : "m"(q)); return q; }
+static T bar (T p) { return foo (p); }
+
+T tmp;
+T tmp2;
+
+int main () {
+ __builtin_memset (&tmp, 0x55, sizeof (tmp));
+ tmp2 = bar (tmp);
+ if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
+ __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub4.C b/gcc/testsuite/g++.dg/wrappers/strub4.C
new file mode 100644
index 00000000000..d021fca88e4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub4.C
@@ -0,0 +1,18 @@
+// { dg-do run }
+
+namespace
+{
+ class foo
+ {
+ public:
+ foo();
+ };
+
+ foo::foo() {}
+
+ foo bar;
+}
+
+int main()
+{
+}
diff --git a/gcc/tree-cfg.c b/gcc/tree-cfg.c
index 38269a27b79..b5e0af8b3a1 100644
--- a/gcc/tree-cfg.c
+++ b/gcc/tree-cfg.c
@@ -5617,6 +5617,7 @@ gimple_verify_flow_info (void)
{
gimple *stmt = gsi_stmt (gsi);
+ /* Do NOT disregard debug stmts after found_ctrl_stmt. */
if (found_ctrl_stmt)
{
error ("control flow in the middle of basic block %d",
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index aa9757a2fe9..0210f84bc0c 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -494,8 +494,9 @@ extern gimple_opt_pass *make_pass_adjust_alignment (gcc::context *ctxt);
/* IPA Passes */
extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt);
-extern simple_ipa_opt_pass
- *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub_mode (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_tree_profile (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_auto_profile (gcc::context *ctxt);
diff --git a/libgcc/Makefile.in b/libgcc/Makefile.in
index 2c8be561eb5..6b7b12493f5 100644
--- a/libgcc/Makefile.in
+++ b/libgcc/Makefile.in
@@ -428,7 +428,7 @@ ifneq ($(enable_shared),yes)
iterator = $(patsubst %,$(srcdir)/static-object.mk,$(iter-items))
endif
-LIB2ADD += enable-execute-stack.c
+LIB2ADD += enable-execute-stack.c $(srcdir)/strub.c
# While emutls.c has nothing to do with EH, it is in LIB2ADDEH*
# instead of LIB2ADD because that's the way to be sure on some targets
diff --git a/libgcc/libgcc2.h b/libgcc/libgcc2.h
index 1819ff3ac3d..857091e65c8 100644
--- a/libgcc/libgcc2.h
+++ b/libgcc/libgcc2.h
@@ -532,6 +532,10 @@ extern int __parityDI2 (UDWtype);
extern void __enable_execute_stack (void *);
+extern void __strub_enter (void **);
+extern void __strub_update (void**);
+extern void __strub_leave (void **);
+
#ifndef HIDE_EXPORTS
#pragma GCC visibility pop
#endif
diff --git a/libgcc/strub.c b/libgcc/strub.c
new file mode 100644
index 00000000000..fd6e27556e4
--- /dev/null
+++ b/libgcc/strub.c
@@ -0,0 +1,112 @@
+/* Stack scrubbing infrastructure
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+Under Section 7 of GPL version 3, you are granted additional
+permissions described in the GCC Runtime Library Exception, version
+3.1, as published by the Free Software Foundation.
+
+You should have received a copy of the GNU General Public License and
+a copy of the GCC Runtime Library Exception along with this program;
+see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
+<http://www.gnu.org/licenses/>. */
+
+#include "tconfig.h"
+#include "tsystem.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "libgcc_tm.h"
+#include "libgcc2.h"
+
+#ifndef STACK_GROWS_DOWNWARD
+# define TOPS >
+#else
+# define TOPS <
+#endif
+
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+
+/* Enter a stack scrubbing context, initializing the watermark to the caller's
+ stack address. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_enter (void **watermark)
+{
+ *watermark = __builtin_frame_address (0);
+}
+
+/* Update the watermark within a stack scrubbing context with the current stack
+ pointer. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_update (void **watermark)
+{
+ void *sp = __builtin_frame_address (0);
+
+ if (sp TOPS *watermark)
+ *watermark = sp;
+}
+
+#ifndef TARGET_STRUB_USE_DYNAMIC_ARRAY
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+#endif
+
+#ifndef TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+# ifdef TARGET_STRUB_MAY_USE_MEMSET
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+# else
+# define TARGET_STRUB_MAY_USE_MEMSET 1
+# endif
+#endif
+
+/* Leave a stack scrubbing context, restoring and updating SAVED, and
+ clearing the stack between top and watermark. */
+void ATTRIBUTE_STRUB_CALLABLE
+#if ! TARGET_STRUB_MAY_USE_MEMSET
+__attribute__ ((__optimize__ ("-fno-tree-loop-distribute-patterns")))
+#endif
+__strub_leave (void **mark)
+{
+ void *sp = __builtin_stack_address ();
+
+ void **base, **end;
+#ifndef STACK_GROWS_DOWNWARD
+ base = sp;
+ end = *mark;
+#else
+ base = *mark;
+ end = sp;
+#endif
+
+ ptrdiff_t len = end - base;
+ if (len <= 0)
+ return;
+
+#if ! TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+ /* Allocate a dynamically-sized array covering the desired range, so that we
+ can safely call memset on it. */
+ void *ptr[len];
+ base = &ptr[0];
+ end = &ptr[len];
+#else
+ void **ptr = end;
+#endif /* TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY */
+
+ /* ldist turns this into a memset. Without the dynamic array above, that call
+ is likely unsafe: possibly tail-called, and likely scribbling over its own
+ stack frame. */
+ while (base < end)
+ *base++ = 0;
+
+ asm ("" : : "m" (ptr));
+}
^ permalink raw reply [flat|nested] 12+ messages in thread
* [gcc(refs/users/aoliva/heads/strub)] strub with function and variable attributes
@ 2021-07-25 1:43 Alexandre Oliva
0 siblings, 0 replies; 12+ messages in thread
From: Alexandre Oliva @ 2021-07-25 1:43 UTC (permalink / raw)
To: gcc-cvs
https://gcc.gnu.org/g:e2b5b936e3fd1fab3cb0a7cdb580a677712300ea
commit e2b5b936e3fd1fab3cb0a7cdb580a677712300ea
Author: Alexandre Oliva <oliva@adacore.com>
Date: Thu Jul 22 05:47:05 2021 -0300
strub with function and variable attributes
still missing verification of calls, documentation, tests,
builtin expansion
Diff:
---
gcc/Makefile.in | 1 +
gcc/builtins.c | 12 +
gcc/builtins.def | 4 +
gcc/c-family/c-attribs.c | 39 +
gcc/common.opt | 16 +
gcc/doc/extend.texi | 4 +
gcc/ipa-inline.c | 6 +
gcc/ipa-strub.c | 2308 ++++++++++++++++++++++++++++++++
gcc/ipa-strub.h | 25 +
gcc/passes.def | 2 +
gcc/testsuite/g++.dg/wrappers/strub1.C | 18 +
gcc/testsuite/g++.dg/wrappers/strub2.C | 22 +
gcc/testsuite/g++.dg/wrappers/strub3.C | 22 +
gcc/testsuite/g++.dg/wrappers/strub4.C | 18 +
gcc/tree-cfg.c | 1 +
gcc/tree-pass.h | 5 +-
libgcc/Makefile.in | 2 +-
libgcc/libgcc2.h | 4 +
libgcc/strub.c | 112 ++
19 files changed, 2618 insertions(+), 3 deletions(-)
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 1666ef84d6a..163fd1a856c 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1468,6 +1468,7 @@ OBJS = \
ipa-reference.o \
ipa-ref.o \
ipa-utils.o \
+ ipa-strub.o \
ipa.o \
ira.o \
ira-build.o \
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 170d776c410..84539d73c8c 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -153,6 +153,7 @@ static rtx expand_builtin_strnlen (tree, rtx, machine_mode);
static rtx expand_builtin_alloca (tree);
static rtx expand_builtin_unop (machine_mode, tree, rtx, rtx, optab);
static rtx expand_builtin_frame_address (tree, tree);
+static rtx expand_builtin_stack_address ();
static tree stabilize_va_list_loc (location_t, tree, int);
static rtx expand_builtin_expect (tree, rtx);
static rtx expand_builtin_expect_with_probability (tree, rtx);
@@ -7893,6 +7894,14 @@ expand_builtin_frame_address (tree fndecl, tree exp)
}
}
+/* Expand a call to builtin functions __builtin_stack_address. */
+
+static rtx
+expand_builtin_stack_address ()
+{
+ return copy_addr_to_reg (stack_pointer_rtx);
+}
+
/* Expand EXP, a call to the alloca builtin. Return NULL_RTX if we
failed and the caller should emit a normal call. */
@@ -10151,6 +10160,9 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
case BUILT_IN_RETURN_ADDRESS:
return expand_builtin_frame_address (fndecl, exp);
+ case BUILT_IN_STACK_ADDRESS:
+ return expand_builtin_stack_address ();
+
/* Returns the address of the area where the structure is returned.
0 otherwise. */
case BUILT_IN_AGGREGATE_INCOMING_ADDRESS:
diff --git a/gcc/builtins.def b/gcc/builtins.def
index ec556df4f66..4545033e664 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -878,6 +878,10 @@ DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSL, "ffsl", BT_FN_INT_LONG, ATTR_CONST_NOTHRO
DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSLL, "ffsll", BT_FN_INT_LONGLONG, ATTR_CONST_NOTHROW_LEAF_LIST)
DEF_EXT_LIB_BUILTIN (BUILT_IN_FORK, "fork", BT_FN_PID, ATTR_NOTHROW_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FRAME_ADDRESS, "frame_address", BT_FN_PTR_UINT, ATTR_NULL)
+DEF_GCC_BUILTIN (BUILT_IN_STACK_ADDRESS, "stack_address", BT_FN_PTR, ATTR_NULL)
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_ENTER, "__builtin___strub_enter")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_UPDATE, "__builtin___strub_update")
+DEF_BUILTIN_STUB (BUILT_IN___STRUB_LEAVE, "__builtin___strub_leave")
/* [trans-mem]: Adjust BUILT_IN_TM_FREE if BUILT_IN_FREE is changed. */
DEF_LIB_BUILTIN (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_LEAF_LIST)
DEF_GCC_BUILTIN (BUILT_IN_FROB_RETURN_ADDR, "frob_return_addr", BT_FN_PTR_PTR, ATTR_NULL)
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index e60fb31d8c8..f596e3f7237 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -69,6 +69,7 @@ static tree handle_asan_odr_indicator_attribute (tree *, tree, tree, int,
static tree handle_stack_protect_attribute (tree *, tree, tree, int, bool *);
static tree handle_no_stack_protector_function_attribute (tree *, tree,
tree, int, bool *);
+static tree handle_strub_attribute (tree *, tree, tree, int, bool *);
static tree handle_noinline_attribute (tree *, tree, tree, int, bool *);
static tree handle_noclone_attribute (tree *, tree, tree, int, bool *);
static tree handle_nocf_check_attribute (tree *, tree, tree, int, bool *);
@@ -306,6 +307,8 @@ const struct attribute_spec c_common_attribute_table[] =
{ "no_stack_protector", 0, 0, true, false, false, false,
handle_no_stack_protector_function_attribute,
attr_stack_protect_exclusions },
+ { "strub", 0, 1, false, true, false, true,
+ handle_strub_attribute, NULL },
{ "noinline", 0, 0, true, false, false, false,
handle_noinline_attribute,
attr_noinline_exclusions },
@@ -1290,6 +1293,42 @@ handle_noipa_attribute (tree *node, tree name, tree, int, bool *no_add_attrs)
return NULL_TREE;
}
+/* Handle a "strub" attribute; arguments as in
+ struct attribute_spec.handler. */
+
+static tree
+handle_strub_attribute (tree *node, tree name,
+ tree args,
+ int ARG_UNUSED (flags), bool *no_add_attrs)
+{
+ if (args
+ && (TREE_CODE (*node) == FUNCTION_TYPE
+ || TREE_CODE (*node) == METHOD_TYPE))
+ {
+ /* 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)
+ {
+ warning (OPT_Wattributes,
+ "%qE attribute ignored because of argument %qE",
+ name, TREE_VALUE (args));
+ *no_add_attrs = true;
+ }
+
+ args = TREE_CHAIN (args);
+ }
+
+ if (args)
+ warning (OPT_Wattributes,
+ "ignoring excess %qE attribute arguments starting at %qE",
+ name, TREE_VALUE (args));
+
+ return NULL_TREE;
+}
+
/* Handle a "noinline" attribute; arguments as in
struct attribute_spec.handler. */
diff --git a/gcc/common.opt b/gcc/common.opt
index d9da1131eda..72a0b9e7a0c 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,6 +2687,22 @@ fstrict-overflow
Common
Treat signed overflow as undefined. Negated as -fwrapv -fwrapv-pointer.
+fstrub
+Common Var(flag_strub, 3) Init(-2)
+Enable (or disable) stack scrubbing for all viable functions
+
+fstrub-default
+Common Var(flag_strub, -1)
+Enable stack scrub as requested through attributes
+
+fstrub-at-calls
+Common RejectNegative Var(flag_strub, 1)
+Enable at-calls stack scrubbing for all viable functions
+
+fstrub-internal
+Common RejectNegative Var(flag_strub, 2)
+Enable internal stack scrubbing for all viable functions
+
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 b83cd4919bb..4e4206b2f35 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -11670,6 +11670,10 @@ option is in effect. Such calls should only be made in debugging
situations.
@end deftypefn
+@deftypefn {Built-in Function} {void *} __builtin_stack_address ()
+This function returns the value of the stack pointer register.
+@end deftypefn
+
@node Vector Extensions
@section Using Vector Instructions through Built-in Functions
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 413446bcc46..7f4bc44d2bb 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -119,6 +119,7 @@ along with GCC; see the file COPYING3. If not see
#include "stringpool.h"
#include "attribs.h"
#include "asan.h"
+#include "ipa-strub.h"
typedef fibonacci_heap <sreal, cgraph_edge> edge_heap_t;
typedef fibonacci_node <sreal, cgraph_edge> edge_heap_node_t;
@@ -396,6 +397,11 @@ 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))
+ {
+ e->inline_failed = CIF_UNSPECIFIED;
+ inlinable = false;
+ }
if (!inlinable && report)
report_inline_failed_reason (e);
return inlinable;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
new file mode 100644
index 00000000000..f9a3c92a115
--- /dev/null
+++ b/gcc/ipa-strub.c
@@ -0,0 +1,2308 @@
+/* strub (stack scrubbing) support.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "gimplify.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "gimple-iterator.h"
+#include "gimplify-me.h"
+#include "tree-into-ssa.h"
+#include "tree-ssa.h"
+#include "tree-cfg.h"
+#include "cfghooks.h"
+#include "cfgloop.h"
+#include "cfgcleanup.h"
+#include "tree-eh.h"
+#include "except.h"
+#include "builtins.h"
+#include "attribs.h"
+#include "tree-inline.h"
+#include "cgraph.h"
+#include "alloc-pool.h"
+#include "symbol-summary.h"
+#include "ipa-prop.h"
+#include "ipa-fnsummary.h"
+#include "symtab-thunks.h"
+#include "gimple-fold.h"
+#include "fold-const.h"
+#include "gimple-walk.h"
+#include "tree-dfa.h"
+#include "langhooks.h"
+#include "calls.h"
+#include "vec.h"
+#include "stor-layout.h"
+#include "varasm.h"
+#include "alias.h"
+#include "diagnostic.h"
+#include "intl.h"
+#include "ipa-strub.h"
+#include "attr-fnspec.h"
+
+enum strub_mode {
+ /* This mode denotes a regular function, that does not require stack
+ scrubbing (strubbing). It may call any other functions, but if
+ it calls AT_CALLS (or WRAPPED) ones, strubbing logic is
+ automatically introduced around those calls (the latter, by
+ inlining INTERNAL wrappers). */
+ STRUB_DISABLED = 0,
+
+ /* This denotes a function whose signature is (to be) modified to
+ take an extra parameter, for stack use annotation, and its
+ callers must initialize and pass that argument, and perform the
+ strubbing. Functions that are explicitly marked with attribute
+ strub must have the mark visible wherever the function is,
+ including aliases, and overriders and overriding methods.
+ Functions that are implicitly marked for strubbing, for accessing
+ variables explicitly marked as such, will only select this
+ strubbing method if they are internal to a translation unit. It
+ can only be inlined into other strubbing functions, i.e.,
+ STRUB_AT_CALLS or STRUB_WRAPPED. */
+ STRUB_AT_CALLS = 1,
+
+ /* This denotes a function that is to perform strubbing internally,
+ without any changes to its interface (the function is turned into
+ a strubbing wrapper, and its original body is moved to a separate
+ STRUB_WRAPPED function, with a modified interface). Functions
+ may be explicitly marked with attribute strub(2), and the
+ attribute must be visible at the point of definition. Functions
+ that are explicitly marked for strubbing, for accessing variables
+ explicitly marked as such, may select this strubbing mode if
+ their interface cannot change, e.g. because its interface is
+ visible to other translation units, directly, by indirection
+ (having its address taken), inheritance, etc. Functions that use
+ this method must not have the noclone attribute, nor the noipa
+ one. Functions marked as always_inline may select this mode, but
+ they are NOT wrapped, they remain unchanged, and are only inlined
+ into strubbed contexts. Once non-always_inline functions are
+ wrapped, the wrapper becomes STRUB_WRAPPER, and the wrapped becomes
+ STRUB_WRAPPED. */
+ STRUB_INTERNAL = 2,
+
+ /* This denotes a function whose stack is not strubbed, but that is
+ nevertheless explicitly or implicitly marked as callable from strubbing
+ functions. Normally, only STRUB_AT_CALLS (and STRUB_INTERNAL ->
+ STRUB_WRAPPED) functions can be called from strubbing contexts (bodies of
+ STRUB_AT_CALLS, STRUB_INTERNAL and STRUB_WRAPPED functions), but attribute
+ strub(3) enables other functions to be (indirectly) called from these
+ contexts. Some builtins and internal functions may be implicitly marked as
+ STRUB_CALLABLE. */
+ STRUB_CALLABLE = 3,
+
+ /* This denotes the function that took over the body of a
+ STRUB_INTERNAL function. At first, it's only called by its
+ wrapper, but the wrapper may be inlined. The wrapped function,
+ in turn, can only be inlined into other functions whose stack
+ frames are strubbed, i.e., that are STRUB_WRAPPED or
+ STRUB_AT_CALLS. */
+ STRUB_WRAPPED = -1,
+
+ /* This denotes the wrapper function that replaced the STRUB_INTERNAL
+ function. This mode overrides the STRUB_INTERNAL mode at the time the
+ internal to-be-wrapped function becomes a wrapper, so that inlining logic
+ can tell one from the other. */
+ STRUB_WRAPPER = -2,
+
+ /* This denotes an always_inline function that requires strubbing. It can
+ only be called from, and inlined into, other strubbing contexts. */
+ STRUB_INLINABLE = -3,
+};
+
+static tree
+get_strub_attr_from_type (tree type)
+{
+ return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
+}
+
+static tree
+get_strub_attr_from_decl (tree decl)
+{
+ tree ret = lookup_attribute ("strub", DECL_ATTRIBUTES (decl));
+ if (ret)
+ return ret;
+ return get_strub_attr_from_type (TREE_TYPE (decl));
+}
+
+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);
+
+#if 0 /* ??? use symbolic mode names with interned strings? */
+ char *s = NULL;
+
+ switch (strub_mode)
+ {
+
+ }
+#endif
+}
+
+static enum strub_mode
+get_strub_mode_from_attr (tree strub_attr)
+{
+ enum strub_mode mode = STRUB_DISABLED;
+
+ if (strub_attr)
+ {
+ if (!TREE_VALUE (strub_attr))
+ mode = STRUB_AT_CALLS;
+ 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 /* ??? Support symbolic mode names? */
+ gcc_unreachable ();
+ }
+
+ return mode;
+}
+
+static enum strub_mode
+get_strub_mode (cgraph_node *node)
+{
+ return get_strub_mode_from_attr (get_strub_attr_from_decl (node->decl));
+}
+
+static bool
+calls_builtin_va_start_p (cgraph_node *node)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (fndecl_built_in_p (cdecl, BUILT_IN_VA_START))
+ return true;
+ }
+
+ return result;
+}
+
+static bool
+calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
+{
+ bool result = false;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!fndecl_built_in_p (cdecl, BUILT_IN_APPLY_ARGS))
+ continue;
+
+ result = true;
+
+ if (!report)
+ break;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "at-calls strub does not support call to %qD",
+ cdecl);
+ }
+
+ return result;
+}
+
+static bool
+can_strub_at_calls_p (cgraph_node *node, bool report = false)
+{
+ return !calls_builtin_apply_args_p (node, report);
+}
+
+#define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
+
+/* We can't perform internal strubbing if the function body involves certain
+ features:
+
+ - a non-default __builtin_va_start (e.g. x86's __builtin_ms_va_start) is
+ currently unsupported because we can't discover the corresponding va_copy and
+ va_end decls in the wrapper, and we don't convey the alternate variable
+ arguments ABI to the modified wrapped function. The default
+ __builtin_va_start is supported by calling va_start/va_end at the wrapper,
+ that takes variable arguments, passing a pointer to the va_list object to the
+ wrapped function, that runs va_copy from it where the original function ran
+ va_start.
+
+ __builtin_next_arg is currently unsupported because the wrapped function
+ won't be a variable argument function. We could process it in the wrapper,
+ that remains a variable argument function, and replace calls in the wrapped
+ body, but we currently don't.
+
+ __builtin_return_address is rejected because it's generally used when the
+ actual caller matters, and introducing a wrapper breaks such uses as those in
+ the unwinder. */
+
+static bool
+can_strub_internally_p (cgraph_node *node, bool report = false)
+{
+ bool result = true;
+
+ for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ tree cdecl = e->callee->decl;
+ if (!((fndecl_built_in_p (cdecl, BUILT_IN_VA_START)
+ && cdecl != builtin_decl_explicit (BUILT_IN_VA_START))
+ || fndecl_built_in_p (cdecl, BUILT_IN_NEXT_ARG)
+ || fndecl_built_in_p (cdecl, BUILT_IN_RETURN_ADDRESS)))
+ continue;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (e->call_stmt),
+ "internal strub does not support call to %qD",
+ cdecl);
+ }
+
+ basic_block bb;
+ if (node->has_gimple_body_p ())
+ {
+ /* Label values referenced 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. */
+
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ glabel *label_stmt = dyn_cast <glabel *> (gsi_stmt (gsi));
+ tree target;
+
+ if (!label_stmt)
+ break;
+
+ 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;
+
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (gimple_location (label_stmt),
+ "internal strub does not support user labels");
+ }
+ }
+
+ if (list_length (TYPE_ARG_TYPES (TREE_TYPE (node->decl)))
+ >= (((HOST_WIDE_INT) 1 << IPA_PARAM_MAX_INDEX_BITS)
+ - STRUB_INTERNAL_MAX_EXTRA_ARGS))
+ {
+ result = false;
+
+ if (!report)
+ return result;
+
+ sorry_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD has too many arguments for internal strub",
+ node->decl);
+ }
+
+ return result;
+}
+
+static bool
+strub_from_body_p (cgraph_node *node)
+{
+ if (!node->has_gimple_body_p ())
+ return false;
+
+ /* If any local variable is marked for strub... */
+ unsigned i;
+ tree var;
+ FOR_EACH_LOCAL_DECL (DECL_STRUCT_FUNCTION (node->decl),
+ i, var)
+ if (get_strub_attr_from_decl (var))
+ return true;
+
+ /* Now scan the body for loads with strub types. */
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!gimple_assign_load_p (stmt))
+ continue;
+
+ tree rhs = gimple_assign_rhs1 (stmt);
+ if (get_strub_attr_from_type (TREE_TYPE (rhs)))
+ return true;
+ }
+
+ return false;
+}
+
+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);
+
+ /* 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;
+ /* 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
+ safely undergo at-calls strubbing. Internal mode will still be used in
+ functions that request it explicitly with attribute strub(2), or when the
+ function body requires strubbing and at-calls strubbing is not viable. */
+ const bool strub_flag_at_calls = flag_strub == 1;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo internal strubbing. At-calls mode will still be used in
+ functions that requiest it explicitly with attribute strub() or strub(1),
+ or when the function body requires strubbing and internal strubbing is not
+ viable. */
+ const bool strub_flag_internal = flag_strub == 2;
+ /* On top of default, also enable strub implicitly for functions that can
+ safely undergo strubbing in either mode. When both modes are viable,
+ at-calls is preferred. */
+ const bool strub_flag_either = flag_strub == 3;
+ /* Besides the default behavior, enable strub implicitly for all viable
+ functions. */
+ const bool strub_flag_viable = flag_strub > 0;
+
+ /* The consider_* variables should be true if selecting the corresponding
+ strub modes would be consistent with requests from attributes and command
+ line flags. Attributes associated with functions pretty much mandate a
+ selection, and should report an error if not satisfied; strub_flag_auto
+ implicitly enables some viable strub mode if that's required by references
+ to variables marked for strub; strub_flag_viable enables strub if viable
+ (even when favoring one mode, body-requested strub can still be satisfied
+ by either mode), and falls back to callable, silently unless variables
+ require strubbing. */
+
+ const bool consider_at_calls
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_AT_CALLS
+ : true));
+ const bool consider_internal
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_INTERNAL
+ : true));
+
+ const bool consider_callable
+ = (!strub_flag_disabled
+ && (strub_attr
+ ? req_mode == STRUB_CALLABLE
+ : strub_flag_viable));
+
+ /* This is a shorthand for either strub-enabled mode. */
+ const bool consider_strub
+ = (consider_at_calls || consider_internal);
+
+ /* We can cope with always_inline functions even with noipa and noclone,
+ because we just leave them alone. */
+ const bool is_always_inline
+ = (lookup_attribute ("always_inline",
+ DECL_ATTRIBUTES (node->decl)));
+
+ /* Strubbing in general, and each specific strub mode, may have its own set of
+ requirements. We require noipa for strubbing, either because of cloning
+ required for internal strub, or because of caller enumeration required for
+ at-calls strub. We don't consider the at-calls mode eligible if it's not
+ even considered, it has no further requirements. Internal mode requires
+ cloning and the absence of certain features in the body and, like at-calls,
+ it's not eligible if it's not even under consideration.
+
+ ??? Do we need target hooks for further constraints? E.g., x86's
+ "interrupt" attribute breaks internal strubbing because the wrapped clone
+ carries the attribute and thus isn't callable; in this case, we could use a
+ target hook to adjust the clone instead. */
+ const bool strub_eligible
+ = (consider_strub
+ && (is_always_inline
+ || !lookup_attribute ("noipa",
+ DECL_ATTRIBUTES (node->decl))));
+ const bool at_calls_eligible
+ = (consider_at_calls && strub_eligible
+ && can_strub_at_calls_p (node));
+ const bool internal_eligible
+ = (consider_internal && strub_eligible
+ && (is_always_inline
+ || (!lookup_attribute ("noclone", DECL_ATTRIBUTES (node->decl))
+ && can_strub_internally_p (node))));
+
+ /* In addition to the strict eligibility requirements, some additional
+ constraints are placed on implicit selection of certain modes. These do
+ not prevent the selection of a mode if explicitly specified as part of a
+ function interface (the strub attribute), but they may prevent modes from
+ being selected by the command line or by function bodies. The only actual
+ constraint is on at-calls mode: since we change the function's exposed
+ signature, we won't do it implicitly if the function can possibly be used
+ in ways that do not expect the signature change, e.g., if the function is
+ available to or interposable by other units, if its address is taken,
+ etc. */
+ const bool at_calls_viable
+ = (at_calls_eligible
+ && (strub_attr
+ || (node->has_gimple_body_p ()
+ && node->get_availability () > AVAIL_INTERPOSABLE
+ && ((!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->binds_to_current_def_p ()
+ && node->can_be_local_p ()))
+ && node->only_called_directly_p ())));
+ const bool internal_viable
+ = (internal_eligible);
+
+ /* Shorthand. */
+ const bool strub_viable
+ = (at_calls_viable || internal_viable);
+
+ /* We wish to analyze the body, to look for implicit requests for strub, both
+ to implicitly enable it when the body calls for it, and to report errors if
+ the body calls for it but neither mode is viable (even if that follows from
+ non-eligibility because of the explicit specification of some non-strubbing
+ mode). We can refrain from scanning the body only in rare circumstances:
+ when strub is enabled by a function attribute (scanning might be redundant
+ in telling us to also enable it), and when we are enabling strub implicitly
+ but there are non-viable modes: we want to know whether strubbing is
+ required, to fallback to another mode, even if we're only enabling a
+ certain mode, or, when either mode would do, to report an error if neither
+ happens to be viable. */
+ const bool analyze_body
+ = (strub_attr
+ ? !consider_strub
+ : (strub_flag_auto
+ || (strub_flag_viable && (!at_calls_viable && !internal_viable))
+ || (strub_flag_either && !strub_viable)));
+
+ /* Cases in which strubbing is enabled or disabled by strub_flag_auto.
+ Unsatisfiable requests ought to be reported. */
+ const bool strub_required
+ = ((strub_attr && consider_strub)
+ || (analyze_body && strub_from_body_p (node)));
+
+ /* Besides the required cases, we want to abide by the requests to enabling on
+ an if-viable basis. */
+ const bool strub_enable
+ = (strub_required
+ || (strub_flag_at_calls && at_calls_viable)
+ || (strub_flag_internal && internal_viable)
+ || (strub_flag_either && strub_viable));
+
+ /* And now we're finally ready to select a mode that abides by the viability
+ and eligibility constraints, and that satisfies the strubbing requirements
+ and requests, subject to the constraints. If both modes are viable and
+ strub is to be enabled, pick STRUB_AT_CALLS unless STRUB_INTERNAL was named
+ as preferred. */
+ 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
+ : (strub_enable && at_calls_viable)
+ ? STRUB_AT_CALLS
+ : consider_callable
+ ? STRUB_CALLABLE
+ : STRUB_DISABLED);
+
+ switch (mode)
+ {
+ case STRUB_CALLABLE:
+ if (is_always_inline)
+ break;
+ /* Fall through. */
+
+ case STRUB_DISABLED:
+ if (strub_enable && !strub_attr)
+ {
+ gcc_checking_assert (analyze_body);
+ error_at (DECL_SOURCE_LOCATION (node->decl),
+ "%qD requires strub, but no viable strub mode was found",
+ node->decl);
+ break;
+ }
+ /* Fall through. */
+
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ /* Differences from an mode requested through a function attribute are
+ reported in set_strub_mode_to. */
+ break;
+
+ case STRUB_WRAPPED:
+ case STRUB_WRAPPER:
+ default:
+ gcc_unreachable ();
+ }
+
+ return mode;
+}
+
+static void
+set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+ enum strub_mode req_mode = get_strub_mode_from_attr (attr);
+
+ if (attr)
+ {
+ /* Check for and report incompatible mode changes. */
+ if (mode != req_mode
+ && !(req_mode == STRUB_INTERNAL
+ && (mode == STRUB_WRAPPED
+ || mode == STRUB_WRAPPER))
+ && !((req_mode == STRUB_INTERNAL
+ || req_mode == STRUB_AT_CALLS
+ || req_mode == STRUB_CALLABLE)
+ && 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));
+ if (node->alias)
+ {
+ cgraph_node *target = node->ultimate_alias_target ();
+ error_at (DECL_SOURCE_LOCATION (target->decl),
+ "the incompatible selection was determined"
+ " by ultimate alias target %qD",
+ target->decl);
+ }
+
+ /* Report any incompatibilities with explicitly-requested strub. */
+ switch (req_mode)
+ {
+ case STRUB_AT_CALLS:
+ can_strub_at_calls_p (node, true);
+ break;
+
+ case STRUB_INTERNAL:
+ can_strub_internally_p (node, true);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Drop any incompatible strub attributes leading the decl attribute
+ chain. Return if we find one with the mode we need. */
+ for (;;)
+ {
+ if (mode == req_mode)
+ return;
+
+ if (DECL_ATTRIBUTES (node->decl) != attr)
+ break;
+
+ DECL_ATTRIBUTES (node->decl) = TREE_CHAIN (attr);
+ attr = get_strub_attr_from_decl (node->decl);
+ if (!attr)
+ break;
+
+ req_mode = get_strub_mode_from_attr (attr);
+ }
+ }
+ else if (mode == req_mode)
+ return;
+
+ DECL_ATTRIBUTES (node->decl) = tree_cons (get_identifier ("strub"),
+ get_strub_mode_attr_value (mode),
+ DECL_ATTRIBUTES (node->decl));
+}
+
+static enum strub_mode
+set_strub_mode (cgraph_node *node)
+{
+ tree attr = get_strub_attr_from_decl (node->decl);
+
+ enum strub_mode mode = (node->alias
+ ? get_strub_mode (node->ultimate_alias_target ())
+ : compute_strub_mode (node, attr));
+
+ set_strub_mode_to (node, mode);
+
+ return mode;
+}
+
+#if 0
+/* Non-strub functions shouldn't be called from strub functions,
+ except through callable ones. */
+
+static bool
+strub_callable_from_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode caller_mode = get_strub_mode (caller);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return true;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return false;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ return true;
+}
+#endif
+
+/* We wish to avoid inlining WRAPPED functions back into their
+ WRAPPERs. More generally, we wish to avoid inlining
+ strubbed functions into non-strubbed ones. */
+
+bool
+strub_inlinable_p (cgraph_node *callee, cgraph_node *caller)
+{
+ strub_mode callee_mode = get_strub_mode (callee);
+
+ switch (callee_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ break;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ return true;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ strub_mode caller_mode = get_strub_mode (caller);
+
+ switch (caller_mode)
+ {
+ case STRUB_WRAPPED:
+ case STRUB_AT_CALLS:
+ case STRUB_INTERNAL:
+ case STRUB_INLINABLE:
+ return true;
+
+ case STRUB_WRAPPER:
+ case STRUB_DISABLED:
+ case STRUB_CALLABLE:
+ break;
+
+ default:
+ gcc_unreachable ();
+ }
+
+ return false;
+}
+
+namespace {
+
+const pass_data pass_data_ipa_strub_mode = {
+ SIMPLE_IPA_PASS,
+ "strubm",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ 0, // properties_finish
+};
+
+class pass_ipa_strub_mode : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub_mode (gcc::context *ctxt)
+ : simple_ipa_opt_pass (pass_data_ipa_strub_mode, ctxt)
+ {}
+ opt_pass *clone () { return new pass_ipa_strub_mode (m_ctxt); }
+ virtual bool gate (function *) { return flag_strub; }
+ virtual unsigned int execute (function *);
+};
+
+const pass_data pass_data_ipa_strub = {
+ SIMPLE_IPA_PASS,
+ "strub",
+ OPTGROUP_NONE,
+ TV_NONE,
+ PROP_cfg, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // properties_start
+ TODO_update_ssa
+ | TODO_cleanup_cfg
+ | TODO_rebuild_cgraph_edges
+ | TODO_verify_il, // properties_finish
+};
+
+class pass_ipa_strub : public simple_ipa_opt_pass
+{
+public:
+ pass_ipa_strub (gcc::context *ctxt)
+ : 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 unsigned int execute (function *);
+
+#define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST) \
+ static tree get_ ## NAME () { \
+ tree decl = builtin_decl_explicit (CODE); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ decl = add_builtin_function \
+ ("__builtin_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ NULL, NULL); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ set_strub_mode_to (cgraph_node::get_create (decl), \
+ STRUB_CALLABLE); \
+ } \
+ return decl; \
+ }
+
+ DEF_NM_BUILTIN (stack_address,
+ BUILT_IN_STACK_ADDRESS,
+ (ptr_type_node, NULL))
+
+#undef DEF_NM_BUILTIN
+
+#define DEF_SS_BUILTIN(NAME, FNSPEC, CODE, FNTYPELIST) \
+ static tree get_ ## NAME () { \
+ tree decl = builtin_decl_explicit (CODE); \
+ if (!decl) \
+ { \
+ tree type = build_function_type_list FNTYPELIST; \
+ tree attrs = NULL; \
+ if (FNSPEC) \
+ attrs = tree_cons (get_identifier ("fn spec"), \
+ build_tree_list \
+ (NULL_TREE, \
+ build_string (strlen (FNSPEC), \
+ (FNSPEC))), \
+ attrs); \
+ decl = add_builtin_function_ext_scope \
+ ("__builtin___strub_" #NAME, \
+ type, CODE, BUILT_IN_NORMAL, \
+ "__strub_" #NAME, attrs); \
+ TREE_NOTHROW (decl) = true; \
+ set_builtin_decl ((CODE), decl, true); \
+ set_strub_mode_to (cgraph_node::get_create (decl), \
+ STRUB_CALLABLE); \
+ } \
+ return decl; \
+ }
+
+ DEF_SS_BUILTIN (enter, ". Ot",
+ BUILT_IN___STRUB_ENTER,
+ (void_type_node, get_pptr (), NULL))
+ DEF_SS_BUILTIN (update, ". Wt",
+ BUILT_IN___STRUB_UPDATE,
+ (void_type_node, get_pptr (), NULL))
+ DEF_SS_BUILTIN (leave, ". w ",
+ BUILT_IN___STRUB_LEAVE,
+ (void_type_node, get_pptr (), NULL))
+
+#undef DEF_SS_BUILTIN
+
+#define DEF_IDENT(NAME) \
+ static inline tree get_ ## NAME () { \
+ static tree identifier = NULL_TREE; \
+ if (!identifier) \
+ identifier = get_identifier (".strub." #NAME); \
+ return identifier; \
+ }
+
+ DEF_IDENT (watermark_ptr)
+ DEF_IDENT (va_list_ptr)
+ DEF_IDENT (apply_args_ptr)
+
+#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 gimple_seq
+ call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
+ gimple_seq seq = NULL)
+ {
+ tree uwm = get_update ();
+ gcall *update = gimple_build_call (uwm, 1, wmptr);
+ gimple_seq_add_stmt (&seq, update);
+ if (node)
+#if !IMPLICIT_CGRAPH_EDGES
+ node->create_edge (cgraph_node::get_create (uwm), update, count, false);
+#else
+ (void)count;
+#endif
+ return seq;
+ }
+
+};
+
+} // anon namespace
+
+#if 0
+static bool
+may_throw_p (gcall *stmt)
+{
+ return flag_exceptions && !gimple_call_nothrow_p (stmt);
+}
+
+static bool
+strub_this_call_p (gcall *stmt)
+{
+ if (gimple_call_internal_p (stmt))
+ return false;
+
+ /* If there's no outgoing path in which to do the scrubbing, don't
+ bother. */
+ if (gimple_call_noreturn_p (stmt) && !may_throw_p (stmt))
+ return false;
+
+ /* ??? Maybe non-mandatory tail calls should be disabled for
+ scrubbing. Or maybe it won't matter, as long as both tail-caller
+ and callee are scrubbing-capable. */
+ if (gimple_call_must_tail_p (stmt) || gimple_call_tail_p (stmt))
+ return false;
+
+ if (gimple_alloca_call_p (stmt))
+ return true;
+
+ tree fndecl = gimple_call_fndecl (stmt);
+ if (!fndecl)
+ return true;
+
+ if (DECL_BUILT_IN_CLASS (fndecl) != BUILT_IN_NORMAL)
+ return true;
+
+ enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl);
+
+ switch (fcode)
+ {
+ CASE_BUILT_IN_ALLOCA:
+ return true;
+
+ case BUILT_IN_NONE:
+ return true;
+
+ case BUILT_IN___STRUB_ENTER:
+ case BUILT_IN___STRUB_UPDATE:
+ case BUILT_IN___STRUB_LEAVE:
+ return false;
+
+ case BUILT_IN_CLASSIFY_TYPE:
+ case BUILT_IN_CONSTANT_P:
+ return false;
+
+ case BUILT_IN_RETURN_ADDRESS:
+ case BUILT_IN_FRAME_ADDRESS:
+ case BUILT_IN_STACK_ADDRESS:
+ case BUILT_IN_AGGREGATE_INCOMING_ADDRESS:
+ return false;
+
+ case BUILT_IN_STACK_SAVE:
+ case BUILT_IN_STACK_RESTORE:
+ case BUILT_IN_ASAN_ALLOCAS_UNPOISON:
+ return false;
+
+ case BUILT_IN_SETJMP_SETUP:
+ case BUILT_IN_SETJMP_RECEIVER:
+ case BUILT_IN_LONGJMP:
+ case BUILT_IN_NONLOCAL_GOTO:
+ case BUILT_IN_UPDATE_SETJMP_BUF:
+ case BUILT_IN_TRAP:
+ case BUILT_IN_UNREACHABLE:
+ return false;
+
+ case BUILT_IN_UNWIND_INIT:
+ case BUILT_IN_DWARF_CFA:
+#ifdef DWARF2_UNWIND_INFO
+ case BUILT_IN_DWARF_SP_COLUMN:
+ case BUILT_IN_INIT_DWARF_REG_SIZES:
+#endif
+ case BUILT_IN_FROB_RETURN_ADDR:
+ case BUILT_IN_EXTRACT_RETURN_ADDR:
+ case BUILT_IN_EH_RETURN:
+ case BUILT_IN_EH_RETURN_DATA_REGNO:
+ case BUILT_IN_EXTEND_POINTER:
+ case BUILT_IN_EH_POINTER:
+ case BUILT_IN_EH_FILTER:
+ case BUILT_IN_EH_COPY_VALUES:
+ return false;
+
+ case BUILT_IN_VA_START:
+ case BUILT_IN_VA_END:
+ case BUILT_IN_VA_COPY:
+ case BUILT_IN_EXPECT:
+ case BUILT_IN_EXPECT_WITH_PROBABILITY:
+ case BUILT_IN_ASSUME_ALIGNED:
+ case BUILT_IN_PREFETCH:
+ return false;
+
+ case BUILT_IN_ATOMIC_ALWAYS_LOCK_FREE:
+ case BUILT_IN_OBJECT_SIZE:
+ case BUILT_IN_THREAD_POINTER:
+ case BUILT_IN_SET_THREAD_POINTER:
+ return false;
+
+ default:
+ return true;
+ }
+}
+#endif
+
+typedef hash_set<tree> indirect_parms_t;
+
+static tree
+maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
+{
+ if (DECL_P (op))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (op))
+ {
+ tree ret = gimple_fold_indirect_ref (op);
+ if (!ret)
+ ret = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (op)),
+ op,
+ build_int_cst (TREE_TYPE (op), 0));
+ return ret;
+ }
+ }
+ else if (TREE_CODE (op) == ADDR_EXPR
+ && DECL_P (TREE_OPERAND (op, 0)))
+ {
+ *rec = 0;
+ if (indirect_parms.contains (TREE_OPERAND (op, 0)))
+ return TREE_OPERAND (op, 0);
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_make_indirect (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ indirect_parms_t &indirect_parms = *(indirect_parms_t *)wi->info;
+
+ if (!*op || TYPE_P (*op))
+ {
+ *rec = 0;
+ return NULL_TREE;
+ }
+
+ if (tree repl = maybe_make_indirect (indirect_parms, *op, rec))
+ {
+ *op = repl;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
+{
+ walk_stmt_info *wi = (walk_stmt_info *)arg;
+ gimple_stmt_iterator &gsi = *(gimple_stmt_iterator *)wi->info;
+
+ *rec = 0;
+
+ if (!*op || TREE_CODE (*op) != ADDR_EXPR)
+ return NULL_TREE;
+
+ if (!is_gimple_val (*op))
+ {
+ tree ret = force_gimple_operand_gsi (&gsi, *op, true,
+ NULL_TREE, true, GSI_SAME_STMT);
+ gcc_assert (ret != *op);
+ *op = ret;
+ wi->changed = true;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+build_ref_type_for (tree parm, bool nonaliased = true)
+{
+ gcc_checking_assert (TREE_CODE (parm) == PARM_DECL);
+
+ tree ref_type = build_reference_type (TREE_TYPE (parm));
+
+ if (!nonaliased)
+ return ref_type;
+
+ /* Each PARM turned indirect still points to the distinct memory area at the
+ wrapper, and the reference in unchanging, so we might qualify it, but...
+ const is not really important, since we're only using default defs for the
+ reference parm anyway, and not introducing any defs, and restrict seems to
+ cause trouble. E.g., libgnat/s-concat3.adb:str_concat_3 has memmoves that,
+ if it's wrapped, the memmoves are deleted in dse1. Using a distinct alias
+ set seems to not run afoul of this problem, and it hopefully enables the
+ compiler to tell the pointers do point to objects that are not otherwise
+ aliased. */
+#if 1
+ tree qref_type = build_variant_type_copy (ref_type);
+
+ TYPE_ALIAS_SET (qref_type) = new_alias_set ();
+ record_alias_subset (TYPE_ALIAS_SET (qref_type), get_alias_set (ref_type));
+
+ return qref_type;
+#else
+ tree qref_type = build_qualified_type (ref_type,
+ TYPE_QUAL_RESTRICT
+ | TYPE_QUAL_CONST);
+
+ return qref_type;
+#endif
+}
+
+/* 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)
+{
+#if IMPLICIT_CGRAPH_EDGES
+ return;
+#endif
+
+ cgraph_node *node = cgraph_node::get_create (current_function_decl);
+
+ for (gimple_stmt_iterator gsi = gsi_start (seq);
+ !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (!is_a <gcall *> (stmt))
+ 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);
+ }
+}
+
+static void
+gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
+{
+ gimple *stmt = gsi_stmt (gsi);
+
+ gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
+ 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;
+ bool nothrow_p = (must_not_throw_p
+ || (call && gimple_call_nothrow_p (call))
+ || (eh_lp <= 0
+ && (TREE_NOTHROW (cfun->decl)
+ || !flag_exceptions)));
+
+ if (noreturn_p && nothrow_p)
+ return;
+
+ /* Don't expect an EH edge if we're not to throw, or if we're not in an EH
+ region yet. */
+ bool no_eh_edge_p = (nothrow_p || !eh_lp);
+ bool must_end_bb = stmt_ends_bb_p (stmt);
+
+ edge eft = NULL, eeh = NULL;
+ if (must_end_bb && !(noreturn_p && no_eh_edge_p))
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+
+ edge e;
+ edge_iterator ei;
+ FOR_EACH_EDGE (e, ei, gsi_bb (gsi)->succs)
+ {
+ if ((e->flags & EDGE_EH))
+ {
+ gcc_checking_assert (!eeh);
+ eeh = e;
+#if !CHECKING_P
+ if (eft || noreturn_p)
+ break;
+#endif
+ }
+ if ((e->flags & EDGE_FALLTHRU))
+ {
+ gcc_checking_assert (!eft);
+ eft = e;
+#if !CHECKING_P
+ if (eeh || no_eh_edge_p)
+ break;
+#endif
+ }
+ }
+
+ gcc_checking_assert (!(eft && (eft->flags & EDGE_FALLTHRU))
+ == noreturn_p);
+ gcc_checking_assert (!(eeh && (eeh->flags & EDGE_EH))
+ == no_eh_edge_p);
+ gcc_checking_assert (eft != eeh);
+ }
+
+ if (!noreturn_p)
+ {
+ gimple_seq nseq = nothrow_p ? seq : gimple_seq_copy (seq);
+
+ if (must_end_bb)
+ {
+ gcc_checking_assert (gsi_one_before_end_p (gsi));
+ add_call_edges_for_seq (nseq, eft->count ());
+ gsi_insert_seq_on_edge_immediate (eft, nseq);
+ }
+ else
+ {
+ add_call_edges_for_seq (nseq, gsi_bb (gsi)->count);
+ gsi_insert_seq_after (&gsi, nseq, GSI_SAME_STMT);
+ }
+ }
+
+ if (nothrow_p)
+ return;
+
+ if (eh_lp)
+ {
+ add_call_edges_for_seq (seq, eeh->count ());
+ gsi_insert_seq_on_edge_immediate (eeh, seq);
+ return;
+ }
+
+ /* A throwing call may appear within a basic block in a function that doesn't
+ have any EH regions. We're going to add a cleanup if so, therefore the
+ block will have to be split. */
+ basic_block bb = gsi_bb (gsi);
+ if (!gsi_one_before_end_p (gsi))
+ split_block (bb, stmt);
+
+ /* Create a new block for the EH cleanup. */
+ basic_block bb_eh_cleanup = create_empty_bb (bb);
+ if (dom_info_available_p (CDI_DOMINATORS))
+ set_immediate_dominator (CDI_DOMINATORS, bb_eh_cleanup, bb);
+ if (current_loops)
+ add_bb_to_loop (bb_eh_cleanup, current_loops->tree_root);
+
+ /* Make the new block an EH cleanup for the call. */
+ eh_region new_r = gen_eh_region_cleanup (NULL);
+ eh_landing_pad lp = gen_eh_landing_pad (new_r);
+ tree label = gimple_block_label (bb_eh_cleanup);
+ lp->post_landing_pad = label;
+ EH_LANDING_PAD_NR (label) = lp->index;
+ add_stmt_to_eh_lp (stmt, lp->index);
+
+ /* Add the cleanup code to the EH cleanup block. */
+ gsi = gsi_after_labels (bb_eh_cleanup);
+ gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT);
+
+ /* And then propagate the exception further. */
+ gresx *resx = gimple_build_resx (new_r->index);
+ gsi_insert_before (&gsi, resx, GSI_SAME_STMT);
+
+ /* Finally, wire the EH cleanup block into the CFG. */
+ make_eh_edges (stmt);
+ add_call_edges_for_seq (seq, single_pred_edge (bb_eh_cleanup)->count ());
+}
+
+/* Copy the attribute list at *ATTRS, minus any NAME attributes, leaving
+ shareable trailing nodes alone. */
+
+static inline void
+remove_named_attribute_unsharing (const char *name, tree *attrs)
+{
+ while (tree found = lookup_attribute (name, *attrs))
+ {
+ /* Copy nodes up to the next NAME attribute. */
+ while (*attrs != found)
+ {
+ *attrs = tree_cons (TREE_PURPOSE (*attrs),
+ TREE_VALUE (*attrs),
+ TREE_CHAIN (*attrs));
+ attrs = &TREE_CHAIN (*attrs);
+ }
+ /* Then drop it. */
+ gcc_checking_assert (*attrs == found);
+ *attrs = TREE_CHAIN (*attrs);
+ }
+}
+
+unsigned int
+pass_ipa_strub_mode::execute (function *)
+{
+ cgraph_node *onode;
+
+ /* If no strub flag was given in the command line,
+ set the actual default. */
+ if (flag_strub == -2)
+ flag_strub = -1;
+
+ bool any_strub = false;
+
+ for (int aliases = 0; aliases <= 1; aliases++)
+ FOR_EACH_FUNCTION (onode)
+ {
+ if (!onode->alias != !aliases)
+ continue;
+
+ enum strub_mode mode = set_strub_mode (onode);
+
+ if (mode == STRUB_AT_CALLS || mode == STRUB_INTERNAL)
+ any_strub = true;
+ }
+
+ if (!any_strub)
+ flag_strub = 0;
+
+ return 0;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub_mode (gcc::context *ctxt)
+{
+ return new pass_ipa_strub_mode (ctxt);
+}
+
+unsigned int
+pass_ipa_strub::execute (function *)
+{
+ cgraph_node *onode;
+
+ FOR_EACH_FUNCTION (onode)
+ {
+ enum strub_mode mode = get_strub_mode (onode);
+
+ if (mode == STRUB_AT_CALLS)
+ {
+ 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));
+
+ 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. */
+ 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;
+
+ tree wmptr = build_decl (DECL_SOURCE_LOCATION (onode->decl),
+ PARM_DECL,
+ get_watermark_ptr (),
+ get_qpptr ());
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_ARG_TYPE (wmptr) = get_qpptr ();
+ DECL_CONTEXT (wmptr) = onode->decl;
+ TREE_USED (wmptr) = 1;
+ DECL_CHAIN (wmptr) = *pargs;
+ *pargs = wmptr;
+
+ if (onode->alias)
+ continue;
+
+ 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);
+
+ /* 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 (nargs + 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 0
+ tree lswm = create_tmp_var (ptr_type_node, ".L.strub.watermark");
+ gassign *load = gimple_build_assign (lswm, swm);
+ gimple_seq_add_stmt (&seq, load);
+#else
+ tree lswm = unshare_expr (swmp);
+#endif
+
+ gcall *sleave = gimple_build_call (get_leave (), 1, lswm);
+ 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);
+
+ pop_cfun ();
+ }
+
+ if (!onode->has_gimple_body_p ())
+ continue;
+
+ cgraph_node *nnode = onode;
+ push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca)
+ {
+ 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);
+
+ if (!is_gimple_call (stmt))
+ continue;
+
+ gcall *call = as_a <gcall *> (stmt);
+
+ if (gimple_alloca_call_p (call))
+ {
+ /* Capture stack growth. */
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)
+ ->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ }
+ }
+
+ pop_cfun ();
+
+#if 0
+ compute_fn_summary (onode, true);
+#endif
+ continue;
+ }
+
+ if (mode != STRUB_INTERNAL
+ || !onode->has_gimple_body_p ())
+ 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 (!DECL_STRUCT_FUNCTION (onode->decl))
+ {
+ inform (DECL_SOURCE_LOCATION (onode->decl),
+ "not splitting struct-less function %qD for stack scrubbing",
+ onode->decl);
+ continue;
+ }
+
+ if (!onode->lowered)
+ {
+ inform (DECL_SOURCE_LOCATION (onode->decl),
+ "not splitting non-lowered function %qD for stack scrubbing",
+ 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 stack scrubbing",
+ onode->decl);
+ continue;
+ }
+
+ bool is_stdarg = calls_builtin_va_start_p (onode);;
+ bool apply_args = calls_builtin_apply_args_p (onode);
+
+ vec<ipa_adjusted_param, va_gc> *nparms = NULL;
+ unsigned j = 0;
+ {
+ // The following loop copied from ipa-split.c:split_function.
+ for (tree parm = DECL_ARGUMENTS (onode->decl);
+ parm; parm = DECL_CHAIN (parm), j++)
+ {
+ ipa_adjusted_param adj = {};
+ adj.op = IPA_PARAM_OP_COPY;
+ adj.base_index = j;
+ adj.prev_clone_index = j;
+ vec_safe_push (nparms, adj);
+ }
+
+ if (apply_args)
+ {
+ ipa_adjusted_param aaadj = {};
+ aaadj.op = IPA_PARAM_OP_NEW;
+ aaadj.type = get_qpptr ();
+ vec_safe_push (nparms, aaadj);
+ }
+
+ if (is_stdarg)
+ {
+ ipa_adjusted_param vladj = {};
+ vladj.op = IPA_PARAM_OP_NEW;
+ vladj.type = get_qpvalst ();
+ vec_safe_push (nparms, vladj);
+ }
+
+ ipa_adjusted_param wmadj = {};
+ wmadj.op = IPA_PARAM_OP_NEW;
+ wmadj.type = get_qpptr ();
+ vec_safe_push (nparms, wmadj);
+ }
+ ipa_param_adjustments adj (nparms, -1, false);
+
+ cgraph_node *nnode = onode->create_version_clone_with_body
+ (auto_vec<cgraph_edge *> (0),
+ NULL, &adj, NULL, NULL, "strub", NULL);
+
+ if (!nnode)
+ {
+ error_at (DECL_SOURCE_LOCATION (onode->decl),
+ "failed to split %qD for stack scrubbing",
+ onode->decl);
+ continue;
+ }
+
+ onode->split_part = true;
+ if (onode->calls_comdat_local)
+ nnode->add_to_same_comdat_group (onode);
+
+ gcc_checking_assert (!DECL_STRUCT_FUNCTION (nnode->decl)->stdarg);
+
+ set_strub_mode_to (onode, STRUB_WRAPPER);
+ set_strub_mode_to (nnode, STRUB_WRAPPED);
+
+ /* Decide which of the wrapped function's parms we want to turn into
+ references to the argument passed to the wrapper. In general, we want to
+ copy small arguments, and avoid copying large ones. Variable-sized array
+ lengths given by other arguments, as in 20020210-1.c, would lead to
+ problems if passed by value, after resetting the original function and
+ dropping the length computation; passing them by reference works.
+ DECL_BY_REFERENCE is *not* a substitute for this: it involves copying
+ anyway, but performed at the caller. */
+ indirect_parms_t indirect_nparms (3, false);
+ unsigned adjust_ftype = 0;
+ for (tree parm = DECL_ARGUMENTS (onode->decl),
+ nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (TREE_TYPE (nnode->decl));
+ parm;
+ parm = DECL_CHAIN (parm),
+ nparm = DECL_CHAIN (nparm),
+ nparmt = nparmt ? TREE_CHAIN (nparmt) : NULL_TREE)
+ if (!(0 /* DECL_BY_REFERENCE (narg) */
+ || is_gimple_reg_type (TREE_TYPE (nparm))
+ || VECTOR_TYPE_P (TREE_TYPE (nparm))
+ || TREE_CODE (TREE_TYPE (nparm)) == COMPLEX_TYPE
+ || (tree_fits_uhwi_p (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ && (tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
+ <= 4 * UNITS_PER_WORD))))
+ {
+ indirect_nparms.add (nparm);
+
+ /* ??? Is there any case in which it is not safe to suggest the parms
+ turned indirect don't alias anything else? They are distinct,
+ unaliased memory in the wrapper, and the wrapped can't possibly
+ take pointers into them because none of the pointers passed to the
+ wrapper can alias other incoming parameters passed by value, even
+ if with transparent reference, and the wrapper doesn't take any
+ extra parms that could point into wrapper's parms. So we can
+ probably drop the TREE_ADDRESSABLE and keep the true. */
+ tree ref_type = build_ref_type_for (nparm,
+ true
+ || !TREE_ADDRESSABLE (parm));
+
+ DECL_ARG_TYPE (nparm) = TREE_TYPE (nparm) = ref_type;
+ relayout_decl (nparm);
+ TREE_ADDRESSABLE (nparm) = 0;
+ DECL_BY_REFERENCE (nparm) = 0;
+ DECL_NOT_GIMPLE_REG_P (nparm) = 0;
+ /* ??? This avoids mismatches in debug info bind stmts in
+ e.g. a-chahan . */
+ DECL_ABSTRACT_ORIGIN (nparm) = NULL;
+
+ if (nparmt)
+ adjust_ftype++;
+ }
+
+ /* Also adjust the wrapped function type, if needed. */
+ if (adjust_ftype)
+ {
+ tree nftype = TREE_TYPE (nnode->decl);
+
+ /* We always add at least one argument at the end of the signature, when
+ cloning the function, so we don't expect to need to duplicate the
+ type here. */
+ gcc_checking_assert (TYPE_ARG_TYPES (nftype)
+ != TYPE_ARG_TYPES (TREE_TYPE (onode->decl)));
+
+ /* Check that fnspec still works for the modified function signature,
+ and drop it otherwise. */
+ bool drop_fnspec = false;
+ tree fnspec = lookup_attribute ("fn spec", TYPE_ATTRIBUTES (nftype));
+ attr_fnspec spec = fnspec ? attr_fnspec (fnspec) : attr_fnspec ("");
+
+ unsigned retcopy;
+ if (!(fnspec && spec.returns_arg (&retcopy)))
+ retcopy = (unsigned) -1;
+
+ unsigned i = 0;
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl),
+ nparmt = TYPE_ARG_TYPES (nftype);
+ adjust_ftype > 0;
+ nparm = DECL_CHAIN (nparm), nparmt = TREE_CHAIN (nparmt), i++)
+ if (indirect_nparms.contains (nparm))
+ {
+ TREE_VALUE (nparmt) = TREE_TYPE (nparm);
+ adjust_ftype--;
+
+ if (fnspec && !drop_fnspec)
+ {
+ if (i == retcopy)
+ drop_fnspec = true;
+ else if (spec.arg_specified_p (i))
+ {
+ /* Properties that apply to pointers only must not be
+ present, because we don't make pointers further
+ indirect. */
+ gcc_checking_assert
+ (!spec.arg_max_access_size_given_by_arg_p (i, NULL));
+ gcc_checking_assert (!spec.arg_copied_to_arg_p (i, NULL));
+
+ /* Any claim of direct access only is invalidated by
+ adding an indirection level. */
+ if (spec.arg_direct_p (i))
+ drop_fnspec = true;
+
+ /* If there's a claim the argument is not read from, the
+ added indirection invalidates it: if the argument is
+ used at all, then the pointer will necessarily be
+ read. */
+ if (!spec.arg_maybe_read_p (i)
+ && spec.arg_used_p (i))
+ drop_fnspec = true;
+ }
+ }
+ }
+
+ /* ??? Maybe we could adjust it instead. */
+ if (drop_fnspec)
+ remove_named_attribute_unsharing ("fn spec",
+ &TYPE_ATTRIBUTES (nftype));
+
+ TREE_TYPE (nnode->decl) = nftype;
+ }
+
+ {
+ tree decl = onode->decl;
+ cgraph_node *target = nnode;
+
+ { // copied from create_wrapper
+
+ /* Preserve DECL_RESULT so we get right by reference flag. */
+ tree decl_result = DECL_RESULT (decl);
+
+ /* Remove the function's body but keep arguments to be reused
+ for thunk. */
+ onode->release_body (true);
+ onode->reset ();
+
+ DECL_UNINLINABLE (decl) = false;
+ DECL_RESULT (decl) = decl_result;
+ DECL_INITIAL (decl) = NULL;
+ allocate_struct_function (decl, false);
+ set_cfun (NULL);
+
+ /* Turn alias into thunk and expand it into GIMPLE representation. */
+ onode->definition = true;
+
+ thunk_info::get_create (onode);
+ onode->thunk = true;
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (target, NULL, onode->count);
+#endif
+ onode->callees->can_throw_external = !TREE_NOTHROW (target->decl);
+
+ tree arguments = DECL_ARGUMENTS (decl);
+
+ while (arguments)
+ {
+ TREE_ADDRESSABLE (arguments) = false;
+ arguments = TREE_CHAIN (arguments);
+ }
+
+ {
+ tree alias = onode->callees->callee->decl;
+ tree thunk_fndecl = decl;
+ tree a;
+
+ int nxargs = 1 + is_stdarg + apply_args;
+
+ { // Simplified from expand_thunk.
+ tree restype;
+ basic_block bb, then_bb, else_bb, return_bb;
+ gimple_stmt_iterator bsi;
+ int nargs = 0;
+ tree arg;
+ int i;
+ tree resdecl;
+ tree restmp = NULL;
+
+ gcall *call;
+ greturn *ret;
+ bool alias_is_noreturn = TREE_THIS_VOLATILE (alias);
+
+ a = DECL_ARGUMENTS (thunk_fndecl);
+
+ current_function_decl = thunk_fndecl;
+
+ /* Ensure thunks are emitted in their correct sections. */
+ resolve_unique_section (thunk_fndecl, 0,
+ flag_function_sections);
+
+ bitmap_obstack_initialize (NULL);
+
+ /* Build the return declaration for the function. */
+ restype = TREE_TYPE (TREE_TYPE (thunk_fndecl));
+ if (DECL_RESULT (thunk_fndecl) == NULL_TREE)
+ {
+ resdecl = build_decl (input_location, RESULT_DECL, 0, restype);
+ DECL_ARTIFICIAL (resdecl) = 1;
+ DECL_IGNORED_P (resdecl) = 1;
+ DECL_CONTEXT (resdecl) = thunk_fndecl;
+ DECL_RESULT (thunk_fndecl) = resdecl;
+ }
+ else
+ resdecl = DECL_RESULT (thunk_fndecl);
+
+ profile_count cfg_count = onode->count;
+ if (!cfg_count.initialized_p ())
+ cfg_count = profile_count::from_gcov_type (BB_FREQ_MAX).guessed_local ();
+
+ bb = then_bb = else_bb = return_bb
+ = init_lowered_empty_function (thunk_fndecl, true, cfg_count);
+
+ bsi = gsi_start_bb (bb);
+
+ /* Build call to the function being thunked. */
+ if (!VOID_TYPE_P (restype)
+ && (!alias_is_noreturn
+ || TREE_ADDRESSABLE (restype)
+ || TREE_CODE (TYPE_SIZE_UNIT (restype)) != INTEGER_CST))
+ {
+ if (DECL_BY_REFERENCE (resdecl))
+ {
+ restmp = gimple_fold_indirect_ref (resdecl);
+ if (!restmp)
+ restmp = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (resdecl)),
+ resdecl,
+ build_int_cst (TREE_TYPE (resdecl), 0));
+ }
+ else if (!is_gimple_reg_type (restype))
+ {
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl)))
+ {
+ restmp = resdecl;
+
+ if (VAR_P (restmp))
+ {
+ add_local_decl (cfun, restmp);
+ BLOCK_VARS (DECL_INITIAL (current_function_decl))
+ = restmp;
+ }
+ }
+ else
+ restmp = create_tmp_var (restype, "retval");
+ }
+ else
+ restmp = create_tmp_reg (restype, "retval");
+ }
+
+ for (arg = a; arg; arg = DECL_CHAIN (arg))
+ nargs++;
+ auto_vec<tree> vargs (nargs + nxargs);
+ i = 0;
+ arg = a;
+
+ if (nargs)
+ for (tree nparm = DECL_ARGUMENTS (nnode->decl);
+ i < nargs;
+ i++, arg = DECL_CHAIN (arg), nparm = DECL_CHAIN (nparm))
+ {
+ tree save_arg = arg;
+ tree tmp = arg;
+
+ /* Arrange to pass indirectly the parms, if we decided to do
+ so, and revert its type in the wrapper. */
+ if (indirect_nparms.contains (nparm))
+ {
+ tree ref_type = TREE_TYPE (nparm);
+ TREE_ADDRESSABLE (arg) = true;
+ tree addr = build1 (ADDR_EXPR, ref_type, arg);
+ tmp = arg = addr;
+ }
+ else
+ DECL_NOT_GIMPLE_REG_P (arg) = 0;
+
+ /* Convert the argument back to the type used by the calling
+ conventions, e.g. a non-prototyped float type is passed as
+ double, as in 930603-1.c, and needs to be converted back to
+ double to be passed on unchanged to the wrapped
+ function. */
+ if (TREE_TYPE (nparm) != DECL_ARG_TYPE (nparm))
+ arg = fold_convert (DECL_ARG_TYPE (nparm), arg);
+
+ if (!is_gimple_val (arg))
+ {
+ tmp = create_tmp_reg (TYPE_MAIN_VARIANT
+ (TREE_TYPE (arg)), "arg");
+ gimple *stmt = gimple_build_assign (tmp, arg);
+ gsi_insert_after (&bsi, stmt, GSI_NEW_STMT);
+ }
+ vargs.quick_push (tmp);
+ arg = save_arg;
+ }
+ /* These strub arguments are adjusted later. */
+ if (apply_args)
+ vargs.quick_push (null_pointer_node);
+ if (is_stdarg)
+ vargs.quick_push (null_pointer_node);
+ vargs.quick_push (null_pointer_node);
+ call = gimple_build_call_vec (build_fold_addr_expr_loc (0, alias),
+ vargs);
+ onode->callees->call_stmt = call;
+ // gimple_call_set_from_thunk (call, true);
+ if (DECL_STATIC_CHAIN (alias))
+ {
+ tree p = DECL_STRUCT_FUNCTION (alias)->static_chain_decl;
+ tree type = TREE_TYPE (p);
+ tree decl = build_decl (DECL_SOURCE_LOCATION (thunk_fndecl),
+ PARM_DECL, create_tmp_var_name ("CHAIN"),
+ type);
+ DECL_ARTIFICIAL (decl) = 1;
+ DECL_IGNORED_P (decl) = 1;
+ TREE_USED (decl) = 1;
+ DECL_CONTEXT (decl) = thunk_fndecl;
+ DECL_ARG_TYPE (decl) = type;
+ TREE_READONLY (decl) = 1;
+
+ struct function *sf = DECL_STRUCT_FUNCTION (thunk_fndecl);
+ sf->static_chain_decl = decl;
+
+ gimple_call_set_chain (call, decl);
+ }
+
+ /* Return slot optimization is always possible and in fact required to
+ return values with DECL_BY_REFERENCE. */
+ if (aggregate_value_p (resdecl, TREE_TYPE (thunk_fndecl))
+ && (!is_gimple_reg_type (TREE_TYPE (resdecl))
+ || DECL_BY_REFERENCE (resdecl)))
+ gimple_call_set_return_slot_opt (call, true);
+
+ if (restmp)
+ {
+ gimple_call_set_lhs (call, restmp);
+ gcc_assert (useless_type_conversion_p (TREE_TYPE (restmp),
+ TREE_TYPE (TREE_TYPE (alias))));
+ }
+ gsi_insert_after (&bsi, call, GSI_NEW_STMT);
+ if (!alias_is_noreturn)
+ {
+ /* Build return value. */
+ if (!DECL_BY_REFERENCE (resdecl))
+ ret = gimple_build_return (restmp);
+ else
+ ret = gimple_build_return (resdecl);
+
+ gsi_insert_after (&bsi, ret, GSI_NEW_STMT);
+ }
+ else
+ {
+ remove_edge (single_succ_edge (bb));
+ }
+
+ cfun->gimple_df->in_ssa_p = true;
+ update_max_bb_count ();
+ profile_status_for_fn (cfun)
+ = cfg_count.initialized_p () && cfg_count.ipa_p ()
+ ? PROFILE_READ : PROFILE_GUESSED;
+ /* FIXME: C++ FE should stop setting TREE_ASM_WRITTEN on thunks. */
+ // TREE_ASM_WRITTEN (thunk_fndecl) = false;
+ delete_unreachable_blocks ();
+ update_ssa (TODO_update_ssa);
+ checking_verify_flow_info ();
+ free_dominance_info (CDI_DOMINATORS);
+
+ /* Since we want to emit the thunk, we explicitly mark its name as
+ referenced. */
+ onode->thunk = false;
+ onode->lowered = true;
+ bitmap_obstack_release (NULL);
+ }
+ current_function_decl = NULL;
+ set_cfun (NULL);
+ }
+
+ thunk_info::remove (onode);
+
+ // some more of create_wrapper at the end of the next block.
+ }
+ }
+
+ {
+ tree aaptr = NULL_TREE;
+ tree vaptr = NULL_TREE;
+ tree wmptr = NULL_TREE;
+ for (tree arg = DECL_ARGUMENTS (nnode->decl); arg; arg = DECL_CHAIN (arg))
+ {
+ aaptr = vaptr;
+ vaptr = wmptr;
+ wmptr = arg;
+ }
+
+ if (!apply_args)
+ aaptr = NULL_TREE;
+ /* The trailing args are [apply_args], [va_list_ptr], and
+ watermark. If we don't have a va_list_ptr, the penultimate
+ argument is apply_args.
+ */
+ else if (!is_stdarg)
+ aaptr = vaptr;
+
+ if (!is_stdarg)
+ vaptr = NULL_TREE;
+
+ DECL_NAME (wmptr) = get_watermark_ptr ();
+ DECL_ARTIFICIAL (wmptr) = 1;
+ DECL_IGNORED_P (wmptr) = 1;
+ TREE_USED (wmptr) = 1;
+
+ if (is_stdarg)
+ {
+ DECL_NAME (vaptr) = get_va_list_ptr ();
+ DECL_ARTIFICIAL (vaptr) = 1;
+ DECL_IGNORED_P (vaptr) = 1;
+ TREE_USED (vaptr) = 1;
+ }
+
+ if (apply_args)
+ {
+ DECL_NAME (aaptr) = get_apply_args_ptr ();
+ DECL_ARTIFICIAL (aaptr) = 1;
+ DECL_IGNORED_P (aaptr) = 1;
+ TREE_USED (aaptr) = 1;
+ }
+
+ push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
+
+ {
+ edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun));
+ gimple_seq seq = call_update_watermark (wmptr, nnode, e->src->count);
+ gsi_insert_seq_on_edge_immediate (e, seq);
+ }
+
+ bool any_indirect = !indirect_nparms.is_empty ();
+
+ if (any_indirect)
+ {
+ 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);
+
+ 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
+ || is_stdarg || apply_args)
+ for (cgraph_edge *e = nnode->callees, *enext; e; e = enext)
+ {
+ gcall *call = e->call_stmt;
+ gimple_stmt_iterator gsi = gsi_for_stmt (call);
+ tree fndecl = e->callee->decl;
+
+ enext = e->next_callee;
+
+ if (gimple_alloca_call_p (call))
+ {
+ gimple_seq seq = call_update_watermark (wmptr, NULL,
+ gsi_bb (gsi)->count);
+ gsi_insert_finally_seq_after_call (gsi, seq);
+ }
+ else if (fndecl && is_stdarg
+ && fndecl_built_in_p (fndecl, BUILT_IN_VA_START))
+ {
+ if (builtin_decl_explicit (BUILT_IN_VA_START) != fndecl)
+ sorry_at (gimple_location (call),
+ "nonstandard stdarg conventions");
+ tree bvacopy = builtin_decl_explicit (BUILT_IN_VA_COPY);
+ gimple_call_set_fndecl (call, bvacopy);
+ tree arg = vaptr;
+ /* The va_copy source must be dereferenced, unless it's an array
+ type, that would have decayed to a pointer. */
+ if (TREE_CODE (TREE_TYPE (TREE_TYPE (vaptr))) != ARRAY_TYPE)
+ {
+ arg = gimple_fold_indirect_ref (vaptr);
+ if (!arg)
+ arg = build2 (MEM_REF,
+ TREE_TYPE (TREE_TYPE (vaptr)),
+ vaptr,
+ build_int_cst (TREE_TYPE (vaptr), 0));
+ }
+ gimple_call_set_arg (call, 1, arg);
+ update_stmt (call);
+ e->redirect_callee (cgraph_node::get_create (bvacopy));
+ }
+ else if (fndecl && apply_args
+ && fndecl_built_in_p (fndecl, BUILT_IN_APPLY_ARGS))
+ {
+ tree lhs = gimple_call_lhs (call);
+ gassign *assign = gimple_build_assign (lhs, aaptr);
+ gsi_replace (&gsi, assign, true);
+ cgraph_edge::remove (e);
+ }
+ }
+
+ { // a little more copied from create_wrapper
+
+ /* Inline summary set-up. */
+ nnode->analyze ();
+ // inline_analyze_function (nnode);
+ }
+
+ pop_cfun ();
+ }
+
+ {
+ 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));
+
+ 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
+ onode->create_edge (cgraph_node::get_create (enter),
+ stptr, gsi_bb (gsi)->count, false);
+#endif
+
+ int nargs = gimple_call_num_args (wrcall);
+
+ gimple_seq seq = NULL;
+
+ if (apply_args)
+ {
+ tree aalst = create_tmp_var (ptr_type_node, ".strub.appargs");
+ tree bappargs = builtin_decl_explicit (BUILT_IN_APPLY_ARGS);
+ gcall *appargs = gimple_build_call (bappargs, 0);
+ gimple_call_set_lhs (appargs, aalst);
+ gsi_insert_before (&gsi, appargs, GSI_SAME_STMT);
+ gimple_call_set_arg (wrcall, nargs - 2 - is_stdarg, aalst);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bappargs),
+ appargs, gsi_bb (gsi)->count, false);
+#endif
+ }
+
+ if (is_stdarg)
+ {
+ tree valst = create_tmp_var (va_list_type_node, ".strub.va_list");
+ TREE_ADDRESSABLE (valst) = true;
+ tree vaptr = build1 (ADDR_EXPR,
+ build_pointer_type (va_list_type_node),
+ valst);
+ gimple_call_set_arg (wrcall, nargs - 2, unshare_expr (vaptr));
+
+ tree bvastart = builtin_decl_explicit (BUILT_IN_VA_START);
+ gcall *vastart = gimple_build_call (bvastart, 2,
+ unshare_expr (vaptr),
+ integer_zero_node);
+ gsi_insert_before (&gsi, vastart, GSI_SAME_STMT);
+#if !IMPLICIT_CGRAPH_EDGES
+ onode->create_edge (cgraph_node::get_create (bvastart),
+ vastart, gsi_bb (gsi)->count, false);
+#endif
+
+ tree bvaend = builtin_decl_explicit (BUILT_IN_VA_END);
+ gcall *vaend = gimple_build_call (bvaend, 1, unshare_expr (vaptr));
+ gimple_seq_add_stmt (&seq, vaend);
+ }
+
+ gimple_call_set_arg (wrcall, nargs - 1, unshare_expr (swmp));
+ // gimple_call_set_tail (wrcall, false);
+ update_stmt (wrcall);
+
+ {
+#if 0
+ tree lswm = create_tmp_var (ptr_type_node, ".L.strub.watermark");
+ gassign *load = gimple_build_assign (lswm, swm);
+ gimple_seq_add_stmt (&seq, load);
+#else
+ tree lswm = unshare_expr (swmp);
+#endif
+
+ gcall *sleave = gimple_build_call (get_leave (), 1, lswm);
+ 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);
+
+ /* For nnode, we don't rebuild edges because we wish to retain
+ any redirections copied to it from earlier passes, so we add
+ call graph edges explicitly there, but for onode, we create a
+ fresh function, so we may as well just issue the calls and
+ then rebuild all cgraph edges. */
+ // cgraph_edge::rebuild_edges ();
+ onode->analyze ();
+ // inline_analyze_function (onode);
+
+ pop_cfun ();
+ }
+
+#if 0
+ compute_fn_summary (onode, true);
+ compute_fn_summary (nnode, true);
+#endif
+ }
+
+ /* ??? Check that strub functions don't call non-strub functions, and that
+ always_inline strub functions are only called by strub functions. */
+
+ return 0;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub (gcc::context *ctxt)
+{
+ return new pass_ipa_strub (ctxt);
+}
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
new file mode 100644
index 00000000000..cadbca5002a
--- /dev/null
+++ b/gcc/ipa-strub.h
@@ -0,0 +1,25 @@
+/* strub (stack scrubbing) infrastructure.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+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);
diff --git a/gcc/passes.def b/gcc/passes.def
index f5d88a61b0e..677c000e80b 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -53,6 +53,7 @@ along with GCC; see the file COPYING3. If not see
INSERT_PASSES_AFTER (all_small_ipa_passes)
NEXT_PASS (pass_ipa_free_lang_data);
NEXT_PASS (pass_ipa_function_and_variable_visibility);
+ NEXT_PASS (pass_ipa_strub_mode);
NEXT_PASS (pass_build_ssa_passes);
PUSH_INSERT_PASSES_WITHIN (pass_build_ssa_passes)
NEXT_PASS (pass_fixup_cfg);
@@ -111,6 +112,7 @@ along with GCC; see the file COPYING3. If not see
POP_INSERT_PASSES ()
NEXT_PASS (pass_ipa_remove_symbols);
+ NEXT_PASS (pass_ipa_strub);
NEXT_PASS (pass_ipa_oacc);
PUSH_INSERT_PASSES_WITHIN (pass_ipa_oacc)
NEXT_PASS (pass_ipa_pta);
diff --git a/gcc/testsuite/g++.dg/wrappers/strub1.C b/gcc/testsuite/g++.dg/wrappers/strub1.C
new file mode 100644
index 00000000000..a474a929649
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub1.C
@@ -0,0 +1,18 @@
+// { dg-do run }
+
+// Check that we don't get extra copies.
+
+struct T {
+ T &self;
+ void check () const { if (&self != this) __builtin_abort (); }
+ T() : self (*this) { check (); }
+ T(const T& ck) : self (*this) { ck.check (); check (); }
+ ~T() { check (); }
+};
+
+T foo (T) { return T(); }
+T bar (T p) { return foo (p); }
+
+int main () {
+ bar (T{});
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub2.C b/gcc/testsuite/g++.dg/wrappers/strub2.C
new file mode 100644
index 00000000000..25a62166448
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub2.C
@@ -0,0 +1,22 @@
+// { dg-do run }
+
+// This doesn't really test anything yet. We should mark the
+// variables as requiring strubbing, and somehow check that the
+// wrapped functions take the parameter by reference.
+
+struct T {
+ char d[32 * sizeof(void*)];
+};
+
+T foo (T q) { asm ("" : : "m"(q)); return q; }
+T bar (T p) { return foo (p); }
+
+T tmp;
+T tmp2;
+
+int main () {
+ __builtin_memset (&tmp, 0x55, sizeof (tmp));
+ tmp2 = bar (tmp);
+ if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
+ __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub3.C b/gcc/testsuite/g++.dg/wrappers/strub3.C
new file mode 100644
index 00000000000..e1b51cd0399
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub3.C
@@ -0,0 +1,22 @@
+// { dg-do run }
+
+// This doesn't really test anything yet. We should mark the
+// variables as requiring strubbing, and somehow check that the
+// wrapped functions take the parameter by reference.
+
+struct T {
+ char d[32 * sizeof(void*)];
+};
+
+static T foo (T q) { asm ("" : : "m"(q)); return q; }
+static T bar (T p) { return foo (p); }
+
+T tmp;
+T tmp2;
+
+int main () {
+ __builtin_memset (&tmp, 0x55, sizeof (tmp));
+ tmp2 = bar (tmp);
+ if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
+ __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub4.C b/gcc/testsuite/g++.dg/wrappers/strub4.C
new file mode 100644
index 00000000000..d021fca88e4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/wrappers/strub4.C
@@ -0,0 +1,18 @@
+// { dg-do run }
+
+namespace
+{
+ class foo
+ {
+ public:
+ foo();
+ };
+
+ foo::foo() {}
+
+ foo bar;
+}
+
+int main()
+{
+}
diff --git a/gcc/tree-cfg.c b/gcc/tree-cfg.c
index 38269a27b79..b5e0af8b3a1 100644
--- a/gcc/tree-cfg.c
+++ b/gcc/tree-cfg.c
@@ -5617,6 +5617,7 @@ gimple_verify_flow_info (void)
{
gimple *stmt = gsi_stmt (gsi);
+ /* Do NOT disregard debug stmts after found_ctrl_stmt. */
if (found_ctrl_stmt)
{
error ("control flow in the middle of basic block %d",
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index aa9757a2fe9..0210f84bc0c 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -494,8 +494,9 @@ extern gimple_opt_pass *make_pass_adjust_alignment (gcc::context *ctxt);
/* IPA Passes */
extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt);
-extern simple_ipa_opt_pass
- *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_function_and_variable_visibility (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub_mode (gcc::context *ctxt);
+extern simple_ipa_opt_pass *make_pass_ipa_strub (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_tree_profile (gcc::context *ctxt);
extern simple_ipa_opt_pass *make_pass_ipa_auto_profile (gcc::context *ctxt);
diff --git a/libgcc/Makefile.in b/libgcc/Makefile.in
index 2c8be561eb5..6b7b12493f5 100644
--- a/libgcc/Makefile.in
+++ b/libgcc/Makefile.in
@@ -428,7 +428,7 @@ ifneq ($(enable_shared),yes)
iterator = $(patsubst %,$(srcdir)/static-object.mk,$(iter-items))
endif
-LIB2ADD += enable-execute-stack.c
+LIB2ADD += enable-execute-stack.c $(srcdir)/strub.c
# While emutls.c has nothing to do with EH, it is in LIB2ADDEH*
# instead of LIB2ADD because that's the way to be sure on some targets
diff --git a/libgcc/libgcc2.h b/libgcc/libgcc2.h
index 1819ff3ac3d..857091e65c8 100644
--- a/libgcc/libgcc2.h
+++ b/libgcc/libgcc2.h
@@ -532,6 +532,10 @@ extern int __parityDI2 (UDWtype);
extern void __enable_execute_stack (void *);
+extern void __strub_enter (void **);
+extern void __strub_update (void**);
+extern void __strub_leave (void **);
+
#ifndef HIDE_EXPORTS
#pragma GCC visibility pop
#endif
diff --git a/libgcc/strub.c b/libgcc/strub.c
new file mode 100644
index 00000000000..fd6e27556e4
--- /dev/null
+++ b/libgcc/strub.c
@@ -0,0 +1,112 @@
+/* Stack scrubbing infrastructure
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ Contributed by Alexandre Oliva <oliva@adacore.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+Under Section 7 of GPL version 3, you are granted additional
+permissions described in the GCC Runtime Library Exception, version
+3.1, as published by the Free Software Foundation.
+
+You should have received a copy of the GNU General Public License and
+a copy of the GCC Runtime Library Exception along with this program;
+see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
+<http://www.gnu.org/licenses/>. */
+
+#include "tconfig.h"
+#include "tsystem.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "libgcc_tm.h"
+#include "libgcc2.h"
+
+#ifndef STACK_GROWS_DOWNWARD
+# define TOPS >
+#else
+# define TOPS <
+#endif
+
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ (3)))
+
+/* Enter a stack scrubbing context, initializing the watermark to the caller's
+ stack address. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_enter (void **watermark)
+{
+ *watermark = __builtin_frame_address (0);
+}
+
+/* Update the watermark within a stack scrubbing context with the current stack
+ pointer. */
+void ATTRIBUTE_STRUB_CALLABLE
+__strub_update (void **watermark)
+{
+ void *sp = __builtin_frame_address (0);
+
+ if (sp TOPS *watermark)
+ *watermark = sp;
+}
+
+#ifndef TARGET_STRUB_USE_DYNAMIC_ARRAY
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+#endif
+
+#ifndef TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+# ifdef TARGET_STRUB_MAY_USE_MEMSET
+# define TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY 1
+# else
+# define TARGET_STRUB_MAY_USE_MEMSET 1
+# endif
+#endif
+
+/* Leave a stack scrubbing context, restoring and updating SAVED, and
+ clearing the stack between top and watermark. */
+void ATTRIBUTE_STRUB_CALLABLE
+#if ! TARGET_STRUB_MAY_USE_MEMSET
+__attribute__ ((__optimize__ ("-fno-tree-loop-distribute-patterns")))
+#endif
+__strub_leave (void **mark)
+{
+ void *sp = __builtin_stack_address ();
+
+ void **base, **end;
+#ifndef STACK_GROWS_DOWNWARD
+ base = sp;
+ end = *mark;
+#else
+ base = *mark;
+ end = sp;
+#endif
+
+ ptrdiff_t len = end - base;
+ if (len <= 0)
+ return;
+
+#if ! TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY
+ /* Allocate a dynamically-sized array covering the desired range, so that we
+ can safely call memset on it. */
+ void *ptr[len];
+ base = &ptr[0];
+ end = &ptr[len];
+#else
+ void **ptr = end;
+#endif /* TARGET_STRUB_DONT_USE_DYNAMIC_ARRAY */
+
+ /* ldist turns this into a memset. Without the dynamic array above, that call
+ is likely unsafe: possibly tail-called, and likely scribbling over its own
+ stack frame. */
+ while (base < end)
+ *base++ = 0;
+
+ asm ("" : : "m" (ptr));
+}
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2021-08-28 6:30 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-08-28 6:30 [gcc(refs/users/aoliva/heads/strub)] strub with function and variable attributes Alexandre Oliva
-- strict thread matches above, loose matches on Subject: below --
2021-08-28 4:13 Alexandre Oliva
2021-08-20 4:21 Alexandre Oliva
2021-08-20 4:20 Alexandre Oliva
2021-08-19 2:58 Alexandre Oliva
2021-08-04 8:55 Alexandre Oliva
2021-07-28 9:35 Alexandre Oliva
2021-07-28 7:06 Alexandre Oliva
2021-07-28 6:33 Alexandre Oliva
2021-07-26 13:31 Alexandre Oliva
2021-07-25 15:58 Alexandre Oliva
2021-07-25 1:43 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).