diff mbox series

[3/5] x86: Add -mfunction-return=

Message ID 20180107225904.11535-4-hjl.tools@gmail.com
State New
Headers show
Series x86: CVE-2017-5715, aka Spectre | expand

Commit Message

H.J. Lu Jan. 7, 2018, 10:59 p.m. UTC
Add -mfunction-return= option to convert function return to call and
return thunks.  The default is 'keep', which keeps function return
unmodified.  'thunk' converts function return to call and return thunk.
'thunk-inline' converts function return to inlined call and return thunk.  'thunk-extern' converts function return to external call and return
thunk provided in a separate object file.  You can control this behavior
for a specific function by using the function attribute function_return.

Function return thunk is the same as memory thunk for -mindirect-branch=
where the return address is at the top of the stack:

__x86_return_thunk:
	call L2
L1:
	lfence
	jmp L1
L2:
	lea 8(%rsp), %rsp|lea 4(%esp), %esp
	ret

and function return becomes

	jmp __x86_return_thunk

-mindirect-branch= tests are updated with -mfunction-return=keep to
avoid false test failures when -mfunction-return=lfence is added to
RUNTESTFLAGS for "make check".

gcc/

	* config/i386/i386-protos.h (ix86_output_function_return): New.
	* config/i386/i386.c (ix86_set_indirect_branch_type): Also
	set function_return_type.
	(indirect_thunk_name): Add ret_p to indicate thunk for function
	return.
	(output_indirect_thunk_function): Pass false to
	indirect_thunk_name.
	(ix86_output_indirect_branch): Likewise.
	(output_indirect_thunk_function): Create alias for function
	return thunk if regno < 0.
	(ix86_output_function_return): New function.
	(ix86_handle_fndecl_attribute): Handle function_return.
	(ix86_attribute_table): Add function_return.
	* config/i386/i386.h (machine_function): Add
	function_return_type.
	* config/i386/i386.md (simple_return_internal): Use
	ix86_output_function_return.
	(simple_return_internal_long): Likewise.
	* config/i386/i386.opt (mfunction-return=): New option.
	(indirect_branch): Mention -mfunction-return=.
	* doc/extend.texi: Document function_return function attribute.
	* doc/invoke.texi: Document -mfunction-return= option.

gcc/testsuite/

	* gcc.target/i386/indirect-thunk-1.c (dg-options): Add
	-mfunction-return=keep.
	* gcc.target/i386/indirect-thunk-2.c: Likewise.
	* gcc.target/i386/indirect-thunk-3.c: Likewise.
	* gcc.target/i386/indirect-thunk-4.c: Likewise.
	* gcc.target/i386/indirect-thunk-5.c: Likewise.
	* gcc.target/i386/indirect-thunk-6.c: Likewise.
	* gcc.target/i386/indirect-thunk-7.c: Likewise.
	* gcc.target/i386/indirect-thunk-attr-1.c: Likewise.
	* gcc.target/i386/indirect-thunk-attr-2.c: Likewise.
	* gcc.target/i386/indirect-thunk-attr-3.c: Likewise.
	* gcc.target/i386/indirect-thunk-attr-4.c: Likewise.
	* gcc.target/i386/indirect-thunk-attr-5.c: Likewise.
	* gcc.target/i386/indirect-thunk-attr-6.c: Likewise.
	* gcc.target/i386/indirect-thunk-attr-7.c: Likewise.
	* gcc.target/i386/indirect-thunk-attr-8.c: Likewise.
	* gcc.target/i386/indirect-thunk-bnd-1.c: Likewise.
	* gcc.target/i386/indirect-thunk-bnd-2.c: Likewise.
	* gcc.target/i386/indirect-thunk-bnd-3.c: Likewise.
	* gcc.target/i386/indirect-thunk-bnd-4.c: Likewise.
	* gcc.target/i386/indirect-thunk-extern-1.c: Likewise.
	* gcc.target/i386/indirect-thunk-extern-2.c: Likewise.
	* gcc.target/i386/indirect-thunk-extern-3.c: Likewise.
	* gcc.target/i386/indirect-thunk-extern-4.c: Likewise.
	* gcc.target/i386/indirect-thunk-extern-5.c: Likewise.
	* gcc.target/i386/indirect-thunk-extern-6.c: Likewise.
	* gcc.target/i386/indirect-thunk-extern-7.c: Likewise.
	* gcc.target/i386/indirect-thunk-inline-1.c: Likewise.
	* gcc.target/i386/indirect-thunk-inline-2.c: Likewise.
	* gcc.target/i386/indirect-thunk-inline-3.c: Likewise.
	* gcc.target/i386/indirect-thunk-inline-4.c: Likewise.
	* gcc.target/i386/indirect-thunk-inline-5.c: Likewise.
	* gcc.target/i386/indirect-thunk-inline-6.c: Likewise.
	* gcc.target/i386/indirect-thunk-inline-7.c: Likewise.
	* gcc.target/i386/ret-thunk-1.c: New test.
	* gcc.target/i386/ret-thunk-10.c: Likewise.
	* gcc.target/i386/ret-thunk-11.c: Likewise.
	* gcc.target/i386/ret-thunk-12.c: Likewise.
	* gcc.target/i386/ret-thunk-13.c: Likewise.
	* gcc.target/i386/ret-thunk-14.c: Likewise.
	* gcc.target/i386/ret-thunk-15.c: Likewise.
	* gcc.target/i386/ret-thunk-16.c: Likewise.
	* gcc.target/i386/ret-thunk-2.c: Likewise.
	* gcc.target/i386/ret-thunk-3.c: Likewise.
	* gcc.target/i386/ret-thunk-4.c: Likewise.
	* gcc.target/i386/ret-thunk-5.c: Likewise.
	* gcc.target/i386/ret-thunk-6.c: Likewise.
	* gcc.target/i386/ret-thunk-7.c: Likewise.
	* gcc.target/i386/ret-thunk-8.c: Likewise.
	* gcc.target/i386/ret-thunk-9.c: Likewise.
---
 gcc/config/i386/i386-protos.h                      |   1 +
 gcc/config/i386/i386.c                             | 146 ++++++++++++++++++++-
 gcc/config/i386/i386.h                             |   3 +
 gcc/config/i386/i386.md                            |   9 +-
 gcc/config/i386/i386.opt                           |   6 +-
 gcc/doc/extend.texi                                |   9 ++
 gcc/doc/invoke.texi                                |  14 +-
 gcc/testsuite/gcc.target/i386/indirect-thunk-1.c   |   2 +-
 gcc/testsuite/gcc.target/i386/indirect-thunk-2.c   |   2 +-
 gcc/testsuite/gcc.target/i386/indirect-thunk-3.c   |   2 +-
 gcc/testsuite/gcc.target/i386/indirect-thunk-4.c   |   2 +-
 gcc/testsuite/gcc.target/i386/indirect-thunk-5.c   |   2 +-
 gcc/testsuite/gcc.target/i386/indirect-thunk-6.c   |   2 +-
 gcc/testsuite/gcc.target/i386/indirect-thunk-7.c   |   2 +-
 .../gcc.target/i386/indirect-thunk-attr-1.c        |   2 +-
 .../gcc.target/i386/indirect-thunk-attr-2.c        |   2 +-
 .../gcc.target/i386/indirect-thunk-attr-3.c        |   2 +-
 .../gcc.target/i386/indirect-thunk-attr-4.c        |   2 +-
 .../gcc.target/i386/indirect-thunk-attr-5.c        |   2 +-
 .../gcc.target/i386/indirect-thunk-attr-6.c        |   2 +-
 .../gcc.target/i386/indirect-thunk-attr-7.c        |   2 +-
 .../gcc.target/i386/indirect-thunk-attr-8.c        |   2 +-
 .../gcc.target/i386/indirect-thunk-bnd-1.c         |   2 +-
 .../gcc.target/i386/indirect-thunk-bnd-2.c         |   2 +-
 .../gcc.target/i386/indirect-thunk-bnd-3.c         |   2 +-
 .../gcc.target/i386/indirect-thunk-bnd-4.c         |   2 +-
 .../gcc.target/i386/indirect-thunk-extern-1.c      |   2 +-
 .../gcc.target/i386/indirect-thunk-extern-2.c      |   2 +-
 .../gcc.target/i386/indirect-thunk-extern-3.c      |   2 +-
 .../gcc.target/i386/indirect-thunk-extern-4.c      |   2 +-
 .../gcc.target/i386/indirect-thunk-extern-5.c      |   2 +-
 .../gcc.target/i386/indirect-thunk-extern-6.c      |   2 +-
 .../gcc.target/i386/indirect-thunk-extern-7.c      |   2 +-
 .../gcc.target/i386/indirect-thunk-inline-1.c      |   2 +-
 .../gcc.target/i386/indirect-thunk-inline-2.c      |   2 +-
 .../gcc.target/i386/indirect-thunk-inline-3.c      |   2 +-
 .../gcc.target/i386/indirect-thunk-inline-4.c      |   2 +-
 .../gcc.target/i386/indirect-thunk-inline-5.c      |   2 +-
 .../gcc.target/i386/indirect-thunk-inline-6.c      |   2 +-
 .../gcc.target/i386/indirect-thunk-inline-7.c      |   2 +-
 gcc/testsuite/gcc.target/i386/ret-thunk-1.c        |  12 ++
 gcc/testsuite/gcc.target/i386/ret-thunk-10.c       |  22 ++++
 gcc/testsuite/gcc.target/i386/ret-thunk-11.c       |  22 ++++
 gcc/testsuite/gcc.target/i386/ret-thunk-12.c       |  21 +++
 gcc/testsuite/gcc.target/i386/ret-thunk-13.c       |  21 +++
 gcc/testsuite/gcc.target/i386/ret-thunk-14.c       |  21 +++
 gcc/testsuite/gcc.target/i386/ret-thunk-15.c       |  21 +++
 gcc/testsuite/gcc.target/i386/ret-thunk-16.c       |  18 +++
 gcc/testsuite/gcc.target/i386/ret-thunk-2.c        |  12 ++
 gcc/testsuite/gcc.target/i386/ret-thunk-3.c        |  12 ++
 gcc/testsuite/gcc.target/i386/ret-thunk-4.c        |  12 ++
 gcc/testsuite/gcc.target/i386/ret-thunk-5.c        |  14 ++
 gcc/testsuite/gcc.target/i386/ret-thunk-6.c        |  13 ++
 gcc/testsuite/gcc.target/i386/ret-thunk-7.c        |  13 ++
 gcc/testsuite/gcc.target/i386/ret-thunk-8.c        |  14 ++
 gcc/testsuite/gcc.target/i386/ret-thunk-9.c        |  23 ++++
 56 files changed, 476 insertions(+), 49 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/i386/ret-thunk-1.c
 create mode 100644 gcc/testsuite/gcc.target/i386/ret-thunk-10.c
 create mode 100644 gcc/testsuite/gcc.target/i386/ret-thunk-11.c
 create mode 100644 gcc/testsuite/gcc.target/i386/ret-thunk-12.c
 create mode 100644 gcc/testsuite/gcc.target/i386/ret-thunk-13.c
 create mode 100644 gcc/testsuite/gcc.target/i386/ret-thunk-14.c
 create mode 100644 gcc/testsuite/gcc.target/i386/ret-thunk-15.c
 create mode 100644 gcc/testsuite/gcc.target/i386/ret-thunk-16.c
 create mode 100644 gcc/testsuite/gcc.target/i386/ret-thunk-2.c
 create mode 100644 gcc/testsuite/gcc.target/i386/ret-thunk-3.c
 create mode 100644 gcc/testsuite/gcc.target/i386/ret-thunk-4.c
 create mode 100644 gcc/testsuite/gcc.target/i386/ret-thunk-5.c
 create mode 100644 gcc/testsuite/gcc.target/i386/ret-thunk-6.c
 create mode 100644 gcc/testsuite/gcc.target/i386/ret-thunk-7.c
 create mode 100644 gcc/testsuite/gcc.target/i386/ret-thunk-8.c
 create mode 100644 gcc/testsuite/gcc.target/i386/ret-thunk-9.c

Comments

Martin Liška Jan. 8, 2018, 9:56 a.m. UTC | #1
On 01/07/2018 11:59 PM, H.J. Lu wrote:
> Function return thunk is the same as memory thunk for -mindirect-branch=
> where the return address is at the top of the stack:
> 
> __x86_return_thunk:
> 	call L2
> L1:
> 	lfence
> 	jmp L1
> L2:
> 	lea 8(%rsp), %rsp|lea 4(%esp), %esp
> 	ret
> 
> and function return becomes
> 
> 	jmp __x86_return_thunk

Hello.

Can you please explain more usage of the option? Is to prevent a speculative
execution of 'ret' instruction (which is an indirect call), as described in [1]?
The paper mentions that return stack predictors are commonly implemented in some form.
Looks that current version of Linux patches does not use the option.

Thanks,
Martin

[1] https://support.google.com/faqs/answer/7625886
H.J. Lu Jan. 8, 2018, 11:59 a.m. UTC | #2
On Mon, Jan 8, 2018 at 1:56 AM, Martin Liška <mliska@suse.cz> wrote:
> On 01/07/2018 11:59 PM, H.J. Lu wrote:
>> Function return thunk is the same as memory thunk for -mindirect-branch=
>> where the return address is at the top of the stack:
>>
>> __x86_return_thunk:
>>       call L2
>> L1:
>>       lfence
>>       jmp L1
>> L2:
>>       lea 8(%rsp), %rsp|lea 4(%esp), %esp
>>       ret
>>
>> and function return becomes
>>
>>       jmp __x86_return_thunk
>
> Hello.
>
> Can you please explain more usage of the option? Is to prevent a speculative
> execution of 'ret' instruction (which is an indirect call), as described in [1]?
> The paper mentions that return stack predictors are commonly implemented in some form.
> Looks that current version of Linux patches does not use the option.
>

This option is requested by Linux kernel people.  It may be used in
the future.
David Woodhouse Jan. 8, 2018, 9:10 p.m. UTC | #3
On Mon, 2018-01-08 at 03:59 -0800, H.J. Lu wrote:
> On Mon, Jan 8, 2018 at 1:56 AM, Martin Liška <mliska@suse.cz> wrote:
> > 
> > On 01/07/2018 11:59 PM, H.J. Lu wrote:
> > > 
> > > Function return thunk is the same as memory thunk for -mindirect-branch=
> > > where the return address is at the top of the stack:
> > > 
> > > __x86_return_thunk:
> > >       call L2
> > > L1:
> > >       lfence
> > >       jmp L1
> > > L2:
> > >       lea 8(%rsp), %rsp|lea 4(%esp), %esp
> > >       ret
> > > 
> > > and function return becomes
> > > 
> > >       jmp __x86_return_thunk
> > Hello.
> > 
> > Can you please explain more usage of the option? Is to prevent a speculative
> > execution of 'ret' instruction (which is an indirect call), as described in [1]?
> > The paper mentions that return stack predictors are commonly implemented in some form.
> > Looks that current version of Linux patches does not use the option.
> > 
> This option is requested by Linux kernel people.  It may be used in
> the future.

Right. Essentially the new set of vulnerability are all about
speculative execution. Instructions which *don't* get committed, and
it's supposed to be like they never happen, actually *do* have side-
effects and can leak information.

This is *particularly* problematic for Intel CPUs where the CPU
architects said "ah, screw it, let's not do memory permission checks in
advance; as long as we make sure it's done before we *commit* an
instruction it'll be fine". With the result that you can now basically
read *all* of kernel memory, and hence all of physical memory, directly
from userspace on Intel CPUs. Oops :)

The fix for *that* one is to actually remove the kernel pages from the
page tables while running userspace, instead of just setting the
permissions to prevent access. Hence the whole Kernel Page Table
Isolation thing.

The next interesting attack is the so-called "variant 2" where the
attacker pollutes the branch predictors so that in *kernel* mode the
CPU *speculatively* runs... well, whatever the attacker wants. This is
one that affects lots of vendors, not just Intel. We mitigate this by
eliminating *all* the indirect branches in the kernel, to make it
immune to such an attack.

This is all very well, but *some* CPUs also pull in predictions from
the generic branch target predictor when the return stack buffer has
underflowed (e.g. if there was a call stack of more than X depth).
Hence, in some cases we may yet end up needing this -mfunction-return=
thunk too. As you (Martin) note, we don't use it *yet*. The full set of
mitigations for the various attacks are still being put together, and
the optimal choice for each CPU family does end up being different.
Jeff Law Jan. 11, 2018, 10:54 p.m. UTC | #4
On 01/07/2018 03:59 PM, H.J. Lu wrote:
> Add -mfunction-return= option to convert function return to call and
> return thunks.  The default is 'keep', which keeps function return
> unmodified.  'thunk' converts function return to call and return thunk.
> 'thunk-inline' converts function return to inlined call and return thunk.  'thunk-extern' converts function return to external call and return
> thunk provided in a separate object file.  You can control this behavior
> for a specific function by using the function attribute function_return.
> 
> Function return thunk is the same as memory thunk for -mindirect-branch=
> where the return address is at the top of the stack:
> 
> __x86_return_thunk:
> 	call L2
> L1:
> 	lfence
> 	jmp L1
> L2:
> 	lea 8(%rsp), %rsp|lea 4(%esp), %esp
> 	ret
> 
> and function return becomes
> 
> 	jmp __x86_return_thunk
> 
> -mindirect-branch= tests are updated with -mfunction-return=keep to
> avoid false test failures when -mfunction-return=lfence is added to
> RUNTESTFLAGS for "make check".
> 
> gcc/
> 
> 	* config/i386/i386-protos.h (ix86_output_function_return): New.
> 	* config/i386/i386.c (ix86_set_indirect_branch_type): Also
> 	set function_return_type.
> 	(indirect_thunk_name): Add ret_p to indicate thunk for function
> 	return.
> 	(output_indirect_thunk_function): Pass false to
> 	indirect_thunk_name.
> 	(ix86_output_indirect_branch): Likewise.
> 	(output_indirect_thunk_function): Create alias for function
> 	return thunk if regno < 0.
> 	(ix86_output_function_return): New function.
> 	(ix86_handle_fndecl_attribute): Handle function_return.
> 	(ix86_attribute_table): Add function_return.
> 	* config/i386/i386.h (machine_function): Add
> 	function_return_type.
> 	* config/i386/i386.md (simple_return_internal): Use
> 	ix86_output_function_return.
> 	(simple_return_internal_long): Likewise.
> 	* config/i386/i386.opt (mfunction-return=): New option.
> 	(indirect_branch): Mention -mfunction-return=.
> 	* doc/extend.texi: Document function_return function attribute.
> 	* doc/invoke.texi: Document -mfunction-return= option.
> 
> gcc/testsuite/
> 
> 	* gcc.target/i386/indirect-thunk-1.c (dg-options): Add
> 	-mfunction-return=keep.
> 	* gcc.target/i386/indirect-thunk-2.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-3.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-4.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-5.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-6.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-7.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-attr-1.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-attr-2.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-attr-3.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-attr-4.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-attr-5.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-attr-6.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-attr-7.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-attr-8.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-bnd-1.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-bnd-2.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-bnd-3.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-bnd-4.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-extern-1.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-extern-2.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-extern-3.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-extern-4.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-extern-5.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-extern-6.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-extern-7.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-inline-1.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-inline-2.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-inline-3.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-inline-4.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-inline-5.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-inline-6.c: Likewise.
> 	* gcc.target/i386/indirect-thunk-inline-7.c: Likewise.
> 	* gcc.target/i386/ret-thunk-1.c: New test.
> 	* gcc.target/i386/ret-thunk-10.c: Likewise.
> 	* gcc.target/i386/ret-thunk-11.c: Likewise.
> 	* gcc.target/i386/ret-thunk-12.c: Likewise.
> 	* gcc.target/i386/ret-thunk-13.c: Likewise.
> 	* gcc.target/i386/ret-thunk-14.c: Likewise.
> 	* gcc.target/i386/ret-thunk-15.c: Likewise.
> 	* gcc.target/i386/ret-thunk-16.c: Likewise.
> 	* gcc.target/i386/ret-thunk-2.c: Likewise.
> 	* gcc.target/i386/ret-thunk-3.c: Likewise.
> 	* gcc.target/i386/ret-thunk-4.c: Likewise.
> 	* gcc.target/i386/ret-thunk-5.c: Likewise.
> 	* gcc.target/i386/ret-thunk-6.c: Likewise.
> 	* gcc.target/i386/ret-thunk-7.c: Likewise.
> 	* gcc.target/i386/ret-thunk-8.c: Likewise.
> 	* gcc.target/i386/ret-thunk-9.c: Likewise.
Same high level comments apply here.  ie, expecting Uros to chime in and
thus final approval rests with Uros.  Is there significant value in
having return thunks in the object file?

Is this something that is currently used or requested by the kernel teams?

> @@ -28539,6 +28610,43 @@ ix86_output_indirect_jmp (rtx call_op, bool ret_p)
>      return "%!jmp\t%A0";
>  }
>  
> +const char *
> +ix86_output_function_return (bool long_p)
Needs function comment.


> diff --git a/gcc/config/i386/i386.opt b/gcc/config/i386/i386.opt
> index f3e43da0aed..2b62363973d 100644
> --- a/gcc/config/i386/i386.opt
> +++ b/gcc/config/i386/i386.opt
> @@ -1026,9 +1026,13 @@ mindirect-branch=
>  Target Report RejectNegative Joined Enum(indirect_branch) Var(ix86_indirect_branch) Init(indirect_branch_keep)
>  Convert indirect call and jump.
>  
> +mfunction-return=
> +Target Report RejectNegative Joined Enum(indirect_branch) Var(ix86_function_return) Init(indirect_branch_keep)
> +Convert function return.
Again, reads poorly.
H.J. Lu Jan. 11, 2018, 11:03 p.m. UTC | #5
On Thu, Jan 11, 2018 at 2:54 PM, Jeff Law <law@redhat.com> wrote:
> On 01/07/2018 03:59 PM, H.J. Lu wrote:
>> Add -mfunction-return= option to convert function return to call and
>> return thunks.  The default is 'keep', which keeps function return
>> unmodified.  'thunk' converts function return to call and return thunk.
>> 'thunk-inline' converts function return to inlined call and return thunk.  'thunk-extern' converts function return to external call and return
>> thunk provided in a separate object file.  You can control this behavior
>> for a specific function by using the function attribute function_return.
>>
>> Function return thunk is the same as memory thunk for -mindirect-branch=
>> where the return address is at the top of the stack:
>>
>> __x86_return_thunk:
>>       call L2
>> L1:
>>       lfence
>>       jmp L1
>> L2:
>>       lea 8(%rsp), %rsp|lea 4(%esp), %esp
>>       ret
>>
>> and function return becomes
>>
>>       jmp __x86_return_thunk
>>
>> -mindirect-branch= tests are updated with -mfunction-return=keep to
>> avoid false test failures when -mfunction-return=lfence is added to
>> RUNTESTFLAGS for "make check".
>>
>> gcc/
>>
>>       * config/i386/i386-protos.h (ix86_output_function_return): New.
>>       * config/i386/i386.c (ix86_set_indirect_branch_type): Also
>>       set function_return_type.
>>       (indirect_thunk_name): Add ret_p to indicate thunk for function
>>       return.
>>       (output_indirect_thunk_function): Pass false to
>>       indirect_thunk_name.
>>       (ix86_output_indirect_branch): Likewise.
>>       (output_indirect_thunk_function): Create alias for function
>>       return thunk if regno < 0.
>>       (ix86_output_function_return): New function.
>>       (ix86_handle_fndecl_attribute): Handle function_return.
>>       (ix86_attribute_table): Add function_return.
>>       * config/i386/i386.h (machine_function): Add
>>       function_return_type.
>>       * config/i386/i386.md (simple_return_internal): Use
>>       ix86_output_function_return.
>>       (simple_return_internal_long): Likewise.
>>       * config/i386/i386.opt (mfunction-return=): New option.
>>       (indirect_branch): Mention -mfunction-return=.
>>       * doc/extend.texi: Document function_return function attribute.
>>       * doc/invoke.texi: Document -mfunction-return= option.
>>
>> gcc/testsuite/
>>
>>       * gcc.target/i386/indirect-thunk-1.c (dg-options): Add
>>       -mfunction-return=keep.
>>       * gcc.target/i386/indirect-thunk-2.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-3.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-4.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-5.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-6.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-7.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-attr-1.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-attr-2.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-attr-3.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-attr-4.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-attr-5.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-attr-6.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-attr-7.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-attr-8.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-bnd-1.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-bnd-2.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-bnd-3.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-bnd-4.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-extern-1.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-extern-2.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-extern-3.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-extern-4.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-extern-5.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-extern-6.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-extern-7.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-inline-1.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-inline-2.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-inline-3.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-inline-4.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-inline-5.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-inline-6.c: Likewise.
>>       * gcc.target/i386/indirect-thunk-inline-7.c: Likewise.
>>       * gcc.target/i386/ret-thunk-1.c: New test.
>>       * gcc.target/i386/ret-thunk-10.c: Likewise.
>>       * gcc.target/i386/ret-thunk-11.c: Likewise.
>>       * gcc.target/i386/ret-thunk-12.c: Likewise.
>>       * gcc.target/i386/ret-thunk-13.c: Likewise.
>>       * gcc.target/i386/ret-thunk-14.c: Likewise.
>>       * gcc.target/i386/ret-thunk-15.c: Likewise.
>>       * gcc.target/i386/ret-thunk-16.c: Likewise.
>>       * gcc.target/i386/ret-thunk-2.c: Likewise.
>>       * gcc.target/i386/ret-thunk-3.c: Likewise.
>>       * gcc.target/i386/ret-thunk-4.c: Likewise.
>>       * gcc.target/i386/ret-thunk-5.c: Likewise.
>>       * gcc.target/i386/ret-thunk-6.c: Likewise.
>>       * gcc.target/i386/ret-thunk-7.c: Likewise.
>>       * gcc.target/i386/ret-thunk-8.c: Likewise.
>>       * gcc.target/i386/ret-thunk-9.c: Likewise.
> Same high level comments apply here.  ie, expecting Uros to chime in and
> thus final approval rests with Uros.  Is there significant value in
> having return thunks in the object file?

To better support -mindirect-branch-loop= if it is ever needed.

> Is this something that is currently used or requested by the kernel teams?

Yes, it is requested by the kernel teams?

>> @@ -28539,6 +28610,43 @@ ix86_output_indirect_jmp (rtx call_op, bool ret_p)
>>      return "%!jmp\t%A0";
>>  }
>>
>> +const char *
>> +ix86_output_function_return (bool long_p)
> Needs function comment.

Will do.

>
>> diff --git a/gcc/config/i386/i386.opt b/gcc/config/i386/i386.opt
>> index f3e43da0aed..2b62363973d 100644
>> --- a/gcc/config/i386/i386.opt
>> +++ b/gcc/config/i386/i386.opt
>> @@ -1026,9 +1026,13 @@ mindirect-branch=
>>  Target Report RejectNegative Joined Enum(indirect_branch) Var(ix86_indirect_branch) Init(indirect_branch_keep)
>>  Convert indirect call and jump.
>>
>> +mfunction-return=
>> +Target Report RejectNegative Joined Enum(indirect_branch) Var(ix86_function_return) Init(indirect_branch_keep)
>> +Convert function return.
> Again, reads poorly.
>

Will update.
diff mbox series

Patch

diff --git a/gcc/config/i386/i386-protos.h b/gcc/config/i386/i386-protos.h
index bf11cc426f9..fb86f00b3a6 100644
--- a/gcc/config/i386/i386-protos.h
+++ b/gcc/config/i386/i386-protos.h
@@ -306,6 +306,7 @@  extern enum attr_cpu ix86_schedule;
 
 extern const char * ix86_output_call_insn (rtx_insn *insn, rtx call_op);
 extern const char * ix86_output_indirect_jmp (rtx call_op, bool ret_p);
+extern const char * ix86_output_function_return (bool long_p);
 extern bool ix86_operands_ok_for_move_multiple (rtx *operands, bool load,
 						machine_mode mode);
 extern int ix86_min_insn_size (rtx_insn *);
diff --git a/gcc/config/i386/i386.c b/gcc/config/i386/i386.c
index f327a6b1b62..b75fc48a4da 100644
--- a/gcc/config/i386/i386.c
+++ b/gcc/config/i386/i386.c
@@ -5833,6 +5833,31 @@  ix86_set_indirect_branch_type (tree fndecl)
       else
 	cfun->machine->indirect_branch_type = ix86_indirect_branch;
     }
+
+  if (cfun->machine->function_return_type == indirect_branch_unset)
+    {
+      tree attr = lookup_attribute ("function_return",
+				    DECL_ATTRIBUTES (fndecl));
+      if (attr != NULL)
+	{
+	  tree args = TREE_VALUE (attr);
+	  if (args == NULL)
+	    gcc_unreachable ();
+	  tree cst = TREE_VALUE (args);
+	  if (strcmp (TREE_STRING_POINTER (cst), "keep") == 0)
+	    cfun->machine->function_return_type = indirect_branch_keep;
+	  else if (strcmp (TREE_STRING_POINTER (cst), "thunk") == 0)
+	    cfun->machine->function_return_type = indirect_branch_thunk;
+	  else if (strcmp (TREE_STRING_POINTER (cst), "thunk-inline") == 0)
+	    cfun->machine->function_return_type = indirect_branch_thunk_inline;
+	  else if (strcmp (TREE_STRING_POINTER (cst), "thunk-extern") == 0)
+	    cfun->machine->function_return_type = indirect_branch_thunk_extern;
+	  else
+	    gcc_unreachable ();
+	}
+      else
+	cfun->machine->function_return_type = ix86_function_return;
+    }
 }
 
 /* Establish appropriate back-end context for processing the function
@@ -10695,8 +10720,12 @@  static int indirect_thunks_bnd_used;
 /* Fills in the label name that should be used for the indirect thunk.  */
 
 static void
-indirect_thunk_name (char name[32], int regno, bool need_bnd_p)
+indirect_thunk_name (char name[32], int regno, bool need_bnd_p,
+		     bool ret_p)
 {
+  if (regno >= 0 && ret_p)
+    gcc_unreachable ();
+
   if (USE_HIDDEN_LINKONCE)
     {
       const char *bnd = need_bnd_p ? "_bnd" : "";
@@ -10711,7 +10740,10 @@  indirect_thunk_name (char name[32], int regno, bool need_bnd_p)
 		   bnd, reg_prefix, reg_names[regno]);
 	}
       else
-	sprintf (name, "__x86_indirect_thunk%s", bnd);
+	{
+	  const char *ret = ret_p ? "return" : "indirect";
+	  sprintf (name, "__x86_%s_thunk%s", ret, bnd);
+	}
     }
   else
     {
@@ -10724,10 +10756,20 @@  indirect_thunk_name (char name[32], int regno, bool need_bnd_p)
 	}
       else
 	{
-	  if (need_bnd_p)
-	    ASM_GENERATE_INTERNAL_LABEL (name, "LITB", 0);
+	  if (ret_p)
+	    {
+	      if (need_bnd_p)
+		ASM_GENERATE_INTERNAL_LABEL (name, "LRTB", 0);
+	      else
+		ASM_GENERATE_INTERNAL_LABEL (name, "LRT", 0);
+	    }
 	  else
-	    ASM_GENERATE_INTERNAL_LABEL (name, "LIT", 0);
+	    {
+	      if (need_bnd_p)
+		ASM_GENERATE_INTERNAL_LABEL (name, "LITB", 0);
+	      else
+		ASM_GENERATE_INTERNAL_LABEL (name, "LIT", 0);
+	    }
 	}
     }
 }
@@ -10808,7 +10850,7 @@  output_indirect_thunk_function (bool need_bnd_p, int regno)
   tree decl;
 
   /* Create __x86_indirect_thunk/__x86_indirect_thunk_bnd.  */
-  indirect_thunk_name (name, regno, need_bnd_p);
+  indirect_thunk_name (name, regno, need_bnd_p, false);
   decl = build_decl (BUILTINS_LOCATION, FUNCTION_DECL,
 		     get_identifier (name),
 		     build_function_type_list (void_type_node, NULL_TREE));
@@ -10851,6 +10893,35 @@  output_indirect_thunk_function (bool need_bnd_p, int regno)
 	ASM_OUTPUT_LABEL (asm_out_file, name);
       }
 
+  if (regno < 0)
+    {
+      /* Create alias for __x86.return_thunk/__x86.return_thunk_bnd.  */
+      char alias[32];
+
+      indirect_thunk_name (alias, regno, need_bnd_p, true);
+      ASM_OUTPUT_DEF (asm_out_file, alias, name);
+#if TARGET_MACHO
+      if (TARGET_MACHO)
+	{
+	  fputs ("\t.weak_definition\t", asm_out_file);
+	  assemble_name (asm_out_file, alias);
+	  fputs ("\n\t.private_extern\t", asm_out_file);
+	  assemble_name (asm_out_file, alias);
+	  putc ('\n', asm_out_file);
+	}
+#else
+      if (USE_HIDDEN_LINKONCE)
+	{
+	  fputs ("\t.globl\t", asm_out_file);
+	  assemble_name (asm_out_file, alias);
+	  putc ('\n', asm_out_file);
+	  fputs ("\t.hidden\t", asm_out_file);
+	  assemble_name (asm_out_file, alias);
+	  putc ('\n', asm_out_file);
+	}
+#endif
+    }
+
   DECL_INITIAL (decl) = make_node (BLOCK);
   current_function_decl = decl;
   allocate_struct_function (decl, false);
@@ -28410,7 +28481,7 @@  ix86_output_indirect_branch (rtx call_op, const char *xasm,
 		indirect_thunk_needed = true;
 	    }
 	}
-      indirect_thunk_name (thunk_name_buf, regno, need_bnd_p);
+      indirect_thunk_name (thunk_name_buf, regno, need_bnd_p, false);
       thunk_name = thunk_name_buf;
     }
   else
@@ -28539,6 +28610,43 @@  ix86_output_indirect_jmp (rtx call_op, bool ret_p)
     return "%!jmp\t%A0";
 }
 
+const char *
+ix86_output_function_return (bool long_p)
+{
+  if (cfun->machine->function_return_type != indirect_branch_keep)
+    {
+      char thunk_name[32];
+      bool need_bnd_p = ix86_bnd_prefixed_insn_p (current_output_insn);
+
+      if (cfun->machine->function_return_type
+	  != indirect_branch_thunk_inline)
+	{
+	  bool need_thunk = (cfun->machine->function_return_type
+			     == indirect_branch_thunk);
+	  indirect_thunk_name (thunk_name, -1, need_bnd_p, true);
+	  if (need_bnd_p)
+	    {
+	      indirect_thunk_bnd_needed |= need_thunk;
+	      fprintf (asm_out_file, "\tbnd jmp\t%s\n", thunk_name);
+	    }
+	  else
+	    {
+	      indirect_thunk_needed |= need_thunk;
+	      fprintf (asm_out_file, "\tjmp\t%s\n", thunk_name);
+	    }
+	}
+      else
+	output_indirect_thunk (need_bnd_p, -1);
+
+      return "";
+    }
+
+  if (!long_p || ix86_bnd_prefixed_insn_p (current_output_insn))
+    return "%!ret";
+
+  return "rep%; ret";
+}
+
 /* Output the assembly for a call instruction.  */
 
 const char *
@@ -40849,6 +40957,28 @@  ix86_handle_fndecl_attribute (tree *node, tree name, tree args, int,
 	}
     }
 
+  if (is_attribute_p ("function_return", name))
+    {
+      tree cst = TREE_VALUE (args);
+      if (TREE_CODE (cst) != STRING_CST)
+	{
+	  warning (OPT_Wattributes,
+		   "%qE attribute requires a string constant argument",
+		   name);
+	  *no_add_attrs = true;
+	}
+      else if (strcmp (TREE_STRING_POINTER (cst), "keep") != 0
+	       && strcmp (TREE_STRING_POINTER (cst), "thunk") != 0
+	       && strcmp (TREE_STRING_POINTER (cst), "thunk-inline") != 0
+	       && strcmp (TREE_STRING_POINTER (cst), "thunk-extern") != 0)
+	{
+	  warning (OPT_Wattributes,
+		   "argument to %qE attribute is not "
+		   "(keep|thunk|thunk-inline|thunk-extern)", name);
+	  *no_add_attrs = true;
+	}
+    }
+
   return NULL_TREE;
 }
 
@@ -45263,6 +45393,8 @@  static const struct attribute_spec ix86_attribute_table[] =
     ix86_handle_fndecl_attribute, NULL },
   { "indirect_branch", 1, 1, true, false, false, false,
     ix86_handle_fndecl_attribute, NULL },
+  { "function_return", 1, 1, true, false, false, false,
+    ix86_handle_fndecl_attribute, NULL },
 
   /* End element.  */
   { NULL, 0, 0, false, false, false, false, NULL, NULL }
diff --git a/gcc/config/i386/i386.h b/gcc/config/i386/i386.h
index 51a920298a4..05cc9647755 100644
--- a/gcc/config/i386/i386.h
+++ b/gcc/config/i386/i386.h
@@ -2575,6 +2575,9 @@  struct GTY(()) machine_function {
      "indirect_jump" or "tablejump".  */
   BOOL_BITFIELD has_local_indirect_jump : 1;
 
+  /* How to generate function return.  */
+  ENUM_BITFIELD(indirect_branch) function_return_type : 3;
+
   /* If true, the current function is a function specified with
      the "interrupt" or "no_caller_saved_registers" attribute.  */
   BOOL_BITFIELD no_caller_saved_registers : 1;
diff --git a/gcc/config/i386/i386.md b/gcc/config/i386/i386.md
index a7573c468ae..6c832a867c8 100644
--- a/gcc/config/i386/i386.md
+++ b/gcc/config/i386/i386.md
@@ -13050,7 +13050,7 @@ 
 (define_insn "simple_return_internal"
   [(simple_return)]
   "reload_completed"
-  "%!ret"
+  "* return ix86_output_function_return (false);"
   [(set_attr "length" "1")
    (set_attr "atom_unit" "jeu")
    (set_attr "length_immediate" "0")
@@ -13072,12 +13072,7 @@ 
   [(simple_return)
    (unspec [(const_int 0)] UNSPEC_REP)]
   "reload_completed"
-{
-  if (ix86_bnd_prefixed_insn_p (insn))
-    return "%!ret";
-
-  return "rep%; ret";
-}
+  "* return ix86_output_function_return (true);"
   [(set_attr "length" "2")
    (set_attr "atom_unit" "jeu")
    (set_attr "length_immediate" "0")
diff --git a/gcc/config/i386/i386.opt b/gcc/config/i386/i386.opt
index f3e43da0aed..2b62363973d 100644
--- a/gcc/config/i386/i386.opt
+++ b/gcc/config/i386/i386.opt
@@ -1026,9 +1026,13 @@  mindirect-branch=
 Target Report RejectNegative Joined Enum(indirect_branch) Var(ix86_indirect_branch) Init(indirect_branch_keep)
 Convert indirect call and jump.
 
+mfunction-return=
+Target Report RejectNegative Joined Enum(indirect_branch) Var(ix86_function_return) Init(indirect_branch_keep)
+Convert function return.
+
 Enum
 Name(indirect_branch) Type(enum indirect_branch)
-Known indirect branch choices (for use with the -mindirect-branch= option):
+Known indirect branch choices (for use with the -mindirect-branch=/-mfunction-return= options):
 
 EnumValue
 Enum(indirect_branch) String(keep) Value(indirect_branch_keep)
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 6e48d4108a2..7690a2bdb7d 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -5745,6 +5745,15 @@  indirect call and jump to inlined call and return thunk.
 @samp{thunk-extern} converts indirect call and jump to external call
 and return thunk provided in a separate object file.
 
+@item function_return("@var{choice}")
+@cindex @code{function_return} function attribute, x86
+On x86 targets, the @code{function_return} attribute causes the compiler
+to convert function return with @var{choice}.  @samp{keep} keeps function
+return unmodified.  @samp{thunk} converts function return to call and
+return thunk.  @samp{thunk-inline} converts function return to inlined
+call and return thunk.  @samp{thunk-extern} converts function return to
+external call and return thunk provided in a separate object file.
+
 @item nocf_check
 @cindex @code{nocf_check} function attribute
 The @code{nocf_check} attribute on a function is used to inform the
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index ee5248aba59..fa72a929751 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -1227,7 +1227,8 @@  See RS/6000 and PowerPC Options.
 -mstack-protector-guard-offset=@var{offset} @gol
 -mstack-protector-guard-symbol=@var{symbol} -mmitigate-rop @gol
 -mgeneral-regs-only -mcall-ms2sysv-xlogues @gol
--mindirect-branch=@var{choice} -mindirect-branch-loop=@var{choice}}
+-mindirect-branch=@var{choice} -mindirect-branch-loop=@var{choice}
+-mfunction-return==@var{choice}}
 
 @emph{x86 Windows Options}
 @gccoptlist{-mconsole  -mcygwin  -mno-cygwin  -mdll @gol
@@ -26776,6 +26777,17 @@  to external call and return thunk provided in a separate object file.
 You can control this behavior for a specific function by using the
 function attribute @code{indirect_branch}.  @xref{Function Attributes}.
 
+@item -mfunction-return=@var{choice}
+@opindex -mfunction-return
+Convert function return with @var{choice}.  The default is @samp{keep},
+which keeps function return unmodified.  @samp{thunk} converts function
+return to call and return thunk.  @samp{thunk-inline} converts function
+return to inlined call and return thunk.  @samp{thunk-extern} converts
+function return to external call and return thunk provided in a separate
+object file.  You can control this behavior for a specific function by
+using the function attribute @code{function_return}.
+@xref{Function Attributes}.
+
 @item -mindirect-branch-loop=@var{choice}
 @opindex -mindirect-branch-boop
 Control loop filler in call and return thunk for indirect call and jump.
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-1.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-1.c
index 08827448325..bd29d466f5c 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-1.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-1.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -mindirect-branch=thunk -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk -fno-pic" } */
 
 typedef void (*dispatch_t)(long offset);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-2.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-2.c
index 1344b6bc0e9..cf6a6736524 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-2.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-2.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -mindirect-branch=thunk -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk -fno-pic" } */
 
 typedef void (*dispatch_t)(long offset);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-3.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-3.c
index dcc9ef75df6..46925434933 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-3.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-3.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -mindirect-branch=thunk -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk -fno-pic" } */
 
 typedef void (*dispatch_t)(long offset);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-4.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-4.c
index 2502860b6e6..ee95d21c08e 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-4.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-4.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -mindirect-branch=thunk -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk -fno-pic" } */
 
 typedef void (*dispatch_t)(long offset);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-5.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-5.c
index 58c81d16316..7ea4af5bb71 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-5.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-5.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile { target *-*-linux* } } */
-/* { dg-options "-O2 -fpic -fno-plt -mindirect-branch=thunk" } */
+/* { dg-options "-O2 -mfunction-return=keep -fpic -fno-plt -mindirect-branch=thunk" } */
 
 extern void bar (void);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-6.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-6.c
index b4c528a5b30..cbf2159ce50 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-6.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-6.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile { target *-*-linux* } } */
-/* { dg-options "-O2 -fpic -fno-plt -mindirect-branch=thunk" } */
+/* { dg-options "-O2 -mfunction-return=keep -fpic -fno-plt -mindirect-branch=thunk" } */
 
 extern void bar (void);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-7.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-7.c
index 4553ef0b622..44130a4175d 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-7.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-7.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -mindirect-branch=thunk -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk -fno-pic" } */
 
 void func0 (void);
 void func1 (void);
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-1.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-1.c
index b8e9851c76f..2e3e3a5ca8b 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-1.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-1.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -fno-pic" } */
 
 typedef void (*dispatch_t)(long offset);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-2.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-2.c
index 1d6d18c2aba..c88fd33c494 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-2.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-2.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -fno-pic" } */
 
 typedef void (*dispatch_t)(long offset);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-3.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-3.c
index af167840b81..21b69728796 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-3.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-3.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -fno-pic" } */
 
 typedef void (*dispatch_t)(long offset);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-4.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-4.c
index 146124894a0..0bd6aab2fd6 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-4.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-4.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -fno-pic" } */
 
 typedef void (*dispatch_t)(long offset);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-5.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-5.c
index 0833606046b..98785a38248 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-5.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-5.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -fno-pic" } */
 
 typedef void (*dispatch_t)(long offset);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-6.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-6.c
index 2eba0fbd9b2..a498a39e404 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-6.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-6.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -fno-pic" } */
 
 typedef void (*dispatch_t)(long offset);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-7.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-7.c
index f58427eae11..66f295d1eb6 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-7.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-7.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -fno-pic" } */
 
 void func0 (void);
 void func1 (void);
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-8.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-8.c
index 6960fa0bbfb..c246f974610 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-8.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-8.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -mindirect-branch=thunk -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk -fno-pic" } */
 
 void func0 (void);
 void func1 (void);
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-1.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-1.c
index 21b25ec5bbf..154eb7adfcd 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-1.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-1.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile { target { ! x32 } } } */
-/* { dg-options "-O2 -mindirect-branch=thunk -fcheck-pointer-bounds -mmpx -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk -fcheck-pointer-bounds -mmpx -fno-pic" } */
 
 void (*dispatch) (char *);
 char buf[10];
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-2.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-2.c
index 7bf7e6a1095..62d9831af27 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-2.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-2.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile { target { ! x32 } } } */
-/* { dg-options "-O2 -mindirect-branch=thunk -fcheck-pointer-bounds -mmpx -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk -fcheck-pointer-bounds -mmpx -fno-pic" } */
 
 void (*dispatch) (char *);
 char buf[10];
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-3.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-3.c
index 14c60f232db..b5a601a4da3 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-3.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-3.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile { target { *-*-linux* && { ! x32 } } } } */
-/* { dg-options "-O2 -mindirect-branch=thunk -fcheck-pointer-bounds -mmpx -fpic -fno-plt" } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk -fcheck-pointer-bounds -mmpx -fpic -fno-plt" } */
 
 void bar (char *);
 char buf[10];
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-4.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-4.c
index 4fd6f360801..c36a821c8e5 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-4.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-4.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile { target { *-*-linux* && { ! x32 } } } } */
-/* { dg-options "-O2 -mindirect-branch=thunk -fcheck-pointer-bounds -mmpx -fpic -fno-plt" } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk -fcheck-pointer-bounds -mmpx -fpic -fno-plt" } */
 
 void bar (char *);
 char buf[10];
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-1.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-1.c
index 49f27b49465..637fc3d3f4e 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-1.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-1.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -mindirect-branch=thunk-extern -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk-extern -fno-pic" } */
 
 typedef void (*dispatch_t)(long offset);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-2.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-2.c
index a1e3eb6fc74..ff9efe03fe6 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-2.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-2.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -mindirect-branch=thunk-extern -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk-extern -fno-pic" } */
 
 typedef void (*dispatch_t)(long offset);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-3.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-3.c
index 395634e7e5c..2686a5f2db4 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-3.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-3.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -mindirect-branch=thunk-extern -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk-extern -fno-pic" } */
 
 typedef void (*dispatch_t)(long offset);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-4.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-4.c
index fd3f63379a1..f07f6b214ad 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-4.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-4.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -mindirect-branch=thunk-extern -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk-extern -fno-pic" } */
 
 typedef void (*dispatch_t)(long offset);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-5.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-5.c
index ba2f92b6f34..21740ac5b7f 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-5.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-5.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile { target *-*-linux* } } */
-/* { dg-options "-O2 -fpic -fno-plt -mindirect-branch=thunk-extern" } */
+/* { dg-options "-O2 -mfunction-return=keep -fpic -fno-plt -mindirect-branch=thunk-extern" } */
 
 extern void bar (void);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-6.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-6.c
index 0c5a2d472c6..a77c1f470b8 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-6.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-6.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile { target *-*-linux* } } */
-/* { dg-options "-O2 -fpic -fno-plt -mindirect-branch=thunk-extern" } */
+/* { dg-options "-O2 -mfunction-return=keep -fpic -fno-plt -mindirect-branch=thunk-extern" } */
 
 extern void bar (void);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-7.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-7.c
index 665252327aa..e64910fd4aa 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-7.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-7.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -mindirect-branch=thunk-extern -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk-extern -fno-pic" } */
 
 void func0 (void);
 void func1 (void);
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-1.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-1.c
index 3ace8d1b031..efa0096e1e0 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-1.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-1.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -mindirect-branch=thunk-inline -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk-inline -fno-pic" } */
 
 typedef void (*dispatch_t)(long offset);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-2.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-2.c
index 6c97b96f1f2..775d0b8c53e 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-2.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-2.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -mindirect-branch=thunk-inline -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk-inline -fno-pic" } */
 
 typedef void (*dispatch_t)(long offset);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-3.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-3.c
index 8f6759cbf06..788271f049f 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-3.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-3.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -mindirect-branch=thunk-inline -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk-inline -fno-pic" } */
 
 typedef void (*dispatch_t)(long offset);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-4.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-4.c
index b07d08cab0f..ef8a2c746a7 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-4.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-4.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -mindirect-branch=thunk-inline -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk-inline -fno-pic" } */
 
 typedef void (*dispatch_t)(long offset);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-5.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-5.c
index 10794886b1b..848ceefca02 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-5.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-5.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile { target *-*-linux* } } */
-/* { dg-options "-O2 -fpic -fno-plt -mindirect-branch=thunk-inline" } */
+/* { dg-options "-O2 -mfunction-return=keep -fpic -fno-plt -mindirect-branch=thunk-inline" } */
 
 extern void bar (void);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-6.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-6.c
index a26ec4b06ed..64608100782 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-6.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-6.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile { target *-*-linux* } } */
-/* { dg-options "-O2 -fpic -fno-plt -mindirect-branch=thunk-inline" } */
+/* { dg-options "-O2 -mfunction-return=keep -fpic -fno-plt -mindirect-branch=thunk-inline" } */
 
 extern void bar (void);
 
diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-7.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-7.c
index 77253af17c6..3c2758360f5 100644
--- a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-7.c
+++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-7.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-O2 -mindirect-branch=thunk-inline -fno-pic" } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk-inline -fno-pic" } */
 
 void func0 (void);
 void func1 (void);
diff --git a/gcc/testsuite/gcc.target/i386/ret-thunk-1.c b/gcc/testsuite/gcc.target/i386/ret-thunk-1.c
new file mode 100644
index 00000000000..07f382c21b2
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/ret-thunk-1.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mfunction-return=thunk" } */
+
+void
+foo (void)
+{
+}
+
+/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_return_thunk" } } */
+/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler {\tlfence} } } */
diff --git a/gcc/testsuite/gcc.target/i386/ret-thunk-10.c b/gcc/testsuite/gcc.target/i386/ret-thunk-10.c
new file mode 100644
index 00000000000..d7313d631aa
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/ret-thunk-10.c
@@ -0,0 +1,22 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mfunction-return=thunk-inline -mindirect-branch=thunk -fno-pic" } */
+
+extern void (*bar) (void);
+
+int
+foo (void)
+{
+  bar ();
+  return 0;
+}
+
+/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler-not "jmp\[ \t\]*__x86_return_thunk" } } */
+/* { dg-final { scan-assembler-times {\tlfence} 2 } } */
+/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?bar" { target { ! x32 } } } } */
+/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" { target { ! x32 } }  } } */
+/* { dg-final { scan-assembler "__x86_indirect_thunk:" { target { ! x32 } }  } } */
+/* { dg-final { scan-assembler "call\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target { x32 } }  } } */
+/* { dg-final { scan-assembler "__x86_indirect_thunk_(r|e)ax:" { target { x32 } }  } } */
+/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" { target x32 } } } */
diff --git a/gcc/testsuite/gcc.target/i386/ret-thunk-11.c b/gcc/testsuite/gcc.target/i386/ret-thunk-11.c
new file mode 100644
index 00000000000..66efd2adfd3
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/ret-thunk-11.c
@@ -0,0 +1,22 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mfunction-return=thunk-extern -mindirect-branch=thunk -fno-pic" } */
+
+extern void (*bar) (void);
+
+int
+foo (void)
+{
+  bar ();
+  return 0;
+}
+
+/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_return_thunk" } } */
+/* { dg-final { scan-assembler-times {\tlfence} 1 } } */
+/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?bar" { target { ! x32 } } } } */
+/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" { target { ! x32 } } } } */
+/* { dg-final { scan-assembler "__x86_indirect_thunk:" { target { ! x32 } }  } } */
+/* { dg-final { scan-assembler "call\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target { x32 } }  } } */
+/* { dg-final { scan-assembler "__x86_indirect_thunk_(r|e)ax:" { target { x32 } }  } } */
+/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" { target x32 } } } */
diff --git a/gcc/testsuite/gcc.target/i386/ret-thunk-12.c b/gcc/testsuite/gcc.target/i386/ret-thunk-12.c
new file mode 100644
index 00000000000..b5306581e91
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/ret-thunk-12.c
@@ -0,0 +1,21 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk -fno-pic" } */
+
+extern void (*bar) (void);
+
+int
+foo (void)
+{
+  bar ();
+  return 0;
+}
+
+/* { dg-final { scan-assembler-not "jmp\[ \t\]*__x86_return_thunk" } } */
+/* { dg-final { scan-assembler-times {\tlfence} 1 } } */
+/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" { target { ! x32 } } } } */
+/* { dg-final { scan-assembler "__x86_indirect_thunk:" { target { ! x32 } }  } } */
+/* { dg-final { scan-assembler "call\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target { x32 } }  } } */
+/* { dg-final { scan-assembler "__x86_indirect_thunk_(r|e)ax:" { target { x32 } }  } } */
+/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" { target x32 } } } */
diff --git a/gcc/testsuite/gcc.target/i386/ret-thunk-13.c b/gcc/testsuite/gcc.target/i386/ret-thunk-13.c
new file mode 100644
index 00000000000..3759246d7ff
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/ret-thunk-13.c
@@ -0,0 +1,21 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk-inline -fno-pic" } */
+
+extern void (*bar) (void);
+extern int foo (void) __attribute__ ((function_return("thunk")));
+
+int
+foo (void)
+{
+  bar ();
+  return 0;
+}
+
+/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_return_thunk" } } */
+/* { dg-final { scan-assembler-times {\tlfence} 2 } } */
+/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?bar" { target { ! x32 } } } } */
+/* { dg-final { scan-assembler-times "jmp\[ \t\]*\.LIND" 3 } } */
+/* { dg-final { scan-assembler-times "call\[ \t\]*\.LIND" 3 } } */
+/* { dg-final { scan-assembler-not "jmp\[ \t\]*__x86_indirect_thunk" } } */
+/* { dg-final { scan-assembler-not "call\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target { x32 } }  } } */
+/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" { target x32 } } } */
diff --git a/gcc/testsuite/gcc.target/i386/ret-thunk-14.c b/gcc/testsuite/gcc.target/i386/ret-thunk-14.c
new file mode 100644
index 00000000000..6827fa250aa
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/ret-thunk-14.c
@@ -0,0 +1,21 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=thunk-extern -fno-pic" } */
+
+extern void (*bar) (void);
+
+__attribute__ ((function_return("thunk-inline")))
+int
+foo (void)
+{
+  bar ();
+  return 0;
+}
+
+/* { dg-final { scan-assembler-times {\tlfence} 1 } } */
+/* { dg-final { scan-assembler-not "jmp\[ \t\]*__x86_return_thunk" } } */
+/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?bar" { target { ! x32 } } } } */
+/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" { target { ! x32 } } } } */
+/* { dg-final { scan-assembler "call\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target { x32 } }  } } */
+/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" { target x32 } } } */
diff --git a/gcc/testsuite/gcc.target/i386/ret-thunk-15.c b/gcc/testsuite/gcc.target/i386/ret-thunk-15.c
new file mode 100644
index 00000000000..0437ca2453b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/ret-thunk-15.c
@@ -0,0 +1,21 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mfunction-return=keep -mindirect-branch=keep -fno-pic" } */
+
+extern void (*bar) (void);
+
+__attribute__ ((function_return("thunk-extern"), indirect_branch("thunk")))
+int
+foo (void)
+{
+  bar ();
+  return 0;
+}
+
+/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_return_thunk" } } */
+/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler-times {\tlfence} 1 } } */
+/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?bar" { target { ! x32 } } } } */
+/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" { target { ! x32 } } } } */
+/* { dg-final { scan-assembler "call\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target x32 } } } */
+/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" { target x32 } } } */
diff --git a/gcc/testsuite/gcc.target/i386/ret-thunk-16.c b/gcc/testsuite/gcc.target/i386/ret-thunk-16.c
new file mode 100644
index 00000000000..173fe243d7b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/ret-thunk-16.c
@@ -0,0 +1,18 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mfunction-return=thunk-inline -mindirect-branch=thunk-extern -fno-pic" } */
+
+extern void (*bar) (void);
+
+__attribute__ ((function_return("keep"), indirect_branch("keep")))
+int
+foo (void)
+{
+  bar ();
+  return 0;
+}
+
+/* { dg-final { scan-assembler-not "__x86_indirect_thunk" } } */
+/* { dg-final { scan-assembler-not "__x86_return_thunk" } } */
+/* { dg-final { scan-assembler-not {\tlfence} } } */
+/* { dg-final { scan-assembler-not "jmp\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler-not "call\[ \t\]*\.LIND" } } */
diff --git a/gcc/testsuite/gcc.target/i386/ret-thunk-2.c b/gcc/testsuite/gcc.target/i386/ret-thunk-2.c
new file mode 100644
index 00000000000..5516813a290
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/ret-thunk-2.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mfunction-return=thunk-inline" } */
+
+void
+foo (void)
+{
+}
+
+/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler {\tlfence} } } */
+/* { dg-final { scan-assembler-not "jmp\[ \t\]*__x86_return_thunk" } } */
diff --git a/gcc/testsuite/gcc.target/i386/ret-thunk-3.c b/gcc/testsuite/gcc.target/i386/ret-thunk-3.c
new file mode 100644
index 00000000000..9f1ade857ef
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/ret-thunk-3.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mfunction-return=thunk-extern" } */
+
+void
+foo (void)
+{
+}
+
+/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_return_thunk" } } */
+/* { dg-final { scan-assembler-not {\tlfence} } } */
+/* { dg-final { scan-assembler-not "jmp\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler-not "call\[ \t\]*\.LIND" } } */
diff --git a/gcc/testsuite/gcc.target/i386/ret-thunk-4.c b/gcc/testsuite/gcc.target/i386/ret-thunk-4.c
new file mode 100644
index 00000000000..abecde0a550
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/ret-thunk-4.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mfunction-return=keep" } */
+
+void
+foo (void)
+{
+}
+
+/* { dg-final { scan-assembler-not "jmp\[ \t\]*__x86_return_thunk" } } */
+/* { dg-final { scan-assembler-not {\tlfence} } } */
+/* { dg-final { scan-assembler-not "jmp\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler-not "call\[ \t\]*\.LIND" } } */
diff --git a/gcc/testsuite/gcc.target/i386/ret-thunk-5.c b/gcc/testsuite/gcc.target/i386/ret-thunk-5.c
new file mode 100644
index 00000000000..3b51a9931db
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/ret-thunk-5.c
@@ -0,0 +1,14 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mfunction-return=keep" } */
+
+extern void foo (void) __attribute__ ((function_return("thunk")));
+
+void
+foo (void)
+{
+}
+
+/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_return_thunk" } } */
+/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler {\tlfence} } } */
diff --git a/gcc/testsuite/gcc.target/i386/ret-thunk-6.c b/gcc/testsuite/gcc.target/i386/ret-thunk-6.c
new file mode 100644
index 00000000000..52160e0ee77
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/ret-thunk-6.c
@@ -0,0 +1,13 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mfunction-return=keep" } */
+
+__attribute__ ((function_return("thunk-inline")))
+void
+foo (void)
+{
+}
+
+/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler {\tlfence} } } */
+/* { dg-final { scan-assembler-not "jmp\[ \t\]*__x86_return_thunk" } } */
diff --git a/gcc/testsuite/gcc.target/i386/ret-thunk-7.c b/gcc/testsuite/gcc.target/i386/ret-thunk-7.c
new file mode 100644
index 00000000000..65819c2ab76
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/ret-thunk-7.c
@@ -0,0 +1,13 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mfunction-return=keep" } */
+
+__attribute__ ((function_return("thunk-extern")))
+void
+foo (void)
+{
+}
+
+/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_return_thunk" } } */
+/* { dg-final { scan-assembler-not {\tlfence} } } */
+/* { dg-final { scan-assembler-not "jmp\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler-not "call\[ \t\]*\.LIND" } } */
diff --git a/gcc/testsuite/gcc.target/i386/ret-thunk-8.c b/gcc/testsuite/gcc.target/i386/ret-thunk-8.c
new file mode 100644
index 00000000000..a6a1bbc054b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/ret-thunk-8.c
@@ -0,0 +1,14 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mfunction-return=thunk-inline" } */
+
+extern void foo (void) __attribute__ ((function_return("keep")));
+
+void
+foo (void)
+{
+}
+
+/* { dg-final { scan-assembler-not "jmp\[ \t\]*__x86_return_thunk" } } */
+/* { dg-final { scan-assembler-not {\tlfence} } } */
+/* { dg-final { scan-assembler-not "jmp\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler-not "call\[ \t\]*\.LIND" } } */
diff --git a/gcc/testsuite/gcc.target/i386/ret-thunk-9.c b/gcc/testsuite/gcc.target/i386/ret-thunk-9.c
new file mode 100644
index 00000000000..adf83df4776
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/ret-thunk-9.c
@@ -0,0 +1,23 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -mfunction-return=thunk -mindirect-branch=thunk -fno-pic" } */
+
+extern void (*bar) (void);
+
+int
+foo (void)
+{
+  bar ();
+  return 0;
+}
+
+/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_return_thunk" } } */
+/* { dg-final { scan-assembler-not "__x86_return_thunk:" } } */
+/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */
+/* { dg-final { scan-assembler "__x86_indirect_thunk:" } } */
+/* { dg-final { scan-assembler-times {\tlfence} 1 { target { ! x32 } } } } */
+/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?bar" { target { ! x32 } } } } */
+/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" { target { ! x32 } } } } */
+/* { dg-final { scan-assembler-times {\tlfence} 2 { target { x32 } } } } */
+/* { dg-final { scan-assembler "call\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target { x32 } } } } */
+/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" { target x32 } } } */