public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] strub: machine-independent stack scrubbing
       [not found] <ormtqpsbuc.fsf@lxoliva.fsfla.org>
@ 2021-09-09  7:11 ` Alexandre Oliva
  2022-07-29  6:16   ` [PATCH v2 00/10] Introduce " Alexandre Oliva
  0 siblings, 1 reply; 59+ messages in thread
From: Alexandre Oliva @ 2021-09-09  7:11 UTC (permalink / raw)
  To: gcc-patches
  Cc: Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Richard Biener, Jim Wilson

(moving from gcc@ to gcc-patches@)

On Jul 14, 2021, Alexandre Oliva <oliva@adacore.com> wrote:

> I've been working on an implementation of stack scrubbing

https://gcc.gnu.org/pipermail/gcc/2021-July/236780.html


Here's the patch that implements it.


introduce stack scrub (strub) feature

This patch adds the strub attribute for function and variable types,
command-line options, passes and adjustments to implement it,
documentation, and tests.


Regstrapped on x86_64-linux-gnu.  Also successfully bootstrapped with
-fstrub=all, along with other patches in branch users/aoliva/heads/strub:
https://gcc.gnu.org/pipermail/gcc-patches/2021-July/575040.html
https://gcc.gnu.org/pipermail/gcc-patches/2021-July/575043.html
https://gcc.gnu.org/pipermail/gcc-patches/2021-July/575044.html

Is this ok to install?


for  gcc/ChangeLog

	* Makefile.in (OBJS): Add ipa-strub.o.
	* builtins.def (BUILT_IN_STACK_ADDRESS): New.
	(BUILT_IN___STRUB_ENTER): New.
	(BUILT_IN___STRUB_UPDATE): New.
	(BUILT_IN___STRUB_LEAVE): New.
	* builtins.c: Include ipa-strub.h.
	(STACK_STOPS, STACK_UNSIGNED): Define.
	(expand_builtin_stack_address): New.
	(expand_builtin_strub_enter): New.
	(expand_builtin_strub_update): New.
	(expand_builtin_strub_leave): New.
	(expand_bulitin): Call them.
	* common.opt (fstrub=*): New options.
	* doc/extend.texi (strub): New type attribute.
	(__builtin_stack_address): New function.
	(Stack Scrubbing): New section.
	* doc/invoke.texi (-fstrub=*): New options.
	(-fdump-ipa-*): New passes.
	* ipa-inline.c: Include ipa-strub.h.
	(can_inline_edge_p): Test strub_inlinable_to_p.
	* ipa-split.c: Include ipa-strub.h.
	(execute_split_functions): Test strub_splittable_p.
	* ipa-strub.c, ipa-strub.h: New.
	* passes.def: Add strub_mode and strub passes.
	* tree-cfg.c (gimple_verify_flow_info): Note on debug stmts.
	* tree-pass.h (make_pass_ipa_strub_mode): Declare.
	(make_pass_ipa_strub): Declare.
	(make_pass_ipa_function_and_variable_visibility): Fix
	formatting.
	* tree-ssa-ccp.c (optimize_stack_restore): Keep restores
	before strub leave.
	* multiple_target.c (pass_target_clone::gate): Test seen_error.
	* attribs.c: Include ipa-strub.h.
	(decl_attributes): Support applying attributes to function
	type, rather than pointer type, at handler's request.
	(comp_type_attributes): Combine strub_comptypes and target
	comp_type results.

for  gcc/c-family/ChangeLog

	* c-attribs.c: Include ipa-strub.h.
	(handle_strub_attribute): New.
	(c_common_attribute_table): Add strub.

for  gcc/ada/ChangeLog

	* doc/gnat_rm.rst: Add...
	* doc/gnat_rm/security_hardening_features.rst: New.
	* doc/gnat_rm/about_this_guide.rst: Link to new chapter.
	* libgnat/a-except.adb: Make Rcheck_CE_* strub-callable.
	* libgnat/a-except.ads (Raise_Exception): Likewise.
	(Raise_Exception_Always): Likewise.
	* libgnat/s-arit128.ads (Multiply_With_Ovflo_Check128):
	Likewise.
	* libgnat/s-arit64.ads (Multiply_With_Ovflo_Check64):
	Likewise.
	* libgnat/s-secsta.ads (SS_Allocate, SS_Mark, SS_Release):
	Likewise.
	* gcc-interface/trans.c: Include ipa-strub.h.
	(gigi): Make internal decls for targets of compiler-generated
	calls strub-callable too.
	* gcc-interface/utils.c: Include ipa-strub.h.
	(handle_strub_attribute): New.
	(gnat_internal_attribute_table): Add strub.

for  gcc/testsuite/ChangeLog

	* c-c++-common/strub-O0.c: New.
	* c-c++-common/strub-O1.c: New.
	* c-c++-common/strub-O2.c: New.
	* c-c++-common/strub-O2fni.c: New.
	* c-c++-common/strub-O3.c: New.
	* c-c++-common/strub-O3fni.c: New.
	* c-c++-common/strub-Og.c: New.
	* c-c++-common/strub-Os.c: New.
	* c-c++-common/strub-all1.c: New.
	* c-c++-common/strub-all2.c: New.
	* c-c++-common/strub-apply1.c: New.
	* c-c++-common/strub-apply2.c: New.
	* c-c++-common/strub-apply3.c: New.
	* c-c++-common/strub-apply4.c: New.
	* c-c++-common/strub-at-calls1.c: New.
	* c-c++-common/strub-at-calls2.c: New.
	* c-c++-common/strub-defer-O1.c: New.
	* c-c++-common/strub-defer-O2.c: New.
	* c-c++-common/strub-defer-O3.c: New.
	* c-c++-common/strub-defer-Os.c: New.
	* c-c++-common/strub-internal1.c: New.
	* c-c++-common/strub-internal2.c: New.
	* c-c++-common/strub-parms1.c: New.
	* c-c++-common/strub-parms2.c: New.
	* c-c++-common/strub-parms3.c: New.
	* c-c++-common/strub-relaxed1.c: New.
	* c-c++-common/strub-relaxed2.c: New.
	* c-c++-common/strub-short-O0-exc.c: New.
	* c-c++-common/strub-short-O0.c: New.
	* c-c++-common/strub-short-O1.c: New.
	* c-c++-common/strub-short-O2.c: New.
	* c-c++-common/strub-short-O3.c: New.
	* c-c++-common/strub-short-Os.c: New.
	* c-c++-common/strub-strict1.c: New.
	* c-c++-common/strub-strict2.c: New.
	* c-c++-common/strub-tail-O1.c: New.
	* c-c++-common/strub-tail-O2.c: New.
	* c-c++-common/torture/strub-callable1.c: New.
	* c-c++-common/torture/strub-callable2.c: New.
	* c-c++-common/torture/strub-const1.c: New.
	* c-c++-common/torture/strub-const2.c: New.
	* c-c++-common/torture/strub-const3.c: New.
	* c-c++-common/torture/strub-const4.c: New.
	* c-c++-common/torture/strub-data1.c: New.
	* c-c++-common/torture/strub-data2.c: New.
	* c-c++-common/torture/strub-data3.c: New.
	* c-c++-common/torture/strub-data4.c: New.
	* c-c++-common/torture/strub-data5.c: New.
	* c-c++-common/torture/strub-indcall1.c: New.
	* c-c++-common/torture/strub-indcall2.c: New.
	* c-c++-common/torture/strub-indcall3.c: New.
	* c-c++-common/torture/strub-inlinable1.c: New.
	* c-c++-common/torture/strub-inlinable2.c: New.
	* c-c++-common/torture/strub-ptrfn1.c: New.
	* c-c++-common/torture/strub-ptrfn2.c: New.
	* c-c++-common/torture/strub-ptrfn3.c: New.
	* c-c++-common/torture/strub-ptrfn4.c: New.
	* c-c++-common/torture/strub-pure1.c: New.
	* c-c++-common/torture/strub-pure2.c: New.
	* c-c++-common/torture/strub-pure3.c: New.
	* c-c++-common/torture/strub-pure4.c: New.
	* c-c++-common/torture/strub-run1.c: New.
	* c-c++-common/torture/strub-run2.c: New.
	* c-c++-common/torture/strub-run3.c: New.
	* c-c++-common/torture/strub-run4.c: New.
	* c-c++-common/torture/strub-run4c.c: New.
	* c-c++-common/torture/strub-run4d.c: New.
	* c-c++-common/torture/strub-run4i.c: New.
	* g++.dg/strub-run1.C: New.
	* g++.dg/torture/strub-init1.C: New.
	* g++.dg/torture/strub-init2.C: New.
	* g++.dg/torture/strub-init3.C: New.
	* gnat.dg/strub_attr.adb, gnat.dg/strub_attr.ads: New.
	* gnat.dg/strub_ind.adb, gnat.dg/strub_ind.ads: New.

for  libgcc/ChangeLog

	* Makefile.in (LIB2ADD): Add strub.c.
	* libgcc2.h (__strub_enter, __strub_update, __strub_leave):
	Declare.
	* strub.c: New.
---
 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    |   88 +
 gcc/ada/gcc-interface/trans.c                      |    5 
 gcc/ada/gcc-interface/utils.c                      |   67 
 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/attribs.c                                      |   39 
 gcc/builtins.c                                     |  273 ++
 gcc/builtins.def                                   |    4 
 gcc/c-family/c-attribs.c                           |   68 
 gcc/common.opt                                     |   29 
 gcc/doc/extend.texi                                |  307 ++
 gcc/doc/invoke.texi                                |   60 
 gcc/ipa-inline.c                                   |    6 
 gcc/ipa-split.c                                    |    7 
 gcc/ipa-strub.c                                    | 3336 ++++++++++++++++++++
 gcc/ipa-strub.h                                    |   45 
 gcc/multiple_target.c                              |    2 
 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-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-relaxed1.c        |   18 
 gcc/testsuite/c-c++-common/strub-relaxed2.c        |   14 
 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-strict1.c         |   36 
 gcc/testsuite/c-c++-common/strub-strict2.c         |   25 
 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   |   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 
 .../c-c++-common/torture/strub-inlinable1.c        |   16 
 .../c-c++-common/torture/strub-inlinable2.c        |    7 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |   10 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |   55 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c  |   50 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c  |   43 
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |   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               |   37 
 gcc/testsuite/gnat.dg/strub_attr.ads               |   12 
 gcc/testsuite/gnat.dg/strub_ind.adb                |   44 
 gcc/testsuite/gnat.dg/strub_ind.ads                |   23 
 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 +
 106 files changed, 6588 insertions(+), 11 deletions(-)
 create mode 100644 gcc/ada/doc/gnat_rm/security_hardening_features.rst
 create mode 100644 gcc/ipa-strub.c
 create mode 100644 gcc/ipa-strub.h
 create mode 100644 gcc/testsuite/c-c++-common/strub-O0.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O2fni.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O3fni.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-Og.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-Os.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-all1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-all2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-apply1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-apply2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-apply3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-apply4.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-at-calls1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-at-calls2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-defer-O1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-defer-O2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-defer-O3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-defer-Os.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-internal1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-internal2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-parms1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-parms2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-parms3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-relaxed1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-relaxed2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O0-exc.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O0.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-Os.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-strict1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-strict2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-tail-O1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-tail-O2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-callable1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-callable2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data5.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-indcall1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-indcall2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-indcall3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4c.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4d.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4i.c
 create mode 100644 gcc/testsuite/g++.dg/strub-run1.C
 create mode 100644 gcc/testsuite/g++.dg/torture/strub-init1.C
 create mode 100644 gcc/testsuite/g++.dg/torture/strub-init2.C
 create mode 100644 gcc/testsuite/g++.dg/torture/strub-init3.C
 create mode 100644 gcc/testsuite/gnat.dg/strub_attr.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_attr.ads
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind.ads
 create mode 100644 libgcc/strub.c

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index f0c560fe45b77..b60bf8f3d3e64 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1472,6 +1472,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 97f7e4d457036..7743ef8b5f437 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 b48785eeed0e9..9defee818aca3 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 0000000000000..753452904128e
--- /dev/null
+++ b/gcc/ada/doc/gnat_rm/security_hardening_features.rst
@@ -0,0 +1,88 @@
+.. _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.
+
+.. code-block:: ada
+
+     procedure Foo;
+     pragma Machine_Attribute (Foo, "zero_call_used_regs", "used");
+     --  Before returning, Foo scrubs only call-clobbered registers
+     --  that it uses itself.
+
+     function Bar return Integer;
+     pragma Machine_Attribute (Bar, "zero_call_used_regs", "all");
+     --  Before returning, Bar scrubs all call-clobbered registers.
+
+
+For usage and more details on the command line option, and on the
+``zero_call_used_regs`` attribute, see :title:`Using the GNU Compiler
+Collection (GCC)`.
+
+
+.. Stack Scrubbing:
+
+Stack Scrubbing
+===============
+
+GNAT can generate code to zero-out stack frames used by subprograms.
+
+It can be activated with the :samp:`Machine_Attribute` pragma, on
+specific subprograms, variables, and types.
+
+.. code-block:: ada
+
+     function Foo returns Integer;
+     pragma Machine_Attribute (Foo, "strub");
+     --  Foo and its callers are modified so as to scrub the stack
+     --  space used by Foo after it returns.
+
+     procedure Bar;
+     pragma Machine_Attribute (Bar, "strub", "internal");
+     --  Bar is turned into a wrapper for its original body,
+     --  and they scrub the stack used by the original body.
+
+     Var : Integer;
+     pragma Machine_Attribute (Var, "strub");
+     --  Reading from Var in a subprogram enables stack scrubbing
+     --  of the stack space used by the subprogram.
+
+
+There are also *-fstrub* command line options to control default
+settings.  For usage and more details on the command line option, and
+on the ``strub`` attribute, see :title:`Using the GNU Compiler
+Collection (GCC)`.
+
+Note that Ada secondary stacks are not scrubbed.  The restriction
+``No_Secondary_Stack`` avoids their use, and thus their accidental
+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, or with an interface-compatible one.
+
+Even then, applying the pragma to anything other than subprograms and
+scalar variables is not currently supported, and may be silently
+ignored.  Specifically, it is not recommended to rely on any effects
+of this pragma on access types and objects to data variables and to
+subprograms.
diff --git a/gcc/ada/gcc-interface/trans.c b/gcc/ada/gcc-interface/trans.c
index 3df56aa0560e1..f1524a3fb906c 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 f85373e2f76ef..193772cc82970 100644
--- a/gcc/ada/gcc-interface/utils.c
+++ b/gcc/ada/gcc-interface/utils.c
@@ -39,6 +39,7 @@
 #include "varasm.h"
 #include "toplev.h"
 #include "opts.h"
+#include "ipa-strub.h"
 #include "output.h"
 #include "debug.h"
 #include "convert.h"
@@ -94,6 +95,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 +159,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 +6603,69 @@ 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
+      && POINTER_TYPE_P (*node)
+      && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*node)))
+    *node = TREE_TYPE (*node);
+
+  if (args && FUNC_OR_METHOD_TYPE_P (*node))
+    {
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
+	{
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
+
+	case 0:
+	  warning (OPT_Wattributes,
+		   "%qE attribute ignored because of argument %qE",
+		   name, TREE_VALUE (args));
+	  *no_add_attrs = true;
+	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
+	}
+
+      args = TREE_CHAIN (args);
+    }
+
+  if (args)
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
+
+  /* If we see a strub-enabling attribute, and we're at the default setting,
+     implicitly or explicitly, note that the attribute was seen, so that we can
+     reduce the compile-time overhead to nearly zero when the strub feature is
+     not used.  */
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
+
+  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 c332afad0f8a1..593392809d92d 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 2b27adb6ca1b8..b5a2e48f96aae 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 6213cfb569a24..5d3fff6d8e14f 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 c9141f5fe3e89..170c2bdf1de93 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 7d6b1b9a90eb0..63df82c8392e4 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/attribs.c b/gcc/attribs.c
index 0d22c20a35e16..07f81285d7a2d 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "attribs.h"
 #include "fold-const.h"
+#include "ipa-strub.h"
 #include "stor-layout.h"
 #include "langhooks.h"
 #include "plugin.h"
@@ -617,12 +618,11 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  flags &= ~(int) ATTR_FLAG_TYPE_IN_PLACE;
 	}
 
-      if (spec->function_type_required && TREE_CODE (*anode) != FUNCTION_TYPE
-	  && TREE_CODE (*anode) != METHOD_TYPE)
+      if (spec->function_type_required
+	  && !FUNC_OR_METHOD_TYPE_P (*anode))
 	{
 	  if (TREE_CODE (*anode) == POINTER_TYPE
-	      && (TREE_CODE (TREE_TYPE (*anode)) == FUNCTION_TYPE
-		  || TREE_CODE (TREE_TYPE (*anode)) == METHOD_TYPE))
+	      && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
 	    {
 	      /* OK, this is a bit convoluted.  We can't just make a copy
 		 of the pointer type and modify its TREE_TYPE, because if
@@ -715,7 +715,23 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  tree ret = (spec->handler) (cur_and_last_decl, name, args,
 				      flags|cxx11_flag, &no_add_attrs);
 
-	  *anode = cur_and_last_decl[0];
+	  if (*anode != cur_and_last_decl[0])
+	    {
+	      /* Even if !spec->function_type_required, allow the attribute
+		 handler to request the attribute to be applied to the function
+		 type, rather than to the function pointer type, by setting
+		 cur_and_last_decl[0] to the function type.  */
+	      if (!fn_ptr_tmp
+		  && POINTER_TYPE_P (*anode)
+		  && TREE_TYPE (*anode) == cur_and_last_decl[0]
+		  && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
+		{
+		  fn_ptr_tmp = TREE_TYPE (*anode);
+		  fn_ptr_quals = TYPE_QUALS (*anode);
+		  anode = &fn_ptr_tmp;
+		}
+	      *anode = cur_and_last_decl[0];
+	    }
 	  if (ret == error_mark_node)
 	    {
 	      warning (OPT_Wattributes, "%qE attribute ignored", name);
@@ -1353,9 +1369,20 @@ comp_type_attributes (const_tree type1, const_tree type2)
   if ((lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type1)) != NULL)
       ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) != NULL))
     return 0;
+  int strub_ret = strub_comptypes (CONST_CAST_TREE (type1),
+				   CONST_CAST_TREE (type2));
+  if (strub_ret == 0)
+    return strub_ret;
   /* As some type combinations - like default calling-convention - might
      be compatible, we have to call the target hook to get the final result.  */
-  return targetm.comp_type_attributes (type1, type2);
+  int target_ret = targetm.comp_type_attributes (type1, type2);
+  if (target_ret == 0)
+    return target_ret;
+  if (strub_ret == 2 || target_ret == 2)
+    return 2;
+  if (strub_ret == 1 && target_ret == 1)
+    return 1;
+  gcc_unreachable ();
 }
 
 /* PREDICATE acts as a function of type:
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 9954862776125..7ad91ea6f7a3e 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 45a09b4d42def..f83c1f5fcd7ed 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 d14e9c441b39b..0453ac563cc40 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "common/common-target.h"
 #include "langhooks.h"
 #include "tree-inline.h"
+#include "ipa-strub.h"
 #include "toplev.h"
 #include "tree-iterator.h"
 #include "opts.h"
@@ -69,6 +70,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 *);
@@ -308,6 +310,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 },
@@ -1294,6 +1298,70 @@ 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
+      && POINTER_TYPE_P (*node)
+      && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*node)))
+    *node = TREE_TYPE (*node);
+
+  if (args && FUNC_OR_METHOD_TYPE_P (*node))
+    {
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
+	{
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
+
+	case 0:
+	  warning (OPT_Wattributes,
+		   "%qE attribute ignored because of argument %qE",
+		   name, TREE_VALUE (args));
+	  *no_add_attrs = true;
+	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
+	}
+
+      args = TREE_CHAIN (args);
+    }
+
+  if (args)
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
+
+  /* If we see a strub-enabling attribute, and we're at the default setting,
+     implicitly or explicitly, note that the attribute was seen, so that we can
+     reduce the compile-time overhead to nearly zero when the strub feature is
+     not used.  */
+  if (enable && flag_strub < -2)
+    flag_strub += 2;
+
+  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 7d69ab5ef7c3d..ef018ebf73512 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2687,6 +2687,35 @@ fstrict-overflow
 Common
 Treat signed overflow as undefined.  Negated as -fwrapv -fwrapv-pointer.
 
+fstrub=disable
+Common RejectNegative Var(flag_strub, 0)
+Disable stack scrub entirely, disregarding strub attributes.
+
+fstrub=strict
+Common RejectNegative Var(flag_strub, -4)
+Enable stack scrub as per attributes, with strict call checking.
+
+; If any strub-enabling attribute is seen when the default or strict
+; initializer values are in effect, flag_strub is bumped up by 2.  The
+; scrub mode gate function will then bump these initializer values to
+; 0 if no strub-enabling attribute is seen.  This minimizes the strub
+; overhead.
+fstrub=relaxed
+Common RejectNegative Var(flag_strub, -3) Init(-3)
+Restore default strub mode: as per attributes, with relaxed checking.
+
+fstrub=all
+Common RejectNegative Var(flag_strub, 3)
+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.
+
 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 7fb22ed806323..d85e695d85cc0 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 internal interfaces.
 * Vector Extensions::   Using vector instructions through built-in functions.
 * Offsetof::            Special syntax for implementing @code{offsetof}.
 * __sync Builtins::     Legacy built-in functions for atomic memory access.
@@ -8725,6 +8726,263 @@ 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.  Being a type attribute, it attaches to types, even when
+specified in function and variable declarations.  When applied to
+function types, it takes an optional string argument.  When applied to a
+pointer-to-function type, if the optional argument is given, it gets
+propagated to the function type.
+
+@smallexample
+/* A strub variable.  */
+int __attribute__ ((strub)) var;
+/* A strub variable that happens to be a pointer.  */
+__attribute__ ((strub)) int *strub_ptr_to_int;
+/* A pointer type that may point to a strub variable.  */
+typedef int __attribute__ ((strub)) *ptr_to_strub_int_type;
+
+/* A declaration of a strub function.  */
+extern int __attribute__ ((strub)) foo (void);
+/* A pointer to that strub function.  */
+int __attribute__ ((strub ("at-calls"))) (*ptr_to_strub_fn)(void) = foo;
+@end smallexample
+
+A function associated with @code{at-calls} @code{strub} mode
+(@code{strub("at-calls")}, or just @code{strub}) undergoes interface
+changes.  Its callers are adjusted to match the changes, and to scrub
+(overwrite with zeros) the stack space used by the called function after
+it returns.  The interface change makes the function type incompatible
+with an unadorned but otherwise equivalent type, so @emph{every}
+declaration and every type that may be used to call the function must be
+associated with this strub mode.
+
+A function associated with @code{internal} @code{strub} mode
+(@code{strub("internal")}) retains an unmodified, type-compatible
+interface, but it may be turned into a wrapper that calls the wrapped
+body using a custom interface.  The wrapper then scrubs the stack space
+used by the wrapped body.  Though the wrapped body has its stack space
+scrubbed, the wrapper does not, so arguments and return values may
+remain unscrubbed even when such a function is called by another
+function that enables @code{strub}.  This is why, when compiling with
+@option{-fstrub=strict}, a @code{strub} context is not allowed to call
+@code{internal} @code{strub} functions.
+
+@smallexample
+/* A declaration of an internal-strub function.  */
+extern int __attribute__ ((strub ("internal"))) bar (void);
+
+int __attribute__ ((strub))
+baz (void)
+@{
+  /* Ok, foo was declared above as an at-calls strub function.  */
+  foo ();
+  /* Not allowed in strict mode, otherwise allowed.  */
+  bar ();
+@}
+@end smallexample
+
+An automatically-allocated variable associated with the @code{strub}
+attribute causes the (immediately) enclosing function to have
+@code{strub} enabled.
+
+A statically-allocated variable associated with the @code{strub}
+attribute causes functions that @emph{read} it, through its @code{strub}
+data type, to have @code{strub} enabled.  Reading data by dereferencing
+a pointer to a @code{strub} data type has the same effect.  Note: The
+attribute does not carry over from a composite type to the types of its
+components, so the intended effect may not be obtained with non-scalar
+types.
+
+When selecting a @code{strub}-enabled mode for a function that is not
+explicitly associated with one, because of @code{strub} variables or
+data pointers, the function must satisfy @code{internal} mode viability
+requirements (see below), even when @code{at-calls} mode is also viable
+and, being more efficient, ends up selected as an optimization.
+
+@smallexample
+/* zapme is implicitly strub-enabled because of strub variables.
+   Optimization may change its strub mode, but not the requirements.  */
+static int
+zapme (int i)
+@{
+  /* A local strub variable enables strub.  */
+  int __attribute__ ((strub)) lvar;
+  /* Reading strub data through a pointer-to-strub enables strub.  */
+  lvar = * (ptr_to_strub_int_type) &i;
+  /* Writing to a global strub variable does not enable strub.  */
+  var = lvar;
+  /* Reading from a global strub variable enables strub.  */
+  return var;
+@}
+@end smallexample
+
+A @code{strub} context is the body (as opposed to the interface) of a
+function that has @code{strub} enabled, be it explicitly, by
+@code{at-calls} or @code{internal} mode, or implicitly, due to
+@code{strub} variables or command-line options.
+
+A function of a type associated with the @code{disabled} @code{strub}
+mode (@code{strub("disabled")} will not have its own stack space
+scrubbed.  Such functions @emph{cannot} be called from within
+@code{strub} contexts.
+
+In order to enable a function to be called from within @code{strub}
+contexts without having its stack space scrubbed, associate it with the
+@code{callable} @code{strub} mode (@code{strub("callable")}).
+
+When a function is not assigned a @code{strub} mode, explicitly or
+implicitly, the mode defaults to @code{callable}, except when compiling
+with @option{-fstrub=strict}, that causes @code{strub} mode to default
+to @code{disabled}.
+
+@example
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+ /* Implicitly disabled with -fstrub=strict, otherwise callable.  */
+extern int bah (void);
+
+int __attribute__ ((strub))
+bal (void)
+@{
+  /* Not allowed, bad is not strub-callable.  */
+  bad ();
+  /* Ok, bac is strub-callable.  */
+  bac ();
+  /* Not allowed with -fstrub=strict, otherwise allowed.  */
+  bah ();
+@}
+@end example
+
+Function types marked @code{callable} and @code{disabled} are not
+mutually compatible types, but the underlying interfaces are compatible,
+so it is safe to convert pointers between them, and to use such pointers
+or alternate declarations to call them.  Interfaces are also
+interchangeable between them and @code{internal} (but not
+@code{at-calls}!), but adding @code{internal} to a pointer type will not
+cause the pointed-to function to perform stack scrubbing.
+
+@example
+void __attribute__ ((strub))
+bap (void)
+@{
+  /* Assign a callable function to pointer-to-disabled.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bac;
+  /* Not allowed: calls disabled type in a strub context.  */
+  d_p ();
+
+  /* Assign a disabled function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bad;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an internal function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  c_p = bar;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an at-calls function to pointer-to-callable.
+     Flaggged as incompatible.  */
+  c_p = bal;
+  /* The call through an interface-incompatible type will not use the
+     modified interface expected by the at-calls function, so it is
+     likely to misbehave at runtime.  */
+  c_p ();
+@}
+@end example
+
+@code{Strub} contexts are never inlined into non-@code{strub} contexts.
+When an @code{internal}-strub function is split up, the wrapper can
+often be inlined, but the wrapped body @emph{never} is.  A function
+marked as @code{always_inline}, even if explicitly assigned
+@code{internal} strub mode, will not undergo wrapping, so its body gets
+inlined as required.
+
+@example
+inline int __attribute__ ((strub ("at-calls")))
+inl_atc (void)
+@{
+  /* This body may get inlined into strub contexts.  */
+@}
+
+inline int __attribute__ ((strub ("internal")))
+inl_int (void)
+@{
+  /* This body NEVER gets inlined, though its wrapper may.  */
+@}
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+@{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+@}
+
+void __attribute__ ((strub ("disabled")))
+bat (void)
+@{
+  /* Not allowed, cannot inline into a non-strub context.  */
+  inl_int_ali ();
+@}
+@end example
+
+@cindex strub eligibility and viability
+Some @option{-fstrub=*} command line options enable @code{strub} modes
+implicitly where viable.  A @code{strub} mode is only viable for a
+function if the function is eligible for that mode, and if other
+conditions, detailed below, are satisfied.  If it's not eligible for a
+mode, attempts to explicitly associate it with that mode are rejected
+with an error message.  If it is eligible, that mode may be assigned
+explicitly through this attribute, but implicit assignment through
+command-line options may involve additional viability requirements.
+
+A function is ineligible for @code{at-calls} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+@code{At-calls} @code{strub} mode, if not requested through the function
+type, is only viable for an eligible function if the function is not
+visible to other translation units, and if it doesn't have its address
+taken.
+
+@smallexample
+/* bar is eligible for at-calls strub mode,
+   but not viable for that mode because it is visible to other units.
+   It is eligible and viable for internal strub mode.  */
+void bav () @{@}
+
+/* setp is eligible for at-calls strub mode,
+   but not viable for that mode because its address is taken.
+   It is eligible and viable for internal strub mode.  */
+void setp (void) @{ static void (*p)(void); = setp; @}
+@end smallexample
+
+A function is ineligible for @code{internal} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, or if attribute
+@code{noipa} is present.  For an @code{always_inline} function, meeting
+these requirements is enough to make it eligible.  Any function that has
+attribute @code{noclone}, that uses such extensions as non-local labels,
+computed gotos, alternate variable argument passing interfaces,
+@code{__builtin_next_arg}, or @code{__builtin_return_address}, or that
+takes too many (about 64Ki) arguments is ineligible, unless it is
+@code{always_inline}.  For @code{internal} @code{strub} mode, all
+eligible functions are viable.
+
+@smallexample
+/* flop is not eligible, thus not viable, for at-calls strub mode.
+   Likewise for internal strub mode.  */
+__attribute__ ((noipa)) void flop (void) @{@}
+
+/* flip is eligible and viable for at-calls strub mode.
+   It would be ineligible for internal strub mode, because of noclone,
+   if it weren't for always_inline.  With always_inline, noclone is not
+   an obstacle, so it is also eligible and viable for internal strub mode.  */
+inline __attribute__ ((noclone, always_inline)) void flip (void) @{@}
+@end smallexample
+
 @item unused
 @cindex @code{unused} type attribute
 When attached to a type (including a @code{union} or a @code{struct}),
@@ -11749,6 +12007,55 @@ 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 internal interfaces
+
+Stack scrubbing involves cooperation between a @code{strub} context,
+i.e., a function whose stack frame is to be zeroed-out, and its callers.
+The caller initializes a stack watermark, the @code{strub} context
+updates the watermark according to its stack use, and the caller zeroes
+it out once it regains control, whether by the callee's returning or by
+an exception.
+
+Each of these steps is performed by a different builtin function call.
+Calls to these builtins are introduced automatically, in response to
+@code{strub} attributes and command-line options; they are not expected
+to be explicitly called by source code.
+
+The functions that implement the builtins are available in libgcc but,
+depending on optimization levels, they are expanded internally, adjusted
+to account for inlining, and sometimes combined/deferred (e.g. passing
+the caller-supplied watermark on to callees, refraining from erasing
+stack areas that the caller will) to enable tail calls and to optimize
+for code size.
+
+@deftypefn {Built-in Function} {void} __builtin___strub_enter (void **@var{wmptr})
+This function initializes a stack @var{watermark} variable with the
+current top of the stack.  A call to this builtin function is introduced
+before entering a @code{strub} context.  It remains as a function call
+if optimization is not enabled.
+@end deftypefn
+
+@deftypefn {Built-in Function} {void} __builtin___strub_update (void **@var{wmptr})
+This function updates a stack @var{watermark} variable with the current
+top of the stack, if it tops the previous watermark.  A call to this
+builtin function is inserted within @code{strub} contexts, whenever
+additional stack space may have been used.  It remains as a function
+call at optimization levels lower than 2.
+@end deftypefn
+
+@deftypefn {Built-in Function} {void} __builtin___strub_leave (void **@var{wmptr})
+This function overwrites the memory area between the current top of the
+stack, and the @var{watermark}ed address.  A call to this builtin
+function is inserted after leaving a @code{strub} context.  It remains
+as a function call at optimization levels lower than 3, and it is guarded by
+a condition at level 2.
+@end deftypefn
+
 @node Vector Extensions
 @section Using Vector Instructions through Built-in Functions
 
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 8969bac664d31..afcb37914acb1 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -599,6 +599,8 @@ Objective-C and Objective-C++ Dialects}.
 -fstack-protector-explicit  -fstack-check @gol
 -fstack-limit-register=@var{reg}  -fstack-limit-symbol=@var{sym} @gol
 -fno-stack-limit  -fsplit-stack @gol
+-fstrub=disable -fstrub=strict -fstrub=relaxed @gol
+-fstrub=all -fstrub=at-calls -fstrub=internal @gol
 -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
 -fvtv-counts  -fvtv-debug @gol
 -finstrument-functions @gol
@@ -15512,6 +15514,56 @@ 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=disable
+@opindex -fstrub=disable
+Disable stack scrubbing entirely, ignoring any @code{strub} attributes.
+See @xref{Common Type Attributes}.
+
+@item -fstrub=strict
+@opindex fstrub=strict
+Functions default to @code{strub} mode @code{disabled}, and apply
+@option{strict}ly the restriction that only functions associated with
+@code{strub}-@code{callable} modes (@code{at-calls}, @code{callable} and
+@code{always_inline} @code{internal}) are @code{callable} by functions
+with @code{strub}-enabled modes (@code{at-calls} and @code{internal}).
+
+@item -fstrub=relaxed
+@opindex fstrub=relaxed
+Restore the default stack scrub (@code{strub}) setting, namely,
+@code{strub} is only enabled as required by @code{strub} attributes
+associated with function and data types.  @code{Relaxed} means that
+strub contexts are only prevented from calling functions explicitly
+associated with @code{strub} mode @code{disabled}.  This option is only
+useful to override other @option{-fstrub=*} options that precede it in
+the command line.
+
+@item -fstrub=at-calls
+@opindex fstrub=at-calls
+Enable @code{at-calls} @code{strub} mode where viable.  The primary use
+of this option is for testing.  It exercises the @code{strub} machinery
+in scenarios strictly local to a translation unit.  This @code{strub}
+mode modifies function interfaces, so any function that is visible to
+other translation units, or that has its address taken, will @emph{not}
+be affected by this option.  Optimization options may also affect
+viability.  See the @code{strub} attribute documentation for details on
+viability and eligibility requirements.
+
+@item -fstrub=internal
+@opindex fstrub=internal
+Enable @code{internal} @code{strub} mode where viable.  The primary use
+of this option is for testing.  This option is intended to exercise
+thoroughly parts of the @code{strub} machinery that implement the less
+efficient, but interface-preserving @code{strub} mode.  Functions that
+would not be affected by this option are quite uncommon.
+
+@item -fstrub=all
+@opindex fstrub=all
+Enable some @code{strub} mode where viable.  When both strub modes are
+viable, @code{at-calls} is preferred.  @option{-fdump-ipa-strubm} adds
+function attributes that tell which mode was selected for each function.
+The primary use of this option is for testing, to exercise thoroughly
+the @code{strub} machinery.
+
 @item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 @opindex fvtable-verify
 This option is only available when compiling C++ code.
@@ -17385,6 +17437,14 @@ and inlining decisions.
 @item inline
 Dump after function inlining.
 
+@item strubm
+Dump after selecting @code{strub} modes, and recording the selections as
+function attributes.
+
+@item strub
+Dump @code{strub} transformations: interface changes, function wrapping,
+and insertion of builtin calls for stack scrubbing and watermarking.
+
 @end table
 
 Additionally, the options @option{-optimized}, @option{-missed},
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index 413446bcc46cd..932e49dba8ef6 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_to_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 c68577d04a994..8c880747eb7a3 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 0000000000000..663fe7b2920d2
--- /dev/null
+++ b/gcc/ipa-strub.c
@@ -0,0 +1,3336 @@
+/* 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,
+
+};
+
+/* Look up a strub attribute in TYPE, and return it.  */
+
+static tree
+get_strub_attr_from_type (tree type)
+{
+  return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
+}
+
+/* Look up a strub attribute in DECL or in its type, and return it.  */
+
+static tree
+get_strub_attr_from_decl (tree decl)
+{
+  tree ret = lookup_attribute ("strub", DECL_ATTRIBUTES (decl));
+  if (ret)
+    return ret;
+  return get_strub_attr_from_type (TREE_TYPE (decl));
+}
+
+/* Define a function to cache identifier ID, to be used as a strub attribute
+   parameter for a strub mode named after NAME.  */
+#define DEF_STRUB_IDS(NAME, ID)				\
+static inline tree get_strub_mode_id_ ## NAME () {	\
+  static tree identifier = NULL_TREE;			\
+  if (!identifier)					\
+    identifier = get_identifier (ID);			\
+  return identifier;					\
+}
+/* Same as DEF_STRUB_IDS, but use the string expansion of NAME as ID.  */
+#define DEF_STRUB_ID(NAME)				\
+DEF_STRUB_IDS (NAME, #NAME)
+
+/* Define functions for each of the strub mode identifiers.
+   Expose dashes rather than underscores.  */
+DEF_STRUB_ID (disabled)
+DEF_STRUB_IDS (at_calls, "at-calls")
+DEF_STRUB_ID (internal)
+DEF_STRUB_ID (callable)
+DEF_STRUB_ID (wrapped)
+DEF_STRUB_ID (wrapper)
+DEF_STRUB_ID (inlinable)
+DEF_STRUB_IDS (at_calls_opt, "at-calls-opt")
+
+/* Release the temporary macro names.  */
+#undef DEF_STRUB_IDS
+#undef DEF_STRUB_ID
+
+/* Return the identifier corresponding to strub MODE.  */
+
+static tree
+get_strub_mode_attr_parm (enum strub_mode mode)
+{
+  switch (mode)
+    {
+    case STRUB_DISABLED:
+      return get_strub_mode_id_disabled ();
+
+    case STRUB_AT_CALLS:
+      return get_strub_mode_id_at_calls ();
+
+    case STRUB_INTERNAL:
+      return get_strub_mode_id_internal ();
+
+    case STRUB_CALLABLE:
+      return get_strub_mode_id_callable ();
+
+    case STRUB_WRAPPED:
+      return get_strub_mode_id_wrapped ();
+
+    case STRUB_WRAPPER:
+      return get_strub_mode_id_wrapper ();
+
+    case STRUB_INLINABLE:
+      return get_strub_mode_id_inlinable ();
+
+    case STRUB_AT_CALLS_OPT:
+      return get_strub_mode_id_at_calls_opt ();
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+/* Return the parmeters (TREE_VALUE) for a strub attribute of MODE.
+   We know we use a single parameter, so we bypass the creation of a
+   tree list.  */
+
+static tree
+get_strub_mode_attr_value (enum strub_mode mode)
+{
+  return get_strub_mode_attr_parm (mode);
+}
+
+/* Determine whether ID is a well-formed strub mode-specifying attribute
+   parameter for a function (type).  Only user-visible modes are accepted, and
+   ID must be non-NULL.
+
+   For unacceptable parms, return 0, otherwise a nonzero value as below.
+
+   If the parm enables strub, return positive, otherwise negative.
+
+   If the affected type must be a distinct, incompatible type,return an integer
+   of absolute value 2, otherwise 1.  */
+
+int
+strub_validate_fn_attr_parm (tree id)
+{
+  int ret;
+  const char *s = NULL;
+  size_t len = 0;
+
+  /* do NOT test for NULL.  This is only to be called with non-NULL arguments.
+     We assume that the strub parameter applies to a function, because only
+     functions accept an explicit argument.  If we accepted NULL, and we
+     happened to be called to verify the argument for a variable, our return
+     values would be wrong.  */
+  if (TREE_CODE (id) == STRING_CST)
+    {
+      s = TREE_STRING_POINTER (id);
+      len = TREE_STRING_LENGTH (id) - 1;
+    }
+  else if (TREE_CODE (id) == IDENTIFIER_NODE)
+    {
+      s = IDENTIFIER_POINTER (id);
+      len = IDENTIFIER_LENGTH (id);
+    }
+  else
+    return 0;
+
+  enum strub_mode mode;
+
+  if (len != 8)
+    return 0;
+
+  switch (s[0])
+    {
+    case 'd':
+      mode = STRUB_DISABLED;
+      ret = -1;
+      break;
+
+    case 'a':
+      mode = STRUB_AT_CALLS;
+      ret = 2;
+      break;
+
+    case 'i':
+      mode = STRUB_INTERNAL;
+      ret = 1;
+      break;
+
+    case 'c':
+      mode = STRUB_CALLABLE;
+      ret = -2;
+      break;
+
+    default:
+      /* Other parms are for internal use only.  */
+      return 0;
+    }
+
+  tree mode_id = get_strub_mode_attr_parm (mode);
+
+  if (TREE_CODE (id) == IDENTIFIER_NODE
+      ? id != mode_id
+      : strncmp (s, IDENTIFIER_POINTER (mode_id), len) != 0)
+    return 0;
+
+  return ret;
+}
+
+/* Return the strub mode from STRUB_ATTR.  VAR_P should be true if the attribute
+   is taken from a variable, rather than from a function, or a type thereof.  */
+
+static enum strub_mode
+get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
+{
+  enum strub_mode mode = STRUB_DISABLED;
+
+  if (strub_attr)
+    {
+      if (!TREE_VALUE (strub_attr))
+	mode = !var_p ? STRUB_AT_CALLS : STRUB_INTERNAL;
+      else
+	{
+	  gcc_checking_assert (!var_p);
+	  tree id = TREE_VALUE (strub_attr);
+	  if (TREE_CODE (id) == TREE_LIST)
+	    id = TREE_VALUE (id);
+	  const char *s = (TREE_CODE (id) == STRING_CST
+			   ? TREE_STRING_POINTER (id)
+			   : IDENTIFIER_POINTER (id));
+	  size_t len = (TREE_CODE (id) == STRING_CST
+			? TREE_STRING_LENGTH (id) - 1
+			: IDENTIFIER_LENGTH (id));
+
+	  switch (len)
+	    {
+	    case 7:
+	      switch (s[6])
+		{
+		case 'r':
+		  mode = STRUB_WRAPPER;
+		  break;
+
+		case 'd':
+		  mode = STRUB_WRAPPED;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 8:
+	      switch (s[0])
+		{
+		case 'd':
+		  mode = STRUB_DISABLED;
+		  break;
+
+		case 'a':
+		  mode = STRUB_AT_CALLS;
+		  break;
+
+		case 'i':
+		  mode = STRUB_INTERNAL;
+		  break;
+
+		case 'c':
+		  mode = STRUB_CALLABLE;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 9:
+	      mode = STRUB_INLINABLE;
+	      break;
+
+	    case 12:
+	      mode = STRUB_AT_CALLS_OPT;
+	      break;
+
+	    default:
+	      gcc_unreachable ();
+	    }
+
+	  gcc_checking_assert (TREE_CODE (id) == IDENTIFIER_NODE
+			       ? id == get_strub_mode_attr_parm (mode)
+			       : strncmp (IDENTIFIER_POINTER
+					  (get_strub_mode_attr_parm (mode)),
+					  s, len) == 0);
+	}
+    }
+
+  return mode;
+}
+
+/* Look up, decode and return the strub mode associated with FNDECL.  */
+
+static enum strub_mode
+get_strub_mode_from_fndecl (tree fndecl)
+{
+  return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
+}
+
+/* Look up, decode and return the strub mode associated with NODE.  */
+
+static enum strub_mode
+get_strub_mode (cgraph_node *node)
+{
+  return get_strub_mode_from_fndecl (node->decl);
+}
+
+/* Look up, decode and return the strub mode associated with TYPE.  */
+
+static enum strub_mode
+get_strub_mode_from_type (tree type)
+{
+  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 >= -1 && !var_p)
+    return STRUB_CALLABLE;
+
+  return STRUB_DISABLED;
+}
+
+\f
+/* Return true iff NODE calls builtin va_start.  */
+
+static bool
+calls_builtin_va_start_p (cgraph_node *node)
+{
+  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;
+}
+
+/* Return true iff NODE calls builtin apply_args, and optionally REPORT it.  */
+
+static bool
+calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
+{
+  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;
+}
+
+/* Return true iff NODE carries the always_inline attribute.  */
+
+static inline bool
+strub_always_inline_p (cgraph_node *node)
+{
+  return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
+}
+
+/* Return true iff NODE is potentially eligible for any strub-enabled mode, and
+   optionally REPORT the reasons for ineligibility.  */
+
+static inline bool
+can_strub_p (cgraph_node *node, bool report = false)
+{
+  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;
+}
+
+/* Return true iff NODE is eligible for at-calls strub, and optionally REPORT
+   the reasons for ineligibility.  Besides general non-eligibility for
+   strub-enabled modes, at-calls rules out calling builtin apply_args.  */
+
+static bool
+can_strub_at_calls_p (cgraph_node *node, bool report = false)
+{
+  bool result = !report || can_strub_p (node, report);
+
+  if (!result && !report)
+    return result;
+
+  return !calls_builtin_apply_args_p (node, report);
+}
+
+/* Symbolic macro for the max number of arguments that internal strub may add to
+   a function.  */
+
+#define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
+
+/* We can't perform internal strubbing if the function body involves certain
+   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 references are not preserved when copying.  If referenced
+	 in nested functions, as in 920415-1.c and 920721-4.c their decls get
+	 remapped independently.  The exclusion below might be too broad, in
+	 that we might be able to support correctly cases in which the labels
+	 are only used internally in a function, but disconnecting forced labels
+	 from their original declarations is undesirable in general.  */
+      basic_block bb;
+      FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+	for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+	     !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);
+
+	    if (!FORCED_LABEL (target))
+	      continue;
+
+	    result = false;
+
+	    if (!report)
+	      return result;
+
+	    sorry_at (gimple_location (label_stmt),
+		      "internal %<strub%> does not support forced 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;
+}
+
+/* Return TRUE iff NODE has any strub-requiring local variable, or accesses (as
+   in reading) any variable through a strub-requiring type.  */
+
+static bool
+strub_from_body_p (cgraph_node *node)
+{
+  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-requiring types.
+     ??? Compound types don't propagate the strub requirement to
+     component types.  */
+  basic_block bb;
+  FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+	 !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;
+    }
+}
+
+/* Compute the strub mode to be used for NODE.  STRUB_ATTR should be the strub
+   attribute,found for NODE, if any.  */
+
+static enum strub_mode
+compute_strub_mode (cgraph_node *node, tree strub_attr)
+{
+  enum strub_mode req_mode = get_strub_mode_from_attr (strub_attr);
+
+  gcc_checking_assert (flag_strub >= -2 && flag_strub <= 3);
+
+  /* Symbolic encodings of the -fstrub-* flags.  */
+  /* Enable strub when explicitly requested through attributes to functions or
+     variables, reporting errors if the requests cannot be satisfied.  */
+  const bool strub_flag_auto = flag_strub < 0;
+  /* strub_flag_auto with strub call verification; without this, functions are
+     implicitly callable.  */
+  const bool strub_flag_strict = flag_strub < -1;
+  /* Disable strub altogether, ignore attributes entirely.  */
+  const bool strub_flag_disabled = flag_strub == 0;
+  /* On top of _auto, also enable strub implicitly for functions that can
+     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_strict
+	      || 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->externally_visible
+		   || (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)
+       : (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;
+}
+
+/* Set FNDT's strub mode to callable.
+   FNDT may be a function decl or a function type.  */
+
+void
+strub_make_callable (tree fndt)
+{
+  strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
+}
+
+/* Set NODE to strub MODE.  Report incompatibilities between MODE and the mode
+   requested through explicit attributes, and cases of non-eligibility.  */
+
+static void
+set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
+{
+  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 %qE selected for %qD, when %qE was requested",
+		    get_strub_mode_attr_parm (mode),
+		    node->decl,
+		    get_strub_mode_attr_parm (req_mode));
+	  if (node->alias)
+	    {
+	      cgraph_node *target = node->ultimate_alias_target ();
+	      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);
+}
+
+/* Compute and set NODE's strub mode.  */
+
+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);
+}
+
+\f
+/* Non-strub functions shouldn't be called from within strub contexts,
+   except through callable ones.  Always inline strub functions can
+   only be called from strub functions.  */
+
+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 >= -1);
+
+    case STRUB_DISABLED:
+      return false;
+
+    case STRUB_CALLABLE:
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  return true;
+}
+
+/* Return true if CALLEE can be inlined into CALLER.  We wish to avoid inlining
+   WRAPPED functions back into their WRAPPERs.  More generally, we wish to avoid
+   inlining strubbed functions into non-strubbed ones.  CALLER doesn't have to
+   be an immediate caller of CALLEE: the immediate caller may have already been
+   cloned for inlining, and then CALLER may be further up the original call
+   chain.  ???  It would be nice if our own caller would retry inlining callee
+   if caller gets inlined.  */
+
+bool
+strub_inlinable_to_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:
+      /* When we consider inlining, we've already verified callability, so we
+	 can even inline callable and then disabled into a strub context.  That
+	 will get strubbed along with the context, so it's hopefully not a
+	 problem.  */
+      return true;
+
+    default:
+      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 types T1 and T2 are strub-compatible.  Return 1 if the strub modes
+   are the same, 2 if they are interchangeable, and 0 otherwise.  */
+
+int
+strub_comptypes (tree t1, tree t2)
+{
+  if (TREE_CODE (t1) != TREE_CODE (t2))
+    return 0;
+
+  enum strub_mode m1 = get_strub_mode_from_type (t1);
+  enum strub_mode m2 = get_strub_mode_from_type (t2);
+
+  if (m1 == m2)
+    return 1;
+
+  /* We're dealing with types, so only strub modes that can be selected by
+     attributes in the front end matter.  If either mode is at-calls (for
+     functions) or internal (for variables), the conversion is not
+     compatible.  */
+  bool var_p = !FUNC_OR_METHOD_TYPE_P (t1);
+  enum strub_mode mr = var_p ? STRUB_INTERNAL : STRUB_AT_CALLS;
+  if (m1 == mr || m2 == mr)
+    return 0;
+
+  return 2;
+}
+
+/* Check that strub functions don't call non-strub functions, and that
+   always_inline strub functions are only called by strub
+   functions.  */
+
+static void
+verify_strub ()
+{
+  cgraph_node *node;
+
+  /* It's expected that check strub-wise pointer type compatibility of variables
+     and of functions is already taken care of by front-ends, on account of the
+     attribute's being marked as affecting type identity and of the creation of
+     distinct types.  */
+
+  /* Check that call targets in strub contexts have strub-callable types.  */
+
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+  {
+    enum strub_mode caller_mode = get_strub_mode (node);
+    bool strub_context
+      = (caller_mode == STRUB_AT_CALLS
+	 || caller_mode == STRUB_AT_CALLS_OPT
+	 || caller_mode == STRUB_INTERNAL
+	 || caller_mode == STRUB_WRAPPED
+	 || caller_mode == STRUB_INLINABLE);
+
+    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);
+	  }
+      }
+  }
+}
+
+namespace {
+
+/* Define a pass to compute strub modes.  */
+const pass_data pass_data_ipa_strub_mode = {
+  SIMPLE_IPA_PASS,
+  "strubm",
+  OPTGROUP_NONE,
+  TV_NONE,
+  PROP_cfg, // properties_required
+  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 relaxed (-3) and strict (-4) settings, that only enable strub at a
+       function or variable attribute's request, the attribute handler changes
+       flag_strub to -1 or -2, respectively, if any strub-enabling occurence of
+       the attribute is found.  Therefore, if it remains at -3 or -4, nothing
+       that would enable strub was found, so we can disable it and avoid the
+       overhead.  */
+    if (flag_strub < -2)
+      flag_strub = 0;
+    return flag_strub;
+  }
+  virtual unsigned int execute (function *);
+};
+
+/* Define a pass to introduce strub transformations.  */
+const pass_data pass_data_ipa_strub = {
+  SIMPLE_IPA_PASS,
+  "strub",
+  OPTGROUP_NONE,
+  TV_NONE,
+  PROP_cfg | PROP_ssa, // 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 && !seen_error (); }
+  virtual unsigned int execute (function *);
+
+  /* Define on demand and cache some types we use often.  */
+#define DEF_TYPE(NAME, INIT)			\
+  static inline tree get_ ## NAME () {		\
+    static tree type = NULL_TREE;		\
+    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 non-strub builtins on demand.  */
+#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 strub builtins on demand.  */
+#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 strub identifiers on demand.  */
+#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 *);
+
+  /* Add to SEQ a call to the strub watermark update builtin, taking NODE's
+     location if given.  Optionally add the corresponding edge from NODE, with
+     execution frequency COUNT.  Return the modified SEQ.  */
+
+  static inline gimple_seq
+  call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
+			 gimple_seq seq = NULL)
+    {
+      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
+
+/* Gather with this type a collection of parameters that we're turning into
+   explicit references.  */
+
+typedef hash_set<tree> indirect_parms_t;
+
+/* Dereference OP's incoming turned-into-reference parm if it's an
+   INDIRECT_PARMS or an ADDR_EXPR thereof.  Set *REC and return according to
+   gimple-walking expectations.  */
+
+static tree
+maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
+{
+  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)))
+	{
+	  op = TREE_OPERAND (op, 0);
+	  return op;
+	}
+    }
+
+  return NULL_TREE;
+}
+
+/* A gimple-walking function that adds dereferencing to indirect parms.  */
+
+static tree
+walk_make_indirect (tree *op, int *rec, void *arg)
+{
+  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;
+}
+
+/* A gimple-walking function that turns any non-gimple-val ADDR_EXPRs into a
+   separate SSA.  Though addresses of e.g. parameters, and of members thereof,
+   are gimple vals, turning parameters into references, with an extra layer of
+   indirection and thus explicit dereferencing, need to be regimplified.  */
+
+static tree
+walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
+{
+  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;
+}
+
+/* Turn STMT's PHI arg defs into separate SSA defs if they've become
+   non-gimple_val.  Return TRUE if any edge insertions need to be committed.  */
+
+static bool
+walk_regimplify_phi (gphi *stmt)
+{
+  bool needs_commit = false;
+
+  for (unsigned i = 0, n = gimple_phi_num_args (stmt); i < n; i++)
+    {
+      tree op = gimple_phi_arg_def (stmt, i);
+      if ((TREE_CODE (op) == ADDR_EXPR
+	   && !is_gimple_val (op))
+	  /* ??? A PARM_DECL that was addressable in the original function and
+	     had its address in PHI nodes, but that became a reference in the
+	     wrapped clone would NOT be updated by update_ssa in PHI nodes.
+	     Alas, if we were to create a default def for it now, update_ssa
+	     would complain that the symbol that needed rewriting already has
+	     SSA names associated with it.  OTOH, leaving the PARM_DECL alone,
+	     it eventually causes errors because it remains unchanged in PHI
+	     nodes, but it gets rewritten as expected if it appears in other
+	     stmts.  So we cheat a little here, and force the PARM_DECL out of
+	     the PHI node and into an assignment.  It's a little expensive,
+	     because we insert it at the edge, which introduces a basic block
+	     that's entirely unnecessary, but it works, and the block will be
+	     removed as the default def gets propagated back into the PHI node,
+	     so the final optimized code looks just as expected.  */
+	  || (TREE_CODE (op) == PARM_DECL
+	      && !TREE_ADDRESSABLE (op)))
+	{
+	  tree temp = make_ssa_name (TREE_TYPE (op), stmt);
+	  if (TREE_CODE (op) == PARM_DECL)
+	    SET_SSA_NAME_VAR_OR_IDENTIFIER (temp, DECL_NAME (op));
+	  SET_PHI_ARG_DEF (stmt, i, temp);
+
+	  gimple *assign = gimple_build_assign (temp, op);
+	  if (gimple_phi_arg_has_location (stmt, i))
+	    gimple_set_location (assign, gimple_phi_arg_location (stmt, i));
+	  gsi_insert_on_edge (gimple_phi_arg_edge (stmt, i), assign);
+	  needs_commit = true;
+	}
+    }
+
+  return needs_commit;
+}
+
+/* Create a reference type to use for PARM when turning it into a reference.
+   NONALIASED causes the reference type to gain its own separate alias set, so
+   that accessing the indirectly-passed parm won'will not add aliasing
+   noise.  */
+
+static tree
+build_ref_type_for (tree parm, bool nonaliased = true)
+{
+  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);
+
+      gcall *call = dyn_cast <gcall *> (stmt);
+      if (!call)
+	continue;
+
+      tree callee = gimple_call_fndecl (call);
+      gcc_checking_assert (callee);
+      node->create_edge (cgraph_node::get_create (callee), call, count, false);
+    }
+}
+
+/* Insert SEQ after the call at GSI, as if the call was in a try block with SEQ
+   as finally, i.e., SEQ will run after the call whether it returns or
+   propagates an exception.  This handles block splitting, EH edge and block
+   creation, noreturn and nothrow optimizations, and even throwing calls without
+   preexisting local handlers.  */
+
+static void
+gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
+{
+  if (!seq)
+    return;
+
+  gimple *stmt = gsi_stmt (gsi);
+
+  if (gimple_has_location (stmt))
+    annotate_all_with_location (seq, gimple_location (stmt));
+
+  gcall *call = dyn_cast <gcall *> (stmt);
+  bool noreturn_p = call && gimple_call_noreturn_p (call);
+  int eh_lp = lookup_stmt_eh_lp (stmt);
+  bool must_not_throw_p = eh_lp < 0;
+  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);
+    }
+}
+
+/* Record the order of the last cgraph entry whose mode we've already set, so
+   that we can perform mode setting incrementally without duplication.  */
+static int last_cgraph_order;
+
+/* Set strub modes for functions introduced since the last call.  */
+
+static void
+ipa_strub_set_mode_for_new_functions ()
+{
+  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_fndecl (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;
+}
+
+/* Adjust a call to an at-calls call target.  Create a watermark local variable
+   if needed, initialize it before, pass it to the callee according to the
+   modified at-calls interface, and release the callee's stack space after the
+   call, if not deferred.  If the call is const or pure, arrange for the
+   watermark to not be assumed unused or unchanged.  */
+
+void
+pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
+{
+  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);
+}
+
+/* Adjust all at-calls calls in NODE. */
+
+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 ();
+    }
+}
+
+/* The strubm (strub mode) pass computes a strub mode for each function in the
+   call graph, and checks, before any inlining, that strub callability
+   requirements in effect are satisfied.  */
+
+unsigned int
+pass_ipa_strub_mode::execute (function *)
+{
+  last_cgraph_order = 0;
+  ipa_strub_set_mode_for_new_functions ();
+
+  /* Verify before any inlining or other transformations.  */
+  verify_strub ();
+
+  return 0;
+}
+
+/* Create a strub mode pass.  */
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub_mode (gcc::context *ctxt)
+{
+  return new pass_ipa_strub_mode (ctxt);
+}
+
+/* The strub pass proper adjusts types, signatures, and at-calls calls, and
+   splits internal-strub functions.  */
+
+unsigned int
+pass_ipa_strub::execute (function *)
+{
+  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;
+
+	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);
+
+		  gcall *call = dyn_cast <gcall *> (stmt);
+
+		  if (!call)
+		    continue;
+
+		  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 ();
+      }
+  }
+
+  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;
+      }
+
+    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;
+	  bool needs_commit = false;
+	  FOR_EACH_BB_FN (bb, cfun)
+	    {
+	      for (gphi_iterator gsi = gsi_start_nonvirtual_phis (bb);
+		   !gsi_end_p (gsi);
+		   gsi_next_nonvirtual_phi (&gsi))
+		{
+		  gphi *stmt = gsi.phi ();
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed && !is_gimple_debug (gsi_stmt (gsi)))
+		    if (walk_regimplify_phi (stmt))
+		      needs_commit = true;
+		}
+
+	      for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+		   !gsi_end_p (gsi); gsi_next (&gsi))
+		{
+		  gimple *stmt = gsi_stmt (gsi);
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed)
+		    {
+		      if (!is_gimple_debug (stmt))
+			{
+			  wi.info = &gsi;
+			  walk_gimple_op (stmt, walk_regimplify_addr_expr,
+					  &wi);
+			}
+		      update_stmt (stmt);
+		    }
+		}
+	    }
+	  if (needs_commit)
+	    gsi_commit_edge_inserts ();
+	}
+
+      if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca
+	  || 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)));
+
+      gcall *wrcall;
+      while (!(wrcall = dyn_cast <gcall *> (gsi_stmt (gsi))))
+	gsi_next (&gsi);
+
+      tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
+      TREE_ADDRESSABLE (swm) = true;
+      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 ();
+    }
+  }
+
+  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 0000000000000..5c0b266c3f81b
--- /dev/null
+++ b/gcc/ipa-strub.h
@@ -0,0 +1,45 @@
+/* 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 CALLEE can be inlined into CALLER, as far as stack scrubbing
+   constraints are concerned.  CALLEE doesn't have to be called directly by
+   CALLER, but the returned value says nothing about intervening functions.  */
+extern bool strub_inlinable_to_p (cgraph_node *callee, cgraph_node *caller);
+
+/* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
+extern bool strub_splittable_p (cgraph_node *node);
+
+/* 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);
+
+/* Return zero iff ID is NOT an acceptable parameter for a user-supplied strub
+   attribute for a function.  Otherwise, return >0 if it enables strub, <0 if it
+   does not.  Return +/-1 if the attribute-modified type is compatible with the
+   type without the attribute, or +/-2 if it is not compatible.  */
+extern int strub_validate_fn_attr_parm (tree id);
+
+/* Like comptypes, return 0 if t1 and t2 are not compatible, 1 if they are
+   compatible, and 2 if they are nearly compatible.  Same strub mode is
+   compatible, interface-compatible strub modes are nearly compatible.  */
+extern int strub_comptypes (tree t1, tree t2);
diff --git a/gcc/multiple_target.c b/gcc/multiple_target.c
index 28d5f95dca52b..07cdb66e337bd 100644
--- a/gcc/multiple_target.c
+++ b/gcc/multiple_target.c
@@ -557,7 +557,7 @@ public:
 bool
 pass_target_clone::gate (function *)
 {
-  return true;
+  return !seen_error ();
 }
 
 } // anon namespace
diff --git a/gcc/passes.def b/gcc/passes.def
index d7a1f8c97a676..8e4638d20edac 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 0000000000000..c7a79a6ea0d8a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O0.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=strict -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 0000000000000..96285c975d98e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=strict -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 0000000000000..8edc0d8aa1321
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
+
+/* At -O2, without -fno-inline, we fully expand enter and update, and add a test
+   around the leave call.  */
+
+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 0000000000000..c6d900cf3c45b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -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 0000000000000..33ee465e51cb6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O3.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=strict -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 0000000000000..2936f82079e18
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=strict -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 0000000000000..479746e57d87e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-Og -fstrub=strict -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 0000000000000..2241d4ea07f27
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
+
+/* At -Os, without -fno-inline, we fully expand enter, and also update.  The
+   expanded update might be larger than a call proper, but argument saving and
+   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 0000000000000..a322bcc5da606
--- /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 \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
new file mode 100644
index 0000000000000..db60026d0e080
--- /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 \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
new file mode 100644
index 0000000000000..2f462adc1efe0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+void __attribute__ ((__strub__ ("callable")))
+apply_function (void *args)
+{
+  __builtin_apply (0, args, 0);
+}
+
+void __attribute__ ((__strub__ ("internal")))
+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 0000000000000..a5d7551f5da5c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+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 0000000000000..64422a0d1e880
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+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 0000000000000..15ffaa031b899
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -fdump-ipa-strubm" } */
+
+/* Check that implicit enabling of strub mode selects internal strub when the
+   function uses __builtin_apply_args, that prevents the optimization to
+   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 \[(\]internal\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
new file mode 100644
index 0000000000000..b70843b4215a4
--- /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 \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
new file mode 100644
index 0000000000000..97a3988a6b922
--- /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 \[(\]callable\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
new file mode 100644
index 0000000000000..3d73431b3dcd3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict -O1" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+   the strubbing to its caller at -O1.  */
+
+#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 0000000000000..fddf3c745e7e6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -0,0 +1,8 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict -O2" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+   the strubbing to its caller at -O2.  */
+
+#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 0000000000000..2bc384ee8d1e6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -0,0 +1,93 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict -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__ ("callable")))
+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__ ("callable"))) 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__ ("callable")))
+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__ ("at-calls"), __noinline__, __noclone__))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ ("at-calls")))
+char *
+deferred_at_calls ()
+{
+  char *ret = at_calls ();
+  if (EXPECT_DEFERRAL !look_for_string (ret))
+    __builtin_abort ();
+  return ret;
+}
+
+static __attribute__ ((__strub__ ("internal")))
+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 0000000000000..fbaf85fe0fafe
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict -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 0000000000000..e9d7b7b9ee0a8
--- /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 \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
new file mode 100644
index 0000000000000..8b8e15a51c71c
--- /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 \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
new file mode 100644
index 0000000000000..0a4a7539d3489
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ ("internal")))
+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__ ("internal")))
+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__ ("internal")))
+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__ ("internal")))
+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 0000000000000..147171d96d5a1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ ("at-calls")))
+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__ ("at-calls")))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ ("at-calls")))
+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 0000000000000..4e92682895a43
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -0,0 +1,58 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that uses of a strub variable implicitly enables internal strub for
+   publicly-visible functions, and causes the same transformations to their
+   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-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
new file mode 100644
index 0000000000000..e2f9d8aebca58
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  Without the error,
+   inlining takes place.  */
+
+#include "strub-strict1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
new file mode 100644
index 0000000000000..98474435d2e59
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  */
+
+#include "strub-strict2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
new file mode 100644
index 0000000000000..1de15342595e4
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=strict -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 0000000000000..f9209c819004b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=strict -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 0000000000000..bed1dcfb54a45
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=strict -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 0000000000000..6bf0071f52b93
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -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 0000000000000..4732f515bf70c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
+   enter and leave calls within strub contexts, passing on the enclosing
+   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 0000000000000..8d6424c479a3a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
+   enter and leave calls within strub contexts, passing on the enclosing
+   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-strict1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
new file mode 100644
index 0000000000000..368522442066e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-strict1.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm" } */
+
+static int __attribute__ ((__strub__)) var;
+
+/* h becomes STRUB_INLINABLE, because of the use of the strub variable,
+   and the always_inline flag.  It would get inlined before pass_ipa_strub, if
+   it weren't for the error.  */
+static inline void
+__attribute__ ((__always_inline__))
+h() {
+  var++;
+}
+
+/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
+   the viability of at-calls strubbing.  Though internally a strub context, its
+   interface is not strub-enabled, so it's not callable from within strub
+   contexts.  */
+static inline void
+g() {
+  var--;
+  h();
+}
+
+/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
+   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
+void
+f() {
+  var++;
+  g();  /* { dg-error "calling non-.strub." } */
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-strict2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
new file mode 100644
index 0000000000000..b4f2888321821
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-strict2.c
@@ -0,0 +1,25 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm" } */
+
+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 \[(\]internal\[)\]" 2 "strubm" } } */
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 0000000000000..e48e0610e079b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=strict -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 0000000000000..87cda7ab21b16
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued.
+   Tail calls are short-circuited at -O2+.  */
+
+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 0000000000000..b5e45ab0525ad
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -0,0 +1,9 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+/* Check that strub and non-strub functions can be called from non-strub
+   contexts, and that strub and callable functions can be called from strub
+   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 0000000000000..96aa7fe4b07f7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -0,0 +1,264 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+/* Check that impermissible (cross-strub-context) calls are reported.  */
+
+extern int __attribute__ ((__strub__ ("callable"))) xcallable (void);
+extern int __attribute__ ((__strub__ ("internal"))) xinternal (void);
+extern int __attribute__ ((__strub__ ("at-calls"))) xat_calls (void);
+extern int __attribute__ ((__strub__ ("disabled"))) xdisabled (void);
+
+int __attribute__ ((__strub__ ("callable"))) callable (void);
+int __attribute__ ((__strub__ ("internal"))) internal (void);
+int __attribute__ ((__strub__ ("at-calls"))) at_calls (void);
+int __attribute__ ((__strub__ ("disabled"))) disabled (void);
+
+int __attribute__ ((__strub__)) var;
+int var_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
+icallable (void);
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
+iinternal (void);
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
+iat_calls (void);
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
+idisabled (void);
+static inline int __attribute__ ((__always_inline__))
+ivar_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
+i_callable (void) { return 0; }
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
+i_internal (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
+i_at_calls (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
+i_disabled (void) { return 0; }
+static inline int __attribute__ ((__always_inline__))
+i_var_user (void) { return var; }
+
+#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__ ("callable")))
+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__ ("internal")))
+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__ ("at-calls")))
+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__ ("disabled")))
+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 __attribute__ ((__always_inline__, __strub__ ("at-calls")))
+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 0000000000000..2857195706ed6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const function call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   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 0000000000000..98a92bc9eac2b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const function call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   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 0000000000000..5511a6e1e71d3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   and another to make sure it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ ("internal"), __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 0000000000000..47ee927964dff
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const wrapping call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   before the call, and another to make sure it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ ("internal")))
+#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 0000000000000..7c27a2a1a6dca
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..e66d903780afd
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..5e08e0e58c658
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* The pointer itself is a strub variable, that would enable internal strubbing
+   if its value was used.  Here, it's only overwritten, so no strub.  */
+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 0000000000000..a818e7a38bb5f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* The pointer itself is a strub variable, that would enable internal strubbing
+   if its value was used.  Here, it's only overwritten, so no strub.  */
+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 0000000000000..ddb0b5c0543b0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+/* It would be desirable to issue at least warnings for these.  */
+
+typedef int __attribute__ ((__strub__)) strub_int;
+strub_int *ptr;
+
+int *f () {
+  return ptr; /* { dg-message "incompatible|invalid conversion" } */
+}
+
+strub_int *g () {
+  return f (); /* { dg-message "incompatible|invalid conversion" } */
+}
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 0000000000000..c165f312f16de
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..69fcff8d3763d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..ff006224909bd
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
new file mode 100644
index 0000000000000..614b02228ba29
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+inline void __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+}
+
+void
+bat (void)
+{
+  /* Not allowed, not a strub context.  */
+  inl_int_ali (); /* { dg-error "context" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
new file mode 100644
index 0000000000000..f9a6b4a16faf8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=all" } */
+
+#include "strub-inlinable1.c"
+
+/* With -fstrub=all, the caller becomes a strub context, so the strub-inlinable
+   callee is not rejected.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
new file mode 100644
index 0000000000000..b4a7f3992bbaa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+typedef void ft (void);
+typedef void ft2 (int, int);
+extern ft __attribute__ ((__strub__)) fnac;
+
+ft * f (void) {
+  return fnac; /* { dg-message "incompatible|invalid conversion" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
new file mode 100644
index 0000000000000..d9d2c0caec42d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
@@ -0,0 +1,55 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic" } */
+
+/* C++ does not warn about the partial incompatibilities.
+
+   The d_p () calls are actually rejected, even in C++, but they are XFAILed
+   here because we don't get far enough in the compilation as to observe them,
+   because the incompatibilities are errors without -fpermissive.
+   strub-ptrfn3.c uses -fpermissive to check those.
+ */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
new file mode 100644
index 0000000000000..e1f179e160e5c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
@@ -0,0 +1,50 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic -fpermissive" } */
+/* { dg-prune-output "command-line option .-fpermissive." } */
+
+/* See strub-ptrfn2.c.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
new file mode 100644
index 0000000000000..70b558afad040
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+/* This is strub-ptrfn2.c without -Wpedantic.
+
+   Even C doesn't report the (not-quite-)compatible conversions without it.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
new file mode 100644
index 0000000000000..a262a086837b2
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure function call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+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 0000000000000..4c4bd50c209a0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure function call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   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 0000000000000..ce195c6b1f1b6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ ("internal"), __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 0000000000000..75cd54ccb5b5d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure wrapping call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ ("internal")))
+#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 0000000000000..c596066a045eb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -0,0 +1,85 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
+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__ ("callable"))) 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__ ("at-calls")))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ ("internal")))
+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 0000000000000..1fdba214a07f9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
+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__ ("at-calls")))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ ("internal")))
+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 0000000000000..afbc2cc9ab484
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict" } */
+/* { 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__ ("callable")))
+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__ ("at-calls")))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ ("internal")))
+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 0000000000000..5300f1d330b87
--- /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__ ("internal")))
+char *
+internal ()
+{
+  return intermediate ();
+}
+
+int __attribute__ ((__strub__ ("disabled")))
+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 0000000000000..57f9baf758ded
--- /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 0000000000000..08de3f1c3b17c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict" } */
+/* { dg-require-effective-target alloca } */
+
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
+
+#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 0000000000000..459f6886c5499
--- /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 0000000000000..0d367fb83d09d
--- /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 0000000000000..c226ab10ff651
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..a7911f1fa7212
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..6ebebcd01e8ea
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..10445d7cf8451
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -0,0 +1,37 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" }
+
+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 (F (X));
+   --  function G return Integer is (FP (X));
+   --  Calling G would likely raise an exception, because although FP
+   --  carries the strub at-calls attribute needed to call F, the
+   --  attribute is dropped from the type used for the call proper.
+end Strub_Attr;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 0 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark_ptr" 6 "strub" } }
+--  We have 1 at-calls subprogram (F) and 2 wrapped (P and G).
+--  For each of them, there's one match for the wrapped signature, 
+--  and one for the update call.
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 27 "strub" } }
+--  The 6 matches above, plus:
+--  5*2: wm var decl, enter, call, leave and clobber for each wrapper;
+--  2*1: an extra leave and clobber for the exception paths in the wrappers.
+--  7*1: for the F call in G, including EH path.
diff --git a/gcc/testsuite/gnat.dg/strub_attr.ads b/gcc/testsuite/gnat.dg/strub_attr.ads
new file mode 100644
index 0000000000000..a94c23bf41833
--- /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/testsuite/gnat.dg/strub_ind.adb b/gcc/testsuite/gnat.dg/strub_ind.adb
new file mode 100644
index 0000000000000..d08341a23b351
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind.adb
@@ -0,0 +1,44 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
+
+--  This is essentially the same test as strub_attr.adb, 
+--  but applying attributes to access types as well.
+--  That doesn't quite work yet, so we get an error we shouldn't get.
+
+package body Strub_Ind is
+   E : exception;
+
+   procedure P (X : Integer) is
+   begin
+      raise E;
+   end;
+   
+   function F (X : Integer) return Integer is
+   begin
+      return X * X;
+   end;
+   
+   function G return Integer is (FP (X)); -- { dg-bogus "non-.strub." "" { xfail *-*-* } }
+   --  Calling G would likely raise an exception, because although FP
+   --  carries the strub at-calls attribute needed to call F, the
+   --  attribute is dropped from the type used for the call proper.
+
+
+   type GT is access function return Integer;
+   pragma Machine_Attribute (GT, "strub", "at-calls");
+   --  The pragma above seems to have no effect.
+
+   GP : GT := G'Access; -- { dg-warning "incompatible" "" { xfail *-*-* } }
+   pragma Machine_Attribute (GP, "strub", "at-calls");
+   --  The pragma above does modify GP's type,
+   --  but dereferencing it uses an unmodified copy of the type.
+   --  The initializer should be diagnosed:
+   --  GT should only reference functions with at-calls strub.
+
+end Strub_Ind;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 0 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
+
+--  No "strub" dump checking because of the bogus error above.
diff --git a/gcc/testsuite/gnat.dg/strub_ind.ads b/gcc/testsuite/gnat.dg/strub_ind.ads
new file mode 100644
index 0000000000000..53dede60eacdd
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind.ads
@@ -0,0 +1,23 @@
+package Strub_Ind is
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "internal");
+
+   function F (X : Integer) return Integer;
+   pragma Machine_Attribute (F, "strub");
+
+   X : Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+
+   function G return Integer;
+
+   type FT is access function (X : Integer) return Integer;
+   pragma Machine_Attribute (FT, "strub", "at-calls");
+   --  The pragma above seems to get discarded in GNAT; Gigi doesn't see it.
+
+   FP : FT := F'Access;
+   pragma Machine_Attribute (FP, "strub", "at-calls");
+   --  The pragma above does modify FP's type,
+   --  but a call with it gets it converted to its Ada type,
+   --  that is cached by the translator as the unmodified type.
+
+end Strub_Ind;
diff --git a/gcc/tree-cfg.c b/gcc/tree-cfg.c
index 61b53913e0b30..9b01f2bb01ba7 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 83941bc0ceef0..df4abdf144928 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 70ce6a4d5b8d1..dca5233cc7bb6 100644
--- a/gcc/tree-ssa-ccp.c
+++ b/gcc/tree-ssa-ccp.c
@@ -3050,7 +3050,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 7ec9758455445..c4ee6701cb33b 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 1819ff3ac3d03..857091e65c82b 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 0000000000000..7c58deee1e602
--- /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__ ("callable")))
+
+/* 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));
+}


-- 
Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
   Free Software Activist                       GNU Toolchain Engineer
Disinformation flourishes because many people care deeply about injustice
but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* [PATCH v2 00/10] Introduce strub: machine-independent stack scrubbing
  2021-09-09  7:11 ` [PATCH] strub: machine-independent stack scrubbing Alexandre Oliva
@ 2022-07-29  6:16   ` Alexandre Oliva
  2022-07-29  6:24     ` [PATCH v2 01/10] Introduce strub: documentation, and new command-line options Alexandre Oliva
                       ` (11 more replies)
  0 siblings, 12 replies; 59+ messages in thread
From: Alexandre Oliva @ 2022-07-29  6:16 UTC (permalink / raw)
  To: gcc-patches
  Cc: Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Richard Biener, Jim Wilson


This patch adds the strub attribute for function and variable types,
command-line options, passes and adjustments to implement it,
documentation, and tests.

Stack scrubbing is implemented in a machine-independent way: functions
with strub enabled are modified so that they take an extra stack
watermark argument, that they update with their stack use, and the
caller can then zero it out once it regains control, whether by return
or exception.  There are two ways to go about it: at-calls, that
modifies the visible interface (signature) of the function, and
internal, in which the body is moved to a clone, the clone undergoes
the interface change, and the function becomes a wrapper, preserving
its original interface, that calls the clone and then clears the stack
used by it.

Variables can also be annotated with the strub attribute, so that
functions that read from them get stack scrubbing enabled implicitly,
whether at-calls, for functions only usable within a translation unit,
or internal, for functions whose interfaces must not be modified.

There is a strict mode, in which functions that have their stack
scrubbed can only call other functions with stack-scrubbing
interfaces, or those explicitly marked as callable from strub
contexts, so that an entire call chain gets scrubbing, at once or
piecemeal depending on optimization levels.  In the default mode,
relaxed, this requirement is not enforced by the compiler.

The implementation adds two IPA passes, one that assigns strub modes
early on, another that modifies interfaces and adds calls to the
builtins that jointly implement stack scrubbing.  Another builtin,
that obtains the stack pointer, is added for use in the implementation
of the builtins, whether expanded inline or called in libgcc.

There are new command-line options to change operation modes and to
force the feature disabled; it is enabled by default, but it has no
effect and is implicitly disabled if the strub attribute is never
used.  There are also options meant to use for testing the feature,
enabling different strubbing modes for all (viable) functions.

I'm going to split up the very large patch into separate posts:
1:documentation, tests (2:C/C++ torture, 3:C/C++ non-torture, 4:C++
and Ada), 5:builtins and runtime, 6:attributes, 7:interfaces and
adjustments, and ipa-strub.cc fragments (8:strub modes, 9:strub mode
assignment pass, and 10:strub pass proper).

This is an refreshed and updated version of the patch posted in Sept
last year.  It was regstrapped on x86_64-linux-gnu, bootstrapped with a
patchlet that enables -fstrub=all by default (with a fix for a warning
exposed by it).  Earlier, essentially the same change has undergone
significant testing, with and without -fstrub=all on multiple embedded
platforms.  Ok to install?


for  gcc/ChangeLog

	* Makefile.in (OBJS): Add ipa-strub.o.
	* builtins.def (BUILT_IN_STACK_ADDRESS): New.
	(BUILT_IN___STRUB_ENTER): New.
	(BUILT_IN___STRUB_UPDATE): New.
	(BUILT_IN___STRUB_LEAVE): New.
	* builtins.cc: Include ipa-strub.h.
	(STACK_STOPS, STACK_UNSIGNED): Define.
	(expand_builtin_stack_address): New.
	(expand_builtin_strub_enter): New.
	(expand_builtin_strub_update): New.
	(expand_builtin_strub_leave): New.
	(expand_bulitin): Call them.
	* common.opt (fstrub=*): New options.
	* doc/extend.texi (strub): New type attribute.
	(__builtin_stack_address): New function.
	(Stack Scrubbing): New section.
	* doc/invoke.texi (-fstrub=*): New options.
	(-fdump-ipa-*): New passes.
	* ipa-inline.cc: Include ipa-strub.h.
	(can_inline_edge_p): Test strub_inlinable_to_p.
	* ipa-split.cc: Include ipa-strub.h.
	(execute_split_functions): Test strub_splittable_p.
	* ipa-strub.cc, ipa-strub.h: New.
	* passes.def: Add strub_mode and strub passes.
	* tree-cfg.cc (gimple_verify_flow_info): Note on debug stmts.
	* tree-pass.h (make_pass_ipa_strub_mode): Declare.
	(make_pass_ipa_strub): Declare.
	(make_pass_ipa_function_and_variable_visibility): Fix
	formatting.
	* tree-ssa-ccp.cc (optimize_stack_restore): Keep restores
	before strub leave.
	* multiple_target.cc (pass_target_clone::gate): Test seen_error.
	* attribs.cc: Include ipa-strub.h.
	(decl_attributes): Support applying attributes to function
	type, rather than pointer type, at handler's request.
	(comp_type_attributes): Combine strub_comptypes and target
	comp_type results.

for  gcc/c-family/ChangeLog

	* c-attribs.cc: Include ipa-strub.h.
	(handle_strub_attribute): New.
	(c_common_attribute_table): Add strub.

for  gcc/ada/ChangeLog

	* gcc-interface/trans.cc: Include ipa-strub.h.
	(gigi): Make internal decls for targets of compiler-generated
	calls strub-callable too.
	(build_raise_check): Likewise.
	* gcc-interface/utils.cc: Include ipa-strub.h.
	(handle_strub_attribute): New.
	(gnat_internal_attribute_table): Add strub.

for  gcc/testsuite/ChangeLog

	* c-c++-common/strub-O0.c: New.
	* c-c++-common/strub-O1.c: New.
	* c-c++-common/strub-O2.c: New.
	* c-c++-common/strub-O2fni.c: New.
	* c-c++-common/strub-O3.c: New.
	* c-c++-common/strub-O3fni.c: New.
	* c-c++-common/strub-Og.c: New.
	* c-c++-common/strub-Os.c: New.
	* c-c++-common/strub-all1.c: New.
	* c-c++-common/strub-all2.c: New.
	* c-c++-common/strub-apply1.c: New.
	* c-c++-common/strub-apply2.c: New.
	* c-c++-common/strub-apply3.c: New.
	* c-c++-common/strub-apply4.c: New.
	* c-c++-common/strub-at-calls1.c: New.
	* c-c++-common/strub-at-calls2.c: New.
	* c-c++-common/strub-defer-O1.c: New.
	* c-c++-common/strub-defer-O2.c: New.
	* c-c++-common/strub-defer-O3.c: New.
	* c-c++-common/strub-defer-Os.c: New.
	* c-c++-common/strub-internal1.c: New.
	* c-c++-common/strub-internal2.c: New.
	* c-c++-common/strub-parms1.c: New.
	* c-c++-common/strub-parms2.c: New.
	* c-c++-common/strub-parms3.c: New.
	* c-c++-common/strub-relaxed1.c: New.
	* c-c++-common/strub-relaxed2.c: New.
	* c-c++-common/strub-short-O0-exc.c: New.
	* c-c++-common/strub-short-O0.c: New.
	* c-c++-common/strub-short-O1.c: New.
	* c-c++-common/strub-short-O2.c: New.
	* c-c++-common/strub-short-O3.c: New.
	* c-c++-common/strub-short-Os.c: New.
	* c-c++-common/strub-strict1.c: New.
	* c-c++-common/strub-strict2.c: New.
	* c-c++-common/strub-tail-O1.c: New.
	* c-c++-common/strub-tail-O2.c: New.
	* c-c++-common/torture/strub-callable1.c: New.
	* c-c++-common/torture/strub-callable2.c: New.
	* c-c++-common/torture/strub-const1.c: New.
	* c-c++-common/torture/strub-const2.c: New.
	* c-c++-common/torture/strub-const3.c: New.
	* c-c++-common/torture/strub-const4.c: New.
	* c-c++-common/torture/strub-data1.c: New.
	* c-c++-common/torture/strub-data2.c: New.
	* c-c++-common/torture/strub-data3.c: New.
	* c-c++-common/torture/strub-data4.c: New.
	* c-c++-common/torture/strub-data5.c: New.
	* c-c++-common/torture/strub-indcall1.c: New.
	* c-c++-common/torture/strub-indcall2.c: New.
	* c-c++-common/torture/strub-indcall3.c: New.
	* c-c++-common/torture/strub-inlinable1.c: New.
	* c-c++-common/torture/strub-inlinable2.c: New.
	* c-c++-common/torture/strub-ptrfn1.c: New.
	* c-c++-common/torture/strub-ptrfn2.c: New.
	* c-c++-common/torture/strub-ptrfn3.c: New.
	* c-c++-common/torture/strub-ptrfn4.c: New.
	* c-c++-common/torture/strub-pure1.c: New.
	* c-c++-common/torture/strub-pure2.c: New.
	* c-c++-common/torture/strub-pure3.c: New.
	* c-c++-common/torture/strub-pure4.c: New.
	* c-c++-common/torture/strub-run1.c: New.
	* c-c++-common/torture/strub-run2.c: New.
	* c-c++-common/torture/strub-run3.c: New.
	* c-c++-common/torture/strub-run4.c: New.
	* c-c++-common/torture/strub-run4c.c: New.
	* c-c++-common/torture/strub-run4d.c: New.
	* c-c++-common/torture/strub-run4i.c: New.
	* g++.dg/strub-run1.C: New.
	* g++.dg/torture/strub-init1.C: New.
	* g++.dg/torture/strub-init2.C: New.
	* g++.dg/torture/strub-init3.C: New.
	* gnat.dg/strub_attr.adb, gnat.dg/strub_attr.ads: New.
	* gnat.dg/strub_ind.adb, gnat.dg/strub_ind.ads: New.

for  libgcc/ChangeLog

	* Makefile.in (LIB2ADD): Add strub.c.
	* libgcc2.h (__strub_enter, __strub_update, __strub_leave):
	Declare.
	* strub.c: New.
---
 gcc/Makefile.in                                    |    1 
 gcc/ada/gcc-interface/trans.cc                     |   18 
 gcc/ada/gcc-interface/utils.cc                     |   73 
 gcc/attribs.cc                                     |   40 
 gcc/builtins.cc                                    |  273 ++
 gcc/builtins.def                                   |    4 
 gcc/c-family/c-attribs.cc                          |   82 
 gcc/common.opt                                     |   29 
 gcc/doc/extend.texi                                |  307 ++
 gcc/doc/invoke.texi                                |   60 
 gcc/ipa-inline.cc                                  |    6 
 gcc/ipa-split.cc                                   |    7 
 gcc/ipa-strub.cc                                   | 3489 ++++++++++++++++++++
 gcc/ipa-strub.h                                    |   45 
 gcc/multiple_target.cc                             |    2 
 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-defer-O1.c        |    7 
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |    8 
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |   97 +
 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-relaxed1.c        |   18 
 gcc/testsuite/c-c++-common/strub-relaxed2.c        |   14 
 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-strict1.c         |   36 
 gcc/testsuite/c-c++-common/strub-strict2.c         |   25 
 gcc/testsuite/c-c++-common/strub-tail-O1.c         |    8 
 gcc/testsuite/c-c++-common/strub-tail-O2.c         |   14 
 gcc/testsuite/c-c++-common/strub-var1.c            |   24 
 .../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   |   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 
 .../c-c++-common/torture/strub-inlinable1.c        |   16 
 .../c-c++-common/torture/strub-inlinable2.c        |    7 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |   10 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |   55 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c  |   50 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c  |   43 
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |   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    |   90 +
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |   79 
 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_access.adb             |   21 
 gcc/testsuite/gnat.dg/strub_access1.adb            |   16 
 gcc/testsuite/gnat.dg/strub_attr.adb               |   37 
 gcc/testsuite/gnat.dg/strub_attr.ads               |   12 
 gcc/testsuite/gnat.dg/strub_disp.adb               |   64 
 gcc/testsuite/gnat.dg/strub_disp1.adb              |   79 
 gcc/testsuite/gnat.dg/strub_ind.adb                |   33 
 gcc/testsuite/gnat.dg/strub_ind.ads                |   17 
 gcc/testsuite/gnat.dg/strub_ind1.adb               |   41 
 gcc/testsuite/gnat.dg/strub_ind1.ads               |   17 
 gcc/testsuite/gnat.dg/strub_ind2.adb               |   34 
 gcc/testsuite/gnat.dg/strub_ind2.ads               |   17 
 gcc/testsuite/gnat.dg/strub_intf.adb               |   93 +
 gcc/testsuite/gnat.dg/strub_intf1.adb              |   86 
 gcc/testsuite/gnat.dg/strub_intf2.adb              |   55 
 gcc/testsuite/gnat.dg/strub_renm.adb               |   21 
 gcc/testsuite/gnat.dg/strub_renm1.adb              |   32 
 gcc/testsuite/gnat.dg/strub_renm2.adb              |   32 
 gcc/testsuite/gnat.dg/strub_var.adb                |   16 
 gcc/testsuite/gnat.dg/strub_var1.adb               |   20 
 gcc/tree-cfg.cc                                    |    1 
 gcc/tree-pass.h                                    |    5 
 gcc/tree-ssa-ccp.cc                                |    4 
 libgcc/Makefile.in                                 |    3 
 libgcc/libgcc2.h                                   |    4 
 libgcc/strub.c                                     |  112 +
 115 files changed, 7227 insertions(+), 12 deletions(-)
 create mode 100644 gcc/ipa-strub.cc
 create mode 100644 gcc/ipa-strub.h
 create mode 100644 gcc/testsuite/c-c++-common/strub-O0.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O2fni.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O3fni.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-Og.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-Os.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-all1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-all2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-apply1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-apply2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-apply3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-apply4.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-at-calls1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-at-calls2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-defer-O1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-defer-O2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-defer-O3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-defer-Os.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-internal1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-internal2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-parms1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-parms2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-parms3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-relaxed1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-relaxed2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O0-exc.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O0.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-Os.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-strict1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-strict2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-tail-O1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-tail-O2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-var1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-callable1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-callable2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data5.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-indcall1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-indcall2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-indcall3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4c.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4d.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4i.c
 create mode 100644 gcc/testsuite/g++.dg/strub-run1.C
 create mode 100644 gcc/testsuite/g++.dg/torture/strub-init1.C
 create mode 100644 gcc/testsuite/g++.dg/torture/strub-init2.C
 create mode 100644 gcc/testsuite/g++.dg/torture/strub-init3.C
 create mode 100644 gcc/testsuite/gnat.dg/strub_access.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_access1.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_attr.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_attr.ads
 create mode 100644 gcc/testsuite/gnat.dg/strub_disp.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_disp1.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind.ads
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind1.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind1.ads
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind2.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind2.ads
 create mode 100644 gcc/testsuite/gnat.dg/strub_intf.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_intf1.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_intf2.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_renm.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_renm1.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_renm2.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_var.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_var1.adb
 create mode 100644 libgcc/strub.c



-- 
Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
   Free Software Activist                       GNU Toolchain Engineer
Disinformation flourishes because many people care deeply about injustice
but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* [PATCH v2 01/10] Introduce strub: documentation, and new command-line options
  2022-07-29  6:16   ` [PATCH v2 00/10] Introduce " Alexandre Oliva
@ 2022-07-29  6:24     ` Alexandre Oliva
  2022-07-29  6:25     ` [PATCH v2 02/10] Introduce strub: torture tests for C and C++ Alexandre Oliva
                       ` (10 subsequent siblings)
  11 siblings, 0 replies; 59+ messages in thread
From: Alexandre Oliva @ 2022-07-29  6:24 UTC (permalink / raw)
  To: gcc-patches
  Cc: Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Richard Biener, Jim Wilson


Ada already has some strub documentation in
gcc/ada/doc/gnat_rm/security_hardening_features.rst.

for  gcc/ChangeLog

	* common.opt (fstrub=*): New options.
	* doc/extend.texi (strub): New type attribute.
	(__builtin_stack_address): New function.
	(Stack Scrubbing): New section.
	* doc/invoke.texi (-fstrub=*): New options.
	(-fdump-ipa-*): New passes.

diff --git a/gcc/common.opt b/gcc/common.opt
index e7a51e882bade..2c635806bbf2c 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2780,6 +2780,35 @@ fstrict-overflow
 Common
 Treat signed overflow as undefined.  Negated as -fwrapv -fwrapv-pointer.
 
+fstrub=disable
+Common RejectNegative Var(flag_strub, 0)
+Disable stack scrub entirely, disregarding strub attributes.
+
+fstrub=strict
+Common RejectNegative Var(flag_strub, -4)
+Enable stack scrub as per attributes, with strict call checking.
+
+; If any strub-enabling attribute is seen when the default or strict
+; initializer values are in effect, flag_strub is bumped up by 2.  The
+; scrub mode gate function will then bump these initializer values to
+; 0 if no strub-enabling attribute is seen.  This minimizes the strub
+; overhead.
+fstrub=relaxed
+Common RejectNegative Var(flag_strub, -3) Init(-3)
+Restore default strub mode: as per attributes, with relaxed checking.
+
+fstrub=all
+Common RejectNegative Var(flag_strub, 3)
+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.
+
 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 7fe7f8817cdd4..90d38c9f08362 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 internal interfaces.
 * Vector Extensions::   Using vector instructions through built-in functions.
 * Offsetof::            Special syntax for implementing @code{offsetof}.
 * __sync Builtins::     Legacy built-in functions for atomic memory access.
@@ -8890,6 +8891,263 @@ 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.  Being a type attribute, it attaches to types, even when
+specified in function and variable declarations.  When applied to
+function types, it takes an optional string argument.  When applied to a
+pointer-to-function type, if the optional argument is given, it gets
+propagated to the function type.
+
+@smallexample
+/* A strub variable.  */
+int __attribute__ ((strub)) var;
+/* A strub variable that happens to be a pointer.  */
+__attribute__ ((strub)) int *strub_ptr_to_int;
+/* A pointer type that may point to a strub variable.  */
+typedef int __attribute__ ((strub)) *ptr_to_strub_int_type;
+
+/* A declaration of a strub function.  */
+extern int __attribute__ ((strub)) foo (void);
+/* A pointer to that strub function.  */
+int __attribute__ ((strub ("at-calls"))) (*ptr_to_strub_fn)(void) = foo;
+@end smallexample
+
+A function associated with @code{at-calls} @code{strub} mode
+(@code{strub("at-calls")}, or just @code{strub}) undergoes interface
+changes.  Its callers are adjusted to match the changes, and to scrub
+(overwrite with zeros) the stack space used by the called function after
+it returns.  The interface change makes the function type incompatible
+with an unadorned but otherwise equivalent type, so @emph{every}
+declaration and every type that may be used to call the function must be
+associated with this strub mode.
+
+A function associated with @code{internal} @code{strub} mode
+(@code{strub("internal")}) retains an unmodified, type-compatible
+interface, but it may be turned into a wrapper that calls the wrapped
+body using a custom interface.  The wrapper then scrubs the stack space
+used by the wrapped body.  Though the wrapped body has its stack space
+scrubbed, the wrapper does not, so arguments and return values may
+remain unscrubbed even when such a function is called by another
+function that enables @code{strub}.  This is why, when compiling with
+@option{-fstrub=strict}, a @code{strub} context is not allowed to call
+@code{internal} @code{strub} functions.
+
+@smallexample
+/* A declaration of an internal-strub function.  */
+extern int __attribute__ ((strub ("internal"))) bar (void);
+
+int __attribute__ ((strub))
+baz (void)
+@{
+  /* Ok, foo was declared above as an at-calls strub function.  */
+  foo ();
+  /* Not allowed in strict mode, otherwise allowed.  */
+  bar ();
+@}
+@end smallexample
+
+An automatically-allocated variable associated with the @code{strub}
+attribute causes the (immediately) enclosing function to have
+@code{strub} enabled.
+
+A statically-allocated variable associated with the @code{strub}
+attribute causes functions that @emph{read} it, through its @code{strub}
+data type, to have @code{strub} enabled.  Reading data by dereferencing
+a pointer to a @code{strub} data type has the same effect.  Note: The
+attribute does not carry over from a composite type to the types of its
+components, so the intended effect may not be obtained with non-scalar
+types.
+
+When selecting a @code{strub}-enabled mode for a function that is not
+explicitly associated with one, because of @code{strub} variables or
+data pointers, the function must satisfy @code{internal} mode viability
+requirements (see below), even when @code{at-calls} mode is also viable
+and, being more efficient, ends up selected as an optimization.
+
+@smallexample
+/* zapme is implicitly strub-enabled because of strub variables.
+   Optimization may change its strub mode, but not the requirements.  */
+static int
+zapme (int i)
+@{
+  /* A local strub variable enables strub.  */
+  int __attribute__ ((strub)) lvar;
+  /* Reading strub data through a pointer-to-strub enables strub.  */
+  lvar = * (ptr_to_strub_int_type) &i;
+  /* Writing to a global strub variable does not enable strub.  */
+  var = lvar;
+  /* Reading from a global strub variable enables strub.  */
+  return var;
+@}
+@end smallexample
+
+A @code{strub} context is the body (as opposed to the interface) of a
+function that has @code{strub} enabled, be it explicitly, by
+@code{at-calls} or @code{internal} mode, or implicitly, due to
+@code{strub} variables or command-line options.
+
+A function of a type associated with the @code{disabled} @code{strub}
+mode (@code{strub("disabled")} will not have its own stack space
+scrubbed.  Such functions @emph{cannot} be called from within
+@code{strub} contexts.
+
+In order to enable a function to be called from within @code{strub}
+contexts without having its stack space scrubbed, associate it with the
+@code{callable} @code{strub} mode (@code{strub("callable")}).
+
+When a function is not assigned a @code{strub} mode, explicitly or
+implicitly, the mode defaults to @code{callable}, except when compiling
+with @option{-fstrub=strict}, that causes @code{strub} mode to default
+to @code{disabled}.
+
+@example
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+ /* Implicitly disabled with -fstrub=strict, otherwise callable.  */
+extern int bah (void);
+
+int __attribute__ ((strub))
+bal (void)
+@{
+  /* Not allowed, bad is not strub-callable.  */
+  bad ();
+  /* Ok, bac is strub-callable.  */
+  bac ();
+  /* Not allowed with -fstrub=strict, otherwise allowed.  */
+  bah ();
+@}
+@end example
+
+Function types marked @code{callable} and @code{disabled} are not
+mutually compatible types, but the underlying interfaces are compatible,
+so it is safe to convert pointers between them, and to use such pointers
+or alternate declarations to call them.  Interfaces are also
+interchangeable between them and @code{internal} (but not
+@code{at-calls}!), but adding @code{internal} to a pointer type will not
+cause the pointed-to function to perform stack scrubbing.
+
+@example
+void __attribute__ ((strub))
+bap (void)
+@{
+  /* Assign a callable function to pointer-to-disabled.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bac;
+  /* Not allowed: calls disabled type in a strub context.  */
+  d_p ();
+
+  /* Assign a disabled function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bad;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an internal function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  c_p = bar;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an at-calls function to pointer-to-callable.
+     Flaggged as incompatible.  */
+  c_p = bal;
+  /* The call through an interface-incompatible type will not use the
+     modified interface expected by the at-calls function, so it is
+     likely to misbehave at runtime.  */
+  c_p ();
+@}
+@end example
+
+@code{Strub} contexts are never inlined into non-@code{strub} contexts.
+When an @code{internal}-strub function is split up, the wrapper can
+often be inlined, but the wrapped body @emph{never} is.  A function
+marked as @code{always_inline}, even if explicitly assigned
+@code{internal} strub mode, will not undergo wrapping, so its body gets
+inlined as required.
+
+@example
+inline int __attribute__ ((strub ("at-calls")))
+inl_atc (void)
+@{
+  /* This body may get inlined into strub contexts.  */
+@}
+
+inline int __attribute__ ((strub ("internal")))
+inl_int (void)
+@{
+  /* This body NEVER gets inlined, though its wrapper may.  */
+@}
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+@{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+@}
+
+void __attribute__ ((strub ("disabled")))
+bat (void)
+@{
+  /* Not allowed, cannot inline into a non-strub context.  */
+  inl_int_ali ();
+@}
+@end example
+
+@cindex strub eligibility and viability
+Some @option{-fstrub=*} command line options enable @code{strub} modes
+implicitly where viable.  A @code{strub} mode is only viable for a
+function if the function is eligible for that mode, and if other
+conditions, detailed below, are satisfied.  If it's not eligible for a
+mode, attempts to explicitly associate it with that mode are rejected
+with an error message.  If it is eligible, that mode may be assigned
+explicitly through this attribute, but implicit assignment through
+command-line options may involve additional viability requirements.
+
+A function is ineligible for @code{at-calls} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+@code{At-calls} @code{strub} mode, if not requested through the function
+type, is only viable for an eligible function if the function is not
+visible to other translation units, if it doesn't have its address
+taken, and if it is never called with a function type overrider.
+
+@smallexample
+/* bar is eligible for at-calls strub mode,
+   but not viable for that mode because it is visible to other units.
+   It is eligible and viable for internal strub mode.  */
+void bav () @{@}
+
+/* setp is eligible for at-calls strub mode,
+   but not viable for that mode because its address is taken.
+   It is eligible and viable for internal strub mode.  */
+void setp (void) @{ static void (*p)(void); = setp; @}
+@end smallexample
+
+A function is ineligible for @code{internal} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, or if attribute
+@code{noipa} is present.  For an @code{always_inline} function, meeting
+these requirements is enough to make it eligible.  Any function that has
+attribute @code{noclone}, that uses such extensions as non-local labels,
+computed gotos, alternate variable argument passing interfaces,
+@code{__builtin_next_arg}, or @code{__builtin_return_address}, or that
+takes too many (about 64Ki) arguments is ineligible, unless it is
+@code{always_inline}.  For @code{internal} @code{strub} mode, all
+eligible functions are viable.
+
+@smallexample
+/* flop is not eligible, thus not viable, for at-calls strub mode.
+   Likewise for internal strub mode.  */
+__attribute__ ((noipa)) void flop (void) @{@}
+
+/* flip is eligible and viable for at-calls strub mode.
+   It would be ineligible for internal strub mode, because of noclone,
+   if it weren't for always_inline.  With always_inline, noclone is not
+   an obstacle, so it is also eligible and viable for internal strub mode.  */
+inline __attribute__ ((noclone, always_inline)) void flip (void) @{@}
+@end smallexample
+
 @item unused
 @cindex @code{unused} type attribute
 When attached to a type (including a @code{union} or a @code{struct}),
@@ -11927,6 +12185,55 @@ 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 internal interfaces
+
+Stack scrubbing involves cooperation between a @code{strub} context,
+i.e., a function whose stack frame is to be zeroed-out, and its callers.
+The caller initializes a stack watermark, the @code{strub} context
+updates the watermark according to its stack use, and the caller zeroes
+it out once it regains control, whether by the callee's returning or by
+an exception.
+
+Each of these steps is performed by a different builtin function call.
+Calls to these builtins are introduced automatically, in response to
+@code{strub} attributes and command-line options; they are not expected
+to be explicitly called by source code.
+
+The functions that implement the builtins are available in libgcc but,
+depending on optimization levels, they are expanded internally, adjusted
+to account for inlining, and sometimes combined/deferred (e.g. passing
+the caller-supplied watermark on to callees, refraining from erasing
+stack areas that the caller will) to enable tail calls and to optimize
+for code size.
+
+@deftypefn {Built-in Function} {void} __builtin___strub_enter (void **@var{wmptr})
+This function initializes a stack @var{watermark} variable with the
+current top of the stack.  A call to this builtin function is introduced
+before entering a @code{strub} context.  It remains as a function call
+if optimization is not enabled.
+@end deftypefn
+
+@deftypefn {Built-in Function} {void} __builtin___strub_update (void **@var{wmptr})
+This function updates a stack @var{watermark} variable with the current
+top of the stack, if it tops the previous watermark.  A call to this
+builtin function is inserted within @code{strub} contexts, whenever
+additional stack space may have been used.  It remains as a function
+call at optimization levels lower than 2.
+@end deftypefn
+
+@deftypefn {Built-in Function} {void} __builtin___strub_leave (void **@var{wmptr})
+This function overwrites the memory area between the current top of the
+stack, and the @var{watermark}ed address.  A call to this builtin
+function is inserted after leaving a @code{strub} context.  It remains
+as a function call at optimization levels lower than 3, and it is guarded by
+a condition at level 2.
+@end deftypefn
+
 @node Vector Extensions
 @section Using Vector Instructions through Built-in Functions
 
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index fa23fbeaaaa22..1226efb5e7e3a 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -624,6 +624,8 @@ Objective-C and Objective-C++ Dialects}.
 -fstack-protector-explicit  -fstack-check @gol
 -fstack-limit-register=@var{reg}  -fstack-limit-symbol=@var{sym} @gol
 -fno-stack-limit  -fsplit-stack @gol
+-fstrub=disable -fstrub=strict -fstrub=relaxed @gol
+-fstrub=all -fstrub=at-calls -fstrub=internal @gol
 -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]} @gol
 -fvtv-counts  -fvtv-debug @gol
 -finstrument-functions  -finstrument-functions-once @gol
@@ -16482,6 +16484,56 @@ 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=disable
+@opindex -fstrub=disable
+Disable stack scrubbing entirely, ignoring any @code{strub} attributes.
+See @xref{Common Type Attributes}.
+
+@item -fstrub=strict
+@opindex fstrub=strict
+Functions default to @code{strub} mode @code{disabled}, and apply
+@option{strict}ly the restriction that only functions associated with
+@code{strub}-@code{callable} modes (@code{at-calls}, @code{callable} and
+@code{always_inline} @code{internal}) are @code{callable} by functions
+with @code{strub}-enabled modes (@code{at-calls} and @code{internal}).
+
+@item -fstrub=relaxed
+@opindex fstrub=relaxed
+Restore the default stack scrub (@code{strub}) setting, namely,
+@code{strub} is only enabled as required by @code{strub} attributes
+associated with function and data types.  @code{Relaxed} means that
+strub contexts are only prevented from calling functions explicitly
+associated with @code{strub} mode @code{disabled}.  This option is only
+useful to override other @option{-fstrub=*} options that precede it in
+the command line.
+
+@item -fstrub=at-calls
+@opindex fstrub=at-calls
+Enable @code{at-calls} @code{strub} mode where viable.  The primary use
+of this option is for testing.  It exercises the @code{strub} machinery
+in scenarios strictly local to a translation unit.  This @code{strub}
+mode modifies function interfaces, so any function that is visible to
+other translation units, or that has its address taken, will @emph{not}
+be affected by this option.  Optimization options may also affect
+viability.  See the @code{strub} attribute documentation for details on
+viability and eligibility requirements.
+
+@item -fstrub=internal
+@opindex fstrub=internal
+Enable @code{internal} @code{strub} mode where viable.  The primary use
+of this option is for testing.  This option is intended to exercise
+thoroughly parts of the @code{strub} machinery that implement the less
+efficient, but interface-preserving @code{strub} mode.  Functions that
+would not be affected by this option are quite uncommon.
+
+@item -fstrub=all
+@opindex fstrub=all
+Enable some @code{strub} mode where viable.  When both strub modes are
+viable, @code{at-calls} is preferred.  @option{-fdump-ipa-strubm} adds
+function attributes that tell which mode was selected for each function.
+The primary use of this option is for testing, to exercise thoroughly
+the @code{strub} machinery.
+
 @item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 @opindex fvtable-verify
 This option is only available when compiling C++ code.
@@ -18379,6 +18431,14 @@ and inlining decisions.
 @item inline
 Dump after function inlining.
 
+@item strubm
+Dump after selecting @code{strub} modes, and recording the selections as
+function attributes.
+
+@item strub
+Dump @code{strub} transformations: interface changes, function wrapping,
+and insertion of builtin calls for stack scrubbing and watermarking.
+
 @end table
 
 Additionally, the options @option{-optimized}, @option{-missed},

-- 
Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
   Free Software Activist                       GNU Toolchain Engineer
Disinformation flourishes because many people care deeply about injustice
but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* [PATCH v2 02/10] Introduce strub: torture tests for C and C++
  2022-07-29  6:16   ` [PATCH v2 00/10] Introduce " Alexandre Oliva
  2022-07-29  6:24     ` [PATCH v2 01/10] Introduce strub: documentation, and new command-line options Alexandre Oliva
@ 2022-07-29  6:25     ` Alexandre Oliva
  2022-08-09 13:34       ` Alexandre Oliva
  2022-07-29  6:25     ` [PATCH v2 03/10] Introduce strub: non-torture " Alexandre Oliva
                       ` (9 subsequent siblings)
  11 siblings, 1 reply; 59+ messages in thread
From: Alexandre Oliva @ 2022-07-29  6:25 UTC (permalink / raw)
  To: gcc-patches
  Cc: Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Richard Biener, Jim Wilson


for  gcc/testsuite/ChangeLog

	* c-c++-common/torture/strub-callable1.c: New.
	* c-c++-common/torture/strub-callable2.c: New.
	* c-c++-common/torture/strub-const1.c: New.
	* c-c++-common/torture/strub-const2.c: New.
	* c-c++-common/torture/strub-const3.c: New.
	* c-c++-common/torture/strub-const4.c: New.
	* c-c++-common/torture/strub-data1.c: New.
	* c-c++-common/torture/strub-data2.c: New.
	* c-c++-common/torture/strub-data3.c: New.
	* c-c++-common/torture/strub-data4.c: New.
	* c-c++-common/torture/strub-data5.c: New.
	* c-c++-common/torture/strub-indcall1.c: New.
	* c-c++-common/torture/strub-indcall2.c: New.
	* c-c++-common/torture/strub-indcall3.c: New.
	* c-c++-common/torture/strub-inlinable1.c: New.
	* c-c++-common/torture/strub-inlinable2.c: New.
	* c-c++-common/torture/strub-ptrfn1.c: New.
	* c-c++-common/torture/strub-ptrfn2.c: New.
	* c-c++-common/torture/strub-ptrfn3.c: New.
	* c-c++-common/torture/strub-ptrfn4.c: New.
	* c-c++-common/torture/strub-pure1.c: New.
	* c-c++-common/torture/strub-pure2.c: New.
	* c-c++-common/torture/strub-pure3.c: New.
	* c-c++-common/torture/strub-pure4.c: New.
	* c-c++-common/torture/strub-run1.c: New.
	* c-c++-common/torture/strub-run2.c: New.
	* c-c++-common/torture/strub-run3.c: New.
	* c-c++-common/torture/strub-run4.c: New.
	* c-c++-common/torture/strub-run4c.c: New.
	* c-c++-common/torture/strub-run4d.c: New.
	* c-c++-common/torture/strub-run4i.c: New.

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 0000000000000..b5e45ab0525ad
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -0,0 +1,9 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+/* Check that strub and non-strub functions can be called from non-strub
+   contexts, and that strub and callable functions can be called from strub
+   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 0000000000000..96aa7fe4b07f7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -0,0 +1,264 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+/* Check that impermissible (cross-strub-context) calls are reported.  */
+
+extern int __attribute__ ((__strub__ ("callable"))) xcallable (void);
+extern int __attribute__ ((__strub__ ("internal"))) xinternal (void);
+extern int __attribute__ ((__strub__ ("at-calls"))) xat_calls (void);
+extern int __attribute__ ((__strub__ ("disabled"))) xdisabled (void);
+
+int __attribute__ ((__strub__ ("callable"))) callable (void);
+int __attribute__ ((__strub__ ("internal"))) internal (void);
+int __attribute__ ((__strub__ ("at-calls"))) at_calls (void);
+int __attribute__ ((__strub__ ("disabled"))) disabled (void);
+
+int __attribute__ ((__strub__)) var;
+int var_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
+icallable (void);
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
+iinternal (void);
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
+iat_calls (void);
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
+idisabled (void);
+static inline int __attribute__ ((__always_inline__))
+ivar_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
+i_callable (void) { return 0; }
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
+i_internal (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
+i_at_calls (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
+i_disabled (void) { return 0; }
+static inline int __attribute__ ((__always_inline__))
+i_var_user (void) { return var; }
+
+#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__ ("callable")))
+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__ ("internal")))
+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__ ("at-calls")))
+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__ ("disabled")))
+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 __attribute__ ((__always_inline__, __strub__ ("at-calls")))
+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 0000000000000..2857195706ed6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const function call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   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 0000000000000..98a92bc9eac2b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const function call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   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 0000000000000..5511a6e1e71d3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   and another to make sure it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ ("internal"), __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 0000000000000..47ee927964dff
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const wrapping call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   before the call, and another to make sure it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ ("internal")))
+#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 0000000000000..7c27a2a1a6dca
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..e66d903780afd
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..5e08e0e58c658
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* The pointer itself is a strub variable, that would enable internal strubbing
+   if its value was used.  Here, it's only overwritten, so no strub.  */
+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 0000000000000..a818e7a38bb5f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* The pointer itself is a strub variable, that would enable internal strubbing
+   if its value was used.  Here, it's only overwritten, so no strub.  */
+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 0000000000000..ddb0b5c0543b0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+/* It would be desirable to issue at least warnings for these.  */
+
+typedef int __attribute__ ((__strub__)) strub_int;
+strub_int *ptr;
+
+int *f () {
+  return ptr; /* { dg-message "incompatible|invalid conversion" } */
+}
+
+strub_int *g () {
+  return f (); /* { dg-message "incompatible|invalid conversion" } */
+}
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 0000000000000..c165f312f16de
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..69fcff8d3763d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..ff006224909bd
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
new file mode 100644
index 0000000000000..614b02228ba29
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+inline void __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+}
+
+void
+bat (void)
+{
+  /* Not allowed, not a strub context.  */
+  inl_int_ali (); /* { dg-error "context" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
new file mode 100644
index 0000000000000..f9a6b4a16faf8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=all" } */
+
+#include "strub-inlinable1.c"
+
+/* With -fstrub=all, the caller becomes a strub context, so the strub-inlinable
+   callee is not rejected.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
new file mode 100644
index 0000000000000..b4a7f3992bbaa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+typedef void ft (void);
+typedef void ft2 (int, int);
+extern ft __attribute__ ((__strub__)) fnac;
+
+ft * f (void) {
+  return fnac; /* { dg-message "incompatible|invalid conversion" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
new file mode 100644
index 0000000000000..d9d2c0caec42d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
@@ -0,0 +1,55 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic" } */
+
+/* C++ does not warn about the partial incompatibilities.
+
+   The d_p () calls are actually rejected, even in C++, but they are XFAILed
+   here because we don't get far enough in the compilation as to observe them,
+   because the incompatibilities are errors without -fpermissive.
+   strub-ptrfn3.c uses -fpermissive to check those.
+ */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
new file mode 100644
index 0000000000000..e1f179e160e5c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
@@ -0,0 +1,50 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic -fpermissive" } */
+/* { dg-prune-output "command-line option .-fpermissive." } */
+
+/* See strub-ptrfn2.c.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
new file mode 100644
index 0000000000000..70b558afad040
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+/* This is strub-ptrfn2.c without -Wpedantic.
+
+   Even C doesn't report the (not-quite-)compatible conversions without it.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
new file mode 100644
index 0000000000000..a262a086837b2
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure function call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+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 0000000000000..4c4bd50c209a0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure function call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   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 0000000000000..ce195c6b1f1b6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ ("internal"), __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 0000000000000..75cd54ccb5b5d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure wrapping call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ ("internal")))
+#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 0000000000000..b24a1c7a345fa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -0,0 +1,90 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  Avoid the use of red zones by avoiding
+   leaf functions.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+/* Pad before and after the string on the stack, so that it's not overwritten by
+   regular stack use.  */
+#define PAD 7
+
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
+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__ ("callable"))) callable_t (char *);
+  callable_t *f = 0;
+
+  char s[2 * PAD + 1][sizeof (test_string)];
+  __builtin_strcpy (s[PAD], test_string);
+  asm ("" : "+m" (s), "+r" (f));
+
+  if (__builtin_expect (!f, 1))
+    return (char *) __builtin_stack_address ();
+
+  f (s[PAD]);
+  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__ ("at-calls")))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ ("internal")))
+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 0000000000000..1df2ffe2fe58c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -0,0 +1,79 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  Allow red zones to be used.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+/* Pad before and after the string on the stack, so that it's not overwritten by
+   regular stack use.  */
+#define PAD 7
+
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
+char *
+leak_string (void)
+{
+  int len = sizeof (test_string);
+  asm ("" : "+rm" (len));
+  char s[2 * PAD + 1][len];
+  __builtin_strcpy (s[PAD], 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__ ("at-calls")))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ ("internal")))
+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 0000000000000..afbc2cc9ab484
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict" } */
+/* { 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__ ("callable")))
+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__ ("at-calls")))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ ("internal")))
+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 0000000000000..5300f1d330b87
--- /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__ ("internal")))
+char *
+internal ()
+{
+  return intermediate ();
+}
+
+int __attribute__ ((__strub__ ("disabled")))
+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 0000000000000..57f9baf758ded
--- /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 0000000000000..08de3f1c3b17c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict" } */
+/* { dg-require-effective-target alloca } */
+
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
+
+#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 0000000000000..459f6886c5499
--- /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"

-- 
Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
   Free Software Activist                       GNU Toolchain Engineer
Disinformation flourishes because many people care deeply about injustice
but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* [PATCH v2 03/10] Introduce strub: non-torture tests for C and C++
  2022-07-29  6:16   ` [PATCH v2 00/10] Introduce " Alexandre Oliva
  2022-07-29  6:24     ` [PATCH v2 01/10] Introduce strub: documentation, and new command-line options Alexandre Oliva
  2022-07-29  6:25     ` [PATCH v2 02/10] Introduce strub: torture tests for C and C++ Alexandre Oliva
@ 2022-07-29  6:25     ` Alexandre Oliva
  2022-07-29  6:26     ` [PATCH v2 04/10] Introduce strub: tests for C++ and Ada Alexandre Oliva
                       ` (8 subsequent siblings)
  11 siblings, 0 replies; 59+ messages in thread
From: Alexandre Oliva @ 2022-07-29  6:25 UTC (permalink / raw)
  To: gcc-patches
  Cc: Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Richard Biener, Jim Wilson


for  gcc/testsuite/ChangeLog

	* c-c++-common/strub-O0.c: New.
	* c-c++-common/strub-O1.c: New.
	* c-c++-common/strub-O2.c: New.
	* c-c++-common/strub-O2fni.c: New.
	* c-c++-common/strub-O3.c: New.
	* c-c++-common/strub-O3fni.c: New.
	* c-c++-common/strub-Og.c: New.
	* c-c++-common/strub-Os.c: New.
	* c-c++-common/strub-all1.c: New.
	* c-c++-common/strub-all2.c: New.
	* c-c++-common/strub-apply1.c: New.
	* c-c++-common/strub-apply2.c: New.
	* c-c++-common/strub-apply3.c: New.
	* c-c++-common/strub-apply4.c: New.
	* c-c++-common/strub-at-calls1.c: New.
	* c-c++-common/strub-at-calls2.c: New.
	* c-c++-common/strub-defer-O1.c: New.
	* c-c++-common/strub-defer-O2.c: New.
	* c-c++-common/strub-defer-O3.c: New.
	* c-c++-common/strub-defer-Os.c: New.
	* c-c++-common/strub-internal1.c: New.
	* c-c++-common/strub-internal2.c: New.
	* c-c++-common/strub-parms1.c: New.
	* c-c++-common/strub-parms2.c: New.
	* c-c++-common/strub-parms3.c: New.
	* c-c++-common/strub-relaxed1.c: New.
	* c-c++-common/strub-relaxed2.c: New.
	* c-c++-common/strub-short-O0-exc.c: New.
	* c-c++-common/strub-short-O0.c: New.
	* c-c++-common/strub-short-O1.c: New.
	* c-c++-common/strub-short-O2.c: New.
	* c-c++-common/strub-short-O3.c: New.
	* c-c++-common/strub-short-Os.c: New.
	* c-c++-common/strub-strict1.c: New.
	* c-c++-common/strub-strict2.c: New.
	* c-c++-common/strub-tail-O1.c: New.
	* c-c++-common/strub-tail-O2.c: New.

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 0000000000000..c7a79a6ea0d8a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O0.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=strict -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 0000000000000..96285c975d98e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=strict -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 0000000000000..8edc0d8aa1321
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
+
+/* At -O2, without -fno-inline, we fully expand enter and update, and add a test
+   around the leave call.  */
+
+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 0000000000000..c6d900cf3c45b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -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 0000000000000..33ee465e51cb6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O3.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=strict -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 0000000000000..2936f82079e18
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=strict -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 0000000000000..479746e57d87e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-Og -fstrub=strict -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 0000000000000..2241d4ea07f27
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
+
+/* At -Os, without -fno-inline, we fully expand enter, and also update.  The
+   expanded update might be larger than a call proper, but argument saving and
+   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 0000000000000..a322bcc5da606
--- /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 \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
new file mode 100644
index 0000000000000..db60026d0e080
--- /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 \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
new file mode 100644
index 0000000000000..2f462adc1efe0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+void __attribute__ ((__strub__ ("callable")))
+apply_function (void *args)
+{
+  __builtin_apply (0, args, 0);
+}
+
+void __attribute__ ((__strub__ ("internal")))
+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 0000000000000..a5d7551f5da5c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+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 0000000000000..64422a0d1e880
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+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 0000000000000..15ffaa031b899
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -fdump-ipa-strubm" } */
+
+/* Check that implicit enabling of strub mode selects internal strub when the
+   function uses __builtin_apply_args, that prevents the optimization to
+   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 \[(\]internal\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
new file mode 100644
index 0000000000000..b70843b4215a4
--- /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 \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
new file mode 100644
index 0000000000000..97a3988a6b922
--- /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 \[(\]callable\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
new file mode 100644
index 0000000000000..3d73431b3dcd3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict -O1" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+   the strubbing to its caller at -O1.  */
+
+#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 0000000000000..fddf3c745e7e6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -0,0 +1,8 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict -O2" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+   the strubbing to its caller at -O2.  */
+
+#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 0000000000000..5974e72efed46
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -0,0 +1,97 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict -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";
+
+/* Pad before and after the string on the stack, so that it's not overwritten by
+   regular stack use.  */
+#define PAD 7
+
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
+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__ ("callable"))) callable_t (char *);
+  callable_t *f = 0;
+
+  char s[2 * PAD + 1][sizeof (test_string)];
+  __builtin_strcpy (s[PAD], test_string);
+  asm ("" : "+m" (s), "+r" (f));
+
+  if (__builtin_expect (!f, 1))
+    return (char*)__builtin_stack_address ();
+
+  f (s[PAD]);
+  return 0;
+}
+
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
+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__ ("at-calls"), __noinline__, __noclone__))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ ("at-calls")))
+char *
+deferred_at_calls ()
+{
+  char *ret = at_calls ();
+  if (EXPECT_DEFERRAL !look_for_string (ret))
+    __builtin_abort ();
+  return ret;
+}
+
+static __attribute__ ((__strub__ ("internal")))
+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 0000000000000..fbaf85fe0fafe
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict -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 0000000000000..e9d7b7b9ee0a8
--- /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 \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
new file mode 100644
index 0000000000000..8b8e15a51c71c
--- /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 \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
new file mode 100644
index 0000000000000..0a4a7539d3489
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ ("internal")))
+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__ ("internal")))
+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__ ("internal")))
+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__ ("internal")))
+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 0000000000000..147171d96d5a1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ ("at-calls")))
+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__ ("at-calls")))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ ("at-calls")))
+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 0000000000000..4e92682895a43
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -0,0 +1,58 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that uses of a strub variable implicitly enables internal strub for
+   publicly-visible functions, and causes the same transformations to their
+   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-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
new file mode 100644
index 0000000000000..e2f9d8aebca58
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  Without the error,
+   inlining takes place.  */
+
+#include "strub-strict1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
new file mode 100644
index 0000000000000..98474435d2e59
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  */
+
+#include "strub-strict2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
new file mode 100644
index 0000000000000..1de15342595e4
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=strict -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 0000000000000..f9209c819004b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=strict -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 0000000000000..bed1dcfb54a45
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=strict -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 0000000000000..6bf0071f52b93
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -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 0000000000000..4732f515bf70c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
+   enter and leave calls within strub contexts, passing on the enclosing
+   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 0000000000000..8d6424c479a3a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
+   enter and leave calls within strub contexts, passing on the enclosing
+   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-strict1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
new file mode 100644
index 0000000000000..368522442066e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-strict1.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm" } */
+
+static int __attribute__ ((__strub__)) var;
+
+/* h becomes STRUB_INLINABLE, because of the use of the strub variable,
+   and the always_inline flag.  It would get inlined before pass_ipa_strub, if
+   it weren't for the error.  */
+static inline void
+__attribute__ ((__always_inline__))
+h() {
+  var++;
+}
+
+/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
+   the viability of at-calls strubbing.  Though internally a strub context, its
+   interface is not strub-enabled, so it's not callable from within strub
+   contexts.  */
+static inline void
+g() {
+  var--;
+  h();
+}
+
+/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
+   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
+void
+f() {
+  var++;
+  g();  /* { dg-error "calling non-.strub." } */
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-strict2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
new file mode 100644
index 0000000000000..b4f2888321821
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-strict2.c
@@ -0,0 +1,25 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm" } */
+
+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 \[(\]internal\[)\]" 2 "strubm" } } */
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 0000000000000..e48e0610e079b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=strict -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 0000000000000..87cda7ab21b16
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued.
+   Tail calls are short-circuited at -O2+.  */
+
+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/strub-var1.c b/gcc/testsuite/c-c++-common/strub-var1.c
new file mode 100644
index 0000000000000..eb6250fd39c90
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-var1.c
@@ -0,0 +1,24 @@
+/* { dg-do compile } */
+
+int __attribute__ ((strub)) x;
+float __attribute__ ((strub)) f;
+double __attribute__ ((strub)) d;
+
+/* The attribute applies to the type of the declaration, i.e., to the pointer
+   variable p, not to the pointed-to integer.  */
+int __attribute__ ((strub)) *
+p = &x; /* { dg-message "incompatible|invalid conversion" } */
+
+typedef int __attribute__ ((strub)) strub_int;
+strub_int *q = &x; /* Now this is compatible.  */
+
+int __attribute__ ((strub))
+a[2]; /* { dg-warning "does not apply to elements" } */
+
+int __attribute__ ((vector_size (4 * sizeof (int))))
+    __attribute__ ((strub))
+v; /* { dg-warning "does not apply to elements" } */
+
+struct s {
+  int i, j;
+} __attribute__ ((strub)) w; /* { dg-warning "does not apply to fields" } */

-- 
Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
   Free Software Activist                       GNU Toolchain Engineer
Disinformation flourishes because many people care deeply about injustice
but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* [PATCH v2 04/10] Introduce strub: tests for C++ and Ada
  2022-07-29  6:16   ` [PATCH v2 00/10] Introduce " Alexandre Oliva
                       ` (2 preceding siblings ...)
  2022-07-29  6:25     ` [PATCH v2 03/10] Introduce strub: non-torture " Alexandre Oliva
@ 2022-07-29  6:26     ` Alexandre Oliva
  2022-07-29  6:26     ` [PATCH v2 05/10] Introduce strub: builtins and runtime Alexandre Oliva
                       ` (7 subsequent siblings)
  11 siblings, 0 replies; 59+ messages in thread
From: Alexandre Oliva @ 2022-07-29  6:26 UTC (permalink / raw)
  To: gcc-patches
  Cc: Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Richard Biener, Jim Wilson


for  gcc/testsuite/ChangeLog

	* g++.dg/strub-run1.C: New.
	* g++.dg/torture/strub-init1.C: New.
	* g++.dg/torture/strub-init2.C: New.
	* g++.dg/torture/strub-init3.C: New.
	* gnat.dg/strub_attr.adb, gnat.dg/strub_attr.ads: New.
	* gnat.dg/strub_ind.adb, gnat.dg/strub_ind.ads: New.

diff --git a/gcc/testsuite/g++.dg/strub-run1.C b/gcc/testsuite/g++.dg/strub-run1.C
new file mode 100644
index 0000000000000..0d367fb83d09d
--- /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 0000000000000..c226ab10ff651
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..a7911f1fa7212
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..6ebebcd01e8ea
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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_access.adb b/gcc/testsuite/gnat.dg/strub_access.adb
new file mode 100644
index 0000000000000..29e6996ecf61c
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_access.adb
@@ -0,0 +1,21 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=relaxed -fdump-ipa-strubm" }
+
+--  The main subprogram doesn't read from the automatic variable, but
+--  being an automatic variable, its presence should be enough for the
+--  procedure to get strub enabled.
+
+procedure Strub_Access is
+   type Strub_Int is new Integer;
+   pragma Machine_Attribute (Strub_Int, "strub");
+   
+   X : aliased Strub_Int := 0;
+
+   function F (P : access Strub_Int) return Strub_Int is (P.all);
+
+begin
+   X := F (X'Access);
+end Strub_Access;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 1 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls-opt\[)\]\[)\]" 1 "strubm" } }
diff --git a/gcc/testsuite/gnat.dg/strub_access1.adb b/gcc/testsuite/gnat.dg/strub_access1.adb
new file mode 100644
index 0000000000000..dae4706016436
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_access1.adb
@@ -0,0 +1,16 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=relaxed" }
+
+--  Check that we reject 'Access of a strub variable whose type does
+--  not carry a strub modifier.
+
+procedure Strub_Access1 is
+   X : aliased Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+
+   function F (P : access Integer) return Integer is (P.all);
+   
+begin
+   X := F (X'Unchecked_access); -- OK.
+   X := F (X'Access); -- { dg-error "target access type drops .strub. mode" }
+end Strub_Access1;
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
new file mode 100644
index 0000000000000..10445d7cf8451
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -0,0 +1,37 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" }
+
+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 (F (X));
+   --  function G return Integer is (FP (X));
+   --  Calling G would likely raise an exception, because although FP
+   --  carries the strub at-calls attribute needed to call F, the
+   --  attribute is dropped from the type used for the call proper.
+end Strub_Attr;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 0 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark_ptr" 6 "strub" } }
+--  We have 1 at-calls subprogram (F) and 2 wrapped (P and G).
+--  For each of them, there's one match for the wrapped signature, 
+--  and one for the update call.
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 27 "strub" } }
+--  The 6 matches above, plus:
+--  5*2: wm var decl, enter, call, leave and clobber for each wrapper;
+--  2*1: an extra leave and clobber for the exception paths in the wrappers.
+--  7*1: for the F call in G, including EH path.
diff --git a/gcc/testsuite/gnat.dg/strub_attr.ads b/gcc/testsuite/gnat.dg/strub_attr.ads
new file mode 100644
index 0000000000000..a94c23bf41833
--- /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/testsuite/gnat.dg/strub_disp.adb b/gcc/testsuite/gnat.dg/strub_disp.adb
new file mode 100644
index 0000000000000..3dbcc4a357cba
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_disp.adb
@@ -0,0 +1,64 @@
+--  { dg-do compile }
+
+procedure Strub_Disp is
+   package Foo is
+      type A is tagged null record;
+
+      procedure P (I : Integer; X : A);
+      pragma Machine_Attribute (P, "strub", "at-calls");
+      
+      function F (X : access A) return Integer;
+
+      type B is new A with null record;
+      
+      overriding
+      procedure P (I : Integer; X : B); -- { dg-error "requires the same .strub. mode" }
+
+      overriding
+      function F (X : access B) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+
+   end Foo;
+
+   package body Foo is
+      procedure P (I : Integer; X : A) is
+      begin
+	 null;
+      end;
+      
+      function F (X : access A) return Integer is (0);
+
+      overriding
+      procedure P (I : Integer; X : B) is
+      begin
+	 P (I, A (X));
+      end;
+      
+      overriding
+      function F (X : access B) return Integer is (1);
+   end Foo;
+
+   use Foo;
+
+   procedure Q (X : A'Class) is
+   begin
+      P (-1, X);
+   end;
+
+   XA : aliased A;
+   XB : aliased B;
+   I : Integer := 0;
+   XC : access A'Class;
+begin
+   Q (XA);
+   Q (XB);
+   
+   I := I + F (XA'Access);
+   I := I + F (XB'Access);
+
+   XC := XA'Access;
+   I := I + F (XC);
+
+   XC := XB'Access;
+   I := I + F (XC);
+end Strub_Disp;
diff --git a/gcc/testsuite/gnat.dg/strub_disp1.adb b/gcc/testsuite/gnat.dg/strub_disp1.adb
new file mode 100644
index 0000000000000..09756a74b7d81
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_disp1.adb
@@ -0,0 +1,79 @@
+--  { dg-do compile }
+--  { dg-options "-fdump-ipa-strub" }
+
+-- Check that at-calls dispatching calls are transformed.
+
+procedure Strub_Disp1 is
+   package Foo is
+      type A is tagged null record;
+
+      procedure P (I : Integer; X : A);
+      pragma Machine_Attribute (P, "strub", "at-calls");
+      
+      function F (X : access A) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+      type B is new A with null record;
+      
+      overriding
+      procedure P (I : Integer; X : B);
+      pragma Machine_Attribute (P, "strub", "at-calls");
+
+      overriding
+      function F (X : access B) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+   end Foo;
+
+   package body Foo is
+      procedure P (I : Integer; X : A) is
+      begin
+	 null;
+      end;
+      
+      function F (X : access A) return Integer is (0);
+
+      overriding
+      procedure P (I : Integer; X : B) is
+      begin
+	 P (I, A (X)); -- strub-at-calls non-dispatching call
+      end;
+      
+      overriding
+      function F (X : access B) return Integer is (1);
+   end Foo;
+
+   use Foo;
+
+   procedure Q (X : A'Class) is
+   begin
+      P (-1, X); -- strub-at-calls dispatching call.
+   end;
+
+   XA : aliased A;
+   XB : aliased B;
+   I : Integer := 0;
+   XC : access A'Class;
+begin
+   Q (XA);
+   Q (XB);
+   
+   I := I + F (XA'Access); -- strub-at-calls non-dispatching call
+   I := I + F (XB'Access); -- strub-at-calls non-dispatching call
+
+   XC := XA'Access;
+   I := I + F (XC); -- strub-at-calls dispatching call.
+
+   XC := XB'Access;
+   I := I + F (XC); -- strub-at-calls dispatching call.
+end Strub_Disp1;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 4 "strub" } }
+
+--  Count the strub-at-calls non-dispatching calls 
+--  (+ 2 each, for the matching prototypes)
+--  { dg-final { scan-ipa-dump-times "foo\.p \[(\]\[^\n\]*watermark" 3 "strub" } }
+--  { dg-final { scan-ipa-dump-times "foo\.f \[(\]\[^\n\]*watermark" 4 "strub" } }
+
+--  Count the strub-at-calls dispatching calls.
+--  { dg-final { scan-ipa-dump-times "_\[0-9\]* \[(\]\[^\n\]*watermark" 3 "strub" } }
diff --git a/gcc/testsuite/gnat.dg/strub_ind.adb b/gcc/testsuite/gnat.dg/strub_ind.adb
new file mode 100644
index 0000000000000..da56acaa957d2
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind.adb
@@ -0,0 +1,33 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict" }
+
+--  This is essentially the same test as strub_attr.adb, 
+--  but applying attributes to access types as well.
+--  That doesn't quite work yet, so we get an error we shouldn't get.
+
+package body Strub_Ind is
+   E : exception;
+
+   function G return Integer;
+
+   procedure P (X : Integer) is
+   begin
+      raise E;
+   end;
+   
+   function F (X : Integer) return Integer is
+   begin
+      return X * X;
+   end;
+   
+   function G return Integer is (FP (X));
+
+   type GT is access function return Integer;
+
+   type GT_SAC is access function return Integer;
+   pragma Machine_Attribute (GT_SAC, "strub", "at-calls");
+
+   GP : GT_SAC := GT_SAC (GT'(G'Access)); -- { dg-error "incompatible" }
+   -- pragma Machine_Attribute (GP, "strub", "at-calls");
+
+end Strub_Ind;
diff --git a/gcc/testsuite/gnat.dg/strub_ind.ads b/gcc/testsuite/gnat.dg/strub_ind.ads
new file mode 100644
index 0000000000000..99a65fc24b1ec
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind.ads
@@ -0,0 +1,17 @@
+package Strub_Ind is
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "internal");
+
+   function F (X : Integer) return Integer;
+   pragma Machine_Attribute (F, "strub");
+
+   X : Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+
+   type FT is access function (X : Integer) return Integer;
+   pragma Machine_Attribute (FT, "strub", "at-calls");
+
+   FP : FT := F'Access;
+   -- pragma Machine_Attribute (FP, "strub", "at-calls"); -- not needed
+
+end Strub_Ind;
diff --git a/gcc/testsuite/gnat.dg/strub_ind1.adb b/gcc/testsuite/gnat.dg/strub_ind1.adb
new file mode 100644
index 0000000000000..825e395e6819c
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind1.adb
@@ -0,0 +1,41 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
+
+--  This is essentially the same test as strub_attr.adb, 
+--  but with an explicit conversion.
+
+package body Strub_Ind1 is
+   E : exception;
+
+   type Strub_Int is New Integer;
+   pragma Machine_Attribute (Strub_Int, "strub");
+
+   function G return Integer;
+   pragma Machine_Attribute (G, "strub", "disabled");
+
+   procedure P (X : Integer) is
+   begin
+      raise E;
+   end;
+   
+   function G return Integer is (FP (X));
+
+   type GT is access function return Integer;
+   pragma Machine_Attribute (GT, "strub", "disabled");
+
+   type GT_SC is access function return Integer;
+   pragma Machine_Attribute (GT_SC, "strub", "callable");
+
+   GP : GT_SC := GT_SC (GT'(G'Access));
+   --  pragma Machine_Attribute (GP, "strub", "callable"); -- not needed.
+
+   function F (X : Integer) return Integer is
+   begin
+      return X * GP.all;
+   end;
+   
+end Strub_Ind1;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]disabled\[)\]\[)\]" 1 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 1 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
diff --git a/gcc/testsuite/gnat.dg/strub_ind1.ads b/gcc/testsuite/gnat.dg/strub_ind1.ads
new file mode 100644
index 0000000000000..d3f1273b3a6b9
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind1.ads
@@ -0,0 +1,17 @@
+package Strub_Ind1 is
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "internal");
+
+   function F (X : Integer) return Integer;
+   pragma Machine_Attribute (F, "strub");
+
+   X : aliased Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+
+   type FT is access function (X : Integer) return Integer;
+   pragma Machine_Attribute (FT, "strub", "at-calls");
+
+   FP : FT := F'Access;
+   pragma Machine_Attribute (FP, "strub", "at-calls");
+
+end Strub_Ind1;
diff --git a/gcc/testsuite/gnat.dg/strub_ind2.adb b/gcc/testsuite/gnat.dg/strub_ind2.adb
new file mode 100644
index 0000000000000..e918b39263117
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind2.adb
@@ -0,0 +1,34 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict" }
+
+--  This is essentially the same test as strub_attr.adb, 
+--  but with an explicit conversion.
+
+package body Strub_Ind2 is
+   E : exception;
+
+   function G return Integer;
+   pragma Machine_Attribute (G, "strub", "callable");
+
+   procedure P (X : Integer) is
+   begin
+      raise E;
+   end;
+   
+   function G return Integer is (FP (X));
+
+   type GT is access function return Integer;
+   pragma Machine_Attribute (GT, "strub", "callable");
+
+   type GT_SD is access function return Integer;
+   pragma Machine_Attribute (GT_SD, "strub", "disabled");
+
+   GP : GT_SD := GT_SD (GT'(G'Access));
+   --  pragma Machine_Attribute (GP, "strub", "disabled"); -- not needed.
+
+   function F (X : Integer) return Integer is
+   begin
+      return X * GP.all; --  { dg-error "using non-.strub. type" }
+   end;
+   
+end Strub_Ind2;
diff --git a/gcc/testsuite/gnat.dg/strub_ind2.ads b/gcc/testsuite/gnat.dg/strub_ind2.ads
new file mode 100644
index 0000000000000..e13865ec49c38
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind2.ads
@@ -0,0 +1,17 @@
+package Strub_Ind2 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");
+
+   type FT is access function (X : Integer) return Integer;
+   pragma Machine_Attribute (FT, "strub", "at-calls");
+
+   FP : FT := F'Access;
+   pragma Machine_Attribute (FP, "strub", "at-calls");
+
+end Strub_Ind2;
diff --git a/gcc/testsuite/gnat.dg/strub_intf.adb b/gcc/testsuite/gnat.dg/strub_intf.adb
new file mode 100644
index 0000000000000..728b85572b719
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_intf.adb
@@ -0,0 +1,93 @@
+--  { dg-do compile }
+
+--  Check that strub mode mismatches between overrider and overridden
+--  subprograms are reported.
+
+procedure Strub_Intf is
+   package Foo is
+      type TP is interface;
+      procedure P (I : Integer; X : TP) is abstract;
+      pragma Machine_Attribute (P, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+
+      type TF is interface;
+      function F (X : access TF) return Integer is abstract;
+
+      type TX is interface;
+      procedure P (I : Integer; X : TX) is abstract;
+
+      type TI is interface and TP and TF and TX;
+      --  When we freeze TI, we detect the mismatch between the
+      --  inherited P and another parent's P.  Because TP appears
+      --  before TX, we inherit P from TP, and report the mismatch at
+      --  the pragma inherited from TP against TX's P.  In contrast,
+      --  when we freeze TII below, since TX appears before TP, we
+      --  report the error at the line in which the inherited
+      --  subprogram is synthesized, namely the line below, against
+      --  the line of the pragma.
+
+      type TII is interface and TX and TP and TF; -- { dg-error "requires the same .strub. mode" }
+
+      function F (X : access TI) return Integer is abstract;
+      pragma Machine_Attribute (F, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+
+      type A is new TI with null record;
+
+      procedure P (I : Integer; X : A);
+      pragma Machine_Attribute (P, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+      
+      function F (X : access A) return Integer; -- { dg-error "requires the same .strub. mode" }
+
+      type B is new TI with null record;
+      
+      overriding
+      procedure P (I : Integer; X : B); -- { dg-error "requires the same .strub. mode" }
+
+      overriding
+      function F (X : access B) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+
+   end Foo;
+
+   package body Foo is
+      procedure P (I : Integer; X : A) is
+      begin
+	 null;
+      end;
+      
+      function F (X : access A) return Integer is (0);
+
+      overriding
+      procedure P (I : Integer; X : B) is
+      begin
+	 P (I, A (X));
+      end;
+      
+      overriding
+      function F (X : access B) return Integer is (1);
+
+   end Foo;
+
+   use Foo;
+
+   procedure Q (X : TX'Class) is
+   begin
+      P (-1, X);
+   end;
+
+   XA : aliased A;
+   XB : aliased B;
+   I : Integer := 0;
+   XC : access TI'Class;
+begin
+   Q (XA);
+   Q (XB);
+   
+   I := I + F (XA'Access);
+   I := I + F (XB'Access);
+
+   XC := XA'Access;
+   I := I + F (XC);
+
+   XC := XB'Access;
+   I := I + F (XC);
+end Strub_Intf;
diff --git a/gcc/testsuite/gnat.dg/strub_intf1.adb b/gcc/testsuite/gnat.dg/strub_intf1.adb
new file mode 100644
index 0000000000000..aa68fcd2c0b0e
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_intf1.adb
@@ -0,0 +1,86 @@
+--  { dg-do compile }
+--  { dg-options "-fdump-ipa-strub" }
+
+-- Check that at-calls dispatching calls to interfaces are transformed.
+
+procedure Strub_Intf1 is
+   package Foo is
+      type TX is Interface;
+      procedure P (I : Integer; X : TX) is abstract;
+      pragma Machine_Attribute (P, "strub", "at-calls");
+      function F (X : access TX) return Integer is abstract;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+      type A is new TX with null record;
+
+      procedure P (I : Integer; X : A);
+      pragma Machine_Attribute (P, "strub", "at-calls");
+      
+      function F (X : access A) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+      type B is new TX with null record;
+      
+      overriding
+      procedure P (I : Integer; X : B);
+      pragma Machine_Attribute (P, "strub", "at-calls");
+
+      overriding
+      function F (X : access B) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+   end Foo;
+
+   package body Foo is
+      procedure P (I : Integer; X : A) is
+      begin
+	 null;
+      end;
+      
+      function F (X : access A) return Integer is (0);
+
+      overriding
+      procedure P (I : Integer; X : B) is
+      begin
+	 P (I, A (X));
+      end;
+      
+      overriding
+      function F (X : access B) return Integer is (1);
+
+   end Foo;
+
+   use Foo;
+
+   procedure Q (X : TX'Class) is
+   begin
+      P (-1, X);
+   end;
+
+   XA : aliased A;
+   XB : aliased B;
+   I : Integer := 0;
+   XC : access TX'Class;
+begin
+   Q (XA);
+   Q (XB);
+   
+   I := I + F (XA'Access);
+   I := I + F (XB'Access);
+
+   XC := XA'Access;
+   I := I + F (XC);
+
+   XC := XB'Access;
+   I := I + F (XC);
+end Strub_Intf1;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 4 "strub" } }
+
+--  Count the strub-at-calls non-dispatching calls 
+--  (+ 2 each, for the matching prototypes)
+--  { dg-final { scan-ipa-dump-times "foo\.p \[(\]\[^\n\]*watermark" 3 "strub" } }
+--  { dg-final { scan-ipa-dump-times "foo\.f \[(\]\[^\n\]*watermark" 4 "strub" } }
+
+--  Count the strub-at-calls dispatching calls.
+--  { dg-final { scan-ipa-dump-times "_\[0-9\]* \[(\]\[^\n\]*watermark" 3 "strub" } }
diff --git a/gcc/testsuite/gnat.dg/strub_intf2.adb b/gcc/testsuite/gnat.dg/strub_intf2.adb
new file mode 100644
index 0000000000000..e8880dbc43730
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_intf2.adb
@@ -0,0 +1,55 @@
+--  { dg-do compile }
+
+--  Check that strub mode mismatches between overrider and overridden
+--  subprograms are reported even when the overriders for an
+--  interface's subprograms are inherited from a type that is not a
+--  descendent of the interface.
+
+procedure Strub_Intf2 is
+   package Foo is
+      type A is tagged null record;
+
+      procedure P (I : Integer; X : A);
+      pragma Machine_Attribute (P, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+      
+      function F (X : access A) return Integer;
+
+      type TX is Interface;
+
+      procedure P (I : Integer; X : TX) is abstract; 
+
+      function F (X : access TX) return Integer is abstract;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+      type B is new A and TX with null record; -- { dg-error "requires the same .strub. mode" }
+
+   end Foo;
+
+   package body Foo is
+      procedure P (I : Integer; X : A) is
+      begin
+	 null;
+      end;
+      
+      function F (X : access A) return Integer is (0);
+
+   end Foo;
+
+   use Foo;
+
+   procedure Q (X : TX'Class) is
+   begin
+      P (-1, X);
+   end;
+
+   XB : aliased B;
+   I : Integer := 0;
+   XC : access TX'Class;
+begin
+   Q (XB);
+   
+   I := I + F (XB'Access);
+
+   XC := XB'Access;
+   I := I + F (XC);
+end Strub_Intf2;
diff --git a/gcc/testsuite/gnat.dg/strub_renm.adb b/gcc/testsuite/gnat.dg/strub_renm.adb
new file mode 100644
index 0000000000000..217367e712d82
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_renm.adb
@@ -0,0 +1,21 @@
+--  { dg-do compile }
+
+procedure Strub_Renm is
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "at-calls");
+
+   function F return Integer;
+   pragma Machine_Attribute (F, "strub", "internal");
+
+   procedure Q (X : Integer) renames P; -- { dg-error "requires the same .strub. mode" }
+
+   function G return Integer renames F;
+   pragma Machine_Attribute (G, "strub", "callable"); -- { dg-error "requires the same .strub. mode" }
+
+   procedure P (X : Integer) is null;
+   function F return Integer is (0);
+
+begin
+   P (F);
+   Q (G);
+end Strub_Renm;
diff --git a/gcc/testsuite/gnat.dg/strub_renm1.adb b/gcc/testsuite/gnat.dg/strub_renm1.adb
new file mode 100644
index 0000000000000..a11adbfb5a9d6
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_renm1.adb
@@ -0,0 +1,32 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=relaxed -fdump-ipa-strub" }
+
+procedure Strub_Renm1 is
+   V : Integer := 0;
+   pragma Machine_Attribute (V, "strub");
+
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "at-calls");
+
+   function F return Integer;
+
+   procedure Q (X : Integer) renames P;
+   pragma Machine_Attribute (Q, "strub", "at-calls");
+
+   function G return Integer renames F;
+   pragma Machine_Attribute (G, "strub", "internal");
+
+   procedure P (X : Integer) is null;
+   function F return Integer is (0);
+
+begin
+   P (F);
+   Q (G);
+end Strub_Renm1;
+
+--  This is for P; Q is an alias.
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 1 "strub" } }
+
+--  This is *not* for G, but for Strub_Renm1.
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]wrapped\[)\]\[)\]" 1 "strub" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]wrapper\[)\]\[)\]" 1 "strub" } }
diff --git a/gcc/testsuite/gnat.dg/strub_renm2.adb b/gcc/testsuite/gnat.dg/strub_renm2.adb
new file mode 100644
index 0000000000000..c488c20826fdb
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_renm2.adb
@@ -0,0 +1,32 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strub" }
+
+procedure Strub_Renm2 is
+   V : Integer := 0;
+   pragma Machine_Attribute (V, "strub");
+
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "at-calls");
+
+   function F return Integer;
+
+   procedure Q (X : Integer) renames P;
+   pragma Machine_Attribute (Q, "strub", "at-calls");
+
+   type T is access function return Integer;
+
+   type TC is access function return Integer;
+   pragma Machine_Attribute (TC, "strub", "callable");
+
+   FCptr : constant TC := TC (T'(F'Access));
+
+   function G return Integer renames FCptr.all;
+   pragma Machine_Attribute (G, "strub", "callable");
+
+   procedure P (X : Integer) is null;
+   function F return Integer is (0);
+
+begin
+   P (F);  -- { dg-error "calling non-.strub." }
+   Q (G);  -- ok, G is callable.
+end Strub_Renm2;
diff --git a/gcc/testsuite/gnat.dg/strub_var.adb b/gcc/testsuite/gnat.dg/strub_var.adb
new file mode 100644
index 0000000000000..3d158de28031f
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_var.adb
@@ -0,0 +1,16 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
+
+-- We don't read from the automatic variable, but being an automatic
+--  variable, its presence should be enough for the procedure to get
+--  strub enabled.
+
+with Strub_Attr;
+procedure Strub_Var is
+   X : Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+begin
+   X := Strub_Attr.F (0);
+end Strub_Var;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 1 "strubm" } }
diff --git a/gcc/testsuite/gnat.dg/strub_var1.adb b/gcc/testsuite/gnat.dg/strub_var1.adb
new file mode 100644
index 0000000000000..6a504e09198b6
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_var1.adb
@@ -0,0 +1,20 @@
+--  { dg-do compile }
+
+with Strub_Attr;
+procedure Strub_Var1 is
+   type TA  -- { dg-warning "does not apply to elements" }
+      is array (1..2) of Integer;
+   pragma Machine_Attribute (TA, "strub");
+   
+   A : TA := (0, 0);  -- { dg-warning "does not apply to elements" }
+   
+   type TR is record  -- { dg-warning "does not apply to fields" }
+      M, N : Integer;
+   end record;
+   pragma Machine_Attribute (TR, "strub");
+   
+   R : TR := (0, 0);
+
+begin
+   A(2) := Strub_Attr.F (A(1));
+end Strub_Var1;

-- 
Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
   Free Software Activist                       GNU Toolchain Engineer
Disinformation flourishes because many people care deeply about injustice
but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* [PATCH v2 05/10] Introduce strub: builtins and runtime
  2022-07-29  6:16   ` [PATCH v2 00/10] Introduce " Alexandre Oliva
                       ` (3 preceding siblings ...)
  2022-07-29  6:26     ` [PATCH v2 04/10] Introduce strub: tests for C++ and Ada Alexandre Oliva
@ 2022-07-29  6:26     ` Alexandre Oliva
  2022-07-29  6:27     ` [PATCH v2 06/10] Introduce strub: attributes Alexandre Oliva
                       ` (6 subsequent siblings)
  11 siblings, 0 replies; 59+ messages in thread
From: Alexandre Oliva @ 2022-07-29  6:26 UTC (permalink / raw)
  To: gcc-patches
  Cc: Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Richard Biener, Jim Wilson


for  gcc/ChangeLog

	* builtins.def (BUILT_IN_STACK_ADDRESS): New.
	(BUILT_IN___STRUB_ENTER): New.
	(BUILT_IN___STRUB_UPDATE): New.
	(BUILT_IN___STRUB_LEAVE): New.
	* builtins.cc: Include ipa-strub.h.
	(STACK_STOPS, STACK_UNSIGNED): Define.
	(expand_builtin_stack_address): New.
	(expand_builtin_strub_enter): New.
	(expand_builtin_strub_update): New.
	(expand_builtin_strub_leave): New.
	(expand_bulitin): Call them.

for  libgcc/ChangeLog

	* Makefile.in (LIB2ADD): Add strub.c.
	* libgcc2.h (__strub_enter, __strub_update, __strub_leave):
	Declare.
	* strub.c: New.

diff --git a/gcc/builtins.cc b/gcc/builtins.cc
index b08b4365da36b..656186c308997 100644
--- a/gcc/builtins.cc
+++ b/gcc/builtins.cc
@@ -71,6 +71,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"
@@ -151,6 +152,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);
@@ -4968,6 +4970,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.  */
 
@@ -7263,6 +7515,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 005976f34e913..98763df73da8c 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/libgcc/Makefile.in b/libgcc/Makefile.in
index 1fe708a93f731..114076ad0a3f3 100644
--- a/libgcc/Makefile.in
+++ b/libgcc/Makefile.in
@@ -430,6 +430,9 @@ endif
 
 LIB2ADD += enable-execute-stack.c
 
+# Stack scrubbing infrastructure.
+LIB2ADD += $(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
 # (e.g. *-*-darwin*) only one copy of it is linked.
diff --git a/libgcc/libgcc2.h b/libgcc/libgcc2.h
index fc24ac34502bc..c45973f18d23b 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 0000000000000..90d3e82067b2f
--- /dev/null
+++ b/libgcc/strub.c
@@ -0,0 +1,112 @@
+/* Stack scrubbing infrastructure
+   Copyright (C) 2021-2022 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__ ("callable")))
+
+/* 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));
+}

-- 
Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
   Free Software Activist                       GNU Toolchain Engineer
Disinformation flourishes because many people care deeply about injustice
but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* [PATCH v2 06/10] Introduce strub: attributes
  2022-07-29  6:16   ` [PATCH v2 00/10] Introduce " Alexandre Oliva
                       ` (4 preceding siblings ...)
  2022-07-29  6:26     ` [PATCH v2 05/10] Introduce strub: builtins and runtime Alexandre Oliva
@ 2022-07-29  6:27     ` Alexandre Oliva
  2022-07-29  6:28     ` [PATCH v2 07/10] Introduce strub: infrastructure interfaces and adjustments Alexandre Oliva
                       ` (5 subsequent siblings)
  11 siblings, 0 replies; 59+ messages in thread
From: Alexandre Oliva @ 2022-07-29  6:27 UTC (permalink / raw)
  To: gcc-patches
  Cc: Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Richard Biener, Jim Wilson


Ada already has support for the strub attributes stubbed-out, and the
front-end code already has support for them and their effects in the
type system.

for  gcc/ChangeLog

	* attribs.cc: Include ipa-strub.h.
	(decl_attributes): Support applying attributes to function
	type, rather than pointer type, at handler's request.
	(comp_type_attributes): Combine strub_comptypes and target
	comp_type results.

for  gcc/c-family/ChangeLog

	* c-attribs.cc: Include ipa-strub.h.
	(handle_strub_attribute): New.
	(c_common_attribute_table): Add strub.

for  gcc/ada/ChangeLog

	* gcc-interface/utils.cc: Include ipa-strub.h.
	(handle_strub_attribute): New.
	(gnat_internal_attribute_table): Add strub.

diff --git a/gcc/attribs.cc b/gcc/attribs.cc
index fb89616ff296b..d559cfc1b9f4e 100644
--- a/gcc/attribs.cc
+++ b/gcc/attribs.cc
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "attribs.h"
 #include "fold-const.h"
+#include "ipa-strub.h"
 #include "stor-layout.h"
 #include "langhooks.h"
 #include "plugin.h"
@@ -774,12 +775,11 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  flags &= ~(int) ATTR_FLAG_TYPE_IN_PLACE;
 	}
 
-      if (spec->function_type_required && TREE_CODE (*anode) != FUNCTION_TYPE
-	  && TREE_CODE (*anode) != METHOD_TYPE)
+      if (spec->function_type_required
+	  && !FUNC_OR_METHOD_TYPE_P (*anode))
 	{
 	  if (TREE_CODE (*anode) == POINTER_TYPE
-	      && (TREE_CODE (TREE_TYPE (*anode)) == FUNCTION_TYPE
-		  || TREE_CODE (TREE_TYPE (*anode)) == METHOD_TYPE))
+	      && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
 	    {
 	      /* OK, this is a bit convoluted.  We can't just make a copy
 		 of the pointer type and modify its TREE_TYPE, because if
@@ -887,7 +887,24 @@ decl_attributes (tree *node, tree attributes, int flags,
 	      TYPE_NAME (tt) = *node;
 	    }
 
-	  *anode = cur_and_last_decl[0];
+	  if (*anode != cur_and_last_decl[0])
+	    {
+	      /* Even if !spec->function_type_required, allow the attribute
+		 handler to request the attribute to be applied to the function
+		 type, rather than to the function pointer type, by setting
+		 cur_and_last_decl[0] to the function type.  */
+	      if (!fn_ptr_tmp
+		  && POINTER_TYPE_P (*anode)
+		  && TREE_TYPE (*anode) == cur_and_last_decl[0]
+		  && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
+		{
+		  fn_ptr_tmp = TREE_TYPE (*anode);
+		  fn_ptr_quals = TYPE_QUALS (*anode);
+		  anode = &fn_ptr_tmp;
+		}
+	      *anode = cur_and_last_decl[0];
+	    }
+
 	  if (ret == error_mark_node)
 	    {
 	      warning (OPT_Wattributes, "%qE attribute ignored", name);
@@ -1491,9 +1508,20 @@ comp_type_attributes (const_tree type1, const_tree type2)
   if ((lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type1)) != NULL)
       ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) != NULL))
     return 0;
+  int strub_ret = strub_comptypes (CONST_CAST_TREE (type1),
+				   CONST_CAST_TREE (type2));
+  if (strub_ret == 0)
+    return strub_ret;
   /* As some type combinations - like default calling-convention - might
      be compatible, we have to call the target hook to get the final result.  */
-  return targetm.comp_type_attributes (type1, type2);
+  int target_ret = targetm.comp_type_attributes (type1, type2);
+  if (target_ret == 0)
+    return target_ret;
+  if (strub_ret == 2 || target_ret == 2)
+    return 2;
+  if (strub_ret == 1 && target_ret == 1)
+    return 1;
+  gcc_unreachable ();
 }
 
 /* PREDICATE acts as a function of type:
diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc
index e4f1d3542f378..08c7d71f827a2 100644
--- a/gcc/c-family/c-attribs.cc
+++ b/gcc/c-family/c-attribs.cc
@@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "common/common-target.h"
 #include "langhooks.h"
 #include "tree-inline.h"
+#include "ipa-strub.h"
 #include "toplev.h"
 #include "tree-iterator.h"
 #include "opts.h"
@@ -69,6 +70,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 *);
@@ -314,6 +316,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 },
@@ -1327,6 +1331,84 @@ 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 && FUNCTION_POINTER_TYPE_P (*node))
+    *node = TREE_TYPE (*node);
+
+  if (args && FUNC_OR_METHOD_TYPE_P (*node))
+    {
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
+	{
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
+
+	case 0:
+	  warning (OPT_Wattributes,
+		   "%qE attribute ignored because of argument %qE",
+		   name, TREE_VALUE (args));
+	  *no_add_attrs = true;
+	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
+	}
+
+      args = TREE_CHAIN (args);
+    }
+
+  if (args)
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
+
+  /* Warn about unmet expectations that the strub attribute works like a
+     qualifier.  ??? Could/should we extend it to the element/field types
+     here?  */
+  if (TREE_CODE (*node) == ARRAY_TYPE
+      || VECTOR_TYPE_P (*node)
+      || TREE_CODE (*node) == COMPLEX_TYPE)
+    warning (OPT_Wattributes,
+	     "attribute %qE does not apply to elements"
+	     " of non-scalar type %qT",
+	     name, *node);
+  else if (RECORD_OR_UNION_TYPE_P (*node))
+    warning (OPT_Wattributes,
+	     "attribute %qE does not apply to fields"
+	     " of aggregate type %qT",
+	     name, *node);
+
+  /* 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 += 2;
+
+  return NULL_TREE;
+}
+
 /* Handle a "noinline" attribute; arguments as in
    struct attribute_spec.handler.  */
 
diff --git a/gcc/ada/gcc-interface/utils.cc b/gcc/ada/gcc-interface/utils.cc
index a57143021a79e..36b1345530d71 100644
--- a/gcc/ada/gcc-interface/utils.cc
+++ b/gcc/ada/gcc-interface/utils.cc
@@ -39,6 +39,7 @@
 #include "varasm.h"
 #include "toplev.h"
 #include "opts.h"
+#include "ipa-strub.h"
 #include "output.h"
 #include "debug.h"
 #include "convert.h"
@@ -6601,9 +6602,77 @@ handle_no_stack_protector_attribute (tree *node, tree name, tree, int,
    struct attribute_spec.handler.  */
 
 static tree
-handle_strub_attribute (tree *, tree, tree, int, bool *no_add_attrs)
+handle_strub_attribute (tree *node, tree name,
+			tree args,
+			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
-  *no_add_attrs = true;
+  bool enable = true;
+
+  if (args && FUNCTION_POINTER_TYPE_P (*node))
+    *node = TREE_TYPE (*node);
+
+  if (args && FUNC_OR_METHOD_TYPE_P (*node))
+    {
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
+	{
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
+
+	case 0:
+	  warning (OPT_Wattributes,
+		   "%qE attribute ignored because of argument %qE",
+		   name, TREE_VALUE (args));
+	  *no_add_attrs = true;
+	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
+	}
+
+      args = TREE_CHAIN (args);
+    }
+
+  if (args)
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
+
+  /* Warn about unmet expectations that the strub attribute works like a
+     qualifier.  ??? Could/should we extend it to the element/field types
+     here?  */
+  if (TREE_CODE (*node) == ARRAY_TYPE
+      || VECTOR_TYPE_P (*node)
+      || TREE_CODE (*node) == COMPLEX_TYPE)
+    warning (OPT_Wattributes,
+	     "attribute %qE does not apply to elements"
+	     " of non-scalar type %qT",
+	     name, *node);
+  else if (RECORD_OR_UNION_TYPE_P (*node))
+    warning (OPT_Wattributes,
+	     "attribute %qE does not apply to fields"
+	     " of aggregate type %qT",
+	     name, *node);
+
+  /* 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 += 2;
+
   return NULL_TREE;
 }
 

-- 
Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
   Free Software Activist                       GNU Toolchain Engineer
Disinformation flourishes because many people care deeply about injustice
but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* [PATCH v2 07/10] Introduce strub: infrastructure interfaces and adjustments
  2022-07-29  6:16   ` [PATCH v2 00/10] Introduce " Alexandre Oliva
                       ` (5 preceding siblings ...)
  2022-07-29  6:27     ` [PATCH v2 06/10] Introduce strub: attributes Alexandre Oliva
@ 2022-07-29  6:28     ` Alexandre Oliva
  2022-07-29  6:28     ` [PATCH v2 08/10] Introduce strub: strub modes Alexandre Oliva
                       ` (4 subsequent siblings)
  11 siblings, 0 replies; 59+ messages in thread
From: Alexandre Oliva @ 2022-07-29  6:28 UTC (permalink / raw)
  To: gcc-patches
  Cc: Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Richard Biener, Jim Wilson


Introduce the new strub passes, adjust other passes and front-end
declaration for strub.

for  gcc/ChangeLog

	* Makefile.in (OBJS): Add ipa-strub.o.
	* ipa-inline.cc: Include ipa-strub.h.
	(can_inline_edge_p): Test strub_inlinable_to_p.
	* ipa-split.cc: Include ipa-strub.h.
	(execute_split_functions): Test strub_splittable_p.
	* ipa-strub.h: New.
	* passes.def: Add strub_mode and strub passes.
	* tree-cfg.cc (gimple_verify_flow_info): Note on debug stmts.
	* tree-pass.h (make_pass_ipa_strub_mode): Declare.
	(make_pass_ipa_strub): Declare.
	(make_pass_ipa_function_and_variable_visibility): Fix
	formatting.
	* tree-ssa-ccp.cc (optimize_stack_restore): Keep restores
	before strub leave.
	* multiple_target.cc (pass_target_clone::gate): Test seen_error.

for  gcc/ada/ChangeLog

	* gcc-interface/trans.cc: Include ipa-strub.h.
	(gigi): Make internal decls for targets of compiler-generated
	calls strub-callable too.
	(build_raise_check): Likewise.

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 203f0a15187d2..4100531d73ae7 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1480,6 +1480,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.cc b/gcc/ada/gcc-interface/trans.cc
index c1dd567b2e4e2..e3553a7a87bfc 100644
--- a/gcc/ada/gcc-interface/trans.cc
+++ b/gcc/ada/gcc-interface/trans.cc
@@ -69,6 +69,21 @@
 #include "ada-tree.h"
 #include "gigi.h"
 
+/* The following #include is for strub_make_callable.
+
+   This function marks a function as safe to call from strub contexts.  We mark
+   Ada subprograms that may be called implicitly by the compiler, and that won't
+   leave on the stack caller data passed to them.  This stops implicit calls
+   introduced in subprograms that have their stack scrubbed from being flagged
+   as unsafe, even in -fstrub=strict mode.
+
+   These subprograms are also marked with the strub(callable) attribute in Ada
+   sources, but their declarations aren't necessarily imported by GNAT, or made
+   visible to gigi, in units that end up relying on them.  So when gigi
+   introduces their declarations on its own, it must also add the attribute, by
+   calling strub_make_callable.  */
+#include "ipa-strub.h"
+
 /* We should avoid allocating more than ALLOCA_THRESHOLD bytes via alloca,
    for fear of running out of stack space.  If we need more, we use xmalloc
    instead.  */
@@ -449,6 +464,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)
     {
@@ -461,6 +477,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.  */
@@ -716,6 +733,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/ipa-inline.cc b/gcc/ipa-inline.cc
index 14969198cde1c..0674fe138574d 100644
--- a/gcc/ipa-inline.cc
+++ b/gcc/ipa-inline.cc
@@ -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;
@@ -397,6 +398,11 @@ can_inline_edge_p (struct cgraph_edge *e, bool report,
       inlinable = false;
     }
 
+  if (inlinable && !strub_inlinable_to_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.cc b/gcc/ipa-split.cc
index 16734617d0381..b3b9963f13669 100644
--- a/gcc/ipa-split.cc
+++ b/gcc/ipa-split.cc
@@ -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.  */
 
@@ -1810,6 +1811,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.h b/gcc/ipa-strub.h
new file mode 100644
index 0000000000000..29869fadfa6c9
--- /dev/null
+++ b/gcc/ipa-strub.h
@@ -0,0 +1,45 @@
+/* strub (stack scrubbing) infrastructure.
+   Copyright (C) 2021-2022 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 CALLEE can be inlined into CALLER, as far as stack scrubbing
+   constraints are concerned.  CALLEE doesn't have to be called directly by
+   CALLER, but the returned value says nothing about intervening functions.  */
+extern bool strub_inlinable_to_p (cgraph_node *callee, cgraph_node *caller);
+
+/* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
+extern bool strub_splittable_p (cgraph_node *node);
+
+/* 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);
+
+/* Return zero iff ID is NOT an acceptable parameter for a user-supplied strub
+   attribute for a function.  Otherwise, return >0 if it enables strub, <0 if it
+   does not.  Return +/-1 if the attribute-modified type is compatible with the
+   type without the attribute, or +/-2 if it is not compatible.  */
+extern int strub_validate_fn_attr_parm (tree id);
+
+/* Like comptypes, return 0 if t1 and t2 are not compatible, 1 if they are
+   compatible, and 2 if they are nearly compatible.  Same strub mode is
+   compatible, interface-compatible strub modes are nearly compatible.  */
+extern int strub_comptypes (tree t1, tree t2);
diff --git a/gcc/multiple_target.cc b/gcc/multiple_target.cc
index 3e2d26882c8e9..7cbd0a571e1ff 100644
--- a/gcc/multiple_target.cc
+++ b/gcc/multiple_target.cc
@@ -536,7 +536,7 @@ public:
 bool
 pass_target_clone::gate (function *)
 {
-  return true;
+  return !seen_error ();
 }
 
 } // anon namespace
diff --git a/gcc/passes.def b/gcc/passes.def
index 6bb92efacd451..513c5c29cd281 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -52,6 +52,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);
@@ -113,6 +114,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/tree-cfg.cc b/gcc/tree-cfg.cc
index 5bcf78198e78b..6144b9ee4fca4 100644
--- a/gcc/tree-cfg.cc
+++ b/gcc/tree-cfg.cc
@@ -5658,6 +5658,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 4dfe05ed8e0de..ecf1f9ef28b12 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -502,8 +502,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.cc b/gcc/tree-ssa-ccp.cc
index 85c046078249c..8d04e55ba6523 100644
--- a/gcc/tree-ssa-ccp.cc
+++ b/gcc/tree-ssa-ccp.cc
@@ -3054,7 +3054,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))

-- 
Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
   Free Software Activist                       GNU Toolchain Engineer
Disinformation flourishes because many people care deeply about injustice
but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* [PATCH v2 08/10] Introduce strub: strub modes
  2022-07-29  6:16   ` [PATCH v2 00/10] Introduce " Alexandre Oliva
                       ` (6 preceding siblings ...)
  2022-07-29  6:28     ` [PATCH v2 07/10] Introduce strub: infrastructure interfaces and adjustments Alexandre Oliva
@ 2022-07-29  6:28     ` Alexandre Oliva
  2022-07-29  6:30     ` [PATCH v2 09/10] Introduce strub: strubm (mode assignment) pass Alexandre Oliva
                       ` (3 subsequent siblings)
  11 siblings, 0 replies; 59+ messages in thread
From: Alexandre Oliva @ 2022-07-29  6:28 UTC (permalink / raw)
  To: gcc-patches
  Cc: Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Richard Biener, Jim Wilson


This initial fragment of ipa-strub.cc covers strub modes and their
internal representation.

for  gcc/ChangeLog

	* ipa-strub.cc: New.

diff --git a/gcc/ipa-strub.cc b/gcc/ipa-strub.cc
new file mode 100644
index 0000000000000..d61b7e2e36e43
--- /dev/null
+++ b/gcc/ipa-strub.cc
@@ -0,0 +1,3489 @@
+/* strub (stack scrubbing) support.
+   Copyright (C) 2021-2022 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,
+
+};
+
+/* Look up a strub attribute in TYPE, and return it.  */
+
+static tree
+get_strub_attr_from_type (tree type)
+{
+  return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
+}
+
+/* Look up a strub attribute in DECL or in its type, and return it.  */
+
+static tree
+get_strub_attr_from_decl (tree decl)
+{
+  tree ret = lookup_attribute ("strub", DECL_ATTRIBUTES (decl));
+  if (ret)
+    return ret;
+  return get_strub_attr_from_type (TREE_TYPE (decl));
+}
+
+/* Define a function to cache identifier ID, to be used as a strub attribute
+   parameter for a strub mode named after NAME.  */
+#define DEF_STRUB_IDS(NAME, ID)				\
+static inline tree get_strub_mode_id_ ## NAME () {	\
+  static tree identifier = NULL_TREE;			\
+  if (!identifier)					\
+    identifier = get_identifier (ID);			\
+  return identifier;					\
+}
+/* Same as DEF_STRUB_IDS, but use the string expansion of NAME as ID.  */
+#define DEF_STRUB_ID(NAME)				\
+DEF_STRUB_IDS (NAME, #NAME)
+
+/* Define functions for each of the strub mode identifiers.
+   Expose dashes rather than underscores.  */
+DEF_STRUB_ID (disabled)
+DEF_STRUB_IDS (at_calls, "at-calls")
+DEF_STRUB_ID (internal)
+DEF_STRUB_ID (callable)
+DEF_STRUB_ID (wrapped)
+DEF_STRUB_ID (wrapper)
+DEF_STRUB_ID (inlinable)
+DEF_STRUB_IDS (at_calls_opt, "at-calls-opt")
+
+/* Release the temporary macro names.  */
+#undef DEF_STRUB_IDS
+#undef DEF_STRUB_ID
+
+/* Return the identifier corresponding to strub MODE.  */
+
+static tree
+get_strub_mode_attr_parm (enum strub_mode mode)
+{
+  switch (mode)
+    {
+    case STRUB_DISABLED:
+      return get_strub_mode_id_disabled ();
+
+    case STRUB_AT_CALLS:
+      return get_strub_mode_id_at_calls ();
+
+    case STRUB_INTERNAL:
+      return get_strub_mode_id_internal ();
+
+    case STRUB_CALLABLE:
+      return get_strub_mode_id_callable ();
+
+    case STRUB_WRAPPED:
+      return get_strub_mode_id_wrapped ();
+
+    case STRUB_WRAPPER:
+      return get_strub_mode_id_wrapper ();
+
+    case STRUB_INLINABLE:
+      return get_strub_mode_id_inlinable ();
+
+    case STRUB_AT_CALLS_OPT:
+      return get_strub_mode_id_at_calls_opt ();
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+/* Return the parmeters (TREE_VALUE) for a strub attribute of MODE.
+   We know we use a single parameter, so we bypass the creation of a
+   tree list.  */
+
+static tree
+get_strub_mode_attr_value (enum strub_mode mode)
+{
+  return get_strub_mode_attr_parm (mode);
+}
+
+/* Determine whether ID is a well-formed strub mode-specifying attribute
+   parameter for a function (type).  Only user-visible modes are accepted, and
+   ID must be non-NULL.
+
+   For unacceptable parms, return 0, otherwise a nonzero value as below.
+
+   If the parm enables strub, return positive, otherwise negative.
+
+   If the affected type must be a distinct, incompatible type,return an integer
+   of absolute value 2, otherwise 1.  */
+
+int
+strub_validate_fn_attr_parm (tree id)
+{
+  int ret;
+  const char *s = NULL;
+  size_t len = 0;
+
+  /* do NOT test for NULL.  This is only to be called with non-NULL arguments.
+     We assume that the strub parameter applies to a function, because only
+     functions accept an explicit argument.  If we accepted NULL, and we
+     happened to be called to verify the argument for a variable, our return
+     values would be wrong.  */
+  if (TREE_CODE (id) == STRING_CST)
+    {
+      s = TREE_STRING_POINTER (id);
+      len = TREE_STRING_LENGTH (id) - 1;
+    }
+  else if (TREE_CODE (id) == IDENTIFIER_NODE)
+    {
+      s = IDENTIFIER_POINTER (id);
+      len = IDENTIFIER_LENGTH (id);
+    }
+  else
+    return 0;
+
+  enum strub_mode mode;
+
+  if (len != 8)
+    return 0;
+
+  switch (s[0])
+    {
+    case 'd':
+      mode = STRUB_DISABLED;
+      ret = -1;
+      break;
+
+    case 'a':
+      mode = STRUB_AT_CALLS;
+      ret = 2;
+      break;
+
+    case 'i':
+      mode = STRUB_INTERNAL;
+      ret = 1;
+      break;
+
+    case 'c':
+      mode = STRUB_CALLABLE;
+      ret = -2;
+      break;
+
+    default:
+      /* Other parms are for internal use only.  */
+      return 0;
+    }
+
+  tree mode_id = get_strub_mode_attr_parm (mode);
+
+  if (TREE_CODE (id) == IDENTIFIER_NODE
+      ? id != mode_id
+      : strncmp (s, IDENTIFIER_POINTER (mode_id), len) != 0)
+    return 0;
+
+  return ret;
+}
+
+/* Return the strub mode from STRUB_ATTR.  VAR_P should be TRUE if the attribute
+   is taken from a variable, rather than from a function, or a type thereof.  */
+
+static enum strub_mode
+get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
+{
+  enum strub_mode mode = STRUB_DISABLED;
+
+  if (strub_attr)
+    {
+      if (!TREE_VALUE (strub_attr))
+	mode = !var_p ? STRUB_AT_CALLS : STRUB_INTERNAL;
+      else
+	{
+	  gcc_checking_assert (!var_p);
+	  tree id = TREE_VALUE (strub_attr);
+	  if (TREE_CODE (id) == TREE_LIST)
+	    id = TREE_VALUE (id);
+	  const char *s = (TREE_CODE (id) == STRING_CST
+			   ? TREE_STRING_POINTER (id)
+			   : IDENTIFIER_POINTER (id));
+	  size_t len = (TREE_CODE (id) == STRING_CST
+			? TREE_STRING_LENGTH (id) - 1
+			: IDENTIFIER_LENGTH (id));
+
+	  switch (len)
+	    {
+	    case 7:
+	      switch (s[6])
+		{
+		case 'r':
+		  mode = STRUB_WRAPPER;
+		  break;
+
+		case 'd':
+		  mode = STRUB_WRAPPED;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 8:
+	      switch (s[0])
+		{
+		case 'd':
+		  mode = STRUB_DISABLED;
+		  break;
+
+		case 'a':
+		  mode = STRUB_AT_CALLS;
+		  break;
+
+		case 'i':
+		  mode = STRUB_INTERNAL;
+		  break;
+
+		case 'c':
+		  mode = STRUB_CALLABLE;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 9:
+	      mode = STRUB_INLINABLE;
+	      break;
+
+	    case 12:
+	      mode = STRUB_AT_CALLS_OPT;
+	      break;
+
+	    default:
+	      gcc_unreachable ();
+	    }
+
+	  gcc_checking_assert (TREE_CODE (id) == IDENTIFIER_NODE
+			       ? id == get_strub_mode_attr_parm (mode)
+			       : strncmp (IDENTIFIER_POINTER
+					  (get_strub_mode_attr_parm (mode)),
+					  s, len) == 0);
+	}
+    }
+
+  return mode;
+}
+
+/* Look up, decode and return the strub mode associated with FNDECL.  */
+
+static enum strub_mode
+get_strub_mode_from_fndecl (tree fndecl)
+{
+  return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
+}
+
+/* Look up, decode and return the strub mode associated with NODE.  */
+
+static enum strub_mode
+get_strub_mode (cgraph_node *node)
+{
+  return get_strub_mode_from_fndecl (node->decl);
+}
+
+/* Look up, decode and return the strub mode associated with TYPE.  */
+
+static enum strub_mode
+get_strub_mode_from_type (tree type)
+{
+  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 >= -1 && !var_p)
+    return STRUB_CALLABLE;
+
+  return STRUB_DISABLED;
+}
+
+\f

-- 
Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
   Free Software Activist                       GNU Toolchain Engineer
Disinformation flourishes because many people care deeply about injustice
but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* [PATCH v2 09/10] Introduce strub: strubm (mode assignment) pass
  2022-07-29  6:16   ` [PATCH v2 00/10] Introduce " Alexandre Oliva
                       ` (7 preceding siblings ...)
  2022-07-29  6:28     ` [PATCH v2 08/10] Introduce strub: strub modes Alexandre Oliva
@ 2022-07-29  6:30     ` Alexandre Oliva
  2022-07-29  6:34     ` [PATCH v2 10/10] Introduce strub: strub pass Alexandre Oliva
                       ` (2 subsequent siblings)
  11 siblings, 0 replies; 59+ messages in thread
From: Alexandre Oliva @ 2022-07-29  6:30 UTC (permalink / raw)
  To: gcc-patches
  Cc: Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Richard Biener, Jim Wilson


This middle fragment of ipa-strub.cc covers strub mode selection and
assignment logic, and most of the pass that performs that assignment.

+/* Return TRUE iff NODE calls builtin va_start.  */
+
+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;
+}
+
+/* Return TRUE iff NODE calls builtin apply_args, and optionally REPORT it.  */
+
+static bool
+calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
+{
+  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;
+}
+
+/* Return TRUE iff NODE carries the always_inline attribute.  */
+
+static inline bool
+strub_always_inline_p (cgraph_node *node)
+{
+  return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
+}
+
+/* Return TRUE iff NODE is potentially eligible for any strub-enabled mode, and
+   optionally REPORT the reasons for ineligibility.  */
+
+static inline bool
+can_strub_p (cgraph_node *node, bool report = false)
+{
+  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;
+}
+
+/* Return TRUE iff NODE is eligible for at-calls strub, and optionally REPORT
+   the reasons for ineligibility.  Besides general non-eligibility for
+   strub-enabled modes, at-calls rules out calling builtin apply_args.  */
+
+static bool
+can_strub_at_calls_p (cgraph_node *node, bool report = false)
+{
+  bool result = !report || can_strub_p (node, report);
+
+  if (!result && !report)
+    return result;
+
+  return !calls_builtin_apply_args_p (node, report);
+}
+
+/* Return TRUE iff the called function (pointer or, if available,
+   decl) undergoes a significant type conversion for the call.  Strub
+   mode changes between function types, and other non-useless type
+   conversions, are regarded as significant.  When the function type
+   is overridden, the effective strub mode for the call is that of the
+   call fntype, rather than that of the pointer or of the decl.
+   Functions called with type overrides cannot undergo type changes;
+   it's as if their address was taken, so they're considered
+   non-viable for implicit at-calls strub mode.  */
+
+static inline bool
+strub_call_fntype_override_p (const gcall *gs)
+{
+  if (gimple_call_internal_p (gs))
+    return false;
+  tree fn_type = TREE_TYPE (TREE_TYPE (gimple_call_fn (gs)));
+  if (tree decl = gimple_call_fndecl (gs))
+    fn_type = TREE_TYPE (decl);
+
+  /* We do NOT want to take the mode from the decl here.  This
+     function is used to tell whether we can change the strub mode of
+     a function, and whether the effective mode for the call is to be
+     taken from the decl or from an overrider type.  When the strub
+     mode is explicitly declared, or overridden with a type cast, the
+     difference will be noticed in function types.  However, if the
+     strub mode is implicit due to e.g. strub variables or -fstrub=*
+     command-line flags, we will adjust call types along with function
+     types.  In either case, the presence of type or strub mode
+     overriders in calls will prevent a function from having its strub
+     modes changed in ways that would imply type changes, but taking
+     strub modes from decls would defeat this, since we set strub
+     modes and then call this function to tell whether the original
+     type was overridden to decide whether to adjust the call.  We
+     need the answer to be about the type, not the decl.  */
+  enum strub_mode mode = get_strub_mode_from_type (fn_type);
+  return (get_strub_mode_from_type (gs->u.fntype) != mode
+	  || !useless_type_conversion_p (gs->u.fntype, fn_type));
+}
+
+/* Return TRUE iff NODE is called directly with a type override.  */
+
+static bool
+called_directly_with_type_override_p (cgraph_node *node, void *)
+{
+  for (cgraph_edge *e = node->callers; e; e = e->next_caller)
+    if (strub_call_fntype_override_p (e->call_stmt))
+      return true;
+
+  return false;
+}
+
+/* Return TRUE iff NODE or any other nodes aliased to it are called
+   with type overrides.  We can't safely change the type of such
+   functions.  */
+
+static bool
+called_with_type_override_p (cgraph_node *node)
+{
+  return (node->call_for_symbol_thunks_and_aliases
+	  (called_directly_with_type_override_p, NULL, true, true));
+}
+
+/* Symbolic macro for the max number of arguments that internal strub may add to
+   a function.  */
+
+#define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
+
+/* We can't perform internal strubbing if the function body involves certain
+   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 references are not preserved when copying.  If referenced
+	 in nested functions, as in 920415-1.c and 920721-4.c their decls get
+	 remapped independently.  The exclusion below might be too broad, in
+	 that we might be able to support correctly cases in which the labels
+	 are only used internally in a function, but disconnecting forced labels
+	 from their original declarations is undesirable in general.  */
+      basic_block bb;
+      FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+	for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+	     !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);
+
+	    if (!FORCED_LABEL (target))
+	      continue;
+
+	    result = false;
+
+	    if (!report)
+	      return result;
+
+	    sorry_at (gimple_location (label_stmt),
+		      "internal %<strub%> does not support forced 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;
+}
+
+/* Return TRUE iff NODE has any strub-requiring local variable, or accesses (as
+   in reading) any variable through a strub-requiring type.  */
+
+static bool
+strub_from_body_p (cgraph_node *node)
+{
+  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-requiring types.
+     ??? Compound types don't propagate the strub requirement to
+     component types.  */
+  basic_block bb;
+  FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+	 !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;
+    }
+}
+
+/* Compute the strub mode to be used for NODE.  STRUB_ATTR should be the strub
+   attribute,found for NODE, if any.  */
+
+static enum strub_mode
+compute_strub_mode (cgraph_node *node, tree strub_attr)
+{
+  enum strub_mode req_mode = get_strub_mode_from_attr (strub_attr);
+
+  gcc_checking_assert (flag_strub >= -2 && flag_strub <= 3);
+
+  /* Symbolic encodings of the -fstrub-* flags.  */
+  /* Enable strub when explicitly requested through attributes to functions or
+     variables, reporting errors if the requests cannot be satisfied.  */
+  const bool strub_flag_auto = flag_strub < 0;
+  /* strub_flag_auto with strub call verification; without this, functions are
+     implicitly callable.  */
+  const bool strub_flag_strict = flag_strub < -1;
+  /* Disable strub altogether, ignore attributes entirely.  */
+  const bool strub_flag_disabled = flag_strub == 0;
+  /* On top of _auto, also enable strub implicitly for functions that can
+     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_strict
+	      || 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->externally_visible
+		   || (node->binds_to_current_def_p ()
+		       && node->can_be_local_p ()))
+	       && node->only_called_directly_p ()
+	       && !called_with_type_override_p (node))));
+  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)
+       : (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;
+}
+
+/* Set FNDT's strub mode to callable.
+   FNDT may be a function decl or a function type.  */
+
+void
+strub_make_callable (tree fndt)
+{
+  strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
+}
+
+/* Set NODE to strub MODE.  Report incompatibilities between MODE and the mode
+   requested through explicit attributes, and cases of non-eligibility.  */
+
+static void
+set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
+{
+  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 %qE selected for %qD, when %qE was requested",
+		    get_strub_mode_attr_parm (mode),
+		    node->decl,
+		    get_strub_mode_attr_parm (req_mode));
+	  if (node->alias)
+	    {
+	      cgraph_node *target = node->ultimate_alias_target ();
+	      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);
+}
+
+/* Compute and set NODE's strub mode.  */
+
+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);
+}
+
+\f
+/* Non-strub functions shouldn't be called from within strub contexts,
+   except through callable ones.  Always inline strub functions can
+   only be called from strub functions.  */
+
+static bool
+strub_callable_from_p (strub_mode caller_mode, strub_mode callee_mode)
+{
+  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 >= -1);
+
+    case STRUB_DISABLED:
+      return false;
+
+    case STRUB_CALLABLE:
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  return true;
+}
+
+/* Return TRUE iff CALLEE can be inlined into CALLER.  We wish to avoid inlining
+   WRAPPED functions back into their WRAPPERs.  More generally, we wish to avoid
+   inlining strubbed functions into non-strubbed ones.  CALLER doesn't have to
+   be an immediate caller of CALLEE: the immediate caller may have already been
+   cloned for inlining, and then CALLER may be further up the original call
+   chain.  ???  It would be nice if our own caller would retry inlining callee
+   if caller gets inlined.  */
+
+bool
+strub_inlinable_to_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:
+      /* When we consider inlining, we've already verified callability, so we
+	 can even inline callable and then disabled into a strub context.  That
+	 will get strubbed along with the context, so it's hopefully not a
+	 problem.  */
+      return true;
+
+    default:
+      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 types T1 and T2 are strub-compatible.  Return 1 if the strub modes
+   are the same, 2 if they are interchangeable, and 0 otherwise.  */
+
+int
+strub_comptypes (tree t1, tree t2)
+{
+  if (TREE_CODE (t1) != TREE_CODE (t2))
+    return 0;
+
+  enum strub_mode m1 = get_strub_mode_from_type (t1);
+  enum strub_mode m2 = get_strub_mode_from_type (t2);
+
+  if (m1 == m2)
+    return 1;
+
+  /* We're dealing with types, so only strub modes that can be selected by
+     attributes in the front end matter.  If either mode is at-calls (for
+     functions) or internal (for variables), the conversion is not
+     compatible.  */
+  bool var_p = !FUNC_OR_METHOD_TYPE_P (t1);
+  enum strub_mode mr = var_p ? STRUB_INTERNAL : STRUB_AT_CALLS;
+  if (m1 == mr || m2 == mr)
+    return 0;
+
+  return 2;
+}
+
+/* Return the effective strub mode used for CALL, and set *TYPEP to
+   the effective type used for the call.  The effective type and mode
+   are those of the callee, unless the call involves a typecast.  */
+
+static enum strub_mode
+effective_strub_mode_for_call (gcall *call, tree *typep)
+{
+  tree type;
+  enum strub_mode mode;
+
+  if (strub_call_fntype_override_p (call))
+    {
+      type = gimple_call_fntype (call);
+      mode = get_strub_mode_from_type (type);
+    }
+  else
+    {
+      type = TREE_TYPE (TREE_TYPE (gimple_call_fn (call)));
+      tree decl = gimple_call_fndecl (call);
+      if (decl)
+	mode = get_strub_mode_from_fndecl (decl);
+      else
+	mode = get_strub_mode_from_type (type);
+    }
+
+  if (typep)
+    *typep = type;
+
+  return mode;
+}
+
+/* Create a distinct copy of the type of NODE's function, and change
+   the fntype of all calls to it with the same main type to the new
+   type.  */
+
+static void
+distinctify_node_type (cgraph_node *node)
+{
+  tree old_type = TREE_TYPE (node->decl);
+  tree new_type = build_distinct_type_copy (old_type);
+  tree new_ptr_type = NULL_TREE;
+
+  /* Remap any calls to node->decl that use old_type, or a variant
+     thereof, to new_type as well.  We don't look for aliases, their
+     declarations will have their types changed independently, and
+     we'll adjust their fntypes then.  */
+  for (cgraph_edge *e = node->callers; e; e = e->next_caller)
+    {
+      tree fnaddr = gimple_call_fn (e->call_stmt);
+      gcc_checking_assert (TREE_CODE (fnaddr) == ADDR_EXPR
+			   && TREE_OPERAND (fnaddr, 0) == node->decl);
+      if (strub_call_fntype_override_p (e->call_stmt))
+	continue;
+      if (!new_ptr_type)
+	new_ptr_type = build_pointer_type (new_type);
+      TREE_TYPE (fnaddr) = new_ptr_type;
+      gimple_call_set_fntype (e->call_stmt, new_type);
+    }
+
+  TREE_TYPE (node->decl) = new_type;
+}
+
+/* Return TRUE iff TYPE and any variants have the same strub mode.  */
+
+static bool
+same_strub_mode_in_variants_p (tree type)
+{
+  enum strub_mode mode = get_strub_mode_from_type (type);
+
+  for (tree other = TYPE_MAIN_VARIANT (type);
+       other != NULL_TREE; other = TYPE_NEXT_VARIANT (other))
+    if (type != other && mode != get_strub_mode_from_type (other))
+      return false;
+
+  /* Check that the canonical type, if set, either is in the same
+     variant chain, or has the same strub mode as type.  Also check
+     the variants of the canonical type.  */
+  if (TYPE_CANONICAL (type)
+      && (TYPE_MAIN_VARIANT (TYPE_CANONICAL (type))
+	  != TYPE_MAIN_VARIANT (type)))
+    {
+      if (mode != get_strub_mode_from_type (TYPE_CANONICAL (type)))
+	return false;
+      else
+	return same_strub_mode_in_variants_p (TYPE_CANONICAL (type));
+    }
+
+  return true;
+}
+
+/* Check that strub functions don't call non-strub functions, and that
+   always_inline strub functions are only called by strub
+   functions.  */
+
+static void
+verify_strub ()
+{
+  cgraph_node *node;
+
+  /* It's expected that check strub-wise pointer type compatibility of variables
+     and of functions is already taken care of by front-ends, on account of the
+     attribute's being marked as affecting type identity and of the creation of
+     distinct types.  */
+
+  /* Check that call targets in strub contexts have strub-callable types.  */
+
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+  {
+    enum strub_mode caller_mode = get_strub_mode (node);
+
+    for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
+      {
+	gcc_checking_assert (e->indirect_unknown_callee);
+
+	enum strub_mode callee_mode
+	  = effective_strub_mode_for_call (e->call_stmt, NULL);
+
+	if (!strub_callable_from_p (caller_mode, callee_mode))
+	  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);
+
+	tree callee_fntype;
+	enum strub_mode callee_mode
+	  = effective_strub_mode_for_call (e->call_stmt, &callee_fntype);
+
+	if (!strub_callable_from_p (caller_mode, callee_mode))
+	  {
+	    if (callee_mode == 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)
+		     && caller_mode == STRUB_INTERNAL)
+	      /* This is ok, it will be kept in the STRUB_WRAPPER, and removed
+		 from the STRUB_WRAPPED's strub context.  */
+	      continue;
+	    else if (!strub_call_fntype_override_p (e->call_stmt))
+	      error_at (gimple_location (e->call_stmt),
+			"calling non-%<strub%> %qD in %<strub%> context %qD",
+			e->callee->decl, node->decl);
+	    else
+	      error_at (gimple_location (e->call_stmt),
+			"calling %qD using non-%<strub%> type %qT"
+			" in %<strub%> context %qD",
+			e->callee->decl, callee_fntype, node->decl);
+	  }
+      }
+  }
+}
+
+namespace {
+
+/* Define a pass to compute strub modes.  */
+const pass_data pass_data_ipa_strub_mode = {
+  SIMPLE_IPA_PASS,
+  "strubm",
+  OPTGROUP_NONE,
+  TV_NONE,
+  PROP_cfg, // properties_required
+  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 relaxed (-3) and strict (-4) settings, that only enable strub at a
+       function or variable attribute's request, the attribute handler changes
+       flag_strub to -1 or -2, respectively, if any strub-enabling occurence of
+       the attribute is found.  Therefore, if it remains at -3 or -4, nothing
+       that would enable strub was found, so we can disable it and avoid the
+       overhead.  */
+    if (flag_strub < -2)
+      flag_strub = 0;
+    return flag_strub;
+  }
+  virtual unsigned int execute (function *);
+};
+

-- 
Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
   Free Software Activist                       GNU Toolchain Engineer
Disinformation flourishes because many people care deeply about injustice
but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* [PATCH v2 10/10] Introduce strub: strub pass
  2022-07-29  6:16   ` [PATCH v2 00/10] Introduce " Alexandre Oliva
                       ` (8 preceding siblings ...)
  2022-07-29  6:30     ` [PATCH v2 09/10] Introduce strub: strubm (mode assignment) pass Alexandre Oliva
@ 2022-07-29  6:34     ` Alexandre Oliva
  2022-07-29  6:36     ` [PATCH v2 00/10] Introduce strub: machine-independent stack scrubbing Alexandre Oliva
  2023-06-16  6:09     ` [PATCH v3] " Alexandre Oliva
  11 siblings, 0 replies; 59+ messages in thread
From: Alexandre Oliva @ 2022-07-29  6:34 UTC (permalink / raw)
  To: gcc-patches
  Cc: Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Richard Biener, Jim Wilson


This final fragment of ipa-strub.cc adds the strub pass, that
implements the needed function interface changes and adds calls to the
strub builtins.

+/* Define a pass to introduce strub transformations.  */
+const pass_data pass_data_ipa_strub = {
+  SIMPLE_IPA_PASS,
+  "strub",
+  OPTGROUP_NONE,
+  TV_NONE,
+  PROP_cfg | PROP_ssa, // 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 && !seen_error (); }
+  virtual unsigned int execute (function *);
+
+  /* Define on demand and cache some types we use often.  */
+#define DEF_TYPE(NAME, INIT)			\
+  static inline tree get_ ## NAME () {		\
+    static tree type = NULL_TREE;		\
+    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 non-strub builtins on demand.  */
+#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 strub builtins on demand.  */
+#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 strub identifiers on demand.  */
+#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, tree);
+  static inline void adjust_at_calls_calls (cgraph_node *);
+
+  /* Add to SEQ a call to the strub watermark update builtin, taking NODE's
+     location if given.  Optionally add the corresponding edge from NODE, with
+     execution frequency COUNT.  Return the modified SEQ.  */
+
+  static inline gimple_seq
+  call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
+			 gimple_seq seq = NULL)
+    {
+      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
+
+/* Gather with this type a collection of parameters that we're turning into
+   explicit references.  */
+
+typedef hash_set<tree> indirect_parms_t;
+
+/* Dereference OP's incoming turned-into-reference parm if it's an
+   INDIRECT_PARMS or an ADDR_EXPR thereof.  Set *REC and return according to
+   gimple-walking expectations.  */
+
+static tree
+maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
+{
+  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)))
+	{
+	  op = TREE_OPERAND (op, 0);
+	  return op;
+	}
+    }
+
+  return NULL_TREE;
+}
+
+/* A gimple-walking function that adds dereferencing to indirect parms.  */
+
+static tree
+walk_make_indirect (tree *op, int *rec, void *arg)
+{
+  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;
+}
+
+/* A gimple-walking function that turns any non-gimple-val ADDR_EXPRs into a
+   separate SSA.  Though addresses of e.g. parameters, and of members thereof,
+   are gimple vals, turning parameters into references, with an extra layer of
+   indirection and thus explicit dereferencing, need to be regimplified.  */
+
+static tree
+walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
+{
+  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;
+}
+
+/* Turn STMT's PHI arg defs into separate SSA defs if they've become
+   non-gimple_val.  Return TRUE if any edge insertions need to be committed.  */
+
+static bool
+walk_regimplify_phi (gphi *stmt)
+{
+  bool needs_commit = false;
+
+  for (unsigned i = 0, n = gimple_phi_num_args (stmt); i < n; i++)
+    {
+      tree op = gimple_phi_arg_def (stmt, i);
+      if ((TREE_CODE (op) == ADDR_EXPR
+	   && !is_gimple_val (op))
+	  /* ??? A PARM_DECL that was addressable in the original function and
+	     had its address in PHI nodes, but that became a reference in the
+	     wrapped clone would NOT be updated by update_ssa in PHI nodes.
+	     Alas, if we were to create a default def for it now, update_ssa
+	     would complain that the symbol that needed rewriting already has
+	     SSA names associated with it.  OTOH, leaving the PARM_DECL alone,
+	     it eventually causes errors because it remains unchanged in PHI
+	     nodes, but it gets rewritten as expected if it appears in other
+	     stmts.  So we cheat a little here, and force the PARM_DECL out of
+	     the PHI node and into an assignment.  It's a little expensive,
+	     because we insert it at the edge, which introduces a basic block
+	     that's entirely unnecessary, but it works, and the block will be
+	     removed as the default def gets propagated back into the PHI node,
+	     so the final optimized code looks just as expected.  */
+	  || (TREE_CODE (op) == PARM_DECL
+	      && !TREE_ADDRESSABLE (op)))
+	{
+	  tree temp = make_ssa_name (TREE_TYPE (op), stmt);
+	  if (TREE_CODE (op) == PARM_DECL)
+	    SET_SSA_NAME_VAR_OR_IDENTIFIER (temp, DECL_NAME (op));
+	  SET_PHI_ARG_DEF (stmt, i, temp);
+
+	  gimple *assign = gimple_build_assign (temp, op);
+	  if (gimple_phi_arg_has_location (stmt, i))
+	    gimple_set_location (assign, gimple_phi_arg_location (stmt, i));
+	  gsi_insert_on_edge (gimple_phi_arg_edge (stmt, i), assign);
+	  needs_commit = true;
+	}
+    }
+
+  return needs_commit;
+}
+
+/* Create a reference type to use for PARM when turning it into a reference.
+   NONALIASED causes the reference type to gain its own separate alias set, so
+   that accessing the indirectly-passed parm won'will not add aliasing
+   noise.  */
+
+static tree
+build_ref_type_for (tree parm, bool nonaliased = true)
+{
+  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);
+
+      gcall *call = dyn_cast <gcall *> (stmt);
+      if (!call)
+	continue;
+
+      tree callee = gimple_call_fndecl (call);
+      gcc_checking_assert (callee);
+      node->create_edge (cgraph_node::get_create (callee), call, count, false);
+    }
+}
+
+/* Insert SEQ after the call at GSI, as if the call was in a try block with SEQ
+   as finally, i.e., SEQ will run after the call whether it returns or
+   propagates an exception.  This handles block splitting, EH edge and block
+   creation, noreturn and nothrow optimizations, and even throwing calls without
+   preexisting local handlers.  */
+
+static void
+gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
+{
+  if (!seq)
+    return;
+
+  gimple *stmt = gsi_stmt (gsi);
+
+  if (gimple_has_location (stmt))
+    annotate_all_with_location (seq, gimple_location (stmt));
+
+  gcall *call = dyn_cast <gcall *> (stmt);
+  bool noreturn_p = call && gimple_call_noreturn_p (call);
+  int eh_lp = lookup_stmt_eh_lp (stmt);
+  bool must_not_throw_p = eh_lp < 0;
+  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);
+    }
+}
+
+/* Record the order of the last cgraph entry whose mode we've already set, so
+   that we can perform mode setting incrementally without duplication.  */
+static int last_cgraph_order;
+
+/* Set strub modes for functions introduced since the last call.  */
+
+static void
+ipa_strub_set_mode_for_new_functions ()
+{
+  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_fndecl (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;
+
+  gcc_checking_assert (same_strub_mode_in_variants_p (type));
+
+  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;
+}
+
+/* Adjust a call to an at-calls call target.  Create a watermark local variable
+   if needed, initialize it before, pass it to the callee according to the
+   modified at-calls interface, and release the callee's stack space after the
+   call, if not deferred.  If the call is const or pure, arrange for the
+   watermark to not be assumed unused or unchanged.  */
+
+void
+pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args,
+				      tree callee_fntype)
+{
+  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);
+    gimple_call_set_fntype (new_stmt, callee_fntype);
+
+    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);
+}
+
+/* Adjust all at-calls calls in NODE. */
+
+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;
+	  enum strub_mode callee_mode
+	    = effective_strub_mode_for_call (e->call_stmt, &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, callee_fntype);
+	}
+      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);
+
+	  tree callee_fntype;
+	  enum strub_mode callee_mode
+	    = effective_strub_mode_for_call (e->call_stmt, &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, callee_fntype);
+	}
+      pop_cfun ();
+    }
+}
+
+/* The strubm (strub mode) pass computes a strub mode for each function in the
+   call graph, and checks, before any inlining, that strub callability
+   requirements in effect are satisfied.  */
+
+unsigned int
+pass_ipa_strub_mode::execute (function *)
+{
+  last_cgraph_order = 0;
+  ipa_strub_set_mode_for_new_functions ();
+
+  /* Verify before any inlining or other transformations.  */
+  verify_strub ();
+
+  return 0;
+}
+
+/* Create a strub mode pass.  */
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub_mode (gcc::context *ctxt)
+{
+  return new pass_ipa_strub_mode (ctxt);
+}
+
+/* The strub pass proper adjusts types, signatures, and at-calls calls, and
+   splits internal-strub functions.  */
+
+unsigned int
+pass_ipa_strub::execute (function *)
+{
+  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)
+	  distinctify_node_type (onode);
+
+	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;
+
+	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);
+
+		  gcall *call = dyn_cast <gcall *> (stmt);
+
+		  if (!call)
+		    continue;
+
+		  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 ();
+      }
+  }
+
+  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;
+      }
+
+    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);
+
+    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;
+	  bool needs_commit = false;
+	  FOR_EACH_BB_FN (bb, cfun)
+	    {
+	      for (gphi_iterator gsi = gsi_start_nonvirtual_phis (bb);
+		   !gsi_end_p (gsi);
+		   gsi_next_nonvirtual_phi (&gsi))
+		{
+		  gphi *stmt = gsi.phi ();
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed && !is_gimple_debug (gsi_stmt (gsi)))
+		    if (walk_regimplify_phi (stmt))
+		      needs_commit = true;
+		}
+
+	      for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+		   !gsi_end_p (gsi); gsi_next (&gsi))
+		{
+		  gimple *stmt = gsi_stmt (gsi);
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed)
+		    {
+		      if (!is_gimple_debug (stmt))
+			{
+			  wi.info = &gsi;
+			  walk_gimple_op (stmt, walk_regimplify_addr_expr,
+					  &wi);
+			}
+		      update_stmt (stmt);
+		    }
+		}
+	    }
+	  if (needs_commit)
+	    gsi_commit_edge_inserts ();
+	}
+
+      if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca
+	  || 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)));
+
+      gcall *wrcall;
+      while (!(wrcall = dyn_cast <gcall *> (gsi_stmt (gsi))))
+	gsi_next (&gsi);
+
+      tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
+      TREE_ADDRESSABLE (swm) = true;
+      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 ();
+    }
+  }
+
+  return 0;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub (gcc::context *ctxt)
+{
+  return new pass_ipa_strub (ctxt);
+}

-- 
Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
   Free Software Activist                       GNU Toolchain Engineer
Disinformation flourishes because many people care deeply about injustice
but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* Re: [PATCH v2 00/10] Introduce strub: machine-independent stack scrubbing
  2022-07-29  6:16   ` [PATCH v2 00/10] Introduce " Alexandre Oliva
                       ` (9 preceding siblings ...)
  2022-07-29  6:34     ` [PATCH v2 10/10] Introduce strub: strub pass Alexandre Oliva
@ 2022-07-29  6:36     ` Alexandre Oliva
  2022-10-10  8:48       ` Richard Biener
  2023-06-16  6:09     ` [PATCH v3] " Alexandre Oliva
  11 siblings, 1 reply; 59+ messages in thread
From: Alexandre Oliva @ 2022-07-29  6:36 UTC (permalink / raw)
  To: gcc-patches
  Cc: Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Richard Biener, Jim Wilson

On Jul 29, 2022, Alexandre Oliva <oliva@adacore.com> wrote:

> This patch adds the strub attribute for function and variable types,
> command-line options, passes and adjustments to implement it,
> documentation, and tests.

The entire patch, and the patchlets used for testing, are available from
the GCC git repo, branch refs/users/aoliva/heads/strub:

e9d1e252e0bcd silence warnings
4a93638ee91b1 enable strub for all viable functions by default, for testing
29e98f10c5faa Introduce strub: machine-independent stack scrubbing

-- 
Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
   Free Software Activist                       GNU Toolchain Engineer
Disinformation flourishes because many people care deeply about injustice
but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* Re: [PATCH v2 02/10] Introduce strub: torture tests for C and C++
  2022-07-29  6:25     ` [PATCH v2 02/10] Introduce strub: torture tests for C and C++ Alexandre Oliva
@ 2022-08-09 13:34       ` Alexandre Oliva
  0 siblings, 0 replies; 59+ messages in thread
From: Alexandre Oliva @ 2022-08-09 13:34 UTC (permalink / raw)
  To: gcc-patches
  Cc: Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Richard Biener, Jim Wilson

Ping?
https://gcc.gnu.org/pipermail/gcc-patches/2022-July/599011.html

Here's an incremental patch for some of these tests, that avoids some
relatively rare spurious failures.


diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
index b24a1c7a345fa..7458b3fb54da5 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -80,11 +80,16 @@ internal ()
 
 int main ()
 {
-  if (!look_for_string (callable ()))
-    __builtin_abort ();
-  if (look_for_string (at_calls ()))
-    __builtin_abort ();
-  if (look_for_string (internal ()))
-    __builtin_abort ();
+  /* Since these test check stack contents above the top of the stack, an
+     unexpected asynchronous signal or interrupt might overwrite the bits we
+     expect to find and cause spurious fails.  Tolerate one such overall
+     spurious fail by retrying.  */
+  int i = 1;
+  while (!look_for_string (callable ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (at_calls ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (internal ()))
+    if (!i--) __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
index 1df2ffe2fe58c..5d60a7775f4bb 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -69,11 +69,16 @@ internal ()
 
 int main ()
 {
-  if (!look_for_string (callable ()))
-    __builtin_abort ();
-  if (look_for_string (at_calls ()))
-    __builtin_abort ();
-  if (look_for_string (internal ()))
-    __builtin_abort ();
+  /* Since these test check stack contents above the top of the stack, an
+     unexpected asynchronous signal or interrupt might overwrite the bits we
+     expect to find and cause spurious fails.  Tolerate one such overall
+     spurious fail by retrying.  */
+  int i = 1;
+  while (!look_for_string (callable ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (at_calls ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (internal ()))
+    if (!i--) __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
index afbc2cc9ab484..c2ad710858e87 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -65,11 +65,16 @@ internal ()
 
 int main ()
 {
-  if (!look_for_string (callable ()))
-    __builtin_abort ();
-  if (look_for_string (at_calls ()))
-    __builtin_abort ();
-  if (look_for_string (internal ()))
-    __builtin_abort ();
+  /* Since these test check stack contents above the top of the stack, an
+     unexpected asynchronous signal or interrupt might overwrite the bits we
+     expect to find and cause spurious fails.  Tolerate one such overall
+     spurious fail by retrying.  */
+  int i = 1;
+  while (!look_for_string (callable ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (at_calls ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (internal ()))
+    if (!i--) __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
index 5300f1d330b87..3b36b8e5d68ef 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -95,7 +95,12 @@ internal ()
 int __attribute__ ((__strub__ ("disabled")))
 main ()
 {
-  if (look_for_string (internal ()))
-    __builtin_abort ();
+  /* Since these test check stack contents above the top of the stack, an
+     unexpected asynchronous signal or interrupt might overwrite the bits we
+     expect to find and cause spurious fails.  Tolerate one such overall
+     spurious fail by retrying.  */
+  int i = 1;
+  while (look_for_string (internal ()))
+    if (!i--) __builtin_abort ();
   __builtin_exit (0);
 }


-- 
Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
   Free Software Activist                       GNU Toolchain Engineer
Disinformation flourishes because many people care deeply about injustice
but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* Re: [PATCH v2 00/10] Introduce strub: machine-independent stack scrubbing
  2022-07-29  6:36     ` [PATCH v2 00/10] Introduce strub: machine-independent stack scrubbing Alexandre Oliva
@ 2022-10-10  8:48       ` Richard Biener
  2022-10-11 11:57         ` Alexandre Oliva
  0 siblings, 1 reply; 59+ messages in thread
From: Richard Biener @ 2022-10-10  8:48 UTC (permalink / raw)
  To: Alexandre Oliva
  Cc: gcc-patches, Jeremy Bennett, Craig Blackmore, Graham Markall,
	Martin Jambor, Jan Hubicka, Jim Wilson

On Fri, Jul 29, 2022 at 8:36 AM Alexandre Oliva <oliva@adacore.com> wrote:
>
> On Jul 29, 2022, Alexandre Oliva <oliva@adacore.com> wrote:
>
> > This patch adds the strub attribute for function and variable types,
> > command-line options, passes and adjustments to implement it,
> > documentation, and tests.
>
> The entire patch, and the patchlets used for testing, are available from
> the GCC git repo, branch refs/users/aoliva/heads/strub:

As noted in the Cauldron Discussion I think you should do all instrumentation
post-IPA only to simplify your life not needing to handle inlining of
instrumentation
and to get the full benefit of WPA analysis when used with LTO.  That might
even mean doing it as a regular IPA pass where clones are used for strubbing
and original bodies are used for inlining.

Richard.

> e9d1e252e0bcd silence warnings
> 4a93638ee91b1 enable strub for all viable functions by default, for testing
> 29e98f10c5faa Introduce strub: machine-independent stack scrubbing
>
> --
> Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
>    Free Software Activist                       GNU Toolchain Engineer
> Disinformation flourishes because many people care deeply about injustice
> but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* Re: [PATCH v2 00/10] Introduce strub: machine-independent stack scrubbing
  2022-10-10  8:48       ` Richard Biener
@ 2022-10-11 11:57         ` Alexandre Oliva
  2022-10-11 11:59           ` Richard Biener
  0 siblings, 1 reply; 59+ messages in thread
From: Alexandre Oliva @ 2022-10-11 11:57 UTC (permalink / raw)
  To: Richard Biener
  Cc: gcc-patches, Jeremy Bennett, Craig Blackmore, Graham Markall,
	Martin Jambor, Jan Hubicka, Jim Wilson

On Oct 10, 2022, Richard Biener <richard.guenther@gmail.com> wrote:

> As noted in the Cauldron Discussion I think you should do all
> instrumentation post-IPA only to simplify your life not needing to
> handle inlining of instrumentation

I looked a bit into that after the Cauldron, and recalled why I wanted
to instrument before inlining: in the case of internal strub, that
introduces a wrapper, it's desirable to be able to inline the wrapper.

-- 
Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
   Free Software Activist                       GNU Toolchain Engineer
Disinformation flourishes because many people care deeply about injustice
but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* Re: [PATCH v2 00/10] Introduce strub: machine-independent stack scrubbing
  2022-10-11 11:57         ` Alexandre Oliva
@ 2022-10-11 11:59           ` Richard Biener
  2022-10-11 13:33             ` Alexandre Oliva
  0 siblings, 1 reply; 59+ messages in thread
From: Richard Biener @ 2022-10-11 11:59 UTC (permalink / raw)
  To: Alexandre Oliva
  Cc: gcc-patches, Jeremy Bennett, Craig Blackmore, Graham Markall,
	Martin Jambor, Jan Hubicka, Jim Wilson

On Tue, Oct 11, 2022 at 1:57 PM Alexandre Oliva <oliva@adacore.com> wrote:
>
> On Oct 10, 2022, Richard Biener <richard.guenther@gmail.com> wrote:
>
> > As noted in the Cauldron Discussion I think you should do all
> > instrumentation post-IPA only to simplify your life not needing to
> > handle inlining of instrumentation
>
> I looked a bit into that after the Cauldron, and recalled why I wanted
> to instrument before inlining: in the case of internal strub, that
> introduces a wrapper, it's desirable to be able to inline the wrapper.

I think if the wrapper is created at IPA time it is also available for
IPA inlining.

Richard.

>
> --
> Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
>    Free Software Activist                       GNU Toolchain Engineer
> Disinformation flourishes because many people care deeply about injustice
> but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* Re: [PATCH v2 00/10] Introduce strub: machine-independent stack scrubbing
  2022-10-11 11:59           ` Richard Biener
@ 2022-10-11 13:33             ` Alexandre Oliva
  2022-10-13 11:38               ` Richard Biener
  0 siblings, 1 reply; 59+ messages in thread
From: Alexandre Oliva @ 2022-10-11 13:33 UTC (permalink / raw)
  To: Richard Biener
  Cc: gcc-patches, Jeremy Bennett, Craig Blackmore, Graham Markall,
	Martin Jambor, Jan Hubicka, Jim Wilson

On Oct 11, 2022, Richard Biener <richard.guenther@gmail.com> wrote:

> On Tue, Oct 11, 2022 at 1:57 PM Alexandre Oliva <oliva@adacore.com> wrote:
>> 
>> On Oct 10, 2022, Richard Biener <richard.guenther@gmail.com> wrote:
>> 
>> > As noted in the Cauldron Discussion I think you should do all
>> > instrumentation post-IPA only to simplify your life not needing to
>> > handle inlining of instrumentation
>> 
>> I looked a bit into that after the Cauldron, and recalled why I wanted
>> to instrument before inlining: in the case of internal strub, that
>> introduces a wrapper, it's desirable to be able to inline the wrapper.

> I think if the wrapper is created at IPA time it is also available for
> IPA inlining.

Yeah, but now I'm not sure what you're suggesting.  The wrapper is
instrumentation, and requires instrumentation of the wrapped
counterpart, so that can't be post-IPA.

-- 
Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
   Free Software Activist                       GNU Toolchain Engineer
Disinformation flourishes because many people care deeply about injustice
but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* Re: [PATCH v2 00/10] Introduce strub: machine-independent stack scrubbing
  2022-10-11 13:33             ` Alexandre Oliva
@ 2022-10-13 11:38               ` Richard Biener
  2022-10-13 13:15                 ` Alexandre Oliva
  0 siblings, 1 reply; 59+ messages in thread
From: Richard Biener @ 2022-10-13 11:38 UTC (permalink / raw)
  To: Alexandre Oliva
  Cc: gcc-patches, Jeremy Bennett, Craig Blackmore, Graham Markall,
	Martin Jambor, Jan Hubicka, Jim Wilson

On Tue, Oct 11, 2022 at 3:33 PM Alexandre Oliva <oliva@adacore.com> wrote:
>
> On Oct 11, 2022, Richard Biener <richard.guenther@gmail.com> wrote:
>
> > On Tue, Oct 11, 2022 at 1:57 PM Alexandre Oliva <oliva@adacore.com> wrote:
> >>
> >> On Oct 10, 2022, Richard Biener <richard.guenther@gmail.com> wrote:
> >>
> >> > As noted in the Cauldron Discussion I think you should do all
> >> > instrumentation post-IPA only to simplify your life not needing to
> >> > handle inlining of instrumentation
> >>
> >> I looked a bit into that after the Cauldron, and recalled why I wanted
> >> to instrument before inlining: in the case of internal strub, that
> >> introduces a wrapper, it's desirable to be able to inline the wrapper.
>
> > I think if the wrapper is created at IPA time it is also available for
> > IPA inlining.
>
> Yeah, but now I'm not sure what you're suggesting.  The wrapper is
> instrumentation, and requires instrumentation of the wrapped
> counterpart, so that can't be post-IPA.

IPA folks can probably explain better but there's IPA (local)
analysis, IPA (global) propagation
and IPA (local) code generation.  You'd instrument (and actually
create the wrappers)
at IPA code generation time but virtually the wrapper would become existent
somewhen during IPA propagation by means of a clone to be materialized
(I understand the wrapper is something like a thunk).  The IPA
propagation phase would
decide which calls should go to the wrapper (and instrumented
function) and which
can use the original uninstrumented function (maybe from local already
strubbed functions).

Richard.

>
> --
> Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
>    Free Software Activist                       GNU Toolchain Engineer
> Disinformation flourishes because many people care deeply about injustice
> but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* Re: [PATCH v2 00/10] Introduce strub: machine-independent stack scrubbing
  2022-10-13 11:38               ` Richard Biener
@ 2022-10-13 13:15                 ` Alexandre Oliva
  0 siblings, 0 replies; 59+ messages in thread
From: Alexandre Oliva @ 2022-10-13 13:15 UTC (permalink / raw)
  To: Richard Biener
  Cc: gcc-patches, Jeremy Bennett, Craig Blackmore, Graham Markall,
	Martin Jambor, Jan Hubicka, Jim Wilson

On Oct 13, 2022, Richard Biener <richard.guenther@gmail.com> wrote:

> On Tue, Oct 11, 2022 at 3:33 PM Alexandre Oliva <oliva@adacore.com> wrote:
>> 
>> On Oct 11, 2022, Richard Biener <richard.guenther@gmail.com> wrote:
>> 
>> > On Tue, Oct 11, 2022 at 1:57 PM Alexandre Oliva <oliva@adacore.com> wrote:
>> >>
>> >> On Oct 10, 2022, Richard Biener <richard.guenther@gmail.com> wrote:
>> >>
>> >> > As noted in the Cauldron Discussion I think you should do all
>> >> > instrumentation post-IPA only to simplify your life not needing to
>> >> > handle inlining of instrumentation
>> >>
>> >> I looked a bit into that after the Cauldron, and recalled why I wanted
>> >> to instrument before inlining: in the case of internal strub, that
>> >> introduces a wrapper, it's desirable to be able to inline the wrapper.
>> 
>> > I think if the wrapper is created at IPA time it is also available for
>> > IPA inlining.
>> 
>> Yeah, but now I'm not sure what you're suggesting.  The wrapper is
>> instrumentation, and requires instrumentation of the wrapped
>> counterpart, so that can't be post-IPA.

> IPA folks can probably explain better but there's IPA (local)
> analysis, IPA (global) propagation
> and IPA (local) code generation.

I think we're miscommunicating.

None of these are post-IPA, they're all part of IPA.

At first, you'd suggested instrumentation to be made post-IPA, to avoid
the trouble of inlining instrumentation.  But we *do* want to inline the
instrumentation.

Now you seem to be suggesting a major revamp of the implementation to
integrate it into IPA, rather than post-IPA, while I keep on trying to
reconcile that with the initial recommendation of moving it post-IPA.

Have you dropped the initial recommendation, and moved on to an
unrelated recommendation?

If so, I can stop trying to reconcile the unrelated recommendations as
if they were related, and focus on the newer one alone.

My reasons to not want to integrate strub tightly into IPA
infrastructure was that there was no perceived benefit from a tighter
integration, and I wasn't sure the feature would be welcome, so I
designed something that could be added in a very standalone way, maybe
even as a plugin.

Maybe there is interest and this decoupling could be reduced, but
there'd have to be very compelling reasons to justify undergoing such
major reengineering.

-- 
Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
   Free Software Activist                       GNU Toolchain Engineer
Disinformation flourishes because many people care deeply about injustice
but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* Re: [PATCH v3] Introduce strub: machine-independent stack scrubbing
  2022-07-29  6:16   ` [PATCH v2 00/10] Introduce " Alexandre Oliva
                       ` (10 preceding siblings ...)
  2022-07-29  6:36     ` [PATCH v2 00/10] Introduce strub: machine-independent stack scrubbing Alexandre Oliva
@ 2023-06-16  6:09     ` Alexandre Oliva
  2023-06-27 21:28       ` Qing Zhao
  2023-10-20  6:03       ` [PATCH v4] " Alexandre Oliva
  11 siblings, 2 replies; 59+ messages in thread
From: Alexandre Oliva @ 2023-06-16  6:09 UTC (permalink / raw)
  To: gcc-patches
  Cc: Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Richard Biener, Jim Wilson


This patch adds the strub attribute for function and variable types,
command-line options, passes and adjustments to implement it,
documentation, and tests.

Stack scrubbing is implemented in a machine-independent way: functions
with strub enabled are modified so that they take an extra stack
watermark argument, that they update with their stack use, and the
caller can then zero it out once it regains control, whether by return
or exception.  There are two ways to go about it: at-calls, that
modifies the visible interface (signature) of the function, and
internal, in which the body is moved to a clone, the clone undergoes
the interface change, and the function becomes a wrapper, preserving
its original interface, that calls the clone and then clears the stack
used by it.

Variables can also be annotated with the strub attribute, so that
functions that read from them get stack scrubbing enabled implicitly,
whether at-calls, for functions only usable within a translation unit,
or internal, for functions whose interfaces must not be modified.

There is a strict mode, in which functions that have their stack
scrubbed can only call other functions with stack-scrubbing
interfaces, or those explicitly marked as callable from strub
contexts, so that an entire call chain gets scrubbing, at once or
piecemeal depending on optimization levels.  In the default mode,
relaxed, this requirement is not enforced by the compiler.

The implementation adds two IPA passes, one that assigns strub modes
early on, another that modifies interfaces and adds calls to the
builtins that jointly implement stack scrubbing.  Another builtin,
that obtains the stack pointer, is added for use in the implementation
of the builtins, whether expanded inline or called in libgcc.

There are new command-line options to change operation modes and to
force the feature disabled; it is enabled by default, but it has no
effect and is implicitly disabled if the strub attribute is never
used.  There are also options meant to use for testing the feature,
enabling different strubbing modes for all (viable) functions.

Regstrapped on x86_64-linux-gnu.  Also tested with gcc-13, and with
various other targets.  Ok to install?

There have been only minor changes since v2:

- scrub the stack in the same direction it grows, inline and out-of-line

- remove need for stack space in __strub_leave

- add (ultimately not needed) means to avoid using the red zone in
  __strub_leave

- introduce and document TARGET_ macros to tune __strub_leave

- drop a misoptimization in inlined __strub_enter

- fix handling of cgraph edges without call stmts

- adjust some testcases (async stack uses; Ada compiler bug fix)

- drop bits for compatibility with gcc 10

- preserve the comdat group when resetting a function into a strub
  wrapper, coping with a symtab_node::reset change in gcc-13


for  gcc/ChangeLog

	* Makefile.in (OBJS): Add ipa-strub.o.
	* builtins.def (BUILT_IN_STACK_ADDRESS): New.
	(BUILT_IN___STRUB_ENTER): New.
	(BUILT_IN___STRUB_UPDATE): New.
	(BUILT_IN___STRUB_LEAVE): New.
	* builtins.cc: Include ipa-strub.h.
	(STACK_STOPS, STACK_UNSIGNED): Define.
	(expand_builtin_stack_address): New.
	(expand_builtin_strub_enter): New.
	(expand_builtin_strub_update): New.
	(expand_builtin_strub_leave): New.
	(expand_builtin): Call them.
	* common.opt (fstrub=*): New options.
	* doc/extend.texi (strub): New type attribute.
	(__builtin_stack_address): New function.
	(Stack Scrubbing): New section.
	* doc/invoke.texi (-fstrub=*): New options.
	(-fdump-ipa-*): New passes.
	* ipa-inline.cc: Include ipa-strub.h.
	(can_inline_edge_p): Test strub_inlinable_to_p.
	* ipa-split.cc: Include ipa-strub.h.
	(execute_split_functions): Test strub_splittable_p.
	* ipa-strub.cc, ipa-strub.h: New.
	* passes.def: Add strub_mode and strub passes.
	* tree-cfg.cc (gimple_verify_flow_info): Note on debug stmts.
	* tree-pass.h (make_pass_ipa_strub_mode): Declare.
	(make_pass_ipa_strub): Declare.
	(make_pass_ipa_function_and_variable_visibility): Fix
	formatting.
	* tree-ssa-ccp.cc (optimize_stack_restore): Keep restores
	before strub leave.
	* multiple_target.cc (pass_target_clone::gate): Test seen_error.
	* attribs.cc: Include ipa-strub.h.
	(decl_attributes): Support applying attributes to function
	type, rather than pointer type, at handler's request.
	(comp_type_attributes): Combine strub_comptypes and target
	comp_type results.
	* doc/tm.texi.in (TARGET_STRUB_USE_DYNAMIC_ARRAY): New.
	(TARGET_STRUB_MAY_USE_MEMSET): New.
	* doc/tm.texi: Rebuilt.
	* cgraph.h (symtab_node::reset): Add preserve_comdat_group
	param, with a default.
	* cgraphunit.cc (symtab_node::reset): Use it.

for  gcc/c-family/ChangeLog

	* c-attribs.cc: Include ipa-strub.h.
	(handle_strub_attribute): New.
	(c_common_attribute_table): Add strub.

for  gcc/ada/ChangeLog

	* gcc-interface/trans.cc: Include ipa-strub.h.
	(gigi): Make internal decls for targets of compiler-generated
	calls strub-callable too.
	(build_raise_check): Likewise.
	* gcc-interface/utils.cc: Include ipa-strub.h.
	(handle_strub_attribute): New.
	(gnat_internal_attribute_table): Add strub.

for  gcc/testsuite/ChangeLog

	* c-c++-common/strub-O0.c: New.
	* c-c++-common/strub-O1.c: New.
	* c-c++-common/strub-O2.c: New.
	* c-c++-common/strub-O2fni.c: New.
	* c-c++-common/strub-O3.c: New.
	* c-c++-common/strub-O3fni.c: New.
	* c-c++-common/strub-Og.c: New.
	* c-c++-common/strub-Os.c: New.
	* c-c++-common/strub-all1.c: New.
	* c-c++-common/strub-all2.c: New.
	* c-c++-common/strub-apply1.c: New.
	* c-c++-common/strub-apply2.c: New.
	* c-c++-common/strub-apply3.c: New.
	* c-c++-common/strub-apply4.c: New.
	* c-c++-common/strub-at-calls1.c: New.
	* c-c++-common/strub-at-calls2.c: New.
	* c-c++-common/strub-defer-O1.c: New.
	* c-c++-common/strub-defer-O2.c: New.
	* c-c++-common/strub-defer-O3.c: New.
	* c-c++-common/strub-defer-Os.c: New.
	* c-c++-common/strub-internal1.c: New.
	* c-c++-common/strub-internal2.c: New.
	* c-c++-common/strub-parms1.c: New.
	* c-c++-common/strub-parms2.c: New.
	* c-c++-common/strub-parms3.c: New.
	* c-c++-common/strub-relaxed1.c: New.
	* c-c++-common/strub-relaxed2.c: New.
	* c-c++-common/strub-short-O0-exc.c: New.
	* c-c++-common/strub-short-O0.c: New.
	* c-c++-common/strub-short-O1.c: New.
	* c-c++-common/strub-short-O2.c: New.
	* c-c++-common/strub-short-O3.c: New.
	* c-c++-common/strub-short-Os.c: New.
	* c-c++-common/strub-strict1.c: New.
	* c-c++-common/strub-strict2.c: New.
	* c-c++-common/strub-tail-O1.c: New.
	* c-c++-common/strub-tail-O2.c: New.
	* c-c++-common/torture/strub-callable1.c: New.
	* c-c++-common/torture/strub-callable2.c: New.
	* c-c++-common/torture/strub-const1.c: New.
	* c-c++-common/torture/strub-const2.c: New.
	* c-c++-common/torture/strub-const3.c: New.
	* c-c++-common/torture/strub-const4.c: New.
	* c-c++-common/torture/strub-data1.c: New.
	* c-c++-common/torture/strub-data2.c: New.
	* c-c++-common/torture/strub-data3.c: New.
	* c-c++-common/torture/strub-data4.c: New.
	* c-c++-common/torture/strub-data5.c: New.
	* c-c++-common/torture/strub-indcall1.c: New.
	* c-c++-common/torture/strub-indcall2.c: New.
	* c-c++-common/torture/strub-indcall3.c: New.
	* c-c++-common/torture/strub-inlinable1.c: New.
	* c-c++-common/torture/strub-inlinable2.c: New.
	* c-c++-common/torture/strub-ptrfn1.c: New.
	* c-c++-common/torture/strub-ptrfn2.c: New.
	* c-c++-common/torture/strub-ptrfn3.c: New.
	* c-c++-common/torture/strub-ptrfn4.c: New.
	* c-c++-common/torture/strub-pure1.c: New.
	* c-c++-common/torture/strub-pure2.c: New.
	* c-c++-common/torture/strub-pure3.c: New.
	* c-c++-common/torture/strub-pure4.c: New.
	* c-c++-common/torture/strub-run1.c: New.
	* c-c++-common/torture/strub-run2.c: New.
	* c-c++-common/torture/strub-run3.c: New.
	* c-c++-common/torture/strub-run4.c: New.
	* c-c++-common/torture/strub-run4c.c: New.
	* c-c++-common/torture/strub-run4d.c: New.
	* c-c++-common/torture/strub-run4i.c: New.
	* g++.dg/strub-run1.C: New.
	* g++.dg/torture/strub-init1.C: New.
	* g++.dg/torture/strub-init2.C: New.
	* g++.dg/torture/strub-init3.C: New.
	* gnat.dg/strub_attr.adb, gnat.dg/strub_attr.ads: New.
	* gnat.dg/strub_ind.adb, gnat.dg/strub_ind.ads: New.

for  libgcc/ChangeLog

	* Makefile.in (LIB2ADD): Add strub.c.
	* libgcc2.h (__strub_enter, __strub_update, __strub_leave):
	Declare.
	* strub.c: New.
---
 gcc/Makefile.in                                    |    1 
 gcc/ada/gcc-interface/trans.cc                     |   18 
 gcc/ada/gcc-interface/utils.cc                     |   73 
 gcc/attribs.cc                                     |   37 
 gcc/builtins.cc                                    |  269 ++
 gcc/builtins.def                                   |    4 
 gcc/c-family/c-attribs.cc                          |   82 
 gcc/cgraph.h                                       |    2 
 gcc/cgraphunit.cc                                  |    5 
 gcc/common.opt                                     |   29 
 gcc/doc/extend.texi                                |  307 ++
 gcc/doc/invoke.texi                                |   60 
 gcc/doc/tm.texi                                    |   19 
 gcc/doc/tm.texi.in                                 |   19 
 gcc/ipa-inline.cc                                  |    6 
 gcc/ipa-split.cc                                   |    7 
 gcc/ipa-strub.cc                                   | 3450 ++++++++++++++++++++
 gcc/ipa-strub.h                                    |   45 
 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-defer-O1.c        |    7 
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |    8 
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |   97 +
 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-relaxed1.c        |   18 
 gcc/testsuite/c-c++-common/strub-relaxed2.c        |   14 
 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-strict1.c         |   36 
 gcc/testsuite/c-c++-common/strub-strict2.c         |   25 
 gcc/testsuite/c-c++-common/strub-tail-O1.c         |    8 
 gcc/testsuite/c-c++-common/strub-tail-O2.c         |   14 
 gcc/testsuite/c-c++-common/strub-var1.c            |   24 
 .../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   |   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 
 .../c-c++-common/torture/strub-inlinable1.c        |   16 
 .../c-c++-common/torture/strub-inlinable2.c        |    7 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |   10 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |   55 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c  |   50 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c  |   43 
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |   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    |   95 +
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |   84 
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |   80 
 gcc/testsuite/c-c++-common/torture/strub-run4.c    |  106 +
 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_access.adb             |   21 
 gcc/testsuite/gnat.dg/strub_access1.adb            |   16 
 gcc/testsuite/gnat.dg/strub_attr.adb               |   37 
 gcc/testsuite/gnat.dg/strub_attr.ads               |   12 
 gcc/testsuite/gnat.dg/strub_disp.adb               |   64 
 gcc/testsuite/gnat.dg/strub_disp1.adb              |   79 
 gcc/testsuite/gnat.dg/strub_ind.adb                |   33 
 gcc/testsuite/gnat.dg/strub_ind.ads                |   17 
 gcc/testsuite/gnat.dg/strub_ind1.adb               |   41 
 gcc/testsuite/gnat.dg/strub_ind1.ads               |   17 
 gcc/testsuite/gnat.dg/strub_ind2.adb               |   34 
 gcc/testsuite/gnat.dg/strub_ind2.ads               |   17 
 gcc/testsuite/gnat.dg/strub_intf.adb               |   93 +
 gcc/testsuite/gnat.dg/strub_intf1.adb              |   86 
 gcc/testsuite/gnat.dg/strub_intf2.adb              |   55 
 gcc/testsuite/gnat.dg/strub_renm.adb               |   21 
 gcc/testsuite/gnat.dg/strub_renm1.adb              |   32 
 gcc/testsuite/gnat.dg/strub_renm2.adb              |   32 
 gcc/testsuite/gnat.dg/strub_var.adb                |   16 
 gcc/testsuite/gnat.dg/strub_var1.adb               |   20 
 gcc/tree-cfg.cc                                    |    1 
 gcc/tree-pass.h                                    |    5 
 gcc/tree-ssa-ccp.cc                                |    4 
 libgcc/Makefile.in                                 |    3 
 libgcc/libgcc2.h                                   |    4 
 libgcc/strub.c                                     |  149 +
 118 files changed, 7281 insertions(+), 12 deletions(-)
 create mode 100644 gcc/ipa-strub.cc
 create mode 100644 gcc/ipa-strub.h
 create mode 100644 gcc/testsuite/c-c++-common/strub-O0.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O2fni.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O3fni.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-Og.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-Os.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-all1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-all2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-apply1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-apply2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-apply3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-apply4.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-at-calls1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-at-calls2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-defer-O1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-defer-O2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-defer-O3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-defer-Os.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-internal1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-internal2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-parms1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-parms2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-parms3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-relaxed1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-relaxed2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O0-exc.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O0.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-Os.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-strict1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-strict2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-tail-O1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-tail-O2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-var1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-callable1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-callable2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data5.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-indcall1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-indcall2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-indcall3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4c.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4d.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4i.c
 create mode 100644 gcc/testsuite/g++.dg/strub-run1.C
 create mode 100644 gcc/testsuite/g++.dg/torture/strub-init1.C
 create mode 100644 gcc/testsuite/g++.dg/torture/strub-init2.C
 create mode 100644 gcc/testsuite/g++.dg/torture/strub-init3.C
 create mode 100644 gcc/testsuite/gnat.dg/strub_access.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_access1.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_attr.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_attr.ads
 create mode 100644 gcc/testsuite/gnat.dg/strub_disp.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_disp1.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind.ads
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind1.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind1.ads
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind2.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind2.ads
 create mode 100644 gcc/testsuite/gnat.dg/strub_intf.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_intf1.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_intf2.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_renm.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_renm1.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_renm2.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_var.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_var1.adb
 create mode 100644 libgcc/strub.c

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 3c5804bdb21fe..96d920d1f6a8d 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1520,6 +1520,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.cc b/gcc/ada/gcc-interface/trans.cc
index 965565ad2da01..6a1aa7d3ca6ea 100644
--- a/gcc/ada/gcc-interface/trans.cc
+++ b/gcc/ada/gcc-interface/trans.cc
@@ -69,6 +69,21 @@
 #include "ada-tree.h"
 #include "gigi.h"
 
+/* The following #include is for strub_make_callable.
+
+   This function marks a function as safe to call from strub contexts.  We mark
+   Ada subprograms that may be called implicitly by the compiler, and that won't
+   leave on the stack caller data passed to them.  This stops implicit calls
+   introduced in subprograms that have their stack scrubbed from being flagged
+   as unsafe, even in -fstrub=strict mode.
+
+   These subprograms are also marked with the strub(callable) attribute in Ada
+   sources, but their declarations aren't necessarily imported by GNAT, or made
+   visible to gigi, in units that end up relying on them.  So when gigi
+   introduces their declarations on its own, it must also add the attribute, by
+   calling strub_make_callable.  */
+#include "ipa-strub.h"
+
 /* We should avoid allocating more than ALLOCA_THRESHOLD bytes via alloca,
    for fear of running out of stack space.  If we need more, we use xmalloc
    instead.  */
@@ -454,6 +469,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)
     {
@@ -466,6 +482,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.  */
@@ -723,6 +740,7 @@ build_raise_check (int check, enum exception_info_kind kind)
 			   NULL_TREE, is_default, true, true, true, false,
 			   false, NULL, Empty);
   set_call_expr_flags (result, ECF_NORETURN | ECF_THROW);
+  strub_make_callable (result);
 
   return result;
 }
diff --git a/gcc/ada/gcc-interface/utils.cc b/gcc/ada/gcc-interface/utils.cc
index d0a13d2af3331..f36af1d52b85a 100644
--- a/gcc/ada/gcc-interface/utils.cc
+++ b/gcc/ada/gcc-interface/utils.cc
@@ -39,6 +39,7 @@
 #include "varasm.h"
 #include "toplev.h"
 #include "opts.h"
+#include "ipa-strub.h"
 #include "output.h"
 #include "debug.h"
 #include "convert.h"
@@ -6632,9 +6633,77 @@ handle_no_stack_protector_attribute (tree *node, tree name, tree, int,
    struct attribute_spec.handler.  */
 
 static tree
-handle_strub_attribute (tree *, tree, tree, int, bool *no_add_attrs)
+handle_strub_attribute (tree *node, tree name,
+			tree args,
+			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
-  *no_add_attrs = true;
+  bool enable = true;
+
+  if (args && FUNCTION_POINTER_TYPE_P (*node))
+    *node = TREE_TYPE (*node);
+
+  if (args && FUNC_OR_METHOD_TYPE_P (*node))
+    {
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
+	{
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
+
+	case 0:
+	  warning (OPT_Wattributes,
+		   "%qE attribute ignored because of argument %qE",
+		   name, TREE_VALUE (args));
+	  *no_add_attrs = true;
+	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
+	}
+
+      args = TREE_CHAIN (args);
+    }
+
+  if (args)
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
+
+  /* Warn about unmet expectations that the strub attribute works like a
+     qualifier.  ??? Could/should we extend it to the element/field types
+     here?  */
+  if (TREE_CODE (*node) == ARRAY_TYPE
+      || VECTOR_TYPE_P (*node)
+      || TREE_CODE (*node) == COMPLEX_TYPE)
+    warning (OPT_Wattributes,
+	     "attribute %qE does not apply to elements"
+	     " of non-scalar type %qT",
+	     name, *node);
+  else if (RECORD_OR_UNION_TYPE_P (*node))
+    warning (OPT_Wattributes,
+	     "attribute %qE does not apply to fields"
+	     " of aggregate type %qT",
+	     name, *node);
+
+  /* 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 += 2;
+
   return NULL_TREE;
 }
 
diff --git a/gcc/attribs.cc b/gcc/attribs.cc
index b8cb55b97df38..ee075b0fe1bbd 100644
--- a/gcc/attribs.cc
+++ b/gcc/attribs.cc
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "attribs.h"
 #include "fold-const.h"
+#include "ipa-strub.h"
 #include "stor-layout.h"
 #include "langhooks.h"
 #include "plugin.h"
@@ -779,8 +780,8 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  flags &= ~(int) ATTR_FLAG_TYPE_IN_PLACE;
 	}
 
-      if (spec->function_type_required && TREE_CODE (*anode) != FUNCTION_TYPE
-	  && TREE_CODE (*anode) != METHOD_TYPE)
+      if (spec->function_type_required
+	  && !FUNC_OR_METHOD_TYPE_P (*anode))
 	{
 	  if (TREE_CODE (*anode) == POINTER_TYPE
 	      && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
@@ -892,7 +893,24 @@ decl_attributes (tree *node, tree attributes, int flags,
 	      TYPE_NAME (tt) = *node;
 	    }
 
-	  *anode = cur_and_last_decl[0];
+	  if (*anode != cur_and_last_decl[0])
+	    {
+	      /* Even if !spec->function_type_required, allow the attribute
+		 handler to request the attribute to be applied to the function
+		 type, rather than to the function pointer type, by setting
+		 cur_and_last_decl[0] to the function type.  */
+	      if (!fn_ptr_tmp
+		  && POINTER_TYPE_P (*anode)
+		  && TREE_TYPE (*anode) == cur_and_last_decl[0]
+		  && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
+		{
+		  fn_ptr_tmp = TREE_TYPE (*anode);
+		  fn_ptr_quals = TYPE_QUALS (*anode);
+		  anode = &fn_ptr_tmp;
+		}
+	      *anode = cur_and_last_decl[0];
+	    }
+
 	  if (ret == error_mark_node)
 	    {
 	      warning (OPT_Wattributes, "%qE attribute ignored", name);
@@ -1495,9 +1513,20 @@ comp_type_attributes (const_tree type1, const_tree type2)
   if ((lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type1)) != NULL)
       ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) != NULL))
     return 0;
+  int strub_ret = strub_comptypes (CONST_CAST_TREE (type1),
+				   CONST_CAST_TREE (type2));
+  if (strub_ret == 0)
+    return strub_ret;
   /* As some type combinations - like default calling-convention - might
      be compatible, we have to call the target hook to get the final result.  */
-  return targetm.comp_type_attributes (type1, type2);
+  int target_ret = targetm.comp_type_attributes (type1, type2);
+  if (target_ret == 0)
+    return target_ret;
+  if (strub_ret == 2 || target_ret == 2)
+    return 2;
+  if (strub_ret == 1 && target_ret == 1)
+    return 1;
+  gcc_unreachable ();
 }
 
 /* PREDICATE acts as a function of type:
diff --git a/gcc/builtins.cc b/gcc/builtins.cc
index 8400adaf5b4db..a6dc923de5712 100644
--- a/gcc/builtins.cc
+++ b/gcc/builtins.cc
@@ -71,6 +71,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"
@@ -152,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);
@@ -5281,6 +5283,252 @@ 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 || 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 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);
+    }
+
+  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 (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);
+    }
+
+  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);
+
+      /* ??? It would be nice to use setmem or similar patterns here,
+	 but they do not necessarily obey the stack growth direction,
+	 which has security implications.  We also have to avoid calls
+	 (memset, bzero or any machine-specific ones), which are
+	 likely unsafe here (see TARGET_STRUB_MAY_USE_MEMSET).  */
+#ifndef STACK_GROWS_DOWNWARD
+      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));
+#else
+      rtx decr = plus_constant (Pmode, end, -ulen);
+      rtx dstm = gen_rtx_MEM (ptr_mode, end);
+
+      rtx_code_label *loop = gen_label_rtx ();
+      emit_label (loop);
+      emit_move_insn (end, force_operand (decr, NULL_RTX));
+      emit_move_insn (dstm, zero);
+#endif
+      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.  */
 
@@ -7608,6 +7856,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 1958b3abf598b..7dbaa803ab778 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -989,6 +989,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.cc b/gcc/c-family/c-attribs.cc
index 365319e642b1a..31ca21803002f 100644
--- a/gcc/c-family/c-attribs.cc
+++ b/gcc/c-family/c-attribs.cc
@@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "common/common-target.h"
 #include "langhooks.h"
 #include "tree-inline.h"
+#include "ipa-strub.h"
 #include "toplev.h"
 #include "tree-iterator.h"
 #include "opts.h"
@@ -69,6 +70,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 *);
@@ -321,6 +323,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 },
@@ -1430,6 +1434,84 @@ 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 && FUNCTION_POINTER_TYPE_P (*node))
+    *node = TREE_TYPE (*node);
+
+  if (args && FUNC_OR_METHOD_TYPE_P (*node))
+    {
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
+	{
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
+
+	case 0:
+	  warning (OPT_Wattributes,
+		   "%qE attribute ignored because of argument %qE",
+		   name, TREE_VALUE (args));
+	  *no_add_attrs = true;
+	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
+	}
+
+      args = TREE_CHAIN (args);
+    }
+
+  if (args)
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
+
+  /* Warn about unmet expectations that the strub attribute works like a
+     qualifier.  ??? Could/should we extend it to the element/field types
+     here?  */
+  if (TREE_CODE (*node) == ARRAY_TYPE
+      || VECTOR_TYPE_P (*node)
+      || TREE_CODE (*node) == COMPLEX_TYPE)
+    warning (OPT_Wattributes,
+	     "attribute %qE does not apply to elements"
+	     " of non-scalar type %qT",
+	     name, *node);
+  else if (RECORD_OR_UNION_TYPE_P (*node))
+    warning (OPT_Wattributes,
+	     "attribute %qE does not apply to fields"
+	     " of aggregate type %qT",
+	     name, *node);
+
+  /* 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 += 2;
+
+  return NULL_TREE;
+}
+
 /* Handle a "noinline" attribute; arguments as in
    struct attribute_spec.handler.  */
 
diff --git a/gcc/cgraph.h b/gcc/cgraph.h
index cedaaac3a45b7..177bd9dc5b7ac 100644
--- a/gcc/cgraph.h
+++ b/gcc/cgraph.h
@@ -153,7 +153,7 @@ public:
   void remove (void);
 
   /* Undo any definition or use of the symbol.  */
-  void reset (void);
+  void reset (bool preserve_comdat_group = false);
 
   /* Dump symtab node to F.  */
   void dump (FILE *f);
diff --git a/gcc/cgraphunit.cc b/gcc/cgraphunit.cc
index bccd2f2abb5a3..9a550a5cce645 100644
--- a/gcc/cgraphunit.cc
+++ b/gcc/cgraphunit.cc
@@ -384,7 +384,7 @@ symbol_table::process_new_functions (void)
    functions or variables.  */
 
 void
-symtab_node::reset (void)
+symtab_node::reset (bool preserve_comdat_group)
 {
   /* Reset our data structures so we can analyze the function again.  */
   analyzed = false;
@@ -395,7 +395,8 @@ symtab_node::reset (void)
   cpp_implicit_alias = false;
 
   remove_all_references ();
-  remove_from_same_comdat_group ();
+  if (!preserve_comdat_group)
+    remove_from_same_comdat_group ();
 
   if (cgraph_node *cn = dyn_cast <cgraph_node *> (this))
     {
diff --git a/gcc/common.opt b/gcc/common.opt
index feea4920c9d75..d2e1d606b442d 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2845,6 +2845,35 @@ fstrict-overflow
 Common
 Treat signed overflow as undefined.  Negated as -fwrapv -fwrapv-pointer.
 
+fstrub=disable
+Common RejectNegative Var(flag_strub, 0)
+Disable stack scrub entirely, disregarding strub attributes.
+
+fstrub=strict
+Common RejectNegative Var(flag_strub, -4)
+Enable stack scrub as per attributes, with strict call checking.
+
+; If any strub-enabling attribute is seen when the default or strict
+; initializer values are in effect, flag_strub is bumped up by 2.  The
+; scrub mode gate function will then bump these initializer values to
+; 0 if no strub-enabling attribute is seen.  This minimizes the strub
+; overhead.
+fstrub=relaxed
+Common RejectNegative Var(flag_strub, -3) Init(-3)
+Restore default strub mode: as per attributes, with relaxed checking.
+
+fstrub=all
+Common RejectNegative Var(flag_strub, 3)
+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.
+
 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 f8b0bb53ef5d4..0e9edd35ca348 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 internal interfaces.
 * Vector Extensions::   Using vector instructions through built-in functions.
 * Offsetof::            Special syntax for implementing @code{offsetof}.
 * __sync Builtins::     Legacy built-in functions for atomic memory access.
@@ -8950,6 +8951,263 @@ pid_t wait (wait_status_ptr_t p)
 @}
 @end smallexample
 
+@cindex @code{strub} type attribute
+@item strub
+This attribute defines stack-scrubbing properties of functions and
+variables.  Being a type attribute, it attaches to types, even when
+specified in function and variable declarations.  When applied to
+function types, it takes an optional string argument.  When applied to a
+pointer-to-function type, if the optional argument is given, it gets
+propagated to the function type.
+
+@smallexample
+/* A strub variable.  */
+int __attribute__ ((strub)) var;
+/* A strub variable that happens to be a pointer.  */
+__attribute__ ((strub)) int *strub_ptr_to_int;
+/* A pointer type that may point to a strub variable.  */
+typedef int __attribute__ ((strub)) *ptr_to_strub_int_type;
+
+/* A declaration of a strub function.  */
+extern int __attribute__ ((strub)) foo (void);
+/* A pointer to that strub function.  */
+int __attribute__ ((strub ("at-calls"))) (*ptr_to_strub_fn)(void) = foo;
+@end smallexample
+
+A function associated with @code{at-calls} @code{strub} mode
+(@code{strub("at-calls")}, or just @code{strub}) undergoes interface
+changes.  Its callers are adjusted to match the changes, and to scrub
+(overwrite with zeros) the stack space used by the called function after
+it returns.  The interface change makes the function type incompatible
+with an unadorned but otherwise equivalent type, so @emph{every}
+declaration and every type that may be used to call the function must be
+associated with this strub mode.
+
+A function associated with @code{internal} @code{strub} mode
+(@code{strub("internal")}) retains an unmodified, type-compatible
+interface, but it may be turned into a wrapper that calls the wrapped
+body using a custom interface.  The wrapper then scrubs the stack space
+used by the wrapped body.  Though the wrapped body has its stack space
+scrubbed, the wrapper does not, so arguments and return values may
+remain unscrubbed even when such a function is called by another
+function that enables @code{strub}.  This is why, when compiling with
+@option{-fstrub=strict}, a @code{strub} context is not allowed to call
+@code{internal} @code{strub} functions.
+
+@smallexample
+/* A declaration of an internal-strub function.  */
+extern int __attribute__ ((strub ("internal"))) bar (void);
+
+int __attribute__ ((strub))
+baz (void)
+@{
+  /* Ok, foo was declared above as an at-calls strub function.  */
+  foo ();
+  /* Not allowed in strict mode, otherwise allowed.  */
+  bar ();
+@}
+@end smallexample
+
+An automatically-allocated variable associated with the @code{strub}
+attribute causes the (immediately) enclosing function to have
+@code{strub} enabled.
+
+A statically-allocated variable associated with the @code{strub}
+attribute causes functions that @emph{read} it, through its @code{strub}
+data type, to have @code{strub} enabled.  Reading data by dereferencing
+a pointer to a @code{strub} data type has the same effect.  Note: The
+attribute does not carry over from a composite type to the types of its
+components, so the intended effect may not be obtained with non-scalar
+types.
+
+When selecting a @code{strub}-enabled mode for a function that is not
+explicitly associated with one, because of @code{strub} variables or
+data pointers, the function must satisfy @code{internal} mode viability
+requirements (see below), even when @code{at-calls} mode is also viable
+and, being more efficient, ends up selected as an optimization.
+
+@smallexample
+/* zapme is implicitly strub-enabled because of strub variables.
+   Optimization may change its strub mode, but not the requirements.  */
+static int
+zapme (int i)
+@{
+  /* A local strub variable enables strub.  */
+  int __attribute__ ((strub)) lvar;
+  /* Reading strub data through a pointer-to-strub enables strub.  */
+  lvar = * (ptr_to_strub_int_type) &i;
+  /* Writing to a global strub variable does not enable strub.  */
+  var = lvar;
+  /* Reading from a global strub variable enables strub.  */
+  return var;
+@}
+@end smallexample
+
+A @code{strub} context is the body (as opposed to the interface) of a
+function that has @code{strub} enabled, be it explicitly, by
+@code{at-calls} or @code{internal} mode, or implicitly, due to
+@code{strub} variables or command-line options.
+
+A function of a type associated with the @code{disabled} @code{strub}
+mode (@code{strub("disabled")} will not have its own stack space
+scrubbed.  Such functions @emph{cannot} be called from within
+@code{strub} contexts.
+
+In order to enable a function to be called from within @code{strub}
+contexts without having its stack space scrubbed, associate it with the
+@code{callable} @code{strub} mode (@code{strub("callable")}).
+
+When a function is not assigned a @code{strub} mode, explicitly or
+implicitly, the mode defaults to @code{callable}, except when compiling
+with @option{-fstrub=strict}, that causes @code{strub} mode to default
+to @code{disabled}.
+
+@example
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+ /* Implicitly disabled with -fstrub=strict, otherwise callable.  */
+extern int bah (void);
+
+int __attribute__ ((strub))
+bal (void)
+@{
+  /* Not allowed, bad is not strub-callable.  */
+  bad ();
+  /* Ok, bac is strub-callable.  */
+  bac ();
+  /* Not allowed with -fstrub=strict, otherwise allowed.  */
+  bah ();
+@}
+@end example
+
+Function types marked @code{callable} and @code{disabled} are not
+mutually compatible types, but the underlying interfaces are compatible,
+so it is safe to convert pointers between them, and to use such pointers
+or alternate declarations to call them.  Interfaces are also
+interchangeable between them and @code{internal} (but not
+@code{at-calls}!), but adding @code{internal} to a pointer type will not
+cause the pointed-to function to perform stack scrubbing.
+
+@example
+void __attribute__ ((strub))
+bap (void)
+@{
+  /* Assign a callable function to pointer-to-disabled.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bac;
+  /* Not allowed: calls disabled type in a strub context.  */
+  d_p ();
+
+  /* Assign a disabled function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bad;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an internal function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  c_p = bar;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an at-calls function to pointer-to-callable.
+     Flaggged as incompatible.  */
+  c_p = bal;
+  /* The call through an interface-incompatible type will not use the
+     modified interface expected by the at-calls function, so it is
+     likely to misbehave at runtime.  */
+  c_p ();
+@}
+@end example
+
+@code{Strub} contexts are never inlined into non-@code{strub} contexts.
+When an @code{internal}-strub function is split up, the wrapper can
+often be inlined, but the wrapped body @emph{never} is.  A function
+marked as @code{always_inline}, even if explicitly assigned
+@code{internal} strub mode, will not undergo wrapping, so its body gets
+inlined as required.
+
+@example
+inline int __attribute__ ((strub ("at-calls")))
+inl_atc (void)
+@{
+  /* This body may get inlined into strub contexts.  */
+@}
+
+inline int __attribute__ ((strub ("internal")))
+inl_int (void)
+@{
+  /* This body NEVER gets inlined, though its wrapper may.  */
+@}
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+@{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+@}
+
+void __attribute__ ((strub ("disabled")))
+bat (void)
+@{
+  /* Not allowed, cannot inline into a non-strub context.  */
+  inl_int_ali ();
+@}
+@end example
+
+@cindex strub eligibility and viability
+Some @option{-fstrub=*} command line options enable @code{strub} modes
+implicitly where viable.  A @code{strub} mode is only viable for a
+function if the function is eligible for that mode, and if other
+conditions, detailed below, are satisfied.  If it's not eligible for a
+mode, attempts to explicitly associate it with that mode are rejected
+with an error message.  If it is eligible, that mode may be assigned
+explicitly through this attribute, but implicit assignment through
+command-line options may involve additional viability requirements.
+
+A function is ineligible for @code{at-calls} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+@code{At-calls} @code{strub} mode, if not requested through the function
+type, is only viable for an eligible function if the function is not
+visible to other translation units, if it doesn't have its address
+taken, and if it is never called with a function type overrider.
+
+@smallexample
+/* bar is eligible for at-calls strub mode,
+   but not viable for that mode because it is visible to other units.
+   It is eligible and viable for internal strub mode.  */
+void bav () @{@}
+
+/* setp is eligible for at-calls strub mode,
+   but not viable for that mode because its address is taken.
+   It is eligible and viable for internal strub mode.  */
+void setp (void) @{ static void (*p)(void); = setp; @}
+@end smallexample
+
+A function is ineligible for @code{internal} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, or if attribute
+@code{noipa} is present.  For an @code{always_inline} function, meeting
+these requirements is enough to make it eligible.  Any function that has
+attribute @code{noclone}, that uses such extensions as non-local labels,
+computed gotos, alternate variable argument passing interfaces,
+@code{__builtin_next_arg}, or @code{__builtin_return_address}, or that
+takes too many (about 64Ki) arguments is ineligible, unless it is
+@code{always_inline}.  For @code{internal} @code{strub} mode, all
+eligible functions are viable.
+
+@smallexample
+/* flop is not eligible, thus not viable, for at-calls strub mode.
+   Likewise for internal strub mode.  */
+__attribute__ ((noipa)) void flop (void) @{@}
+
+/* flip is eligible and viable for at-calls strub mode.
+   It would be ineligible for internal strub mode, because of noclone,
+   if it weren't for always_inline.  With always_inline, noclone is not
+   an obstacle, so it is also eligible and viable for internal strub mode.  */
+inline __attribute__ ((noclone, always_inline)) void flip (void) @{@}
+@end smallexample
+
 @cindex @code{unused} type attribute
 @item unused
 When attached to a type (including a @code{union} or a @code{struct}),
@@ -12041,6 +12299,55 @@ option is in effect.  Such calls should only be made in debugging
 situations.
 @enddefbuiltin
 
+@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 internal interfaces
+
+Stack scrubbing involves cooperation between a @code{strub} context,
+i.e., a function whose stack frame is to be zeroed-out, and its callers.
+The caller initializes a stack watermark, the @code{strub} context
+updates the watermark according to its stack use, and the caller zeroes
+it out once it regains control, whether by the callee's returning or by
+an exception.
+
+Each of these steps is performed by a different builtin function call.
+Calls to these builtins are introduced automatically, in response to
+@code{strub} attributes and command-line options; they are not expected
+to be explicitly called by source code.
+
+The functions that implement the builtins are available in libgcc but,
+depending on optimization levels, they are expanded internally, adjusted
+to account for inlining, and sometimes combined/deferred (e.g. passing
+the caller-supplied watermark on to callees, refraining from erasing
+stack areas that the caller will) to enable tail calls and to optimize
+for code size.
+
+@deftypefn {Built-in Function} {void} __builtin___strub_enter (void **@var{wmptr})
+This function initializes a stack @var{watermark} variable with the
+current top of the stack.  A call to this builtin function is introduced
+before entering a @code{strub} context.  It remains as a function call
+if optimization is not enabled.
+@end deftypefn
+
+@deftypefn {Built-in Function} {void} __builtin___strub_update (void **@var{wmptr})
+This function updates a stack @var{watermark} variable with the current
+top of the stack, if it tops the previous watermark.  A call to this
+builtin function is inserted within @code{strub} contexts, whenever
+additional stack space may have been used.  It remains as a function
+call at optimization levels lower than 2.
+@end deftypefn
+
+@deftypefn {Built-in Function} {void} __builtin___strub_leave (void **@var{wmptr})
+This function overwrites the memory area between the current top of the
+stack, and the @var{watermark}ed address.  A call to this builtin
+function is inserted after leaving a @code{strub} context.  It remains
+as a function call at optimization levels lower than 3, and it is guarded by
+a condition at level 2.
+@end deftypefn
+
 @node Vector Extensions
 @section Using Vector Instructions through Built-in Functions
 
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 45a9f2372c969..b8c3c2863fa74 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -642,6 +642,8 @@ Objective-C and Objective-C++ Dialects}.
 -fstack-protector-explicit  -fstack-check
 -fstack-limit-register=@var{reg}  -fstack-limit-symbol=@var{sym}
 -fno-stack-limit  -fsplit-stack
+-fstrub=disable  -fstrub=strict  -fstrub=relaxed
+-fstrub=all  -fstrub=at-calls  -fstrub=internal
 -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 -fvtv-counts  -fvtv-debug
 -finstrument-functions  -finstrument-functions-once
@@ -17449,6 +17451,56 @@ 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.
 
+@opindex -fstrub=disable
+@item -fstrub=disable
+Disable stack scrubbing entirely, ignoring any @code{strub} attributes.
+See @xref{Common Type Attributes}.
+
+@opindex fstrub=strict
+@item -fstrub=strict
+Functions default to @code{strub} mode @code{disabled}, and apply
+@option{strict}ly the restriction that only functions associated with
+@code{strub}-@code{callable} modes (@code{at-calls}, @code{callable} and
+@code{always_inline} @code{internal}) are @code{callable} by functions
+with @code{strub}-enabled modes (@code{at-calls} and @code{internal}).
+
+@opindex fstrub=relaxed
+@item -fstrub=relaxed
+Restore the default stack scrub (@code{strub}) setting, namely,
+@code{strub} is only enabled as required by @code{strub} attributes
+associated with function and data types.  @code{Relaxed} means that
+strub contexts are only prevented from calling functions explicitly
+associated with @code{strub} mode @code{disabled}.  This option is only
+useful to override other @option{-fstrub=*} options that precede it in
+the command line.
+
+@opindex fstrub=at-calls
+@item -fstrub=at-calls
+Enable @code{at-calls} @code{strub} mode where viable.  The primary use
+of this option is for testing.  It exercises the @code{strub} machinery
+in scenarios strictly local to a translation unit.  This @code{strub}
+mode modifies function interfaces, so any function that is visible to
+other translation units, or that has its address taken, will @emph{not}
+be affected by this option.  Optimization options may also affect
+viability.  See the @code{strub} attribute documentation for details on
+viability and eligibility requirements.
+
+@opindex fstrub=internal
+@item -fstrub=internal
+Enable @code{internal} @code{strub} mode where viable.  The primary use
+of this option is for testing.  This option is intended to exercise
+thoroughly parts of the @code{strub} machinery that implement the less
+efficient, but interface-preserving @code{strub} mode.  Functions that
+would not be affected by this option are quite uncommon.
+
+@opindex fstrub=all
+@item -fstrub=all
+Enable some @code{strub} mode where viable.  When both strub modes are
+viable, @code{at-calls} is preferred.  @option{-fdump-ipa-strubm} adds
+function attributes that tell which mode was selected for each function.
+The primary use of this option is for testing, to exercise thoroughly
+the @code{strub} machinery.
+
 @opindex fvtable-verify
 @item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 This option is only available when compiling C++ code.
@@ -19354,6 +19406,14 @@ and inlining decisions.
 @item inline
 Dump after function inlining.
 
+@item strubm
+Dump after selecting @code{strub} modes, and recording the selections as
+function attributes.
+
+@item strub
+Dump @code{strub} transformations: interface changes, function wrapping,
+and insertion of builtin calls for stack scrubbing and watermarking.
+
 @end table
 
 Additionally, the options @option{-optimized}, @option{-missed},
diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index 95ba56e05ae4a..903ebecc8e4d3 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -3399,6 +3399,25 @@ in DWARF 2 debug information.  The default is zero.  A different value
 may reduce the size of debug information on some ports.
 @end defmac
 
+@defmac TARGET_STRUB_USE_DYNAMIC_ARRAY
+If defined to nonzero, @code{__strub_leave} will allocate a dynamic
+array covering the stack range that needs scrubbing before clearing it.
+Allocating the array tends to make scrubbing slower, but it enables the
+scrubbing to be safely implemented with a @code{memset} call, which
+could make up for the difference.
+@end defmac
+
+@defmac TARGET_STRUB_MAY_USE_MEMSET
+If defined to nonzero, enable @code{__strub_leave} to be optimized so as
+to call @code{memset} for stack scrubbing.  This is only enabled by
+default if @code{TARGET_STRUB_USE_DYNAMIC_ARRAY} is enabled; it's not
+advisable to enable it otherwise, since @code{memset} would then likely
+overwrite its own stack frame, but it might work if the target ABI
+enables @code{memset} to not use the stack at all, not even for
+arguments or its return address, and its implementation is trivial
+enough that it doesn't use a stack frame.
+@end defmac
+
 @node Exception Handling
 @subsection Exception Handling Support
 @cindex exception handling
diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
index 4ac96dc357d35..25dbb37fb9445 100644
--- a/gcc/doc/tm.texi.in
+++ b/gcc/doc/tm.texi.in
@@ -2648,6 +2648,25 @@ in DWARF 2 debug information.  The default is zero.  A different value
 may reduce the size of debug information on some ports.
 @end defmac
 
+@defmac TARGET_STRUB_USE_DYNAMIC_ARRAY
+If defined to nonzero, @code{__strub_leave} will allocate a dynamic
+array covering the stack range that needs scrubbing before clearing it.
+Allocating the array tends to make scrubbing slower, but it enables the
+scrubbing to be safely implemented with a @code{memset} call, which
+could make up for the difference.
+@end defmac
+
+@defmac TARGET_STRUB_MAY_USE_MEMSET
+If defined to nonzero, enable @code{__strub_leave} to be optimized so as
+to call @code{memset} for stack scrubbing.  This is only enabled by
+default if @code{TARGET_STRUB_USE_DYNAMIC_ARRAY} is enabled; it's not
+advisable to enable it otherwise, since @code{memset} would then likely
+overwrite its own stack frame, but it might work if the target ABI
+enables @code{memset} to not use the stack at all, not even for
+arguments or its return address, and its implementation is trivial
+enough that it doesn't use a stack frame.
+@end defmac
+
 @node Exception Handling
 @subsection Exception Handling Support
 @cindex exception handling
diff --git a/gcc/ipa-inline.cc b/gcc/ipa-inline.cc
index efc8df7d4e0f6..3f8299b8ef47e 100644
--- a/gcc/ipa-inline.cc
+++ b/gcc/ipa-inline.cc
@@ -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"
 
 /* Inliner uses greedy algorithm to inline calls in a priority order.
    Badness is used as the key in a Fibonacci heap which roughly corresponds
@@ -443,6 +444,11 @@ can_inline_edge_p (struct cgraph_edge *e, bool report,
       inlinable = false;
     }
 
+  if (inlinable && !strub_inlinable_to_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.cc b/gcc/ipa-split.cc
index 6730f4f9d0e31..1a7285ff5dcf8 100644
--- a/gcc/ipa-split.cc
+++ b/gcc/ipa-split.cc
@@ -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.  */
 
@@ -1811,6 +1812,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.cc b/gcc/ipa-strub.cc
new file mode 100644
index 0000000000000..73a8771315c35
--- /dev/null
+++ b/gcc/ipa-strub.cc
@@ -0,0 +1,3450 @@
+/* strub (stack scrubbing) support.
+   Copyright (C) 2021-2022 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"
+#include "symtab-thunks.h"
+#include "attr-fnspec.h"
+
+/* 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,
+
+};
+
+/* Look up a strub attribute in TYPE, and return it.  */
+
+static tree
+get_strub_attr_from_type (tree type)
+{
+  return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
+}
+
+/* Look up a strub attribute in DECL or in its type, and return it.  */
+
+static tree
+get_strub_attr_from_decl (tree decl)
+{
+  tree ret = lookup_attribute ("strub", DECL_ATTRIBUTES (decl));
+  if (ret)
+    return ret;
+  return get_strub_attr_from_type (TREE_TYPE (decl));
+}
+
+/* Define a function to cache identifier ID, to be used as a strub attribute
+   parameter for a strub mode named after NAME.  */
+#define DEF_STRUB_IDS(NAME, ID)				\
+static inline tree get_strub_mode_id_ ## NAME () {	\
+  static tree identifier = NULL_TREE;			\
+  if (!identifier)					\
+    identifier = get_identifier (ID);			\
+  return identifier;					\
+}
+/* Same as DEF_STRUB_IDS, but use the string expansion of NAME as ID.  */
+#define DEF_STRUB_ID(NAME)				\
+DEF_STRUB_IDS (NAME, #NAME)
+
+/* Define functions for each of the strub mode identifiers.
+   Expose dashes rather than underscores.  */
+DEF_STRUB_ID (disabled)
+DEF_STRUB_IDS (at_calls, "at-calls")
+DEF_STRUB_ID (internal)
+DEF_STRUB_ID (callable)
+DEF_STRUB_ID (wrapped)
+DEF_STRUB_ID (wrapper)
+DEF_STRUB_ID (inlinable)
+DEF_STRUB_IDS (at_calls_opt, "at-calls-opt")
+
+/* Release the temporary macro names.  */
+#undef DEF_STRUB_IDS
+#undef DEF_STRUB_ID
+
+/* Return the identifier corresponding to strub MODE.  */
+
+static tree
+get_strub_mode_attr_parm (enum strub_mode mode)
+{
+  switch (mode)
+    {
+    case STRUB_DISABLED:
+      return get_strub_mode_id_disabled ();
+
+    case STRUB_AT_CALLS:
+      return get_strub_mode_id_at_calls ();
+
+    case STRUB_INTERNAL:
+      return get_strub_mode_id_internal ();
+
+    case STRUB_CALLABLE:
+      return get_strub_mode_id_callable ();
+
+    case STRUB_WRAPPED:
+      return get_strub_mode_id_wrapped ();
+
+    case STRUB_WRAPPER:
+      return get_strub_mode_id_wrapper ();
+
+    case STRUB_INLINABLE:
+      return get_strub_mode_id_inlinable ();
+
+    case STRUB_AT_CALLS_OPT:
+      return get_strub_mode_id_at_calls_opt ();
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+/* Return the parmeters (TREE_VALUE) for a strub attribute of MODE.
+   We know we use a single parameter, so we bypass the creation of a
+   tree list.  */
+
+static tree
+get_strub_mode_attr_value (enum strub_mode mode)
+{
+  return get_strub_mode_attr_parm (mode);
+}
+
+/* Determine whether ID is a well-formed strub mode-specifying attribute
+   parameter for a function (type).  Only user-visible modes are accepted, and
+   ID must be non-NULL.
+
+   For unacceptable parms, return 0, otherwise a nonzero value as below.
+
+   If the parm enables strub, return positive, otherwise negative.
+
+   If the affected type must be a distinct, incompatible type,return an integer
+   of absolute value 2, otherwise 1.  */
+
+int
+strub_validate_fn_attr_parm (tree id)
+{
+  int ret;
+  const char *s = NULL;
+  size_t len = 0;
+
+  /* do NOT test for NULL.  This is only to be called with non-NULL arguments.
+     We assume that the strub parameter applies to a function, because only
+     functions accept an explicit argument.  If we accepted NULL, and we
+     happened to be called to verify the argument for a variable, our return
+     values would be wrong.  */
+  if (TREE_CODE (id) == STRING_CST)
+    {
+      s = TREE_STRING_POINTER (id);
+      len = TREE_STRING_LENGTH (id) - 1;
+    }
+  else if (TREE_CODE (id) == IDENTIFIER_NODE)
+    {
+      s = IDENTIFIER_POINTER (id);
+      len = IDENTIFIER_LENGTH (id);
+    }
+  else
+    return 0;
+
+  enum strub_mode mode;
+
+  if (len != 8)
+    return 0;
+
+  switch (s[0])
+    {
+    case 'd':
+      mode = STRUB_DISABLED;
+      ret = -1;
+      break;
+
+    case 'a':
+      mode = STRUB_AT_CALLS;
+      ret = 2;
+      break;
+
+    case 'i':
+      mode = STRUB_INTERNAL;
+      ret = 1;
+      break;
+
+    case 'c':
+      mode = STRUB_CALLABLE;
+      ret = -2;
+      break;
+
+    default:
+      /* Other parms are for internal use only.  */
+      return 0;
+    }
+
+  tree mode_id = get_strub_mode_attr_parm (mode);
+
+  if (TREE_CODE (id) == IDENTIFIER_NODE
+      ? id != mode_id
+      : strncmp (s, IDENTIFIER_POINTER (mode_id), len) != 0)
+    return 0;
+
+  return ret;
+}
+
+/* Return the strub mode from STRUB_ATTR.  VAR_P should be TRUE if the attribute
+   is taken from a variable, rather than from a function, or a type thereof.  */
+
+static enum strub_mode
+get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
+{
+  enum strub_mode mode = STRUB_DISABLED;
+
+  if (strub_attr)
+    {
+      if (!TREE_VALUE (strub_attr))
+	mode = !var_p ? STRUB_AT_CALLS : STRUB_INTERNAL;
+      else
+	{
+	  gcc_checking_assert (!var_p);
+	  tree id = TREE_VALUE (strub_attr);
+	  if (TREE_CODE (id) == TREE_LIST)
+	    id = TREE_VALUE (id);
+	  const char *s = (TREE_CODE (id) == STRING_CST
+			   ? TREE_STRING_POINTER (id)
+			   : IDENTIFIER_POINTER (id));
+	  size_t len = (TREE_CODE (id) == STRING_CST
+			? TREE_STRING_LENGTH (id) - 1
+			: IDENTIFIER_LENGTH (id));
+
+	  switch (len)
+	    {
+	    case 7:
+	      switch (s[6])
+		{
+		case 'r':
+		  mode = STRUB_WRAPPER;
+		  break;
+
+		case 'd':
+		  mode = STRUB_WRAPPED;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 8:
+	      switch (s[0])
+		{
+		case 'd':
+		  mode = STRUB_DISABLED;
+		  break;
+
+		case 'a':
+		  mode = STRUB_AT_CALLS;
+		  break;
+
+		case 'i':
+		  mode = STRUB_INTERNAL;
+		  break;
+
+		case 'c':
+		  mode = STRUB_CALLABLE;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 9:
+	      mode = STRUB_INLINABLE;
+	      break;
+
+	    case 12:
+	      mode = STRUB_AT_CALLS_OPT;
+	      break;
+
+	    default:
+	      gcc_unreachable ();
+	    }
+
+	  gcc_checking_assert (TREE_CODE (id) == IDENTIFIER_NODE
+			       ? id == get_strub_mode_attr_parm (mode)
+			       : strncmp (IDENTIFIER_POINTER
+					  (get_strub_mode_attr_parm (mode)),
+					  s, len) == 0);
+	}
+    }
+
+  return mode;
+}
+
+/* Look up, decode and return the strub mode associated with FNDECL.  */
+
+static enum strub_mode
+get_strub_mode_from_fndecl (tree fndecl)
+{
+  return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
+}
+
+/* Look up, decode and return the strub mode associated with NODE.  */
+
+static enum strub_mode
+get_strub_mode (cgraph_node *node)
+{
+  return get_strub_mode_from_fndecl (node->decl);
+}
+
+/* Look up, decode and return the strub mode associated with TYPE.  */
+
+static enum strub_mode
+get_strub_mode_from_type (tree type)
+{
+  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 >= -1 && !var_p)
+    return STRUB_CALLABLE;
+
+  return STRUB_DISABLED;
+}
+
+\f
+/* Return TRUE iff NODE calls builtin va_start.  */
+
+static bool
+calls_builtin_va_start_p (cgraph_node *node)
+{
+  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;
+}
+
+/* Return TRUE iff NODE calls builtin apply_args, and optionally REPORT it.  */
+
+static bool
+calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
+{
+  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 (e->call_stmt
+		? gimple_location (e->call_stmt)
+		: DECL_SOURCE_LOCATION (node->decl),
+		"at-calls %<strub%> does not support call to %qD",
+		cdecl);
+    }
+
+  return result;
+}
+
+/* Return TRUE iff NODE carries the always_inline attribute.  */
+
+static inline bool
+strub_always_inline_p (cgraph_node *node)
+{
+  return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
+}
+
+/* Return TRUE iff NODE is potentially eligible for any strub-enabled mode, and
+   optionally REPORT the reasons for ineligibility.  */
+
+static inline bool
+can_strub_p (cgraph_node *node, bool report = false)
+{
+  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;
+}
+
+/* Return TRUE iff NODE is eligible for at-calls strub, and optionally REPORT
+   the reasons for ineligibility.  Besides general non-eligibility for
+   strub-enabled modes, at-calls rules out calling builtin apply_args.  */
+
+static bool
+can_strub_at_calls_p (cgraph_node *node, bool report = false)
+{
+  bool result = !report || can_strub_p (node, report);
+
+  if (!result && !report)
+    return result;
+
+  return !calls_builtin_apply_args_p (node, report);
+}
+
+/* Return TRUE iff the called function (pointer or, if available,
+   decl) undergoes a significant type conversion for the call.  Strub
+   mode changes between function types, and other non-useless type
+   conversions, are regarded as significant.  When the function type
+   is overridden, the effective strub mode for the call is that of the
+   call fntype, rather than that of the pointer or of the decl.
+   Functions called with type overrides cannot undergo type changes;
+   it's as if their address was taken, so they're considered
+   non-viable for implicit at-calls strub mode.  */
+
+static inline bool
+strub_call_fntype_override_p (const gcall *gs)
+{
+  if (gimple_call_internal_p (gs))
+    return false;
+  tree fn_type = TREE_TYPE (TREE_TYPE (gimple_call_fn (gs)));
+  if (tree decl = gimple_call_fndecl (gs))
+    fn_type = TREE_TYPE (decl);
+
+  /* We do NOT want to take the mode from the decl here.  This
+     function is used to tell whether we can change the strub mode of
+     a function, and whether the effective mode for the call is to be
+     taken from the decl or from an overrider type.  When the strub
+     mode is explicitly declared, or overridden with a type cast, the
+     difference will be noticed in function types.  However, if the
+     strub mode is implicit due to e.g. strub variables or -fstrub=*
+     command-line flags, we will adjust call types along with function
+     types.  In either case, the presence of type or strub mode
+     overriders in calls will prevent a function from having its strub
+     modes changed in ways that would imply type changes, but taking
+     strub modes from decls would defeat this, since we set strub
+     modes and then call this function to tell whether the original
+     type was overridden to decide whether to adjust the call.  We
+     need the answer to be about the type, not the decl.  */
+  enum strub_mode mode = get_strub_mode_from_type (fn_type);
+  return (get_strub_mode_from_type (gs->u.fntype) != mode
+	  || !useless_type_conversion_p (gs->u.fntype, fn_type));
+}
+
+/* Return TRUE iff NODE is called directly with a type override.  */
+
+static bool
+called_directly_with_type_override_p (cgraph_node *node, void *)
+{
+  for (cgraph_edge *e = node->callers; e; e = e->next_caller)
+    if (e->call_stmt && strub_call_fntype_override_p (e->call_stmt))
+      return true;
+
+  return false;
+}
+
+/* Return TRUE iff NODE or any other nodes aliased to it are called
+   with type overrides.  We can't safely change the type of such
+   functions.  */
+
+static bool
+called_with_type_override_p (cgraph_node *node)
+{
+  return (node->call_for_symbol_thunks_and_aliases
+	  (called_directly_with_type_override_p, NULL, true, true));
+}
+
+/* Symbolic macro for the max number of arguments that internal strub may add to
+   a function.  */
+
+#define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
+
+/* We can't perform internal strubbing if the function body involves certain
+   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 (e->call_stmt
+		    ? gimple_location (e->call_stmt)
+		    : DECL_SOURCE_LOCATION (node->decl),
+		    "%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 references are not preserved when copying.  If referenced
+	 in nested functions, as in 920415-1.c and 920721-4.c their decls get
+	 remapped independently.  The exclusion below might be too broad, in
+	 that we might be able to support correctly cases in which the labels
+	 are only used internally in a function, but disconnecting forced labels
+	 from their original declarations is undesirable in general.  */
+      basic_block bb;
+      FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+	for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+	     !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);
+
+	    if (!FORCED_LABEL (target))
+	      continue;
+
+	    result = false;
+
+	    if (!report)
+	      return result;
+
+	    sorry_at (gimple_location (label_stmt),
+		      "internal %<strub%> does not support forced 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;
+}
+
+/* Return TRUE iff NODE has any strub-requiring local variable, or accesses (as
+   in reading) any variable through a strub-requiring type.  */
+
+static bool
+strub_from_body_p (cgraph_node *node)
+{
+  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-requiring types.
+     ??? Compound types don't propagate the strub requirement to
+     component types.  */
+  basic_block bb;
+  FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+	 !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;
+    }
+}
+
+/* Compute the strub mode to be used for NODE.  STRUB_ATTR should be the strub
+   attribute,found for NODE, if any.  */
+
+static enum strub_mode
+compute_strub_mode (cgraph_node *node, tree strub_attr)
+{
+  enum strub_mode req_mode = get_strub_mode_from_attr (strub_attr);
+
+  gcc_checking_assert (flag_strub >= -2 && flag_strub <= 3);
+
+  /* Symbolic encodings of the -fstrub-* flags.  */
+  /* Enable strub when explicitly requested through attributes to functions or
+     variables, reporting errors if the requests cannot be satisfied.  */
+  const bool strub_flag_auto = flag_strub < 0;
+  /* strub_flag_auto with strub call verification; without this, functions are
+     implicitly callable.  */
+  const bool strub_flag_strict = flag_strub < -1;
+  /* Disable strub altogether, ignore attributes entirely.  */
+  const bool strub_flag_disabled = flag_strub == 0;
+  /* On top of _auto, also enable strub implicitly for functions that can
+     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_strict
+	      || 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->externally_visible
+		   || (node->binds_to_current_def_p ()
+		       && node->can_be_local_p ()))
+	       && node->only_called_directly_p ()
+	       && !called_with_type_override_p (node))));
+  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)
+       : (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;
+}
+
+/* Set FNDT's strub mode to callable.
+   FNDT may be a function decl or a function type.  */
+
+void
+strub_make_callable (tree fndt)
+{
+  strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
+}
+
+/* Set NODE to strub MODE.  Report incompatibilities between MODE and the mode
+   requested through explicit attributes, and cases of non-eligibility.  */
+
+static void
+set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
+{
+  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 %qE selected for %qD, when %qE was requested",
+		    get_strub_mode_attr_parm (mode),
+		    node->decl,
+		    get_strub_mode_attr_parm (req_mode));
+	  if (node->alias)
+	    {
+	      cgraph_node *target = node->ultimate_alias_target ();
+	      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);
+}
+
+/* Compute and set NODE's strub mode.  */
+
+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);
+}
+
+\f
+/* Non-strub functions shouldn't be called from within strub contexts,
+   except through callable ones.  Always inline strub functions can
+   only be called from strub functions.  */
+
+static bool
+strub_callable_from_p (strub_mode caller_mode, strub_mode callee_mode)
+{
+  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 >= -1);
+
+    case STRUB_DISABLED:
+      return false;
+
+    case STRUB_CALLABLE:
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  return true;
+}
+
+/* Return TRUE iff CALLEE can be inlined into CALLER.  We wish to avoid inlining
+   WRAPPED functions back into their WRAPPERs.  More generally, we wish to avoid
+   inlining strubbed functions into non-strubbed ones.  CALLER doesn't have to
+   be an immediate caller of CALLEE: the immediate caller may have already been
+   cloned for inlining, and then CALLER may be further up the original call
+   chain.  ???  It would be nice if our own caller would retry inlining callee
+   if caller gets inlined.  */
+
+bool
+strub_inlinable_to_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:
+      /* When we consider inlining, we've already verified callability, so we
+	 can even inline callable and then disabled into a strub context.  That
+	 will get strubbed along with the context, so it's hopefully not a
+	 problem.  */
+      return true;
+
+    default:
+      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 types T1 and T2 are strub-compatible.  Return 1 if the strub modes
+   are the same, 2 if they are interchangeable, and 0 otherwise.  */
+
+int
+strub_comptypes (tree t1, tree t2)
+{
+  if (TREE_CODE (t1) != TREE_CODE (t2))
+    return 0;
+
+  enum strub_mode m1 = get_strub_mode_from_type (t1);
+  enum strub_mode m2 = get_strub_mode_from_type (t2);
+
+  if (m1 == m2)
+    return 1;
+
+  /* We're dealing with types, so only strub modes that can be selected by
+     attributes in the front end matter.  If either mode is at-calls (for
+     functions) or internal (for variables), the conversion is not
+     compatible.  */
+  bool var_p = !FUNC_OR_METHOD_TYPE_P (t1);
+  enum strub_mode mr = var_p ? STRUB_INTERNAL : STRUB_AT_CALLS;
+  if (m1 == mr || m2 == mr)
+    return 0;
+
+  return 2;
+}
+
+/* Return the effective strub mode used for CALL, and set *TYPEP to
+   the effective type used for the call.  The effective type and mode
+   are those of the callee, unless the call involves a typecast.  */
+
+static enum strub_mode
+effective_strub_mode_for_call (gcall *call, tree *typep)
+{
+  tree type;
+  enum strub_mode mode;
+
+  if (strub_call_fntype_override_p (call))
+    {
+      type = gimple_call_fntype (call);
+      mode = get_strub_mode_from_type (type);
+    }
+  else
+    {
+      type = TREE_TYPE (TREE_TYPE (gimple_call_fn (call)));
+      tree decl = gimple_call_fndecl (call);
+      if (decl)
+	mode = get_strub_mode_from_fndecl (decl);
+      else
+	mode = get_strub_mode_from_type (type);
+    }
+
+  if (typep)
+    *typep = type;
+
+  return mode;
+}
+
+/* Create a distinct copy of the type of NODE's function, and change
+   the fntype of all calls to it with the same main type to the new
+   type.  */
+
+static void
+distinctify_node_type (cgraph_node *node)
+{
+  tree old_type = TREE_TYPE (node->decl);
+  tree new_type = build_distinct_type_copy (old_type);
+  tree new_ptr_type = NULL_TREE;
+
+  /* Remap any calls to node->decl that use old_type, or a variant
+     thereof, to new_type as well.  We don't look for aliases, their
+     declarations will have their types changed independently, and
+     we'll adjust their fntypes then.  */
+  for (cgraph_edge *e = node->callers; e; e = e->next_caller)
+    {
+      if (!e->call_stmt)
+	continue;
+      tree fnaddr = gimple_call_fn (e->call_stmt);
+      gcc_checking_assert (TREE_CODE (fnaddr) == ADDR_EXPR
+			   && TREE_OPERAND (fnaddr, 0) == node->decl);
+      if (strub_call_fntype_override_p (e->call_stmt))
+	continue;
+      if (!new_ptr_type)
+	new_ptr_type = build_pointer_type (new_type);
+      TREE_TYPE (fnaddr) = new_ptr_type;
+      gimple_call_set_fntype (e->call_stmt, new_type);
+    }
+
+  TREE_TYPE (node->decl) = new_type;
+}
+
+/* Return TRUE iff TYPE and any variants have the same strub mode.  */
+
+static bool
+same_strub_mode_in_variants_p (tree type)
+{
+  enum strub_mode mode = get_strub_mode_from_type (type);
+
+  for (tree other = TYPE_MAIN_VARIANT (type);
+       other != NULL_TREE; other = TYPE_NEXT_VARIANT (other))
+    if (type != other && mode != get_strub_mode_from_type (other))
+      return false;
+
+  /* Check that the canonical type, if set, either is in the same
+     variant chain, or has the same strub mode as type.  Also check
+     the variants of the canonical type.  */
+  if (TYPE_CANONICAL (type)
+      && (TYPE_MAIN_VARIANT (TYPE_CANONICAL (type))
+	  != TYPE_MAIN_VARIANT (type)))
+    {
+      if (mode != get_strub_mode_from_type (TYPE_CANONICAL (type)))
+	return false;
+      else
+	return same_strub_mode_in_variants_p (TYPE_CANONICAL (type));
+    }
+
+  return true;
+}
+
+/* Check that strub functions don't call non-strub functions, and that
+   always_inline strub functions are only called by strub
+   functions.  */
+
+static void
+verify_strub ()
+{
+  cgraph_node *node;
+
+  /* It's expected that check strub-wise pointer type compatibility of variables
+     and of functions is already taken care of by front-ends, on account of the
+     attribute's being marked as affecting type identity and of the creation of
+     distinct types.  */
+
+  /* Check that call targets in strub contexts have strub-callable types.  */
+
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+  {
+    enum strub_mode caller_mode = get_strub_mode (node);
+
+    for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
+      {
+	gcc_checking_assert (e->indirect_unknown_callee);
+
+	if (!e->call_stmt)
+	  continue;
+
+	enum strub_mode callee_mode
+	  = effective_strub_mode_for_call (e->call_stmt, NULL);
+
+	if (!strub_callable_from_p (caller_mode, callee_mode))
+	  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 (!e->call_stmt)
+	  continue;
+
+	tree callee_fntype;
+	enum strub_mode callee_mode
+	  = effective_strub_mode_for_call (e->call_stmt, &callee_fntype);
+
+	if (!strub_callable_from_p (caller_mode, callee_mode))
+	  {
+	    if (callee_mode == 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)
+		     && caller_mode == STRUB_INTERNAL)
+	      /* This is ok, it will be kept in the STRUB_WRAPPER, and removed
+		 from the STRUB_WRAPPED's strub context.  */
+	      continue;
+	    else if (!strub_call_fntype_override_p (e->call_stmt))
+	      error_at (gimple_location (e->call_stmt),
+			"calling non-%<strub%> %qD in %<strub%> context %qD",
+			e->callee->decl, node->decl);
+	    else
+	      error_at (gimple_location (e->call_stmt),
+			"calling %qD using non-%<strub%> type %qT"
+			" in %<strub%> context %qD",
+			e->callee->decl, callee_fntype, node->decl);
+	  }
+      }
+  }
+}
+
+namespace {
+
+/* Define a pass to compute strub modes.  */
+const pass_data pass_data_ipa_strub_mode = {
+  SIMPLE_IPA_PASS,
+  "strubm",
+  OPTGROUP_NONE,
+  TV_NONE,
+  PROP_cfg, // properties_required
+  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 relaxed (-3) and strict (-4) settings, that only enable strub at a
+       function or variable attribute's request, the attribute handler changes
+       flag_strub to -1 or -2, respectively, if any strub-enabling occurence of
+       the attribute is found.  Therefore, if it remains at -3 or -4, nothing
+       that would enable strub was found, so we can disable it and avoid the
+       overhead.  */
+    if (flag_strub < -2)
+      flag_strub = 0;
+    return flag_strub;
+  }
+  virtual unsigned int execute (function *);
+};
+
+/* Define a pass to introduce strub transformations.  */
+const pass_data pass_data_ipa_strub = {
+  SIMPLE_IPA_PASS,
+  "strub",
+  OPTGROUP_NONE,
+  TV_NONE,
+  PROP_cfg | PROP_ssa, // 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 && !seen_error (); }
+  virtual unsigned int execute (function *);
+
+  /* Define on demand and cache some types we use often.  */
+#define DEF_TYPE(NAME, INIT)			\
+  static inline tree get_ ## NAME () {		\
+    static tree type = NULL_TREE;		\
+    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 non-strub builtins on demand.  */
+#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 strub builtins on demand.  */
+#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);			\
+      }								\
+    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 strub identifiers on demand.  */
+#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, tree);
+  static inline void adjust_at_calls_calls (cgraph_node *);
+
+  /* Add to SEQ a call to the strub watermark update builtin, taking NODE's
+     location if given.  Optionally add the corresponding edge from NODE, with
+     execution frequency COUNT.  Return the modified SEQ.  */
+
+  static inline gimple_seq
+  call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
+			 gimple_seq seq = NULL)
+    {
+      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)
+	node->create_edge (cgraph_node::get_create (uwm), update, count, false);
+      return seq;
+    }
+
+};
+
+} // anon namespace
+
+/* Gather with this type a collection of parameters that we're turning into
+   explicit references.  */
+
+typedef hash_set<tree> indirect_parms_t;
+
+/* Dereference OP's incoming turned-into-reference parm if it's an
+   INDIRECT_PARMS or an ADDR_EXPR thereof.  Set *REC and return according to
+   gimple-walking expectations.  */
+
+static tree
+maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
+{
+  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)))
+	{
+	  op = TREE_OPERAND (op, 0);
+	  return op;
+	}
+    }
+
+  return NULL_TREE;
+}
+
+/* A gimple-walking function that adds dereferencing to indirect parms.  */
+
+static tree
+walk_make_indirect (tree *op, int *rec, void *arg)
+{
+  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;
+}
+
+/* A gimple-walking function that turns any non-gimple-val ADDR_EXPRs into a
+   separate SSA.  Though addresses of e.g. parameters, and of members thereof,
+   are gimple vals, turning parameters into references, with an extra layer of
+   indirection and thus explicit dereferencing, need to be regimplified.  */
+
+static tree
+walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
+{
+  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;
+}
+
+/* Turn STMT's PHI arg defs into separate SSA defs if they've become
+   non-gimple_val.  Return TRUE if any edge insertions need to be committed.  */
+
+static bool
+walk_regimplify_phi (gphi *stmt)
+{
+  bool needs_commit = false;
+
+  for (unsigned i = 0, n = gimple_phi_num_args (stmt); i < n; i++)
+    {
+      tree op = gimple_phi_arg_def (stmt, i);
+      if ((TREE_CODE (op) == ADDR_EXPR
+	   && !is_gimple_val (op))
+	  /* ??? A PARM_DECL that was addressable in the original function and
+	     had its address in PHI nodes, but that became a reference in the
+	     wrapped clone would NOT be updated by update_ssa in PHI nodes.
+	     Alas, if we were to create a default def for it now, update_ssa
+	     would complain that the symbol that needed rewriting already has
+	     SSA names associated with it.  OTOH, leaving the PARM_DECL alone,
+	     it eventually causes errors because it remains unchanged in PHI
+	     nodes, but it gets rewritten as expected if it appears in other
+	     stmts.  So we cheat a little here, and force the PARM_DECL out of
+	     the PHI node and into an assignment.  It's a little expensive,
+	     because we insert it at the edge, which introduces a basic block
+	     that's entirely unnecessary, but it works, and the block will be
+	     removed as the default def gets propagated back into the PHI node,
+	     so the final optimized code looks just as expected.  */
+	  || (TREE_CODE (op) == PARM_DECL
+	      && !TREE_ADDRESSABLE (op)))
+	{
+	  tree temp = make_ssa_name (TREE_TYPE (op), stmt);
+	  if (TREE_CODE (op) == PARM_DECL)
+	    SET_SSA_NAME_VAR_OR_IDENTIFIER (temp, DECL_NAME (op));
+	  SET_PHI_ARG_DEF (stmt, i, temp);
+
+	  gimple *assign = gimple_build_assign (temp, op);
+	  if (gimple_phi_arg_has_location (stmt, i))
+	    gimple_set_location (assign, gimple_phi_arg_location (stmt, i));
+	  gsi_insert_on_edge (gimple_phi_arg_edge (stmt, i), assign);
+	  needs_commit = true;
+	}
+    }
+
+  return needs_commit;
+}
+
+/* Create a reference type to use for PARM when turning it into a reference.
+   NONALIASED causes the reference type to gain its own separate alias set, so
+   that accessing the indirectly-passed parm won'will not add aliasing
+   noise.  */
+
+static tree
+build_ref_type_for (tree parm, bool nonaliased = true)
+{
+  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)
+{
+  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);
+
+      gcall *call = dyn_cast <gcall *> (stmt);
+      if (!call)
+	continue;
+
+      tree callee = gimple_call_fndecl (call);
+      gcc_checking_assert (callee);
+      node->create_edge (cgraph_node::get_create (callee), call, count, false);
+    }
+}
+
+/* Insert SEQ after the call at GSI, as if the call was in a try block with SEQ
+   as finally, i.e., SEQ will run after the call whether it returns or
+   propagates an exception.  This handles block splitting, EH edge and block
+   creation, noreturn and nothrow optimizations, and even throwing calls without
+   preexisting local handlers.  */
+
+static void
+gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
+{
+  if (!seq)
+    return;
+
+  gimple *stmt = gsi_stmt (gsi);
+
+  if (gimple_has_location (stmt))
+    annotate_all_with_location (seq, gimple_location (stmt));
+
+  gcall *call = dyn_cast <gcall *> (stmt);
+  bool noreturn_p = call && gimple_call_noreturn_p (call);
+  int eh_lp = lookup_stmt_eh_lp (stmt);
+  bool must_not_throw_p = eh_lp < 0;
+  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);
+    }
+}
+
+/* Record the order of the last cgraph entry whose mode we've already set, so
+   that we can perform mode setting incrementally without duplication.  */
+static int last_cgraph_order;
+
+/* Set strub modes for functions introduced since the last call.  */
+
+static void
+ipa_strub_set_mode_for_new_functions ()
+{
+  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_fndecl (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;
+
+  gcc_checking_assert (same_strub_mode_in_variants_p (type));
+
+  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;
+}
+
+/* Adjust a call to an at-calls call target.  Create a watermark local variable
+   if needed, initialize it before, pass it to the callee according to the
+   modified at-calls interface, and release the callee's stack space after the
+   call, if not deferred.  If the call is const or pure, arrange for the
+   watermark to not be assumed unused or unchanged.  */
+
+void
+pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args,
+				      tree callee_fntype)
+{
+  gcc_checking_assert (e->call_stmt);
+  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);
+      e->caller->create_edge (cgraph_node::get_create (enter),
+			      stptr, gsi_bb (gsi)->count, false);
+    }
+
+
+  /* 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);
+    gimple_call_set_fntype (new_stmt, callee_fntype);
+
+    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);
+}
+
+/* Adjust all at-calls calls in NODE. */
+
+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);
+
+	  if (!e->call_stmt)
+	    continue;
+
+	  tree callee_fntype;
+	  enum strub_mode callee_mode
+	    = effective_strub_mode_for_call (e->call_stmt, &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, callee_fntype);
+	}
+      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);
+
+	  if (!e->call_stmt)
+	    continue;
+
+	  tree callee_fntype;
+	  enum strub_mode callee_mode
+	    = effective_strub_mode_for_call (e->call_stmt, &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, callee_fntype);
+	}
+      pop_cfun ();
+    }
+}
+
+/* The strubm (strub mode) pass computes a strub mode for each function in the
+   call graph, and checks, before any inlining, that strub callability
+   requirements in effect are satisfied.  */
+
+unsigned int
+pass_ipa_strub_mode::execute (function *)
+{
+  last_cgraph_order = 0;
+  ipa_strub_set_mode_for_new_functions ();
+
+  /* Verify before any inlining or other transformations.  */
+  verify_strub ();
+
+  return 0;
+}
+
+/* Create a strub mode pass.  */
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub_mode (gcc::context *ctxt)
+{
+  return new pass_ipa_strub_mode (ctxt);
+}
+
+/* The strub pass proper adjusts types, signatures, and at-calls calls, and
+   splits internal-strub functions.  */
+
+unsigned int
+pass_ipa_strub::execute (function *)
+{
+  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)
+	  distinctify_node_type (onode);
+
+	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;
+
+	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);
+
+		  gcall *call = dyn_cast <gcall *> (stmt);
+
+		  if (!call)
+		    continue;
+
+		  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 ();
+      }
+  }
+
+  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;
+      }
+
+    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);
+
+    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;
+	  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;
+	     i++, nparm = DECL_CHAIN (nparm), nparmt = TREE_CHAIN (nparmt))
+	  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;
+      }
+
+#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 (/* unlike create_wrapper: preserve_comdat_group = */true);
+
+	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;
+	onode->create_edge (target, NULL, onode->count);
+	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 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;
+	  bool needs_commit = false;
+	  FOR_EACH_BB_FN (bb, cfun)
+	    {
+	      for (gphi_iterator gsi = gsi_start_nonvirtual_phis (bb);
+		   !gsi_end_p (gsi);
+		   gsi_next_nonvirtual_phi (&gsi))
+		{
+		  gphi *stmt = gsi.phi ();
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed && !is_gimple_debug (gsi_stmt (gsi)))
+		    if (walk_regimplify_phi (stmt))
+		      needs_commit = true;
+		}
+
+	      for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+		   !gsi_end_p (gsi); gsi_next (&gsi))
+		{
+		  gimple *stmt = gsi_stmt (gsi);
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed)
+		    {
+		      if (!is_gimple_debug (stmt))
+			{
+			  wi.info = &gsi;
+			  walk_gimple_op (stmt, walk_regimplify_addr_expr,
+					  &wi);
+			}
+		      update_stmt (stmt);
+		    }
+		}
+	    }
+	  if (needs_commit)
+	    gsi_commit_edge_inserts ();
+	}
+
+      if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca
+	  || is_stdarg || apply_args)
+	for (cgraph_edge *e = nnode->callees, *enext; e; e = enext)
+	  {
+	    if (!e->call_stmt)
+	      continue;
+
+	    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)));
+
+      gcall *wrcall;
+      while (!(wrcall = dyn_cast <gcall *> (gsi_stmt (gsi))))
+	gsi_next (&gsi);
+
+      tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
+      TREE_ADDRESSABLE (swm) = true;
+      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);
+      onode->create_edge (cgraph_node::get_create (enter),
+			  stptr, gsi_bb (gsi)->count, false);
+
+      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);
+	  onode->create_edge (cgraph_node::get_create (bappargs),
+			      appargs, gsi_bb (gsi)->count, false);
+	}
+
+      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);
+	  onode->create_edge (cgraph_node::get_create (bvastart),
+			      vastart, gsi_bb (gsi)->count, false);
+
+	  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 ();
+    }
+  }
+
+  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 0000000000000..29869fadfa6c9
--- /dev/null
+++ b/gcc/ipa-strub.h
@@ -0,0 +1,45 @@
+/* strub (stack scrubbing) infrastructure.
+   Copyright (C) 2021-2022 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 CALLEE can be inlined into CALLER, as far as stack scrubbing
+   constraints are concerned.  CALLEE doesn't have to be called directly by
+   CALLER, but the returned value says nothing about intervening functions.  */
+extern bool strub_inlinable_to_p (cgraph_node *callee, cgraph_node *caller);
+
+/* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
+extern bool strub_splittable_p (cgraph_node *node);
+
+/* 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);
+
+/* Return zero iff ID is NOT an acceptable parameter for a user-supplied strub
+   attribute for a function.  Otherwise, return >0 if it enables strub, <0 if it
+   does not.  Return +/-1 if the attribute-modified type is compatible with the
+   type without the attribute, or +/-2 if it is not compatible.  */
+extern int strub_validate_fn_attr_parm (tree id);
+
+/* Like comptypes, return 0 if t1 and t2 are not compatible, 1 if they are
+   compatible, and 2 if they are nearly compatible.  Same strub mode is
+   compatible, interface-compatible strub modes are nearly compatible.  */
+extern int strub_comptypes (tree t1, tree t2);
diff --git a/gcc/passes.def b/gcc/passes.def
index 6166279ec42c8..6cc7efd5d7653 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -52,6 +52,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);
@@ -113,6 +114,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 0000000000000..c7a79a6ea0d8a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O0.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=strict -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 0000000000000..96285c975d98e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=strict -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 0000000000000..8edc0d8aa1321
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
+
+/* At -O2, without -fno-inline, we fully expand enter and update, and add a test
+   around the leave call.  */
+
+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 0000000000000..c6d900cf3c45b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -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 0000000000000..33ee465e51cb6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O3.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=strict -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 0000000000000..2936f82079e18
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=strict -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 0000000000000..479746e57d87e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-Og -fstrub=strict -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 0000000000000..2241d4ea07f27
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
+
+/* At -Os, without -fno-inline, we fully expand enter, and also update.  The
+   expanded update might be larger than a call proper, but argument saving and
+   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 0000000000000..a322bcc5da606
--- /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 \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
new file mode 100644
index 0000000000000..db60026d0e080
--- /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 \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
new file mode 100644
index 0000000000000..2f462adc1efe0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+void __attribute__ ((__strub__ ("callable")))
+apply_function (void *args)
+{
+  __builtin_apply (0, args, 0);
+}
+
+void __attribute__ ((__strub__ ("internal")))
+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 0000000000000..a5d7551f5da5c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+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 0000000000000..64422a0d1e880
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+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 0000000000000..15ffaa031b899
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -fdump-ipa-strubm" } */
+
+/* Check that implicit enabling of strub mode selects internal strub when the
+   function uses __builtin_apply_args, that prevents the optimization to
+   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 \[(\]internal\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
new file mode 100644
index 0000000000000..b70843b4215a4
--- /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 \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
new file mode 100644
index 0000000000000..97a3988a6b922
--- /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 \[(\]callable\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
new file mode 100644
index 0000000000000..3d73431b3dcd3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict -O1" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+   the strubbing to its caller at -O1.  */
+
+#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 0000000000000..fddf3c745e7e6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -0,0 +1,8 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict -O2" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+   the strubbing to its caller at -O2.  */
+
+#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 0000000000000..5974e72efed46
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -0,0 +1,97 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict -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";
+
+/* Pad before and after the string on the stack, so that it's not overwritten by
+   regular stack use.  */
+#define PAD 7
+
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
+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__ ("callable"))) callable_t (char *);
+  callable_t *f = 0;
+
+  char s[2 * PAD + 1][sizeof (test_string)];
+  __builtin_strcpy (s[PAD], test_string);
+  asm ("" : "+m" (s), "+r" (f));
+
+  if (__builtin_expect (!f, 1))
+    return (char*)__builtin_stack_address ();
+
+  f (s[PAD]);
+  return 0;
+}
+
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
+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__ ("at-calls"), __noinline__, __noclone__))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ ("at-calls")))
+char *
+deferred_at_calls ()
+{
+  char *ret = at_calls ();
+  if (EXPECT_DEFERRAL !look_for_string (ret))
+    __builtin_abort ();
+  return ret;
+}
+
+static __attribute__ ((__strub__ ("internal")))
+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 0000000000000..fbaf85fe0fafe
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict -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 0000000000000..e9d7b7b9ee0a8
--- /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 \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
new file mode 100644
index 0000000000000..8b8e15a51c71c
--- /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 \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
new file mode 100644
index 0000000000000..0a4a7539d3489
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ ("internal")))
+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__ ("internal")))
+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__ ("internal")))
+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__ ("internal")))
+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 0000000000000..147171d96d5a1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ ("at-calls")))
+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__ ("at-calls")))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ ("at-calls")))
+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 0000000000000..4e92682895a43
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -0,0 +1,58 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that uses of a strub variable implicitly enables internal strub for
+   publicly-visible functions, and causes the same transformations to their
+   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-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
new file mode 100644
index 0000000000000..e2f9d8aebca58
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  Without the error,
+   inlining takes place.  */
+
+#include "strub-strict1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
new file mode 100644
index 0000000000000..98474435d2e59
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  */
+
+#include "strub-strict2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
new file mode 100644
index 0000000000000..1de15342595e4
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=strict -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 0000000000000..f9209c819004b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=strict -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 0000000000000..bed1dcfb54a45
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=strict -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 0000000000000..6bf0071f52b93
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -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 0000000000000..4732f515bf70c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
+   enter and leave calls within strub contexts, passing on the enclosing
+   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 0000000000000..8d6424c479a3a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
+   enter and leave calls within strub contexts, passing on the enclosing
+   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-strict1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
new file mode 100644
index 0000000000000..368522442066e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-strict1.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm" } */
+
+static int __attribute__ ((__strub__)) var;
+
+/* h becomes STRUB_INLINABLE, because of the use of the strub variable,
+   and the always_inline flag.  It would get inlined before pass_ipa_strub, if
+   it weren't for the error.  */
+static inline void
+__attribute__ ((__always_inline__))
+h() {
+  var++;
+}
+
+/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
+   the viability of at-calls strubbing.  Though internally a strub context, its
+   interface is not strub-enabled, so it's not callable from within strub
+   contexts.  */
+static inline void
+g() {
+  var--;
+  h();
+}
+
+/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
+   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
+void
+f() {
+  var++;
+  g();  /* { dg-error "calling non-.strub." } */
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-strict2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
new file mode 100644
index 0000000000000..b4f2888321821
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-strict2.c
@@ -0,0 +1,25 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm" } */
+
+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 \[(\]internal\[)\]" 2 "strubm" } } */
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 0000000000000..e48e0610e079b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=strict -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 0000000000000..87cda7ab21b16
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued.
+   Tail calls are short-circuited at -O2+.  */
+
+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/strub-var1.c b/gcc/testsuite/c-c++-common/strub-var1.c
new file mode 100644
index 0000000000000..eb6250fd39c90
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-var1.c
@@ -0,0 +1,24 @@
+/* { dg-do compile } */
+
+int __attribute__ ((strub)) x;
+float __attribute__ ((strub)) f;
+double __attribute__ ((strub)) d;
+
+/* The attribute applies to the type of the declaration, i.e., to the pointer
+   variable p, not to the pointed-to integer.  */
+int __attribute__ ((strub)) *
+p = &x; /* { dg-message "incompatible|invalid conversion" } */
+
+typedef int __attribute__ ((strub)) strub_int;
+strub_int *q = &x; /* Now this is compatible.  */
+
+int __attribute__ ((strub))
+a[2]; /* { dg-warning "does not apply to elements" } */
+
+int __attribute__ ((vector_size (4 * sizeof (int))))
+    __attribute__ ((strub))
+v; /* { dg-warning "does not apply to elements" } */
+
+struct s {
+  int i, j;
+} __attribute__ ((strub)) w; /* { dg-warning "does not apply to fields" } */
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 0000000000000..b5e45ab0525ad
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -0,0 +1,9 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+/* Check that strub and non-strub functions can be called from non-strub
+   contexts, and that strub and callable functions can be called from strub
+   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 0000000000000..96aa7fe4b07f7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -0,0 +1,264 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+/* Check that impermissible (cross-strub-context) calls are reported.  */
+
+extern int __attribute__ ((__strub__ ("callable"))) xcallable (void);
+extern int __attribute__ ((__strub__ ("internal"))) xinternal (void);
+extern int __attribute__ ((__strub__ ("at-calls"))) xat_calls (void);
+extern int __attribute__ ((__strub__ ("disabled"))) xdisabled (void);
+
+int __attribute__ ((__strub__ ("callable"))) callable (void);
+int __attribute__ ((__strub__ ("internal"))) internal (void);
+int __attribute__ ((__strub__ ("at-calls"))) at_calls (void);
+int __attribute__ ((__strub__ ("disabled"))) disabled (void);
+
+int __attribute__ ((__strub__)) var;
+int var_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
+icallable (void);
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
+iinternal (void);
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
+iat_calls (void);
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
+idisabled (void);
+static inline int __attribute__ ((__always_inline__))
+ivar_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
+i_callable (void) { return 0; }
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
+i_internal (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
+i_at_calls (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
+i_disabled (void) { return 0; }
+static inline int __attribute__ ((__always_inline__))
+i_var_user (void) { return var; }
+
+#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__ ("callable")))
+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__ ("internal")))
+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__ ("at-calls")))
+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__ ("disabled")))
+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 __attribute__ ((__always_inline__, __strub__ ("at-calls")))
+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 0000000000000..2857195706ed6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const function call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   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 0000000000000..98a92bc9eac2b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const function call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   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 0000000000000..5511a6e1e71d3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   and another to make sure it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ ("internal"), __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 0000000000000..47ee927964dff
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const wrapping call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   before the call, and another to make sure it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ ("internal")))
+#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 0000000000000..7c27a2a1a6dca
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..e66d903780afd
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..5e08e0e58c658
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* The pointer itself is a strub variable, that would enable internal strubbing
+   if its value was used.  Here, it's only overwritten, so no strub.  */
+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 0000000000000..a818e7a38bb5f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* The pointer itself is a strub variable, that would enable internal strubbing
+   if its value was used.  Here, it's only overwritten, so no strub.  */
+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 0000000000000..ddb0b5c0543b0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+/* It would be desirable to issue at least warnings for these.  */
+
+typedef int __attribute__ ((__strub__)) strub_int;
+strub_int *ptr;
+
+int *f () {
+  return ptr; /* { dg-message "incompatible|invalid conversion" } */
+}
+
+strub_int *g () {
+  return f (); /* { dg-message "incompatible|invalid conversion" } */
+}
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 0000000000000..c165f312f16de
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..69fcff8d3763d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..ff006224909bd
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
new file mode 100644
index 0000000000000..614b02228ba29
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+inline void __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+}
+
+void
+bat (void)
+{
+  /* Not allowed, not a strub context.  */
+  inl_int_ali (); /* { dg-error "context" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
new file mode 100644
index 0000000000000..f9a6b4a16faf8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=all" } */
+
+#include "strub-inlinable1.c"
+
+/* With -fstrub=all, the caller becomes a strub context, so the strub-inlinable
+   callee is not rejected.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
new file mode 100644
index 0000000000000..b4a7f3992bbaa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+typedef void ft (void);
+typedef void ft2 (int, int);
+extern ft __attribute__ ((__strub__)) fnac;
+
+ft * f (void) {
+  return fnac; /* { dg-message "incompatible|invalid conversion" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
new file mode 100644
index 0000000000000..d9d2c0caec42d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
@@ -0,0 +1,55 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic" } */
+
+/* C++ does not warn about the partial incompatibilities.
+
+   The d_p () calls are actually rejected, even in C++, but they are XFAILed
+   here because we don't get far enough in the compilation as to observe them,
+   because the incompatibilities are errors without -fpermissive.
+   strub-ptrfn3.c uses -fpermissive to check those.
+ */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
new file mode 100644
index 0000000000000..e1f179e160e5c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
@@ -0,0 +1,50 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic -fpermissive" } */
+/* { dg-prune-output "command-line option .-fpermissive." } */
+
+/* See strub-ptrfn2.c.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
new file mode 100644
index 0000000000000..70b558afad040
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+/* This is strub-ptrfn2.c without -Wpedantic.
+
+   Even C doesn't report the (not-quite-)compatible conversions without it.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
new file mode 100644
index 0000000000000..a262a086837b2
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure function call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+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 0000000000000..4c4bd50c209a0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure function call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   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 0000000000000..ce195c6b1f1b6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ ("internal"), __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 0000000000000..75cd54ccb5b5d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure wrapping call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ ("internal")))
+#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 0000000000000..7458b3fb54da5
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -0,0 +1,95 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  Avoid the use of red zones by avoiding
+   leaf functions.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+/* Pad before and after the string on the stack, so that it's not overwritten by
+   regular stack use.  */
+#define PAD 7
+
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
+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__ ("callable"))) callable_t (char *);
+  callable_t *f = 0;
+
+  char s[2 * PAD + 1][sizeof (test_string)];
+  __builtin_strcpy (s[PAD], test_string);
+  asm ("" : "+m" (s), "+r" (f));
+
+  if (__builtin_expect (!f, 1))
+    return (char *) __builtin_stack_address ();
+
+  f (s[PAD]);
+  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__ ("at-calls")))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ ("internal")))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  /* Since these test check stack contents above the top of the stack, an
+     unexpected asynchronous signal or interrupt might overwrite the bits we
+     expect to find and cause spurious fails.  Tolerate one such overall
+     spurious fail by retrying.  */
+  int i = 1;
+  while (!look_for_string (callable ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (at_calls ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (internal ()))
+    if (!i--) __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 0000000000000..5d60a7775f4bb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -0,0 +1,84 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  Allow red zones to be used.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+/* Pad before and after the string on the stack, so that it's not overwritten by
+   regular stack use.  */
+#define PAD 7
+
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
+char *
+leak_string (void)
+{
+  int len = sizeof (test_string);
+  asm ("" : "+rm" (len));
+  char s[2 * PAD + 1][len];
+  __builtin_strcpy (s[PAD], 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__ ("at-calls")))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ ("internal")))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  /* Since these test check stack contents above the top of the stack, an
+     unexpected asynchronous signal or interrupt might overwrite the bits we
+     expect to find and cause spurious fails.  Tolerate one such overall
+     spurious fail by retrying.  */
+  int i = 1;
+  while (!look_for_string (callable ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (at_calls ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (internal ()))
+    if (!i--) __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 0000000000000..c2ad710858e87
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -0,0 +1,80 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict" } */
+/* { 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__ ("callable")))
+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__ ("at-calls")))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ ("internal")))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  /* Since these test check stack contents above the top of the stack, an
+     unexpected asynchronous signal or interrupt might overwrite the bits we
+     expect to find and cause spurious fails.  Tolerate one such overall
+     spurious fail by retrying.  */
+  int i = 1;
+  while (!look_for_string (callable ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (at_calls ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (internal ()))
+    if (!i--) __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 0000000000000..3b36b8e5d68ef
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -0,0 +1,106 @@
+/* { 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__ ("internal")))
+char *
+internal ()
+{
+  return intermediate ();
+}
+
+int __attribute__ ((__strub__ ("disabled")))
+main ()
+{
+  /* Since these test check stack contents above the top of the stack, an
+     unexpected asynchronous signal or interrupt might overwrite the bits we
+     expect to find and cause spurious fails.  Tolerate one such overall
+     spurious fail by retrying.  */
+  int i = 1;
+  while (look_for_string (internal ()))
+    if (!i--) __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 0000000000000..57f9baf758ded
--- /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 0000000000000..08de3f1c3b17c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict" } */
+/* { dg-require-effective-target alloca } */
+
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
+
+#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 0000000000000..459f6886c5499
--- /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 0000000000000..0d367fb83d09d
--- /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 0000000000000..c226ab10ff651
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..a7911f1fa7212
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..6ebebcd01e8ea
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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_access.adb b/gcc/testsuite/gnat.dg/strub_access.adb
new file mode 100644
index 0000000000000..29e6996ecf61c
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_access.adb
@@ -0,0 +1,21 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=relaxed -fdump-ipa-strubm" }
+
+--  The main subprogram doesn't read from the automatic variable, but
+--  being an automatic variable, its presence should be enough for the
+--  procedure to get strub enabled.
+
+procedure Strub_Access is
+   type Strub_Int is new Integer;
+   pragma Machine_Attribute (Strub_Int, "strub");
+   
+   X : aliased Strub_Int := 0;
+
+   function F (P : access Strub_Int) return Strub_Int is (P.all);
+
+begin
+   X := F (X'Access);
+end Strub_Access;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 1 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls-opt\[)\]\[)\]" 1 "strubm" } }
diff --git a/gcc/testsuite/gnat.dg/strub_access1.adb b/gcc/testsuite/gnat.dg/strub_access1.adb
new file mode 100644
index 0000000000000..dae4706016436
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_access1.adb
@@ -0,0 +1,16 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=relaxed" }
+
+--  Check that we reject 'Access of a strub variable whose type does
+--  not carry a strub modifier.
+
+procedure Strub_Access1 is
+   X : aliased Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+
+   function F (P : access Integer) return Integer is (P.all);
+   
+begin
+   X := F (X'Unchecked_access); -- OK.
+   X := F (X'Access); -- { dg-error "target access type drops .strub. mode" }
+end Strub_Access1;
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
new file mode 100644
index 0000000000000..10445d7cf8451
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -0,0 +1,37 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" }
+
+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 (F (X));
+   --  function G return Integer is (FP (X));
+   --  Calling G would likely raise an exception, because although FP
+   --  carries the strub at-calls attribute needed to call F, the
+   --  attribute is dropped from the type used for the call proper.
+end Strub_Attr;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 0 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark_ptr" 6 "strub" } }
+--  We have 1 at-calls subprogram (F) and 2 wrapped (P and G).
+--  For each of them, there's one match for the wrapped signature, 
+--  and one for the update call.
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 27 "strub" } }
+--  The 6 matches above, plus:
+--  5*2: wm var decl, enter, call, leave and clobber for each wrapper;
+--  2*1: an extra leave and clobber for the exception paths in the wrappers.
+--  7*1: for the F call in G, including EH path.
diff --git a/gcc/testsuite/gnat.dg/strub_attr.ads b/gcc/testsuite/gnat.dg/strub_attr.ads
new file mode 100644
index 0000000000000..a94c23bf41833
--- /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/testsuite/gnat.dg/strub_disp.adb b/gcc/testsuite/gnat.dg/strub_disp.adb
new file mode 100644
index 0000000000000..3dbcc4a357cba
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_disp.adb
@@ -0,0 +1,64 @@
+--  { dg-do compile }
+
+procedure Strub_Disp is
+   package Foo is
+      type A is tagged null record;
+
+      procedure P (I : Integer; X : A);
+      pragma Machine_Attribute (P, "strub", "at-calls");
+      
+      function F (X : access A) return Integer;
+
+      type B is new A with null record;
+      
+      overriding
+      procedure P (I : Integer; X : B); -- { dg-error "requires the same .strub. mode" }
+
+      overriding
+      function F (X : access B) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+
+   end Foo;
+
+   package body Foo is
+      procedure P (I : Integer; X : A) is
+      begin
+	 null;
+      end;
+      
+      function F (X : access A) return Integer is (0);
+
+      overriding
+      procedure P (I : Integer; X : B) is
+      begin
+	 P (I, A (X));
+      end;
+      
+      overriding
+      function F (X : access B) return Integer is (1);
+   end Foo;
+
+   use Foo;
+
+   procedure Q (X : A'Class) is
+   begin
+      P (-1, X);
+   end;
+
+   XA : aliased A;
+   XB : aliased B;
+   I : Integer := 0;
+   XC : access A'Class;
+begin
+   Q (XA);
+   Q (XB);
+   
+   I := I + F (XA'Access);
+   I := I + F (XB'Access);
+
+   XC := XA'Access;
+   I := I + F (XC);
+
+   XC := XB'Access;
+   I := I + F (XC);
+end Strub_Disp;
diff --git a/gcc/testsuite/gnat.dg/strub_disp1.adb b/gcc/testsuite/gnat.dg/strub_disp1.adb
new file mode 100644
index 0000000000000..09756a74b7d81
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_disp1.adb
@@ -0,0 +1,79 @@
+--  { dg-do compile }
+--  { dg-options "-fdump-ipa-strub" }
+
+-- Check that at-calls dispatching calls are transformed.
+
+procedure Strub_Disp1 is
+   package Foo is
+      type A is tagged null record;
+
+      procedure P (I : Integer; X : A);
+      pragma Machine_Attribute (P, "strub", "at-calls");
+      
+      function F (X : access A) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+      type B is new A with null record;
+      
+      overriding
+      procedure P (I : Integer; X : B);
+      pragma Machine_Attribute (P, "strub", "at-calls");
+
+      overriding
+      function F (X : access B) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+   end Foo;
+
+   package body Foo is
+      procedure P (I : Integer; X : A) is
+      begin
+	 null;
+      end;
+      
+      function F (X : access A) return Integer is (0);
+
+      overriding
+      procedure P (I : Integer; X : B) is
+      begin
+	 P (I, A (X)); -- strub-at-calls non-dispatching call
+      end;
+      
+      overriding
+      function F (X : access B) return Integer is (1);
+   end Foo;
+
+   use Foo;
+
+   procedure Q (X : A'Class) is
+   begin
+      P (-1, X); -- strub-at-calls dispatching call.
+   end;
+
+   XA : aliased A;
+   XB : aliased B;
+   I : Integer := 0;
+   XC : access A'Class;
+begin
+   Q (XA);
+   Q (XB);
+   
+   I := I + F (XA'Access); -- strub-at-calls non-dispatching call
+   I := I + F (XB'Access); -- strub-at-calls non-dispatching call
+
+   XC := XA'Access;
+   I := I + F (XC); -- strub-at-calls dispatching call.
+
+   XC := XB'Access;
+   I := I + F (XC); -- strub-at-calls dispatching call.
+end Strub_Disp1;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 4 "strub" } }
+
+--  Count the strub-at-calls non-dispatching calls 
+--  (+ 2 each, for the matching prototypes)
+--  { dg-final { scan-ipa-dump-times "foo\.p \[(\]\[^\n\]*watermark" 3 "strub" } }
+--  { dg-final { scan-ipa-dump-times "foo\.f \[(\]\[^\n\]*watermark" 4 "strub" } }
+
+--  Count the strub-at-calls dispatching calls.
+--  { dg-final { scan-ipa-dump-times "_\[0-9\]* \[(\]\[^\n\]*watermark" 3 "strub" } }
diff --git a/gcc/testsuite/gnat.dg/strub_ind.adb b/gcc/testsuite/gnat.dg/strub_ind.adb
new file mode 100644
index 0000000000000..da56acaa957d2
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind.adb
@@ -0,0 +1,33 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict" }
+
+--  This is essentially the same test as strub_attr.adb, 
+--  but applying attributes to access types as well.
+--  That doesn't quite work yet, so we get an error we shouldn't get.
+
+package body Strub_Ind is
+   E : exception;
+
+   function G return Integer;
+
+   procedure P (X : Integer) is
+   begin
+      raise E;
+   end;
+   
+   function F (X : Integer) return Integer is
+   begin
+      return X * X;
+   end;
+   
+   function G return Integer is (FP (X));
+
+   type GT is access function return Integer;
+
+   type GT_SAC is access function return Integer;
+   pragma Machine_Attribute (GT_SAC, "strub", "at-calls");
+
+   GP : GT_SAC := GT_SAC (GT'(G'Access)); -- { dg-error "incompatible" }
+   -- pragma Machine_Attribute (GP, "strub", "at-calls");
+
+end Strub_Ind;
diff --git a/gcc/testsuite/gnat.dg/strub_ind.ads b/gcc/testsuite/gnat.dg/strub_ind.ads
new file mode 100644
index 0000000000000..99a65fc24b1ec
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind.ads
@@ -0,0 +1,17 @@
+package Strub_Ind is
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "internal");
+
+   function F (X : Integer) return Integer;
+   pragma Machine_Attribute (F, "strub");
+
+   X : Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+
+   type FT is access function (X : Integer) return Integer;
+   pragma Machine_Attribute (FT, "strub", "at-calls");
+
+   FP : FT := F'Access;
+   -- pragma Machine_Attribute (FP, "strub", "at-calls"); -- not needed
+
+end Strub_Ind;
diff --git a/gcc/testsuite/gnat.dg/strub_ind1.adb b/gcc/testsuite/gnat.dg/strub_ind1.adb
new file mode 100644
index 0000000000000..825e395e6819c
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind1.adb
@@ -0,0 +1,41 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
+
+--  This is essentially the same test as strub_attr.adb, 
+--  but with an explicit conversion.
+
+package body Strub_Ind1 is
+   E : exception;
+
+   type Strub_Int is New Integer;
+   pragma Machine_Attribute (Strub_Int, "strub");
+
+   function G return Integer;
+   pragma Machine_Attribute (G, "strub", "disabled");
+
+   procedure P (X : Integer) is
+   begin
+      raise E;
+   end;
+   
+   function G return Integer is (FP (X));
+
+   type GT is access function return Integer;
+   pragma Machine_Attribute (GT, "strub", "disabled");
+
+   type GT_SC is access function return Integer;
+   pragma Machine_Attribute (GT_SC, "strub", "callable");
+
+   GP : GT_SC := GT_SC (GT'(G'Access));
+   --  pragma Machine_Attribute (GP, "strub", "callable"); -- not needed.
+
+   function F (X : Integer) return Integer is
+   begin
+      return X * GP.all;
+   end;
+   
+end Strub_Ind1;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]disabled\[)\]\[)\]" 1 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 1 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
diff --git a/gcc/testsuite/gnat.dg/strub_ind1.ads b/gcc/testsuite/gnat.dg/strub_ind1.ads
new file mode 100644
index 0000000000000..d3f1273b3a6b9
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind1.ads
@@ -0,0 +1,17 @@
+package Strub_Ind1 is
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "internal");
+
+   function F (X : Integer) return Integer;
+   pragma Machine_Attribute (F, "strub");
+
+   X : aliased Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+
+   type FT is access function (X : Integer) return Integer;
+   pragma Machine_Attribute (FT, "strub", "at-calls");
+
+   FP : FT := F'Access;
+   pragma Machine_Attribute (FP, "strub", "at-calls");
+
+end Strub_Ind1;
diff --git a/gcc/testsuite/gnat.dg/strub_ind2.adb b/gcc/testsuite/gnat.dg/strub_ind2.adb
new file mode 100644
index 0000000000000..e918b39263117
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind2.adb
@@ -0,0 +1,34 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict" }
+
+--  This is essentially the same test as strub_attr.adb, 
+--  but with an explicit conversion.
+
+package body Strub_Ind2 is
+   E : exception;
+
+   function G return Integer;
+   pragma Machine_Attribute (G, "strub", "callable");
+
+   procedure P (X : Integer) is
+   begin
+      raise E;
+   end;
+   
+   function G return Integer is (FP (X));
+
+   type GT is access function return Integer;
+   pragma Machine_Attribute (GT, "strub", "callable");
+
+   type GT_SD is access function return Integer;
+   pragma Machine_Attribute (GT_SD, "strub", "disabled");
+
+   GP : GT_SD := GT_SD (GT'(G'Access));
+   --  pragma Machine_Attribute (GP, "strub", "disabled"); -- not needed.
+
+   function F (X : Integer) return Integer is
+   begin
+      return X * GP.all; --  { dg-error "using non-.strub. type" }
+   end;
+   
+end Strub_Ind2;
diff --git a/gcc/testsuite/gnat.dg/strub_ind2.ads b/gcc/testsuite/gnat.dg/strub_ind2.ads
new file mode 100644
index 0000000000000..e13865ec49c38
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind2.ads
@@ -0,0 +1,17 @@
+package Strub_Ind2 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");
+
+   type FT is access function (X : Integer) return Integer;
+   pragma Machine_Attribute (FT, "strub", "at-calls");
+
+   FP : FT := F'Access;
+   pragma Machine_Attribute (FP, "strub", "at-calls");
+
+end Strub_Ind2;
diff --git a/gcc/testsuite/gnat.dg/strub_intf.adb b/gcc/testsuite/gnat.dg/strub_intf.adb
new file mode 100644
index 0000000000000..8f0212a75866f
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_intf.adb
@@ -0,0 +1,93 @@
+--  { dg-do compile }
+
+--  Check that strub mode mismatches between overrider and overridden
+--  subprograms are reported.
+
+procedure Strub_Intf is
+   package Foo is
+      type TP is interface;
+      procedure P (I : Integer; X : TP) is abstract;
+      pragma Machine_Attribute (P, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+
+      type TF is interface;
+      function F (X : access TF) return Integer is abstract;
+
+      type TX is interface;
+      procedure P (I : Integer; X : TX) is abstract;
+
+      type TI is interface and TP and TF and TX;
+      --  When we freeze TI, we detect the mismatch between the
+      --  inherited P and another parent's P.  Because TP appears
+      --  before TX, we inherit P from TP, and report the mismatch at
+      --  the pragma inherited from TP against TX's P.  In contrast,
+      --  when we freeze TII below, since TX appears before TP, we
+      --  report the error at the line in which the inherited
+      --  subprogram is synthesized, namely the line below, against
+      --  the line of the pragma.
+
+      type TII is interface and TX and TP and TF; -- { dg-error "requires the same .strub. mode" }
+
+      function F (X : access TI) return Integer is abstract;
+      pragma Machine_Attribute (F, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+
+      type A is new TI with null record;
+
+      procedure P (I : Integer; X : A);
+      pragma Machine_Attribute (P, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+      
+      function F (X : access A) return Integer; -- { dg-error "requires the same .strub. mode" }
+
+      type B is new TI with null record;
+      
+      overriding
+      procedure P (I : Integer; X : B); -- { dg-error "requires the same .strub. mode" }
+
+      overriding
+      function F (X : access B) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+
+   end Foo;
+
+   package body Foo is
+      procedure P (I : Integer; X : A) is
+      begin
+	 null;
+      end;
+      
+      function F (X : access A) return Integer is (0);
+
+      overriding
+      procedure P (I : Integer; X : B) is
+      begin
+	 null;
+      end;
+      
+      overriding
+      function F (X : access B) return Integer is (1);
+
+   end Foo;
+
+   use Foo;
+
+   procedure Q (X : TX'Class) is
+   begin
+      P (-1, X);
+   end;
+
+   XA : aliased A;
+   XB : aliased B;
+   I : Integer := 0;
+   XC : access TI'Class;
+begin
+   Q (XA);
+   Q (XB);
+   
+   I := I + F (XA'Access);
+   I := I + F (XB'Access);
+
+   XC := XA'Access;
+   I := I + F (XC);
+
+   XC := XB'Access;
+   I := I + F (XC);
+end Strub_Intf;
diff --git a/gcc/testsuite/gnat.dg/strub_intf1.adb b/gcc/testsuite/gnat.dg/strub_intf1.adb
new file mode 100644
index 0000000000000..bf77321cef790
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_intf1.adb
@@ -0,0 +1,86 @@
+--  { dg-do compile }
+--  { dg-options "-fdump-ipa-strub" }
+
+-- Check that at-calls dispatching calls to interfaces are transformed.
+
+procedure Strub_Intf1 is
+   package Foo is
+      type TX is Interface;
+      procedure P (I : Integer; X : TX) is abstract;
+      pragma Machine_Attribute (P, "strub", "at-calls");
+      function F (X : access TX) return Integer is abstract;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+      type A is new TX with null record;
+
+      procedure P (I : Integer; X : A);
+      pragma Machine_Attribute (P, "strub", "at-calls");
+      
+      function F (X : access A) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+      type B is new TX with null record;
+      
+      overriding
+      procedure P (I : Integer; X : B);
+      pragma Machine_Attribute (P, "strub", "at-calls");
+
+      overriding
+      function F (X : access B) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+   end Foo;
+
+   package body Foo is
+      procedure P (I : Integer; X : A) is
+      begin
+	 null;
+      end;
+      
+      function F (X : access A) return Integer is (0);
+
+      overriding
+      procedure P (I : Integer; X : B) is
+      begin
+	 null;
+      end;
+      
+      overriding
+      function F (X : access B) return Integer is (1);
+
+   end Foo;
+
+   use Foo;
+
+   procedure Q (X : TX'Class) is
+   begin
+      P (-1, X);
+   end;
+
+   XA : aliased A;
+   XB : aliased B;
+   I : Integer := 0;
+   XC : access TX'Class;
+begin
+   Q (XA);
+   Q (XB);
+   
+   I := I + F (XA'Access);
+   I := I + F (XB'Access);
+
+   XC := XA'Access;
+   I := I + F (XC);
+
+   XC := XB'Access;
+   I := I + F (XC);
+end Strub_Intf1;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 4 "strub" } }
+
+--  Count the strub-at-calls non-dispatching calls 
+--  (+ 2 each, for the matching prototypes)
+--  { dg-final { scan-ipa-dump-times "foo\.p \[(\]\[^\n\]*watermark" 2 "strub" } }
+--  { dg-final { scan-ipa-dump-times "foo\.f \[(\]\[^\n\]*watermark" 4 "strub" } }
+
+--  Count the strub-at-calls dispatching calls.
+--  { dg-final { scan-ipa-dump-times "_\[0-9\]* \[(\]\[^\n\]*watermark" 3 "strub" } }
diff --git a/gcc/testsuite/gnat.dg/strub_intf2.adb b/gcc/testsuite/gnat.dg/strub_intf2.adb
new file mode 100644
index 0000000000000..e8880dbc43730
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_intf2.adb
@@ -0,0 +1,55 @@
+--  { dg-do compile }
+
+--  Check that strub mode mismatches between overrider and overridden
+--  subprograms are reported even when the overriders for an
+--  interface's subprograms are inherited from a type that is not a
+--  descendent of the interface.
+
+procedure Strub_Intf2 is
+   package Foo is
+      type A is tagged null record;
+
+      procedure P (I : Integer; X : A);
+      pragma Machine_Attribute (P, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+      
+      function F (X : access A) return Integer;
+
+      type TX is Interface;
+
+      procedure P (I : Integer; X : TX) is abstract; 
+
+      function F (X : access TX) return Integer is abstract;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+      type B is new A and TX with null record; -- { dg-error "requires the same .strub. mode" }
+
+   end Foo;
+
+   package body Foo is
+      procedure P (I : Integer; X : A) is
+      begin
+	 null;
+      end;
+      
+      function F (X : access A) return Integer is (0);
+
+   end Foo;
+
+   use Foo;
+
+   procedure Q (X : TX'Class) is
+   begin
+      P (-1, X);
+   end;
+
+   XB : aliased B;
+   I : Integer := 0;
+   XC : access TX'Class;
+begin
+   Q (XB);
+   
+   I := I + F (XB'Access);
+
+   XC := XB'Access;
+   I := I + F (XC);
+end Strub_Intf2;
diff --git a/gcc/testsuite/gnat.dg/strub_renm.adb b/gcc/testsuite/gnat.dg/strub_renm.adb
new file mode 100644
index 0000000000000..217367e712d82
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_renm.adb
@@ -0,0 +1,21 @@
+--  { dg-do compile }
+
+procedure Strub_Renm is
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "at-calls");
+
+   function F return Integer;
+   pragma Machine_Attribute (F, "strub", "internal");
+
+   procedure Q (X : Integer) renames P; -- { dg-error "requires the same .strub. mode" }
+
+   function G return Integer renames F;
+   pragma Machine_Attribute (G, "strub", "callable"); -- { dg-error "requires the same .strub. mode" }
+
+   procedure P (X : Integer) is null;
+   function F return Integer is (0);
+
+begin
+   P (F);
+   Q (G);
+end Strub_Renm;
diff --git a/gcc/testsuite/gnat.dg/strub_renm1.adb b/gcc/testsuite/gnat.dg/strub_renm1.adb
new file mode 100644
index 0000000000000..a11adbfb5a9d6
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_renm1.adb
@@ -0,0 +1,32 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=relaxed -fdump-ipa-strub" }
+
+procedure Strub_Renm1 is
+   V : Integer := 0;
+   pragma Machine_Attribute (V, "strub");
+
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "at-calls");
+
+   function F return Integer;
+
+   procedure Q (X : Integer) renames P;
+   pragma Machine_Attribute (Q, "strub", "at-calls");
+
+   function G return Integer renames F;
+   pragma Machine_Attribute (G, "strub", "internal");
+
+   procedure P (X : Integer) is null;
+   function F return Integer is (0);
+
+begin
+   P (F);
+   Q (G);
+end Strub_Renm1;
+
+--  This is for P; Q is an alias.
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 1 "strub" } }
+
+--  This is *not* for G, but for Strub_Renm1.
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]wrapped\[)\]\[)\]" 1 "strub" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]wrapper\[)\]\[)\]" 1 "strub" } }
diff --git a/gcc/testsuite/gnat.dg/strub_renm2.adb b/gcc/testsuite/gnat.dg/strub_renm2.adb
new file mode 100644
index 0000000000000..c488c20826fdb
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_renm2.adb
@@ -0,0 +1,32 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strub" }
+
+procedure Strub_Renm2 is
+   V : Integer := 0;
+   pragma Machine_Attribute (V, "strub");
+
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "at-calls");
+
+   function F return Integer;
+
+   procedure Q (X : Integer) renames P;
+   pragma Machine_Attribute (Q, "strub", "at-calls");
+
+   type T is access function return Integer;
+
+   type TC is access function return Integer;
+   pragma Machine_Attribute (TC, "strub", "callable");
+
+   FCptr : constant TC := TC (T'(F'Access));
+
+   function G return Integer renames FCptr.all;
+   pragma Machine_Attribute (G, "strub", "callable");
+
+   procedure P (X : Integer) is null;
+   function F return Integer is (0);
+
+begin
+   P (F);  -- { dg-error "calling non-.strub." }
+   Q (G);  -- ok, G is callable.
+end Strub_Renm2;
diff --git a/gcc/testsuite/gnat.dg/strub_var.adb b/gcc/testsuite/gnat.dg/strub_var.adb
new file mode 100644
index 0000000000000..3d158de28031f
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_var.adb
@@ -0,0 +1,16 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
+
+-- We don't read from the automatic variable, but being an automatic
+--  variable, its presence should be enough for the procedure to get
+--  strub enabled.
+
+with Strub_Attr;
+procedure Strub_Var is
+   X : Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+begin
+   X := Strub_Attr.F (0);
+end Strub_Var;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 1 "strubm" } }
diff --git a/gcc/testsuite/gnat.dg/strub_var1.adb b/gcc/testsuite/gnat.dg/strub_var1.adb
new file mode 100644
index 0000000000000..6a504e09198b6
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_var1.adb
@@ -0,0 +1,20 @@
+--  { dg-do compile }
+
+with Strub_Attr;
+procedure Strub_Var1 is
+   type TA  -- { dg-warning "does not apply to elements" }
+      is array (1..2) of Integer;
+   pragma Machine_Attribute (TA, "strub");
+   
+   A : TA := (0, 0);  -- { dg-warning "does not apply to elements" }
+   
+   type TR is record  -- { dg-warning "does not apply to fields" }
+      M, N : Integer;
+   end record;
+   pragma Machine_Attribute (TR, "strub");
+   
+   R : TR := (0, 0);
+
+begin
+   A(2) := Strub_Attr.F (A(1));
+end Strub_Var1;
diff --git a/gcc/tree-cfg.cc b/gcc/tree-cfg.cc
index 30f26af69f287..395d32f5faaca 100644
--- a/gcc/tree-cfg.cc
+++ b/gcc/tree-cfg.cc
@@ -5738,6 +5738,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 b39d2c5db958d..e628a87187075 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -506,8 +506,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.cc b/gcc/tree-ssa-ccp.cc
index 26d5e445abd55..4428133c188be 100644
--- a/gcc/tree-ssa-ccp.cc
+++ b/gcc/tree-ssa-ccp.cc
@@ -3069,7 +3069,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 0135d2740661f..d30e791bb956c 100644
--- a/libgcc/Makefile.in
+++ b/libgcc/Makefile.in
@@ -433,6 +433,9 @@ LIB2ADD += enable-execute-stack.c
 # Control Flow Redundancy hardening out-of-line checker.
 LIB2ADD += $(srcdir)/hardcfr.c
 
+# Stack scrubbing infrastructure.
+LIB2ADD += $(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
 # (e.g. *-*-darwin*) only one copy of it is linked.
diff --git a/libgcc/libgcc2.h b/libgcc/libgcc2.h
index 3ec9bbd816475..5cdb58648ac33 100644
--- a/libgcc/libgcc2.h
+++ b/libgcc/libgcc2.h
@@ -536,6 +536,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 0000000000000..2a2b930b6039d
--- /dev/null
+++ b/libgcc/strub.c
@@ -0,0 +1,149 @@
+/* Stack scrubbing infrastructure
+   Copyright (C) 2021-2022 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__ ("callable")))
+
+/* 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;
+}
+
+#if TARGET_STRUB_USE_DYNAMIC_ARRAY && ! defined TARGET_STRUB_MAY_USE_MEMSET
+# define TARGET_STRUB_MAY_USE_MEMSET 1
+#endif
+
+#if defined __x86_64__ && __OPTIMIZE__
+# define TARGET_STRUB_DISABLE_RED_ZONE \
+  /* __attribute__ ((__target__ ("no-red-zone"))) // not needed when optimizing */
+#elif !defined RED_ZONE_SIZE || defined __i386__
+# define TARGET_STRUB_DISABLE_RED_ZONE
+#endif
+
+#ifndef TARGET_STRUB_DISABLE_RED_ZONE
+/* Dummy function, called to force the caller to not be a leaf function, so
+   that it can't use the red zone.  */
+static void ATTRIBUTE_STRUB_CALLABLE
+__attribute__ ((__noinline__, __noipa__))
+__strub_dummy_force_no_leaf (void)
+{
+}
+#endif
+
+/* Leave a stack scrubbing context, clearing the stack between its top and
+   *MARK.  */
+void ATTRIBUTE_STRUB_CALLABLE
+#if ! TARGET_STRUB_MAY_USE_MEMSET
+__attribute__ ((__optimize__ ("-fno-tree-loop-distribute-patterns")))
+#endif
+#ifdef TARGET_STRUB_DISABLE_RED_ZONE
+TARGET_STRUB_DISABLE_RED_ZONE
+#endif
+__strub_leave (void **mark)
+{
+  void *sp = __builtin_stack_address ();
+
+  void **base, **end;
+#ifndef STACK_GROWS_DOWNWARD
+  base = sp; /* ??? Do we need an offset here?  */
+  end = *mark;
+#else
+  base = *mark;
+  end = sp; /* ??? Does any platform require an offset here?  */
+#endif
+
+  if (! (base < end))
+    return;
+
+#if TARGET_STRUB_USE_DYNAMIC_ARRAY
+  /* Compute the length without assuming the pointers are both sufficiently
+     aligned.  They should be, but pointer differences expected to be exact may
+     yield unexpected results when the assumption doesn't hold.  Given the
+     potential security implications, compute the length without that
+     expectation.  If the pointers are misaligned, we may leave a partial
+     unscrubbed word behind.  */
+  ptrdiff_t len = ((char *)end - (char *)base) / sizeof (void *);
+  /* 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];
+#elifndef TARGET_STRUB_DISABLE_RED_ZONE
+  /* Prevent the use of the red zone, by making this function non-leaf through
+     an unreachable call that, because of the asm stmt, the compiler will
+     consider reachable.  */
+  asm goto ("" : : : : no_leaf);
+  if (0)
+    {
+    no_leaf:
+      __strub_dummy_force_no_leaf ();
+      return;
+    }
+#endif
+
+  /* ldist may turn these loops into a memset (thus the conditional
+     -fno-tree-loop-distribute-patterns above).  Without the dynamic array
+     above, that call would likely be unsafe: possibly tail-called, and likely
+     scribbling over its own stack frame.  */
+#ifndef STACK_GROWS_DOWNWARD
+  do
+    *base++ = 0;
+  while (base < end);
+  /* Make sure the stack overwrites are not optimized away.  */
+  asm ("" : : "m" (end[0]));
+#else
+  do
+    *--end = 0;
+  while (base < end);
+  /* Make sure the stack overwrites are not optimized away.  */
+  asm ("" : : "m" (base[0]));
+#endif
+}


-- 
Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
   Free Software Activist                       GNU Toolchain Engineer
Disinformation flourishes because many people care deeply about injustice
but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* Re: [PATCH v3] Introduce strub: machine-independent stack scrubbing
  2023-06-16  6:09     ` [PATCH v3] " Alexandre Oliva
@ 2023-06-27 21:28       ` Qing Zhao
  2023-06-28  8:20         ` Alexandre Oliva
  2023-10-20  6:03       ` [PATCH v4] " Alexandre Oliva
  1 sibling, 1 reply; 59+ messages in thread
From: Qing Zhao @ 2023-06-27 21:28 UTC (permalink / raw)
  To: Alexandre Oliva
  Cc: Qing Zhao via Gcc-patches, Jeremy Bennett, Craig Blackmore,
	Graham Markall, Martin Jambor, Jan Hubicka, Richard Biener,
	Jim Wilson

Hi, Alexandre,

Thanks a lot for the work. I think that this will be a valuable feature to be added for GCC’s security functionality. 

I have several questions on this patch:


1.  The implementation of register scrubbing,  -fzero-call-used-regs,  is to insert the register zeroing sequence in the routine’s epilogue,
So each routine will be responsible to clean its own call-clobbered registers before returning. 
     This is simple and straightforward, no change to the function’s interface.

     I am wondering why stack scrubbing, proposed in this patch series, cannot do the stack scrubbing in the routine’s epilogue similar as
register scrubbing?

     There are the following benefits from doing the stack scrubbing in the callee’s epilogue:
      A.  The size of the stack need to be cleaned is known by itself, no need to pass this information to other routines,
            Therefore  functions' interface change can be avoided; no need to change the caller’s body, no need for cloning the callee, etc.
      B.   As a result, the runtime overhead of stack scrubbing should be reduced.
      C.  If we do the stack scrubbing in a very late stage and in the routine’s epilogue, similar as register scrubbing, we don’t need 
            to deal with the complicated call-chain staff anymore, right?


   So, what’s the fundamental issues that stack scrubbing cannot be done by the routine itself but its caller?

2.  I have concerns on the runtime performance overhead, do you have any data on this for your current implementation?

3. You mentioned that there are several “modes” for this feature, could you please provide more details on the modes and their description?

thanks.

Qing



> On Jun 16, 2023, at 2:09 AM, Alexandre Oliva via Gcc-patches <gcc-patches@gcc.gnu.org> wrote:
> 
> 
> This patch adds the strub attribute for function and variable types,
> command-line options, passes and adjustments to implement it,
> documentation, and tests.
> 
> Stack scrubbing is implemented in a machine-independent way: functions
> with strub enabled are modified so that they take an extra stack
> watermark argument, that they update with their stack use, and the
> caller can then zero it out once it regains control, whether by return
> or exception.  There are two ways to go about it: at-calls, that
> modifies the visible interface (signature) of the function, and
> internal, in which the body is moved to a clone, the clone undergoes
> the interface change, and the function becomes a wrapper, preserving
> its original interface, that calls the clone and then clears the stack
> used by it.
> 
> Variables can also be annotated with the strub attribute, so that
> functions that read from them get stack scrubbing enabled implicitly,
> whether at-calls, for functions only usable within a translation unit,
> or internal, for functions whose interfaces must not be modified.
> 
> There is a strict mode, in which functions that have their stack
> scrubbed can only call other functions with stack-scrubbing
> interfaces, or those explicitly marked as callable from strub
> contexts, so that an entire call chain gets scrubbing, at once or
> piecemeal depending on optimization levels.  In the default mode,
> relaxed, this requirement is not enforced by the compiler.
> 
> The implementation adds two IPA passes, one that assigns strub modes
> early on, another that modifies interfaces and adds calls to the
> builtins that jointly implement stack scrubbing.  Another builtin,
> that obtains the stack pointer, is added for use in the implementation
> of the builtins, whether expanded inline or called in libgcc.
> 
> There are new command-line options to change operation modes and to
> force the feature disabled; it is enabled by default, but it has no
> effect and is implicitly disabled if the strub attribute is never
> used.  There are also options meant to use for testing the feature,
> enabling different strubbing modes for all (viable) functions.
> 
> Regstrapped on x86_64-linux-gnu.  Also tested with gcc-13, and with
> various other targets.  Ok to install?
> 
> There have been only minor changes since v2:
> 
> - scrub the stack in the same direction it grows, inline and out-of-line
> 
> - remove need for stack space in __strub_leave
> 
> - add (ultimately not needed) means to avoid using the red zone in
>  __strub_leave
> 
> - introduce and document TARGET_ macros to tune __strub_leave
> 
> - drop a misoptimization in inlined __strub_enter
> 
> - fix handling of cgraph edges without call stmts
> 
> - adjust some testcases (async stack uses; Ada compiler bug fix)
> 
> - drop bits for compatibility with gcc 10
> 
> - preserve the comdat group when resetting a function into a strub
>  wrapper, coping with a symtab_node::reset change in gcc-13
> 
> 
> for  gcc/ChangeLog
> 
> 	* Makefile.in (OBJS): Add ipa-strub.o.
> 	* builtins.def (BUILT_IN_STACK_ADDRESS): New.
> 	(BUILT_IN___STRUB_ENTER): New.
> 	(BUILT_IN___STRUB_UPDATE): New.
> 	(BUILT_IN___STRUB_LEAVE): New.
> 	* builtins.cc: Include ipa-strub.h.
> 	(STACK_STOPS, STACK_UNSIGNED): Define.
> 	(expand_builtin_stack_address): New.
> 	(expand_builtin_strub_enter): New.
> 	(expand_builtin_strub_update): New.
> 	(expand_builtin_strub_leave): New.
> 	(expand_builtin): Call them.
> 	* common.opt (fstrub=*): New options.
> 	* doc/extend.texi (strub): New type attribute.
> 	(__builtin_stack_address): New function.
> 	(Stack Scrubbing): New section.
> 	* doc/invoke.texi (-fstrub=*): New options.
> 	(-fdump-ipa-*): New passes.
> 	* ipa-inline.cc: Include ipa-strub.h.
> 	(can_inline_edge_p): Test strub_inlinable_to_p.
> 	* ipa-split.cc: Include ipa-strub.h.
> 	(execute_split_functions): Test strub_splittable_p.
> 	* ipa-strub.cc, ipa-strub.h: New.
> 	* passes.def: Add strub_mode and strub passes.
> 	* tree-cfg.cc (gimple_verify_flow_info): Note on debug stmts.
> 	* tree-pass.h (make_pass_ipa_strub_mode): Declare.
> 	(make_pass_ipa_strub): Declare.
> 	(make_pass_ipa_function_and_variable_visibility): Fix
> 	formatting.
> 	* tree-ssa-ccp.cc (optimize_stack_restore): Keep restores
> 	before strub leave.
> 	* multiple_target.cc (pass_target_clone::gate): Test seen_error.
> 	* attribs.cc: Include ipa-strub.h.
> 	(decl_attributes): Support applying attributes to function
> 	type, rather than pointer type, at handler's request.
> 	(comp_type_attributes): Combine strub_comptypes and target
> 	comp_type results.
> 	* doc/tm.texi.in (TARGET_STRUB_USE_DYNAMIC_ARRAY): New.
> 	(TARGET_STRUB_MAY_USE_MEMSET): New.
> 	* doc/tm.texi: Rebuilt.
> 	* cgraph.h (symtab_node::reset): Add preserve_comdat_group
> 	param, with a default.
> 	* cgraphunit.cc (symtab_node::reset): Use it.
> 
> for  gcc/c-family/ChangeLog
> 
> 	* c-attribs.cc: Include ipa-strub.h.
> 	(handle_strub_attribute): New.
> 	(c_common_attribute_table): Add strub.
> 
> for  gcc/ada/ChangeLog
> 
> 	* gcc-interface/trans.cc: Include ipa-strub.h.
> 	(gigi): Make internal decls for targets of compiler-generated
> 	calls strub-callable too.
> 	(build_raise_check): Likewise.
> 	* gcc-interface/utils.cc: Include ipa-strub.h.
> 	(handle_strub_attribute): New.
> 	(gnat_internal_attribute_table): Add strub.
> 
> for  gcc/testsuite/ChangeLog
> 
> 	* c-c++-common/strub-O0.c: New.
> 	* c-c++-common/strub-O1.c: New.
> 	* c-c++-common/strub-O2.c: New.
> 	* c-c++-common/strub-O2fni.c: New.
> 	* c-c++-common/strub-O3.c: New.
> 	* c-c++-common/strub-O3fni.c: New.
> 	* c-c++-common/strub-Og.c: New.
> 	* c-c++-common/strub-Os.c: New.
> 	* c-c++-common/strub-all1.c: New.
> 	* c-c++-common/strub-all2.c: New.
> 	* c-c++-common/strub-apply1.c: New.
> 	* c-c++-common/strub-apply2.c: New.
> 	* c-c++-common/strub-apply3.c: New.
> 	* c-c++-common/strub-apply4.c: New.
> 	* c-c++-common/strub-at-calls1.c: New.
> 	* c-c++-common/strub-at-calls2.c: New.
> 	* c-c++-common/strub-defer-O1.c: New.
> 	* c-c++-common/strub-defer-O2.c: New.
> 	* c-c++-common/strub-defer-O3.c: New.
> 	* c-c++-common/strub-defer-Os.c: New.
> 	* c-c++-common/strub-internal1.c: New.
> 	* c-c++-common/strub-internal2.c: New.
> 	* c-c++-common/strub-parms1.c: New.
> 	* c-c++-common/strub-parms2.c: New.
> 	* c-c++-common/strub-parms3.c: New.
> 	* c-c++-common/strub-relaxed1.c: New.
> 	* c-c++-common/strub-relaxed2.c: New.
> 	* c-c++-common/strub-short-O0-exc.c: New.
> 	* c-c++-common/strub-short-O0.c: New.
> 	* c-c++-common/strub-short-O1.c: New.
> 	* c-c++-common/strub-short-O2.c: New.
> 	* c-c++-common/strub-short-O3.c: New.
> 	* c-c++-common/strub-short-Os.c: New.
> 	* c-c++-common/strub-strict1.c: New.
> 	* c-c++-common/strub-strict2.c: New.
> 	* c-c++-common/strub-tail-O1.c: New.
> 	* c-c++-common/strub-tail-O2.c: New.
> 	* c-c++-common/torture/strub-callable1.c: New.
> 	* c-c++-common/torture/strub-callable2.c: New.
> 	* c-c++-common/torture/strub-const1.c: New.
> 	* c-c++-common/torture/strub-const2.c: New.
> 	* c-c++-common/torture/strub-const3.c: New.
> 	* c-c++-common/torture/strub-const4.c: New.
> 	* c-c++-common/torture/strub-data1.c: New.
> 	* c-c++-common/torture/strub-data2.c: New.
> 	* c-c++-common/torture/strub-data3.c: New.
> 	* c-c++-common/torture/strub-data4.c: New.
> 	* c-c++-common/torture/strub-data5.c: New.
> 	* c-c++-common/torture/strub-indcall1.c: New.
> 	* c-c++-common/torture/strub-indcall2.c: New.
> 	* c-c++-common/torture/strub-indcall3.c: New.
> 	* c-c++-common/torture/strub-inlinable1.c: New.
> 	* c-c++-common/torture/strub-inlinable2.c: New.
> 	* c-c++-common/torture/strub-ptrfn1.c: New.
> 	* c-c++-common/torture/strub-ptrfn2.c: New.
> 	* c-c++-common/torture/strub-ptrfn3.c: New.
> 	* c-c++-common/torture/strub-ptrfn4.c: New.
> 	* c-c++-common/torture/strub-pure1.c: New.
> 	* c-c++-common/torture/strub-pure2.c: New.
> 	* c-c++-common/torture/strub-pure3.c: New.
> 	* c-c++-common/torture/strub-pure4.c: New.
> 	* c-c++-common/torture/strub-run1.c: New.
> 	* c-c++-common/torture/strub-run2.c: New.
> 	* c-c++-common/torture/strub-run3.c: New.
> 	* c-c++-common/torture/strub-run4.c: New.
> 	* c-c++-common/torture/strub-run4c.c: New.
> 	* c-c++-common/torture/strub-run4d.c: New.
> 	* c-c++-common/torture/strub-run4i.c: New.
> 	* g++.dg/strub-run1.C: New.
> 	* g++.dg/torture/strub-init1.C: New.
> 	* g++.dg/torture/strub-init2.C: New.
> 	* g++.dg/torture/strub-init3.C: New.
> 	* gnat.dg/strub_attr.adb, gnat.dg/strub_attr.ads: New.
> 	* gnat.dg/strub_ind.adb, gnat.dg/strub_ind.ads: New.
> 
> for  libgcc/ChangeLog
> 
> 	* Makefile.in (LIB2ADD): Add strub.c.
> 	* libgcc2.h (__strub_enter, __strub_update, __strub_leave):
> 	Declare.
> 	* strub.c: New.


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

* Re: [PATCH v3] Introduce strub: machine-independent stack scrubbing
  2023-06-27 21:28       ` Qing Zhao
@ 2023-06-28  8:20         ` Alexandre Oliva
  0 siblings, 0 replies; 59+ messages in thread
From: Alexandre Oliva @ 2023-06-28  8:20 UTC (permalink / raw)
  To: Qing Zhao
  Cc: Qing Zhao via Gcc-patches, Jeremy Bennett, Craig Blackmore,
	Graham Markall, Martin Jambor, Jan Hubicka, Richard Biener,
	Jim Wilson

Hello, Qing,

On Jun 27, 2023, Qing Zhao <qing.zhao@oracle.com> wrote:

> I am wondering why stack scrubbing, proposed in this patch series, cannot do the stack scrubbing in the routine’s epilogue similar as
> register scrubbing?

There were multiple considerations that led to this design decision:

- Stack scrubbing in epilogues would be highly target-dependent

An epilogue expected to scrub the stack of its containing function would
not usually be able to call memset; there might not even be registers
available to do the cleaning, let alone to do it efficiently.  Since
epilogues are output after register allocation, the epilogue code
generator would have to allocate registers itself to do the job,
avoiding call-saved registers (that would have to be restored before
scrubbing the stack holding them), those holding return values, and
taking care of any machine- or ABI-specific conventions that apply to
epilogues.

- Exception Handling

Raising or propagating an exception requires a function's stack frame to
be active.  It wouldn't be possible for e.g. a cleanup handler to clean
up the stack frame holding it and then propagating the exception: either
the scrubbing would have to leave much of the stack frame alone for
propagation to work, or it would scrub too much and propagation would
fail.

So we had to devise a way for stack frames to be scrubbed and protect
the sensitive data in them even if an exception is raised or propagated
out of the sensitive frame.

- Variable frame size

Though many functions have static frame sizes, there are cases in which
a function dynamically allocates and releases stack space, and that
extra space should be scrubbed as well.  So the improvements out of a
known frame size are not a given, and we may need a watermark to handle
the general case.

Now consider that this watermark needs to survive past the point in
which the epilogue restores call-saved registers, so that the save area
can be scrubbed.  Call-clobbered registers might not be available, or
need scrubbing themselves.

A caller-owned watermark relieves the callee from these contradictory
requirements, enables the register pointing to the watermark to be
reused by the callee as soon as it's no longer needed; aggregation of
scrubbing, passing on the watermark when tail-calling another scrubbed
subprogram; caller and callee to be compiled separately, circumstances
in which the caller (in the strub("at-calls") mode) wouldn't know how
much stack space used by the callee is to be scrubbed.

- Watermark as in/out argument

Thus, watermarks, and caller-based scrubbing were required, so we might
as well use the same strategy for non-exceptional exit paths to make it
portable.

We've explored various possibilities of watermark passing to reduce the
impact on the ABI:

-- a single global variable wouldn't do in multi-threaded programs;
we need per-thread stack information.  TLS is not available on every
target, it's emulated with high overhead on some, and even when it
doesn't use part of the thread's stack for static thread-local
storage, each caller of a scrubbing function would have to preserve
that variable somehow (presumably in its own stack frame) before
reusing it to communicate with its callee.

-- a thread-local pointer to a heap-allocated parallel stack of
stack-scrubbing ranges might avoid holding the watermarks in the
stack, or passing pointers to them as arguments, leaving the entire
scrub range management in the library.  that would make the __strub_*
library components heavy enough that inlining them would not be
viable.  Furthermore, making such low-level APIs heap allocators
normally makes for problems of async-signal safety, and prevents heap
implementations from relying on such low-level APIs.

-- using the static chain machinery to convey to scrubbed callees
access to the callee's watermark seems viable, if onerous, but the
chained records live in the stack anyway, and there are targets that
do not support static chains.

-- an out parameter might do for "amount of stack used", but
making it an in/out watermark enabled aggregation and tail-calling;
early set-and-forget on fixed-size stack frames; and assured
initialization, even in case of an early asynchronous exception.

- Internal scrubbing

Though we have implemented strub("internal") through wrappers that call
the actual function and then scrub its stack space, we have envisioned
an alternate implementation that, through machine-specific support,
performs actual internal scrubbing, arranging the stack frame in such a
way that epilogues and EH cleanups can scrub most, if not all of the
stack frame (analogous to how the wrapper only scrubs the wrapped frame,
not its own), and taking advantage of constant frame sizes where
possible.  At least with variable frame sizes, the amount of stack space
to be scrubbed in the epilogue (or in an EH cleanup) will have to be
held in a local variable or somesuch, and at least for nonleaf
functions, that surely will end up in the stack one way or another.

> 2.  I have concerns on the runtime performance overhead, do you have any data on this for your current implementation?

Though one could conceivable build entire applications with the testing
option -fstrub=all, and that works AFAICT, the expected use case is
marking sensitive functions or variables for strubbing, and there aren't
benchmarks for this use case.


> 3. You mentioned that there are several “modes” for this feature,
> could you please provide more details on the modes and their
> description?

There's strict vs relaxed, and there's internal vs at-calls.  The
documentation for these modes included in the patch in quite extensive.
Rather than duplicating it here in other words, I suppose it would be a
better "test" for the documentation to have others go through it, try to
make sense of it, and point out passages that are unclear or hard to
understand.  WDYT?

Thanks,

-- 
Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
   Free Software Activist                       GNU Toolchain Engineer
Disinformation flourishes because many people care deeply about injustice
but very few check the facts.  Ask me about <https://stallmansupport.org>

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

* [PATCH v4] Introduce strub: machine-independent stack scrubbing
  2023-06-16  6:09     ` [PATCH v3] " Alexandre Oliva
  2023-06-27 21:28       ` Qing Zhao
@ 2023-10-20  6:03       ` Alexandre Oliva
  2023-10-26  6:15         ` Alexandre Oliva
  1 sibling, 1 reply; 59+ messages in thread
From: Alexandre Oliva @ 2023-10-20  6:03 UTC (permalink / raw)
  To: gcc-patches
  Cc: Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Richard Biener, Jim Wilson, Jeff Law

This is a refreshed and improved version of the version posted back in
June.  https://gcc.gnu.org/pipermail/gcc-patches/2023-June/621936.html

Compared with the previous version, this patch initializes probabilities
of newly-created EH edges and execution counts of EH newly-created
blocks; drops obsolete preprocessor conditionals; improve GCing of
identifiers, decls and types introduced by strub; enable gengtype to
cope with token pasting within multi-line macros; gimplify wrapped
va-list loading (needed on ppc64le); adjusted more testcases to tolerate
async stack overwrites.

Regstrapped on x86_64-linux-gnu and ppc64le-linux-gnu, also boostrapped
with -fstrub=all.  Ok to install?

----

This patch adds the strub attribute for function and variable types,
command-line options, passes and adjustments to implement it,
documentation, and tests.

Stack scrubbing is implemented in a machine-independent way: functions
with strub enabled are modified so that they take an extra stack
watermark argument, that they update with their stack use, and the
caller can then zero it out once it regains control, whether by return
or exception.  There are two ways to go about it: at-calls, that
modifies the visible interface (signature) of the function, and
internal, in which the body is moved to a clone, the clone undergoes
the interface change, and the function becomes a wrapper, preserving
its original interface, that calls the clone and then clears the stack
used by it.

Variables can also be annotated with the strub attribute, so that
functions that read from them get stack scrubbing enabled implicitly,
whether at-calls, for functions only usable within a translation unit,
or internal, for functions whose interfaces must not be modified.

There is a strict mode, in which functions that have their stack
scrubbed can only call other functions with stack-scrubbing
interfaces, or those explicitly marked as callable from strub
contexts, so that an entire call chain gets scrubbing, at once or
piecemeal depending on optimization levels.  In the default mode,
relaxed, this requirement is not enforced by the compiler.

The implementation adds two IPA passes, one that assigns strub modes
early on, another that modifies interfaces and adds calls to the
builtins that jointly implement stack scrubbing.  Another builtin,
that obtains the stack pointer, is added for use in the implementation
of the builtins, whether expanded inline or called in libgcc.

There are new command-line options to change operation modes and to
force the feature disabled; it is enabled by default, but it has no
effect and is implicitly disabled if the strub attribute is never
used.  There are also options meant to use for testing the feature,
enabling different strubbing modes for all (viable) functions.


for  gcc/ChangeLog

	* Makefile.in (OBJS): Add ipa-strub.o.
	(GTFILES): Add ipa-strub.cc.
	* builtins.def (BUILT_IN_STACK_ADDRESS): New.
	(BUILT_IN___STRUB_ENTER): New.
	(BUILT_IN___STRUB_UPDATE): New.
	(BUILT_IN___STRUB_LEAVE): New.
	* builtins.cc: Include ipa-strub.h.
	(STACK_STOPS, STACK_UNSIGNED): Define.
	(expand_builtin_stack_address): New.
	(expand_builtin_strub_enter): New.
	(expand_builtin_strub_update): New.
	(expand_builtin_strub_leave): New.
	(expand_builtin): Call them.
	* common.opt (fstrub=*): New options.
	* doc/extend.texi (strub): New type attribute.
	(__builtin_stack_address): New function.
	(Stack Scrubbing): New section.
	* doc/invoke.texi (-fstrub=*): New options.
	(-fdump-ipa-*): New passes.
	* gengtype-lex.l: Ignore multi-line pp-directives.
	* ipa-inline.cc: Include ipa-strub.h.
	(can_inline_edge_p): Test strub_inlinable_to_p.
	* ipa-split.cc: Include ipa-strub.h.
	(execute_split_functions): Test strub_splittable_p.
	* ipa-strub.cc, ipa-strub.h: New.
	* passes.def: Add strub_mode and strub passes.
	* tree-cfg.cc (gimple_verify_flow_info): Note on debug stmts.
	* tree-pass.h (make_pass_ipa_strub_mode): Declare.
	(make_pass_ipa_strub): Declare.
	(make_pass_ipa_function_and_variable_visibility): Fix
	formatting.
	* tree-ssa-ccp.cc (optimize_stack_restore): Keep restores
	before strub leave.
	* multiple_target.cc (pass_target_clone::gate): Test seen_error.
	* attribs.cc: Include ipa-strub.h.
	(decl_attributes): Support applying attributes to function
	type, rather than pointer type, at handler's request.
	(comp_type_attributes): Combine strub_comptypes and target
	comp_type results.
	* doc/tm.texi.in (TARGET_STRUB_USE_DYNAMIC_ARRAY): New.
	(TARGET_STRUB_MAY_USE_MEMSET): New.
	* doc/tm.texi: Rebuilt.
	* cgraph.h (symtab_node::reset): Add preserve_comdat_group
	param, with a default.
	* cgraphunit.cc (symtab_node::reset): Use it.

for  gcc/c-family/ChangeLog

	* c-attribs.cc: Include ipa-strub.h.
	(handle_strub_attribute): New.
	(c_common_attribute_table): Add strub.

for  gcc/ada/ChangeLog

	* gcc-interface/trans.cc: Include ipa-strub.h.
	(gigi): Make internal decls for targets of compiler-generated
	calls strub-callable too.
	(build_raise_check): Likewise.
	* gcc-interface/utils.cc: Include ipa-strub.h.
	(handle_strub_attribute): New.
	(gnat_internal_attribute_table): Add strub.

for  gcc/testsuite/ChangeLog

	* c-c++-common/strub-O0.c: New.
	* c-c++-common/strub-O1.c: New.
	* c-c++-common/strub-O2.c: New.
	* c-c++-common/strub-O2fni.c: New.
	* c-c++-common/strub-O3.c: New.
	* c-c++-common/strub-O3fni.c: New.
	* c-c++-common/strub-Og.c: New.
	* c-c++-common/strub-Os.c: New.
	* c-c++-common/strub-all1.c: New.
	* c-c++-common/strub-all2.c: New.
	* c-c++-common/strub-apply1.c: New.
	* c-c++-common/strub-apply2.c: New.
	* c-c++-common/strub-apply3.c: New.
	* c-c++-common/strub-apply4.c: New.
	* c-c++-common/strub-at-calls1.c: New.
	* c-c++-common/strub-at-calls2.c: New.
	* c-c++-common/strub-defer-O1.c: New.
	* c-c++-common/strub-defer-O2.c: New.
	* c-c++-common/strub-defer-O3.c: New.
	* c-c++-common/strub-defer-Os.c: New.
	* c-c++-common/strub-internal1.c: New.
	* c-c++-common/strub-internal2.c: New.
	* c-c++-common/strub-parms1.c: New.
	* c-c++-common/strub-parms2.c: New.
	* c-c++-common/strub-parms3.c: New.
	* c-c++-common/strub-relaxed1.c: New.
	* c-c++-common/strub-relaxed2.c: New.
	* c-c++-common/strub-short-O0-exc.c: New.
	* c-c++-common/strub-short-O0.c: New.
	* c-c++-common/strub-short-O1.c: New.
	* c-c++-common/strub-short-O2.c: New.
	* c-c++-common/strub-short-O3.c: New.
	* c-c++-common/strub-short-Os.c: New.
	* c-c++-common/strub-strict1.c: New.
	* c-c++-common/strub-strict2.c: New.
	* c-c++-common/strub-tail-O1.c: New.
	* c-c++-common/strub-tail-O2.c: New.
	* c-c++-common/torture/strub-callable1.c: New.
	* c-c++-common/torture/strub-callable2.c: New.
	* c-c++-common/torture/strub-const1.c: New.
	* c-c++-common/torture/strub-const2.c: New.
	* c-c++-common/torture/strub-const3.c: New.
	* c-c++-common/torture/strub-const4.c: New.
	* c-c++-common/torture/strub-data1.c: New.
	* c-c++-common/torture/strub-data2.c: New.
	* c-c++-common/torture/strub-data3.c: New.
	* c-c++-common/torture/strub-data4.c: New.
	* c-c++-common/torture/strub-data5.c: New.
	* c-c++-common/torture/strub-indcall1.c: New.
	* c-c++-common/torture/strub-indcall2.c: New.
	* c-c++-common/torture/strub-indcall3.c: New.
	* c-c++-common/torture/strub-inlinable1.c: New.
	* c-c++-common/torture/strub-inlinable2.c: New.
	* c-c++-common/torture/strub-ptrfn1.c: New.
	* c-c++-common/torture/strub-ptrfn2.c: New.
	* c-c++-common/torture/strub-ptrfn3.c: New.
	* c-c++-common/torture/strub-ptrfn4.c: New.
	* c-c++-common/torture/strub-pure1.c: New.
	* c-c++-common/torture/strub-pure2.c: New.
	* c-c++-common/torture/strub-pure3.c: New.
	* c-c++-common/torture/strub-pure4.c: New.
	* c-c++-common/torture/strub-run1.c: New.
	* c-c++-common/torture/strub-run2.c: New.
	* c-c++-common/torture/strub-run3.c: New.
	* c-c++-common/torture/strub-run4.c: New.
	* c-c++-common/torture/strub-run4c.c: New.
	* c-c++-common/torture/strub-run4d.c: New.
	* c-c++-common/torture/strub-run4i.c: New.
	* g++.dg/strub-run1.C: New.
	* g++.dg/torture/strub-init1.C: New.
	* g++.dg/torture/strub-init2.C: New.
	* g++.dg/torture/strub-init3.C: New.
	* gnat.dg/strub_attr.adb, gnat.dg/strub_attr.ads: New.
	* gnat.dg/strub_ind.adb, gnat.dg/strub_ind.ads: New.

for  libgcc/ChangeLog

	* Makefile.in (LIB2ADD): Add strub.c.
	* libgcc2.h (__strub_enter, __strub_update, __strub_leave):
	Declare.
	* strub.c: New.
---
 gcc/Makefile.in                                    |    2 
 gcc/ada/gcc-interface/trans.cc                     |   18 
 gcc/ada/gcc-interface/utils.cc                     |   73 
 gcc/attribs.cc                                     |   37 
 gcc/builtins.cc                                    |  269 ++
 gcc/builtins.def                                   |    4 
 gcc/c-family/c-attribs.cc                          |   82 
 gcc/cgraph.h                                       |    2 
 gcc/cgraphunit.cc                                  |    5 
 gcc/common.opt                                     |   29 
 gcc/doc/extend.texi                                |  307 ++
 gcc/doc/invoke.texi                                |   60 
 gcc/doc/tm.texi                                    |   19 
 gcc/doc/tm.texi.in                                 |   19 
 gcc/gengtype-lex.l                                 |    3 
 gcc/ipa-inline.cc                                  |    6 
 gcc/ipa-split.cc                                   |    7 
 gcc/ipa-strub.cc                                   | 3454 ++++++++++++++++++++
 gcc/ipa-strub.h                                    |   45 
 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-defer-O1.c        |    7 
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |    8 
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |  110 +
 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-relaxed1.c        |   18 
 gcc/testsuite/c-c++-common/strub-relaxed2.c        |   14 
 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-strict1.c         |   36 
 gcc/testsuite/c-c++-common/strub-strict2.c         |   25 
 gcc/testsuite/c-c++-common/strub-tail-O1.c         |    8 
 gcc/testsuite/c-c++-common/strub-tail-O2.c         |   14 
 gcc/testsuite/c-c++-common/strub-var1.c            |   24 
 .../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   |   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 
 .../c-c++-common/torture/strub-inlinable1.c        |   16 
 .../c-c++-common/torture/strub-inlinable2.c        |    7 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |   10 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |   55 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c  |   50 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c  |   43 
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |   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    |   95 +
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |   84 
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |   80 
 gcc/testsuite/c-c++-common/torture/strub-run4.c    |  106 +
 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_access.adb             |   21 
 gcc/testsuite/gnat.dg/strub_access1.adb            |   16 
 gcc/testsuite/gnat.dg/strub_attr.adb               |   37 
 gcc/testsuite/gnat.dg/strub_attr.ads               |   12 
 gcc/testsuite/gnat.dg/strub_disp.adb               |   64 
 gcc/testsuite/gnat.dg/strub_disp1.adb              |   79 
 gcc/testsuite/gnat.dg/strub_ind.adb                |   33 
 gcc/testsuite/gnat.dg/strub_ind.ads                |   17 
 gcc/testsuite/gnat.dg/strub_ind1.adb               |   41 
 gcc/testsuite/gnat.dg/strub_ind1.ads               |   17 
 gcc/testsuite/gnat.dg/strub_ind2.adb               |   34 
 gcc/testsuite/gnat.dg/strub_ind2.ads               |   17 
 gcc/testsuite/gnat.dg/strub_intf.adb               |   93 +
 gcc/testsuite/gnat.dg/strub_intf1.adb              |   86 
 gcc/testsuite/gnat.dg/strub_intf2.adb              |   55 
 gcc/testsuite/gnat.dg/strub_renm.adb               |   21 
 gcc/testsuite/gnat.dg/strub_renm1.adb              |   32 
 gcc/testsuite/gnat.dg/strub_renm2.adb              |   32 
 gcc/testsuite/gnat.dg/strub_var.adb                |   16 
 gcc/testsuite/gnat.dg/strub_var1.adb               |   20 
 gcc/tree-cfg.cc                                    |    1 
 gcc/tree-pass.h                                    |    5 
 gcc/tree-ssa-ccp.cc                                |    4 
 libgcc/Makefile.in                                 |    3 
 libgcc/libgcc2.h                                   |    4 
 libgcc/strub.c                                     |  149 +
 119 files changed, 7302 insertions(+), 12 deletions(-)
 create mode 100644 gcc/ipa-strub.cc
 create mode 100644 gcc/ipa-strub.h
 create mode 100644 gcc/testsuite/c-c++-common/strub-O0.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O2fni.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O3fni.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-Og.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-Os.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-all1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-all2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-apply1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-apply2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-apply3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-apply4.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-at-calls1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-at-calls2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-defer-O1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-defer-O2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-defer-O3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-defer-Os.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-internal1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-internal2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-parms1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-parms2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-parms3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-relaxed1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-relaxed2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O0-exc.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O0.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-Os.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-strict1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-strict2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-tail-O1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-tail-O2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-var1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-callable1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-callable2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data5.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-indcall1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-indcall2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-indcall3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4c.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4d.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4i.c
 create mode 100644 gcc/testsuite/g++.dg/strub-run1.C
 create mode 100644 gcc/testsuite/g++.dg/torture/strub-init1.C
 create mode 100644 gcc/testsuite/g++.dg/torture/strub-init2.C
 create mode 100644 gcc/testsuite/g++.dg/torture/strub-init3.C
 create mode 100644 gcc/testsuite/gnat.dg/strub_access.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_access1.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_attr.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_attr.ads
 create mode 100644 gcc/testsuite/gnat.dg/strub_disp.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_disp1.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind.ads
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind1.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind1.ads
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind2.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind2.ads
 create mode 100644 gcc/testsuite/gnat.dg/strub_intf.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_intf1.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_intf2.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_renm.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_renm1.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_renm2.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_var.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_var1.adb
 create mode 100644 libgcc/strub.c

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index a25a1e32fbc5f..53e953e224344 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1542,6 +1542,7 @@ OBJS = \
 	ipa-reference.o \
 	ipa-ref.o \
 	ipa-utils.o \
+	ipa-strub.o \
 	ipa.o \
 	ira.o \
 	ira-build.o \
@@ -2846,6 +2847,7 @@ GTFILES = $(CPPLIB_H) $(srcdir)/input.h $(srcdir)/coretypes.h \
   $(srcdir)/sanopt.cc \
   $(srcdir)/sancov.cc \
   $(srcdir)/ipa-devirt.cc \
+  $(srcdir)/ipa-strub.cc \
   $(srcdir)/internal-fn.h \
   $(srcdir)/calls.cc \
   $(srcdir)/omp-general.h \
diff --git a/gcc/ada/gcc-interface/trans.cc b/gcc/ada/gcc-interface/trans.cc
index 89f0a07c824e3..49046bf51fb38 100644
--- a/gcc/ada/gcc-interface/trans.cc
+++ b/gcc/ada/gcc-interface/trans.cc
@@ -69,6 +69,21 @@
 #include "ada-tree.h"
 #include "gigi.h"
 
+/* The following #include is for strub_make_callable.
+
+   This function marks a function as safe to call from strub contexts.  We mark
+   Ada subprograms that may be called implicitly by the compiler, and that won't
+   leave on the stack caller data passed to them.  This stops implicit calls
+   introduced in subprograms that have their stack scrubbed from being flagged
+   as unsafe, even in -fstrub=strict mode.
+
+   These subprograms are also marked with the strub(callable) attribute in Ada
+   sources, but their declarations aren't necessarily imported by GNAT, or made
+   visible to gigi, in units that end up relying on them.  So when gigi
+   introduces their declarations on its own, it must also add the attribute, by
+   calling strub_make_callable.  */
+#include "ipa-strub.h"
+
 /* We should avoid allocating more than ALLOCA_THRESHOLD bytes via alloca,
    for fear of running out of stack space.  If we need more, we use xmalloc
    instead.  */
@@ -454,6 +469,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)
     {
@@ -466,6 +482,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.  */
@@ -722,6 +739,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);
   set_call_expr_flags (result, ECF_NORETURN | ECF_XTHROW);
 
   return result;
diff --git a/gcc/ada/gcc-interface/utils.cc b/gcc/ada/gcc-interface/utils.cc
index 4e2ed173fbe97..5b4d16d7904ae 100644
--- a/gcc/ada/gcc-interface/utils.cc
+++ b/gcc/ada/gcc-interface/utils.cc
@@ -39,6 +39,7 @@
 #include "varasm.h"
 #include "toplev.h"
 #include "opts.h"
+#include "ipa-strub.h"
 #include "output.h"
 #include "debug.h"
 #include "convert.h"
@@ -6727,9 +6728,77 @@ handle_no_stack_protector_attribute (tree *node, tree name, tree, int,
    struct attribute_spec.handler.  */
 
 static tree
-handle_strub_attribute (tree *, tree, tree, int, bool *no_add_attrs)
+handle_strub_attribute (tree *node, tree name,
+			tree args,
+			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
-  *no_add_attrs = true;
+  bool enable = true;
+
+  if (args && FUNCTION_POINTER_TYPE_P (*node))
+    *node = TREE_TYPE (*node);
+
+  if (args && FUNC_OR_METHOD_TYPE_P (*node))
+    {
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
+	{
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
+
+	case 0:
+	  warning (OPT_Wattributes,
+		   "%qE attribute ignored because of argument %qE",
+		   name, TREE_VALUE (args));
+	  *no_add_attrs = true;
+	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
+	}
+
+      args = TREE_CHAIN (args);
+    }
+
+  if (args)
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
+
+  /* Warn about unmet expectations that the strub attribute works like a
+     qualifier.  ??? Could/should we extend it to the element/field types
+     here?  */
+  if (TREE_CODE (*node) == ARRAY_TYPE
+      || VECTOR_TYPE_P (*node)
+      || TREE_CODE (*node) == COMPLEX_TYPE)
+    warning (OPT_Wattributes,
+	     "attribute %qE does not apply to elements"
+	     " of non-scalar type %qT",
+	     name, *node);
+  else if (RECORD_OR_UNION_TYPE_P (*node))
+    warning (OPT_Wattributes,
+	     "attribute %qE does not apply to fields"
+	     " of aggregate type %qT",
+	     name, *node);
+
+  /* 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 += 2;
+
   return NULL_TREE;
 }
 
diff --git a/gcc/attribs.cc b/gcc/attribs.cc
index 229d8b32c1ee0..caa157d0a4e24 100644
--- a/gcc/attribs.cc
+++ b/gcc/attribs.cc
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "attribs.h"
 #include "fold-const.h"
+#include "ipa-strub.h"
 #include "stor-layout.h"
 #include "langhooks.h"
 #include "plugin.h"
@@ -785,8 +786,8 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  flags &= ~(int) ATTR_FLAG_TYPE_IN_PLACE;
 	}
 
-      if (spec->function_type_required && TREE_CODE (*anode) != FUNCTION_TYPE
-	  && TREE_CODE (*anode) != METHOD_TYPE)
+      if (spec->function_type_required
+	  && !FUNC_OR_METHOD_TYPE_P (*anode))
 	{
 	  if (TREE_CODE (*anode) == POINTER_TYPE
 	      && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
@@ -898,7 +899,24 @@ decl_attributes (tree *node, tree attributes, int flags,
 	      TYPE_NAME (tt) = *node;
 	    }
 
-	  *anode = cur_and_last_decl[0];
+	  if (*anode != cur_and_last_decl[0])
+	    {
+	      /* Even if !spec->function_type_required, allow the attribute
+		 handler to request the attribute to be applied to the function
+		 type, rather than to the function pointer type, by setting
+		 cur_and_last_decl[0] to the function type.  */
+	      if (!fn_ptr_tmp
+		  && POINTER_TYPE_P (*anode)
+		  && TREE_TYPE (*anode) == cur_and_last_decl[0]
+		  && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
+		{
+		  fn_ptr_tmp = TREE_TYPE (*anode);
+		  fn_ptr_quals = TYPE_QUALS (*anode);
+		  anode = &fn_ptr_tmp;
+		}
+	      *anode = cur_and_last_decl[0];
+	    }
+
 	  if (ret == error_mark_node)
 	    {
 	      warning (OPT_Wattributes, "%qE attribute ignored", name);
@@ -1501,9 +1519,20 @@ comp_type_attributes (const_tree type1, const_tree type2)
   if ((lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type1)) != NULL)
       ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) != NULL))
     return 0;
+  int strub_ret = strub_comptypes (CONST_CAST_TREE (type1),
+				   CONST_CAST_TREE (type2));
+  if (strub_ret == 0)
+    return strub_ret;
   /* As some type combinations - like default calling-convention - might
      be compatible, we have to call the target hook to get the final result.  */
-  return targetm.comp_type_attributes (type1, type2);
+  int target_ret = targetm.comp_type_attributes (type1, type2);
+  if (target_ret == 0)
+    return target_ret;
+  if (strub_ret == 2 || target_ret == 2)
+    return 2;
+  if (strub_ret == 1 && target_ret == 1)
+    return 1;
+  gcc_unreachable ();
 }
 
 /* PREDICATE acts as a function of type:
diff --git a/gcc/builtins.cc b/gcc/builtins.cc
index cb90bd03b3ea3..b89c7a9376a95 100644
--- a/gcc/builtins.cc
+++ b/gcc/builtins.cc
@@ -71,6 +71,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"
@@ -151,6 +152,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);
@@ -5258,6 +5260,252 @@ 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 || 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 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);
+    }
+
+  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 (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);
+    }
+
+  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);
+
+      /* ??? It would be nice to use setmem or similar patterns here,
+	 but they do not necessarily obey the stack growth direction,
+	 which has security implications.  We also have to avoid calls
+	 (memset, bzero or any machine-specific ones), which are
+	 likely unsafe here (see TARGET_STRUB_MAY_USE_MEMSET).  */
+#ifndef STACK_GROWS_DOWNWARD
+      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));
+#else
+      rtx decr = plus_constant (Pmode, end, -ulen);
+      rtx dstm = gen_rtx_MEM (ptr_mode, end);
+
+      rtx_code_label *loop = gen_label_rtx ();
+      emit_label (loop);
+      emit_move_insn (end, force_operand (decr, NULL_RTX));
+      emit_move_insn (dstm, zero);
+#endif
+      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.  */
 
@@ -7585,6 +7833,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 eb6f4ec2034cd..068a60e1e6dce 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -995,6 +995,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.cc b/gcc/c-family/c-attribs.cc
index 232365d46e237..241e50bbd81ce 100644
--- a/gcc/c-family/c-attribs.cc
+++ b/gcc/c-family/c-attribs.cc
@@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "common/common-target.h"
 #include "langhooks.h"
 #include "tree-inline.h"
+#include "ipa-strub.h"
 #include "toplev.h"
 #include "tree-iterator.h"
 #include "opts.h"
@@ -69,6 +70,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 *);
@@ -321,6 +323,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 },
@@ -1476,6 +1480,84 @@ 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 && FUNCTION_POINTER_TYPE_P (*node))
+    *node = TREE_TYPE (*node);
+
+  if (args && FUNC_OR_METHOD_TYPE_P (*node))
+    {
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
+	{
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
+
+	case 0:
+	  warning (OPT_Wattributes,
+		   "%qE attribute ignored because of argument %qE",
+		   name, TREE_VALUE (args));
+	  *no_add_attrs = true;
+	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
+	}
+
+      args = TREE_CHAIN (args);
+    }
+
+  if (args)
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
+
+  /* Warn about unmet expectations that the strub attribute works like a
+     qualifier.  ??? Could/should we extend it to the element/field types
+     here?  */
+  if (TREE_CODE (*node) == ARRAY_TYPE
+      || VECTOR_TYPE_P (*node)
+      || TREE_CODE (*node) == COMPLEX_TYPE)
+    warning (OPT_Wattributes,
+	     "attribute %qE does not apply to elements"
+	     " of non-scalar type %qT",
+	     name, *node);
+  else if (RECORD_OR_UNION_TYPE_P (*node))
+    warning (OPT_Wattributes,
+	     "attribute %qE does not apply to fields"
+	     " of aggregate type %qT",
+	     name, *node);
+
+  /* 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 += 2;
+
+  return NULL_TREE;
+}
+
 /* Handle a "noinline" attribute; arguments as in
    struct attribute_spec.handler.  */
 
diff --git a/gcc/cgraph.h b/gcc/cgraph.h
index cedaaac3a45b7..177bd9dc5b7ac 100644
--- a/gcc/cgraph.h
+++ b/gcc/cgraph.h
@@ -153,7 +153,7 @@ public:
   void remove (void);
 
   /* Undo any definition or use of the symbol.  */
-  void reset (void);
+  void reset (bool preserve_comdat_group = false);
 
   /* Dump symtab node to F.  */
   void dump (FILE *f);
diff --git a/gcc/cgraphunit.cc b/gcc/cgraphunit.cc
index bccd2f2abb5a3..9a550a5cce645 100644
--- a/gcc/cgraphunit.cc
+++ b/gcc/cgraphunit.cc
@@ -384,7 +384,7 @@ symbol_table::process_new_functions (void)
    functions or variables.  */
 
 void
-symtab_node::reset (void)
+symtab_node::reset (bool preserve_comdat_group)
 {
   /* Reset our data structures so we can analyze the function again.  */
   analyzed = false;
@@ -395,7 +395,8 @@ symtab_node::reset (void)
   cpp_implicit_alias = false;
 
   remove_all_references ();
-  remove_from_same_comdat_group ();
+  if (!preserve_comdat_group)
+    remove_from_same_comdat_group ();
 
   if (cgraph_node *cn = dyn_cast <cgraph_node *> (this))
     {
diff --git a/gcc/common.opt b/gcc/common.opt
index ce34075561f9f..c35b6a3de552a 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2875,6 +2875,35 @@ fstrict-overflow
 Common
 Treat signed overflow as undefined.  Negated as -fwrapv -fwrapv-pointer.
 
+fstrub=disable
+Common RejectNegative Var(flag_strub, 0)
+Disable stack scrub entirely, disregarding strub attributes.
+
+fstrub=strict
+Common RejectNegative Var(flag_strub, -4)
+Enable stack scrub as per attributes, with strict call checking.
+
+; If any strub-enabling attribute is seen when the default or strict
+; initializer values are in effect, flag_strub is bumped up by 2.  The
+; scrub mode gate function will then bump these initializer values to
+; 0 if no strub-enabling attribute is seen.  This minimizes the strub
+; overhead.
+fstrub=relaxed
+Common RejectNegative Var(flag_strub, -3) Init(-3)
+Restore default strub mode: as per attributes, with relaxed checking.
+
+fstrub=all
+Common RejectNegative Var(flag_strub, 3)
+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.
+
 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 e1295898fc25c..0cdbbc208c059 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 internal interfaces.
 * Vector Extensions::   Using vector instructions through built-in functions.
 * Offsetof::            Special syntax for implementing @code{offsetof}.
 * __sync Builtins::     Legacy built-in functions for atomic memory access.
@@ -9096,6 +9097,263 @@ pid_t wait (wait_status_ptr_t p)
 @}
 @end smallexample
 
+@cindex @code{strub} type attribute
+@item strub
+This attribute defines stack-scrubbing properties of functions and
+variables.  Being a type attribute, it attaches to types, even when
+specified in function and variable declarations.  When applied to
+function types, it takes an optional string argument.  When applied to a
+pointer-to-function type, if the optional argument is given, it gets
+propagated to the function type.
+
+@smallexample
+/* A strub variable.  */
+int __attribute__ ((strub)) var;
+/* A strub variable that happens to be a pointer.  */
+__attribute__ ((strub)) int *strub_ptr_to_int;
+/* A pointer type that may point to a strub variable.  */
+typedef int __attribute__ ((strub)) *ptr_to_strub_int_type;
+
+/* A declaration of a strub function.  */
+extern int __attribute__ ((strub)) foo (void);
+/* A pointer to that strub function.  */
+int __attribute__ ((strub ("at-calls"))) (*ptr_to_strub_fn)(void) = foo;
+@end smallexample
+
+A function associated with @code{at-calls} @code{strub} mode
+(@code{strub("at-calls")}, or just @code{strub}) undergoes interface
+changes.  Its callers are adjusted to match the changes, and to scrub
+(overwrite with zeros) the stack space used by the called function after
+it returns.  The interface change makes the function type incompatible
+with an unadorned but otherwise equivalent type, so @emph{every}
+declaration and every type that may be used to call the function must be
+associated with this strub mode.
+
+A function associated with @code{internal} @code{strub} mode
+(@code{strub("internal")}) retains an unmodified, type-compatible
+interface, but it may be turned into a wrapper that calls the wrapped
+body using a custom interface.  The wrapper then scrubs the stack space
+used by the wrapped body.  Though the wrapped body has its stack space
+scrubbed, the wrapper does not, so arguments and return values may
+remain unscrubbed even when such a function is called by another
+function that enables @code{strub}.  This is why, when compiling with
+@option{-fstrub=strict}, a @code{strub} context is not allowed to call
+@code{internal} @code{strub} functions.
+
+@smallexample
+/* A declaration of an internal-strub function.  */
+extern int __attribute__ ((strub ("internal"))) bar (void);
+
+int __attribute__ ((strub))
+baz (void)
+@{
+  /* Ok, foo was declared above as an at-calls strub function.  */
+  foo ();
+  /* Not allowed in strict mode, otherwise allowed.  */
+  bar ();
+@}
+@end smallexample
+
+An automatically-allocated variable associated with the @code{strub}
+attribute causes the (immediately) enclosing function to have
+@code{strub} enabled.
+
+A statically-allocated variable associated with the @code{strub}
+attribute causes functions that @emph{read} it, through its @code{strub}
+data type, to have @code{strub} enabled.  Reading data by dereferencing
+a pointer to a @code{strub} data type has the same effect.  Note: The
+attribute does not carry over from a composite type to the types of its
+components, so the intended effect may not be obtained with non-scalar
+types.
+
+When selecting a @code{strub}-enabled mode for a function that is not
+explicitly associated with one, because of @code{strub} variables or
+data pointers, the function must satisfy @code{internal} mode viability
+requirements (see below), even when @code{at-calls} mode is also viable
+and, being more efficient, ends up selected as an optimization.
+
+@smallexample
+/* zapme is implicitly strub-enabled because of strub variables.
+   Optimization may change its strub mode, but not the requirements.  */
+static int
+zapme (int i)
+@{
+  /* A local strub variable enables strub.  */
+  int __attribute__ ((strub)) lvar;
+  /* Reading strub data through a pointer-to-strub enables strub.  */
+  lvar = * (ptr_to_strub_int_type) &i;
+  /* Writing to a global strub variable does not enable strub.  */
+  var = lvar;
+  /* Reading from a global strub variable enables strub.  */
+  return var;
+@}
+@end smallexample
+
+A @code{strub} context is the body (as opposed to the interface) of a
+function that has @code{strub} enabled, be it explicitly, by
+@code{at-calls} or @code{internal} mode, or implicitly, due to
+@code{strub} variables or command-line options.
+
+A function of a type associated with the @code{disabled} @code{strub}
+mode (@code{strub("disabled")} will not have its own stack space
+scrubbed.  Such functions @emph{cannot} be called from within
+@code{strub} contexts.
+
+In order to enable a function to be called from within @code{strub}
+contexts without having its stack space scrubbed, associate it with the
+@code{callable} @code{strub} mode (@code{strub("callable")}).
+
+When a function is not assigned a @code{strub} mode, explicitly or
+implicitly, the mode defaults to @code{callable}, except when compiling
+with @option{-fstrub=strict}, that causes @code{strub} mode to default
+to @code{disabled}.
+
+@example
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+ /* Implicitly disabled with -fstrub=strict, otherwise callable.  */
+extern int bah (void);
+
+int __attribute__ ((strub))
+bal (void)
+@{
+  /* Not allowed, bad is not strub-callable.  */
+  bad ();
+  /* Ok, bac is strub-callable.  */
+  bac ();
+  /* Not allowed with -fstrub=strict, otherwise allowed.  */
+  bah ();
+@}
+@end example
+
+Function types marked @code{callable} and @code{disabled} are not
+mutually compatible types, but the underlying interfaces are compatible,
+so it is safe to convert pointers between them, and to use such pointers
+or alternate declarations to call them.  Interfaces are also
+interchangeable between them and @code{internal} (but not
+@code{at-calls}!), but adding @code{internal} to a pointer type will not
+cause the pointed-to function to perform stack scrubbing.
+
+@example
+void __attribute__ ((strub))
+bap (void)
+@{
+  /* Assign a callable function to pointer-to-disabled.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bac;
+  /* Not allowed: calls disabled type in a strub context.  */
+  d_p ();
+
+  /* Assign a disabled function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bad;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an internal function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  c_p = bar;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an at-calls function to pointer-to-callable.
+     Flaggged as incompatible.  */
+  c_p = bal;
+  /* The call through an interface-incompatible type will not use the
+     modified interface expected by the at-calls function, so it is
+     likely to misbehave at runtime.  */
+  c_p ();
+@}
+@end example
+
+@code{Strub} contexts are never inlined into non-@code{strub} contexts.
+When an @code{internal}-strub function is split up, the wrapper can
+often be inlined, but the wrapped body @emph{never} is.  A function
+marked as @code{always_inline}, even if explicitly assigned
+@code{internal} strub mode, will not undergo wrapping, so its body gets
+inlined as required.
+
+@example
+inline int __attribute__ ((strub ("at-calls")))
+inl_atc (void)
+@{
+  /* This body may get inlined into strub contexts.  */
+@}
+
+inline int __attribute__ ((strub ("internal")))
+inl_int (void)
+@{
+  /* This body NEVER gets inlined, though its wrapper may.  */
+@}
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+@{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+@}
+
+void __attribute__ ((strub ("disabled")))
+bat (void)
+@{
+  /* Not allowed, cannot inline into a non-strub context.  */
+  inl_int_ali ();
+@}
+@end example
+
+@cindex strub eligibility and viability
+Some @option{-fstrub=*} command line options enable @code{strub} modes
+implicitly where viable.  A @code{strub} mode is only viable for a
+function if the function is eligible for that mode, and if other
+conditions, detailed below, are satisfied.  If it's not eligible for a
+mode, attempts to explicitly associate it with that mode are rejected
+with an error message.  If it is eligible, that mode may be assigned
+explicitly through this attribute, but implicit assignment through
+command-line options may involve additional viability requirements.
+
+A function is ineligible for @code{at-calls} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+@code{At-calls} @code{strub} mode, if not requested through the function
+type, is only viable for an eligible function if the function is not
+visible to other translation units, if it doesn't have its address
+taken, and if it is never called with a function type overrider.
+
+@smallexample
+/* bar is eligible for at-calls strub mode,
+   but not viable for that mode because it is visible to other units.
+   It is eligible and viable for internal strub mode.  */
+void bav () @{@}
+
+/* setp is eligible for at-calls strub mode,
+   but not viable for that mode because its address is taken.
+   It is eligible and viable for internal strub mode.  */
+void setp (void) @{ static void (*p)(void); = setp; @}
+@end smallexample
+
+A function is ineligible for @code{internal} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, or if attribute
+@code{noipa} is present.  For an @code{always_inline} function, meeting
+these requirements is enough to make it eligible.  Any function that has
+attribute @code{noclone}, that uses such extensions as non-local labels,
+computed gotos, alternate variable argument passing interfaces,
+@code{__builtin_next_arg}, or @code{__builtin_return_address}, or that
+takes too many (about 64Ki) arguments is ineligible, unless it is
+@code{always_inline}.  For @code{internal} @code{strub} mode, all
+eligible functions are viable.
+
+@smallexample
+/* flop is not eligible, thus not viable, for at-calls strub mode.
+   Likewise for internal strub mode.  */
+__attribute__ ((noipa)) void flop (void) @{@}
+
+/* flip is eligible and viable for at-calls strub mode.
+   It would be ineligible for internal strub mode, because of noclone,
+   if it weren't for always_inline.  With always_inline, noclone is not
+   an obstacle, so it is also eligible and viable for internal strub mode.  */
+inline __attribute__ ((noclone, always_inline)) void flip (void) @{@}
+@end smallexample
+
 @cindex @code{unused} type attribute
 @item unused
 When attached to a type (including a @code{union} or a @code{struct}),
@@ -12241,6 +12499,55 @@ option is in effect.  Such calls should only be made in debugging
 situations.
 @enddefbuiltin
 
+@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 internal interfaces
+
+Stack scrubbing involves cooperation between a @code{strub} context,
+i.e., a function whose stack frame is to be zeroed-out, and its callers.
+The caller initializes a stack watermark, the @code{strub} context
+updates the watermark according to its stack use, and the caller zeroes
+it out once it regains control, whether by the callee's returning or by
+an exception.
+
+Each of these steps is performed by a different builtin function call.
+Calls to these builtins are introduced automatically, in response to
+@code{strub} attributes and command-line options; they are not expected
+to be explicitly called by source code.
+
+The functions that implement the builtins are available in libgcc but,
+depending on optimization levels, they are expanded internally, adjusted
+to account for inlining, and sometimes combined/deferred (e.g. passing
+the caller-supplied watermark on to callees, refraining from erasing
+stack areas that the caller will) to enable tail calls and to optimize
+for code size.
+
+@deftypefn {Built-in Function} {void} __builtin___strub_enter (void **@var{wmptr})
+This function initializes a stack @var{watermark} variable with the
+current top of the stack.  A call to this builtin function is introduced
+before entering a @code{strub} context.  It remains as a function call
+if optimization is not enabled.
+@end deftypefn
+
+@deftypefn {Built-in Function} {void} __builtin___strub_update (void **@var{wmptr})
+This function updates a stack @var{watermark} variable with the current
+top of the stack, if it tops the previous watermark.  A call to this
+builtin function is inserted within @code{strub} contexts, whenever
+additional stack space may have been used.  It remains as a function
+call at optimization levels lower than 2.
+@end deftypefn
+
+@deftypefn {Built-in Function} {void} __builtin___strub_leave (void **@var{wmptr})
+This function overwrites the memory area between the current top of the
+stack, and the @var{watermark}ed address.  A call to this builtin
+function is inserted after leaving a @code{strub} context.  It remains
+as a function call at optimization levels lower than 3, and it is guarded by
+a condition at level 2.
+@end deftypefn
+
 @node Vector Extensions
 @section Using Vector Instructions through Built-in Functions
 
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 1a762bdcc480f..f18c31fca1f2f 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -649,6 +649,8 @@ Objective-C and Objective-C++ Dialects}.
 -fstack-protector-explicit  -fstack-check
 -fstack-limit-register=@var{reg}  -fstack-limit-symbol=@var{sym}
 -fno-stack-limit  -fsplit-stack
+-fstrub=disable  -fstrub=strict  -fstrub=relaxed
+-fstrub=all  -fstrub=at-calls  -fstrub=internal
 -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 -fvtv-counts  -fvtv-debug
 -finstrument-functions  -finstrument-functions-once
@@ -17689,6 +17691,56 @@ 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.
 
+@opindex -fstrub=disable
+@item -fstrub=disable
+Disable stack scrubbing entirely, ignoring any @code{strub} attributes.
+See @xref{Common Type Attributes}.
+
+@opindex fstrub=strict
+@item -fstrub=strict
+Functions default to @code{strub} mode @code{disabled}, and apply
+@option{strict}ly the restriction that only functions associated with
+@code{strub}-@code{callable} modes (@code{at-calls}, @code{callable} and
+@code{always_inline} @code{internal}) are @code{callable} by functions
+with @code{strub}-enabled modes (@code{at-calls} and @code{internal}).
+
+@opindex fstrub=relaxed
+@item -fstrub=relaxed
+Restore the default stack scrub (@code{strub}) setting, namely,
+@code{strub} is only enabled as required by @code{strub} attributes
+associated with function and data types.  @code{Relaxed} means that
+strub contexts are only prevented from calling functions explicitly
+associated with @code{strub} mode @code{disabled}.  This option is only
+useful to override other @option{-fstrub=*} options that precede it in
+the command line.
+
+@opindex fstrub=at-calls
+@item -fstrub=at-calls
+Enable @code{at-calls} @code{strub} mode where viable.  The primary use
+of this option is for testing.  It exercises the @code{strub} machinery
+in scenarios strictly local to a translation unit.  This @code{strub}
+mode modifies function interfaces, so any function that is visible to
+other translation units, or that has its address taken, will @emph{not}
+be affected by this option.  Optimization options may also affect
+viability.  See the @code{strub} attribute documentation for details on
+viability and eligibility requirements.
+
+@opindex fstrub=internal
+@item -fstrub=internal
+Enable @code{internal} @code{strub} mode where viable.  The primary use
+of this option is for testing.  This option is intended to exercise
+thoroughly parts of the @code{strub} machinery that implement the less
+efficient, but interface-preserving @code{strub} mode.  Functions that
+would not be affected by this option are quite uncommon.
+
+@opindex fstrub=all
+@item -fstrub=all
+Enable some @code{strub} mode where viable.  When both strub modes are
+viable, @code{at-calls} is preferred.  @option{-fdump-ipa-strubm} adds
+function attributes that tell which mode was selected for each function.
+The primary use of this option is for testing, to exercise thoroughly
+the @code{strub} machinery.
+
 @opindex fvtable-verify
 @item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 This option is only available when compiling C++ code.
@@ -19594,6 +19646,14 @@ and inlining decisions.
 @item inline
 Dump after function inlining.
 
+@item strubm
+Dump after selecting @code{strub} modes, and recording the selections as
+function attributes.
+
+@item strub
+Dump @code{strub} transformations: interface changes, function wrapping,
+and insertion of builtin calls for stack scrubbing and watermarking.
+
 @end table
 
 Additionally, the options @option{-optimized}, @option{-missed},
diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index f7ac806ff157c..3c6fc506cc1c1 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -3449,6 +3449,25 @@ in DWARF 2 debug information.  The default is zero.  A different value
 may reduce the size of debug information on some ports.
 @end defmac
 
+@defmac TARGET_STRUB_USE_DYNAMIC_ARRAY
+If defined to nonzero, @code{__strub_leave} will allocate a dynamic
+array covering the stack range that needs scrubbing before clearing it.
+Allocating the array tends to make scrubbing slower, but it enables the
+scrubbing to be safely implemented with a @code{memset} call, which
+could make up for the difference.
+@end defmac
+
+@defmac TARGET_STRUB_MAY_USE_MEMSET
+If defined to nonzero, enable @code{__strub_leave} to be optimized so as
+to call @code{memset} for stack scrubbing.  This is only enabled by
+default if @code{TARGET_STRUB_USE_DYNAMIC_ARRAY} is enabled; it's not
+advisable to enable it otherwise, since @code{memset} would then likely
+overwrite its own stack frame, but it might work if the target ABI
+enables @code{memset} to not use the stack at all, not even for
+arguments or its return address, and its implementation is trivial
+enough that it doesn't use a stack frame.
+@end defmac
+
 @node Exception Handling
 @subsection Exception Handling Support
 @cindex exception handling
diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
index 141027e0bb464..d513de365e93e 100644
--- a/gcc/doc/tm.texi.in
+++ b/gcc/doc/tm.texi.in
@@ -2685,6 +2685,25 @@ in DWARF 2 debug information.  The default is zero.  A different value
 may reduce the size of debug information on some ports.
 @end defmac
 
+@defmac TARGET_STRUB_USE_DYNAMIC_ARRAY
+If defined to nonzero, @code{__strub_leave} will allocate a dynamic
+array covering the stack range that needs scrubbing before clearing it.
+Allocating the array tends to make scrubbing slower, but it enables the
+scrubbing to be safely implemented with a @code{memset} call, which
+could make up for the difference.
+@end defmac
+
+@defmac TARGET_STRUB_MAY_USE_MEMSET
+If defined to nonzero, enable @code{__strub_leave} to be optimized so as
+to call @code{memset} for stack scrubbing.  This is only enabled by
+default if @code{TARGET_STRUB_USE_DYNAMIC_ARRAY} is enabled; it's not
+advisable to enable it otherwise, since @code{memset} would then likely
+overwrite its own stack frame, but it might work if the target ABI
+enables @code{memset} to not use the stack at all, not even for
+arguments or its return address, and its implementation is trivial
+enough that it doesn't use a stack frame.
+@end defmac
+
 @node Exception Handling
 @subsection Exception Handling Support
 @cindex exception handling
diff --git a/gcc/gengtype-lex.l b/gcc/gengtype-lex.l
index 34837d9dc9a8f..a7bb44cf2b9ad 100644
--- a/gcc/gengtype-lex.l
+++ b/gcc/gengtype-lex.l
@@ -165,6 +165,9 @@ CXX_KEYWORD inline|public:|private:|protected:|template|operator|friend|static|m
 [(){},*:<>;=%/|+\!\?\.-]	{ return yytext[0]; }
 
    /* ignore pp-directives */
+^{HWS}"#"{HWS}[a-z_]+([^\n]*"\\"\n)+[^\n]*\n   {
+  update_lineno (yytext, yyleng);
+}
 ^{HWS}"#"{HWS}[a-z_]+[^\n]*\n   {lexer_line.line++;}
 
 .				{
diff --git a/gcc/ipa-inline.cc b/gcc/ipa-inline.cc
index dc120e6da5af6..dbc3c7e8fdc88 100644
--- a/gcc/ipa-inline.cc
+++ b/gcc/ipa-inline.cc
@@ -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"
 
 /* Inliner uses greedy algorithm to inline calls in a priority order.
    Badness is used as the key in a Fibonacci heap which roughly corresponds
@@ -443,6 +444,11 @@ can_inline_edge_p (struct cgraph_edge *e, bool report,
       inlinable = false;
     }
 
+  if (inlinable && !strub_inlinable_to_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.cc b/gcc/ipa-split.cc
index 6730f4f9d0e31..1a7285ff5dcf8 100644
--- a/gcc/ipa-split.cc
+++ b/gcc/ipa-split.cc
@@ -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.  */
 
@@ -1811,6 +1812,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.cc b/gcc/ipa-strub.cc
new file mode 100644
index 0000000000000..612ece9f18552
--- /dev/null
+++ b/gcc/ipa-strub.cc
@@ -0,0 +1,3454 @@
+/* strub (stack scrubbing) support.
+   Copyright (C) 2021-2022 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"
+#include "symtab-thunks.h"
+#include "attr-fnspec.h"
+
+/* 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,
+
+};
+
+/* Look up a strub attribute in TYPE, and return it.  */
+
+static tree
+get_strub_attr_from_type (tree type)
+{
+  return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
+}
+
+/* Look up a strub attribute in DECL or in its type, and return it.  */
+
+static tree
+get_strub_attr_from_decl (tree decl)
+{
+  tree ret = lookup_attribute ("strub", DECL_ATTRIBUTES (decl));
+  if (ret)
+    return ret;
+  return get_strub_attr_from_type (TREE_TYPE (decl));
+}
+
+#define STRUB_ID_COUNT		8
+#define STRUB_IDENT_COUNT	3
+#define STRUB_TYPE_COUNT	5
+
+#define STRUB_ID_BASE		0
+#define STRUB_IDENT_BASE	(STRUB_ID_BASE + STRUB_ID_COUNT)
+#define STRUB_TYPE_BASE		(STRUB_IDENT_BASE + STRUB_IDENT_COUNT)
+#define STRUB_CACHE_SIZE	(STRUB_TYPE_BASE + STRUB_TYPE_COUNT)
+
+/* Keep the strub mode and temp identifiers and types from being GC'd.  */
+static GTY((deletable)) tree strub_cache[STRUB_CACHE_SIZE];
+
+/* Define a function to cache identifier ID, to be used as a strub attribute
+   parameter for a strub mode named after NAME.  */
+#define DEF_STRUB_IDS(IDX, NAME, ID)				\
+static inline tree get_strub_mode_id_ ## NAME () {		\
+  int idx = STRUB_ID_BASE + IDX;				\
+  tree identifier = strub_cache[idx];				\
+  if (!identifier)						\
+    strub_cache[idx] = identifier = get_identifier (ID);	\
+  return identifier;						\
+}
+/* Same as DEF_STRUB_IDS, but use the string expansion of NAME as ID.  */
+#define DEF_STRUB_ID(IDX, NAME)			\
+  DEF_STRUB_IDS (IDX, NAME, #NAME)
+
+/* Define functions for each of the strub mode identifiers.
+   Expose dashes rather than underscores.  */
+DEF_STRUB_ID (0, disabled)
+DEF_STRUB_IDS (1, at_calls, "at-calls")
+DEF_STRUB_ID (2, internal)
+DEF_STRUB_ID (3, callable)
+DEF_STRUB_ID (4, wrapped)
+DEF_STRUB_ID (5, wrapper)
+DEF_STRUB_ID (6, inlinable)
+DEF_STRUB_IDS (7, at_calls_opt, "at-calls-opt")
+
+/* Release the temporary macro names.  */
+#undef DEF_STRUB_IDS
+#undef DEF_STRUB_ID
+
+/* Return the identifier corresponding to strub MODE.  */
+
+static tree
+get_strub_mode_attr_parm (enum strub_mode mode)
+{
+  switch (mode)
+    {
+    case STRUB_DISABLED:
+      return get_strub_mode_id_disabled ();
+
+    case STRUB_AT_CALLS:
+      return get_strub_mode_id_at_calls ();
+
+    case STRUB_INTERNAL:
+      return get_strub_mode_id_internal ();
+
+    case STRUB_CALLABLE:
+      return get_strub_mode_id_callable ();
+
+    case STRUB_WRAPPED:
+      return get_strub_mode_id_wrapped ();
+
+    case STRUB_WRAPPER:
+      return get_strub_mode_id_wrapper ();
+
+    case STRUB_INLINABLE:
+      return get_strub_mode_id_inlinable ();
+
+    case STRUB_AT_CALLS_OPT:
+      return get_strub_mode_id_at_calls_opt ();
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+/* Return the parmeters (TREE_VALUE) for a strub attribute of MODE.
+   We know we use a single parameter, so we bypass the creation of a
+   tree list.  */
+
+static tree
+get_strub_mode_attr_value (enum strub_mode mode)
+{
+  return get_strub_mode_attr_parm (mode);
+}
+
+/* Determine whether ID is a well-formed strub mode-specifying attribute
+   parameter for a function (type).  Only user-visible modes are accepted, and
+   ID must be non-NULL.
+
+   For unacceptable parms, return 0, otherwise a nonzero value as below.
+
+   If the parm enables strub, return positive, otherwise negative.
+
+   If the affected type must be a distinct, incompatible type,return an integer
+   of absolute value 2, otherwise 1.  */
+
+int
+strub_validate_fn_attr_parm (tree id)
+{
+  int ret;
+  const char *s = NULL;
+  size_t len = 0;
+
+  /* do NOT test for NULL.  This is only to be called with non-NULL arguments.
+     We assume that the strub parameter applies to a function, because only
+     functions accept an explicit argument.  If we accepted NULL, and we
+     happened to be called to verify the argument for a variable, our return
+     values would be wrong.  */
+  if (TREE_CODE (id) == STRING_CST)
+    {
+      s = TREE_STRING_POINTER (id);
+      len = TREE_STRING_LENGTH (id) - 1;
+    }
+  else if (TREE_CODE (id) == IDENTIFIER_NODE)
+    {
+      s = IDENTIFIER_POINTER (id);
+      len = IDENTIFIER_LENGTH (id);
+    }
+  else
+    return 0;
+
+  enum strub_mode mode;
+
+  if (len != 8)
+    return 0;
+
+  switch (s[0])
+    {
+    case 'd':
+      mode = STRUB_DISABLED;
+      ret = -1;
+      break;
+
+    case 'a':
+      mode = STRUB_AT_CALLS;
+      ret = 2;
+      break;
+
+    case 'i':
+      mode = STRUB_INTERNAL;
+      ret = 1;
+      break;
+
+    case 'c':
+      mode = STRUB_CALLABLE;
+      ret = -2;
+      break;
+
+    default:
+      /* Other parms are for internal use only.  */
+      return 0;
+    }
+
+  tree mode_id = get_strub_mode_attr_parm (mode);
+
+  if (TREE_CODE (id) == IDENTIFIER_NODE
+      ? id != mode_id
+      : strncmp (s, IDENTIFIER_POINTER (mode_id), len) != 0)
+    return 0;
+
+  return ret;
+}
+
+/* Return the strub mode from STRUB_ATTR.  VAR_P should be TRUE if the attribute
+   is taken from a variable, rather than from a function, or a type thereof.  */
+
+static enum strub_mode
+get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
+{
+  enum strub_mode mode = STRUB_DISABLED;
+
+  if (strub_attr)
+    {
+      if (!TREE_VALUE (strub_attr))
+	mode = !var_p ? STRUB_AT_CALLS : STRUB_INTERNAL;
+      else
+	{
+	  gcc_checking_assert (!var_p);
+	  tree id = TREE_VALUE (strub_attr);
+	  if (TREE_CODE (id) == TREE_LIST)
+	    id = TREE_VALUE (id);
+	  const char *s = (TREE_CODE (id) == STRING_CST
+			   ? TREE_STRING_POINTER (id)
+			   : IDENTIFIER_POINTER (id));
+	  size_t len = (TREE_CODE (id) == STRING_CST
+			? TREE_STRING_LENGTH (id) - 1
+			: IDENTIFIER_LENGTH (id));
+
+	  switch (len)
+	    {
+	    case 7:
+	      switch (s[6])
+		{
+		case 'r':
+		  mode = STRUB_WRAPPER;
+		  break;
+
+		case 'd':
+		  mode = STRUB_WRAPPED;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 8:
+	      switch (s[0])
+		{
+		case 'd':
+		  mode = STRUB_DISABLED;
+		  break;
+
+		case 'a':
+		  mode = STRUB_AT_CALLS;
+		  break;
+
+		case 'i':
+		  mode = STRUB_INTERNAL;
+		  break;
+
+		case 'c':
+		  mode = STRUB_CALLABLE;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 9:
+	      mode = STRUB_INLINABLE;
+	      break;
+
+	    case 12:
+	      mode = STRUB_AT_CALLS_OPT;
+	      break;
+
+	    default:
+	      gcc_unreachable ();
+	    }
+
+	  gcc_checking_assert (TREE_CODE (id) == IDENTIFIER_NODE
+			       ? id == get_strub_mode_attr_parm (mode)
+			       : strncmp (IDENTIFIER_POINTER
+					  (get_strub_mode_attr_parm (mode)),
+					  s, len) == 0);
+	}
+    }
+
+  return mode;
+}
+
+/* Look up, decode and return the strub mode associated with FNDECL.  */
+
+static enum strub_mode
+get_strub_mode_from_fndecl (tree fndecl)
+{
+  return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
+}
+
+/* Look up, decode and return the strub mode associated with NODE.  */
+
+static enum strub_mode
+get_strub_mode (cgraph_node *node)
+{
+  return get_strub_mode_from_fndecl (node->decl);
+}
+
+/* Look up, decode and return the strub mode associated with TYPE.  */
+
+static enum strub_mode
+get_strub_mode_from_type (tree type)
+{
+  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 >= -1 && !var_p)
+    return STRUB_CALLABLE;
+
+  return STRUB_DISABLED;
+}
+
+\f
+/* Return TRUE iff NODE calls builtin va_start.  */
+
+static bool
+calls_builtin_va_start_p (cgraph_node *node)
+{
+  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;
+}
+
+/* Return TRUE iff NODE calls builtin apply_args, and optionally REPORT it.  */
+
+static bool
+calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
+{
+  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 (e->call_stmt
+		? gimple_location (e->call_stmt)
+		: DECL_SOURCE_LOCATION (node->decl),
+		"at-calls %<strub%> does not support call to %qD",
+		cdecl);
+    }
+
+  return result;
+}
+
+/* Return TRUE iff NODE carries the always_inline attribute.  */
+
+static inline bool
+strub_always_inline_p (cgraph_node *node)
+{
+  return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
+}
+
+/* Return TRUE iff NODE is potentially eligible for any strub-enabled mode, and
+   optionally REPORT the reasons for ineligibility.  */
+
+static inline bool
+can_strub_p (cgraph_node *node, bool report = false)
+{
+  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;
+}
+
+/* Return TRUE iff NODE is eligible for at-calls strub, and optionally REPORT
+   the reasons for ineligibility.  Besides general non-eligibility for
+   strub-enabled modes, at-calls rules out calling builtin apply_args.  */
+
+static bool
+can_strub_at_calls_p (cgraph_node *node, bool report = false)
+{
+  bool result = !report || can_strub_p (node, report);
+
+  if (!result && !report)
+    return result;
+
+  return !calls_builtin_apply_args_p (node, report);
+}
+
+/* Return TRUE iff the called function (pointer or, if available,
+   decl) undergoes a significant type conversion for the call.  Strub
+   mode changes between function types, and other non-useless type
+   conversions, are regarded as significant.  When the function type
+   is overridden, the effective strub mode for the call is that of the
+   call fntype, rather than that of the pointer or of the decl.
+   Functions called with type overrides cannot undergo type changes;
+   it's as if their address was taken, so they're considered
+   non-viable for implicit at-calls strub mode.  */
+
+static inline bool
+strub_call_fntype_override_p (const gcall *gs)
+{
+  if (gimple_call_internal_p (gs))
+    return false;
+  tree fn_type = TREE_TYPE (TREE_TYPE (gimple_call_fn (gs)));
+  if (tree decl = gimple_call_fndecl (gs))
+    fn_type = TREE_TYPE (decl);
+
+  /* We do NOT want to take the mode from the decl here.  This
+     function is used to tell whether we can change the strub mode of
+     a function, and whether the effective mode for the call is to be
+     taken from the decl or from an overrider type.  When the strub
+     mode is explicitly declared, or overridden with a type cast, the
+     difference will be noticed in function types.  However, if the
+     strub mode is implicit due to e.g. strub variables or -fstrub=*
+     command-line flags, we will adjust call types along with function
+     types.  In either case, the presence of type or strub mode
+     overriders in calls will prevent a function from having its strub
+     modes changed in ways that would imply type changes, but taking
+     strub modes from decls would defeat this, since we set strub
+     modes and then call this function to tell whether the original
+     type was overridden to decide whether to adjust the call.  We
+     need the answer to be about the type, not the decl.  */
+  enum strub_mode mode = get_strub_mode_from_type (fn_type);
+  return (get_strub_mode_from_type (gs->u.fntype) != mode
+	  || !useless_type_conversion_p (gs->u.fntype, fn_type));
+}
+
+/* Return TRUE iff NODE is called directly with a type override.  */
+
+static bool
+called_directly_with_type_override_p (cgraph_node *node, void *)
+{
+  for (cgraph_edge *e = node->callers; e; e = e->next_caller)
+    if (e->call_stmt && strub_call_fntype_override_p (e->call_stmt))
+      return true;
+
+  return false;
+}
+
+/* Return TRUE iff NODE or any other nodes aliased to it are called
+   with type overrides.  We can't safely change the type of such
+   functions.  */
+
+static bool
+called_with_type_override_p (cgraph_node *node)
+{
+  return (node->call_for_symbol_thunks_and_aliases
+	  (called_directly_with_type_override_p, NULL, true, true));
+}
+
+/* Symbolic macro for the max number of arguments that internal strub may add to
+   a function.  */
+
+#define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
+
+/* We can't perform internal strubbing if the function body involves certain
+   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 (e->call_stmt
+		    ? gimple_location (e->call_stmt)
+		    : DECL_SOURCE_LOCATION (node->decl),
+		    "%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 references are not preserved when copying.  If referenced
+	 in nested functions, as in 920415-1.c and 920721-4.c their decls get
+	 remapped independently.  The exclusion below might be too broad, in
+	 that we might be able to support correctly cases in which the labels
+	 are only used internally in a function, but disconnecting forced labels
+	 from their original declarations is undesirable in general.  */
+      basic_block bb;
+      FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+	for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+	     !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);
+
+	    if (!FORCED_LABEL (target))
+	      continue;
+
+	    result = false;
+
+	    if (!report)
+	      return result;
+
+	    sorry_at (gimple_location (label_stmt),
+		      "internal %<strub%> does not support forced 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;
+}
+
+/* Return TRUE iff NODE has any strub-requiring local variable, or accesses (as
+   in reading) any variable through a strub-requiring type.  */
+
+static bool
+strub_from_body_p (cgraph_node *node)
+{
+  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-requiring types.
+     ??? Compound types don't propagate the strub requirement to
+     component types.  */
+  basic_block bb;
+  FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+	 !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;
+    }
+}
+
+/* Compute the strub mode to be used for NODE.  STRUB_ATTR should be the strub
+   attribute,found for NODE, if any.  */
+
+static enum strub_mode
+compute_strub_mode (cgraph_node *node, tree strub_attr)
+{
+  enum strub_mode req_mode = get_strub_mode_from_attr (strub_attr);
+
+  gcc_checking_assert (flag_strub >= -2 && flag_strub <= 3);
+
+  /* Symbolic encodings of the -fstrub-* flags.  */
+  /* Enable strub when explicitly requested through attributes to functions or
+     variables, reporting errors if the requests cannot be satisfied.  */
+  const bool strub_flag_auto = flag_strub < 0;
+  /* strub_flag_auto with strub call verification; without this, functions are
+     implicitly callable.  */
+  const bool strub_flag_strict = flag_strub < -1;
+  /* Disable strub altogether, ignore attributes entirely.  */
+  const bool strub_flag_disabled = flag_strub == 0;
+  /* On top of _auto, also enable strub implicitly for functions that can
+     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_strict
+	      || 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->externally_visible
+		   || (node->binds_to_current_def_p ()
+		       && node->can_be_local_p ()))
+	       && node->only_called_directly_p ()
+	       && !called_with_type_override_p (node))));
+  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)
+       : (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;
+}
+
+/* Set FNDT's strub mode to callable.
+   FNDT may be a function decl or a function type.  */
+
+void
+strub_make_callable (tree fndt)
+{
+  strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
+}
+
+/* Set NODE to strub MODE.  Report incompatibilities between MODE and the mode
+   requested through explicit attributes, and cases of non-eligibility.  */
+
+static void
+set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
+{
+  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 %qE selected for %qD, when %qE was requested",
+		    get_strub_mode_attr_parm (mode),
+		    node->decl,
+		    get_strub_mode_attr_parm (req_mode));
+	  if (node->alias)
+	    {
+	      cgraph_node *target = node->ultimate_alias_target ();
+	      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);
+}
+
+/* Compute and set NODE's strub mode.  */
+
+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);
+}
+
+\f
+/* Non-strub functions shouldn't be called from within strub contexts,
+   except through callable ones.  Always inline strub functions can
+   only be called from strub functions.  */
+
+static bool
+strub_callable_from_p (strub_mode caller_mode, strub_mode callee_mode)
+{
+  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 >= -1);
+
+    case STRUB_DISABLED:
+      return false;
+
+    case STRUB_CALLABLE:
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  return true;
+}
+
+/* Return TRUE iff CALLEE can be inlined into CALLER.  We wish to avoid inlining
+   WRAPPED functions back into their WRAPPERs.  More generally, we wish to avoid
+   inlining strubbed functions into non-strubbed ones.  CALLER doesn't have to
+   be an immediate caller of CALLEE: the immediate caller may have already been
+   cloned for inlining, and then CALLER may be further up the original call
+   chain.  ???  It would be nice if our own caller would retry inlining callee
+   if caller gets inlined.  */
+
+bool
+strub_inlinable_to_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:
+      /* When we consider inlining, we've already verified callability, so we
+	 can even inline callable and then disabled into a strub context.  That
+	 will get strubbed along with the context, so it's hopefully not a
+	 problem.  */
+      return true;
+
+    default:
+      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 types T1 and T2 are strub-compatible.  Return 1 if the strub modes
+   are the same, 2 if they are interchangeable, and 0 otherwise.  */
+
+int
+strub_comptypes (tree t1, tree t2)
+{
+  if (TREE_CODE (t1) != TREE_CODE (t2))
+    return 0;
+
+  enum strub_mode m1 = get_strub_mode_from_type (t1);
+  enum strub_mode m2 = get_strub_mode_from_type (t2);
+
+  if (m1 == m2)
+    return 1;
+
+  /* We're dealing with types, so only strub modes that can be selected by
+     attributes in the front end matter.  If either mode is at-calls (for
+     functions) or internal (for variables), the conversion is not
+     compatible.  */
+  bool var_p = !FUNC_OR_METHOD_TYPE_P (t1);
+  enum strub_mode mr = var_p ? STRUB_INTERNAL : STRUB_AT_CALLS;
+  if (m1 == mr || m2 == mr)
+    return 0;
+
+  return 2;
+}
+
+/* Return the effective strub mode used for CALL, and set *TYPEP to
+   the effective type used for the call.  The effective type and mode
+   are those of the callee, unless the call involves a typecast.  */
+
+static enum strub_mode
+effective_strub_mode_for_call (gcall *call, tree *typep)
+{
+  tree type;
+  enum strub_mode mode;
+
+  if (strub_call_fntype_override_p (call))
+    {
+      type = gimple_call_fntype (call);
+      mode = get_strub_mode_from_type (type);
+    }
+  else
+    {
+      type = TREE_TYPE (TREE_TYPE (gimple_call_fn (call)));
+      tree decl = gimple_call_fndecl (call);
+      if (decl)
+	mode = get_strub_mode_from_fndecl (decl);
+      else
+	mode = get_strub_mode_from_type (type);
+    }
+
+  if (typep)
+    *typep = type;
+
+  return mode;
+}
+
+/* Create a distinct copy of the type of NODE's function, and change
+   the fntype of all calls to it with the same main type to the new
+   type.  */
+
+static void
+distinctify_node_type (cgraph_node *node)
+{
+  tree old_type = TREE_TYPE (node->decl);
+  tree new_type = build_distinct_type_copy (old_type);
+  tree new_ptr_type = NULL_TREE;
+
+  /* Remap any calls to node->decl that use old_type, or a variant
+     thereof, to new_type as well.  We don't look for aliases, their
+     declarations will have their types changed independently, and
+     we'll adjust their fntypes then.  */
+  for (cgraph_edge *e = node->callers; e; e = e->next_caller)
+    {
+      if (!e->call_stmt)
+	continue;
+      tree fnaddr = gimple_call_fn (e->call_stmt);
+      gcc_checking_assert (TREE_CODE (fnaddr) == ADDR_EXPR
+			   && TREE_OPERAND (fnaddr, 0) == node->decl);
+      if (strub_call_fntype_override_p (e->call_stmt))
+	continue;
+      if (!new_ptr_type)
+	new_ptr_type = build_pointer_type (new_type);
+      TREE_TYPE (fnaddr) = new_ptr_type;
+      gimple_call_set_fntype (e->call_stmt, new_type);
+    }
+
+  TREE_TYPE (node->decl) = new_type;
+}
+
+/* Return TRUE iff TYPE and any variants have the same strub mode.  */
+
+static bool
+same_strub_mode_in_variants_p (tree type)
+{
+  enum strub_mode mode = get_strub_mode_from_type (type);
+
+  for (tree other = TYPE_MAIN_VARIANT (type);
+       other != NULL_TREE; other = TYPE_NEXT_VARIANT (other))
+    if (type != other && mode != get_strub_mode_from_type (other))
+      return false;
+
+  /* Check that the canonical type, if set, either is in the same
+     variant chain, or has the same strub mode as type.  Also check
+     the variants of the canonical type.  */
+  if (TYPE_CANONICAL (type)
+      && (TYPE_MAIN_VARIANT (TYPE_CANONICAL (type))
+	  != TYPE_MAIN_VARIANT (type)))
+    {
+      if (mode != get_strub_mode_from_type (TYPE_CANONICAL (type)))
+	return false;
+      else
+	return same_strub_mode_in_variants_p (TYPE_CANONICAL (type));
+    }
+
+  return true;
+}
+
+/* Check that strub functions don't call non-strub functions, and that
+   always_inline strub functions are only called by strub
+   functions.  */
+
+static void
+verify_strub ()
+{
+  cgraph_node *node;
+
+  /* It's expected that check strub-wise pointer type compatibility of variables
+     and of functions is already taken care of by front-ends, on account of the
+     attribute's being marked as affecting type identity and of the creation of
+     distinct types.  */
+
+  /* Check that call targets in strub contexts have strub-callable types.  */
+
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+  {
+    enum strub_mode caller_mode = get_strub_mode (node);
+
+    for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
+      {
+	gcc_checking_assert (e->indirect_unknown_callee);
+
+	if (!e->call_stmt)
+	  continue;
+
+	enum strub_mode callee_mode
+	  = effective_strub_mode_for_call (e->call_stmt, NULL);
+
+	if (!strub_callable_from_p (caller_mode, callee_mode))
+	  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 (!e->call_stmt)
+	  continue;
+
+	tree callee_fntype;
+	enum strub_mode callee_mode
+	  = effective_strub_mode_for_call (e->call_stmt, &callee_fntype);
+
+	if (!strub_callable_from_p (caller_mode, callee_mode))
+	  {
+	    if (callee_mode == 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)
+		     && caller_mode == STRUB_INTERNAL)
+	      /* This is ok, it will be kept in the STRUB_WRAPPER, and removed
+		 from the STRUB_WRAPPED's strub context.  */
+	      continue;
+	    else if (!strub_call_fntype_override_p (e->call_stmt))
+	      error_at (gimple_location (e->call_stmt),
+			"calling non-%<strub%> %qD in %<strub%> context %qD",
+			e->callee->decl, node->decl);
+	    else
+	      error_at (gimple_location (e->call_stmt),
+			"calling %qD using non-%<strub%> type %qT"
+			" in %<strub%> context %qD",
+			e->callee->decl, callee_fntype, node->decl);
+	  }
+      }
+  }
+}
+
+namespace {
+
+/* Define a pass to compute strub modes.  */
+const pass_data pass_data_ipa_strub_mode = {
+  SIMPLE_IPA_PASS,
+  "strubm",
+  OPTGROUP_NONE,
+  TV_NONE,
+  PROP_cfg, // properties_required
+  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 relaxed (-3) and strict (-4) settings, that only enable strub at a
+       function or variable attribute's request, the attribute handler changes
+       flag_strub to -1 or -2, respectively, if any strub-enabling occurence of
+       the attribute is found.  Therefore, if it remains at -3 or -4, nothing
+       that would enable strub was found, so we can disable it and avoid the
+       overhead.  */
+    if (flag_strub < -2)
+      flag_strub = 0;
+    return flag_strub;
+  }
+  virtual unsigned int execute (function *);
+};
+
+/* Define a pass to introduce strub transformations.  */
+const pass_data pass_data_ipa_strub = {
+  SIMPLE_IPA_PASS,
+  "strub",
+  OPTGROUP_NONE,
+  TV_NONE,
+  PROP_cfg | PROP_ssa, // 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 && !seen_error (); }
+  virtual unsigned int execute (function *);
+
+  /* Define on demand and cache some types we use often.  */
+#define DEF_TYPE(IDX, NAME, INIT)		\
+  static inline tree get_ ## NAME () {		\
+    int idx = STRUB_TYPE_BASE + IDX;		\
+    static tree type = strub_cache[idx];	\
+    if (!type)					\
+      strub_cache[idx] = 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 (0, wmt, build_variant_type_copy (ptr_type_node))
+
+  DEF_TYPE (1, pwmt, build_reference_type (get_wmt ()))
+
+  DEF_TYPE (2, qpwmt,
+	    build_qualified_type (get_pwmt (),
+				  TYPE_QUAL_RESTRICT
+				  /* | TYPE_QUAL_CONST */))
+
+  DEF_TYPE (3, qptr,
+	    build_qualified_type (ptr_type_node,
+				  TYPE_QUAL_RESTRICT
+				  | TYPE_QUAL_CONST))
+
+  DEF_TYPE (4, qpvalst,
+	    build_qualified_type (build_reference_type
+				  (va_list_type_node),
+				  TYPE_QUAL_RESTRICT
+				  /* | TYPE_QUAL_CONST */))
+
+#undef DEF_TYPE
+
+  /* Define non-strub builtins on demand.  */
+#define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST)			\
+  static tree get_ ## NAME () {					\
+    tree decl = builtin_decl_explicit (CODE);			\
+    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 strub builtins on demand.  */
+#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);			\
+      }								\
+    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 strub identifiers on demand.  */
+#define DEF_IDENT(IDX, NAME)						\
+  static inline tree get_ ## NAME () {					\
+    int idx = STRUB_IDENT_BASE + IDX;					\
+    tree identifier = strub_cache[idx];					\
+    if (!identifier)							\
+      strub_cache[idx] = identifier = get_identifier (".strub." #NAME);	\
+    return identifier;							\
+  }
+
+  DEF_IDENT (0, watermark_ptr)
+  DEF_IDENT (1, va_list_ptr)
+  DEF_IDENT (2, apply_args)
+
+#undef DEF_IDENT
+
+  static inline int adjust_at_calls_type (tree);
+  static inline void adjust_at_calls_call (cgraph_edge *, int, tree);
+  static inline void adjust_at_calls_calls (cgraph_node *);
+
+  /* Add to SEQ a call to the strub watermark update builtin, taking NODE's
+     location if given.  Optionally add the corresponding edge from NODE, with
+     execution frequency COUNT.  Return the modified SEQ.  */
+
+  static inline gimple_seq
+  call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
+			 gimple_seq seq = NULL)
+    {
+      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)
+	node->create_edge (cgraph_node::get_create (uwm), update, count, false);
+      return seq;
+    }
+
+};
+
+} // anon namespace
+
+/* Gather with this type a collection of parameters that we're turning into
+   explicit references.  */
+
+typedef hash_set<tree> indirect_parms_t;
+
+/* Dereference OP's incoming turned-into-reference parm if it's an
+   INDIRECT_PARMS or an ADDR_EXPR thereof.  Set *REC and return according to
+   gimple-walking expectations.  */
+
+static tree
+maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
+{
+  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)))
+	{
+	  op = TREE_OPERAND (op, 0);
+	  return op;
+	}
+    }
+
+  return NULL_TREE;
+}
+
+/* A gimple-walking function that adds dereferencing to indirect parms.  */
+
+static tree
+walk_make_indirect (tree *op, int *rec, void *arg)
+{
+  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;
+}
+
+/* A gimple-walking function that turns any non-gimple-val ADDR_EXPRs into a
+   separate SSA.  Though addresses of e.g. parameters, and of members thereof,
+   are gimple vals, turning parameters into references, with an extra layer of
+   indirection and thus explicit dereferencing, need to be regimplified.  */
+
+static tree
+walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
+{
+  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;
+}
+
+/* Turn STMT's PHI arg defs into separate SSA defs if they've become
+   non-gimple_val.  Return TRUE if any edge insertions need to be committed.  */
+
+static bool
+walk_regimplify_phi (gphi *stmt)
+{
+  bool needs_commit = false;
+
+  for (unsigned i = 0, n = gimple_phi_num_args (stmt); i < n; i++)
+    {
+      tree op = gimple_phi_arg_def (stmt, i);
+      if ((TREE_CODE (op) == ADDR_EXPR
+	   && !is_gimple_val (op))
+	  /* ??? A PARM_DECL that was addressable in the original function and
+	     had its address in PHI nodes, but that became a reference in the
+	     wrapped clone would NOT be updated by update_ssa in PHI nodes.
+	     Alas, if we were to create a default def for it now, update_ssa
+	     would complain that the symbol that needed rewriting already has
+	     SSA names associated with it.  OTOH, leaving the PARM_DECL alone,
+	     it eventually causes errors because it remains unchanged in PHI
+	     nodes, but it gets rewritten as expected if it appears in other
+	     stmts.  So we cheat a little here, and force the PARM_DECL out of
+	     the PHI node and into an assignment.  It's a little expensive,
+	     because we insert it at the edge, which introduces a basic block
+	     that's entirely unnecessary, but it works, and the block will be
+	     removed as the default def gets propagated back into the PHI node,
+	     so the final optimized code looks just as expected.  */
+	  || (TREE_CODE (op) == PARM_DECL
+	      && !TREE_ADDRESSABLE (op)))
+	{
+	  tree temp = make_ssa_name (TREE_TYPE (op), stmt);
+	  if (TREE_CODE (op) == PARM_DECL)
+	    SET_SSA_NAME_VAR_OR_IDENTIFIER (temp, DECL_NAME (op));
+	  SET_PHI_ARG_DEF (stmt, i, temp);
+
+	  gimple *assign = gimple_build_assign (temp, op);
+	  if (gimple_phi_arg_has_location (stmt, i))
+	    gimple_set_location (assign, gimple_phi_arg_location (stmt, i));
+	  gsi_insert_on_edge (gimple_phi_arg_edge (stmt, i), assign);
+	  needs_commit = true;
+	}
+    }
+
+  return needs_commit;
+}
+
+/* Create a reference type to use for PARM when turning it into a reference.
+   NONALIASED causes the reference type to gain its own separate alias set, so
+   that accessing the indirectly-passed parm won'will not add aliasing
+   noise.  */
+
+static tree
+build_ref_type_for (tree parm, bool nonaliased = true)
+{
+  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.  */
+  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;
+}
+
+/* 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)
+{
+  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);
+
+      gcall *call = dyn_cast <gcall *> (stmt);
+      if (!call)
+	continue;
+
+      tree callee = gimple_call_fndecl (call);
+      gcc_checking_assert (callee);
+      node->create_edge (cgraph_node::get_create (callee), call, count, false);
+    }
+}
+
+/* Insert SEQ after the call at GSI, as if the call was in a try block with SEQ
+   as finally, i.e., SEQ will run after the call whether it returns or
+   propagates an exception.  This handles block splitting, EH edge and block
+   creation, noreturn and nothrow optimizations, and even throwing calls without
+   preexisting local handlers.  */
+
+static void
+gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
+{
+  if (!seq)
+    return;
+
+  gimple *stmt = gsi_stmt (gsi);
+
+  if (gimple_has_location (stmt))
+    annotate_all_with_location (seq, gimple_location (stmt));
+
+  gcall *call = dyn_cast <gcall *> (stmt);
+  bool noreturn_p = call && gimple_call_noreturn_p (call);
+  int eh_lp = lookup_stmt_eh_lp (stmt);
+  bool must_not_throw_p = eh_lp < 0;
+  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.  */
+  edge neeh = make_eh_edges (stmt);
+  neeh->probability = profile_probability::never ();
+  gcc_checking_assert (neeh->dest == bb_eh_cleanup);
+  gcc_checking_assert (!neeh->dest->count.initialized_p ());
+  neeh->dest->count = neeh->count ();
+  add_call_edges_for_seq (seq, neeh->dest->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);
+    }
+}
+
+/* Record the order of the last cgraph entry whose mode we've already set, so
+   that we can perform mode setting incrementally without duplication.  */
+static int last_cgraph_order;
+
+/* Set strub modes for functions introduced since the last call.  */
+
+static void
+ipa_strub_set_mode_for_new_functions ()
+{
+  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_fndecl (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;
+
+  gcc_checking_assert (same_strub_mode_in_variants_p (type));
+
+  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;
+}
+
+/* Adjust a call to an at-calls call target.  Create a watermark local variable
+   if needed, initialize it before, pass it to the callee according to the
+   modified at-calls interface, and release the callee's stack space after the
+   call, if not deferred.  If the call is const or pure, arrange for the
+   watermark to not be assumed unused or unchanged.  */
+
+void
+pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args,
+				      tree callee_fntype)
+{
+  gcc_checking_assert (e->call_stmt);
+  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);
+      e->caller->create_edge (cgraph_node::get_create (enter),
+			      stptr, gsi_bb (gsi)->count, false);
+    }
+
+
+  /* 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++)
+      vargs.quick_push (gimple_call_arg (stmt, i));
+
+    if (gimple_call_internal_p (stmt))
+      gcc_unreachable ();
+    else
+      new_stmt = gimple_build_call_vec (gimple_call_fn (stmt), vargs);
+    gimple_call_set_fntype (new_stmt, callee_fntype);
+
+    if (gimple_call_lhs (stmt))
+      gimple_call_set_lhs (new_stmt, gimple_call_lhs (stmt));
+
+    gimple_move_vops (new_stmt, stmt);
+
+    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);
+}
+
+/* Adjust all at-calls calls in NODE. */
+
+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);
+
+	  if (!e->call_stmt)
+	    continue;
+
+	  tree callee_fntype;
+	  enum strub_mode callee_mode
+	    = effective_strub_mode_for_call (e->call_stmt, &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, callee_fntype);
+	}
+      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);
+
+	  if (!e->call_stmt)
+	    continue;
+
+	  tree callee_fntype;
+	  enum strub_mode callee_mode
+	    = effective_strub_mode_for_call (e->call_stmt, &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, callee_fntype);
+	}
+      pop_cfun ();
+    }
+}
+
+/* The strubm (strub mode) pass computes a strub mode for each function in the
+   call graph, and checks, before any inlining, that strub callability
+   requirements in effect are satisfied.  */
+
+unsigned int
+pass_ipa_strub_mode::execute (function *)
+{
+  last_cgraph_order = 0;
+  ipa_strub_set_mode_for_new_functions ();
+
+  /* Verify before any inlining or other transformations.  */
+  verify_strub ();
+
+  return 0;
+}
+
+/* Create a strub mode pass.  */
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub_mode (gcc::context *ctxt)
+{
+  return new pass_ipa_strub_mode (ctxt);
+}
+
+/* The strub pass proper adjusts types, signatures, and at-calls calls, and
+   splits internal-strub functions.  */
+
+unsigned int
+pass_ipa_strub::execute (function *)
+{
+  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)
+	  distinctify_node_type (onode);
+
+	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;
+
+	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);
+
+		  gcall *call = dyn_cast <gcall *> (stmt);
+
+		  if (!call)
+		    continue;
+
+		  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 ();
+      }
+  }
+
+  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;
+      }
+
+    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);
+
+    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;
+	  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;
+	     i++, nparm = DECL_CHAIN (nparm), nparmt = TREE_CHAIN (nparmt))
+	  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;
+      }
+
+#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 (/* unlike create_wrapper: preserve_comdat_group = */true);
+
+	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;
+	onode->create_edge (target, NULL, onode->count);
+	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 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;
+	  bool needs_commit = false;
+	  FOR_EACH_BB_FN (bb, cfun)
+	    {
+	      for (gphi_iterator gsi = gsi_start_nonvirtual_phis (bb);
+		   !gsi_end_p (gsi);
+		   gsi_next_nonvirtual_phi (&gsi))
+		{
+		  gphi *stmt = gsi.phi ();
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed && !is_gimple_debug (gsi_stmt (gsi)))
+		    if (walk_regimplify_phi (stmt))
+		      needs_commit = true;
+		}
+
+	      for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+		   !gsi_end_p (gsi); gsi_next (&gsi))
+		{
+		  gimple *stmt = gsi_stmt (gsi);
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed)
+		    {
+		      if (!is_gimple_debug (stmt))
+			{
+			  wi.info = &gsi;
+			  walk_gimple_op (stmt, walk_regimplify_addr_expr,
+					  &wi);
+			}
+		      update_stmt (stmt);
+		    }
+		}
+	    }
+	  if (needs_commit)
+	    gsi_commit_edge_inserts ();
+	}
+
+      if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca
+	  || is_stdarg || apply_args)
+	for (cgraph_edge *e = nnode->callees, *enext; e; e = enext)
+	  {
+	    if (!e->call_stmt)
+	      continue;
+
+	    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));
+		    if (!is_gimple_val (arg))
+		      arg = force_gimple_operand_gsi (&gsi, arg, true,
+						      NULL_TREE, true, GSI_SAME_STMT);
+		  }
+		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)));
+
+      gcall *wrcall;
+      while (!(wrcall = dyn_cast <gcall *> (gsi_stmt (gsi))))
+	gsi_next (&gsi);
+
+      tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
+      TREE_ADDRESSABLE (swm) = true;
+      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);
+      onode->create_edge (cgraph_node::get_create (enter),
+			  stptr, gsi_bb (gsi)->count, false);
+
+      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);
+	  onode->create_edge (cgraph_node::get_create (bappargs),
+			      appargs, gsi_bb (gsi)->count, false);
+	}
+
+      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);
+	  onode->create_edge (cgraph_node::get_create (bvastart),
+			      vastart, gsi_bb (gsi)->count, false);
+
+	  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 ();
+    }
+  }
+
+  return 0;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub (gcc::context *ctxt)
+{
+  return new pass_ipa_strub (ctxt);
+}
+
+#include "gt-ipa-strub.h"
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
new file mode 100644
index 0000000000000..29869fadfa6c9
--- /dev/null
+++ b/gcc/ipa-strub.h
@@ -0,0 +1,45 @@
+/* strub (stack scrubbing) infrastructure.
+   Copyright (C) 2021-2022 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 CALLEE can be inlined into CALLER, as far as stack scrubbing
+   constraints are concerned.  CALLEE doesn't have to be called directly by
+   CALLER, but the returned value says nothing about intervening functions.  */
+extern bool strub_inlinable_to_p (cgraph_node *callee, cgraph_node *caller);
+
+/* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
+extern bool strub_splittable_p (cgraph_node *node);
+
+/* 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);
+
+/* Return zero iff ID is NOT an acceptable parameter for a user-supplied strub
+   attribute for a function.  Otherwise, return >0 if it enables strub, <0 if it
+   does not.  Return +/-1 if the attribute-modified type is compatible with the
+   type without the attribute, or +/-2 if it is not compatible.  */
+extern int strub_validate_fn_attr_parm (tree id);
+
+/* Like comptypes, return 0 if t1 and t2 are not compatible, 1 if they are
+   compatible, and 2 if they are nearly compatible.  Same strub mode is
+   compatible, interface-compatible strub modes are nearly compatible.  */
+extern int strub_comptypes (tree t1, tree t2);
diff --git a/gcc/passes.def b/gcc/passes.def
index 1e1950bdb39cb..d515e77be0399 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -52,6 +52,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);
@@ -115,6 +116,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 0000000000000..c7a79a6ea0d8a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O0.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=strict -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 0000000000000..96285c975d98e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=strict -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 0000000000000..8edc0d8aa1321
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
+
+/* At -O2, without -fno-inline, we fully expand enter and update, and add a test
+   around the leave call.  */
+
+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 0000000000000..c6d900cf3c45b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -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 0000000000000..33ee465e51cb6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O3.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=strict -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 0000000000000..2936f82079e18
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=strict -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 0000000000000..479746e57d87e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-Og -fstrub=strict -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 0000000000000..2241d4ea07f27
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
+
+/* At -Os, without -fno-inline, we fully expand enter, and also update.  The
+   expanded update might be larger than a call proper, but argument saving and
+   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 0000000000000..a322bcc5da606
--- /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 \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
new file mode 100644
index 0000000000000..db60026d0e080
--- /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 \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
new file mode 100644
index 0000000000000..2f462adc1efe0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+void __attribute__ ((__strub__ ("callable")))
+apply_function (void *args)
+{
+  __builtin_apply (0, args, 0);
+}
+
+void __attribute__ ((__strub__ ("internal")))
+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 0000000000000..a5d7551f5da5c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+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 0000000000000..64422a0d1e880
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+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 0000000000000..15ffaa031b899
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -fdump-ipa-strubm" } */
+
+/* Check that implicit enabling of strub mode selects internal strub when the
+   function uses __builtin_apply_args, that prevents the optimization to
+   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 \[(\]internal\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
new file mode 100644
index 0000000000000..b70843b4215a4
--- /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 \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
new file mode 100644
index 0000000000000..97a3988a6b922
--- /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 \[(\]callable\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
new file mode 100644
index 0000000000000..3d73431b3dcd3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict -O1" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+   the strubbing to its caller at -O1.  */
+
+#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 0000000000000..fddf3c745e7e6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -0,0 +1,8 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict -O2" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+   the strubbing to its caller at -O2.  */
+
+#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 0000000000000..7ebc65b58dd72
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -0,0 +1,110 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict -O3" } */
+
+/* Check that a strub function called by another strub function defers the
+   strubbing to its caller at -O3.  */
+
+#ifndef EXPECT_DEFERRAL
+/* Other strub-defer*.c tests override this macro.  */
+# define EXPECT_DEFERRAL
+#endif
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+/* Pad before and after the string on the stack, so that it's not overwritten by
+   regular stack use.  */
+#define PAD 7
+
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
+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__ ("callable"))) callable_t (char *);
+  callable_t *f = 0;
+
+  char s[2 * PAD + 1][sizeof (test_string)];
+  __builtin_strcpy (s[PAD], test_string);
+  asm ("" : "+m" (s), "+r" (f));
+
+  if (__builtin_expect (!f, 1))
+    return (char*)__builtin_stack_address ();
+
+  f (s[PAD]);
+  return 0;
+}
+
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
+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__ ("at-calls"), __noinline__, __noclone__))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ ("at-calls")))
+char *
+deferred_at_calls ()
+{
+  char *ret;
+  int i = 1;
+  /* Since these test check stack contents above the top of the stack, an
+     unexpected asynchronous signal or interrupt might overwrite the bits we
+     expect to find and cause spurious fails.  Tolerate one such overall
+     spurious fail by retrying.  */
+  while (EXPECT_DEFERRAL !look_for_string ((ret = at_calls ())))
+    if (!i--) __builtin_abort ();
+  return ret;
+}
+
+static __attribute__ ((__strub__ ("internal")))
+char *
+deferred_internal ()
+{
+  int i = 1;
+  char *ret;
+  while (EXPECT_DEFERRAL !look_for_string ((ret = at_calls ())))
+    if (!i--) __builtin_abort ();
+  return ret;
+}
+
+int main ()
+{
+  int i = 1;
+  /* These calls should not be subject to spurious fails: whether or not some
+     asynchronous event overwrites the scrubbed stack space, the string won't
+     remain there.  Unless the asynchronous event happens to write the string
+     where we look for it, but what are the odds?  Anyway, it doesn't hurt to
+     retry, even if just for symmetry.  */
+  while (look_for_string (deferred_at_calls ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (deferred_internal ()))
+    if (!i--) __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 0000000000000..fbaf85fe0fafe
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict -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 0000000000000..e9d7b7b9ee0a8
--- /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 \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
new file mode 100644
index 0000000000000..8b8e15a51c71c
--- /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 \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
new file mode 100644
index 0000000000000..0a4a7539d3489
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ ("internal")))
+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__ ("internal")))
+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__ ("internal")))
+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__ ("internal")))
+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 0000000000000..147171d96d5a1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ ("at-calls")))
+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__ ("at-calls")))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ ("at-calls")))
+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 0000000000000..4e92682895a43
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -0,0 +1,58 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that uses of a strub variable implicitly enables internal strub for
+   publicly-visible functions, and causes the same transformations to their
+   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-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
new file mode 100644
index 0000000000000..e2f9d8aebca58
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  Without the error,
+   inlining takes place.  */
+
+#include "strub-strict1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
new file mode 100644
index 0000000000000..98474435d2e59
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  */
+
+#include "strub-strict2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
new file mode 100644
index 0000000000000..1de15342595e4
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=strict -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 0000000000000..f9209c819004b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=strict -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 0000000000000..bed1dcfb54a45
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=strict -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 0000000000000..6bf0071f52b93
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -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 0000000000000..4732f515bf70c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
+   enter and leave calls within strub contexts, passing on the enclosing
+   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 0000000000000..8d6424c479a3a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
+   enter and leave calls within strub contexts, passing on the enclosing
+   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-strict1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
new file mode 100644
index 0000000000000..368522442066e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-strict1.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm" } */
+
+static int __attribute__ ((__strub__)) var;
+
+/* h becomes STRUB_INLINABLE, because of the use of the strub variable,
+   and the always_inline flag.  It would get inlined before pass_ipa_strub, if
+   it weren't for the error.  */
+static inline void
+__attribute__ ((__always_inline__))
+h() {
+  var++;
+}
+
+/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
+   the viability of at-calls strubbing.  Though internally a strub context, its
+   interface is not strub-enabled, so it's not callable from within strub
+   contexts.  */
+static inline void
+g() {
+  var--;
+  h();
+}
+
+/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
+   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
+void
+f() {
+  var++;
+  g();  /* { dg-error "calling non-.strub." } */
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-strict2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
new file mode 100644
index 0000000000000..b4f2888321821
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-strict2.c
@@ -0,0 +1,25 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm" } */
+
+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 \[(\]internal\[)\]" 2 "strubm" } } */
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 0000000000000..e48e0610e079b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=strict -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 0000000000000..87cda7ab21b16
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued.
+   Tail calls are short-circuited at -O2+.  */
+
+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/strub-var1.c b/gcc/testsuite/c-c++-common/strub-var1.c
new file mode 100644
index 0000000000000..eb6250fd39c90
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-var1.c
@@ -0,0 +1,24 @@
+/* { dg-do compile } */
+
+int __attribute__ ((strub)) x;
+float __attribute__ ((strub)) f;
+double __attribute__ ((strub)) d;
+
+/* The attribute applies to the type of the declaration, i.e., to the pointer
+   variable p, not to the pointed-to integer.  */
+int __attribute__ ((strub)) *
+p = &x; /* { dg-message "incompatible|invalid conversion" } */
+
+typedef int __attribute__ ((strub)) strub_int;
+strub_int *q = &x; /* Now this is compatible.  */
+
+int __attribute__ ((strub))
+a[2]; /* { dg-warning "does not apply to elements" } */
+
+int __attribute__ ((vector_size (4 * sizeof (int))))
+    __attribute__ ((strub))
+v; /* { dg-warning "does not apply to elements" } */
+
+struct s {
+  int i, j;
+} __attribute__ ((strub)) w; /* { dg-warning "does not apply to fields" } */
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 0000000000000..b5e45ab0525ad
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -0,0 +1,9 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+/* Check that strub and non-strub functions can be called from non-strub
+   contexts, and that strub and callable functions can be called from strub
+   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 0000000000000..96aa7fe4b07f7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -0,0 +1,264 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+/* Check that impermissible (cross-strub-context) calls are reported.  */
+
+extern int __attribute__ ((__strub__ ("callable"))) xcallable (void);
+extern int __attribute__ ((__strub__ ("internal"))) xinternal (void);
+extern int __attribute__ ((__strub__ ("at-calls"))) xat_calls (void);
+extern int __attribute__ ((__strub__ ("disabled"))) xdisabled (void);
+
+int __attribute__ ((__strub__ ("callable"))) callable (void);
+int __attribute__ ((__strub__ ("internal"))) internal (void);
+int __attribute__ ((__strub__ ("at-calls"))) at_calls (void);
+int __attribute__ ((__strub__ ("disabled"))) disabled (void);
+
+int __attribute__ ((__strub__)) var;
+int var_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
+icallable (void);
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
+iinternal (void);
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
+iat_calls (void);
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
+idisabled (void);
+static inline int __attribute__ ((__always_inline__))
+ivar_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
+i_callable (void) { return 0; }
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
+i_internal (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
+i_at_calls (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
+i_disabled (void) { return 0; }
+static inline int __attribute__ ((__always_inline__))
+i_var_user (void) { return var; }
+
+#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__ ("callable")))
+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__ ("internal")))
+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__ ("at-calls")))
+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__ ("disabled")))
+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 __attribute__ ((__always_inline__, __strub__ ("at-calls")))
+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 0000000000000..2857195706ed6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const function call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   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 0000000000000..98a92bc9eac2b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const function call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   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 0000000000000..5511a6e1e71d3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   and another to make sure it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ ("internal"), __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 0000000000000..47ee927964dff
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const wrapping call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   before the call, and another to make sure it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ ("internal")))
+#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 0000000000000..7c27a2a1a6dca
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..e66d903780afd
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..5e08e0e58c658
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* The pointer itself is a strub variable, that would enable internal strubbing
+   if its value was used.  Here, it's only overwritten, so no strub.  */
+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 0000000000000..a818e7a38bb5f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* The pointer itself is a strub variable, that would enable internal strubbing
+   if its value was used.  Here, it's only overwritten, so no strub.  */
+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 0000000000000..ddb0b5c0543b0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+/* It would be desirable to issue at least warnings for these.  */
+
+typedef int __attribute__ ((__strub__)) strub_int;
+strub_int *ptr;
+
+int *f () {
+  return ptr; /* { dg-message "incompatible|invalid conversion" } */
+}
+
+strub_int *g () {
+  return f (); /* { dg-message "incompatible|invalid conversion" } */
+}
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 0000000000000..c165f312f16de
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..69fcff8d3763d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..ff006224909bd
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
new file mode 100644
index 0000000000000..614b02228ba29
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+inline void __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+}
+
+void
+bat (void)
+{
+  /* Not allowed, not a strub context.  */
+  inl_int_ali (); /* { dg-error "context" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
new file mode 100644
index 0000000000000..f9a6b4a16faf8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=all" } */
+
+#include "strub-inlinable1.c"
+
+/* With -fstrub=all, the caller becomes a strub context, so the strub-inlinable
+   callee is not rejected.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
new file mode 100644
index 0000000000000..b4a7f3992bbaa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+typedef void ft (void);
+typedef void ft2 (int, int);
+extern ft __attribute__ ((__strub__)) fnac;
+
+ft * f (void) {
+  return fnac; /* { dg-message "incompatible|invalid conversion" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
new file mode 100644
index 0000000000000..d9d2c0caec42d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
@@ -0,0 +1,55 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic" } */
+
+/* C++ does not warn about the partial incompatibilities.
+
+   The d_p () calls are actually rejected, even in C++, but they are XFAILed
+   here because we don't get far enough in the compilation as to observe them,
+   because the incompatibilities are errors without -fpermissive.
+   strub-ptrfn3.c uses -fpermissive to check those.
+ */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
new file mode 100644
index 0000000000000..e1f179e160e5c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
@@ -0,0 +1,50 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic -fpermissive" } */
+/* { dg-prune-output "command-line option .-fpermissive." } */
+
+/* See strub-ptrfn2.c.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
new file mode 100644
index 0000000000000..70b558afad040
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+/* This is strub-ptrfn2.c without -Wpedantic.
+
+   Even C doesn't report the (not-quite-)compatible conversions without it.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
new file mode 100644
index 0000000000000..a262a086837b2
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure function call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+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 0000000000000..4c4bd50c209a0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure function call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   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 0000000000000..ce195c6b1f1b6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ ("internal"), __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 0000000000000..75cd54ccb5b5d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure wrapping call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ ("internal")))
+#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 0000000000000..7458b3fb54da5
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -0,0 +1,95 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  Avoid the use of red zones by avoiding
+   leaf functions.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+/* Pad before and after the string on the stack, so that it's not overwritten by
+   regular stack use.  */
+#define PAD 7
+
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
+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__ ("callable"))) callable_t (char *);
+  callable_t *f = 0;
+
+  char s[2 * PAD + 1][sizeof (test_string)];
+  __builtin_strcpy (s[PAD], test_string);
+  asm ("" : "+m" (s), "+r" (f));
+
+  if (__builtin_expect (!f, 1))
+    return (char *) __builtin_stack_address ();
+
+  f (s[PAD]);
+  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__ ("at-calls")))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ ("internal")))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  /* Since these test check stack contents above the top of the stack, an
+     unexpected asynchronous signal or interrupt might overwrite the bits we
+     expect to find and cause spurious fails.  Tolerate one such overall
+     spurious fail by retrying.  */
+  int i = 1;
+  while (!look_for_string (callable ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (at_calls ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (internal ()))
+    if (!i--) __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 0000000000000..5d60a7775f4bb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -0,0 +1,84 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  Allow red zones to be used.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+/* Pad before and after the string on the stack, so that it's not overwritten by
+   regular stack use.  */
+#define PAD 7
+
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
+char *
+leak_string (void)
+{
+  int len = sizeof (test_string);
+  asm ("" : "+rm" (len));
+  char s[2 * PAD + 1][len];
+  __builtin_strcpy (s[PAD], 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__ ("at-calls")))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ ("internal")))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  /* Since these test check stack contents above the top of the stack, an
+     unexpected asynchronous signal or interrupt might overwrite the bits we
+     expect to find and cause spurious fails.  Tolerate one such overall
+     spurious fail by retrying.  */
+  int i = 1;
+  while (!look_for_string (callable ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (at_calls ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (internal ()))
+    if (!i--) __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 0000000000000..c2ad710858e87
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -0,0 +1,80 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict" } */
+/* { 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__ ("callable")))
+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__ ("at-calls")))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ ("internal")))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  /* Since these test check stack contents above the top of the stack, an
+     unexpected asynchronous signal or interrupt might overwrite the bits we
+     expect to find and cause spurious fails.  Tolerate one such overall
+     spurious fail by retrying.  */
+  int i = 1;
+  while (!look_for_string (callable ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (at_calls ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (internal ()))
+    if (!i--) __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 0000000000000..3b36b8e5d68ef
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -0,0 +1,106 @@
+/* { 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__ ("internal")))
+char *
+internal ()
+{
+  return intermediate ();
+}
+
+int __attribute__ ((__strub__ ("disabled")))
+main ()
+{
+  /* Since these test check stack contents above the top of the stack, an
+     unexpected asynchronous signal or interrupt might overwrite the bits we
+     expect to find and cause spurious fails.  Tolerate one such overall
+     spurious fail by retrying.  */
+  int i = 1;
+  while (look_for_string (internal ()))
+    if (!i--) __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 0000000000000..57f9baf758ded
--- /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 0000000000000..08de3f1c3b17c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict" } */
+/* { dg-require-effective-target alloca } */
+
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
+
+#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 0000000000000..459f6886c5499
--- /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 0000000000000..0d367fb83d09d
--- /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 0000000000000..c226ab10ff651
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..a7911f1fa7212
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..6ebebcd01e8ea
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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_access.adb b/gcc/testsuite/gnat.dg/strub_access.adb
new file mode 100644
index 0000000000000..29e6996ecf61c
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_access.adb
@@ -0,0 +1,21 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=relaxed -fdump-ipa-strubm" }
+
+--  The main subprogram doesn't read from the automatic variable, but
+--  being an automatic variable, its presence should be enough for the
+--  procedure to get strub enabled.
+
+procedure Strub_Access is
+   type Strub_Int is new Integer;
+   pragma Machine_Attribute (Strub_Int, "strub");
+   
+   X : aliased Strub_Int := 0;
+
+   function F (P : access Strub_Int) return Strub_Int is (P.all);
+
+begin
+   X := F (X'Access);
+end Strub_Access;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 1 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls-opt\[)\]\[)\]" 1 "strubm" } }
diff --git a/gcc/testsuite/gnat.dg/strub_access1.adb b/gcc/testsuite/gnat.dg/strub_access1.adb
new file mode 100644
index 0000000000000..dae4706016436
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_access1.adb
@@ -0,0 +1,16 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=relaxed" }
+
+--  Check that we reject 'Access of a strub variable whose type does
+--  not carry a strub modifier.
+
+procedure Strub_Access1 is
+   X : aliased Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+
+   function F (P : access Integer) return Integer is (P.all);
+   
+begin
+   X := F (X'Unchecked_access); -- OK.
+   X := F (X'Access); -- { dg-error "target access type drops .strub. mode" }
+end Strub_Access1;
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
new file mode 100644
index 0000000000000..10445d7cf8451
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -0,0 +1,37 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" }
+
+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 (F (X));
+   --  function G return Integer is (FP (X));
+   --  Calling G would likely raise an exception, because although FP
+   --  carries the strub at-calls attribute needed to call F, the
+   --  attribute is dropped from the type used for the call proper.
+end Strub_Attr;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 0 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark_ptr" 6 "strub" } }
+--  We have 1 at-calls subprogram (F) and 2 wrapped (P and G).
+--  For each of them, there's one match for the wrapped signature, 
+--  and one for the update call.
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 27 "strub" } }
+--  The 6 matches above, plus:
+--  5*2: wm var decl, enter, call, leave and clobber for each wrapper;
+--  2*1: an extra leave and clobber for the exception paths in the wrappers.
+--  7*1: for the F call in G, including EH path.
diff --git a/gcc/testsuite/gnat.dg/strub_attr.ads b/gcc/testsuite/gnat.dg/strub_attr.ads
new file mode 100644
index 0000000000000..a94c23bf41833
--- /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/testsuite/gnat.dg/strub_disp.adb b/gcc/testsuite/gnat.dg/strub_disp.adb
new file mode 100644
index 0000000000000..3dbcc4a357cba
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_disp.adb
@@ -0,0 +1,64 @@
+--  { dg-do compile }
+
+procedure Strub_Disp is
+   package Foo is
+      type A is tagged null record;
+
+      procedure P (I : Integer; X : A);
+      pragma Machine_Attribute (P, "strub", "at-calls");
+      
+      function F (X : access A) return Integer;
+
+      type B is new A with null record;
+      
+      overriding
+      procedure P (I : Integer; X : B); -- { dg-error "requires the same .strub. mode" }
+
+      overriding
+      function F (X : access B) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+
+   end Foo;
+
+   package body Foo is
+      procedure P (I : Integer; X : A) is
+      begin
+	 null;
+      end;
+      
+      function F (X : access A) return Integer is (0);
+
+      overriding
+      procedure P (I : Integer; X : B) is
+      begin
+	 P (I, A (X));
+      end;
+      
+      overriding
+      function F (X : access B) return Integer is (1);
+   end Foo;
+
+   use Foo;
+
+   procedure Q (X : A'Class) is
+   begin
+      P (-1, X);
+   end;
+
+   XA : aliased A;
+   XB : aliased B;
+   I : Integer := 0;
+   XC : access A'Class;
+begin
+   Q (XA);
+   Q (XB);
+   
+   I := I + F (XA'Access);
+   I := I + F (XB'Access);
+
+   XC := XA'Access;
+   I := I + F (XC);
+
+   XC := XB'Access;
+   I := I + F (XC);
+end Strub_Disp;
diff --git a/gcc/testsuite/gnat.dg/strub_disp1.adb b/gcc/testsuite/gnat.dg/strub_disp1.adb
new file mode 100644
index 0000000000000..09756a74b7d81
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_disp1.adb
@@ -0,0 +1,79 @@
+--  { dg-do compile }
+--  { dg-options "-fdump-ipa-strub" }
+
+-- Check that at-calls dispatching calls are transformed.
+
+procedure Strub_Disp1 is
+   package Foo is
+      type A is tagged null record;
+
+      procedure P (I : Integer; X : A);
+      pragma Machine_Attribute (P, "strub", "at-calls");
+      
+      function F (X : access A) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+      type B is new A with null record;
+      
+      overriding
+      procedure P (I : Integer; X : B);
+      pragma Machine_Attribute (P, "strub", "at-calls");
+
+      overriding
+      function F (X : access B) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+   end Foo;
+
+   package body Foo is
+      procedure P (I : Integer; X : A) is
+      begin
+	 null;
+      end;
+      
+      function F (X : access A) return Integer is (0);
+
+      overriding
+      procedure P (I : Integer; X : B) is
+      begin
+	 P (I, A (X)); -- strub-at-calls non-dispatching call
+      end;
+      
+      overriding
+      function F (X : access B) return Integer is (1);
+   end Foo;
+
+   use Foo;
+
+   procedure Q (X : A'Class) is
+   begin
+      P (-1, X); -- strub-at-calls dispatching call.
+   end;
+
+   XA : aliased A;
+   XB : aliased B;
+   I : Integer := 0;
+   XC : access A'Class;
+begin
+   Q (XA);
+   Q (XB);
+   
+   I := I + F (XA'Access); -- strub-at-calls non-dispatching call
+   I := I + F (XB'Access); -- strub-at-calls non-dispatching call
+
+   XC := XA'Access;
+   I := I + F (XC); -- strub-at-calls dispatching call.
+
+   XC := XB'Access;
+   I := I + F (XC); -- strub-at-calls dispatching call.
+end Strub_Disp1;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 4 "strub" } }
+
+--  Count the strub-at-calls non-dispatching calls 
+--  (+ 2 each, for the matching prototypes)
+--  { dg-final { scan-ipa-dump-times "foo\.p \[(\]\[^\n\]*watermark" 3 "strub" } }
+--  { dg-final { scan-ipa-dump-times "foo\.f \[(\]\[^\n\]*watermark" 4 "strub" } }
+
+--  Count the strub-at-calls dispatching calls.
+--  { dg-final { scan-ipa-dump-times "_\[0-9\]* \[(\]\[^\n\]*watermark" 3 "strub" } }
diff --git a/gcc/testsuite/gnat.dg/strub_ind.adb b/gcc/testsuite/gnat.dg/strub_ind.adb
new file mode 100644
index 0000000000000..da56acaa957d2
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind.adb
@@ -0,0 +1,33 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict" }
+
+--  This is essentially the same test as strub_attr.adb, 
+--  but applying attributes to access types as well.
+--  That doesn't quite work yet, so we get an error we shouldn't get.
+
+package body Strub_Ind is
+   E : exception;
+
+   function G return Integer;
+
+   procedure P (X : Integer) is
+   begin
+      raise E;
+   end;
+   
+   function F (X : Integer) return Integer is
+   begin
+      return X * X;
+   end;
+   
+   function G return Integer is (FP (X));
+
+   type GT is access function return Integer;
+
+   type GT_SAC is access function return Integer;
+   pragma Machine_Attribute (GT_SAC, "strub", "at-calls");
+
+   GP : GT_SAC := GT_SAC (GT'(G'Access)); -- { dg-error "incompatible" }
+   -- pragma Machine_Attribute (GP, "strub", "at-calls");
+
+end Strub_Ind;
diff --git a/gcc/testsuite/gnat.dg/strub_ind.ads b/gcc/testsuite/gnat.dg/strub_ind.ads
new file mode 100644
index 0000000000000..99a65fc24b1ec
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind.ads
@@ -0,0 +1,17 @@
+package Strub_Ind is
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "internal");
+
+   function F (X : Integer) return Integer;
+   pragma Machine_Attribute (F, "strub");
+
+   X : Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+
+   type FT is access function (X : Integer) return Integer;
+   pragma Machine_Attribute (FT, "strub", "at-calls");
+
+   FP : FT := F'Access;
+   -- pragma Machine_Attribute (FP, "strub", "at-calls"); -- not needed
+
+end Strub_Ind;
diff --git a/gcc/testsuite/gnat.dg/strub_ind1.adb b/gcc/testsuite/gnat.dg/strub_ind1.adb
new file mode 100644
index 0000000000000..825e395e6819c
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind1.adb
@@ -0,0 +1,41 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
+
+--  This is essentially the same test as strub_attr.adb, 
+--  but with an explicit conversion.
+
+package body Strub_Ind1 is
+   E : exception;
+
+   type Strub_Int is New Integer;
+   pragma Machine_Attribute (Strub_Int, "strub");
+
+   function G return Integer;
+   pragma Machine_Attribute (G, "strub", "disabled");
+
+   procedure P (X : Integer) is
+   begin
+      raise E;
+   end;
+   
+   function G return Integer is (FP (X));
+
+   type GT is access function return Integer;
+   pragma Machine_Attribute (GT, "strub", "disabled");
+
+   type GT_SC is access function return Integer;
+   pragma Machine_Attribute (GT_SC, "strub", "callable");
+
+   GP : GT_SC := GT_SC (GT'(G'Access));
+   --  pragma Machine_Attribute (GP, "strub", "callable"); -- not needed.
+
+   function F (X : Integer) return Integer is
+   begin
+      return X * GP.all;
+   end;
+   
+end Strub_Ind1;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]disabled\[)\]\[)\]" 1 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 1 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
diff --git a/gcc/testsuite/gnat.dg/strub_ind1.ads b/gcc/testsuite/gnat.dg/strub_ind1.ads
new file mode 100644
index 0000000000000..d3f1273b3a6b9
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind1.ads
@@ -0,0 +1,17 @@
+package Strub_Ind1 is
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "internal");
+
+   function F (X : Integer) return Integer;
+   pragma Machine_Attribute (F, "strub");
+
+   X : aliased Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+
+   type FT is access function (X : Integer) return Integer;
+   pragma Machine_Attribute (FT, "strub", "at-calls");
+
+   FP : FT := F'Access;
+   pragma Machine_Attribute (FP, "strub", "at-calls");
+
+end Strub_Ind1;
diff --git a/gcc/testsuite/gnat.dg/strub_ind2.adb b/gcc/testsuite/gnat.dg/strub_ind2.adb
new file mode 100644
index 0000000000000..e918b39263117
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind2.adb
@@ -0,0 +1,34 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict" }
+
+--  This is essentially the same test as strub_attr.adb, 
+--  but with an explicit conversion.
+
+package body Strub_Ind2 is
+   E : exception;
+
+   function G return Integer;
+   pragma Machine_Attribute (G, "strub", "callable");
+
+   procedure P (X : Integer) is
+   begin
+      raise E;
+   end;
+   
+   function G return Integer is (FP (X));
+
+   type GT is access function return Integer;
+   pragma Machine_Attribute (GT, "strub", "callable");
+
+   type GT_SD is access function return Integer;
+   pragma Machine_Attribute (GT_SD, "strub", "disabled");
+
+   GP : GT_SD := GT_SD (GT'(G'Access));
+   --  pragma Machine_Attribute (GP, "strub", "disabled"); -- not needed.
+
+   function F (X : Integer) return Integer is
+   begin
+      return X * GP.all; --  { dg-error "using non-.strub. type" }
+   end;
+   
+end Strub_Ind2;
diff --git a/gcc/testsuite/gnat.dg/strub_ind2.ads b/gcc/testsuite/gnat.dg/strub_ind2.ads
new file mode 100644
index 0000000000000..e13865ec49c38
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind2.ads
@@ -0,0 +1,17 @@
+package Strub_Ind2 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");
+
+   type FT is access function (X : Integer) return Integer;
+   pragma Machine_Attribute (FT, "strub", "at-calls");
+
+   FP : FT := F'Access;
+   pragma Machine_Attribute (FP, "strub", "at-calls");
+
+end Strub_Ind2;
diff --git a/gcc/testsuite/gnat.dg/strub_intf.adb b/gcc/testsuite/gnat.dg/strub_intf.adb
new file mode 100644
index 0000000000000..8f0212a75866f
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_intf.adb
@@ -0,0 +1,93 @@
+--  { dg-do compile }
+
+--  Check that strub mode mismatches between overrider and overridden
+--  subprograms are reported.
+
+procedure Strub_Intf is
+   package Foo is
+      type TP is interface;
+      procedure P (I : Integer; X : TP) is abstract;
+      pragma Machine_Attribute (P, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+
+      type TF is interface;
+      function F (X : access TF) return Integer is abstract;
+
+      type TX is interface;
+      procedure P (I : Integer; X : TX) is abstract;
+
+      type TI is interface and TP and TF and TX;
+      --  When we freeze TI, we detect the mismatch between the
+      --  inherited P and another parent's P.  Because TP appears
+      --  before TX, we inherit P from TP, and report the mismatch at
+      --  the pragma inherited from TP against TX's P.  In contrast,
+      --  when we freeze TII below, since TX appears before TP, we
+      --  report the error at the line in which the inherited
+      --  subprogram is synthesized, namely the line below, against
+      --  the line of the pragma.
+
+      type TII is interface and TX and TP and TF; -- { dg-error "requires the same .strub. mode" }
+
+      function F (X : access TI) return Integer is abstract;
+      pragma Machine_Attribute (F, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+
+      type A is new TI with null record;
+
+      procedure P (I : Integer; X : A);
+      pragma Machine_Attribute (P, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+      
+      function F (X : access A) return Integer; -- { dg-error "requires the same .strub. mode" }
+
+      type B is new TI with null record;
+      
+      overriding
+      procedure P (I : Integer; X : B); -- { dg-error "requires the same .strub. mode" }
+
+      overriding
+      function F (X : access B) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+
+   end Foo;
+
+   package body Foo is
+      procedure P (I : Integer; X : A) is
+      begin
+	 null;
+      end;
+      
+      function F (X : access A) return Integer is (0);
+
+      overriding
+      procedure P (I : Integer; X : B) is
+      begin
+	 null;
+      end;
+      
+      overriding
+      function F (X : access B) return Integer is (1);
+
+   end Foo;
+
+   use Foo;
+
+   procedure Q (X : TX'Class) is
+   begin
+      P (-1, X);
+   end;
+
+   XA : aliased A;
+   XB : aliased B;
+   I : Integer := 0;
+   XC : access TI'Class;
+begin
+   Q (XA);
+   Q (XB);
+   
+   I := I + F (XA'Access);
+   I := I + F (XB'Access);
+
+   XC := XA'Access;
+   I := I + F (XC);
+
+   XC := XB'Access;
+   I := I + F (XC);
+end Strub_Intf;
diff --git a/gcc/testsuite/gnat.dg/strub_intf1.adb b/gcc/testsuite/gnat.dg/strub_intf1.adb
new file mode 100644
index 0000000000000..bf77321cef790
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_intf1.adb
@@ -0,0 +1,86 @@
+--  { dg-do compile }
+--  { dg-options "-fdump-ipa-strub" }
+
+-- Check that at-calls dispatching calls to interfaces are transformed.
+
+procedure Strub_Intf1 is
+   package Foo is
+      type TX is Interface;
+      procedure P (I : Integer; X : TX) is abstract;
+      pragma Machine_Attribute (P, "strub", "at-calls");
+      function F (X : access TX) return Integer is abstract;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+      type A is new TX with null record;
+
+      procedure P (I : Integer; X : A);
+      pragma Machine_Attribute (P, "strub", "at-calls");
+      
+      function F (X : access A) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+      type B is new TX with null record;
+      
+      overriding
+      procedure P (I : Integer; X : B);
+      pragma Machine_Attribute (P, "strub", "at-calls");
+
+      overriding
+      function F (X : access B) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+   end Foo;
+
+   package body Foo is
+      procedure P (I : Integer; X : A) is
+      begin
+	 null;
+      end;
+      
+      function F (X : access A) return Integer is (0);
+
+      overriding
+      procedure P (I : Integer; X : B) is
+      begin
+	 null;
+      end;
+      
+      overriding
+      function F (X : access B) return Integer is (1);
+
+   end Foo;
+
+   use Foo;
+
+   procedure Q (X : TX'Class) is
+   begin
+      P (-1, X);
+   end;
+
+   XA : aliased A;
+   XB : aliased B;
+   I : Integer := 0;
+   XC : access TX'Class;
+begin
+   Q (XA);
+   Q (XB);
+   
+   I := I + F (XA'Access);
+   I := I + F (XB'Access);
+
+   XC := XA'Access;
+   I := I + F (XC);
+
+   XC := XB'Access;
+   I := I + F (XC);
+end Strub_Intf1;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 4 "strub" } }
+
+--  Count the strub-at-calls non-dispatching calls 
+--  (+ 2 each, for the matching prototypes)
+--  { dg-final { scan-ipa-dump-times "foo\.p \[(\]\[^\n\]*watermark" 2 "strub" } }
+--  { dg-final { scan-ipa-dump-times "foo\.f \[(\]\[^\n\]*watermark" 4 "strub" } }
+
+--  Count the strub-at-calls dispatching calls.
+--  { dg-final { scan-ipa-dump-times "_\[0-9\]* \[(\]\[^\n\]*watermark" 3 "strub" } }
diff --git a/gcc/testsuite/gnat.dg/strub_intf2.adb b/gcc/testsuite/gnat.dg/strub_intf2.adb
new file mode 100644
index 0000000000000..e8880dbc43730
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_intf2.adb
@@ -0,0 +1,55 @@
+--  { dg-do compile }
+
+--  Check that strub mode mismatches between overrider and overridden
+--  subprograms are reported even when the overriders for an
+--  interface's subprograms are inherited from a type that is not a
+--  descendent of the interface.
+
+procedure Strub_Intf2 is
+   package Foo is
+      type A is tagged null record;
+
+      procedure P (I : Integer; X : A);
+      pragma Machine_Attribute (P, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+      
+      function F (X : access A) return Integer;
+
+      type TX is Interface;
+
+      procedure P (I : Integer; X : TX) is abstract; 
+
+      function F (X : access TX) return Integer is abstract;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+      type B is new A and TX with null record; -- { dg-error "requires the same .strub. mode" }
+
+   end Foo;
+
+   package body Foo is
+      procedure P (I : Integer; X : A) is
+      begin
+	 null;
+      end;
+      
+      function F (X : access A) return Integer is (0);
+
+   end Foo;
+
+   use Foo;
+
+   procedure Q (X : TX'Class) is
+   begin
+      P (-1, X);
+   end;
+
+   XB : aliased B;
+   I : Integer := 0;
+   XC : access TX'Class;
+begin
+   Q (XB);
+   
+   I := I + F (XB'Access);
+
+   XC := XB'Access;
+   I := I + F (XC);
+end Strub_Intf2;
diff --git a/gcc/testsuite/gnat.dg/strub_renm.adb b/gcc/testsuite/gnat.dg/strub_renm.adb
new file mode 100644
index 0000000000000..217367e712d82
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_renm.adb
@@ -0,0 +1,21 @@
+--  { dg-do compile }
+
+procedure Strub_Renm is
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "at-calls");
+
+   function F return Integer;
+   pragma Machine_Attribute (F, "strub", "internal");
+
+   procedure Q (X : Integer) renames P; -- { dg-error "requires the same .strub. mode" }
+
+   function G return Integer renames F;
+   pragma Machine_Attribute (G, "strub", "callable"); -- { dg-error "requires the same .strub. mode" }
+
+   procedure P (X : Integer) is null;
+   function F return Integer is (0);
+
+begin
+   P (F);
+   Q (G);
+end Strub_Renm;
diff --git a/gcc/testsuite/gnat.dg/strub_renm1.adb b/gcc/testsuite/gnat.dg/strub_renm1.adb
new file mode 100644
index 0000000000000..a11adbfb5a9d6
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_renm1.adb
@@ -0,0 +1,32 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=relaxed -fdump-ipa-strub" }
+
+procedure Strub_Renm1 is
+   V : Integer := 0;
+   pragma Machine_Attribute (V, "strub");
+
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "at-calls");
+
+   function F return Integer;
+
+   procedure Q (X : Integer) renames P;
+   pragma Machine_Attribute (Q, "strub", "at-calls");
+
+   function G return Integer renames F;
+   pragma Machine_Attribute (G, "strub", "internal");
+
+   procedure P (X : Integer) is null;
+   function F return Integer is (0);
+
+begin
+   P (F);
+   Q (G);
+end Strub_Renm1;
+
+--  This is for P; Q is an alias.
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 1 "strub" } }
+
+--  This is *not* for G, but for Strub_Renm1.
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]wrapped\[)\]\[)\]" 1 "strub" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]wrapper\[)\]\[)\]" 1 "strub" } }
diff --git a/gcc/testsuite/gnat.dg/strub_renm2.adb b/gcc/testsuite/gnat.dg/strub_renm2.adb
new file mode 100644
index 0000000000000..c488c20826fdb
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_renm2.adb
@@ -0,0 +1,32 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strub" }
+
+procedure Strub_Renm2 is
+   V : Integer := 0;
+   pragma Machine_Attribute (V, "strub");
+
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "at-calls");
+
+   function F return Integer;
+
+   procedure Q (X : Integer) renames P;
+   pragma Machine_Attribute (Q, "strub", "at-calls");
+
+   type T is access function return Integer;
+
+   type TC is access function return Integer;
+   pragma Machine_Attribute (TC, "strub", "callable");
+
+   FCptr : constant TC := TC (T'(F'Access));
+
+   function G return Integer renames FCptr.all;
+   pragma Machine_Attribute (G, "strub", "callable");
+
+   procedure P (X : Integer) is null;
+   function F return Integer is (0);
+
+begin
+   P (F);  -- { dg-error "calling non-.strub." }
+   Q (G);  -- ok, G is callable.
+end Strub_Renm2;
diff --git a/gcc/testsuite/gnat.dg/strub_var.adb b/gcc/testsuite/gnat.dg/strub_var.adb
new file mode 100644
index 0000000000000..3d158de28031f
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_var.adb
@@ -0,0 +1,16 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
+
+-- We don't read from the automatic variable, but being an automatic
+--  variable, its presence should be enough for the procedure to get
+--  strub enabled.
+
+with Strub_Attr;
+procedure Strub_Var is
+   X : Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+begin
+   X := Strub_Attr.F (0);
+end Strub_Var;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 1 "strubm" } }
diff --git a/gcc/testsuite/gnat.dg/strub_var1.adb b/gcc/testsuite/gnat.dg/strub_var1.adb
new file mode 100644
index 0000000000000..6a504e09198b6
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_var1.adb
@@ -0,0 +1,20 @@
+--  { dg-do compile }
+
+with Strub_Attr;
+procedure Strub_Var1 is
+   type TA  -- { dg-warning "does not apply to elements" }
+      is array (1..2) of Integer;
+   pragma Machine_Attribute (TA, "strub");
+   
+   A : TA := (0, 0);  -- { dg-warning "does not apply to elements" }
+   
+   type TR is record  -- { dg-warning "does not apply to fields" }
+      M, N : Integer;
+   end record;
+   pragma Machine_Attribute (TR, "strub");
+   
+   R : TR := (0, 0);
+
+begin
+   A(2) := Strub_Attr.F (A(1));
+end Strub_Var1;
diff --git a/gcc/tree-cfg.cc b/gcc/tree-cfg.cc
index ffeb20b717aea..f748ca8508940 100644
--- a/gcc/tree-cfg.cc
+++ b/gcc/tree-cfg.cc
@@ -5774,6 +5774,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 09e6ada5b2f91..36f955a0b37ee 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -510,8 +510,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.cc b/gcc/tree-ssa-ccp.cc
index 1a555ae682638..03ff88afadddd 100644
--- a/gcc/tree-ssa-ccp.cc
+++ b/gcc/tree-ssa-ccp.cc
@@ -3073,7 +3073,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 8dedd10f79a30..d8163c5af9903 100644
--- a/libgcc/Makefile.in
+++ b/libgcc/Makefile.in
@@ -433,6 +433,9 @@ LIB2ADD += enable-execute-stack.c
 # Control Flow Redundancy hardening out-of-line checker.
 LIB2ADD += $(srcdir)/hardcfr.c
 
+# Stack scrubbing infrastructure.
+LIB2ADD += $(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
 # (e.g. *-*-darwin*) only one copy of it is linked.
diff --git a/libgcc/libgcc2.h b/libgcc/libgcc2.h
index 06e26cad884b4..0b97eec472586 100644
--- a/libgcc/libgcc2.h
+++ b/libgcc/libgcc2.h
@@ -551,6 +551,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 0000000000000..2a2b930b6039d
--- /dev/null
+++ b/libgcc/strub.c
@@ -0,0 +1,149 @@
+/* Stack scrubbing infrastructure
+   Copyright (C) 2021-2022 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__ ("callable")))
+
+/* 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;
+}
+
+#if TARGET_STRUB_USE_DYNAMIC_ARRAY && ! defined TARGET_STRUB_MAY_USE_MEMSET
+# define TARGET_STRUB_MAY_USE_MEMSET 1
+#endif
+
+#if defined __x86_64__ && __OPTIMIZE__
+# define TARGET_STRUB_DISABLE_RED_ZONE \
+  /* __attribute__ ((__target__ ("no-red-zone"))) // not needed when optimizing */
+#elif !defined RED_ZONE_SIZE || defined __i386__
+# define TARGET_STRUB_DISABLE_RED_ZONE
+#endif
+
+#ifndef TARGET_STRUB_DISABLE_RED_ZONE
+/* Dummy function, called to force the caller to not be a leaf function, so
+   that it can't use the red zone.  */
+static void ATTRIBUTE_STRUB_CALLABLE
+__attribute__ ((__noinline__, __noipa__))
+__strub_dummy_force_no_leaf (void)
+{
+}
+#endif
+
+/* Leave a stack scrubbing context, clearing the stack between its top and
+   *MARK.  */
+void ATTRIBUTE_STRUB_CALLABLE
+#if ! TARGET_STRUB_MAY_USE_MEMSET
+__attribute__ ((__optimize__ ("-fno-tree-loop-distribute-patterns")))
+#endif
+#ifdef TARGET_STRUB_DISABLE_RED_ZONE
+TARGET_STRUB_DISABLE_RED_ZONE
+#endif
+__strub_leave (void **mark)
+{
+  void *sp = __builtin_stack_address ();
+
+  void **base, **end;
+#ifndef STACK_GROWS_DOWNWARD
+  base = sp; /* ??? Do we need an offset here?  */
+  end = *mark;
+#else
+  base = *mark;
+  end = sp; /* ??? Does any platform require an offset here?  */
+#endif
+
+  if (! (base < end))
+    return;
+
+#if TARGET_STRUB_USE_DYNAMIC_ARRAY
+  /* Compute the length without assuming the pointers are both sufficiently
+     aligned.  They should be, but pointer differences expected to be exact may
+     yield unexpected results when the assumption doesn't hold.  Given the
+     potential security implications, compute the length without that
+     expectation.  If the pointers are misaligned, we may leave a partial
+     unscrubbed word behind.  */
+  ptrdiff_t len = ((char *)end - (char *)base) / sizeof (void *);
+  /* 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];
+#elifndef TARGET_STRUB_DISABLE_RED_ZONE
+  /* Prevent the use of the red zone, by making this function non-leaf through
+     an unreachable call that, because of the asm stmt, the compiler will
+     consider reachable.  */
+  asm goto ("" : : : : no_leaf);
+  if (0)
+    {
+    no_leaf:
+      __strub_dummy_force_no_leaf ();
+      return;
+    }
+#endif
+
+  /* ldist may turn these loops into a memset (thus the conditional
+     -fno-tree-loop-distribute-patterns above).  Without the dynamic array
+     above, that call would likely be unsafe: possibly tail-called, and likely
+     scribbling over its own stack frame.  */
+#ifndef STACK_GROWS_DOWNWARD
+  do
+    *base++ = 0;
+  while (base < end);
+  /* Make sure the stack overwrites are not optimized away.  */
+  asm ("" : : "m" (end[0]));
+#else
+  do
+    *--end = 0;
+  while (base < end);
+  /* Make sure the stack overwrites are not optimized away.  */
+  asm ("" : : "m" (base[0]));
+#endif
+}


-- 
Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
   Free Software Activist                   GNU Toolchain Engineer
More tolerance and less prejudice are key for inclusion and diversity
Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH v4] Introduce strub: machine-independent stack scrubbing
  2023-10-20  6:03       ` [PATCH v4] " Alexandre Oliva
@ 2023-10-26  6:15         ` Alexandre Oliva
  2023-11-20 12:40           ` Alexandre Oliva
  0 siblings, 1 reply; 59+ messages in thread
From: Alexandre Oliva @ 2023-10-26  6:15 UTC (permalink / raw)
  To: gcc-patches
  Cc: Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Richard Biener, Jim Wilson, Jeff Law

Ping? https://gcc.gnu.org/pipermail/gcc-patches/2023-October/633675.html

I'm combining the gcc/ipa-strub.cc bits from
https://gcc.gnu.org/pipermail/gcc-patches/2023-October/633526.html

-- 
Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
   Free Software Activist                   GNU Toolchain Engineer
More tolerance and less prejudice are key for inclusion and diversity
Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH v4] Introduce strub: machine-independent stack scrubbing
  2023-10-26  6:15         ` Alexandre Oliva
@ 2023-11-20 12:40           ` Alexandre Oliva
  2023-11-22 14:14             ` Richard Biener
  0 siblings, 1 reply; 59+ messages in thread
From: Alexandre Oliva @ 2023-11-20 12:40 UTC (permalink / raw)
  To: gcc-patches
  Cc: Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Richard Biener, Jim Wilson, Jeff Law

On Oct 26, 2023, Alexandre Oliva <oliva@adacore.com> wrote:

>> This is a refreshed and improved version of the version posted back in
>> June.  https://gcc.gnu.org/pipermail/gcc-patches/2023-June/621936.html

> Ping? https://gcc.gnu.org/pipermail/gcc-patches/2023-October/633675.html
> I'm combining the gcc/ipa-strub.cc bits from
> https://gcc.gnu.org/pipermail/gcc-patches/2023-October/633526.html

Ping?
Retested on x86_64-linux-gnu, with and without -fstrub=all.

>> This patch adds the strub attribute for function and variable types,
>> command-line options, passes and adjustments to implement it,
>> documentation, and tests.

>> Stack scrubbing is implemented in a machine-independent way: functions
...

-- 
Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
   Free Software Activist                   GNU Toolchain Engineer
More tolerance and less prejudice are key for inclusion and diversity
Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH v4] Introduce strub: machine-independent stack scrubbing
  2023-11-20 12:40           ` Alexandre Oliva
@ 2023-11-22 14:14             ` Richard Biener
  2023-11-23 10:56               ` Alexandre Oliva
  0 siblings, 1 reply; 59+ messages in thread
From: Richard Biener @ 2023-11-22 14:14 UTC (permalink / raw)
  To: Alexandre Oliva
  Cc: gcc-patches, Jeremy Bennett, Craig Blackmore, Graham Markall,
	Martin Jambor, Jan Hubicka, Jim Wilson, Jeff Law, Jakub Jelinek

On Mon, Nov 20, 2023 at 1:40 PM Alexandre Oliva <oliva@adacore.com> wrote:
>
> On Oct 26, 2023, Alexandre Oliva <oliva@adacore.com> wrote:
>
> >> This is a refreshed and improved version of the version posted back in
> >> June.  https://gcc.gnu.org/pipermail/gcc-patches/2023-June/621936.html
>
> > Ping? https://gcc.gnu.org/pipermail/gcc-patches/2023-October/633675.html
> > I'm combining the gcc/ipa-strub.cc bits from
> > https://gcc.gnu.org/pipermail/gcc-patches/2023-October/633526.html
>
> Ping?
> Retested on x86_64-linux-gnu, with and without -fstrub=all.

@@ -898,7 +899,24 @@ decl_attributes (tree *node, tree attributes, int flags,
       TYPE_NAME (tt) = *node;
     }

-  *anode = cur_and_last_decl[0];
+  if (*anode != cur_and_last_decl[0])
+    {
+      /* Even if !spec->function_type_required, allow the attribute
+ handler to request the attribute to be applied to the function
+ type, rather than to the function pointer type, by setting
+ cur_and_last_decl[0] to the function type.  */
+      if (!fn_ptr_tmp
+  && POINTER_TYPE_P (*anode)
+  && TREE_TYPE (*anode) == cur_and_last_decl[0]
+  && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
+ {
+  fn_ptr_tmp = TREE_TYPE (*anode);
+  fn_ptr_quals = TYPE_QUALS (*anode);
+  anode = &fn_ptr_tmp;
+ }
+      *anode = cur_and_last_decl[0];
+    }
+

what is this a workaround for?  Isn't there a suitable parsing position
for placing the attribute?

+#ifndef STACK_GROWS_DOWNWARD
+# define STACK_TOPS GT
+#else
+# define STACK_TOPS LT
+#endif

according to docs this is defined to 0 or 1 so the above looks wrong
(it's always defined).

+  if (optimize < 2 || optimize_size || flag_no_inline)
+    return NULL_RTX;

I'm wondering about these checks in the expansions of the builtins,
I think this is about inline expanding or emitting a libcall, right?
I wonder if you should use optimize_function_for_speed (cfun) instead?
Usually -fno-inline shouldn't affect such calls, but -fno-builtin-FOO would.
I have no strong opinion here though.

The new builtins seem undocumented - usually those are documented
within extend.texi - I guess placing __builtin___strub_enter calls in
the code manually will break in interesting ways - if that's not supposed
to happen the trick is to embed a space in the name of the built-in.
__builtin_stack_address looks like something users will pick up though
(and thus should be documented)?

-symtab_node::reset (void)
+symtab_node::reset (bool preserve_comdat_group)

not sure what for, I'll leave Honza to comment.

+/* Create a distinct copy of the type of NODE's function, and change
+   the fntype of all calls to it with the same main type to the new
+   type.  */
+
+static void
+distinctify_node_type (cgraph_node *node)
+{
+  tree old_type = TREE_TYPE (node->decl);
+  tree new_type = build_distinct_type_copy (old_type);
+  tree new_ptr_type = NULL_TREE;
+
+  /* Remap any calls to node->decl that use old_type, or a variant
+     thereof, to new_type as well.  We don't look for aliases, their
+     declarations will have their types changed independently, and
+     we'll adjust their fntypes then.  */
+  for (cgraph_edge *e = node->callers; e; e = e->next_caller)
+    {
+      if (!e->call_stmt)
+ continue;
+      tree fnaddr = gimple_call_fn (e->call_stmt);
+      gcc_checking_assert (TREE_CODE (fnaddr) == ADDR_EXPR
+   && TREE_OPERAND (fnaddr, 0) == node->decl);
+      if (strub_call_fntype_override_p (e->call_stmt))
+ continue;
+      if (!new_ptr_type)
+ new_ptr_type = build_pointer_type (new_type);
+      TREE_TYPE (fnaddr) = new_ptr_type;
+      gimple_call_set_fntype (e->call_stmt, new_type);
+    }
+
+  TREE_TYPE (node->decl) = new_type;

it does feel like there's IPA mechanisms to deal with what you are trying to do
here (or in the caller(s)).


+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 ();

if  (flag_checking) verify_strub ();

please.  I guess we talked about this last year - what's the reason to have both
an IPA pass and a simple IPA pass?  IIRC the simple IPA pass is a simple
one because it wants to see inlined bodies and "fixes" those up?  Some toplevel
comments explaining both passes in the ipa-strub.cc pass would be nice to
have.  I guess I also asked before - did you try it with -flto?

+    /* 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);
...

IMHO it's a bad idea, at least initially, to mess with the ABI in a heuristical
way this way.  I see you need all sorts of "fixup", including re-gimplification
of stmts, etc., double-ugh.  Ideally IPA-SRA can already do parts of those
transforms which would mean ipa-param-manipulation has generic code
to encode them?  I realize you can't tail-call, but I wonder if some clever
machine-specific tricks would work, I guess it would need the wrapper
to be target generated of course.

As it's only for optimization, how much of the code would go away when
you avoid changing the parameters of the wrapped function?  Could
one easily disable the optimization via a --param maybe?

There's still much commented code and FIXMEs in, a lot of cgraph
related code is used which I can't review very well.

I fear that -fstrub is going to be actually used by people - are you up
to the task fixing things?

+void ATTRIBUTE_STRUB_CALLABLE
+__strub_enter (void **watermark)
+{
+  *watermark = __builtin_frame_address (0);

in your experience, do these (or the inline version) work reliably
when optimizations like omitting the frame pointer or shrink-wrapping
happen from a security point of view?

You are adding symbols to libgcc but I don't see you amending
libgcc/libgcc-std.ver.in?

Neither the documentation in extend.texi nor the one in invoke.texi
mentions the target audience of -fstrub - I suppose it is to ensure
secrets are erased from the stack in case they are stored into locals
or spilled and thus it complements -fzero-call-used-regs.  The patches
mention the red zone, documenting the guarantees and caveats
would be nice to have.  I think the documentation belongs to the
attribute documentation.

I hope Honza can have a quick look over the IPA passes.  As said,
eliding the parmeter optimization from this patch, possibly
implementing missing pieces in ipa-param-manipulation might be
a better way than taking what is percieved as a huge ugly part
of the patch.

Thanks,
Richard.

> >> This patch adds the strub attribute for function and variable types,
> >> command-line options, passes and adjustments to implement it,
> >> documentation, and tests.
>
> >> Stack scrubbing is implemented in a machine-independent way: functions
> ...
>
> --
> Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
>    Free Software Activist                   GNU Toolchain Engineer
> More tolerance and less prejudice are key for inclusion and diversity
> Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH v4] Introduce strub: machine-independent stack scrubbing
  2023-11-22 14:14             ` Richard Biener
@ 2023-11-23 10:56               ` Alexandre Oliva
  2023-11-23 12:05                 ` Richard Biener
  0 siblings, 1 reply; 59+ messages in thread
From: Alexandre Oliva @ 2023-11-23 10:56 UTC (permalink / raw)
  To: Richard Biener
  Cc: gcc-patches, Jeremy Bennett, Craig Blackmore, Graham Markall,
	Martin Jambor, Jan Hubicka, Jim Wilson, Jeff Law, Jakub Jelinek

Hello, Richi,

Thanks for the extensive review!

On Nov 22, 2023, Richard Biener <richard.guenther@gmail.com> wrote:

> On Mon, Nov 20, 2023 at 1:40 PM Alexandre Oliva <oliva@adacore.com> wrote:
>> 
>> On Oct 26, 2023, Alexandre Oliva <oliva@adacore.com> wrote:
>> 
>> >> This is a refreshed and improved version of the version posted back in
>> >> June.  https://gcc.gnu.org/pipermail/gcc-patches/2023-June/621936.html
>> 
>> > Ping? https://gcc.gnu.org/pipermail/gcc-patches/2023-October/633675.html
>> > I'm combining the gcc/ipa-strub.cc bits from
>> > https://gcc.gnu.org/pipermail/gcc-patches/2023-October/633526.html
>> 
>> Ping?
>> Retested on x86_64-linux-gnu, with and without -fstrub=all.

> @@ -898,7 +899,24 @@ decl_attributes (tree *node, tree attributes, int flags,
>        TYPE_NAME (tt) = *node;
>      }

> -  *anode = cur_and_last_decl[0];
> +  if (*anode != cur_and_last_decl[0])
> +    {
> +      /* Even if !spec->function_type_required, allow the attribute
> + handler to request the attribute to be applied to the function
> + type, rather than to the function pointer type, by setting
> + cur_and_last_decl[0] to the function type.  */
> +      if (!fn_ptr_tmp
> +  && POINTER_TYPE_P (*anode)
> +  && TREE_TYPE (*anode) == cur_and_last_decl[0]
> +  && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
> + {
> +  fn_ptr_tmp = TREE_TYPE (*anode);
> +  fn_ptr_quals = TYPE_QUALS (*anode);
> +  anode = &fn_ptr_tmp;
> + }
> +      *anode = cur_and_last_decl[0];
> +    }
> +

> what is this a workaround for?

For the fact that the strub attribute attaches to types, whether data or
function types, so we can't have fn_type_req, but when it's a function
or pointer-to-function type, we want to affect the function type, rather
than the pointer type, when the attribute has an argument.  The argument
names the strub mode for a function; that only applies to function
types, never to data types.

The hunk above introduces the means for the attribute handler to choose
what to attach the attribute t.

> Isn't there a suitable parsing position for placing the attribute?

It's been a while, but IIRC the need for this first came up in Ada,
where attributes can't just go anywhere, and it was further complicated
by the fact that Ada doesn't have first-class function or procedure
types, only access-to-them, but we needed some means for the attributes
to apply to the function type.

> +#ifndef STACK_GROWS_DOWNWARD
> +# define STACK_TOPS GT
> +#else
> +# define STACK_TOPS LT
> +#endif

> according to docs this is defined to 0 or 1 so the above looks wrong
> (it's always defined).

Ugh.  Thanks, will fix.  (I'm pretty sure I had notes somewhere stating
that stack-grows-upwards hadn't been tested, and that was for the sheer
lack of platforms making that choice, but I hoped it wasn't that broken
:-(

> +  if (optimize < 2 || optimize_size || flag_no_inline)
> +    return NULL_RTX;

> I'm wondering about these checks in the expansions of the builtins,
> I think this is about inline expanding or emitting a libcall, right?

Yeah.

> I wonder if you should use optimize_function_for_speed (cfun) instead?
> Usually -fno-inline shouldn't affect such calls, but -fno-builtin-FOO would.
> I have no strong opinion here though.

I've occasionally wondered whether builtins were the best framework for
these semi-internal calls.

> The new builtins seem undocumented - usually those are documented
> within extend.texi

Erhm...  Weird.  I had documentation for them.

(checks)

No, it's there, in extend.texi, right after __builtin_stack_address.
It's admittedly a big patch :-/

> I guess placing __builtin___strub_enter calls in the code manually
> will break in interesting ways - if that's not supposed to happen the
> trick is to embed a space in the name of the built-in.

Yeah, I was a little torn between the choices here.  On the one hand, I
needed visible symbols for the out-of-line implementations, so I figured
that trying to hide the builtins wouldn't bring any advantage.

However, I've also designed the builtins with interfaces that would
avoid disruption even with explicit calls.  __strub_enter and
__strub_update only initialize or adjust a pointer handed to them.
__strub_leave will erase things from the top of the stack to the
pointer, so if the watermark is "active stack", nothing happens, and
things only get cleared if it points to "unused stack space".  There's
potential for disruption if one passes a statically-allocated pointer to
it, but nothing much different from memsetting that memory range, core
wars-style.

> -symtab_node::reset (void)
> +symtab_node::reset (bool preserve_comdat_group)

> not sure what for, I'll leave Honza to comment.

This restores the possibility of getting the pre-PR107897 behavior, that
the strub wrapper/wrapped splitting relied on.  Conceptually, the
original function becomes the wrapped one, and the wrapper that calls it
is kind of an implementation detail to preserve the exposed API/ABI
while introducing strubbing around the body, so preserving the comdat
group makes sense.  ISTR getting strub regressions when the patch for
PR107897 was put in, but my notes don't detail what broke, alas.

> +/* Create a distinct copy of the type of NODE's function, and change
> +   the fntype of all calls to it with the same main type to the new
> +   type.  */
> +
> +static void
> +distinctify_node_type (cgraph_node *node)
> +{
> +  tree old_type = TREE_TYPE (node->decl);
> +  tree new_type = build_distinct_type_copy (old_type);
> +  tree new_ptr_type = NULL_TREE;
> +
> +  /* Remap any calls to node->decl that use old_type, or a variant
> +     thereof, to new_type as well.  We don't look for aliases, their
> +     declarations will have their types changed independently, and
> +     we'll adjust their fntypes then.  */
> +  for (cgraph_edge *e = node->callers; e; e = e->next_caller)
> +    {
> +      if (!e->call_stmt)
> + continue;
> +      tree fnaddr = gimple_call_fn (e->call_stmt);
> +      gcc_checking_assert (TREE_CODE (fnaddr) == ADDR_EXPR
> +   && TREE_OPERAND (fnaddr, 0) == node->decl);
> +      if (strub_call_fntype_override_p (e->call_stmt))
> + continue;
> +      if (!new_ptr_type)
> + new_ptr_type = build_pointer_type (new_type);
> +      TREE_TYPE (fnaddr) = new_ptr_type;
> +      gimple_call_set_fntype (e->call_stmt, new_type);
> +    }
> +
> +  TREE_TYPE (node->decl) = new_type;

> it does feel like there's IPA mechanisms to deal with what you are trying to do
> here (or in the caller(s)).

If there is, I couldn't find it.  There are some vaguely similar
operations, but nothing that will modify the ABI of exported functions
like what strub-at-calls purports to do.  A synthetic argument gets
added to the function's interface, but since that type could conceivably
have been associated/unified with non-strub types, or with similar types
with different strub modes that don't undergo such transformations, we
have to build a new type for the function, and adjust the type in the
call graph.  IPA can do this as part of replacing calls with specialized
clones of a function, but here the attribute calls for an adjustment to
the function type, vaguely resembling the introduction of the implicit
'this' in non-static member functions in C++, but without much help from
the front-end, and without exposing the formal to the front-end.  It
doesn't seem to me that IPA is willing to help with that, and even if it
were, it would require strub to become an actual IPA pass, which would
be a huge undertaking without clear benefit.

> +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 ();

> if  (flag_checking) verify_strub ();

> please.

No, no, verify_strub is not an internal consistency check, it's part of
the implementation that verifies that strictness requirements have been
met WRT calls requested by the user.  In strict mode, a strub function
can only call other strub functions or functions explicitly marked as
callable from strub contexts.  This security requirement is to be
enforced, and diagnostics are to be issued in case of violation,
regardless of the self-checking mode of the compiler.

> I guess we talked about this last year - what's the reason to have both
> an IPA pass and a simple IPA pass?

We did.  I didn't recall all the reasons on the spot when you asked, but
I followed up by email:
https://gcc.gnu.org/pipermail/gcc-patches/2022-October/603255.html

The earlier pass, that must be placed before any inlining, is the simple
IPA one, and it only assigns modes.

It must be that early because strub-enabled functions must never be
inlined into strub-disabled ones.  So we have to determine modes before
any sort of inlining.

But we can and want to inline strub-enabled functions into other
strub-enabled functions.  But we don't want to inline a wrapped function
back into its wrapper, so we want to (early) inline functions before
they're split, and we also want to inline wrappers after they're split
if possible.

So the assignment pass has to be before any inlining, and the
wrapping/splitting (for internal strub) must be after some inlining but
before the last attempt at inlining.

> IIRC the simple IPA pass is a simple one because it wants to see
> inlined bodies and "fixes" those up?  Some toplevel comments
> explaining both passes in the ipa-strub.cc pass would be nice to have.

ACK, will do.

> I guess I also asked before - did you try it with -flto?

Yeah, I've tested it extensively with -flto during development.  I've
run the GCC testsuite with a patch equivalent to -fstrub=all enabled by
default during development, and I still do so occasionally.  I didn't
before the ping, but prompted by you, I did again.  Torture tests and
specific lto tests don't seem to face any trouble.  And there's no
reason why they should.  The spots in which the strub passes were
introduced involved exploring where they fit best, and adding calls such
as analyzing nodes.  There are even commented-out vestigial calls of
inline_analyze_function, from a time when the pass was inserted at a
different spot and that call was required.

> +    /* 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);
> ...

> IMHO it's a bad idea, at least initially, to mess with the ABI in a heuristical
> way this way.

Do you realize that this is only about the wrapped ABI, used exclusively
by the wrapper, to avoid pointlessly copying large arguments twice?  The
wrapper already needs a custom ABI, because of the added watermark
pointer, and va_list and whatnot when needed, so a little further
customization to avoid a quite significant overhead seemed desirable.

> I see you need all sorts of "fixup", including re-gimplification
> of stmts, etc., double-ugh.

*nod*, I've considered going the nested-function way, enabling the
wrapped function to access wrapper's variables directly, but (i) support
for nested functions isn't available everywhere, and (ii) strub
functions could already be nested functions, and the wrapped body would
need access to both enclosing contexts, so adjustments would have to be
made anyway.

> Ideally IPA-SRA can already do parts of those
> transforms which would mean ipa-param-manipulation has generic code
> to encode them?

You'd think so.  I tried that, run into various issues, still have
pending patches I submitted separately to address them, but...  at some
point I had to stop waiting for feedback and had make it work without
that.  I figured what I was doing wasn't a good fit and moved on.

Now, it would be great if IPA could enable me to split the entire body
of a function *while* adding custom arguments, such as the watermark,
va_list, etc.  It didn't look like it could or even wanted to, but maybe
the presence of a useful feature like stack scrubbing could motivate the
introduction of such infrastructure.

> I realize you can't tail-call, but I wonder if some clever
> machine-specific tricks would work, I guess it would need the wrapper
> to be target generated of course.

I'm not sure what you have in mind here, but one of the goals was to
introduce stack scrubbing without machine-dependent code.

We are considering an improvement for strub(internal) to do away with
wrappers given machine-specific prologue/epilogue support, but that's
not even in the roadmap, and it's not clear how that would interact with
exceptions, which was a primary driver for the currently-proposed
approaches.

> As it's only for optimization, how much of the code would go away when
> you avoid changing the parameters of the wrapped function?

We can't avoid changing them entirely, and IIRC some of the
infrastructure for regimplification was also used for va_list and
apply_args handling, so it wouldn't save all that much.  It is an
important optimization, to avoid doubling the stack requirements for
parameters with -fstrub=internal (because wrapper passes arguments on to
wrapped body), and the optimization is already implemented, so it would
be a pity to take it out, and you wouldn't even get rid of any
significant amount of code.

> Could one easily disable the optimization via a --param maybe?

The param is not implemented, but guarding the loop you commented on by
it would accomplish it.  You probably won't want to bootstrap GCC with
-fstrub=all along with it, though ;-)

> There's still much commented code and FIXMEs in, a lot of cgraph
> related code is used which I can't review very well.

Now that you mentioned it, I realize I hadn't yet dropped some of the
preprocessed-out bits when I posted v4.  I did so afterwards.  I was
trying to avoid posting such a monster patch again, but I guess I will
have to post at least another version :-(

But yeah, there are some wishlist items that remain, and some bits that
could probably go away.

> I fear that -fstrub is going to be actually used by people - are you up
> to the task fixing things?

I, for one, would welcome that, and I am (professionally) on the hook to
fix it, so I'd better be up to it ;-) Building it on top of
infrastructure I'm closely familiar with, rather than e.g. on IPA, that
I'm not so much, is a plus in this regard.  That said, I've fixed bugs
in GCC most everywhere, from front-ends to back-ends, so I'm pretty sure
I've got it covered in terms of knowledge.  As for time, if it gets too
much interest (is there such a thing? :-) I might have trouble keeping
up (there are only so many hours in the day), and it would likely be
wise then for others to become familiar with it.  I'd be happy to share
knowledge, and I know I have support from my "employer" (in quotes
because I'm not formally employed, I'm a consultant) to maintain the
code I contribute to the community on its behalf, even having other work
and non-work commitments.  So, bring them on, I say ;-)

> +void ATTRIBUTE_STRUB_CALLABLE
> +__strub_enter (void **watermark)
> +{
> +  *watermark = __builtin_frame_address (0);

> in your experience, do these (or the inline version) work reliably
> when optimizations like omitting the frame pointer or shrink-wrapping
> happen from a security point of view?

Yeah.  We're talking about the frame address for the current function
here, and the compiler knows what it is, even if it optimizes the heck
out of the function.  In the builtin expansion, it's the top of the
stack instead, and the compiler also has to have a good grasp of where
that is.  Red zones are a small complication, but not really much
trouble here: it's a watermark we're talking about, how high the stack
may have gotten, so we know we have to scrub no further than that.  If
it goes too far (as it often does with red zones), we just scrub a
little too much.  What shouldn't happen is not going far enough, but
since we rely on the stack frame address for __strub_update to update
the watermark, and the caller can't really misrepresent its own stack
use without risking the callee to corrupt it, this works really well.
If strub_update gets builtin-expanded inline, it uses the adjusted (for
red zone) stack pointer instead, after any internal stack allocation
(prologue and alloca), but it won't necessarily capture non-accumulated
outgoing args.  The arguments passed to a callee are not regarded as
part of the caller stack frame, so that should be fine.  In order to
cover them, one must have a strub-enabled caller and a strub(at-calls)
callee.

> You are adding symbols to libgcc but I don't see you amending
> libgcc/libgcc-std.ver.in?

That's a good catch, thanks.

hardcfr symbols are missing too.

Will fix.

> Neither the documentation in extend.texi nor the one in invoke.texi
> mentions the target audience of -fstrub

ACK, good point, despite all the detail, that is too implicit.  Will
fix.

> The patches mention the red zone, documenting the guarantees and
> caveats would be nice to have.  I think the documentation belongs to
> the attribute documentation.

It is kind of there, but since the details vary depending on the strub
mode, they're given where the modes are detailed, rather than as if
applying to all strub modes.  (some of the modes are disabled and
callable, that don't do any strubbing whatsoever)

> I hope Honza can have a quick look over the IPA passes.

Likewise.  I shall wait for further feedback before posting a huge v5,
but I'll be happy to refresh the users/aoliva/heads/strub branch in the
git repo as I adjust the patch if that helps.  Currently the patch is in
users/aoliva/heads/testme.

> a better way than taking what is percieved as a huge ugly part
> of the patch.

ISTM you're misperceiving the amount of code involved in that
optimization.  There is a significant amount of wrapper/wrapped
parameter manipulation that is *not* involved with the optimization, and
that was already there before the optimization was implemented.  The
optimization was a tiny addition over it.

Thanks again,

-- 
Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
   Free Software Activist                   GNU Toolchain Engineer
More tolerance and less prejudice are key for inclusion and diversity
Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH v4] Introduce strub: machine-independent stack scrubbing
  2023-11-23 10:56               ` Alexandre Oliva
@ 2023-11-23 12:05                 ` Richard Biener
  2023-11-29  8:53                   ` Alexandre Oliva
  0 siblings, 1 reply; 59+ messages in thread
From: Richard Biener @ 2023-11-23 12:05 UTC (permalink / raw)
  To: Alexandre Oliva
  Cc: gcc-patches, Jeremy Bennett, Craig Blackmore, Graham Markall,
	Martin Jambor, Jan Hubicka, Jim Wilson, Jeff Law, Jakub Jelinek

On Thu, Nov 23, 2023 at 11:56 AM Alexandre Oliva <oliva@adacore.com> wrote:
>
> Hello, Richi,
>
> Thanks for the extensive review!
>
> On Nov 22, 2023, Richard Biener <richard.guenther@gmail.com> wrote:
>
> > On Mon, Nov 20, 2023 at 1:40 PM Alexandre Oliva <oliva@adacore.com> wrote:
> >>
> >> On Oct 26, 2023, Alexandre Oliva <oliva@adacore.com> wrote:
> >>
> >> >> This is a refreshed and improved version of the version posted back in
> >> >> June.  https://gcc.gnu.org/pipermail/gcc-patches/2023-June/621936.html
> >>
> >> > Ping? https://gcc.gnu.org/pipermail/gcc-patches/2023-October/633675.html
> >> > I'm combining the gcc/ipa-strub.cc bits from
> >> > https://gcc.gnu.org/pipermail/gcc-patches/2023-October/633526.html
> >>
> >> Ping?
> >> Retested on x86_64-linux-gnu, with and without -fstrub=all.
>
> > @@ -898,7 +899,24 @@ decl_attributes (tree *node, tree attributes, int flags,
> >        TYPE_NAME (tt) = *node;
> >      }
>
> > -  *anode = cur_and_last_decl[0];
> > +  if (*anode != cur_and_last_decl[0])
> > +    {
> > +      /* Even if !spec->function_type_required, allow the attribute
> > + handler to request the attribute to be applied to the function
> > + type, rather than to the function pointer type, by setting
> > + cur_and_last_decl[0] to the function type.  */
> > +      if (!fn_ptr_tmp
> > +  && POINTER_TYPE_P (*anode)
> > +  && TREE_TYPE (*anode) == cur_and_last_decl[0]
> > +  && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
> > + {
> > +  fn_ptr_tmp = TREE_TYPE (*anode);
> > +  fn_ptr_quals = TYPE_QUALS (*anode);
> > +  anode = &fn_ptr_tmp;
> > + }
> > +      *anode = cur_and_last_decl[0];
> > +    }
> > +
>
> > what is this a workaround for?
>
> For the fact that the strub attribute attaches to types, whether data or
> function types, so we can't have fn_type_req, but when it's a function
> or pointer-to-function type, we want to affect the function type, rather
> than the pointer type, when the attribute has an argument.  The argument
> names the strub mode for a function; that only applies to function
> types, never to data types.
>
> The hunk above introduces the means for the attribute handler to choose
> what to attach the attribute t.
>
> > Isn't there a suitable parsing position for placing the attribute?
>
> It's been a while, but IIRC the need for this first came up in Ada,
> where attributes can't just go anywhere, and it was further complicated
> by the fact that Ada doesn't have first-class function or procedure
> types, only access-to-them, but we needed some means for the attributes
> to apply to the function type.
>
> > +#ifndef STACK_GROWS_DOWNWARD
> > +# define STACK_TOPS GT
> > +#else
> > +# define STACK_TOPS LT
> > +#endif
>
> > according to docs this is defined to 0 or 1 so the above looks wrong
> > (it's always defined).
>
> Ugh.  Thanks, will fix.  (I'm pretty sure I had notes somewhere stating
> that stack-grows-upwards hadn't been tested, and that was for the sheer
> lack of platforms making that choice, but I hoped it wasn't that broken
> :-(
>
> > +  if (optimize < 2 || optimize_size || flag_no_inline)
> > +    return NULL_RTX;
>
> > I'm wondering about these checks in the expansions of the builtins,
> > I think this is about inline expanding or emitting a libcall, right?
>
> Yeah.
>
> > I wonder if you should use optimize_function_for_speed (cfun) instead?
> > Usually -fno-inline shouldn't affect such calls, but -fno-builtin-FOO would.
> > I have no strong opinion here though.
>
> I've occasionally wondered whether builtins were the best framework for
> these semi-internal calls.
>
> > The new builtins seem undocumented - usually those are documented
> > within extend.texi
>
> Erhm...  Weird.  I had documentation for them.
>
> (checks)
>
> No, it's there, in extend.texi, right after __builtin_stack_address.
> It's admittedly a big patch :-/
>
> > I guess placing __builtin___strub_enter calls in the code manually
> > will break in interesting ways - if that's not supposed to happen the
> > trick is to embed a space in the name of the built-in.
>
> Yeah, I was a little torn between the choices here.  On the one hand, I
> needed visible symbols for the out-of-line implementations, so I figured
> that trying to hide the builtins wouldn't bring any advantage.
>
> However, I've also designed the builtins with interfaces that would
> avoid disruption even with explicit calls.  __strub_enter and
> __strub_update only initialize or adjust a pointer handed to them.
> __strub_leave will erase things from the top of the stack to the
> pointer, so if the watermark is "active stack", nothing happens, and
> things only get cleared if it points to "unused stack space".  There's
> potential for disruption if one passes a statically-allocated pointer to
> it, but nothing much different from memsetting that memory range, core
> wars-style.
>
> > -symtab_node::reset (void)
> > +symtab_node::reset (bool preserve_comdat_group)
>
> > not sure what for, I'll leave Honza to comment.
>
> This restores the possibility of getting the pre-PR107897 behavior, that
> the strub wrapper/wrapped splitting relied on.  Conceptually, the
> original function becomes the wrapped one, and the wrapper that calls it
> is kind of an implementation detail to preserve the exposed API/ABI
> while introducing strubbing around the body, so preserving the comdat
> group makes sense.  ISTR getting strub regressions when the patch for
> PR107897 was put in, but my notes don't detail what broke, alas.
>
> > +/* Create a distinct copy of the type of NODE's function, and change
> > +   the fntype of all calls to it with the same main type to the new
> > +   type.  */
> > +
> > +static void
> > +distinctify_node_type (cgraph_node *node)
> > +{
> > +  tree old_type = TREE_TYPE (node->decl);
> > +  tree new_type = build_distinct_type_copy (old_type);
> > +  tree new_ptr_type = NULL_TREE;
> > +
> > +  /* Remap any calls to node->decl that use old_type, or a variant
> > +     thereof, to new_type as well.  We don't look for aliases, their
> > +     declarations will have their types changed independently, and
> > +     we'll adjust their fntypes then.  */
> > +  for (cgraph_edge *e = node->callers; e; e = e->next_caller)
> > +    {
> > +      if (!e->call_stmt)
> > + continue;
> > +      tree fnaddr = gimple_call_fn (e->call_stmt);
> > +      gcc_checking_assert (TREE_CODE (fnaddr) == ADDR_EXPR
> > +   && TREE_OPERAND (fnaddr, 0) == node->decl);
> > +      if (strub_call_fntype_override_p (e->call_stmt))
> > + continue;
> > +      if (!new_ptr_type)
> > + new_ptr_type = build_pointer_type (new_type);
> > +      TREE_TYPE (fnaddr) = new_ptr_type;
> > +      gimple_call_set_fntype (e->call_stmt, new_type);
> > +    }
> > +
> > +  TREE_TYPE (node->decl) = new_type;
>
> > it does feel like there's IPA mechanisms to deal with what you are trying to do
> > here (or in the caller(s)).
>
> If there is, I couldn't find it.  There are some vaguely similar
> operations, but nothing that will modify the ABI of exported functions
> like what strub-at-calls purports to do.  A synthetic argument gets
> added to the function's interface, but since that type could conceivably
> have been associated/unified with non-strub types, or with similar types
> with different strub modes that don't undergo such transformations, we
> have to build a new type for the function, and adjust the type in the
> call graph.  IPA can do this as part of replacing calls with specialized
> clones of a function, but here the attribute calls for an adjustment to
> the function type, vaguely resembling the introduction of the implicit
> 'this' in non-static member functions in C++, but without much help from
> the front-end, and without exposing the formal to the front-end.  It
> doesn't seem to me that IPA is willing to help with that, and even if it
> were, it would require strub to become an actual IPA pass, which would
> be a huge undertaking without clear benefit.

Conceptually it shouldn't be much different from what IPA-SRA does
which is cloning a function but with different arguments, the function
signature transform described in terms of ipa-param-manipulation bits.
I've talked with Martin and at least there's currently no
by-value-to-by-reference
"transform", but IPA-SRA can pass two registers instead of one aggregate
for example.  There's IPA_PARAM_OP_NEW already to add a new param.
In principle the whole function rewriting (apart of recovering
from inlining) should be doable within this framework.

> > +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 ();
>
> > if  (flag_checking) verify_strub ();
>
> > please.
>
> No, no, verify_strub is not an internal consistency check, it's part of
> the implementation that verifies that strictness requirements have been
> met WRT calls requested by the user.  In strict mode, a strub function
> can only call other strub functions or functions explicitly marked as
> callable from strub contexts.  This security requirement is to be
> enforced, and diagnostics are to be issued in case of violation,
> regardless of the self-checking mode of the compiler.
>
> > I guess we talked about this last year - what's the reason to have both
> > an IPA pass and a simple IPA pass?
>
> We did.  I didn't recall all the reasons on the spot when you asked, but
> I followed up by email:
> https://gcc.gnu.org/pipermail/gcc-patches/2022-October/603255.html
>
> The earlier pass, that must be placed before any inlining, is the simple
> IPA one, and it only assigns modes.
>
> It must be that early because strub-enabled functions must never be
> inlined into strub-disabled ones.  So we have to determine modes before
> any sort of inlining.
>
> But we can and want to inline strub-enabled functions into other
> strub-enabled functions.  But we don't want to inline a wrapped function
> back into its wrapper, so we want to (early) inline functions before
> they're split, and we also want to inline wrappers after they're split
> if possible.
>
> So the assignment pass has to be before any inlining, and the
> wrapping/splitting (for internal strub) must be after some inlining but
> before the last attempt at inlining.
>
> > IIRC the simple IPA pass is a simple one because it wants to see
> > inlined bodies and "fixes" those up?  Some toplevel comments
> > explaining both passes in the ipa-strub.cc pass would be nice to have.
>
> ACK, will do.
>
> > I guess I also asked before - did you try it with -flto?
>
> Yeah, I've tested it extensively with -flto during development.  I've
> run the GCC testsuite with a patch equivalent to -fstrub=all enabled by
> default during development, and I still do so occasionally.  I didn't
> before the ping, but prompted by you, I did again.  Torture tests and
> specific lto tests don't seem to face any trouble.  And there's no
> reason why they should.  The spots in which the strub passes were
> introduced involved exploring where they fit best, and adding calls such
> as analyzing nodes.  There are even commented-out vestigial calls of
> inline_analyze_function, from a time when the pass was inserted at a
> different spot and that call was required.
>
> > +    /* 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);
> > ...
>
> > IMHO it's a bad idea, at least initially, to mess with the ABI in a heuristical
> > way this way.
>
> Do you realize that this is only about the wrapped ABI, used exclusively
> by the wrapper, to avoid pointlessly copying large arguments twice?  The
> wrapper already needs a custom ABI, because of the added watermark
> pointer, and va_list and whatnot when needed, so a little further
> customization to avoid a quite significant overhead seemed desirable.

I understood the purpose of this, but I also saw it's only ever needed for
non-strict operation and I think that if you care about security you'd never
want to operate in a way that you don't absolutely know the function
you call isn't going to scrub your secrets ...

But yes, I also wondered why you even run into re-gimplification issues.
If you make sure to never pass things as reference that are registers
in the callee context then replacing the PARM_DECL there with
a MEM_REF (new-PARM_DECL) should work without re-gimplification.
OK, existing MEM_REFs need folding to be canonical again, but that
should be it.

Note I didn't look into the re-gimplification code much, I just saw
it and wondered, thinking this would be the reason for it?

> > I see you need all sorts of "fixup", including re-gimplification
> > of stmts, etc., double-ugh.
>
> *nod*, I've considered going the nested-function way, enabling the
> wrapped function to access wrapper's variables directly, but (i) support
> for nested functions isn't available everywhere, and (ii) strub
> functions could already be nested functions, and the wrapped body would
> need access to both enclosing contexts, so adjustments would have to be
> made anyway.
>
> > Ideally IPA-SRA can already do parts of those
> > transforms which would mean ipa-param-manipulation has generic code
> > to encode them?
>
> You'd think so.  I tried that, run into various issues, still have
> pending patches I submitted separately to address them, but...  at some
> point I had to stop waiting for feedback and had make it work without
> that.  I figured what I was doing wasn't a good fit and moved on.
>
> Now, it would be great if IPA could enable me to split the entire body
> of a function *while* adding custom arguments, such as the watermark,
> va_list, etc.  It didn't look like it could or even wanted to, but maybe
> the presence of a useful feature like stack scrubbing could motivate the
> introduction of such infrastructure.
>
> > I realize you can't tail-call, but I wonder if some clever
> > machine-specific tricks would work, I guess it would need the wrapper
> > to be target generated of course.
>
> I'm not sure what you have in mind here, but one of the goals was to
> introduce stack scrubbing without machine-dependent code.
>
> We are considering an improvement for strub(internal) to do away with
> wrappers given machine-specific prologue/epilogue support, but that's
> not even in the roadmap, and it's not clear how that would interact with
> exceptions, which was a primary driver for the currently-proposed
> approaches.

Understood.  But I didn't remember seeing that you do sth like wrapping
each "strubbed call" inside a try { call(); } finally { do-strub } to
ensure this.
Guess it was well hidden in the large patch ;)

> > As it's only for optimization, how much of the code would go away when
> > you avoid changing the parameters of the wrapped function?
>
> We can't avoid changing them entirely, and IIRC some of the
> infrastructure for regimplification was also used for va_list and
> apply_args handling, so it wouldn't save all that much.

Ah, var-args ... indeed for simple forwarding it shouldn't be too bad.
I wonder if this were a way to remove the restriction on function
splitting of var-args functions - there's a recent bugreport about that
(before any va_arg () has been called, of course).

>  It is an
> important optimization, to avoid doubling the stack requirements for
> parameters with -fstrub=internal (because wrapper passes arguments on to
> wrapped body), and the optimization is already implemented, so it would
> be a pity to take it out, and you wouldn't even get rid of any
> significant amount of code.
>
> > Could one easily disable the optimization via a --param maybe?
>
> The param is not implemented, but guarding the loop you commented on by
> it would accomplish it.  You probably won't want to bootstrap GCC with
> -fstrub=all along with it, though ;-)
>
> > There's still much commented code and FIXMEs in, a lot of cgraph
> > related code is used which I can't review very well.
>
> Now that you mentioned it, I realize I hadn't yet dropped some of the
> preprocessed-out bits when I posted v4.  I did so afterwards.  I was
> trying to avoid posting such a monster patch again, but I guess I will
> have to post at least another version :-(
>
> But yeah, there are some wishlist items that remain, and some bits that
> could probably go away.
>
> > I fear that -fstrub is going to be actually used by people - are you up
> > to the task fixing things?
>
> I, for one, would welcome that, and I am (professionally) on the hook to
> fix it, so I'd better be up to it ;-) Building it on top of
> infrastructure I'm closely familiar with, rather than e.g. on IPA, that
> I'm not so much, is a plus in this regard.  That said, I've fixed bugs
> in GCC most everywhere, from front-ends to back-ends, so I'm pretty sure
> I've got it covered in terms of knowledge.  As for time, if it gets too
> much interest (is there such a thing? :-) I might have trouble keeping
> up (there are only so many hours in the day), and it would likely be
> wise then for others to become familiar with it.  I'd be happy to share
> knowledge, and I know I have support from my "employer" (in quotes
> because I'm not formally employed, I'm a consultant) to maintain the
> code I contribute to the community on its behalf, even having other work
> and non-work commitments.  So, bring them on, I say ;-)
>
> > +void ATTRIBUTE_STRUB_CALLABLE
> > +__strub_enter (void **watermark)
> > +{
> > +  *watermark = __builtin_frame_address (0);
>
> > in your experience, do these (or the inline version) work reliably
> > when optimizations like omitting the frame pointer or shrink-wrapping
> > happen from a security point of view?
>
> Yeah.  We're talking about the frame address for the current function
> here, and the compiler knows what it is, even if it optimizes the heck
> out of the function.  In the builtin expansion, it's the top of the
> stack instead, and the compiler also has to have a good grasp of where
> that is.  Red zones are a small complication, but not really much
> trouble here: it's a watermark we're talking about, how high the stack
> may have gotten, so we know we have to scrub no further than that.  If
> it goes too far (as it often does with red zones), we just scrub a
> little too much.  What shouldn't happen is not going far enough, but
> since we rely on the stack frame address for __strub_update to update
> the watermark, and the caller can't really misrepresent its own stack
> use without risking the callee to corrupt it, this works really well.
> If strub_update gets builtin-expanded inline, it uses the adjusted (for
> red zone) stack pointer instead, after any internal stack allocation
> (prologue and alloca), but it won't necessarily capture non-accumulated
> outgoing args.  The arguments passed to a callee are not regarded as
> part of the caller stack frame, so that should be fine.  In order to
> cover them, one must have a strub-enabled caller and a strub(at-calls)
> callee.
>
> > You are adding symbols to libgcc but I don't see you amending
> > libgcc/libgcc-std.ver.in?
>
> That's a good catch, thanks.
>
> hardcfr symbols are missing too.
>
> Will fix.
>
> > Neither the documentation in extend.texi nor the one in invoke.texi
> > mentions the target audience of -fstrub
>
> ACK, good point, despite all the detail, that is too implicit.  Will
> fix.
>
> > The patches mention the red zone, documenting the guarantees and
> > caveats would be nice to have.  I think the documentation belongs to
> > the attribute documentation.
>
> It is kind of there, but since the details vary depending on the strub
> mode, they're given where the modes are detailed, rather than as if
> applying to all strub modes.  (some of the modes are disabled and
> callable, that don't do any strubbing whatsoever)
>
> > I hope Honza can have a quick look over the IPA passes.
>
> Likewise.  I shall wait for further feedback before posting a huge v5,
> but I'll be happy to refresh the users/aoliva/heads/strub branch in the
> git repo as I adjust the patch if that helps.  Currently the patch is in
> users/aoliva/heads/testme.
>
> > a better way than taking what is percieved as a huge ugly part
> > of the patch.
>
> ISTM you're misperceiving the amount of code involved in that
> optimization.  There is a significant amount of wrapper/wrapped
> parameter manipulation that is *not* involved with the optimization, and
> that was already there before the optimization was implemented.  The
> optimization was a tiny addition over it.

I see.

Thanks,
Richard.

> Thanks again,
>
> --
> Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
>    Free Software Activist                   GNU Toolchain Engineer
> More tolerance and less prejudice are key for inclusion and diversity
> Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH v4] Introduce strub: machine-independent stack scrubbing
  2023-11-23 12:05                 ` Richard Biener
@ 2023-11-29  8:53                   ` Alexandre Oliva
  2023-11-29 12:48                     ` Richard Biener
  0 siblings, 1 reply; 59+ messages in thread
From: Alexandre Oliva @ 2023-11-29  8:53 UTC (permalink / raw)
  To: Richard Biener
  Cc: gcc-patches, Jeremy Bennett, Craig Blackmore, Graham Markall,
	Martin Jambor, Jan Hubicka, Jim Wilson, Jeff Law, Jakub Jelinek

On Nov 23, 2023, Richard Biener <richard.guenther@gmail.com> wrote:

> Conceptually it shouldn't be much different from what IPA-SRA does
> which is cloning a function but with different arguments, the function
> signature transform described in terms of ipa-param-manipulation bits.
> I've talked with Martin and at least there's currently no
> by-value-to-by-reference
> "transform", but IPA-SRA can pass two registers instead of one aggregate
> for example.  There's IPA_PARAM_OP_NEW already to add a new param.
> In principle the whole function rewriting (apart of recovering
> from inlining) should be doable within this framework.

I agree, but my attempts to do so have been unfruitful, and hit various
very obscure issues that made it unworkable.  Maybe someone smarter than
I am, or with more time than I had, can make the conversion.  The uses
of IPA_PARAM_OP_NEW for the cloning are there, and the more conservative
choices I made got it to work reliably, unlike the more adventurous
alternatives I tried.  One of the obscure issues I recall hitting was
this one.  I can probably still locate the early strub patch that hit
the described issue back then, if there's interest.
https://gcc.gnu.org/pipermail/gcc-patches/2021-July/575044.html

>> The
>> wrapper already needs a custom ABI, because of the added watermark
>> pointer, and va_list and whatnot when needed, so a little further
>> customization to avoid a quite significant overhead seemed desirable.

> I understood the purpose of this, but I also saw it's only ever needed for
> non-strict operation

Erhm...  I suppose you're not talking about -fstrub=strict, because I
can't see how this relates with the by-reference argument passing from
wrapper to wrapped in internal strubbing.

> and I think that if you care about security you'd never
> want to operate in a way that you don't absolutely know the function
> you call isn't going to scrub your secrets ...

The ABI between wrapper and wrapped function splitting in internal strub
doesn't change this.

> But yes, I also wondered why you even run into re-gimplification issues.

Because &arg_#(D)[n_#] is good gimple, but &(*byref_arg_#(D))[n_#] isn't.

Maybe instead of going through regimplify, we could explicitly create an
SSA name for the indirection load, so that the purpose is made more
explicit.

> replacing the PARM_DECL there with a MEM_REF (new-PARM_DECL) should
> work without re-gimplification.

FWIW, I thought so as well ;-)

> I didn't remember seeing that you do sth like wrapping
> each "strubbed call" inside a try { call(); } finally { do-strub } to
> ensure this.
> Guess it was well hidden in the large patch ;)

Indeed, gsi_insert_finally_seq_after_call is where this is taken care of.

>> > As it's only for optimization, how much of the code would go away when
>> > you avoid changing the parameters of the wrapped function?
>> 
>> We can't avoid changing them entirely, and IIRC some of the
>> infrastructure for regimplification was also used for va_list and
>> apply_args handling, so it wouldn't save all that much.

> Ah, var-args ... indeed for simple forwarding it shouldn't be too bad.
> I wonder if this were a way to remove the restriction on function
> splitting of var-args functions - there's a recent bugreport about that
> (before any va_arg () has been called, of course).

If running va_start unconditionally in varargs functions is generally
acceptable, then the method I've used for wrapping varargs functions
should work generally, yeah.  The trick was to turn:

foo (...)
{
  va_list ap;

  ...
  va_start (&ap, <ignored>);
  ...
}

into

wrapped_foo (va_list &wrap)
{
  va_list ap;

  ...
  va_copy (ap, wrap);
  ...
}

foo (...)
{
  va_list wrap;

  va_start (wrap, <also ignored>);
  wrapped_foo (wrap);
  va_end (wrap);
}


Here's the opening comment I added to ipa-strub.cc:

/* This file introduces two passes that, together, implement
   machine-independent stack scrubbing, strub for short.  It arranges
   for stack frames that have strub enabled to be zeroed-out after
   relinquishing control to a caller, whether by returning or by
   propagating an exception.  This admittedly unusual design decision
   was driven by exception support (one needs a stack frame to be
   active to propagate exceptions out of it), and it enabled an
   implementation that is entirely machine-independent (no custom
   epilogue code is required).

   Strub modes can be selected for stack frames by attaching attribute
   strub to functions or to variables (to their types, actually).
   Different strub modes, with different implementation details, are
   available, and they can be selected by an argument to the strub
   attribute.  When enabled by strub-enabled variables, whether by
   accessing (as in reading from) statically-allocated ones, or by
   introducing (as in declaring) automatically-allocated ones, a
   suitable mode is selected automatically.

   At-calls mode modifies the interface of a function, adding a stack
   watermark argument, that callers use to clean up the stack frame of
   the called function.  Because of the interface change, it can only
   be used when explicitly selected, or when a function is internal to
   a translation unit.  Strub-at-calls function types are distinct
   from their original types (they're not modified in-place), and they
   are not interchangeable with other function types.

   Internal mode, in turn, does not modify the type or the interface
   of a function.  It is currently implemented by turning the function
   into a wrapper, moving the function body to a separate wrapped
   function, and scrubbing the wrapped body's stack in the wrapper.
   Internal-strub function types are mostly interface-compatible with
   other strub modes, namely callable (from strub functions, though
   not strub-enabled) and disabled (not callable from strub
   functions).

   Always_inline functions can be strub functions, but they can only
   be called from other strub functions, because strub functions must
   never be inlined into non-strub functions.  Internal and at-calls
   modes are indistinguishable when it comes to always_inline
   functions: they will necessarily be inlined into another strub
   function, and will thus be integrated into the caller's stack
   frame, whatever the mode.  (Contrast with non-always_inline strub
   functions: an at-calls function can be called from other strub
   functions, ensuring no discontinuity in stack erasing, whereas an
   internal-strub function can only be called from other strub
   functions if it happens to be inlined, or if -fstrub=relaxed mode
   is in effect (that's the default).  In -fstrub=strict mode,
   internal-strub functions are not callable from strub functions,
   because the wrapper itself is not strubbed.

   The implementation involves two simple-IPA passes.  The earliest
   one, strub-mode, assigns strub modes to functions.  It needs to run
   before any inlining, so that we can prevent inlining of strub
   functions into non-strub functions.  It notes explicit strub mode
   requests, enables strub in response to strub variables and testing
   options, and flags unsatisfiable requests.

   Three possibilities of unsatisfiable requests come to mind: (a)
   when a strub mode is explicitly selected, but the function uses
   features that make it ineligible for that mode (e.g. at-calls rules
   out calling __builtin_apply_args, because of the interface changes,
   and internal mode rules out noclone or otherwise non-versionable
   functions, non-default varargs, non-local or forced labels, and
   functions with far too many arguments); (b) when some strub mode
   must be enabled because of a strub variable, but the function is
   not eligible or not viable for any mode; and (c) when
   -fstrub=strict is enabled, and calls are found in strub functions
   to functions that are not callable from strub contexts.
   compute_strub_mode implements (a) and (b), and verify_strub
   implements (c).

   The second IPA pass modifies interfaces of at-calls-strub functions
   and types, introduces strub calls in and around them. and splits
   internal-strub functions.  It is placed after early inlining, so
   that even internal-strub functions get a chance of being inlined
   into other strub functions, but before non-early inlining, so that
   internal-strub wrapper functions still get a chance of inlining
   after splitting.

   Wrappers avoid duplicating the copying of large arguments again by
   passing them by reference to the wrapped bodies.  This involves
   occasional SSA rewriting of address computations, because of the
   additional indirection.  Besides these changes, and the
   introduction of the stack watermark parameter, wrappers and wrapped
   functions cooperate to handle variable argument lists (performing
   va_start in the wrapper, passing the list as an argument, and
   replacing va_start calls in the wrapped body with va_copy), and
   __builtin_apply_args (also called in the wrapper and passed to the
   wrapped body as an argument).

   Strub bodies (both internal-mode wrapped bodies, and at-calls
   functions) always start by adjusting the watermark parameter, by
   calling __builtin___strub_update.  The compiler inserts them in the
   main strub pass.  Allocations of additional stack space for the
   frame (__builtin_alloca) are also followed by watermark updates.
   Stack space temporarily allocated to pass arguments to other
   functions, released right after the call, is not regarded as part
   of the frame.  Around calls to them, i.e., in internal-mode
   wrappers and at-calls callers (even calls through pointers), calls
   to __builtin___strub_enter and __builtin___strub_leave are
   inserted, the latter as a __finally block, so that it runs at
   regular and exceptional exit paths.  strub_enter only initializes
   the stack watermark, and strub_leave is where the scrubbing takes
   place, overwriting with zeros the stack space from the top of the
   stack to the watermark.

   These calls can be optimized in various cases.  In
   pass_ipa_strub::adjust_at_calls_call, for example, we enable
   tail-calling and other optimized calls from one strub body to
   another by passing on the watermark parameter.  The builtins
   themselves may undergo inline substitution during expansion,
   dependign on optimization levels.  This involves dealing with stack
   red zones (when the builtins are called out-of-line, the red zone
   cannot be used) and other ugly details related with inlining strub
   bodies into other strub bodies (see expand_builtin_strub_update).
   expand_builtin_strub_leave may even perform partial inline
   substitution.  */

The type attribute description now starts like this:

@cindex @code{strub} type attribute
@item strub
This attribute defines stack-scrubbing properties of functions and
variables, so that functions that access sensitive data can have their
stack frames zeroed-out upon returning or propagating exceptions.  This
may be enabled explicitly, by selecting certain @code{strub} modes for
specific functions, or implicitly, by means of @code{strub} variables.

And I've fixed the symbol versioning for strub and hardcfr functions.

I've just pushed the base strub patch and the recent incremental changes
(and some commits used for testing) to refs/users/aoliva/heads/strub.


Here are changes.html entries for this and for the other newly-added
features:

new AdaCore-contributed hardening features in gcc 13 and 14

Mention hardening of conditionals (added in gcc 13), control flow
redundancy, hardened booleans, and stack scrubbing.

Also cover forced inlining of string operations while at that.
---
 htdocs/gcc-13/changes.html |    6 ++++++
 htdocs/gcc-14/changes.html |   29 +++++++++++++++++++++++++++++
 2 files changed, 35 insertions(+)

diff --git a/htdocs/gcc-13/changes.html b/htdocs/gcc-13/changes.html
index 8ef3d63952daf..c1dea18caf59b 100644
--- a/htdocs/gcc-13/changes.html
+++ b/htdocs/gcc-13/changes.html
@@ -168,6 +168,12 @@ You may also want to check out our
     been added, see also
     <a href="https://gcc.gnu.org/onlinedocs/gcc/Freestanding-Environments.html">Profiling and Test Coverage in Freestanding Environments</a>.
   </li>
+  <li>
+    New options <code>-fharden-compares</code>
+    and <code>-fharden-conditional-branches</code> to verify compares
+    and conditional branches, to detect some power-deprivation
+    hardware attacks, using reversed conditions.
+  </li>
 </ul>
 
 
diff --git a/htdocs/gcc-14/changes.html b/htdocs/gcc-14/changes.html
index 2088ee91a34e1..cb92f8d8095c3 100644
--- a/htdocs/gcc-14/changes.html
+++ b/htdocs/gcc-14/changes.html
@@ -109,6 +109,35 @@ a work-in-progress.</p>
     of hardening flags.  The options it enables can be displayed using the
     <code>--help=hardened</code> option.
   </li>
+  <li>
+    New option <code>-fharden-control-flow-redundancy</code>, to
+    verify, at the end of functions, that the visited basic blocks
+    correspond to a legitimate execution path, so as to detect and
+    prevent attacks that transfer control into the middle of
+    functions.
+  </li>
+  <li>
+    New type attribute <code>hardbool</code>, for C and Ada.  Hardened
+    booleans take user-specified representations for <code>true</code>
+    and <code>false</code>, presumably with higher hamming distance
+    than standard booleans, and get verified at every use, detecting
+    memory corruption and some malicious attacks.
+  </li>
+  <li>
+    New type attribute <code>strub</code> to control stack scrubbing
+    properties of functions and variables.  The stack frame used by
+    functions marked with the attribute gets zeroed-out upon returning
+    or exception escaping.  Scalar variables marked with the attribute
+    cause functions contaning or accessing them to get stack scrubbing
+    enabled implicitly.
+  </li>
+  <li>
+    New option <code>-finline-stringops</code>, to force inline
+    expansion of <code>memcmp</code>, <code>memcpy</code>,
+    <code>memmove</code> and <code>memset</code>, even when that is
+    not an optimization, to avoid relying on library
+    implementations.
+  </li>
 </ul>
 <!-- .................................................................. -->
 <h2 id="languages">New Languages and Language specific improvements</h2>


-- 
Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
   Free Software Activist                   GNU Toolchain Engineer
More tolerance and less prejudice are key for inclusion and diversity
Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH v4] Introduce strub: machine-independent stack scrubbing
  2023-11-29  8:53                   ` Alexandre Oliva
@ 2023-11-29 12:48                     ` Richard Biener
  2023-11-30  4:13                       ` Alexandre Oliva
  2023-11-30  5:04                       ` [PATCH v4] Introduce strub: machine-independent stack scrubbing Alexandre Oliva
  0 siblings, 2 replies; 59+ messages in thread
From: Richard Biener @ 2023-11-29 12:48 UTC (permalink / raw)
  To: Alexandre Oliva
  Cc: gcc-patches, Jeremy Bennett, Craig Blackmore, Graham Markall,
	Martin Jambor, Jan Hubicka, Jim Wilson, Jeff Law, Jakub Jelinek

On Wed, Nov 29, 2023 at 9:53 AM Alexandre Oliva <oliva@adacore.com> wrote:
>
> On Nov 23, 2023, Richard Biener <richard.guenther@gmail.com> wrote:
>
> > Conceptually it shouldn't be much different from what IPA-SRA does
> > which is cloning a function but with different arguments, the function
> > signature transform described in terms of ipa-param-manipulation bits.
> > I've talked with Martin and at least there's currently no
> > by-value-to-by-reference
> > "transform", but IPA-SRA can pass two registers instead of one aggregate
> > for example.  There's IPA_PARAM_OP_NEW already to add a new param.
> > In principle the whole function rewriting (apart of recovering
> > from inlining) should be doable within this framework.
>
> I agree, but my attempts to do so have been unfruitful, and hit various
> very obscure issues that made it unworkable.  Maybe someone smarter than
> I am, or with more time than I had, can make the conversion.  The uses
> of IPA_PARAM_OP_NEW for the cloning are there, and the more conservative
> choices I made got it to work reliably, unlike the more adventurous
> alternatives I tried.  One of the obscure issues I recall hitting was
> this one.  I can probably still locate the early strub patch that hit
> the described issue back then, if there's interest.
> https://gcc.gnu.org/pipermail/gcc-patches/2021-July/575044.html
>
> >> The
> >> wrapper already needs a custom ABI, because of the added watermark
> >> pointer, and va_list and whatnot when needed, so a little further
> >> customization to avoid a quite significant overhead seemed desirable.
>
> > I understood the purpose of this, but I also saw it's only ever needed for
> > non-strict operation
>
> Erhm...  I suppose you're not talking about -fstrub=strict, because I
> can't see how this relates with the by-reference argument passing from
> wrapper to wrapped in internal strubbing.
>
> > and I think that if you care about security you'd never
> > want to operate in a way that you don't absolutely know the function
> > you call isn't going to scrub your secrets ...
>
> The ABI between wrapper and wrapped function splitting in internal strub
> doesn't change this.
>
> > But yes, I also wondered why you even run into re-gimplification issues.
>
> Because &arg_#(D)[n_#] is good gimple, but &(*byref_arg_#(D))[n_#] isn't.

'arg_#(D)' looks like a SSA name, and no, taking the address doesn't work,
so I assume it was &MEM[arg_(D)][n_#] which is indeed OK.  But you
shouldn't need to change a pointer argument to be passed by reference,
do you?  As said, you want to restrict by-reference passing to arguments
that are !is_gimple_reg_type ().  Everywhere where a plain PARM_DECL
was valid a *ptr indirection is as well.

> Maybe instead of going through regimplify, we could explicitly create an
> SSA name for the indirection load, so that the purpose is made more
> explicit.
>
> > replacing the PARM_DECL there with a MEM_REF (new-PARM_DECL) should
> > work without re-gimplification.
>
> FWIW, I thought so as well ;-)
>
> > I didn't remember seeing that you do sth like wrapping
> > each "strubbed call" inside a try { call(); } finally { do-strub } to
> > ensure this.
> > Guess it was well hidden in the large patch ;)
>
> Indeed, gsi_insert_finally_seq_after_call is where this is taken care of.
>
> >> > As it's only for optimization, how much of the code would go away when
> >> > you avoid changing the parameters of the wrapped function?
> >>
> >> We can't avoid changing them entirely, and IIRC some of the
> >> infrastructure for regimplification was also used for va_list and
> >> apply_args handling, so it wouldn't save all that much.
>
> > Ah, var-args ... indeed for simple forwarding it shouldn't be too bad.
> > I wonder if this were a way to remove the restriction on function
> > splitting of var-args functions - there's a recent bugreport about that
> > (before any va_arg () has been called, of course).
>
> If running va_start unconditionally in varargs functions is generally
> acceptable, then the method I've used for wrapping varargs functions
> should work generally, yeah.  The trick was to turn:
>
> foo (...)
> {
>   va_list ap;
>
>   ...
>   va_start (&ap, <ignored>);
>   ...
> }
>
> into
>
> wrapped_foo (va_list &wrap)
> {
>   va_list ap;
>
>   ...
>   va_copy (ap, wrap);
>   ...
> }
>
> foo (...)
> {
>   va_list wrap;
>
>   va_start (wrap, <also ignored>);
>   wrapped_foo (wrap);
>   va_end (wrap);
> }

Ah, nice trick indeed.  I think that should work, it might not
be optimal (I don't think we will optimize the va_copy).

>
> Here's the opening comment I added to ipa-strub.cc:
>
> /* This file introduces two passes that, together, implement
>    machine-independent stack scrubbing, strub for short.  It arranges
>    for stack frames that have strub enabled to be zeroed-out after
>    relinquishing control to a caller, whether by returning or by
>    propagating an exception.  This admittedly unusual design decision
>    was driven by exception support (one needs a stack frame to be
>    active to propagate exceptions out of it), and it enabled an
>    implementation that is entirely machine-independent (no custom
>    epilogue code is required).
>
>    Strub modes can be selected for stack frames by attaching attribute
>    strub to functions or to variables (to their types, actually).
>    Different strub modes, with different implementation details, are
>    available, and they can be selected by an argument to the strub
>    attribute.  When enabled by strub-enabled variables, whether by
>    accessing (as in reading from) statically-allocated ones, or by
>    introducing (as in declaring) automatically-allocated ones, a
>    suitable mode is selected automatically.
>
>    At-calls mode modifies the interface of a function, adding a stack
>    watermark argument, that callers use to clean up the stack frame of
>    the called function.  Because of the interface change, it can only
>    be used when explicitly selected, or when a function is internal to
>    a translation unit.  Strub-at-calls function types are distinct
>    from their original types (they're not modified in-place), and they
>    are not interchangeable with other function types.
>
>    Internal mode, in turn, does not modify the type or the interface
>    of a function.  It is currently implemented by turning the function
>    into a wrapper, moving the function body to a separate wrapped
>    function, and scrubbing the wrapped body's stack in the wrapper.
>    Internal-strub function types are mostly interface-compatible with
>    other strub modes, namely callable (from strub functions, though
>    not strub-enabled) and disabled (not callable from strub
>    functions).
>
>    Always_inline functions can be strub functions, but they can only
>    be called from other strub functions, because strub functions must
>    never be inlined into non-strub functions.  Internal and at-calls
>    modes are indistinguishable when it comes to always_inline
>    functions: they will necessarily be inlined into another strub
>    function, and will thus be integrated into the caller's stack
>    frame, whatever the mode.  (Contrast with non-always_inline strub
>    functions: an at-calls function can be called from other strub
>    functions, ensuring no discontinuity in stack erasing, whereas an
>    internal-strub function can only be called from other strub
>    functions if it happens to be inlined, or if -fstrub=relaxed mode
>    is in effect (that's the default).  In -fstrub=strict mode,
>    internal-strub functions are not callable from strub functions,
>    because the wrapper itself is not strubbed.
>
>    The implementation involves two simple-IPA passes.  The earliest
>    one, strub-mode, assigns strub modes to functions.  It needs to run
>    before any inlining, so that we can prevent inlining of strub
>    functions into non-strub functions.  It notes explicit strub mode
>    requests, enables strub in response to strub variables and testing
>    options, and flags unsatisfiable requests.
>
>    Three possibilities of unsatisfiable requests come to mind: (a)
>    when a strub mode is explicitly selected, but the function uses
>    features that make it ineligible for that mode (e.g. at-calls rules
>    out calling __builtin_apply_args, because of the interface changes,
>    and internal mode rules out noclone or otherwise non-versionable
>    functions, non-default varargs, non-local or forced labels, and
>    functions with far too many arguments); (b) when some strub mode
>    must be enabled because of a strub variable, but the function is
>    not eligible or not viable for any mode; and (c) when
>    -fstrub=strict is enabled, and calls are found in strub functions
>    to functions that are not callable from strub contexts.
>    compute_strub_mode implements (a) and (b), and verify_strub
>    implements (c).
>
>    The second IPA pass modifies interfaces of at-calls-strub functions
>    and types, introduces strub calls in and around them. and splits
>    internal-strub functions.  It is placed after early inlining, so
>    that even internal-strub functions get a chance of being inlined
>    into other strub functions, but before non-early inlining, so that
>    internal-strub wrapper functions still get a chance of inlining
>    after splitting.
>
>    Wrappers avoid duplicating the copying of large arguments again by
>    passing them by reference to the wrapped bodies.  This involves
>    occasional SSA rewriting of address computations, because of the
>    additional indirection.  Besides these changes, and the
>    introduction of the stack watermark parameter, wrappers and wrapped
>    functions cooperate to handle variable argument lists (performing
>    va_start in the wrapper, passing the list as an argument, and
>    replacing va_start calls in the wrapped body with va_copy), and
>    __builtin_apply_args (also called in the wrapper and passed to the
>    wrapped body as an argument).
>
>    Strub bodies (both internal-mode wrapped bodies, and at-calls
>    functions) always start by adjusting the watermark parameter, by
>    calling __builtin___strub_update.  The compiler inserts them in the
>    main strub pass.  Allocations of additional stack space for the
>    frame (__builtin_alloca) are also followed by watermark updates.
>    Stack space temporarily allocated to pass arguments to other
>    functions, released right after the call, is not regarded as part
>    of the frame.  Around calls to them, i.e., in internal-mode
>    wrappers and at-calls callers (even calls through pointers), calls
>    to __builtin___strub_enter and __builtin___strub_leave are
>    inserted, the latter as a __finally block, so that it runs at
>    regular and exceptional exit paths.  strub_enter only initializes
>    the stack watermark, and strub_leave is where the scrubbing takes
>    place, overwriting with zeros the stack space from the top of the
>    stack to the watermark.
>
>    These calls can be optimized in various cases.  In
>    pass_ipa_strub::adjust_at_calls_call, for example, we enable
>    tail-calling and other optimized calls from one strub body to
>    another by passing on the watermark parameter.  The builtins
>    themselves may undergo inline substitution during expansion,
>    dependign on optimization levels.  This involves dealing with stack
>    red zones (when the builtins are called out-of-line, the red zone
>    cannot be used) and other ugly details related with inlining strub
>    bodies into other strub bodies (see expand_builtin_strub_update).
>    expand_builtin_strub_leave may even perform partial inline
>    substitution.  */

Thanks.

> The type attribute description now starts like this:
>
> @cindex @code{strub} type attribute
> @item strub
> This attribute defines stack-scrubbing properties of functions and
> variables, so that functions that access sensitive data can have their
> stack frames zeroed-out upon returning or propagating exceptions.  This
> may be enabled explicitly, by selecting certain @code{strub} modes for
> specific functions, or implicitly, by means of @code{strub} variables.
>
> And I've fixed the symbol versioning for strub and hardcfr functions.
>
> I've just pushed the base strub patch and the recent incremental changes
> (and some commits used for testing) to refs/users/aoliva/heads/strub.
>
>
> Here are changes.html entries for this and for the other newly-added
> features:
>
> new AdaCore-contributed hardening features in gcc 13 and 14
>
> Mention hardening of conditionals (added in gcc 13), control flow
> redundancy, hardened booleans, and stack scrubbing.
>
> Also cover forced inlining of string operations while at that.
> ---
>  htdocs/gcc-13/changes.html |    6 ++++++
>  htdocs/gcc-14/changes.html |   29 +++++++++++++++++++++++++++++
>  2 files changed, 35 insertions(+)
>
> diff --git a/htdocs/gcc-13/changes.html b/htdocs/gcc-13/changes.html
> index 8ef3d63952daf..c1dea18caf59b 100644
> --- a/htdocs/gcc-13/changes.html
> +++ b/htdocs/gcc-13/changes.html
> @@ -168,6 +168,12 @@ You may also want to check out our
>      been added, see also
>      <a href="https://gcc.gnu.org/onlinedocs/gcc/Freestanding-Environments.html">Profiling and Test Coverage in Freestanding Environments</a>.
>    </li>
> +  <li>
> +    New options <code>-fharden-compares</code>
> +    and <code>-fharden-conditional-branches</code> to verify compares
> +    and conditional branches, to detect some power-deprivation
> +    hardware attacks, using reversed conditions.
> +  </li>
>  </ul>
>
>
> diff --git a/htdocs/gcc-14/changes.html b/htdocs/gcc-14/changes.html
> index 2088ee91a34e1..cb92f8d8095c3 100644
> --- a/htdocs/gcc-14/changes.html
> +++ b/htdocs/gcc-14/changes.html
> @@ -109,6 +109,35 @@ a work-in-progress.</p>
>      of hardening flags.  The options it enables can be displayed using the
>      <code>--help=hardened</code> option.
>    </li>
> +  <li>
> +    New option <code>-fharden-control-flow-redundancy</code>, to
> +    verify, at the end of functions, that the visited basic blocks
> +    correspond to a legitimate execution path, so as to detect and
> +    prevent attacks that transfer control into the middle of
> +    functions.
> +  </li>
> +  <li>
> +    New type attribute <code>hardbool</code>, for C and Ada.  Hardened
> +    booleans take user-specified representations for <code>true</code>
> +    and <code>false</code>, presumably with higher hamming distance
> +    than standard booleans, and get verified at every use, detecting
> +    memory corruption and some malicious attacks.
> +  </li>
> +  <li>
> +    New type attribute <code>strub</code> to control stack scrubbing
> +    properties of functions and variables.  The stack frame used by
> +    functions marked with the attribute gets zeroed-out upon returning
> +    or exception escaping.  Scalar variables marked with the attribute
> +    cause functions contaning or accessing them to get stack scrubbing
> +    enabled implicitly.
> +  </li>
> +  <li>
> +    New option <code>-finline-stringops</code>, to force inline
> +    expansion of <code>memcmp</code>, <code>memcpy</code>,
> +    <code>memmove</code> and <code>memset</code>, even when that is
> +    not an optimization, to avoid relying on library
> +    implementations.
> +  </li>
>  </ul>
>  <!-- .................................................................. -->
>  <h2 id="languages">New Languages and Language specific improvements</h2>

LGTM.

Can you check on the pass-by-reference thing again please?

Let's see if Honza or Martin have any comments on the IPA bits, I just
mentioned what I think should be doable ...

Thanks,
Richard.

>
> --
> Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
>    Free Software Activist                   GNU Toolchain Engineer
> More tolerance and less prejudice are key for inclusion and diversity
> Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH v4] Introduce strub: machine-independent stack scrubbing
  2023-11-29 12:48                     ` Richard Biener
@ 2023-11-30  4:13                       ` Alexandre Oliva
  2023-11-30 12:00                         ` Richard Biener
  2023-11-30  5:04                       ` [PATCH v4] Introduce strub: machine-independent stack scrubbing Alexandre Oliva
  1 sibling, 1 reply; 59+ messages in thread
From: Alexandre Oliva @ 2023-11-30  4:13 UTC (permalink / raw)
  To: Richard Biener
  Cc: gcc-patches, Jeremy Bennett, Craig Blackmore, Graham Markall,
	Martin Jambor, Jan Hubicka, Jim Wilson, Jeff Law, Jakub Jelinek

On Nov 29, 2023, Richard Biener <richard.guenther@gmail.com> wrote:

>> Because &arg_#(D)[n_#] is good gimple, but &(*byref_arg_#(D))[n_#] isn't.

> 'arg_#(D)' looks like a SSA name, and no, taking the address doesn't work,
> so I assume it was &MEM[arg_(D)][n_#] which is indeed OK.

Yeah.

> But you shouldn't need to change a pointer argument to be passed by
> reference, do you?

True, my attempt to simplify the example moved it past the breaking point.

IIRC the actual situations I hit involved computing address of members
of compound objects, such as struct members, even array elements
thereof.  They became problematic after replacing the object with a
dereference in gimple stmts.  The (effectively) offsetting operation is
well-formed gimple, but IIRC adding dereferencing to it made it
malformed gimple.  I don't immediately see why this should be the case,
since it's still offsetting, so perhaps I misremember.

> As said, you want to restrict by-reference passing to arguments
> that are !is_gimple_reg_type ()

*nod*, it was already there:

      if (!(0 /* DECL_BY_REFERENCE (narg) */
	    || is_gimple_reg_type (TREE_TYPE (nparm))
            ...
	{
	  indirect_nparms.add (nparm);

>> Here are changes.html entries for this and for the other newly-added
>> features:

> LGTM.

Was that an ok to install, once the relevant pieces are in?

> Can you check on the pass-by-reference thing again please?

Sure.  I'll get back to you shortly.

If argument indirection becomes the only blocking issue, I'd be happy to
disable it, or even split out the patch that introduces it, so that the
bulk of the feature can go in while we sort out these details.
Disabling it is as simple as:

diff --git a/gcc/ipa-strub.cc b/gcc/ipa-strub.cc
index 293bec132b885..90770202fb851 100644
--- a/gcc/ipa-strub.cc
+++ b/gcc/ipa-strub.cc
@@ -2831,6 +2831,7 @@ pass_ipa_strub::execute (function *)
 	   parm = DECL_CHAIN (parm),
 	   nparm = DECL_CHAIN (nparm),
 	   nparmt = nparmt ? TREE_CHAIN (nparmt) : NULL_TREE)
+      if (true) ; else // ??? Disable parm indirection for now.
       if (!(0 /* DECL_BY_REFERENCE (narg) */
 	    || is_gimple_reg_type (TREE_TYPE (nparm))
 	    || VECTOR_TYPE_P (TREE_TYPE (nparm))


> Let's see if Honza or Martin have any comments on the IPA bits, I just
> mentioned what I think should be doable ...

I'm curious as to what you're hoping for.  I mean, I am using
create_version_clone_with_body, adding the new params and copying the
preexisting ones, and modifying some argument types for indirection
after cloning.  The problems I faced were as I tried to replace params
with their indirected versions.  According to my notes and my
recollection, that's where I hit most of the trouble.  But what would
this really buy us?  Do you envision a possibility of actually splitting
out the original function body, so that IPA takes care of the whole
wrapping?  AFAICT that would require a lot more infrastructure to deal
with new and modified parameters, though the details of what I learned
about this API back then, and that made it clear I wouldn't be able to
use it, seem to have faded away from my memory.

-- 
Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
   Free Software Activist                   GNU Toolchain Engineer
More tolerance and less prejudice are key for inclusion and diversity
Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH v4] Introduce strub: machine-independent stack scrubbing
  2023-11-29 12:48                     ` Richard Biener
  2023-11-30  4:13                       ` Alexandre Oliva
@ 2023-11-30  5:04                       ` Alexandre Oliva
  2023-11-30 11:56                         ` Richard Biener
  1 sibling, 1 reply; 59+ messages in thread
From: Alexandre Oliva @ 2023-11-30  5:04 UTC (permalink / raw)
  To: Richard Biener
  Cc: gcc-patches, Jeremy Bennett, Craig Blackmore, Graham Markall,
	Martin Jambor, Jan Hubicka, Jim Wilson, Jeff Law, Jakub Jelinek

On Nov 29, 2023, Richard Biener <richard.guenther@gmail.com> wrote:

> On Wed, Nov 29, 2023 at 9:53 AM Alexandre Oliva <oliva@adacore.com> wrote:

>> Because &arg_#(D)[n_#] is good gimple, but &(*byref_arg_#(D))[n_#] isn't.

> 'arg_#(D)' looks like a SSA name, and no, taking the address doesn't work,
> so I assume it was &MEM[arg_(D)][n_#] which is indeed OK.  But you
> shouldn't need to change a pointer argument to be passed by reference,
> do you?  As said, you want to restrict by-reference passing to arguments
> that are !is_gimple_reg_type ().  Everywhere where a plain PARM_DECL
> was valid a *ptr indirection is as well.

> Can you check on the pass-by-reference thing again please?

Applying the following patchlet on top of refs/users/aoliva/heads/strub
(that has a -fstrub=all patchlet in it) I get a build error in libgo
building golang.org/x/mod/sumdb.o:

In function ‘golang_0org_1x_1mod_1sumdb.Client.checkTrees.strub.0’:
go1: error: invalid argument to gimple call
&older_195(D)->Hash
# VUSE <.MEM_55>
_16 = __builtin_memcmp (&h, &older_195(D)->Hash, 32);
during IPA pass: strub

golang.org/x/mod/sumdb.go.057i.remove_symbols:  _5 = __builtin_memcmp (&h, &older.Hash, 32);

within golang_0org_1x_1mod_1sumdb.Client.checkTrees becomes, in wrapped
version thereof:

golang.org/x/mod/sumdb.go.058i.strub:  _16 = __builtin_memcmp (&h, &older_195(D)->Hash, 32);

It's not even the case that Hash is at offset 0 into older's type, but I
suspect the reason why the former is well-formed while the latter is not
the offset, but the SSA_NAME.


diff --git a/gcc/ipa-strub.cc b/gcc/ipa-strub.cc
index 293bec132b8..515ab9a2ee5 100644
--- a/gcc/ipa-strub.cc
+++ b/gcc/ipa-strub.cc
@@ -1950,7 +1950,7 @@ walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
   if (!*op || TREE_CODE (*op) != ADDR_EXPR)
     return NULL_TREE;
 
-  if (!is_gimple_val (*op))
+  if (0 && !is_gimple_val (*op))
     {
       tree ret = force_gimple_operand_gsi (&gsi, *op, true,
                                           NULL_TREE, true, GSI_SAME_STMT);

-- 
Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
   Free Software Activist                   GNU Toolchain Engineer
More tolerance and less prejudice are key for inclusion and diversity
Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH v4] Introduce strub: machine-independent stack scrubbing
  2023-11-30  5:04                       ` [PATCH v4] Introduce strub: machine-independent stack scrubbing Alexandre Oliva
@ 2023-11-30 11:56                         ` Richard Biener
  0 siblings, 0 replies; 59+ messages in thread
From: Richard Biener @ 2023-11-30 11:56 UTC (permalink / raw)
  To: Alexandre Oliva
  Cc: gcc-patches, Jeremy Bennett, Craig Blackmore, Graham Markall,
	Martin Jambor, Jan Hubicka, Jim Wilson, Jeff Law, Jakub Jelinek

On Thu, Nov 30, 2023 at 6:04 AM Alexandre Oliva <oliva@adacore.com> wrote:
>
> On Nov 29, 2023, Richard Biener <richard.guenther@gmail.com> wrote:
>
> > On Wed, Nov 29, 2023 at 9:53 AM Alexandre Oliva <oliva@adacore.com> wrote:
>
> >> Because &arg_#(D)[n_#] is good gimple, but &(*byref_arg_#(D))[n_#] isn't.
>
> > 'arg_#(D)' looks like a SSA name, and no, taking the address doesn't work,
> > so I assume it was &MEM[arg_(D)][n_#] which is indeed OK.  But you
> > shouldn't need to change a pointer argument to be passed by reference,
> > do you?  As said, you want to restrict by-reference passing to arguments
> > that are !is_gimple_reg_type ().  Everywhere where a plain PARM_DECL
> > was valid a *ptr indirection is as well.
>
> > Can you check on the pass-by-reference thing again please?
>
> Applying the following patchlet on top of refs/users/aoliva/heads/strub
> (that has a -fstrub=all patchlet in it) I get a build error in libgo
> building golang.org/x/mod/sumdb.o:
>
> In function ‘golang_0org_1x_1mod_1sumdb.Client.checkTrees.strub.0’:
> go1: error: invalid argument to gimple call
> &older_195(D)->Hash
> # VUSE <.MEM_55>
> _16 = __builtin_memcmp (&h, &older_195(D)->Hash, 32);
> during IPA pass: strub
>
> golang.org/x/mod/sumdb.go.057i.remove_symbols:  _5 = __builtin_memcmp (&h, &older.Hash, 32);

Ah, yeah - &older.Hash is considered a constant while &older_195(D)->Hash isn't.

So indeed you are correct - you'll need adjustments, sorry for
misleading you ...

Richard.

> within golang_0org_1x_1mod_1sumdb.Client.checkTrees becomes, in wrapped
> version thereof:
>
> golang.org/x/mod/sumdb.go.058i.strub:  _16 = __builtin_memcmp (&h, &older_195(D)->Hash, 32);
>
> It's not even the case that Hash is at offset 0 into older's type, but I
> suspect the reason why the former is well-formed while the latter is not
> the offset, but the SSA_NAME.
>
>
> diff --git a/gcc/ipa-strub.cc b/gcc/ipa-strub.cc
> index 293bec132b8..515ab9a2ee5 100644
> --- a/gcc/ipa-strub.cc
> +++ b/gcc/ipa-strub.cc
> @@ -1950,7 +1950,7 @@ walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
>    if (!*op || TREE_CODE (*op) != ADDR_EXPR)
>      return NULL_TREE;
>
> -  if (!is_gimple_val (*op))
> +  if (0 && !is_gimple_val (*op))
>      {
>        tree ret = force_gimple_operand_gsi (&gsi, *op, true,
>                                            NULL_TREE, true, GSI_SAME_STMT);
>
> --
> Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
>    Free Software Activist                   GNU Toolchain Engineer
> More tolerance and less prejudice are key for inclusion and diversity
> Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH v4] Introduce strub: machine-independent stack scrubbing
  2023-11-30  4:13                       ` Alexandre Oliva
@ 2023-11-30 12:00                         ` Richard Biener
  2023-12-02 17:56                           ` [PATCH v5] " Alexandre Oliva
  2023-12-20  8:15                           ` [PATCH FYI] www: new AdaCore-contributed hardening features in gcc 13 and 14 Alexandre Oliva
  0 siblings, 2 replies; 59+ messages in thread
From: Richard Biener @ 2023-11-30 12:00 UTC (permalink / raw)
  To: Alexandre Oliva
  Cc: gcc-patches, Jeremy Bennett, Craig Blackmore, Graham Markall,
	Martin Jambor, Jan Hubicka, Jim Wilson, Jeff Law, Jakub Jelinek

On Thu, Nov 30, 2023 at 5:13 AM Alexandre Oliva <oliva@adacore.com> wrote:
>
> On Nov 29, 2023, Richard Biener <richard.guenther@gmail.com> wrote:
>
> >> Because &arg_#(D)[n_#] is good gimple, but &(*byref_arg_#(D))[n_#] isn't.
>
> > 'arg_#(D)' looks like a SSA name, and no, taking the address doesn't work,
> > so I assume it was &MEM[arg_(D)][n_#] which is indeed OK.
>
> Yeah.
>
> > But you shouldn't need to change a pointer argument to be passed by
> > reference, do you?
>
> True, my attempt to simplify the example moved it past the breaking point.
>
> IIRC the actual situations I hit involved computing address of members
> of compound objects, such as struct members, even array elements
> thereof.  They became problematic after replacing the object with a
> dereference in gimple stmts.  The (effectively) offsetting operation is
> well-formed gimple, but IIRC adding dereferencing to it made it
> malformed gimple.  I don't immediately see why this should be the case,
> since it's still offsetting, so perhaps I misremember.
>
> > As said, you want to restrict by-reference passing to arguments
> > that are !is_gimple_reg_type ()
>
> *nod*, it was already there:
>
>       if (!(0 /* DECL_BY_REFERENCE (narg) */
>             || is_gimple_reg_type (TREE_TYPE (nparm))
>             ...
>         {
>           indirect_nparms.add (nparm);
>
> >> Here are changes.html entries for this and for the other newly-added
> >> features:
>
> > LGTM.
>
> Was that an ok to install, once the relevant pieces are in?

See below.

> > Can you check on the pass-by-reference thing again please?
>
> Sure.  I'll get back to you shortly.
>
> If argument indirection becomes the only blocking issue, I'd be happy to
> disable it, or even split out the patch that introduces it, so that the
> bulk of the feature can go in while we sort out these details.
> Disabling it is as simple as:
>
> diff --git a/gcc/ipa-strub.cc b/gcc/ipa-strub.cc
> index 293bec132b885..90770202fb851 100644
> --- a/gcc/ipa-strub.cc
> +++ b/gcc/ipa-strub.cc
> @@ -2831,6 +2831,7 @@ pass_ipa_strub::execute (function *)
>            parm = DECL_CHAIN (parm),
>            nparm = DECL_CHAIN (nparm),
>            nparmt = nparmt ? TREE_CHAIN (nparmt) : NULL_TREE)
> +      if (true) ; else // ??? Disable parm indirection for now.
>        if (!(0 /* DECL_BY_REFERENCE (narg) */
>             || is_gimple_reg_type (TREE_TYPE (nparm))
>             || VECTOR_TYPE_P (TREE_TYPE (nparm))
>
>
> > Let's see if Honza or Martin have any comments on the IPA bits, I just
> > mentioned what I think should be doable ...
>
> I'm curious as to what you're hoping for.  I mean, I am using
> create_version_clone_with_body, adding the new params and copying the
> preexisting ones, and modifying some argument types for indirection
> after cloning.  The problems I faced were as I tried to replace params
> with their indirected versions.  According to my notes and my
> recollection, that's where I hit most of the trouble.  But what would
> this really buy us?  Do you envision a possibility of actually splitting
> out the original function body, so that IPA takes care of the whole
> wrapping?  AFAICT that would require a lot more infrastructure to deal
> with new and modified parameters, though the details of what I learned
> about this API back then, and that made it clear I wouldn't be able to
> use it, seem to have faded away from my memory.

In the end I was hoping for general comments on the cgraph usage
and for the specifics indeed being able to use IPA mechanisms
to perform the stmt re-writing (and re-gimplification) for the value to
by-reference replacement.  The core copy_body mechanism
might support it by simply registering *new_param as replacement
for 'old_param' in the copy_body_data decl_map.

Iff the IPA folks (Honza or Martin) don't have any further comments
the patch is OK to install by next Monday.

Thanks,
Richard.

>
> --
> Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
>    Free Software Activist                   GNU Toolchain Engineer
> More tolerance and less prejudice are key for inclusion and diversity
> Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* [PATCH v5] Introduce strub: machine-independent stack scrubbing
  2023-11-30 12:00                         ` Richard Biener
@ 2023-12-02 17:56                           ` Alexandre Oliva
  2023-12-05  6:25                             ` Alexandre Oliva
                                               ` (4 more replies)
  2023-12-20  8:15                           ` [PATCH FYI] www: new AdaCore-contributed hardening features in gcc 13 and 14 Alexandre Oliva
  1 sibling, 5 replies; 59+ messages in thread
From: Alexandre Oliva @ 2023-12-02 17:56 UTC (permalink / raw)
  To: Richard Biener
  Cc: gcc-patches, Jeremy Bennett, Craig Blackmore, Graham Markall,
	Martin Jambor, Jan Hubicka, Jim Wilson, Jeff Law, Jakub Jelinek

On Nov 30, 2023, Richard Biener <richard.guenther@gmail.com> wrote:

> On Thu, Nov 30, 2023 at 5:13 AM Alexandre Oliva <oliva@adacore.com> wrote:

>> >> Here are changes.html entries for this and for the other newly-added
>> >> features:
>> 
>> > LGTM.
>> 
>> Was that an ok to install, once the relevant pieces are in?

> See below.

FWIW, I meant specifically about the web site changes.html patch in the
question above.

> In the end I was hoping for general comments on the cgraph usage
> and for the specifics indeed being able to use IPA mechanisms
> to perform the stmt re-writing (and re-gimplification) for the value to
> by-reference replacement.  The core copy_body mechanism
> might support it by simply registering *new_param as replacement
> for 'old_param' in the copy_body_data decl_map.

I see.  That's indeed a good direction for evolution, that I'd like us
to take, and having the base code in with some more manageable way to
experiment smaller changes will make this transition significantly
easier IMHO.  But that kind of infrastructure change would also likely
be stage1 material.

> Iff the IPA folks (Honza or Martin) don't have any further comments
> the patch is OK to install by next Monday.

Thanks, here's the consolidated patch that I hope to install by Monday.


Introduce strub: machine-independent stack scrubbing

This patch adds the strub attribute for function and variable types,
command-line options, passes and adjustments to implement it,
documentation, and tests.

Stack scrubbing is implemented in a machine-independent way: functions
with strub enabled are modified so that they take an extra stack
watermark argument, that they update with their stack use, and the
caller can then zero it out once it regains control, whether by return
or exception.  There are two ways to go about it: at-calls, that
modifies the visible interface (signature) of the function, and
internal, in which the body is moved to a clone, the clone undergoes
the interface change, and the function becomes a wrapper, preserving
its original interface, that calls the clone and then clears the stack
used by it.

Variables can also be annotated with the strub attribute, so that
functions that read from them get stack scrubbing enabled implicitly,
whether at-calls, for functions only usable within a translation unit,
or internal, for functions whose interfaces must not be modified.

There is a strict mode, in which functions that have their stack
scrubbed can only call other functions with stack-scrubbing
interfaces, or those explicitly marked as callable from strub
contexts, so that an entire call chain gets scrubbing, at once or
piecemeal depending on optimization levels.  In the default mode,
relaxed, this requirement is not enforced by the compiler.

The implementation adds two IPA passes, one that assigns strub modes
early on, another that modifies interfaces and adds calls to the
builtins that jointly implement stack scrubbing.  Another builtin,
that obtains the stack pointer, is added for use in the implementation
of the builtins, whether expanded inline or called in libgcc.

There are new command-line options to change operation modes and to
force the feature disabled; it is enabled by default, but it has no
effect and is implicitly disabled if the strub attribute is never
used.  There are also options meant to use for testing the feature,
enabling different strubbing modes for all (viable) functions.


for  gcc/ChangeLog

	* Makefile.in (OBJS): Add ipa-strub.o.
	(GTFILES): Add ipa-strub.cc.
	* builtins.def (BUILT_IN_STACK_ADDRESS): New.
	(BUILT_IN___STRUB_ENTER): New.
	(BUILT_IN___STRUB_UPDATE): New.
	(BUILT_IN___STRUB_LEAVE): New.
	* builtins.cc: Include ipa-strub.h.
	(STACK_STOPS, STACK_UNSIGNED): Define.
	(expand_builtin_stack_address): New.
	(expand_builtin_strub_enter): New.
	(expand_builtin_strub_update): New.
	(expand_builtin_strub_leave): New.
	(expand_builtin): Call them.
	* common.opt (fstrub=*): New options.
	* doc/extend.texi (strub): New type attribute.
	(__builtin_stack_address): New function.
	(Stack Scrubbing): New section.
	* doc/invoke.texi (-fstrub=*): New options.
	(-fdump-ipa-*): New passes.
	* gengtype-lex.l: Ignore multi-line pp-directives.
	* ipa-inline.cc: Include ipa-strub.h.
	(can_inline_edge_p): Test strub_inlinable_to_p.
	* ipa-split.cc: Include ipa-strub.h.
	(execute_split_functions): Test strub_splittable_p.
	* ipa-strub.cc, ipa-strub.h: New.
	* passes.def: Add strub_mode and strub passes.
	* tree-cfg.cc (gimple_verify_flow_info): Note on debug stmts.
	* tree-pass.h (make_pass_ipa_strub_mode): Declare.
	(make_pass_ipa_strub): Declare.
	(make_pass_ipa_function_and_variable_visibility): Fix
	formatting.
	* tree-ssa-ccp.cc (optimize_stack_restore): Keep restores
	before strub leave.
	* multiple_target.cc (pass_target_clone::gate): Test seen_error.
	* attribs.cc: Include ipa-strub.h.
	(decl_attributes): Support applying attributes to function
	type, rather than pointer type, at handler's request.
	(comp_type_attributes): Combine strub_comptypes and target
	comp_type results.
	* doc/tm.texi.in (TARGET_STRUB_USE_DYNAMIC_ARRAY): New.
	(TARGET_STRUB_MAY_USE_MEMSET): New.
	* doc/tm.texi: Rebuilt.
	* cgraph.h (symtab_node::reset): Add preserve_comdat_group
	param, with a default.
	* cgraphunit.cc (symtab_node::reset): Use it.

for  gcc/c-family/ChangeLog

	* c-attribs.cc: Include ipa-strub.h.
	(handle_strub_attribute): New.
	(c_common_attribute_table): Add strub.

for  gcc/ada/ChangeLog

	* gcc-interface/trans.cc: Include ipa-strub.h.
	(gigi): Make internal decls for targets of compiler-generated
	calls strub-callable too.
	(build_raise_check): Likewise.
	* gcc-interface/utils.cc: Include ipa-strub.h.
	(handle_strub_attribute): New.
	(gnat_internal_attribute_table): Add strub.

for  gcc/testsuite/ChangeLog

	* c-c++-common/strub-O0.c: New.
	* c-c++-common/strub-O1.c: New.
	* c-c++-common/strub-O2.c: New.
	* c-c++-common/strub-O2fni.c: New.
	* c-c++-common/strub-O3.c: New.
	* c-c++-common/strub-O3fni.c: New.
	* c-c++-common/strub-Og.c: New.
	* c-c++-common/strub-Os.c: New.
	* c-c++-common/strub-all1.c: New.
	* c-c++-common/strub-all2.c: New.
	* c-c++-common/strub-apply1.c: New.
	* c-c++-common/strub-apply2.c: New.
	* c-c++-common/strub-apply3.c: New.
	* c-c++-common/strub-apply4.c: New.
	* c-c++-common/strub-at-calls1.c: New.
	* c-c++-common/strub-at-calls2.c: New.
	* c-c++-common/strub-defer-O1.c: New.
	* c-c++-common/strub-defer-O2.c: New.
	* c-c++-common/strub-defer-O3.c: New.
	* c-c++-common/strub-defer-Os.c: New.
	* c-c++-common/strub-internal1.c: New.
	* c-c++-common/strub-internal2.c: New.
	* c-c++-common/strub-parms1.c: New.
	* c-c++-common/strub-parms2.c: New.
	* c-c++-common/strub-parms3.c: New.
	* c-c++-common/strub-relaxed1.c: New.
	* c-c++-common/strub-relaxed2.c: New.
	* c-c++-common/strub-short-O0-exc.c: New.
	* c-c++-common/strub-short-O0.c: New.
	* c-c++-common/strub-short-O1.c: New.
	* c-c++-common/strub-short-O2.c: New.
	* c-c++-common/strub-short-O3.c: New.
	* c-c++-common/strub-short-Os.c: New.
	* c-c++-common/strub-strict1.c: New.
	* c-c++-common/strub-strict2.c: New.
	* c-c++-common/strub-tail-O1.c: New.
	* c-c++-common/strub-tail-O2.c: New.
	* c-c++-common/torture/strub-callable1.c: New.
	* c-c++-common/torture/strub-callable2.c: New.
	* c-c++-common/torture/strub-const1.c: New.
	* c-c++-common/torture/strub-const2.c: New.
	* c-c++-common/torture/strub-const3.c: New.
	* c-c++-common/torture/strub-const4.c: New.
	* c-c++-common/torture/strub-data1.c: New.
	* c-c++-common/torture/strub-data2.c: New.
	* c-c++-common/torture/strub-data3.c: New.
	* c-c++-common/torture/strub-data4.c: New.
	* c-c++-common/torture/strub-data5.c: New.
	* c-c++-common/torture/strub-indcall1.c: New.
	* c-c++-common/torture/strub-indcall2.c: New.
	* c-c++-common/torture/strub-indcall3.c: New.
	* c-c++-common/torture/strub-inlinable1.c: New.
	* c-c++-common/torture/strub-inlinable2.c: New.
	* c-c++-common/torture/strub-ptrfn1.c: New.
	* c-c++-common/torture/strub-ptrfn2.c: New.
	* c-c++-common/torture/strub-ptrfn3.c: New.
	* c-c++-common/torture/strub-ptrfn4.c: New.
	* c-c++-common/torture/strub-pure1.c: New.
	* c-c++-common/torture/strub-pure2.c: New.
	* c-c++-common/torture/strub-pure3.c: New.
	* c-c++-common/torture/strub-pure4.c: New.
	* c-c++-common/torture/strub-run1.c: New.
	* c-c++-common/torture/strub-run2.c: New.
	* c-c++-common/torture/strub-run3.c: New.
	* c-c++-common/torture/strub-run4.c: New.
	* c-c++-common/torture/strub-run4c.c: New.
	* c-c++-common/torture/strub-run4d.c: New.
	* c-c++-common/torture/strub-run4i.c: New.
	* g++.dg/strub-run1.C: New.
	* g++.dg/torture/strub-init1.C: New.
	* g++.dg/torture/strub-init2.C: New.
	* g++.dg/torture/strub-init3.C: New.
	* gnat.dg/strub_attr.adb, gnat.dg/strub_attr.ads: New.
	* gnat.dg/strub_ind.adb, gnat.dg/strub_ind.ads: New.

for  libgcc/ChangeLog

	* Makefile.in (LIB2ADD): Add strub.c.
	* libgcc2.h (__strub_enter, __strub_update, __strub_leave):
	Declare.
	* strub.c: New.
	* libgcc-std.ver.in (__strub_enter): Add to GCC_14.0.0.
	(__strub_update, __strub_leave): Likewise.
---
 gcc/Makefile.in                                    |    2 
 gcc/ada/gcc-interface/trans.cc                     |   18 
 gcc/ada/gcc-interface/utils.cc                     |   73 
 gcc/attribs.cc                                     |   37 
 gcc/builtins.cc                                    |  269 ++
 gcc/builtins.def                                   |    4 
 gcc/c-family/c-attribs.cc                          |   82 
 gcc/cgraph.h                                       |    2 
 gcc/cgraphunit.cc                                  |    5 
 gcc/common.opt                                     |   29 
 gcc/doc/extend.texi                                |  312 ++
 gcc/doc/invoke.texi                                |   60 
 gcc/doc/tm.texi                                    |   19 
 gcc/doc/tm.texi.in                                 |   19 
 gcc/gengtype-lex.l                                 |    3 
 gcc/ipa-inline.cc                                  |    6 
 gcc/ipa-split.cc                                   |    7 
 gcc/ipa-strub.cc                                   | 3573 ++++++++++++++++++++
 gcc/ipa-strub.h                                    |   45 
 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-defer-O1.c        |    7 
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |    8 
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |  110 +
 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-relaxed1.c        |   18 
 gcc/testsuite/c-c++-common/strub-relaxed2.c        |   14 
 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-strict1.c         |   36 
 gcc/testsuite/c-c++-common/strub-strict2.c         |   25 
 gcc/testsuite/c-c++-common/strub-tail-O1.c         |    8 
 gcc/testsuite/c-c++-common/strub-tail-O2.c         |   14 
 gcc/testsuite/c-c++-common/strub-var1.c            |   24 
 .../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   |   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 
 .../c-c++-common/torture/strub-inlinable1.c        |   16 
 .../c-c++-common/torture/strub-inlinable2.c        |    7 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |   10 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |   55 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c  |   50 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c  |   43 
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |   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    |   95 +
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |   84 
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |   80 
 gcc/testsuite/c-c++-common/torture/strub-run4.c    |  106 +
 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_access.adb             |   21 
 gcc/testsuite/gnat.dg/strub_access1.adb            |   16 
 gcc/testsuite/gnat.dg/strub_attr.adb               |   37 
 gcc/testsuite/gnat.dg/strub_attr.ads               |   12 
 gcc/testsuite/gnat.dg/strub_disp.adb               |   64 
 gcc/testsuite/gnat.dg/strub_disp1.adb              |   79 
 gcc/testsuite/gnat.dg/strub_ind.adb                |   33 
 gcc/testsuite/gnat.dg/strub_ind.ads                |   17 
 gcc/testsuite/gnat.dg/strub_ind1.adb               |   41 
 gcc/testsuite/gnat.dg/strub_ind1.ads               |   17 
 gcc/testsuite/gnat.dg/strub_ind2.adb               |   34 
 gcc/testsuite/gnat.dg/strub_ind2.ads               |   17 
 gcc/testsuite/gnat.dg/strub_intf.adb               |   93 +
 gcc/testsuite/gnat.dg/strub_intf1.adb              |   86 
 gcc/testsuite/gnat.dg/strub_intf2.adb              |   55 
 gcc/testsuite/gnat.dg/strub_renm.adb               |   21 
 gcc/testsuite/gnat.dg/strub_renm1.adb              |   32 
 gcc/testsuite/gnat.dg/strub_renm2.adb              |   32 
 gcc/testsuite/gnat.dg/strub_var.adb                |   16 
 gcc/testsuite/gnat.dg/strub_var1.adb               |   20 
 gcc/tree-cfg.cc                                    |    1 
 gcc/tree-pass.h                                    |    5 
 gcc/tree-ssa-ccp.cc                                |    4 
 libgcc/Makefile.in                                 |    3 
 libgcc/libgcc-std.ver.in                           |    3 
 libgcc/libgcc2.h                                   |    4 
 libgcc/strub.c                                     |  149 +
 120 files changed, 7429 insertions(+), 12 deletions(-)
 create mode 100644 gcc/ipa-strub.cc
 create mode 100644 gcc/ipa-strub.h
 create mode 100644 gcc/testsuite/c-c++-common/strub-O0.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O2fni.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-O3fni.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-Og.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-Os.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-all1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-all2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-apply1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-apply2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-apply3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-apply4.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-at-calls1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-at-calls2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-defer-O1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-defer-O2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-defer-O3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-defer-Os.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-internal1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-internal2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-parms1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-parms2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-parms3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-relaxed1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-relaxed2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O0-exc.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O0.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-O3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-short-Os.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-strict1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-strict2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-tail-O1.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-tail-O2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-var1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-callable1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-callable2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data5.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-indcall1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-indcall2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-indcall3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run1.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run2.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run3.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4c.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4d.c
 create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4i.c
 create mode 100644 gcc/testsuite/g++.dg/strub-run1.C
 create mode 100644 gcc/testsuite/g++.dg/torture/strub-init1.C
 create mode 100644 gcc/testsuite/g++.dg/torture/strub-init2.C
 create mode 100644 gcc/testsuite/g++.dg/torture/strub-init3.C
 create mode 100644 gcc/testsuite/gnat.dg/strub_access.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_access1.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_attr.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_attr.ads
 create mode 100644 gcc/testsuite/gnat.dg/strub_disp.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_disp1.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind.ads
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind1.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind1.ads
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind2.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_ind2.ads
 create mode 100644 gcc/testsuite/gnat.dg/strub_intf.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_intf1.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_intf2.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_renm.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_renm1.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_renm2.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_var.adb
 create mode 100644 gcc/testsuite/gnat.dg/strub_var1.adb
 create mode 100644 libgcc/strub.c

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index e154f7c0055f9..68410a86af5a9 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1557,6 +1557,7 @@ OBJS = \
 	ipa-reference.o \
 	ipa-ref.o \
 	ipa-utils.o \
+	ipa-strub.o \
 	ipa.o \
 	ira.o \
 	ira-build.o \
@@ -2879,6 +2880,7 @@ GTFILES = $(CPPLIB_H) $(srcdir)/input.h $(srcdir)/coretypes.h \
   $(srcdir)/sanopt.cc \
   $(srcdir)/sancov.cc \
   $(srcdir)/ipa-devirt.cc \
+  $(srcdir)/ipa-strub.cc \
   $(srcdir)/internal-fn.h \
   $(srcdir)/calls.cc \
   $(srcdir)/omp-general.h \
diff --git a/gcc/ada/gcc-interface/trans.cc b/gcc/ada/gcc-interface/trans.cc
index 9c418beda9643..5e9e92d8b7257 100644
--- a/gcc/ada/gcc-interface/trans.cc
+++ b/gcc/ada/gcc-interface/trans.cc
@@ -69,6 +69,21 @@
 #include "ada-tree.h"
 #include "gigi.h"
 
+/* The following #include is for strub_make_callable.
+
+   This function marks a function as safe to call from strub contexts.  We mark
+   Ada subprograms that may be called implicitly by the compiler, and that won't
+   leave on the stack caller data passed to them.  This stops implicit calls
+   introduced in subprograms that have their stack scrubbed from being flagged
+   as unsafe, even in -fstrub=strict mode.
+
+   These subprograms are also marked with the strub(callable) attribute in Ada
+   sources, but their declarations aren't necessarily imported by GNAT, or made
+   visible to gigi, in units that end up relying on them.  So when gigi
+   introduces their declarations on its own, it must also add the attribute, by
+   calling strub_make_callable.  */
+#include "ipa-strub.h"
+
 /* We should avoid allocating more than ALLOCA_THRESHOLD bytes via alloca,
    for fear of running out of stack space.  If we need more, we use xmalloc
    instead.  */
@@ -454,6 +469,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)
     {
@@ -466,6 +482,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.  */
@@ -722,6 +739,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);
   set_call_expr_flags (result, ECF_NORETURN | ECF_XTHROW);
 
   return result;
diff --git a/gcc/ada/gcc-interface/utils.cc b/gcc/ada/gcc-interface/utils.cc
index f46454d6545a2..8d1237fdbb3e2 100644
--- a/gcc/ada/gcc-interface/utils.cc
+++ b/gcc/ada/gcc-interface/utils.cc
@@ -39,6 +39,7 @@
 #include "varasm.h"
 #include "toplev.h"
 #include "opts.h"
+#include "ipa-strub.h"
 #include "output.h"
 #include "debug.h"
 #include "convert.h"
@@ -6742,9 +6743,77 @@ handle_no_stack_protector_attribute (tree *node, tree name, tree, int,
    struct attribute_spec.handler.  */
 
 static tree
-handle_strub_attribute (tree *, tree, tree, int, bool *no_add_attrs)
+handle_strub_attribute (tree *node, tree name,
+			tree args,
+			int ARG_UNUSED (flags), bool *no_add_attrs)
 {
-  *no_add_attrs = true;
+  bool enable = true;
+
+  if (args && FUNCTION_POINTER_TYPE_P (*node))
+    *node = TREE_TYPE (*node);
+
+  if (args && FUNC_OR_METHOD_TYPE_P (*node))
+    {
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
+	{
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
+
+	case 0:
+	  warning (OPT_Wattributes,
+		   "%qE attribute ignored because of argument %qE",
+		   name, TREE_VALUE (args));
+	  *no_add_attrs = true;
+	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
+	}
+
+      args = TREE_CHAIN (args);
+    }
+
+  if (args)
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
+
+  /* Warn about unmet expectations that the strub attribute works like a
+     qualifier.  ??? Could/should we extend it to the element/field types
+     here?  */
+  if (TREE_CODE (*node) == ARRAY_TYPE
+      || VECTOR_TYPE_P (*node)
+      || TREE_CODE (*node) == COMPLEX_TYPE)
+    warning (OPT_Wattributes,
+	     "attribute %qE does not apply to elements"
+	     " of non-scalar type %qT",
+	     name, *node);
+  else if (RECORD_OR_UNION_TYPE_P (*node))
+    warning (OPT_Wattributes,
+	     "attribute %qE does not apply to fields"
+	     " of aggregate type %qT",
+	     name, *node);
+
+  /* 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 += 2;
+
   return NULL_TREE;
 }
 
diff --git a/gcc/attribs.cc b/gcc/attribs.cc
index eff99002fbb92..dd0408635670f 100644
--- a/gcc/attribs.cc
+++ b/gcc/attribs.cc
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "attribs.h"
 #include "fold-const.h"
+#include "ipa-strub.h"
 #include "stor-layout.h"
 #include "langhooks.h"
 #include "plugin.h"
@@ -789,8 +790,8 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  flags &= ~(int) ATTR_FLAG_TYPE_IN_PLACE;
 	}
 
-      if (spec->function_type_required && TREE_CODE (*anode) != FUNCTION_TYPE
-	  && TREE_CODE (*anode) != METHOD_TYPE)
+      if (spec->function_type_required
+	  && !FUNC_OR_METHOD_TYPE_P (*anode))
 	{
 	  if (TREE_CODE (*anode) == POINTER_TYPE
 	      && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
@@ -905,7 +906,24 @@ decl_attributes (tree *node, tree attributes, int flags,
 	      TYPE_NAME (tt) = *node;
 	    }
 
-	  *anode = cur_and_last_decl[0];
+	  if (*anode != cur_and_last_decl[0])
+	    {
+	      /* Even if !spec->function_type_required, allow the attribute
+		 handler to request the attribute to be applied to the function
+		 type, rather than to the function pointer type, by setting
+		 cur_and_last_decl[0] to the function type.  */
+	      if (!fn_ptr_tmp
+		  && POINTER_TYPE_P (*anode)
+		  && TREE_TYPE (*anode) == cur_and_last_decl[0]
+		  && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
+		{
+		  fn_ptr_tmp = TREE_TYPE (*anode);
+		  fn_ptr_quals = TYPE_QUALS (*anode);
+		  anode = &fn_ptr_tmp;
+		}
+	      *anode = cur_and_last_decl[0];
+	    }
+
 	  if (ret == error_mark_node)
 	    {
 	      warning (OPT_Wattributes, "%qE attribute ignored", name);
@@ -1508,9 +1526,20 @@ comp_type_attributes (const_tree type1, const_tree type2)
   if ((lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type1)) != NULL)
       ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) != NULL))
     return 0;
+  int strub_ret = strub_comptypes (CONST_CAST_TREE (type1),
+				   CONST_CAST_TREE (type2));
+  if (strub_ret == 0)
+    return strub_ret;
   /* As some type combinations - like default calling-convention - might
      be compatible, we have to call the target hook to get the final result.  */
-  return targetm.comp_type_attributes (type1, type2);
+  int target_ret = targetm.comp_type_attributes (type1, type2);
+  if (target_ret == 0)
+    return target_ret;
+  if (strub_ret == 2 || target_ret == 2)
+    return 2;
+  if (strub_ret == 1 && target_ret == 1)
+    return 1;
+  gcc_unreachable ();
 }
 
 /* PREDICATE acts as a function of type:
diff --git a/gcc/builtins.cc b/gcc/builtins.cc
index 4fc58a0bda9b8..555d2897938ca 100644
--- a/gcc/builtins.cc
+++ b/gcc/builtins.cc
@@ -71,6 +71,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"
@@ -151,6 +152,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);
@@ -5370,6 +5372,252 @@ expand_builtin_frame_address (tree fndecl, tree exp)
     }
 }
 
+#if ! 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 || 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 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);
+    }
+
+  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 (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);
+    }
+
+  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);
+
+#if ! 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);
+
+      /* ??? It would be nice to use setmem or similar patterns here,
+	 but they do not necessarily obey the stack growth direction,
+	 which has security implications.  We also have to avoid calls
+	 (memset, bzero or any machine-specific ones), which are
+	 likely unsafe here (see TARGET_STRUB_MAY_USE_MEMSET).  */
+#if ! STACK_GROWS_DOWNWARD
+      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));
+#else
+      rtx decr = plus_constant (Pmode, end, -ulen);
+      rtx dstm = gen_rtx_MEM (ptr_mode, end);
+
+      rtx_code_label *loop = gen_label_rtx ();
+      emit_label (loop);
+      emit_move_insn (end, force_operand (decr, NULL_RTX));
+      emit_move_insn (dstm, zero);
+#endif
+      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.  */
 
@@ -7705,6 +7953,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 33e6cad8ce14f..f03df32f98013 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -999,6 +999,10 @@ DEF_EXT_LIB_BUILTIN    (BUILT_IN_FFSLL, "ffsll", BT_FN_INT_LONGLONG, ATTR_CONST_
 DEF_GCC_BUILTIN        (BUILT_IN_FFSG, "ffsg", BT_FN_INT_VAR, ATTR_CONST_NOTHROW_TYPEGENERIC_LEAF)
 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.cc b/gcc/c-family/c-attribs.cc
index 45af07453ea3c..acc09e4b27a5b 100644
--- a/gcc/c-family/c-attribs.cc
+++ b/gcc/c-family/c-attribs.cc
@@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "common/common-target.h"
 #include "langhooks.h"
 #include "tree-inline.h"
+#include "ipa-strub.h"
 #include "toplev.h"
 #include "tree-iterator.h"
 #include "opts.h"
@@ -69,6 +70,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 *);
@@ -322,6 +324,8 @@ const struct attribute_spec c_common_gnu_attributes[] =
   { "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 },
@@ -1489,6 +1493,84 @@ 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 && FUNCTION_POINTER_TYPE_P (*node))
+    *node = TREE_TYPE (*node);
+
+  if (args && FUNC_OR_METHOD_TYPE_P (*node))
+    {
+      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
+	{
+	case 1:
+	case 2:
+	  enable = true;
+	  break;
+
+	case 0:
+	  warning (OPT_Wattributes,
+		   "%qE attribute ignored because of argument %qE",
+		   name, TREE_VALUE (args));
+	  *no_add_attrs = true;
+	  enable = false;
+	  break;
+
+	case -1:
+	case -2:
+	  enable = false;
+	  break;
+
+	default:
+	  gcc_unreachable ();
+	}
+
+      args = TREE_CHAIN (args);
+    }
+
+  if (args)
+    {
+      warning (OPT_Wattributes,
+	       "ignoring attribute %qE because of excess arguments"
+	       " starting at %qE",
+	       name, TREE_VALUE (args));
+      *no_add_attrs = true;
+      enable = false;
+    }
+
+  /* Warn about unmet expectations that the strub attribute works like a
+     qualifier.  ??? Could/should we extend it to the element/field types
+     here?  */
+  if (TREE_CODE (*node) == ARRAY_TYPE
+      || VECTOR_TYPE_P (*node)
+      || TREE_CODE (*node) == COMPLEX_TYPE)
+    warning (OPT_Wattributes,
+	     "attribute %qE does not apply to elements"
+	     " of non-scalar type %qT",
+	     name, *node);
+  else if (RECORD_OR_UNION_TYPE_P (*node))
+    warning (OPT_Wattributes,
+	     "attribute %qE does not apply to fields"
+	     " of aggregate type %qT",
+	     name, *node);
+
+  /* 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 += 2;
+
+  return NULL_TREE;
+}
+
 /* Handle a "noinline" attribute; arguments as in
    struct attribute_spec.handler.  */
 
diff --git a/gcc/cgraph.h b/gcc/cgraph.h
index cfdd9f693a889..2b32055761688 100644
--- a/gcc/cgraph.h
+++ b/gcc/cgraph.h
@@ -153,7 +153,7 @@ public:
   void remove (void);
 
   /* Undo any definition or use of the symbol.  */
-  void reset (void);
+  void reset (bool preserve_comdat_group = false);
 
   /* Dump symtab node to F.  */
   void dump (FILE *f);
diff --git a/gcc/cgraphunit.cc b/gcc/cgraphunit.cc
index bccd2f2abb5a3..9a550a5cce645 100644
--- a/gcc/cgraphunit.cc
+++ b/gcc/cgraphunit.cc
@@ -384,7 +384,7 @@ symbol_table::process_new_functions (void)
    functions or variables.  */
 
 void
-symtab_node::reset (void)
+symtab_node::reset (bool preserve_comdat_group)
 {
   /* Reset our data structures so we can analyze the function again.  */
   analyzed = false;
@@ -395,7 +395,8 @@ symtab_node::reset (void)
   cpp_implicit_alias = false;
 
   remove_all_references ();
-  remove_from_same_comdat_group ();
+  if (!preserve_comdat_group)
+    remove_from_same_comdat_group ();
 
   if (cgraph_node *cn = dyn_cast <cgraph_node *> (this))
     {
diff --git a/gcc/common.opt b/gcc/common.opt
index 161a035d736a3..f070aff8cbc1b 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2917,6 +2917,35 @@ fstrict-overflow
 Common
 Treat signed overflow as undefined.  Negated as -fwrapv -fwrapv-pointer.
 
+fstrub=disable
+Common RejectNegative Var(flag_strub, 0)
+Disable stack scrub entirely, disregarding strub attributes.
+
+fstrub=strict
+Common RejectNegative Var(flag_strub, -4)
+Enable stack scrub as per attributes, with strict call checking.
+
+; If any strub-enabling attribute is seen when the default or strict
+; initializer values are in effect, flag_strub is bumped up by 2.  The
+; scrub mode gate function will then bump these initializer values to
+; 0 if no strub-enabling attribute is seen.  This minimizes the strub
+; overhead.
+fstrub=relaxed
+Common RejectNegative Var(flag_strub, -3) Init(-3)
+Restore default strub mode: as per attributes, with relaxed checking.
+
+fstrub=all
+Common RejectNegative Var(flag_strub, 3)
+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.
+
 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 9592cfee1d279..e9bc9c4fe84c8 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 internal interfaces.
 * Vector Extensions::   Using vector instructions through built-in functions.
 * Offsetof::            Special syntax for implementing @code{offsetof}.
 * __sync Builtins::     Legacy built-in functions for atomic memory access.
@@ -9225,6 +9226,268 @@ pid_t wait (wait_status_ptr_t p)
 @}
 @end smallexample
 
+@cindex @code{strub} type attribute
+@item strub
+This attribute defines stack-scrubbing properties of functions and
+variables, so that functions that access sensitive data can have their
+stack frames zeroed-out upon returning or propagating exceptions.  This
+may be enabled explicitly, by selecting certain @code{strub} modes for
+specific functions, or implicitly, by means of @code{strub} variables.
+
+Being a type attribute, it attaches to types, even when specified in
+function and variable declarations.  When applied to function types, it
+takes an optional string argument.  When applied to a
+pointer-to-function type, if the optional argument is given, it gets
+propagated to the function type.
+
+@smallexample
+/* A strub variable.  */
+int __attribute__ ((strub)) var;
+/* A strub variable that happens to be a pointer.  */
+__attribute__ ((strub)) int *strub_ptr_to_int;
+/* A pointer type that may point to a strub variable.  */
+typedef int __attribute__ ((strub)) *ptr_to_strub_int_type;
+
+/* A declaration of a strub function.  */
+extern int __attribute__ ((strub)) foo (void);
+/* A pointer to that strub function.  */
+int __attribute__ ((strub ("at-calls"))) (*ptr_to_strub_fn)(void) = foo;
+@end smallexample
+
+A function associated with @code{at-calls} @code{strub} mode
+(@code{strub("at-calls")}, or just @code{strub}) undergoes interface
+changes.  Its callers are adjusted to match the changes, and to scrub
+(overwrite with zeros) the stack space used by the called function after
+it returns.  The interface change makes the function type incompatible
+with an unadorned but otherwise equivalent type, so @emph{every}
+declaration and every type that may be used to call the function must be
+associated with this strub mode.
+
+A function associated with @code{internal} @code{strub} mode
+(@code{strub("internal")}) retains an unmodified, type-compatible
+interface, but it may be turned into a wrapper that calls the wrapped
+body using a custom interface.  The wrapper then scrubs the stack space
+used by the wrapped body.  Though the wrapped body has its stack space
+scrubbed, the wrapper does not, so arguments and return values may
+remain unscrubbed even when such a function is called by another
+function that enables @code{strub}.  This is why, when compiling with
+@option{-fstrub=strict}, a @code{strub} context is not allowed to call
+@code{internal} @code{strub} functions.
+
+@smallexample
+/* A declaration of an internal-strub function.  */
+extern int __attribute__ ((strub ("internal"))) bar (void);
+
+int __attribute__ ((strub))
+baz (void)
+@{
+  /* Ok, foo was declared above as an at-calls strub function.  */
+  foo ();
+  /* Not allowed in strict mode, otherwise allowed.  */
+  bar ();
+@}
+@end smallexample
+
+An automatically-allocated variable associated with the @code{strub}
+attribute causes the (immediately) enclosing function to have
+@code{strub} enabled.
+
+A statically-allocated variable associated with the @code{strub}
+attribute causes functions that @emph{read} it, through its @code{strub}
+data type, to have @code{strub} enabled.  Reading data by dereferencing
+a pointer to a @code{strub} data type has the same effect.  Note: The
+attribute does not carry over from a composite type to the types of its
+components, so the intended effect may not be obtained with non-scalar
+types.
+
+When selecting a @code{strub}-enabled mode for a function that is not
+explicitly associated with one, because of @code{strub} variables or
+data pointers, the function must satisfy @code{internal} mode viability
+requirements (see below), even when @code{at-calls} mode is also viable
+and, being more efficient, ends up selected as an optimization.
+
+@smallexample
+/* zapme is implicitly strub-enabled because of strub variables.
+   Optimization may change its strub mode, but not the requirements.  */
+static int
+zapme (int i)
+@{
+  /* A local strub variable enables strub.  */
+  int __attribute__ ((strub)) lvar;
+  /* Reading strub data through a pointer-to-strub enables strub.  */
+  lvar = * (ptr_to_strub_int_type) &i;
+  /* Writing to a global strub variable does not enable strub.  */
+  var = lvar;
+  /* Reading from a global strub variable enables strub.  */
+  return var;
+@}
+@end smallexample
+
+A @code{strub} context is the body (as opposed to the interface) of a
+function that has @code{strub} enabled, be it explicitly, by
+@code{at-calls} or @code{internal} mode, or implicitly, due to
+@code{strub} variables or command-line options.
+
+A function of a type associated with the @code{disabled} @code{strub}
+mode (@code{strub("disabled")} will not have its own stack space
+scrubbed.  Such functions @emph{cannot} be called from within
+@code{strub} contexts.
+
+In order to enable a function to be called from within @code{strub}
+contexts without having its stack space scrubbed, associate it with the
+@code{callable} @code{strub} mode (@code{strub("callable")}).
+
+When a function is not assigned a @code{strub} mode, explicitly or
+implicitly, the mode defaults to @code{callable}, except when compiling
+with @option{-fstrub=strict}, that causes @code{strub} mode to default
+to @code{disabled}.
+
+@example
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+ /* Implicitly disabled with -fstrub=strict, otherwise callable.  */
+extern int bah (void);
+
+int __attribute__ ((strub))
+bal (void)
+@{
+  /* Not allowed, bad is not strub-callable.  */
+  bad ();
+  /* Ok, bac is strub-callable.  */
+  bac ();
+  /* Not allowed with -fstrub=strict, otherwise allowed.  */
+  bah ();
+@}
+@end example
+
+Function types marked @code{callable} and @code{disabled} are not
+mutually compatible types, but the underlying interfaces are compatible,
+so it is safe to convert pointers between them, and to use such pointers
+or alternate declarations to call them.  Interfaces are also
+interchangeable between them and @code{internal} (but not
+@code{at-calls}!), but adding @code{internal} to a pointer type will not
+cause the pointed-to function to perform stack scrubbing.
+
+@example
+void __attribute__ ((strub))
+bap (void)
+@{
+  /* Assign a callable function to pointer-to-disabled.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bac;
+  /* Not allowed: calls disabled type in a strub context.  */
+  d_p ();
+
+  /* Assign a disabled function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bad;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an internal function to pointer-to-callable.
+     Flagged as not quite compatible with -Wpedantic.  */
+  c_p = bar;
+  /* Ok, safe.  */
+  c_p ();
+
+  /* Assign an at-calls function to pointer-to-callable.
+     Flaggged as incompatible.  */
+  c_p = bal;
+  /* The call through an interface-incompatible type will not use the
+     modified interface expected by the at-calls function, so it is
+     likely to misbehave at runtime.  */
+  c_p ();
+@}
+@end example
+
+@code{Strub} contexts are never inlined into non-@code{strub} contexts.
+When an @code{internal}-strub function is split up, the wrapper can
+often be inlined, but the wrapped body @emph{never} is.  A function
+marked as @code{always_inline}, even if explicitly assigned
+@code{internal} strub mode, will not undergo wrapping, so its body gets
+inlined as required.
+
+@example
+inline int __attribute__ ((strub ("at-calls")))
+inl_atc (void)
+@{
+  /* This body may get inlined into strub contexts.  */
+@}
+
+inline int __attribute__ ((strub ("internal")))
+inl_int (void)
+@{
+  /* This body NEVER gets inlined, though its wrapper may.  */
+@}
+
+inline int __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+@{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+@}
+
+void __attribute__ ((strub ("disabled")))
+bat (void)
+@{
+  /* Not allowed, cannot inline into a non-strub context.  */
+  inl_int_ali ();
+@}
+@end example
+
+@cindex strub eligibility and viability
+Some @option{-fstrub=*} command line options enable @code{strub} modes
+implicitly where viable.  A @code{strub} mode is only viable for a
+function if the function is eligible for that mode, and if other
+conditions, detailed below, are satisfied.  If it's not eligible for a
+mode, attempts to explicitly associate it with that mode are rejected
+with an error message.  If it is eligible, that mode may be assigned
+explicitly through this attribute, but implicit assignment through
+command-line options may involve additional viability requirements.
+
+A function is ineligible for @code{at-calls} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, if attribute
+@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
+@code{At-calls} @code{strub} mode, if not requested through the function
+type, is only viable for an eligible function if the function is not
+visible to other translation units, if it doesn't have its address
+taken, and if it is never called with a function type overrider.
+
+@smallexample
+/* bar is eligible for at-calls strub mode,
+   but not viable for that mode because it is visible to other units.
+   It is eligible and viable for internal strub mode.  */
+void bav () @{@}
+
+/* setp is eligible for at-calls strub mode,
+   but not viable for that mode because its address is taken.
+   It is eligible and viable for internal strub mode.  */
+void setp (void) @{ static void (*p)(void); = setp; @}
+@end smallexample
+
+A function is ineligible for @code{internal} @code{strub} mode if a
+different @code{strub} mode is explicitly requested, or if attribute
+@code{noipa} is present.  For an @code{always_inline} function, meeting
+these requirements is enough to make it eligible.  Any function that has
+attribute @code{noclone}, that uses such extensions as non-local labels,
+computed gotos, alternate variable argument passing interfaces,
+@code{__builtin_next_arg}, or @code{__builtin_return_address}, or that
+takes too many (about 64Ki) arguments is ineligible, unless it is
+@code{always_inline}.  For @code{internal} @code{strub} mode, all
+eligible functions are viable.
+
+@smallexample
+/* flop is not eligible, thus not viable, for at-calls strub mode.
+   Likewise for internal strub mode.  */
+__attribute__ ((noipa)) void flop (void) @{@}
+
+/* flip is eligible and viable for at-calls strub mode.
+   It would be ineligible for internal strub mode, because of noclone,
+   if it weren't for always_inline.  With always_inline, noclone is not
+   an obstacle, so it is also eligible and viable for internal strub mode.  */
+inline __attribute__ ((noclone, always_inline)) void flip (void) @{@}
+@end smallexample
+
 @cindex @code{unused} type attribute
 @item unused
 When attached to a type (including a @code{union} or a @code{struct}),
@@ -12375,6 +12638,55 @@ option is in effect.  Such calls should only be made in debugging
 situations.
 @enddefbuiltin
 
+@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 internal interfaces
+
+Stack scrubbing involves cooperation between a @code{strub} context,
+i.e., a function whose stack frame is to be zeroed-out, and its callers.
+The caller initializes a stack watermark, the @code{strub} context
+updates the watermark according to its stack use, and the caller zeroes
+it out once it regains control, whether by the callee's returning or by
+an exception.
+
+Each of these steps is performed by a different builtin function call.
+Calls to these builtins are introduced automatically, in response to
+@code{strub} attributes and command-line options; they are not expected
+to be explicitly called by source code.
+
+The functions that implement the builtins are available in libgcc but,
+depending on optimization levels, they are expanded internally, adjusted
+to account for inlining, and sometimes combined/deferred (e.g. passing
+the caller-supplied watermark on to callees, refraining from erasing
+stack areas that the caller will) to enable tail calls and to optimize
+for code size.
+
+@deftypefn {Built-in Function} {void} __builtin___strub_enter (void **@var{wmptr})
+This function initializes a stack @var{watermark} variable with the
+current top of the stack.  A call to this builtin function is introduced
+before entering a @code{strub} context.  It remains as a function call
+if optimization is not enabled.
+@end deftypefn
+
+@deftypefn {Built-in Function} {void} __builtin___strub_update (void **@var{wmptr})
+This function updates a stack @var{watermark} variable with the current
+top of the stack, if it tops the previous watermark.  A call to this
+builtin function is inserted within @code{strub} contexts, whenever
+additional stack space may have been used.  It remains as a function
+call at optimization levels lower than 2.
+@end deftypefn
+
+@deftypefn {Built-in Function} {void} __builtin___strub_leave (void **@var{wmptr})
+This function overwrites the memory area between the current top of the
+stack, and the @var{watermark}ed address.  A call to this builtin
+function is inserted after leaving a @code{strub} context.  It remains
+as a function call at optimization levels lower than 3, and it is guarded by
+a condition at level 2.
+@end deftypefn
+
 @node Vector Extensions
 @section Using Vector Instructions through Built-in Functions
 
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 2fab4c5d71fd0..04d7ecd4593e1 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -654,6 +654,8 @@ Objective-C and Objective-C++ Dialects}.
 -fstack-protector-explicit  -fstack-check
 -fstack-limit-register=@var{reg}  -fstack-limit-symbol=@var{sym}
 -fno-stack-limit  -fsplit-stack
+-fstrub=disable  -fstrub=strict  -fstrub=relaxed
+-fstrub=all  -fstrub=at-calls  -fstrub=internal
 -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 -fvtv-counts  -fvtv-debug
 -finstrument-functions  -finstrument-functions-once
@@ -17890,6 +17892,56 @@ 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.
 
+@opindex -fstrub=disable
+@item -fstrub=disable
+Disable stack scrubbing entirely, ignoring any @code{strub} attributes.
+See @xref{Common Type Attributes}.
+
+@opindex fstrub=strict
+@item -fstrub=strict
+Functions default to @code{strub} mode @code{disabled}, and apply
+@option{strict}ly the restriction that only functions associated with
+@code{strub}-@code{callable} modes (@code{at-calls}, @code{callable} and
+@code{always_inline} @code{internal}) are @code{callable} by functions
+with @code{strub}-enabled modes (@code{at-calls} and @code{internal}).
+
+@opindex fstrub=relaxed
+@item -fstrub=relaxed
+Restore the default stack scrub (@code{strub}) setting, namely,
+@code{strub} is only enabled as required by @code{strub} attributes
+associated with function and data types.  @code{Relaxed} means that
+strub contexts are only prevented from calling functions explicitly
+associated with @code{strub} mode @code{disabled}.  This option is only
+useful to override other @option{-fstrub=*} options that precede it in
+the command line.
+
+@opindex fstrub=at-calls
+@item -fstrub=at-calls
+Enable @code{at-calls} @code{strub} mode where viable.  The primary use
+of this option is for testing.  It exercises the @code{strub} machinery
+in scenarios strictly local to a translation unit.  This @code{strub}
+mode modifies function interfaces, so any function that is visible to
+other translation units, or that has its address taken, will @emph{not}
+be affected by this option.  Optimization options may also affect
+viability.  See the @code{strub} attribute documentation for details on
+viability and eligibility requirements.
+
+@opindex fstrub=internal
+@item -fstrub=internal
+Enable @code{internal} @code{strub} mode where viable.  The primary use
+of this option is for testing.  This option is intended to exercise
+thoroughly parts of the @code{strub} machinery that implement the less
+efficient, but interface-preserving @code{strub} mode.  Functions that
+would not be affected by this option are quite uncommon.
+
+@opindex fstrub=all
+@item -fstrub=all
+Enable some @code{strub} mode where viable.  When both strub modes are
+viable, @code{at-calls} is preferred.  @option{-fdump-ipa-strubm} adds
+function attributes that tell which mode was selected for each function.
+The primary use of this option is for testing, to exercise thoroughly
+the @code{strub} machinery.
+
 @opindex fvtable-verify
 @item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
 This option is only available when compiling C++ code.
@@ -19809,6 +19861,14 @@ and inlining decisions.
 @item inline
 Dump after function inlining.
 
+@item strubm
+Dump after selecting @code{strub} modes, and recording the selections as
+function attributes.
+
+@item strub
+Dump @code{strub} transformations: interface changes, function wrapping,
+and insertion of builtin calls for stack scrubbing and watermarking.
+
 @end table
 
 Additionally, the options @option{-optimized}, @option{-missed},
diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index 7c5d2e523601d..c4b2ee4b8091a 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -3450,6 +3450,25 @@ in DWARF 2 debug information.  The default is zero.  A different value
 may reduce the size of debug information on some ports.
 @end defmac
 
+@defmac TARGET_STRUB_USE_DYNAMIC_ARRAY
+If defined to nonzero, @code{__strub_leave} will allocate a dynamic
+array covering the stack range that needs scrubbing before clearing it.
+Allocating the array tends to make scrubbing slower, but it enables the
+scrubbing to be safely implemented with a @code{memset} call, which
+could make up for the difference.
+@end defmac
+
+@defmac TARGET_STRUB_MAY_USE_MEMSET
+If defined to nonzero, enable @code{__strub_leave} to be optimized so as
+to call @code{memset} for stack scrubbing.  This is only enabled by
+default if @code{TARGET_STRUB_USE_DYNAMIC_ARRAY} is enabled; it's not
+advisable to enable it otherwise, since @code{memset} would then likely
+overwrite its own stack frame, but it might work if the target ABI
+enables @code{memset} to not use the stack at all, not even for
+arguments or its return address, and its implementation is trivial
+enough that it doesn't use a stack frame.
+@end defmac
+
 @node Exception Handling
 @subsection Exception Handling Support
 @cindex exception handling
diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
index c24493add5748..9cbde5f8b740a 100644
--- a/gcc/doc/tm.texi.in
+++ b/gcc/doc/tm.texi.in
@@ -2686,6 +2686,25 @@ in DWARF 2 debug information.  The default is zero.  A different value
 may reduce the size of debug information on some ports.
 @end defmac
 
+@defmac TARGET_STRUB_USE_DYNAMIC_ARRAY
+If defined to nonzero, @code{__strub_leave} will allocate a dynamic
+array covering the stack range that needs scrubbing before clearing it.
+Allocating the array tends to make scrubbing slower, but it enables the
+scrubbing to be safely implemented with a @code{memset} call, which
+could make up for the difference.
+@end defmac
+
+@defmac TARGET_STRUB_MAY_USE_MEMSET
+If defined to nonzero, enable @code{__strub_leave} to be optimized so as
+to call @code{memset} for stack scrubbing.  This is only enabled by
+default if @code{TARGET_STRUB_USE_DYNAMIC_ARRAY} is enabled; it's not
+advisable to enable it otherwise, since @code{memset} would then likely
+overwrite its own stack frame, but it might work if the target ABI
+enables @code{memset} to not use the stack at all, not even for
+arguments or its return address, and its implementation is trivial
+enough that it doesn't use a stack frame.
+@end defmac
+
 @node Exception Handling
 @subsection Exception Handling Support
 @cindex exception handling
diff --git a/gcc/gengtype-lex.l b/gcc/gengtype-lex.l
index 34837d9dc9a8f..a7bb44cf2b9ad 100644
--- a/gcc/gengtype-lex.l
+++ b/gcc/gengtype-lex.l
@@ -165,6 +165,9 @@ CXX_KEYWORD inline|public:|private:|protected:|template|operator|friend|static|m
 [(){},*:<>;=%/|+\!\?\.-]	{ return yytext[0]; }
 
    /* ignore pp-directives */
+^{HWS}"#"{HWS}[a-z_]+([^\n]*"\\"\n)+[^\n]*\n   {
+  update_lineno (yytext, yyleng);
+}
 ^{HWS}"#"{HWS}[a-z_]+[^\n]*\n   {lexer_line.line++;}
 
 .				{
diff --git a/gcc/ipa-inline.cc b/gcc/ipa-inline.cc
index dc120e6da5af6..dbc3c7e8fdc88 100644
--- a/gcc/ipa-inline.cc
+++ b/gcc/ipa-inline.cc
@@ -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"
 
 /* Inliner uses greedy algorithm to inline calls in a priority order.
    Badness is used as the key in a Fibonacci heap which roughly corresponds
@@ -443,6 +444,11 @@ can_inline_edge_p (struct cgraph_edge *e, bool report,
       inlinable = false;
     }
 
+  if (inlinable && !strub_inlinable_to_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.cc b/gcc/ipa-split.cc
index 6730f4f9d0e31..1a7285ff5dcf8 100644
--- a/gcc/ipa-split.cc
+++ b/gcc/ipa-split.cc
@@ -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.  */
 
@@ -1811,6 +1812,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.cc b/gcc/ipa-strub.cc
new file mode 100644
index 0000000000000..293bec132b885
--- /dev/null
+++ b/gcc/ipa-strub.cc
@@ -0,0 +1,3573 @@
+/* strub (stack scrubbing) support.
+   Copyright (C) 2021-2023 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"
+#include "symtab-thunks.h"
+#include "attr-fnspec.h"
+
+/* This file introduces two passes that, together, implement
+   machine-independent stack scrubbing, strub for short.  It arranges
+   for stack frames that have strub enabled to be zeroed-out after
+   relinquishing control to a caller, whether by returning or by
+   propagating an exception.  This admittedly unusual design decision
+   was driven by exception support (one needs a stack frame to be
+   active to propagate exceptions out of it), and it enabled an
+   implementation that is entirely machine-independent (no custom
+   epilogue code is required).
+
+   Strub modes can be selected for stack frames by attaching attribute
+   strub to functions or to variables (to their types, actually).
+   Different strub modes, with different implementation details, are
+   available, and they can be selected by an argument to the strub
+   attribute.  When enabled by strub-enabled variables, whether by
+   accessing (as in reading from) statically-allocated ones, or by
+   introducing (as in declaring) automatically-allocated ones, a
+   suitable mode is selected automatically.
+
+   At-calls mode modifies the interface of a function, adding a stack
+   watermark argument, that callers use to clean up the stack frame of
+   the called function.  Because of the interface change, it can only
+   be used when explicitly selected, or when a function is internal to
+   a translation unit.  Strub-at-calls function types are distinct
+   from their original types (they're not modified in-place), and they
+   are not interchangeable with other function types.
+
+   Internal mode, in turn, does not modify the type or the interface
+   of a function.  It is currently implemented by turning the function
+   into a wrapper, moving the function body to a separate wrapped
+   function, and scrubbing the wrapped body's stack in the wrapper.
+   Internal-strub function types are mostly interface-compatible with
+   other strub modes, namely callable (from strub functions, though
+   not strub-enabled) and disabled (not callable from strub
+   functions).
+
+   Always_inline functions can be strub functions, but they can only
+   be called from other strub functions, because strub functions must
+   never be inlined into non-strub functions.  Internal and at-calls
+   modes are indistinguishable when it comes to always_inline
+   functions: they will necessarily be inlined into another strub
+   function, and will thus be integrated into the caller's stack
+   frame, whatever the mode.  (Contrast with non-always_inline strub
+   functions: an at-calls function can be called from other strub
+   functions, ensuring no discontinuity in stack erasing, whereas an
+   internal-strub function can only be called from other strub
+   functions if it happens to be inlined, or if -fstrub=relaxed mode
+   is in effect (that's the default).  In -fstrub=strict mode,
+   internal-strub functions are not callable from strub functions,
+   because the wrapper itself is not strubbed.
+
+   The implementation involves two simple-IPA passes.  The earliest
+   one, strub-mode, assigns strub modes to functions.  It needs to run
+   before any inlining, so that we can prevent inlining of strub
+   functions into non-strub functions.  It notes explicit strub mode
+   requests, enables strub in response to strub variables and testing
+   options, and flags unsatisfiable requests.
+
+   Three possibilities of unsatisfiable requests come to mind: (a)
+   when a strub mode is explicitly selected, but the function uses
+   features that make it ineligible for that mode (e.g. at-calls rules
+   out calling __builtin_apply_args, because of the interface changes,
+   and internal mode rules out noclone or otherwise non-versionable
+   functions, non-default varargs, non-local or forced labels, and
+   functions with far too many arguments); (b) when some strub mode
+   must be enabled because of a strub variable, but the function is
+   not eligible or not viable for any mode; and (c) when
+   -fstrub=strict is enabled, and calls are found in strub functions
+   to functions that are not callable from strub contexts.
+   compute_strub_mode implements (a) and (b), and verify_strub
+   implements (c).
+
+   The second IPA pass modifies interfaces of at-calls-strub functions
+   and types, introduces strub calls in and around them. and splits
+   internal-strub functions.  It is placed after early inlining, so
+   that even internal-strub functions get a chance of being inlined
+   into other strub functions, but before non-early inlining, so that
+   internal-strub wrapper functions still get a chance of inlining
+   after splitting.
+
+   Wrappers avoid duplicating the copying of large arguments again by
+   passing them by reference to the wrapped bodies.  This involves
+   occasional SSA rewriting of address computations, because of the
+   additional indirection.  Besides these changes, and the
+   introduction of the stack watermark parameter, wrappers and wrapped
+   functions cooperate to handle variable argument lists (performing
+   va_start in the wrapper, passing the list as an argument, and
+   replacing va_start calls in the wrapped body with va_copy), and
+   __builtin_apply_args (also called in the wrapper and passed to the
+   wrapped body as an argument).
+
+   Strub bodies (both internal-mode wrapped bodies, and at-calls
+   functions) always start by adjusting the watermark parameter, by
+   calling __builtin___strub_update.  The compiler inserts them in the
+   main strub pass.  Allocations of additional stack space for the
+   frame (__builtin_alloca) are also followed by watermark updates.
+   Stack space temporarily allocated to pass arguments to other
+   functions, released right after the call, is not regarded as part
+   of the frame.  Around calls to them, i.e., in internal-mode
+   wrappers and at-calls callers (even calls through pointers), calls
+   to __builtin___strub_enter and __builtin___strub_leave are
+   inserted, the latter as a __finally block, so that it runs at
+   regular and exceptional exit paths.  strub_enter only initializes
+   the stack watermark, and strub_leave is where the scrubbing takes
+   place, overwriting with zeros the stack space from the top of the
+   stack to the watermark.
+
+   These calls can be optimized in various cases.  In
+   pass_ipa_strub::adjust_at_calls_call, for example, we enable
+   tail-calling and other optimized calls from one strub body to
+   another by passing on the watermark parameter.  The builtins
+   themselves may undergo inline substitution during expansion,
+   dependign on optimization levels.  This involves dealing with stack
+   red zones (when the builtins are called out-of-line, the red zone
+   cannot be used) and other ugly details related with inlining strub
+   bodies into other strub bodies (see expand_builtin_strub_update).
+   expand_builtin_strub_leave may even perform partial inline
+   substitution.  */
+
+/* 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,
+
+};
+
+/* Look up a strub attribute in TYPE, and return it.  */
+
+static tree
+get_strub_attr_from_type (tree type)
+{
+  return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
+}
+
+/* Look up a strub attribute in DECL or in its type, and return it.  */
+
+static tree
+get_strub_attr_from_decl (tree decl)
+{
+  tree ret = lookup_attribute ("strub", DECL_ATTRIBUTES (decl));
+  if (ret)
+    return ret;
+  return get_strub_attr_from_type (TREE_TYPE (decl));
+}
+
+#define STRUB_ID_COUNT		8
+#define STRUB_IDENT_COUNT	3
+#define STRUB_TYPE_COUNT	5
+
+#define STRUB_ID_BASE		0
+#define STRUB_IDENT_BASE	(STRUB_ID_BASE + STRUB_ID_COUNT)
+#define STRUB_TYPE_BASE		(STRUB_IDENT_BASE + STRUB_IDENT_COUNT)
+#define STRUB_CACHE_SIZE	(STRUB_TYPE_BASE + STRUB_TYPE_COUNT)
+
+/* Keep the strub mode and temp identifiers and types from being GC'd.  */
+static GTY((deletable)) tree strub_cache[STRUB_CACHE_SIZE];
+
+/* Define a function to cache identifier ID, to be used as a strub attribute
+   parameter for a strub mode named after NAME.  */
+#define DEF_STRUB_IDS(IDX, NAME, ID)				\
+static inline tree get_strub_mode_id_ ## NAME () {		\
+  int idx = STRUB_ID_BASE + IDX;				\
+  tree identifier = strub_cache[idx];				\
+  if (!identifier)						\
+    strub_cache[idx] = identifier = get_identifier (ID);	\
+  return identifier;						\
+}
+/* Same as DEF_STRUB_IDS, but use the string expansion of NAME as ID.  */
+#define DEF_STRUB_ID(IDX, NAME)			\
+  DEF_STRUB_IDS (IDX, NAME, #NAME)
+
+/* Define functions for each of the strub mode identifiers.
+   Expose dashes rather than underscores.  */
+DEF_STRUB_ID (0, disabled)
+DEF_STRUB_IDS (1, at_calls, "at-calls")
+DEF_STRUB_ID (2, internal)
+DEF_STRUB_ID (3, callable)
+DEF_STRUB_ID (4, wrapped)
+DEF_STRUB_ID (5, wrapper)
+DEF_STRUB_ID (6, inlinable)
+DEF_STRUB_IDS (7, at_calls_opt, "at-calls-opt")
+
+/* Release the temporary macro names.  */
+#undef DEF_STRUB_IDS
+#undef DEF_STRUB_ID
+
+/* Return the identifier corresponding to strub MODE.  */
+
+static tree
+get_strub_mode_attr_parm (enum strub_mode mode)
+{
+  switch (mode)
+    {
+    case STRUB_DISABLED:
+      return get_strub_mode_id_disabled ();
+
+    case STRUB_AT_CALLS:
+      return get_strub_mode_id_at_calls ();
+
+    case STRUB_INTERNAL:
+      return get_strub_mode_id_internal ();
+
+    case STRUB_CALLABLE:
+      return get_strub_mode_id_callable ();
+
+    case STRUB_WRAPPED:
+      return get_strub_mode_id_wrapped ();
+
+    case STRUB_WRAPPER:
+      return get_strub_mode_id_wrapper ();
+
+    case STRUB_INLINABLE:
+      return get_strub_mode_id_inlinable ();
+
+    case STRUB_AT_CALLS_OPT:
+      return get_strub_mode_id_at_calls_opt ();
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+/* Return the parmeters (TREE_VALUE) for a strub attribute of MODE.
+   We know we use a single parameter, so we bypass the creation of a
+   tree list.  */
+
+static tree
+get_strub_mode_attr_value (enum strub_mode mode)
+{
+  return get_strub_mode_attr_parm (mode);
+}
+
+/* Determine whether ID is a well-formed strub mode-specifying attribute
+   parameter for a function (type).  Only user-visible modes are accepted, and
+   ID must be non-NULL.
+
+   For unacceptable parms, return 0, otherwise a nonzero value as below.
+
+   If the parm enables strub, return positive, otherwise negative.
+
+   If the affected type must be a distinct, incompatible type,return an integer
+   of absolute value 2, otherwise 1.  */
+
+int
+strub_validate_fn_attr_parm (tree id)
+{
+  int ret;
+  const char *s = NULL;
+  size_t len = 0;
+
+  /* do NOT test for NULL.  This is only to be called with non-NULL arguments.
+     We assume that the strub parameter applies to a function, because only
+     functions accept an explicit argument.  If we accepted NULL, and we
+     happened to be called to verify the argument for a variable, our return
+     values would be wrong.  */
+  if (TREE_CODE (id) == STRING_CST)
+    {
+      s = TREE_STRING_POINTER (id);
+      len = TREE_STRING_LENGTH (id) - 1;
+    }
+  else if (TREE_CODE (id) == IDENTIFIER_NODE)
+    {
+      s = IDENTIFIER_POINTER (id);
+      len = IDENTIFIER_LENGTH (id);
+    }
+  else
+    return 0;
+
+  enum strub_mode mode;
+
+  if (len != 8)
+    return 0;
+
+  switch (s[0])
+    {
+    case 'd':
+      mode = STRUB_DISABLED;
+      ret = -1;
+      break;
+
+    case 'a':
+      mode = STRUB_AT_CALLS;
+      ret = 2;
+      break;
+
+    case 'i':
+      mode = STRUB_INTERNAL;
+      ret = 1;
+      break;
+
+    case 'c':
+      mode = STRUB_CALLABLE;
+      ret = -2;
+      break;
+
+    default:
+      /* Other parms are for internal use only.  */
+      return 0;
+    }
+
+  tree mode_id = get_strub_mode_attr_parm (mode);
+
+  if (TREE_CODE (id) == IDENTIFIER_NODE
+      ? id != mode_id
+      : strncmp (s, IDENTIFIER_POINTER (mode_id), len) != 0)
+    return 0;
+
+  return ret;
+}
+
+/* Return the strub mode from STRUB_ATTR.  VAR_P should be TRUE if the attribute
+   is taken from a variable, rather than from a function, or a type thereof.  */
+
+static enum strub_mode
+get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
+{
+  enum strub_mode mode = STRUB_DISABLED;
+
+  if (strub_attr)
+    {
+      if (!TREE_VALUE (strub_attr))
+	mode = !var_p ? STRUB_AT_CALLS : STRUB_INTERNAL;
+      else
+	{
+	  gcc_checking_assert (!var_p);
+	  tree id = TREE_VALUE (strub_attr);
+	  if (TREE_CODE (id) == TREE_LIST)
+	    id = TREE_VALUE (id);
+	  const char *s = (TREE_CODE (id) == STRING_CST
+			   ? TREE_STRING_POINTER (id)
+			   : IDENTIFIER_POINTER (id));
+	  size_t len = (TREE_CODE (id) == STRING_CST
+			? TREE_STRING_LENGTH (id) - 1
+			: IDENTIFIER_LENGTH (id));
+
+	  switch (len)
+	    {
+	    case 7:
+	      switch (s[6])
+		{
+		case 'r':
+		  mode = STRUB_WRAPPER;
+		  break;
+
+		case 'd':
+		  mode = STRUB_WRAPPED;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 8:
+	      switch (s[0])
+		{
+		case 'd':
+		  mode = STRUB_DISABLED;
+		  break;
+
+		case 'a':
+		  mode = STRUB_AT_CALLS;
+		  break;
+
+		case 'i':
+		  mode = STRUB_INTERNAL;
+		  break;
+
+		case 'c':
+		  mode = STRUB_CALLABLE;
+		  break;
+
+		default:
+		  gcc_unreachable ();
+		}
+	      break;
+
+	    case 9:
+	      mode = STRUB_INLINABLE;
+	      break;
+
+	    case 12:
+	      mode = STRUB_AT_CALLS_OPT;
+	      break;
+
+	    default:
+	      gcc_unreachable ();
+	    }
+
+	  gcc_checking_assert (TREE_CODE (id) == IDENTIFIER_NODE
+			       ? id == get_strub_mode_attr_parm (mode)
+			       : strncmp (IDENTIFIER_POINTER
+					  (get_strub_mode_attr_parm (mode)),
+					  s, len) == 0);
+	}
+    }
+
+  return mode;
+}
+
+/* Look up, decode and return the strub mode associated with FNDECL.  */
+
+static enum strub_mode
+get_strub_mode_from_fndecl (tree fndecl)
+{
+  return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
+}
+
+/* Look up, decode and return the strub mode associated with NODE.  */
+
+static enum strub_mode
+get_strub_mode (cgraph_node *node)
+{
+  return get_strub_mode_from_fndecl (node->decl);
+}
+
+/* Look up, decode and return the strub mode associated with TYPE.  */
+
+static enum strub_mode
+get_strub_mode_from_type (tree type)
+{
+  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 >= -1 && !var_p)
+    return STRUB_CALLABLE;
+
+  return STRUB_DISABLED;
+}
+
+\f
+/* Return TRUE iff NODE calls builtin va_start.  */
+
+static bool
+calls_builtin_va_start_p (cgraph_node *node)
+{
+  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;
+}
+
+/* Return TRUE iff NODE calls builtin apply_args, and optionally REPORT it.  */
+
+static bool
+calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
+{
+  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 (e->call_stmt
+		? gimple_location (e->call_stmt)
+		: DECL_SOURCE_LOCATION (node->decl),
+		"at-calls %<strub%> does not support call to %qD",
+		cdecl);
+    }
+
+  return result;
+}
+
+/* Return TRUE iff NODE carries the always_inline attribute.  */
+
+static inline bool
+strub_always_inline_p (cgraph_node *node)
+{
+  return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
+}
+
+/* Return TRUE iff NODE is potentially eligible for any strub-enabled mode, and
+   optionally REPORT the reasons for ineligibility.  */
+
+static inline bool
+can_strub_p (cgraph_node *node, bool report = false)
+{
+  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;
+}
+
+/* Return TRUE iff NODE is eligible for at-calls strub, and optionally REPORT
+   the reasons for ineligibility.  Besides general non-eligibility for
+   strub-enabled modes, at-calls rules out calling builtin apply_args.  */
+
+static bool
+can_strub_at_calls_p (cgraph_node *node, bool report = false)
+{
+  bool result = !report || can_strub_p (node, report);
+
+  if (!result && !report)
+    return result;
+
+  return !calls_builtin_apply_args_p (node, report);
+}
+
+/* Return TRUE iff the called function (pointer or, if available,
+   decl) undergoes a significant type conversion for the call.  Strub
+   mode changes between function types, and other non-useless type
+   conversions, are regarded as significant.  When the function type
+   is overridden, the effective strub mode for the call is that of the
+   call fntype, rather than that of the pointer or of the decl.
+   Functions called with type overrides cannot undergo type changes;
+   it's as if their address was taken, so they're considered
+   non-viable for implicit at-calls strub mode.  */
+
+static inline bool
+strub_call_fntype_override_p (const gcall *gs)
+{
+  if (gimple_call_internal_p (gs))
+    return false;
+  tree fn_type = TREE_TYPE (TREE_TYPE (gimple_call_fn (gs)));
+  if (tree decl = gimple_call_fndecl (gs))
+    fn_type = TREE_TYPE (decl);
+
+  /* We do NOT want to take the mode from the decl here.  This
+     function is used to tell whether we can change the strub mode of
+     a function, and whether the effective mode for the call is to be
+     taken from the decl or from an overrider type.  When the strub
+     mode is explicitly declared, or overridden with a type cast, the
+     difference will be noticed in function types.  However, if the
+     strub mode is implicit due to e.g. strub variables or -fstrub=*
+     command-line flags, we will adjust call types along with function
+     types.  In either case, the presence of type or strub mode
+     overriders in calls will prevent a function from having its strub
+     modes changed in ways that would imply type changes, but taking
+     strub modes from decls would defeat this, since we set strub
+     modes and then call this function to tell whether the original
+     type was overridden to decide whether to adjust the call.  We
+     need the answer to be about the type, not the decl.  */
+  enum strub_mode mode = get_strub_mode_from_type (fn_type);
+  return (get_strub_mode_from_type (gs->u.fntype) != mode
+	  || !useless_type_conversion_p (gs->u.fntype, fn_type));
+}
+
+/* Return TRUE iff NODE is called directly with a type override.  */
+
+static bool
+called_directly_with_type_override_p (cgraph_node *node, void *)
+{
+  for (cgraph_edge *e = node->callers; e; e = e->next_caller)
+    if (e->call_stmt && strub_call_fntype_override_p (e->call_stmt))
+      return true;
+
+  return false;
+}
+
+/* Return TRUE iff NODE or any other nodes aliased to it are called
+   with type overrides.  We can't safely change the type of such
+   functions.  */
+
+static bool
+called_with_type_override_p (cgraph_node *node)
+{
+  return (node->call_for_symbol_thunks_and_aliases
+	  (called_directly_with_type_override_p, NULL, true, true));
+}
+
+/* Symbolic macro for the max number of arguments that internal strub may add to
+   a function.  */
+
+#define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
+
+/* We can't perform internal strubbing if the function body involves certain
+   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 (e->call_stmt
+		    ? gimple_location (e->call_stmt)
+		    : DECL_SOURCE_LOCATION (node->decl),
+		    "%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 references are not preserved when copying.  If referenced
+	 in nested functions, as in 920415-1.c and 920721-4.c their decls get
+	 remapped independently.  The exclusion below might be too broad, in
+	 that we might be able to support correctly cases in which the labels
+	 are only used internally in a function, but disconnecting forced labels
+	 from their original declarations is undesirable in general.  */
+      basic_block bb;
+      FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+	for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+	     !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);
+
+	    if (!FORCED_LABEL (target))
+	      continue;
+
+	    result = false;
+
+	    if (!report)
+	      return result;
+
+	    sorry_at (gimple_location (label_stmt),
+		      "internal %<strub%> does not support forced 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;
+}
+
+/* Return TRUE iff NODE has any strub-requiring local variable, or accesses (as
+   in reading) any variable through a strub-requiring type.  */
+
+static bool
+strub_from_body_p (cgraph_node *node)
+{
+  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-requiring types.
+     ??? Compound types don't propagate the strub requirement to
+     component types.  */
+  basic_block bb;
+  FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
+    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+	 !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;
+    }
+}
+
+/* Compute the strub mode to be used for NODE.  STRUB_ATTR should be the strub
+   attribute,found for NODE, if any.  */
+
+static enum strub_mode
+compute_strub_mode (cgraph_node *node, tree strub_attr)
+{
+  enum strub_mode req_mode = get_strub_mode_from_attr (strub_attr);
+
+  gcc_checking_assert (flag_strub >= -2 && flag_strub <= 3);
+
+  /* Symbolic encodings of the -fstrub-* flags.  */
+  /* Enable strub when explicitly requested through attributes to functions or
+     variables, reporting errors if the requests cannot be satisfied.  */
+  const bool strub_flag_auto = flag_strub < 0;
+  /* strub_flag_auto with strub call verification; without this, functions are
+     implicitly callable.  */
+  const bool strub_flag_strict = flag_strub < -1;
+  /* Disable strub altogether, ignore attributes entirely.  */
+  const bool strub_flag_disabled = flag_strub == 0;
+  /* On top of _auto, also enable strub implicitly for functions that can
+     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_strict
+	      || 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->externally_visible
+		   || (node->binds_to_current_def_p ()
+		       && node->can_be_local_p ()))
+	       && node->only_called_directly_p ()
+	       && !called_with_type_override_p (node))));
+  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)
+       : (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;
+}
+
+/* Set FNDT's strub mode to callable.
+   FNDT may be a function decl or a function type.  */
+
+void
+strub_make_callable (tree fndt)
+{
+  strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
+}
+
+/* Set NODE to strub MODE.  Report incompatibilities between MODE and the mode
+   requested through explicit attributes, and cases of non-eligibility.  */
+
+static void
+set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
+{
+  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 %qE selected for %qD, when %qE was requested",
+		    get_strub_mode_attr_parm (mode),
+		    node->decl,
+		    get_strub_mode_attr_parm (req_mode));
+	  if (node->alias)
+	    {
+	      cgraph_node *target = node->ultimate_alias_target ();
+	      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);
+}
+
+/* Compute and set NODE's strub mode.  */
+
+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);
+}
+
+\f
+/* Non-strub functions shouldn't be called from within strub contexts,
+   except through callable ones.  Always inline strub functions can
+   only be called from strub functions.  */
+
+static bool
+strub_callable_from_p (strub_mode caller_mode, strub_mode callee_mode)
+{
+  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 >= -1);
+
+    case STRUB_DISABLED:
+      return false;
+
+    case STRUB_CALLABLE:
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  return true;
+}
+
+/* Return TRUE iff CALLEE can be inlined into CALLER.  We wish to avoid inlining
+   WRAPPED functions back into their WRAPPERs.  More generally, we wish to avoid
+   inlining strubbed functions into non-strubbed ones.  CALLER doesn't have to
+   be an immediate caller of CALLEE: the immediate caller may have already been
+   cloned for inlining, and then CALLER may be further up the original call
+   chain.  ???  It would be nice if our own caller would retry inlining callee
+   if caller gets inlined.  */
+
+bool
+strub_inlinable_to_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:
+      /* When we consider inlining, we've already verified callability, so we
+	 can even inline callable and then disabled into a strub context.  That
+	 will get strubbed along with the context, so it's hopefully not a
+	 problem.  */
+      return true;
+
+    default:
+      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 types T1 and T2 are strub-compatible.  Return 1 if the strub modes
+   are the same, 2 if they are interchangeable, and 0 otherwise.  */
+
+int
+strub_comptypes (tree t1, tree t2)
+{
+  if (TREE_CODE (t1) != TREE_CODE (t2))
+    return 0;
+
+  enum strub_mode m1 = get_strub_mode_from_type (t1);
+  enum strub_mode m2 = get_strub_mode_from_type (t2);
+
+  if (m1 == m2)
+    return 1;
+
+  /* We're dealing with types, so only strub modes that can be selected by
+     attributes in the front end matter.  If either mode is at-calls (for
+     functions) or internal (for variables), the conversion is not
+     compatible.  */
+  bool var_p = !FUNC_OR_METHOD_TYPE_P (t1);
+  enum strub_mode mr = var_p ? STRUB_INTERNAL : STRUB_AT_CALLS;
+  if (m1 == mr || m2 == mr)
+    return 0;
+
+  return 2;
+}
+
+/* Return the effective strub mode used for CALL, and set *TYPEP to
+   the effective type used for the call.  The effective type and mode
+   are those of the callee, unless the call involves a typecast.  */
+
+static enum strub_mode
+effective_strub_mode_for_call (gcall *call, tree *typep)
+{
+  tree type;
+  enum strub_mode mode;
+
+  if (strub_call_fntype_override_p (call))
+    {
+      type = gimple_call_fntype (call);
+      mode = get_strub_mode_from_type (type);
+    }
+  else
+    {
+      type = TREE_TYPE (TREE_TYPE (gimple_call_fn (call)));
+      tree decl = gimple_call_fndecl (call);
+      if (decl)
+	mode = get_strub_mode_from_fndecl (decl);
+      else
+	mode = get_strub_mode_from_type (type);
+    }
+
+  if (typep)
+    *typep = type;
+
+  return mode;
+}
+
+/* Create a distinct copy of the type of NODE's function, and change
+   the fntype of all calls to it with the same main type to the new
+   type.  */
+
+static void
+distinctify_node_type (cgraph_node *node)
+{
+  tree old_type = TREE_TYPE (node->decl);
+  tree new_type = build_distinct_type_copy (old_type);
+  tree new_ptr_type = NULL_TREE;
+
+  /* Remap any calls to node->decl that use old_type, or a variant
+     thereof, to new_type as well.  We don't look for aliases, their
+     declarations will have their types changed independently, and
+     we'll adjust their fntypes then.  */
+  for (cgraph_edge *e = node->callers; e; e = e->next_caller)
+    {
+      if (!e->call_stmt)
+	continue;
+      tree fnaddr = gimple_call_fn (e->call_stmt);
+      gcc_checking_assert (TREE_CODE (fnaddr) == ADDR_EXPR
+			   && TREE_OPERAND (fnaddr, 0) == node->decl);
+      if (strub_call_fntype_override_p (e->call_stmt))
+	continue;
+      if (!new_ptr_type)
+	new_ptr_type = build_pointer_type (new_type);
+      TREE_TYPE (fnaddr) = new_ptr_type;
+      gimple_call_set_fntype (e->call_stmt, new_type);
+    }
+
+  TREE_TYPE (node->decl) = new_type;
+}
+
+/* Return TRUE iff TYPE and any variants have the same strub mode.  */
+
+static bool
+same_strub_mode_in_variants_p (tree type)
+{
+  enum strub_mode mode = get_strub_mode_from_type (type);
+
+  for (tree other = TYPE_MAIN_VARIANT (type);
+       other != NULL_TREE; other = TYPE_NEXT_VARIANT (other))
+    if (type != other && mode != get_strub_mode_from_type (other))
+      return false;
+
+  /* Check that the canonical type, if set, either is in the same
+     variant chain, or has the same strub mode as type.  Also check
+     the variants of the canonical type.  */
+  if (TYPE_CANONICAL (type)
+      && (TYPE_MAIN_VARIANT (TYPE_CANONICAL (type))
+	  != TYPE_MAIN_VARIANT (type)))
+    {
+      if (mode != get_strub_mode_from_type (TYPE_CANONICAL (type)))
+	return false;
+      else
+	return same_strub_mode_in_variants_p (TYPE_CANONICAL (type));
+    }
+
+  return true;
+}
+
+/* Check that strub functions don't call non-strub functions, and that
+   always_inline strub functions are only called by strub
+   functions.  */
+
+static void
+verify_strub ()
+{
+  cgraph_node *node;
+
+  /* It's expected that check strub-wise pointer type compatibility of variables
+     and of functions is already taken care of by front-ends, on account of the
+     attribute's being marked as affecting type identity and of the creation of
+     distinct types.  */
+
+  /* Check that call targets in strub contexts have strub-callable types.  */
+
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+  {
+    enum strub_mode caller_mode = get_strub_mode (node);
+
+    for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
+      {
+	gcc_checking_assert (e->indirect_unknown_callee);
+
+	if (!e->call_stmt)
+	  continue;
+
+	enum strub_mode callee_mode
+	  = effective_strub_mode_for_call (e->call_stmt, NULL);
+
+	if (!strub_callable_from_p (caller_mode, callee_mode))
+	  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 (!e->call_stmt)
+	  continue;
+
+	tree callee_fntype;
+	enum strub_mode callee_mode
+	  = effective_strub_mode_for_call (e->call_stmt, &callee_fntype);
+
+	if (!strub_callable_from_p (caller_mode, callee_mode))
+	  {
+	    if (callee_mode == 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)
+		     && caller_mode == STRUB_INTERNAL)
+	      /* This is ok, it will be kept in the STRUB_WRAPPER, and removed
+		 from the STRUB_WRAPPED's strub context.  */
+	      continue;
+	    else if (!strub_call_fntype_override_p (e->call_stmt))
+	      error_at (gimple_location (e->call_stmt),
+			"calling non-%<strub%> %qD in %<strub%> context %qD",
+			e->callee->decl, node->decl);
+	    else
+	      error_at (gimple_location (e->call_stmt),
+			"calling %qD using non-%<strub%> type %qT"
+			" in %<strub%> context %qD",
+			e->callee->decl, callee_fntype, node->decl);
+	  }
+      }
+  }
+}
+
+namespace {
+
+/* Define a pass to compute strub modes.  */
+const pass_data pass_data_ipa_strub_mode = {
+  SIMPLE_IPA_PASS,
+  "strubm",
+  OPTGROUP_NONE,
+  TV_NONE,
+  PROP_cfg, // properties_required
+  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 relaxed (-3) and strict (-4) settings, that only enable strub at a
+       function or variable attribute's request, the attribute handler changes
+       flag_strub to -1 or -2, respectively, if any strub-enabling occurence of
+       the attribute is found.  Therefore, if it remains at -3 or -4, nothing
+       that would enable strub was found, so we can disable it and avoid the
+       overhead.  */
+    if (flag_strub < -2)
+      flag_strub = 0;
+    return flag_strub;
+  }
+  virtual unsigned int execute (function *);
+};
+
+/* Define a pass to introduce strub transformations.  */
+const pass_data pass_data_ipa_strub = {
+  SIMPLE_IPA_PASS,
+  "strub",
+  OPTGROUP_NONE,
+  TV_NONE,
+  PROP_cfg | PROP_ssa, // 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 && !seen_error (); }
+  virtual unsigned int execute (function *);
+
+  /* Define on demand and cache some types we use often.  */
+#define DEF_TYPE(IDX, NAME, INIT)		\
+  static inline tree get_ ## NAME () {		\
+    int idx = STRUB_TYPE_BASE + IDX;		\
+    static tree type = strub_cache[idx];	\
+    if (!type)					\
+      strub_cache[idx] = 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 (0, wmt, build_variant_type_copy (ptr_type_node))
+
+  DEF_TYPE (1, pwmt, build_reference_type (get_wmt ()))
+
+  DEF_TYPE (2, qpwmt,
+	    build_qualified_type (get_pwmt (),
+				  TYPE_QUAL_RESTRICT
+				  /* | TYPE_QUAL_CONST */))
+
+  DEF_TYPE (3, qptr,
+	    build_qualified_type (ptr_type_node,
+				  TYPE_QUAL_RESTRICT
+				  | TYPE_QUAL_CONST))
+
+  DEF_TYPE (4, qpvalst,
+	    build_qualified_type (build_reference_type
+				  (va_list_type_node),
+				  TYPE_QUAL_RESTRICT
+				  /* | TYPE_QUAL_CONST */))
+
+#undef DEF_TYPE
+
+  /* Define non-strub builtins on demand.  */
+#define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST)			\
+  static tree get_ ## NAME () {					\
+    tree decl = builtin_decl_explicit (CODE);			\
+    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 strub builtins on demand.  */
+#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);			\
+      }								\
+    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 strub identifiers on demand.  */
+#define DEF_IDENT(IDX, NAME)						\
+  static inline tree get_ ## NAME () {					\
+    int idx = STRUB_IDENT_BASE + IDX;					\
+    tree identifier = strub_cache[idx];					\
+    if (!identifier)							\
+      strub_cache[idx] = identifier = get_identifier (".strub." #NAME);	\
+    return identifier;							\
+  }
+
+  DEF_IDENT (0, watermark_ptr)
+  DEF_IDENT (1, va_list_ptr)
+  DEF_IDENT (2, apply_args)
+
+#undef DEF_IDENT
+
+  static inline int adjust_at_calls_type (tree);
+  static inline void adjust_at_calls_call (cgraph_edge *, int, tree);
+  static inline void adjust_at_calls_calls (cgraph_node *);
+
+  /* Add to SEQ a call to the strub watermark update builtin, taking NODE's
+     location if given.  Optionally add the corresponding edge from NODE, with
+     execution frequency COUNT.  Return the modified SEQ.  */
+
+  static inline gimple_seq
+  call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
+			 gimple_seq seq = NULL)
+    {
+      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)
+	node->create_edge (cgraph_node::get_create (uwm), update, count, false);
+      return seq;
+    }
+
+};
+
+} // anon namespace
+
+/* Gather with this type a collection of parameters that we're turning into
+   explicit references.  */
+
+typedef hash_set<tree> indirect_parms_t;
+
+/* Dereference OP's incoming turned-into-reference parm if it's an
+   INDIRECT_PARMS or an ADDR_EXPR thereof.  Set *REC and return according to
+   gimple-walking expectations.  */
+
+static tree
+maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
+{
+  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)))
+	{
+	  op = TREE_OPERAND (op, 0);
+	  return op;
+	}
+    }
+
+  return NULL_TREE;
+}
+
+/* A gimple-walking function that adds dereferencing to indirect parms.  */
+
+static tree
+walk_make_indirect (tree *op, int *rec, void *arg)
+{
+  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;
+}
+
+/* A gimple-walking function that turns any non-gimple-val ADDR_EXPRs into a
+   separate SSA.  Though addresses of e.g. parameters, and of members thereof,
+   are gimple vals, turning parameters into references, with an extra layer of
+   indirection and thus explicit dereferencing, need to be regimplified.  */
+
+static tree
+walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
+{
+  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;
+}
+
+/* Turn STMT's PHI arg defs into separate SSA defs if they've become
+   non-gimple_val.  Return TRUE if any edge insertions need to be committed.  */
+
+static bool
+walk_regimplify_phi (gphi *stmt)
+{
+  bool needs_commit = false;
+
+  for (unsigned i = 0, n = gimple_phi_num_args (stmt); i < n; i++)
+    {
+      tree op = gimple_phi_arg_def (stmt, i);
+      if ((TREE_CODE (op) == ADDR_EXPR
+	   && !is_gimple_val (op))
+	  /* ??? A PARM_DECL that was addressable in the original function and
+	     had its address in PHI nodes, but that became a reference in the
+	     wrapped clone would NOT be updated by update_ssa in PHI nodes.
+	     Alas, if we were to create a default def for it now, update_ssa
+	     would complain that the symbol that needed rewriting already has
+	     SSA names associated with it.  OTOH, leaving the PARM_DECL alone,
+	     it eventually causes errors because it remains unchanged in PHI
+	     nodes, but it gets rewritten as expected if it appears in other
+	     stmts.  So we cheat a little here, and force the PARM_DECL out of
+	     the PHI node and into an assignment.  It's a little expensive,
+	     because we insert it at the edge, which introduces a basic block
+	     that's entirely unnecessary, but it works, and the block will be
+	     removed as the default def gets propagated back into the PHI node,
+	     so the final optimized code looks just as expected.  */
+	  || (TREE_CODE (op) == PARM_DECL
+	      && !TREE_ADDRESSABLE (op)))
+	{
+	  tree temp = make_ssa_name (TREE_TYPE (op), stmt);
+	  if (TREE_CODE (op) == PARM_DECL)
+	    SET_SSA_NAME_VAR_OR_IDENTIFIER (temp, DECL_NAME (op));
+	  SET_PHI_ARG_DEF (stmt, i, temp);
+
+	  gimple *assign = gimple_build_assign (temp, op);
+	  if (gimple_phi_arg_has_location (stmt, i))
+	    gimple_set_location (assign, gimple_phi_arg_location (stmt, i));
+	  gsi_insert_on_edge (gimple_phi_arg_edge (stmt, i), assign);
+	  needs_commit = true;
+	}
+    }
+
+  return needs_commit;
+}
+
+/* Create a reference type to use for PARM when turning it into a reference.
+   NONALIASED causes the reference type to gain its own separate alias set, so
+   that accessing the indirectly-passed parm won'will not add aliasing
+   noise.  */
+
+static tree
+build_ref_type_for (tree parm, bool nonaliased = true)
+{
+  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.  */
+  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;
+}
+
+/* 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)
+{
+  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);
+
+      gcall *call = dyn_cast <gcall *> (stmt);
+      if (!call)
+	continue;
+
+      tree callee = gimple_call_fndecl (call);
+      gcc_checking_assert (callee);
+      node->create_edge (cgraph_node::get_create (callee), call, count, false);
+    }
+}
+
+/* Insert SEQ after the call at GSI, as if the call was in a try block with SEQ
+   as finally, i.e., SEQ will run after the call whether it returns or
+   propagates an exception.  This handles block splitting, EH edge and block
+   creation, noreturn and nothrow optimizations, and even throwing calls without
+   preexisting local handlers.  */
+
+static void
+gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
+{
+  if (!seq)
+    return;
+
+  gimple *stmt = gsi_stmt (gsi);
+
+  if (gimple_has_location (stmt))
+    annotate_all_with_location (seq, gimple_location (stmt));
+
+  gcall *call = dyn_cast <gcall *> (stmt);
+  bool noreturn_p = call && gimple_call_noreturn_p (call);
+  int eh_lp = lookup_stmt_eh_lp (stmt);
+  bool must_not_throw_p = eh_lp < 0;
+  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.  */
+  edge neeh = make_eh_edge (stmt);
+  neeh->probability = profile_probability::never ();
+  gcc_checking_assert (neeh->dest == bb_eh_cleanup);
+  gcc_checking_assert (!neeh->dest->count.initialized_p ());
+  neeh->dest->count = neeh->count ();
+  add_call_edges_for_seq (seq, neeh->dest->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);
+    }
+}
+
+/* Record the order of the last cgraph entry whose mode we've already set, so
+   that we can perform mode setting incrementally without duplication.  */
+static int last_cgraph_order;
+
+/* Set strub modes for functions introduced since the last call.  */
+
+static void
+ipa_strub_set_mode_for_new_functions ()
+{
+  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_fndecl (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;
+
+  gcc_checking_assert (same_strub_mode_in_variants_p (type));
+
+  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;
+}
+
+/* Adjust a call to an at-calls call target.  Create a watermark local variable
+   if needed, initialize it before, pass it to the callee according to the
+   modified at-calls interface, and release the callee's stack space after the
+   call, if not deferred.  If the call is const or pure, arrange for the
+   watermark to not be assumed unused or unchanged.  */
+
+void
+pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args,
+				      tree callee_fntype)
+{
+  gcc_checking_assert (e->call_stmt);
+  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);
+      e->caller->create_edge (cgraph_node::get_create (enter),
+			      stptr, gsi_bb (gsi)->count, false);
+    }
+
+
+  /* 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++)
+      vargs.quick_push (gimple_call_arg (stmt, i));
+
+    if (gimple_call_internal_p (stmt))
+      gcc_unreachable ();
+    else
+      new_stmt = gimple_build_call_vec (gimple_call_fn (stmt), vargs);
+    gimple_call_set_fntype (new_stmt, callee_fntype);
+
+    if (gimple_call_lhs (stmt))
+      gimple_call_set_lhs (new_stmt, gimple_call_lhs (stmt));
+
+    gimple_move_vops (new_stmt, stmt);
+
+    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);
+}
+
+/* Adjust all at-calls calls in NODE. */
+
+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);
+
+	  if (!e->call_stmt)
+	    continue;
+
+	  tree callee_fntype;
+	  enum strub_mode callee_mode
+	    = effective_strub_mode_for_call (e->call_stmt, &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, callee_fntype);
+	}
+      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);
+
+	  if (!e->call_stmt)
+	    continue;
+
+	  tree callee_fntype;
+	  enum strub_mode callee_mode
+	    = effective_strub_mode_for_call (e->call_stmt, &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, callee_fntype);
+	}
+      pop_cfun ();
+    }
+}
+
+/* The strubm (strub mode) pass computes a strub mode for each function in the
+   call graph, and checks, before any inlining, that strub callability
+   requirements in effect are satisfied.  */
+
+unsigned int
+pass_ipa_strub_mode::execute (function *)
+{
+  last_cgraph_order = 0;
+  ipa_strub_set_mode_for_new_functions ();
+
+  /* Verify before any inlining or other transformations.  */
+  verify_strub ();
+
+  return 0;
+}
+
+/* Create a strub mode pass.  */
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub_mode (gcc::context *ctxt)
+{
+  return new pass_ipa_strub_mode (ctxt);
+}
+
+/* The strub pass proper adjusts types, signatures, and at-calls calls, and
+   splits internal-strub functions.  */
+
+unsigned int
+pass_ipa_strub::execute (function *)
+{
+  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)
+	  distinctify_node_type (onode);
+
+	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;
+
+	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);
+
+		  gcall *call = dyn_cast <gcall *> (stmt);
+
+		  if (!call)
+		    continue;
+
+		  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 ();
+      }
+  }
+
+  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;
+      }
+
+    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);
+
+    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;
+	  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;
+	     i++, nparm = DECL_CHAIN (nparm), nparmt = TREE_CHAIN (nparmt))
+	  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;
+      }
+
+#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 (/* unlike create_wrapper: preserve_comdat_group = */true);
+
+	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;
+	onode->create_edge (target, NULL, onode->count);
+	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 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;
+	  bool needs_commit = false;
+	  FOR_EACH_BB_FN (bb, cfun)
+	    {
+	      for (gphi_iterator gsi = gsi_start_nonvirtual_phis (bb);
+		   !gsi_end_p (gsi);
+		   gsi_next_nonvirtual_phi (&gsi))
+		{
+		  gphi *stmt = gsi.phi ();
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed && !is_gimple_debug (gsi_stmt (gsi)))
+		    if (walk_regimplify_phi (stmt))
+		      needs_commit = true;
+		}
+
+	      for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
+		   !gsi_end_p (gsi); gsi_next (&gsi))
+		{
+		  gimple *stmt = gsi_stmt (gsi);
+
+		  walk_stmt_info wi = {};
+		  wi.info = &indirect_nparms;
+		  walk_gimple_op (stmt, walk_make_indirect, &wi);
+		  if (wi.changed)
+		    {
+		      if (!is_gimple_debug (stmt))
+			{
+			  wi.info = &gsi;
+			  walk_gimple_op (stmt, walk_regimplify_addr_expr,
+					  &wi);
+			}
+		      update_stmt (stmt);
+		    }
+		}
+	    }
+	  if (needs_commit)
+	    gsi_commit_edge_inserts ();
+	}
+
+      if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca
+	  || is_stdarg || apply_args)
+	for (cgraph_edge *e = nnode->callees, *enext; e; e = enext)
+	  {
+	    if (!e->call_stmt)
+	      continue;
+
+	    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));
+		    if (!is_gimple_val (arg))
+		      arg = force_gimple_operand_gsi (&gsi, arg, true,
+						      NULL_TREE, true, GSI_SAME_STMT);
+		  }
+		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)));
+
+      gcall *wrcall;
+      while (!(wrcall = dyn_cast <gcall *> (gsi_stmt (gsi))))
+	gsi_next (&gsi);
+
+      tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
+      TREE_ADDRESSABLE (swm) = true;
+      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);
+      onode->create_edge (cgraph_node::get_create (enter),
+			  stptr, gsi_bb (gsi)->count, false);
+
+      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);
+	  onode->create_edge (cgraph_node::get_create (bappargs),
+			      appargs, gsi_bb (gsi)->count, false);
+	}
+
+      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);
+	  onode->create_edge (cgraph_node::get_create (bvastart),
+			      vastart, gsi_bb (gsi)->count, false);
+
+	  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 ();
+    }
+  }
+
+  return 0;
+}
+
+simple_ipa_opt_pass *
+make_pass_ipa_strub (gcc::context *ctxt)
+{
+  return new pass_ipa_strub (ctxt);
+}
+
+#include "gt-ipa-strub.h"
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
new file mode 100644
index 0000000000000..f367a4a0ef827
--- /dev/null
+++ b/gcc/ipa-strub.h
@@ -0,0 +1,45 @@
+/* strub (stack scrubbing) infrastructure.
+   Copyright (C) 2021-2023 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 CALLEE can be inlined into CALLER, as far as stack scrubbing
+   constraints are concerned.  CALLEE doesn't have to be called directly by
+   CALLER, but the returned value says nothing about intervening functions.  */
+extern bool strub_inlinable_to_p (cgraph_node *callee, cgraph_node *caller);
+
+/* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
+extern bool strub_splittable_p (cgraph_node *node);
+
+/* 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);
+
+/* Return zero iff ID is NOT an acceptable parameter for a user-supplied strub
+   attribute for a function.  Otherwise, return >0 if it enables strub, <0 if it
+   does not.  Return +/-1 if the attribute-modified type is compatible with the
+   type without the attribute, or +/-2 if it is not compatible.  */
+extern int strub_validate_fn_attr_parm (tree id);
+
+/* Like comptypes, return 0 if t1 and t2 are not compatible, 1 if they are
+   compatible, and 2 if they are nearly compatible.  Same strub mode is
+   compatible, interface-compatible strub modes are nearly compatible.  */
+extern int strub_comptypes (tree t1, tree t2);
diff --git a/gcc/passes.def b/gcc/passes.def
index 1e1950bdb39cb..d515e77be0399 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -52,6 +52,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);
@@ -115,6 +116,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 0000000000000..c7a79a6ea0d8a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O0.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=strict -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 0000000000000..96285c975d98e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=strict -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 0000000000000..8edc0d8aa1321
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
+
+/* At -O2, without -fno-inline, we fully expand enter and update, and add a test
+   around the leave call.  */
+
+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 0000000000000..c6d900cf3c45b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -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 0000000000000..33ee465e51cb6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O3.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=strict -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 0000000000000..2936f82079e18
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=strict -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 0000000000000..479746e57d87e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-Og -fstrub=strict -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 0000000000000..2241d4ea07f27
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
+
+/* At -Os, without -fno-inline, we fully expand enter, and also update.  The
+   expanded update might be larger than a call proper, but argument saving and
+   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 0000000000000..a322bcc5da606
--- /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 \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
new file mode 100644
index 0000000000000..db60026d0e080
--- /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 \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
new file mode 100644
index 0000000000000..2f462adc1efe0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+void __attribute__ ((__strub__ ("callable")))
+apply_function (void *args)
+{
+  __builtin_apply (0, args, 0);
+}
+
+void __attribute__ ((__strub__ ("internal")))
+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 0000000000000..a5d7551f5da5c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+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 0000000000000..64422a0d1e880
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+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 0000000000000..15ffaa031b899
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -fdump-ipa-strubm" } */
+
+/* Check that implicit enabling of strub mode selects internal strub when the
+   function uses __builtin_apply_args, that prevents the optimization to
+   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 \[(\]internal\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
new file mode 100644
index 0000000000000..b70843b4215a4
--- /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 \[(\]at-calls\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
new file mode 100644
index 0000000000000..97a3988a6b922
--- /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 \[(\]callable\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
new file mode 100644
index 0000000000000..3d73431b3dcd3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict -O1" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+   the strubbing to its caller at -O1.  */
+
+#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 0000000000000..fddf3c745e7e6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -0,0 +1,8 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict -O2" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+   the strubbing to its caller at -O2.  */
+
+#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 0000000000000..7ebc65b58dd72
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -0,0 +1,110 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict -O3" } */
+
+/* Check that a strub function called by another strub function defers the
+   strubbing to its caller at -O3.  */
+
+#ifndef EXPECT_DEFERRAL
+/* Other strub-defer*.c tests override this macro.  */
+# define EXPECT_DEFERRAL
+#endif
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+/* Pad before and after the string on the stack, so that it's not overwritten by
+   regular stack use.  */
+#define PAD 7
+
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
+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__ ("callable"))) callable_t (char *);
+  callable_t *f = 0;
+
+  char s[2 * PAD + 1][sizeof (test_string)];
+  __builtin_strcpy (s[PAD], test_string);
+  asm ("" : "+m" (s), "+r" (f));
+
+  if (__builtin_expect (!f, 1))
+    return (char*)__builtin_stack_address ();
+
+  f (s[PAD]);
+  return 0;
+}
+
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
+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__ ("at-calls"), __noinline__, __noclone__))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ ("at-calls")))
+char *
+deferred_at_calls ()
+{
+  char *ret;
+  int i = 1;
+  /* Since these test check stack contents above the top of the stack, an
+     unexpected asynchronous signal or interrupt might overwrite the bits we
+     expect to find and cause spurious fails.  Tolerate one such overall
+     spurious fail by retrying.  */
+  while (EXPECT_DEFERRAL !look_for_string ((ret = at_calls ())))
+    if (!i--) __builtin_abort ();
+  return ret;
+}
+
+static __attribute__ ((__strub__ ("internal")))
+char *
+deferred_internal ()
+{
+  int i = 1;
+  char *ret;
+  while (EXPECT_DEFERRAL !look_for_string ((ret = at_calls ())))
+    if (!i--) __builtin_abort ();
+  return ret;
+}
+
+int main ()
+{
+  int i = 1;
+  /* These calls should not be subject to spurious fails: whether or not some
+     asynchronous event overwrites the scrubbed stack space, the string won't
+     remain there.  Unless the asynchronous event happens to write the string
+     where we look for it, but what are the odds?  Anyway, it doesn't hurt to
+     retry, even if just for symmetry.  */
+  while (look_for_string (deferred_at_calls ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (deferred_internal ()))
+    if (!i--) __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 0000000000000..fbaf85fe0fafe
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict -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 0000000000000..e9d7b7b9ee0a8
--- /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 \[(\]callable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
new file mode 100644
index 0000000000000..8b8e15a51c71c
--- /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 \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
new file mode 100644
index 0000000000000..0a4a7539d3489
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ ("internal")))
+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__ ("internal")))
+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__ ("internal")))
+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__ ("internal")))
+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 0000000000000..147171d96d5a1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ ("at-calls")))
+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__ ("at-calls")))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ ("at-calls")))
+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 0000000000000..4e92682895a43
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -0,0 +1,58 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that uses of a strub variable implicitly enables internal strub for
+   publicly-visible functions, and causes the same transformations to their
+   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-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
new file mode 100644
index 0000000000000..e2f9d8aebca58
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  Without the error,
+   inlining takes place.  */
+
+#include "strub-strict1.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
new file mode 100644
index 0000000000000..98474435d2e59
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+
+/* The difference between relaxed and strict in this case is that we accept the
+   call from one internal-strub function to another.  */
+
+#include "strub-strict2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
new file mode 100644
index 0000000000000..1de15342595e4
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=strict -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 0000000000000..f9209c819004b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -fstrub=strict -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 0000000000000..bed1dcfb54a45
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=strict -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 0000000000000..6bf0071f52b93
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -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 0000000000000..4732f515bf70c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
+   enter and leave calls within strub contexts, passing on the enclosing
+   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 0000000000000..8d6424c479a3a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
+   enter and leave calls within strub contexts, passing on the enclosing
+   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-strict1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
new file mode 100644
index 0000000000000..368522442066e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-strict1.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm" } */
+
+static int __attribute__ ((__strub__)) var;
+
+/* h becomes STRUB_INLINABLE, because of the use of the strub variable,
+   and the always_inline flag.  It would get inlined before pass_ipa_strub, if
+   it weren't for the error.  */
+static inline void
+__attribute__ ((__always_inline__))
+h() {
+  var++;
+}
+
+/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
+   the viability of at-calls strubbing.  Though internally a strub context, its
+   interface is not strub-enabled, so it's not callable from within strub
+   contexts.  */
+static inline void
+g() {
+  var--;
+  h();
+}
+
+/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
+   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
+void
+f() {
+  var++;
+  g();  /* { dg-error "calling non-.strub." } */
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-strict2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
new file mode 100644
index 0000000000000..b4f2888321821
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-strict2.c
@@ -0,0 +1,25 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strubm" } */
+
+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 \[(\]internal\[)\]" 2 "strubm" } } */
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 0000000000000..e48e0610e079b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-O1 -fstrub=strict -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 0000000000000..87cda7ab21b16
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+
+/* Check that the expected strub calls are issued.
+   Tail calls are short-circuited at -O2+.  */
+
+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/strub-var1.c b/gcc/testsuite/c-c++-common/strub-var1.c
new file mode 100644
index 0000000000000..eb6250fd39c90
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-var1.c
@@ -0,0 +1,24 @@
+/* { dg-do compile } */
+
+int __attribute__ ((strub)) x;
+float __attribute__ ((strub)) f;
+double __attribute__ ((strub)) d;
+
+/* The attribute applies to the type of the declaration, i.e., to the pointer
+   variable p, not to the pointed-to integer.  */
+int __attribute__ ((strub)) *
+p = &x; /* { dg-message "incompatible|invalid conversion" } */
+
+typedef int __attribute__ ((strub)) strub_int;
+strub_int *q = &x; /* Now this is compatible.  */
+
+int __attribute__ ((strub))
+a[2]; /* { dg-warning "does not apply to elements" } */
+
+int __attribute__ ((vector_size (4 * sizeof (int))))
+    __attribute__ ((strub))
+v; /* { dg-warning "does not apply to elements" } */
+
+struct s {
+  int i, j;
+} __attribute__ ((strub)) w; /* { dg-warning "does not apply to fields" } */
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 0000000000000..b5e45ab0525ad
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -0,0 +1,9 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+/* Check that strub and non-strub functions can be called from non-strub
+   contexts, and that strub and callable functions can be called from strub
+   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 0000000000000..96aa7fe4b07f7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -0,0 +1,264 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+/* Check that impermissible (cross-strub-context) calls are reported.  */
+
+extern int __attribute__ ((__strub__ ("callable"))) xcallable (void);
+extern int __attribute__ ((__strub__ ("internal"))) xinternal (void);
+extern int __attribute__ ((__strub__ ("at-calls"))) xat_calls (void);
+extern int __attribute__ ((__strub__ ("disabled"))) xdisabled (void);
+
+int __attribute__ ((__strub__ ("callable"))) callable (void);
+int __attribute__ ((__strub__ ("internal"))) internal (void);
+int __attribute__ ((__strub__ ("at-calls"))) at_calls (void);
+int __attribute__ ((__strub__ ("disabled"))) disabled (void);
+
+int __attribute__ ((__strub__)) var;
+int var_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
+icallable (void);
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
+iinternal (void);
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
+iat_calls (void);
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
+idisabled (void);
+static inline int __attribute__ ((__always_inline__))
+ivar_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
+i_callable (void) { return 0; }
+static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
+i_internal (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
+i_at_calls (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
+i_disabled (void) { return 0; }
+static inline int __attribute__ ((__always_inline__))
+i_var_user (void) { return var; }
+
+#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__ ("callable")))
+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__ ("internal")))
+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__ ("at-calls")))
+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__ ("disabled")))
+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 __attribute__ ((__always_inline__, __strub__ ("at-calls")))
+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 0000000000000..2857195706ed6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const function call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   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 0000000000000..98a92bc9eac2b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const function call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   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 0000000000000..5511a6e1e71d3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   and another to make sure it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ ("internal"), __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 0000000000000..47ee927964dff
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const wrapping call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   before the call, and another to make sure it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ ("internal")))
+#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 0000000000000..7c27a2a1a6dca
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..e66d903780afd
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..5e08e0e58c658
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* The pointer itself is a strub variable, that would enable internal strubbing
+   if its value was used.  Here, it's only overwritten, so no strub.  */
+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 0000000000000..a818e7a38bb5f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* The pointer itself is a strub variable, that would enable internal strubbing
+   if its value was used.  Here, it's only overwritten, so no strub.  */
+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 0000000000000..ddb0b5c0543b0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+/* It would be desirable to issue at least warnings for these.  */
+
+typedef int __attribute__ ((__strub__)) strub_int;
+strub_int *ptr;
+
+int *f () {
+  return ptr; /* { dg-message "incompatible|invalid conversion" } */
+}
+
+strub_int *g () {
+  return f (); /* { dg-message "incompatible|invalid conversion" } */
+}
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 0000000000000..c165f312f16de
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..69fcff8d3763d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..ff006224909bd
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
new file mode 100644
index 0000000000000..614b02228ba29
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+inline void __attribute__ ((strub ("internal"), always_inline))
+inl_int_ali (void)
+{
+  /* No internal wrapper, so this body ALWAYS gets inlined,
+     but it cannot be called from non-strub contexts.  */
+}
+
+void
+bat (void)
+{
+  /* Not allowed, not a strub context.  */
+  inl_int_ali (); /* { dg-error "context" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
new file mode 100644
index 0000000000000..f9a6b4a16faf8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
@@ -0,0 +1,7 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=all" } */
+
+#include "strub-inlinable1.c"
+
+/* With -fstrub=all, the caller becomes a strub context, so the strub-inlinable
+   callee is not rejected.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
new file mode 100644
index 0000000000000..b4a7f3992bbaa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict" } */
+
+typedef void ft (void);
+typedef void ft2 (int, int);
+extern ft __attribute__ ((__strub__)) fnac;
+
+ft * f (void) {
+  return fnac; /* { dg-message "incompatible|invalid conversion" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
new file mode 100644
index 0000000000000..d9d2c0caec42d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
@@ -0,0 +1,55 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic" } */
+
+/* C++ does not warn about the partial incompatibilities.
+
+   The d_p () calls are actually rejected, even in C++, but they are XFAILed
+   here because we don't get far enough in the compilation as to observe them,
+   because the incompatibilities are errors without -fpermissive.
+   strub-ptrfn3.c uses -fpermissive to check those.
+ */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail c++ } } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
new file mode 100644
index 0000000000000..e1f179e160e5c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
@@ -0,0 +1,50 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic -fpermissive" } */
+/* { dg-prune-output "command-line option .-fpermissive." } */
+
+/* See strub-ptrfn2.c.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
+  c_p ();
+  a_p ();
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
new file mode 100644
index 0000000000000..70b558afad040
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed" } */
+
+/* This is strub-ptrfn2.c without -Wpedantic.
+
+   Even C doesn't report the (not-quite-)compatible conversions without it.  */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac;
+  c_p = bad;
+  c_p = bar;
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
new file mode 100644
index 0000000000000..a262a086837b2
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure function call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+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 0000000000000..4c4bd50c209a0
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure function call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   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 0000000000000..ce195c6b1f1b6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ ("internal"), __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 0000000000000..75cd54ccb5b5d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure wrapping call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ ("internal")))
+#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 0000000000000..7458b3fb54da5
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -0,0 +1,95 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  Avoid the use of red zones by avoiding
+   leaf functions.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+/* Pad before and after the string on the stack, so that it's not overwritten by
+   regular stack use.  */
+#define PAD 7
+
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
+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__ ("callable"))) callable_t (char *);
+  callable_t *f = 0;
+
+  char s[2 * PAD + 1][sizeof (test_string)];
+  __builtin_strcpy (s[PAD], test_string);
+  asm ("" : "+m" (s), "+r" (f));
+
+  if (__builtin_expect (!f, 1))
+    return (char *) __builtin_stack_address ();
+
+  f (s[PAD]);
+  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__ ("at-calls")))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ ("internal")))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  /* Since these test check stack contents above the top of the stack, an
+     unexpected asynchronous signal or interrupt might overwrite the bits we
+     expect to find and cause spurious fails.  Tolerate one such overall
+     spurious fail by retrying.  */
+  int i = 1;
+  while (!look_for_string (callable ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (at_calls ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (internal ()))
+    if (!i--) __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 0000000000000..5d60a7775f4bb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -0,0 +1,84 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  Allow red zones to be used.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+/* Pad before and after the string on the stack, so that it's not overwritten by
+   regular stack use.  */
+#define PAD 7
+
+static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
+char *
+leak_string (void)
+{
+  int len = sizeof (test_string);
+  asm ("" : "+rm" (len));
+  char s[2 * PAD + 1][len];
+  __builtin_strcpy (s[PAD], 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__ ("at-calls")))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ ("internal")))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  /* Since these test check stack contents above the top of the stack, an
+     unexpected asynchronous signal or interrupt might overwrite the bits we
+     expect to find and cause spurious fails.  Tolerate one such overall
+     spurious fail by retrying.  */
+  int i = 1;
+  while (!look_for_string (callable ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (at_calls ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (internal ()))
+    if (!i--) __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 0000000000000..c2ad710858e87
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -0,0 +1,80 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict" } */
+/* { 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__ ("callable")))
+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__ ("at-calls")))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ ("internal")))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  /* Since these test check stack contents above the top of the stack, an
+     unexpected asynchronous signal or interrupt might overwrite the bits we
+     expect to find and cause spurious fails.  Tolerate one such overall
+     spurious fail by retrying.  */
+  int i = 1;
+  while (!look_for_string (callable ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (at_calls ()))
+    if (!i--) __builtin_abort ();
+  while (look_for_string (internal ()))
+    if (!i--) __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 0000000000000..3b36b8e5d68ef
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -0,0 +1,106 @@
+/* { 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__ ("internal")))
+char *
+internal ()
+{
+  return intermediate ();
+}
+
+int __attribute__ ((__strub__ ("disabled")))
+main ()
+{
+  /* Since these test check stack contents above the top of the stack, an
+     unexpected asynchronous signal or interrupt might overwrite the bits we
+     expect to find and cause spurious fails.  Tolerate one such overall
+     spurious fail by retrying.  */
+  int i = 1;
+  while (look_for_string (internal ()))
+    if (!i--) __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 0000000000000..57f9baf758ded
--- /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 0000000000000..08de3f1c3b17c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=strict" } */
+/* { dg-require-effective-target alloca } */
+
+#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
+
+#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 0000000000000..459f6886c5499
--- /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 0000000000000..0d367fb83d09d
--- /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 0000000000000..c226ab10ff651
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..a7911f1fa7212
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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 0000000000000..6ebebcd01e8ea
--- /dev/null
+++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -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_access.adb b/gcc/testsuite/gnat.dg/strub_access.adb
new file mode 100644
index 0000000000000..29e6996ecf61c
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_access.adb
@@ -0,0 +1,21 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=relaxed -fdump-ipa-strubm" }
+
+--  The main subprogram doesn't read from the automatic variable, but
+--  being an automatic variable, its presence should be enough for the
+--  procedure to get strub enabled.
+
+procedure Strub_Access is
+   type Strub_Int is new Integer;
+   pragma Machine_Attribute (Strub_Int, "strub");
+   
+   X : aliased Strub_Int := 0;
+
+   function F (P : access Strub_Int) return Strub_Int is (P.all);
+
+begin
+   X := F (X'Access);
+end Strub_Access;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 1 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls-opt\[)\]\[)\]" 1 "strubm" } }
diff --git a/gcc/testsuite/gnat.dg/strub_access1.adb b/gcc/testsuite/gnat.dg/strub_access1.adb
new file mode 100644
index 0000000000000..dae4706016436
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_access1.adb
@@ -0,0 +1,16 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=relaxed" }
+
+--  Check that we reject 'Access of a strub variable whose type does
+--  not carry a strub modifier.
+
+procedure Strub_Access1 is
+   X : aliased Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+
+   function F (P : access Integer) return Integer is (P.all);
+   
+begin
+   X := F (X'Unchecked_access); -- OK.
+   X := F (X'Access); -- { dg-error "target access type drops .strub. mode" }
+end Strub_Access1;
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
new file mode 100644
index 0000000000000..10445d7cf8451
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -0,0 +1,37 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" }
+
+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 (F (X));
+   --  function G return Integer is (FP (X));
+   --  Calling G would likely raise an exception, because although FP
+   --  carries the strub at-calls attribute needed to call F, the
+   --  attribute is dropped from the type used for the call proper.
+end Strub_Attr;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 2 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 0 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark_ptr" 6 "strub" } }
+--  We have 1 at-calls subprogram (F) and 2 wrapped (P and G).
+--  For each of them, there's one match for the wrapped signature, 
+--  and one for the update call.
+
+--  { dg-final { scan-ipa-dump-times "strub.watermark" 27 "strub" } }
+--  The 6 matches above, plus:
+--  5*2: wm var decl, enter, call, leave and clobber for each wrapper;
+--  2*1: an extra leave and clobber for the exception paths in the wrappers.
+--  7*1: for the F call in G, including EH path.
diff --git a/gcc/testsuite/gnat.dg/strub_attr.ads b/gcc/testsuite/gnat.dg/strub_attr.ads
new file mode 100644
index 0000000000000..a94c23bf41833
--- /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/testsuite/gnat.dg/strub_disp.adb b/gcc/testsuite/gnat.dg/strub_disp.adb
new file mode 100644
index 0000000000000..3dbcc4a357cba
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_disp.adb
@@ -0,0 +1,64 @@
+--  { dg-do compile }
+
+procedure Strub_Disp is
+   package Foo is
+      type A is tagged null record;
+
+      procedure P (I : Integer; X : A);
+      pragma Machine_Attribute (P, "strub", "at-calls");
+      
+      function F (X : access A) return Integer;
+
+      type B is new A with null record;
+      
+      overriding
+      procedure P (I : Integer; X : B); -- { dg-error "requires the same .strub. mode" }
+
+      overriding
+      function F (X : access B) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+
+   end Foo;
+
+   package body Foo is
+      procedure P (I : Integer; X : A) is
+      begin
+	 null;
+      end;
+      
+      function F (X : access A) return Integer is (0);
+
+      overriding
+      procedure P (I : Integer; X : B) is
+      begin
+	 P (I, A (X));
+      end;
+      
+      overriding
+      function F (X : access B) return Integer is (1);
+   end Foo;
+
+   use Foo;
+
+   procedure Q (X : A'Class) is
+   begin
+      P (-1, X);
+   end;
+
+   XA : aliased A;
+   XB : aliased B;
+   I : Integer := 0;
+   XC : access A'Class;
+begin
+   Q (XA);
+   Q (XB);
+   
+   I := I + F (XA'Access);
+   I := I + F (XB'Access);
+
+   XC := XA'Access;
+   I := I + F (XC);
+
+   XC := XB'Access;
+   I := I + F (XC);
+end Strub_Disp;
diff --git a/gcc/testsuite/gnat.dg/strub_disp1.adb b/gcc/testsuite/gnat.dg/strub_disp1.adb
new file mode 100644
index 0000000000000..09756a74b7d81
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_disp1.adb
@@ -0,0 +1,79 @@
+--  { dg-do compile }
+--  { dg-options "-fdump-ipa-strub" }
+
+-- Check that at-calls dispatching calls are transformed.
+
+procedure Strub_Disp1 is
+   package Foo is
+      type A is tagged null record;
+
+      procedure P (I : Integer; X : A);
+      pragma Machine_Attribute (P, "strub", "at-calls");
+      
+      function F (X : access A) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+      type B is new A with null record;
+      
+      overriding
+      procedure P (I : Integer; X : B);
+      pragma Machine_Attribute (P, "strub", "at-calls");
+
+      overriding
+      function F (X : access B) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+   end Foo;
+
+   package body Foo is
+      procedure P (I : Integer; X : A) is
+      begin
+	 null;
+      end;
+      
+      function F (X : access A) return Integer is (0);
+
+      overriding
+      procedure P (I : Integer; X : B) is
+      begin
+	 P (I, A (X)); -- strub-at-calls non-dispatching call
+      end;
+      
+      overriding
+      function F (X : access B) return Integer is (1);
+   end Foo;
+
+   use Foo;
+
+   procedure Q (X : A'Class) is
+   begin
+      P (-1, X); -- strub-at-calls dispatching call.
+   end;
+
+   XA : aliased A;
+   XB : aliased B;
+   I : Integer := 0;
+   XC : access A'Class;
+begin
+   Q (XA);
+   Q (XB);
+   
+   I := I + F (XA'Access); -- strub-at-calls non-dispatching call
+   I := I + F (XB'Access); -- strub-at-calls non-dispatching call
+
+   XC := XA'Access;
+   I := I + F (XC); -- strub-at-calls dispatching call.
+
+   XC := XB'Access;
+   I := I + F (XC); -- strub-at-calls dispatching call.
+end Strub_Disp1;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 4 "strub" } }
+
+--  Count the strub-at-calls non-dispatching calls 
+--  (+ 2 each, for the matching prototypes)
+--  { dg-final { scan-ipa-dump-times "foo\.p \[(\]\[^\n\]*watermark" 3 "strub" } }
+--  { dg-final { scan-ipa-dump-times "foo\.f \[(\]\[^\n\]*watermark" 4 "strub" } }
+
+--  Count the strub-at-calls dispatching calls.
+--  { dg-final { scan-ipa-dump-times "_\[0-9\]* \[(\]\[^\n\]*watermark" 3 "strub" } }
diff --git a/gcc/testsuite/gnat.dg/strub_ind.adb b/gcc/testsuite/gnat.dg/strub_ind.adb
new file mode 100644
index 0000000000000..da56acaa957d2
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind.adb
@@ -0,0 +1,33 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict" }
+
+--  This is essentially the same test as strub_attr.adb, 
+--  but applying attributes to access types as well.
+--  That doesn't quite work yet, so we get an error we shouldn't get.
+
+package body Strub_Ind is
+   E : exception;
+
+   function G return Integer;
+
+   procedure P (X : Integer) is
+   begin
+      raise E;
+   end;
+   
+   function F (X : Integer) return Integer is
+   begin
+      return X * X;
+   end;
+   
+   function G return Integer is (FP (X));
+
+   type GT is access function return Integer;
+
+   type GT_SAC is access function return Integer;
+   pragma Machine_Attribute (GT_SAC, "strub", "at-calls");
+
+   GP : GT_SAC := GT_SAC (GT'(G'Access)); -- { dg-error "incompatible" }
+   -- pragma Machine_Attribute (GP, "strub", "at-calls");
+
+end Strub_Ind;
diff --git a/gcc/testsuite/gnat.dg/strub_ind.ads b/gcc/testsuite/gnat.dg/strub_ind.ads
new file mode 100644
index 0000000000000..99a65fc24b1ec
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind.ads
@@ -0,0 +1,17 @@
+package Strub_Ind is
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "internal");
+
+   function F (X : Integer) return Integer;
+   pragma Machine_Attribute (F, "strub");
+
+   X : Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+
+   type FT is access function (X : Integer) return Integer;
+   pragma Machine_Attribute (FT, "strub", "at-calls");
+
+   FP : FT := F'Access;
+   -- pragma Machine_Attribute (FP, "strub", "at-calls"); -- not needed
+
+end Strub_Ind;
diff --git a/gcc/testsuite/gnat.dg/strub_ind1.adb b/gcc/testsuite/gnat.dg/strub_ind1.adb
new file mode 100644
index 0000000000000..825e395e6819c
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind1.adb
@@ -0,0 +1,41 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
+
+--  This is essentially the same test as strub_attr.adb, 
+--  but with an explicit conversion.
+
+package body Strub_Ind1 is
+   E : exception;
+
+   type Strub_Int is New Integer;
+   pragma Machine_Attribute (Strub_Int, "strub");
+
+   function G return Integer;
+   pragma Machine_Attribute (G, "strub", "disabled");
+
+   procedure P (X : Integer) is
+   begin
+      raise E;
+   end;
+   
+   function G return Integer is (FP (X));
+
+   type GT is access function return Integer;
+   pragma Machine_Attribute (GT, "strub", "disabled");
+
+   type GT_SC is access function return Integer;
+   pragma Machine_Attribute (GT_SC, "strub", "callable");
+
+   GP : GT_SC := GT_SC (GT'(G'Access));
+   --  pragma Machine_Attribute (GP, "strub", "callable"); -- not needed.
+
+   function F (X : Integer) return Integer is
+   begin
+      return X * GP.all;
+   end;
+   
+end Strub_Ind1;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]disabled\[)\]\[)\]" 1 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 1 "strubm" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
diff --git a/gcc/testsuite/gnat.dg/strub_ind1.ads b/gcc/testsuite/gnat.dg/strub_ind1.ads
new file mode 100644
index 0000000000000..d3f1273b3a6b9
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind1.ads
@@ -0,0 +1,17 @@
+package Strub_Ind1 is
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "internal");
+
+   function F (X : Integer) return Integer;
+   pragma Machine_Attribute (F, "strub");
+
+   X : aliased Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+
+   type FT is access function (X : Integer) return Integer;
+   pragma Machine_Attribute (FT, "strub", "at-calls");
+
+   FP : FT := F'Access;
+   pragma Machine_Attribute (FP, "strub", "at-calls");
+
+end Strub_Ind1;
diff --git a/gcc/testsuite/gnat.dg/strub_ind2.adb b/gcc/testsuite/gnat.dg/strub_ind2.adb
new file mode 100644
index 0000000000000..e918b39263117
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind2.adb
@@ -0,0 +1,34 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict" }
+
+--  This is essentially the same test as strub_attr.adb, 
+--  but with an explicit conversion.
+
+package body Strub_Ind2 is
+   E : exception;
+
+   function G return Integer;
+   pragma Machine_Attribute (G, "strub", "callable");
+
+   procedure P (X : Integer) is
+   begin
+      raise E;
+   end;
+   
+   function G return Integer is (FP (X));
+
+   type GT is access function return Integer;
+   pragma Machine_Attribute (GT, "strub", "callable");
+
+   type GT_SD is access function return Integer;
+   pragma Machine_Attribute (GT_SD, "strub", "disabled");
+
+   GP : GT_SD := GT_SD (GT'(G'Access));
+   --  pragma Machine_Attribute (GP, "strub", "disabled"); -- not needed.
+
+   function F (X : Integer) return Integer is
+   begin
+      return X * GP.all; --  { dg-error "using non-.strub. type" }
+   end;
+   
+end Strub_Ind2;
diff --git a/gcc/testsuite/gnat.dg/strub_ind2.ads b/gcc/testsuite/gnat.dg/strub_ind2.ads
new file mode 100644
index 0000000000000..e13865ec49c38
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_ind2.ads
@@ -0,0 +1,17 @@
+package Strub_Ind2 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");
+
+   type FT is access function (X : Integer) return Integer;
+   pragma Machine_Attribute (FT, "strub", "at-calls");
+
+   FP : FT := F'Access;
+   pragma Machine_Attribute (FP, "strub", "at-calls");
+
+end Strub_Ind2;
diff --git a/gcc/testsuite/gnat.dg/strub_intf.adb b/gcc/testsuite/gnat.dg/strub_intf.adb
new file mode 100644
index 0000000000000..8f0212a75866f
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_intf.adb
@@ -0,0 +1,93 @@
+--  { dg-do compile }
+
+--  Check that strub mode mismatches between overrider and overridden
+--  subprograms are reported.
+
+procedure Strub_Intf is
+   package Foo is
+      type TP is interface;
+      procedure P (I : Integer; X : TP) is abstract;
+      pragma Machine_Attribute (P, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+
+      type TF is interface;
+      function F (X : access TF) return Integer is abstract;
+
+      type TX is interface;
+      procedure P (I : Integer; X : TX) is abstract;
+
+      type TI is interface and TP and TF and TX;
+      --  When we freeze TI, we detect the mismatch between the
+      --  inherited P and another parent's P.  Because TP appears
+      --  before TX, we inherit P from TP, and report the mismatch at
+      --  the pragma inherited from TP against TX's P.  In contrast,
+      --  when we freeze TII below, since TX appears before TP, we
+      --  report the error at the line in which the inherited
+      --  subprogram is synthesized, namely the line below, against
+      --  the line of the pragma.
+
+      type TII is interface and TX and TP and TF; -- { dg-error "requires the same .strub. mode" }
+
+      function F (X : access TI) return Integer is abstract;
+      pragma Machine_Attribute (F, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+
+      type A is new TI with null record;
+
+      procedure P (I : Integer; X : A);
+      pragma Machine_Attribute (P, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+      
+      function F (X : access A) return Integer; -- { dg-error "requires the same .strub. mode" }
+
+      type B is new TI with null record;
+      
+      overriding
+      procedure P (I : Integer; X : B); -- { dg-error "requires the same .strub. mode" }
+
+      overriding
+      function F (X : access B) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+
+   end Foo;
+
+   package body Foo is
+      procedure P (I : Integer; X : A) is
+      begin
+	 null;
+      end;
+      
+      function F (X : access A) return Integer is (0);
+
+      overriding
+      procedure P (I : Integer; X : B) is
+      begin
+	 null;
+      end;
+      
+      overriding
+      function F (X : access B) return Integer is (1);
+
+   end Foo;
+
+   use Foo;
+
+   procedure Q (X : TX'Class) is
+   begin
+      P (-1, X);
+   end;
+
+   XA : aliased A;
+   XB : aliased B;
+   I : Integer := 0;
+   XC : access TI'Class;
+begin
+   Q (XA);
+   Q (XB);
+   
+   I := I + F (XA'Access);
+   I := I + F (XB'Access);
+
+   XC := XA'Access;
+   I := I + F (XC);
+
+   XC := XB'Access;
+   I := I + F (XC);
+end Strub_Intf;
diff --git a/gcc/testsuite/gnat.dg/strub_intf1.adb b/gcc/testsuite/gnat.dg/strub_intf1.adb
new file mode 100644
index 0000000000000..bf77321cef790
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_intf1.adb
@@ -0,0 +1,86 @@
+--  { dg-do compile }
+--  { dg-options "-fdump-ipa-strub" }
+
+-- Check that at-calls dispatching calls to interfaces are transformed.
+
+procedure Strub_Intf1 is
+   package Foo is
+      type TX is Interface;
+      procedure P (I : Integer; X : TX) is abstract;
+      pragma Machine_Attribute (P, "strub", "at-calls");
+      function F (X : access TX) return Integer is abstract;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+      type A is new TX with null record;
+
+      procedure P (I : Integer; X : A);
+      pragma Machine_Attribute (P, "strub", "at-calls");
+      
+      function F (X : access A) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+      type B is new TX with null record;
+      
+      overriding
+      procedure P (I : Integer; X : B);
+      pragma Machine_Attribute (P, "strub", "at-calls");
+
+      overriding
+      function F (X : access B) return Integer;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+   end Foo;
+
+   package body Foo is
+      procedure P (I : Integer; X : A) is
+      begin
+	 null;
+      end;
+      
+      function F (X : access A) return Integer is (0);
+
+      overriding
+      procedure P (I : Integer; X : B) is
+      begin
+	 null;
+      end;
+      
+      overriding
+      function F (X : access B) return Integer is (1);
+
+   end Foo;
+
+   use Foo;
+
+   procedure Q (X : TX'Class) is
+   begin
+      P (-1, X);
+   end;
+
+   XA : aliased A;
+   XB : aliased B;
+   I : Integer := 0;
+   XC : access TX'Class;
+begin
+   Q (XA);
+   Q (XB);
+   
+   I := I + F (XA'Access);
+   I := I + F (XB'Access);
+
+   XC := XA'Access;
+   I := I + F (XC);
+
+   XC := XB'Access;
+   I := I + F (XC);
+end Strub_Intf1;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 4 "strub" } }
+
+--  Count the strub-at-calls non-dispatching calls 
+--  (+ 2 each, for the matching prototypes)
+--  { dg-final { scan-ipa-dump-times "foo\.p \[(\]\[^\n\]*watermark" 2 "strub" } }
+--  { dg-final { scan-ipa-dump-times "foo\.f \[(\]\[^\n\]*watermark" 4 "strub" } }
+
+--  Count the strub-at-calls dispatching calls.
+--  { dg-final { scan-ipa-dump-times "_\[0-9\]* \[(\]\[^\n\]*watermark" 3 "strub" } }
diff --git a/gcc/testsuite/gnat.dg/strub_intf2.adb b/gcc/testsuite/gnat.dg/strub_intf2.adb
new file mode 100644
index 0000000000000..e8880dbc43730
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_intf2.adb
@@ -0,0 +1,55 @@
+--  { dg-do compile }
+
+--  Check that strub mode mismatches between overrider and overridden
+--  subprograms are reported even when the overriders for an
+--  interface's subprograms are inherited from a type that is not a
+--  descendent of the interface.
+
+procedure Strub_Intf2 is
+   package Foo is
+      type A is tagged null record;
+
+      procedure P (I : Integer; X : A);
+      pragma Machine_Attribute (P, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
+      
+      function F (X : access A) return Integer;
+
+      type TX is Interface;
+
+      procedure P (I : Integer; X : TX) is abstract; 
+
+      function F (X : access TX) return Integer is abstract;
+      pragma Machine_Attribute (F, "strub", "at-calls");
+
+      type B is new A and TX with null record; -- { dg-error "requires the same .strub. mode" }
+
+   end Foo;
+
+   package body Foo is
+      procedure P (I : Integer; X : A) is
+      begin
+	 null;
+      end;
+      
+      function F (X : access A) return Integer is (0);
+
+   end Foo;
+
+   use Foo;
+
+   procedure Q (X : TX'Class) is
+   begin
+      P (-1, X);
+   end;
+
+   XB : aliased B;
+   I : Integer := 0;
+   XC : access TX'Class;
+begin
+   Q (XB);
+   
+   I := I + F (XB'Access);
+
+   XC := XB'Access;
+   I := I + F (XC);
+end Strub_Intf2;
diff --git a/gcc/testsuite/gnat.dg/strub_renm.adb b/gcc/testsuite/gnat.dg/strub_renm.adb
new file mode 100644
index 0000000000000..217367e712d82
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_renm.adb
@@ -0,0 +1,21 @@
+--  { dg-do compile }
+
+procedure Strub_Renm is
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "at-calls");
+
+   function F return Integer;
+   pragma Machine_Attribute (F, "strub", "internal");
+
+   procedure Q (X : Integer) renames P; -- { dg-error "requires the same .strub. mode" }
+
+   function G return Integer renames F;
+   pragma Machine_Attribute (G, "strub", "callable"); -- { dg-error "requires the same .strub. mode" }
+
+   procedure P (X : Integer) is null;
+   function F return Integer is (0);
+
+begin
+   P (F);
+   Q (G);
+end Strub_Renm;
diff --git a/gcc/testsuite/gnat.dg/strub_renm1.adb b/gcc/testsuite/gnat.dg/strub_renm1.adb
new file mode 100644
index 0000000000000..a11adbfb5a9d6
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_renm1.adb
@@ -0,0 +1,32 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=relaxed -fdump-ipa-strub" }
+
+procedure Strub_Renm1 is
+   V : Integer := 0;
+   pragma Machine_Attribute (V, "strub");
+
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "at-calls");
+
+   function F return Integer;
+
+   procedure Q (X : Integer) renames P;
+   pragma Machine_Attribute (Q, "strub", "at-calls");
+
+   function G return Integer renames F;
+   pragma Machine_Attribute (G, "strub", "internal");
+
+   procedure P (X : Integer) is null;
+   function F return Integer is (0);
+
+begin
+   P (F);
+   Q (G);
+end Strub_Renm1;
+
+--  This is for P; Q is an alias.
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 1 "strub" } }
+
+--  This is *not* for G, but for Strub_Renm1.
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]wrapped\[)\]\[)\]" 1 "strub" } }
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]wrapper\[)\]\[)\]" 1 "strub" } }
diff --git a/gcc/testsuite/gnat.dg/strub_renm2.adb b/gcc/testsuite/gnat.dg/strub_renm2.adb
new file mode 100644
index 0000000000000..c488c20826fdb
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_renm2.adb
@@ -0,0 +1,32 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strub" }
+
+procedure Strub_Renm2 is
+   V : Integer := 0;
+   pragma Machine_Attribute (V, "strub");
+
+   procedure P (X : Integer);
+   pragma Machine_Attribute (P, "strub", "at-calls");
+
+   function F return Integer;
+
+   procedure Q (X : Integer) renames P;
+   pragma Machine_Attribute (Q, "strub", "at-calls");
+
+   type T is access function return Integer;
+
+   type TC is access function return Integer;
+   pragma Machine_Attribute (TC, "strub", "callable");
+
+   FCptr : constant TC := TC (T'(F'Access));
+
+   function G return Integer renames FCptr.all;
+   pragma Machine_Attribute (G, "strub", "callable");
+
+   procedure P (X : Integer) is null;
+   function F return Integer is (0);
+
+begin
+   P (F);  -- { dg-error "calling non-.strub." }
+   Q (G);  -- ok, G is callable.
+end Strub_Renm2;
diff --git a/gcc/testsuite/gnat.dg/strub_var.adb b/gcc/testsuite/gnat.dg/strub_var.adb
new file mode 100644
index 0000000000000..3d158de28031f
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_var.adb
@@ -0,0 +1,16 @@
+--  { dg-do compile }
+--  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
+
+-- We don't read from the automatic variable, but being an automatic
+--  variable, its presence should be enough for the procedure to get
+--  strub enabled.
+
+with Strub_Attr;
+procedure Strub_Var is
+   X : Integer := 0;
+   pragma Machine_Attribute (X, "strub");
+begin
+   X := Strub_Attr.F (0);
+end Strub_Var;
+
+--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 1 "strubm" } }
diff --git a/gcc/testsuite/gnat.dg/strub_var1.adb b/gcc/testsuite/gnat.dg/strub_var1.adb
new file mode 100644
index 0000000000000..6a504e09198b6
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/strub_var1.adb
@@ -0,0 +1,20 @@
+--  { dg-do compile }
+
+with Strub_Attr;
+procedure Strub_Var1 is
+   type TA  -- { dg-warning "does not apply to elements" }
+      is array (1..2) of Integer;
+   pragma Machine_Attribute (TA, "strub");
+   
+   A : TA := (0, 0);  -- { dg-warning "does not apply to elements" }
+   
+   type TR is record  -- { dg-warning "does not apply to fields" }
+      M, N : Integer;
+   end record;
+   pragma Machine_Attribute (TR, "strub");
+   
+   R : TR := (0, 0);
+
+begin
+   A(2) := Strub_Attr.F (A(1));
+end Strub_Var1;
diff --git a/gcc/tree-cfg.cc b/gcc/tree-cfg.cc
index a30a2de33a106..cefbb86be7bb4 100644
--- a/gcc/tree-cfg.cc
+++ b/gcc/tree-cfg.cc
@@ -5790,6 +5790,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 09e6ada5b2f91..36f955a0b37ee 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -510,8 +510,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.cc b/gcc/tree-ssa-ccp.cc
index 1a555ae682638..03ff88afadddd 100644
--- a/gcc/tree-ssa-ccp.cc
+++ b/gcc/tree-ssa-ccp.cc
@@ -3073,7 +3073,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 8dedd10f79a30..d8163c5af9903 100644
--- a/libgcc/Makefile.in
+++ b/libgcc/Makefile.in
@@ -433,6 +433,9 @@ LIB2ADD += enable-execute-stack.c
 # Control Flow Redundancy hardening out-of-line checker.
 LIB2ADD += $(srcdir)/hardcfr.c
 
+# Stack scrubbing infrastructure.
+LIB2ADD += $(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
 # (e.g. *-*-darwin*) only one copy of it is linked.
diff --git a/libgcc/libgcc-std.ver.in b/libgcc/libgcc-std.ver.in
index de00db647570c..a6b89632cae3e 100644
--- a/libgcc/libgcc-std.ver.in
+++ b/libgcc/libgcc-std.ver.in
@@ -1957,4 +1957,7 @@ GCC_14.0.0 {
   __PFX__floatbitintsf
   __PFX__floatbitintdf
   __PFX__hardcfr_check
+  __PFX__strub_enter
+  __PFX__strub_update
+  __PFX__strub_leave
 }
diff --git a/libgcc/libgcc2.h b/libgcc/libgcc2.h
index 7e6696d7ab14a..750670e8caaf3 100644
--- a/libgcc/libgcc2.h
+++ b/libgcc/libgcc2.h
@@ -554,6 +554,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 0000000000000..b0f990d9deebb
--- /dev/null
+++ b/libgcc/strub.c
@@ -0,0 +1,149 @@
+/* Stack scrubbing infrastructure
+   Copyright (C) 2021-2023 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"
+
+#if ! STACK_GROWS_DOWNWARD
+# define TOPS >
+#else
+# define TOPS <
+#endif
+
+#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ ("callable")))
+
+/* 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;
+}
+
+#if TARGET_STRUB_USE_DYNAMIC_ARRAY && ! defined TARGET_STRUB_MAY_USE_MEMSET
+# define TARGET_STRUB_MAY_USE_MEMSET 1
+#endif
+
+#if defined __x86_64__ && __OPTIMIZE__
+# define TARGET_STRUB_DISABLE_RED_ZONE \
+  /* __attribute__ ((__target__ ("no-red-zone"))) // not needed when optimizing */
+#elif !defined RED_ZONE_SIZE || defined __i386__
+# define TARGET_STRUB_DISABLE_RED_ZONE
+#endif
+
+#ifndef TARGET_STRUB_DISABLE_RED_ZONE
+/* Dummy function, called to force the caller to not be a leaf function, so
+   that it can't use the red zone.  */
+static void ATTRIBUTE_STRUB_CALLABLE
+__attribute__ ((__noinline__, __noipa__))
+__strub_dummy_force_no_leaf (void)
+{
+}
+#endif
+
+/* Leave a stack scrubbing context, clearing the stack between its top and
+   *MARK.  */
+void ATTRIBUTE_STRUB_CALLABLE
+#if ! TARGET_STRUB_MAY_USE_MEMSET
+__attribute__ ((__optimize__ ("-fno-tree-loop-distribute-patterns")))
+#endif
+#ifdef TARGET_STRUB_DISABLE_RED_ZONE
+TARGET_STRUB_DISABLE_RED_ZONE
+#endif
+__strub_leave (void **mark)
+{
+  void *sp = __builtin_stack_address ();
+
+  void **base, **end;
+#if ! STACK_GROWS_DOWNWARD
+  base = sp; /* ??? Do we need an offset here?  */
+  end = *mark;
+#else
+  base = *mark;
+  end = sp; /* ??? Does any platform require an offset here?  */
+#endif
+
+  if (! (base < end))
+    return;
+
+#if TARGET_STRUB_USE_DYNAMIC_ARRAY
+  /* Compute the length without assuming the pointers are both sufficiently
+     aligned.  They should be, but pointer differences expected to be exact may
+     yield unexpected results when the assumption doesn't hold.  Given the
+     potential security implications, compute the length without that
+     expectation.  If the pointers are misaligned, we may leave a partial
+     unscrubbed word behind.  */
+  ptrdiff_t len = ((char *)end - (char *)base) / sizeof (void *);
+  /* 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];
+#elifndef TARGET_STRUB_DISABLE_RED_ZONE
+  /* Prevent the use of the red zone, by making this function non-leaf through
+     an unreachable call that, because of the asm stmt, the compiler will
+     consider reachable.  */
+  asm goto ("" : : : : no_leaf);
+  if (0)
+    {
+    no_leaf:
+      __strub_dummy_force_no_leaf ();
+      return;
+    }
+#endif
+
+  /* ldist may turn these loops into a memset (thus the conditional
+     -fno-tree-loop-distribute-patterns above).  Without the dynamic array
+     above, that call would likely be unsafe: possibly tail-called, and likely
+     scribbling over its own stack frame.  */
+#if ! STACK_GROWS_DOWNWARD
+  do
+    *base++ = 0;
+  while (base < end);
+  /* Make sure the stack overwrites are not optimized away.  */
+  asm ("" : : "m" (end[0]));
+#else
+  do
+    *--end = 0;
+  while (base < end);
+  /* Make sure the stack overwrites are not optimized away.  */
+  asm ("" : : "m" (base[0]));
+#endif
+}


-- 
Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
   Free Software Activist                   GNU Toolchain Engineer
More tolerance and less prejudice are key for inclusion and diversity
Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH v5] Introduce strub: machine-independent stack scrubbing
  2023-12-02 17:56                           ` [PATCH v5] " Alexandre Oliva
@ 2023-12-05  6:25                             ` Alexandre Oliva
  2023-12-06  1:04                               ` Alexandre Oliva
  2023-12-05  9:01                             ` Richard Biener
                                               ` (3 subsequent siblings)
  4 siblings, 1 reply; 59+ messages in thread
From: Alexandre Oliva @ 2023-12-05  6:25 UTC (permalink / raw)
  To: Richard Biener
  Cc: gcc-patches, Jeremy Bennett, Craig Blackmore, Graham Markall,
	Martin Jambor, Jan Hubicka, Jim Wilson, Jeff Law, Jakub Jelinek

The recently-installed patch for interprocedural value-range propagation
enabled some folding that was not expected by the strub-const testcases,
causing them to fail.

I'm making the following adjustments to them to restore the behavior
they tested for, and to make them more future-proof to future
improvements of ivrp.

I intend to install this as part of the monster patch upthread.


--- a/gcc/testsuite/c-c++-common/torture/strub-const1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -1,18 +1,22 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
 
-/* Check that, along with a strub const function call, we issue an asm statement
-   to make sure the watermark passed to it is held in memory before the call,
-   and another to make sure it is not assumed to be unchanged.  */
+/* 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.  f
+   should not be inlined into g, but if it were too simple it might be folded
+   by interprocedural value-range propagation.  */
+
+extern int __attribute__ ((__strub__ ("callable"), __const__)) c ();
 
 int __attribute__ ((__strub__, __const__))
-f() {
-  return 0;
+f () {
+  return c ();
 }
 
 int
-g() {
-  return f();
+g () {
+  return f ();
 }
 
 /* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
--- a/gcc/testsuite/c-c++-common/torture/strub-const2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -6,17 +6,19 @@
    before the call, and another to make sure it is not assumed to be
    unchanged.  */
 
+extern int __attribute__ ((__strub__ ("callable"), __const__)) c ();
+
 int __attribute__ ((__strub__))
 #if ! __OPTIMIZE__
 __attribute__ ((__const__))
 #endif
-f() {
-  return 0;
+f () {
+  return c ();
 }
 
 int
-g() {
-  return f();
+g () {
+  return f ();
 }
 
 /* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */


-- 
Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
   Free Software Activist                   GNU Toolchain Engineer
More tolerance and less prejudice are key for inclusion and diversity
Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH v5] Introduce strub: machine-independent stack scrubbing
  2023-12-02 17:56                           ` [PATCH v5] " Alexandre Oliva
  2023-12-05  6:25                             ` Alexandre Oliva
@ 2023-12-05  9:01                             ` Richard Biener
  2023-12-06  8:36                             ` Causes to nvptx bootstrap fail: " Tobias Burnus
                                               ` (2 subsequent siblings)
  4 siblings, 0 replies; 59+ messages in thread
From: Richard Biener @ 2023-12-05  9:01 UTC (permalink / raw)
  To: Alexandre Oliva
  Cc: gcc-patches, Jeremy Bennett, Craig Blackmore, Graham Markall,
	Martin Jambor, Jan Hubicka, Jim Wilson, Jeff Law, Jakub Jelinek

On Sat, Dec 2, 2023 at 6:56 PM Alexandre Oliva <oliva@adacore.com> wrote:
>
> On Nov 30, 2023, Richard Biener <richard.guenther@gmail.com> wrote:
>
> > On Thu, Nov 30, 2023 at 5:13 AM Alexandre Oliva <oliva@adacore.com> wrote:
>
> >> >> Here are changes.html entries for this and for the other newly-added
> >> >> features:
> >>
> >> > LGTM.
> >>
> >> Was that an ok to install, once the relevant pieces are in?
>
> > See below.
>
> FWIW, I meant specifically about the web site changes.html patch in the
> question above.

Ah yes, of course (OK)!

> > In the end I was hoping for general comments on the cgraph usage
> > and for the specifics indeed being able to use IPA mechanisms
> > to perform the stmt re-writing (and re-gimplification) for the value to
> > by-reference replacement.  The core copy_body mechanism
> > might support it by simply registering *new_param as replacement
> > for 'old_param' in the copy_body_data decl_map.
>
> I see.  That's indeed a good direction for evolution, that I'd like us
> to take, and having the base code in with some more manageable way to
> experiment smaller changes will make this transition significantly
> easier IMHO.  But that kind of infrastructure change would also likely
> be stage1 material.

Agreed.

> > Iff the IPA folks (Honza or Martin) don't have any further comments
> > the patch is OK to install by next Monday.
>
> Thanks, here's the consolidated patch that I hope to install by Monday.

Thanks,
Richard.

>
> Introduce strub: machine-independent stack scrubbing
>
> This patch adds the strub attribute for function and variable types,
> command-line options, passes and adjustments to implement it,
> documentation, and tests.
>
> Stack scrubbing is implemented in a machine-independent way: functions
> with strub enabled are modified so that they take an extra stack
> watermark argument, that they update with their stack use, and the
> caller can then zero it out once it regains control, whether by return
> or exception.  There are two ways to go about it: at-calls, that
> modifies the visible interface (signature) of the function, and
> internal, in which the body is moved to a clone, the clone undergoes
> the interface change, and the function becomes a wrapper, preserving
> its original interface, that calls the clone and then clears the stack
> used by it.
>
> Variables can also be annotated with the strub attribute, so that
> functions that read from them get stack scrubbing enabled implicitly,
> whether at-calls, for functions only usable within a translation unit,
> or internal, for functions whose interfaces must not be modified.
>
> There is a strict mode, in which functions that have their stack
> scrubbed can only call other functions with stack-scrubbing
> interfaces, or those explicitly marked as callable from strub
> contexts, so that an entire call chain gets scrubbing, at once or
> piecemeal depending on optimization levels.  In the default mode,
> relaxed, this requirement is not enforced by the compiler.
>
> The implementation adds two IPA passes, one that assigns strub modes
> early on, another that modifies interfaces and adds calls to the
> builtins that jointly implement stack scrubbing.  Another builtin,
> that obtains the stack pointer, is added for use in the implementation
> of the builtins, whether expanded inline or called in libgcc.
>
> There are new command-line options to change operation modes and to
> force the feature disabled; it is enabled by default, but it has no
> effect and is implicitly disabled if the strub attribute is never
> used.  There are also options meant to use for testing the feature,
> enabling different strubbing modes for all (viable) functions.
>
>
> for  gcc/ChangeLog
>
>         * Makefile.in (OBJS): Add ipa-strub.o.
>         (GTFILES): Add ipa-strub.cc.
>         * builtins.def (BUILT_IN_STACK_ADDRESS): New.
>         (BUILT_IN___STRUB_ENTER): New.
>         (BUILT_IN___STRUB_UPDATE): New.
>         (BUILT_IN___STRUB_LEAVE): New.
>         * builtins.cc: Include ipa-strub.h.
>         (STACK_STOPS, STACK_UNSIGNED): Define.
>         (expand_builtin_stack_address): New.
>         (expand_builtin_strub_enter): New.
>         (expand_builtin_strub_update): New.
>         (expand_builtin_strub_leave): New.
>         (expand_builtin): Call them.
>         * common.opt (fstrub=*): New options.
>         * doc/extend.texi (strub): New type attribute.
>         (__builtin_stack_address): New function.
>         (Stack Scrubbing): New section.
>         * doc/invoke.texi (-fstrub=*): New options.
>         (-fdump-ipa-*): New passes.
>         * gengtype-lex.l: Ignore multi-line pp-directives.
>         * ipa-inline.cc: Include ipa-strub.h.
>         (can_inline_edge_p): Test strub_inlinable_to_p.
>         * ipa-split.cc: Include ipa-strub.h.
>         (execute_split_functions): Test strub_splittable_p.
>         * ipa-strub.cc, ipa-strub.h: New.
>         * passes.def: Add strub_mode and strub passes.
>         * tree-cfg.cc (gimple_verify_flow_info): Note on debug stmts.
>         * tree-pass.h (make_pass_ipa_strub_mode): Declare.
>         (make_pass_ipa_strub): Declare.
>         (make_pass_ipa_function_and_variable_visibility): Fix
>         formatting.
>         * tree-ssa-ccp.cc (optimize_stack_restore): Keep restores
>         before strub leave.
>         * multiple_target.cc (pass_target_clone::gate): Test seen_error.
>         * attribs.cc: Include ipa-strub.h.
>         (decl_attributes): Support applying attributes to function
>         type, rather than pointer type, at handler's request.
>         (comp_type_attributes): Combine strub_comptypes and target
>         comp_type results.
>         * doc/tm.texi.in (TARGET_STRUB_USE_DYNAMIC_ARRAY): New.
>         (TARGET_STRUB_MAY_USE_MEMSET): New.
>         * doc/tm.texi: Rebuilt.
>         * cgraph.h (symtab_node::reset): Add preserve_comdat_group
>         param, with a default.
>         * cgraphunit.cc (symtab_node::reset): Use it.
>
> for  gcc/c-family/ChangeLog
>
>         * c-attribs.cc: Include ipa-strub.h.
>         (handle_strub_attribute): New.
>         (c_common_attribute_table): Add strub.
>
> for  gcc/ada/ChangeLog
>
>         * gcc-interface/trans.cc: Include ipa-strub.h.
>         (gigi): Make internal decls for targets of compiler-generated
>         calls strub-callable too.
>         (build_raise_check): Likewise.
>         * gcc-interface/utils.cc: Include ipa-strub.h.
>         (handle_strub_attribute): New.
>         (gnat_internal_attribute_table): Add strub.
>
> for  gcc/testsuite/ChangeLog
>
>         * c-c++-common/strub-O0.c: New.
>         * c-c++-common/strub-O1.c: New.
>         * c-c++-common/strub-O2.c: New.
>         * c-c++-common/strub-O2fni.c: New.
>         * c-c++-common/strub-O3.c: New.
>         * c-c++-common/strub-O3fni.c: New.
>         * c-c++-common/strub-Og.c: New.
>         * c-c++-common/strub-Os.c: New.
>         * c-c++-common/strub-all1.c: New.
>         * c-c++-common/strub-all2.c: New.
>         * c-c++-common/strub-apply1.c: New.
>         * c-c++-common/strub-apply2.c: New.
>         * c-c++-common/strub-apply3.c: New.
>         * c-c++-common/strub-apply4.c: New.
>         * c-c++-common/strub-at-calls1.c: New.
>         * c-c++-common/strub-at-calls2.c: New.
>         * c-c++-common/strub-defer-O1.c: New.
>         * c-c++-common/strub-defer-O2.c: New.
>         * c-c++-common/strub-defer-O3.c: New.
>         * c-c++-common/strub-defer-Os.c: New.
>         * c-c++-common/strub-internal1.c: New.
>         * c-c++-common/strub-internal2.c: New.
>         * c-c++-common/strub-parms1.c: New.
>         * c-c++-common/strub-parms2.c: New.
>         * c-c++-common/strub-parms3.c: New.
>         * c-c++-common/strub-relaxed1.c: New.
>         * c-c++-common/strub-relaxed2.c: New.
>         * c-c++-common/strub-short-O0-exc.c: New.
>         * c-c++-common/strub-short-O0.c: New.
>         * c-c++-common/strub-short-O1.c: New.
>         * c-c++-common/strub-short-O2.c: New.
>         * c-c++-common/strub-short-O3.c: New.
>         * c-c++-common/strub-short-Os.c: New.
>         * c-c++-common/strub-strict1.c: New.
>         * c-c++-common/strub-strict2.c: New.
>         * c-c++-common/strub-tail-O1.c: New.
>         * c-c++-common/strub-tail-O2.c: New.
>         * c-c++-common/torture/strub-callable1.c: New.
>         * c-c++-common/torture/strub-callable2.c: New.
>         * c-c++-common/torture/strub-const1.c: New.
>         * c-c++-common/torture/strub-const2.c: New.
>         * c-c++-common/torture/strub-const3.c: New.
>         * c-c++-common/torture/strub-const4.c: New.
>         * c-c++-common/torture/strub-data1.c: New.
>         * c-c++-common/torture/strub-data2.c: New.
>         * c-c++-common/torture/strub-data3.c: New.
>         * c-c++-common/torture/strub-data4.c: New.
>         * c-c++-common/torture/strub-data5.c: New.
>         * c-c++-common/torture/strub-indcall1.c: New.
>         * c-c++-common/torture/strub-indcall2.c: New.
>         * c-c++-common/torture/strub-indcall3.c: New.
>         * c-c++-common/torture/strub-inlinable1.c: New.
>         * c-c++-common/torture/strub-inlinable2.c: New.
>         * c-c++-common/torture/strub-ptrfn1.c: New.
>         * c-c++-common/torture/strub-ptrfn2.c: New.
>         * c-c++-common/torture/strub-ptrfn3.c: New.
>         * c-c++-common/torture/strub-ptrfn4.c: New.
>         * c-c++-common/torture/strub-pure1.c: New.
>         * c-c++-common/torture/strub-pure2.c: New.
>         * c-c++-common/torture/strub-pure3.c: New.
>         * c-c++-common/torture/strub-pure4.c: New.
>         * c-c++-common/torture/strub-run1.c: New.
>         * c-c++-common/torture/strub-run2.c: New.
>         * c-c++-common/torture/strub-run3.c: New.
>         * c-c++-common/torture/strub-run4.c: New.
>         * c-c++-common/torture/strub-run4c.c: New.
>         * c-c++-common/torture/strub-run4d.c: New.
>         * c-c++-common/torture/strub-run4i.c: New.
>         * g++.dg/strub-run1.C: New.
>         * g++.dg/torture/strub-init1.C: New.
>         * g++.dg/torture/strub-init2.C: New.
>         * g++.dg/torture/strub-init3.C: New.
>         * gnat.dg/strub_attr.adb, gnat.dg/strub_attr.ads: New.
>         * gnat.dg/strub_ind.adb, gnat.dg/strub_ind.ads: New.
>
> for  libgcc/ChangeLog
>
>         * Makefile.in (LIB2ADD): Add strub.c.
>         * libgcc2.h (__strub_enter, __strub_update, __strub_leave):
>         Declare.
>         * strub.c: New.
>         * libgcc-std.ver.in (__strub_enter): Add to GCC_14.0.0.
>         (__strub_update, __strub_leave): Likewise.
> ---
>  gcc/Makefile.in                                    |    2
>  gcc/ada/gcc-interface/trans.cc                     |   18
>  gcc/ada/gcc-interface/utils.cc                     |   73
>  gcc/attribs.cc                                     |   37
>  gcc/builtins.cc                                    |  269 ++
>  gcc/builtins.def                                   |    4
>  gcc/c-family/c-attribs.cc                          |   82
>  gcc/cgraph.h                                       |    2
>  gcc/cgraphunit.cc                                  |    5
>  gcc/common.opt                                     |   29
>  gcc/doc/extend.texi                                |  312 ++
>  gcc/doc/invoke.texi                                |   60
>  gcc/doc/tm.texi                                    |   19
>  gcc/doc/tm.texi.in                                 |   19
>  gcc/gengtype-lex.l                                 |    3
>  gcc/ipa-inline.cc                                  |    6
>  gcc/ipa-split.cc                                   |    7
>  gcc/ipa-strub.cc                                   | 3573 ++++++++++++++++++++
>  gcc/ipa-strub.h                                    |   45
>  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-defer-O1.c        |    7
>  gcc/testsuite/c-c++-common/strub-defer-O2.c        |    8
>  gcc/testsuite/c-c++-common/strub-defer-O3.c        |  110 +
>  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-relaxed1.c        |   18
>  gcc/testsuite/c-c++-common/strub-relaxed2.c        |   14
>  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-strict1.c         |   36
>  gcc/testsuite/c-c++-common/strub-strict2.c         |   25
>  gcc/testsuite/c-c++-common/strub-tail-O1.c         |    8
>  gcc/testsuite/c-c++-common/strub-tail-O2.c         |   14
>  gcc/testsuite/c-c++-common/strub-var1.c            |   24
>  .../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   |   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
>  .../c-c++-common/torture/strub-inlinable1.c        |   16
>  .../c-c++-common/torture/strub-inlinable2.c        |    7
>  gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |   10
>  gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |   55
>  gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c  |   50
>  gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c  |   43
>  gcc/testsuite/c-c++-common/torture/strub-pure1.c   |   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    |   95 +
>  gcc/testsuite/c-c++-common/torture/strub-run2.c    |   84
>  gcc/testsuite/c-c++-common/torture/strub-run3.c    |   80
>  gcc/testsuite/c-c++-common/torture/strub-run4.c    |  106 +
>  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_access.adb             |   21
>  gcc/testsuite/gnat.dg/strub_access1.adb            |   16
>  gcc/testsuite/gnat.dg/strub_attr.adb               |   37
>  gcc/testsuite/gnat.dg/strub_attr.ads               |   12
>  gcc/testsuite/gnat.dg/strub_disp.adb               |   64
>  gcc/testsuite/gnat.dg/strub_disp1.adb              |   79
>  gcc/testsuite/gnat.dg/strub_ind.adb                |   33
>  gcc/testsuite/gnat.dg/strub_ind.ads                |   17
>  gcc/testsuite/gnat.dg/strub_ind1.adb               |   41
>  gcc/testsuite/gnat.dg/strub_ind1.ads               |   17
>  gcc/testsuite/gnat.dg/strub_ind2.adb               |   34
>  gcc/testsuite/gnat.dg/strub_ind2.ads               |   17
>  gcc/testsuite/gnat.dg/strub_intf.adb               |   93 +
>  gcc/testsuite/gnat.dg/strub_intf1.adb              |   86
>  gcc/testsuite/gnat.dg/strub_intf2.adb              |   55
>  gcc/testsuite/gnat.dg/strub_renm.adb               |   21
>  gcc/testsuite/gnat.dg/strub_renm1.adb              |   32
>  gcc/testsuite/gnat.dg/strub_renm2.adb              |   32
>  gcc/testsuite/gnat.dg/strub_var.adb                |   16
>  gcc/testsuite/gnat.dg/strub_var1.adb               |   20
>  gcc/tree-cfg.cc                                    |    1
>  gcc/tree-pass.h                                    |    5
>  gcc/tree-ssa-ccp.cc                                |    4
>  libgcc/Makefile.in                                 |    3
>  libgcc/libgcc-std.ver.in                           |    3
>  libgcc/libgcc2.h                                   |    4
>  libgcc/strub.c                                     |  149 +
>  120 files changed, 7429 insertions(+), 12 deletions(-)
>  create mode 100644 gcc/ipa-strub.cc
>  create mode 100644 gcc/ipa-strub.h
>  create mode 100644 gcc/testsuite/c-c++-common/strub-O0.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-O1.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-O2.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-O2fni.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-O3.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-O3fni.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-Og.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-Os.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-all1.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-all2.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-apply1.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-apply2.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-apply3.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-apply4.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-at-calls1.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-at-calls2.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-defer-O1.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-defer-O2.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-defer-O3.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-defer-Os.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-internal1.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-internal2.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-parms1.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-parms2.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-parms3.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-relaxed1.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-relaxed2.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-short-O0-exc.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-short-O0.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-short-O1.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-short-O2.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-short-O3.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-short-Os.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-strict1.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-strict2.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-tail-O1.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-tail-O2.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-var1.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-callable1.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-callable2.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const1.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const2.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const3.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-const4.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data1.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data2.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data3.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data4.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-data5.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-indcall1.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-indcall2.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-indcall3.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure1.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure2.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure3.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-pure4.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run1.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run2.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run3.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4c.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4d.c
>  create mode 100644 gcc/testsuite/c-c++-common/torture/strub-run4i.c
>  create mode 100644 gcc/testsuite/g++.dg/strub-run1.C
>  create mode 100644 gcc/testsuite/g++.dg/torture/strub-init1.C
>  create mode 100644 gcc/testsuite/g++.dg/torture/strub-init2.C
>  create mode 100644 gcc/testsuite/g++.dg/torture/strub-init3.C
>  create mode 100644 gcc/testsuite/gnat.dg/strub_access.adb
>  create mode 100644 gcc/testsuite/gnat.dg/strub_access1.adb
>  create mode 100644 gcc/testsuite/gnat.dg/strub_attr.adb
>  create mode 100644 gcc/testsuite/gnat.dg/strub_attr.ads
>  create mode 100644 gcc/testsuite/gnat.dg/strub_disp.adb
>  create mode 100644 gcc/testsuite/gnat.dg/strub_disp1.adb
>  create mode 100644 gcc/testsuite/gnat.dg/strub_ind.adb
>  create mode 100644 gcc/testsuite/gnat.dg/strub_ind.ads
>  create mode 100644 gcc/testsuite/gnat.dg/strub_ind1.adb
>  create mode 100644 gcc/testsuite/gnat.dg/strub_ind1.ads
>  create mode 100644 gcc/testsuite/gnat.dg/strub_ind2.adb
>  create mode 100644 gcc/testsuite/gnat.dg/strub_ind2.ads
>  create mode 100644 gcc/testsuite/gnat.dg/strub_intf.adb
>  create mode 100644 gcc/testsuite/gnat.dg/strub_intf1.adb
>  create mode 100644 gcc/testsuite/gnat.dg/strub_intf2.adb
>  create mode 100644 gcc/testsuite/gnat.dg/strub_renm.adb
>  create mode 100644 gcc/testsuite/gnat.dg/strub_renm1.adb
>  create mode 100644 gcc/testsuite/gnat.dg/strub_renm2.adb
>  create mode 100644 gcc/testsuite/gnat.dg/strub_var.adb
>  create mode 100644 gcc/testsuite/gnat.dg/strub_var1.adb
>  create mode 100644 libgcc/strub.c
>
> diff --git a/gcc/Makefile.in b/gcc/Makefile.in
> index e154f7c0055f9..68410a86af5a9 100644
> --- a/gcc/Makefile.in
> +++ b/gcc/Makefile.in
> @@ -1557,6 +1557,7 @@ OBJS = \
>         ipa-reference.o \
>         ipa-ref.o \
>         ipa-utils.o \
> +       ipa-strub.o \
>         ipa.o \
>         ira.o \
>         ira-build.o \
> @@ -2879,6 +2880,7 @@ GTFILES = $(CPPLIB_H) $(srcdir)/input.h $(srcdir)/coretypes.h \
>    $(srcdir)/sanopt.cc \
>    $(srcdir)/sancov.cc \
>    $(srcdir)/ipa-devirt.cc \
> +  $(srcdir)/ipa-strub.cc \
>    $(srcdir)/internal-fn.h \
>    $(srcdir)/calls.cc \
>    $(srcdir)/omp-general.h \
> diff --git a/gcc/ada/gcc-interface/trans.cc b/gcc/ada/gcc-interface/trans.cc
> index 9c418beda9643..5e9e92d8b7257 100644
> --- a/gcc/ada/gcc-interface/trans.cc
> +++ b/gcc/ada/gcc-interface/trans.cc
> @@ -69,6 +69,21 @@
>  #include "ada-tree.h"
>  #include "gigi.h"
>
> +/* The following #include is for strub_make_callable.
> +
> +   This function marks a function as safe to call from strub contexts.  We mark
> +   Ada subprograms that may be called implicitly by the compiler, and that won't
> +   leave on the stack caller data passed to them.  This stops implicit calls
> +   introduced in subprograms that have their stack scrubbed from being flagged
> +   as unsafe, even in -fstrub=strict mode.
> +
> +   These subprograms are also marked with the strub(callable) attribute in Ada
> +   sources, but their declarations aren't necessarily imported by GNAT, or made
> +   visible to gigi, in units that end up relying on them.  So when gigi
> +   introduces their declarations on its own, it must also add the attribute, by
> +   calling strub_make_callable.  */
> +#include "ipa-strub.h"
> +
>  /* We should avoid allocating more than ALLOCA_THRESHOLD bytes via alloca,
>     for fear of running out of stack space.  If we need more, we use xmalloc
>     instead.  */
> @@ -454,6 +469,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)
>      {
> @@ -466,6 +482,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.  */
> @@ -722,6 +739,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);
>    set_call_expr_flags (result, ECF_NORETURN | ECF_XTHROW);
>
>    return result;
> diff --git a/gcc/ada/gcc-interface/utils.cc b/gcc/ada/gcc-interface/utils.cc
> index f46454d6545a2..8d1237fdbb3e2 100644
> --- a/gcc/ada/gcc-interface/utils.cc
> +++ b/gcc/ada/gcc-interface/utils.cc
> @@ -39,6 +39,7 @@
>  #include "varasm.h"
>  #include "toplev.h"
>  #include "opts.h"
> +#include "ipa-strub.h"
>  #include "output.h"
>  #include "debug.h"
>  #include "convert.h"
> @@ -6742,9 +6743,77 @@ handle_no_stack_protector_attribute (tree *node, tree name, tree, int,
>     struct attribute_spec.handler.  */
>
>  static tree
> -handle_strub_attribute (tree *, tree, tree, int, bool *no_add_attrs)
> +handle_strub_attribute (tree *node, tree name,
> +                       tree args,
> +                       int ARG_UNUSED (flags), bool *no_add_attrs)
>  {
> -  *no_add_attrs = true;
> +  bool enable = true;
> +
> +  if (args && FUNCTION_POINTER_TYPE_P (*node))
> +    *node = TREE_TYPE (*node);
> +
> +  if (args && FUNC_OR_METHOD_TYPE_P (*node))
> +    {
> +      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
> +       {
> +       case 1:
> +       case 2:
> +         enable = true;
> +         break;
> +
> +       case 0:
> +         warning (OPT_Wattributes,
> +                  "%qE attribute ignored because of argument %qE",
> +                  name, TREE_VALUE (args));
> +         *no_add_attrs = true;
> +         enable = false;
> +         break;
> +
> +       case -1:
> +       case -2:
> +         enable = false;
> +         break;
> +
> +       default:
> +         gcc_unreachable ();
> +       }
> +
> +      args = TREE_CHAIN (args);
> +    }
> +
> +  if (args)
> +    {
> +      warning (OPT_Wattributes,
> +              "ignoring attribute %qE because of excess arguments"
> +              " starting at %qE",
> +              name, TREE_VALUE (args));
> +      *no_add_attrs = true;
> +      enable = false;
> +    }
> +
> +  /* Warn about unmet expectations that the strub attribute works like a
> +     qualifier.  ??? Could/should we extend it to the element/field types
> +     here?  */
> +  if (TREE_CODE (*node) == ARRAY_TYPE
> +      || VECTOR_TYPE_P (*node)
> +      || TREE_CODE (*node) == COMPLEX_TYPE)
> +    warning (OPT_Wattributes,
> +            "attribute %qE does not apply to elements"
> +            " of non-scalar type %qT",
> +            name, *node);
> +  else if (RECORD_OR_UNION_TYPE_P (*node))
> +    warning (OPT_Wattributes,
> +            "attribute %qE does not apply to fields"
> +            " of aggregate type %qT",
> +            name, *node);
> +
> +  /* 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 += 2;
> +
>    return NULL_TREE;
>  }
>
> diff --git a/gcc/attribs.cc b/gcc/attribs.cc
> index eff99002fbb92..dd0408635670f 100644
> --- a/gcc/attribs.cc
> +++ b/gcc/attribs.cc
> @@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
>  #include "diagnostic-core.h"
>  #include "attribs.h"
>  #include "fold-const.h"
> +#include "ipa-strub.h"
>  #include "stor-layout.h"
>  #include "langhooks.h"
>  #include "plugin.h"
> @@ -789,8 +790,8 @@ decl_attributes (tree *node, tree attributes, int flags,
>           flags &= ~(int) ATTR_FLAG_TYPE_IN_PLACE;
>         }
>
> -      if (spec->function_type_required && TREE_CODE (*anode) != FUNCTION_TYPE
> -         && TREE_CODE (*anode) != METHOD_TYPE)
> +      if (spec->function_type_required
> +         && !FUNC_OR_METHOD_TYPE_P (*anode))
>         {
>           if (TREE_CODE (*anode) == POINTER_TYPE
>               && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
> @@ -905,7 +906,24 @@ decl_attributes (tree *node, tree attributes, int flags,
>               TYPE_NAME (tt) = *node;
>             }
>
> -         *anode = cur_and_last_decl[0];
> +         if (*anode != cur_and_last_decl[0])
> +           {
> +             /* Even if !spec->function_type_required, allow the attribute
> +                handler to request the attribute to be applied to the function
> +                type, rather than to the function pointer type, by setting
> +                cur_and_last_decl[0] to the function type.  */
> +             if (!fn_ptr_tmp
> +                 && POINTER_TYPE_P (*anode)
> +                 && TREE_TYPE (*anode) == cur_and_last_decl[0]
> +                 && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode)))
> +               {
> +                 fn_ptr_tmp = TREE_TYPE (*anode);
> +                 fn_ptr_quals = TYPE_QUALS (*anode);
> +                 anode = &fn_ptr_tmp;
> +               }
> +             *anode = cur_and_last_decl[0];
> +           }
> +
>           if (ret == error_mark_node)
>             {
>               warning (OPT_Wattributes, "%qE attribute ignored", name);
> @@ -1508,9 +1526,20 @@ comp_type_attributes (const_tree type1, const_tree type2)
>    if ((lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type1)) != NULL)
>        ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) != NULL))
>      return 0;
> +  int strub_ret = strub_comptypes (CONST_CAST_TREE (type1),
> +                                  CONST_CAST_TREE (type2));
> +  if (strub_ret == 0)
> +    return strub_ret;
>    /* As some type combinations - like default calling-convention - might
>       be compatible, we have to call the target hook to get the final result.  */
> -  return targetm.comp_type_attributes (type1, type2);
> +  int target_ret = targetm.comp_type_attributes (type1, type2);
> +  if (target_ret == 0)
> +    return target_ret;
> +  if (strub_ret == 2 || target_ret == 2)
> +    return 2;
> +  if (strub_ret == 1 && target_ret == 1)
> +    return 1;
> +  gcc_unreachable ();
>  }
>
>  /* PREDICATE acts as a function of type:
> diff --git a/gcc/builtins.cc b/gcc/builtins.cc
> index 4fc58a0bda9b8..555d2897938ca 100644
> --- a/gcc/builtins.cc
> +++ b/gcc/builtins.cc
> @@ -71,6 +71,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"
> @@ -151,6 +152,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);
> @@ -5370,6 +5372,252 @@ expand_builtin_frame_address (tree fndecl, tree exp)
>      }
>  }
>
> +#if ! 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 || 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 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);
> +    }
> +
> +  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 (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);
> +    }
> +
> +  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);
> +
> +#if ! 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);
> +
> +      /* ??? It would be nice to use setmem or similar patterns here,
> +        but they do not necessarily obey the stack growth direction,
> +        which has security implications.  We also have to avoid calls
> +        (memset, bzero or any machine-specific ones), which are
> +        likely unsafe here (see TARGET_STRUB_MAY_USE_MEMSET).  */
> +#if ! STACK_GROWS_DOWNWARD
> +      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));
> +#else
> +      rtx decr = plus_constant (Pmode, end, -ulen);
> +      rtx dstm = gen_rtx_MEM (ptr_mode, end);
> +
> +      rtx_code_label *loop = gen_label_rtx ();
> +      emit_label (loop);
> +      emit_move_insn (end, force_operand (decr, NULL_RTX));
> +      emit_move_insn (dstm, zero);
> +#endif
> +      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.  */
>
> @@ -7705,6 +7953,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 33e6cad8ce14f..f03df32f98013 100644
> --- a/gcc/builtins.def
> +++ b/gcc/builtins.def
> @@ -999,6 +999,10 @@ DEF_EXT_LIB_BUILTIN    (BUILT_IN_FFSLL, "ffsll", BT_FN_INT_LONGLONG, ATTR_CONST_
>  DEF_GCC_BUILTIN        (BUILT_IN_FFSG, "ffsg", BT_FN_INT_VAR, ATTR_CONST_NOTHROW_TYPEGENERIC_LEAF)
>  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.cc b/gcc/c-family/c-attribs.cc
> index 45af07453ea3c..acc09e4b27a5b 100644
> --- a/gcc/c-family/c-attribs.cc
> +++ b/gcc/c-family/c-attribs.cc
> @@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
>  #include "common/common-target.h"
>  #include "langhooks.h"
>  #include "tree-inline.h"
> +#include "ipa-strub.h"
>  #include "toplev.h"
>  #include "tree-iterator.h"
>  #include "opts.h"
> @@ -69,6 +70,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 *);
> @@ -322,6 +324,8 @@ const struct attribute_spec c_common_gnu_attributes[] =
>    { "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 },
> @@ -1489,6 +1493,84 @@ 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 && FUNCTION_POINTER_TYPE_P (*node))
> +    *node = TREE_TYPE (*node);
> +
> +  if (args && FUNC_OR_METHOD_TYPE_P (*node))
> +    {
> +      switch (strub_validate_fn_attr_parm (TREE_VALUE (args)))
> +       {
> +       case 1:
> +       case 2:
> +         enable = true;
> +         break;
> +
> +       case 0:
> +         warning (OPT_Wattributes,
> +                  "%qE attribute ignored because of argument %qE",
> +                  name, TREE_VALUE (args));
> +         *no_add_attrs = true;
> +         enable = false;
> +         break;
> +
> +       case -1:
> +       case -2:
> +         enable = false;
> +         break;
> +
> +       default:
> +         gcc_unreachable ();
> +       }
> +
> +      args = TREE_CHAIN (args);
> +    }
> +
> +  if (args)
> +    {
> +      warning (OPT_Wattributes,
> +              "ignoring attribute %qE because of excess arguments"
> +              " starting at %qE",
> +              name, TREE_VALUE (args));
> +      *no_add_attrs = true;
> +      enable = false;
> +    }
> +
> +  /* Warn about unmet expectations that the strub attribute works like a
> +     qualifier.  ??? Could/should we extend it to the element/field types
> +     here?  */
> +  if (TREE_CODE (*node) == ARRAY_TYPE
> +      || VECTOR_TYPE_P (*node)
> +      || TREE_CODE (*node) == COMPLEX_TYPE)
> +    warning (OPT_Wattributes,
> +            "attribute %qE does not apply to elements"
> +            " of non-scalar type %qT",
> +            name, *node);
> +  else if (RECORD_OR_UNION_TYPE_P (*node))
> +    warning (OPT_Wattributes,
> +            "attribute %qE does not apply to fields"
> +            " of aggregate type %qT",
> +            name, *node);
> +
> +  /* 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 += 2;
> +
> +  return NULL_TREE;
> +}
> +
>  /* Handle a "noinline" attribute; arguments as in
>     struct attribute_spec.handler.  */
>
> diff --git a/gcc/cgraph.h b/gcc/cgraph.h
> index cfdd9f693a889..2b32055761688 100644
> --- a/gcc/cgraph.h
> +++ b/gcc/cgraph.h
> @@ -153,7 +153,7 @@ public:
>    void remove (void);
>
>    /* Undo any definition or use of the symbol.  */
> -  void reset (void);
> +  void reset (bool preserve_comdat_group = false);
>
>    /* Dump symtab node to F.  */
>    void dump (FILE *f);
> diff --git a/gcc/cgraphunit.cc b/gcc/cgraphunit.cc
> index bccd2f2abb5a3..9a550a5cce645 100644
> --- a/gcc/cgraphunit.cc
> +++ b/gcc/cgraphunit.cc
> @@ -384,7 +384,7 @@ symbol_table::process_new_functions (void)
>     functions or variables.  */
>
>  void
> -symtab_node::reset (void)
> +symtab_node::reset (bool preserve_comdat_group)
>  {
>    /* Reset our data structures so we can analyze the function again.  */
>    analyzed = false;
> @@ -395,7 +395,8 @@ symtab_node::reset (void)
>    cpp_implicit_alias = false;
>
>    remove_all_references ();
> -  remove_from_same_comdat_group ();
> +  if (!preserve_comdat_group)
> +    remove_from_same_comdat_group ();
>
>    if (cgraph_node *cn = dyn_cast <cgraph_node *> (this))
>      {
> diff --git a/gcc/common.opt b/gcc/common.opt
> index 161a035d736a3..f070aff8cbc1b 100644
> --- a/gcc/common.opt
> +++ b/gcc/common.opt
> @@ -2917,6 +2917,35 @@ fstrict-overflow
>  Common
>  Treat signed overflow as undefined.  Negated as -fwrapv -fwrapv-pointer.
>
> +fstrub=disable
> +Common RejectNegative Var(flag_strub, 0)
> +Disable stack scrub entirely, disregarding strub attributes.
> +
> +fstrub=strict
> +Common RejectNegative Var(flag_strub, -4)
> +Enable stack scrub as per attributes, with strict call checking.
> +
> +; If any strub-enabling attribute is seen when the default or strict
> +; initializer values are in effect, flag_strub is bumped up by 2.  The
> +; scrub mode gate function will then bump these initializer values to
> +; 0 if no strub-enabling attribute is seen.  This minimizes the strub
> +; overhead.
> +fstrub=relaxed
> +Common RejectNegative Var(flag_strub, -3) Init(-3)
> +Restore default strub mode: as per attributes, with relaxed checking.
> +
> +fstrub=all
> +Common RejectNegative Var(flag_strub, 3)
> +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.
> +
>  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 9592cfee1d279..e9bc9c4fe84c8 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 internal interfaces.
>  * Vector Extensions::   Using vector instructions through built-in functions.
>  * Offsetof::            Special syntax for implementing @code{offsetof}.
>  * __sync Builtins::     Legacy built-in functions for atomic memory access.
> @@ -9225,6 +9226,268 @@ pid_t wait (wait_status_ptr_t p)
>  @}
>  @end smallexample
>
> +@cindex @code{strub} type attribute
> +@item strub
> +This attribute defines stack-scrubbing properties of functions and
> +variables, so that functions that access sensitive data can have their
> +stack frames zeroed-out upon returning or propagating exceptions.  This
> +may be enabled explicitly, by selecting certain @code{strub} modes for
> +specific functions, or implicitly, by means of @code{strub} variables.
> +
> +Being a type attribute, it attaches to types, even when specified in
> +function and variable declarations.  When applied to function types, it
> +takes an optional string argument.  When applied to a
> +pointer-to-function type, if the optional argument is given, it gets
> +propagated to the function type.
> +
> +@smallexample
> +/* A strub variable.  */
> +int __attribute__ ((strub)) var;
> +/* A strub variable that happens to be a pointer.  */
> +__attribute__ ((strub)) int *strub_ptr_to_int;
> +/* A pointer type that may point to a strub variable.  */
> +typedef int __attribute__ ((strub)) *ptr_to_strub_int_type;
> +
> +/* A declaration of a strub function.  */
> +extern int __attribute__ ((strub)) foo (void);
> +/* A pointer to that strub function.  */
> +int __attribute__ ((strub ("at-calls"))) (*ptr_to_strub_fn)(void) = foo;
> +@end smallexample
> +
> +A function associated with @code{at-calls} @code{strub} mode
> +(@code{strub("at-calls")}, or just @code{strub}) undergoes interface
> +changes.  Its callers are adjusted to match the changes, and to scrub
> +(overwrite with zeros) the stack space used by the called function after
> +it returns.  The interface change makes the function type incompatible
> +with an unadorned but otherwise equivalent type, so @emph{every}
> +declaration and every type that may be used to call the function must be
> +associated with this strub mode.
> +
> +A function associated with @code{internal} @code{strub} mode
> +(@code{strub("internal")}) retains an unmodified, type-compatible
> +interface, but it may be turned into a wrapper that calls the wrapped
> +body using a custom interface.  The wrapper then scrubs the stack space
> +used by the wrapped body.  Though the wrapped body has its stack space
> +scrubbed, the wrapper does not, so arguments and return values may
> +remain unscrubbed even when such a function is called by another
> +function that enables @code{strub}.  This is why, when compiling with
> +@option{-fstrub=strict}, a @code{strub} context is not allowed to call
> +@code{internal} @code{strub} functions.
> +
> +@smallexample
> +/* A declaration of an internal-strub function.  */
> +extern int __attribute__ ((strub ("internal"))) bar (void);
> +
> +int __attribute__ ((strub))
> +baz (void)
> +@{
> +  /* Ok, foo was declared above as an at-calls strub function.  */
> +  foo ();
> +  /* Not allowed in strict mode, otherwise allowed.  */
> +  bar ();
> +@}
> +@end smallexample
> +
> +An automatically-allocated variable associated with the @code{strub}
> +attribute causes the (immediately) enclosing function to have
> +@code{strub} enabled.
> +
> +A statically-allocated variable associated with the @code{strub}
> +attribute causes functions that @emph{read} it, through its @code{strub}
> +data type, to have @code{strub} enabled.  Reading data by dereferencing
> +a pointer to a @code{strub} data type has the same effect.  Note: The
> +attribute does not carry over from a composite type to the types of its
> +components, so the intended effect may not be obtained with non-scalar
> +types.
> +
> +When selecting a @code{strub}-enabled mode for a function that is not
> +explicitly associated with one, because of @code{strub} variables or
> +data pointers, the function must satisfy @code{internal} mode viability
> +requirements (see below), even when @code{at-calls} mode is also viable
> +and, being more efficient, ends up selected as an optimization.
> +
> +@smallexample
> +/* zapme is implicitly strub-enabled because of strub variables.
> +   Optimization may change its strub mode, but not the requirements.  */
> +static int
> +zapme (int i)
> +@{
> +  /* A local strub variable enables strub.  */
> +  int __attribute__ ((strub)) lvar;
> +  /* Reading strub data through a pointer-to-strub enables strub.  */
> +  lvar = * (ptr_to_strub_int_type) &i;
> +  /* Writing to a global strub variable does not enable strub.  */
> +  var = lvar;
> +  /* Reading from a global strub variable enables strub.  */
> +  return var;
> +@}
> +@end smallexample
> +
> +A @code{strub} context is the body (as opposed to the interface) of a
> +function that has @code{strub} enabled, be it explicitly, by
> +@code{at-calls} or @code{internal} mode, or implicitly, due to
> +@code{strub} variables or command-line options.
> +
> +A function of a type associated with the @code{disabled} @code{strub}
> +mode (@code{strub("disabled")} will not have its own stack space
> +scrubbed.  Such functions @emph{cannot} be called from within
> +@code{strub} contexts.
> +
> +In order to enable a function to be called from within @code{strub}
> +contexts without having its stack space scrubbed, associate it with the
> +@code{callable} @code{strub} mode (@code{strub("callable")}).
> +
> +When a function is not assigned a @code{strub} mode, explicitly or
> +implicitly, the mode defaults to @code{callable}, except when compiling
> +with @option{-fstrub=strict}, that causes @code{strub} mode to default
> +to @code{disabled}.
> +
> +@example
> +extern int __attribute__ ((strub ("callable"))) bac (void);
> +extern int __attribute__ ((strub ("disabled"))) bad (void);
> + /* Implicitly disabled with -fstrub=strict, otherwise callable.  */
> +extern int bah (void);
> +
> +int __attribute__ ((strub))
> +bal (void)
> +@{
> +  /* Not allowed, bad is not strub-callable.  */
> +  bad ();
> +  /* Ok, bac is strub-callable.  */
> +  bac ();
> +  /* Not allowed with -fstrub=strict, otherwise allowed.  */
> +  bah ();
> +@}
> +@end example
> +
> +Function types marked @code{callable} and @code{disabled} are not
> +mutually compatible types, but the underlying interfaces are compatible,
> +so it is safe to convert pointers between them, and to use such pointers
> +or alternate declarations to call them.  Interfaces are also
> +interchangeable between them and @code{internal} (but not
> +@code{at-calls}!), but adding @code{internal} to a pointer type will not
> +cause the pointed-to function to perform stack scrubbing.
> +
> +@example
> +void __attribute__ ((strub))
> +bap (void)
> +@{
> +  /* Assign a callable function to pointer-to-disabled.
> +     Flagged as not quite compatible with -Wpedantic.  */
> +  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bac;
> +  /* Not allowed: calls disabled type in a strub context.  */
> +  d_p ();
> +
> +  /* Assign a disabled function to pointer-to-callable.
> +     Flagged as not quite compatible with -Wpedantic.  */
> +  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bad;
> +  /* Ok, safe.  */
> +  c_p ();
> +
> +  /* Assign an internal function to pointer-to-callable.
> +     Flagged as not quite compatible with -Wpedantic.  */
> +  c_p = bar;
> +  /* Ok, safe.  */
> +  c_p ();
> +
> +  /* Assign an at-calls function to pointer-to-callable.
> +     Flaggged as incompatible.  */
> +  c_p = bal;
> +  /* The call through an interface-incompatible type will not use the
> +     modified interface expected by the at-calls function, so it is
> +     likely to misbehave at runtime.  */
> +  c_p ();
> +@}
> +@end example
> +
> +@code{Strub} contexts are never inlined into non-@code{strub} contexts.
> +When an @code{internal}-strub function is split up, the wrapper can
> +often be inlined, but the wrapped body @emph{never} is.  A function
> +marked as @code{always_inline}, even if explicitly assigned
> +@code{internal} strub mode, will not undergo wrapping, so its body gets
> +inlined as required.
> +
> +@example
> +inline int __attribute__ ((strub ("at-calls")))
> +inl_atc (void)
> +@{
> +  /* This body may get inlined into strub contexts.  */
> +@}
> +
> +inline int __attribute__ ((strub ("internal")))
> +inl_int (void)
> +@{
> +  /* This body NEVER gets inlined, though its wrapper may.  */
> +@}
> +
> +inline int __attribute__ ((strub ("internal"), always_inline))
> +inl_int_ali (void)
> +@{
> +  /* No internal wrapper, so this body ALWAYS gets inlined,
> +     but it cannot be called from non-strub contexts.  */
> +@}
> +
> +void __attribute__ ((strub ("disabled")))
> +bat (void)
> +@{
> +  /* Not allowed, cannot inline into a non-strub context.  */
> +  inl_int_ali ();
> +@}
> +@end example
> +
> +@cindex strub eligibility and viability
> +Some @option{-fstrub=*} command line options enable @code{strub} modes
> +implicitly where viable.  A @code{strub} mode is only viable for a
> +function if the function is eligible for that mode, and if other
> +conditions, detailed below, are satisfied.  If it's not eligible for a
> +mode, attempts to explicitly associate it with that mode are rejected
> +with an error message.  If it is eligible, that mode may be assigned
> +explicitly through this attribute, but implicit assignment through
> +command-line options may involve additional viability requirements.
> +
> +A function is ineligible for @code{at-calls} @code{strub} mode if a
> +different @code{strub} mode is explicitly requested, if attribute
> +@code{noipa} is present, or if it calls @code{__builtin_apply_args}.
> +@code{At-calls} @code{strub} mode, if not requested through the function
> +type, is only viable for an eligible function if the function is not
> +visible to other translation units, if it doesn't have its address
> +taken, and if it is never called with a function type overrider.
> +
> +@smallexample
> +/* bar is eligible for at-calls strub mode,
> +   but not viable for that mode because it is visible to other units.
> +   It is eligible and viable for internal strub mode.  */
> +void bav () @{@}
> +
> +/* setp is eligible for at-calls strub mode,
> +   but not viable for that mode because its address is taken.
> +   It is eligible and viable for internal strub mode.  */
> +void setp (void) @{ static void (*p)(void); = setp; @}
> +@end smallexample
> +
> +A function is ineligible for @code{internal} @code{strub} mode if a
> +different @code{strub} mode is explicitly requested, or if attribute
> +@code{noipa} is present.  For an @code{always_inline} function, meeting
> +these requirements is enough to make it eligible.  Any function that has
> +attribute @code{noclone}, that uses such extensions as non-local labels,
> +computed gotos, alternate variable argument passing interfaces,
> +@code{__builtin_next_arg}, or @code{__builtin_return_address}, or that
> +takes too many (about 64Ki) arguments is ineligible, unless it is
> +@code{always_inline}.  For @code{internal} @code{strub} mode, all
> +eligible functions are viable.
> +
> +@smallexample
> +/* flop is not eligible, thus not viable, for at-calls strub mode.
> +   Likewise for internal strub mode.  */
> +__attribute__ ((noipa)) void flop (void) @{@}
> +
> +/* flip is eligible and viable for at-calls strub mode.
> +   It would be ineligible for internal strub mode, because of noclone,
> +   if it weren't for always_inline.  With always_inline, noclone is not
> +   an obstacle, so it is also eligible and viable for internal strub mode.  */
> +inline __attribute__ ((noclone, always_inline)) void flip (void) @{@}
> +@end smallexample
> +
>  @cindex @code{unused} type attribute
>  @item unused
>  When attached to a type (including a @code{union} or a @code{struct}),
> @@ -12375,6 +12638,55 @@ option is in effect.  Such calls should only be made in debugging
>  situations.
>  @enddefbuiltin
>
> +@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 internal interfaces
> +
> +Stack scrubbing involves cooperation between a @code{strub} context,
> +i.e., a function whose stack frame is to be zeroed-out, and its callers.
> +The caller initializes a stack watermark, the @code{strub} context
> +updates the watermark according to its stack use, and the caller zeroes
> +it out once it regains control, whether by the callee's returning or by
> +an exception.
> +
> +Each of these steps is performed by a different builtin function call.
> +Calls to these builtins are introduced automatically, in response to
> +@code{strub} attributes and command-line options; they are not expected
> +to be explicitly called by source code.
> +
> +The functions that implement the builtins are available in libgcc but,
> +depending on optimization levels, they are expanded internally, adjusted
> +to account for inlining, and sometimes combined/deferred (e.g. passing
> +the caller-supplied watermark on to callees, refraining from erasing
> +stack areas that the caller will) to enable tail calls and to optimize
> +for code size.
> +
> +@deftypefn {Built-in Function} {void} __builtin___strub_enter (void **@var{wmptr})
> +This function initializes a stack @var{watermark} variable with the
> +current top of the stack.  A call to this builtin function is introduced
> +before entering a @code{strub} context.  It remains as a function call
> +if optimization is not enabled.
> +@end deftypefn
> +
> +@deftypefn {Built-in Function} {void} __builtin___strub_update (void **@var{wmptr})
> +This function updates a stack @var{watermark} variable with the current
> +top of the stack, if it tops the previous watermark.  A call to this
> +builtin function is inserted within @code{strub} contexts, whenever
> +additional stack space may have been used.  It remains as a function
> +call at optimization levels lower than 2.
> +@end deftypefn
> +
> +@deftypefn {Built-in Function} {void} __builtin___strub_leave (void **@var{wmptr})
> +This function overwrites the memory area between the current top of the
> +stack, and the @var{watermark}ed address.  A call to this builtin
> +function is inserted after leaving a @code{strub} context.  It remains
> +as a function call at optimization levels lower than 3, and it is guarded by
> +a condition at level 2.
> +@end deftypefn
> +
>  @node Vector Extensions
>  @section Using Vector Instructions through Built-in Functions
>
> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index 2fab4c5d71fd0..04d7ecd4593e1 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -654,6 +654,8 @@ Objective-C and Objective-C++ Dialects}.
>  -fstack-protector-explicit  -fstack-check
>  -fstack-limit-register=@var{reg}  -fstack-limit-symbol=@var{sym}
>  -fno-stack-limit  -fsplit-stack
> +-fstrub=disable  -fstrub=strict  -fstrub=relaxed
> +-fstrub=all  -fstrub=at-calls  -fstrub=internal
>  -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
>  -fvtv-counts  -fvtv-debug
>  -finstrument-functions  -finstrument-functions-once
> @@ -17890,6 +17892,56 @@ 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.
>
> +@opindex -fstrub=disable
> +@item -fstrub=disable
> +Disable stack scrubbing entirely, ignoring any @code{strub} attributes.
> +See @xref{Common Type Attributes}.
> +
> +@opindex fstrub=strict
> +@item -fstrub=strict
> +Functions default to @code{strub} mode @code{disabled}, and apply
> +@option{strict}ly the restriction that only functions associated with
> +@code{strub}-@code{callable} modes (@code{at-calls}, @code{callable} and
> +@code{always_inline} @code{internal}) are @code{callable} by functions
> +with @code{strub}-enabled modes (@code{at-calls} and @code{internal}).
> +
> +@opindex fstrub=relaxed
> +@item -fstrub=relaxed
> +Restore the default stack scrub (@code{strub}) setting, namely,
> +@code{strub} is only enabled as required by @code{strub} attributes
> +associated with function and data types.  @code{Relaxed} means that
> +strub contexts are only prevented from calling functions explicitly
> +associated with @code{strub} mode @code{disabled}.  This option is only
> +useful to override other @option{-fstrub=*} options that precede it in
> +the command line.
> +
> +@opindex fstrub=at-calls
> +@item -fstrub=at-calls
> +Enable @code{at-calls} @code{strub} mode where viable.  The primary use
> +of this option is for testing.  It exercises the @code{strub} machinery
> +in scenarios strictly local to a translation unit.  This @code{strub}
> +mode modifies function interfaces, so any function that is visible to
> +other translation units, or that has its address taken, will @emph{not}
> +be affected by this option.  Optimization options may also affect
> +viability.  See the @code{strub} attribute documentation for details on
> +viability and eligibility requirements.
> +
> +@opindex fstrub=internal
> +@item -fstrub=internal
> +Enable @code{internal} @code{strub} mode where viable.  The primary use
> +of this option is for testing.  This option is intended to exercise
> +thoroughly parts of the @code{strub} machinery that implement the less
> +efficient, but interface-preserving @code{strub} mode.  Functions that
> +would not be affected by this option are quite uncommon.
> +
> +@opindex fstrub=all
> +@item -fstrub=all
> +Enable some @code{strub} mode where viable.  When both strub modes are
> +viable, @code{at-calls} is preferred.  @option{-fdump-ipa-strubm} adds
> +function attributes that tell which mode was selected for each function.
> +The primary use of this option is for testing, to exercise thoroughly
> +the @code{strub} machinery.
> +
>  @opindex fvtable-verify
>  @item -fvtable-verify=@r{[}std@r{|}preinit@r{|}none@r{]}
>  This option is only available when compiling C++ code.
> @@ -19809,6 +19861,14 @@ and inlining decisions.
>  @item inline
>  Dump after function inlining.
>
> +@item strubm
> +Dump after selecting @code{strub} modes, and recording the selections as
> +function attributes.
> +
> +@item strub
> +Dump @code{strub} transformations: interface changes, function wrapping,
> +and insertion of builtin calls for stack scrubbing and watermarking.
> +
>  @end table
>
>  Additionally, the options @option{-optimized}, @option{-missed},
> diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
> index 7c5d2e523601d..c4b2ee4b8091a 100644
> --- a/gcc/doc/tm.texi
> +++ b/gcc/doc/tm.texi
> @@ -3450,6 +3450,25 @@ in DWARF 2 debug information.  The default is zero.  A different value
>  may reduce the size of debug information on some ports.
>  @end defmac
>
> +@defmac TARGET_STRUB_USE_DYNAMIC_ARRAY
> +If defined to nonzero, @code{__strub_leave} will allocate a dynamic
> +array covering the stack range that needs scrubbing before clearing it.
> +Allocating the array tends to make scrubbing slower, but it enables the
> +scrubbing to be safely implemented with a @code{memset} call, which
> +could make up for the difference.
> +@end defmac
> +
> +@defmac TARGET_STRUB_MAY_USE_MEMSET
> +If defined to nonzero, enable @code{__strub_leave} to be optimized so as
> +to call @code{memset} for stack scrubbing.  This is only enabled by
> +default if @code{TARGET_STRUB_USE_DYNAMIC_ARRAY} is enabled; it's not
> +advisable to enable it otherwise, since @code{memset} would then likely
> +overwrite its own stack frame, but it might work if the target ABI
> +enables @code{memset} to not use the stack at all, not even for
> +arguments or its return address, and its implementation is trivial
> +enough that it doesn't use a stack frame.
> +@end defmac
> +
>  @node Exception Handling
>  @subsection Exception Handling Support
>  @cindex exception handling
> diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
> index c24493add5748..9cbde5f8b740a 100644
> --- a/gcc/doc/tm.texi.in
> +++ b/gcc/doc/tm.texi.in
> @@ -2686,6 +2686,25 @@ in DWARF 2 debug information.  The default is zero.  A different value
>  may reduce the size of debug information on some ports.
>  @end defmac
>
> +@defmac TARGET_STRUB_USE_DYNAMIC_ARRAY
> +If defined to nonzero, @code{__strub_leave} will allocate a dynamic
> +array covering the stack range that needs scrubbing before clearing it.
> +Allocating the array tends to make scrubbing slower, but it enables the
> +scrubbing to be safely implemented with a @code{memset} call, which
> +could make up for the difference.
> +@end defmac
> +
> +@defmac TARGET_STRUB_MAY_USE_MEMSET
> +If defined to nonzero, enable @code{__strub_leave} to be optimized so as
> +to call @code{memset} for stack scrubbing.  This is only enabled by
> +default if @code{TARGET_STRUB_USE_DYNAMIC_ARRAY} is enabled; it's not
> +advisable to enable it otherwise, since @code{memset} would then likely
> +overwrite its own stack frame, but it might work if the target ABI
> +enables @code{memset} to not use the stack at all, not even for
> +arguments or its return address, and its implementation is trivial
> +enough that it doesn't use a stack frame.
> +@end defmac
> +
>  @node Exception Handling
>  @subsection Exception Handling Support
>  @cindex exception handling
> diff --git a/gcc/gengtype-lex.l b/gcc/gengtype-lex.l
> index 34837d9dc9a8f..a7bb44cf2b9ad 100644
> --- a/gcc/gengtype-lex.l
> +++ b/gcc/gengtype-lex.l
> @@ -165,6 +165,9 @@ CXX_KEYWORD inline|public:|private:|protected:|template|operator|friend|static|m
>  [(){},*:<>;=%/|+\!\?\.-]       { return yytext[0]; }
>
>     /* ignore pp-directives */
> +^{HWS}"#"{HWS}[a-z_]+([^\n]*"\\"\n)+[^\n]*\n   {
> +  update_lineno (yytext, yyleng);
> +}
>  ^{HWS}"#"{HWS}[a-z_]+[^\n]*\n   {lexer_line.line++;}
>
>  .                              {
> diff --git a/gcc/ipa-inline.cc b/gcc/ipa-inline.cc
> index dc120e6da5af6..dbc3c7e8fdc88 100644
> --- a/gcc/ipa-inline.cc
> +++ b/gcc/ipa-inline.cc
> @@ -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"
>
>  /* Inliner uses greedy algorithm to inline calls in a priority order.
>     Badness is used as the key in a Fibonacci heap which roughly corresponds
> @@ -443,6 +444,11 @@ can_inline_edge_p (struct cgraph_edge *e, bool report,
>        inlinable = false;
>      }
>
> +  if (inlinable && !strub_inlinable_to_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.cc b/gcc/ipa-split.cc
> index 6730f4f9d0e31..1a7285ff5dcf8 100644
> --- a/gcc/ipa-split.cc
> +++ b/gcc/ipa-split.cc
> @@ -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.  */
>
> @@ -1811,6 +1812,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.cc b/gcc/ipa-strub.cc
> new file mode 100644
> index 0000000000000..293bec132b885
> --- /dev/null
> +++ b/gcc/ipa-strub.cc
> @@ -0,0 +1,3573 @@
> +/* strub (stack scrubbing) support.
> +   Copyright (C) 2021-2023 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"
> +#include "symtab-thunks.h"
> +#include "attr-fnspec.h"
> +
> +/* This file introduces two passes that, together, implement
> +   machine-independent stack scrubbing, strub for short.  It arranges
> +   for stack frames that have strub enabled to be zeroed-out after
> +   relinquishing control to a caller, whether by returning or by
> +   propagating an exception.  This admittedly unusual design decision
> +   was driven by exception support (one needs a stack frame to be
> +   active to propagate exceptions out of it), and it enabled an
> +   implementation that is entirely machine-independent (no custom
> +   epilogue code is required).
> +
> +   Strub modes can be selected for stack frames by attaching attribute
> +   strub to functions or to variables (to their types, actually).
> +   Different strub modes, with different implementation details, are
> +   available, and they can be selected by an argument to the strub
> +   attribute.  When enabled by strub-enabled variables, whether by
> +   accessing (as in reading from) statically-allocated ones, or by
> +   introducing (as in declaring) automatically-allocated ones, a
> +   suitable mode is selected automatically.
> +
> +   At-calls mode modifies the interface of a function, adding a stack
> +   watermark argument, that callers use to clean up the stack frame of
> +   the called function.  Because of the interface change, it can only
> +   be used when explicitly selected, or when a function is internal to
> +   a translation unit.  Strub-at-calls function types are distinct
> +   from their original types (they're not modified in-place), and they
> +   are not interchangeable with other function types.
> +
> +   Internal mode, in turn, does not modify the type or the interface
> +   of a function.  It is currently implemented by turning the function
> +   into a wrapper, moving the function body to a separate wrapped
> +   function, and scrubbing the wrapped body's stack in the wrapper.
> +   Internal-strub function types are mostly interface-compatible with
> +   other strub modes, namely callable (from strub functions, though
> +   not strub-enabled) and disabled (not callable from strub
> +   functions).
> +
> +   Always_inline functions can be strub functions, but they can only
> +   be called from other strub functions, because strub functions must
> +   never be inlined into non-strub functions.  Internal and at-calls
> +   modes are indistinguishable when it comes to always_inline
> +   functions: they will necessarily be inlined into another strub
> +   function, and will thus be integrated into the caller's stack
> +   frame, whatever the mode.  (Contrast with non-always_inline strub
> +   functions: an at-calls function can be called from other strub
> +   functions, ensuring no discontinuity in stack erasing, whereas an
> +   internal-strub function can only be called from other strub
> +   functions if it happens to be inlined, or if -fstrub=relaxed mode
> +   is in effect (that's the default).  In -fstrub=strict mode,
> +   internal-strub functions are not callable from strub functions,
> +   because the wrapper itself is not strubbed.
> +
> +   The implementation involves two simple-IPA passes.  The earliest
> +   one, strub-mode, assigns strub modes to functions.  It needs to run
> +   before any inlining, so that we can prevent inlining of strub
> +   functions into non-strub functions.  It notes explicit strub mode
> +   requests, enables strub in response to strub variables and testing
> +   options, and flags unsatisfiable requests.
> +
> +   Three possibilities of unsatisfiable requests come to mind: (a)
> +   when a strub mode is explicitly selected, but the function uses
> +   features that make it ineligible for that mode (e.g. at-calls rules
> +   out calling __builtin_apply_args, because of the interface changes,
> +   and internal mode rules out noclone or otherwise non-versionable
> +   functions, non-default varargs, non-local or forced labels, and
> +   functions with far too many arguments); (b) when some strub mode
> +   must be enabled because of a strub variable, but the function is
> +   not eligible or not viable for any mode; and (c) when
> +   -fstrub=strict is enabled, and calls are found in strub functions
> +   to functions that are not callable from strub contexts.
> +   compute_strub_mode implements (a) and (b), and verify_strub
> +   implements (c).
> +
> +   The second IPA pass modifies interfaces of at-calls-strub functions
> +   and types, introduces strub calls in and around them. and splits
> +   internal-strub functions.  It is placed after early inlining, so
> +   that even internal-strub functions get a chance of being inlined
> +   into other strub functions, but before non-early inlining, so that
> +   internal-strub wrapper functions still get a chance of inlining
> +   after splitting.
> +
> +   Wrappers avoid duplicating the copying of large arguments again by
> +   passing them by reference to the wrapped bodies.  This involves
> +   occasional SSA rewriting of address computations, because of the
> +   additional indirection.  Besides these changes, and the
> +   introduction of the stack watermark parameter, wrappers and wrapped
> +   functions cooperate to handle variable argument lists (performing
> +   va_start in the wrapper, passing the list as an argument, and
> +   replacing va_start calls in the wrapped body with va_copy), and
> +   __builtin_apply_args (also called in the wrapper and passed to the
> +   wrapped body as an argument).
> +
> +   Strub bodies (both internal-mode wrapped bodies, and at-calls
> +   functions) always start by adjusting the watermark parameter, by
> +   calling __builtin___strub_update.  The compiler inserts them in the
> +   main strub pass.  Allocations of additional stack space for the
> +   frame (__builtin_alloca) are also followed by watermark updates.
> +   Stack space temporarily allocated to pass arguments to other
> +   functions, released right after the call, is not regarded as part
> +   of the frame.  Around calls to them, i.e., in internal-mode
> +   wrappers and at-calls callers (even calls through pointers), calls
> +   to __builtin___strub_enter and __builtin___strub_leave are
> +   inserted, the latter as a __finally block, so that it runs at
> +   regular and exceptional exit paths.  strub_enter only initializes
> +   the stack watermark, and strub_leave is where the scrubbing takes
> +   place, overwriting with zeros the stack space from the top of the
> +   stack to the watermark.
> +
> +   These calls can be optimized in various cases.  In
> +   pass_ipa_strub::adjust_at_calls_call, for example, we enable
> +   tail-calling and other optimized calls from one strub body to
> +   another by passing on the watermark parameter.  The builtins
> +   themselves may undergo inline substitution during expansion,
> +   dependign on optimization levels.  This involves dealing with stack
> +   red zones (when the builtins are called out-of-line, the red zone
> +   cannot be used) and other ugly details related with inlining strub
> +   bodies into other strub bodies (see expand_builtin_strub_update).
> +   expand_builtin_strub_leave may even perform partial inline
> +   substitution.  */
> +
> +/* 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,
> +
> +};
> +
> +/* Look up a strub attribute in TYPE, and return it.  */
> +
> +static tree
> +get_strub_attr_from_type (tree type)
> +{
> +  return lookup_attribute ("strub", TYPE_ATTRIBUTES (type));
> +}
> +
> +/* Look up a strub attribute in DECL or in its type, and return it.  */
> +
> +static tree
> +get_strub_attr_from_decl (tree decl)
> +{
> +  tree ret = lookup_attribute ("strub", DECL_ATTRIBUTES (decl));
> +  if (ret)
> +    return ret;
> +  return get_strub_attr_from_type (TREE_TYPE (decl));
> +}
> +
> +#define STRUB_ID_COUNT         8
> +#define STRUB_IDENT_COUNT      3
> +#define STRUB_TYPE_COUNT       5
> +
> +#define STRUB_ID_BASE          0
> +#define STRUB_IDENT_BASE       (STRUB_ID_BASE + STRUB_ID_COUNT)
> +#define STRUB_TYPE_BASE                (STRUB_IDENT_BASE + STRUB_IDENT_COUNT)
> +#define STRUB_CACHE_SIZE       (STRUB_TYPE_BASE + STRUB_TYPE_COUNT)
> +
> +/* Keep the strub mode and temp identifiers and types from being GC'd.  */
> +static GTY((deletable)) tree strub_cache[STRUB_CACHE_SIZE];
> +
> +/* Define a function to cache identifier ID, to be used as a strub attribute
> +   parameter for a strub mode named after NAME.  */
> +#define DEF_STRUB_IDS(IDX, NAME, ID)                           \
> +static inline tree get_strub_mode_id_ ## NAME () {             \
> +  int idx = STRUB_ID_BASE + IDX;                               \
> +  tree identifier = strub_cache[idx];                          \
> +  if (!identifier)                                             \
> +    strub_cache[idx] = identifier = get_identifier (ID);       \
> +  return identifier;                                           \
> +}
> +/* Same as DEF_STRUB_IDS, but use the string expansion of NAME as ID.  */
> +#define DEF_STRUB_ID(IDX, NAME)                        \
> +  DEF_STRUB_IDS (IDX, NAME, #NAME)
> +
> +/* Define functions for each of the strub mode identifiers.
> +   Expose dashes rather than underscores.  */
> +DEF_STRUB_ID (0, disabled)
> +DEF_STRUB_IDS (1, at_calls, "at-calls")
> +DEF_STRUB_ID (2, internal)
> +DEF_STRUB_ID (3, callable)
> +DEF_STRUB_ID (4, wrapped)
> +DEF_STRUB_ID (5, wrapper)
> +DEF_STRUB_ID (6, inlinable)
> +DEF_STRUB_IDS (7, at_calls_opt, "at-calls-opt")
> +
> +/* Release the temporary macro names.  */
> +#undef DEF_STRUB_IDS
> +#undef DEF_STRUB_ID
> +
> +/* Return the identifier corresponding to strub MODE.  */
> +
> +static tree
> +get_strub_mode_attr_parm (enum strub_mode mode)
> +{
> +  switch (mode)
> +    {
> +    case STRUB_DISABLED:
> +      return get_strub_mode_id_disabled ();
> +
> +    case STRUB_AT_CALLS:
> +      return get_strub_mode_id_at_calls ();
> +
> +    case STRUB_INTERNAL:
> +      return get_strub_mode_id_internal ();
> +
> +    case STRUB_CALLABLE:
> +      return get_strub_mode_id_callable ();
> +
> +    case STRUB_WRAPPED:
> +      return get_strub_mode_id_wrapped ();
> +
> +    case STRUB_WRAPPER:
> +      return get_strub_mode_id_wrapper ();
> +
> +    case STRUB_INLINABLE:
> +      return get_strub_mode_id_inlinable ();
> +
> +    case STRUB_AT_CALLS_OPT:
> +      return get_strub_mode_id_at_calls_opt ();
> +
> +    default:
> +      gcc_unreachable ();
> +    }
> +}
> +
> +/* Return the parmeters (TREE_VALUE) for a strub attribute of MODE.
> +   We know we use a single parameter, so we bypass the creation of a
> +   tree list.  */
> +
> +static tree
> +get_strub_mode_attr_value (enum strub_mode mode)
> +{
> +  return get_strub_mode_attr_parm (mode);
> +}
> +
> +/* Determine whether ID is a well-formed strub mode-specifying attribute
> +   parameter for a function (type).  Only user-visible modes are accepted, and
> +   ID must be non-NULL.
> +
> +   For unacceptable parms, return 0, otherwise a nonzero value as below.
> +
> +   If the parm enables strub, return positive, otherwise negative.
> +
> +   If the affected type must be a distinct, incompatible type,return an integer
> +   of absolute value 2, otherwise 1.  */
> +
> +int
> +strub_validate_fn_attr_parm (tree id)
> +{
> +  int ret;
> +  const char *s = NULL;
> +  size_t len = 0;
> +
> +  /* do NOT test for NULL.  This is only to be called with non-NULL arguments.
> +     We assume that the strub parameter applies to a function, because only
> +     functions accept an explicit argument.  If we accepted NULL, and we
> +     happened to be called to verify the argument for a variable, our return
> +     values would be wrong.  */
> +  if (TREE_CODE (id) == STRING_CST)
> +    {
> +      s = TREE_STRING_POINTER (id);
> +      len = TREE_STRING_LENGTH (id) - 1;
> +    }
> +  else if (TREE_CODE (id) == IDENTIFIER_NODE)
> +    {
> +      s = IDENTIFIER_POINTER (id);
> +      len = IDENTIFIER_LENGTH (id);
> +    }
> +  else
> +    return 0;
> +
> +  enum strub_mode mode;
> +
> +  if (len != 8)
> +    return 0;
> +
> +  switch (s[0])
> +    {
> +    case 'd':
> +      mode = STRUB_DISABLED;
> +      ret = -1;
> +      break;
> +
> +    case 'a':
> +      mode = STRUB_AT_CALLS;
> +      ret = 2;
> +      break;
> +
> +    case 'i':
> +      mode = STRUB_INTERNAL;
> +      ret = 1;
> +      break;
> +
> +    case 'c':
> +      mode = STRUB_CALLABLE;
> +      ret = -2;
> +      break;
> +
> +    default:
> +      /* Other parms are for internal use only.  */
> +      return 0;
> +    }
> +
> +  tree mode_id = get_strub_mode_attr_parm (mode);
> +
> +  if (TREE_CODE (id) == IDENTIFIER_NODE
> +      ? id != mode_id
> +      : strncmp (s, IDENTIFIER_POINTER (mode_id), len) != 0)
> +    return 0;
> +
> +  return ret;
> +}
> +
> +/* Return the strub mode from STRUB_ATTR.  VAR_P should be TRUE if the attribute
> +   is taken from a variable, rather than from a function, or a type thereof.  */
> +
> +static enum strub_mode
> +get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
> +{
> +  enum strub_mode mode = STRUB_DISABLED;
> +
> +  if (strub_attr)
> +    {
> +      if (!TREE_VALUE (strub_attr))
> +       mode = !var_p ? STRUB_AT_CALLS : STRUB_INTERNAL;
> +      else
> +       {
> +         gcc_checking_assert (!var_p);
> +         tree id = TREE_VALUE (strub_attr);
> +         if (TREE_CODE (id) == TREE_LIST)
> +           id = TREE_VALUE (id);
> +         const char *s = (TREE_CODE (id) == STRING_CST
> +                          ? TREE_STRING_POINTER (id)
> +                          : IDENTIFIER_POINTER (id));
> +         size_t len = (TREE_CODE (id) == STRING_CST
> +                       ? TREE_STRING_LENGTH (id) - 1
> +                       : IDENTIFIER_LENGTH (id));
> +
> +         switch (len)
> +           {
> +           case 7:
> +             switch (s[6])
> +               {
> +               case 'r':
> +                 mode = STRUB_WRAPPER;
> +                 break;
> +
> +               case 'd':
> +                 mode = STRUB_WRAPPED;
> +                 break;
> +
> +               default:
> +                 gcc_unreachable ();
> +               }
> +             break;
> +
> +           case 8:
> +             switch (s[0])
> +               {
> +               case 'd':
> +                 mode = STRUB_DISABLED;
> +                 break;
> +
> +               case 'a':
> +                 mode = STRUB_AT_CALLS;
> +                 break;
> +
> +               case 'i':
> +                 mode = STRUB_INTERNAL;
> +                 break;
> +
> +               case 'c':
> +                 mode = STRUB_CALLABLE;
> +                 break;
> +
> +               default:
> +                 gcc_unreachable ();
> +               }
> +             break;
> +
> +           case 9:
> +             mode = STRUB_INLINABLE;
> +             break;
> +
> +           case 12:
> +             mode = STRUB_AT_CALLS_OPT;
> +             break;
> +
> +           default:
> +             gcc_unreachable ();
> +           }
> +
> +         gcc_checking_assert (TREE_CODE (id) == IDENTIFIER_NODE
> +                              ? id == get_strub_mode_attr_parm (mode)
> +                              : strncmp (IDENTIFIER_POINTER
> +                                         (get_strub_mode_attr_parm (mode)),
> +                                         s, len) == 0);
> +       }
> +    }
> +
> +  return mode;
> +}
> +
> +/* Look up, decode and return the strub mode associated with FNDECL.  */
> +
> +static enum strub_mode
> +get_strub_mode_from_fndecl (tree fndecl)
> +{
> +  return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
> +}
> +
> +/* Look up, decode and return the strub mode associated with NODE.  */
> +
> +static enum strub_mode
> +get_strub_mode (cgraph_node *node)
> +{
> +  return get_strub_mode_from_fndecl (node->decl);
> +}
> +
> +/* Look up, decode and return the strub mode associated with TYPE.  */
> +
> +static enum strub_mode
> +get_strub_mode_from_type (tree type)
> +{
> +  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 >= -1 && !var_p)
> +    return STRUB_CALLABLE;
> +
> +  return STRUB_DISABLED;
> +}
> +
> +
> +/* Return TRUE iff NODE calls builtin va_start.  */
> +
> +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;
> +}
> +
> +/* Return TRUE iff NODE calls builtin apply_args, and optionally REPORT it.  */
> +
> +static bool
> +calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
> +{
> +  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 (e->call_stmt
> +               ? gimple_location (e->call_stmt)
> +               : DECL_SOURCE_LOCATION (node->decl),
> +               "at-calls %<strub%> does not support call to %qD",
> +               cdecl);
> +    }
> +
> +  return result;
> +}
> +
> +/* Return TRUE iff NODE carries the always_inline attribute.  */
> +
> +static inline bool
> +strub_always_inline_p (cgraph_node *node)
> +{
> +  return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
> +}
> +
> +/* Return TRUE iff NODE is potentially eligible for any strub-enabled mode, and
> +   optionally REPORT the reasons for ineligibility.  */
> +
> +static inline bool
> +can_strub_p (cgraph_node *node, bool report = false)
> +{
> +  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;
> +}
> +
> +/* Return TRUE iff NODE is eligible for at-calls strub, and optionally REPORT
> +   the reasons for ineligibility.  Besides general non-eligibility for
> +   strub-enabled modes, at-calls rules out calling builtin apply_args.  */
> +
> +static bool
> +can_strub_at_calls_p (cgraph_node *node, bool report = false)
> +{
> +  bool result = !report || can_strub_p (node, report);
> +
> +  if (!result && !report)
> +    return result;
> +
> +  return !calls_builtin_apply_args_p (node, report);
> +}
> +
> +/* Return TRUE iff the called function (pointer or, if available,
> +   decl) undergoes a significant type conversion for the call.  Strub
> +   mode changes between function types, and other non-useless type
> +   conversions, are regarded as significant.  When the function type
> +   is overridden, the effective strub mode for the call is that of the
> +   call fntype, rather than that of the pointer or of the decl.
> +   Functions called with type overrides cannot undergo type changes;
> +   it's as if their address was taken, so they're considered
> +   non-viable for implicit at-calls strub mode.  */
> +
> +static inline bool
> +strub_call_fntype_override_p (const gcall *gs)
> +{
> +  if (gimple_call_internal_p (gs))
> +    return false;
> +  tree fn_type = TREE_TYPE (TREE_TYPE (gimple_call_fn (gs)));
> +  if (tree decl = gimple_call_fndecl (gs))
> +    fn_type = TREE_TYPE (decl);
> +
> +  /* We do NOT want to take the mode from the decl here.  This
> +     function is used to tell whether we can change the strub mode of
> +     a function, and whether the effective mode for the call is to be
> +     taken from the decl or from an overrider type.  When the strub
> +     mode is explicitly declared, or overridden with a type cast, the
> +     difference will be noticed in function types.  However, if the
> +     strub mode is implicit due to e.g. strub variables or -fstrub=*
> +     command-line flags, we will adjust call types along with function
> +     types.  In either case, the presence of type or strub mode
> +     overriders in calls will prevent a function from having its strub
> +     modes changed in ways that would imply type changes, but taking
> +     strub modes from decls would defeat this, since we set strub
> +     modes and then call this function to tell whether the original
> +     type was overridden to decide whether to adjust the call.  We
> +     need the answer to be about the type, not the decl.  */
> +  enum strub_mode mode = get_strub_mode_from_type (fn_type);
> +  return (get_strub_mode_from_type (gs->u.fntype) != mode
> +         || !useless_type_conversion_p (gs->u.fntype, fn_type));
> +}
> +
> +/* Return TRUE iff NODE is called directly with a type override.  */
> +
> +static bool
> +called_directly_with_type_override_p (cgraph_node *node, void *)
> +{
> +  for (cgraph_edge *e = node->callers; e; e = e->next_caller)
> +    if (e->call_stmt && strub_call_fntype_override_p (e->call_stmt))
> +      return true;
> +
> +  return false;
> +}
> +
> +/* Return TRUE iff NODE or any other nodes aliased to it are called
> +   with type overrides.  We can't safely change the type of such
> +   functions.  */
> +
> +static bool
> +called_with_type_override_p (cgraph_node *node)
> +{
> +  return (node->call_for_symbol_thunks_and_aliases
> +         (called_directly_with_type_override_p, NULL, true, true));
> +}
> +
> +/* Symbolic macro for the max number of arguments that internal strub may add to
> +   a function.  */
> +
> +#define STRUB_INTERNAL_MAX_EXTRA_ARGS 3
> +
> +/* We can't perform internal strubbing if the function body involves certain
> +   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 (e->call_stmt
> +                   ? gimple_location (e->call_stmt)
> +                   : DECL_SOURCE_LOCATION (node->decl),
> +                   "%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 references are not preserved when copying.  If referenced
> +        in nested functions, as in 920415-1.c and 920721-4.c their decls get
> +        remapped independently.  The exclusion below might be too broad, in
> +        that we might be able to support correctly cases in which the labels
> +        are only used internally in a function, but disconnecting forced labels
> +        from their original declarations is undesirable in general.  */
> +      basic_block bb;
> +      FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
> +       for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
> +            !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);
> +
> +           if (!FORCED_LABEL (target))
> +             continue;
> +
> +           result = false;
> +
> +           if (!report)
> +             return result;
> +
> +           sorry_at (gimple_location (label_stmt),
> +                     "internal %<strub%> does not support forced 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;
> +}
> +
> +/* Return TRUE iff NODE has any strub-requiring local variable, or accesses (as
> +   in reading) any variable through a strub-requiring type.  */
> +
> +static bool
> +strub_from_body_p (cgraph_node *node)
> +{
> +  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-requiring types.
> +     ??? Compound types don't propagate the strub requirement to
> +     component types.  */
> +  basic_block bb;
> +  FOR_EACH_BB_FN (bb, DECL_STRUCT_FUNCTION (node->decl))
> +    for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
> +        !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;
> +    }
> +}
> +
> +/* Compute the strub mode to be used for NODE.  STRUB_ATTR should be the strub
> +   attribute,found for NODE, if any.  */
> +
> +static enum strub_mode
> +compute_strub_mode (cgraph_node *node, tree strub_attr)
> +{
> +  enum strub_mode req_mode = get_strub_mode_from_attr (strub_attr);
> +
> +  gcc_checking_assert (flag_strub >= -2 && flag_strub <= 3);
> +
> +  /* Symbolic encodings of the -fstrub-* flags.  */
> +  /* Enable strub when explicitly requested through attributes to functions or
> +     variables, reporting errors if the requests cannot be satisfied.  */
> +  const bool strub_flag_auto = flag_strub < 0;
> +  /* strub_flag_auto with strub call verification; without this, functions are
> +     implicitly callable.  */
> +  const bool strub_flag_strict = flag_strub < -1;
> +  /* Disable strub altogether, ignore attributes entirely.  */
> +  const bool strub_flag_disabled = flag_strub == 0;
> +  /* On top of _auto, also enable strub implicitly for functions that can
> +     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_strict
> +             || 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->externally_visible
> +                  || (node->binds_to_current_def_p ()
> +                      && node->can_be_local_p ()))
> +              && node->only_called_directly_p ()
> +              && !called_with_type_override_p (node))));
> +  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)
> +       : (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;
> +}
> +
> +/* Set FNDT's strub mode to callable.
> +   FNDT may be a function decl or a function type.  */
> +
> +void
> +strub_make_callable (tree fndt)
> +{
> +  strub_set_fndt_mode_to (fndt, STRUB_CALLABLE, false);
> +}
> +
> +/* Set NODE to strub MODE.  Report incompatibilities between MODE and the mode
> +   requested through explicit attributes, and cases of non-eligibility.  */
> +
> +static void
> +set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
> +{
> +  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 %qE selected for %qD, when %qE was requested",
> +                   get_strub_mode_attr_parm (mode),
> +                   node->decl,
> +                   get_strub_mode_attr_parm (req_mode));
> +         if (node->alias)
> +           {
> +             cgraph_node *target = node->ultimate_alias_target ();
> +             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);
> +}
> +
> +/* Compute and set NODE's strub mode.  */
> +
> +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 (strub_mode caller_mode, strub_mode callee_mode)
> +{
> +  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 >= -1);
> +
> +    case STRUB_DISABLED:
> +      return false;
> +
> +    case STRUB_CALLABLE:
> +      break;
> +
> +    default:
> +      gcc_unreachable ();
> +    }
> +
> +  return true;
> +}
> +
> +/* Return TRUE iff CALLEE can be inlined into CALLER.  We wish to avoid inlining
> +   WRAPPED functions back into their WRAPPERs.  More generally, we wish to avoid
> +   inlining strubbed functions into non-strubbed ones.  CALLER doesn't have to
> +   be an immediate caller of CALLEE: the immediate caller may have already been
> +   cloned for inlining, and then CALLER may be further up the original call
> +   chain.  ???  It would be nice if our own caller would retry inlining callee
> +   if caller gets inlined.  */
> +
> +bool
> +strub_inlinable_to_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:
> +      /* When we consider inlining, we've already verified callability, so we
> +        can even inline callable and then disabled into a strub context.  That
> +        will get strubbed along with the context, so it's hopefully not a
> +        problem.  */
> +      return true;
> +
> +    default:
> +      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 types T1 and T2 are strub-compatible.  Return 1 if the strub modes
> +   are the same, 2 if they are interchangeable, and 0 otherwise.  */
> +
> +int
> +strub_comptypes (tree t1, tree t2)
> +{
> +  if (TREE_CODE (t1) != TREE_CODE (t2))
> +    return 0;
> +
> +  enum strub_mode m1 = get_strub_mode_from_type (t1);
> +  enum strub_mode m2 = get_strub_mode_from_type (t2);
> +
> +  if (m1 == m2)
> +    return 1;
> +
> +  /* We're dealing with types, so only strub modes that can be selected by
> +     attributes in the front end matter.  If either mode is at-calls (for
> +     functions) or internal (for variables), the conversion is not
> +     compatible.  */
> +  bool var_p = !FUNC_OR_METHOD_TYPE_P (t1);
> +  enum strub_mode mr = var_p ? STRUB_INTERNAL : STRUB_AT_CALLS;
> +  if (m1 == mr || m2 == mr)
> +    return 0;
> +
> +  return 2;
> +}
> +
> +/* Return the effective strub mode used for CALL, and set *TYPEP to
> +   the effective type used for the call.  The effective type and mode
> +   are those of the callee, unless the call involves a typecast.  */
> +
> +static enum strub_mode
> +effective_strub_mode_for_call (gcall *call, tree *typep)
> +{
> +  tree type;
> +  enum strub_mode mode;
> +
> +  if (strub_call_fntype_override_p (call))
> +    {
> +      type = gimple_call_fntype (call);
> +      mode = get_strub_mode_from_type (type);
> +    }
> +  else
> +    {
> +      type = TREE_TYPE (TREE_TYPE (gimple_call_fn (call)));
> +      tree decl = gimple_call_fndecl (call);
> +      if (decl)
> +       mode = get_strub_mode_from_fndecl (decl);
> +      else
> +       mode = get_strub_mode_from_type (type);
> +    }
> +
> +  if (typep)
> +    *typep = type;
> +
> +  return mode;
> +}
> +
> +/* Create a distinct copy of the type of NODE's function, and change
> +   the fntype of all calls to it with the same main type to the new
> +   type.  */
> +
> +static void
> +distinctify_node_type (cgraph_node *node)
> +{
> +  tree old_type = TREE_TYPE (node->decl);
> +  tree new_type = build_distinct_type_copy (old_type);
> +  tree new_ptr_type = NULL_TREE;
> +
> +  /* Remap any calls to node->decl that use old_type, or a variant
> +     thereof, to new_type as well.  We don't look for aliases, their
> +     declarations will have their types changed independently, and
> +     we'll adjust their fntypes then.  */
> +  for (cgraph_edge *e = node->callers; e; e = e->next_caller)
> +    {
> +      if (!e->call_stmt)
> +       continue;
> +      tree fnaddr = gimple_call_fn (e->call_stmt);
> +      gcc_checking_assert (TREE_CODE (fnaddr) == ADDR_EXPR
> +                          && TREE_OPERAND (fnaddr, 0) == node->decl);
> +      if (strub_call_fntype_override_p (e->call_stmt))
> +       continue;
> +      if (!new_ptr_type)
> +       new_ptr_type = build_pointer_type (new_type);
> +      TREE_TYPE (fnaddr) = new_ptr_type;
> +      gimple_call_set_fntype (e->call_stmt, new_type);
> +    }
> +
> +  TREE_TYPE (node->decl) = new_type;
> +}
> +
> +/* Return TRUE iff TYPE and any variants have the same strub mode.  */
> +
> +static bool
> +same_strub_mode_in_variants_p (tree type)
> +{
> +  enum strub_mode mode = get_strub_mode_from_type (type);
> +
> +  for (tree other = TYPE_MAIN_VARIANT (type);
> +       other != NULL_TREE; other = TYPE_NEXT_VARIANT (other))
> +    if (type != other && mode != get_strub_mode_from_type (other))
> +      return false;
> +
> +  /* Check that the canonical type, if set, either is in the same
> +     variant chain, or has the same strub mode as type.  Also check
> +     the variants of the canonical type.  */
> +  if (TYPE_CANONICAL (type)
> +      && (TYPE_MAIN_VARIANT (TYPE_CANONICAL (type))
> +         != TYPE_MAIN_VARIANT (type)))
> +    {
> +      if (mode != get_strub_mode_from_type (TYPE_CANONICAL (type)))
> +       return false;
> +      else
> +       return same_strub_mode_in_variants_p (TYPE_CANONICAL (type));
> +    }
> +
> +  return true;
> +}
> +
> +/* Check that strub functions don't call non-strub functions, and that
> +   always_inline strub functions are only called by strub
> +   functions.  */
> +
> +static void
> +verify_strub ()
> +{
> +  cgraph_node *node;
> +
> +  /* It's expected that check strub-wise pointer type compatibility of variables
> +     and of functions is already taken care of by front-ends, on account of the
> +     attribute's being marked as affecting type identity and of the creation of
> +     distinct types.  */
> +
> +  /* Check that call targets in strub contexts have strub-callable types.  */
> +
> +  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
> +  {
> +    enum strub_mode caller_mode = get_strub_mode (node);
> +
> +    for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
> +      {
> +       gcc_checking_assert (e->indirect_unknown_callee);
> +
> +       if (!e->call_stmt)
> +         continue;
> +
> +       enum strub_mode callee_mode
> +         = effective_strub_mode_for_call (e->call_stmt, NULL);
> +
> +       if (!strub_callable_from_p (caller_mode, callee_mode))
> +         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 (!e->call_stmt)
> +         continue;
> +
> +       tree callee_fntype;
> +       enum strub_mode callee_mode
> +         = effective_strub_mode_for_call (e->call_stmt, &callee_fntype);
> +
> +       if (!strub_callable_from_p (caller_mode, callee_mode))
> +         {
> +           if (callee_mode == 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)
> +                    && caller_mode == STRUB_INTERNAL)
> +             /* This is ok, it will be kept in the STRUB_WRAPPER, and removed
> +                from the STRUB_WRAPPED's strub context.  */
> +             continue;
> +           else if (!strub_call_fntype_override_p (e->call_stmt))
> +             error_at (gimple_location (e->call_stmt),
> +                       "calling non-%<strub%> %qD in %<strub%> context %qD",
> +                       e->callee->decl, node->decl);
> +           else
> +             error_at (gimple_location (e->call_stmt),
> +                       "calling %qD using non-%<strub%> type %qT"
> +                       " in %<strub%> context %qD",
> +                       e->callee->decl, callee_fntype, node->decl);
> +         }
> +      }
> +  }
> +}
> +
> +namespace {
> +
> +/* Define a pass to compute strub modes.  */
> +const pass_data pass_data_ipa_strub_mode = {
> +  SIMPLE_IPA_PASS,
> +  "strubm",
> +  OPTGROUP_NONE,
> +  TV_NONE,
> +  PROP_cfg, // properties_required
> +  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 relaxed (-3) and strict (-4) settings, that only enable strub at a
> +       function or variable attribute's request, the attribute handler changes
> +       flag_strub to -1 or -2, respectively, if any strub-enabling occurence of
> +       the attribute is found.  Therefore, if it remains at -3 or -4, nothing
> +       that would enable strub was found, so we can disable it and avoid the
> +       overhead.  */
> +    if (flag_strub < -2)
> +      flag_strub = 0;
> +    return flag_strub;
> +  }
> +  virtual unsigned int execute (function *);
> +};
> +
> +/* Define a pass to introduce strub transformations.  */
> +const pass_data pass_data_ipa_strub = {
> +  SIMPLE_IPA_PASS,
> +  "strub",
> +  OPTGROUP_NONE,
> +  TV_NONE,
> +  PROP_cfg | PROP_ssa, // 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 && !seen_error (); }
> +  virtual unsigned int execute (function *);
> +
> +  /* Define on demand and cache some types we use often.  */
> +#define DEF_TYPE(IDX, NAME, INIT)              \
> +  static inline tree get_ ## NAME () {         \
> +    int idx = STRUB_TYPE_BASE + IDX;           \
> +    static tree type = strub_cache[idx];       \
> +    if (!type)                                 \
> +      strub_cache[idx] = 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 (0, wmt, build_variant_type_copy (ptr_type_node))
> +
> +  DEF_TYPE (1, pwmt, build_reference_type (get_wmt ()))
> +
> +  DEF_TYPE (2, qpwmt,
> +           build_qualified_type (get_pwmt (),
> +                                 TYPE_QUAL_RESTRICT
> +                                 /* | TYPE_QUAL_CONST */))
> +
> +  DEF_TYPE (3, qptr,
> +           build_qualified_type (ptr_type_node,
> +                                 TYPE_QUAL_RESTRICT
> +                                 | TYPE_QUAL_CONST))
> +
> +  DEF_TYPE (4, qpvalst,
> +           build_qualified_type (build_reference_type
> +                                 (va_list_type_node),
> +                                 TYPE_QUAL_RESTRICT
> +                                 /* | TYPE_QUAL_CONST */))
> +
> +#undef DEF_TYPE
> +
> +  /* Define non-strub builtins on demand.  */
> +#define DEF_NM_BUILTIN(NAME, CODE, FNTYPELIST)                 \
> +  static tree get_ ## NAME () {                                        \
> +    tree decl = builtin_decl_explicit (CODE);                  \
> +    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 strub builtins on demand.  */
> +#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);                  \
> +      }                                                                \
> +    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 strub identifiers on demand.  */
> +#define DEF_IDENT(IDX, NAME)                                           \
> +  static inline tree get_ ## NAME () {                                 \
> +    int idx = STRUB_IDENT_BASE + IDX;                                  \
> +    tree identifier = strub_cache[idx];                                        \
> +    if (!identifier)                                                   \
> +      strub_cache[idx] = identifier = get_identifier (".strub." #NAME);        \
> +    return identifier;                                                 \
> +  }
> +
> +  DEF_IDENT (0, watermark_ptr)
> +  DEF_IDENT (1, va_list_ptr)
> +  DEF_IDENT (2, apply_args)
> +
> +#undef DEF_IDENT
> +
> +  static inline int adjust_at_calls_type (tree);
> +  static inline void adjust_at_calls_call (cgraph_edge *, int, tree);
> +  static inline void adjust_at_calls_calls (cgraph_node *);
> +
> +  /* Add to SEQ a call to the strub watermark update builtin, taking NODE's
> +     location if given.  Optionally add the corresponding edge from NODE, with
> +     execution frequency COUNT.  Return the modified SEQ.  */
> +
> +  static inline gimple_seq
> +  call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
> +                        gimple_seq seq = NULL)
> +    {
> +      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)
> +       node->create_edge (cgraph_node::get_create (uwm), update, count, false);
> +      return seq;
> +    }
> +
> +};
> +
> +} // anon namespace
> +
> +/* Gather with this type a collection of parameters that we're turning into
> +   explicit references.  */
> +
> +typedef hash_set<tree> indirect_parms_t;
> +
> +/* Dereference OP's incoming turned-into-reference parm if it's an
> +   INDIRECT_PARMS or an ADDR_EXPR thereof.  Set *REC and return according to
> +   gimple-walking expectations.  */
> +
> +static tree
> +maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec)
> +{
> +  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)))
> +       {
> +         op = TREE_OPERAND (op, 0);
> +         return op;
> +       }
> +    }
> +
> +  return NULL_TREE;
> +}
> +
> +/* A gimple-walking function that adds dereferencing to indirect parms.  */
> +
> +static tree
> +walk_make_indirect (tree *op, int *rec, void *arg)
> +{
> +  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;
> +}
> +
> +/* A gimple-walking function that turns any non-gimple-val ADDR_EXPRs into a
> +   separate SSA.  Though addresses of e.g. parameters, and of members thereof,
> +   are gimple vals, turning parameters into references, with an extra layer of
> +   indirection and thus explicit dereferencing, need to be regimplified.  */
> +
> +static tree
> +walk_regimplify_addr_expr (tree *op, int *rec, void *arg)
> +{
> +  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;
> +}
> +
> +/* Turn STMT's PHI arg defs into separate SSA defs if they've become
> +   non-gimple_val.  Return TRUE if any edge insertions need to be committed.  */
> +
> +static bool
> +walk_regimplify_phi (gphi *stmt)
> +{
> +  bool needs_commit = false;
> +
> +  for (unsigned i = 0, n = gimple_phi_num_args (stmt); i < n; i++)
> +    {
> +      tree op = gimple_phi_arg_def (stmt, i);
> +      if ((TREE_CODE (op) == ADDR_EXPR
> +          && !is_gimple_val (op))
> +         /* ??? A PARM_DECL that was addressable in the original function and
> +            had its address in PHI nodes, but that became a reference in the
> +            wrapped clone would NOT be updated by update_ssa in PHI nodes.
> +            Alas, if we were to create a default def for it now, update_ssa
> +            would complain that the symbol that needed rewriting already has
> +            SSA names associated with it.  OTOH, leaving the PARM_DECL alone,
> +            it eventually causes errors because it remains unchanged in PHI
> +            nodes, but it gets rewritten as expected if it appears in other
> +            stmts.  So we cheat a little here, and force the PARM_DECL out of
> +            the PHI node and into an assignment.  It's a little expensive,
> +            because we insert it at the edge, which introduces a basic block
> +            that's entirely unnecessary, but it works, and the block will be
> +            removed as the default def gets propagated back into the PHI node,
> +            so the final optimized code looks just as expected.  */
> +         || (TREE_CODE (op) == PARM_DECL
> +             && !TREE_ADDRESSABLE (op)))
> +       {
> +         tree temp = make_ssa_name (TREE_TYPE (op), stmt);
> +         if (TREE_CODE (op) == PARM_DECL)
> +           SET_SSA_NAME_VAR_OR_IDENTIFIER (temp, DECL_NAME (op));
> +         SET_PHI_ARG_DEF (stmt, i, temp);
> +
> +         gimple *assign = gimple_build_assign (temp, op);
> +         if (gimple_phi_arg_has_location (stmt, i))
> +           gimple_set_location (assign, gimple_phi_arg_location (stmt, i));
> +         gsi_insert_on_edge (gimple_phi_arg_edge (stmt, i), assign);
> +         needs_commit = true;
> +       }
> +    }
> +
> +  return needs_commit;
> +}
> +
> +/* Create a reference type to use for PARM when turning it into a reference.
> +   NONALIASED causes the reference type to gain its own separate alias set, so
> +   that accessing the indirectly-passed parm won'will not add aliasing
> +   noise.  */
> +
> +static tree
> +build_ref_type_for (tree parm, bool nonaliased = true)
> +{
> +  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.  */
> +  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;
> +}
> +
> +/* 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)
> +{
> +  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);
> +
> +      gcall *call = dyn_cast <gcall *> (stmt);
> +      if (!call)
> +       continue;
> +
> +      tree callee = gimple_call_fndecl (call);
> +      gcc_checking_assert (callee);
> +      node->create_edge (cgraph_node::get_create (callee), call, count, false);
> +    }
> +}
> +
> +/* Insert SEQ after the call at GSI, as if the call was in a try block with SEQ
> +   as finally, i.e., SEQ will run after the call whether it returns or
> +   propagates an exception.  This handles block splitting, EH edge and block
> +   creation, noreturn and nothrow optimizations, and even throwing calls without
> +   preexisting local handlers.  */
> +
> +static void
> +gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
> +{
> +  if (!seq)
> +    return;
> +
> +  gimple *stmt = gsi_stmt (gsi);
> +
> +  if (gimple_has_location (stmt))
> +    annotate_all_with_location (seq, gimple_location (stmt));
> +
> +  gcall *call = dyn_cast <gcall *> (stmt);
> +  bool noreturn_p = call && gimple_call_noreturn_p (call);
> +  int eh_lp = lookup_stmt_eh_lp (stmt);
> +  bool must_not_throw_p = eh_lp < 0;
> +  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.  */
> +  edge neeh = make_eh_edge (stmt);
> +  neeh->probability = profile_probability::never ();
> +  gcc_checking_assert (neeh->dest == bb_eh_cleanup);
> +  gcc_checking_assert (!neeh->dest->count.initialized_p ());
> +  neeh->dest->count = neeh->count ();
> +  add_call_edges_for_seq (seq, neeh->dest->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);
> +    }
> +}
> +
> +/* Record the order of the last cgraph entry whose mode we've already set, so
> +   that we can perform mode setting incrementally without duplication.  */
> +static int last_cgraph_order;
> +
> +/* Set strub modes for functions introduced since the last call.  */
> +
> +static void
> +ipa_strub_set_mode_for_new_functions ()
> +{
> +  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_fndecl (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;
> +
> +  gcc_checking_assert (same_strub_mode_in_variants_p (type));
> +
> +  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;
> +}
> +
> +/* Adjust a call to an at-calls call target.  Create a watermark local variable
> +   if needed, initialize it before, pass it to the callee according to the
> +   modified at-calls interface, and release the callee's stack space after the
> +   call, if not deferred.  If the call is const or pure, arrange for the
> +   watermark to not be assumed unused or unchanged.  */
> +
> +void
> +pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args,
> +                                     tree callee_fntype)
> +{
> +  gcc_checking_assert (e->call_stmt);
> +  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);
> +      e->caller->create_edge (cgraph_node::get_create (enter),
> +                             stptr, gsi_bb (gsi)->count, false);
> +    }
> +
> +
> +  /* 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++)
> +      vargs.quick_push (gimple_call_arg (stmt, i));
> +
> +    if (gimple_call_internal_p (stmt))
> +      gcc_unreachable ();
> +    else
> +      new_stmt = gimple_build_call_vec (gimple_call_fn (stmt), vargs);
> +    gimple_call_set_fntype (new_stmt, callee_fntype);
> +
> +    if (gimple_call_lhs (stmt))
> +      gimple_call_set_lhs (new_stmt, gimple_call_lhs (stmt));
> +
> +    gimple_move_vops (new_stmt, stmt);
> +
> +    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);
> +}
> +
> +/* Adjust all at-calls calls in NODE. */
> +
> +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);
> +
> +         if (!e->call_stmt)
> +           continue;
> +
> +         tree callee_fntype;
> +         enum strub_mode callee_mode
> +           = effective_strub_mode_for_call (e->call_stmt, &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, callee_fntype);
> +       }
> +      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);
> +
> +         if (!e->call_stmt)
> +           continue;
> +
> +         tree callee_fntype;
> +         enum strub_mode callee_mode
> +           = effective_strub_mode_for_call (e->call_stmt, &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, callee_fntype);
> +       }
> +      pop_cfun ();
> +    }
> +}
> +
> +/* The strubm (strub mode) pass computes a strub mode for each function in the
> +   call graph, and checks, before any inlining, that strub callability
> +   requirements in effect are satisfied.  */
> +
> +unsigned int
> +pass_ipa_strub_mode::execute (function *)
> +{
> +  last_cgraph_order = 0;
> +  ipa_strub_set_mode_for_new_functions ();
> +
> +  /* Verify before any inlining or other transformations.  */
> +  verify_strub ();
> +
> +  return 0;
> +}
> +
> +/* Create a strub mode pass.  */
> +
> +simple_ipa_opt_pass *
> +make_pass_ipa_strub_mode (gcc::context *ctxt)
> +{
> +  return new pass_ipa_strub_mode (ctxt);
> +}
> +
> +/* The strub pass proper adjusts types, signatures, and at-calls calls, and
> +   splits internal-strub functions.  */
> +
> +unsigned int
> +pass_ipa_strub::execute (function *)
> +{
> +  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)
> +         distinctify_node_type (onode);
> +
> +       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;
> +
> +       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);
> +
> +                 gcall *call = dyn_cast <gcall *> (stmt);
> +
> +                 if (!call)
> +                   continue;
> +
> +                 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 ();
> +      }
> +  }
> +
> +  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;
> +      }
> +
> +    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);
> +
> +    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;
> +         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;
> +            i++, nparm = DECL_CHAIN (nparm), nparmt = TREE_CHAIN (nparmt))
> +         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;
> +      }
> +
> +#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 (/* unlike create_wrapper: preserve_comdat_group = */true);
> +
> +       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;
> +       onode->create_edge (target, NULL, onode->count);
> +       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 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;
> +         bool needs_commit = false;
> +         FOR_EACH_BB_FN (bb, cfun)
> +           {
> +             for (gphi_iterator gsi = gsi_start_nonvirtual_phis (bb);
> +                  !gsi_end_p (gsi);
> +                  gsi_next_nonvirtual_phi (&gsi))
> +               {
> +                 gphi *stmt = gsi.phi ();
> +
> +                 walk_stmt_info wi = {};
> +                 wi.info = &indirect_nparms;
> +                 walk_gimple_op (stmt, walk_make_indirect, &wi);
> +                 if (wi.changed && !is_gimple_debug (gsi_stmt (gsi)))
> +                   if (walk_regimplify_phi (stmt))
> +                     needs_commit = true;
> +               }
> +
> +             for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
> +                  !gsi_end_p (gsi); gsi_next (&gsi))
> +               {
> +                 gimple *stmt = gsi_stmt (gsi);
> +
> +                 walk_stmt_info wi = {};
> +                 wi.info = &indirect_nparms;
> +                 walk_gimple_op (stmt, walk_make_indirect, &wi);
> +                 if (wi.changed)
> +                   {
> +                     if (!is_gimple_debug (stmt))
> +                       {
> +                         wi.info = &gsi;
> +                         walk_gimple_op (stmt, walk_regimplify_addr_expr,
> +                                         &wi);
> +                       }
> +                     update_stmt (stmt);
> +                   }
> +               }
> +           }
> +         if (needs_commit)
> +           gsi_commit_edge_inserts ();
> +       }
> +
> +      if (DECL_STRUCT_FUNCTION (nnode->decl)->calls_alloca
> +         || is_stdarg || apply_args)
> +       for (cgraph_edge *e = nnode->callees, *enext; e; e = enext)
> +         {
> +           if (!e->call_stmt)
> +             continue;
> +
> +           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));
> +                   if (!is_gimple_val (arg))
> +                     arg = force_gimple_operand_gsi (&gsi, arg, true,
> +                                                     NULL_TREE, true, GSI_SAME_STMT);
> +                 }
> +               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)));
> +
> +      gcall *wrcall;
> +      while (!(wrcall = dyn_cast <gcall *> (gsi_stmt (gsi))))
> +       gsi_next (&gsi);
> +
> +      tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
> +      TREE_ADDRESSABLE (swm) = true;
> +      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);
> +      onode->create_edge (cgraph_node::get_create (enter),
> +                         stptr, gsi_bb (gsi)->count, false);
> +
> +      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);
> +         onode->create_edge (cgraph_node::get_create (bappargs),
> +                             appargs, gsi_bb (gsi)->count, false);
> +       }
> +
> +      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);
> +         onode->create_edge (cgraph_node::get_create (bvastart),
> +                             vastart, gsi_bb (gsi)->count, false);
> +
> +         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 ();
> +    }
> +  }
> +
> +  return 0;
> +}
> +
> +simple_ipa_opt_pass *
> +make_pass_ipa_strub (gcc::context *ctxt)
> +{
> +  return new pass_ipa_strub (ctxt);
> +}
> +
> +#include "gt-ipa-strub.h"
> diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
> new file mode 100644
> index 0000000000000..f367a4a0ef827
> --- /dev/null
> +++ b/gcc/ipa-strub.h
> @@ -0,0 +1,45 @@
> +/* strub (stack scrubbing) infrastructure.
> +   Copyright (C) 2021-2023 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 CALLEE can be inlined into CALLER, as far as stack scrubbing
> +   constraints are concerned.  CALLEE doesn't have to be called directly by
> +   CALLER, but the returned value says nothing about intervening functions.  */
> +extern bool strub_inlinable_to_p (cgraph_node *callee, cgraph_node *caller);
> +
> +/* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
> +extern bool strub_splittable_p (cgraph_node *node);
> +
> +/* 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);
> +
> +/* Return zero iff ID is NOT an acceptable parameter for a user-supplied strub
> +   attribute for a function.  Otherwise, return >0 if it enables strub, <0 if it
> +   does not.  Return +/-1 if the attribute-modified type is compatible with the
> +   type without the attribute, or +/-2 if it is not compatible.  */
> +extern int strub_validate_fn_attr_parm (tree id);
> +
> +/* Like comptypes, return 0 if t1 and t2 are not compatible, 1 if they are
> +   compatible, and 2 if they are nearly compatible.  Same strub mode is
> +   compatible, interface-compatible strub modes are nearly compatible.  */
> +extern int strub_comptypes (tree t1, tree t2);
> diff --git a/gcc/passes.def b/gcc/passes.def
> index 1e1950bdb39cb..d515e77be0399 100644
> --- a/gcc/passes.def
> +++ b/gcc/passes.def
> @@ -52,6 +52,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);
> @@ -115,6 +116,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 0000000000000..c7a79a6ea0d8a
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-O0.c
> @@ -0,0 +1,14 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O0 -fstrub=strict -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 0000000000000..96285c975d98e
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-O1.c
> @@ -0,0 +1,15 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O1 -fstrub=strict -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 0000000000000..8edc0d8aa1321
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-O2.c
> @@ -0,0 +1,16 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
> +
> +/* At -O2, without -fno-inline, we fully expand enter and update, and add a test
> +   around the leave call.  */
> +
> +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 0000000000000..c6d900cf3c45b
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
> @@ -0,0 +1,15 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fstrub=strict -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 0000000000000..33ee465e51cb6
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-O3.c
> @@ -0,0 +1,12 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O3 -fstrub=strict -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 0000000000000..2936f82079e18
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
> @@ -0,0 +1,15 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O3 -fstrub=strict -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 0000000000000..479746e57d87e
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-Og.c
> @@ -0,0 +1,16 @@
> +/* { dg-do compile } */
> +/* { dg-options "-Og -fstrub=strict -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 0000000000000..2241d4ea07f27
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-Os.c
> @@ -0,0 +1,18 @@
> +/* { dg-do compile } */
> +/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
> +
> +/* At -Os, without -fno-inline, we fully expand enter, and also update.  The
> +   expanded update might be larger than a call proper, but argument saving and
> +   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 0000000000000..a322bcc5da606
> --- /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 \[(\]callable\[)\]" 1 "strubm" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
> +
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
> diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
> new file mode 100644
> index 0000000000000..db60026d0e080
> --- /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 \[(\]internal\[)\]" 2 "strubm" } } */
> +
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
> diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
> new file mode 100644
> index 0000000000000..2f462adc1efe0
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-apply1.c
> @@ -0,0 +1,15 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict" } */
> +
> +void __attribute__ ((__strub__ ("callable")))
> +apply_function (void *args)
> +{
> +  __builtin_apply (0, args, 0);
> +}
> +
> +void __attribute__ ((__strub__ ("internal")))
> +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 0000000000000..a5d7551f5da5c
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-apply2.c
> @@ -0,0 +1,12 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict" } */
> +
> +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 0000000000000..64422a0d1e880
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-apply3.c
> @@ -0,0 +1,8 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict" } */
> +
> +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 0000000000000..15ffaa031b899
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-apply4.c
> @@ -0,0 +1,21 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fstrub=strict -fdump-ipa-strubm" } */
> +
> +/* Check that implicit enabling of strub mode selects internal strub when the
> +   function uses __builtin_apply_args, that prevents the optimization to
> +   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 \[(\]internal\[)\]" 1 "strubm" } } */
> diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
> new file mode 100644
> index 0000000000000..b70843b4215a4
> --- /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 \[(\]at-calls\[)\]" 1 "strubm" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */
> +
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strub" } } */
> diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
> new file mode 100644
> index 0000000000000..97a3988a6b922
> --- /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 \[(\]callable\[)\]" 2 "strubm" } } */
> +
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strub" } } */
> diff --git a/gcc/testsuite/c-c++-common/strub-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
> new file mode 100644
> index 0000000000000..3d73431b3dcd3
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
> @@ -0,0 +1,7 @@
> +/* { dg-do run } */
> +/* { dg-options "-fstrub=strict -O1" } */
> +
> +/* Check that a strub function called by another strub function does NOT defer
> +   the strubbing to its caller at -O1.  */
> +
> +#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 0000000000000..fddf3c745e7e6
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
> @@ -0,0 +1,8 @@
> +/* { dg-do run } */
> +/* { dg-options "-fstrub=strict -O2" } */
> +
> +/* Check that a strub function called by another strub function does NOT defer
> +   the strubbing to its caller at -O2.  */
> +
> +#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 0000000000000..7ebc65b58dd72
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
> @@ -0,0 +1,110 @@
> +/* { dg-do run } */
> +/* { dg-options "-fstrub=strict -O3" } */
> +
> +/* Check that a strub function called by another strub function defers the
> +   strubbing to its caller at -O3.  */
> +
> +#ifndef EXPECT_DEFERRAL
> +/* Other strub-defer*.c tests override this macro.  */
> +# define EXPECT_DEFERRAL
> +#endif
> +
> +const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
> +
> +/* Pad before and after the string on the stack, so that it's not overwritten by
> +   regular stack use.  */
> +#define PAD 7
> +
> +static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
> +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__ ("callable"))) callable_t (char *);
> +  callable_t *f = 0;
> +
> +  char s[2 * PAD + 1][sizeof (test_string)];
> +  __builtin_strcpy (s[PAD], test_string);
> +  asm ("" : "+m" (s), "+r" (f));
> +
> +  if (__builtin_expect (!f, 1))
> +    return (char*)__builtin_stack_address ();
> +
> +  f (s[PAD]);
> +  return 0;
> +}
> +
> +static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
> +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__ ("at-calls"), __noinline__, __noclone__))
> +char *
> +at_calls ()
> +{
> +  return leak_string ();
> +}
> +
> +static __attribute__ ((__strub__ ("at-calls")))
> +char *
> +deferred_at_calls ()
> +{
> +  char *ret;
> +  int i = 1;
> +  /* Since these test check stack contents above the top of the stack, an
> +     unexpected asynchronous signal or interrupt might overwrite the bits we
> +     expect to find and cause spurious fails.  Tolerate one such overall
> +     spurious fail by retrying.  */
> +  while (EXPECT_DEFERRAL !look_for_string ((ret = at_calls ())))
> +    if (!i--) __builtin_abort ();
> +  return ret;
> +}
> +
> +static __attribute__ ((__strub__ ("internal")))
> +char *
> +deferred_internal ()
> +{
> +  int i = 1;
> +  char *ret;
> +  while (EXPECT_DEFERRAL !look_for_string ((ret = at_calls ())))
> +    if (!i--) __builtin_abort ();
> +  return ret;
> +}
> +
> +int main ()
> +{
> +  int i = 1;
> +  /* These calls should not be subject to spurious fails: whether or not some
> +     asynchronous event overwrites the scrubbed stack space, the string won't
> +     remain there.  Unless the asynchronous event happens to write the string
> +     where we look for it, but what are the odds?  Anyway, it doesn't hurt to
> +     retry, even if just for symmetry.  */
> +  while (look_for_string (deferred_at_calls ()))
> +    if (!i--) __builtin_abort ();
> +  while (look_for_string (deferred_internal ()))
> +    if (!i--) __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 0000000000000..fbaf85fe0fafe
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
> @@ -0,0 +1,7 @@
> +/* { dg-do run } */
> +/* { dg-options "-fstrub=strict -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 0000000000000..e9d7b7b9ee0a8
> --- /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 \[(\]callable\[)\]" 1 "strubm" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
> +
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
> diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
> new file mode 100644
> index 0000000000000..8b8e15a51c71c
> --- /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 \[(\]internal\[)\]" 2 "strubm" } } */
> +
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
> diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
> new file mode 100644
> index 0000000000000..0a4a7539d3489
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-parms1.c
> @@ -0,0 +1,48 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +
> +#include <stdarg.h>
> +
> +void __attribute__ ((__strub__ ("internal")))
> +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__ ("internal")))
> +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__ ("internal")))
> +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__ ("internal")))
> +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 0000000000000..147171d96d5a1
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-parms2.c
> @@ -0,0 +1,36 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +
> +#include <stdarg.h>
> +
> +void __attribute__ ((__strub__ ("at-calls")))
> +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__ ("at-calls")))
> +large_byref_arg (struct large_arg la)
> +{
> +}
> +
> +/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
> +
> +void __attribute__ ((__strub__ ("at-calls")))
> +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 0000000000000..4e92682895a43
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-parms3.c
> @@ -0,0 +1,58 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +
> +/* Check that uses of a strub variable implicitly enables internal strub for
> +   publicly-visible functions, and causes the same transformations to their
> +   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-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
> new file mode 100644
> index 0000000000000..e2f9d8aebca58
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
> @@ -0,0 +1,18 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
> +
> +/* The difference between relaxed and strict in this case is that we accept the
> +   call from one internal-strub function to another.  Without the error,
> +   inlining takes place.  */
> +
> +#include "strub-strict1.c"
> +
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
> +
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */
> diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
> new file mode 100644
> index 0000000000000..98474435d2e59
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
> @@ -0,0 +1,14 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
> +
> +/* The difference between relaxed and strict in this case is that we accept the
> +   call from one internal-strub function to another.  */
> +
> +#include "strub-strict2.c"
> +
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */
> +
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */
> diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
> new file mode 100644
> index 0000000000000..1de15342595e4
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
> @@ -0,0 +1,10 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O0 -fstrub=strict -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 0000000000000..f9209c819004b
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
> @@ -0,0 +1,10 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O0 -fstrub=strict -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 0000000000000..bed1dcfb54a45
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
> @@ -0,0 +1,10 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O1 -fstrub=strict -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 0000000000000..6bf0071f52b93
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
> @@ -0,0 +1,10 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fstrub=strict -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 0000000000000..4732f515bf70c
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
> @@ -0,0 +1,12 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
> +
> +/* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
> +   enter and leave calls within strub contexts, passing on the enclosing
> +   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 0000000000000..8d6424c479a3a
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
> @@ -0,0 +1,12 @@
> +/* { dg-do compile } */
> +/* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
> +
> +/* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
> +   enter and leave calls within strub contexts, passing on the enclosing
> +   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-strict1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
> new file mode 100644
> index 0000000000000..368522442066e
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-strict1.c
> @@ -0,0 +1,36 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -fdump-ipa-strubm" } */
> +
> +static int __attribute__ ((__strub__)) var;
> +
> +/* h becomes STRUB_INLINABLE, because of the use of the strub variable,
> +   and the always_inline flag.  It would get inlined before pass_ipa_strub, if
> +   it weren't for the error.  */
> +static inline void
> +__attribute__ ((__always_inline__))
> +h() {
> +  var++;
> +}
> +
> +/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and
> +   the viability of at-calls strubbing.  Though internally a strub context, its
> +   interface is not strub-enabled, so it's not callable from within strub
> +   contexts.  */
> +static inline void
> +g() {
> +  var--;
> +  h();
> +}
> +
> +/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets
> +   split into STRUB_WRAPPER and STRUB_WRAPPED.  */
> +void
> +f() {
> +  var++;
> +  g();  /* { dg-error "calling non-.strub." } */
> +}
> +
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */
> +/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */
> diff --git a/gcc/testsuite/c-c++-common/strub-strict2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
> new file mode 100644
> index 0000000000000..b4f2888321821
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-strict2.c
> @@ -0,0 +1,25 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -fdump-ipa-strubm" } */
> +
> +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 \[(\]internal\[)\]" 2 "strubm" } } */
> 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 0000000000000..e48e0610e079b
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
> @@ -0,0 +1,8 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O1 -fstrub=strict -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 0000000000000..87cda7ab21b16
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
> @@ -0,0 +1,14 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
> +
> +/* Check that the expected strub calls are issued.
> +   Tail calls are short-circuited at -O2+.  */
> +
> +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/strub-var1.c b/gcc/testsuite/c-c++-common/strub-var1.c
> new file mode 100644
> index 0000000000000..eb6250fd39c90
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-var1.c
> @@ -0,0 +1,24 @@
> +/* { dg-do compile } */
> +
> +int __attribute__ ((strub)) x;
> +float __attribute__ ((strub)) f;
> +double __attribute__ ((strub)) d;
> +
> +/* The attribute applies to the type of the declaration, i.e., to the pointer
> +   variable p, not to the pointed-to integer.  */
> +int __attribute__ ((strub)) *
> +p = &x; /* { dg-message "incompatible|invalid conversion" } */
> +
> +typedef int __attribute__ ((strub)) strub_int;
> +strub_int *q = &x; /* Now this is compatible.  */
> +
> +int __attribute__ ((strub))
> +a[2]; /* { dg-warning "does not apply to elements" } */
> +
> +int __attribute__ ((vector_size (4 * sizeof (int))))
> +    __attribute__ ((strub))
> +v; /* { dg-warning "does not apply to elements" } */
> +
> +struct s {
> +  int i, j;
> +} __attribute__ ((strub)) w; /* { dg-warning "does not apply to fields" } */
> 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 0000000000000..b5e45ab0525ad
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
> @@ -0,0 +1,9 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict" } */
> +
> +/* Check that strub and non-strub functions can be called from non-strub
> +   contexts, and that strub and callable functions can be called from strub
> +   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 0000000000000..96aa7fe4b07f7
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
> @@ -0,0 +1,264 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict" } */
> +
> +/* Check that impermissible (cross-strub-context) calls are reported.  */
> +
> +extern int __attribute__ ((__strub__ ("callable"))) xcallable (void);
> +extern int __attribute__ ((__strub__ ("internal"))) xinternal (void);
> +extern int __attribute__ ((__strub__ ("at-calls"))) xat_calls (void);
> +extern int __attribute__ ((__strub__ ("disabled"))) xdisabled (void);
> +
> +int __attribute__ ((__strub__ ("callable"))) callable (void);
> +int __attribute__ ((__strub__ ("internal"))) internal (void);
> +int __attribute__ ((__strub__ ("at-calls"))) at_calls (void);
> +int __attribute__ ((__strub__ ("disabled"))) disabled (void);
> +
> +int __attribute__ ((__strub__)) var;
> +int var_user (void);
> +
> +static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
> +icallable (void);
> +static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
> +iinternal (void);
> +static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
> +iat_calls (void);
> +static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
> +idisabled (void);
> +static inline int __attribute__ ((__always_inline__))
> +ivar_user (void);
> +
> +static inline int __attribute__ ((__always_inline__, __strub__ ("callable")))
> +i_callable (void) { return 0; }
> +static inline int __attribute__ ((__always_inline__, __strub__ ("internal")))
> +i_internal (void) { return var; }
> +static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls")))
> +i_at_calls (void) { return var; }
> +static inline int __attribute__ ((__always_inline__, __strub__ ("disabled")))
> +i_disabled (void) { return 0; }
> +static inline int __attribute__ ((__always_inline__))
> +i_var_user (void) { return var; }
> +
> +#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__ ("callable")))
> +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__ ("internal")))
> +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__ ("at-calls")))
> +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__ ("disabled")))
> +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 __attribute__ ((__always_inline__, __strub__ ("at-calls")))
> +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 0000000000000..2857195706ed6
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
> @@ -0,0 +1,18 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +
> +/* Check that, along with a strub const function call, we issue an asm statement
> +   to make sure the watermark passed to it is held in memory before the call,
> +   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 0000000000000..98a92bc9eac2b
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
> @@ -0,0 +1,22 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +
> +/* Check that, along with a strub implicitly-const function call, we issue an
> +   asm statement to make sure the watermark passed to it is held in memory
> +   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 0000000000000..5511a6e1e71d3
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
> @@ -0,0 +1,13 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +
> +/* Check that, along with a strub const wrapping call, we issue an asm statement
> +   to make sure the watermark passed to it is held in memory before the call,
> +   and another to make sure it is not assumed to be unchanged.  */
> +
> +int __attribute__ ((__strub__ ("internal"), __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 0000000000000..47ee927964dff
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
> @@ -0,0 +1,17 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +
> +/* Check that, along with a strub implicitly-const wrapping call, we issue an
> +   asm statement to make sure the watermark passed to it is held in memory
> +   before the call, and another to make sure it is not assumed to be
> +   unchanged.  */
> +
> +int __attribute__ ((__strub__ ("internal")))
> +#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 0000000000000..7c27a2a1a6dca
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
> @@ -0,0 +1,13 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -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 0000000000000..e66d903780afd
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
> @@ -0,0 +1,14 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -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 0000000000000..5e08e0e58c658
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
> @@ -0,0 +1,14 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +
> +/* The pointer itself is a strub variable, that would enable internal strubbing
> +   if its value was used.  Here, it's only overwritten, so no strub.  */
> +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 0000000000000..a818e7a38bb5f
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
> @@ -0,0 +1,14 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +
> +/* The pointer itself is a strub variable, that would enable internal strubbing
> +   if its value was used.  Here, it's only overwritten, so no strub.  */
> +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 0000000000000..ddb0b5c0543b0
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
> @@ -0,0 +1,15 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict" } */
> +
> +/* It would be desirable to issue at least warnings for these.  */
> +
> +typedef int __attribute__ ((__strub__)) strub_int;
> +strub_int *ptr;
> +
> +int *f () {
> +  return ptr; /* { dg-message "incompatible|invalid conversion" } */
> +}
> +
> +strub_int *g () {
> +  return f (); /* { dg-message "incompatible|invalid conversion" } */
> +}
> 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 0000000000000..c165f312f16de
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
> @@ -0,0 +1,14 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -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 0000000000000..69fcff8d3763d
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
> @@ -0,0 +1,14 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -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 0000000000000..ff006224909bd
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
> @@ -0,0 +1,14 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -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-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
> new file mode 100644
> index 0000000000000..614b02228ba29
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
> @@ -0,0 +1,16 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=relaxed" } */
> +
> +inline void __attribute__ ((strub ("internal"), always_inline))
> +inl_int_ali (void)
> +{
> +  /* No internal wrapper, so this body ALWAYS gets inlined,
> +     but it cannot be called from non-strub contexts.  */
> +}
> +
> +void
> +bat (void)
> +{
> +  /* Not allowed, not a strub context.  */
> +  inl_int_ali (); /* { dg-error "context" } */
> +}
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
> new file mode 100644
> index 0000000000000..f9a6b4a16faf8
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
> @@ -0,0 +1,7 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=all" } */
> +
> +#include "strub-inlinable1.c"
> +
> +/* With -fstrub=all, the caller becomes a strub context, so the strub-inlinable
> +   callee is not rejected.  */
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
> new file mode 100644
> index 0000000000000..b4a7f3992bbaa
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
> @@ -0,0 +1,10 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict" } */
> +
> +typedef void ft (void);
> +typedef void ft2 (int, int);
> +extern ft __attribute__ ((__strub__)) fnac;
> +
> +ft * f (void) {
> +  return fnac; /* { dg-message "incompatible|invalid conversion" } */
> +}
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
> new file mode 100644
> index 0000000000000..d9d2c0caec42d
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
> @@ -0,0 +1,55 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=relaxed -Wpedantic" } */
> +
> +/* C++ does not warn about the partial incompatibilities.
> +
> +   The d_p () calls are actually rejected, even in C++, but they are XFAILed
> +   here because we don't get far enough in the compilation as to observe them,
> +   because the incompatibilities are errors without -fpermissive.
> +   strub-ptrfn3.c uses -fpermissive to check those.
> + */
> +
> +extern int __attribute__ ((strub ("callable"))) bac (void);
> +extern int __attribute__ ((strub ("disabled"))) bad (void);
> +extern int __attribute__ ((strub ("internal"))) bar (void);
> +extern int __attribute__ ((strub ("at-calls"))) bal (void);
> +
> +void __attribute__ ((strub))
> +bap (void)
> +{
> +  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
> +  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
> +  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
> +
> +  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
> +  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
> +  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
> +  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
> +  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
> +
> +  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail c++ } } */
> +  c_p ();
> +  a_p ();
> +}
> +
> +void __attribute__ ((strub))
> +baP (void)
> +{
> +  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
> +  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
> +  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
> +
> +  d_fn_t *d_p = bad;
> +  c_fn_t *c_p = bac;
> +  a_fn_t *a_p = bal;
> +
> +  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
> +  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
> +  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
> +  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
> +  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
> +
> +  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail c++ } } */
> +  c_p ();
> +  a_p ();
> +}
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
> new file mode 100644
> index 0000000000000..e1f179e160e5c
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
> @@ -0,0 +1,50 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=relaxed -Wpedantic -fpermissive" } */
> +/* { dg-prune-output "command-line option .-fpermissive." } */
> +
> +/* See strub-ptrfn2.c.  */
> +
> +extern int __attribute__ ((strub ("callable"))) bac (void);
> +extern int __attribute__ ((strub ("disabled"))) bad (void);
> +extern int __attribute__ ((strub ("internal"))) bar (void);
> +extern int __attribute__ ((strub ("at-calls"))) bal (void);
> +
> +void __attribute__ ((strub))
> +bap (void)
> +{
> +  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
> +  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
> +  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
> +
> +  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
> +  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
> +  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
> +  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
> +  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
> +
> +  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
> +  c_p ();
> +  a_p ();
> +}
> +
> +void __attribute__ ((strub))
> +baP (void)
> +{
> +  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
> +  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
> +  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
> +
> +  d_fn_t *d_p = bad;
> +  c_fn_t *c_p = bac;
> +  a_fn_t *a_p = bal;
> +
> +  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
> +  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
> +  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
> +  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
> +  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
> +
> +  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */
> +  c_p ();
> +  a_p ();
> +}
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
> new file mode 100644
> index 0000000000000..70b558afad040
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
> @@ -0,0 +1,43 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=relaxed" } */
> +
> +/* This is strub-ptrfn2.c without -Wpedantic.
> +
> +   Even C doesn't report the (not-quite-)compatible conversions without it.  */
> +
> +extern int __attribute__ ((strub ("callable"))) bac (void);
> +extern int __attribute__ ((strub ("disabled"))) bad (void);
> +extern int __attribute__ ((strub ("internal"))) bar (void);
> +extern int __attribute__ ((strub ("at-calls"))) bal (void);
> +
> +void __attribute__ ((strub))
> +bap (void)
> +{
> +  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
> +  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
> +  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
> +
> +  d_p = bac;
> +  c_p = bad;
> +  c_p = bar;
> +  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
> +  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
> +}
> +
> +void __attribute__ ((strub))
> +baP (void)
> +{
> +  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
> +  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
> +  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
> +
> +  d_fn_t *d_p = bad;
> +  c_fn_t *c_p = bac;
> +  a_fn_t *a_p = bal;
> +
> +  d_p = bac;
> +  c_p = bad;
> +  c_p = bar;
> +  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
> +  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
> +}
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
> new file mode 100644
> index 0000000000000..a262a086837b2
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
> @@ -0,0 +1,18 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +
> +/* Check that, along with a strub pure function call, we issue an asm statement
> +   to make sure the watermark passed to it is not assumed to be unchanged.  */
> +
> +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 0000000000000..4c4bd50c209a0
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
> @@ -0,0 +1,22 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +
> +/* Check that, along with a strub implicitly-pure function call, we issue an asm
> +   statement to make sure the watermark passed to it is not assumed to be
> +   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 0000000000000..ce195c6b1f1b6
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
> @@ -0,0 +1,13 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +
> +/* Check that, along with a strub pure wrapping call, we issue an asm statement
> +   to make sure the watermark passed to it is not assumed to be unchanged.  */
> +
> +int __attribute__ ((__strub__ ("internal"), __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 0000000000000..75cd54ccb5b5d
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
> @@ -0,0 +1,17 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +
> +/* Check that, along with a strub implicitly-pure wrapping call, we issue an asm
> +   statement to make sure the watermark passed to it is not assumed to be
> +   unchanged.  */
> +
> +int __attribute__ ((__strub__ ("internal")))
> +#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 0000000000000..7458b3fb54da5
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
> @@ -0,0 +1,95 @@
> +/* { dg-do run } */
> +/* { dg-options "-fstrub=strict" } */
> +
> +/* Check that a non-strub function leaves a string behind in the stack, and that
> +   equivalent strub functions don't.  Avoid the use of red zones by avoiding
> +   leaf functions.  */
> +
> +const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
> +
> +/* Pad before and after the string on the stack, so that it's not overwritten by
> +   regular stack use.  */
> +#define PAD 7
> +
> +static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
> +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__ ("callable"))) callable_t (char *);
> +  callable_t *f = 0;
> +
> +  char s[2 * PAD + 1][sizeof (test_string)];
> +  __builtin_strcpy (s[PAD], test_string);
> +  asm ("" : "+m" (s), "+r" (f));
> +
> +  if (__builtin_expect (!f, 1))
> +    return (char *) __builtin_stack_address ();
> +
> +  f (s[PAD]);
> +  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__ ("at-calls")))
> +char *
> +at_calls ()
> +{
> +  return leak_string ();
> +}
> +
> +static __attribute__ ((__strub__ ("internal")))
> +char *
> +internal ()
> +{
> +  return leak_string ();
> +}
> +
> +int main ()
> +{
> +  /* Since these test check stack contents above the top of the stack, an
> +     unexpected asynchronous signal or interrupt might overwrite the bits we
> +     expect to find and cause spurious fails.  Tolerate one such overall
> +     spurious fail by retrying.  */
> +  int i = 1;
> +  while (!look_for_string (callable ()))
> +    if (!i--) __builtin_abort ();
> +  while (look_for_string (at_calls ()))
> +    if (!i--) __builtin_abort ();
> +  while (look_for_string (internal ()))
> +    if (!i--) __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 0000000000000..5d60a7775f4bb
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
> @@ -0,0 +1,84 @@
> +/* { dg-do run } */
> +/* { dg-options "-fstrub=strict" } */
> +
> +/* Check that a non-strub function leaves a string behind in the stack, and that
> +   equivalent strub functions don't.  Allow red zones to be used.  */
> +
> +const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
> +
> +/* Pad before and after the string on the stack, so that it's not overwritten by
> +   regular stack use.  */
> +#define PAD 7
> +
> +static inline __attribute__ ((__always_inline__, __strub__ ("callable")))
> +char *
> +leak_string (void)
> +{
> +  int len = sizeof (test_string);
> +  asm ("" : "+rm" (len));
> +  char s[2 * PAD + 1][len];
> +  __builtin_strcpy (s[PAD], 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__ ("at-calls")))
> +char *
> +at_calls ()
> +{
> +  return leak_string ();
> +}
> +
> +static __attribute__ ((__strub__ ("internal")))
> +char *
> +internal ()
> +{
> +  return leak_string ();
> +}
> +
> +int main ()
> +{
> +  /* Since these test check stack contents above the top of the stack, an
> +     unexpected asynchronous signal or interrupt might overwrite the bits we
> +     expect to find and cause spurious fails.  Tolerate one such overall
> +     spurious fail by retrying.  */
> +  int i = 1;
> +  while (!look_for_string (callable ()))
> +    if (!i--) __builtin_abort ();
> +  while (look_for_string (at_calls ()))
> +    if (!i--) __builtin_abort ();
> +  while (look_for_string (internal ()))
> +    if (!i--) __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 0000000000000..c2ad710858e87
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
> @@ -0,0 +1,80 @@
> +/* { dg-do run } */
> +/* { dg-options "-fstrub=strict" } */
> +/* { 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__ ("callable")))
> +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__ ("at-calls")))
> +char *
> +at_calls ()
> +{
> +  return leak_string ();
> +}
> +
> +static __attribute__ ((__strub__ ("internal")))
> +char *
> +internal ()
> +{
> +  return leak_string ();
> +}
> +
> +int main ()
> +{
> +  /* Since these test check stack contents above the top of the stack, an
> +     unexpected asynchronous signal or interrupt might overwrite the bits we
> +     expect to find and cause spurious fails.  Tolerate one such overall
> +     spurious fail by retrying.  */
> +  int i = 1;
> +  while (!look_for_string (callable ()))
> +    if (!i--) __builtin_abort ();
> +  while (look_for_string (at_calls ()))
> +    if (!i--) __builtin_abort ();
> +  while (look_for_string (internal ()))
> +    if (!i--) __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 0000000000000..3b36b8e5d68ef
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
> @@ -0,0 +1,106 @@
> +/* { 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__ ("internal")))
> +char *
> +internal ()
> +{
> +  return intermediate ();
> +}
> +
> +int __attribute__ ((__strub__ ("disabled")))
> +main ()
> +{
> +  /* Since these test check stack contents above the top of the stack, an
> +     unexpected asynchronous signal or interrupt might overwrite the bits we
> +     expect to find and cause spurious fails.  Tolerate one such overall
> +     spurious fail by retrying.  */
> +  int i = 1;
> +  while (look_for_string (internal ()))
> +    if (!i--) __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 0000000000000..57f9baf758ded
> --- /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 0000000000000..08de3f1c3b17c
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
> @@ -0,0 +1,7 @@
> +/* { dg-do run } */
> +/* { dg-options "-fstrub=strict" } */
> +/* { dg-require-effective-target alloca } */
> +
> +#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
> +
> +#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 0000000000000..459f6886c5499
> --- /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 0000000000000..0d367fb83d09d
> --- /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 0000000000000..c226ab10ff651
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
> @@ -0,0 +1,13 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -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 0000000000000..a7911f1fa7212
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
> @@ -0,0 +1,14 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -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 0000000000000..6ebebcd01e8ea
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
> @@ -0,0 +1,13 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fstrub=strict -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_access.adb b/gcc/testsuite/gnat.dg/strub_access.adb
> new file mode 100644
> index 0000000000000..29e6996ecf61c
> --- /dev/null
> +++ b/gcc/testsuite/gnat.dg/strub_access.adb
> @@ -0,0 +1,21 @@
> +--  { dg-do compile }
> +--  { dg-options "-fstrub=relaxed -fdump-ipa-strubm" }
> +
> +--  The main subprogram doesn't read from the automatic variable, but
> +--  being an automatic variable, its presence should be enough for the
> +--  procedure to get strub enabled.
> +
> +procedure Strub_Access is
> +   type Strub_Int is new Integer;
> +   pragma Machine_Attribute (Strub_Int, "strub");
> +
> +   X : aliased Strub_Int := 0;
> +
> +   function F (P : access Strub_Int) return Strub_Int is (P.all);
> +
> +begin
> +   X := F (X'Access);
> +end Strub_Access;
> +
> +--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 1 "strubm" } }
> +--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls-opt\[)\]\[)\]" 1 "strubm" } }
> diff --git a/gcc/testsuite/gnat.dg/strub_access1.adb b/gcc/testsuite/gnat.dg/strub_access1.adb
> new file mode 100644
> index 0000000000000..dae4706016436
> --- /dev/null
> +++ b/gcc/testsuite/gnat.dg/strub_access1.adb
> @@ -0,0 +1,16 @@
> +--  { dg-do compile }
> +--  { dg-options "-fstrub=relaxed" }
> +
> +--  Check that we reject 'Access of a strub variable whose type does
> +--  not carry a strub modifier.
> +
> +procedure Strub_Access1 is
> +   X : aliased Integer := 0;
> +   pragma Machine_Attribute (X, "strub");
> +
> +   function F (P : access Integer) return Integer is (P.all);
> +
> +begin
> +   X := F (X'Unchecked_access); -- OK.
> +   X := F (X'Access); -- { dg-error "target access type drops .strub. mode" }
> +end Strub_Access1;
> diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
> new file mode 100644
> index 0000000000000..10445d7cf8451
> --- /dev/null
> +++ b/gcc/testsuite/gnat.dg/strub_attr.adb
> @@ -0,0 +1,37 @@
> +--  { dg-do compile }
> +--  { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" }
> +
> +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 (F (X));
> +   --  function G return Integer is (FP (X));
> +   --  Calling G would likely raise an exception, because although FP
> +   --  carries the strub at-calls attribute needed to call F, the
> +   --  attribute is dropped from the type used for the call proper.
> +end Strub_Attr;
> +
> +--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 2 "strubm" } }
> +--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 0 "strubm" } }
> +--  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
> +
> +--  { dg-final { scan-ipa-dump-times "strub.watermark_ptr" 6 "strub" } }
> +--  We have 1 at-calls subprogram (F) and 2 wrapped (P and G).
> +--  For each of them, there's one match for the wrapped signature,
> +--  and one for the update call.
> +
> +--  { dg-final { scan-ipa-dump-times "strub.watermark" 27 "strub" } }
> +--  The 6 matches above, plus:
> +--  5*2: wm var decl, enter, call, leave and clobber for each wrapper;
> +--  2*1: an extra leave and clobber for the exception paths in the wrappers.
> +--  7*1: for the F call in G, including EH path.
> diff --git a/gcc/testsuite/gnat.dg/strub_attr.ads b/gcc/testsuite/gnat.dg/strub_attr.ads
> new file mode 100644
> index 0000000000000..a94c23bf41833
> --- /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/testsuite/gnat.dg/strub_disp.adb b/gcc/testsuite/gnat.dg/strub_disp.adb
> new file mode 100644
> index 0000000000000..3dbcc4a357cba
> --- /dev/null
> +++ b/gcc/testsuite/gnat.dg/strub_disp.adb
> @@ -0,0 +1,64 @@
> +--  { dg-do compile }
> +
> +procedure Strub_Disp is
> +   package Foo is
> +      type A is tagged null record;
> +
> +      procedure P (I : Integer; X : A);
> +      pragma Machine_Attribute (P, "strub", "at-calls");
> +
> +      function F (X : access A) return Integer;
> +
> +      type B is new A with null record;
> +
> +      overriding
> +      procedure P (I : Integer; X : B); -- { dg-error "requires the same .strub. mode" }
> +
> +      overriding
> +      function F (X : access B) return Integer;
> +      pragma Machine_Attribute (F, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
> +
> +   end Foo;
> +
> +   package body Foo is
> +      procedure P (I : Integer; X : A) is
> +      begin
> +        null;
> +      end;
> +
> +      function F (X : access A) return Integer is (0);
> +
> +      overriding
> +      procedure P (I : Integer; X : B) is
> +      begin
> +        P (I, A (X));
> +      end;
> +
> +      overriding
> +      function F (X : access B) return Integer is (1);
> +   end Foo;
> +
> +   use Foo;
> +
> +   procedure Q (X : A'Class) is
> +   begin
> +      P (-1, X);
> +   end;
> +
> +   XA : aliased A;
> +   XB : aliased B;
> +   I : Integer := 0;
> +   XC : access A'Class;
> +begin
> +   Q (XA);
> +   Q (XB);
> +
> +   I := I + F (XA'Access);
> +   I := I + F (XB'Access);
> +
> +   XC := XA'Access;
> +   I := I + F (XC);
> +
> +   XC := XB'Access;
> +   I := I + F (XC);
> +end Strub_Disp;
> diff --git a/gcc/testsuite/gnat.dg/strub_disp1.adb b/gcc/testsuite/gnat.dg/strub_disp1.adb
> new file mode 100644
> index 0000000000000..09756a74b7d81
> --- /dev/null
> +++ b/gcc/testsuite/gnat.dg/strub_disp1.adb
> @@ -0,0 +1,79 @@
> +--  { dg-do compile }
> +--  { dg-options "-fdump-ipa-strub" }
> +
> +-- Check that at-calls dispatching calls are transformed.
> +
> +procedure Strub_Disp1 is
> +   package Foo is
> +      type A is tagged null record;
> +
> +      procedure P (I : Integer; X : A);
> +      pragma Machine_Attribute (P, "strub", "at-calls");
> +
> +      function F (X : access A) return Integer;
> +      pragma Machine_Attribute (F, "strub", "at-calls");
> +
> +      type B is new A with null record;
> +
> +      overriding
> +      procedure P (I : Integer; X : B);
> +      pragma Machine_Attribute (P, "strub", "at-calls");
> +
> +      overriding
> +      function F (X : access B) return Integer;
> +      pragma Machine_Attribute (F, "strub", "at-calls");
> +
> +   end Foo;
> +
> +   package body Foo is
> +      procedure P (I : Integer; X : A) is
> +      begin
> +        null;
> +      end;
> +
> +      function F (X : access A) return Integer is (0);
> +
> +      overriding
> +      procedure P (I : Integer; X : B) is
> +      begin
> +        P (I, A (X)); -- strub-at-calls non-dispatching call
> +      end;
> +
> +      overriding
> +      function F (X : access B) return Integer is (1);
> +   end Foo;
> +
> +   use Foo;
> +
> +   procedure Q (X : A'Class) is
> +   begin
> +      P (-1, X); -- strub-at-calls dispatching call.
> +   end;
> +
> +   XA : aliased A;
> +   XB : aliased B;
> +   I : Integer := 0;
> +   XC : access A'Class;
> +begin
> +   Q (XA);
> +   Q (XB);
> +
> +   I := I + F (XA'Access); -- strub-at-calls non-dispatching call
> +   I := I + F (XB'Access); -- strub-at-calls non-dispatching call
> +
> +   XC := XA'Access;
> +   I := I + F (XC); -- strub-at-calls dispatching call.
> +
> +   XC := XB'Access;
> +   I := I + F (XC); -- strub-at-calls dispatching call.
> +end Strub_Disp1;
> +
> +--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 4 "strub" } }
> +
> +--  Count the strub-at-calls non-dispatching calls
> +--  (+ 2 each, for the matching prototypes)
> +--  { dg-final { scan-ipa-dump-times "foo\.p \[(\]\[^\n\]*watermark" 3 "strub" } }
> +--  { dg-final { scan-ipa-dump-times "foo\.f \[(\]\[^\n\]*watermark" 4 "strub" } }
> +
> +--  Count the strub-at-calls dispatching calls.
> +--  { dg-final { scan-ipa-dump-times "_\[0-9\]* \[(\]\[^\n\]*watermark" 3 "strub" } }
> diff --git a/gcc/testsuite/gnat.dg/strub_ind.adb b/gcc/testsuite/gnat.dg/strub_ind.adb
> new file mode 100644
> index 0000000000000..da56acaa957d2
> --- /dev/null
> +++ b/gcc/testsuite/gnat.dg/strub_ind.adb
> @@ -0,0 +1,33 @@
> +--  { dg-do compile }
> +--  { dg-options "-fstrub=strict" }
> +
> +--  This is essentially the same test as strub_attr.adb,
> +--  but applying attributes to access types as well.
> +--  That doesn't quite work yet, so we get an error we shouldn't get.
> +
> +package body Strub_Ind is
> +   E : exception;
> +
> +   function G return Integer;
> +
> +   procedure P (X : Integer) is
> +   begin
> +      raise E;
> +   end;
> +
> +   function F (X : Integer) return Integer is
> +   begin
> +      return X * X;
> +   end;
> +
> +   function G return Integer is (FP (X));
> +
> +   type GT is access function return Integer;
> +
> +   type GT_SAC is access function return Integer;
> +   pragma Machine_Attribute (GT_SAC, "strub", "at-calls");
> +
> +   GP : GT_SAC := GT_SAC (GT'(G'Access)); -- { dg-error "incompatible" }
> +   -- pragma Machine_Attribute (GP, "strub", "at-calls");
> +
> +end Strub_Ind;
> diff --git a/gcc/testsuite/gnat.dg/strub_ind.ads b/gcc/testsuite/gnat.dg/strub_ind.ads
> new file mode 100644
> index 0000000000000..99a65fc24b1ec
> --- /dev/null
> +++ b/gcc/testsuite/gnat.dg/strub_ind.ads
> @@ -0,0 +1,17 @@
> +package Strub_Ind is
> +   procedure P (X : Integer);
> +   pragma Machine_Attribute (P, "strub", "internal");
> +
> +   function F (X : Integer) return Integer;
> +   pragma Machine_Attribute (F, "strub");
> +
> +   X : Integer := 0;
> +   pragma Machine_Attribute (X, "strub");
> +
> +   type FT is access function (X : Integer) return Integer;
> +   pragma Machine_Attribute (FT, "strub", "at-calls");
> +
> +   FP : FT := F'Access;
> +   -- pragma Machine_Attribute (FP, "strub", "at-calls"); -- not needed
> +
> +end Strub_Ind;
> diff --git a/gcc/testsuite/gnat.dg/strub_ind1.adb b/gcc/testsuite/gnat.dg/strub_ind1.adb
> new file mode 100644
> index 0000000000000..825e395e6819c
> --- /dev/null
> +++ b/gcc/testsuite/gnat.dg/strub_ind1.adb
> @@ -0,0 +1,41 @@
> +--  { dg-do compile }
> +--  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
> +
> +--  This is essentially the same test as strub_attr.adb,
> +--  but with an explicit conversion.
> +
> +package body Strub_Ind1 is
> +   E : exception;
> +
> +   type Strub_Int is New Integer;
> +   pragma Machine_Attribute (Strub_Int, "strub");
> +
> +   function G return Integer;
> +   pragma Machine_Attribute (G, "strub", "disabled");
> +
> +   procedure P (X : Integer) is
> +   begin
> +      raise E;
> +   end;
> +
> +   function G return Integer is (FP (X));
> +
> +   type GT is access function return Integer;
> +   pragma Machine_Attribute (GT, "strub", "disabled");
> +
> +   type GT_SC is access function return Integer;
> +   pragma Machine_Attribute (GT_SC, "strub", "callable");
> +
> +   GP : GT_SC := GT_SC (GT'(G'Access));
> +   --  pragma Machine_Attribute (GP, "strub", "callable"); -- not needed.
> +
> +   function F (X : Integer) return Integer is
> +   begin
> +      return X * GP.all;
> +   end;
> +
> +end Strub_Ind1;
> +
> +--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]disabled\[)\]\[)\]" 1 "strubm" } }
> +--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 1 "strubm" } }
> +--  { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } }
> diff --git a/gcc/testsuite/gnat.dg/strub_ind1.ads b/gcc/testsuite/gnat.dg/strub_ind1.ads
> new file mode 100644
> index 0000000000000..d3f1273b3a6b9
> --- /dev/null
> +++ b/gcc/testsuite/gnat.dg/strub_ind1.ads
> @@ -0,0 +1,17 @@
> +package Strub_Ind1 is
> +   procedure P (X : Integer);
> +   pragma Machine_Attribute (P, "strub", "internal");
> +
> +   function F (X : Integer) return Integer;
> +   pragma Machine_Attribute (F, "strub");
> +
> +   X : aliased Integer := 0;
> +   pragma Machine_Attribute (X, "strub");
> +
> +   type FT is access function (X : Integer) return Integer;
> +   pragma Machine_Attribute (FT, "strub", "at-calls");
> +
> +   FP : FT := F'Access;
> +   pragma Machine_Attribute (FP, "strub", "at-calls");
> +
> +end Strub_Ind1;
> diff --git a/gcc/testsuite/gnat.dg/strub_ind2.adb b/gcc/testsuite/gnat.dg/strub_ind2.adb
> new file mode 100644
> index 0000000000000..e918b39263117
> --- /dev/null
> +++ b/gcc/testsuite/gnat.dg/strub_ind2.adb
> @@ -0,0 +1,34 @@
> +--  { dg-do compile }
> +--  { dg-options "-fstrub=strict" }
> +
> +--  This is essentially the same test as strub_attr.adb,
> +--  but with an explicit conversion.
> +
> +package body Strub_Ind2 is
> +   E : exception;
> +
> +   function G return Integer;
> +   pragma Machine_Attribute (G, "strub", "callable");
> +
> +   procedure P (X : Integer) is
> +   begin
> +      raise E;
> +   end;
> +
> +   function G return Integer is (FP (X));
> +
> +   type GT is access function return Integer;
> +   pragma Machine_Attribute (GT, "strub", "callable");
> +
> +   type GT_SD is access function return Integer;
> +   pragma Machine_Attribute (GT_SD, "strub", "disabled");
> +
> +   GP : GT_SD := GT_SD (GT'(G'Access));
> +   --  pragma Machine_Attribute (GP, "strub", "disabled"); -- not needed.
> +
> +   function F (X : Integer) return Integer is
> +   begin
> +      return X * GP.all; --  { dg-error "using non-.strub. type" }
> +   end;
> +
> +end Strub_Ind2;
> diff --git a/gcc/testsuite/gnat.dg/strub_ind2.ads b/gcc/testsuite/gnat.dg/strub_ind2.ads
> new file mode 100644
> index 0000000000000..e13865ec49c38
> --- /dev/null
> +++ b/gcc/testsuite/gnat.dg/strub_ind2.ads
> @@ -0,0 +1,17 @@
> +package Strub_Ind2 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");
> +
> +   type FT is access function (X : Integer) return Integer;
> +   pragma Machine_Attribute (FT, "strub", "at-calls");
> +
> +   FP : FT := F'Access;
> +   pragma Machine_Attribute (FP, "strub", "at-calls");
> +
> +end Strub_Ind2;
> diff --git a/gcc/testsuite/gnat.dg/strub_intf.adb b/gcc/testsuite/gnat.dg/strub_intf.adb
> new file mode 100644
> index 0000000000000..8f0212a75866f
> --- /dev/null
> +++ b/gcc/testsuite/gnat.dg/strub_intf.adb
> @@ -0,0 +1,93 @@
> +--  { dg-do compile }
> +
> +--  Check that strub mode mismatches between overrider and overridden
> +--  subprograms are reported.
> +
> +procedure Strub_Intf is
> +   package Foo is
> +      type TP is interface;
> +      procedure P (I : Integer; X : TP) is abstract;
> +      pragma Machine_Attribute (P, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
> +
> +      type TF is interface;
> +      function F (X : access TF) return Integer is abstract;
> +
> +      type TX is interface;
> +      procedure P (I : Integer; X : TX) is abstract;
> +
> +      type TI is interface and TP and TF and TX;
> +      --  When we freeze TI, we detect the mismatch between the
> +      --  inherited P and another parent's P.  Because TP appears
> +      --  before TX, we inherit P from TP, and report the mismatch at
> +      --  the pragma inherited from TP against TX's P.  In contrast,
> +      --  when we freeze TII below, since TX appears before TP, we
> +      --  report the error at the line in which the inherited
> +      --  subprogram is synthesized, namely the line below, against
> +      --  the line of the pragma.
> +
> +      type TII is interface and TX and TP and TF; -- { dg-error "requires the same .strub. mode" }
> +
> +      function F (X : access TI) return Integer is abstract;
> +      pragma Machine_Attribute (F, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
> +
> +      type A is new TI with null record;
> +
> +      procedure P (I : Integer; X : A);
> +      pragma Machine_Attribute (P, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
> +
> +      function F (X : access A) return Integer; -- { dg-error "requires the same .strub. mode" }
> +
> +      type B is new TI with null record;
> +
> +      overriding
> +      procedure P (I : Integer; X : B); -- { dg-error "requires the same .strub. mode" }
> +
> +      overriding
> +      function F (X : access B) return Integer;
> +      pragma Machine_Attribute (F, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
> +
> +   end Foo;
> +
> +   package body Foo is
> +      procedure P (I : Integer; X : A) is
> +      begin
> +        null;
> +      end;
> +
> +      function F (X : access A) return Integer is (0);
> +
> +      overriding
> +      procedure P (I : Integer; X : B) is
> +      begin
> +        null;
> +      end;
> +
> +      overriding
> +      function F (X : access B) return Integer is (1);
> +
> +   end Foo;
> +
> +   use Foo;
> +
> +   procedure Q (X : TX'Class) is
> +   begin
> +      P (-1, X);
> +   end;
> +
> +   XA : aliased A;
> +   XB : aliased B;
> +   I : Integer := 0;
> +   XC : access TI'Class;
> +begin
> +   Q (XA);
> +   Q (XB);
> +
> +   I := I + F (XA'Access);
> +   I := I + F (XB'Access);
> +
> +   XC := XA'Access;
> +   I := I + F (XC);
> +
> +   XC := XB'Access;
> +   I := I + F (XC);
> +end Strub_Intf;
> diff --git a/gcc/testsuite/gnat.dg/strub_intf1.adb b/gcc/testsuite/gnat.dg/strub_intf1.adb
> new file mode 100644
> index 0000000000000..bf77321cef790
> --- /dev/null
> +++ b/gcc/testsuite/gnat.dg/strub_intf1.adb
> @@ -0,0 +1,86 @@
> +--  { dg-do compile }
> +--  { dg-options "-fdump-ipa-strub" }
> +
> +-- Check that at-calls dispatching calls to interfaces are transformed.
> +
> +procedure Strub_Intf1 is
> +   package Foo is
> +      type TX is Interface;
> +      procedure P (I : Integer; X : TX) is abstract;
> +      pragma Machine_Attribute (P, "strub", "at-calls");
> +      function F (X : access TX) return Integer is abstract;
> +      pragma Machine_Attribute (F, "strub", "at-calls");
> +
> +      type A is new TX with null record;
> +
> +      procedure P (I : Integer; X : A);
> +      pragma Machine_Attribute (P, "strub", "at-calls");
> +
> +      function F (X : access A) return Integer;
> +      pragma Machine_Attribute (F, "strub", "at-calls");
> +
> +      type B is new TX with null record;
> +
> +      overriding
> +      procedure P (I : Integer; X : B);
> +      pragma Machine_Attribute (P, "strub", "at-calls");
> +
> +      overriding
> +      function F (X : access B) return Integer;
> +      pragma Machine_Attribute (F, "strub", "at-calls");
> +
> +   end Foo;
> +
> +   package body Foo is
> +      procedure P (I : Integer; X : A) is
> +      begin
> +        null;
> +      end;
> +
> +      function F (X : access A) return Integer is (0);
> +
> +      overriding
> +      procedure P (I : Integer; X : B) is
> +      begin
> +        null;
> +      end;
> +
> +      overriding
> +      function F (X : access B) return Integer is (1);
> +
> +   end Foo;
> +
> +   use Foo;
> +
> +   procedure Q (X : TX'Class) is
> +   begin
> +      P (-1, X);
> +   end;
> +
> +   XA : aliased A;
> +   XB : aliased B;
> +   I : Integer := 0;
> +   XC : access TX'Class;
> +begin
> +   Q (XA);
> +   Q (XB);
> +
> +   I := I + F (XA'Access);
> +   I := I + F (XB'Access);
> +
> +   XC := XA'Access;
> +   I := I + F (XC);
> +
> +   XC := XB'Access;
> +   I := I + F (XC);
> +end Strub_Intf1;
> +
> +--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 4 "strub" } }
> +
> +--  Count the strub-at-calls non-dispatching calls
> +--  (+ 2 each, for the matching prototypes)
> +--  { dg-final { scan-ipa-dump-times "foo\.p \[(\]\[^\n\]*watermark" 2 "strub" } }
> +--  { dg-final { scan-ipa-dump-times "foo\.f \[(\]\[^\n\]*watermark" 4 "strub" } }
> +
> +--  Count the strub-at-calls dispatching calls.
> +--  { dg-final { scan-ipa-dump-times "_\[0-9\]* \[(\]\[^\n\]*watermark" 3 "strub" } }
> diff --git a/gcc/testsuite/gnat.dg/strub_intf2.adb b/gcc/testsuite/gnat.dg/strub_intf2.adb
> new file mode 100644
> index 0000000000000..e8880dbc43730
> --- /dev/null
> +++ b/gcc/testsuite/gnat.dg/strub_intf2.adb
> @@ -0,0 +1,55 @@
> +--  { dg-do compile }
> +
> +--  Check that strub mode mismatches between overrider and overridden
> +--  subprograms are reported even when the overriders for an
> +--  interface's subprograms are inherited from a type that is not a
> +--  descendent of the interface.
> +
> +procedure Strub_Intf2 is
> +   package Foo is
> +      type A is tagged null record;
> +
> +      procedure P (I : Integer; X : A);
> +      pragma Machine_Attribute (P, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" }
> +
> +      function F (X : access A) return Integer;
> +
> +      type TX is Interface;
> +
> +      procedure P (I : Integer; X : TX) is abstract;
> +
> +      function F (X : access TX) return Integer is abstract;
> +      pragma Machine_Attribute (F, "strub", "at-calls");
> +
> +      type B is new A and TX with null record; -- { dg-error "requires the same .strub. mode" }
> +
> +   end Foo;
> +
> +   package body Foo is
> +      procedure P (I : Integer; X : A) is
> +      begin
> +        null;
> +      end;
> +
> +      function F (X : access A) return Integer is (0);
> +
> +   end Foo;
> +
> +   use Foo;
> +
> +   procedure Q (X : TX'Class) is
> +   begin
> +      P (-1, X);
> +   end;
> +
> +   XB : aliased B;
> +   I : Integer := 0;
> +   XC : access TX'Class;
> +begin
> +   Q (XB);
> +
> +   I := I + F (XB'Access);
> +
> +   XC := XB'Access;
> +   I := I + F (XC);
> +end Strub_Intf2;
> diff --git a/gcc/testsuite/gnat.dg/strub_renm.adb b/gcc/testsuite/gnat.dg/strub_renm.adb
> new file mode 100644
> index 0000000000000..217367e712d82
> --- /dev/null
> +++ b/gcc/testsuite/gnat.dg/strub_renm.adb
> @@ -0,0 +1,21 @@
> +--  { dg-do compile }
> +
> +procedure Strub_Renm is
> +   procedure P (X : Integer);
> +   pragma Machine_Attribute (P, "strub", "at-calls");
> +
> +   function F return Integer;
> +   pragma Machine_Attribute (F, "strub", "internal");
> +
> +   procedure Q (X : Integer) renames P; -- { dg-error "requires the same .strub. mode" }
> +
> +   function G return Integer renames F;
> +   pragma Machine_Attribute (G, "strub", "callable"); -- { dg-error "requires the same .strub. mode" }
> +
> +   procedure P (X : Integer) is null;
> +   function F return Integer is (0);
> +
> +begin
> +   P (F);
> +   Q (G);
> +end Strub_Renm;
> diff --git a/gcc/testsuite/gnat.dg/strub_renm1.adb b/gcc/testsuite/gnat.dg/strub_renm1.adb
> new file mode 100644
> index 0000000000000..a11adbfb5a9d6
> --- /dev/null
> +++ b/gcc/testsuite/gnat.dg/strub_renm1.adb
> @@ -0,0 +1,32 @@
> +--  { dg-do compile }
> +--  { dg-options "-fstrub=relaxed -fdump-ipa-strub" }
> +
> +procedure Strub_Renm1 is
> +   V : Integer := 0;
> +   pragma Machine_Attribute (V, "strub");
> +
> +   procedure P (X : Integer);
> +   pragma Machine_Attribute (P, "strub", "at-calls");
> +
> +   function F return Integer;
> +
> +   procedure Q (X : Integer) renames P;
> +   pragma Machine_Attribute (Q, "strub", "at-calls");
> +
> +   function G return Integer renames F;
> +   pragma Machine_Attribute (G, "strub", "internal");
> +
> +   procedure P (X : Integer) is null;
> +   function F return Integer is (0);
> +
> +begin
> +   P (F);
> +   Q (G);
> +end Strub_Renm1;
> +
> +--  This is for P; Q is an alias.
> +--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 1 "strub" } }
> +
> +--  This is *not* for G, but for Strub_Renm1.
> +--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]wrapped\[)\]\[)\]" 1 "strub" } }
> +--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]wrapper\[)\]\[)\]" 1 "strub" } }
> diff --git a/gcc/testsuite/gnat.dg/strub_renm2.adb b/gcc/testsuite/gnat.dg/strub_renm2.adb
> new file mode 100644
> index 0000000000000..c488c20826fdb
> --- /dev/null
> +++ b/gcc/testsuite/gnat.dg/strub_renm2.adb
> @@ -0,0 +1,32 @@
> +--  { dg-do compile }
> +--  { dg-options "-fstrub=strict -fdump-ipa-strub" }
> +
> +procedure Strub_Renm2 is
> +   V : Integer := 0;
> +   pragma Machine_Attribute (V, "strub");
> +
> +   procedure P (X : Integer);
> +   pragma Machine_Attribute (P, "strub", "at-calls");
> +
> +   function F return Integer;
> +
> +   procedure Q (X : Integer) renames P;
> +   pragma Machine_Attribute (Q, "strub", "at-calls");
> +
> +   type T is access function return Integer;
> +
> +   type TC is access function return Integer;
> +   pragma Machine_Attribute (TC, "strub", "callable");
> +
> +   FCptr : constant TC := TC (T'(F'Access));
> +
> +   function G return Integer renames FCptr.all;
> +   pragma Machine_Attribute (G, "strub", "callable");
> +
> +   procedure P (X : Integer) is null;
> +   function F return Integer is (0);
> +
> +begin
> +   P (F);  -- { dg-error "calling non-.strub." }
> +   Q (G);  -- ok, G is callable.
> +end Strub_Renm2;
> diff --git a/gcc/testsuite/gnat.dg/strub_var.adb b/gcc/testsuite/gnat.dg/strub_var.adb
> new file mode 100644
> index 0000000000000..3d158de28031f
> --- /dev/null
> +++ b/gcc/testsuite/gnat.dg/strub_var.adb
> @@ -0,0 +1,16 @@
> +--  { dg-do compile }
> +--  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
> +
> +-- We don't read from the automatic variable, but being an automatic
> +--  variable, its presence should be enough for the procedure to get
> +--  strub enabled.
> +
> +with Strub_Attr;
> +procedure Strub_Var is
> +   X : Integer := 0;
> +   pragma Machine_Attribute (X, "strub");
> +begin
> +   X := Strub_Attr.F (0);
> +end Strub_Var;
> +
> +--  { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 1 "strubm" } }
> diff --git a/gcc/testsuite/gnat.dg/strub_var1.adb b/gcc/testsuite/gnat.dg/strub_var1.adb
> new file mode 100644
> index 0000000000000..6a504e09198b6
> --- /dev/null
> +++ b/gcc/testsuite/gnat.dg/strub_var1.adb
> @@ -0,0 +1,20 @@
> +--  { dg-do compile }
> +
> +with Strub_Attr;
> +procedure Strub_Var1 is
> +   type TA  -- { dg-warning "does not apply to elements" }
> +      is array (1..2) of Integer;
> +   pragma Machine_Attribute (TA, "strub");
> +
> +   A : TA := (0, 0);  -- { dg-warning "does not apply to elements" }
> +
> +   type TR is record  -- { dg-warning "does not apply to fields" }
> +      M, N : Integer;
> +   end record;
> +   pragma Machine_Attribute (TR, "strub");
> +
> +   R : TR := (0, 0);
> +
> +begin
> +   A(2) := Strub_Attr.F (A(1));
> +end Strub_Var1;
> diff --git a/gcc/tree-cfg.cc b/gcc/tree-cfg.cc
> index a30a2de33a106..cefbb86be7bb4 100644
> --- a/gcc/tree-cfg.cc
> +++ b/gcc/tree-cfg.cc
> @@ -5790,6 +5790,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 09e6ada5b2f91..36f955a0b37ee 100644
> --- a/gcc/tree-pass.h
> +++ b/gcc/tree-pass.h
> @@ -510,8 +510,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.cc b/gcc/tree-ssa-ccp.cc
> index 1a555ae682638..03ff88afadddd 100644
> --- a/gcc/tree-ssa-ccp.cc
> +++ b/gcc/tree-ssa-ccp.cc
> @@ -3073,7 +3073,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 8dedd10f79a30..d8163c5af9903 100644
> --- a/libgcc/Makefile.in
> +++ b/libgcc/Makefile.in
> @@ -433,6 +433,9 @@ LIB2ADD += enable-execute-stack.c
>  # Control Flow Redundancy hardening out-of-line checker.
>  LIB2ADD += $(srcdir)/hardcfr.c
>
> +# Stack scrubbing infrastructure.
> +LIB2ADD += $(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
>  # (e.g. *-*-darwin*) only one copy of it is linked.
> diff --git a/libgcc/libgcc-std.ver.in b/libgcc/libgcc-std.ver.in
> index de00db647570c..a6b89632cae3e 100644
> --- a/libgcc/libgcc-std.ver.in
> +++ b/libgcc/libgcc-std.ver.in
> @@ -1957,4 +1957,7 @@ GCC_14.0.0 {
>    __PFX__floatbitintsf
>    __PFX__floatbitintdf
>    __PFX__hardcfr_check
> +  __PFX__strub_enter
> +  __PFX__strub_update
> +  __PFX__strub_leave
>  }
> diff --git a/libgcc/libgcc2.h b/libgcc/libgcc2.h
> index 7e6696d7ab14a..750670e8caaf3 100644
> --- a/libgcc/libgcc2.h
> +++ b/libgcc/libgcc2.h
> @@ -554,6 +554,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 0000000000000..b0f990d9deebb
> --- /dev/null
> +++ b/libgcc/strub.c
> @@ -0,0 +1,149 @@
> +/* Stack scrubbing infrastructure
> +   Copyright (C) 2021-2023 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"
> +
> +#if ! STACK_GROWS_DOWNWARD
> +# define TOPS >
> +#else
> +# define TOPS <
> +#endif
> +
> +#define ATTRIBUTE_STRUB_CALLABLE __attribute__ ((__strub__ ("callable")))
> +
> +/* 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;
> +}
> +
> +#if TARGET_STRUB_USE_DYNAMIC_ARRAY && ! defined TARGET_STRUB_MAY_USE_MEMSET
> +# define TARGET_STRUB_MAY_USE_MEMSET 1
> +#endif
> +
> +#if defined __x86_64__ && __OPTIMIZE__
> +# define TARGET_STRUB_DISABLE_RED_ZONE \
> +  /* __attribute__ ((__target__ ("no-red-zone"))) // not needed when optimizing */
> +#elif !defined RED_ZONE_SIZE || defined __i386__
> +# define TARGET_STRUB_DISABLE_RED_ZONE
> +#endif
> +
> +#ifndef TARGET_STRUB_DISABLE_RED_ZONE
> +/* Dummy function, called to force the caller to not be a leaf function, so
> +   that it can't use the red zone.  */
> +static void ATTRIBUTE_STRUB_CALLABLE
> +__attribute__ ((__noinline__, __noipa__))
> +__strub_dummy_force_no_leaf (void)
> +{
> +}
> +#endif
> +
> +/* Leave a stack scrubbing context, clearing the stack between its top and
> +   *MARK.  */
> +void ATTRIBUTE_STRUB_CALLABLE
> +#if ! TARGET_STRUB_MAY_USE_MEMSET
> +__attribute__ ((__optimize__ ("-fno-tree-loop-distribute-patterns")))
> +#endif
> +#ifdef TARGET_STRUB_DISABLE_RED_ZONE
> +TARGET_STRUB_DISABLE_RED_ZONE
> +#endif
> +__strub_leave (void **mark)
> +{
> +  void *sp = __builtin_stack_address ();
> +
> +  void **base, **end;
> +#if ! STACK_GROWS_DOWNWARD
> +  base = sp; /* ??? Do we need an offset here?  */
> +  end = *mark;
> +#else
> +  base = *mark;
> +  end = sp; /* ??? Does any platform require an offset here?  */
> +#endif
> +
> +  if (! (base < end))
> +    return;
> +
> +#if TARGET_STRUB_USE_DYNAMIC_ARRAY
> +  /* Compute the length without assuming the pointers are both sufficiently
> +     aligned.  They should be, but pointer differences expected to be exact may
> +     yield unexpected results when the assumption doesn't hold.  Given the
> +     potential security implications, compute the length without that
> +     expectation.  If the pointers are misaligned, we may leave a partial
> +     unscrubbed word behind.  */
> +  ptrdiff_t len = ((char *)end - (char *)base) / sizeof (void *);
> +  /* 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];
> +#elifndef TARGET_STRUB_DISABLE_RED_ZONE
> +  /* Prevent the use of the red zone, by making this function non-leaf through
> +     an unreachable call that, because of the asm stmt, the compiler will
> +     consider reachable.  */
> +  asm goto ("" : : : : no_leaf);
> +  if (0)
> +    {
> +    no_leaf:
> +      __strub_dummy_force_no_leaf ();
> +      return;
> +    }
> +#endif
> +
> +  /* ldist may turn these loops into a memset (thus the conditional
> +     -fno-tree-loop-distribute-patterns above).  Without the dynamic array
> +     above, that call would likely be unsafe: possibly tail-called, and likely
> +     scribbling over its own stack frame.  */
> +#if ! STACK_GROWS_DOWNWARD
> +  do
> +    *base++ = 0;
> +  while (base < end);
> +  /* Make sure the stack overwrites are not optimized away.  */
> +  asm ("" : : "m" (end[0]));
> +#else
> +  do
> +    *--end = 0;
> +  while (base < end);
> +  /* Make sure the stack overwrites are not optimized away.  */
> +  asm ("" : : "m" (base[0]));
> +#endif
> +}
>
>
> --
> Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
>    Free Software Activist                   GNU Toolchain Engineer
> More tolerance and less prejudice are key for inclusion and diversity
> Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH v5] Introduce strub: machine-independent stack scrubbing
  2023-12-05  6:25                             ` Alexandre Oliva
@ 2023-12-06  1:04                               ` Alexandre Oliva
  0 siblings, 0 replies; 59+ messages in thread
From: Alexandre Oliva @ 2023-12-06  1:04 UTC (permalink / raw)
  To: Richard Biener
  Cc: gcc-patches, Jeremy Bennett, Craig Blackmore, Graham Markall,
	Martin Jambor, Jan Hubicka, Jim Wilson, Jeff Law, Jakub Jelinek

On Dec  5, 2023, Alexandre Oliva <oliva@adacore.com> wrote:

> I intend to install this as part of the monster patch upthread.

I tweaked it a little further, so that exceptions don't mess with the
pattern counts, and extending the same anti-vrp measure to the other
strub-const tests, even though they weren't affected.

I also had to tweak strub-ptrfn2.c, because of the recent
warning-to-error changes.

Finally, the ChangeLog checker noticed that this entry was no longer
applicable:

>	* multiple_target.cc (pass_target_clone::gate): Test seen_error.

I'd duplicated exactly the fix for ipa/107897, and didn't realize it had
been fixed independently.

I'm reposting only the parts of the final patch pertaining to the
modified test files below, to spare you all yet another copy of the
moster patch.  The whole thing is r14-6201.  I've also refreshed my
strub repo with the trunk commit (same commit id), with the extra
patchlets for broader strub testing on top of it.

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 0000000000000..5e956cb1a9b6b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -0,0 +1,23 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const function call, we issue an asm
+   statement to make sure the watermark passed to it is held in memory before
+   the call, and another to make sure it is not assumed to be unchanged.  f
+   should not be inlined into g, but if it were too simple it might be folded
+   by interprocedural value-range propagation.  */
+
+extern int __attribute__ ((__strub__ ("callable"),
+			   __const__, __nothrow__)) c ();
+
+int __attribute__ ((__strub__, __const__))
+f () {
+  return c ();
+}
+
+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 0000000000000..73d650292dfbf
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -0,0 +1,25 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const function call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   before the call, and another to make sure it is not assumed to be
+   unchanged.  */
+
+extern int __attribute__ ((__strub__ ("callable"),
+			   __const__, __nothrow__)) c ();
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f () {
+  return c ();
+}
+
+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 0000000000000..2584f1f974a58
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   and another to make sure it is not assumed to be unchanged.  */
+
+extern int __attribute__ ((__strub__ ("callable"),
+			   __const__, __nothrow__)) c ();
+
+int __attribute__ ((__strub__ ("internal"), __const__))
+f () {
+  return c ();
+}
+
+/* { 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 0000000000000..d819f54ec0230
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -0,0 +1,20 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const wrapping call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   before the call, and another to make sure it is not assumed to be
+   unchanged.  */
+
+extern int __attribute__ ((__strub__ ("callable"),
+			   __const__, __nothrow__)) c ();
+
+int __attribute__ ((__strub__ ("internal")))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f () {
+  return c ();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */

[...]

diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
new file mode 100644
index 0000000000000..ef634d351265f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
@@ -0,0 +1,55 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=relaxed -Wpedantic" } */
+
+/* C++ does not warn about the partial incompatibilities.
+
+   The d_p () calls are actually rejected, even in C++, but they are XFAILed
+   here because we don't get far enough in the compilation as to observe them,
+   because the incompatibilities are errors without -fpermissive.
+   strub-ptrfn3.c uses -fpermissive to check those.
+ */
+
+extern int __attribute__ ((strub ("callable"))) bac (void);
+extern int __attribute__ ((strub ("disabled"))) bad (void);
+extern int __attribute__ ((strub ("internal"))) bar (void);
+extern int __attribute__ ((strub ("at-calls"))) bal (void);
+
+void __attribute__ ((strub))
+bap (void)
+{
+  int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad;
+  int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac;
+  int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail *-*-* } } */
+  c_p ();
+  a_p ();
+}
+
+void __attribute__ ((strub))
+baP (void)
+{
+  typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void);
+  typedef int __attribute__ ((strub ("callable"))) c_fn_t (void);
+  typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void);
+
+  d_fn_t *d_p = bad;
+  c_fn_t *c_p = bac;
+  a_fn_t *a_p = bal;
+
+  d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */
+  c_p = bal; /* { dg-message "incompatible|invalid conversion" } */
+  a_p = bac; /* { dg-message "incompatible|invalid conversion" } */
+
+  d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail *-*-* } } */
+  c_p ();
+  a_p ();
+}


-- 
Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
   Free Software Activist                   GNU Toolchain Engineer
More tolerance and less prejudice are key for inclusion and diversity
Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Causes to nvptx bootstrap fail: [PATCH v5] Introduce strub: machine-independent stack scrubbing
  2023-12-02 17:56                           ` [PATCH v5] " Alexandre Oliva
  2023-12-05  6:25                             ` Alexandre Oliva
  2023-12-05  9:01                             ` Richard Biener
@ 2023-12-06  8:36                             ` Tobias Burnus
  2023-12-06 11:32                               ` Thomas Schwinge
  2023-12-06 10:22                             ` Jan Hubicka
  2023-12-11  8:40                             ` [PATCH] testsuite: Disable -fstack-protector* for some strub tests Jakub Jelinek
  4 siblings, 1 reply; 59+ messages in thread
From: Tobias Burnus @ 2023-12-06  8:36 UTC (permalink / raw)
  To: Alexandre Oliva, Richard Biener, Thomas Schwinge
  Cc: gcc-patches, Jeremy Bennett, Craig Blackmore, Graham Markall,
	Martin Jambor, Jan Hubicka, Jim Wilson, Jeff Law, Jakub Jelinek

Hi,

CC'd Thomas.

FYI the newly added file libgcc/strub.c of this patch (aka commit r14-6201-gf0a90c7d7333fc )
causes that nvptx does not bootstrap, failing with:

./gcc/as -v -o strub.o strub.s
Verifying sm_30 code with sm_50 code generation.
  ptxas -c -o /dev/null strub.o --gpu-name sm_50 -O0
ptxas strub.o, line 22; error   : Arguments mismatch for instruction 'st'
ptxas strub.o, line 22; error   : Unknown symbol '%frame'
ptxas strub.o, line 37; error   : Arguments mismatch for instruction 'setp'
ptxas strub.o, line 40; error   : Arguments mismatch for instruction 'st'
ptxas strub.o, line 37; error   : Unknown symbol '%frame'
ptxas strub.o, line 40; error   : Unknown symbol '%frame'
ptxas strub.o, line 59; error   : Arguments mismatch for instruction 'mov'
ptxas strub.o, line 67; error   : Arguments mismatch for instruction 'setp'
ptxas strub.o, line 59; error   : Unknown symbol '%stack'
ptxas strub.o, line 67; error   : Unknown symbol '%stack'
ptxas fatal   : Ptx assembly aborted due to errors
nvptx-as: ptxas returned 255 exit status

That's

.visible .func __strub_enter (.param.u64 %in_ar0)
{
         .reg.u64 %ar0;
         ld.param.u64 %ar0, [%in_ar0];
         .reg.u64 %r23;
                 mov.u64 %r23, %ar0;
                 st.u64  [%r23], %frame;
...

                 setp.le.u64     %r26, %r25, %frame;

...

Tobias

-----------------
Siemens Electronic Design Automation GmbH; Anschrift: Arnulfstraße 201, 80634 München; Gesellschaft mit beschränkter Haftung; Geschäftsführer: Thomas Heurung, Frank Thürauf; Sitz der Gesellschaft: München; Registergericht München, HRB 106955

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

* Re: [PATCH v5] Introduce strub: machine-independent stack scrubbing
  2023-12-02 17:56                           ` [PATCH v5] " Alexandre Oliva
                                               ` (2 preceding siblings ...)
  2023-12-06  8:36                             ` Causes to nvptx bootstrap fail: " Tobias Burnus
@ 2023-12-06 10:22                             ` Jan Hubicka
  2023-12-07 21:19                               ` Alexandre Oliva
  2023-12-07 21:39                               ` Alexandre Oliva
  2023-12-11  8:40                             ` [PATCH] testsuite: Disable -fstack-protector* for some strub tests Jakub Jelinek
  4 siblings, 2 replies; 59+ messages in thread
From: Jan Hubicka @ 2023-12-06 10:22 UTC (permalink / raw)
  To: Alexandre Oliva
  Cc: Richard Biener, gcc-patches, Jeremy Bennett, Craig Blackmore,
	Graham Markall, Martin Jambor, Jim Wilson, Jeff Law,
	Jakub Jelinek

Hi,
I am sorry for sending this late.  I think the ipa changes are generally
fine.  There are few things which was not clear to me.
> for  gcc/ChangeLog
> 
> 	* Makefile.in (OBJS): Add ipa-strub.o.
> 	(GTFILES): Add ipa-strub.cc.
> 	* builtins.def (BUILT_IN_STACK_ADDRESS): New.
> 	(BUILT_IN___STRUB_ENTER): New.
> 	(BUILT_IN___STRUB_UPDATE): New.
> 	(BUILT_IN___STRUB_LEAVE): New.
> 	* builtins.cc: Include ipa-strub.h.
> 	(STACK_STOPS, STACK_UNSIGNED): Define.
> 	(expand_builtin_stack_address): New.
> 	(expand_builtin_strub_enter): New.
> 	(expand_builtin_strub_update): New.
> 	(expand_builtin_strub_leave): New.
> 	(expand_builtin): Call them.
> 	* common.opt (fstrub=*): New options.
> 	* doc/extend.texi (strub): New type attribute.
> 	(__builtin_stack_address): New function.
> 	(Stack Scrubbing): New section.
> 	* doc/invoke.texi (-fstrub=*): New options.
> 	(-fdump-ipa-*): New passes.
> 	* gengtype-lex.l: Ignore multi-line pp-directives.
> 	* ipa-inline.cc: Include ipa-strub.h.
> 	(can_inline_edge_p): Test strub_inlinable_to_p.
> 	* ipa-split.cc: Include ipa-strub.h.
> 	(execute_split_functions): Test strub_splittable_p.
> 	* ipa-strub.cc, ipa-strub.h: New.
> 	* passes.def: Add strub_mode and strub passes.
> 	* tree-cfg.cc (gimple_verify_flow_info): Note on debug stmts.
> 	* tree-pass.h (make_pass_ipa_strub_mode): Declare.
> 	(make_pass_ipa_strub): Declare.
> 	(make_pass_ipa_function_and_variable_visibility): Fix
> 	formatting.
> 	* tree-ssa-ccp.cc (optimize_stack_restore): Keep restores
> 	before strub leave.
> 	* multiple_target.cc (pass_target_clone::gate): Test seen_error.
> 	* attribs.cc: Include ipa-strub.h.
> 	(decl_attributes): Support applying attributes to function
> 	type, rather than pointer type, at handler's request.
> 	(comp_type_attributes): Combine strub_comptypes and target
> 	comp_type results.
> 	* doc/tm.texi.in (TARGET_STRUB_USE_DYNAMIC_ARRAY): New.
> 	(TARGET_STRUB_MAY_USE_MEMSET): New.
> 	* doc/tm.texi: Rebuilt.
> 	* cgraph.h (symtab_node::reset): Add preserve_comdat_group
> 	param, with a default.
> 	* cgraphunit.cc (symtab_node::reset): Use it.
> 
> for  gcc/c-family/ChangeLog
> 
> 	* c-attribs.cc: Include ipa-strub.h.
> 	(handle_strub_attribute): New.
> 	(c_common_attribute_table): Add strub.
> 
> for  gcc/ada/ChangeLog
> 
> 	* gcc-interface/trans.cc: Include ipa-strub.h.
> 	(gigi): Make internal decls for targets of compiler-generated
> 	calls strub-callable too.
> 	(build_raise_check): Likewise.
> 	* gcc-interface/utils.cc: Include ipa-strub.h.
> 	(handle_strub_attribute): New.
> 	(gnat_internal_attribute_table): Add strub.
> 
> for  gcc/testsuite/ChangeLog
> 
> 	* c-c++-common/strub-O0.c: New.
> 	* c-c++-common/strub-O1.c: New.
> 	* c-c++-common/strub-O2.c: New.
> 	* c-c++-common/strub-O2fni.c: New.
> 	* c-c++-common/strub-O3.c: New.
> 	* c-c++-common/strub-O3fni.c: New.
> 	* c-c++-common/strub-Og.c: New.
> 	* c-c++-common/strub-Os.c: New.
> 	* c-c++-common/strub-all1.c: New.
> 	* c-c++-common/strub-all2.c: New.
> 	* c-c++-common/strub-apply1.c: New.
> 	* c-c++-common/strub-apply2.c: New.
> 	* c-c++-common/strub-apply3.c: New.
> 	* c-c++-common/strub-apply4.c: New.
> 	* c-c++-common/strub-at-calls1.c: New.
> 	* c-c++-common/strub-at-calls2.c: New.
> 	* c-c++-common/strub-defer-O1.c: New.
> 	* c-c++-common/strub-defer-O2.c: New.
> 	* c-c++-common/strub-defer-O3.c: New.
> 	* c-c++-common/strub-defer-Os.c: New.
> 	* c-c++-common/strub-internal1.c: New.
> 	* c-c++-common/strub-internal2.c: New.
> 	* c-c++-common/strub-parms1.c: New.
> 	* c-c++-common/strub-parms2.c: New.
> 	* c-c++-common/strub-parms3.c: New.
> 	* c-c++-common/strub-relaxed1.c: New.
> 	* c-c++-common/strub-relaxed2.c: New.
> 	* c-c++-common/strub-short-O0-exc.c: New.
> 	* c-c++-common/strub-short-O0.c: New.
> 	* c-c++-common/strub-short-O1.c: New.
> 	* c-c++-common/strub-short-O2.c: New.
> 	* c-c++-common/strub-short-O3.c: New.
> 	* c-c++-common/strub-short-Os.c: New.
> 	* c-c++-common/strub-strict1.c: New.
> 	* c-c++-common/strub-strict2.c: New.
> 	* c-c++-common/strub-tail-O1.c: New.
> 	* c-c++-common/strub-tail-O2.c: New.
> 	* c-c++-common/torture/strub-callable1.c: New.
> 	* c-c++-common/torture/strub-callable2.c: New.
> 	* c-c++-common/torture/strub-const1.c: New.
> 	* c-c++-common/torture/strub-const2.c: New.
> 	* c-c++-common/torture/strub-const3.c: New.
> 	* c-c++-common/torture/strub-const4.c: New.
> 	* c-c++-common/torture/strub-data1.c: New.
> 	* c-c++-common/torture/strub-data2.c: New.
> 	* c-c++-common/torture/strub-data3.c: New.
> 	* c-c++-common/torture/strub-data4.c: New.
> 	* c-c++-common/torture/strub-data5.c: New.
> 	* c-c++-common/torture/strub-indcall1.c: New.
> 	* c-c++-common/torture/strub-indcall2.c: New.
> 	* c-c++-common/torture/strub-indcall3.c: New.
> 	* c-c++-common/torture/strub-inlinable1.c: New.
> 	* c-c++-common/torture/strub-inlinable2.c: New.
> 	* c-c++-common/torture/strub-ptrfn1.c: New.
> 	* c-c++-common/torture/strub-ptrfn2.c: New.
> 	* c-c++-common/torture/strub-ptrfn3.c: New.
> 	* c-c++-common/torture/strub-ptrfn4.c: New.
> 	* c-c++-common/torture/strub-pure1.c: New.
> 	* c-c++-common/torture/strub-pure2.c: New.
> 	* c-c++-common/torture/strub-pure3.c: New.
> 	* c-c++-common/torture/strub-pure4.c: New.
> 	* c-c++-common/torture/strub-run1.c: New.
> 	* c-c++-common/torture/strub-run2.c: New.
> 	* c-c++-common/torture/strub-run3.c: New.
> 	* c-c++-common/torture/strub-run4.c: New.
> 	* c-c++-common/torture/strub-run4c.c: New.
> 	* c-c++-common/torture/strub-run4d.c: New.
> 	* c-c++-common/torture/strub-run4i.c: New.
> 	* g++.dg/strub-run1.C: New.
> 	* g++.dg/torture/strub-init1.C: New.
> 	* g++.dg/torture/strub-init2.C: New.
> 	* g++.dg/torture/strub-init3.C: New.
> 	* gnat.dg/strub_attr.adb, gnat.dg/strub_attr.ads: New.
> 	* gnat.dg/strub_ind.adb, gnat.dg/strub_ind.ads: New.
> 
> for  libgcc/ChangeLog
> 
> 	* Makefile.in (LIB2ADD): Add strub.c.
> 	* libgcc2.h (__strub_enter, __strub_update, __strub_leave):
> 	Declare.
> 	* strub.c: New.
> 	* libgcc-std.ver.in (__strub_enter): Add to GCC_14.0.0.
> 	(__strub_update, __strub_leave): Likewise.

> +/* Return TRUE iff NODE carries the always_inline attribute.  */
> +
> +static inline bool
> +strub_always_inline_p (cgraph_node *node)
> +{
> +  return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
> +}
We may want to ahve this as cgraph_node::always_inline_p since there are
now quite many places we look up this attribute.
> +/* The strub pass proper adjusts types, signatures, and at-calls calls, and
> +   splits internal-strub functions.  */
> +
> +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.  */

I think Martin ma have more specific opinion on this, but since this is
not running as the ipa pass during WPA stage, I think the param
modification infrastructure is not really that much hepful here. 

It may save bit of legwork on adjusting call sites and callees but it
does not look that bad.
> +	/* ??? Maybe we could adjust it instead.  */
> +	if (drop_fnspec)
> +	  remove_named_attribute_unsharing ("fn spec",
> +					    &TYPE_ATTRIBUTES (nftype));

ipa param modification also doesn't know how to update fn spec, this is
something we should look into next stage1...
There is also access attribute which speaks directly about individual
arugments, perhaps you want to drop this one too?
> +	/* Remove the function's body but keep arguments to be reused
> +	   for thunk.  */
> +	onode->release_body (true);
> +	onode->reset (/* unlike create_wrapper: preserve_comdat_group = */true);
> +
> +	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;

Are variadic thunks working with scrubbing?

Honza

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

* Re: Causes to nvptx bootstrap fail: [PATCH v5] Introduce strub: machine-independent stack scrubbing
  2023-12-06  8:36                             ` Causes to nvptx bootstrap fail: " Tobias Burnus
@ 2023-12-06 11:32                               ` Thomas Schwinge
  2023-12-06 22:12                                 ` Alexandre Oliva
  0 siblings, 1 reply; 59+ messages in thread
From: Thomas Schwinge @ 2023-12-06 11:32 UTC (permalink / raw)
  To: Alexandre Oliva
  Cc: Tobias Burnus, Richard Biener, gcc-patches, Jeremy Bennett,
	Craig Blackmore, Graham Markall, Martin Jambor, Jan Hubicka,
	Jim Wilson, Jeff Law, Jakub Jelinek, Tom de Vries

Hi Alexandre!

On 2023-12-06T09:36:33+0100, Tobias Burnus <tobias@codesourcery.com> wrote:
> FYI the newly added file libgcc/strub.c of this patch (aka commit r14-6201-gf0a90c7d7333fc )
> causes that nvptx does not bootstrap, failing with:

('s%bootstrap%build'.)

> ./gcc/as -v -o strub.o strub.s
> Verifying sm_30 code with sm_50 code generation.
>   ptxas -c -o /dev/null strub.o --gpu-name sm_50 -O0
> ptxas strub.o, line 22; error   : Arguments mismatch for instruction 'st'
> ptxas strub.o, line 22; error   : Unknown symbol '%frame'
> [...]

Per commit r14-6201-gf0a90c7d7333fc7f554b906245c84bdf04d716d7
"Introduce strub: machine-independent stack scrubbing", we have:

    A function associated with @code{at-calls} @code{strub} mode
    (@code{strub("at-calls")}, or just @code{strub}) undergoes interface
    changes.  Its callers are adjusted to match the changes, and to scrub
    (overwrite with zeros) the stack space used by the called function after
    it returns.

As I understand things, this cannot be implemented (at the call site) for
nvptx, given that the callee's stack is not visible there: PTX is unusual
in that the concept of a "standard" stack isn't exposed.

Instead of allowing "strub" pieces that can be implemented, should this
whole machinery generally be disabled (forced '-fstrub=disable', or via a
new target hook?)?  The libgcc functions should then not get defined
(thus, linker error upon accidental use), or should just '__builtin_trap'
if that makes more sense?  Need an effective-target for the test cases.

Alternatively, we may also leave the generic middle end handling alive,
and 'sorry' (or similar) in the nvptx back end, as necessary?


Grüße
 Thomas
-----------------
Siemens Electronic Design Automation GmbH; Anschrift: Arnulfstraße 201, 80634 München; Gesellschaft mit beschränkter Haftung; Geschäftsführer: Thomas Heurung, Frank Thürauf; Sitz der Gesellschaft: München; Registergericht München, HRB 106955

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

* Re: Causes to nvptx bootstrap fail: [PATCH v5] Introduce strub: machine-independent stack scrubbing
  2023-12-06 11:32                               ` Thomas Schwinge
@ 2023-12-06 22:12                                 ` Alexandre Oliva
  2023-12-07  3:33                                   ` [PATCH] strub: enable conditional support Alexandre Oliva
  2023-12-07  7:21                                   ` Causes to nvptx bootstrap fail: [PATCH v5] Introduce strub: machine-independent stack scrubbing Richard Biener
  0 siblings, 2 replies; 59+ messages in thread
From: Alexandre Oliva @ 2023-12-06 22:12 UTC (permalink / raw)
  To: Thomas Schwinge
  Cc: Tobias Burnus, Richard Biener, gcc-patches, Jeremy Bennett,
	Craig Blackmore, Graham Markall, Martin Jambor, Jan Hubicka,
	Jim Wilson, Jeff Law, Jakub Jelinek, Tom de Vries

On Dec  6, 2023, Thomas Schwinge <thomas@codesourcery.com> wrote:

> As I understand things, this cannot be implemented (at the call site) for
> nvptx, given that the callee's stack is not visible there: PTX is unusual
> in that the concept of a "standard" stack isn't exposed.

Not even when one PTX function calls another?  Interesting.  I'd hoped
that with control over entering and leaving strub contexts, one could
(manually) ensure they'd run in the same execution domain.  But if not
even that is possible, it will render the current strub implementation
entirely unusable for this target indeed.

Now, it doesn't seem to me that the build errors being experienced have
to do with that, but rather with lack of or incomplete support for
__builtin_{frame,stack}_address().  Are those errors expected when using
these builtins on this target?  I'd have expected them to compile, even
if something went wrong at runtime.


> Instead of allowing "strub" pieces that can be implemented, should this
> whole machinery generally be disabled (forced '-fstrub=disable', or via a
> new target hook?)?  The libgcc functions should then not get defined
> (thus, linker error upon accidental use), or should just '__builtin_trap'
> if that makes more sense?  Need an effective-target for the test cases.

> Alternatively, we may also leave the generic middle end handling alive,
> and 'sorry' (or similar) in the nvptx back end, as necessary?

Disabling the runtime bits is easy, once we determine what condition we
wish to test for.  I suppose testing for target support in the compiler,
issuing a 'sorry' in case the feature is required, would provide
something for libgcc configure and testsuite effective-target to test
for and decide whether to enable runtime support and run the tests.

-- 
Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
   Free Software Activist                   GNU Toolchain Engineer
More tolerance and less prejudice are key for inclusion and diversity
Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* [PATCH] strub: enable conditional support
  2023-12-06 22:12                                 ` Alexandre Oliva
@ 2023-12-07  3:33                                   ` Alexandre Oliva
  2023-12-07  7:24                                     ` Richard Biener
  2023-12-07 16:44                                     ` Thomas Schwinge
  2023-12-07  7:21                                   ` Causes to nvptx bootstrap fail: [PATCH v5] Introduce strub: machine-independent stack scrubbing Richard Biener
  1 sibling, 2 replies; 59+ messages in thread
From: Alexandre Oliva @ 2023-12-07  3:33 UTC (permalink / raw)
  To: Thomas Schwinge
  Cc: Tobias Burnus, Richard Biener, gcc-patches, Jeremy Bennett,
	Craig Blackmore, Graham Markall, Martin Jambor, Jan Hubicka,
	Jim Wilson, Jeff Law, Jakub Jelinek, Tom de Vries

On Dec  6, 2023, Alexandre Oliva <oliva@adacore.com> wrote:

> Disabling the runtime bits is easy, once we determine what condition we
> wish to test for.  I suppose testing for target support in the compiler,
> issuing a 'sorry' in case the feature is required, would provide
> something for libgcc configure and testsuite effective-target to test
> for and decide whether to enable runtime support and run the tests.

Instead of doing something equivalent to an implicit -fstrub=disable,
that would quietly compile without stack scrubbing, I thought it would
be safer to be noisy if the feature is used (requested, really) when
support is not available.


Targets that don't expose callee stacks to callers, such as nvptx, as
well as -fsplit-stack compilations, violate fundamental assumptions of
the current strub implementation.  This patch enables targets to
disable strub, and disables it when -fsplit-stack is enabled.

When strub support is disabled, the testsuite will now skip strub
tests, and libgcc will not build the strub runtime components.

Regstrapped on x86_64-linux-gnu.  Also tested with an additional patch
for i386.cc that mirrors the nvptx.cc change, to check that strub gets
disabled without noisy test results.  Ok to install?


for  gcc/ChangeLog

	* target.def (have_strub_support_for): New hook.
	* doc/tm.texi.in: Document it.
	* doc/tm.texi: Rebuild.
	* ipa-strub.cc: Include target.h.
	(strub_target_support_p): New.
	(can_strub_p): Call it.  Test for no flag_split_stack.
	(pass_ipa_strub::adjust_at_calls_call): Check for target
	support.
	* config/nvptx/nvptx.cc (TARGET_HAVE_STRUB_SUPPORT_FOR):
        Disable.
	* doc/sourcebuild.texi (strub): Document new effective
	target.

for  gcc/testsuite/ChangeLog

	* gcc.dg/strub-split-stack.c: New.
	* gcc.dg/strub-unsupported.c: New.
	* gcc.dg/strub-unsupported-2.c: New.
	* gcc.dg/strub-unsupported-3.c: New.
	* lib/target-supports.exp (check_effective_target_strub): New.
	* c-c++-common/strub-O0.c: Require effective target strub.
	* c-c++-common/strub-O1.c: Likewise.
	* c-c++-common/strub-O2.c: Likewise.
	* c-c++-common/strub-O2fni.c: Likewise.
	* c-c++-common/strub-O3.c: Likewise.
	* c-c++-common/strub-O3fni.c: Likewise.
	* c-c++-common/strub-Og.c: Likewise.
	* c-c++-common/strub-Os.c: Likewise.
	* c-c++-common/strub-all1.c: Likewise.
	* c-c++-common/strub-all2.c: Likewise.
	* c-c++-common/strub-apply1.c: Likewise.
	* c-c++-common/strub-apply2.c: Likewise.
	* c-c++-common/strub-apply3.c: Likewise.
	* c-c++-common/strub-apply4.c: Likewise.
	* c-c++-common/strub-at-calls1.c: Likewise.
	* c-c++-common/strub-at-calls2.c: Likewise.
	* c-c++-common/strub-defer-O1.c: Likewise.
	* c-c++-common/strub-defer-O2.c: Likewise.
	* c-c++-common/strub-defer-O3.c: Likewise.
	* c-c++-common/strub-defer-Os.c: Likewise.
	* c-c++-common/strub-internal1.c: Likewise.
	* c-c++-common/strub-internal2.c: Likewise.
	* c-c++-common/strub-parms1.c: Likewise.
	* c-c++-common/strub-parms2.c: Likewise.
	* c-c++-common/strub-parms3.c: Likewise.
	* c-c++-common/strub-relaxed1.c: Likewise.
	* c-c++-common/strub-relaxed2.c: Likewise.
	* c-c++-common/strub-short-O0-exc.c: Likewise.
	* c-c++-common/strub-short-O0.c: Likewise.
	* c-c++-common/strub-short-O1.c: Likewise.
	* c-c++-common/strub-short-O2.c: Likewise.
	* c-c++-common/strub-short-O3.c: Likewise.
	* c-c++-common/strub-short-Os.c: Likewise.
	* c-c++-common/strub-strict1.c: Likewise.
	* c-c++-common/strub-strict2.c: Likewise.
	* c-c++-common/strub-tail-O1.c: Likewise.
	* c-c++-common/strub-tail-O2.c: Likewise.
	* c-c++-common/strub-var1.c: Likewise.
	* c-c++-common/torture/strub-callable1.c: Likewise.
	* c-c++-common/torture/strub-callable2.c: Likewise.
	* c-c++-common/torture/strub-const1.c: Likewise.
	* c-c++-common/torture/strub-const2.c: Likewise.
	* c-c++-common/torture/strub-const3.c: Likewise.
	* c-c++-common/torture/strub-const4.c: Likewise.
	* c-c++-common/torture/strub-data1.c: Likewise.
	* c-c++-common/torture/strub-data2.c: Likewise.
	* c-c++-common/torture/strub-data3.c: Likewise.
	* c-c++-common/torture/strub-data4.c: Likewise.
	* c-c++-common/torture/strub-data5.c: Likewise.
	* c-c++-common/torture/strub-indcall1.c: Likewise.
	* c-c++-common/torture/strub-indcall2.c: Likewise.
	* c-c++-common/torture/strub-indcall3.c: Likewise.
	* c-c++-common/torture/strub-inlinable1.c: Likewise.
	* c-c++-common/torture/strub-inlinable2.c: Likewise.
	* c-c++-common/torture/strub-ptrfn1.c: Likewise.
	* c-c++-common/torture/strub-ptrfn2.c: Likewise.
	* c-c++-common/torture/strub-ptrfn3.c: Likewise.
	* c-c++-common/torture/strub-ptrfn4.c: Likewise.
	* c-c++-common/torture/strub-pure1.c: Likewise.
	* c-c++-common/torture/strub-pure2.c: Likewise.
	* c-c++-common/torture/strub-pure3.c: Likewise.
	* c-c++-common/torture/strub-pure4.c: Likewise.
	* c-c++-common/torture/strub-run1.c: Likewise.
	* c-c++-common/torture/strub-run2.c: Likewise.
	* c-c++-common/torture/strub-run3.c: Likewise.
	* c-c++-common/torture/strub-run4.c: Likewise.
	* c-c++-common/torture/strub-run4c.c: Likewise.
	* c-c++-common/torture/strub-run4d.c: Likewise.
	* c-c++-common/torture/strub-run4i.c: Likewise.
	* g++.dg/strub-run1.C: Likewise.
	* g++.dg/torture/strub-init1.C: Likewise.
	* g++.dg/torture/strub-init2.C: Likewise.
	* g++.dg/torture/strub-init3.C: Likewise.
	* gnat.dg/strub_attr.adb: Likewise.
	* gnat.dg/strub_ind.adb: Likewise.
	* gnat.dg/strub_access.adb: Likewise.
	* gnat.dg/strub_access1.adb: Likewise.
	* gnat.dg/strub_disp.adb: Likewise.
	* gnat.dg/strub_disp1.adb: Likewise.
	* gnat.dg/strub_ind1.adb: Likewise.
	* gnat.dg/strub_ind2.adb: Likewise.
	* gnat.dg/strub_intf.adb: Likewise.
	* gnat.dg/strub_intf1.adb: Likewise.
	* gnat.dg/strub_intf2.adb: Likewise.
	* gnat.dg/strub_renm.adb: Likewise.
	* gnat.dg/strub_renm1.adb: Likewise.
	* gnat.dg/strub_renm2.adb: Likewise.
	* gnat.dg/strub_var.adb: Likewise.
	* gnat.dg/strub_var1.adb: Likewise.

for  libgcc/ChangeLog

	* configure.ac: Check for strub support.
	* configure: Rebuilt.
	* Makefile.in: Compile strub.c conditionally.
---
 gcc/config/nvptx/nvptx.cc                          |    3 +
 gcc/doc/sourcebuild.texi                           |    3 +
 gcc/doc/tm.texi                                    |    6 ++
 gcc/doc/tm.texi.in                                 |    2 +
 gcc/ipa-strub.cc                                   |   54 +++++++++++++++++++-
 gcc/target.def                                     |    8 +++
 gcc/testsuite/c-c++-common/strub-O0.c              |    1 
 gcc/testsuite/c-c++-common/strub-O1.c              |    1 
 gcc/testsuite/c-c++-common/strub-O2.c              |    1 
 gcc/testsuite/c-c++-common/strub-O2fni.c           |    1 
 gcc/testsuite/c-c++-common/strub-O3.c              |    1 
 gcc/testsuite/c-c++-common/strub-O3fni.c           |    1 
 gcc/testsuite/c-c++-common/strub-Og.c              |    1 
 gcc/testsuite/c-c++-common/strub-Os.c              |    1 
 gcc/testsuite/c-c++-common/strub-all1.c            |    1 
 gcc/testsuite/c-c++-common/strub-all2.c            |    1 
 gcc/testsuite/c-c++-common/strub-apply1.c          |    1 
 gcc/testsuite/c-c++-common/strub-apply2.c          |    1 
 gcc/testsuite/c-c++-common/strub-apply3.c          |    1 
 gcc/testsuite/c-c++-common/strub-apply4.c          |    1 
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |    1 
 gcc/testsuite/c-c++-common/strub-at-calls2.c       |    1 
 gcc/testsuite/c-c++-common/strub-defer-O1.c        |    1 
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |    1 
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |    1 
 gcc/testsuite/c-c++-common/strub-defer-Os.c        |    1 
 gcc/testsuite/c-c++-common/strub-internal1.c       |    1 
 gcc/testsuite/c-c++-common/strub-internal2.c       |    1 
 gcc/testsuite/c-c++-common/strub-parms1.c          |    1 
 gcc/testsuite/c-c++-common/strub-parms2.c          |    1 
 gcc/testsuite/c-c++-common/strub-parms3.c          |    1 
 gcc/testsuite/c-c++-common/strub-relaxed1.c        |    1 
 gcc/testsuite/c-c++-common/strub-relaxed2.c        |    1 
 gcc/testsuite/c-c++-common/strub-short-O0-exc.c    |    1 
 gcc/testsuite/c-c++-common/strub-short-O0.c        |    1 
 gcc/testsuite/c-c++-common/strub-short-O1.c        |    1 
 gcc/testsuite/c-c++-common/strub-short-O2.c        |    1 
 gcc/testsuite/c-c++-common/strub-short-O3.c        |    1 
 gcc/testsuite/c-c++-common/strub-short-Os.c        |    1 
 gcc/testsuite/c-c++-common/strub-split-stack.c     |   10 ++++
 gcc/testsuite/c-c++-common/strub-strict1.c         |    1 
 gcc/testsuite/c-c++-common/strub-strict2.c         |    1 
 gcc/testsuite/c-c++-common/strub-tail-O1.c         |    1 
 gcc/testsuite/c-c++-common/strub-tail-O2.c         |    1 
 gcc/testsuite/c-c++-common/strub-unsupported-2.c   |   13 +++++
 gcc/testsuite/c-c++-common/strub-unsupported-3.c   |   18 +++++++
 gcc/testsuite/c-c++-common/strub-unsupported.c     |   21 ++++++++
 gcc/testsuite/c-c++-common/strub-var1.c            |    1 
 .../c-c++-common/torture/strub-callable1.c         |    1 
 .../c-c++-common/torture/strub-callable2.c         |    1 
 gcc/testsuite/c-c++-common/torture/strub-const1.c  |    1 
 gcc/testsuite/c-c++-common/torture/strub-const2.c  |    1 
 gcc/testsuite/c-c++-common/torture/strub-const3.c  |    1 
 gcc/testsuite/c-c++-common/torture/strub-const4.c  |    1 
 gcc/testsuite/c-c++-common/torture/strub-data1.c   |    1 
 gcc/testsuite/c-c++-common/torture/strub-data2.c   |    1 
 gcc/testsuite/c-c++-common/torture/strub-data3.c   |    1 
 gcc/testsuite/c-c++-common/torture/strub-data4.c   |    1 
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |    1 
 .../c-c++-common/torture/strub-indcall1.c          |    1 
 .../c-c++-common/torture/strub-indcall2.c          |    1 
 .../c-c++-common/torture/strub-indcall3.c          |    1 
 .../c-c++-common/torture/strub-inlinable1.c        |    1 
 .../c-c++-common/torture/strub-inlinable2.c        |    1 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |    1 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |    1 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c  |    1 
 gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c  |    1 
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |    1 
 gcc/testsuite/c-c++-common/torture/strub-pure2.c   |    1 
 gcc/testsuite/c-c++-common/torture/strub-pure3.c   |    1 
 gcc/testsuite/c-c++-common/torture/strub-pure4.c   |    1 
 gcc/testsuite/c-c++-common/torture/strub-run1.c    |    1 
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |    1 
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |    1 
 gcc/testsuite/c-c++-common/torture/strub-run4.c    |    1 
 gcc/testsuite/c-c++-common/torture/strub-run4c.c   |    1 
 gcc/testsuite/c-c++-common/torture/strub-run4d.c   |    1 
 gcc/testsuite/c-c++-common/torture/strub-run4i.c   |    1 
 gcc/testsuite/g++.dg/strub-run1.C                  |    1 
 gcc/testsuite/g++.dg/torture/strub-init1.C         |    1 
 gcc/testsuite/g++.dg/torture/strub-init2.C         |    1 
 gcc/testsuite/g++.dg/torture/strub-init3.C         |    1 
 gcc/testsuite/gnat.dg/strub_access.adb             |    1 
 gcc/testsuite/gnat.dg/strub_access1.adb            |    1 
 gcc/testsuite/gnat.dg/strub_attr.adb               |    1 
 gcc/testsuite/gnat.dg/strub_disp.adb               |    1 
 gcc/testsuite/gnat.dg/strub_disp1.adb              |    1 
 gcc/testsuite/gnat.dg/strub_ind.adb                |    1 
 gcc/testsuite/gnat.dg/strub_ind1.adb               |    1 
 gcc/testsuite/gnat.dg/strub_ind2.adb               |    1 
 gcc/testsuite/gnat.dg/strub_intf.adb               |    1 
 gcc/testsuite/gnat.dg/strub_intf1.adb              |    1 
 gcc/testsuite/gnat.dg/strub_intf2.adb              |    1 
 gcc/testsuite/gnat.dg/strub_renm.adb               |    1 
 gcc/testsuite/gnat.dg/strub_renm1.adb              |    1 
 gcc/testsuite/gnat.dg/strub_renm2.adb              |    1 
 gcc/testsuite/gnat.dg/strub_var.adb                |    1 
 gcc/testsuite/gnat.dg/strub_var1.adb               |    1 
 gcc/testsuite/lib/target-supports.exp              |    7 +++
 libgcc/Makefile.in                                 |    2 -
 libgcc/configure                                   |   26 ++++++++++
 libgcc/configure.ac                                |   13 +++++
 103 files changed, 272 insertions(+), 3 deletions(-)
 create mode 100644 gcc/testsuite/c-c++-common/strub-split-stack.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-unsupported-2.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-unsupported-3.c
 create mode 100644 gcc/testsuite/c-c++-common/strub-unsupported.c

diff --git a/gcc/config/nvptx/nvptx.cc b/gcc/config/nvptx/nvptx.cc
index ae20802c87996..3fb1deb70fda1 100644
--- a/gcc/config/nvptx/nvptx.cc
+++ b/gcc/config/nvptx/nvptx.cc
@@ -7789,6 +7789,9 @@ nvptx_asm_output_def_from_decls (FILE *stream, tree name, tree value)
 #undef TARGET_LIBC_HAS_FUNCTION
 #define TARGET_LIBC_HAS_FUNCTION nvptx_libc_has_function
 
+#undef TARGET_HAVE_STRUB_SUPPORT_FOR
+#define TARGET_HAVE_STRUB_SUPPORT_FOR hook_bool_tree_false
+
 struct gcc_target targetm = TARGET_INITIALIZER;
 
 #include "gt-nvptx.h"
diff --git a/gcc/doc/sourcebuild.texi b/gcc/doc/sourcebuild.texi
index c990902685417..26a7e9c350703 100644
--- a/gcc/doc/sourcebuild.texi
+++ b/gcc/doc/sourcebuild.texi
@@ -2983,6 +2983,9 @@ Target supports statically linking @samp{libgfortran}.
 @item string_merging
 Target supports merging string constants at link time.
 
+@item strub
+Target supports attribute @code{strub} for stack scrubbing.
+
 @item ucn
 Target supports compiling and assembling UCN.
 
diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index 89a1735dd7992..768ada0af5222 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -3450,6 +3450,12 @@ in DWARF 2 debug information.  The default is zero.  A different value
 may reduce the size of debug information on some ports.
 @end defmac
 
+@deftypefn {Target Hook} bool TARGET_HAVE_STRUB_SUPPORT_FOR (tree)
+Returns true if the target supports stack scrubbing for the given function
+or type, otherwise return false.  The default implementation always returns
+true.
+@end deftypefn
+
 @defmac TARGET_STRUB_USE_DYNAMIC_ARRAY
 If defined to nonzero, @code{__strub_leave} will allocate a dynamic
 array covering the stack range that needs scrubbing before clearing it.
diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
index ebc1d3de5caaa..4fe0805394ea4 100644
--- a/gcc/doc/tm.texi.in
+++ b/gcc/doc/tm.texi.in
@@ -2686,6 +2686,8 @@ in DWARF 2 debug information.  The default is zero.  A different value
 may reduce the size of debug information on some ports.
 @end defmac
 
+@hook TARGET_HAVE_STRUB_SUPPORT_FOR
+
 @defmac TARGET_STRUB_USE_DYNAMIC_ARRAY
 If defined to nonzero, @code{__strub_leave} will allocate a dynamic
 array covering the stack range that needs scrubbing before clearing it.
diff --git a/gcc/ipa-strub.cc b/gcc/ipa-strub.cc
index 293bec132b885..2afb7a455751d 100644
--- a/gcc/ipa-strub.cc
+++ b/gcc/ipa-strub.cc
@@ -60,6 +60,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "ipa-strub.h"
 #include "symtab-thunks.h"
 #include "attr-fnspec.h"
+#include "target.h"
 
 /* This file introduces two passes that, together, implement
    machine-independent stack scrubbing, strub for short.  It arranges
@@ -631,17 +632,60 @@ strub_always_inline_p (cgraph_node *node)
   return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
 }
 
+/* Return TRUE iff the target has strub support for T, a function
+   decl, or a type used in an indirect call, and optionally REPORT the
+   reasons for ineligibility.  If T is a type and error REPORTing is
+   enabled, the LOCation (of the indirect call) should be provided.  */
+static inline bool
+strub_target_support_p (tree t, bool report = false,
+			location_t loc = UNKNOWN_LOCATION)
+{
+  bool result = true;
+
+  if (!targetm.have_strub_support_for (t))
+    {
+      result = false;
+
+      if (!report)
+	return result;
+
+      if (DECL_P (t))
+	sorry_at (DECL_SOURCE_LOCATION (t),
+		  "%qD is not eligible for %<strub%>"
+		  " on the target system", t);
+      else
+	sorry_at (loc,
+		  "unsupported %<strub%> call"
+		  " on the target system");
+    }
+
+  return result;
+}
+
 /* Return TRUE iff NODE is potentially eligible for any strub-enabled mode, and
    optionally REPORT the reasons for ineligibility.  */
 
 static inline bool
 can_strub_p (cgraph_node *node, bool report = false)
 {
-  bool result = true;
+  bool result = strub_target_support_p (node->decl, report);
 
-  if (!report && strub_always_inline_p (node))
+  if (!report && (!result || strub_always_inline_p (node)))
     return result;
 
+  if (flag_split_stack)
+    {
+      result = false;
+
+      if (!report)
+	return result;
+
+      sorry_at (DECL_SOURCE_LOCATION (node->decl),
+		"%qD is not eligible for %<strub%>"
+		" because %<-fsplit-stack%> is enabled",
+		node->decl);
+    }
+
   if (lookup_attribute ("noipa", DECL_ATTRIBUTES (node->decl)))
     {
       result = false;
@@ -2417,6 +2461,12 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args,
 			 && (TREE_TYPE (gimple_call_arg (ocall, named_args))
 			     == get_pwmt ())));
 
+  tree tsup;
+  if (!(tsup = gimple_call_fndecl (ocall)))
+    tsup = TREE_TYPE (TREE_TYPE (gimple_call_fn (ocall)));
+  if (!strub_target_support_p (tsup, true, gimple_location (ocall)))
+    return;
+
   /* 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.  */
diff --git a/gcc/target.def b/gcc/target.def
index 52b83e091b94b..08218f3a42adf 100644
--- a/gcc/target.def
+++ b/gcc/target.def
@@ -4457,6 +4457,14 @@ otherwise return false.  The default implementation always returns true.",
  bool, (void),
  hook_bool_void_true)
 
+DEFHOOK
+(have_strub_support_for,
+ "Returns true if the target supports stack scrubbing for the given function\n\
+or type, otherwise return false.  The default implementation always returns\n\
+true.",
+ bool, (tree),
+ hook_bool_tree_true)
+
 DEFHOOK
 (have_speculation_safe_value,
 "This hook is used to determine the level of target support for\n\
diff --git a/gcc/testsuite/c-c++-common/strub-O0.c b/gcc/testsuite/c-c++-common/strub-O0.c
index c7a79a6ea0d8a..f0a3f7b4c6f9a 100644
--- a/gcc/testsuite/c-c++-common/strub-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-O0.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-O0 -fstrub=strict -fdump-rtl-expand" } */
+/* { dg-require-effective-target strub } */
 
 /* At -O0, none of the strub builtins are expanded inline.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O1.c b/gcc/testsuite/c-c++-common/strub-O1.c
index 96285c975d98e..50403426b18f2 100644
--- a/gcc/testsuite/c-c++-common/strub-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-O1.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-O1 -fstrub=strict -fdump-rtl-expand" } */
+/* { dg-require-effective-target strub } */
 
 /* At -O1, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
index 8edc0d8aa1321..37e02998e318e 100644
--- a/gcc/testsuite/c-c++-common/strub-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
+/* { dg-require-effective-target strub } */
 
 /* At -O2, without -fno-inline, we fully expand enter and update, and add a test
    around the leave call.  */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
index c6d900cf3c45b..905e2c6b2ffca 100644
--- a/gcc/testsuite/c-c++-common/strub-O2fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
+/* { dg-require-effective-target strub } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3.c b/gcc/testsuite/c-c++-common/strub-O3.c
index 33ee465e51cb6..3bbf132bdf1ea 100644
--- a/gcc/testsuite/c-c++-common/strub-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-O3.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand" } */
+/* { dg-require-effective-target strub } */
 
 int __attribute__ ((__strub__)) var;
 
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
index 2936f82079e18..c46fce38e5c91 100644
--- a/gcc/testsuite/c-c++-common/strub-O3fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
+/* { dg-require-effective-target strub } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
index 479746e57d87e..3b8eb19765cd6 100644
--- a/gcc/testsuite/c-c++-common/strub-Og.c
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-Og -fstrub=strict -fdump-rtl-expand" } */
+/* { dg-require-effective-target strub } */
 
 /* At -Og, without -fno-inline, we fully expand enter, but neither update nor
    leave.  */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
index 2241d4ea07f27..8cfb253d6764c 100644
--- a/gcc/testsuite/c-c++-common/strub-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
+/* { dg-require-effective-target strub } */
 
 /* At -Os, without -fno-inline, we fully expand enter, and also update.  The
    expanded update might be larger than a call proper, but argument saving and
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
index a322bcc5da606..2037f681f2973 100644
--- a/gcc/testsuite/c-c++-common/strub-all1.c
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=all -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
index db60026d0e080..c026e7d9d289b 100644
--- a/gcc/testsuite/c-c++-common/strub-all2.c
+++ b/gcc/testsuite/c-c++-common/strub-all2.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=all -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-require-effective-target 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
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
index 2f462adc1efe0..3edc89c54eea1 100644
--- a/gcc/testsuite/c-c++-common/strub-apply1.c
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict" } */
+/* { dg-require-effective-target strub } */
 
 void __attribute__ ((__strub__ ("callable")))
 apply_function (void *args)
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
index a5d7551f5da5c..838fc75273450 100644
--- a/gcc/testsuite/c-c++-common/strub-apply2.c
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict" } */
+/* { dg-require-effective-target strub } */
 
 extern void __attribute__ ((__strub__))
 apply_function (void *args);
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
index 64422a0d1e880..0206e4d930e7d 100644
--- a/gcc/testsuite/c-c++-common/strub-apply3.c
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict" } */
+/* { dg-require-effective-target strub } */
 
 void __attribute__ ((__strub__))
 apply_function (void *args)
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
index 15ffaa031b899..e82504728b2c6 100644
--- a/gcc/testsuite/c-c++-common/strub-apply4.c
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-O2 -fstrub=strict -fdump-ipa-strubm" } */
+/* { dg-require-effective-target strub } */
 
 /* Check that implicit enabling of strub mode selects internal strub when the
    function uses __builtin_apply_args, that prevents the optimization to
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
index b70843b4215a4..a20acc0a48a58 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=at-calls -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
index 97a3988a6b922..7915b33a39a0a 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls2.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls2.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=at-calls -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-require-effective-target 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
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
index 3d73431b3dcd3..3689998b5a323 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -1,5 +1,6 @@
 /* { dg-do run } */
 /* { dg-options "-fstrub=strict -O1" } */
+/* { dg-require-effective-target strub } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O1.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
index fddf3c745e7e6..9e01949db6be9 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -1,5 +1,6 @@
 /* { dg-do run } */
 /* { dg-options "-fstrub=strict -O2" } */
+/* { dg-require-effective-target strub } */
 
 /* Check that a strub function called by another strub function does NOT defer
    the strubbing to its caller at -O2.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
index 7ebc65b58dd72..40ee8edd1e0e6 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -1,5 +1,6 @@
 /* { dg-do run } */
 /* { dg-options "-fstrub=strict -O3" } */
+/* { dg-require-effective-target strub } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -O3.  */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
index fbaf85fe0fafe..67ea9f0463975 100644
--- a/gcc/testsuite/c-c++-common/strub-defer-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -1,5 +1,6 @@
 /* { dg-do run } */
 /* { dg-options "-fstrub=strict -Os" } */
+/* { dg-require-effective-target strub } */
 
 /* Check that a strub function called by another strub function defers the
    strubbing to its caller at -Os.  */
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
index e9d7b7b9ee0a8..d17254904e50a 100644
--- a/gcc/testsuite/c-c++-common/strub-internal1.c
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=internal -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
index 8b8e15a51c71c..afc9189701f82 100644
--- a/gcc/testsuite/c-c++-common/strub-internal2.c
+++ b/gcc/testsuite/c-c++-common/strub-internal2.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=internal -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* g becomes STRUB_INTERNAL, because of the flag.  */
 static void
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
index 0a4a7539d3489..f410b268971a6 100644
--- a/gcc/testsuite/c-c++-common/strub-parms1.c
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 #include <stdarg.h>
 
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
index 147171d96d5a1..6f572115a88c3 100644
--- a/gcc/testsuite/c-c++-common/strub-parms2.c
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 #include <stdarg.h>
 
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
index 4e92682895a43..7383fea9ce881 100644
--- a/gcc/testsuite/c-c++-common/strub-parms3.c
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* Check that uses of a strub variable implicitly enables internal strub for
    publicly-visible functions, and causes the same transformations to their
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
index e2f9d8aebca58..d2b4b52c51e60 100644
--- a/gcc/testsuite/c-c++-common/strub-relaxed1.c
+++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* The difference between relaxed and strict in this case is that we accept the
    call from one internal-strub function to another.  Without the error,
diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
index 98474435d2e59..9e5a8e76b6c3d 100644
--- a/gcc/testsuite/c-c++-common/strub-relaxed2.c
+++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* The difference between relaxed and strict in this case is that we accept the
    call from one internal-strub function to another.  */
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
index 1de15342595e4..aaeba2a2159a9 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-O0 -fstrub=strict -fexceptions -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
index f9209c819004b..30cbdd819f176 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O0.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-O0 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
index bed1dcfb54a45..911fdfb6db9a5 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
index 6bf0071f52b93..9b23ee3ac3312 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* Check that the expected strub calls are issued.  */
 
diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
index 4732f515bf70c..4b3a8f843ea19 100644
--- a/gcc/testsuite/c-c++-common/strub-short-O3.c
+++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
index 8d6424c479a3a..3627a2406000b 100644
--- a/gcc/testsuite/c-c++-common/strub-short-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
    enter and leave calls within strub contexts, passing on the enclosing
diff --git a/gcc/testsuite/c-c++-common/strub-split-stack.c b/gcc/testsuite/c-c++-common/strub-split-stack.c
new file mode 100644
index 0000000000000..7a030cdb9e9e6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-split-stack.c
@@ -0,0 +1,10 @@
+/* { dg-do compile } */
+/* { dg-options "-fsplit-stack" } */
+/* { dg-require-effective-target strub } */
+/* { dg-require-effective-target split_stack } */
+
+void __attribute__ ((__strub__))
+f () {} /* { dg-message "not eligible|requested" } */
+
+void __attribute__ ((__strub__ ("internal")))
+g () {} /* { dg-message "not eligible|requested" } */
diff --git a/gcc/testsuite/c-c++-common/strub-strict1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
index 368522442066e..503eb1734e36f 100644
--- a/gcc/testsuite/c-c++-common/strub-strict1.c
+++ b/gcc/testsuite/c-c++-common/strub-strict1.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strubm" } */
+/* { dg-require-effective-target strub } */
 
 static int __attribute__ ((__strub__)) var;
 
diff --git a/gcc/testsuite/c-c++-common/strub-strict2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
index b4f2888321821..3bf1aa30b4af1 100644
--- a/gcc/testsuite/c-c++-common/strub-strict2.c
+++ b/gcc/testsuite/c-c++-common/strub-strict2.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strubm" } */
+/* { dg-require-effective-target strub } */
 
 static int __attribute__ ((__strub__)) var;
 
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
index e48e0610e079b..ba4b1623e281a 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O1.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 #include "strub-tail-O2.c"
 
diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
index 87cda7ab21b16..043813b1de467 100644
--- a/gcc/testsuite/c-c++-common/strub-tail-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* Check that the expected strub calls are issued.
    Tail calls are short-circuited at -O2+.  */
diff --git a/gcc/testsuite/c-c++-common/strub-unsupported-2.c b/gcc/testsuite/c-c++-common/strub-unsupported-2.c
new file mode 100644
index 0000000000000..3586f4f679dfe
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-unsupported-2.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+
+/* Check that, when strub is not supported (so no dg-required-effective-target
+   strub above), we report when pointers to strub functions are called.  This
+   cannot be part of strub-unsupported.c because errors in the strub-mode pass
+   prevent the main strub pass, where errors at calls are detected, from
+   running.  */
+
+void __attribute__ ((__strub__ ("at-calls"))) (*p) (void);
+
+void m () {
+  p (); /* { dg-message "unsupported" "" { target { ! strub } } } */
+}
diff --git a/gcc/testsuite/c-c++-common/strub-unsupported-3.c b/gcc/testsuite/c-c++-common/strub-unsupported-3.c
new file mode 100644
index 0000000000000..d6fb4c525c4a6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-unsupported-3.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+
+/* Check that, when strub is not supported (so no dg-required-effective-target
+   strub above), we report when strub functions that are not defined are
+   called.  This cannot be part of strub-unsupported-2.c because errors in the
+   strub-mode pass prevent the main strub pass, where errors at calls are
+   detected, from running.  */
+
+extern void __attribute__ ((__strub__))
+s (void); /* { dg-message "not eligible|requested" "" { target { ! strub } } } */
+
+extern void __attribute__ ((__strub__ ("internal")))
+t (void); /* { dg-message "not eligible|requested" "" { target { ! strub } } } */
+
+void m () {
+  s ();
+  t ();
+}
diff --git a/gcc/testsuite/c-c++-common/strub-unsupported.c b/gcc/testsuite/c-c++-common/strub-unsupported.c
new file mode 100644
index 0000000000000..cb5c4049495c4
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-unsupported.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+
+/* Check that, when strub is not supported (so no dg-required-effective-target
+   strub above), we report when strub functions are defined, and when they're
+   called in ways that would require changes.  */
+
+void __attribute__ ((__strub__))
+f (void) {} /* { dg-message "not eligible|requested" "" { target { ! strub } } } */
+
+void __attribute__ ((__strub__ ("internal")))
+g (void) {} /* { dg-message "not eligible|requested" "" { target { ! strub } } } */
+
+/* This only gets an error when called, see strub-unsupported-2.c.  */
+void __attribute__ ((__strub__ ("at-calls"))) (*p) (void);
+
+/* These too, see strub-unsupported-3.c.  */
+extern void __attribute__ ((__strub__))
+s (void);
+
+extern void __attribute__ ((__strub__ ("internal")))
+t (void);
diff --git a/gcc/testsuite/c-c++-common/strub-var1.c b/gcc/testsuite/c-c++-common/strub-var1.c
index eb6250fd39c90..67014aa5de84a 100644
--- a/gcc/testsuite/c-c++-common/strub-var1.c
+++ b/gcc/testsuite/c-c++-common/strub-var1.c
@@ -1,4 +1,5 @@
 /* { dg-do compile } */
+/* { dg-require-effective-target strub } */
 
 int __attribute__ ((strub)) x;
 float __attribute__ ((strub)) f;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
index b5e45ab0525ad..86dbee6746d1b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict" } */
+/* { dg-require-effective-target strub } */
 
 /* Check that strub and non-strub functions can be called from non-strub
    contexts, and that strub and callable functions can be called from strub
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
index 96aa7fe4b07f7..9da120f615645 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-callable2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict" } */
+/* { dg-require-effective-target strub } */
 
 /* Check that impermissible (cross-strub-context) calls are reported.  */
 
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
index 5e956cb1a9b6b..22056713cce4b 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+/* { dg-require-effective-target 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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
index 73d650292dfbf..a105c66d7a9c9 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* Check that, along with a strub implicitly-const function call, we issue an
    asm statement to make sure the watermark passed to it is held in memory
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
index 2584f1f974a58..386200c2784a4 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+/* { dg-require-effective-target 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,
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
index d819f54ec0230..817e9fa2118b6 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-const4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+/* { dg-require-effective-target 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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data1.c b/gcc/testsuite/c-c++-common/torture/strub-data1.c
index 7c27a2a1a6dca..132ab63ef733a 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* The pointed-to data enables strubbing if accessed.  */
 int __attribute__ ((__strub__)) var;
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data2.c b/gcc/testsuite/c-c++-common/torture/strub-data2.c
index e66d903780afd..b660702d26e75 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* The pointer itself is a strub variable, enabling internal strubbing when
    its value is used.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data3.c b/gcc/testsuite/c-c++-common/torture/strub-data3.c
index 5e08e0e58c658..fc44eef6f8fb5 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* The pointer itself is a strub variable, that would enable internal strubbing
    if its value was used.  Here, it's only overwritten, so no strub.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data4.c b/gcc/testsuite/c-c++-common/torture/strub-data4.c
index a818e7a38bb5f..85e2f59055b57 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* The pointer itself is a strub variable, that would enable internal strubbing
    if its value was used.  Here, it's only overwritten, so no strub.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
index ddb0b5c0543b0..0a5edac414df1 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict" } */
+/* { dg-require-effective-target strub } */
 
 /* It would be desirable to issue at least warnings for these.  */
 
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
index c165f312f16de..988954e7ed6bc 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 typedef void __attribute__ ((__strub__)) fntype ();
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
index 69fcff8d3763d..d3ca91389a700 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
index ff006224909bd..89b5979cf7b78 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 typedef void __attribute__ ((__strub__)) fntype (int, int, ...);
 fntype (*ptr);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
index 614b02228ba29..4917dda8826d9 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=relaxed" } */
+/* { dg-require-effective-target strub } */
 
 inline void __attribute__ ((strub ("internal"), always_inline))
 inl_int_ali (void)
diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
index f9a6b4a16faf8..c45903856d4ff 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=all" } */
+/* { dg-require-effective-target strub } */
 
 #include "strub-inlinable1.c"
 
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
index b4a7f3992bbaa..b0d6139f0a870 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict" } */
+/* { dg-require-effective-target strub } */
 
 typedef void ft (void);
 typedef void ft2 (int, int);
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
index ef634d351265f..1148c246f2059 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=relaxed -Wpedantic" } */
+/* { dg-require-effective-target strub } */
 
 /* C++ does not warn about the partial incompatibilities.
 
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
index e1f179e160e5c..06a72d86d2c58 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
@@ -1,6 +1,7 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=relaxed -Wpedantic -fpermissive" } */
 /* { dg-prune-output "command-line option .-fpermissive." } */
+/* { dg-require-effective-target strub } */
 
 /* See strub-ptrfn2.c.  */
 
diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
index 70b558afad040..83ea1af7056e7 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=relaxed" } */
+/* { dg-require-effective-target strub } */
 
 /* This is strub-ptrfn2.c without -Wpedantic.
 
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
index a262a086837b2..2643136f178cc 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* Check that, along with a strub pure function call, we issue an asm statement
    to make sure the watermark passed to it is not assumed to be unchanged.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
index 4c4bd50c209a0..8bda129b77dc6 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+/* { dg-require-effective-target strub } */
 
 /* Check that, along with a strub implicitly-pure function call, we issue an asm
    statement to make sure the watermark passed to it is not assumed to be
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
index ce195c6b1f1b6..00bcbdd097af8 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+/* { dg-require-effective-target 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.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
index 75cd54ccb5b5d..ea7c40e7912b4 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-pure4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+/* { dg-require-effective-target 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
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
index 7458b3fb54da5..fdf100428631d 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run1.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -1,5 +1,6 @@
 /* { dg-do run } */
 /* { dg-options "-fstrub=strict" } */
+/* { dg-require-effective-target strub } */
 
 /* Check that a non-strub function leaves a string behind in the stack, and that
    equivalent strub functions don't.  Avoid the use of red zones by avoiding
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
index 5d60a7775f4bb..1228a66599721 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run2.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -1,5 +1,6 @@
 /* { dg-do run } */
 /* { dg-options "-fstrub=strict" } */
+/* { dg-require-effective-target strub } */
 
 /* Check that a non-strub function leaves a string behind in the stack, and that
    equivalent strub functions don't.  Allow red zones to be used.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
index c2ad710858e87..e5047a988f5bf 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run3.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -1,6 +1,7 @@
 /* { dg-do run } */
 /* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
+/* { dg-require-effective-target strub } */
 
 /* Check that a non-strub function leaves a string behind in the stack, and that
    equivalent strub functions don't.  */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
index 3b36b8e5d68ef..0e84a4bab80fc 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
@@ -1,6 +1,7 @@
 /* { dg-do run } */
 /* { dg-options "-fstrub=all" } */
 /* { dg-require-effective-target alloca } */
+/* { dg-require-effective-target strub } */
 
 /* Check that multi-level, multi-inlined functions still get cleaned up as
    expected, without overwriting temporary stack allocations while they should
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4c.c b/gcc/testsuite/c-c++-common/torture/strub-run4c.c
index 57f9baf758ded..edc98486dc93a 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4c.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4c.c
@@ -1,5 +1,6 @@
 /* { dg-do run } */
 /* { dg-options "-fstrub=at-calls" } */
 /* { dg-require-effective-target alloca } */
+/* { dg-require-effective-target strub } */
 
 #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
index 08de3f1c3b17c..487ed08bb6606 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4d.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
@@ -1,6 +1,7 @@
 /* { dg-do run } */
 /* { dg-options "-fstrub=strict" } */
 /* { dg-require-effective-target alloca } */
+/* { dg-require-effective-target strub } */
 
 #define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
 
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4i.c b/gcc/testsuite/c-c++-common/torture/strub-run4i.c
index 459f6886c5499..a85447ffabfae 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-run4i.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-run4i.c
@@ -1,5 +1,6 @@
 /* { dg-do run } */
 /* { dg-options "-fstrub=internal" } */
 /* { dg-require-effective-target alloca } */
+/* { dg-require-effective-target strub } */
 
 #include "strub-run4.c"
diff --git a/gcc/testsuite/g++.dg/strub-run1.C b/gcc/testsuite/g++.dg/strub-run1.C
index 0d367fb83d09d..beb8b811f8fca 100644
--- a/gcc/testsuite/g++.dg/strub-run1.C
+++ b/gcc/testsuite/g++.dg/strub-run1.C
@@ -1,5 +1,6 @@
 // { dg-do run }
 // { dg-options "-fstrub=internal" }
+// { dg-require-effective-target strub }
 
 // Check that we don't get extra copies.
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init1.C b/gcc/testsuite/g++.dg/torture/strub-init1.C
index c226ab10ff651..6ae45fadd70ba 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init1.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+// { dg-require-effective-target strub }
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init2.C b/gcc/testsuite/g++.dg/torture/strub-init2.C
index a7911f1fa7212..8f4849c7fde78 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init2.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+// { dg-require-effective-target strub }
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/g++.dg/torture/strub-init3.C b/gcc/testsuite/g++.dg/torture/strub-init3.C
index 6ebebcd01e8ea..14f28e3c276bd 100644
--- a/gcc/testsuite/g++.dg/torture/strub-init3.C
+++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
@@ -1,5 +1,6 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
+// { dg-require-effective-target strub }
 
 extern int __attribute__((__strub__)) initializer ();
 
diff --git a/gcc/testsuite/gnat.dg/strub_access.adb b/gcc/testsuite/gnat.dg/strub_access.adb
index 29e6996ecf61c..488a2d64afe31 100644
--- a/gcc/testsuite/gnat.dg/strub_access.adb
+++ b/gcc/testsuite/gnat.dg/strub_access.adb
@@ -1,5 +1,6 @@
 --  { dg-do compile }
 --  { dg-options "-fstrub=relaxed -fdump-ipa-strubm" }
+--  { dg-require-effective-target strub }
 
 --  The main subprogram doesn't read from the automatic variable, but
 --  being an automatic variable, its presence should be enough for the
diff --git a/gcc/testsuite/gnat.dg/strub_access1.adb b/gcc/testsuite/gnat.dg/strub_access1.adb
index dae4706016436..4a8653c4d843f 100644
--- a/gcc/testsuite/gnat.dg/strub_access1.adb
+++ b/gcc/testsuite/gnat.dg/strub_access1.adb
@@ -1,5 +1,6 @@
 --  { dg-do compile }
 --  { dg-options "-fstrub=relaxed" }
+--  { dg-require-effective-target strub }
 
 --  Check that we reject 'Access of a strub variable whose type does
 --  not carry a strub modifier.
diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
index 10445d7cf8451..eb7826dc990f4 100644
--- a/gcc/testsuite/gnat.dg/strub_attr.adb
+++ b/gcc/testsuite/gnat.dg/strub_attr.adb
@@ -1,5 +1,6 @@
 --  { dg-do compile }
 --  { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" }
+--  { dg-require-effective-target strub }
 
 package body Strub_Attr is
    E : exception;
diff --git a/gcc/testsuite/gnat.dg/strub_disp.adb b/gcc/testsuite/gnat.dg/strub_disp.adb
index 3dbcc4a357cba..f23d4675def38 100644
--- a/gcc/testsuite/gnat.dg/strub_disp.adb
+++ b/gcc/testsuite/gnat.dg/strub_disp.adb
@@ -1,4 +1,5 @@
 --  { dg-do compile }
+--  { dg-require-effective-target strub }
 
 procedure Strub_Disp is
    package Foo is
diff --git a/gcc/testsuite/gnat.dg/strub_disp1.adb b/gcc/testsuite/gnat.dg/strub_disp1.adb
index 09756a74b7d81..9c4c7f696371d 100644
--- a/gcc/testsuite/gnat.dg/strub_disp1.adb
+++ b/gcc/testsuite/gnat.dg/strub_disp1.adb
@@ -1,5 +1,6 @@
 --  { dg-do compile }
 --  { dg-options "-fdump-ipa-strub" }
+--  { dg-require-effective-target strub }
 
 -- Check that at-calls dispatching calls are transformed.
 
diff --git a/gcc/testsuite/gnat.dg/strub_ind.adb b/gcc/testsuite/gnat.dg/strub_ind.adb
index da56acaa957d2..613db69305e05 100644
--- a/gcc/testsuite/gnat.dg/strub_ind.adb
+++ b/gcc/testsuite/gnat.dg/strub_ind.adb
@@ -1,5 +1,6 @@
 --  { dg-do compile }
 --  { dg-options "-fstrub=strict" }
+--  { dg-require-effective-target strub }
 
 --  This is essentially the same test as strub_attr.adb, 
 --  but applying attributes to access types as well.
diff --git a/gcc/testsuite/gnat.dg/strub_ind1.adb b/gcc/testsuite/gnat.dg/strub_ind1.adb
index 825e395e6819c..245b0a830f691 100644
--- a/gcc/testsuite/gnat.dg/strub_ind1.adb
+++ b/gcc/testsuite/gnat.dg/strub_ind1.adb
@@ -1,5 +1,6 @@
 --  { dg-do compile }
 --  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
+--  { dg-require-effective-target strub }
 
 --  This is essentially the same test as strub_attr.adb, 
 --  but with an explicit conversion.
diff --git a/gcc/testsuite/gnat.dg/strub_ind2.adb b/gcc/testsuite/gnat.dg/strub_ind2.adb
index e918b39263117..b9bfe50e9296e 100644
--- a/gcc/testsuite/gnat.dg/strub_ind2.adb
+++ b/gcc/testsuite/gnat.dg/strub_ind2.adb
@@ -1,5 +1,6 @@
 --  { dg-do compile }
 --  { dg-options "-fstrub=strict" }
+--  { dg-require-effective-target strub }
 
 --  This is essentially the same test as strub_attr.adb, 
 --  but with an explicit conversion.
diff --git a/gcc/testsuite/gnat.dg/strub_intf.adb b/gcc/testsuite/gnat.dg/strub_intf.adb
index 8f0212a75866f..f43854705d073 100644
--- a/gcc/testsuite/gnat.dg/strub_intf.adb
+++ b/gcc/testsuite/gnat.dg/strub_intf.adb
@@ -1,4 +1,5 @@
 --  { dg-do compile }
+--  { dg-require-effective-target strub }
 
 --  Check that strub mode mismatches between overrider and overridden
 --  subprograms are reported.
diff --git a/gcc/testsuite/gnat.dg/strub_intf1.adb b/gcc/testsuite/gnat.dg/strub_intf1.adb
index bf77321cef790..7a38a4c49ba8d 100644
--- a/gcc/testsuite/gnat.dg/strub_intf1.adb
+++ b/gcc/testsuite/gnat.dg/strub_intf1.adb
@@ -1,5 +1,6 @@
 --  { dg-do compile }
 --  { dg-options "-fdump-ipa-strub" }
+--  { dg-require-effective-target strub }
 
 -- Check that at-calls dispatching calls to interfaces are transformed.
 
diff --git a/gcc/testsuite/gnat.dg/strub_intf2.adb b/gcc/testsuite/gnat.dg/strub_intf2.adb
index e8880dbc43730..7992b7344fb87 100644
--- a/gcc/testsuite/gnat.dg/strub_intf2.adb
+++ b/gcc/testsuite/gnat.dg/strub_intf2.adb
@@ -1,4 +1,5 @@
 --  { dg-do compile }
+--  { dg-require-effective-target strub }
 
 --  Check that strub mode mismatches between overrider and overridden
 --  subprograms are reported even when the overriders for an
diff --git a/gcc/testsuite/gnat.dg/strub_renm.adb b/gcc/testsuite/gnat.dg/strub_renm.adb
index 217367e712d82..abfb120b51468 100644
--- a/gcc/testsuite/gnat.dg/strub_renm.adb
+++ b/gcc/testsuite/gnat.dg/strub_renm.adb
@@ -1,4 +1,5 @@
 --  { dg-do compile }
+--  { dg-require-effective-target strub }
 
 procedure Strub_Renm is
    procedure P (X : Integer);
diff --git a/gcc/testsuite/gnat.dg/strub_renm1.adb b/gcc/testsuite/gnat.dg/strub_renm1.adb
index a11adbfb5a9d6..68d3230b5356c 100644
--- a/gcc/testsuite/gnat.dg/strub_renm1.adb
+++ b/gcc/testsuite/gnat.dg/strub_renm1.adb
@@ -1,5 +1,6 @@
 --  { dg-do compile }
 --  { dg-options "-fstrub=relaxed -fdump-ipa-strub" }
+--  { dg-require-effective-target strub }
 
 procedure Strub_Renm1 is
    V : Integer := 0;
diff --git a/gcc/testsuite/gnat.dg/strub_renm2.adb b/gcc/testsuite/gnat.dg/strub_renm2.adb
index c488c20826fdb..3cb81ea03f763 100644
--- a/gcc/testsuite/gnat.dg/strub_renm2.adb
+++ b/gcc/testsuite/gnat.dg/strub_renm2.adb
@@ -1,5 +1,6 @@
 --  { dg-do compile }
 --  { dg-options "-fstrub=strict -fdump-ipa-strub" }
+--  { dg-require-effective-target strub }
 
 procedure Strub_Renm2 is
    V : Integer := 0;
diff --git a/gcc/testsuite/gnat.dg/strub_var.adb b/gcc/testsuite/gnat.dg/strub_var.adb
index 3d158de28031f..7c6affa06d4ab 100644
--- a/gcc/testsuite/gnat.dg/strub_var.adb
+++ b/gcc/testsuite/gnat.dg/strub_var.adb
@@ -1,5 +1,6 @@
 --  { dg-do compile }
 --  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
+--  { dg-require-effective-target strub }
 
 -- We don't read from the automatic variable, but being an automatic
 --  variable, its presence should be enough for the procedure to get
diff --git a/gcc/testsuite/gnat.dg/strub_var1.adb b/gcc/testsuite/gnat.dg/strub_var1.adb
index 6a504e09198b6..64b7e65fe9b0f 100644
--- a/gcc/testsuite/gnat.dg/strub_var1.adb
+++ b/gcc/testsuite/gnat.dg/strub_var1.adb
@@ -1,4 +1,5 @@
 --  { dg-do compile }
+--  { dg-require-effective-target strub }
 
 with Strub_Attr;
 procedure Strub_Var1 is
diff --git a/gcc/testsuite/lib/target-supports.exp b/gcc/testsuite/lib/target-supports.exp
index 3fcce6be49d6f..40a60c198cfe8 100644
--- a/gcc/testsuite/lib/target-supports.exp
+++ b/gcc/testsuite/lib/target-supports.exp
@@ -1302,6 +1302,13 @@ proc check_stack_check_available { stack_kind } {
     } "$stack_opt"]
 }
 
+# Return 1 if the target supports stack scrubbing.
+proc check_effective_target_strub {} {
+    return [check_no_compiler_messages strub assembly {
+	void __attribute__ ((__strub__)) fn (void) {}
+    } ""]
+}
+
 # Return 1 if compilation with -freorder-blocks-and-partition is error-free
 # for trivial code, 0 otherwise.  As some targets (ARM for example) only
 # warn when -fprofile-use is also supplied we test that combination too.
diff --git a/libgcc/Makefile.in b/libgcc/Makefile.in
index d8163c5af9903..3f77283490ef6 100644
--- a/libgcc/Makefile.in
+++ b/libgcc/Makefile.in
@@ -434,7 +434,7 @@ LIB2ADD += enable-execute-stack.c
 LIB2ADD += $(srcdir)/hardcfr.c
 
 # Stack scrubbing infrastructure.
-LIB2ADD += $(srcdir)/strub.c
+@HAVE_STRUB_SUPPORT@LIB2ADD += $(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/configure b/libgcc/configure
index cf149209652e3..567158955a329 100755
--- a/libgcc/configure
+++ b/libgcc/configure
@@ -593,6 +593,7 @@ asm_hidden_op
 extra_parts
 cpu_type
 get_gcc_base_ver
+HAVE_STRUB_SUPPORT
 thread_header
 tm_defines
 tm_file
@@ -5702,6 +5703,31 @@ esac
 
 
 
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for strub support" >&5
+$as_echo_n "checking for strub support... " >&6; }
+if ${libgcc_cv_strub_support+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+void __attribute__ ((__strub__)) fn (void) {}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  libgcc_cv_strub_support=yes
+else
+  libgcc_cv_strub_support=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $libgcc_cv_strub_support" >&5
+$as_echo "$libgcc_cv_strub_support" >&6; }
+if test "x$libgcc_cv_strub_support" != xno; then
+  HAVE_STRUB_SUPPORT=
+else
+  HAVE_STRUB_SUPPORT='# '
+fi
+
+
 # Determine what GCC version number to use in filesystem paths.
 
   get_gcc_base_ver="cat"
diff --git a/libgcc/configure.ac b/libgcc/configure.ac
index 2fc9d5d7c93e9..9c0e415501a80 100644
--- a/libgcc/configure.ac
+++ b/libgcc/configure.ac
@@ -694,6 +694,19 @@ AC_SUBST(tm_defines)
 # Map from thread model to thread header.
 GCC_AC_THREAD_HEADER([$target_thread_file])
 
+AC_CACHE_CHECK([for strub support],
+  [libgcc_cv_strub_support],
+  [AC_COMPILE_IFELSE(
+    [AC_LANG_SOURCE([void __attribute__ ((__strub__)) fn (void) {}])],
+    [libgcc_cv_strub_support=yes],
+    [libgcc_cv_strub_support=no])])
+if test "x$libgcc_cv_strub_support" != xno; then
+  HAVE_STRUB_SUPPORT=
+else
+  HAVE_STRUB_SUPPORT='# '
+fi
+AC_SUBST(HAVE_STRUB_SUPPORT)
+
 # Determine what GCC version number to use in filesystem paths.
 GCC_BASE_VER
 


-- 
Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
   Free Software Activist                   GNU Toolchain Engineer
More tolerance and less prejudice are key for inclusion and diversity
Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: Causes to nvptx bootstrap fail: [PATCH v5] Introduce strub: machine-independent stack scrubbing
  2023-12-06 22:12                                 ` Alexandre Oliva
  2023-12-07  3:33                                   ` [PATCH] strub: enable conditional support Alexandre Oliva
@ 2023-12-07  7:21                                   ` Richard Biener
  1 sibling, 0 replies; 59+ messages in thread
From: Richard Biener @ 2023-12-07  7:21 UTC (permalink / raw)
  To: Alexandre Oliva
  Cc: Thomas Schwinge, Tobias Burnus, gcc-patches, Jeremy Bennett,
	Craig Blackmore, Graham Markall, Martin Jambor, Jan Hubicka,
	Jim Wilson, Jeff Law, Jakub Jelinek, Tom de Vries

On Wed, Dec 6, 2023 at 11:12 PM Alexandre Oliva <oliva@adacore.com> wrote:
>
> On Dec  6, 2023, Thomas Schwinge <thomas@codesourcery.com> wrote:
>
> > As I understand things, this cannot be implemented (at the call site) for
> > nvptx, given that the callee's stack is not visible there: PTX is unusual
> > in that the concept of a "standard" stack isn't exposed.
>
> Not even when one PTX function calls another?  Interesting.  I'd hoped
> that with control over entering and leaving strub contexts, one could
> (manually) ensure they'd run in the same execution domain.  But if not
> even that is possible, it will render the current strub implementation
> entirely unusable for this target indeed.
>
> Now, it doesn't seem to me that the build errors being experienced have
> to do with that, but rather with lack of or incomplete support for
> __builtin_{frame,stack}_address().  Are those errors expected when using
> these builtins on this target?  I'd have expected them to compile, even
> if something went wrong at runtime.
>
>
> > Instead of allowing "strub" pieces that can be implemented, should this
> > whole machinery generally be disabled (forced '-fstrub=disable', or via a
> > new target hook?)?  The libgcc functions should then not get defined
> > (thus, linker error upon accidental use), or should just '__builtin_trap'
> > if that makes more sense?  Need an effective-target for the test cases.
>
> > Alternatively, we may also leave the generic middle end handling alive,
> > and 'sorry' (or similar) in the nvptx back end, as necessary?
>
> Disabling the runtime bits is easy, once we determine what condition we
> wish to test for.  I suppose testing for target support in the compiler,
> issuing a 'sorry' in case the feature is required, would provide
> something for libgcc configure and testsuite effective-target to test
> for and decide whether to enable runtime support and run the tests.

There's always the possibility to hardcode target triplets we don't support
of course.

Richard.

> --
> Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
>    Free Software Activist                   GNU Toolchain Engineer
> More tolerance and less prejudice are key for inclusion and diversity
> Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH] strub: enable conditional support
  2023-12-07  3:33                                   ` [PATCH] strub: enable conditional support Alexandre Oliva
@ 2023-12-07  7:24                                     ` Richard Biener
  2023-12-07 16:44                                     ` Thomas Schwinge
  1 sibling, 0 replies; 59+ messages in thread
From: Richard Biener @ 2023-12-07  7:24 UTC (permalink / raw)
  To: Alexandre Oliva
  Cc: Thomas Schwinge, Tobias Burnus, gcc-patches, Jeremy Bennett,
	Craig Blackmore, Graham Markall, Martin Jambor, Jan Hubicka,
	Jim Wilson, Jeff Law, Jakub Jelinek, Tom de Vries

On Thu, Dec 7, 2023 at 4:34 AM Alexandre Oliva <oliva@adacore.com> wrote:
>
> On Dec  6, 2023, Alexandre Oliva <oliva@adacore.com> wrote:
>
> > Disabling the runtime bits is easy, once we determine what condition we
> > wish to test for.  I suppose testing for target support in the compiler,
> > issuing a 'sorry' in case the feature is required, would provide
> > something for libgcc configure and testsuite effective-target to test
> > for and decide whether to enable runtime support and run the tests.
>
> Instead of doing something equivalent to an implicit -fstrub=disable,
> that would quietly compile without stack scrubbing, I thought it would
> be safer to be noisy if the feature is used (requested, really) when
> support is not available.
>
>
> Targets that don't expose callee stacks to callers, such as nvptx, as
> well as -fsplit-stack compilations, violate fundamental assumptions of
> the current strub implementation.  This patch enables targets to
> disable strub, and disables it when -fsplit-stack is enabled.
>
> When strub support is disabled, the testsuite will now skip strub
> tests, and libgcc will not build the strub runtime components.
>
> Regstrapped on x86_64-linux-gnu.  Also tested with an additional patch
> for i386.cc that mirrors the nvptx.cc change, to check that strub gets
> disabled without noisy test results.  Ok to install?

OK.

Thanks,
Richard.

>
> for  gcc/ChangeLog
>
>         * target.def (have_strub_support_for): New hook.
>         * doc/tm.texi.in: Document it.
>         * doc/tm.texi: Rebuild.
>         * ipa-strub.cc: Include target.h.
>         (strub_target_support_p): New.
>         (can_strub_p): Call it.  Test for no flag_split_stack.
>         (pass_ipa_strub::adjust_at_calls_call): Check for target
>         support.
>         * config/nvptx/nvptx.cc (TARGET_HAVE_STRUB_SUPPORT_FOR):
>         Disable.
>         * doc/sourcebuild.texi (strub): Document new effective
>         target.
>
> for  gcc/testsuite/ChangeLog
>
>         * gcc.dg/strub-split-stack.c: New.
>         * gcc.dg/strub-unsupported.c: New.
>         * gcc.dg/strub-unsupported-2.c: New.
>         * gcc.dg/strub-unsupported-3.c: New.
>         * lib/target-supports.exp (check_effective_target_strub): New.
>         * c-c++-common/strub-O0.c: Require effective target strub.
>         * c-c++-common/strub-O1.c: Likewise.
>         * c-c++-common/strub-O2.c: Likewise.
>         * c-c++-common/strub-O2fni.c: Likewise.
>         * c-c++-common/strub-O3.c: Likewise.
>         * c-c++-common/strub-O3fni.c: Likewise.
>         * c-c++-common/strub-Og.c: Likewise.
>         * c-c++-common/strub-Os.c: Likewise.
>         * c-c++-common/strub-all1.c: Likewise.
>         * c-c++-common/strub-all2.c: Likewise.
>         * c-c++-common/strub-apply1.c: Likewise.
>         * c-c++-common/strub-apply2.c: Likewise.
>         * c-c++-common/strub-apply3.c: Likewise.
>         * c-c++-common/strub-apply4.c: Likewise.
>         * c-c++-common/strub-at-calls1.c: Likewise.
>         * c-c++-common/strub-at-calls2.c: Likewise.
>         * c-c++-common/strub-defer-O1.c: Likewise.
>         * c-c++-common/strub-defer-O2.c: Likewise.
>         * c-c++-common/strub-defer-O3.c: Likewise.
>         * c-c++-common/strub-defer-Os.c: Likewise.
>         * c-c++-common/strub-internal1.c: Likewise.
>         * c-c++-common/strub-internal2.c: Likewise.
>         * c-c++-common/strub-parms1.c: Likewise.
>         * c-c++-common/strub-parms2.c: Likewise.
>         * c-c++-common/strub-parms3.c: Likewise.
>         * c-c++-common/strub-relaxed1.c: Likewise.
>         * c-c++-common/strub-relaxed2.c: Likewise.
>         * c-c++-common/strub-short-O0-exc.c: Likewise.
>         * c-c++-common/strub-short-O0.c: Likewise.
>         * c-c++-common/strub-short-O1.c: Likewise.
>         * c-c++-common/strub-short-O2.c: Likewise.
>         * c-c++-common/strub-short-O3.c: Likewise.
>         * c-c++-common/strub-short-Os.c: Likewise.
>         * c-c++-common/strub-strict1.c: Likewise.
>         * c-c++-common/strub-strict2.c: Likewise.
>         * c-c++-common/strub-tail-O1.c: Likewise.
>         * c-c++-common/strub-tail-O2.c: Likewise.
>         * c-c++-common/strub-var1.c: Likewise.
>         * c-c++-common/torture/strub-callable1.c: Likewise.
>         * c-c++-common/torture/strub-callable2.c: Likewise.
>         * c-c++-common/torture/strub-const1.c: Likewise.
>         * c-c++-common/torture/strub-const2.c: Likewise.
>         * c-c++-common/torture/strub-const3.c: Likewise.
>         * c-c++-common/torture/strub-const4.c: Likewise.
>         * c-c++-common/torture/strub-data1.c: Likewise.
>         * c-c++-common/torture/strub-data2.c: Likewise.
>         * c-c++-common/torture/strub-data3.c: Likewise.
>         * c-c++-common/torture/strub-data4.c: Likewise.
>         * c-c++-common/torture/strub-data5.c: Likewise.
>         * c-c++-common/torture/strub-indcall1.c: Likewise.
>         * c-c++-common/torture/strub-indcall2.c: Likewise.
>         * c-c++-common/torture/strub-indcall3.c: Likewise.
>         * c-c++-common/torture/strub-inlinable1.c: Likewise.
>         * c-c++-common/torture/strub-inlinable2.c: Likewise.
>         * c-c++-common/torture/strub-ptrfn1.c: Likewise.
>         * c-c++-common/torture/strub-ptrfn2.c: Likewise.
>         * c-c++-common/torture/strub-ptrfn3.c: Likewise.
>         * c-c++-common/torture/strub-ptrfn4.c: Likewise.
>         * c-c++-common/torture/strub-pure1.c: Likewise.
>         * c-c++-common/torture/strub-pure2.c: Likewise.
>         * c-c++-common/torture/strub-pure3.c: Likewise.
>         * c-c++-common/torture/strub-pure4.c: Likewise.
>         * c-c++-common/torture/strub-run1.c: Likewise.
>         * c-c++-common/torture/strub-run2.c: Likewise.
>         * c-c++-common/torture/strub-run3.c: Likewise.
>         * c-c++-common/torture/strub-run4.c: Likewise.
>         * c-c++-common/torture/strub-run4c.c: Likewise.
>         * c-c++-common/torture/strub-run4d.c: Likewise.
>         * c-c++-common/torture/strub-run4i.c: Likewise.
>         * g++.dg/strub-run1.C: Likewise.
>         * g++.dg/torture/strub-init1.C: Likewise.
>         * g++.dg/torture/strub-init2.C: Likewise.
>         * g++.dg/torture/strub-init3.C: Likewise.
>         * gnat.dg/strub_attr.adb: Likewise.
>         * gnat.dg/strub_ind.adb: Likewise.
>         * gnat.dg/strub_access.adb: Likewise.
>         * gnat.dg/strub_access1.adb: Likewise.
>         * gnat.dg/strub_disp.adb: Likewise.
>         * gnat.dg/strub_disp1.adb: Likewise.
>         * gnat.dg/strub_ind1.adb: Likewise.
>         * gnat.dg/strub_ind2.adb: Likewise.
>         * gnat.dg/strub_intf.adb: Likewise.
>         * gnat.dg/strub_intf1.adb: Likewise.
>         * gnat.dg/strub_intf2.adb: Likewise.
>         * gnat.dg/strub_renm.adb: Likewise.
>         * gnat.dg/strub_renm1.adb: Likewise.
>         * gnat.dg/strub_renm2.adb: Likewise.
>         * gnat.dg/strub_var.adb: Likewise.
>         * gnat.dg/strub_var1.adb: Likewise.
>
> for  libgcc/ChangeLog
>
>         * configure.ac: Check for strub support.
>         * configure: Rebuilt.
>         * Makefile.in: Compile strub.c conditionally.
> ---
>  gcc/config/nvptx/nvptx.cc                          |    3 +
>  gcc/doc/sourcebuild.texi                           |    3 +
>  gcc/doc/tm.texi                                    |    6 ++
>  gcc/doc/tm.texi.in                                 |    2 +
>  gcc/ipa-strub.cc                                   |   54 +++++++++++++++++++-
>  gcc/target.def                                     |    8 +++
>  gcc/testsuite/c-c++-common/strub-O0.c              |    1
>  gcc/testsuite/c-c++-common/strub-O1.c              |    1
>  gcc/testsuite/c-c++-common/strub-O2.c              |    1
>  gcc/testsuite/c-c++-common/strub-O2fni.c           |    1
>  gcc/testsuite/c-c++-common/strub-O3.c              |    1
>  gcc/testsuite/c-c++-common/strub-O3fni.c           |    1
>  gcc/testsuite/c-c++-common/strub-Og.c              |    1
>  gcc/testsuite/c-c++-common/strub-Os.c              |    1
>  gcc/testsuite/c-c++-common/strub-all1.c            |    1
>  gcc/testsuite/c-c++-common/strub-all2.c            |    1
>  gcc/testsuite/c-c++-common/strub-apply1.c          |    1
>  gcc/testsuite/c-c++-common/strub-apply2.c          |    1
>  gcc/testsuite/c-c++-common/strub-apply3.c          |    1
>  gcc/testsuite/c-c++-common/strub-apply4.c          |    1
>  gcc/testsuite/c-c++-common/strub-at-calls1.c       |    1
>  gcc/testsuite/c-c++-common/strub-at-calls2.c       |    1
>  gcc/testsuite/c-c++-common/strub-defer-O1.c        |    1
>  gcc/testsuite/c-c++-common/strub-defer-O2.c        |    1
>  gcc/testsuite/c-c++-common/strub-defer-O3.c        |    1
>  gcc/testsuite/c-c++-common/strub-defer-Os.c        |    1
>  gcc/testsuite/c-c++-common/strub-internal1.c       |    1
>  gcc/testsuite/c-c++-common/strub-internal2.c       |    1
>  gcc/testsuite/c-c++-common/strub-parms1.c          |    1
>  gcc/testsuite/c-c++-common/strub-parms2.c          |    1
>  gcc/testsuite/c-c++-common/strub-parms3.c          |    1
>  gcc/testsuite/c-c++-common/strub-relaxed1.c        |    1
>  gcc/testsuite/c-c++-common/strub-relaxed2.c        |    1
>  gcc/testsuite/c-c++-common/strub-short-O0-exc.c    |    1
>  gcc/testsuite/c-c++-common/strub-short-O0.c        |    1
>  gcc/testsuite/c-c++-common/strub-short-O1.c        |    1
>  gcc/testsuite/c-c++-common/strub-short-O2.c        |    1
>  gcc/testsuite/c-c++-common/strub-short-O3.c        |    1
>  gcc/testsuite/c-c++-common/strub-short-Os.c        |    1
>  gcc/testsuite/c-c++-common/strub-split-stack.c     |   10 ++++
>  gcc/testsuite/c-c++-common/strub-strict1.c         |    1
>  gcc/testsuite/c-c++-common/strub-strict2.c         |    1
>  gcc/testsuite/c-c++-common/strub-tail-O1.c         |    1
>  gcc/testsuite/c-c++-common/strub-tail-O2.c         |    1
>  gcc/testsuite/c-c++-common/strub-unsupported-2.c   |   13 +++++
>  gcc/testsuite/c-c++-common/strub-unsupported-3.c   |   18 +++++++
>  gcc/testsuite/c-c++-common/strub-unsupported.c     |   21 ++++++++
>  gcc/testsuite/c-c++-common/strub-var1.c            |    1
>  .../c-c++-common/torture/strub-callable1.c         |    1
>  .../c-c++-common/torture/strub-callable2.c         |    1
>  gcc/testsuite/c-c++-common/torture/strub-const1.c  |    1
>  gcc/testsuite/c-c++-common/torture/strub-const2.c  |    1
>  gcc/testsuite/c-c++-common/torture/strub-const3.c  |    1
>  gcc/testsuite/c-c++-common/torture/strub-const4.c  |    1
>  gcc/testsuite/c-c++-common/torture/strub-data1.c   |    1
>  gcc/testsuite/c-c++-common/torture/strub-data2.c   |    1
>  gcc/testsuite/c-c++-common/torture/strub-data3.c   |    1
>  gcc/testsuite/c-c++-common/torture/strub-data4.c   |    1
>  gcc/testsuite/c-c++-common/torture/strub-data5.c   |    1
>  .../c-c++-common/torture/strub-indcall1.c          |    1
>  .../c-c++-common/torture/strub-indcall2.c          |    1
>  .../c-c++-common/torture/strub-indcall3.c          |    1
>  .../c-c++-common/torture/strub-inlinable1.c        |    1
>  .../c-c++-common/torture/strub-inlinable2.c        |    1
>  gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |    1
>  gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |    1
>  gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c  |    1
>  gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c  |    1
>  gcc/testsuite/c-c++-common/torture/strub-pure1.c   |    1
>  gcc/testsuite/c-c++-common/torture/strub-pure2.c   |    1
>  gcc/testsuite/c-c++-common/torture/strub-pure3.c   |    1
>  gcc/testsuite/c-c++-common/torture/strub-pure4.c   |    1
>  gcc/testsuite/c-c++-common/torture/strub-run1.c    |    1
>  gcc/testsuite/c-c++-common/torture/strub-run2.c    |    1
>  gcc/testsuite/c-c++-common/torture/strub-run3.c    |    1
>  gcc/testsuite/c-c++-common/torture/strub-run4.c    |    1
>  gcc/testsuite/c-c++-common/torture/strub-run4c.c   |    1
>  gcc/testsuite/c-c++-common/torture/strub-run4d.c   |    1
>  gcc/testsuite/c-c++-common/torture/strub-run4i.c   |    1
>  gcc/testsuite/g++.dg/strub-run1.C                  |    1
>  gcc/testsuite/g++.dg/torture/strub-init1.C         |    1
>  gcc/testsuite/g++.dg/torture/strub-init2.C         |    1
>  gcc/testsuite/g++.dg/torture/strub-init3.C         |    1
>  gcc/testsuite/gnat.dg/strub_access.adb             |    1
>  gcc/testsuite/gnat.dg/strub_access1.adb            |    1
>  gcc/testsuite/gnat.dg/strub_attr.adb               |    1
>  gcc/testsuite/gnat.dg/strub_disp.adb               |    1
>  gcc/testsuite/gnat.dg/strub_disp1.adb              |    1
>  gcc/testsuite/gnat.dg/strub_ind.adb                |    1
>  gcc/testsuite/gnat.dg/strub_ind1.adb               |    1
>  gcc/testsuite/gnat.dg/strub_ind2.adb               |    1
>  gcc/testsuite/gnat.dg/strub_intf.adb               |    1
>  gcc/testsuite/gnat.dg/strub_intf1.adb              |    1
>  gcc/testsuite/gnat.dg/strub_intf2.adb              |    1
>  gcc/testsuite/gnat.dg/strub_renm.adb               |    1
>  gcc/testsuite/gnat.dg/strub_renm1.adb              |    1
>  gcc/testsuite/gnat.dg/strub_renm2.adb              |    1
>  gcc/testsuite/gnat.dg/strub_var.adb                |    1
>  gcc/testsuite/gnat.dg/strub_var1.adb               |    1
>  gcc/testsuite/lib/target-supports.exp              |    7 +++
>  libgcc/Makefile.in                                 |    2 -
>  libgcc/configure                                   |   26 ++++++++++
>  libgcc/configure.ac                                |   13 +++++
>  103 files changed, 272 insertions(+), 3 deletions(-)
>  create mode 100644 gcc/testsuite/c-c++-common/strub-split-stack.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-unsupported-2.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-unsupported-3.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-unsupported.c
>
> diff --git a/gcc/config/nvptx/nvptx.cc b/gcc/config/nvptx/nvptx.cc
> index ae20802c87996..3fb1deb70fda1 100644
> --- a/gcc/config/nvptx/nvptx.cc
> +++ b/gcc/config/nvptx/nvptx.cc
> @@ -7789,6 +7789,9 @@ nvptx_asm_output_def_from_decls (FILE *stream, tree name, tree value)
>  #undef TARGET_LIBC_HAS_FUNCTION
>  #define TARGET_LIBC_HAS_FUNCTION nvptx_libc_has_function
>
> +#undef TARGET_HAVE_STRUB_SUPPORT_FOR
> +#define TARGET_HAVE_STRUB_SUPPORT_FOR hook_bool_tree_false
> +
>  struct gcc_target targetm = TARGET_INITIALIZER;
>
>  #include "gt-nvptx.h"
> diff --git a/gcc/doc/sourcebuild.texi b/gcc/doc/sourcebuild.texi
> index c990902685417..26a7e9c350703 100644
> --- a/gcc/doc/sourcebuild.texi
> +++ b/gcc/doc/sourcebuild.texi
> @@ -2983,6 +2983,9 @@ Target supports statically linking @samp{libgfortran}.
>  @item string_merging
>  Target supports merging string constants at link time.
>
> +@item strub
> +Target supports attribute @code{strub} for stack scrubbing.
> +
>  @item ucn
>  Target supports compiling and assembling UCN.
>
> diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
> index 89a1735dd7992..768ada0af5222 100644
> --- a/gcc/doc/tm.texi
> +++ b/gcc/doc/tm.texi
> @@ -3450,6 +3450,12 @@ in DWARF 2 debug information.  The default is zero.  A different value
>  may reduce the size of debug information on some ports.
>  @end defmac
>
> +@deftypefn {Target Hook} bool TARGET_HAVE_STRUB_SUPPORT_FOR (tree)
> +Returns true if the target supports stack scrubbing for the given function
> +or type, otherwise return false.  The default implementation always returns
> +true.
> +@end deftypefn
> +
>  @defmac TARGET_STRUB_USE_DYNAMIC_ARRAY
>  If defined to nonzero, @code{__strub_leave} will allocate a dynamic
>  array covering the stack range that needs scrubbing before clearing it.
> diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
> index ebc1d3de5caaa..4fe0805394ea4 100644
> --- a/gcc/doc/tm.texi.in
> +++ b/gcc/doc/tm.texi.in
> @@ -2686,6 +2686,8 @@ in DWARF 2 debug information.  The default is zero.  A different value
>  may reduce the size of debug information on some ports.
>  @end defmac
>
> +@hook TARGET_HAVE_STRUB_SUPPORT_FOR
> +
>  @defmac TARGET_STRUB_USE_DYNAMIC_ARRAY
>  If defined to nonzero, @code{__strub_leave} will allocate a dynamic
>  array covering the stack range that needs scrubbing before clearing it.
> diff --git a/gcc/ipa-strub.cc b/gcc/ipa-strub.cc
> index 293bec132b885..2afb7a455751d 100644
> --- a/gcc/ipa-strub.cc
> +++ b/gcc/ipa-strub.cc
> @@ -60,6 +60,7 @@ along with GCC; see the file COPYING3.  If not see
>  #include "ipa-strub.h"
>  #include "symtab-thunks.h"
>  #include "attr-fnspec.h"
> +#include "target.h"
>
>  /* This file introduces two passes that, together, implement
>     machine-independent stack scrubbing, strub for short.  It arranges
> @@ -631,17 +632,60 @@ strub_always_inline_p (cgraph_node *node)
>    return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
>  }
>
> +/* Return TRUE iff the target has strub support for T, a function
> +   decl, or a type used in an indirect call, and optionally REPORT the
> +   reasons for ineligibility.  If T is a type and error REPORTing is
> +   enabled, the LOCation (of the indirect call) should be provided.  */
> +static inline bool
> +strub_target_support_p (tree t, bool report = false,
> +                       location_t loc = UNKNOWN_LOCATION)
> +{
> +  bool result = true;
> +
> +  if (!targetm.have_strub_support_for (t))
> +    {
> +      result = false;
> +
> +      if (!report)
> +       return result;
> +
> +      if (DECL_P (t))
> +       sorry_at (DECL_SOURCE_LOCATION (t),
> +                 "%qD is not eligible for %<strub%>"
> +                 " on the target system", t);
> +      else
> +       sorry_at (loc,
> +                 "unsupported %<strub%> call"
> +                 " on the target system");
> +    }
> +
> +  return result;
> +}
> +
>  /* Return TRUE iff NODE is potentially eligible for any strub-enabled mode, and
>     optionally REPORT the reasons for ineligibility.  */
>
>  static inline bool
>  can_strub_p (cgraph_node *node, bool report = false)
>  {
> -  bool result = true;
> +  bool result = strub_target_support_p (node->decl, report);
>
> -  if (!report && strub_always_inline_p (node))
> +  if (!report && (!result || strub_always_inline_p (node)))
>      return result;
>
> +  if (flag_split_stack)
> +    {
> +      result = false;
> +
> +      if (!report)
> +       return result;
> +
> +      sorry_at (DECL_SOURCE_LOCATION (node->decl),
> +               "%qD is not eligible for %<strub%>"
> +               " because %<-fsplit-stack%> is enabled",
> +               node->decl);
> +    }
> +
>    if (lookup_attribute ("noipa", DECL_ATTRIBUTES (node->decl)))
>      {
>        result = false;
> @@ -2417,6 +2461,12 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args,
>                          && (TREE_TYPE (gimple_call_arg (ocall, named_args))
>                              == get_pwmt ())));
>
> +  tree tsup;
> +  if (!(tsup = gimple_call_fndecl (ocall)))
> +    tsup = TREE_TYPE (TREE_TYPE (gimple_call_fn (ocall)));
> +  if (!strub_target_support_p (tsup, true, gimple_location (ocall)))
> +    return;
> +
>    /* 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.  */
> diff --git a/gcc/target.def b/gcc/target.def
> index 52b83e091b94b..08218f3a42adf 100644
> --- a/gcc/target.def
> +++ b/gcc/target.def
> @@ -4457,6 +4457,14 @@ otherwise return false.  The default implementation always returns true.",
>   bool, (void),
>   hook_bool_void_true)
>
> +DEFHOOK
> +(have_strub_support_for,
> + "Returns true if the target supports stack scrubbing for the given function\n\
> +or type, otherwise return false.  The default implementation always returns\n\
> +true.",
> + bool, (tree),
> + hook_bool_tree_true)
> +
>  DEFHOOK
>  (have_speculation_safe_value,
>  "This hook is used to determine the level of target support for\n\
> diff --git a/gcc/testsuite/c-c++-common/strub-O0.c b/gcc/testsuite/c-c++-common/strub-O0.c
> index c7a79a6ea0d8a..f0a3f7b4c6f9a 100644
> --- a/gcc/testsuite/c-c++-common/strub-O0.c
> +++ b/gcc/testsuite/c-c++-common/strub-O0.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O0 -fstrub=strict -fdump-rtl-expand" } */
> +/* { dg-require-effective-target strub } */
>
>  /* At -O0, none of the strub builtins are expanded inline.  */
>
> diff --git a/gcc/testsuite/c-c++-common/strub-O1.c b/gcc/testsuite/c-c++-common/strub-O1.c
> index 96285c975d98e..50403426b18f2 100644
> --- a/gcc/testsuite/c-c++-common/strub-O1.c
> +++ b/gcc/testsuite/c-c++-common/strub-O1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O1 -fstrub=strict -fdump-rtl-expand" } */
> +/* { dg-require-effective-target strub } */
>
>  /* At -O1, without -fno-inline, we fully expand enter, but neither update nor
>     leave.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
> index 8edc0d8aa1321..37e02998e318e 100644
> --- a/gcc/testsuite/c-c++-common/strub-O2.c
> +++ b/gcc/testsuite/c-c++-common/strub-O2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
> +/* { dg-require-effective-target strub } */
>
>  /* At -O2, without -fno-inline, we fully expand enter and update, and add a test
>     around the leave call.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
> index c6d900cf3c45b..905e2c6b2ffca 100644
> --- a/gcc/testsuite/c-c++-common/strub-O2fni.c
> +++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
> +/* { dg-require-effective-target strub } */
>
>  /* With -fno-inline, none of the strub builtins are inlined.  */
>
> diff --git a/gcc/testsuite/c-c++-common/strub-O3.c b/gcc/testsuite/c-c++-common/strub-O3.c
> index 33ee465e51cb6..3bbf132bdf1ea 100644
> --- a/gcc/testsuite/c-c++-common/strub-O3.c
> +++ b/gcc/testsuite/c-c++-common/strub-O3.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand" } */
> +/* { dg-require-effective-target strub } */
>
>  int __attribute__ ((__strub__)) var;
>
> diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
> index 2936f82079e18..c46fce38e5c91 100644
> --- a/gcc/testsuite/c-c++-common/strub-O3fni.c
> +++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
> +/* { dg-require-effective-target strub } */
>
>  /* With -fno-inline, none of the strub builtins are inlined.  */
>
> diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
> index 479746e57d87e..3b8eb19765cd6 100644
> --- a/gcc/testsuite/c-c++-common/strub-Og.c
> +++ b/gcc/testsuite/c-c++-common/strub-Og.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-Og -fstrub=strict -fdump-rtl-expand" } */
> +/* { dg-require-effective-target strub } */
>
>  /* At -Og, without -fno-inline, we fully expand enter, but neither update nor
>     leave.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
> index 2241d4ea07f27..8cfb253d6764c 100644
> --- a/gcc/testsuite/c-c++-common/strub-Os.c
> +++ b/gcc/testsuite/c-c++-common/strub-Os.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
> +/* { dg-require-effective-target strub } */
>
>  /* At -Os, without -fno-inline, we fully expand enter, and also update.  The
>     expanded update might be larger than a call proper, but argument saving and
> diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
> index a322bcc5da606..2037f681f2973 100644
> --- a/gcc/testsuite/c-c++-common/strub-all1.c
> +++ b/gcc/testsuite/c-c++-common/strub-all1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=all -fdump-ipa-strubm -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
>     strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
> index db60026d0e080..c026e7d9d289b 100644
> --- a/gcc/testsuite/c-c++-common/strub-all2.c
> +++ b/gcc/testsuite/c-c++-common/strub-all2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=all -fdump-ipa-strubm -fdump-ipa-strub" } */
> +/* { dg-require-effective-target 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
> diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
> index 2f462adc1efe0..3edc89c54eea1 100644
> --- a/gcc/testsuite/c-c++-common/strub-apply1.c
> +++ b/gcc/testsuite/c-c++-common/strub-apply1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict" } */
> +/* { dg-require-effective-target strub } */
>
>  void __attribute__ ((__strub__ ("callable")))
>  apply_function (void *args)
> diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
> index a5d7551f5da5c..838fc75273450 100644
> --- a/gcc/testsuite/c-c++-common/strub-apply2.c
> +++ b/gcc/testsuite/c-c++-common/strub-apply2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict" } */
> +/* { dg-require-effective-target strub } */
>
>  extern void __attribute__ ((__strub__))
>  apply_function (void *args);
> diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
> index 64422a0d1e880..0206e4d930e7d 100644
> --- a/gcc/testsuite/c-c++-common/strub-apply3.c
> +++ b/gcc/testsuite/c-c++-common/strub-apply3.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict" } */
> +/* { dg-require-effective-target strub } */
>
>  void __attribute__ ((__strub__))
>  apply_function (void *args)
> diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
> index 15ffaa031b899..e82504728b2c6 100644
> --- a/gcc/testsuite/c-c++-common/strub-apply4.c
> +++ b/gcc/testsuite/c-c++-common/strub-apply4.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O2 -fstrub=strict -fdump-ipa-strubm" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that implicit enabling of strub mode selects internal strub when the
>     function uses __builtin_apply_args, that prevents the optimization to
> diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
> index b70843b4215a4..a20acc0a48a58 100644
> --- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
> +++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=at-calls -fdump-ipa-strubm -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
>     strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
> index 97a3988a6b922..7915b33a39a0a 100644
> --- a/gcc/testsuite/c-c++-common/strub-at-calls2.c
> +++ b/gcc/testsuite/c-c++-common/strub-at-calls2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=at-calls -fdump-ipa-strubm -fdump-ipa-strub" } */
> +/* { dg-require-effective-target 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
> diff --git a/gcc/testsuite/c-c++-common/strub-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
> index 3d73431b3dcd3..3689998b5a323 100644
> --- a/gcc/testsuite/c-c++-common/strub-defer-O1.c
> +++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=strict -O1" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that a strub function called by another strub function does NOT defer
>     the strubbing to its caller at -O1.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
> index fddf3c745e7e6..9e01949db6be9 100644
> --- a/gcc/testsuite/c-c++-common/strub-defer-O2.c
> +++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=strict -O2" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that a strub function called by another strub function does NOT defer
>     the strubbing to its caller at -O2.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
> index 7ebc65b58dd72..40ee8edd1e0e6 100644
> --- a/gcc/testsuite/c-c++-common/strub-defer-O3.c
> +++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
> @@ -1,5 +1,6 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=strict -O3" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that a strub function called by another strub function defers the
>     strubbing to its caller at -O3.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
> index fbaf85fe0fafe..67ea9f0463975 100644
> --- a/gcc/testsuite/c-c++-common/strub-defer-Os.c
> +++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
> @@ -1,5 +1,6 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=strict -Os" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that a strub function called by another strub function defers the
>     strubbing to its caller at -Os.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
> index e9d7b7b9ee0a8..d17254904e50a 100644
> --- a/gcc/testsuite/c-c++-common/strub-internal1.c
> +++ b/gcc/testsuite/c-c++-common/strub-internal1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=internal -fdump-ipa-strubm -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
>     strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
> index 8b8e15a51c71c..afc9189701f82 100644
> --- a/gcc/testsuite/c-c++-common/strub-internal2.c
> +++ b/gcc/testsuite/c-c++-common/strub-internal2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=internal -fdump-ipa-strubm -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* g becomes STRUB_INTERNAL, because of the flag.  */
>  static void
> diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
> index 0a4a7539d3489..f410b268971a6 100644
> --- a/gcc/testsuite/c-c++-common/strub-parms1.c
> +++ b/gcc/testsuite/c-c++-common/strub-parms1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  #include <stdarg.h>
>
> diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
> index 147171d96d5a1..6f572115a88c3 100644
> --- a/gcc/testsuite/c-c++-common/strub-parms2.c
> +++ b/gcc/testsuite/c-c++-common/strub-parms2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  #include <stdarg.h>
>
> diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
> index 4e92682895a43..7383fea9ce881 100644
> --- a/gcc/testsuite/c-c++-common/strub-parms3.c
> +++ b/gcc/testsuite/c-c++-common/strub-parms3.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that uses of a strub variable implicitly enables internal strub for
>     publicly-visible functions, and causes the same transformations to their
> diff --git a/gcc/testsuite/c-c++-common/strub-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
> index e2f9d8aebca58..d2b4b52c51e60 100644
> --- a/gcc/testsuite/c-c++-common/strub-relaxed1.c
> +++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* The difference between relaxed and strict in this case is that we accept the
>     call from one internal-strub function to another.  Without the error,
> diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
> index 98474435d2e59..9e5a8e76b6c3d 100644
> --- a/gcc/testsuite/c-c++-common/strub-relaxed2.c
> +++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* The difference between relaxed and strict in this case is that we accept the
>     call from one internal-strub function to another.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
> index 1de15342595e4..aaeba2a2159a9 100644
> --- a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
> +++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O0 -fstrub=strict -fexceptions -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that the expected strub calls are issued.  */
>
> diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
> index f9209c819004b..30cbdd819f176 100644
> --- a/gcc/testsuite/c-c++-common/strub-short-O0.c
> +++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O0 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that the expected strub calls are issued.  */
>
> diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
> index bed1dcfb54a45..911fdfb6db9a5 100644
> --- a/gcc/testsuite/c-c++-common/strub-short-O1.c
> +++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that the expected strub calls are issued.  */
>
> diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
> index 6bf0071f52b93..9b23ee3ac3312 100644
> --- a/gcc/testsuite/c-c++-common/strub-short-O2.c
> +++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that the expected strub calls are issued.  */
>
> diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
> index 4732f515bf70c..4b3a8f843ea19 100644
> --- a/gcc/testsuite/c-c++-common/strub-short-O3.c
> +++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
>     enter and leave calls within strub contexts, passing on the enclosing
> diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
> index 8d6424c479a3a..3627a2406000b 100644
> --- a/gcc/testsuite/c-c++-common/strub-short-Os.c
> +++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
>     enter and leave calls within strub contexts, passing on the enclosing
> diff --git a/gcc/testsuite/c-c++-common/strub-split-stack.c b/gcc/testsuite/c-c++-common/strub-split-stack.c
> new file mode 100644
> index 0000000000000..7a030cdb9e9e6
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-split-stack.c
> @@ -0,0 +1,10 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fsplit-stack" } */
> +/* { dg-require-effective-target strub } */
> +/* { dg-require-effective-target split_stack } */
> +
> +void __attribute__ ((__strub__))
> +f () {} /* { dg-message "not eligible|requested" } */
> +
> +void __attribute__ ((__strub__ ("internal")))
> +g () {} /* { dg-message "not eligible|requested" } */
> diff --git a/gcc/testsuite/c-c++-common/strub-strict1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
> index 368522442066e..503eb1734e36f 100644
> --- a/gcc/testsuite/c-c++-common/strub-strict1.c
> +++ b/gcc/testsuite/c-c++-common/strub-strict1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strubm" } */
> +/* { dg-require-effective-target strub } */
>
>  static int __attribute__ ((__strub__)) var;
>
> diff --git a/gcc/testsuite/c-c++-common/strub-strict2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
> index b4f2888321821..3bf1aa30b4af1 100644
> --- a/gcc/testsuite/c-c++-common/strub-strict2.c
> +++ b/gcc/testsuite/c-c++-common/strub-strict2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strubm" } */
> +/* { dg-require-effective-target strub } */
>
>  static int __attribute__ ((__strub__)) var;
>
> diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
> index e48e0610e079b..ba4b1623e281a 100644
> --- a/gcc/testsuite/c-c++-common/strub-tail-O1.c
> +++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  #include "strub-tail-O2.c"
>
> diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
> index 87cda7ab21b16..043813b1de467 100644
> --- a/gcc/testsuite/c-c++-common/strub-tail-O2.c
> +++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that the expected strub calls are issued.
>     Tail calls are short-circuited at -O2+.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-unsupported-2.c b/gcc/testsuite/c-c++-common/strub-unsupported-2.c
> new file mode 100644
> index 0000000000000..3586f4f679dfe
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-unsupported-2.c
> @@ -0,0 +1,13 @@
> +/* { dg-do compile } */
> +
> +/* Check that, when strub is not supported (so no dg-required-effective-target
> +   strub above), we report when pointers to strub functions are called.  This
> +   cannot be part of strub-unsupported.c because errors in the strub-mode pass
> +   prevent the main strub pass, where errors at calls are detected, from
> +   running.  */
> +
> +void __attribute__ ((__strub__ ("at-calls"))) (*p) (void);
> +
> +void m () {
> +  p (); /* { dg-message "unsupported" "" { target { ! strub } } } */
> +}
> diff --git a/gcc/testsuite/c-c++-common/strub-unsupported-3.c b/gcc/testsuite/c-c++-common/strub-unsupported-3.c
> new file mode 100644
> index 0000000000000..d6fb4c525c4a6
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-unsupported-3.c
> @@ -0,0 +1,18 @@
> +/* { dg-do compile } */
> +
> +/* Check that, when strub is not supported (so no dg-required-effective-target
> +   strub above), we report when strub functions that are not defined are
> +   called.  This cannot be part of strub-unsupported-2.c because errors in the
> +   strub-mode pass prevent the main strub pass, where errors at calls are
> +   detected, from running.  */
> +
> +extern void __attribute__ ((__strub__))
> +s (void); /* { dg-message "not eligible|requested" "" { target { ! strub } } } */
> +
> +extern void __attribute__ ((__strub__ ("internal")))
> +t (void); /* { dg-message "not eligible|requested" "" { target { ! strub } } } */
> +
> +void m () {
> +  s ();
> +  t ();
> +}
> diff --git a/gcc/testsuite/c-c++-common/strub-unsupported.c b/gcc/testsuite/c-c++-common/strub-unsupported.c
> new file mode 100644
> index 0000000000000..cb5c4049495c4
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-unsupported.c
> @@ -0,0 +1,21 @@
> +/* { dg-do compile } */
> +
> +/* Check that, when strub is not supported (so no dg-required-effective-target
> +   strub above), we report when strub functions are defined, and when they're
> +   called in ways that would require changes.  */
> +
> +void __attribute__ ((__strub__))
> +f (void) {} /* { dg-message "not eligible|requested" "" { target { ! strub } } } */
> +
> +void __attribute__ ((__strub__ ("internal")))
> +g (void) {} /* { dg-message "not eligible|requested" "" { target { ! strub } } } */
> +
> +/* This only gets an error when called, see strub-unsupported-2.c.  */
> +void __attribute__ ((__strub__ ("at-calls"))) (*p) (void);
> +
> +/* These too, see strub-unsupported-3.c.  */
> +extern void __attribute__ ((__strub__))
> +s (void);
> +
> +extern void __attribute__ ((__strub__ ("internal")))
> +t (void);
> diff --git a/gcc/testsuite/c-c++-common/strub-var1.c b/gcc/testsuite/c-c++-common/strub-var1.c
> index eb6250fd39c90..67014aa5de84a 100644
> --- a/gcc/testsuite/c-c++-common/strub-var1.c
> +++ b/gcc/testsuite/c-c++-common/strub-var1.c
> @@ -1,4 +1,5 @@
>  /* { dg-do compile } */
> +/* { dg-require-effective-target strub } */
>
>  int __attribute__ ((strub)) x;
>  float __attribute__ ((strub)) f;
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
> index b5e45ab0525ad..86dbee6746d1b 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-callable1.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that strub and non-strub functions can be called from non-strub
>     contexts, and that strub and callable functions can be called from strub
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
> index 96aa7fe4b07f7..9da120f615645 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-callable2.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that impermissible (cross-strub-context) calls are reported.  */
>
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
> index 5e956cb1a9b6b..22056713cce4b 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-const1.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target 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
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
> index 73d650292dfbf..a105c66d7a9c9 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-const2.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that, along with a strub implicitly-const function call, we issue an
>     asm statement to make sure the watermark passed to it is held in memory
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
> index 2584f1f974a58..386200c2784a4 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-const3.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target 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,
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
> index d819f54ec0230..817e9fa2118b6 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-const4.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target 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
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-data1.c b/gcc/testsuite/c-c++-common/torture/strub-data1.c
> index 7c27a2a1a6dca..132ab63ef733a 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-data1.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* The pointed-to data enables strubbing if accessed.  */
>  int __attribute__ ((__strub__)) var;
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-data2.c b/gcc/testsuite/c-c++-common/torture/strub-data2.c
> index e66d903780afd..b660702d26e75 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-data2.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* The pointer itself is a strub variable, enabling internal strubbing when
>     its value is used.  */
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-data3.c b/gcc/testsuite/c-c++-common/torture/strub-data3.c
> index 5e08e0e58c658..fc44eef6f8fb5 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-data3.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* The pointer itself is a strub variable, that would enable internal strubbing
>     if its value was used.  Here, it's only overwritten, so no strub.  */
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-data4.c b/gcc/testsuite/c-c++-common/torture/strub-data4.c
> index a818e7a38bb5f..85e2f59055b57 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-data4.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* The pointer itself is a strub variable, that would enable internal strubbing
>     if its value was used.  Here, it's only overwritten, so no strub.  */
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
> index ddb0b5c0543b0..0a5edac414df1 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict" } */
> +/* { dg-require-effective-target strub } */
>
>  /* It would be desirable to issue at least warnings for these.  */
>
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
> index c165f312f16de..988954e7ed6bc 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  typedef void __attribute__ ((__strub__)) fntype ();
>  fntype (*ptr);
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
> index 69fcff8d3763d..d3ca91389a700 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  typedef void __attribute__ ((__strub__)) fntype (int, int);
>  fntype (*ptr);
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
> index ff006224909bd..89b5979cf7b78 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  typedef void __attribute__ ((__strub__)) fntype (int, int, ...);
>  fntype (*ptr);
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
> index 614b02228ba29..4917dda8826d9 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=relaxed" } */
> +/* { dg-require-effective-target strub } */
>
>  inline void __attribute__ ((strub ("internal"), always_inline))
>  inl_int_ali (void)
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
> index f9a6b4a16faf8..c45903856d4ff 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=all" } */
> +/* { dg-require-effective-target strub } */
>
>  #include "strub-inlinable1.c"
>
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
> index b4a7f3992bbaa..b0d6139f0a870 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict" } */
> +/* { dg-require-effective-target strub } */
>
>  typedef void ft (void);
>  typedef void ft2 (int, int);
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
> index ef634d351265f..1148c246f2059 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=relaxed -Wpedantic" } */
> +/* { dg-require-effective-target strub } */
>
>  /* C++ does not warn about the partial incompatibilities.
>
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
> index e1f179e160e5c..06a72d86d2c58 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
> @@ -1,6 +1,7 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=relaxed -Wpedantic -fpermissive" } */
>  /* { dg-prune-output "command-line option .-fpermissive." } */
> +/* { dg-require-effective-target strub } */
>
>  /* See strub-ptrfn2.c.  */
>
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
> index 70b558afad040..83ea1af7056e7 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=relaxed" } */
> +/* { dg-require-effective-target strub } */
>
>  /* This is strub-ptrfn2.c without -Wpedantic.
>
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
> index a262a086837b2..2643136f178cc 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-pure1.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that, along with a strub pure function call, we issue an asm statement
>     to make sure the watermark passed to it is not assumed to be unchanged.  */
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
> index 4c4bd50c209a0..8bda129b77dc6 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-pure2.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that, along with a strub implicitly-pure function call, we issue an asm
>     statement to make sure the watermark passed to it is not assumed to be
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
> index ce195c6b1f1b6..00bcbdd097af8 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-pure3.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target 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.  */
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
> index 75cd54ccb5b5d..ea7c40e7912b4 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-pure4.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target 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
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
> index 7458b3fb54da5..fdf100428631d 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-run1.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=strict" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that a non-strub function leaves a string behind in the stack, and that
>     equivalent strub functions don't.  Avoid the use of red zones by avoiding
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
> index 5d60a7775f4bb..1228a66599721 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-run2.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=strict" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that a non-strub function leaves a string behind in the stack, and that
>     equivalent strub functions don't.  Allow red zones to be used.  */
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
> index c2ad710858e87..e5047a988f5bf 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-run3.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
> @@ -1,6 +1,7 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=strict" } */
>  /* { dg-require-effective-target alloca } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that a non-strub function leaves a string behind in the stack, and that
>     equivalent strub functions don't.  */
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
> index 3b36b8e5d68ef..0e84a4bab80fc 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-run4.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
> @@ -1,6 +1,7 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=all" } */
>  /* { dg-require-effective-target alloca } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that multi-level, multi-inlined functions still get cleaned up as
>     expected, without overwriting temporary stack allocations while they should
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4c.c b/gcc/testsuite/c-c++-common/torture/strub-run4c.c
> index 57f9baf758ded..edc98486dc93a 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-run4c.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-run4c.c
> @@ -1,5 +1,6 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=at-calls" } */
>  /* { dg-require-effective-target alloca } */
> +/* { dg-require-effective-target strub } */
>
>  #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
> index 08de3f1c3b17c..487ed08bb6606 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-run4d.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
> @@ -1,6 +1,7 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=strict" } */
>  /* { dg-require-effective-target alloca } */
> +/* { dg-require-effective-target strub } */
>
>  #define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
>
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4i.c b/gcc/testsuite/c-c++-common/torture/strub-run4i.c
> index 459f6886c5499..a85447ffabfae 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-run4i.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-run4i.c
> @@ -1,5 +1,6 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=internal" } */
>  /* { dg-require-effective-target alloca } */
> +/* { dg-require-effective-target strub } */
>
>  #include "strub-run4.c"
> diff --git a/gcc/testsuite/g++.dg/strub-run1.C b/gcc/testsuite/g++.dg/strub-run1.C
> index 0d367fb83d09d..beb8b811f8fca 100644
> --- a/gcc/testsuite/g++.dg/strub-run1.C
> +++ b/gcc/testsuite/g++.dg/strub-run1.C
> @@ -1,5 +1,6 @@
>  // { dg-do run }
>  // { dg-options "-fstrub=internal" }
> +// { dg-require-effective-target strub }
>
>  // Check that we don't get extra copies.
>
> diff --git a/gcc/testsuite/g++.dg/torture/strub-init1.C b/gcc/testsuite/g++.dg/torture/strub-init1.C
> index c226ab10ff651..6ae45fadd70ba 100644
> --- a/gcc/testsuite/g++.dg/torture/strub-init1.C
> +++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +// { dg-require-effective-target strub }
>
>  extern int __attribute__((__strub__)) initializer ();
>
> diff --git a/gcc/testsuite/g++.dg/torture/strub-init2.C b/gcc/testsuite/g++.dg/torture/strub-init2.C
> index a7911f1fa7212..8f4849c7fde78 100644
> --- a/gcc/testsuite/g++.dg/torture/strub-init2.C
> +++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +// { dg-require-effective-target strub }
>
>  extern int __attribute__((__strub__)) initializer ();
>
> diff --git a/gcc/testsuite/g++.dg/torture/strub-init3.C b/gcc/testsuite/g++.dg/torture/strub-init3.C
> index 6ebebcd01e8ea..14f28e3c276bd 100644
> --- a/gcc/testsuite/g++.dg/torture/strub-init3.C
> +++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +// { dg-require-effective-target strub }
>
>  extern int __attribute__((__strub__)) initializer ();
>
> diff --git a/gcc/testsuite/gnat.dg/strub_access.adb b/gcc/testsuite/gnat.dg/strub_access.adb
> index 29e6996ecf61c..488a2d64afe31 100644
> --- a/gcc/testsuite/gnat.dg/strub_access.adb
> +++ b/gcc/testsuite/gnat.dg/strub_access.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fstrub=relaxed -fdump-ipa-strubm" }
> +--  { dg-require-effective-target strub }
>
>  --  The main subprogram doesn't read from the automatic variable, but
>  --  being an automatic variable, its presence should be enough for the
> diff --git a/gcc/testsuite/gnat.dg/strub_access1.adb b/gcc/testsuite/gnat.dg/strub_access1.adb
> index dae4706016436..4a8653c4d843f 100644
> --- a/gcc/testsuite/gnat.dg/strub_access1.adb
> +++ b/gcc/testsuite/gnat.dg/strub_access1.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fstrub=relaxed" }
> +--  { dg-require-effective-target strub }
>
>  --  Check that we reject 'Access of a strub variable whose type does
>  --  not carry a strub modifier.
> diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
> index 10445d7cf8451..eb7826dc990f4 100644
> --- a/gcc/testsuite/gnat.dg/strub_attr.adb
> +++ b/gcc/testsuite/gnat.dg/strub_attr.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" }
> +--  { dg-require-effective-target strub }
>
>  package body Strub_Attr is
>     E : exception;
> diff --git a/gcc/testsuite/gnat.dg/strub_disp.adb b/gcc/testsuite/gnat.dg/strub_disp.adb
> index 3dbcc4a357cba..f23d4675def38 100644
> --- a/gcc/testsuite/gnat.dg/strub_disp.adb
> +++ b/gcc/testsuite/gnat.dg/strub_disp.adb
> @@ -1,4 +1,5 @@
>  --  { dg-do compile }
> +--  { dg-require-effective-target strub }
>
>  procedure Strub_Disp is
>     package Foo is
> diff --git a/gcc/testsuite/gnat.dg/strub_disp1.adb b/gcc/testsuite/gnat.dg/strub_disp1.adb
> index 09756a74b7d81..9c4c7f696371d 100644
> --- a/gcc/testsuite/gnat.dg/strub_disp1.adb
> +++ b/gcc/testsuite/gnat.dg/strub_disp1.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fdump-ipa-strub" }
> +--  { dg-require-effective-target strub }
>
>  -- Check that at-calls dispatching calls are transformed.
>
> diff --git a/gcc/testsuite/gnat.dg/strub_ind.adb b/gcc/testsuite/gnat.dg/strub_ind.adb
> index da56acaa957d2..613db69305e05 100644
> --- a/gcc/testsuite/gnat.dg/strub_ind.adb
> +++ b/gcc/testsuite/gnat.dg/strub_ind.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fstrub=strict" }
> +--  { dg-require-effective-target strub }
>
>  --  This is essentially the same test as strub_attr.adb,
>  --  but applying attributes to access types as well.
> diff --git a/gcc/testsuite/gnat.dg/strub_ind1.adb b/gcc/testsuite/gnat.dg/strub_ind1.adb
> index 825e395e6819c..245b0a830f691 100644
> --- a/gcc/testsuite/gnat.dg/strub_ind1.adb
> +++ b/gcc/testsuite/gnat.dg/strub_ind1.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
> +--  { dg-require-effective-target strub }
>
>  --  This is essentially the same test as strub_attr.adb,
>  --  but with an explicit conversion.
> diff --git a/gcc/testsuite/gnat.dg/strub_ind2.adb b/gcc/testsuite/gnat.dg/strub_ind2.adb
> index e918b39263117..b9bfe50e9296e 100644
> --- a/gcc/testsuite/gnat.dg/strub_ind2.adb
> +++ b/gcc/testsuite/gnat.dg/strub_ind2.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fstrub=strict" }
> +--  { dg-require-effective-target strub }
>
>  --  This is essentially the same test as strub_attr.adb,
>  --  but with an explicit conversion.
> diff --git a/gcc/testsuite/gnat.dg/strub_intf.adb b/gcc/testsuite/gnat.dg/strub_intf.adb
> index 8f0212a75866f..f43854705d073 100644
> --- a/gcc/testsuite/gnat.dg/strub_intf.adb
> +++ b/gcc/testsuite/gnat.dg/strub_intf.adb
> @@ -1,4 +1,5 @@
>  --  { dg-do compile }
> +--  { dg-require-effective-target strub }
>
>  --  Check that strub mode mismatches between overrider and overridden
>  --  subprograms are reported.
> diff --git a/gcc/testsuite/gnat.dg/strub_intf1.adb b/gcc/testsuite/gnat.dg/strub_intf1.adb
> index bf77321cef790..7a38a4c49ba8d 100644
> --- a/gcc/testsuite/gnat.dg/strub_intf1.adb
> +++ b/gcc/testsuite/gnat.dg/strub_intf1.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fdump-ipa-strub" }
> +--  { dg-require-effective-target strub }
>
>  -- Check that at-calls dispatching calls to interfaces are transformed.
>
> diff --git a/gcc/testsuite/gnat.dg/strub_intf2.adb b/gcc/testsuite/gnat.dg/strub_intf2.adb
> index e8880dbc43730..7992b7344fb87 100644
> --- a/gcc/testsuite/gnat.dg/strub_intf2.adb
> +++ b/gcc/testsuite/gnat.dg/strub_intf2.adb
> @@ -1,4 +1,5 @@
>  --  { dg-do compile }
> +--  { dg-require-effective-target strub }
>
>  --  Check that strub mode mismatches between overrider and overridden
>  --  subprograms are reported even when the overriders for an
> diff --git a/gcc/testsuite/gnat.dg/strub_renm.adb b/gcc/testsuite/gnat.dg/strub_renm.adb
> index 217367e712d82..abfb120b51468 100644
> --- a/gcc/testsuite/gnat.dg/strub_renm.adb
> +++ b/gcc/testsuite/gnat.dg/strub_renm.adb
> @@ -1,4 +1,5 @@
>  --  { dg-do compile }
> +--  { dg-require-effective-target strub }
>
>  procedure Strub_Renm is
>     procedure P (X : Integer);
> diff --git a/gcc/testsuite/gnat.dg/strub_renm1.adb b/gcc/testsuite/gnat.dg/strub_renm1.adb
> index a11adbfb5a9d6..68d3230b5356c 100644
> --- a/gcc/testsuite/gnat.dg/strub_renm1.adb
> +++ b/gcc/testsuite/gnat.dg/strub_renm1.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fstrub=relaxed -fdump-ipa-strub" }
> +--  { dg-require-effective-target strub }
>
>  procedure Strub_Renm1 is
>     V : Integer := 0;
> diff --git a/gcc/testsuite/gnat.dg/strub_renm2.adb b/gcc/testsuite/gnat.dg/strub_renm2.adb
> index c488c20826fdb..3cb81ea03f763 100644
> --- a/gcc/testsuite/gnat.dg/strub_renm2.adb
> +++ b/gcc/testsuite/gnat.dg/strub_renm2.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fstrub=strict -fdump-ipa-strub" }
> +--  { dg-require-effective-target strub }
>
>  procedure Strub_Renm2 is
>     V : Integer := 0;
> diff --git a/gcc/testsuite/gnat.dg/strub_var.adb b/gcc/testsuite/gnat.dg/strub_var.adb
> index 3d158de28031f..7c6affa06d4ab 100644
> --- a/gcc/testsuite/gnat.dg/strub_var.adb
> +++ b/gcc/testsuite/gnat.dg/strub_var.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
> +--  { dg-require-effective-target strub }
>
>  -- We don't read from the automatic variable, but being an automatic
>  --  variable, its presence should be enough for the procedure to get
> diff --git a/gcc/testsuite/gnat.dg/strub_var1.adb b/gcc/testsuite/gnat.dg/strub_var1.adb
> index 6a504e09198b6..64b7e65fe9b0f 100644
> --- a/gcc/testsuite/gnat.dg/strub_var1.adb
> +++ b/gcc/testsuite/gnat.dg/strub_var1.adb
> @@ -1,4 +1,5 @@
>  --  { dg-do compile }
> +--  { dg-require-effective-target strub }
>
>  with Strub_Attr;
>  procedure Strub_Var1 is
> diff --git a/gcc/testsuite/lib/target-supports.exp b/gcc/testsuite/lib/target-supports.exp
> index 3fcce6be49d6f..40a60c198cfe8 100644
> --- a/gcc/testsuite/lib/target-supports.exp
> +++ b/gcc/testsuite/lib/target-supports.exp
> @@ -1302,6 +1302,13 @@ proc check_stack_check_available { stack_kind } {
>      } "$stack_opt"]
>  }
>
> +# Return 1 if the target supports stack scrubbing.
> +proc check_effective_target_strub {} {
> +    return [check_no_compiler_messages strub assembly {
> +       void __attribute__ ((__strub__)) fn (void) {}
> +    } ""]
> +}
> +
>  # Return 1 if compilation with -freorder-blocks-and-partition is error-free
>  # for trivial code, 0 otherwise.  As some targets (ARM for example) only
>  # warn when -fprofile-use is also supplied we test that combination too.
> diff --git a/libgcc/Makefile.in b/libgcc/Makefile.in
> index d8163c5af9903..3f77283490ef6 100644
> --- a/libgcc/Makefile.in
> +++ b/libgcc/Makefile.in
> @@ -434,7 +434,7 @@ LIB2ADD += enable-execute-stack.c
>  LIB2ADD += $(srcdir)/hardcfr.c
>
>  # Stack scrubbing infrastructure.
> -LIB2ADD += $(srcdir)/strub.c
> +@HAVE_STRUB_SUPPORT@LIB2ADD += $(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/configure b/libgcc/configure
> index cf149209652e3..567158955a329 100755
> --- a/libgcc/configure
> +++ b/libgcc/configure
> @@ -593,6 +593,7 @@ asm_hidden_op
>  extra_parts
>  cpu_type
>  get_gcc_base_ver
> +HAVE_STRUB_SUPPORT
>  thread_header
>  tm_defines
>  tm_file
> @@ -5702,6 +5703,31 @@ esac
>
>
>
> +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for strub support" >&5
> +$as_echo_n "checking for strub support... " >&6; }
> +if ${libgcc_cv_strub_support+:} false; then :
> +  $as_echo_n "(cached) " >&6
> +else
> +  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
> +/* end confdefs.h.  */
> +void __attribute__ ((__strub__)) fn (void) {}
> +_ACEOF
> +if ac_fn_c_try_compile "$LINENO"; then :
> +  libgcc_cv_strub_support=yes
> +else
> +  libgcc_cv_strub_support=no
> +fi
> +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
> +fi
> +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $libgcc_cv_strub_support" >&5
> +$as_echo "$libgcc_cv_strub_support" >&6; }
> +if test "x$libgcc_cv_strub_support" != xno; then
> +  HAVE_STRUB_SUPPORT=
> +else
> +  HAVE_STRUB_SUPPORT='# '
> +fi
> +
> +
>  # Determine what GCC version number to use in filesystem paths.
>
>    get_gcc_base_ver="cat"
> diff --git a/libgcc/configure.ac b/libgcc/configure.ac
> index 2fc9d5d7c93e9..9c0e415501a80 100644
> --- a/libgcc/configure.ac
> +++ b/libgcc/configure.ac
> @@ -694,6 +694,19 @@ AC_SUBST(tm_defines)
>  # Map from thread model to thread header.
>  GCC_AC_THREAD_HEADER([$target_thread_file])
>
> +AC_CACHE_CHECK([for strub support],
> +  [libgcc_cv_strub_support],
> +  [AC_COMPILE_IFELSE(
> +    [AC_LANG_SOURCE([void __attribute__ ((__strub__)) fn (void) {}])],
> +    [libgcc_cv_strub_support=yes],
> +    [libgcc_cv_strub_support=no])])
> +if test "x$libgcc_cv_strub_support" != xno; then
> +  HAVE_STRUB_SUPPORT=
> +else
> +  HAVE_STRUB_SUPPORT='# '
> +fi
> +AC_SUBST(HAVE_STRUB_SUPPORT)
> +
>  # Determine what GCC version number to use in filesystem paths.
>  GCC_BASE_VER
>
>
>
> --
> Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
>    Free Software Activist                   GNU Toolchain Engineer
> More tolerance and less prejudice are key for inclusion and diversity
> Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH] strub: enable conditional support
  2023-12-07  3:33                                   ` [PATCH] strub: enable conditional support Alexandre Oliva
  2023-12-07  7:24                                     ` Richard Biener
@ 2023-12-07 16:44                                     ` Thomas Schwinge
  2023-12-07 17:52                                       ` [PATCH] Alexandre Oliva
  1 sibling, 1 reply; 59+ messages in thread
From: Thomas Schwinge @ 2023-12-07 16:44 UTC (permalink / raw)
  To: Alexandre Oliva
  Cc: Tobias Burnus, Richard Biener, gcc-patches, Jeremy Bennett,
	Craig Blackmore, Graham Markall, Martin Jambor, Jan Hubicka,
	Jim Wilson, Jeff Law, Jakub Jelinek, Tom de Vries

Hi Alexandre!

Thank you for looking into this so promptly!

On 2023-12-07T00:33:59-0300, Alexandre Oliva <oliva@adacore.com> wrote:
> On Dec  6, 2023, Alexandre Oliva <oliva@adacore.com> wrote:
>
>> Disabling the runtime bits is easy, once we determine what condition we
>> wish to test for.  I suppose testing for target support in the compiler,
>> issuing a 'sorry' in case the feature is required, would provide
>> something for libgcc configure and testsuite effective-target to test
>> for and decide whether to enable runtime support and run the tests.
>
> Instead of doing something equivalent to an implicit -fstrub=disable,
> that would quietly compile without stack scrubbing, I thought it would
> be safer to be noisy if the feature is used (requested, really) when
> support is not available.
>
>
> Targets that don't expose callee stacks to callers, such as nvptx, as
> well as -fsplit-stack compilations, violate fundamental assumptions of
> the current strub implementation.  This patch enables targets to
> disable strub, and disables it when -fsplit-stack is enabled.
>
> When strub support is disabled, the testsuite will now skip strub
> tests, and libgcc will not build the strub runtime components.
>
> Regstrapped on x86_64-linux-gnu.  Also tested with an additional patch
> for i386.cc that mirrors the nvptx.cc change, to check that strub gets
> disabled without noisy test results.  Ok to install?

GCC/nvptx target again builds as before, and testing just completed: as
expected, the "strub" test cases generally UNSUPPORTED, just something's
wrong with two of the 'c-c++-common/strub-unsupported*.c' test cases:

    +PASS: c-c++-common/strub-unsupported-2.c  -Wc++-compat   (test for warnings, line 12)
    +PASS: c-c++-common/strub-unsupported-2.c  -Wc++-compat  (test for excess errors)

    +FAIL: c-c++-common/strub-unsupported-3.c  -Wc++-compat  (internal compiler error: in verify_curr_properties, at passes.cc:2198)
    +PASS: c-c++-common/strub-unsupported-3.c  -Wc++-compat   (test for warnings, line 10)
    +PASS: c-c++-common/strub-unsupported-3.c  -Wc++-compat   (test for warnings, line 13)
    +FAIL: c-c++-common/strub-unsupported-3.c  -Wc++-compat  (test for excess errors)

    +FAIL: c-c++-common/strub-unsupported.c  -Wc++-compat  (internal compiler error: in verify_curr_properties, at passes.cc:2198)
    +PASS: c-c++-common/strub-unsupported.c  -Wc++-compat   (test for warnings, line 8)
    +PASS: c-c++-common/strub-unsupported.c  -Wc++-compat   (test for warnings, line 11)
    +FAIL: c-c++-common/strub-unsupported.c  -Wc++-compat  (test for excess errors)

Similar for C++ testing.

The ICE is:

    during IPA pass: emutls
    [...]/source-gcc/gcc/testsuite/c-c++-common/strub-unsupported-3.c:18:1: internal compiler error: in verify_curr_properties, at passes.cc:2198
    0x10b671db verify_curr_properties
            [...]/source-gcc/gcc/passes.cc:2198
    0x10b67ca3 do_per_function
            [...]/source-gcc/gcc/passes.cc:1694

I'm certainly fine if we deal with that one incrementally.


I'll answer your (quite right) '__builtin_{frame,stack}_address' remarks
(earlier email) separately, later on.


Grüße
 Thomas


> for  gcc/ChangeLog
>
>       * target.def (have_strub_support_for): New hook.
>       * doc/tm.texi.in: Document it.
>       * doc/tm.texi: Rebuild.
>       * ipa-strub.cc: Include target.h.
>       (strub_target_support_p): New.
>       (can_strub_p): Call it.  Test for no flag_split_stack.
>       (pass_ipa_strub::adjust_at_calls_call): Check for target
>       support.
>       * config/nvptx/nvptx.cc (TARGET_HAVE_STRUB_SUPPORT_FOR):
>         Disable.
>       * doc/sourcebuild.texi (strub): Document new effective
>       target.
>
> for  gcc/testsuite/ChangeLog
>
>       * gcc.dg/strub-split-stack.c: New.
>       * gcc.dg/strub-unsupported.c: New.
>       * gcc.dg/strub-unsupported-2.c: New.
>       * gcc.dg/strub-unsupported-3.c: New.
>       * lib/target-supports.exp (check_effective_target_strub): New.
>       * c-c++-common/strub-O0.c: Require effective target strub.
>       * c-c++-common/strub-O1.c: Likewise.
>       * c-c++-common/strub-O2.c: Likewise.
>       * c-c++-common/strub-O2fni.c: Likewise.
>       * c-c++-common/strub-O3.c: Likewise.
>       * c-c++-common/strub-O3fni.c: Likewise.
>       * c-c++-common/strub-Og.c: Likewise.
>       * c-c++-common/strub-Os.c: Likewise.
>       * c-c++-common/strub-all1.c: Likewise.
>       * c-c++-common/strub-all2.c: Likewise.
>       * c-c++-common/strub-apply1.c: Likewise.
>       * c-c++-common/strub-apply2.c: Likewise.
>       * c-c++-common/strub-apply3.c: Likewise.
>       * c-c++-common/strub-apply4.c: Likewise.
>       * c-c++-common/strub-at-calls1.c: Likewise.
>       * c-c++-common/strub-at-calls2.c: Likewise.
>       * c-c++-common/strub-defer-O1.c: Likewise.
>       * c-c++-common/strub-defer-O2.c: Likewise.
>       * c-c++-common/strub-defer-O3.c: Likewise.
>       * c-c++-common/strub-defer-Os.c: Likewise.
>       * c-c++-common/strub-internal1.c: Likewise.
>       * c-c++-common/strub-internal2.c: Likewise.
>       * c-c++-common/strub-parms1.c: Likewise.
>       * c-c++-common/strub-parms2.c: Likewise.
>       * c-c++-common/strub-parms3.c: Likewise.
>       * c-c++-common/strub-relaxed1.c: Likewise.
>       * c-c++-common/strub-relaxed2.c: Likewise.
>       * c-c++-common/strub-short-O0-exc.c: Likewise.
>       * c-c++-common/strub-short-O0.c: Likewise.
>       * c-c++-common/strub-short-O1.c: Likewise.
>       * c-c++-common/strub-short-O2.c: Likewise.
>       * c-c++-common/strub-short-O3.c: Likewise.
>       * c-c++-common/strub-short-Os.c: Likewise.
>       * c-c++-common/strub-strict1.c: Likewise.
>       * c-c++-common/strub-strict2.c: Likewise.
>       * c-c++-common/strub-tail-O1.c: Likewise.
>       * c-c++-common/strub-tail-O2.c: Likewise.
>       * c-c++-common/strub-var1.c: Likewise.
>       * c-c++-common/torture/strub-callable1.c: Likewise.
>       * c-c++-common/torture/strub-callable2.c: Likewise.
>       * c-c++-common/torture/strub-const1.c: Likewise.
>       * c-c++-common/torture/strub-const2.c: Likewise.
>       * c-c++-common/torture/strub-const3.c: Likewise.
>       * c-c++-common/torture/strub-const4.c: Likewise.
>       * c-c++-common/torture/strub-data1.c: Likewise.
>       * c-c++-common/torture/strub-data2.c: Likewise.
>       * c-c++-common/torture/strub-data3.c: Likewise.
>       * c-c++-common/torture/strub-data4.c: Likewise.
>       * c-c++-common/torture/strub-data5.c: Likewise.
>       * c-c++-common/torture/strub-indcall1.c: Likewise.
>       * c-c++-common/torture/strub-indcall2.c: Likewise.
>       * c-c++-common/torture/strub-indcall3.c: Likewise.
>       * c-c++-common/torture/strub-inlinable1.c: Likewise.
>       * c-c++-common/torture/strub-inlinable2.c: Likewise.
>       * c-c++-common/torture/strub-ptrfn1.c: Likewise.
>       * c-c++-common/torture/strub-ptrfn2.c: Likewise.
>       * c-c++-common/torture/strub-ptrfn3.c: Likewise.
>       * c-c++-common/torture/strub-ptrfn4.c: Likewise.
>       * c-c++-common/torture/strub-pure1.c: Likewise.
>       * c-c++-common/torture/strub-pure2.c: Likewise.
>       * c-c++-common/torture/strub-pure3.c: Likewise.
>       * c-c++-common/torture/strub-pure4.c: Likewise.
>       * c-c++-common/torture/strub-run1.c: Likewise.
>       * c-c++-common/torture/strub-run2.c: Likewise.
>       * c-c++-common/torture/strub-run3.c: Likewise.
>       * c-c++-common/torture/strub-run4.c: Likewise.
>       * c-c++-common/torture/strub-run4c.c: Likewise.
>       * c-c++-common/torture/strub-run4d.c: Likewise.
>       * c-c++-common/torture/strub-run4i.c: Likewise.
>       * g++.dg/strub-run1.C: Likewise.
>       * g++.dg/torture/strub-init1.C: Likewise.
>       * g++.dg/torture/strub-init2.C: Likewise.
>       * g++.dg/torture/strub-init3.C: Likewise.
>       * gnat.dg/strub_attr.adb: Likewise.
>       * gnat.dg/strub_ind.adb: Likewise.
>       * gnat.dg/strub_access.adb: Likewise.
>       * gnat.dg/strub_access1.adb: Likewise.
>       * gnat.dg/strub_disp.adb: Likewise.
>       * gnat.dg/strub_disp1.adb: Likewise.
>       * gnat.dg/strub_ind1.adb: Likewise.
>       * gnat.dg/strub_ind2.adb: Likewise.
>       * gnat.dg/strub_intf.adb: Likewise.
>       * gnat.dg/strub_intf1.adb: Likewise.
>       * gnat.dg/strub_intf2.adb: Likewise.
>       * gnat.dg/strub_renm.adb: Likewise.
>       * gnat.dg/strub_renm1.adb: Likewise.
>       * gnat.dg/strub_renm2.adb: Likewise.
>       * gnat.dg/strub_var.adb: Likewise.
>       * gnat.dg/strub_var1.adb: Likewise.
>
> for  libgcc/ChangeLog
>
>       * configure.ac: Check for strub support.
>       * configure: Rebuilt.
>       * Makefile.in: Compile strub.c conditionally.
> ---
>  gcc/config/nvptx/nvptx.cc                          |    3 +
>  gcc/doc/sourcebuild.texi                           |    3 +
>  gcc/doc/tm.texi                                    |    6 ++
>  gcc/doc/tm.texi.in                                 |    2 +
>  gcc/ipa-strub.cc                                   |   54 +++++++++++++++++++-
>  gcc/target.def                                     |    8 +++
>  gcc/testsuite/c-c++-common/strub-O0.c              |    1
>  gcc/testsuite/c-c++-common/strub-O1.c              |    1
>  gcc/testsuite/c-c++-common/strub-O2.c              |    1
>  gcc/testsuite/c-c++-common/strub-O2fni.c           |    1
>  gcc/testsuite/c-c++-common/strub-O3.c              |    1
>  gcc/testsuite/c-c++-common/strub-O3fni.c           |    1
>  gcc/testsuite/c-c++-common/strub-Og.c              |    1
>  gcc/testsuite/c-c++-common/strub-Os.c              |    1
>  gcc/testsuite/c-c++-common/strub-all1.c            |    1
>  gcc/testsuite/c-c++-common/strub-all2.c            |    1
>  gcc/testsuite/c-c++-common/strub-apply1.c          |    1
>  gcc/testsuite/c-c++-common/strub-apply2.c          |    1
>  gcc/testsuite/c-c++-common/strub-apply3.c          |    1
>  gcc/testsuite/c-c++-common/strub-apply4.c          |    1
>  gcc/testsuite/c-c++-common/strub-at-calls1.c       |    1
>  gcc/testsuite/c-c++-common/strub-at-calls2.c       |    1
>  gcc/testsuite/c-c++-common/strub-defer-O1.c        |    1
>  gcc/testsuite/c-c++-common/strub-defer-O2.c        |    1
>  gcc/testsuite/c-c++-common/strub-defer-O3.c        |    1
>  gcc/testsuite/c-c++-common/strub-defer-Os.c        |    1
>  gcc/testsuite/c-c++-common/strub-internal1.c       |    1
>  gcc/testsuite/c-c++-common/strub-internal2.c       |    1
>  gcc/testsuite/c-c++-common/strub-parms1.c          |    1
>  gcc/testsuite/c-c++-common/strub-parms2.c          |    1
>  gcc/testsuite/c-c++-common/strub-parms3.c          |    1
>  gcc/testsuite/c-c++-common/strub-relaxed1.c        |    1
>  gcc/testsuite/c-c++-common/strub-relaxed2.c        |    1
>  gcc/testsuite/c-c++-common/strub-short-O0-exc.c    |    1
>  gcc/testsuite/c-c++-common/strub-short-O0.c        |    1
>  gcc/testsuite/c-c++-common/strub-short-O1.c        |    1
>  gcc/testsuite/c-c++-common/strub-short-O2.c        |    1
>  gcc/testsuite/c-c++-common/strub-short-O3.c        |    1
>  gcc/testsuite/c-c++-common/strub-short-Os.c        |    1
>  gcc/testsuite/c-c++-common/strub-split-stack.c     |   10 ++++
>  gcc/testsuite/c-c++-common/strub-strict1.c         |    1
>  gcc/testsuite/c-c++-common/strub-strict2.c         |    1
>  gcc/testsuite/c-c++-common/strub-tail-O1.c         |    1
>  gcc/testsuite/c-c++-common/strub-tail-O2.c         |    1
>  gcc/testsuite/c-c++-common/strub-unsupported-2.c   |   13 +++++
>  gcc/testsuite/c-c++-common/strub-unsupported-3.c   |   18 +++++++
>  gcc/testsuite/c-c++-common/strub-unsupported.c     |   21 ++++++++
>  gcc/testsuite/c-c++-common/strub-var1.c            |    1
>  .../c-c++-common/torture/strub-callable1.c         |    1
>  .../c-c++-common/torture/strub-callable2.c         |    1
>  gcc/testsuite/c-c++-common/torture/strub-const1.c  |    1
>  gcc/testsuite/c-c++-common/torture/strub-const2.c  |    1
>  gcc/testsuite/c-c++-common/torture/strub-const3.c  |    1
>  gcc/testsuite/c-c++-common/torture/strub-const4.c  |    1
>  gcc/testsuite/c-c++-common/torture/strub-data1.c   |    1
>  gcc/testsuite/c-c++-common/torture/strub-data2.c   |    1
>  gcc/testsuite/c-c++-common/torture/strub-data3.c   |    1
>  gcc/testsuite/c-c++-common/torture/strub-data4.c   |    1
>  gcc/testsuite/c-c++-common/torture/strub-data5.c   |    1
>  .../c-c++-common/torture/strub-indcall1.c          |    1
>  .../c-c++-common/torture/strub-indcall2.c          |    1
>  .../c-c++-common/torture/strub-indcall3.c          |    1
>  .../c-c++-common/torture/strub-inlinable1.c        |    1
>  .../c-c++-common/torture/strub-inlinable2.c        |    1
>  gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c  |    1
>  gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c  |    1
>  gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c  |    1
>  gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c  |    1
>  gcc/testsuite/c-c++-common/torture/strub-pure1.c   |    1
>  gcc/testsuite/c-c++-common/torture/strub-pure2.c   |    1
>  gcc/testsuite/c-c++-common/torture/strub-pure3.c   |    1
>  gcc/testsuite/c-c++-common/torture/strub-pure4.c   |    1
>  gcc/testsuite/c-c++-common/torture/strub-run1.c    |    1
>  gcc/testsuite/c-c++-common/torture/strub-run2.c    |    1
>  gcc/testsuite/c-c++-common/torture/strub-run3.c    |    1
>  gcc/testsuite/c-c++-common/torture/strub-run4.c    |    1
>  gcc/testsuite/c-c++-common/torture/strub-run4c.c   |    1
>  gcc/testsuite/c-c++-common/torture/strub-run4d.c   |    1
>  gcc/testsuite/c-c++-common/torture/strub-run4i.c   |    1
>  gcc/testsuite/g++.dg/strub-run1.C                  |    1
>  gcc/testsuite/g++.dg/torture/strub-init1.C         |    1
>  gcc/testsuite/g++.dg/torture/strub-init2.C         |    1
>  gcc/testsuite/g++.dg/torture/strub-init3.C         |    1
>  gcc/testsuite/gnat.dg/strub_access.adb             |    1
>  gcc/testsuite/gnat.dg/strub_access1.adb            |    1
>  gcc/testsuite/gnat.dg/strub_attr.adb               |    1
>  gcc/testsuite/gnat.dg/strub_disp.adb               |    1
>  gcc/testsuite/gnat.dg/strub_disp1.adb              |    1
>  gcc/testsuite/gnat.dg/strub_ind.adb                |    1
>  gcc/testsuite/gnat.dg/strub_ind1.adb               |    1
>  gcc/testsuite/gnat.dg/strub_ind2.adb               |    1
>  gcc/testsuite/gnat.dg/strub_intf.adb               |    1
>  gcc/testsuite/gnat.dg/strub_intf1.adb              |    1
>  gcc/testsuite/gnat.dg/strub_intf2.adb              |    1
>  gcc/testsuite/gnat.dg/strub_renm.adb               |    1
>  gcc/testsuite/gnat.dg/strub_renm1.adb              |    1
>  gcc/testsuite/gnat.dg/strub_renm2.adb              |    1
>  gcc/testsuite/gnat.dg/strub_var.adb                |    1
>  gcc/testsuite/gnat.dg/strub_var1.adb               |    1
>  gcc/testsuite/lib/target-supports.exp              |    7 +++
>  libgcc/Makefile.in                                 |    2 -
>  libgcc/configure                                   |   26 ++++++++++
>  libgcc/configure.ac                                |   13 +++++
>  103 files changed, 272 insertions(+), 3 deletions(-)
>  create mode 100644 gcc/testsuite/c-c++-common/strub-split-stack.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-unsupported-2.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-unsupported-3.c
>  create mode 100644 gcc/testsuite/c-c++-common/strub-unsupported.c
>
> diff --git a/gcc/config/nvptx/nvptx.cc b/gcc/config/nvptx/nvptx.cc
> index ae20802c87996..3fb1deb70fda1 100644
> --- a/gcc/config/nvptx/nvptx.cc
> +++ b/gcc/config/nvptx/nvptx.cc
> @@ -7789,6 +7789,9 @@ nvptx_asm_output_def_from_decls (FILE *stream, tree name, tree value)
>  #undef TARGET_LIBC_HAS_FUNCTION
>  #define TARGET_LIBC_HAS_FUNCTION nvptx_libc_has_function
>
> +#undef TARGET_HAVE_STRUB_SUPPORT_FOR
> +#define TARGET_HAVE_STRUB_SUPPORT_FOR hook_bool_tree_false
> +
>  struct gcc_target targetm = TARGET_INITIALIZER;
>
>  #include "gt-nvptx.h"
> diff --git a/gcc/doc/sourcebuild.texi b/gcc/doc/sourcebuild.texi
> index c990902685417..26a7e9c350703 100644
> --- a/gcc/doc/sourcebuild.texi
> +++ b/gcc/doc/sourcebuild.texi
> @@ -2983,6 +2983,9 @@ Target supports statically linking @samp{libgfortran}.
>  @item string_merging
>  Target supports merging string constants at link time.
>
> +@item strub
> +Target supports attribute @code{strub} for stack scrubbing.
> +
>  @item ucn
>  Target supports compiling and assembling UCN.
>
> diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
> index 89a1735dd7992..768ada0af5222 100644
> --- a/gcc/doc/tm.texi
> +++ b/gcc/doc/tm.texi
> @@ -3450,6 +3450,12 @@ in DWARF 2 debug information.  The default is zero.  A different value
>  may reduce the size of debug information on some ports.
>  @end defmac
>
> +@deftypefn {Target Hook} bool TARGET_HAVE_STRUB_SUPPORT_FOR (tree)
> +Returns true if the target supports stack scrubbing for the given function
> +or type, otherwise return false.  The default implementation always returns
> +true.
> +@end deftypefn
> +
>  @defmac TARGET_STRUB_USE_DYNAMIC_ARRAY
>  If defined to nonzero, @code{__strub_leave} will allocate a dynamic
>  array covering the stack range that needs scrubbing before clearing it.
> diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
> index ebc1d3de5caaa..4fe0805394ea4 100644
> --- a/gcc/doc/tm.texi.in
> +++ b/gcc/doc/tm.texi.in
> @@ -2686,6 +2686,8 @@ in DWARF 2 debug information.  The default is zero.  A different value
>  may reduce the size of debug information on some ports.
>  @end defmac
>
> +@hook TARGET_HAVE_STRUB_SUPPORT_FOR
> +
>  @defmac TARGET_STRUB_USE_DYNAMIC_ARRAY
>  If defined to nonzero, @code{__strub_leave} will allocate a dynamic
>  array covering the stack range that needs scrubbing before clearing it.
> diff --git a/gcc/ipa-strub.cc b/gcc/ipa-strub.cc
> index 293bec132b885..2afb7a455751d 100644
> --- a/gcc/ipa-strub.cc
> +++ b/gcc/ipa-strub.cc
> @@ -60,6 +60,7 @@ along with GCC; see the file COPYING3.  If not see
>  #include "ipa-strub.h"
>  #include "symtab-thunks.h"
>  #include "attr-fnspec.h"
> +#include "target.h"
>
>  /* This file introduces two passes that, together, implement
>     machine-independent stack scrubbing, strub for short.  It arranges
> @@ -631,17 +632,60 @@ strub_always_inline_p (cgraph_node *node)
>    return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
>  }
>
> +/* Return TRUE iff the target has strub support for T, a function
> +   decl, or a type used in an indirect call, and optionally REPORT the
> +   reasons for ineligibility.  If T is a type and error REPORTing is
> +   enabled, the LOCation (of the indirect call) should be provided.  */
> +static inline bool
> +strub_target_support_p (tree t, bool report = false,
> +                     location_t loc = UNKNOWN_LOCATION)
> +{
> +  bool result = true;
> +
> +  if (!targetm.have_strub_support_for (t))
> +    {
> +      result = false;
> +
> +      if (!report)
> +     return result;
> +
> +      if (DECL_P (t))
> +     sorry_at (DECL_SOURCE_LOCATION (t),
> +               "%qD is not eligible for %<strub%>"
> +               " on the target system", t);
> +      else
> +     sorry_at (loc,
> +               "unsupported %<strub%> call"
> +               " on the target system");
> +    }
> +
> +  return result;
> +}
> +
>  /* Return TRUE iff NODE is potentially eligible for any strub-enabled mode, and
>     optionally REPORT the reasons for ineligibility.  */
>
>  static inline bool
>  can_strub_p (cgraph_node *node, bool report = false)
>  {
> -  bool result = true;
> +  bool result = strub_target_support_p (node->decl, report);
>
> -  if (!report && strub_always_inline_p (node))
> +  if (!report && (!result || strub_always_inline_p (node)))
>      return result;
>
> +  if (flag_split_stack)
> +    {
> +      result = false;
> +
> +      if (!report)
> +     return result;
> +
> +      sorry_at (DECL_SOURCE_LOCATION (node->decl),
> +             "%qD is not eligible for %<strub%>"
> +             " because %<-fsplit-stack%> is enabled",
> +             node->decl);
> +    }
> +
>    if (lookup_attribute ("noipa", DECL_ATTRIBUTES (node->decl)))
>      {
>        result = false;
> @@ -2417,6 +2461,12 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args,
>                        && (TREE_TYPE (gimple_call_arg (ocall, named_args))
>                            == get_pwmt ())));
>
> +  tree tsup;
> +  if (!(tsup = gimple_call_fndecl (ocall)))
> +    tsup = TREE_TYPE (TREE_TYPE (gimple_call_fn (ocall)));
> +  if (!strub_target_support_p (tsup, true, gimple_location (ocall)))
> +    return;
> +
>    /* 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.  */
> diff --git a/gcc/target.def b/gcc/target.def
> index 52b83e091b94b..08218f3a42adf 100644
> --- a/gcc/target.def
> +++ b/gcc/target.def
> @@ -4457,6 +4457,14 @@ otherwise return false.  The default implementation always returns true.",
>   bool, (void),
>   hook_bool_void_true)
>
> +DEFHOOK
> +(have_strub_support_for,
> + "Returns true if the target supports stack scrubbing for the given function\n\
> +or type, otherwise return false.  The default implementation always returns\n\
> +true.",
> + bool, (tree),
> + hook_bool_tree_true)
> +
>  DEFHOOK
>  (have_speculation_safe_value,
>  "This hook is used to determine the level of target support for\n\
> diff --git a/gcc/testsuite/c-c++-common/strub-O0.c b/gcc/testsuite/c-c++-common/strub-O0.c
> index c7a79a6ea0d8a..f0a3f7b4c6f9a 100644
> --- a/gcc/testsuite/c-c++-common/strub-O0.c
> +++ b/gcc/testsuite/c-c++-common/strub-O0.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O0 -fstrub=strict -fdump-rtl-expand" } */
> +/* { dg-require-effective-target strub } */
>
>  /* At -O0, none of the strub builtins are expanded inline.  */
>
> diff --git a/gcc/testsuite/c-c++-common/strub-O1.c b/gcc/testsuite/c-c++-common/strub-O1.c
> index 96285c975d98e..50403426b18f2 100644
> --- a/gcc/testsuite/c-c++-common/strub-O1.c
> +++ b/gcc/testsuite/c-c++-common/strub-O1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O1 -fstrub=strict -fdump-rtl-expand" } */
> +/* { dg-require-effective-target strub } */
>
>  /* At -O1, without -fno-inline, we fully expand enter, but neither update nor
>     leave.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
> index 8edc0d8aa1321..37e02998e318e 100644
> --- a/gcc/testsuite/c-c++-common/strub-O2.c
> +++ b/gcc/testsuite/c-c++-common/strub-O2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */
> +/* { dg-require-effective-target strub } */
>
>  /* At -O2, without -fno-inline, we fully expand enter and update, and add a test
>     around the leave call.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
> index c6d900cf3c45b..905e2c6b2ffca 100644
> --- a/gcc/testsuite/c-c++-common/strub-O2fni.c
> +++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
> +/* { dg-require-effective-target strub } */
>
>  /* With -fno-inline, none of the strub builtins are inlined.  */
>
> diff --git a/gcc/testsuite/c-c++-common/strub-O3.c b/gcc/testsuite/c-c++-common/strub-O3.c
> index 33ee465e51cb6..3bbf132bdf1ea 100644
> --- a/gcc/testsuite/c-c++-common/strub-O3.c
> +++ b/gcc/testsuite/c-c++-common/strub-O3.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand" } */
> +/* { dg-require-effective-target strub } */
>
>  int __attribute__ ((__strub__)) var;
>
> diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
> index 2936f82079e18..c46fce38e5c91 100644
> --- a/gcc/testsuite/c-c++-common/strub-O3fni.c
> +++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
> +/* { dg-require-effective-target strub } */
>
>  /* With -fno-inline, none of the strub builtins are inlined.  */
>
> diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
> index 479746e57d87e..3b8eb19765cd6 100644
> --- a/gcc/testsuite/c-c++-common/strub-Og.c
> +++ b/gcc/testsuite/c-c++-common/strub-Og.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-Og -fstrub=strict -fdump-rtl-expand" } */
> +/* { dg-require-effective-target strub } */
>
>  /* At -Og, without -fno-inline, we fully expand enter, but neither update nor
>     leave.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
> index 2241d4ea07f27..8cfb253d6764c 100644
> --- a/gcc/testsuite/c-c++-common/strub-Os.c
> +++ b/gcc/testsuite/c-c++-common/strub-Os.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
> +/* { dg-require-effective-target strub } */
>
>  /* At -Os, without -fno-inline, we fully expand enter, and also update.  The
>     expanded update might be larger than a call proper, but argument saving and
> diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
> index a322bcc5da606..2037f681f2973 100644
> --- a/gcc/testsuite/c-c++-common/strub-all1.c
> +++ b/gcc/testsuite/c-c++-common/strub-all1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=all -fdump-ipa-strubm -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
>     strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c
> index db60026d0e080..c026e7d9d289b 100644
> --- a/gcc/testsuite/c-c++-common/strub-all2.c
> +++ b/gcc/testsuite/c-c++-common/strub-all2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=all -fdump-ipa-strubm -fdump-ipa-strub" } */
> +/* { dg-require-effective-target 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
> diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
> index 2f462adc1efe0..3edc89c54eea1 100644
> --- a/gcc/testsuite/c-c++-common/strub-apply1.c
> +++ b/gcc/testsuite/c-c++-common/strub-apply1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict" } */
> +/* { dg-require-effective-target strub } */
>
>  void __attribute__ ((__strub__ ("callable")))
>  apply_function (void *args)
> diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
> index a5d7551f5da5c..838fc75273450 100644
> --- a/gcc/testsuite/c-c++-common/strub-apply2.c
> +++ b/gcc/testsuite/c-c++-common/strub-apply2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict" } */
> +/* { dg-require-effective-target strub } */
>
>  extern void __attribute__ ((__strub__))
>  apply_function (void *args);
> diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
> index 64422a0d1e880..0206e4d930e7d 100644
> --- a/gcc/testsuite/c-c++-common/strub-apply3.c
> +++ b/gcc/testsuite/c-c++-common/strub-apply3.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict" } */
> +/* { dg-require-effective-target strub } */
>
>  void __attribute__ ((__strub__))
>  apply_function (void *args)
> diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
> index 15ffaa031b899..e82504728b2c6 100644
> --- a/gcc/testsuite/c-c++-common/strub-apply4.c
> +++ b/gcc/testsuite/c-c++-common/strub-apply4.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O2 -fstrub=strict -fdump-ipa-strubm" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that implicit enabling of strub mode selects internal strub when the
>     function uses __builtin_apply_args, that prevents the optimization to
> diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
> index b70843b4215a4..a20acc0a48a58 100644
> --- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
> +++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=at-calls -fdump-ipa-strubm -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
>     strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c
> index 97a3988a6b922..7915b33a39a0a 100644
> --- a/gcc/testsuite/c-c++-common/strub-at-calls2.c
> +++ b/gcc/testsuite/c-c++-common/strub-at-calls2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=at-calls -fdump-ipa-strubm -fdump-ipa-strub" } */
> +/* { dg-require-effective-target 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
> diff --git a/gcc/testsuite/c-c++-common/strub-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
> index 3d73431b3dcd3..3689998b5a323 100644
> --- a/gcc/testsuite/c-c++-common/strub-defer-O1.c
> +++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=strict -O1" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that a strub function called by another strub function does NOT defer
>     the strubbing to its caller at -O1.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
> index fddf3c745e7e6..9e01949db6be9 100644
> --- a/gcc/testsuite/c-c++-common/strub-defer-O2.c
> +++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=strict -O2" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that a strub function called by another strub function does NOT defer
>     the strubbing to its caller at -O2.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
> index 7ebc65b58dd72..40ee8edd1e0e6 100644
> --- a/gcc/testsuite/c-c++-common/strub-defer-O3.c
> +++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
> @@ -1,5 +1,6 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=strict -O3" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that a strub function called by another strub function defers the
>     strubbing to its caller at -O3.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
> index fbaf85fe0fafe..67ea9f0463975 100644
> --- a/gcc/testsuite/c-c++-common/strub-defer-Os.c
> +++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
> @@ -1,5 +1,6 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=strict -Os" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that a strub function called by another strub function defers the
>     strubbing to its caller at -Os.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
> index e9d7b7b9ee0a8..d17254904e50a 100644
> --- a/gcc/testsuite/c-c++-common/strub-internal1.c
> +++ b/gcc/testsuite/c-c++-common/strub-internal1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=internal -fdump-ipa-strubm -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
>     strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c
> index 8b8e15a51c71c..afc9189701f82 100644
> --- a/gcc/testsuite/c-c++-common/strub-internal2.c
> +++ b/gcc/testsuite/c-c++-common/strub-internal2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=internal -fdump-ipa-strubm -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* g becomes STRUB_INTERNAL, because of the flag.  */
>  static void
> diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
> index 0a4a7539d3489..f410b268971a6 100644
> --- a/gcc/testsuite/c-c++-common/strub-parms1.c
> +++ b/gcc/testsuite/c-c++-common/strub-parms1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  #include <stdarg.h>
>
> diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
> index 147171d96d5a1..6f572115a88c3 100644
> --- a/gcc/testsuite/c-c++-common/strub-parms2.c
> +++ b/gcc/testsuite/c-c++-common/strub-parms2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  #include <stdarg.h>
>
> diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
> index 4e92682895a43..7383fea9ce881 100644
> --- a/gcc/testsuite/c-c++-common/strub-parms3.c
> +++ b/gcc/testsuite/c-c++-common/strub-parms3.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that uses of a strub variable implicitly enables internal strub for
>     publicly-visible functions, and causes the same transformations to their
> diff --git a/gcc/testsuite/c-c++-common/strub-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c
> index e2f9d8aebca58..d2b4b52c51e60 100644
> --- a/gcc/testsuite/c-c++-common/strub-relaxed1.c
> +++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* The difference between relaxed and strict in this case is that we accept the
>     call from one internal-strub function to another.  Without the error,
> diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c
> index 98474435d2e59..9e5a8e76b6c3d 100644
> --- a/gcc/testsuite/c-c++-common/strub-relaxed2.c
> +++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* The difference between relaxed and strict in this case is that we accept the
>     call from one internal-strub function to another.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
> index 1de15342595e4..aaeba2a2159a9 100644
> --- a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
> +++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O0 -fstrub=strict -fexceptions -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that the expected strub calls are issued.  */
>
> diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c
> index f9209c819004b..30cbdd819f176 100644
> --- a/gcc/testsuite/c-c++-common/strub-short-O0.c
> +++ b/gcc/testsuite/c-c++-common/strub-short-O0.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O0 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that the expected strub calls are issued.  */
>
> diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c
> index bed1dcfb54a45..911fdfb6db9a5 100644
> --- a/gcc/testsuite/c-c++-common/strub-short-O1.c
> +++ b/gcc/testsuite/c-c++-common/strub-short-O1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that the expected strub calls are issued.  */
>
> diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c
> index 6bf0071f52b93..9b23ee3ac3312 100644
> --- a/gcc/testsuite/c-c++-common/strub-short-O2.c
> +++ b/gcc/testsuite/c-c++-common/strub-short-O2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that the expected strub calls are issued.  */
>
> diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c
> index 4732f515bf70c..4b3a8f843ea19 100644
> --- a/gcc/testsuite/c-c++-common/strub-short-O3.c
> +++ b/gcc/testsuite/c-c++-common/strub-short-O3.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
>     enter and leave calls within strub contexts, passing on the enclosing
> diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c
> index 8d6424c479a3a..3627a2406000b 100644
> --- a/gcc/testsuite/c-c++-common/strub-short-Os.c
> +++ b/gcc/testsuite/c-c++-common/strub-short-Os.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that the expected strub calls are issued.  At -O3 and -Os, we omit
>     enter and leave calls within strub contexts, passing on the enclosing
> diff --git a/gcc/testsuite/c-c++-common/strub-split-stack.c b/gcc/testsuite/c-c++-common/strub-split-stack.c
> new file mode 100644
> index 0000000000000..7a030cdb9e9e6
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-split-stack.c
> @@ -0,0 +1,10 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fsplit-stack" } */
> +/* { dg-require-effective-target strub } */
> +/* { dg-require-effective-target split_stack } */
> +
> +void __attribute__ ((__strub__))
> +f () {} /* { dg-message "not eligible|requested" } */
> +
> +void __attribute__ ((__strub__ ("internal")))
> +g () {} /* { dg-message "not eligible|requested" } */
> diff --git a/gcc/testsuite/c-c++-common/strub-strict1.c b/gcc/testsuite/c-c++-common/strub-strict1.c
> index 368522442066e..503eb1734e36f 100644
> --- a/gcc/testsuite/c-c++-common/strub-strict1.c
> +++ b/gcc/testsuite/c-c++-common/strub-strict1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strubm" } */
> +/* { dg-require-effective-target strub } */
>
>  static int __attribute__ ((__strub__)) var;
>
> diff --git a/gcc/testsuite/c-c++-common/strub-strict2.c b/gcc/testsuite/c-c++-common/strub-strict2.c
> index b4f2888321821..3bf1aa30b4af1 100644
> --- a/gcc/testsuite/c-c++-common/strub-strict2.c
> +++ b/gcc/testsuite/c-c++-common/strub-strict2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strubm" } */
> +/* { dg-require-effective-target strub } */
>
>  static int __attribute__ ((__strub__)) var;
>
> diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c
> index e48e0610e079b..ba4b1623e281a 100644
> --- a/gcc/testsuite/c-c++-common/strub-tail-O1.c
> +++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  #include "strub-tail-O2.c"
>
> diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c
> index 87cda7ab21b16..043813b1de467 100644
> --- a/gcc/testsuite/c-c++-common/strub-tail-O2.c
> +++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that the expected strub calls are issued.
>     Tail calls are short-circuited at -O2+.  */
> diff --git a/gcc/testsuite/c-c++-common/strub-unsupported-2.c b/gcc/testsuite/c-c++-common/strub-unsupported-2.c
> new file mode 100644
> index 0000000000000..3586f4f679dfe
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-unsupported-2.c
> @@ -0,0 +1,13 @@
> +/* { dg-do compile } */
> +
> +/* Check that, when strub is not supported (so no dg-required-effective-target
> +   strub above), we report when pointers to strub functions are called.  This
> +   cannot be part of strub-unsupported.c because errors in the strub-mode pass
> +   prevent the main strub pass, where errors at calls are detected, from
> +   running.  */
> +
> +void __attribute__ ((__strub__ ("at-calls"))) (*p) (void);
> +
> +void m () {
> +  p (); /* { dg-message "unsupported" "" { target { ! strub } } } */
> +}
> diff --git a/gcc/testsuite/c-c++-common/strub-unsupported-3.c b/gcc/testsuite/c-c++-common/strub-unsupported-3.c
> new file mode 100644
> index 0000000000000..d6fb4c525c4a6
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-unsupported-3.c
> @@ -0,0 +1,18 @@
> +/* { dg-do compile } */
> +
> +/* Check that, when strub is not supported (so no dg-required-effective-target
> +   strub above), we report when strub functions that are not defined are
> +   called.  This cannot be part of strub-unsupported-2.c because errors in the
> +   strub-mode pass prevent the main strub pass, where errors at calls are
> +   detected, from running.  */
> +
> +extern void __attribute__ ((__strub__))
> +s (void); /* { dg-message "not eligible|requested" "" { target { ! strub } } } */
> +
> +extern void __attribute__ ((__strub__ ("internal")))
> +t (void); /* { dg-message "not eligible|requested" "" { target { ! strub } } } */
> +
> +void m () {
> +  s ();
> +  t ();
> +}
> diff --git a/gcc/testsuite/c-c++-common/strub-unsupported.c b/gcc/testsuite/c-c++-common/strub-unsupported.c
> new file mode 100644
> index 0000000000000..cb5c4049495c4
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/strub-unsupported.c
> @@ -0,0 +1,21 @@
> +/* { dg-do compile } */
> +
> +/* Check that, when strub is not supported (so no dg-required-effective-target
> +   strub above), we report when strub functions are defined, and when they're
> +   called in ways that would require changes.  */
> +
> +void __attribute__ ((__strub__))
> +f (void) {} /* { dg-message "not eligible|requested" "" { target { ! strub } } } */
> +
> +void __attribute__ ((__strub__ ("internal")))
> +g (void) {} /* { dg-message "not eligible|requested" "" { target { ! strub } } } */
> +
> +/* This only gets an error when called, see strub-unsupported-2.c.  */
> +void __attribute__ ((__strub__ ("at-calls"))) (*p) (void);
> +
> +/* These too, see strub-unsupported-3.c.  */
> +extern void __attribute__ ((__strub__))
> +s (void);
> +
> +extern void __attribute__ ((__strub__ ("internal")))
> +t (void);
> diff --git a/gcc/testsuite/c-c++-common/strub-var1.c b/gcc/testsuite/c-c++-common/strub-var1.c
> index eb6250fd39c90..67014aa5de84a 100644
> --- a/gcc/testsuite/c-c++-common/strub-var1.c
> +++ b/gcc/testsuite/c-c++-common/strub-var1.c
> @@ -1,4 +1,5 @@
>  /* { dg-do compile } */
> +/* { dg-require-effective-target strub } */
>
>  int __attribute__ ((strub)) x;
>  float __attribute__ ((strub)) f;
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
> index b5e45ab0525ad..86dbee6746d1b 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-callable1.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that strub and non-strub functions can be called from non-strub
>     contexts, and that strub and callable functions can be called from strub
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
> index 96aa7fe4b07f7..9da120f615645 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-callable2.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that impermissible (cross-strub-context) calls are reported.  */
>
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
> index 5e956cb1a9b6b..22056713cce4b 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-const1.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target 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
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
> index 73d650292dfbf..a105c66d7a9c9 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-const2.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that, along with a strub implicitly-const function call, we issue an
>     asm statement to make sure the watermark passed to it is held in memory
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
> index 2584f1f974a58..386200c2784a4 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-const3.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target 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,
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
> index d819f54ec0230..817e9fa2118b6 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-const4.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target 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
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-data1.c b/gcc/testsuite/c-c++-common/torture/strub-data1.c
> index 7c27a2a1a6dca..132ab63ef733a 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-data1.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* The pointed-to data enables strubbing if accessed.  */
>  int __attribute__ ((__strub__)) var;
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-data2.c b/gcc/testsuite/c-c++-common/torture/strub-data2.c
> index e66d903780afd..b660702d26e75 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-data2.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* The pointer itself is a strub variable, enabling internal strubbing when
>     its value is used.  */
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-data3.c b/gcc/testsuite/c-c++-common/torture/strub-data3.c
> index 5e08e0e58c658..fc44eef6f8fb5 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-data3.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* The pointer itself is a strub variable, that would enable internal strubbing
>     if its value was used.  Here, it's only overwritten, so no strub.  */
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-data4.c b/gcc/testsuite/c-c++-common/torture/strub-data4.c
> index a818e7a38bb5f..85e2f59055b57 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-data4.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* The pointer itself is a strub variable, that would enable internal strubbing
>     if its value was used.  Here, it's only overwritten, so no strub.  */
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
> index ddb0b5c0543b0..0a5edac414df1 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict" } */
> +/* { dg-require-effective-target strub } */
>
>  /* It would be desirable to issue at least warnings for these.  */
>
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
> index c165f312f16de..988954e7ed6bc 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  typedef void __attribute__ ((__strub__)) fntype ();
>  fntype (*ptr);
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
> index 69fcff8d3763d..d3ca91389a700 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  typedef void __attribute__ ((__strub__)) fntype (int, int);
>  fntype (*ptr);
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
> index ff006224909bd..89b5979cf7b78 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  typedef void __attribute__ ((__strub__)) fntype (int, int, ...);
>  fntype (*ptr);
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
> index 614b02228ba29..4917dda8826d9 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=relaxed" } */
> +/* { dg-require-effective-target strub } */
>
>  inline void __attribute__ ((strub ("internal"), always_inline))
>  inl_int_ali (void)
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
> index f9a6b4a16faf8..c45903856d4ff 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=all" } */
> +/* { dg-require-effective-target strub } */
>
>  #include "strub-inlinable1.c"
>
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
> index b4a7f3992bbaa..b0d6139f0a870 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict" } */
> +/* { dg-require-effective-target strub } */
>
>  typedef void ft (void);
>  typedef void ft2 (int, int);
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
> index ef634d351265f..1148c246f2059 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=relaxed -Wpedantic" } */
> +/* { dg-require-effective-target strub } */
>
>  /* C++ does not warn about the partial incompatibilities.
>
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
> index e1f179e160e5c..06a72d86d2c58 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c
> @@ -1,6 +1,7 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=relaxed -Wpedantic -fpermissive" } */
>  /* { dg-prune-output "command-line option .-fpermissive." } */
> +/* { dg-require-effective-target strub } */
>
>  /* See strub-ptrfn2.c.  */
>
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
> index 70b558afad040..83ea1af7056e7 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=relaxed" } */
> +/* { dg-require-effective-target strub } */
>
>  /* This is strub-ptrfn2.c without -Wpedantic.
>
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
> index a262a086837b2..2643136f178cc 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-pure1.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that, along with a strub pure function call, we issue an asm statement
>     to make sure the watermark passed to it is not assumed to be unchanged.  */
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
> index 4c4bd50c209a0..8bda129b77dc6 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-pure2.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that, along with a strub implicitly-pure function call, we issue an asm
>     statement to make sure the watermark passed to it is not assumed to be
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
> index ce195c6b1f1b6..00bcbdd097af8 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-pure3.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target 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.  */
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
> index 75cd54ccb5b5d..ea7c40e7912b4 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-pure4.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +/* { dg-require-effective-target 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
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
> index 7458b3fb54da5..fdf100428631d 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-run1.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
> @@ -1,5 +1,6 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=strict" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that a non-strub function leaves a string behind in the stack, and that
>     equivalent strub functions don't.  Avoid the use of red zones by avoiding
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
> index 5d60a7775f4bb..1228a66599721 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-run2.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
> @@ -1,5 +1,6 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=strict" } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that a non-strub function leaves a string behind in the stack, and that
>     equivalent strub functions don't.  Allow red zones to be used.  */
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
> index c2ad710858e87..e5047a988f5bf 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-run3.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
> @@ -1,6 +1,7 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=strict" } */
>  /* { dg-require-effective-target alloca } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that a non-strub function leaves a string behind in the stack, and that
>     equivalent strub functions don't.  */
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c
> index 3b36b8e5d68ef..0e84a4bab80fc 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-run4.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c
> @@ -1,6 +1,7 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=all" } */
>  /* { dg-require-effective-target alloca } */
> +/* { dg-require-effective-target strub } */
>
>  /* Check that multi-level, multi-inlined functions still get cleaned up as
>     expected, without overwriting temporary stack allocations while they should
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4c.c b/gcc/testsuite/c-c++-common/torture/strub-run4c.c
> index 57f9baf758ded..edc98486dc93a 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-run4c.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-run4c.c
> @@ -1,5 +1,6 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=at-calls" } */
>  /* { dg-require-effective-target alloca } */
> +/* { dg-require-effective-target strub } */
>
>  #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
> index 08de3f1c3b17c..487ed08bb6606 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-run4d.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c
> @@ -1,6 +1,7 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=strict" } */
>  /* { dg-require-effective-target alloca } */
> +/* { dg-require-effective-target strub } */
>
>  #define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls")))
>
> diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4i.c b/gcc/testsuite/c-c++-common/torture/strub-run4i.c
> index 459f6886c5499..a85447ffabfae 100644
> --- a/gcc/testsuite/c-c++-common/torture/strub-run4i.c
> +++ b/gcc/testsuite/c-c++-common/torture/strub-run4i.c
> @@ -1,5 +1,6 @@
>  /* { dg-do run } */
>  /* { dg-options "-fstrub=internal" } */
>  /* { dg-require-effective-target alloca } */
> +/* { dg-require-effective-target strub } */
>
>  #include "strub-run4.c"
> diff --git a/gcc/testsuite/g++.dg/strub-run1.C b/gcc/testsuite/g++.dg/strub-run1.C
> index 0d367fb83d09d..beb8b811f8fca 100644
> --- a/gcc/testsuite/g++.dg/strub-run1.C
> +++ b/gcc/testsuite/g++.dg/strub-run1.C
> @@ -1,5 +1,6 @@
>  // { dg-do run }
>  // { dg-options "-fstrub=internal" }
> +// { dg-require-effective-target strub }
>
>  // Check that we don't get extra copies.
>
> diff --git a/gcc/testsuite/g++.dg/torture/strub-init1.C b/gcc/testsuite/g++.dg/torture/strub-init1.C
> index c226ab10ff651..6ae45fadd70ba 100644
> --- a/gcc/testsuite/g++.dg/torture/strub-init1.C
> +++ b/gcc/testsuite/g++.dg/torture/strub-init1.C
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +// { dg-require-effective-target strub }
>
>  extern int __attribute__((__strub__)) initializer ();
>
> diff --git a/gcc/testsuite/g++.dg/torture/strub-init2.C b/gcc/testsuite/g++.dg/torture/strub-init2.C
> index a7911f1fa7212..8f4849c7fde78 100644
> --- a/gcc/testsuite/g++.dg/torture/strub-init2.C
> +++ b/gcc/testsuite/g++.dg/torture/strub-init2.C
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +// { dg-require-effective-target strub }
>
>  extern int __attribute__((__strub__)) initializer ();
>
> diff --git a/gcc/testsuite/g++.dg/torture/strub-init3.C b/gcc/testsuite/g++.dg/torture/strub-init3.C
> index 6ebebcd01e8ea..14f28e3c276bd 100644
> --- a/gcc/testsuite/g++.dg/torture/strub-init3.C
> +++ b/gcc/testsuite/g++.dg/torture/strub-init3.C
> @@ -1,5 +1,6 @@
>  /* { dg-do compile } */
>  /* { dg-options "-fstrub=strict -fdump-ipa-strub" } */
> +// { dg-require-effective-target strub }
>
>  extern int __attribute__((__strub__)) initializer ();
>
> diff --git a/gcc/testsuite/gnat.dg/strub_access.adb b/gcc/testsuite/gnat.dg/strub_access.adb
> index 29e6996ecf61c..488a2d64afe31 100644
> --- a/gcc/testsuite/gnat.dg/strub_access.adb
> +++ b/gcc/testsuite/gnat.dg/strub_access.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fstrub=relaxed -fdump-ipa-strubm" }
> +--  { dg-require-effective-target strub }
>
>  --  The main subprogram doesn't read from the automatic variable, but
>  --  being an automatic variable, its presence should be enough for the
> diff --git a/gcc/testsuite/gnat.dg/strub_access1.adb b/gcc/testsuite/gnat.dg/strub_access1.adb
> index dae4706016436..4a8653c4d843f 100644
> --- a/gcc/testsuite/gnat.dg/strub_access1.adb
> +++ b/gcc/testsuite/gnat.dg/strub_access1.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fstrub=relaxed" }
> +--  { dg-require-effective-target strub }
>
>  --  Check that we reject 'Access of a strub variable whose type does
>  --  not carry a strub modifier.
> diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb
> index 10445d7cf8451..eb7826dc990f4 100644
> --- a/gcc/testsuite/gnat.dg/strub_attr.adb
> +++ b/gcc/testsuite/gnat.dg/strub_attr.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" }
> +--  { dg-require-effective-target strub }
>
>  package body Strub_Attr is
>     E : exception;
> diff --git a/gcc/testsuite/gnat.dg/strub_disp.adb b/gcc/testsuite/gnat.dg/strub_disp.adb
> index 3dbcc4a357cba..f23d4675def38 100644
> --- a/gcc/testsuite/gnat.dg/strub_disp.adb
> +++ b/gcc/testsuite/gnat.dg/strub_disp.adb
> @@ -1,4 +1,5 @@
>  --  { dg-do compile }
> +--  { dg-require-effective-target strub }
>
>  procedure Strub_Disp is
>     package Foo is
> diff --git a/gcc/testsuite/gnat.dg/strub_disp1.adb b/gcc/testsuite/gnat.dg/strub_disp1.adb
> index 09756a74b7d81..9c4c7f696371d 100644
> --- a/gcc/testsuite/gnat.dg/strub_disp1.adb
> +++ b/gcc/testsuite/gnat.dg/strub_disp1.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fdump-ipa-strub" }
> +--  { dg-require-effective-target strub }
>
>  -- Check that at-calls dispatching calls are transformed.
>
> diff --git a/gcc/testsuite/gnat.dg/strub_ind.adb b/gcc/testsuite/gnat.dg/strub_ind.adb
> index da56acaa957d2..613db69305e05 100644
> --- a/gcc/testsuite/gnat.dg/strub_ind.adb
> +++ b/gcc/testsuite/gnat.dg/strub_ind.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fstrub=strict" }
> +--  { dg-require-effective-target strub }
>
>  --  This is essentially the same test as strub_attr.adb,
>  --  but applying attributes to access types as well.
> diff --git a/gcc/testsuite/gnat.dg/strub_ind1.adb b/gcc/testsuite/gnat.dg/strub_ind1.adb
> index 825e395e6819c..245b0a830f691 100644
> --- a/gcc/testsuite/gnat.dg/strub_ind1.adb
> +++ b/gcc/testsuite/gnat.dg/strub_ind1.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
> +--  { dg-require-effective-target strub }
>
>  --  This is essentially the same test as strub_attr.adb,
>  --  but with an explicit conversion.
> diff --git a/gcc/testsuite/gnat.dg/strub_ind2.adb b/gcc/testsuite/gnat.dg/strub_ind2.adb
> index e918b39263117..b9bfe50e9296e 100644
> --- a/gcc/testsuite/gnat.dg/strub_ind2.adb
> +++ b/gcc/testsuite/gnat.dg/strub_ind2.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fstrub=strict" }
> +--  { dg-require-effective-target strub }
>
>  --  This is essentially the same test as strub_attr.adb,
>  --  but with an explicit conversion.
> diff --git a/gcc/testsuite/gnat.dg/strub_intf.adb b/gcc/testsuite/gnat.dg/strub_intf.adb
> index 8f0212a75866f..f43854705d073 100644
> --- a/gcc/testsuite/gnat.dg/strub_intf.adb
> +++ b/gcc/testsuite/gnat.dg/strub_intf.adb
> @@ -1,4 +1,5 @@
>  --  { dg-do compile }
> +--  { dg-require-effective-target strub }
>
>  --  Check that strub mode mismatches between overrider and overridden
>  --  subprograms are reported.
> diff --git a/gcc/testsuite/gnat.dg/strub_intf1.adb b/gcc/testsuite/gnat.dg/strub_intf1.adb
> index bf77321cef790..7a38a4c49ba8d 100644
> --- a/gcc/testsuite/gnat.dg/strub_intf1.adb
> +++ b/gcc/testsuite/gnat.dg/strub_intf1.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fdump-ipa-strub" }
> +--  { dg-require-effective-target strub }
>
>  -- Check that at-calls dispatching calls to interfaces are transformed.
>
> diff --git a/gcc/testsuite/gnat.dg/strub_intf2.adb b/gcc/testsuite/gnat.dg/strub_intf2.adb
> index e8880dbc43730..7992b7344fb87 100644
> --- a/gcc/testsuite/gnat.dg/strub_intf2.adb
> +++ b/gcc/testsuite/gnat.dg/strub_intf2.adb
> @@ -1,4 +1,5 @@
>  --  { dg-do compile }
> +--  { dg-require-effective-target strub }
>
>  --  Check that strub mode mismatches between overrider and overridden
>  --  subprograms are reported even when the overriders for an
> diff --git a/gcc/testsuite/gnat.dg/strub_renm.adb b/gcc/testsuite/gnat.dg/strub_renm.adb
> index 217367e712d82..abfb120b51468 100644
> --- a/gcc/testsuite/gnat.dg/strub_renm.adb
> +++ b/gcc/testsuite/gnat.dg/strub_renm.adb
> @@ -1,4 +1,5 @@
>  --  { dg-do compile }
> +--  { dg-require-effective-target strub }
>
>  procedure Strub_Renm is
>     procedure P (X : Integer);
> diff --git a/gcc/testsuite/gnat.dg/strub_renm1.adb b/gcc/testsuite/gnat.dg/strub_renm1.adb
> index a11adbfb5a9d6..68d3230b5356c 100644
> --- a/gcc/testsuite/gnat.dg/strub_renm1.adb
> +++ b/gcc/testsuite/gnat.dg/strub_renm1.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fstrub=relaxed -fdump-ipa-strub" }
> +--  { dg-require-effective-target strub }
>
>  procedure Strub_Renm1 is
>     V : Integer := 0;
> diff --git a/gcc/testsuite/gnat.dg/strub_renm2.adb b/gcc/testsuite/gnat.dg/strub_renm2.adb
> index c488c20826fdb..3cb81ea03f763 100644
> --- a/gcc/testsuite/gnat.dg/strub_renm2.adb
> +++ b/gcc/testsuite/gnat.dg/strub_renm2.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fstrub=strict -fdump-ipa-strub" }
> +--  { dg-require-effective-target strub }
>
>  procedure Strub_Renm2 is
>     V : Integer := 0;
> diff --git a/gcc/testsuite/gnat.dg/strub_var.adb b/gcc/testsuite/gnat.dg/strub_var.adb
> index 3d158de28031f..7c6affa06d4ab 100644
> --- a/gcc/testsuite/gnat.dg/strub_var.adb
> +++ b/gcc/testsuite/gnat.dg/strub_var.adb
> @@ -1,5 +1,6 @@
>  --  { dg-do compile }
>  --  { dg-options "-fstrub=strict -fdump-ipa-strubm" }
> +--  { dg-require-effective-target strub }
>
>  -- We don't read from the automatic variable, but being an automatic
>  --  variable, its presence should be enough for the procedure to get
> diff --git a/gcc/testsuite/gnat.dg/strub_var1.adb b/gcc/testsuite/gnat.dg/strub_var1.adb
> index 6a504e09198b6..64b7e65fe9b0f 100644
> --- a/gcc/testsuite/gnat.dg/strub_var1.adb
> +++ b/gcc/testsuite/gnat.dg/strub_var1.adb
> @@ -1,4 +1,5 @@
>  --  { dg-do compile }
> +--  { dg-require-effective-target strub }
>
>  with Strub_Attr;
>  procedure Strub_Var1 is
> diff --git a/gcc/testsuite/lib/target-supports.exp b/gcc/testsuite/lib/target-supports.exp
> index 3fcce6be49d6f..40a60c198cfe8 100644
> --- a/gcc/testsuite/lib/target-supports.exp
> +++ b/gcc/testsuite/lib/target-supports.exp
> @@ -1302,6 +1302,13 @@ proc check_stack_check_available { stack_kind } {
>      } "$stack_opt"]
>  }
>
> +# Return 1 if the target supports stack scrubbing.
> +proc check_effective_target_strub {} {
> +    return [check_no_compiler_messages strub assembly {
> +     void __attribute__ ((__strub__)) fn (void) {}
> +    } ""]
> +}
> +
>  # Return 1 if compilation with -freorder-blocks-and-partition is error-free
>  # for trivial code, 0 otherwise.  As some targets (ARM for example) only
>  # warn when -fprofile-use is also supplied we test that combination too.
> diff --git a/libgcc/Makefile.in b/libgcc/Makefile.in
> index d8163c5af9903..3f77283490ef6 100644
> --- a/libgcc/Makefile.in
> +++ b/libgcc/Makefile.in
> @@ -434,7 +434,7 @@ LIB2ADD += enable-execute-stack.c
>  LIB2ADD += $(srcdir)/hardcfr.c
>
>  # Stack scrubbing infrastructure.
> -LIB2ADD += $(srcdir)/strub.c
> +@HAVE_STRUB_SUPPORT@LIB2ADD += $(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/configure b/libgcc/configure
> index cf149209652e3..567158955a329 100755
> --- a/libgcc/configure
> +++ b/libgcc/configure
> @@ -593,6 +593,7 @@ asm_hidden_op
>  extra_parts
>  cpu_type
>  get_gcc_base_ver
> +HAVE_STRUB_SUPPORT
>  thread_header
>  tm_defines
>  tm_file
> @@ -5702,6 +5703,31 @@ esac
>
>
>
> +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for strub support" >&5
> +$as_echo_n "checking for strub support... " >&6; }
> +if ${libgcc_cv_strub_support+:} false; then :
> +  $as_echo_n "(cached) " >&6
> +else
> +  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
> +/* end confdefs.h.  */
> +void __attribute__ ((__strub__)) fn (void) {}
> +_ACEOF
> +if ac_fn_c_try_compile "$LINENO"; then :
> +  libgcc_cv_strub_support=yes
> +else
> +  libgcc_cv_strub_support=no
> +fi
> +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
> +fi
> +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $libgcc_cv_strub_support" >&5
> +$as_echo "$libgcc_cv_strub_support" >&6; }
> +if test "x$libgcc_cv_strub_support" != xno; then
> +  HAVE_STRUB_SUPPORT=
> +else
> +  HAVE_STRUB_SUPPORT='# '
> +fi
> +
> +
>  # Determine what GCC version number to use in filesystem paths.
>
>    get_gcc_base_ver="cat"
> diff --git a/libgcc/configure.ac b/libgcc/configure.ac
> index 2fc9d5d7c93e9..9c0e415501a80 100644
> --- a/libgcc/configure.ac
> +++ b/libgcc/configure.ac
> @@ -694,6 +694,19 @@ AC_SUBST(tm_defines)
>  # Map from thread model to thread header.
>  GCC_AC_THREAD_HEADER([$target_thread_file])
>
> +AC_CACHE_CHECK([for strub support],
> +  [libgcc_cv_strub_support],
> +  [AC_COMPILE_IFELSE(
> +    [AC_LANG_SOURCE([void __attribute__ ((__strub__)) fn (void) {}])],
> +    [libgcc_cv_strub_support=yes],
> +    [libgcc_cv_strub_support=no])])
> +if test "x$libgcc_cv_strub_support" != xno; then
> +  HAVE_STRUB_SUPPORT=
> +else
> +  HAVE_STRUB_SUPPORT='# '
> +fi
> +AC_SUBST(HAVE_STRUB_SUPPORT)
> +
>  # Determine what GCC version number to use in filesystem paths.
>  GCC_BASE_VER
>
-----------------
Siemens Electronic Design Automation GmbH; Anschrift: Arnulfstraße 201, 80634 München; Gesellschaft mit beschränkter Haftung; Geschäftsführer: Thomas Heurung, Frank Thürauf; Sitz der Gesellschaft: München; Registergericht München, HRB 106955

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

* [PATCH]
  2023-12-07 16:44                                     ` Thomas Schwinge
@ 2023-12-07 17:52                                       ` Alexandre Oliva
  2023-12-08  6:46                                         ` [PATCH] Richard Biener
  2023-12-08  9:33                                         ` [PATCH] strub: skip emutls after strubm errors Thomas Schwinge
  0 siblings, 2 replies; 59+ messages in thread
From: Alexandre Oliva @ 2023-12-07 17:52 UTC (permalink / raw)
  To: Thomas Schwinge, FX Coudert
  Cc: Tobias Burnus, Richard Biener, gcc-patches, Jeremy Bennett,
	Craig Blackmore, Graham Markall, Martin Jambor, Jan Hubicka,
	Jim Wilson, Jeff Law, Jakub Jelinek, Tom de Vries

On Dec  7, 2023, Thomas Schwinge <thomas@codesourcery.com> wrote:

> Thank you for looking into this so promptly!

You're welcome ;-)


>     during IPA pass: emutls
>     [...]/source-gcc/gcc/testsuite/c-c++-common/strub-unsupported-3.c:18:1: internal compiler error: in verify_curr_properties, at passes.cc:2198

Aah, this smells a lot like the issue that François-Xavier reported,
that the following patch is expected to fix.  I'm still regstrapping it
on x86_64-linux-gnu, after checking that it addressed the symptom on a
cross compiler to the target for which it had originally been reported.
Ok to install, once you confirm that it cures these ICEs?


strub: skip emutls after strubm errors

The emutls pass requires PROP_ssa, but if the strubm pass (or any
other pre-SSA pass) issues errors, all of the build_ssa_passes are
skipped, so the property is not set, but emutls still attempts to run,
on targets that use it, despite earlier errors, so it hits the
unsatisfied requirement.

Adjust emutls to be skipped in case of earlier errors.


for  gcc/ChangeLog

	* tree-emutls.cc: Include diagnostic-core.h.
	(pass_ipa_lower_emutls::gate): Skip if errors were seen.
---
 gcc/tree-emutls.cc |    3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/gcc/tree-emutls.cc b/gcc/tree-emutls.cc
index 5dca5a8291356..38de202717a1a 100644
--- a/gcc/tree-emutls.cc
+++ b/gcc/tree-emutls.cc
@@ -35,6 +35,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "langhooks.h"
 #include "tree-iterator.h"
 #include "gimplify.h"
+#include "diagnostic-core.h" /* for seen_error */
 
 /* Whenever a target does not support thread-local storage (TLS) natively,
    we can emulate it with some run-time support in libgcc.  This will in
@@ -841,7 +842,7 @@ public:
   bool gate (function *) final override
     {
       /* If the target supports TLS natively, we need do nothing here.  */
-      return !targetm.have_tls;
+      return !targetm.have_tls && !seen_error ();
     }
 
   unsigned int execute (function *) final override


-- 
Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
   Free Software Activist                   GNU Toolchain Engineer
More tolerance and less prejudice are key for inclusion and diversity
Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH v5] Introduce strub: machine-independent stack scrubbing
  2023-12-06 10:22                             ` Jan Hubicka
@ 2023-12-07 21:19                               ` Alexandre Oliva
  2023-12-07 21:39                               ` Alexandre Oliva
  1 sibling, 0 replies; 59+ messages in thread
From: Alexandre Oliva @ 2023-12-07 21:19 UTC (permalink / raw)
  To: Jan Hubicka
  Cc: Richard Biener, gcc-patches, Jeremy Bennett, Craig Blackmore,
	Graham Markall, Martin Jambor, Jim Wilson, Jeff Law,
	Jakub Jelinek

On Dec  6, 2023, Jan Hubicka <hubicka@ucw.cz> wrote:

> I am sorry for sending this late.

No need to be sorry.  Thank you very much for taking the time to review
and comment on it.

> I think the ipa changes are generally fine.

Phew :-)

>> +static inline bool
>> +strub_always_inline_p (cgraph_node *node)
>> +{
>> +  return lookup_attribute ("always_inline", DECL_ATTRIBUTES (node->decl));
>> +}
> We may want to ahve this as cgraph_node::always_inline_p since there are
> now quite many places we look up this attribute.

Can do.  Would such a global refactoring still be welcome at this stage,
or should it be saved for stage1?  I guess it could still go in, so
simple it is...


>> +/* The strub pass proper adjusts types, signatures, and at-calls calls, and
>> +   splits internal-strub functions.  */
>> +
>> +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.  */

> I think Martin ma have more specific opinion on this, but since this is
> not running as the ipa pass during WPA stage, I think the param
> modification infrastructure is not really that much hepful here. 

Hmm...  I wonder if this is indeed what Martin refers to.  There are two
separate pieces of logic for parm-tweaking, one for "at-calls" strub
functions, that get the signature and the type of the function itself
modified (akin to adding the implicit "this" parameter to a C++
nonstatic member-function), and is implemented under the comment above,
and there's the splitting-out of "internal" strub function bodies into a
clone with a modified signature, that is implemented elsewhere.  The
latter uses cloning and thus (some, but not much) IPA param modification
infrastructure, but the former doesn't IIRC.

>> +	/* ??? Maybe we could adjust it instead.  */
>> +	if (drop_fnspec)
>> +	  remove_named_attribute_unsharing ("fn spec",
>> +					    &TYPE_ATTRIBUTES (nftype));

> ipa param modification also doesn't know how to update fn spec, this is
> something we should look into next stage1...
> There is also access attribute which speaks directly about individual
> arugments, perhaps you want to drop this one too?

Hmm, I can't recall whether I've come across it before (it sounds
vaguely familiar, but unless they become "fn spec" (ISTR synthetic "fn
spec"s), I think I'd have dealt with them already.

I'll dig it a little further.

> Are variadic thunks working with scrubbing?

Yeah, the wrapper is rewritten to call va_start itself, and the
split-out wrapped body is modified to call va_copy instead of va_start.

-- 
Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
   Free Software Activist                   GNU Toolchain Engineer
More tolerance and less prejudice are key for inclusion and diversity
Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH v5] Introduce strub: machine-independent stack scrubbing
  2023-12-06 10:22                             ` Jan Hubicka
  2023-12-07 21:19                               ` Alexandre Oliva
@ 2023-12-07 21:39                               ` Alexandre Oliva
  2023-12-09  2:08                                 ` [PATCH] strub: add note on attribute access Alexandre Oliva
  1 sibling, 1 reply; 59+ messages in thread
From: Alexandre Oliva @ 2023-12-07 21:39 UTC (permalink / raw)
  To: Jan Hubicka
  Cc: Richard Biener, gcc-patches, Jeremy Bennett, Craig Blackmore,
	Graham Markall, Martin Jambor, Jim Wilson, Jeff Law,
	Jakub Jelinek

On Dec  6, 2023, Jan Hubicka <hubicka@ucw.cz> wrote:

> There is also access attribute which speaks directly about individual
> arugments, perhaps you want to drop this one too?

Ah, I've looked a little into it, and now I have a vague recollection of
why I don't mess with them: they only apply to arguments of pointer or
reference type, and those are not (supposed to be) affected by the
changes, not by indirection (which is what would make some "fn spec"
notes impossible to convey), not by insertion of synthetic parameters.
Those are placed at the end in part to avoid messing with
parameter-index attributes.  So attribute access can safely be left
alone.

Thanks for raising the issue.  Maybe there should be at least a comment
there, and perhaps some asserts to check that pointer and reference
types don't make to indirect_parms.

-- 
Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
   Free Software Activist                   GNU Toolchain Engineer
More tolerance and less prejudice are key for inclusion and diversity
Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH]
  2023-12-07 17:52                                       ` [PATCH] Alexandre Oliva
@ 2023-12-08  6:46                                         ` Richard Biener
  2023-12-08  9:33                                         ` [PATCH] strub: skip emutls after strubm errors Thomas Schwinge
  1 sibling, 0 replies; 59+ messages in thread
From: Richard Biener @ 2023-12-08  6:46 UTC (permalink / raw)
  To: Alexandre Oliva
  Cc: Thomas Schwinge, FX Coudert, Tobias Burnus, gcc-patches,
	Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Jim Wilson, Jeff Law, Jakub Jelinek, Tom de Vries

On Thu, Dec 7, 2023 at 6:52 PM Alexandre Oliva <oliva@adacore.com> wrote:
>
> On Dec  7, 2023, Thomas Schwinge <thomas@codesourcery.com> wrote:
>
> > Thank you for looking into this so promptly!
>
> You're welcome ;-)
>
>
> >     during IPA pass: emutls
> >     [...]/source-gcc/gcc/testsuite/c-c++-common/strub-unsupported-3.c:18:1: internal compiler error: in verify_curr_properties, at passes.cc:2198
>
> Aah, this smells a lot like the issue that François-Xavier reported,
> that the following patch is expected to fix.  I'm still regstrapping it
> on x86_64-linux-gnu, after checking that it addressed the symptom on a
> cross compiler to the target for which it had originally been reported.
> Ok to install, once you confirm that it cures these ICEs?
>
>
> strub: skip emutls after strubm errors
>
> The emutls pass requires PROP_ssa, but if the strubm pass (or any
> other pre-SSA pass) issues errors, all of the build_ssa_passes are
> skipped, so the property is not set, but emutls still attempts to run,
> on targets that use it, despite earlier errors, so it hits the
> unsatisfied requirement.
>
> Adjust emutls to be skipped in case of earlier errors.

OK.

>
> for  gcc/ChangeLog
>
>         * tree-emutls.cc: Include diagnostic-core.h.
>         (pass_ipa_lower_emutls::gate): Skip if errors were seen.
> ---
>  gcc/tree-emutls.cc |    3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/gcc/tree-emutls.cc b/gcc/tree-emutls.cc
> index 5dca5a8291356..38de202717a1a 100644
> --- a/gcc/tree-emutls.cc
> +++ b/gcc/tree-emutls.cc
> @@ -35,6 +35,7 @@ along with GCC; see the file COPYING3.  If not see
>  #include "langhooks.h"
>  #include "tree-iterator.h"
>  #include "gimplify.h"
> +#include "diagnostic-core.h" /* for seen_error */
>
>  /* Whenever a target does not support thread-local storage (TLS) natively,
>     we can emulate it with some run-time support in libgcc.  This will in
> @@ -841,7 +842,7 @@ public:
>    bool gate (function *) final override
>      {
>        /* If the target supports TLS natively, we need do nothing here.  */
> -      return !targetm.have_tls;
> +      return !targetm.have_tls && !seen_error ();
>      }
>
>    unsigned int execute (function *) final override
>
>
> --
> Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
>    Free Software Activist                   GNU Toolchain Engineer
> More tolerance and less prejudice are key for inclusion and diversity
> Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH] strub: skip emutls after strubm errors
  2023-12-07 17:52                                       ` [PATCH] Alexandre Oliva
  2023-12-08  6:46                                         ` [PATCH] Richard Biener
@ 2023-12-08  9:33                                         ` Thomas Schwinge
  2023-12-10  9:16                                           ` FX Coudert
  1 sibling, 1 reply; 59+ messages in thread
From: Thomas Schwinge @ 2023-12-08  9:33 UTC (permalink / raw)
  To: Alexandre Oliva
  Cc: FX Coudert, Tobias Burnus, Richard Biener, gcc-patches,
	Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Jim Wilson, Jeff Law, Jakub Jelinek, Tom de Vries

Hi Alexandre!

On 2023-12-07T14:52:19-0300, Alexandre Oliva <oliva@adacore.com> wrote:
> On Dec  7, 2023, Thomas Schwinge <thomas@codesourcery.com> wrote:
>>     during IPA pass: emutls
>>     [...]/source-gcc/gcc/testsuite/c-c++-common/strub-unsupported-3.c:18:1: internal compiler error: in verify_curr_properties, at passes.cc:2198
>
> Aah, this smells a lot like the issue that François-Xavier reported,
> that the following patch is expected to fix.  I'm still regstrapping it
> on x86_64-linux-gnu, after checking that it addressed the symptom on a
> cross compiler to the target for which it had originally been reported.
> Ok to install, once you confirm that it cures these ICEs?

Yes, GCC/nvptx ICEs gone with that, thanks!


Grüße
 Thomas


> strub: skip emutls after strubm errors
>
> The emutls pass requires PROP_ssa, but if the strubm pass (or any
> other pre-SSA pass) issues errors, all of the build_ssa_passes are
> skipped, so the property is not set, but emutls still attempts to run,
> on targets that use it, despite earlier errors, so it hits the
> unsatisfied requirement.
>
> Adjust emutls to be skipped in case of earlier errors.
>
>
> for  gcc/ChangeLog
>
>       * tree-emutls.cc: Include diagnostic-core.h.
>       (pass_ipa_lower_emutls::gate): Skip if errors were seen.
> ---
>  gcc/tree-emutls.cc |    3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/gcc/tree-emutls.cc b/gcc/tree-emutls.cc
> index 5dca5a8291356..38de202717a1a 100644
> --- a/gcc/tree-emutls.cc
> +++ b/gcc/tree-emutls.cc
> @@ -35,6 +35,7 @@ along with GCC; see the file COPYING3.  If not see
>  #include "langhooks.h"
>  #include "tree-iterator.h"
>  #include "gimplify.h"
> +#include "diagnostic-core.h" /* for seen_error */
>
>  /* Whenever a target does not support thread-local storage (TLS) natively,
>     we can emulate it with some run-time support in libgcc.  This will in
> @@ -841,7 +842,7 @@ public:
>    bool gate (function *) final override
>      {
>        /* If the target supports TLS natively, we need do nothing here.  */
> -      return !targetm.have_tls;
> +      return !targetm.have_tls && !seen_error ();
>      }
>
>    unsigned int execute (function *) final override
-----------------
Siemens Electronic Design Automation GmbH; Anschrift: Arnulfstraße 201, 80634 München; Gesellschaft mit beschränkter Haftung; Geschäftsführer: Thomas Heurung, Frank Thürauf; Sitz der Gesellschaft: München; Registergericht München, HRB 106955

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

* [PATCH] strub: add note on attribute access
  2023-12-07 21:39                               ` Alexandre Oliva
@ 2023-12-09  2:08                                 ` Alexandre Oliva
  2023-12-11  7:26                                   ` Richard Biener
  2023-12-12 14:21                                   ` Jan Hubicka
  0 siblings, 2 replies; 59+ messages in thread
From: Alexandre Oliva @ 2023-12-09  2:08 UTC (permalink / raw)
  To: Jan Hubicka
  Cc: Richard Biener, gcc-patches, Jeremy Bennett, Craig Blackmore,
	Graham Markall, Martin Jambor, Jim Wilson, Jeff Law,
	Jakub Jelinek

On Dec  7, 2023, Alexandre Oliva <oliva@adacore.com> wrote:

> Thanks for raising the issue.  Maybe there should be at least a comment
> there, and perhaps some asserts to check that pointer and reference
> types don't make to indirect_parms.

Document why attribute access doesn't need the same treatment as fn
spec, and check that the assumption behind it holds.

Regstrapped on x86_64-linux-gnu.  Ok to install?


for  gcc/ChangeLog

	* ipa-strub.cc (pass_ipa_strub::execute): Check that we don't
	add indirection to pointer parameters, and document attribute
	access non-interactions.
---
 gcc/ipa-strub.cc |   11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/gcc/ipa-strub.cc b/gcc/ipa-strub.cc
index 2afb7a455751d..8ec6824e8a802 100644
--- a/gcc/ipa-strub.cc
+++ b/gcc/ipa-strub.cc
@@ -2889,6 +2889,13 @@ pass_ipa_strub::execute (function *)
 		&& (tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
 		    <= 4 * UNITS_PER_WORD))))
 	{
+	  /* No point in indirecting pointer types.  Presumably they
+	     won't ever pass the size-based test above, but check the
+	     assumption here, because getting this wrong would mess
+	     with attribute access and possibly others.  We deal with
+	     fn spec below.  */
+	  gcc_checking_assert (!POINTER_TYPE_P (TREE_TYPE (nparm)));
+
 	  indirect_nparms.add (nparm);
 
 	  /* ??? Is there any case in which it is not safe to suggest the parms
@@ -2976,7 +2983,9 @@ pass_ipa_strub::execute (function *)
 		}
 	    }
 
-	/* ??? Maybe we could adjust it instead.  */
+	/* ??? Maybe we could adjust it instead.  Note we don't need
+	   to mess with attribute access: pointer-typed parameters are
+	   not modified, so they can remain unchanged.  */
 	if (drop_fnspec)
 	  remove_named_attribute_unsharing ("fn spec",
 					    &TYPE_ATTRIBUTES (nftype));


-- 
Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
   Free Software Activist                   GNU Toolchain Engineer
More tolerance and less prejudice are key for inclusion and diversity
Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* Re: [PATCH] strub: skip emutls after strubm errors
  2023-12-08  9:33                                         ` [PATCH] strub: skip emutls after strubm errors Thomas Schwinge
@ 2023-12-10  9:16                                           ` FX Coudert
  0 siblings, 0 replies; 59+ messages in thread
From: FX Coudert @ 2023-12-10  9:16 UTC (permalink / raw)
  To: Thomas Schwinge
  Cc: Alexandre Oliva, Tobias Burnus, Richard Biener, GCC Patches,
	Jeremy Bennett, Craig Blackmore, Graham Markall, Martin Jambor,
	Jan Hubicka, Jim Wilson, Jeff Law, Jakub Jelinek, Tom de Vries

> Yes, GCC/nvptx ICEs gone with that, thanks!

And on darwin as well, test results are back to the same state as before. Thanks!

FX

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

* Re: [PATCH] strub: add note on attribute access
  2023-12-09  2:08                                 ` [PATCH] strub: add note on attribute access Alexandre Oliva
@ 2023-12-11  7:26                                   ` Richard Biener
  2023-12-12 14:21                                   ` Jan Hubicka
  1 sibling, 0 replies; 59+ messages in thread
From: Richard Biener @ 2023-12-11  7:26 UTC (permalink / raw)
  To: Alexandre Oliva
  Cc: Jan Hubicka, gcc-patches, Jeremy Bennett, Craig Blackmore,
	Graham Markall, Martin Jambor, Jim Wilson, Jeff Law,
	Jakub Jelinek

On Sat, Dec 9, 2023 at 3:09 AM Alexandre Oliva <oliva@adacore.com> wrote:
>
> On Dec  7, 2023, Alexandre Oliva <oliva@adacore.com> wrote:
>
> > Thanks for raising the issue.  Maybe there should be at least a comment
> > there, and perhaps some asserts to check that pointer and reference
> > types don't make to indirect_parms.
>
> Document why attribute access doesn't need the same treatment as fn
> spec, and check that the assumption behind it holds.
>
> Regstrapped on x86_64-linux-gnu.  Ok to install?

OK

>
> for  gcc/ChangeLog
>
>         * ipa-strub.cc (pass_ipa_strub::execute): Check that we don't
>         add indirection to pointer parameters, and document attribute
>         access non-interactions.
> ---
>  gcc/ipa-strub.cc |   11 ++++++++++-
>  1 file changed, 10 insertions(+), 1 deletion(-)
>
> diff --git a/gcc/ipa-strub.cc b/gcc/ipa-strub.cc
> index 2afb7a455751d..8ec6824e8a802 100644
> --- a/gcc/ipa-strub.cc
> +++ b/gcc/ipa-strub.cc
> @@ -2889,6 +2889,13 @@ pass_ipa_strub::execute (function *)
>                 && (tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (nparm)))
>                     <= 4 * UNITS_PER_WORD))))
>         {
> +         /* No point in indirecting pointer types.  Presumably they
> +            won't ever pass the size-based test above, but check the
> +            assumption here, because getting this wrong would mess
> +            with attribute access and possibly others.  We deal with
> +            fn spec below.  */
> +         gcc_checking_assert (!POINTER_TYPE_P (TREE_TYPE (nparm)));
> +
>           indirect_nparms.add (nparm);
>
>           /* ??? Is there any case in which it is not safe to suggest the parms
> @@ -2976,7 +2983,9 @@ pass_ipa_strub::execute (function *)
>                 }
>             }
>
> -       /* ??? Maybe we could adjust it instead.  */
> +       /* ??? Maybe we could adjust it instead.  Note we don't need
> +          to mess with attribute access: pointer-typed parameters are
> +          not modified, so they can remain unchanged.  */
>         if (drop_fnspec)
>           remove_named_attribute_unsharing ("fn spec",
>                                             &TYPE_ATTRIBUTES (nftype));
>
>
> --
> Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
>    Free Software Activist                   GNU Toolchain Engineer
> More tolerance and less prejudice are key for inclusion and diversity
> Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

* [PATCH] testsuite: Disable -fstack-protector* for some strub tests
  2023-12-02 17:56                           ` [PATCH v5] " Alexandre Oliva
                                               ` (3 preceding siblings ...)
  2023-12-06 10:22                             ` Jan Hubicka
@ 2023-12-11  8:40                             ` Jakub Jelinek
  2023-12-11  8:59                               ` Richard Biener
  4 siblings, 1 reply; 59+ messages in thread
From: Jakub Jelinek @ 2023-12-11  8:40 UTC (permalink / raw)
  To: Richard Biener, Alexandre Oliva; +Cc: gcc-patches

Hi!

In our distro builds, we test with
RUNTESTFLAGS='--target_board=unix\{,-fstack-protector-strong\}'
because SSP is something we use widely in the distribution.
4 new strub test FAIL with that option though, as can be
seen with a simple
make check-gcc check-g++ RUNTESTFLAGS='--target_board=unix\{,-fstack-protector-strong\} dg.exp=strub-O*'
- in particular, the expand dump
\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label
regexps see code_labels in there introduced for stack protector.

The following patch fixes it by using -fno-stack-protector for these
explicitly.

Tested on x86_64-linux, ok for trunk?

2023-12-11  Jakub Jelinek  <jakub@redhat.com>

	* c-c++-common/strub-O2fni.c: Add -fno-stack-protector to dg-options.
	* c-c++-common/strub-O3fni.c: Likewise.
	* c-c++-common/strub-Os.c: Likewise.
	* c-c++-common/strub-Og.c: Likewise. 

--- gcc/testsuite/c-c++-common/strub-O2fni.c.jj	2023-12-08 08:28:23.689170380 +0100
+++ gcc/testsuite/c-c++-common/strub-O2fni.c	2023-12-11 09:25:49.100792709 +0100
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline -fno-stack-protector" } */
 /* { dg-require-effective-target strub } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
--- gcc/testsuite/c-c++-common/strub-O3fni.c.jj	2023-12-08 08:28:23.707170125 +0100
+++ gcc/testsuite/c-c++-common/strub-O3fni.c	2023-12-11 09:25:56.388695362 +0100
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
+/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline -fno-stack-protector" } */
 /* { dg-require-effective-target strub } */
 
 /* With -fno-inline, none of the strub builtins are inlined.  */
--- gcc/testsuite/c-c++-common/strub-Os.c.jj	2023-12-08 08:28:23.707170125 +0100
+++ gcc/testsuite/c-c++-common/strub-Os.c	2023-12-11 09:26:24.994313267 +0100
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
+/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand -fno-stack-protector" } */
 /* { dg-require-effective-target strub } */
 
 /* At -Os, without -fno-inline, we fully expand enter, and also update.  The
--- gcc/testsuite/c-c++-common/strub-Og.c.jj	2023-12-08 08:28:23.707170125 +0100
+++ gcc/testsuite/c-c++-common/strub-Og.c	2023-12-11 09:26:07.077552587 +0100
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-Og -fstrub=strict -fdump-rtl-expand" } */
+/* { dg-options "-Og -fstrub=strict -fdump-rtl-expand -fno-stack-protector" } */
 /* { dg-require-effective-target strub } */
 
 /* At -Og, without -fno-inline, we fully expand enter, but neither update nor

	Jakub


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

* Re: [PATCH] testsuite: Disable -fstack-protector* for some strub tests
  2023-12-11  8:40                             ` [PATCH] testsuite: Disable -fstack-protector* for some strub tests Jakub Jelinek
@ 2023-12-11  8:59                               ` Richard Biener
  0 siblings, 0 replies; 59+ messages in thread
From: Richard Biener @ 2023-12-11  8:59 UTC (permalink / raw)
  To: Jakub Jelinek; +Cc: Alexandre Oliva, gcc-patches

On Mon, 11 Dec 2023, Jakub Jelinek wrote:

> Hi!
> 
> In our distro builds, we test with
> RUNTESTFLAGS='--target_board=unix\{,-fstack-protector-strong\}'
> because SSP is something we use widely in the distribution.
> 4 new strub test FAIL with that option though, as can be
> seen with a simple
> make check-gcc check-g++ RUNTESTFLAGS='--target_board=unix\{,-fstack-protector-strong\} dg.exp=strub-O*'
> - in particular, the expand dump
> \[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label
> regexps see code_labels in there introduced for stack protector.
> 
> The following patch fixes it by using -fno-stack-protector for these
> explicitly.
> 
> Tested on x86_64-linux, ok for trunk?

OK.

> 2023-12-11  Jakub Jelinek  <jakub@redhat.com>
> 
> 	* c-c++-common/strub-O2fni.c: Add -fno-stack-protector to dg-options.
> 	* c-c++-common/strub-O3fni.c: Likewise.
> 	* c-c++-common/strub-Os.c: Likewise.
> 	* c-c++-common/strub-Og.c: Likewise. 
> 
> --- gcc/testsuite/c-c++-common/strub-O2fni.c.jj	2023-12-08 08:28:23.689170380 +0100
> +++ gcc/testsuite/c-c++-common/strub-O2fni.c	2023-12-11 09:25:49.100792709 +0100
> @@ -1,5 +1,5 @@
>  /* { dg-do compile } */
> -/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
> +/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline -fno-stack-protector" } */
>  /* { dg-require-effective-target strub } */
>  
>  /* With -fno-inline, none of the strub builtins are inlined.  */
> --- gcc/testsuite/c-c++-common/strub-O3fni.c.jj	2023-12-08 08:28:23.707170125 +0100
> +++ gcc/testsuite/c-c++-common/strub-O3fni.c	2023-12-11 09:25:56.388695362 +0100
> @@ -1,5 +1,5 @@
>  /* { dg-do compile } */
> -/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline" } */
> +/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline -fno-stack-protector" } */
>  /* { dg-require-effective-target strub } */
>  
>  /* With -fno-inline, none of the strub builtins are inlined.  */
> --- gcc/testsuite/c-c++-common/strub-Os.c.jj	2023-12-08 08:28:23.707170125 +0100
> +++ gcc/testsuite/c-c++-common/strub-Os.c	2023-12-11 09:26:24.994313267 +0100
> @@ -1,5 +1,5 @@
>  /* { dg-do compile } */
> -/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */
> +/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand -fno-stack-protector" } */
>  /* { dg-require-effective-target strub } */
>  
>  /* At -Os, without -fno-inline, we fully expand enter, and also update.  The
> --- gcc/testsuite/c-c++-common/strub-Og.c.jj	2023-12-08 08:28:23.707170125 +0100
> +++ gcc/testsuite/c-c++-common/strub-Og.c	2023-12-11 09:26:07.077552587 +0100
> @@ -1,5 +1,5 @@
>  /* { dg-do compile } */
> -/* { dg-options "-Og -fstrub=strict -fdump-rtl-expand" } */
> +/* { dg-options "-Og -fstrub=strict -fdump-rtl-expand -fno-stack-protector" } */
>  /* { dg-require-effective-target strub } */
>  
>  /* At -Og, without -fno-inline, we fully expand enter, but neither update nor
> 
> 	Jakub
> 
> 

-- 
Richard Biener <rguenther@suse.de>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Ivo Totev, Andrew McDonald, Werner Knoblich; (HRB 36809, AG Nuernberg)

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

* Re: [PATCH] strub: add note on attribute access
  2023-12-09  2:08                                 ` [PATCH] strub: add note on attribute access Alexandre Oliva
  2023-12-11  7:26                                   ` Richard Biener
@ 2023-12-12 14:21                                   ` Jan Hubicka
  1 sibling, 0 replies; 59+ messages in thread
From: Jan Hubicka @ 2023-12-12 14:21 UTC (permalink / raw)
  To: Alexandre Oliva
  Cc: Richard Biener, gcc-patches, Jeremy Bennett, Craig Blackmore,
	Graham Markall, Martin Jambor, Jim Wilson, Jeff Law,
	Jakub Jelinek

> On Dec  7, 2023, Alexandre Oliva <oliva@adacore.com> wrote:
> 
> > Thanks for raising the issue.  Maybe there should be at least a comment
> > there, and perhaps some asserts to check that pointer and reference
> > types don't make to indirect_parms.
> 
> Document why attribute access doesn't need the same treatment as fn
> spec, and check that the assumption behind it holds.
> 
> Regstrapped on x86_64-linux-gnu.  Ok to install?
> 
> 
> for  gcc/ChangeLog
> 
> 	* ipa-strub.cc (pass_ipa_strub::execute): Check that we don't
> 	add indirection to pointer parameters, and document attribute
> 	access non-interactions.
OK,
Honza

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

* [PATCH FYI] www: new AdaCore-contributed hardening features in gcc 13 and 14
  2023-11-30 12:00                         ` Richard Biener
  2023-12-02 17:56                           ` [PATCH v5] " Alexandre Oliva
@ 2023-12-20  8:15                           ` Alexandre Oliva
  1 sibling, 0 replies; 59+ messages in thread
From: Alexandre Oliva @ 2023-12-20  8:15 UTC (permalink / raw)
  To: Richard Biener; +Cc: gcc-patches

On Nov 30, 2023, Richard Biener <richard.guenther@gmail.com> wrote:

>> >> Here are changes.html entries for this and for the other newly-added
>> >> features:
>> 
>> > LGTM.

(sorry, I should be following up two messages upthread, but I don't seem
to have saved that one)

I've finally put in the www changes.


Mention hardening of conditionals (added in gcc 13), control flow
redundancy, hardened booleans, and stack scrubbing.

Also cover forced inlining of string operations while at that.
---
 htdocs/gcc-13/changes.html |    6 ++++++
 htdocs/gcc-14/changes.html |   29 +++++++++++++++++++++++++++++
 2 files changed, 35 insertions(+)

diff --git a/htdocs/gcc-13/changes.html b/htdocs/gcc-13/changes.html
index ee6383a095706..d3bacc167cd30 100644
--- a/htdocs/gcc-13/changes.html
+++ b/htdocs/gcc-13/changes.html
@@ -168,6 +168,12 @@ You may also want to check out our
     been added, see also
     <a href="https://gcc.gnu.org/onlinedocs/gcc/Freestanding-Environments.html">Profiling and Test Coverage in Freestanding Environments</a>.
   </li>
+  <li>
+    New options <code>-fharden-compares</code>
+    and <code>-fharden-conditional-branches</code> to verify compares
+    and conditional branches, to detect some power-deprivation
+    hardware attacks, using reversed conditions.
+  </li>
 </ul>
 
 
diff --git a/htdocs/gcc-14/changes.html b/htdocs/gcc-14/changes.html
index 11c7ca7e6967f..24e6409a11b68 100644
--- a/htdocs/gcc-14/changes.html
+++ b/htdocs/gcc-14/changes.html
@@ -128,6 +128,35 @@ a work-in-progress.</p>
     of hardening flags.  The options it enables can be displayed using the
     <code>--help=hardened</code> option.
   </li>
+  <li>
+    New option <code>-fharden-control-flow-redundancy</code>, to
+    verify, at the end of functions, that the visited basic blocks
+    correspond to a legitimate execution path, so as to detect and
+    prevent attacks that transfer control into the middle of
+    functions.
+  </li>
+  <li>
+    New type attribute <code>hardbool</code>, for C and Ada.  Hardened
+    booleans take user-specified representations for <code>true</code>
+    and <code>false</code>, presumably with higher hamming distance
+    than standard booleans, and get verified at every use, detecting
+    memory corruption and some malicious attacks.
+  </li>
+  <li>
+    New type attribute <code>strub</code> to control stack scrubbing
+    properties of functions and variables.  The stack frame used by
+    functions marked with the attribute gets zeroed-out upon returning
+    or exception escaping.  Scalar variables marked with the attribute
+    cause functions contaning or accessing them to get stack scrubbing
+    enabled implicitly.
+  </li>
+  <li>
+    New option <code>-finline-stringops</code>, to force inline
+    expansion of <code>memcmp</code>, <code>memcpy</code>,
+    <code>memmove</code> and <code>memset</code>, even when that is
+    not an optimization, to avoid relying on library
+    implementations.
+  </li>
 </ul>
 <!-- .................................................................. -->
 <h2 id="languages">New Languages and Language specific improvements</h2>



-- 
Alexandre Oliva, happy hacker            https://FSFLA.org/blogs/lxo/
   Free Software Activist                   GNU Toolchain Engineer
More tolerance and less prejudice are key for inclusion and diversity
Excluding neuro-others for not behaving ""normal"" is *not* inclusive

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

end of thread, other threads:[~2023-12-20  8:15 UTC | newest]

Thread overview: 59+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <ormtqpsbuc.fsf@lxoliva.fsfla.org>
2021-09-09  7:11 ` [PATCH] strub: machine-independent stack scrubbing Alexandre Oliva
2022-07-29  6:16   ` [PATCH v2 00/10] Introduce " Alexandre Oliva
2022-07-29  6:24     ` [PATCH v2 01/10] Introduce strub: documentation, and new command-line options Alexandre Oliva
2022-07-29  6:25     ` [PATCH v2 02/10] Introduce strub: torture tests for C and C++ Alexandre Oliva
2022-08-09 13:34       ` Alexandre Oliva
2022-07-29  6:25     ` [PATCH v2 03/10] Introduce strub: non-torture " Alexandre Oliva
2022-07-29  6:26     ` [PATCH v2 04/10] Introduce strub: tests for C++ and Ada Alexandre Oliva
2022-07-29  6:26     ` [PATCH v2 05/10] Introduce strub: builtins and runtime Alexandre Oliva
2022-07-29  6:27     ` [PATCH v2 06/10] Introduce strub: attributes Alexandre Oliva
2022-07-29  6:28     ` [PATCH v2 07/10] Introduce strub: infrastructure interfaces and adjustments Alexandre Oliva
2022-07-29  6:28     ` [PATCH v2 08/10] Introduce strub: strub modes Alexandre Oliva
2022-07-29  6:30     ` [PATCH v2 09/10] Introduce strub: strubm (mode assignment) pass Alexandre Oliva
2022-07-29  6:34     ` [PATCH v2 10/10] Introduce strub: strub pass Alexandre Oliva
2022-07-29  6:36     ` [PATCH v2 00/10] Introduce strub: machine-independent stack scrubbing Alexandre Oliva
2022-10-10  8:48       ` Richard Biener
2022-10-11 11:57         ` Alexandre Oliva
2022-10-11 11:59           ` Richard Biener
2022-10-11 13:33             ` Alexandre Oliva
2022-10-13 11:38               ` Richard Biener
2022-10-13 13:15                 ` Alexandre Oliva
2023-06-16  6:09     ` [PATCH v3] " Alexandre Oliva
2023-06-27 21:28       ` Qing Zhao
2023-06-28  8:20         ` Alexandre Oliva
2023-10-20  6:03       ` [PATCH v4] " Alexandre Oliva
2023-10-26  6:15         ` Alexandre Oliva
2023-11-20 12:40           ` Alexandre Oliva
2023-11-22 14:14             ` Richard Biener
2023-11-23 10:56               ` Alexandre Oliva
2023-11-23 12:05                 ` Richard Biener
2023-11-29  8:53                   ` Alexandre Oliva
2023-11-29 12:48                     ` Richard Biener
2023-11-30  4:13                       ` Alexandre Oliva
2023-11-30 12:00                         ` Richard Biener
2023-12-02 17:56                           ` [PATCH v5] " Alexandre Oliva
2023-12-05  6:25                             ` Alexandre Oliva
2023-12-06  1:04                               ` Alexandre Oliva
2023-12-05  9:01                             ` Richard Biener
2023-12-06  8:36                             ` Causes to nvptx bootstrap fail: " Tobias Burnus
2023-12-06 11:32                               ` Thomas Schwinge
2023-12-06 22:12                                 ` Alexandre Oliva
2023-12-07  3:33                                   ` [PATCH] strub: enable conditional support Alexandre Oliva
2023-12-07  7:24                                     ` Richard Biener
2023-12-07 16:44                                     ` Thomas Schwinge
2023-12-07 17:52                                       ` [PATCH] Alexandre Oliva
2023-12-08  6:46                                         ` [PATCH] Richard Biener
2023-12-08  9:33                                         ` [PATCH] strub: skip emutls after strubm errors Thomas Schwinge
2023-12-10  9:16                                           ` FX Coudert
2023-12-07  7:21                                   ` Causes to nvptx bootstrap fail: [PATCH v5] Introduce strub: machine-independent stack scrubbing Richard Biener
2023-12-06 10:22                             ` Jan Hubicka
2023-12-07 21:19                               ` Alexandre Oliva
2023-12-07 21:39                               ` Alexandre Oliva
2023-12-09  2:08                                 ` [PATCH] strub: add note on attribute access Alexandre Oliva
2023-12-11  7:26                                   ` Richard Biener
2023-12-12 14:21                                   ` Jan Hubicka
2023-12-11  8:40                             ` [PATCH] testsuite: Disable -fstack-protector* for some strub tests Jakub Jelinek
2023-12-11  8:59                               ` Richard Biener
2023-12-20  8:15                           ` [PATCH FYI] www: new AdaCore-contributed hardening features in gcc 13 and 14 Alexandre Oliva
2023-11-30  5:04                       ` [PATCH v4] Introduce strub: machine-independent stack scrubbing Alexandre Oliva
2023-11-30 11:56                         ` Richard Biener

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