diff options
author | Alexandre Oliva <oliva@adacore.com> | 2023-12-05 21:07:36 -0300 |
---|---|---|
committer | Alexandre Oliva <oliva@gnu.org> | 2023-12-05 21:07:36 -0300 |
commit | f0a90c7d7333fc7f554b906245c84bdf04d716d7 (patch) | |
tree | 4396a55e7bf1d88895102499dbea7fdd7d3ac6d6 /gcc/testsuite | |
parent | 08448dc146b6dd32383d64ab491a594d41f62aaa (diff) | |
download | gcc-f0a90c7d7333fc7f554b906245c84bdf04d716d7.zip gcc-f0a90c7d7333fc7f554b906245c84bdf04d716d7.tar.gz gcc-f0a90c7d7333fc7f554b906245c84bdf04d716d7.tar.bz2 |
Introduce strub: machine-independent stack scrubbing
This patch adds the strub attribute for function and variable types,
command-line options, passes and adjustments to implement it,
documentation, and tests.
Stack scrubbing is implemented in a machine-independent way: functions
with strub enabled are modified so that they take an extra stack
watermark argument, that they update with their stack use, and the
caller can then zero it out once it regains control, whether by return
or exception. There are two ways to go about it: at-calls, that
modifies the visible interface (signature) of the function, and
internal, in which the body is moved to a clone, the clone undergoes
the interface change, and the function becomes a wrapper, preserving
its original interface, that calls the clone and then clears the stack
used by it.
Variables can also be annotated with the strub attribute, so that
functions that read from them get stack scrubbing enabled implicitly,
whether at-calls, for functions only usable within a translation unit,
or internal, for functions whose interfaces must not be modified.
There is a strict mode, in which functions that have their stack
scrubbed can only call other functions with stack-scrubbing
interfaces, or those explicitly marked as callable from strub
contexts, so that an entire call chain gets scrubbing, at once or
piecemeal depending on optimization levels. In the default mode,
relaxed, this requirement is not enforced by the compiler.
The implementation adds two IPA passes, one that assigns strub modes
early on, another that modifies interfaces and adds calls to the
builtins that jointly implement stack scrubbing. Another builtin,
that obtains the stack pointer, is added for use in the implementation
of the builtins, whether expanded inline or called in libgcc.
There are new command-line options to change operation modes and to
force the feature disabled; it is enabled by default, but it has no
effect and is implicitly disabled if the strub attribute is never
used. There are also options meant to use for testing the feature,
enabling different strubbing modes for all (viable) functions.
for gcc/ChangeLog
* Makefile.in (OBJS): Add ipa-strub.o.
(GTFILES): Add ipa-strub.cc.
* builtins.def (BUILT_IN_STACK_ADDRESS): New.
(BUILT_IN___STRUB_ENTER): New.
(BUILT_IN___STRUB_UPDATE): New.
(BUILT_IN___STRUB_LEAVE): New.
* builtins.cc: Include ipa-strub.h.
(STACK_STOPS, STACK_UNSIGNED): Define.
(expand_builtin_stack_address): New.
(expand_builtin_strub_enter): New.
(expand_builtin_strub_update): New.
(expand_builtin_strub_leave): New.
(expand_builtin): Call them.
* common.opt (fstrub=*): New options.
* doc/extend.texi (strub): New type attribute.
(__builtin_stack_address): New function.
(Stack Scrubbing): New section.
* doc/invoke.texi (-fstrub=*): New options.
(-fdump-ipa-*): New passes.
* gengtype-lex.l: Ignore multi-line pp-directives.
* ipa-inline.cc: Include ipa-strub.h.
(can_inline_edge_p): Test strub_inlinable_to_p.
* ipa-split.cc: Include ipa-strub.h.
(execute_split_functions): Test strub_splittable_p.
* ipa-strub.cc, ipa-strub.h: New.
* passes.def: Add strub_mode and strub passes.
* tree-cfg.cc (gimple_verify_flow_info): Note on debug stmts.
* tree-pass.h (make_pass_ipa_strub_mode): Declare.
(make_pass_ipa_strub): Declare.
(make_pass_ipa_function_and_variable_visibility): Fix
formatting.
* tree-ssa-ccp.cc (optimize_stack_restore): Keep restores
before strub leave.
* 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.
Diffstat (limited to 'gcc/testsuite')
93 files changed, 2719 insertions, 0 deletions
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 0000000..c7a79a6 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-O0.c @@ -0,0 +1,14 @@ +/* { dg-do compile } */ +/* { dg-options "-O0 -fstrub=strict -fdump-rtl-expand" } */ + +/* At -O0, none of the strub builtins are expanded inline. */ + +int __attribute__ ((__strub__)) var; + +int f() { + return var; +} + +/* { dg-final { scan-rtl-dump "strub_enter" "expand" } } */ +/* { dg-final { scan-rtl-dump "strub_update" "expand" } } */ +/* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-O1.c b/gcc/testsuite/c-c++-common/strub-O1.c new file mode 100644 index 0000000..96285c9 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-O1.c @@ -0,0 +1,15 @@ +/* { dg-do compile } */ +/* { dg-options "-O1 -fstrub=strict -fdump-rtl-expand" } */ + +/* At -O1, without -fno-inline, we fully expand enter, but neither update nor + leave. */ + +int __attribute__ ((__strub__)) var; + +int f() { + return var; +} + +/* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */ +/* { dg-final { scan-rtl-dump "strub_update" "expand" } } */ +/* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c new file mode 100644 index 0000000..8edc0d8 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-O2.c @@ -0,0 +1,16 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand" } */ + +/* At -O2, without -fno-inline, we fully expand enter and update, and add a test + around the leave call. */ + +int __attribute__ ((__strub__)) var; + +int f() { + return var; +} + +/* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */ +/* { dg-final { scan-rtl-dump-not "strub_update" "expand" } } */ +/* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */ +/* { dg-final { scan-rtl-dump "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c new file mode 100644 index 0000000..c6d900c --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-O2fni.c @@ -0,0 +1,15 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fstrub=strict -fdump-rtl-expand -fno-inline" } */ + +/* With -fno-inline, none of the strub builtins are inlined. */ + +int __attribute__ ((__strub__)) var; + +int f() { + return var; +} + +/* { dg-final { scan-rtl-dump "strub_enter" "expand" } } */ +/* { dg-final { scan-rtl-dump "strub_update" "expand" } } */ +/* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */ +/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-O3.c b/gcc/testsuite/c-c++-common/strub-O3.c new file mode 100644 index 0000000..33ee465 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-O3.c @@ -0,0 +1,12 @@ +/* { dg-do compile } */ +/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand" } */ + +int __attribute__ ((__strub__)) var; + +int f() { + return var; +} + +/* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */ +/* { dg-final { scan-rtl-dump-not "strub_update" "expand" } } */ +/* { dg-final { scan-rtl-dump-not "strub_leave" "expand" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c new file mode 100644 index 0000000..2936f82 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-O3fni.c @@ -0,0 +1,15 @@ +/* { dg-do compile } */ +/* { dg-options "-O3 -fstrub=strict -fdump-rtl-expand -fno-inline" } */ + +/* With -fno-inline, none of the strub builtins are inlined. */ + +int __attribute__ ((__strub__)) var; + +int f() { + return var; +} + +/* { dg-final { scan-rtl-dump "strub_enter" "expand" } } */ +/* { dg-final { scan-rtl-dump "strub_update" "expand" } } */ +/* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */ +/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c new file mode 100644 index 0000000..479746e --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-Og.c @@ -0,0 +1,16 @@ +/* { dg-do compile } */ +/* { dg-options "-Og -fstrub=strict -fdump-rtl-expand" } */ + +/* At -Og, without -fno-inline, we fully expand enter, but neither update nor + leave. */ + +int __attribute__ ((__strub__)) var; + +int f() { + return var; +} + +/* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */ +/* { dg-final { scan-rtl-dump "strub_update" "expand" } } */ +/* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */ +/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c new file mode 100644 index 0000000..2241d4e --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-Os.c @@ -0,0 +1,18 @@ +/* { dg-do compile } */ +/* { dg-options "-Os -fstrub=strict -fdump-rtl-expand" } */ + +/* At -Os, without -fno-inline, we fully expand enter, and also update. The + expanded update might be larger than a call proper, but argument saving and + restoring required by the call will most often make it larger. The leave + call is left untouched. */ + +int __attribute__ ((__strub__)) var; + +int f() { + return var; +} + +/* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */ +/* { dg-final { scan-rtl-dump-not "strub_update" "expand" } } */ +/* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */ +/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c new file mode 100644 index 0000000..a322bcc --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-all1.c @@ -0,0 +1,32 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=all -fdump-ipa-strubm -fdump-ipa-strub" } */ + +/* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the + strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub. */ +static inline void +__attribute__ ((__always_inline__)) +h() { +} + +/* g becomes STRUB_AT_CALLS, because of the flag. */ +static inline void +g() { + h(); +} + +/* f becomes STRUB_INTERNAL because of the flag, and gets split into + STRUB_WRAPPER and STRUB_WRAPPED. */ +void +f() { + g(); +} + +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */ + +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-all2.c b/gcc/testsuite/c-c++-common/strub-all2.c new file mode 100644 index 0000000..db60026 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-all2.c @@ -0,0 +1,24 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=all -fdump-ipa-strubm -fdump-ipa-strub" } */ + +/* g becomes STRUB_INTERNAL, because of the flag. Without inline, force_output + is set for static non-inline functions when not optimizing, and that keeps + only_called_directly_p from returning true, which makes STRUB_AT_CALLS + non-viable. */ +static void +g() { +} + +/* f becomes STRUB_INTERNAL because of the flag, and gets split into + STRUB_WRAPPER and STRUB_WRAPPED. */ +void +f() { + g(); +} + +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */ + +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c new file mode 100644 index 0000000..2f462ad --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-apply1.c @@ -0,0 +1,15 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict" } */ + +void __attribute__ ((__strub__ ("callable"))) +apply_function (void *args) +{ + __builtin_apply (0, args, 0); +} + +void __attribute__ ((__strub__ ("internal"))) +apply_args (int i, int j, double d) +{ + void *args = __builtin_apply_args (); + apply_function (args); +} diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c new file mode 100644 index 0000000..a5d7551 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-apply2.c @@ -0,0 +1,12 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict" } */ + +extern void __attribute__ ((__strub__)) +apply_function (void *args); + +void __attribute__ ((__strub__)) +apply_args (int i, int j, double d) /* { dg-error "selected" } */ +{ + void *args = __builtin_apply_args (); /* { dg-message "does not support" } */ + apply_function (args); +} diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c new file mode 100644 index 0000000..64422a0 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-apply3.c @@ -0,0 +1,8 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict" } */ + +void __attribute__ ((__strub__)) +apply_function (void *args) +{ + __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */ +} diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c new file mode 100644 index 0000000..15ffaa0 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-apply4.c @@ -0,0 +1,21 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fstrub=strict -fdump-ipa-strubm" } */ + +/* Check that implicit enabling of strub mode selects internal strub when the + function uses __builtin_apply_args, that prevents the optimization to + at-calls mode. */ + +int __attribute__ ((__strub__)) var; + +static inline void +apply_args (int i, int j, double d) +{ + var++; + __builtin_apply_args (); +} + +void f() { + apply_args (1, 2, 3); +} + +/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c new file mode 100644 index 0000000..b70843b --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c @@ -0,0 +1,30 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=at-calls -fdump-ipa-strubm -fdump-ipa-strub" } */ + +/* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the + strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub. */ +static inline void +__attribute__ ((__always_inline__)) +h() { +} + +/* g becomes STRUB_AT_CALLS, because of the flag. */ +static inline void +g() { + h(); +} + +/* f does NOT become STRUB_AT_CALLS because it is visible; it becomes + STRUB_CALLABLE. */ +void +f() { + g(); +} + +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strubm" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */ + +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls\[)\]" 1 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-at-calls2.c b/gcc/testsuite/c-c++-common/strub-at-calls2.c new file mode 100644 index 0000000..97a3988 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-at-calls2.c @@ -0,0 +1,23 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=at-calls -fdump-ipa-strubm -fdump-ipa-strub" } */ + +/* g does NOT become STRUB_AT_CALLS because it's not viable. Without inline, + force_output is set for static non-inline functions when not optimizing, and + that keeps only_called_directly_p from returning true, which makes + STRUB_AT_CALLS non-viable. It becomes STRUB_CALLABLE instead. */ +static void +g() { +} + +/* f does NOT become STRUB_AT_CALLS because it is visible; it becomes + STRUB_CALLABLE. */ +void +f() { + g(); +} + +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strubm" } } */ + +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 2 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c new file mode 100644 index 0000000..3d73431 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c @@ -0,0 +1,7 @@ +/* { dg-do run } */ +/* { dg-options "-fstrub=strict -O1" } */ + +/* Check that a strub function called by another strub function does NOT defer + the strubbing to its caller at -O1. */ + +#include "strub-defer-O2.c" diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c new file mode 100644 index 0000000..fddf3c7 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c @@ -0,0 +1,8 @@ +/* { dg-do run } */ +/* { dg-options "-fstrub=strict -O2" } */ + +/* Check that a strub function called by another strub function does NOT defer + the strubbing to its caller at -O2. */ + +#define EXPECT_DEFERRAL ! +#include "strub-defer-O3.c" diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c new file mode 100644 index 0000000..7ebc65b --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c @@ -0,0 +1,110 @@ +/* { dg-do run } */ +/* { dg-options "-fstrub=strict -O3" } */ + +/* Check that a strub function called by another strub function defers the + strubbing to its caller at -O3. */ + +#ifndef EXPECT_DEFERRAL +/* Other strub-defer*.c tests override this macro. */ +# define EXPECT_DEFERRAL +#endif + +const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa"; + +/* Pad before and after the string on the stack, so that it's not overwritten by + regular stack use. */ +#define PAD 7 + +static inline __attribute__ ((__always_inline__, __strub__ ("callable"))) +char * +leak_string (void) +{ + /* We use this variable to avoid any stack red zone. Stack scrubbing covers + it, but __builtin_stack_address, that we take as a reference, doesn't, so + if e.g. callable() were to store the string in the red zone, we wouldn't + find it because it would be outside the range we searched. */ + typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *); + callable_t *f = 0; + + char s[2 * PAD + 1][sizeof (test_string)]; + __builtin_strcpy (s[PAD], test_string); + asm ("" : "+m" (s), "+r" (f)); + + if (__builtin_expect (!f, 1)) + return (char*)__builtin_stack_address (); + + f (s[PAD]); + return 0; +} + +static inline __attribute__ ((__always_inline__, __strub__ ("callable"))) +int +look_for_string (char *e) +{ + char *p = (char*)__builtin_stack_address (); + + if (p == e) + __builtin_abort (); + + if (p > e) + { + char *q = p; + p = e; + e = q; + } + + for (char *re = e - sizeof (test_string); p < re; p++) + for (int i = 0; p[i] == test_string[i]; i++) + if (i == sizeof (test_string) - 1) + return i; + + return 0; +} + +static __attribute__ ((__strub__ ("at-calls"), __noinline__, __noclone__)) +char * +at_calls () +{ + return leak_string (); +} + +static __attribute__ ((__strub__ ("at-calls"))) +char * +deferred_at_calls () +{ + char *ret; + int i = 1; + /* Since these test check stack contents above the top of the stack, an + unexpected asynchronous signal or interrupt might overwrite the bits we + expect to find and cause spurious fails. Tolerate one such overall + spurious fail by retrying. */ + while (EXPECT_DEFERRAL !look_for_string ((ret = at_calls ()))) + if (!i--) __builtin_abort (); + return ret; +} + +static __attribute__ ((__strub__ ("internal"))) +char * +deferred_internal () +{ + int i = 1; + char *ret; + while (EXPECT_DEFERRAL !look_for_string ((ret = at_calls ()))) + if (!i--) __builtin_abort (); + return ret; +} + +int main () +{ + int i = 1; + /* These calls should not be subject to spurious fails: whether or not some + asynchronous event overwrites the scrubbed stack space, the string won't + remain there. Unless the asynchronous event happens to write the string + where we look for it, but what are the odds? Anyway, it doesn't hurt to + retry, even if just for symmetry. */ + while (look_for_string (deferred_at_calls ())) + if (!i--) __builtin_abort (); + while (look_for_string (deferred_internal ())) + if (!i--) __builtin_abort (); + __builtin_exit (0); +} diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c new file mode 100644 index 0000000..fbaf85f --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c @@ -0,0 +1,7 @@ +/* { dg-do run } */ +/* { dg-options "-fstrub=strict -Os" } */ + +/* Check that a strub function called by another strub function defers the + strubbing to its caller at -Os. */ + +#include "strub-defer-O3.c" diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c new file mode 100644 index 0000000..e9d7b7b --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-internal1.c @@ -0,0 +1,31 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=internal -fdump-ipa-strubm -fdump-ipa-strub" } */ + +/* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the + strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub. */ +static inline void +__attribute__ ((__always_inline__)) +h() { +} + +/* g becomes STRUB_INTERNAL because of the flag, and gets split into + STRUB_WRAPPER and STRUB_WRAPPED. */ +static inline void +g() { + h(); +} + +/* f becomes STRUB_INTERNAL because of the flag, and gets split into + STRUB_WRAPPER and STRUB_WRAPPED. */ +void +f() { + g(); +} + +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]callable\[)\]" 1 "strubm" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */ + +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-internal2.c b/gcc/testsuite/c-c++-common/strub-internal2.c new file mode 100644 index 0000000..8b8e15a --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-internal2.c @@ -0,0 +1,21 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=internal -fdump-ipa-strubm -fdump-ipa-strub" } */ + +/* g becomes STRUB_INTERNAL, because of the flag. */ +static void +g() { +} + +/* f becomes STRUB_INTERNAL because of the flag, and gets split into + STRUB_WRAPPER and STRUB_WRAPPED. */ +void +f() { + g(); +} + +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */ + +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c new file mode 100644 index 0000000..0a4a753 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-parms1.c @@ -0,0 +1,48 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +#include <stdarg.h> + +void __attribute__ ((__strub__ ("internal"))) +small_args (int i, long long l, void *p, void **q, double d, char c) +{ +} + +/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */ +/* { dg-final { scan-ipa-dump " \[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */ + + +struct large_arg { + int x[128]; +}; + +void __attribute__ ((__strub__ ("internal"))) +large_byref_arg (struct large_arg la) +{ +} + +/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]*.strub.\[0-9\]* \[(\]struct large_arg & la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */ +/* { dg-final { scan-ipa-dump " \[^ \]*large_byref_arg\[^ \]*.strub.\[0-9\]* \[(\]&\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */ + +void __attribute__ ((__strub__ ("internal"))) +std_arg (int i, ...) +{ + va_list vl; + va_start (vl, i); + va_end (vl); +} + +/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */ +/* { dg-final { scan-ipa-dump " \[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */ +/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */ + +void __attribute__ ((__strub__ ("internal"))) +apply_args (int i, int j, double d) +{ + __builtin_apply_args (); +} + +/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */ +/* { dg-final { scan-ipa-dump " \[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c new file mode 100644 index 0000000..147171d --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-parms2.c @@ -0,0 +1,36 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +#include <stdarg.h> + +void __attribute__ ((__strub__ ("at-calls"))) +small_args (int i, long long l, void *p, void **q, double d, char c) +{ +} + +/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*small_args\[^ \]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */ + + +struct large_arg { + int x[128]; +}; + +void __attribute__ ((__strub__ ("at-calls"))) +large_byref_arg (struct large_arg la) +{ +} + +/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]* \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */ + +void __attribute__ ((__strub__ ("at-calls"))) +std_arg (int i, ...) +{ + va_list vl; + va_start (vl, i); + va_end (vl); +} + +/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*std_arg\[^ \]* \[(\]int i, void \\* &\[^&,\]*.strub.watermark_ptr\[, .]*\[)\]" "strub" } } */ +/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */ +/* { dg-final { scan-ipa-dump-not "va_copy \\(" "strub" } } */ +/* { dg-final { scan-ipa-dump-times "va_end \\(" 1 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c new file mode 100644 index 0000000..4e92682 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-parms3.c @@ -0,0 +1,58 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +/* Check that uses of a strub variable implicitly enables internal strub for + publicly-visible functions, and causes the same transformations to their + signatures as those in strub-parms1.c. */ + +#include <stdarg.h> + +int __attribute__ ((__strub__)) var; + +void +small_args (int i, long long l, void *p, void **q, double d, char c) +{ + var++; +} + +/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */ +/* { dg-final { scan-ipa-dump " \[^ \]*small_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */ + + +struct large_arg { + int x[128]; +}; + +void +large_byref_arg (struct large_arg la) +{ + var++; +} + +/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*large_byref_arg\[^ \]*.strub.\[0-9\]* \[(\]struct large_arg & la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */ +/* { dg-final { scan-ipa-dump " \[^ \]*large_byref_arg\[^ \]*.strub.\[0-9\]* \[(\]&\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */ + +void +std_arg (int i, ...) +{ + va_list vl; + va_start (vl, i); + var++; + va_end (vl); +} + +/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */ +/* { dg-final { scan-ipa-dump " \[^ \]*std_arg\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */ +/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */ + +void +apply_args (int i, int j, double d) +{ + var++; + __builtin_apply_args (); +} + +/* { dg-final { scan-ipa-dump "\n(void )?\[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */ +/* { dg-final { scan-ipa-dump " \[^ \]*apply_args\[^ \]*.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-relaxed1.c b/gcc/testsuite/c-c++-common/strub-relaxed1.c new file mode 100644 index 0000000..e2f9d8a --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-relaxed1.c @@ -0,0 +1,18 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */ + +/* The difference between relaxed and strict in this case is that we accept the + call from one internal-strub function to another. Without the error, + inlining takes place. */ + +#include "strub-strict1.c" + +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */ + +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 1 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 1 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-relaxed2.c b/gcc/testsuite/c-c++-common/strub-relaxed2.c new file mode 100644 index 0000000..9847443 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-relaxed2.c @@ -0,0 +1,14 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=relaxed -fdump-ipa-strubm -fdump-ipa-strub" } */ + +/* The difference between relaxed and strict in this case is that we accept the + call from one internal-strub function to another. */ + +#include "strub-strict2.c" + +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */ + +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapped\[)\]" 2 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]wrapper\[)\]" 2 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-short-O0-exc.c b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c new file mode 100644 index 0000000..1de1534 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-short-O0-exc.c @@ -0,0 +1,10 @@ +/* { dg-do compile } */ +/* { dg-options "-O0 -fstrub=strict -fexceptions -fdump-ipa-strub" } */ + +/* Check that the expected strub calls are issued. */ + +#include "torture/strub-callable1.c" + +/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub_leave" 89 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-short-O0.c b/gcc/testsuite/c-c++-common/strub-short-O0.c new file mode 100644 index 0000000..f9209c8 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-short-O0.c @@ -0,0 +1,10 @@ +/* { dg-do compile } */ +/* { dg-options "-O0 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */ + +/* Check that the expected strub calls are issued. */ + +#include "torture/strub-callable1.c" + +/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-short-O1.c b/gcc/testsuite/c-c++-common/strub-short-O1.c new file mode 100644 index 0000000..bed1dcf --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-short-O1.c @@ -0,0 +1,10 @@ +/* { dg-do compile } */ +/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */ + +/* Check that the expected strub calls are issued. */ + +#include "torture/strub-callable1.c" + +/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-short-O2.c b/gcc/testsuite/c-c++-common/strub-short-O2.c new file mode 100644 index 0000000..6bf0071 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-short-O2.c @@ -0,0 +1,10 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */ + +/* Check that the expected strub calls are issued. */ + +#include "torture/strub-callable1.c" + +/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-short-O3.c b/gcc/testsuite/c-c++-common/strub-short-O3.c new file mode 100644 index 0000000..4732f51 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-short-O3.c @@ -0,0 +1,12 @@ +/* { dg-do compile } */ +/* { dg-options "-O3 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */ + +/* Check that the expected strub calls are issued. At -O3 and -Os, we omit + enter and leave calls within strub contexts, passing on the enclosing + watermark. */ + +#include "torture/strub-callable1.c" + +/* { dg-final { scan-ipa-dump-times "strub_enter" 15 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub_leave" 15 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-short-Os.c b/gcc/testsuite/c-c++-common/strub-short-Os.c new file mode 100644 index 0000000..8d6424c --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-short-Os.c @@ -0,0 +1,12 @@ +/* { dg-do compile } */ +/* { dg-options "-Os -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */ + +/* Check that the expected strub calls are issued. At -O3 and -Os, we omit + enter and leave calls within strub contexts, passing on the enclosing + watermark. */ + +#include "torture/strub-callable1.c" + +/* { dg-final { scan-ipa-dump-times "strub_enter" 15 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub_leave" 15 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-strict1.c b/gcc/testsuite/c-c++-common/strub-strict1.c new file mode 100644 index 0000000..3685224 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-strict1.c @@ -0,0 +1,36 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strubm" } */ + +static int __attribute__ ((__strub__)) var; + +/* h becomes STRUB_INLINABLE, because of the use of the strub variable, + and the always_inline flag. It would get inlined before pass_ipa_strub, if + it weren't for the error. */ +static inline void +__attribute__ ((__always_inline__)) +h() { + var++; +} + +/* g becomes STRUB_AT_CALLS_OPT, because of the use of the strub variable, and + the viability of at-calls strubbing. Though internally a strub context, its + interface is not strub-enabled, so it's not callable from within strub + contexts. */ +static inline void +g() { + var--; + h(); +} + +/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets + split into STRUB_WRAPPER and STRUB_WRAPPED. */ +void +f() { + var++; + g(); /* { dg-error "calling non-.strub." } */ +} + +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 3 "strubm" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]inlinable\[)\]" 1 "strubm" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]at-calls-opt\[)\]" 1 "strubm" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 1 "strubm" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-strict2.c b/gcc/testsuite/c-c++-common/strub-strict2.c new file mode 100644 index 0000000..b4f2888 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-strict2.c @@ -0,0 +1,25 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strubm" } */ + +static int __attribute__ ((__strub__)) var; + +/* g becomes STRUB_INTERNAL because of the use of the strub variable, and gets + split into STRUB_WRAPPER and STRUB_WRAPPED. It's not STRUB_AT_CALLS_OPT + because force_output is set for static non-inline functions when not + optimizing, and that keeps only_called_directly_p from returning true, which + makes STRUB_AT_CALLS[_OPT] non-viable. */ +static void +g() { + var--; +} + +/* f becomes STRUB_INTERNAL because of the use of the strub variable, and gets + split into STRUB_WRAPPER and STRUB_WRAPPED. */ +void +f() { + var++; + g(); /* { dg-error "calling non-.strub." } */ +} + +/* { dg-final { scan-ipa-dump-times "strub \[(\]" 2 "strubm" } } */ +/* { dg-final { scan-ipa-dump-times "strub \[(\]internal\[)\]" 2 "strubm" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-tail-O1.c b/gcc/testsuite/c-c++-common/strub-tail-O1.c new file mode 100644 index 0000000..e48e061 --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-tail-O1.c @@ -0,0 +1,8 @@ +/* { dg-do compile } */ +/* { dg-options "-O1 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */ + +#include "strub-tail-O2.c" + +/* { dg-final { scan-ipa-dump-times "strub_enter" 2 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub_update" 2 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub_leave" 2 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-tail-O2.c b/gcc/testsuite/c-c++-common/strub-tail-O2.c new file mode 100644 index 0000000..87cda7a --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-tail-O2.c @@ -0,0 +1,14 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fstrub=strict -fno-exceptions -fdump-ipa-strub" } */ + +/* Check that the expected strub calls are issued. + Tail calls are short-circuited at -O2+. */ + +int __attribute__ ((__strub__)) +g (int i, int j) { + return g (i, j); +} + +/* { dg-final { scan-ipa-dump-times "strub_enter" 0 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub_update" 2 "strub" } } */ +/* { dg-final { scan-ipa-dump-times "strub_leave" 0 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/strub-var1.c b/gcc/testsuite/c-c++-common/strub-var1.c new file mode 100644 index 0000000..eb6250f --- /dev/null +++ b/gcc/testsuite/c-c++-common/strub-var1.c @@ -0,0 +1,24 @@ +/* { dg-do compile } */ + +int __attribute__ ((strub)) x; +float __attribute__ ((strub)) f; +double __attribute__ ((strub)) d; + +/* The attribute applies to the type of the declaration, i.e., to the pointer + variable p, not to the pointed-to integer. */ +int __attribute__ ((strub)) * +p = &x; /* { dg-message "incompatible|invalid conversion" } */ + +typedef int __attribute__ ((strub)) strub_int; +strub_int *q = &x; /* Now this is compatible. */ + +int __attribute__ ((strub)) +a[2]; /* { dg-warning "does not apply to elements" } */ + +int __attribute__ ((vector_size (4 * sizeof (int)))) + __attribute__ ((strub)) +v; /* { dg-warning "does not apply to elements" } */ + +struct s { + int i, j; +} __attribute__ ((strub)) w; /* { dg-warning "does not apply to fields" } */ diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c new file mode 100644 index 0000000..b5e45ab --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c @@ -0,0 +1,9 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict" } */ + +/* Check that strub and non-strub functions can be called from non-strub + contexts, and that strub and callable functions can be called from strub + contexts. */ + +#define OMIT_IMPERMISSIBLE_CALLS 1 +#include "strub-callable2.c" diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c new file mode 100644 index 0000000..96aa7fe --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c @@ -0,0 +1,264 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict" } */ + +/* Check that impermissible (cross-strub-context) calls are reported. */ + +extern int __attribute__ ((__strub__ ("callable"))) xcallable (void); +extern int __attribute__ ((__strub__ ("internal"))) xinternal (void); +extern int __attribute__ ((__strub__ ("at-calls"))) xat_calls (void); +extern int __attribute__ ((__strub__ ("disabled"))) xdisabled (void); + +int __attribute__ ((__strub__ ("callable"))) callable (void); +int __attribute__ ((__strub__ ("internal"))) internal (void); +int __attribute__ ((__strub__ ("at-calls"))) at_calls (void); +int __attribute__ ((__strub__ ("disabled"))) disabled (void); + +int __attribute__ ((__strub__)) var; +int var_user (void); + +static inline int __attribute__ ((__always_inline__, __strub__ ("callable"))) +icallable (void); +static inline int __attribute__ ((__always_inline__, __strub__ ("internal"))) +iinternal (void); +static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls"))) +iat_calls (void); +static inline int __attribute__ ((__always_inline__, __strub__ ("disabled"))) +idisabled (void); +static inline int __attribute__ ((__always_inline__)) +ivar_user (void); + +static inline int __attribute__ ((__always_inline__, __strub__ ("callable"))) +i_callable (void) { return 0; } +static inline int __attribute__ ((__always_inline__, __strub__ ("internal"))) +i_internal (void) { return var; } +static inline int __attribute__ ((__always_inline__, __strub__ ("at-calls"))) +i_at_calls (void) { return var; } +static inline int __attribute__ ((__always_inline__, __strub__ ("disabled"))) +i_disabled (void) { return 0; } +static inline int __attribute__ ((__always_inline__)) +i_var_user (void) { return var; } + +#define CALLS_GOOD_FOR_STRUB_CONTEXT(ISEP) \ + do { \ + ret += i ## ISEP ## at_calls (); \ + ret += i ## ISEP ## internal (); \ + ret += i ## ISEP ## var_user (); \ + } while (0) + +#define CALLS_GOOD_FOR_NONSTRUB_CONTEXT(ISEP) \ + do { \ + ret += internal (); \ + ret += disabled (); \ + ret += var_user (); \ + \ + ret += i ## ISEP ## disabled (); \ + \ + ret += xinternal (); \ + ret += xdisabled (); \ + } while (0) + +#define CALLS_GOOD_FOR_EITHER_CONTEXT(ISEP) \ + do { \ + ret += i ## ISEP ## callable (); \ + \ + ret += callable (); \ + ret += at_calls (); \ + \ + ret += xat_calls (); \ + ret += xcallable (); \ + } while (0) + +/* Not a strub context, so it can call anything. + Explicitly declared as callable even from within strub contexts. */ +int __attribute__ ((__strub__ ("callable"))) +callable (void) { + int ret = 0; + + /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */ +#if !OMIT_IMPERMISSIBLE_CALLS + ret += iat_calls (); /* { dg-error "in non-.strub. context" } */ + ret += iinternal (); /* { dg-error "in non-.strub. context" } */ + ret += ivar_user (); /* { dg-error "in non-.strub. context" } */ +#endif + CALLS_GOOD_FOR_EITHER_CONTEXT(); + CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); + + return ret; +} + +/* Internal strubbing means the body is a strub context, so it can only call + strub functions, and it's not itself callable from strub functions. */ +int __attribute__ ((__strub__ ("internal"))) +internal (void) { + int ret = var; + + CALLS_GOOD_FOR_STRUB_CONTEXT(); + CALLS_GOOD_FOR_EITHER_CONTEXT(); + /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */ +#if !OMIT_IMPERMISSIBLE_CALLS + ret += internal (); /* { dg-error "in .strub. context" } */ + ret += disabled (); /* { dg-error "in .strub. context" } */ + ret += var_user (); /* { dg-error "in .strub. context" } */ + + ret += idisabled (); /* { dg-error "in .strub. context" } */ + + ret += xinternal (); /* { dg-error "in .strub. context" } */ + ret += xdisabled (); /* { dg-error "in .strub. context" } */ +#endif + + return ret; +} + +int __attribute__ ((__strub__ ("at-calls"))) +at_calls (void) { + int ret = var; + + CALLS_GOOD_FOR_STRUB_CONTEXT(); + CALLS_GOOD_FOR_EITHER_CONTEXT(); + /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */ +#if !OMIT_IMPERMISSIBLE_CALLS + ret += internal (); /* { dg-error "in .strub. context" } */ + ret += disabled (); /* { dg-error "in .strub. context" } */ + ret += var_user (); /* { dg-error "in .strub. context" } */ + + ret += idisabled (); /* { dg-error "in .strub. context" } */ + + ret += xinternal (); /* { dg-error "in .strub. context" } */ + ret += xdisabled (); /* { dg-error "in .strub. context" } */ +#endif + + return ret; +} + +int __attribute__ ((__strub__ ("disabled"))) +disabled () { + int ret = 0; + + /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */ +#if !OMIT_IMPERMISSIBLE_CALLS + ret += iat_calls (); /* { dg-error "in non-.strub. context" } */ + ret += iinternal (); /* { dg-error "in non-.strub. context" } */ + ret += ivar_user (); /* { dg-error "in non-.strub. context" } */ +#endif + CALLS_GOOD_FOR_EITHER_CONTEXT(); + CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); + + return ret; +} + +int +var_user (void) { + int ret = var; + + CALLS_GOOD_FOR_STRUB_CONTEXT(); + CALLS_GOOD_FOR_EITHER_CONTEXT(); + /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */ +#if !OMIT_IMPERMISSIBLE_CALLS + ret += internal (); /* { dg-error "in .strub. context" } */ + ret += disabled (); /* { dg-error "in .strub. context" } */ + ret += var_user (); /* { dg-error "in .strub. context" } */ + + ret += idisabled (); /* { dg-error "in .strub. context" } */ + + ret += xinternal (); /* { dg-error "in .strub. context" } */ + ret += xdisabled (); /* { dg-error "in .strub. context" } */ +#endif + + return ret; +} + +int +icallable (void) +{ + int ret = 0; + + /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */ +#if !OMIT_IMPERMISSIBLE_CALLS + ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */ + ret += i_internal (); /* { dg-error "in non-.strub. context" } */ + ret += i_var_user (); /* { dg-error "in non-.strub. context" } */ +#endif + CALLS_GOOD_FOR_EITHER_CONTEXT(_); + CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); + + return ret; +} + +int +iinternal (void) { + int ret = var; + + CALLS_GOOD_FOR_STRUB_CONTEXT(_); + CALLS_GOOD_FOR_EITHER_CONTEXT(_); + /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */ +#if !OMIT_IMPERMISSIBLE_CALLS + ret += internal (); /* { dg-error "in .strub. context" } */ + ret += disabled (); /* { dg-error "in .strub. context" } */ + ret += var_user (); /* { dg-error "in .strub. context" } */ + + ret += i_disabled (); /* { dg-error "in .strub. context" } */ + + ret += xinternal (); /* { dg-error "in .strub. context" } */ + ret += xdisabled (); /* { dg-error "in .strub. context" } */ +#endif + + return ret; +} + +int __attribute__ ((__always_inline__, __strub__ ("at-calls"))) +iat_calls (void) { + int ret = var; + + CALLS_GOOD_FOR_STRUB_CONTEXT(_); + CALLS_GOOD_FOR_EITHER_CONTEXT(_); + /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */ +#if !OMIT_IMPERMISSIBLE_CALLS + ret += internal (); /* { dg-error "in .strub. context" } */ + ret += disabled (); /* { dg-error "in .strub. context" } */ + ret += var_user (); /* { dg-error "in .strub. context" } */ + + ret += i_disabled (); /* { dg-error "in .strub. context" } */ + + ret += xinternal (); /* { dg-error "in .strub. context" } */ + ret += xdisabled (); /* { dg-error "in .strub. context" } */ +#endif + + return ret; +} + +int +idisabled () { + int ret = 0; + + /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */ +#if !OMIT_IMPERMISSIBLE_CALLS + ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */ + ret += i_internal (); /* { dg-error "in non-.strub. context" } */ + ret += i_var_user (); /* { dg-error "in non-.strub. context" } */ +#endif + CALLS_GOOD_FOR_EITHER_CONTEXT(_); + CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); + + return ret; +} + +int +ivar_user (void) { + int ret = var; + + CALLS_GOOD_FOR_STRUB_CONTEXT(_); + CALLS_GOOD_FOR_EITHER_CONTEXT(_); + /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */ +#if !OMIT_IMPERMISSIBLE_CALLS + ret += internal (); /* { dg-error "in .strub. context" } */ + ret += disabled (); /* { dg-error "in .strub. context" } */ + ret += var_user (); /* { dg-error "in .strub. context" } */ + + ret += i_disabled (); /* { dg-error "in .strub. context" } */ + + ret += xinternal (); /* { dg-error "in .strub. context" } */ + ret += xdisabled (); /* { dg-error "in .strub. context" } */ +#endif + + return ret; +} diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c new file mode 100644 index 0000000..5e956cb --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c @@ -0,0 +1,23 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +/* Check that, along with a strub const function call, we issue an asm + statement to make sure the watermark passed to it is held in memory before + the call, and another to make sure it is not assumed to be unchanged. f + should not be inlined into g, but if it were too simple it might be folded + by interprocedural value-range propagation. */ + +extern int __attribute__ ((__strub__ ("callable"), + __const__, __nothrow__)) c (); + +int __attribute__ ((__strub__, __const__)) +f () { + return c (); +} + +int +g () { + return f (); +} + +/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c new file mode 100644 index 0000000..73d6502 --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c @@ -0,0 +1,25 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +/* Check that, along with a strub implicitly-const function call, we issue an + asm statement to make sure the watermark passed to it is held in memory + before the call, and another to make sure it is not assumed to be + unchanged. */ + +extern int __attribute__ ((__strub__ ("callable"), + __const__, __nothrow__)) c (); + +int __attribute__ ((__strub__)) +#if ! __OPTIMIZE__ +__attribute__ ((__const__)) +#endif +f () { + return c (); +} + +int +g () { + return f (); +} + +/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c new file mode 100644 index 0000000..2584f1f --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c @@ -0,0 +1,16 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +/* Check that, along with a strub const wrapping call, we issue an asm statement + to make sure the watermark passed to it is held in memory before the call, + and another to make sure it is not assumed to be unchanged. */ + +extern int __attribute__ ((__strub__ ("callable"), + __const__, __nothrow__)) c (); + +int __attribute__ ((__strub__ ("internal"), __const__)) +f () { + return c (); +} + +/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c new file mode 100644 index 0000000..d819f54 --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c @@ -0,0 +1,20 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +/* Check that, along with a strub implicitly-const wrapping call, we issue an + asm statement to make sure the watermark passed to it is held in memory + before the call, and another to make sure it is not assumed to be + unchanged. */ + +extern int __attribute__ ((__strub__ ("callable"), + __const__, __nothrow__)) c (); + +int __attribute__ ((__strub__ ("internal"))) +#if ! __OPTIMIZE__ +__attribute__ ((__const__)) +#endif +f () { + return c (); +} + +/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/torture/strub-data1.c b/gcc/testsuite/c-c++-common/torture/strub-data1.c new file mode 100644 index 0000000..7c27a2a --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-data1.c @@ -0,0 +1,13 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +/* The pointed-to data enables strubbing if accessed. */ +int __attribute__ ((__strub__)) var; + +int f() { + return var; +} + +/* { dg-final { scan-ipa-dump "strub_enter" "strub" } } */ +/* { dg-final { scan-ipa-dump "strub_leave" "strub" } } */ +/* { dg-final { scan-ipa-dump "strub_update" "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/torture/strub-data2.c b/gcc/testsuite/c-c++-common/torture/strub-data2.c new file mode 100644 index 0000000..e66d903 --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-data2.c @@ -0,0 +1,14 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +/* The pointer itself is a strub variable, enabling internal strubbing when + its value is used. */ +int __attribute__ ((__strub__)) *ptr; + +int *f() { + return ptr; +} + +/* { dg-final { scan-ipa-dump "strub_enter" "strub" } } */ +/* { dg-final { scan-ipa-dump "strub_leave" "strub" } } */ +/* { dg-final { scan-ipa-dump "strub_update" "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/torture/strub-data3.c b/gcc/testsuite/c-c++-common/torture/strub-data3.c new file mode 100644 index 0000000..5e08e0e --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-data3.c @@ -0,0 +1,14 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +/* The pointer itself is a strub variable, that would enable internal strubbing + if its value was used. Here, it's only overwritten, so no strub. */ +int __attribute__ ((__strub__)) var; + +void f() { + var = 0; +} + +/* { dg-final { scan-ipa-dump-not "strub_enter" "strub" } } */ +/* { dg-final { scan-ipa-dump-not "strub_leave" "strub" } } */ +/* { dg-final { scan-ipa-dump-not "strub_update" "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/torture/strub-data4.c b/gcc/testsuite/c-c++-common/torture/strub-data4.c new file mode 100644 index 0000000..a818e7a --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-data4.c @@ -0,0 +1,14 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +/* The pointer itself is a strub variable, that would enable internal strubbing + if its value was used. Here, it's only overwritten, so no strub. */ +int __attribute__ ((__strub__)) *ptr; + +void f() { + ptr = 0; +} + +/* { dg-final { scan-ipa-dump-not "strub_enter" "strub" } } */ +/* { dg-final { scan-ipa-dump-not "strub_leave" "strub" } } */ +/* { dg-final { scan-ipa-dump-not "strub_update" "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c new file mode 100644 index 0000000..ddb0b5c --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c @@ -0,0 +1,15 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict" } */ + +/* It would be desirable to issue at least warnings for these. */ + +typedef int __attribute__ ((__strub__)) strub_int; +strub_int *ptr; + +int *f () { + return ptr; /* { dg-message "incompatible|invalid conversion" } */ +} + +strub_int *g () { + return f (); /* { dg-message "incompatible|invalid conversion" } */ +} diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall1.c b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c new file mode 100644 index 0000000..c165f31 --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-indcall1.c @@ -0,0 +1,14 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +typedef void __attribute__ ((__strub__)) fntype (); +fntype (*ptr); + +void f() { + ptr (); +} + +/* { dg-final { scan-ipa-dump "strub_enter" "strub" } } */ +/* { dg-final { scan-ipa-dump "(&\.strub\.watermark\.\[0-9\]\+)" "strub" } } */ +/* { dg-final { scan-ipa-dump "strub_leave" "strub" } } */ +/* { dg-final { scan-ipa-dump-not "strub_update" "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall2.c b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c new file mode 100644 index 0000000..69fcff8 --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-indcall2.c @@ -0,0 +1,14 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +typedef void __attribute__ ((__strub__)) fntype (int, int); +fntype (*ptr); + +void f() { + ptr (0, 0); +} + +/* { dg-final { scan-ipa-dump "strub_enter" "strub" } } */ +/* { dg-final { scan-ipa-dump "(0, 0, &\.strub\.watermark\.\[0-9\]\+)" "strub" } } */ +/* { dg-final { scan-ipa-dump "strub_leave" "strub" } } */ +/* { dg-final { scan-ipa-dump-not "strub_update" "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/torture/strub-indcall3.c b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c new file mode 100644 index 0000000..ff00622 --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-indcall3.c @@ -0,0 +1,14 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +typedef void __attribute__ ((__strub__)) fntype (int, int, ...); +fntype (*ptr); + +void f() { + ptr (0, 0, 1, 1); +} + +/* { dg-final { scan-ipa-dump "strub_enter" "strub" } } */ +/* { dg-final { scan-ipa-dump "(0, 0, &\.strub\.watermark\.\[0-9\]\+, 1, 1)" "strub" } } */ +/* { dg-final { scan-ipa-dump "strub_leave" "strub" } } */ +/* { dg-final { scan-ipa-dump-not "strub_update" "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c new file mode 100644 index 0000000..614b022 --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable1.c @@ -0,0 +1,16 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=relaxed" } */ + +inline void __attribute__ ((strub ("internal"), always_inline)) +inl_int_ali (void) +{ + /* No internal wrapper, so this body ALWAYS gets inlined, + but it cannot be called from non-strub contexts. */ +} + +void +bat (void) +{ + /* Not allowed, not a strub context. */ + inl_int_ali (); /* { dg-error "context" } */ +} diff --git a/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c new file mode 100644 index 0000000..f9a6b4a --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-inlinable2.c @@ -0,0 +1,7 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=all" } */ + +#include "strub-inlinable1.c" + +/* With -fstrub=all, the caller becomes a strub context, so the strub-inlinable + callee is not rejected. */ diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c new file mode 100644 index 0000000..b4a7f39 --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn1.c @@ -0,0 +1,10 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict" } */ + +typedef void ft (void); +typedef void ft2 (int, int); +extern ft __attribute__ ((__strub__)) fnac; + +ft * f (void) { + return fnac; /* { dg-message "incompatible|invalid conversion" } */ +} diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c new file mode 100644 index 0000000..ef634d3 --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn2.c @@ -0,0 +1,55 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=relaxed -Wpedantic" } */ + +/* C++ does not warn about the partial incompatibilities. + + The d_p () calls are actually rejected, even in C++, but they are XFAILed + here because we don't get far enough in the compilation as to observe them, + because the incompatibilities are errors without -fpermissive. + strub-ptrfn3.c uses -fpermissive to check those. + */ + +extern int __attribute__ ((strub ("callable"))) bac (void); +extern int __attribute__ ((strub ("disabled"))) bad (void); +extern int __attribute__ ((strub ("internal"))) bar (void); +extern int __attribute__ ((strub ("at-calls"))) bal (void); + +void __attribute__ ((strub)) +bap (void) +{ + int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad; + int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac; + int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal; + + d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */ + c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */ + c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */ + c_p = bal; /* { dg-message "incompatible|invalid conversion" } */ + a_p = bac; /* { dg-message "incompatible|invalid conversion" } */ + + d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail *-*-* } } */ + c_p (); + a_p (); +} + +void __attribute__ ((strub)) +baP (void) +{ + typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void); + typedef int __attribute__ ((strub ("callable"))) c_fn_t (void); + typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void); + + d_fn_t *d_p = bad; + c_fn_t *c_p = bac; + a_fn_t *a_p = bal; + + d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */ + c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */ + c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */ + c_p = bal; /* { dg-message "incompatible|invalid conversion" } */ + a_p = bac; /* { dg-message "incompatible|invalid conversion" } */ + + d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" "" { xfail *-*-* } } */ + c_p (); + a_p (); +} diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c new file mode 100644 index 0000000..e1f179e --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn3.c @@ -0,0 +1,50 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=relaxed -Wpedantic -fpermissive" } */ +/* { dg-prune-output "command-line option .-fpermissive." } */ + +/* See strub-ptrfn2.c. */ + +extern int __attribute__ ((strub ("callable"))) bac (void); +extern int __attribute__ ((strub ("disabled"))) bad (void); +extern int __attribute__ ((strub ("internal"))) bar (void); +extern int __attribute__ ((strub ("at-calls"))) bal (void); + +void __attribute__ ((strub)) +bap (void) +{ + int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad; + int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac; + int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal; + + d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */ + c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */ + c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */ + c_p = bal; /* { dg-message "incompatible|invalid conversion" } */ + a_p = bac; /* { dg-message "incompatible|invalid conversion" } */ + + d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */ + c_p (); + a_p (); +} + +void __attribute__ ((strub)) +baP (void) +{ + typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void); + typedef int __attribute__ ((strub ("callable"))) c_fn_t (void); + typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void); + + d_fn_t *d_p = bad; + c_fn_t *c_p = bac; + a_fn_t *a_p = bal; + + d_p = bac; /* { dg-warning "not quite compatible" "" { xfail c++ } } */ + c_p = bad; /* { dg-warning "not quite compatible" "" { xfail c++ } } */ + c_p = bar; /* { dg-warning "not quite compatible" "" { xfail c++ } } */ + c_p = bal; /* { dg-message "incompatible|invalid conversion" } */ + a_p = bac; /* { dg-message "incompatible|invalid conversion" } */ + + d_p (); /* { dg-error "indirect non-.strub. call in .strub. context" } */ + c_p (); + a_p (); +} diff --git a/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c new file mode 100644 index 0000000..70b558a --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-ptrfn4.c @@ -0,0 +1,43 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=relaxed" } */ + +/* This is strub-ptrfn2.c without -Wpedantic. + + Even C doesn't report the (not-quite-)compatible conversions without it. */ + +extern int __attribute__ ((strub ("callable"))) bac (void); +extern int __attribute__ ((strub ("disabled"))) bad (void); +extern int __attribute__ ((strub ("internal"))) bar (void); +extern int __attribute__ ((strub ("at-calls"))) bal (void); + +void __attribute__ ((strub)) +bap (void) +{ + int __attribute__ ((strub ("disabled"))) (*d_p) (void) = bad; + int __attribute__ ((strub ("callable"))) (*c_p) (void) = bac; + int __attribute__ ((strub ("at-calls"))) (*a_p) (void) = bal; + + d_p = bac; + c_p = bad; + c_p = bar; + c_p = bal; /* { dg-message "incompatible|invalid conversion" } */ + a_p = bac; /* { dg-message "incompatible|invalid conversion" } */ +} + +void __attribute__ ((strub)) +baP (void) +{ + typedef int __attribute__ ((strub ("disabled"))) d_fn_t (void); + typedef int __attribute__ ((strub ("callable"))) c_fn_t (void); + typedef int __attribute__ ((strub ("at-calls"))) a_fn_t (void); + + d_fn_t *d_p = bad; + c_fn_t *c_p = bac; + a_fn_t *a_p = bal; + + d_p = bac; + c_p = bad; + c_p = bar; + c_p = bal; /* { dg-message "incompatible|invalid conversion" } */ + a_p = bac; /* { dg-message "incompatible|invalid conversion" } */ +} diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c new file mode 100644 index 0000000..a262a08 --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c @@ -0,0 +1,18 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +/* Check that, along with a strub pure function call, we issue an asm statement + to make sure the watermark passed to it is not assumed to be unchanged. */ + +int __attribute__ ((__strub__, __pure__)) +f() { + static int i; /* Stop it from being detected as const. */ + return i; +} + +int +g() { + return f(); +} + +/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c new file mode 100644 index 0000000..4c4bd50 --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c @@ -0,0 +1,22 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +/* Check that, along with a strub implicitly-pure function call, we issue an asm + statement to make sure the watermark passed to it is not assumed to be + unchanged. */ + +int __attribute__ ((__strub__)) +#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run. */ +__attribute__ ((__pure__)) +#endif +f() { + static int i; /* Stop it from being detected as const. */ + return i; +} + +int +g() { + return f(); +} + +/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c new file mode 100644 index 0000000..ce195c6 --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c @@ -0,0 +1,13 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +/* Check that, along with a strub pure wrapping call, we issue an asm statement + to make sure the watermark passed to it is not assumed to be unchanged. */ + +int __attribute__ ((__strub__ ("internal"), __pure__)) +f() { + static int i; /* Stop it from being detected as const. */ + return i; +} + +/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c new file mode 100644 index 0000000..75cd54c --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c @@ -0,0 +1,17 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +/* Check that, along with a strub implicitly-pure wrapping call, we issue an asm + statement to make sure the watermark passed to it is not assumed to be + unchanged. */ + +int __attribute__ ((__strub__ ("internal"))) +#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run. */ +__attribute__ ((__pure__)) +#endif +f() { + static int i; /* Stop it from being detected as const. */ + return i; +} + +/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */ diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c new file mode 100644 index 0000000..7458b3f --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c @@ -0,0 +1,95 @@ +/* { dg-do run } */ +/* { dg-options "-fstrub=strict" } */ + +/* Check that a non-strub function leaves a string behind in the stack, and that + equivalent strub functions don't. Avoid the use of red zones by avoiding + leaf functions. */ + +const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa"; + +/* Pad before and after the string on the stack, so that it's not overwritten by + regular stack use. */ +#define PAD 7 + +static inline __attribute__ ((__always_inline__, __strub__ ("callable"))) +char * +leak_string (void) +{ + /* We use this variable to avoid any stack red zone. Stack scrubbing covers + it, but __builtin_stack_address, that we take as a reference, doesn't, so + if e.g. callable() were to store the string in the red zone, we wouldn't + find it because it would be outside the range we searched. */ + typedef void __attribute__ ((__strub__ ("callable"))) callable_t (char *); + callable_t *f = 0; + + char s[2 * PAD + 1][sizeof (test_string)]; + __builtin_strcpy (s[PAD], test_string); + asm ("" : "+m" (s), "+r" (f)); + + if (__builtin_expect (!f, 1)) + return (char *) __builtin_stack_address (); + + f (s[PAD]); + return 0; +} + +static inline __attribute__ ((__always_inline__)) +int +look_for_string (char *e) +{ + char *p = (char *) __builtin_stack_address (); + + if (p == e) + __builtin_abort (); + + if (p > e) + { + char *q = p; + p = e; + e = q; + } + + for (char *re = e - sizeof (test_string); p < re; p++) + for (int i = 0; p[i] == test_string[i]; i++) + if (i == sizeof (test_string) - 1) + return i; + + return 0; +} + +static __attribute__ ((__noinline__, __noclone__)) +char * +callable () +{ + return leak_string (); +} + +static __attribute__ ((__strub__ ("at-calls"))) +char * +at_calls () +{ + return leak_string (); +} + +static __attribute__ ((__strub__ ("internal"))) +char * +internal () +{ + return leak_string (); +} + +int main () +{ + /* Since these test check stack contents above the top of the stack, an + unexpected asynchronous signal or interrupt might overwrite the bits we + expect to find and cause spurious fails. Tolerate one such overall + spurious fail by retrying. */ + int i = 1; + while (!look_for_string (callable ())) + if (!i--) __builtin_abort (); + while (look_for_string (at_calls ())) + if (!i--) __builtin_abort (); + while (look_for_string (internal ())) + if (!i--) __builtin_abort (); + __builtin_exit (0); +} diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c new file mode 100644 index 0000000..5d60a777 --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c @@ -0,0 +1,84 @@ +/* { dg-do run } */ +/* { dg-options "-fstrub=strict" } */ + +/* Check that a non-strub function leaves a string behind in the stack, and that + equivalent strub functions don't. Allow red zones to be used. */ + +const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa"; + +/* Pad before and after the string on the stack, so that it's not overwritten by + regular stack use. */ +#define PAD 7 + +static inline __attribute__ ((__always_inline__, __strub__ ("callable"))) +char * +leak_string (void) +{ + int len = sizeof (test_string); + asm ("" : "+rm" (len)); + char s[2 * PAD + 1][len]; + __builtin_strcpy (s[PAD], test_string); + asm ("" : "+m" (s)); + return (char *) __builtin_stack_address (); +} + +static inline __attribute__ ((__always_inline__)) +int +look_for_string (char *e) +{ + char *p = (char *) __builtin_stack_address (); + + if (p == e) + __builtin_abort (); + + if (p > e) + { + char *q = p; + p = e; + e = q; + } + + for (char *re = e - sizeof (test_string); p < re; p++) + for (int i = 0; p[i] == test_string[i]; i++) + if (i == sizeof (test_string) - 1) + return i; + + return 0; +} + +static __attribute__ ((__noinline__, __noclone__)) +char * +callable () +{ + return leak_string (); +} + +static __attribute__ ((__strub__ ("at-calls"))) +char * +at_calls () +{ + return leak_string (); +} + +static __attribute__ ((__strub__ ("internal"))) +char * +internal () +{ + return leak_string (); +} + +int main () +{ + /* Since these test check stack contents above the top of the stack, an + unexpected asynchronous signal or interrupt might overwrite the bits we + expect to find and cause spurious fails. Tolerate one such overall + spurious fail by retrying. */ + int i = 1; + while (!look_for_string (callable ())) + if (!i--) __builtin_abort (); + while (look_for_string (at_calls ())) + if (!i--) __builtin_abort (); + while (look_for_string (internal ())) + if (!i--) __builtin_abort (); + __builtin_exit (0); +} diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c new file mode 100644 index 0000000..c2ad710 --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c @@ -0,0 +1,80 @@ +/* { dg-do run } */ +/* { dg-options "-fstrub=strict" } */ +/* { dg-require-effective-target alloca } */ + +/* Check that a non-strub function leaves a string behind in the stack, and that + equivalent strub functions don't. */ + +const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa"; + +static inline __attribute__ ((__always_inline__, __strub__ ("callable"))) +char * +leak_string (void) +{ + int len = sizeof (test_string); + char *s = (char *) __builtin_alloca (len); + __builtin_strcpy (s, test_string); + asm ("" : "+m" (s)); + return (char *) __builtin_stack_address (); +} + +static inline __attribute__ ((__always_inline__)) +int +look_for_string (char *e) +{ + char *p = (char *) __builtin_stack_address (); + + if (p == e) + __builtin_abort (); + + if (p > e) + { + char *q = p; + p = e; + e = q; + } + + for (char *re = e - sizeof (test_string); p < re; p++) + for (int i = 0; p[i] == test_string[i]; i++) + if (i == sizeof (test_string) - 1) + return i; + + return 0; +} + +static __attribute__ ((__noinline__, __noclone__)) +char * +callable () +{ + return leak_string (); +} + +static __attribute__ ((__strub__ ("at-calls"))) +char * +at_calls () +{ + return leak_string (); +} + +static __attribute__ ((__strub__ ("internal"))) +char * +internal () +{ + return leak_string (); +} + +int main () +{ + /* Since these test check stack contents above the top of the stack, an + unexpected asynchronous signal or interrupt might overwrite the bits we + expect to find and cause spurious fails. Tolerate one such overall + spurious fail by retrying. */ + int i = 1; + while (!look_for_string (callable ())) + if (!i--) __builtin_abort (); + while (look_for_string (at_calls ())) + if (!i--) __builtin_abort (); + while (look_for_string (internal ())) + if (!i--) __builtin_abort (); + __builtin_exit (0); +} diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4.c b/gcc/testsuite/c-c++-common/torture/strub-run4.c new file mode 100644 index 0000000..3b36b8e --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-run4.c @@ -0,0 +1,106 @@ +/* { dg-do run } */ +/* { dg-options "-fstrub=all" } */ +/* { dg-require-effective-target alloca } */ + +/* Check that multi-level, multi-inlined functions still get cleaned up as + expected, without overwriting temporary stack allocations while they should + still be available. */ + +#ifndef ATTR_STRUB_AT_CALLS +# define ATTR_STRUB_AT_CALLS /* Defined in strub-run4d.c. */ +#endif + +const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa"; + +static inline __attribute__ ((__always_inline__)) +char * +leak_string (void) +{ + int __attribute__ ((__strub__)) len = 512; + asm ("" : "+r" (len)); + char s[len]; + __builtin_strcpy (s, test_string); + __builtin_strcpy (s + len - sizeof (test_string), test_string); + asm ("" : "+m" (s)); + return (char *) __builtin_stack_address (); +} + +static inline __attribute__ ((__always_inline__)) +int +look_for_string (char *e) +{ + char *p = (char *) __builtin_stack_address (); + + if (p == e) + __builtin_abort (); + + if (p > e) + { + char *q = p; + p = e; + e = q; + } + + for (char *re = e - sizeof (test_string); p < re; p++) + for (int i = 0; p[i] == test_string[i]; i++) + if (i == sizeof (test_string) - 1) + return i; + + return 0; +} + +static inline ATTR_STRUB_AT_CALLS +char * +innermost () +{ + int __attribute__ ((__strub__)) len = 512; + asm ("" : "+r" (len)); + char s[len]; + __builtin_strcpy (s, test_string); + __builtin_strcpy (s + len - sizeof (test_string), test_string); + asm ("" : "+m" (s)); + char *ret = leak_string (); + if (__builtin_strcmp (s, test_string) != 0) + __builtin_abort (); + if (__builtin_strcmp (s + len - sizeof (test_string), test_string) != 0) + __builtin_abort (); + return ret; +} + +static inline ATTR_STRUB_AT_CALLS +char * +intermediate () +{ + int __attribute__ ((__strub__)) len = 512; + asm ("" : "+r" (len)); + char s[len]; + __builtin_strcpy (s, test_string); + __builtin_strcpy (s + len - sizeof (test_string), test_string); + asm ("" : "+m" (s)); + char *ret = innermost (); + if (__builtin_strcmp (s, test_string) != 0) + __builtin_abort (); + if (__builtin_strcmp (s + len - sizeof (test_string), test_string) != 0) + __builtin_abort (); + return ret; +} + +static inline __attribute__ ((__strub__ ("internal"))) +char * +internal () +{ + return intermediate (); +} + +int __attribute__ ((__strub__ ("disabled"))) +main () +{ + /* Since these test check stack contents above the top of the stack, an + unexpected asynchronous signal or interrupt might overwrite the bits we + expect to find and cause spurious fails. Tolerate one such overall + spurious fail by retrying. */ + int i = 1; + while (look_for_string (internal ())) + if (!i--) __builtin_abort (); + __builtin_exit (0); +} diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4c.c b/gcc/testsuite/c-c++-common/torture/strub-run4c.c new file mode 100644 index 0000000..57f9baf --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-run4c.c @@ -0,0 +1,5 @@ +/* { dg-do run } */ +/* { dg-options "-fstrub=at-calls" } */ +/* { dg-require-effective-target alloca } */ + +#include "strub-run4.c" diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4d.c b/gcc/testsuite/c-c++-common/torture/strub-run4d.c new file mode 100644 index 0000000..08de3f1 --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-run4d.c @@ -0,0 +1,7 @@ +/* { dg-do run } */ +/* { dg-options "-fstrub=strict" } */ +/* { dg-require-effective-target alloca } */ + +#define ATTR_STRUB_AT_CALLS __attribute__ ((__strub__ ("at-calls"))) + +#include "strub-run4.c" diff --git a/gcc/testsuite/c-c++-common/torture/strub-run4i.c b/gcc/testsuite/c-c++-common/torture/strub-run4i.c new file mode 100644 index 0000000..459f688 --- /dev/null +++ b/gcc/testsuite/c-c++-common/torture/strub-run4i.c @@ -0,0 +1,5 @@ +/* { dg-do run } */ +/* { dg-options "-fstrub=internal" } */ +/* { dg-require-effective-target alloca } */ + +#include "strub-run4.c" diff --git a/gcc/testsuite/g++.dg/strub-run1.C b/gcc/testsuite/g++.dg/strub-run1.C new file mode 100644 index 0000000..0d367fb --- /dev/null +++ b/gcc/testsuite/g++.dg/strub-run1.C @@ -0,0 +1,19 @@ +// { dg-do run } +// { dg-options "-fstrub=internal" } + +// Check that we don't get extra copies. + +struct T { + T &self; + void check () const { if (&self != this) __builtin_abort (); } + T() : self (*this) { check (); } + T(const T& ck) : self (*this) { ck.check (); check (); } + ~T() { check (); } +}; + +T foo (T q) { q.check (); return T(); } +T bar (T p) { p.check (); return foo (p); } + +int main () { + bar (T()).check (); +} diff --git a/gcc/testsuite/g++.dg/torture/strub-init1.C b/gcc/testsuite/g++.dg/torture/strub-init1.C new file mode 100644 index 0000000..c226ab1 --- /dev/null +++ b/gcc/testsuite/g++.dg/torture/strub-init1.C @@ -0,0 +1,13 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +extern int __attribute__((__strub__)) initializer (); + +int f() { + static int x = initializer (); + return x; +} + +/* { dg-final { scan-ipa-dump "strub_enter" "strub" } } */ +/* { dg-final { scan-ipa-dump "strub_leave" "strub" } } */ +/* { dg-final { scan-ipa-dump-not "strub_update" "strub" } } */ diff --git a/gcc/testsuite/g++.dg/torture/strub-init2.C b/gcc/testsuite/g++.dg/torture/strub-init2.C new file mode 100644 index 0000000..a7911f1 --- /dev/null +++ b/gcc/testsuite/g++.dg/torture/strub-init2.C @@ -0,0 +1,14 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +extern int __attribute__((__strub__)) initializer (); + +static int x = initializer (); + +int f() { + return x; +} + +/* { dg-final { scan-ipa-dump "strub_enter" "strub" } } */ +/* { dg-final { scan-ipa-dump "strub_leave" "strub" } } */ +/* { dg-final { scan-ipa-dump-not "strub_update" "strub" } } */ diff --git a/gcc/testsuite/g++.dg/torture/strub-init3.C b/gcc/testsuite/g++.dg/torture/strub-init3.C new file mode 100644 index 0000000..6ebebcd --- /dev/null +++ b/gcc/testsuite/g++.dg/torture/strub-init3.C @@ -0,0 +1,13 @@ +/* { dg-do compile } */ +/* { dg-options "-fstrub=strict -fdump-ipa-strub" } */ + +extern int __attribute__((__strub__)) initializer (); + +int f() { + int x = initializer (); + return x; +} + +/* { dg-final { scan-ipa-dump "strub_enter" "strub" } } */ +/* { dg-final { scan-ipa-dump "strub_leave" "strub" } } */ +/* { dg-final { scan-ipa-dump-not "strub_update" "strub" } } */ diff --git a/gcc/testsuite/gnat.dg/strub_access.adb b/gcc/testsuite/gnat.dg/strub_access.adb new file mode 100644 index 0000000..29e6996 --- /dev/null +++ b/gcc/testsuite/gnat.dg/strub_access.adb @@ -0,0 +1,21 @@ +-- { dg-do compile } +-- { dg-options "-fstrub=relaxed -fdump-ipa-strubm" } + +-- The main subprogram doesn't read from the automatic variable, but +-- being an automatic variable, its presence should be enough for the +-- procedure to get strub enabled. + +procedure Strub_Access is + type Strub_Int is new Integer; + pragma Machine_Attribute (Strub_Int, "strub"); + + X : aliased Strub_Int := 0; + + function F (P : access Strub_Int) return Strub_Int is (P.all); + +begin + X := F (X'Access); +end Strub_Access; + +-- { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 1 "strubm" } } +-- { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls-opt\[)\]\[)\]" 1 "strubm" } } diff --git a/gcc/testsuite/gnat.dg/strub_access1.adb b/gcc/testsuite/gnat.dg/strub_access1.adb new file mode 100644 index 0000000..dae4706 --- /dev/null +++ b/gcc/testsuite/gnat.dg/strub_access1.adb @@ -0,0 +1,16 @@ +-- { dg-do compile } +-- { dg-options "-fstrub=relaxed" } + +-- Check that we reject 'Access of a strub variable whose type does +-- not carry a strub modifier. + +procedure Strub_Access1 is + X : aliased Integer := 0; + pragma Machine_Attribute (X, "strub"); + + function F (P : access Integer) return Integer is (P.all); + +begin + X := F (X'Unchecked_access); -- OK. + X := F (X'Access); -- { dg-error "target access type drops .strub. mode" } +end Strub_Access1; diff --git a/gcc/testsuite/gnat.dg/strub_attr.adb b/gcc/testsuite/gnat.dg/strub_attr.adb new file mode 100644 index 0000000..10445d7 --- /dev/null +++ b/gcc/testsuite/gnat.dg/strub_attr.adb @@ -0,0 +1,37 @@ +-- { dg-do compile } +-- { dg-options "-fstrub=strict -fdump-ipa-strubm -fdump-ipa-strub" } + +package body Strub_Attr is + E : exception; + + procedure P (X : Integer) is + begin + raise E; + end; + + function F (X : Integer) return Integer is + begin + return X * X; + end; + + function G return Integer is (F (X)); + -- function G return Integer is (FP (X)); + -- Calling G would likely raise an exception, because although FP + -- carries the strub at-calls attribute needed to call F, the + -- attribute is dropped from the type used for the call proper. +end Strub_Attr; + +-- { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 2 "strubm" } } +-- { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 0 "strubm" } } +-- { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } } + +-- { dg-final { scan-ipa-dump-times "strub.watermark_ptr" 6 "strub" } } +-- We have 1 at-calls subprogram (F) and 2 wrapped (P and G). +-- For each of them, there's one match for the wrapped signature, +-- and one for the update call. + +-- { dg-final { scan-ipa-dump-times "strub.watermark" 27 "strub" } } +-- The 6 matches above, plus: +-- 5*2: wm var decl, enter, call, leave and clobber for each wrapper; +-- 2*1: an extra leave and clobber for the exception paths in the wrappers. +-- 7*1: for the F call in G, including EH path. diff --git a/gcc/testsuite/gnat.dg/strub_attr.ads b/gcc/testsuite/gnat.dg/strub_attr.ads new file mode 100644 index 0000000..a94c23b --- /dev/null +++ b/gcc/testsuite/gnat.dg/strub_attr.ads @@ -0,0 +1,12 @@ +package Strub_Attr is + procedure P (X : Integer); + pragma Machine_Attribute (P, "strub", "internal"); + + function F (X : Integer) return Integer; + pragma Machine_Attribute (F, "strub"); + + X : Integer := 0; + pragma Machine_Attribute (X, "strub"); + + function G return Integer; +end Strub_Attr; diff --git a/gcc/testsuite/gnat.dg/strub_disp.adb b/gcc/testsuite/gnat.dg/strub_disp.adb new file mode 100644 index 0000000..3dbcc4a --- /dev/null +++ b/gcc/testsuite/gnat.dg/strub_disp.adb @@ -0,0 +1,64 @@ +-- { dg-do compile } + +procedure Strub_Disp is + package Foo is + type A is tagged null record; + + procedure P (I : Integer; X : A); + pragma Machine_Attribute (P, "strub", "at-calls"); + + function F (X : access A) return Integer; + + type B is new A with null record; + + overriding + procedure P (I : Integer; X : B); -- { dg-error "requires the same .strub. mode" } + + overriding + function F (X : access B) return Integer; + pragma Machine_Attribute (F, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" } + + end Foo; + + package body Foo is + procedure P (I : Integer; X : A) is + begin + null; + end; + + function F (X : access A) return Integer is (0); + + overriding + procedure P (I : Integer; X : B) is + begin + P (I, A (X)); + end; + + overriding + function F (X : access B) return Integer is (1); + end Foo; + + use Foo; + + procedure Q (X : A'Class) is + begin + P (-1, X); + end; + + XA : aliased A; + XB : aliased B; + I : Integer := 0; + XC : access A'Class; +begin + Q (XA); + Q (XB); + + I := I + F (XA'Access); + I := I + F (XB'Access); + + XC := XA'Access; + I := I + F (XC); + + XC := XB'Access; + I := I + F (XC); +end Strub_Disp; diff --git a/gcc/testsuite/gnat.dg/strub_disp1.adb b/gcc/testsuite/gnat.dg/strub_disp1.adb new file mode 100644 index 0000000..09756a7 --- /dev/null +++ b/gcc/testsuite/gnat.dg/strub_disp1.adb @@ -0,0 +1,79 @@ +-- { dg-do compile } +-- { dg-options "-fdump-ipa-strub" } + +-- Check that at-calls dispatching calls are transformed. + +procedure Strub_Disp1 is + package Foo is + type A is tagged null record; + + procedure P (I : Integer; X : A); + pragma Machine_Attribute (P, "strub", "at-calls"); + + function F (X : access A) return Integer; + pragma Machine_Attribute (F, "strub", "at-calls"); + + type B is new A with null record; + + overriding + procedure P (I : Integer; X : B); + pragma Machine_Attribute (P, "strub", "at-calls"); + + overriding + function F (X : access B) return Integer; + pragma Machine_Attribute (F, "strub", "at-calls"); + + end Foo; + + package body Foo is + procedure P (I : Integer; X : A) is + begin + null; + end; + + function F (X : access A) return Integer is (0); + + overriding + procedure P (I : Integer; X : B) is + begin + P (I, A (X)); -- strub-at-calls non-dispatching call + end; + + overriding + function F (X : access B) return Integer is (1); + end Foo; + + use Foo; + + procedure Q (X : A'Class) is + begin + P (-1, X); -- strub-at-calls dispatching call. + end; + + XA : aliased A; + XB : aliased B; + I : Integer := 0; + XC : access A'Class; +begin + Q (XA); + Q (XB); + + I := I + F (XA'Access); -- strub-at-calls non-dispatching call + I := I + F (XB'Access); -- strub-at-calls non-dispatching call + + XC := XA'Access; + I := I + F (XC); -- strub-at-calls dispatching call. + + XC := XB'Access; + I := I + F (XC); -- strub-at-calls dispatching call. +end Strub_Disp1; + +-- { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 4 "strub" } } + +-- Count the strub-at-calls non-dispatching calls +-- (+ 2 each, for the matching prototypes) +-- { dg-final { scan-ipa-dump-times "foo\.p \[(\]\[^\n\]*watermark" 3 "strub" } } +-- { dg-final { scan-ipa-dump-times "foo\.f \[(\]\[^\n\]*watermark" 4 "strub" } } + +-- Count the strub-at-calls dispatching calls. +-- { dg-final { scan-ipa-dump-times "_\[0-9\]* \[(\]\[^\n\]*watermark" 3 "strub" } } diff --git a/gcc/testsuite/gnat.dg/strub_ind.adb b/gcc/testsuite/gnat.dg/strub_ind.adb new file mode 100644 index 0000000..da56aca --- /dev/null +++ b/gcc/testsuite/gnat.dg/strub_ind.adb @@ -0,0 +1,33 @@ +-- { dg-do compile } +-- { dg-options "-fstrub=strict" } + +-- This is essentially the same test as strub_attr.adb, +-- but applying attributes to access types as well. +-- That doesn't quite work yet, so we get an error we shouldn't get. + +package body Strub_Ind is + E : exception; + + function G return Integer; + + procedure P (X : Integer) is + begin + raise E; + end; + + function F (X : Integer) return Integer is + begin + return X * X; + end; + + function G return Integer is (FP (X)); + + type GT is access function return Integer; + + type GT_SAC is access function return Integer; + pragma Machine_Attribute (GT_SAC, "strub", "at-calls"); + + GP : GT_SAC := GT_SAC (GT'(G'Access)); -- { dg-error "incompatible" } + -- pragma Machine_Attribute (GP, "strub", "at-calls"); + +end Strub_Ind; diff --git a/gcc/testsuite/gnat.dg/strub_ind.ads b/gcc/testsuite/gnat.dg/strub_ind.ads new file mode 100644 index 0000000..99a65fc --- /dev/null +++ b/gcc/testsuite/gnat.dg/strub_ind.ads @@ -0,0 +1,17 @@ +package Strub_Ind is + procedure P (X : Integer); + pragma Machine_Attribute (P, "strub", "internal"); + + function F (X : Integer) return Integer; + pragma Machine_Attribute (F, "strub"); + + X : Integer := 0; + pragma Machine_Attribute (X, "strub"); + + type FT is access function (X : Integer) return Integer; + pragma Machine_Attribute (FT, "strub", "at-calls"); + + FP : FT := F'Access; + -- pragma Machine_Attribute (FP, "strub", "at-calls"); -- not needed + +end Strub_Ind; diff --git a/gcc/testsuite/gnat.dg/strub_ind1.adb b/gcc/testsuite/gnat.dg/strub_ind1.adb new file mode 100644 index 0000000..825e395 --- /dev/null +++ b/gcc/testsuite/gnat.dg/strub_ind1.adb @@ -0,0 +1,41 @@ +-- { dg-do compile } +-- { dg-options "-fstrub=strict -fdump-ipa-strubm" } + +-- This is essentially the same test as strub_attr.adb, +-- but with an explicit conversion. + +package body Strub_Ind1 is + E : exception; + + type Strub_Int is New Integer; + pragma Machine_Attribute (Strub_Int, "strub"); + + function G return Integer; + pragma Machine_Attribute (G, "strub", "disabled"); + + procedure P (X : Integer) is + begin + raise E; + end; + + function G return Integer is (FP (X)); + + type GT is access function return Integer; + pragma Machine_Attribute (GT, "strub", "disabled"); + + type GT_SC is access function return Integer; + pragma Machine_Attribute (GT_SC, "strub", "callable"); + + GP : GT_SC := GT_SC (GT'(G'Access)); + -- pragma Machine_Attribute (GP, "strub", "callable"); -- not needed. + + function F (X : Integer) return Integer is + begin + return X * GP.all; + end; + +end Strub_Ind1; + +-- { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]disabled\[)\]\[)\]" 1 "strubm" } } +-- { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 1 "strubm" } } +-- { dg-final { scan-ipa-dump-times "\[(\]strub\[)\]" 1 "strubm" } } diff --git a/gcc/testsuite/gnat.dg/strub_ind1.ads b/gcc/testsuite/gnat.dg/strub_ind1.ads new file mode 100644 index 0000000..d3f1273 --- /dev/null +++ b/gcc/testsuite/gnat.dg/strub_ind1.ads @@ -0,0 +1,17 @@ +package Strub_Ind1 is + procedure P (X : Integer); + pragma Machine_Attribute (P, "strub", "internal"); + + function F (X : Integer) return Integer; + pragma Machine_Attribute (F, "strub"); + + X : aliased Integer := 0; + pragma Machine_Attribute (X, "strub"); + + type FT is access function (X : Integer) return Integer; + pragma Machine_Attribute (FT, "strub", "at-calls"); + + FP : FT := F'Access; + pragma Machine_Attribute (FP, "strub", "at-calls"); + +end Strub_Ind1; diff --git a/gcc/testsuite/gnat.dg/strub_ind2.adb b/gcc/testsuite/gnat.dg/strub_ind2.adb new file mode 100644 index 0000000..e918b39 --- /dev/null +++ b/gcc/testsuite/gnat.dg/strub_ind2.adb @@ -0,0 +1,34 @@ +-- { dg-do compile } +-- { dg-options "-fstrub=strict" } + +-- This is essentially the same test as strub_attr.adb, +-- but with an explicit conversion. + +package body Strub_Ind2 is + E : exception; + + function G return Integer; + pragma Machine_Attribute (G, "strub", "callable"); + + procedure P (X : Integer) is + begin + raise E; + end; + + function G return Integer is (FP (X)); + + type GT is access function return Integer; + pragma Machine_Attribute (GT, "strub", "callable"); + + type GT_SD is access function return Integer; + pragma Machine_Attribute (GT_SD, "strub", "disabled"); + + GP : GT_SD := GT_SD (GT'(G'Access)); + -- pragma Machine_Attribute (GP, "strub", "disabled"); -- not needed. + + function F (X : Integer) return Integer is + begin + return X * GP.all; -- { dg-error "using non-.strub. type" } + end; + +end Strub_Ind2; diff --git a/gcc/testsuite/gnat.dg/strub_ind2.ads b/gcc/testsuite/gnat.dg/strub_ind2.ads new file mode 100644 index 0000000..e13865e --- /dev/null +++ b/gcc/testsuite/gnat.dg/strub_ind2.ads @@ -0,0 +1,17 @@ +package Strub_Ind2 is + procedure P (X : Integer); + pragma Machine_Attribute (P, "strub", "internal"); + + function F (X : Integer) return Integer; + pragma Machine_Attribute (F, "strub"); + + X : Integer := 0; + pragma Machine_Attribute (X, "strub"); + + type FT is access function (X : Integer) return Integer; + pragma Machine_Attribute (FT, "strub", "at-calls"); + + FP : FT := F'Access; + pragma Machine_Attribute (FP, "strub", "at-calls"); + +end Strub_Ind2; diff --git a/gcc/testsuite/gnat.dg/strub_intf.adb b/gcc/testsuite/gnat.dg/strub_intf.adb new file mode 100644 index 0000000..8f0212a --- /dev/null +++ b/gcc/testsuite/gnat.dg/strub_intf.adb @@ -0,0 +1,93 @@ +-- { dg-do compile } + +-- Check that strub mode mismatches between overrider and overridden +-- subprograms are reported. + +procedure Strub_Intf is + package Foo is + type TP is interface; + procedure P (I : Integer; X : TP) is abstract; + pragma Machine_Attribute (P, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" } + + type TF is interface; + function F (X : access TF) return Integer is abstract; + + type TX is interface; + procedure P (I : Integer; X : TX) is abstract; + + type TI is interface and TP and TF and TX; + -- When we freeze TI, we detect the mismatch between the + -- inherited P and another parent's P. Because TP appears + -- before TX, we inherit P from TP, and report the mismatch at + -- the pragma inherited from TP against TX's P. In contrast, + -- when we freeze TII below, since TX appears before TP, we + -- report the error at the line in which the inherited + -- subprogram is synthesized, namely the line below, against + -- the line of the pragma. + + type TII is interface and TX and TP and TF; -- { dg-error "requires the same .strub. mode" } + + function F (X : access TI) return Integer is abstract; + pragma Machine_Attribute (F, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" } + + type A is new TI with null record; + + procedure P (I : Integer; X : A); + pragma Machine_Attribute (P, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" } + + function F (X : access A) return Integer; -- { dg-error "requires the same .strub. mode" } + + type B is new TI with null record; + + overriding + procedure P (I : Integer; X : B); -- { dg-error "requires the same .strub. mode" } + + overriding + function F (X : access B) return Integer; + pragma Machine_Attribute (F, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" } + + end Foo; + + package body Foo is + procedure P (I : Integer; X : A) is + begin + null; + end; + + function F (X : access A) return Integer is (0); + + overriding + procedure P (I : Integer; X : B) is + begin + null; + end; + + overriding + function F (X : access B) return Integer is (1); + + end Foo; + + use Foo; + + procedure Q (X : TX'Class) is + begin + P (-1, X); + end; + + XA : aliased A; + XB : aliased B; + I : Integer := 0; + XC : access TI'Class; +begin + Q (XA); + Q (XB); + + I := I + F (XA'Access); + I := I + F (XB'Access); + + XC := XA'Access; + I := I + F (XC); + + XC := XB'Access; + I := I + F (XC); +end Strub_Intf; diff --git a/gcc/testsuite/gnat.dg/strub_intf1.adb b/gcc/testsuite/gnat.dg/strub_intf1.adb new file mode 100644 index 0000000..bf77321 --- /dev/null +++ b/gcc/testsuite/gnat.dg/strub_intf1.adb @@ -0,0 +1,86 @@ +-- { dg-do compile } +-- { dg-options "-fdump-ipa-strub" } + +-- Check that at-calls dispatching calls to interfaces are transformed. + +procedure Strub_Intf1 is + package Foo is + type TX is Interface; + procedure P (I : Integer; X : TX) is abstract; + pragma Machine_Attribute (P, "strub", "at-calls"); + function F (X : access TX) return Integer is abstract; + pragma Machine_Attribute (F, "strub", "at-calls"); + + type A is new TX with null record; + + procedure P (I : Integer; X : A); + pragma Machine_Attribute (P, "strub", "at-calls"); + + function F (X : access A) return Integer; + pragma Machine_Attribute (F, "strub", "at-calls"); + + type B is new TX with null record; + + overriding + procedure P (I : Integer; X : B); + pragma Machine_Attribute (P, "strub", "at-calls"); + + overriding + function F (X : access B) return Integer; + pragma Machine_Attribute (F, "strub", "at-calls"); + + end Foo; + + package body Foo is + procedure P (I : Integer; X : A) is + begin + null; + end; + + function F (X : access A) return Integer is (0); + + overriding + procedure P (I : Integer; X : B) is + begin + null; + end; + + overriding + function F (X : access B) return Integer is (1); + + end Foo; + + use Foo; + + procedure Q (X : TX'Class) is + begin + P (-1, X); + end; + + XA : aliased A; + XB : aliased B; + I : Integer := 0; + XC : access TX'Class; +begin + Q (XA); + Q (XB); + + I := I + F (XA'Access); + I := I + F (XB'Access); + + XC := XA'Access; + I := I + F (XC); + + XC := XB'Access; + I := I + F (XC); +end Strub_Intf1; + +-- { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 4 "strub" } } + +-- Count the strub-at-calls non-dispatching calls +-- (+ 2 each, for the matching prototypes) +-- { dg-final { scan-ipa-dump-times "foo\.p \[(\]\[^\n\]*watermark" 2 "strub" } } +-- { dg-final { scan-ipa-dump-times "foo\.f \[(\]\[^\n\]*watermark" 4 "strub" } } + +-- Count the strub-at-calls dispatching calls. +-- { dg-final { scan-ipa-dump-times "_\[0-9\]* \[(\]\[^\n\]*watermark" 3 "strub" } } diff --git a/gcc/testsuite/gnat.dg/strub_intf2.adb b/gcc/testsuite/gnat.dg/strub_intf2.adb new file mode 100644 index 0000000..e8880db --- /dev/null +++ b/gcc/testsuite/gnat.dg/strub_intf2.adb @@ -0,0 +1,55 @@ +-- { dg-do compile } + +-- Check that strub mode mismatches between overrider and overridden +-- subprograms are reported even when the overriders for an +-- interface's subprograms are inherited from a type that is not a +-- descendent of the interface. + +procedure Strub_Intf2 is + package Foo is + type A is tagged null record; + + procedure P (I : Integer; X : A); + pragma Machine_Attribute (P, "strub", "at-calls"); -- { dg-error "requires the same .strub. mode" } + + function F (X : access A) return Integer; + + type TX is Interface; + + procedure P (I : Integer; X : TX) is abstract; + + function F (X : access TX) return Integer is abstract; + pragma Machine_Attribute (F, "strub", "at-calls"); + + type B is new A and TX with null record; -- { dg-error "requires the same .strub. mode" } + + end Foo; + + package body Foo is + procedure P (I : Integer; X : A) is + begin + null; + end; + + function F (X : access A) return Integer is (0); + + end Foo; + + use Foo; + + procedure Q (X : TX'Class) is + begin + P (-1, X); + end; + + XB : aliased B; + I : Integer := 0; + XC : access TX'Class; +begin + Q (XB); + + I := I + F (XB'Access); + + XC := XB'Access; + I := I + F (XC); +end Strub_Intf2; diff --git a/gcc/testsuite/gnat.dg/strub_renm.adb b/gcc/testsuite/gnat.dg/strub_renm.adb new file mode 100644 index 0000000..217367e --- /dev/null +++ b/gcc/testsuite/gnat.dg/strub_renm.adb @@ -0,0 +1,21 @@ +-- { dg-do compile } + +procedure Strub_Renm is + procedure P (X : Integer); + pragma Machine_Attribute (P, "strub", "at-calls"); + + function F return Integer; + pragma Machine_Attribute (F, "strub", "internal"); + + procedure Q (X : Integer) renames P; -- { dg-error "requires the same .strub. mode" } + + function G return Integer renames F; + pragma Machine_Attribute (G, "strub", "callable"); -- { dg-error "requires the same .strub. mode" } + + procedure P (X : Integer) is null; + function F return Integer is (0); + +begin + P (F); + Q (G); +end Strub_Renm; diff --git a/gcc/testsuite/gnat.dg/strub_renm1.adb b/gcc/testsuite/gnat.dg/strub_renm1.adb new file mode 100644 index 0000000..a11adbf --- /dev/null +++ b/gcc/testsuite/gnat.dg/strub_renm1.adb @@ -0,0 +1,32 @@ +-- { dg-do compile } +-- { dg-options "-fstrub=relaxed -fdump-ipa-strub" } + +procedure Strub_Renm1 is + V : Integer := 0; + pragma Machine_Attribute (V, "strub"); + + procedure P (X : Integer); + pragma Machine_Attribute (P, "strub", "at-calls"); + + function F return Integer; + + procedure Q (X : Integer) renames P; + pragma Machine_Attribute (Q, "strub", "at-calls"); + + function G return Integer renames F; + pragma Machine_Attribute (G, "strub", "internal"); + + procedure P (X : Integer) is null; + function F return Integer is (0); + +begin + P (F); + Q (G); +end Strub_Renm1; + +-- This is for P; Q is an alias. +-- { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]at-calls\[)\]\[)\]" 1 "strub" } } + +-- This is *not* for G, but for Strub_Renm1. +-- { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]wrapped\[)\]\[)\]" 1 "strub" } } +-- { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]wrapper\[)\]\[)\]" 1 "strub" } } diff --git a/gcc/testsuite/gnat.dg/strub_renm2.adb b/gcc/testsuite/gnat.dg/strub_renm2.adb new file mode 100644 index 0000000..c488c20 --- /dev/null +++ b/gcc/testsuite/gnat.dg/strub_renm2.adb @@ -0,0 +1,32 @@ +-- { dg-do compile } +-- { dg-options "-fstrub=strict -fdump-ipa-strub" } + +procedure Strub_Renm2 is + V : Integer := 0; + pragma Machine_Attribute (V, "strub"); + + procedure P (X : Integer); + pragma Machine_Attribute (P, "strub", "at-calls"); + + function F return Integer; + + procedure Q (X : Integer) renames P; + pragma Machine_Attribute (Q, "strub", "at-calls"); + + type T is access function return Integer; + + type TC is access function return Integer; + pragma Machine_Attribute (TC, "strub", "callable"); + + FCptr : constant TC := TC (T'(F'Access)); + + function G return Integer renames FCptr.all; + pragma Machine_Attribute (G, "strub", "callable"); + + procedure P (X : Integer) is null; + function F return Integer is (0); + +begin + P (F); -- { dg-error "calling non-.strub." } + Q (G); -- ok, G is callable. +end Strub_Renm2; diff --git a/gcc/testsuite/gnat.dg/strub_var.adb b/gcc/testsuite/gnat.dg/strub_var.adb new file mode 100644 index 0000000..3d158de --- /dev/null +++ b/gcc/testsuite/gnat.dg/strub_var.adb @@ -0,0 +1,16 @@ +-- { dg-do compile } +-- { dg-options "-fstrub=strict -fdump-ipa-strubm" } + +-- We don't read from the automatic variable, but being an automatic +-- variable, its presence should be enough for the procedure to get +-- strub enabled. + +with Strub_Attr; +procedure Strub_Var is + X : Integer := 0; + pragma Machine_Attribute (X, "strub"); +begin + X := Strub_Attr.F (0); +end Strub_Var; + +-- { dg-final { scan-ipa-dump-times "\[(\]strub \[(\]internal\[)\]\[)\]" 1 "strubm" } } diff --git a/gcc/testsuite/gnat.dg/strub_var1.adb b/gcc/testsuite/gnat.dg/strub_var1.adb new file mode 100644 index 0000000..6a504e0 --- /dev/null +++ b/gcc/testsuite/gnat.dg/strub_var1.adb @@ -0,0 +1,20 @@ +-- { dg-do compile } + +with Strub_Attr; +procedure Strub_Var1 is + type TA -- { dg-warning "does not apply to elements" } + is array (1..2) of Integer; + pragma Machine_Attribute (TA, "strub"); + + A : TA := (0, 0); -- { dg-warning "does not apply to elements" } + + type TR is record -- { dg-warning "does not apply to fields" } + M, N : Integer; + end record; + pragma Machine_Attribute (TR, "strub"); + + R : TR := (0, 0); + +begin + A(2) := Strub_Attr.F (A(1)); +end Strub_Var1; |