From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail-lf1-x12d.google.com (mail-lf1-x12d.google.com [IPv6:2a00:1450:4864:20::12d]) by sourceware.org (Postfix) with ESMTPS id B4A283858C41 for ; Tue, 5 Dec 2023 09:05:28 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org B4A283858C41 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=gmail.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org B4A283858C41 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=2a00:1450:4864:20::12d ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1701767135; cv=none; b=JFGoPgc0ygpFrLyiy9qQfZTrQrt1b8ZhiET45eCPYql2w0TbAT8lJwXwVH2bTNmUvSQNvyUg7k0/h3H9G2SqhJsDgObf+QljreQkGE+L1s/PRyzT/AyMzyMQIq+hpmC9wj8cFcRbdC48oycl55VVYKyuGBFL6qz2WRHgiw8mHlE= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1701767135; c=relaxed/simple; bh=e3wIMxJFjEx9F/252VYfnMYPvJPcOgKYL8hFNkc6oQs=; h=DKIM-Signature:MIME-Version:From:Date:Message-ID:Subject:To; b=p8Mr3kjTCCwo51WltHDqAks2Od+XZfHHNLGigmSi7VgMehh20hhnAqvmXEat226n/r+oddeJbq+J6SWV6H/ZhLjF6JsliM20SJh/Xcr3QTxTlctyQgOt8XzW18zZOMvkaxqpjCSiPhuGp5bmuH8TNoR0yUNklhwU/J4yWlKpkyQ= ARC-Authentication-Results: i=1; server2.sourceware.org Received: by mail-lf1-x12d.google.com with SMTP id 2adb3069b0e04-50c0dbaf2baso129696e87.3 for ; Tue, 05 Dec 2023 01:05:28 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1701767127; x=1702371927; darn=gcc.gnu.org; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=pkvCynYMEK1JAWC+TSr2tghLcpYYG+LXocJO/do3Y5Y=; b=ldsU8IXIN/wsvBOya0mr1dsH+b9WHA6kMLic4W0/WCBtBAurtxozjB+W0sRx8sWvz6 lEibijZs4d2cMngGcIix6Y4+Z1TN/1gxZ5hUk0bkBCtiHS8Yh7ZsTyOOKliXliahnwO3 Ub+xsZx1XbJBi+QKxyPuLaF8JcFukOGUrApC8WUKzsSGUT9VFQiLd44mgBxyEIHnThpm 0mLffsLL/XetIYBprPwvnAGXQ0iySfuEjWLsQvzlSKVfyqKFAYjj85ms48jOGBS8PBsP 4k8orBqvFYMUk1kvt2LrNj2cQjbqPXUe440uVG3rrGSuCzELkTm0A0SOg+NZc84N09ml GhDg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1701767127; x=1702371927; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=pkvCynYMEK1JAWC+TSr2tghLcpYYG+LXocJO/do3Y5Y=; b=GtQF0cfZybrulhI2/Uf4Y75Z/qoEkc3uaffNQU1EhX4d5RbNxL6Kf+9hsQufucVXSu 6mITTaAneEtboBjELJ4ADrcUhzqrWBork06Q5IKlgWvyRORABp7BNKfg9NiSxd1s8r/C skbe3KcMwMwP3TRoE/wThTa9T8j7YAf45otGUIixpbVif12fKMKmgDt5P5LcF0TKudvQ Ve3j8ooM5EhZQPhuALDWkcDvcgnzqOZz4ha7qoZkv8aqLrnT74S89FQQKb/V2g1GsE3U Rgn1xeEIdJT2/V8ZaFAAR/d5MpTBVN+6LaQJkm0iWLI1JmTeFnqe6eOUPjkTnSgs225m wo4A== X-Gm-Message-State: AOJu0YxldMy98yVnDdXozUZHt4XF+/9v+UQIZtijYwc8dYYXah2LsTca Io2c/rAMtX9sF3jyPOYHKlb+u+9xlF3z0vqVouU= X-Google-Smtp-Source: AGHT+IHsOFEJO7LyZPpbhh3+TXnjOgFH9xNwHdVlJ2aNBNpIv5DHYelV5vT66BhK8RVpT31RFknL8Vji9/TgKX/an9U= X-Received: by 2002:ac2:544a:0:b0:50c:504:7408 with SMTP id d10-20020ac2544a000000b0050c05047408mr198387lfn.43.1701767125478; Tue, 05 Dec 2023 01:05:25 -0800 (PST) MIME-Version: 1.0 References: In-Reply-To: From: Richard Biener Date: Tue, 5 Dec 2023 10:01:27 +0100 Message-ID: Subject: Re: [PATCH v5] Introduce strub: machine-independent stack scrubbing To: Alexandre Oliva Cc: gcc-patches@gcc.gnu.org, Jeremy Bennett , Craig Blackmore , Graham Markall , Martin Jambor , Jan Hubicka , Jim Wilson , Jeff Law , Jakub Jelinek Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable X-Spam-Status: No, score=-7.7 required=5.0 tests=BAYES_00,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_FROM,GIT_PATCH_0,RCVD_IN_DNSWL_NONE,SPF_HELO_NONE,SPF_PASS,TXREP,T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org List-Id: On Sat, Dec 2, 2023 at 6:56=E2=80=AFPM Alexandre Oliva = wrote: > > On Nov 30, 2023, Richard Biener wrote: > > > On Thu, Nov 30, 2023 at 5:13=E2=80=AFAM Alexandre Oliva wrote: > > >> >> Here are changes.html entries for this and for the other newly-adde= d > >> >> 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=3D*): New options. > * doc/extend.texi (strub): New type attribute. > (__builtin_stack_address): New function. > (Stack Scrubbing): New section. > * doc/invoke.texi (-fstrub=3D*): 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 =3D \ > ipa-reference.o \ > ipa-ref.o \ > ipa-utils.o \ > + ipa-strub.o \ > ipa.o \ > ira.o \ > ira-build.o \ > @@ -2879,6 +2880,7 @@ GTFILES =3D $(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 th= at won't > + leave on the stack caller data passed to them. This stops implicit c= alls > + introduced in subprograms that have their stack scrubbed from being f= lagged > + as unsafe, even in -fstrub=3Dstrict 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 attrib= ute, by > + calling strub_make_callable. */ > +#include "ipa-strub.h" > + > /* We should avoid allocating more than ALLOCA_THRESHOLD bytes via alloc= a, > for fear of running out of stack space. If we need more, we use xmal= loc > instead. */ > @@ -454,6 +469,7 @@ gigi (Node_Id gnat_root, > int64_type, NULL_TRE= E), > 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, f= alse, > 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_kin= d kind) > =3D create_subprog_decl (get_identifier (Name_Buffer), NULL_TREE, ft= ype, > 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, t= ree 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 =3D true; > + bool enable =3D true; > + > + if (args && FUNCTION_POINTER_TYPE_P (*node)) > + *node =3D 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 =3D true; > + break; > + > + case 0: > + warning (OPT_Wattributes, > + "%qE attribute ignored because of argument %qE", > + name, TREE_VALUE (args)); > + *no_add_attrs =3D true; > + enable =3D false; > + break; > + > + case -1: > + case -2: > + enable =3D false; > + break; > + > + default: > + gcc_unreachable (); > + } > + > + args =3D 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 =3D true; > + enable =3D false; > + } > + > + /* Warn about unmet expectations that the strub attribute works like a > + qualifier. ??? Could/should we extend it to the element/field type= s > + here? */ > + if (TREE_CODE (*node) =3D=3D ARRAY_TYPE > + || VECTOR_TYPE_P (*node) > + || TREE_CODE (*node) =3D=3D 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 sett= ing, > + implicitly or explicitly, note that the attribute was seen, so that= we can > + reduce the compile-time overhead to nearly zero when the strub feat= ure is > + not used. */ > + if (enable && flag_strub < -2) > + flag_strub +=3D 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 fla= gs, > flags &=3D ~(int) ATTR_FLAG_TYPE_IN_PLACE; > } > > - if (spec->function_type_required && TREE_CODE (*anode) !=3D FUNCTI= ON_TYPE > - && TREE_CODE (*anode) !=3D METHOD_TYPE) > + if (spec->function_type_required > + && !FUNC_OR_METHOD_TYPE_P (*anode)) > { > if (TREE_CODE (*anode) =3D=3D POINTER_TYPE > && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode))) > @@ -905,7 +906,24 @@ decl_attributes (tree *node, tree attributes, int fl= ags, > TYPE_NAME (tt) =3D *node; > } > > - *anode =3D cur_and_last_decl[0]; > + if (*anode !=3D cur_and_last_decl[0]) > + { > + /* Even if !spec->function_type_required, allow the attribu= te > + handler to request the attribute to be applied to the fu= nction > + type, rather than to the function pointer type, by setti= ng > + cur_and_last_decl[0] to the function type. */ > + if (!fn_ptr_tmp > + && POINTER_TYPE_P (*anode) > + && TREE_TYPE (*anode) =3D=3D cur_and_last_decl[0] > + && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (*anode))) > + { > + fn_ptr_tmp =3D TREE_TYPE (*anode); > + fn_ptr_quals =3D TYPE_QUALS (*anode); > + anode =3D &fn_ptr_tmp; > + } > + *anode =3D cur_and_last_decl[0]; > + } > + > if (ret =3D=3D 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)) !=3D NUL= L) > ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) !=3D N= ULL)) > return 0; > + int strub_ret =3D strub_comptypes (CONST_CAST_TREE (type1), > + CONST_CAST_TREE (type2)); > + if (strub_ret =3D=3D 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 res= ult. */ > - return targetm.comp_type_attributes (type1, type2); > + int target_ret =3D targetm.comp_type_attributes (type1, type2); > + if (target_ret =3D=3D 0) > + return target_ret; > + if (strub_ret =3D=3D 2 || target_ret =3D=3D 2) > + return 2; > + if (strub_ret =3D=3D 1 && target_ret =3D=3D 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 e= xp) > } > } > > +#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 =3D expand_builtin_stack_address (); > + > + tree wmptr =3D CALL_EXPR_ARG (exp, 0); > + tree wmtype =3D TREE_TYPE (TREE_TYPE (wmptr)); > + tree wmtree =3D fold_build2 (MEM_REF, wmtype, wmptr, > + build_int_cst (TREE_TYPE (wmptr), 0)); > + rtx wmark =3D 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 =3D expand_builtin_stack_address (); > + > +#ifdef RED_ZONE_SIZE > + /* Here's how the strub enter, update and leave functions deal with re= d zones. > + > + If it weren't for red zones, update, called from within a strub con= text, > + 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 strubbi= ng the > + stack afterwards. > + > + Ideally, we'd update the watermark so as to cover the used amount o= f red > + zone, and strub starting at the caller's other end of the (presumab= ly > + 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 t= ell 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 stac= k frame, > + and the strub builtins may all have been inlined, turning a strub f= unction > + into a leaf. > + > + So cleaning the range from the caller's stack pointer (one end of t= he 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 c= ontexts > + to use their own watermark as the strub starting point. So, if A c= alls 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 tha= t > + doesn't overlap with B's. With that, we don't need to know who's l= eaf and > + who isn't: inlined calls will shrink their strub window to zero, ea= ch > + 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 it= self, > + 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-s= trub > + contexts, so it doesn't use the red zone, and it will therefore cor= rectly > + 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_LEA= F) */) > + { > + poly_int64 red_zone_size =3D RED_ZONE_SIZE; > +#if STACK_GROWS_DOWNWARD > + red_zone_size =3D -red_zone_size; > +#endif > + stktop =3D plus_constant (ptr_mode, stktop, red_zone_size); > + stktop =3D force_reg (ptr_mode, stktop); > + } > +#endif > + > + tree wmptr =3D CALL_EXPR_ARG (exp, 0); > + tree wmtype =3D TREE_TYPE (TREE_TYPE (wmptr)); > + tree wmtree =3D fold_build2 (MEM_REF, wmtype, wmptr, > + build_int_cst (TREE_TYPE (wmptr), 0)); > + rtx wmark =3D expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY); > + > + rtx wmarkr =3D force_reg (ptr_mode, wmark); > + > + rtx_code_label *lab =3D 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 t= he > + enclosing function. This avoids a problem with the following scena= rio: A > + calls B and B calls C, and both B and C get inlined into A. B allo= cates > + temporary stack space before calling C. If we don't update A's wat= ermark, > + 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 expan= ding > + strub_leave inline. */ > + tree xwmptr =3D (optimize > 2 > + ? strub_watermark_parm (current_function_decl) > + : wmptr); > + if (wmptr !=3D xwmptr) > + { > + wmptr =3D xwmptr; > + wmtype =3D TREE_TYPE (TREE_TYPE (wmptr)); > + wmtree =3D fold_build2 (MEM_REF, wmtype, wmptr, > + build_int_cst (TREE_TYPE (wmptr), 0)); > + wmark =3D expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY); > + wmarkr =3D force_reg (ptr_mode, wmark); > + > + do_compare_rtx_and_jump (stktop, wmarkr, STACK_TOPS, STACK_UNSIGNE= D, > + 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 =3D NULL_RTX; > + > + if (tree wmptr =3D (optimize > + ? strub_watermark_parm (current_function_decl) > + : NULL_TREE)) > + { > + tree wmtype =3D TREE_TYPE (TREE_TYPE (wmptr)); > + tree wmtree =3D fold_build2 (MEM_REF, wmtype, wmptr, > + build_int_cst (TREE_TYPE (wmptr), 0)); > + rtx wmark =3D expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMO= RY); > + stktop =3D force_reg (ptr_mode, wmark); > + } > + > + if (!stktop) > + stktop =3D expand_builtin_stack_address (); > + > + tree wmptr =3D CALL_EXPR_ARG (exp, 0); > + tree wmtype =3D TREE_TYPE (TREE_TYPE (wmptr)); > + tree wmtree =3D fold_build2 (MEM_REF, wmtype, wmptr, > + build_int_cst (TREE_TYPE (wmptr), 0)); > + rtx wmark =3D expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY); > + > + rtx wmarkr =3D force_reg (ptr_mode, wmark); > + > +#if ! STACK_GROWS_DOWNWARD > + rtx base =3D stktop; > + rtx end =3D wmarkr; > +#else > + rtx base =3D wmarkr; > + rtx end =3D stktop; > +#endif > + > + /* We're going to modify it, so make sure it's not e.g. the stack poin= ter. */ > + base =3D copy_to_reg (base); > + > + rtx_code_label *done =3D 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 with= in the > + loop. */ > + base =3D memory_address (ptr_mode, base); > + end =3D memory_address (ptr_mode, end); > + > + rtx zero =3D force_operand (const0_rtx, NULL_RTX); > + int ulen =3D 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 =3D plus_constant (Pmode, base, ulen); > + rtx dstm =3D gen_rtx_MEM (ptr_mode, base); > + > + rtx_code_label *loop =3D gen_label_rtx (); > + emit_label (loop); > + emit_move_insn (dstm, zero); > + emit_move_insn (base, force_operand (incr, NULL_RTX)); > +#else > + rtx decr =3D plus_constant (Pmode, end, -ulen); > + rtx dstm =3D gen_rtx_MEM (ptr_mode, end); > + > + rtx_code_label *loop =3D 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 subtarge= t, 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 =3D expand_builtin_strub_enter (exp); > + if (target) > + return target; > + break; > + > + case BUILT_IN___STRUB_UPDATE: > + target =3D expand_builtin_strub_update (exp); > + if (target) > + return target; > + break; > + > + case BUILT_IN___STRUB_LEAVE: > + target =3D 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_NOTHR= OW_LIST) > DEF_GCC_BUILTIN (BUILT_IN_FRAME_ADDRESS, "frame_address", BT_FN_P= TR_UINT, ATTR_NULL) > +DEF_GCC_BUILTIN (BUILT_IN_STACK_ADDRESS, "stack_address", BT_FN_P= TR, ATTR_NULL) > +DEF_BUILTIN_STUB (BUILT_IN___STRUB_ENTER, "__builtin___strub_enter= ") > +DEF_BUILTIN_STUB (BUILT_IN___STRUB_UPDATE, "__builtin___strub_upda= te") > +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_NOTH= ROW_LEAF_LIST) > DEF_GCC_BUILTIN (BUILT_IN_FROB_RETURN_ADDR, "frob_return_addr", B= T_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, boo= l *); > 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[]= =3D > { "no_stack_protector", 0, 0, true, false, false, false, > handle_no_stack_protector_function_attribut= e, > 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, tre= e, 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 =3D true; > + > + if (args && FUNCTION_POINTER_TYPE_P (*node)) > + *node =3D 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 =3D true; > + break; > + > + case 0: > + warning (OPT_Wattributes, > + "%qE attribute ignored because of argument %qE", > + name, TREE_VALUE (args)); > + *no_add_attrs =3D true; > + enable =3D false; > + break; > + > + case -1: > + case -2: > + enable =3D false; > + break; > + > + default: > + gcc_unreachable (); > + } > + > + args =3D 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 =3D true; > + enable =3D false; > + } > + > + /* Warn about unmet expectations that the strub attribute works like a > + qualifier. ??? Could/should we extend it to the element/field type= s > + here? */ > + if (TREE_CODE (*node) =3D=3D ARRAY_TYPE > + || VECTOR_TYPE_P (*node) > + || TREE_CODE (*node) =3D=3D 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 sett= ing, > + implicitly or explicitly, note that the attribute was seen, so that= we can > + reduce the compile-time overhead to nearly zero when the strub feat= ure is > + not used. */ > + if (enable && flag_strub < -2) > + flag_strub +=3D 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 =3D 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 =3D false; > @@ -395,7 +395,8 @@ symtab_node::reset (void) > cpp_implicit_alias =3D false; > > remove_all_references (); > - remove_from_same_comdat_group (); > + if (!preserve_comdat_group) > + remove_from_same_comdat_group (); > > if (cgraph_node *cn =3D dyn_cast (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=3Ddisable > +Common RejectNegative Var(flag_strub, 0) > +Disable stack scrub entirely, disregarding strub attributes. > + > +fstrub=3Dstrict > +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=3Drelaxed > +Common RejectNegative Var(flag_strub, -3) Init(-3) > +Restore default strub mode: as per attributes, with relaxed checking. > + > +fstrub=3Dall > +Common RejectNegative Var(flag_strub, 3) > +Enable stack scrubbing for all viable functions. > + > +fstrub=3Dat-calls > +Common RejectNegative Var(flag_strub, 1) > +Enable at-calls stack scrubbing for all viable functions. > + > +fstrub=3Dinternal > +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 curr= ent > function. > * Return Address:: Getting the return or frame address of a functio= n. > +* Stack Scrubbing:: Stack scrubbing internal interfaces. > * Vector Extensions:: Using vector instructions through built-in funct= ions. > * Offsetof:: Special syntax for implementing @code{offsetof}. > * __sync Builtins:: Legacy built-in functions for atomic memory acce= ss. > @@ -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) =3D fo= o; > +@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=3Dstrict}, 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 =3D * (ptr_to_strub_int_type) &i; > + /* Writing to a global strub variable does not enable strub. */ > + var =3D 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=3Dstrict}, 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=3Dstrict, 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=3Dstrict, 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) =3D 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) =3D bad; > + /* Ok, safe. */ > + c_p (); > + > + /* Assign an internal function to pointer-to-callable. > + Flagged as not quite compatible with -Wpedantic. */ > + c_p =3D bar; > + /* Ok, safe. */ > + c_p (); > + > + /* Assign an at-calls function to pointer-to-callable. > + Flaggged as incompatible. */ > + c_p =3D 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=3D*} 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); =3D 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 mod= e. */ > +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 **@v= ar{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 **@v= ar{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 guarde= d 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=3D@var{reg} -fstack-limit-symbol=3D@var{sym} > -fno-stack-limit -fsplit-stack > +-fstrub=3Ddisable -fstrub=3Dstrict -fstrub=3Drelaxed > +-fstrub=3Dall -fstrub=3Dat-calls -fstrub=3Dinternal > -fvtable-verify=3D@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 larg= e stack. Support for > this is implemented in the gold linker in GNU binutils release 2.21 > and later. > > +@opindex -fstrub=3Ddisable > +@item -fstrub=3Ddisable > +Disable stack scrubbing entirely, ignoring any @code{strub} attributes. > +See @xref{Common Type Attributes}. > + > +@opindex fstrub=3Dstrict > +@item -fstrub=3Dstrict > +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=3Drelaxed > +@item -fstrub=3Drelaxed > +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=3D*} options that precede it in > +the command line. > + > +@opindex fstrub=3Dat-calls > +@item -fstrub=3Dat-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=3Dinternal > +@item -fstrub=3Dinternal > +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=3Dall > +@item -fstrub=3Dall > +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=3D@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:|templa= te|operator|friend|static|m > [(){},*:<>;=3D%/|+\!\?\.-] { 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 correspo= nds > @@ -443,6 +444,11 @@ can_inline_edge_p (struct cgraph_edge *e, bool repor= t, > inlinable =3D false; > } > > + if (inlinable && !strub_inlinable_to_p (callee, caller)) > + { > + e->inline_failed =3D CIF_UNSPECIFIED; > + inlinable =3D 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 . > + > +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 > +. */ > + > +#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=3Drelaxed mode > + is in effect (that's the default). In -fstrub=3Dstrict 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=3Dstrict 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 pu= rposes > + 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 c= all, 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 equiv= alent > + effect. Once expand_call gains the ability to issue extra memory use= s 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 =3D 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 =3D 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 =3D 2, > + > + /* This denotes a function whose stack is not strubbed, but that is > + nevertheless explicitly or implicitly marked as callable from strub= bing > + functions. Normally, only STRUB_AT_CALLS (and STRUB_INTERNAL -> > + STRUB_WRAPPED) functions can be called from strubbing contexts (bod= ies of > + STRUB_AT_CALLS, STRUB_INTERNAL and STRUB_WRAPPED functions), but at= tribute > + strub(3) enables other functions to be (indirectly) called from the= se > + contexts. Some builtins and internal functions may be implicitly m= arked as > + STRUB_CALLABLE. */ > + STRUB_CALLABLE =3D 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 =3D -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 =3D -2, > + > + /* This denotes an always_inline function that requires strubbing. It= can > + only be called from, and inlined into, other strubbing contexts. *= / > + STRUB_INLINABLE =3D -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 sin= ce > + at-calls strubbing is viable, that's selected as an optimization. = This > + mode addresses the inconvenience that such functions may have diffe= rent > + 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, wherea= s > + STRUB_INTERNAL would not be callable. */ > + STRUB_AT_CALLS_OPT =3D -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 =3D 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_C= OUNT) > +#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 attri= bute > + 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 =3D STRUB_ID_BASE + IDX; \ > + tree identifier =3D strub_cache[idx]; \ > + if (!identifier) \ > + strub_cache[idx] =3D identifier =3D 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 accepte= d, 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 =3D NULL; > + size_t len =3D 0; > + > + /* do NOT test for NULL. This is only to be called with non-NULL argu= ments. > + We assume that the strub parameter applies to a function, because o= nly > + functions accept an explicit argument. If we accepted NULL, and we > + happened to be called to verify the argument for a variable, our re= turn > + values would be wrong. */ > + if (TREE_CODE (id) =3D=3D STRING_CST) > + { > + s =3D TREE_STRING_POINTER (id); > + len =3D TREE_STRING_LENGTH (id) - 1; > + } > + else if (TREE_CODE (id) =3D=3D IDENTIFIER_NODE) > + { > + s =3D IDENTIFIER_POINTER (id); > + len =3D IDENTIFIER_LENGTH (id); > + } > + else > + return 0; > + > + enum strub_mode mode; > + > + if (len !=3D 8) > + return 0; > + > + switch (s[0]) > + { > + case 'd': > + mode =3D STRUB_DISABLED; > + ret =3D -1; > + break; > + > + case 'a': > + mode =3D STRUB_AT_CALLS; > + ret =3D 2; > + break; > + > + case 'i': > + mode =3D STRUB_INTERNAL; > + ret =3D 1; > + break; > + > + case 'c': > + mode =3D STRUB_CALLABLE; > + ret =3D -2; > + break; > + > + default: > + /* Other parms are for internal use only. */ > + return 0; > + } > + > + tree mode_id =3D get_strub_mode_attr_parm (mode); > + > + if (TREE_CODE (id) =3D=3D IDENTIFIER_NODE > + ? id !=3D mode_id > + : strncmp (s, IDENTIFIER_POINTER (mode_id), len) !=3D 0) > + return 0; > + > + return ret; > +} > + > +/* Return the strub mode from STRUB_ATTR. VAR_P should be TRUE if the a= ttribute > + is taken from a variable, rather than from a function, or a type ther= eof. */ > + > +static enum strub_mode > +get_strub_mode_from_attr (tree strub_attr, bool var_p =3D false) > +{ > + enum strub_mode mode =3D STRUB_DISABLED; > + > + if (strub_attr) > + { > + if (!TREE_VALUE (strub_attr)) > + mode =3D !var_p ? STRUB_AT_CALLS : STRUB_INTERNAL; > + else > + { > + gcc_checking_assert (!var_p); > + tree id =3D TREE_VALUE (strub_attr); > + if (TREE_CODE (id) =3D=3D TREE_LIST) > + id =3D TREE_VALUE (id); > + const char *s =3D (TREE_CODE (id) =3D=3D STRING_CST > + ? TREE_STRING_POINTER (id) > + : IDENTIFIER_POINTER (id)); > + size_t len =3D (TREE_CODE (id) =3D=3D STRING_CST > + ? TREE_STRING_LENGTH (id) - 1 > + : IDENTIFIER_LENGTH (id)); > + > + switch (len) > + { > + case 7: > + switch (s[6]) > + { > + case 'r': > + mode =3D STRUB_WRAPPER; > + break; > + > + case 'd': > + mode =3D STRUB_WRAPPED; > + break; > + > + default: > + gcc_unreachable (); > + } > + break; > + > + case 8: > + switch (s[0]) > + { > + case 'd': > + mode =3D STRUB_DISABLED; > + break; > + > + case 'a': > + mode =3D STRUB_AT_CALLS; > + break; > + > + case 'i': > + mode =3D STRUB_INTERNAL; > + break; > + > + case 'c': > + mode =3D STRUB_CALLABLE; > + break; > + > + default: > + gcc_unreachable (); > + } > + break; > + > + case 9: > + mode =3D STRUB_INLINABLE; > + break; > + > + case 12: > + mode =3D STRUB_AT_CALLS_OPT; > + break; > + > + default: > + gcc_unreachable (); > + } > + > + gcc_checking_assert (TREE_CODE (id) =3D=3D IDENTIFIER_NODE > + ? id =3D=3D get_strub_mode_attr_parm (mode= ) > + : strncmp (IDENTIFIER_POINTER > + (get_strub_mode_attr_parm (mode= )), > + s, len) =3D=3D 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 =3D !FUNC_OR_METHOD_TYPE_P (type); > + tree attr =3D get_strub_attr_from_type (type); > + > + if (attr) > + return get_strub_mode_from_attr (attr, var_p); > + > + if (flag_strub >=3D -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 =3D false; > + > + for (cgraph_edge *e =3D node->callees; e; e =3D e->next_callee) > + { > + tree cdecl =3D 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 =3D false) > +{ > + bool result =3D false; > + > + for (cgraph_edge *e =3D node->callees; e; e =3D e->next_callee) > + { > + tree cdecl =3D e->callee->decl; > + if (!fndecl_built_in_p (cdecl, BUILT_IN_APPLY_ARGS)) > + continue; > + > + result =3D true; > + > + if (!report) > + break; > + > + sorry_at (e->call_stmt > + ? gimple_location (e->call_stmt) > + : DECL_SOURCE_LOCATION (node->decl), > + "at-calls % 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 mo= de, and > + optionally REPORT the reasons for ineligibility. */ > + > +static inline bool > +can_strub_p (cgraph_node *node, bool report =3D false) > +{ > + bool result =3D true; > + > + if (!report && strub_always_inline_p (node)) > + return result; > + > + if (lookup_attribute ("noipa", DECL_ATTRIBUTES (node->decl))) > + { > + result =3D false; > + > + if (!report) > + return result; > + > + sorry_at (DECL_SOURCE_LOCATION (node->decl), > + "%qD is not eligible for %" > + " because of attribute %", > + 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 =3D false; > + > + if (!report) > + return result; > + > + sorry_at (DECL_SOURCE_LOCATION (node->decl), > + "%qD is not eligible for %" > + " because of attribute %", > + node->decl); > + } > + > + return result; > +} > + > +/* Return TRUE iff NODE is eligible for at-calls strub, and optionally R= EPORT > + 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 =3D false) > +{ > + bool result =3D !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 =3D TREE_TYPE (TREE_TYPE (gimple_call_fn (gs))); > + if (tree decl =3D gimple_call_fndecl (gs)) > + fn_type =3D 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=3D* > + 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 =3D get_strub_mode_from_type (fn_type); > + return (get_strub_mode_from_type (gs->u.fntype) !=3D 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 =3D node->callers; e; e =3D 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 ma= y add to > + a function. */ > + > +#define STRUB_INTERNAL_MAX_EXTRA_ARGS 3 > + > +/* We can't perform internal strubbing if the function body involves cer= tain > + 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 variab= le > + arguments ABI to the modified wrapped function. The default > + __builtin_va_start is supported by calling va_start/va_end at the wra= pper, > + that takes variable arguments, passing a pointer to the va_list objec= t to the > + wrapped function, that runs va_copy from it where the original functi= on ran > + va_start. > + > + __builtin_next_arg is currently unsupported because the wrapped funct= ion > + won't be a variable argument function. We could process it in the wr= apper, > + that remains a variable argument function, and replace calls in the w= rapped > + 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 =3D false) > +{ > + bool result =3D !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 =3D false; > + > + if (!report) > + return result; > + > + sorry_at (DECL_SOURCE_LOCATION (node->decl), > + "%qD is not eligible for internal %" > + " because of attribute %", > + node->decl); > + } > + > + if (node->has_gimple_body_p ()) > + { > + for (cgraph_edge *e =3D node->callees; e; e =3D e->next_callee) > + { > + tree cdecl =3D e->callee->decl; > + if (!((fndecl_built_in_p (cdecl, BUILT_IN_VA_START) > + && cdecl !=3D 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 =3D 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 % " > + "because it calls %qD", > + node->decl, cdecl); > + } > + > + struct function *fun =3D DECL_STRUCT_FUNCTION (node->decl); > + if (fun->has_nonlocal_label) > + { > + result =3D false; > + > + if (!report) > + return result; > + > + sorry_at (DECL_SOURCE_LOCATION (node->decl), > + "%qD is not eligible for internal % " > + "because it contains a non-local goto target", > + node->decl); > + } > + > + if (fun->has_forced_label_in_static) > + { > + result =3D false; > + > + if (!report) > + return result; > + > + sorry_at (DECL_SOURCE_LOCATION (node->decl), > + "%qD is not eligible for internal % " > + "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 ref= erenced > + 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 la= bels > + 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 =3D gsi_start_bb (bb); > + !gsi_end_p (gsi); gsi_next (&gsi)) > + { > + glabel *label_stmt =3D dyn_cast (gsi_stmt (gsi)); > + tree target; > + > + if (!label_stmt) > + break; > + > + target =3D gimple_label_label (label_stmt); > + > + if (!FORCED_LABEL (target)) > + continue; > + > + result =3D false; > + > + if (!report) > + return result; > + > + sorry_at (gimple_location (label_stmt), > + "internal % does not support forced labels"= ); > + } > + } > + > + if (list_length (TYPE_ARG_TYPES (TREE_TYPE (node->decl))) > + >=3D (((HOST_WIDE_INT) 1 << IPA_PARAM_MAX_INDEX_BITS) > + - STRUB_INTERNAL_MAX_EXTRA_ARGS)) > + { > + result =3D false; > + > + if (!report) > + return result; > + > + sorry_at (DECL_SOURCE_LOCATION (node->decl), > + "%qD has too many arguments for internal %", > + node->decl); > + } > + > + return result; > +} > + > +/* Return TRUE iff NODE has any strub-requiring local variable, or acces= ses (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)) > + !=3D 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 =3D gsi_start_bb (bb); > + !gsi_end_p (gsi); gsi_next (&gsi)) > + { > + gimple *stmt =3D gsi_stmt (gsi); > + > + if (!gimple_assign_load_p (stmt)) > + continue; > + > + tree rhs =3D gimple_assign_rhs1 (stmt); > + if (get_strub_mode_from_type (TREE_TYPE (rhs)) > + !=3D STRUB_DISABLED) > + return true; > + } > + > + return false; > +} > + > +/* Return TRUE iff node is associated with a builtin that should be call= able > + from strub contexts. */ > + > +static inline bool > +strub_callable_builtin_p (cgraph_node *node) > +{ > + if (DECL_BUILT_IN_CLASS (node->decl) !=3D BUILT_IN_NORMAL) > + return false; > + > + enum built_in_function fcode =3D DECL_FUNCTION_CODE (node->decl); > + > + switch (fcode) > + { > + case BUILT_IN_NONE: > + gcc_unreachable (); > + > + /* This temporarily allocates stack for the call, and we can't rea= sonably > + update the watermark for that. Besides, we don't check the actu= al call > + target, nor its signature, and it seems to be overkill to as muc= h 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 buil= tin call > + the compiler might introduce on its own callable. Anything that= is > + predictable enough as to be known not to allow stack data that s= hould > + be strubbed to unintentionally escape to non-strub contexts can = be > + allowed, and pretty much every builtin appears to fit this descr= iption. > + 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 =3D get_strub_mode_from_attr (strub_attr); > + > + gcc_checking_assert (flag_strub >=3D -2 && flag_strub <=3D 3); > + > + /* Symbolic encodings of the -fstrub-* flags. */ > + /* Enable strub when explicitly requested through attributes to functi= ons or > + variables, reporting errors if the requests cannot be satisfied. *= / > + const bool strub_flag_auto =3D flag_strub < 0; > + /* strub_flag_auto with strub call verification; without this, functio= ns are > + implicitly callable. */ > + const bool strub_flag_strict =3D flag_strub < -1; > + /* Disable strub altogether, ignore attributes entirely. */ > + const bool strub_flag_disabled =3D flag_strub =3D=3D 0; > + /* On top of _auto, also enable strub implicitly for functions that ca= n > + safely undergo at-calls strubbing. Internal mode will still be use= d in > + functions that request it explicitly with attribute strub(2), or wh= en the > + function body requires strubbing and at-calls strubbing is not viab= le. */ > + const bool strub_flag_at_calls =3D flag_strub =3D=3D 1; > + /* On top of default, also enable strub implicitly for functions that = can > + safely undergo internal strubbing. At-calls mode will still be use= d in > + functions that requiest it explicitly with attribute strub() or str= ub(1), > + or when the function body requires strubbing and internal strubbing= is not > + viable. */ > + const bool strub_flag_internal =3D flag_strub =3D=3D 2; > + /* On top of default, also enable strub implicitly for functions that = can > + safely undergo strubbing in either mode. When both modes are viabl= e, > + at-calls is preferred. */ > + const bool strub_flag_either =3D flag_strub =3D=3D 3; > + /* Besides the default behavior, enable strub implicitly for all viabl= e > + functions. */ > + const bool strub_flag_viable =3D flag_strub > 0; > + > + /* The consider_* variables should be TRUE if selecting the correspond= ing > + strub modes would be consistent with requests from attributes and c= ommand > + line flags. Attributes associated with functions pretty much manda= te a > + selection, and should report an error if not satisfied; strub_flag_= auto > + implicitly enables some viable strub mode if that's required by ref= erences > + to variables marked for strub; strub_flag_viable enables strub if v= iable > + (even when favoring one mode, body-requested strub can still be sat= isfied > + by either mode), and falls back to callable, silently unless variab= les > + require strubbing. */ > + > + const bool consider_at_calls > + =3D (!strub_flag_disabled > + && (strub_attr > + ? req_mode =3D=3D STRUB_AT_CALLS > + : true)); > + const bool consider_internal > + =3D (!strub_flag_disabled > + && (strub_attr > + ? req_mode =3D=3D STRUB_INTERNAL > + : true)); > + > + const bool consider_callable > + =3D (!strub_flag_disabled > + && (strub_attr > + ? req_mode =3D=3D STRUB_CALLABLE > + : (!strub_flag_strict > + || strub_callable_builtin_p (node)))); > + > + /* This is a shorthand for either strub-enabled mode. */ > + const bool consider_strub > + =3D (consider_at_calls || consider_internal); > + > + /* We can cope with always_inline functions even with noipa and noclon= e, > + because we just leave them alone. */ > + const bool is_always_inline > + =3D strub_always_inline_p (node); > + > + /* Strubbing in general, and each specific strub mode, may have its ow= n set of > + requirements. We require noipa for strubbing, either because of cl= oning > + required for internal strub, or because of caller enumeration requi= red 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 req= uires > + cloning and the absence of certain features in the body and, like a= t-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 cou= ld use a > + target hook to adjust the clone instead. */ > + const bool strub_eligible > + =3D (consider_strub > + && (is_always_inline || can_strub_p (node))); > + const bool at_calls_eligible > + =3D (consider_at_calls && strub_eligible > + && can_strub_at_calls_p (node)); > + const bool internal_eligible > + =3D (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. The= se do > + not prevent the selection of a mode if explicitly specified as part= of a > + function interface (the strub attribute), but they may prevent mode= s 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 expo= sed > + signature, we won't do it implicitly if the function can possibly b= e used > + in ways that do not expect the signature change, e.g., if the funct= ion is > + available to or interposable by other units, if its address is take= n, > + etc. */ > + const bool at_calls_viable > + =3D (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 > + =3D (internal_eligible); > + > + /* Shorthand. */ > + const bool strub_viable > + =3D (at_calls_viable || internal_viable); > + > + /* We wish to analyze the body, to look for implicit requests for stru= b, both > + to implicitly enable it when the body calls for it, and to report e= rrors if > + the body calls for it but neither mode is viable (even if that foll= ows from > + non-eligibility because of the explicit specification of some non-s= trubbing > + mode). We can refrain from scanning the body only in rare circumst= ances: > + when strub is enabled by a function attribute (scanning might be re= dundant > + in telling us to also enable it), and when we are enabling strub im= plicitly > + but there are non-viable modes: we want to know whether strubbing i= s > + 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 > + =3D (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 > + =3D ((strub_attr && consider_strub) > + || (analyze_body && strub_from_body_p (node))); > + > + /* Besides the required cases, we want to abide by the requests to ena= bling on > + an if-viable basis. */ > + const bool strub_enable > + =3D (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 via= bility > + and eligibility constraints, and that satisfies the strubbing requi= rements > + and requests, subject to the constraints. If both modes are viable= and > + strub is to be enabled, pick STRUB_AT_CALLS unless STRUB_INTERNAL w= as named > + as preferred. */ > + const enum strub_mode mode > + =3D ((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 %," > + " but no viable % 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. Optimi= zation > + 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 =3D tree_cons (get_identifier ("strub"), > + get_strub_mode_attr_value (mode), > + NULL_TREE); > + tree *attrp =3D NULL; > + if (DECL_P (fndt)) > + { > + gcc_checking_assert (FUNC_OR_METHOD_TYPE_P (TREE_TYPE (fndt))); > + attrp =3D &DECL_ATTRIBUTES (fndt); > + } > + else if (FUNC_OR_METHOD_TYPE_P (fndt)) > + attrp =3D &TYPE_ATTRIBUTES (fndt); > + else > + gcc_unreachable (); > + > + TREE_CHAIN (attr) =3D *attrp; > + *attrp =3D 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 th= e 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 =3D get_strub_attr_from_decl (node->decl); > + enum strub_mode req_mode =3D get_strub_mode_from_attr (attr); > + > + if (attr) > + { > + /* Check for and report incompatible mode changes. */ > + if (mode !=3D req_mode > + && !(req_mode =3D=3D STRUB_INTERNAL > + && (mode =3D=3D STRUB_WRAPPED > + || mode =3D=3D STRUB_WRAPPER)) > + && !((req_mode =3D=3D STRUB_INTERNAL > + || req_mode =3D=3D STRUB_AT_CALLS > + || req_mode =3D=3D STRUB_CALLABLE) > + && mode =3D=3D STRUB_INLINABLE)) > + { > + error_at (DECL_SOURCE_LOCATION (node->decl), > + "% mode %qE selected for %qD, when %qE was re= quested", > + get_strub_mode_attr_parm (mode), > + node->decl, > + get_strub_mode_attr_parm (req_mode)); > + if (node->alias) > + { > + cgraph_node *target =3D node->ultimate_alias_target (); > + if (target !=3D 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 attribu= te > + chain. Return if we find one with the mode we need. */ > + for (;;) > + { > + if (mode =3D=3D req_mode) > + return; > + > + if (DECL_ATTRIBUTES (node->decl) !=3D attr) > + break; > + > + DECL_ATTRIBUTES (node->decl) =3D TREE_CHAIN (attr); > + attr =3D get_strub_attr_from_decl (node->decl); > + if (!attr) > + break; > + > + req_mode =3D get_strub_mode_from_attr (attr); > + } > + } > + else if (mode =3D=3D 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 =3D 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 =3D node; > + if (node->alias) > + xnode =3D 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 =3D (xnode !=3D 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 !=3D 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 >=3D -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 h= ave to > + be an immediate caller of CALLEE: the immediate caller may have alrea= dy been > + cloned for inlining, and then CALLER may be further up the original c= all > + 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 =3D 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 =3D 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 str= ub modes > + are the same, 2 if they are interchangeable, and 0 otherwise. */ > + > +int > +strub_comptypes (tree t1, tree t2) > +{ > + if (TREE_CODE (t1) !=3D TREE_CODE (t2)) > + return 0; > + > + enum strub_mode m1 =3D get_strub_mode_from_type (t1); > + enum strub_mode m2 =3D get_strub_mode_from_type (t2); > + > + if (m1 =3D=3D 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 (fo= r > + functions) or internal (for variables), the conversion is not > + compatible. */ > + bool var_p =3D !FUNC_OR_METHOD_TYPE_P (t1); > + enum strub_mode mr =3D var_p ? STRUB_INTERNAL : STRUB_AT_CALLS; > + if (m1 =3D=3D mr || m2 =3D=3D 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 =3D gimple_call_fntype (call); > + mode =3D get_strub_mode_from_type (type); > + } > + else > + { > + type =3D TREE_TYPE (TREE_TYPE (gimple_call_fn (call))); > + tree decl =3D gimple_call_fndecl (call); > + if (decl) > + mode =3D get_strub_mode_from_fndecl (decl); > + else > + mode =3D get_strub_mode_from_type (type); > + } > + > + if (typep) > + *typep =3D 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 =3D TREE_TYPE (node->decl); > + tree new_type =3D build_distinct_type_copy (old_type); > + tree new_ptr_type =3D 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 =3D node->callers; e; e =3D e->next_caller) > + { > + if (!e->call_stmt) > + continue; > + tree fnaddr =3D gimple_call_fn (e->call_stmt); > + gcc_checking_assert (TREE_CODE (fnaddr) =3D=3D ADDR_EXPR > + && TREE_OPERAND (fnaddr, 0) =3D=3D node->decl)= ; > + if (strub_call_fntype_override_p (e->call_stmt)) > + continue; > + if (!new_ptr_type) > + new_ptr_type =3D build_pointer_type (new_type); > + TREE_TYPE (fnaddr) =3D new_ptr_type; > + gimple_call_set_fntype (e->call_stmt, new_type); > + } > + > + TREE_TYPE (node->decl) =3D 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 =3D get_strub_mode_from_type (type); > + > + for (tree other =3D TYPE_MAIN_VARIANT (type); > + other !=3D NULL_TREE; other =3D TYPE_NEXT_VARIANT (other)) > + if (type !=3D other && mode !=3D 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)) > + !=3D TYPE_MAIN_VARIANT (type))) > + { > + if (mode !=3D 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 v= ariables > + 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 crea= tion 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 =3D get_strub_mode (node); > + > + for (cgraph_edge *e =3D node->indirect_calls; e; e =3D e->next_calle= e) > + { > + gcc_checking_assert (e->indirect_unknown_callee); > + > + if (!e->call_stmt) > + continue; > + > + enum strub_mode callee_mode > + =3D 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-% call in % context %qD= ", > + node->decl); > + } > + > + for (cgraph_edge *e =3D node->callees; e; e =3D e->next_callee) > + { > + gcc_checking_assert (!e->indirect_unknown_callee); > + > + if (!e->call_stmt) > + continue; > + > + tree callee_fntype; > + enum strub_mode callee_mode > + =3D effective_strub_mode_for_call (e->call_stmt, &callee_fntype= ); > + > + if (!strub_callable_from_p (caller_mode, callee_mode)) > + { > + if (callee_mode =3D=3D STRUB_INLINABLE) > + error_at (gimple_location (e->call_stmt), > + "calling % % %qD" > + " in non-% context %qD", > + e->callee->decl, node->decl); > + else if (fndecl_built_in_p (e->callee->decl, BUILT_IN_APPLY_A= RGS) > + && caller_mode =3D=3D STRUB_INTERNAL) > + /* This is ok, it will be kept in the STRUB_WRAPPER, and re= moved > + 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-% %qD in % context %= qD", > + e->callee->decl, node->decl); > + else > + error_at (gimple_location (e->call_stmt), > + "calling %qD using non-% type %qT" > + " in % 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 =3D { > + 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 c= hanges > + flag_strub to -1 or -2, respectively, if any strub-enabling occur= ence of > + the attribute is found. Therefore, if it remains at -3 or -4, no= thing > + that would enable strub was found, so we can disable it and avoid= the > + overhead. */ > + if (flag_strub < -2) > + flag_strub =3D 0; > + return flag_strub; > + } > + virtual unsigned int execute (function *); > +}; > + > +/* Define a pass to introduce strub transformations. */ > +const pass_data pass_data_ipa_strub =3D { > + 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 =3D STRUB_TYPE_BASE + IDX; \ > + static tree type =3D strub_cache[idx]; \ > + if (!type) \ > + strub_cache[idx] =3D type =3D (INIT); \ > + return type; \ > + } > + > + /* Use a distinct ptr_type_node to denote the watermark, so that we ca= n > + 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 =3D builtin_decl_explicit (CODE); \ > + if (!decl) \ > + { \ > + tree type =3D build_function_type_list FNTYPELIST; \ > + decl =3D add_builtin_function \ > + ("__builtin_" #NAME, \ > + type, CODE, BUILT_IN_NORMAL, \ > + NULL, NULL); \ > + TREE_NOTHROW (decl) =3D 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 =3D builtin_decl_explicit (CODE); \ > + if (!decl) \ > + { \ > + tree type =3D build_function_type_list FNTYPELIST; \ > + tree attrs =3D NULL; \ > + if (FNSPEC) \ > + attrs =3D tree_cons (get_identifier ("fn spec"), \ > + build_tree_list \ > + (NULL_TREE, \ > + build_string (strlen (FNSPEC), \ > + (FNSPEC))), \ > + attrs); \ > + decl =3D add_builtin_function_ext_scope \ > + ("__builtin___strub_" #NAME, \ > + type, CODE, BUILT_IN_NORMAL, \ > + "__strub_" #NAME, attrs); \ > + TREE_NOTHROW (decl) =3D 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 =3D STRUB_IDENT_BASE + IDX; = \ > + tree identifier =3D strub_cache[idx]; = \ > + if (!identifier) \ > + strub_cache[idx] =3D identifier =3D get_identifier (".strub." #NAM= E); \ > + 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 NOD= E'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 co= unt, > + gimple_seq seq =3D NULL) > + { > + tree uwm =3D get_update (); > + gcall *update =3D 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 i= nto > + explicit references. */ > + > +typedef hash_set 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 accordin= g to > + gimple-walking expectations. */ > + > +static tree > +maybe_make_indirect (indirect_parms_t &indirect_parms, tree op, int *rec= ) > +{ > + if (DECL_P (op)) > + { > + *rec =3D 0; > + if (indirect_parms.contains (op)) > + { > + tree ret =3D gimple_fold_indirect_ref (op); > + if (!ret) > + ret =3D build2 (MEM_REF, > + TREE_TYPE (TREE_TYPE (op)), > + op, > + build_int_cst (TREE_TYPE (op), 0)); > + return ret; > + } > + } > + else if (TREE_CODE (op) =3D=3D ADDR_EXPR > + && DECL_P (TREE_OPERAND (op, 0))) > + { > + *rec =3D 0; > + if (indirect_parms.contains (TREE_OPERAND (op, 0))) > + { > + op =3D 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 =3D (walk_stmt_info *)arg; > + indirect_parms_t &indirect_parms =3D *(indirect_parms_t *)wi->info; > + > + if (!*op || TYPE_P (*op)) > + { > + *rec =3D 0; > + return NULL_TREE; > + } > + > + if (tree repl =3D maybe_make_indirect (indirect_parms, *op, rec)) > + { > + *op =3D repl; > + wi->changed =3D true; > + } > + > + return NULL_TREE; > +} > + > +/* A gimple-walking function that turns any non-gimple-val ADDR_EXPRs in= to a > + separate SSA. Though addresses of e.g. parameters, and of members th= ereof, > + are gimple vals, turning parameters into references, with an extra la= yer 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 =3D (walk_stmt_info *)arg; > + gimple_stmt_iterator &gsi =3D *(gimple_stmt_iterator *)wi->info; > + > + *rec =3D 0; > + > + if (!*op || TREE_CODE (*op) !=3D ADDR_EXPR) > + return NULL_TREE; > + > + if (!is_gimple_val (*op)) > + { > + tree ret =3D force_gimple_operand_gsi (&gsi, *op, true, > + NULL_TREE, true, GSI_SAME_STMT= ); > + gcc_assert (ret !=3D *op); > + *op =3D ret; > + wi->changed =3D 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 commit= ted. */ > + > +static bool > +walk_regimplify_phi (gphi *stmt) > +{ > + bool needs_commit =3D false; > + > + for (unsigned i =3D 0, n =3D gimple_phi_num_args (stmt); i < n; i++) > + { > + tree op =3D gimple_phi_arg_def (stmt, i); > + if ((TREE_CODE (op) =3D=3D ADDR_EXPR > + && !is_gimple_val (op)) > + /* ??? A PARM_DECL that was addressable in the original functio= n 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 node= s. > + 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 a= lone, > + it eventually causes errors because it remains unchanged in = PHI > + nodes, but it gets rewritten as expected if it appears in ot= her > + stmts. So we cheat a little here, and force the PARM_DECL o= ut of > + the PHI node and into an assignment. It's a little expensiv= e, > + because we insert it at the edge, which introduces a basic b= lock > + that's entirely unnecessary, but it works, and the block wil= l 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) =3D=3D PARM_DECL > + && !TREE_ADDRESSABLE (op))) > + { > + tree temp =3D make_ssa_name (TREE_TYPE (op), stmt); > + if (TREE_CODE (op) =3D=3D PARM_DECL) > + SET_SSA_NAME_VAR_OR_IDENTIFIER (temp, DECL_NAME (op)); > + SET_PHI_ARG_DEF (stmt, i, temp); > + > + gimple *assign =3D 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 =3D true; > + } > + } > + > + return needs_commit; > +} > + > +/* Create a reference type to use for PARM when turning it into a refere= nce. > + NONALIASED causes the reference type to gain its own separate alias s= et, so > + that accessing the indirectly-passed parm won'will not add aliasing > + noise. */ > + > +static tree > +build_ref_type_for (tree parm, bool nonaliased =3D true) > +{ > + gcc_checking_assert (TREE_CODE (parm) =3D=3D PARM_DECL); > + > + tree ref_type =3D 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, b= ut... > + const is not really important, since we're only using default defs = for the > + reference parm anyway, and not introducing any defs, and restrict s= eems to > + cause trouble. E.g., libgnat/s-concat3.adb:str_concat_3 has memmov= es that, > + if it's wrapped, the memmoves are deleted in dse1. Using a distinc= t alias > + set seems to not run afoul of this problem, and it hopefully enable= s the > + compiler to tell the pointers do point to objects that are not othe= rwise > + aliased. */ > + tree qref_type =3D build_variant_type_copy (ref_type); > + > + TYPE_ALIAS_SET (qref_type) =3D new_alias_set (); > + record_alias_subset (TYPE_ALIAS_SET (qref_type), get_alias_set (ref_ty= pe)); > + > + return qref_type; > +} > + > +/* Add cgraph edges from current_function_decl to callees in SEQ with fr= equency > + COUNT, assuming all calls in SEQ are direct. */ > + > +static void > +add_call_edges_for_seq (gimple_seq seq, profile_count count) > +{ > + cgraph_node *node =3D cgraph_node::get_create (current_function_decl); > + > + for (gimple_stmt_iterator gsi =3D gsi_start (seq); > + !gsi_end_p (gsi); gsi_next (&gsi)) > + { > + gimple *stmt =3D gsi_stmt (gsi); > + > + gcall *call =3D dyn_cast (stmt); > + if (!call) > + continue; > + > + tree callee =3D 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 w= ith 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 b= lock > + 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 =3D gsi_stmt (gsi); > + > + if (gimple_has_location (stmt)) > + annotate_all_with_location (seq, gimple_location (stmt)); > + > + gcall *call =3D dyn_cast (stmt); > + bool noreturn_p =3D call && gimple_call_noreturn_p (call); > + int eh_lp =3D lookup_stmt_eh_lp (stmt); > + bool must_not_throw_p =3D eh_lp < 0; > + bool nothrow_p =3D (must_not_throw_p > + || (call && gimple_call_nothrow_p (call)) > + || (eh_lp <=3D 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 a= n EH > + region yet. */ > + bool no_eh_edge_p =3D (nothrow_p || !eh_lp); > + bool must_end_bb =3D stmt_ends_bb_p (stmt); > + > + edge eft =3D NULL, eeh =3D 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 =3D e; > +#if !CHECKING_P > + if (eft || noreturn_p) > + break; > +#endif > + } > + if ((e->flags & EDGE_FALLTHRU)) > + { > + gcc_checking_assert (!eft); > + eft =3D e; > +#if !CHECKING_P > + if (eeh || no_eh_edge_p) > + break; > +#endif > + } > + } > + > + gcc_checking_assert (!(eft && (eft->flags & EDGE_FALLTHRU)) > + =3D=3D noreturn_p); > + gcc_checking_assert (!(eeh && (eeh->flags & EDGE_EH)) > + =3D=3D no_eh_edge_p); > + gcc_checking_assert (eft !=3D eeh); > + } > + > + if (!noreturn_p) > + { > + gimple_seq nseq =3D 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 =3D 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 =3D 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 =3D gen_eh_region_cleanup (NULL); > + eh_landing_pad lp =3D gen_eh_landing_pad (new_r); > + tree label =3D gimple_block_label (bb_eh_cleanup); > + lp->post_landing_pad =3D label; > + EH_LANDING_PAD_NR (label) =3D lp->index; > + add_stmt_to_eh_lp (stmt, lp->index); > + > + /* Add the cleanup code to the EH cleanup block. */ > + gsi =3D gsi_after_labels (bb_eh_cleanup); > + gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT); > + > + /* And then propagate the exception further. */ > + gresx *resx =3D 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 =3D make_eh_edge (stmt); > + neeh->probability =3D profile_probability::never (); > + gcc_checking_assert (neeh->dest =3D=3D bb_eh_cleanup); > + gcc_checking_assert (!neeh->dest->count.initialized_p ()); > + neeh->dest->count =3D 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 =3D lookup_attribute (name, *attrs)) > + { > + /* Copy nodes up to the next NAME attribute. */ > + while (*attrs !=3D found) > + { > + *attrs =3D tree_cons (TREE_PURPOSE (*attrs), > + TREE_VALUE (*attrs), > + TREE_CHAIN (*attrs)); > + attrs =3D &TREE_CHAIN (*attrs); > + } > + /* Then drop it. */ > + gcc_checking_assert (*attrs =3D=3D found); > + *attrs =3D TREE_CHAIN (*attrs); > + } > +} > + > +/* Record the order of the last cgraph entry whose mode we've already se= t, 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 =3D=3D last_cgraph_order) > + return; > + > + cgraph_node *node; > + > + /* Go through the functions twice, once over non-aliases, and then ove= r > + aliases, so that aliases can reuse the mode computation of their ul= timate > + targets. */ > + for (int aliases =3D 0; aliases <=3D 1; aliases++) > + FOR_EACH_FUNCTION (node) > + { > + if (!node->alias !=3D !aliases) > + continue; > + > + /* Already done. */ > + if (node->order < last_cgraph_order) > + continue; > + > + set_strub_mode (node); > + } > + > + last_cgraph_order =3D 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 o= ne. */ > + > +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 =3D DECL_ARGUMENTS (fndecl); parm; parm =3D DECL_CHAIN = (parm)) > + /* The type (variant) compare finds the parameter even in a just-cre= ated > + clone, before we set its name, but the type-based compare doesn't= work > + during builtin expansion within the lto compiler, because we'll h= ave > + created a separate variant in that run. */ > + if (TREE_TYPE (parm) =3D=3D pass_ipa_strub::get_qpwmt () > + || DECL_NAME (parm) =3D=3D 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 =3D 0; > + > + gcc_checking_assert (same_strub_mode_in_variants_p (type)); > + > + if (!TYPE_ARG_TYPES (type)) > + return named_args; > + > + tree *tlist =3D &TYPE_ARG_TYPES (type); > + tree qpwmptrt =3D get_qpwmt (); > + while (*tlist && TREE_VALUE (*tlist) !=3D void_type_node) > + { > + /* The type has already been adjusted. */ > + if (TREE_VALUE (*tlist) =3D=3D qpwmptrt) > + return named_args; > + named_args++; > + *tlist =3D tree_cons (TREE_PURPOSE (*tlist), > + TREE_VALUE (*tlist), > + TREE_CHAIN (*tlist)); > + tlist =3D &TREE_CHAIN (*tlist); > + } > + > + /* Add the new argument after all named arguments, so as to not mess w= ith > + attributes that reference parameters. */ > + *tlist =3D tree_cons (NULL_TREE, get_qpwmt (), *tlist); > + > +#if ATTR_FNSPEC_DECONST_WATERMARK > + if (!type_already_adjusted) > + { > + int flags =3D flags_from_decl_or_type (type); > + tree fnspec =3D lookup_attribute ("fn spec", type); > + > + if ((flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS)) || fnspec) > + { > + size_t xargs =3D 1; > + size_t curlen =3D 0, tgtlen =3D 2 + 2 * (named_args + xargs); > + auto_vec nspecv (tgtlen); > + char *nspec =3D &nspecv[0]; /* It will *not* be NUL-terminated!= */ > + if (fnspec) > + { > + tree fnspecstr =3D TREE_VALUE (TREE_VALUE (fnspec)); > + curlen =3D TREE_STRING_LENGTH (fnspecstr); > + memcpy (nspec, TREE_STRING_POINTER (fnspecstr), curlen); > + } > + if (!curlen) > + { > + nspec[curlen++] =3D '.'; > + nspec[curlen++] =3D ((flags & ECF_CONST) > + ? 'c' > + : (flags & ECF_PURE) > + ? 'p' > + : ' '); > + } > + while (curlen < tgtlen - 2 * xargs) > + { > + nspec[curlen++] =3D '.'; > + nspec[curlen++] =3D ' '; > + } > + nspec[curlen++] =3D 'W'; > + nspec[curlen++] =3D 't'; > + > + /* The type has already been copied, if needed, before adding > + parameters. */ > + TYPE_ATTRIBUTES (type) > + =3D 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 v= ariable > + if needed, initialize it before, pass it to the callee according to t= he > + modified at-calls interface, and release the callee's stack space aft= er 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 =3D e->call_stmt; > + gimple_stmt_iterator gsi =3D 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_arg= s)) > + =3D=3D get_pwmt ()))); > + > + /* If we're already within a strub context, pass on the incoming water= mark > + pointer, and omit the enter and leave calls around the modified cal= l, as an > + optimization, or as a means to satisfy a tail-call requirement. */ > + tree swmp =3D ((optimize_size || optimize > 2 > + || gimple_call_must_tail_p (ocall) > + || (optimize =3D=3D 2 && gimple_call_tail_p (ocall))) > + ? strub_watermark_parm (e->caller->decl) > + : NULL_TREE); > + bool omit_own_watermark =3D swmp; > + tree swm =3D NULL_TREE; > + if (!omit_own_watermark) > + { > + swm =3D create_tmp_var (get_wmt (), ".strub.watermark"); > + TREE_ADDRESSABLE (swm) =3D true; > + swmp =3D build1 (ADDR_EXPR, get_pwmt (), swm); > + > + /* Initialize the watermark before the call. */ > + tree enter =3D get_enter (); > + gcall *stptr =3D 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 =3D ocall; > + // Mostly copied from gimple_call_copy_skip_args. > + int i =3D 0; > + int nargs =3D gimple_call_num_args (stmt); > + auto_vec 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 =3D 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 =3D 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 =3D 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 =3D build2 (MEM_REF, > + TREE_TYPE (TREE_TYPE (swmp)), > + swmp, > + build_int_cst (TREE_TYPE (swmp), 0)); > + > + vec *inputs =3D NULL; > + vec *outputs =3D NULL; > + vec_safe_push (outputs, > + build_tree_list > + (build_tree_list > + (NULL_TREE, build_string (2, "=3Dm")), > + unshare_expr (swm))); > + vec_safe_push (inputs, > + build_tree_list > + (build_tree_list > + (NULL_TREE, build_string (1, "m")), > + unshare_expr (swm))); > + gasm *forcemod =3D 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 *inputs =3D NULL; > + vec_safe_push (inputs, > + build_tree_list > + (build_tree_list > + (NULL_TREE, build_string (1, "m")), > + unshare_expr (swm))); > + gasm *force_store =3D 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 =3D gimple_build_call (get_leave (), 1, > + unshare_expr (swmp)); > + gimple_seq_add_stmt (&seq, sleave); > + > + gassign *clobber =3D 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 with= in > + onode. */ > + if (node->indirect_calls) > + { > + push_cfun (DECL_STRUCT_FUNCTION (node->decl)); > + for (cgraph_edge *e =3D node->indirect_calls; e; e =3D e->next_cal= lee) > + { > + gcc_checking_assert (e->indirect_unknown_callee); > + > + if (!e->call_stmt) > + continue; > + > + tree callee_fntype; > + enum strub_mode callee_mode > + =3D effective_strub_mode_for_call (e->call_stmt, &callee_fnty= pe); > + > + if (callee_mode !=3D STRUB_AT_CALLS > + && callee_mode !=3D STRUB_AT_CALLS_OPT) > + continue; > + > + int named_args =3D 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 =3D node->callees; e; e =3D e->next_callee) > + { > + gcc_checking_assert (!e->indirect_unknown_callee); > + > + if (!e->call_stmt) > + continue; > + > + tree callee_fntype; > + enum strub_mode callee_mode > + =3D effective_strub_mode_for_call (e->call_stmt, &callee_fnty= pe); > + > + if (callee_mode !=3D STRUB_AT_CALLS > + && callee_mode !=3D STRUB_AT_CALLS_OPT) > + continue; > + > + int named_args =3D 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 =3D 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 un= less > + strub is explicitly requested. */ > + FOR_EACH_FUNCTION (onode) > + { > + enum strub_mode mode =3D get_strub_mode (onode); > + > + if (mode =3D=3D STRUB_AT_CALLS > + || mode =3D=3D STRUB_AT_CALLS_OPT) > + { > + /* Create a type variant if strubbing was not explicitly requeste= d in > + the function type. */ > + if (get_strub_mode_from_type (TREE_TYPE (onode->decl)) !=3D mode) > + distinctify_node_type (onode); > + > + int named_args =3D 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 noth= ing > + further to do. */ > + if (!onode->has_gimple_body_p ()) > + continue; > + > + tree *pargs =3D &DECL_ARGUMENTS (onode->decl); > + > + /* A noninterposable_alias reuses the same parm decl chain, don't= add > + the parm twice. */ > + bool aliased_parms =3D (onode->alias && *pargs > + && DECL_CONTEXT (*pargs) !=3D onode->decl); > + > + if (aliased_parms) > + continue; > + > + for (int i =3D 0; i < named_args; i++) > + pargs =3D &DECL_CHAIN (*pargs); > + > + tree wmptr =3D build_decl (DECL_SOURCE_LOCATION (onode->decl), > + PARM_DECL, > + get_watermark_ptr (), > + get_qpwmt ()); > + DECL_ARTIFICIAL (wmptr) =3D 1; > + DECL_ARG_TYPE (wmptr) =3D get_qpwmt (); > + DECL_CONTEXT (wmptr) =3D onode->decl; > + TREE_USED (wmptr) =3D 1; > + DECL_CHAIN (wmptr) =3D *pargs; > + *pargs =3D wmptr; > + > + if (onode->alias) > + continue; > + > + cgraph_node *nnode =3D onode; > + push_cfun (DECL_STRUCT_FUNCTION (nnode->decl)); > + > + { > + edge e =3D single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun)); > + gimple_seq seq =3D 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 =3D gsi_start_bb (bb); > + !gsi_end_p (gsi); gsi_next (&gsi)) > + { > + gimple *stmt =3D gsi_stmt (gsi); > + > + gcall *call =3D dyn_cast (stmt); > + > + if (!call) > + continue; > + > + if (gimple_alloca_call_p (call)) > + { > + /* Capture stack growth. */ > + gimple_seq seq =3D call_update_watermark (wmptr, NU= LL, > + 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 =3D get_strub_mode (onode); > + > + if (mode !=3D STRUB_INTERNAL) > + { > + adjust_at_calls_calls (onode); > + continue; > + } > + > + bool is_stdarg =3D calls_builtin_va_start_p (onode);; > + bool apply_args =3D calls_builtin_apply_args_p (onode); > + > + vec *nparms =3D NULL; > + unsigned j =3D 0; > + { > + // The following loop copied from ipa-split.c:split_function. > + for (tree parm =3D DECL_ARGUMENTS (onode->decl); > + parm; parm =3D DECL_CHAIN (parm), j++) > + { > + ipa_adjusted_param adj =3D {}; > + adj.op =3D IPA_PARAM_OP_COPY; > + adj.base_index =3D j; > + adj.prev_clone_index =3D j; > + vec_safe_push (nparms, adj); > + } > + > + if (apply_args) > + { > + ipa_adjusted_param aaadj =3D {}; > + aaadj.op =3D IPA_PARAM_OP_NEW; > + aaadj.type =3D get_qptr (); > + vec_safe_push (nparms, aaadj); > + } > + > + if (is_stdarg) > + { > + ipa_adjusted_param vladj =3D {}; > + vladj.op =3D IPA_PARAM_OP_NEW; > + vladj.type =3D get_qpvalst (); > + vec_safe_push (nparms, vladj); > + } > + > + ipa_adjusted_param wmadj =3D {}; > + wmadj.op =3D IPA_PARAM_OP_NEW; > + wmadj.type =3D get_qpwmt (); > + vec_safe_push (nparms, wmadj); > + } > + ipa_param_adjustments adj (nparms, -1, false); > + > + cgraph_node *nnode =3D onode->create_version_clone_with_body > + (auto_vec (0), > + NULL, &adj, NULL, NULL, "strub", NULL); > + > + if (!nnode) > + { > + error_at (DECL_SOURCE_LOCATION (onode->decl), > + "failed to split %qD for %", > + onode->decl); > + continue; > + } > + > + onode->split_part =3D 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-siz= ed array > + lengths given by other arguments, as in 20020210-1.c, would lead = to > + problems if passed by value, after resetting the original functio= n and > + dropping the length computation; passing them by reference works. > + DECL_BY_REFERENCE is *not* a substitute for this: it involves cop= ying > + anyway, but performed at the caller. */ > + indirect_parms_t indirect_nparms (3, false); > + unsigned adjust_ftype =3D 0; > + unsigned named_args =3D 0; > + for (tree parm =3D DECL_ARGUMENTS (onode->decl), > + nparm =3D DECL_ARGUMENTS (nnode->decl), > + nparmt =3D TYPE_ARG_TYPES (TREE_TYPE (nnode->decl)); > + parm; > + named_args++, > + parm =3D DECL_CHAIN (parm), > + nparm =3D DECL_CHAIN (nparm), > + nparmt =3D 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)) =3D=3D COMPLEX_TYPE > + || (tree_fits_uhwi_p (TYPE_SIZE_UNIT (TREE_TYPE (nparm))) > + && (tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (nparm))) > + <=3D 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 distinc= t, > + unaliased memory in the wrapper, and the wrapped can't possi= bly > + 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 ca= n > + probably drop the TREE_ADDRESSABLE and keep the TRUE. */ > + tree ref_type =3D build_ref_type_for (nparm, > + true > + || !TREE_ADDRESSABLE (parm)= ); > + > + DECL_ARG_TYPE (nparm) =3D TREE_TYPE (nparm) =3D ref_type; > + relayout_decl (nparm); > + TREE_ADDRESSABLE (nparm) =3D 0; > + DECL_BY_REFERENCE (nparm) =3D 0; > + DECL_NOT_GIMPLE_REG_P (nparm) =3D 0; > + /* ??? This avoids mismatches in debug info bind stmts in > + e.g. a-chahan . */ > + DECL_ABSTRACT_ORIGIN (nparm) =3D NULL; > + > + if (nparmt) > + adjust_ftype++; > + } > + > + /* Also adjust the wrapped function type, if needed. */ > + if (adjust_ftype) > + { > + tree nftype =3D TREE_TYPE (nnode->decl); > + > + /* We always add at least one argument at the end of the signatur= e, when > + cloning the function, so we don't expect to need to duplicate = the > + type here. */ > + gcc_checking_assert (TYPE_ARG_TYPES (nftype) > + !=3D TYPE_ARG_TYPES (TREE_TYPE (onode->decl)= )); > + > + /* Check that fnspec still works for the modified function signat= ure, > + and drop it otherwise. */ > + bool drop_fnspec =3D false; > + tree fnspec =3D lookup_attribute ("fn spec", TYPE_ATTRIBUTES (nft= ype)); > + attr_fnspec spec =3D fnspec ? attr_fnspec (fnspec) : attr_fnspec = (""); > + > + unsigned retcopy; > + if (!(fnspec && spec.returns_arg (&retcopy))) > + retcopy =3D (unsigned) -1; > + > + unsigned i =3D 0; > + for (tree nparm =3D DECL_ARGUMENTS (nnode->decl), > + nparmt =3D TYPE_ARG_TYPES (nftype); > + adjust_ftype > 0; > + i++, nparm =3D DECL_CHAIN (nparm), nparmt =3D TREE_CHAIN (np= armt)) > + if (indirect_nparms.contains (nparm)) > + { > + TREE_VALUE (nparmt) =3D TREE_TYPE (nparm); > + adjust_ftype--; > + > + if (fnspec && !drop_fnspec) > + { > + if (i =3D=3D retcopy) > + drop_fnspec =3D 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, NUL= L)); > + gcc_checking_assert (!spec.arg_copied_to_arg_p (i, = NULL)); > + > + /* Any claim of direct access only is invalidated b= y > + adding an indirection level. */ > + if (spec.arg_direct_p (i)) > + drop_fnspec =3D true; > + > + /* If there's a claim the argument is not read from= , the > + added indirection invalidates it: if the argumen= t is > + used at all, then the pointer will necessarily b= e > + read. */ > + if (!spec.arg_maybe_read_p (i) > + && spec.arg_used_p (i)) > + drop_fnspec =3D true; > + } > + } > + } > + > + /* ??? Maybe we could adjust it instead. */ > + if (drop_fnspec) > + remove_named_attribute_unsharing ("fn spec", > + &TYPE_ATTRIBUTES (nftype)); > + > + TREE_TYPE (nnode->decl) =3D nftype; > + } > + > +#if ATTR_FNSPEC_DECONST_WATERMARK > + { > + int flags =3D flags_from_decl_or_type (nnode->decl); > + tree fnspec =3D lookup_attribute ("fn spec", TREE_TYPE (nnode->dec= l)); > + > + if ((flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS)) || fnspec) > + { > + size_t xargs =3D 1 + int (is_stdarg) + int (apply_args); > + size_t curlen =3D 0, tgtlen =3D 2 + 2 * (named_args + xargs); > + auto_vec nspecv (tgtlen); > + char *nspec =3D &nspecv[0]; /* It will *not* be NUL-terminated!= */ > + bool no_writes_p =3D true; > + if (fnspec) > + { > + tree fnspecstr =3D TREE_VALUE (TREE_VALUE (fnspec)); > + curlen =3D TREE_STRING_LENGTH (fnspecstr); > + memcpy (nspec, TREE_STRING_POINTER (fnspecstr), curlen); > + if (!(flags & (ECF_CONST | ECF_PURE | ECF_NOVOPS)) > + && curlen >=3D 2 > + && nspec[1] !=3D 'c' && nspec[1] !=3D 'C' > + && nspec[1] !=3D 'p' && nspec[1] !=3D 'P') > + no_writes_p =3D false; > + } > + if (!curlen) > + { > + nspec[curlen++] =3D '.'; > + nspec[curlen++] =3D ((flags & ECF_CONST) > + ? 'c' > + : (flags & ECF_PURE) > + ? 'p' > + : ' '); > + } > + while (curlen < tgtlen - 2 * xargs) > + { > + nspec[curlen++] =3D '.'; > + nspec[curlen++] =3D ' '; > + } > + > + /* These extra args are unlikely to be present in const or pure > + functions. It's conceivable that a function that takes vari= able > + arguments, or that passes its arguments on to another functi= on, > + could be const or pure, but it would not modify the argument= s, and, > + being pure or const, it couldn't possibly modify or even acc= ess > + memory referenced by them. But it can read from these inter= nal > + 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++] =3D 'r'; > + nspec[curlen++] =3D ' '; > + } > + if (is_stdarg) > + { > + nspec[curlen++] =3D (no_writes_p ? 'r' : '.'); > + nspec[curlen++] =3D (no_writes_p ? 't' : ' '); > + } > + > + nspec[curlen++] =3D 'W'; > + nspec[curlen++] =3D 't'; > + > + /* The type has already been copied before adding parameters. = */ > + gcc_checking_assert (TYPE_ARG_TYPES (TREE_TYPE (nnode->decl)) > + !=3D TYPE_ARG_TYPES (TREE_TYPE (onode->dec= l))); > + TYPE_ATTRIBUTES (TREE_TYPE (nnode->decl)) > + =3D tree_cons (get_identifier ("fn spec"), > + build_tree_list (NULL_TREE, > + build_string (tgtlen, nspec)), > + TYPE_ATTRIBUTES (TREE_TYPE (nnode->decl))); > + } > + } > +#endif > + > + { > + tree decl =3D onode->decl; > + cgraph_node *target =3D nnode; > + > + { // copied from create_wrapper > + > + /* Preserve DECL_RESULT so we get right by reference flag. */ > + tree decl_result =3D 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 =3D= */true); > + > + DECL_UNINLINABLE (decl) =3D false; > + DECL_RESULT (decl) =3D decl_result; > + DECL_INITIAL (decl) =3D NULL; > + allocate_struct_function (decl, false); > + set_cfun (NULL); > + > + /* Turn alias into thunk and expand it into GIMPLE representation= . */ > + onode->definition =3D true; > + > + thunk_info::get_create (onode); > + onode->thunk =3D true; > + onode->create_edge (target, NULL, onode->count); > + onode->callees->can_throw_external =3D !TREE_NOTHROW (target->dec= l); > + > + tree arguments =3D DECL_ARGUMENTS (decl); > + > + while (arguments) > + { > + TREE_ADDRESSABLE (arguments) =3D false; > + arguments =3D TREE_CHAIN (arguments); > + } > + > + { > + tree alias =3D onode->callees->callee->decl; > + tree thunk_fndecl =3D decl; > + tree a; > + > + int nxargs =3D 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 =3D 0; > + tree arg; > + int i; > + tree resdecl; > + tree restmp =3D NULL; > + > + gcall *call; > + greturn *ret; > + bool alias_is_noreturn =3D TREE_THIS_VOLATILE (alias); > + > + a =3D DECL_ARGUMENTS (thunk_fndecl); > + > + current_function_decl =3D 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 =3D TREE_TYPE (TREE_TYPE (thunk_fndecl)); > + if (DECL_RESULT (thunk_fndecl) =3D=3D NULL_TREE) > + { > + resdecl =3D build_decl (input_location, RESULT_DECL, 0, r= estype); > + DECL_ARTIFICIAL (resdecl) =3D 1; > + DECL_IGNORED_P (resdecl) =3D 1; > + DECL_CONTEXT (resdecl) =3D thunk_fndecl; > + DECL_RESULT (thunk_fndecl) =3D resdecl; > + } > + else > + resdecl =3D DECL_RESULT (thunk_fndecl); > + > + profile_count cfg_count =3D onode->count; > + if (!cfg_count.initialized_p ()) > + cfg_count =3D profile_count::from_gcov_type (BB_FREQ_MAX).g= uessed_local (); > + > + bb =3D then_bb =3D else_bb =3D return_bb > + =3D init_lowered_empty_function (thunk_fndecl, true, cfg_co= unt); > + > + bsi =3D 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)) !=3D INTEGER_= CST)) > + { > + if (DECL_BY_REFERENCE (resdecl)) > + { > + restmp =3D gimple_fold_indirect_ref (resdecl); > + if (!restmp) > + restmp =3D 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_fnde= cl))) > + { > + restmp =3D resdecl; > + > + if (VAR_P (restmp)) > + { > + add_local_decl (cfun, restmp); > + BLOCK_VARS (DECL_INITIAL (current_function_de= cl)) > + =3D restmp; > + } > + } > + else > + restmp =3D create_tmp_var (restype, "retval"); > + } > + else > + restmp =3D create_tmp_reg (restype, "retval"); > + } > + > + for (arg =3D a; arg; arg =3D DECL_CHAIN (arg)) > + nargs++; > + auto_vec vargs (nargs + nxargs); > + i =3D 0; > + arg =3D a; > + > + if (nargs) > + for (tree nparm =3D DECL_ARGUMENTS (nnode->decl); > + i < nargs; > + i++, arg =3D DECL_CHAIN (arg), nparm =3D DECL_CHAIN (n= parm)) > + { > + tree save_arg =3D arg; > + tree tmp =3D 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 =3D TREE_TYPE (nparm); > + TREE_ADDRESSABLE (arg) =3D true; > + tree addr =3D build1 (ADDR_EXPR, ref_type, arg); > + tmp =3D arg =3D addr; > + } > + else > + DECL_NOT_GIMPLE_REG_P (arg) =3D 0; > + > + /* Convert the argument back to the type used by the ca= lling > + conventions, e.g. a non-prototyped float type is pas= sed 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) !=3D DECL_ARG_TYPE (nparm)) > + arg =3D fold_convert (DECL_ARG_TYPE (nparm), arg); > + > + if (!is_gimple_val (arg)) > + { > + tmp =3D create_tmp_reg (TYPE_MAIN_VARIANT > + (TREE_TYPE (arg)), "arg"); > + gimple *stmt =3D gimple_build_assign (tmp, arg); > + gsi_insert_after (&bsi, stmt, GSI_NEW_STMT); > + } > + vargs.quick_push (tmp); > + arg =3D 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 =3D gimple_build_call_vec (build_fold_addr_expr_loc (0, = alias), > + vargs); > + onode->callees->call_stmt =3D call; > + // gimple_call_set_from_thunk (call, true); > + if (DECL_STATIC_CHAIN (alias)) > + { > + tree p =3D DECL_STRUCT_FUNCTION (alias)->static_chain_dec= l; > + tree type =3D TREE_TYPE (p); > + tree decl =3D build_decl (DECL_SOURCE_LOCATION (thunk_fnd= ecl), > + PARM_DECL, create_tmp_var_name ("= CHAIN"), > + type); > + DECL_ARTIFICIAL (decl) =3D 1; > + DECL_IGNORED_P (decl) =3D 1; > + TREE_USED (decl) =3D 1; > + DECL_CONTEXT (decl) =3D thunk_fndecl; > + DECL_ARG_TYPE (decl) =3D type; > + TREE_READONLY (decl) =3D 1; > + > + struct function *sf =3D DECL_STRUCT_FUNCTION (thunk_fndec= l); > + sf->static_chain_decl =3D decl; > + > + gimple_call_set_chain (call, decl); > + } > + > + /* Return slot optimization is always possible and in fact re= quired 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_TY= PE (alias)))); > + } > + gsi_insert_after (&bsi, call, GSI_NEW_STMT); > + if (!alias_is_noreturn) > + { > + /* Build return value. */ > + if (!DECL_BY_REFERENCE (resdecl)) > + ret =3D gimple_build_return (restmp); > + else > + ret =3D 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 =3D true; > + update_max_bb_count (); > + profile_status_for_fn (cfun) > + =3D cfg_count.initialized_p () && cfg_count.ipa_p () > + ? PROFILE_READ : PROFILE_GUESSED; > + /* FIXME: C++ FE should stop setting TREE_ASM_WRITTEN on thun= ks. */ > + // TREE_ASM_WRITTEN (thunk_fndecl) =3D 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 na= me as > + referenced. */ > + onode->thunk =3D false; > + onode->lowered =3D true; > + bitmap_obstack_release (NULL); > + } > + current_function_decl =3D NULL; > + set_cfun (NULL); > + } > + > + thunk_info::remove (onode); > + > + // some more of create_wrapper at the end of the next block. > + } > + } > + > + { > + tree aaval =3D NULL_TREE; > + tree vaptr =3D NULL_TREE; > + tree wmptr =3D NULL_TREE; > + for (tree arg =3D DECL_ARGUMENTS (nnode->decl); arg; arg =3D DECL_= CHAIN (arg)) > + { > + aaval =3D vaptr; > + vaptr =3D wmptr; > + wmptr =3D arg; > + } > + > + if (!apply_args) > + aaval =3D 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 =3D vaptr; > + > + if (!is_stdarg) > + vaptr =3D NULL_TREE; > + > + DECL_NAME (wmptr) =3D get_watermark_ptr (); > + DECL_ARTIFICIAL (wmptr) =3D 1; > + DECL_IGNORED_P (wmptr) =3D 1; > + TREE_USED (wmptr) =3D 1; > + > + if (is_stdarg) > + { > + DECL_NAME (vaptr) =3D get_va_list_ptr (); > + DECL_ARTIFICIAL (vaptr) =3D 1; > + DECL_IGNORED_P (vaptr) =3D 1; > + TREE_USED (vaptr) =3D 1; > + } > + > + if (apply_args) > + { > + DECL_NAME (aaval) =3D get_apply_args (); > + DECL_ARTIFICIAL (aaval) =3D 1; > + DECL_IGNORED_P (aaval) =3D 1; > + TREE_USED (aaval) =3D 1; > + } > + > + push_cfun (DECL_STRUCT_FUNCTION (nnode->decl)); > + > + { > + edge e =3D single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun)); > + gimple_seq seq =3D call_update_watermark (wmptr, nnode, e->src->c= ount); > + gsi_insert_seq_on_edge_immediate (e, seq); > + } > + > + bool any_indirect =3D !indirect_nparms.is_empty (); > + > + if (any_indirect) > + { > + basic_block bb; > + bool needs_commit =3D false; > + FOR_EACH_BB_FN (bb, cfun) > + { > + for (gphi_iterator gsi =3D gsi_start_nonvirtual_phis (bb); > + !gsi_end_p (gsi); > + gsi_next_nonvirtual_phi (&gsi)) > + { > + gphi *stmt =3D gsi.phi (); > + > + walk_stmt_info wi =3D {}; > + wi.info =3D &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 =3D true; > + } > + > + for (gimple_stmt_iterator gsi =3D gsi_start_bb (bb); > + !gsi_end_p (gsi); gsi_next (&gsi)) > + { > + gimple *stmt =3D gsi_stmt (gsi); > + > + walk_stmt_info wi =3D {}; > + wi.info =3D &indirect_nparms; > + walk_gimple_op (stmt, walk_make_indirect, &wi); > + if (wi.changed) > + { > + if (!is_gimple_debug (stmt)) > + { > + wi.info =3D &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 =3D nnode->callees, *enext; e; e =3D enext) > + { > + if (!e->call_stmt) > + continue; > + > + gcall *call =3D e->call_stmt; > + gimple_stmt_iterator gsi =3D gsi_for_stmt (call); > + tree fndecl =3D e->callee->decl; > + > + enext =3D e->next_callee; > + > + if (gimple_alloca_call_p (call)) > + { > + gimple_seq seq =3D call_update_watermark (wmptr, NULL, > + gsi_bb (gsi)->cou= nt); > + 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 inel= igible > + for internal strub. */ > + gcc_checking_assert (builtin_decl_explicit (BUILT_IN_VA_S= TART) > + =3D=3D fndecl); > + tree bvacopy =3D builtin_decl_explicit (BUILT_IN_VA_COPY)= ; > + gimple_call_set_fndecl (call, bvacopy); > + tree arg =3D vaptr; > + /* The va_copy source must be dereferenced, unless it's a= n array > + type, that would have decayed to a pointer. */ > + if (TREE_CODE (TREE_TYPE (TREE_TYPE (vaptr))) !=3D ARRAY_= TYPE) > + { > + arg =3D gimple_fold_indirect_ref (vaptr); > + if (!arg) > + arg =3D build2 (MEM_REF, > + TREE_TYPE (TREE_TYPE (vaptr)), > + vaptr, > + build_int_cst (TREE_TYPE (vaptr), 0))= ; > + if (!is_gimple_val (arg)) > + arg =3D force_gimple_operand_gsi (&gsi, arg, true, > + NULL_TREE, true, GS= I_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 =3D gimple_call_lhs (call); > + gimple *assign =3D (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 > + =3D gsi_after_labels (single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun))= ); > + > + gcall *wrcall; > + while (!(wrcall =3D dyn_cast (gsi_stmt (gsi)))) > + gsi_next (&gsi); > + > + tree swm =3D create_tmp_var (get_wmt (), ".strub.watermark"); > + TREE_ADDRESSABLE (swm) =3D true; > + tree swmp =3D build1 (ADDR_EXPR, get_pwmt (), swm); > + > + tree enter =3D get_enter (); > + gcall *stptr =3D 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 =3D gimple_call_num_args (wrcall); > + > + gimple_seq seq =3D NULL; > + > + if (apply_args) > + { > + tree aalst =3D create_tmp_var (ptr_type_node, ".strub.apply_arg= s"); > + tree bappargs =3D builtin_decl_explicit (BUILT_IN_APPLY_ARGS); > + gcall *appargs =3D 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 =3D create_tmp_var (va_list_type_node, ".strub.va_li= st"); > + TREE_ADDRESSABLE (valst) =3D true; > + tree vaptr =3D build1 (ADDR_EXPR, > + build_pointer_type (va_list_type_node), > + valst); > + gimple_call_set_arg (wrcall, nargs - 2, unshare_expr (vaptr)); > + > + tree bvastart =3D builtin_decl_explicit (BUILT_IN_VA_START); > + gcall *vastart =3D 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 =3D builtin_decl_explicit (BUILT_IN_VA_END); > + gcall *vaend =3D gimple_build_call (bvaend, 1, unshare_expr (va= ptr)); > + 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 *inputs =3D NULL; > + vec *outputs =3D NULL; > + vec_safe_push (outputs, > + build_tree_list > + (build_tree_list > + (NULL_TREE, build_string (2, "=3Dm")), > + swm)); > + vec_safe_push (inputs, > + build_tree_list > + (build_tree_list > + (NULL_TREE, build_string (1, "m")), > + swm)); > + gasm *forcemod =3D 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 *inputs =3D NULL; > + vec_safe_push (inputs, > + build_tree_list > + (build_tree_list > + (NULL_TREE, build_string (1, "m")), > + swm)); > + gasm *force_store =3D gimple_build_asm_vec ("", inputs, N= ULL, > + NULL, NULL); > + gimple_set_location (force_store, gimple_location (wrcall= )); > + gsi_insert_before (&gsi, force_store, GSI_SAME_STMT); > + } > + } > +#endif > + > + gcall *sleave =3D gimple_build_call (get_leave (), 1, > + unshare_expr (swmp)); > + gimple_seq_add_stmt (&seq, sleave); > + > + gassign *clobber =3D 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 . > + > +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 > +. */ > + > +/* Return TRUE if CALLEE can be inlined into CALLER, as far as stack scr= ubbing > + constraints are concerned. CALLEE doesn't have to be called directly= by > + CALLER, but the returned value says nothing about intervening functio= ns. */ > +extern bool strub_inlinable_to_p (cgraph_node *callee, cgraph_node *call= er); > + > +/* 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 w= ith 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 a= re > + 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=3Dstrict -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=3Dstrict -fdump-rtl-expand" } */ > + > +/* At -O1, without -fno-inline, we fully expand enter, but neither updat= e 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=3Dstrict -fdump-rtl-expand" } */ > + > +/* At -O2, without -fno-inline, we fully expand enter and update, and ad= d 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=3Dstrict -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=3Dstrict -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=3Dstrict -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=3Dstrict -fdump-rtl-expand" } */ > + > +/* At -Og, without -fno-inline, we fully expand enter, but neither updat= e 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=3Dstrict -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 savi= ng and > + restoring required by the call will most often make it larger. The l= eave > + 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=3Dall -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=3Dall -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_CALL= S > + 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=3Dstrict" } */ > + > +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 =3D __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=3Dstrict" } */ > + > +extern void __attribute__ ((__strub__)) > +apply_function (void *args); > + > +void __attribute__ ((__strub__)) > +apply_args (int i, int j, double d) /* { dg-error "selected" } */ > +{ > + void *args =3D __builtin_apply_args (); /* { dg-message "does not supp= ort" } */ > + 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=3Dstrict" } */ > + > +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=3Dstrict -fdump-ipa-strubm" } */ > + > +/* Check that implicit enabling of strub mode selects internal strub whe= n 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=3Dat-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=3Dat-calls -fdump-ipa-strubm -fdump-ipa-strub" = } */ > + > +/* g does NOT become STRUB_AT_CALLS because it's not viable. Without in= line, > + force_output is set for static non-inline functions when not optimizi= ng, 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=3Dstrict -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=3Dstrict -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=3Dstrict -O3" } */ > + > +/* Check that a strub function called by another strub function defers t= he > + 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[] =3D "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\x= aa"; > + > +/* Pad before and after the string on the stack, so that it's not overwr= itten 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 wou= ldn't > + find it because it would be outside the range we searched. */ > + typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char= *); > + callable_t *f =3D 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 =3D (char*)__builtin_stack_address (); > + > + if (p =3D=3D e) > + __builtin_abort (); > + > + if (p > e) > + { > + char *q =3D p; > + p =3D e; > + e =3D q; > + } > + > + for (char *re =3D e - sizeof (test_string); p < re; p++) > + for (int i =3D 0; p[i] =3D=3D test_string[i]; i++) > + if (i =3D=3D 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 =3D 1; > + /* Since these test check stack contents above the top of the stack, a= n > + unexpected asynchronous signal or interrupt might overwrite the bit= s we > + expect to find and cause spurious fails. Tolerate one such overall > + spurious fail by retrying. */ > + while (EXPECT_DEFERRAL !look_for_string ((ret =3D at_calls ()))) > + if (!i--) __builtin_abort (); > + return ret; > +} > + > +static __attribute__ ((__strub__ ("internal"))) > +char * > +deferred_internal () > +{ > + int i =3D 1; > + char *ret; > + while (EXPECT_DEFERRAL !look_for_string ((ret =3D at_calls ()))) > + if (!i--) __builtin_abort (); > + return ret; > +} > + > +int main () > +{ > + int i =3D 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 s= tring > + where we look for it, but what are the odds? Anyway, it doesn't hu= rt 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=3Dstrict -Os" } */ > + > +/* Check that a strub function called by another strub function defers t= he > + 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=3Dinternal -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=3Dinternal -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=3Dstrict -fdump-ipa-strub" } */ > + > +#include > + > +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\[^ \]*.s= trub.\[0-9\]* \[(\]struct large_arg & la, void \\* &\[^&,\]*.strub.watermar= k_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, vo= id \\* &\[^&,\]*.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=3Dstrict -fdump-ipa-strub" } */ > + > +#include > + > +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\[^ \]* \[(\]i= nt 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\[)\]" "stru= b" } } */ > + > +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=3Dstrict -fdump-ipa-strub" } */ > + > +/* Check that uses of a strub variable implicitly enables internal strub= for > + publicly-visible functions, and causes the same transformations to th= eir > + signatures as those in strub-parms1.c. */ > + > +#include > + > +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\[^ \]*.s= trub.\[0-9\]* \[(\]struct large_arg & la, void \\* &\[^&,\]*.strub.watermar= k_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, vo= id \\* &\[^&,\]*.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=3Drelaxed -fdump-ipa-strubm -fdump-ipa-strub" }= */ > + > +/* The difference between relaxed and strict in this case is that we acc= ept 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 "strub= m" } } */ > +/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "st= rubm" } } */ > +/* { 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 "st= rub" } } */ > +/* { 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=3Drelaxed -fdump-ipa-strubm -fdump-ipa-strub" }= */ > + > +/* The difference between relaxed and strict in this case is that we acc= ept 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/testsu= ite/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=3Dstrict -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=3Dstrict -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=3Dstrict -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=3Dstrict -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=3Dstrict -fno-exceptions -fdump-ipa-strub" = } */ > + > +/* Check that the expected strub calls are issued. At -O3 and -Os, we o= mit > + 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=3Dstrict -fno-exceptions -fdump-ipa-strub" = } */ > + > +/* Check that the expected strub calls are issued. At -O3 and -Os, we o= mit > + 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=3Dstrict -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_str= ub, 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 variabl= e, and > + the viability of at-calls strubbing. Though internally a strub conte= xt, its > + interface is not strub-enabled, so it's not callable from within stru= b > + contexts. */ > +static inline void > +g() { > + var--; > + h(); > +} > + > +/* f becomes STRUB_INTERNAL because of the use of the strub variable, an= d 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 "strub= m" } } */ > +/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "st= rubm" } } */ > +/* { 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=3Dstrict -fdump-ipa-strubm" } */ > + > +static int __attribute__ ((__strub__)) var; > + > +/* g becomes STRUB_INTERNAL because of the use of the strub variable, an= d 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, an= d 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=3Dstrict -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=3Dstrict -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 po= inter > + variable p, not to the pointed-to integer. */ > +int __attribute__ ((strub)) * > +p =3D &x; /* { dg-message "incompatible|invalid conversion" } */ > + > +typedef int __attribute__ ((strub)) strub_int; > +strub_int *q =3D &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/t= estsuite/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=3Dstrict" } */ > + > +/* Check that strub and non-strub functions can be called from non-strub > + contexts, and that strub and callable functions can be called from st= rub > + contexts. */ > + > +#define OMIT_IMPERMISSIBLE_CALLS 1 > +#include "strub-callable2.c" > diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/t= estsuite/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=3Dstrict" } */ > + > +/* 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__ ("callabl= e"))) > +icallable (void); > +static inline int __attribute__ ((__always_inline__, __strub__ ("interna= l"))) > +iinternal (void); > +static inline int __attribute__ ((__always_inline__, __strub__ ("at-call= s"))) > +iat_calls (void); > +static inline int __attribute__ ((__always_inline__, __strub__ ("disable= d"))) > +idisabled (void); > +static inline int __attribute__ ((__always_inline__)) > +ivar_user (void); > + > +static inline int __attribute__ ((__always_inline__, __strub__ ("callabl= e"))) > +i_callable (void) { return 0; } > +static inline int __attribute__ ((__always_inline__, __strub__ ("interna= l"))) > +i_internal (void) { return var; } > +static inline int __attribute__ ((__always_inline__, __strub__ ("at-call= s"))) > +i_at_calls (void) { return var; } > +static inline int __attribute__ ((__always_inline__, __strub__ ("disable= d"))) > +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 +=3D i ## ISEP ## at_calls (); \ > + ret +=3D i ## ISEP ## internal (); \ > + ret +=3D i ## ISEP ## var_user (); \ > + } while (0) > + > +#define CALLS_GOOD_FOR_NONSTRUB_CONTEXT(ISEP) \ > + do { \ > + ret +=3D internal (); \ > + ret +=3D disabled (); \ > + ret +=3D var_user (); \ > + \ > + ret +=3D i ## ISEP ## disabled (); \ > + \ > + ret +=3D xinternal (); \ > + ret +=3D xdisabled (); \ > + } while (0) > + > +#define CALLS_GOOD_FOR_EITHER_CONTEXT(ISEP) \ > + do { \ > + ret +=3D i ## ISEP ## callable (); \ > + \ > + ret +=3D callable (); \ > + ret +=3D at_calls (); \ > + \ > + ret +=3D xat_calls (); \ > + ret +=3D 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 =3D 0; > + > + /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */ > +#if !OMIT_IMPERMISSIBLE_CALLS > + ret +=3D iat_calls (); /* { dg-error "in non-.strub. context" } */ > + ret +=3D iinternal (); /* { dg-error "in non-.strub. context" } */ > + ret +=3D 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 =3D var; > + > + CALLS_GOOD_FOR_STRUB_CONTEXT(); > + CALLS_GOOD_FOR_EITHER_CONTEXT(); > + /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */ > +#if !OMIT_IMPERMISSIBLE_CALLS > + ret +=3D internal (); /* { dg-error "in .strub. context" } */ > + ret +=3D disabled (); /* { dg-error "in .strub. context" } */ > + ret +=3D var_user (); /* { dg-error "in .strub. context" } */ > + > + ret +=3D idisabled (); /* { dg-error "in .strub. context" } */ > + > + ret +=3D xinternal (); /* { dg-error "in .strub. context" } */ > + ret +=3D xdisabled (); /* { dg-error "in .strub. context" } */ > +#endif > + > + return ret; > +} > + > +int __attribute__ ((__strub__ ("at-calls"))) > +at_calls (void) { > + int ret =3D var; > + > + CALLS_GOOD_FOR_STRUB_CONTEXT(); > + CALLS_GOOD_FOR_EITHER_CONTEXT(); > + /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */ > +#if !OMIT_IMPERMISSIBLE_CALLS > + ret +=3D internal (); /* { dg-error "in .strub. context" } */ > + ret +=3D disabled (); /* { dg-error "in .strub. context" } */ > + ret +=3D var_user (); /* { dg-error "in .strub. context" } */ > + > + ret +=3D idisabled (); /* { dg-error "in .strub. context" } */ > + > + ret +=3D xinternal (); /* { dg-error "in .strub. context" } */ > + ret +=3D xdisabled (); /* { dg-error "in .strub. context" } */ > +#endif > + > + return ret; > +} > + > +int __attribute__ ((__strub__ ("disabled"))) > +disabled () { > + int ret =3D 0; > + > + /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */ > +#if !OMIT_IMPERMISSIBLE_CALLS > + ret +=3D iat_calls (); /* { dg-error "in non-.strub. context" } */ > + ret +=3D iinternal (); /* { dg-error "in non-.strub. context" } */ > + ret +=3D 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 =3D var; > + > + CALLS_GOOD_FOR_STRUB_CONTEXT(); > + CALLS_GOOD_FOR_EITHER_CONTEXT(); > + /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */ > +#if !OMIT_IMPERMISSIBLE_CALLS > + ret +=3D internal (); /* { dg-error "in .strub. context" } */ > + ret +=3D disabled (); /* { dg-error "in .strub. context" } */ > + ret +=3D var_user (); /* { dg-error "in .strub. context" } */ > + > + ret +=3D idisabled (); /* { dg-error "in .strub. context" } */ > + > + ret +=3D xinternal (); /* { dg-error "in .strub. context" } */ > + ret +=3D xdisabled (); /* { dg-error "in .strub. context" } */ > +#endif > + > + return ret; > +} > + > +int > +icallable (void) > +{ > + int ret =3D 0; > + > + /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */ > +#if !OMIT_IMPERMISSIBLE_CALLS > + ret +=3D i_at_calls (); /* { dg-error "in non-.strub. context" } */ > + ret +=3D i_internal (); /* { dg-error "in non-.strub. context" } */ > + ret +=3D 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 =3D var; > + > + CALLS_GOOD_FOR_STRUB_CONTEXT(_); > + CALLS_GOOD_FOR_EITHER_CONTEXT(_); > + /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */ > +#if !OMIT_IMPERMISSIBLE_CALLS > + ret +=3D internal (); /* { dg-error "in .strub. context" } */ > + ret +=3D disabled (); /* { dg-error "in .strub. context" } */ > + ret +=3D var_user (); /* { dg-error "in .strub. context" } */ > + > + ret +=3D i_disabled (); /* { dg-error "in .strub. context" } */ > + > + ret +=3D xinternal (); /* { dg-error "in .strub. context" } */ > + ret +=3D xdisabled (); /* { dg-error "in .strub. context" } */ > +#endif > + > + return ret; > +} > + > +int __attribute__ ((__always_inline__, __strub__ ("at-calls"))) > +iat_calls (void) { > + int ret =3D var; > + > + CALLS_GOOD_FOR_STRUB_CONTEXT(_); > + CALLS_GOOD_FOR_EITHER_CONTEXT(_); > + /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */ > +#if !OMIT_IMPERMISSIBLE_CALLS > + ret +=3D internal (); /* { dg-error "in .strub. context" } */ > + ret +=3D disabled (); /* { dg-error "in .strub. context" } */ > + ret +=3D var_user (); /* { dg-error "in .strub. context" } */ > + > + ret +=3D i_disabled (); /* { dg-error "in .strub. context" } */ > + > + ret +=3D xinternal (); /* { dg-error "in .strub. context" } */ > + ret +=3D xdisabled (); /* { dg-error "in .strub. context" } */ > +#endif > + > + return ret; > +} > + > +int > +idisabled () { > + int ret =3D 0; > + > + /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */ > +#if !OMIT_IMPERMISSIBLE_CALLS > + ret +=3D i_at_calls (); /* { dg-error "in non-.strub. context" } */ > + ret +=3D i_internal (); /* { dg-error "in non-.strub. context" } */ > + ret +=3D 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 =3D var; > + > + CALLS_GOOD_FOR_STRUB_CONTEXT(_); > + CALLS_GOOD_FOR_EITHER_CONTEXT(_); > + /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */ > +#if !OMIT_IMPERMISSIBLE_CALLS > + ret +=3D internal (); /* { dg-error "in .strub. context" } */ > + ret +=3D disabled (); /* { dg-error "in .strub. context" } */ > + ret +=3D var_user (); /* { dg-error "in .strub. context" } */ > + > + ret +=3D i_disabled (); /* { dg-error "in .strub. context" } */ > + > + ret +=3D xinternal (); /* { dg-error "in .strub. context" } */ > + ret +=3D xdisabled (); /* { dg-error "in .strub. context" } */ > +#endif > + > + return ret; > +} > diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/test= suite/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=3Dstrict -fdump-ipa-strub" } */ > + > +/* Check that, along with a strub const function call, we issue an asm s= tatement > + 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/test= suite/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=3Dstrict -fdump-ipa-strub" } */ > + > +/* Check that, along with a strub implicitly-const function call, we iss= ue an > + asm statement to make sure the watermark passed to it is held in memo= ry > + 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/test= suite/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=3Dstrict -fdump-ipa-strub" } */ > + > +/* Check that, along with a strub const wrapping call, we issue an asm s= tatement > + 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/test= suite/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=3Dstrict -fdump-ipa-strub" } */ > + > +/* Check that, along with a strub implicitly-const wrapping call, we iss= ue an > + asm statement to make sure the watermark passed to it is held in memo= ry > + 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/tests= uite/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=3Dstrict -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/tests= uite/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=3Dstrict -fdump-ipa-strub" } */ > + > +/* The pointer itself is a strub variable, enabling internal strubbing w= hen > + 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/tests= uite/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=3Dstrict -fdump-ipa-strub" } */ > + > +/* The pointer itself is a strub variable, that would enable internal st= rubbing > + if its value was used. Here, it's only overwritten, so no strub. */ > +int __attribute__ ((__strub__)) var; > + > +void f() { > + var =3D 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/tests= uite/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=3Dstrict -fdump-ipa-strub" } */ > + > +/* The pointer itself is a strub variable, that would enable internal st= rubbing > + if its value was used. Here, it's only overwritten, so no strub. */ > +int __attribute__ ((__strub__)) *ptr; > + > +void f() { > + ptr =3D 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/tests= uite/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=3Dstrict" } */ > + > +/* 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/te= stsuite/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=3Dstrict -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/te= stsuite/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=3Dstrict -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/te= stsuite/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=3Dstrict -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=3Drelaxed" } */ > + > +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=3Dall" } */ > + > +#include "strub-inlinable1.c" > + > +/* With -fstrub=3Dall, 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/test= suite/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=3Dstrict" } */ > + > +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/test= suite/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=3Drelaxed -Wpedantic" } */ > + > +/* C++ does not warn about the partial incompatibilities. > + > + The d_p () calls are actually rejected, even in C++, but they are XFA= ILed > + 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) =3D bad; > + int __attribute__ ((strub ("callable"))) (*c_p) (void) =3D bac; > + int __attribute__ ((strub ("at-calls"))) (*a_p) (void) =3D bal; > + > + d_p =3D bac; /* { dg-warning "not quite compatible" "" { xfail c++ } }= */ > + c_p =3D bad; /* { dg-warning "not quite compatible" "" { xfail c++ } }= */ > + c_p =3D bar; /* { dg-warning "not quite compatible" "" { xfail c++ } }= */ > + c_p =3D bal; /* { dg-message "incompatible|invalid conversion" } */ > + a_p =3D 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 =3D bad; > + c_fn_t *c_p =3D bac; > + a_fn_t *a_p =3D bal; > + > + d_p =3D bac; /* { dg-warning "not quite compatible" "" { xfail c++ } }= */ > + c_p =3D bad; /* { dg-warning "not quite compatible" "" { xfail c++ } }= */ > + c_p =3D bar; /* { dg-warning "not quite compatible" "" { xfail c++ } }= */ > + c_p =3D bal; /* { dg-message "incompatible|invalid conversion" } */ > + a_p =3D 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/test= suite/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=3Drelaxed -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) =3D bad; > + int __attribute__ ((strub ("callable"))) (*c_p) (void) =3D bac; > + int __attribute__ ((strub ("at-calls"))) (*a_p) (void) =3D bal; > + > + d_p =3D bac; /* { dg-warning "not quite compatible" "" { xfail c++ } }= */ > + c_p =3D bad; /* { dg-warning "not quite compatible" "" { xfail c++ } }= */ > + c_p =3D bar; /* { dg-warning "not quite compatible" "" { xfail c++ } }= */ > + c_p =3D bal; /* { dg-message "incompatible|invalid conversion" } */ > + a_p =3D 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 =3D bad; > + c_fn_t *c_p =3D bac; > + a_fn_t *a_p =3D bal; > + > + d_p =3D bac; /* { dg-warning "not quite compatible" "" { xfail c++ } }= */ > + c_p =3D bad; /* { dg-warning "not quite compatible" "" { xfail c++ } }= */ > + c_p =3D bar; /* { dg-warning "not quite compatible" "" { xfail c++ } }= */ > + c_p =3D bal; /* { dg-message "incompatible|invalid conversion" } */ > + a_p =3D 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/test= suite/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=3Drelaxed" } */ > + > +/* 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) =3D bad; > + int __attribute__ ((strub ("callable"))) (*c_p) (void) =3D bac; > + int __attribute__ ((strub ("at-calls"))) (*a_p) (void) =3D bal; > + > + d_p =3D bac; > + c_p =3D bad; > + c_p =3D bar; > + c_p =3D bal; /* { dg-message "incompatible|invalid conversion" } */ > + a_p =3D 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 =3D bad; > + c_fn_t *c_p =3D bac; > + a_fn_t *a_p =3D bal; > + > + d_p =3D bac; > + c_p =3D bad; > + c_p =3D bar; > + c_p =3D bal; /* { dg-message "incompatible|invalid conversion" } */ > + a_p =3D bac; /* { dg-message "incompatible|invalid conversion" } */ > +} > diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/tests= uite/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=3Dstrict -fdump-ipa-strub" } */ > + > +/* Check that, along with a strub pure function call, we issue an asm st= atement > + to make sure the watermark passed to it is not assumed to be unchange= d. */ > + > +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/tests= uite/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=3Dstrict -fdump-ipa-strub" } */ > + > +/* Check that, along with a strub implicitly-pure function call, we issu= e an asm > + statement to make sure the watermark passed to it is not assumed to b= e > + 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/tests= uite/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=3Dstrict -fdump-ipa-strub" } */ > + > +/* Check that, along with a strub pure wrapping call, we issue an asm st= atement > + to make sure the watermark passed to it is not assumed to be unchange= d. */ > + > +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/tests= uite/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=3Dstrict -fdump-ipa-strub" } */ > + > +/* Check that, along with a strub implicitly-pure wrapping call, we issu= e an asm > + statement to make sure the watermark passed to it is not assumed to b= e > + 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/testsu= ite/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=3Dstrict" } */ > + > +/* 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 avoi= ding > + leaf functions. */ > + > +const char test_string[] =3D "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\x= aa"; > + > +/* Pad before and after the string on the stack, so that it's not overwr= itten 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 wou= ldn't > + find it because it would be outside the range we searched. */ > + typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char= *); > + callable_t *f =3D 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 =3D (char *) __builtin_stack_address (); > + > + if (p =3D=3D e) > + __builtin_abort (); > + > + if (p > e) > + { > + char *q =3D p; > + p =3D e; > + e =3D q; > + } > + > + for (char *re =3D e - sizeof (test_string); p < re; p++) > + for (int i =3D 0; p[i] =3D=3D test_string[i]; i++) > + if (i =3D=3D 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, a= n > + unexpected asynchronous signal or interrupt might overwrite the bit= s we > + expect to find and cause spurious fails. Tolerate one such overall > + spurious fail by retrying. */ > + int i =3D 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/testsu= ite/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=3Dstrict" } */ > + > +/* 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[] =3D "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\x= aa"; > + > +/* Pad before and after the string on the stack, so that it's not overwr= itten by > + regular stack use. */ > +#define PAD 7 > + > +static inline __attribute__ ((__always_inline__, __strub__ ("callable"))= ) > +char * > +leak_string (void) > +{ > + int len =3D 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 =3D (char *) __builtin_stack_address (); > + > + if (p =3D=3D e) > + __builtin_abort (); > + > + if (p > e) > + { > + char *q =3D p; > + p =3D e; > + e =3D q; > + } > + > + for (char *re =3D e - sizeof (test_string); p < re; p++) > + for (int i =3D 0; p[i] =3D=3D test_string[i]; i++) > + if (i =3D=3D 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, a= n > + unexpected asynchronous signal or interrupt might overwrite the bit= s we > + expect to find and cause spurious fails. Tolerate one such overall > + spurious fail by retrying. */ > + int i =3D 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/testsu= ite/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=3Dstrict" } */ > +/* { 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[] =3D "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\x= aa"; > + > +static inline __attribute__ ((__always_inline__, __strub__ ("callable"))= ) > +char * > +leak_string (void) > +{ > + int len =3D sizeof (test_string); > + char *s =3D (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 =3D (char *) __builtin_stack_address (); > + > + if (p =3D=3D e) > + __builtin_abort (); > + > + if (p > e) > + { > + char *q =3D p; > + p =3D e; > + e =3D q; > + } > + > + for (char *re =3D e - sizeof (test_string); p < re; p++) > + for (int i =3D 0; p[i] =3D=3D test_string[i]; i++) > + if (i =3D=3D 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, a= n > + unexpected asynchronous signal or interrupt might overwrite the bit= s we > + expect to find and cause spurious fails. Tolerate one such overall > + spurious fail by retrying. */ > + int i =3D 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/testsu= ite/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=3Dall" } */ > +/* { 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[] =3D "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\x= aa"; > + > +static inline __attribute__ ((__always_inline__)) > +char * > +leak_string (void) > +{ > + int __attribute__ ((__strub__)) len =3D 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 =3D (char *) __builtin_stack_address (); > + > + if (p =3D=3D e) > + __builtin_abort (); > + > + if (p > e) > + { > + char *q =3D p; > + p =3D e; > + e =3D q; > + } > + > + for (char *re =3D e - sizeof (test_string); p < re; p++) > + for (int i =3D 0; p[i] =3D=3D test_string[i]; i++) > + if (i =3D=3D sizeof (test_string) - 1) > + return i; > + > + return 0; > +} > + > +static inline ATTR_STRUB_AT_CALLS > +char * > +innermost () > +{ > + int __attribute__ ((__strub__)) len =3D 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 =3D leak_string (); > + if (__builtin_strcmp (s, test_string) !=3D 0) > + __builtin_abort (); > + if (__builtin_strcmp (s + len - sizeof (test_string), test_string) != =3D 0) > + __builtin_abort (); > + return ret; > +} > + > +static inline ATTR_STRUB_AT_CALLS > +char * > +intermediate () > +{ > + int __attribute__ ((__strub__)) len =3D 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 =3D innermost (); > + if (__builtin_strcmp (s, test_string) !=3D 0) > + __builtin_abort (); > + if (__builtin_strcmp (s + len - sizeof (test_string), test_string) != =3D 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, a= n > + unexpected asynchronous signal or interrupt might overwrite the bit= s we > + expect to find and cause spurious fails. Tolerate one such overall > + spurious fail by retrying. */ > + int i =3D 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/tests= uite/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=3Dat-calls" } */ > +/* { dg-require-effective-target alloca } */ > + > +#include "strub-run4.c" > diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4d.c b/gcc/tests= uite/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=3Dstrict" } */ > +/* { 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/tests= uite/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=3Dinternal" } */ > +/* { dg-require-effective-target alloca } */ > + > +#include "strub-run4.c" > diff --git a/gcc/testsuite/g++.dg/strub-run1.C b/gcc/testsuite/g++.dg/str= ub-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=3Dinternal" } > + > +// Check that we don't get extra copies. > + > +struct T { > + T &self; > + void check () const { if (&self !=3D 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=3Dstrict -fdump-ipa-strub" } */ > + > +extern int __attribute__((__strub__)) initializer (); > + > +int f() { > + static int x =3D 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=3Dstrict -fdump-ipa-strub" } */ > + > +extern int __attribute__((__strub__)) initializer (); > + > +static int x =3D 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=3Dstrict -fdump-ipa-strub" } */ > + > +extern int __attribute__((__strub__)) initializer (); > + > +int f() { > + int x =3D 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=3Drelaxed -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 :=3D 0; > + > + function F (P : access Strub_Int) return Strub_Int is (P.all); > + > +begin > + X :=3D 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=3Drelaxed" } > + > +-- Check that we reject 'Access of a strub variable whose type does > +-- not carry a strub modifier. > + > +procedure Strub_Access1 is > + X : aliased Integer :=3D 0; > + pragma Machine_Attribute (X, "strub"); > + > + function F (P : access Integer) return Integer is (P.all); > + > +begin > + X :=3D F (X'Unchecked_access); -- OK. > + X :=3D 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=3Dstrict -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 wrapp= ers. > +-- 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 :=3D 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 :=3D 0; > + XC : access A'Class; > +begin > + Q (XA); > + Q (XB); > + > + I :=3D I + F (XA'Access); > + I :=3D I + F (XB'Access); > + > + XC :=3D XA'Access; > + I :=3D I + F (XC); > + > + XC :=3D XB'Access; > + I :=3D I + F (XC); > +end Strub_Disp; > diff --git a/gcc/testsuite/gnat.dg/strub_disp1.adb b/gcc/testsuite/gnat.d= g/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 :=3D 0; > + XC : access A'Class; > +begin > + Q (XA); > + Q (XB); > + > + I :=3D I + F (XA'Access); -- strub-at-calls non-dispatching call > + I :=3D I + F (XB'Access); -- strub-at-calls non-dispatching call > + > + XC :=3D XA'Access; > + I :=3D I + F (XC); -- strub-at-calls dispatching call. > + > + XC :=3D XB'Access; > + I :=3D 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=3Dstrict" } > + > +-- 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 :=3D 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 :=3D 0; > + pragma Machine_Attribute (X, "strub"); > + > + type FT is access function (X : Integer) return Integer; > + pragma Machine_Attribute (FT, "strub", "at-calls"); > + > + FP : FT :=3D 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=3Dstrict -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 :=3D 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 :=3D 0; > + pragma Machine_Attribute (X, "strub"); > + > + type FT is access function (X : Integer) return Integer; > + pragma Machine_Attribute (FT, "strub", "at-calls"); > + > + FP : FT :=3D 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=3Dstrict" } > + > +-- 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 :=3D 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 :=3D 0; > + pragma Machine_Attribute (X, "strub"); > + > + type FT is access function (X : Integer) return Integer; > + pragma Machine_Attribute (FT, "strub", "at-calls"); > + > + FP : FT :=3D 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 "require= s 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 :=3D 0; > + XC : access TI'Class; > +begin > + Q (XA); > + Q (XB); > + > + I :=3D I + F (XA'Access); > + I :=3D I + F (XB'Access); > + > + XC :=3D XA'Access; > + I :=3D I + F (XC); > + > + XC :=3D XB'Access; > + I :=3D I + F (XC); > +end Strub_Intf; > diff --git a/gcc/testsuite/gnat.dg/strub_intf1.adb b/gcc/testsuite/gnat.d= g/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 :=3D 0; > + XC : access TX'Class; > +begin > + Q (XA); > + Q (XB); > + > + I :=3D I + F (XA'Access); > + I :=3D I + F (XB'Access); > + > + XC :=3D XA'Access; > + I :=3D I + F (XC); > + > + XC :=3D XB'Access; > + I :=3D 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.d= g/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 t= he 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 :=3D 0; > + XC : access TX'Class; > +begin > + Q (XB); > + > + I :=3D I + F (XB'Access); > + > + XC :=3D XB'Access; > + I :=3D 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 "req= uires 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.d= g/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=3Drelaxed -fdump-ipa-strub" } > + > +procedure Strub_Renm1 is > + V : Integer :=3D 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.d= g/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=3Dstrict -fdump-ipa-strub" } > + > +procedure Strub_Renm2 is > + V : Integer :=3D 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 :=3D 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=3Dstrict -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 :=3D 0; > + pragma Machine_Attribute (X, "strub"); > +begin > + X :=3D 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 :=3D (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 :=3D (0, 0); > + > +begin > + A(2) :=3D 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 =3D 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 (g= cc::context *ctxt); > > /* IPA Passes */ > extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ct= xt); > -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_visibili= ty (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 *ct= xt); > extern simple_ipa_opt_pass *make_pass_ipa_auto_profile (gcc::context *ct= xt); > > 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 +=3D enable-execute-stack.c > # Control Flow Redundancy hardening out-of-line checker. > LIB2ADD +=3D $(srcdir)/hardcfr.c > > +# Stack scrubbing infrastructure. > +LIB2ADD +=3D $(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 > + > +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 > +. */ > + > +#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 ca= ller's > + stack address. */ > +void ATTRIBUTE_STRUB_CALLABLE > +__strub_enter (void **watermark) > +{ > + *watermark =3D __builtin_frame_address (0); > +} > + > +/* Update the watermark within a stack scrubbing context with the curren= t stack > + pointer. */ > +void ATTRIBUTE_STRUB_CALLABLE > +__strub_update (void **watermark) > +{ > + void *sp =3D __builtin_frame_address (0); > + > + if (sp TOPS *watermark) > + *watermark =3D sp; > +} > + > +#if TARGET_STRUB_USE_DYNAMIC_ARRAY && ! defined TARGET_STRUB_MAY_USE_MEM= SET > +# 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 opt= imizing */ > +#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 a= nd > + *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 =3D __builtin_stack_address (); > + > + void **base, **end; > +#if ! STACK_GROWS_DOWNWARD > + base =3D sp; /* ??? Do we need an offset here? */ > + end =3D *mark; > +#else > + base =3D *mark; > + end =3D 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 sufficien= tly > + aligned. They should be, but pointer differences expected to be ex= act may > + yield unexpected results when the assumption doesn't hold. Given t= he > + potential security implications, compute the length without that > + expectation. If the pointers are misaligned, we may leave a partia= l > + unscrubbed word behind. */ > + ptrdiff_t len =3D ((char *)end - (char *)base) / sizeof (void *); > + /* Allocate a dynamically-sized array covering the desired range, so t= hat we > + can safely call memset on it. */ > + void *ptr[len]; > + base =3D &ptr[0]; > + end =3D &ptr[len]; > +#elifndef TARGET_STRUB_DISABLE_RED_ZONE > + /* Prevent the use of the red zone, by making this function non-leaf t= hrough > + an unreachable call that, because of the asm stmt, the compiler wil= l > + 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 arr= ay > + above, that call would likely be unsafe: possibly tail-called, and = likely > + scribbling over its own stack frame. */ > +#if ! STACK_GROWS_DOWNWARD > + do > + *base++ =3D 0; > + while (base < end); > + /* Make sure the stack overwrites are not optimized away. */ > + asm ("" : : "m" (end[0])); > +#else > + do > + *--end =3D 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