PowerPC64 ld --no-power10-stubs

Message ID 20200710015555.GE25751@bubble.grove.modra.org
State New
Headers show
Series
  • PowerPC64 ld --no-power10-stubs
Related show

Commit Message

Alan Modra via Binutils July 10, 2020, 1:55 a.m.
Needed for libraries that use ifuncs or other means to support
cpu-optimized versions of functions, some power10, some not, and those
functions make calls using linkage stubs.

bfd/
	* elf64-ppc.h (struct ppc64_elf_params): Add power10_stubs.
	* elf64-ppc.c (struct ppc_link_hash_table): Delete
	power10_stubs.
	(ppc64_elf_check_relocs): Adjust setting of power10_stubs.
	(plt_stub_size, ppc_build_one_stub, ppc_size_one_stub): Adjust
	uses of power10_stubs.
ld/
	* emultempl/ppc64elf.em (params): Init new field.
	(enum ppc64_opt): Add OPTION_POWER10_STUBS and OPTION_NO_POWER10_STUBS.
	(PARSE_AND_LIST_LONGOPTS): Support --power10-stubs and
	--no-power10-stubs.
	(PARSE_AND_LIST_OPTIONS, PARSE_AND_LIST_ARGS_CASES): Likewise.
	* testsuite/ld-powerpc/callstub-3.d: New test.
	* testsuite/ld-powerpc/powerpc.exp: Run it.


-- 
Alan Modra
Australia Development Lab, IBM

Comments

Fangrui Song July 10, 2020, 4:23 a.m. | #1
On 2020-07-10, Alan Modra via Binutils wrote:
>Needed for libraries that use ifuncs or other means to support

>cpu-optimized versions of functions, some power10, some not, and those

>functions make calls using linkage stubs.

>

>bfd/

>	* elf64-ppc.h (struct ppc64_elf_params): Add power10_stubs.

>	* elf64-ppc.c (struct ppc_link_hash_table): Delete

>	power10_stubs.

>	(ppc64_elf_check_relocs): Adjust setting of power10_stubs.

>	(plt_stub_size, ppc_build_one_stub, ppc_size_one_stub): Adjust

>	uses of power10_stubs.

>ld/

>	* emultempl/ppc64elf.em (params): Init new field.

>	(enum ppc64_opt): Add OPTION_POWER10_STUBS and OPTION_NO_POWER10_STUBS.

>	(PARSE_AND_LIST_LONGOPTS): Support --power10-stubs and

>	--no-power10-stubs.

>	(PARSE_AND_LIST_OPTIONS, PARSE_AND_LIST_ARGS_CASES): Likewise.

>	* testsuite/ld-powerpc/callstub-3.d: New test.

>	* testsuite/ld-powerpc/powerpc.exp: Run it.

>

>diff --git a/bfd/elf64-ppc.c b/bfd/elf64-ppc.c

>index 8d710848ba..e54f561019 100644

>--- a/bfd/elf64-ppc.c

>+++ b/bfd/elf64-ppc.c

>@@ -3245,9 +3245,6 @@ struct ppc_link_hash_table

>   /* Whether calls are made via the PLT from NOTOC functions.  */

>   unsigned int notoc_plt:1;

>

>-  /* Whether to use power10 instructions in linkage stubs.  */

>-  unsigned int power10_stubs:1;

>-

>   /* Incremented every time we size stubs.  */

>   unsigned int stub_iteration;

>

>@@ -4602,7 +4599,8 @@ ppc64_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,

> 	case R_PPC64_PLT_PCREL34:

> 	case R_PPC64_PLT_PCREL34_NOTOC:

> 	case R_PPC64_PCREL28:

>-	  htab->power10_stubs = 1;

>+	  if (htab->params->power10_stubs < 0)

>+	    htab->params->power10_stubs = 1;

> 	  break;

> 	default:

> 	  break;

>@@ -10763,7 +10761,7 @@ plt_stub_size (struct ppc_link_hash_table *htab,

>

>   if (stub_entry->stub_type >= ppc_stub_plt_call_notoc)

>     {

>-      if (htab->power10_stubs)

>+      if (htab->params->power10_stubs > 0)

> 	{

> 	  bfd_vma start = (stub_entry->stub_offset

> 			   + stub_entry->group->stub_sec->output_offset

>@@ -11604,7 +11602,7 @@ ppc_build_one_stub (struct bfd_hash_entry *gen_entry, void *in_arg)

>

>       relp = p;

>       num_rel = 0;

>-      if (htab->power10_stubs)

>+      if (htab->params->power10_stubs > 0)

> 	{

> 	  bfd_boolean load = stub_entry->stub_type >= ppc_stub_plt_call_notoc;

> 	  p = build_power10_offset (htab->params->stub_bfd, p, off, odd, load);

>@@ -11643,7 +11641,7 @@ ppc_build_one_stub (struct bfd_hash_entry *gen_entry, void *in_arg)

>       if (info->emitrelocations)

> 	{

> 	  bfd_vma roff = relp - stub_entry->group->stub_sec->contents;

>-	  if (htab->power10_stubs)

>+	  if (htab->params->power10_stubs > 0)

> 	    num_rel += num_relocs_for_power10_offset (off, odd);

> 	  else

> 	    {

>@@ -11653,7 +11651,7 @@ ppc_build_one_stub (struct bfd_hash_entry *gen_entry, void *in_arg)

> 	  r = get_relocs (stub_entry->group->stub_sec, num_rel);

> 	  if (r == NULL)

> 	    return FALSE;

>-	  if (htab->power10_stubs)

>+	  if (htab->params->power10_stubs > 0)

> 	    r = emit_relocs_for_power10_offset (info, r, roff, targ, off, odd);

> 	  else

> 	    r = emit_relocs_for_offset (info, r, roff, targ, off);

>@@ -11671,7 +11669,7 @@ ppc_build_one_stub (struct bfd_hash_entry *gen_entry, void *in_arg)

> 	    }

> 	}

>

>-      if (!htab->power10_stubs

>+      if (htab->params->power10_stubs <= 0

> 	  && htab->glink_eh_frame != NULL

> 	  && htab->glink_eh_frame->size != 0)

> 	{

>@@ -12019,7 +12017,7 @@ ppc_size_one_stub (struct bfd_hash_entry *gen_entry, void *in_arg)

>       if (info->emitrelocations)

> 	{

> 	  unsigned int num_rel;

>-	  if (htab->power10_stubs)

>+	  if (htab->params->power10_stubs > 0)

> 	    num_rel = num_relocs_for_power10_offset (off, odd);

> 	  else

> 	    num_rel = num_relocs_for_offset (off - 8);

>@@ -12027,7 +12025,7 @@ ppc_size_one_stub (struct bfd_hash_entry *gen_entry, void *in_arg)

> 	  stub_entry->group->stub_sec->flags |= SEC_RELOC;

> 	}

>

>-      if (htab->power10_stubs)

>+      if (htab->params->power10_stubs > 0)

> 	extra = size_power10_offset (off, odd);

>       else

> 	extra = size_offset (off - 8);

>@@ -12038,7 +12036,7 @@ ppc_size_one_stub (struct bfd_hash_entry *gen_entry, void *in_arg)

> 	 calculated.  */

>       off -= extra;

>

>-      if (!htab->power10_stubs)

>+      if (htab->params->power10_stubs <= 0)

> 	{

> 	  /* After the bcl, lr has been modified so we need to emit

> 	     .eh_frame info saying the return address is in r12.  */

>@@ -12101,7 +12099,7 @@ ppc_size_one_stub (struct bfd_hash_entry *gen_entry, void *in_arg)

>       if (info->emitrelocations)

> 	{

> 	  unsigned int num_rel;

>-	  if (htab->power10_stubs)

>+	  if (htab->params->power10_stubs > 0)

> 	    num_rel = num_relocs_for_power10_offset (off, odd);

> 	  else

> 	    num_rel = num_relocs_for_offset (off - 8);

>@@ -12111,7 +12109,7 @@ ppc_size_one_stub (struct bfd_hash_entry *gen_entry, void *in_arg)

>

>       size = plt_stub_size (htab, stub_entry, off);

>

>-      if (!htab->power10_stubs)

>+      if (htab->params->power10_stubs <= 0)

> 	{

> 	  /* After the bcl, lr has been modified so we need to emit

> 	     .eh_frame info saying the return address is in r12.  */

>diff --git a/bfd/elf64-ppc.h b/bfd/elf64-ppc.h

>index a2ffd4eb72..547971f8be 100644

>--- a/bfd/elf64-ppc.h

>+++ b/bfd/elf64-ppc.h

>@@ -54,6 +54,9 @@ struct ppc64_elf_params

>   /* Set if PLT call stubs for localentry:0 functions should omit r2 save.  */

>   int plt_localentry0;

>

>+  /* Whether to use power10 instructions in linkage stubs.  */

>+  int power10_stubs;

>+

>   /* Whether to canonicalize .opd so that there are no overlapping

>      .opd entries.  */

>   int non_overlapping_opd;

>diff --git a/ld/ChangeLog b/ld/ChangeLog

>index 14f31a805f..b9c844ced3 100644

>--- a/ld/ChangeLog

>+++ b/ld/ChangeLog

>@@ -1,3 +1,13 @@

>+2020-07-10  Alan Modra  <amodra@gmail.com>

>+

>+	* emultempl/ppc64elf.em (params): Init new field.

>+	(enum ppc64_opt): Add OPTION_POWER10_STUBS and OPTION_NO_POWER10_STUBS.

>+	(PARSE_AND_LIST_LONGOPTS): Support --power10-stubs and

>+	--no-power10-stubs.

>+	(PARSE_AND_LIST_OPTIONS, PARSE_AND_LIST_ARGS_CASES): Likewise.

>+	* testsuite/ld-powerpc/callstub-3.d: New test.

>+	* testsuite/ld-powerpc/powerpc.exp: Run it.

>+

> 2020-07-09  Alan Modra  <amodra@gmail.com>

>

> 	* emulparams/ppcpe.sh: Delete.

>diff --git a/ld/emultempl/ppc64elf.em b/ld/emultempl/ppc64elf.em

>index a2834c8525..4987243fa1 100644

>--- a/ld/emultempl/ppc64elf.em

>+++ b/ld/emultempl/ppc64elf.em

>@@ -38,7 +38,7 @@ static struct ppc64_elf_params params = { NULL,

> 					  &ppc_layout_sections_again,

> 					  1, -1, -1, 0,

> 					  ${DEFAULT_PLT_STATIC_CHAIN-0}, -1, 5,

>-					  -1, 0, -1, -1, 0};

>+					  -1, -1, 0, -1, -1, 0};

>

> /* Fake input file for stubs.  */

> static lang_input_statement_type *stub_file;

>@@ -684,6 +684,8 @@ enum ppc64_opt

>   OPTION_NO_PLT_ALIGN,

>   OPTION_PLT_LOCALENTRY,

>   OPTION_NO_PLT_LOCALENTRY,

>+  OPTION_POWER10_STUBS,

>+  OPTION_NO_POWER10_STUBS,

>   OPTION_STUBSYMS,

>   OPTION_NO_STUBSYMS,

>   OPTION_SAVRES,

>@@ -714,6 +716,8 @@ PARSE_AND_LIST_LONGOPTS=${PARSE_AND_LIST_LONGOPTS}'

>   { "no-plt-align", no_argument, NULL, OPTION_NO_PLT_ALIGN },

>   { "plt-localentry", optional_argument, NULL, OPTION_PLT_LOCALENTRY },

>   { "no-plt-localentry", no_argument, NULL, OPTION_NO_PLT_LOCALENTRY },

>+  { "power10-stubs", no_argument, NULL, OPTION_POWER10_STUBS },

>+  { "no-power10-stubs", no_argument, NULL, OPTION_NO_POWER10_STUBS },

>   { "emit-stub-syms", no_argument, NULL, OPTION_STUBSYMS },

>   { "no-emit-stub-syms", no_argument, NULL, OPTION_NO_STUBSYMS },

>   { "dotsyms", no_argument, NULL, OPTION_DOTSYMS },

>@@ -769,6 +773,12 @@ PARSE_AND_LIST_OPTIONS=${PARSE_AND_LIST_OPTIONS}'

>   --no-plt-localentry         Don'\''t optimize ELFv2 calls\n"

> 		   ));

>   fprintf (file, _("\

>+  --power10-stubs             Use Power10 PLT call stubs (default auto)\n"

>+		   ));

>+  fprintf (file, _("\

>+  --no-power10-stubs          Don'\''t use Power10 PLT call stubs\n"

>+		   ));

>+  fprintf (file, _("\

>   --emit-stub-syms            Label linker stubs with a symbol\n"

> 		   ));

>   fprintf (file, _("\

>@@ -878,6 +888,14 @@ PARSE_AND_LIST_ARGS_CASES=${PARSE_AND_LIST_ARGS_CASES}'

>       params.plt_localentry0 = 0;

>       break;

>

>+    case OPTION_POWER10_STUBS:

>+      params.power10_stubs = 1;

>+      break;

>+

>+    case OPTION_NO_POWER10_STUBS:

>+      params.power10_stubs = 0;

>+      break;

>+

>     case OPTION_STUBSYMS:

>       params.emit_stub_syms = 1;

>       break;

>diff --git a/ld/testsuite/ld-powerpc/callstub-3.d b/ld/testsuite/ld-powerpc/callstub-3.d

>new file mode 100644

>index 0000000000..06cbfbda4a

>--- /dev/null

>+++ b/ld/testsuite/ld-powerpc/callstub-3.d

>@@ -0,0 +1,38 @@

>+#source: callstub-1.s

>+#as: -a64 -mpower10

>+#ld: -melf64ppc -shared --plt-align=0 --hash-style=gnu --no-power10-stubs

>+#objdump: -dr -Mpower10

>+

>+.*

>+

>+Disassembly of section \.text:

>+

>+.*\.plt_call\.f1>:

>+.*:	(f8 41 00 18|18 00 41 f8) 	std     r2,24\(r1\)

>+.*:	(7d 88 02 a6|a6 02 88 7d) 	mflr    r12

>+.*:	(42 9f 00 05|05 00 9f 42) 	bcl     .*

>+.*:	(7d 68 02 a6|a6 02 68 7d) 	mflr    r11

>+.*:	(7d 88 03 a6|a6 03 88 7d) 	mtlr    r12

>+.*:	(3d 8b 00 01|01 00 8b 3d) 	addis   r12,r11,1

>+.*:	(e9 8c .. ..|.. .. 8c e9) 	ld      r12,.*\(r12\)

>+.*:	(7d 89 03 a6|a6 03 89 7d) 	mtctr   r12

>+.*:	(4e 80 04 20|20 04 80 4e) 	bctr

>+

>+.*\.plt_call\.f2>:

>+.*:	(7d 88 02 a6|a6 02 88 7d) 	mflr    r12

>+.*:	(42 9f 00 05|05 00 9f 42) 	bcl     .*

>+.*:	(7d 68 02 a6|a6 02 68 7d) 	mflr    r11

>+.*:	(7d 88 03 a6|a6 03 88 7d) 	mtlr    r12

>+.*:	(3d 8b 00 01|01 00 8b 3d) 	addis   r12,r11,1

>+.*:	(e9 8c .. ..|.. .. 8c e9) 	ld      r12,.*\(r12\)

>+.*:	(7d 89 03 a6|a6 03 89 7d) 	mtctr   r12

>+.*:	(4e 80 04 20|20 04 80 4e) 	bctr

>+

>+#...

>+.*:	(4b ff ff 81|81 ff ff 4b) 	bl      .*\.plt_call\.f1>

>+.*:	(e8 41 00 18|18 00 41 e8) 	ld      r2,24\(r1\)

>+.*:	(4b ff ff 7d|7d ff ff 4b) 	bl      .*\.plt_call\.f1\+0x4>

>+.*:	(4b ff ff 99|99 ff ff 4b) 	bl      .*\.plt_call\.f2>

>+.*:	(04 10 00 01|01 00 10 04) 	pld     r3,.*

>+.*:	(e4 60 .. ..|.. .. 60 e4)

>+#pass

>diff --git a/ld/testsuite/ld-powerpc/powerpc.exp b/ld/testsuite/ld-powerpc/powerpc.exp

>index de676b8176..50553baa90 100644

>--- a/ld/testsuite/ld-powerpc/powerpc.exp

>+++ b/ld/testsuite/ld-powerpc/powerpc.exp

>@@ -402,6 +402,7 @@ if [ supports_ppc64 ] then {

>     run_dump_test "pr23937"

>     run_dump_test "callstub-1"

>     run_dump_test "callstub-2"

>+    run_dump_test "callstub-3"

>     run_dump_test "tlsgd"

>     run_dump_test "tlsld"

>     run_dump_test "tlsie"

>

>-- 

>Alan Modra

>Australia Development Lab, IBM


ELF specific options usually start with -z.

IMHO the name "--no-power10-stubs" does not get to the point. Why is
power10 special? The feature X (which is enabled on power10) is the
thing that is special. The option should just mention X.

The documentation does not seem to be updated.

(I have been waiting for an ABI for a while. Apparently
http://lists.mailinglist.openpowerfoundation.org/pipermail/syssw-elfv2abi/
hasn't been updated for the past 3 months.)
Alan Modra via Binutils July 10, 2020, 7:32 a.m. | #2
* ld.texi (PowerPC64 ELF64): Document --no-inline-optimize,
	--power10-stubs and --no-power10-stubs.

diff --git a/ld/ld.texi b/ld/ld.texi
index 40b042de9b..165a3d935e 100644
--- a/ld/ld.texi
+++ b/ld/ld.texi
@@ -7823,6 +7823,16 @@ reliably for compiler generated code, but may be incorrect if assembly
 code is used to insert TOC entries.  Use this option to disable the
 optimization.
 
+@cindex PowerPC64 inline PLT call optimization
+@kindex --no-inline-optimize
+@item --no-inline-optimize
+PowerPC64 @command{ld} normally replaces inline PLT call sequences
+marked with @code{R_PPC64_PLTSEQ}, @code{R_PPC64_PLTCALL},
+@code{R_PPC64_PLT16_HA} and @code{R_PPC64_PLT16_LO_DS} relocations by
+a number of @code{nop}s and a direct call when the function is defined
+locally and can't be overridden by some other definition.  This option
+disables that optimization.
+
 @cindex PowerPC64 multi-TOC
 @kindex --no-multi-toc
 @item --no-multi-toc
@@ -7904,6 +7914,23 @@ including system libraries, can cause a function that was localentry:0
 to become localentry:8.  This will result in a dynamic loader
 complaint and failure to run.  The option is experimental, use with
 care.  @option{--no-plt-localentry} is the default.
+
+@cindex PowerPC64 Power10 stubs
+@kindex --power10-stubs
+@kindex --no-power10-stubs
+@item --power10-stubs
+@itemx --no-power10-stubs
+When PowerPC64 @command{ld} links input object files containing
+relocations used on power10 prefixed instructions it normally creates
+linkage stubs (PLT call and long branch) using power10 instructions.
+In particular for @code{@@notoc} PLT calls where @code{r2} is not
+known the power10 stubs are smaller and faster, so are preferred for
+power10.  @option{--power10-stubs} and @option{--no-power10-stubs}
+allow you to override the linker's selection of stub instructions.
+For example, when linking a shared library that contains cpu-optimized
+versions of functions for both power9 and power10, you might use
+@option{--no-power10-stubs} so that power9 code making calls doesn't
+attempt to execute power10 instructions.
 @end table
 
 @ifclear GENERIC

-- 
Alan Modra
Australia Development Lab, IBM
Alan Modra via Binutils July 10, 2020, 11:51 a.m. | #3
On Fri, Jul 10, 2020 at 11:25:55AM +0930, Alan Modra wrote:
> Needed for libraries that use ifuncs or other means to support

> cpu-optimized versions of functions, some power10, some not, and those

> functions make calls using linkage stubs.

> 

> bfd/

> 	* elf64-ppc.h (struct ppc64_elf_params): Add power10_stubs.

> 	* elf64-ppc.c (struct ppc_link_hash_table): Delete

> 	power10_stubs.

> 	(ppc64_elf_check_relocs): Adjust setting of power10_stubs.

> 	(plt_stub_size, ppc_build_one_stub, ppc_size_one_stub): Adjust

> 	uses of power10_stubs.

> ld/

> 	* emultempl/ppc64elf.em (params): Init new field.

> 	(enum ppc64_opt): Add OPTION_POWER10_STUBS and OPTION_NO_POWER10_STUBS.

> 	(PARSE_AND_LIST_LONGOPTS): Support --power10-stubs and

> 	--no-power10-stubs.

> 	(PARSE_AND_LIST_OPTIONS, PARSE_AND_LIST_ARGS_CASES): Likewise.

> 	* testsuite/ld-powerpc/callstub-3.d: New test.

> 	* testsuite/ld-powerpc/powerpc.exp: Run it.


Nick, I'd like to apply this along with the doc update to the 2.35
branch.  Is that OK?

-- 
Alan Modra
Australia Development Lab, IBM
Alan Modra via Binutils July 10, 2020, 11:53 a.m. | #4
On Thu, Jul 09, 2020 at 09:23:06PM -0700, Fangrui Song wrote:
> The documentation does not seem to be updated.


Now done, thanks for noticing.

-- 
Alan Modra
Australia Development Lab, IBM
Alan Modra via Binutils July 10, 2020, 12:41 p.m. | #5
Hi Alan,

>> Needed for libraries that use ifuncs or other means to support

>> cpu-optimized versions of functions, some power10, some not, and those

>> functions make calls using linkage stubs.

>>

>> bfd/

>> 	* elf64-ppc.h (struct ppc64_elf_params): Add power10_stubs.

>> 	* elf64-ppc.c (struct ppc_link_hash_table): Delete

>> 	power10_stubs.

>> 	(ppc64_elf_check_relocs): Adjust setting of power10_stubs.

>> 	(plt_stub_size, ppc_build_one_stub, ppc_size_one_stub): Adjust

>> 	uses of power10_stubs.

>> ld/

>> 	* emultempl/ppc64elf.em (params): Init new field.

>> 	(enum ppc64_opt): Add OPTION_POWER10_STUBS and OPTION_NO_POWER10_STUBS.

>> 	(PARSE_AND_LIST_LONGOPTS): Support --power10-stubs and

>> 	--no-power10-stubs.

>> 	(PARSE_AND_LIST_OPTIONS, PARSE_AND_LIST_ARGS_CASES): Likewise.

>> 	* testsuite/ld-powerpc/callstub-3.d: New test.

>> 	* testsuite/ld-powerpc/powerpc.exp: Run it.

> 

> Nick, I'd like to apply this along with the doc update to the 2.35

> branch.  Is that OK?


It is - please go ahead and apply.

Cheers
  Nick
Alan Modra via Binutils July 10, 2020, 12:48 p.m. | #6
On 7/9/20 11:23 PM, Fangrui Song wrote:
> (I have been waiting for an ABI for a while. Apparently

> http://lists.mailinglist.openpowerfoundation.org/pipermail/syssw-elfv2abi/ 

>

> hasn't been updated for the past 3 months.)

As I've told you before, the OPF's publication process is slow. Three 
commiittees need to sign off on a public review, and some of those only 
meet monthly.  The update is working its way through the process.

Patch

diff --git a/bfd/elf64-ppc.c b/bfd/elf64-ppc.c
index 8d710848ba..e54f561019 100644
--- a/bfd/elf64-ppc.c
+++ b/bfd/elf64-ppc.c
@@ -3245,9 +3245,6 @@  struct ppc_link_hash_table
   /* Whether calls are made via the PLT from NOTOC functions.  */
   unsigned int notoc_plt:1;
 
-  /* Whether to use power10 instructions in linkage stubs.  */
-  unsigned int power10_stubs:1;
-
   /* Incremented every time we size stubs.  */
   unsigned int stub_iteration;
 
@@ -4602,7 +4599,8 @@  ppc64_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
 	case R_PPC64_PLT_PCREL34:
 	case R_PPC64_PLT_PCREL34_NOTOC:
 	case R_PPC64_PCREL28:
-	  htab->power10_stubs = 1;
+	  if (htab->params->power10_stubs < 0)
+	    htab->params->power10_stubs = 1;
 	  break;
 	default:
 	  break;
@@ -10763,7 +10761,7 @@  plt_stub_size (struct ppc_link_hash_table *htab,
 
   if (stub_entry->stub_type >= ppc_stub_plt_call_notoc)
     {
-      if (htab->power10_stubs)
+      if (htab->params->power10_stubs > 0)
 	{
 	  bfd_vma start = (stub_entry->stub_offset
 			   + stub_entry->group->stub_sec->output_offset
@@ -11604,7 +11602,7 @@  ppc_build_one_stub (struct bfd_hash_entry *gen_entry, void *in_arg)
 
       relp = p;
       num_rel = 0;
-      if (htab->power10_stubs)
+      if (htab->params->power10_stubs > 0)
 	{
 	  bfd_boolean load = stub_entry->stub_type >= ppc_stub_plt_call_notoc;
 	  p = build_power10_offset (htab->params->stub_bfd, p, off, odd, load);
@@ -11643,7 +11641,7 @@  ppc_build_one_stub (struct bfd_hash_entry *gen_entry, void *in_arg)
       if (info->emitrelocations)
 	{
 	  bfd_vma roff = relp - stub_entry->group->stub_sec->contents;
-	  if (htab->power10_stubs)
+	  if (htab->params->power10_stubs > 0)
 	    num_rel += num_relocs_for_power10_offset (off, odd);
 	  else
 	    {
@@ -11653,7 +11651,7 @@  ppc_build_one_stub (struct bfd_hash_entry *gen_entry, void *in_arg)
 	  r = get_relocs (stub_entry->group->stub_sec, num_rel);
 	  if (r == NULL)
 	    return FALSE;
-	  if (htab->power10_stubs)
+	  if (htab->params->power10_stubs > 0)
 	    r = emit_relocs_for_power10_offset (info, r, roff, targ, off, odd);
 	  else
 	    r = emit_relocs_for_offset (info, r, roff, targ, off);
@@ -11671,7 +11669,7 @@  ppc_build_one_stub (struct bfd_hash_entry *gen_entry, void *in_arg)
 	    }
 	}
 
-      if (!htab->power10_stubs
+      if (htab->params->power10_stubs <= 0
 	  && htab->glink_eh_frame != NULL
 	  && htab->glink_eh_frame->size != 0)
 	{
@@ -12019,7 +12017,7 @@  ppc_size_one_stub (struct bfd_hash_entry *gen_entry, void *in_arg)
       if (info->emitrelocations)
 	{
 	  unsigned int num_rel;
-	  if (htab->power10_stubs)
+	  if (htab->params->power10_stubs > 0)
 	    num_rel = num_relocs_for_power10_offset (off, odd);
 	  else
 	    num_rel = num_relocs_for_offset (off - 8);
@@ -12027,7 +12025,7 @@  ppc_size_one_stub (struct bfd_hash_entry *gen_entry, void *in_arg)
 	  stub_entry->group->stub_sec->flags |= SEC_RELOC;
 	}
 
-      if (htab->power10_stubs)
+      if (htab->params->power10_stubs > 0)
 	extra = size_power10_offset (off, odd);
       else
 	extra = size_offset (off - 8);
@@ -12038,7 +12036,7 @@  ppc_size_one_stub (struct bfd_hash_entry *gen_entry, void *in_arg)
 	 calculated.  */
       off -= extra;
 
-      if (!htab->power10_stubs)
+      if (htab->params->power10_stubs <= 0)
 	{
 	  /* After the bcl, lr has been modified so we need to emit
 	     .eh_frame info saying the return address is in r12.  */
@@ -12101,7 +12099,7 @@  ppc_size_one_stub (struct bfd_hash_entry *gen_entry, void *in_arg)
       if (info->emitrelocations)
 	{
 	  unsigned int num_rel;
-	  if (htab->power10_stubs)
+	  if (htab->params->power10_stubs > 0)
 	    num_rel = num_relocs_for_power10_offset (off, odd);
 	  else
 	    num_rel = num_relocs_for_offset (off - 8);
@@ -12111,7 +12109,7 @@  ppc_size_one_stub (struct bfd_hash_entry *gen_entry, void *in_arg)
 
       size = plt_stub_size (htab, stub_entry, off);
 
-      if (!htab->power10_stubs)
+      if (htab->params->power10_stubs <= 0)
 	{
 	  /* After the bcl, lr has been modified so we need to emit
 	     .eh_frame info saying the return address is in r12.  */
diff --git a/bfd/elf64-ppc.h b/bfd/elf64-ppc.h
index a2ffd4eb72..547971f8be 100644
--- a/bfd/elf64-ppc.h
+++ b/bfd/elf64-ppc.h
@@ -54,6 +54,9 @@  struct ppc64_elf_params
   /* Set if PLT call stubs for localentry:0 functions should omit r2 save.  */
   int plt_localentry0;
 
+  /* Whether to use power10 instructions in linkage stubs.  */
+  int power10_stubs;
+
   /* Whether to canonicalize .opd so that there are no overlapping
      .opd entries.  */
   int non_overlapping_opd;
diff --git a/ld/ChangeLog b/ld/ChangeLog
index 14f31a805f..b9c844ced3 100644
--- a/ld/ChangeLog
+++ b/ld/ChangeLog
@@ -1,3 +1,13 @@ 
+2020-07-10  Alan Modra  <amodra@gmail.com>
+
+	* emultempl/ppc64elf.em (params): Init new field.
+	(enum ppc64_opt): Add OPTION_POWER10_STUBS and OPTION_NO_POWER10_STUBS.
+	(PARSE_AND_LIST_LONGOPTS): Support --power10-stubs and
+	--no-power10-stubs.
+	(PARSE_AND_LIST_OPTIONS, PARSE_AND_LIST_ARGS_CASES): Likewise.
+	* testsuite/ld-powerpc/callstub-3.d: New test.
+	* testsuite/ld-powerpc/powerpc.exp: Run it.
+
 2020-07-09  Alan Modra  <amodra@gmail.com>
 
 	* emulparams/ppcpe.sh: Delete.
diff --git a/ld/emultempl/ppc64elf.em b/ld/emultempl/ppc64elf.em
index a2834c8525..4987243fa1 100644
--- a/ld/emultempl/ppc64elf.em
+++ b/ld/emultempl/ppc64elf.em
@@ -38,7 +38,7 @@  static struct ppc64_elf_params params = { NULL,
 					  &ppc_layout_sections_again,
 					  1, -1, -1, 0,
 					  ${DEFAULT_PLT_STATIC_CHAIN-0}, -1, 5,
-					  -1, 0, -1, -1, 0};
+					  -1, -1, 0, -1, -1, 0};
 
 /* Fake input file for stubs.  */
 static lang_input_statement_type *stub_file;
@@ -684,6 +684,8 @@  enum ppc64_opt
   OPTION_NO_PLT_ALIGN,
   OPTION_PLT_LOCALENTRY,
   OPTION_NO_PLT_LOCALENTRY,
+  OPTION_POWER10_STUBS,
+  OPTION_NO_POWER10_STUBS,
   OPTION_STUBSYMS,
   OPTION_NO_STUBSYMS,
   OPTION_SAVRES,
@@ -714,6 +716,8 @@  PARSE_AND_LIST_LONGOPTS=${PARSE_AND_LIST_LONGOPTS}'
   { "no-plt-align", no_argument, NULL, OPTION_NO_PLT_ALIGN },
   { "plt-localentry", optional_argument, NULL, OPTION_PLT_LOCALENTRY },
   { "no-plt-localentry", no_argument, NULL, OPTION_NO_PLT_LOCALENTRY },
+  { "power10-stubs", no_argument, NULL, OPTION_POWER10_STUBS },
+  { "no-power10-stubs", no_argument, NULL, OPTION_NO_POWER10_STUBS },
   { "emit-stub-syms", no_argument, NULL, OPTION_STUBSYMS },
   { "no-emit-stub-syms", no_argument, NULL, OPTION_NO_STUBSYMS },
   { "dotsyms", no_argument, NULL, OPTION_DOTSYMS },
@@ -769,6 +773,12 @@  PARSE_AND_LIST_OPTIONS=${PARSE_AND_LIST_OPTIONS}'
   --no-plt-localentry         Don'\''t optimize ELFv2 calls\n"
 		   ));
   fprintf (file, _("\
+  --power10-stubs             Use Power10 PLT call stubs (default auto)\n"
+		   ));
+  fprintf (file, _("\
+  --no-power10-stubs          Don'\''t use Power10 PLT call stubs\n"
+		   ));
+  fprintf (file, _("\
   --emit-stub-syms            Label linker stubs with a symbol\n"
 		   ));
   fprintf (file, _("\
@@ -878,6 +888,14 @@  PARSE_AND_LIST_ARGS_CASES=${PARSE_AND_LIST_ARGS_CASES}'
       params.plt_localentry0 = 0;
       break;
 
+    case OPTION_POWER10_STUBS:
+      params.power10_stubs = 1;
+      break;
+
+    case OPTION_NO_POWER10_STUBS:
+      params.power10_stubs = 0;
+      break;
+
     case OPTION_STUBSYMS:
       params.emit_stub_syms = 1;
       break;
diff --git a/ld/testsuite/ld-powerpc/callstub-3.d b/ld/testsuite/ld-powerpc/callstub-3.d
new file mode 100644
index 0000000000..06cbfbda4a
--- /dev/null
+++ b/ld/testsuite/ld-powerpc/callstub-3.d
@@ -0,0 +1,38 @@ 
+#source: callstub-1.s
+#as: -a64 -mpower10
+#ld: -melf64ppc -shared --plt-align=0 --hash-style=gnu --no-power10-stubs
+#objdump: -dr -Mpower10
+
+.*
+
+Disassembly of section \.text:
+
+.*\.plt_call\.f1>:
+.*:	(f8 41 00 18|18 00 41 f8) 	std     r2,24\(r1\)
+.*:	(7d 88 02 a6|a6 02 88 7d) 	mflr    r12
+.*:	(42 9f 00 05|05 00 9f 42) 	bcl     .*
+.*:	(7d 68 02 a6|a6 02 68 7d) 	mflr    r11
+.*:	(7d 88 03 a6|a6 03 88 7d) 	mtlr    r12
+.*:	(3d 8b 00 01|01 00 8b 3d) 	addis   r12,r11,1
+.*:	(e9 8c .. ..|.. .. 8c e9) 	ld      r12,.*\(r12\)
+.*:	(7d 89 03 a6|a6 03 89 7d) 	mtctr   r12
+.*:	(4e 80 04 20|20 04 80 4e) 	bctr
+
+.*\.plt_call\.f2>:
+.*:	(7d 88 02 a6|a6 02 88 7d) 	mflr    r12
+.*:	(42 9f 00 05|05 00 9f 42) 	bcl     .*
+.*:	(7d 68 02 a6|a6 02 68 7d) 	mflr    r11
+.*:	(7d 88 03 a6|a6 03 88 7d) 	mtlr    r12
+.*:	(3d 8b 00 01|01 00 8b 3d) 	addis   r12,r11,1
+.*:	(e9 8c .. ..|.. .. 8c e9) 	ld      r12,.*\(r12\)
+.*:	(7d 89 03 a6|a6 03 89 7d) 	mtctr   r12
+.*:	(4e 80 04 20|20 04 80 4e) 	bctr
+
+#...
+.*:	(4b ff ff 81|81 ff ff 4b) 	bl      .*\.plt_call\.f1>
+.*:	(e8 41 00 18|18 00 41 e8) 	ld      r2,24\(r1\)
+.*:	(4b ff ff 7d|7d ff ff 4b) 	bl      .*\.plt_call\.f1\+0x4>
+.*:	(4b ff ff 99|99 ff ff 4b) 	bl      .*\.plt_call\.f2>
+.*:	(04 10 00 01|01 00 10 04) 	pld     r3,.*
+.*:	(e4 60 .. ..|.. .. 60 e4) 
+#pass
diff --git a/ld/testsuite/ld-powerpc/powerpc.exp b/ld/testsuite/ld-powerpc/powerpc.exp
index de676b8176..50553baa90 100644
--- a/ld/testsuite/ld-powerpc/powerpc.exp
+++ b/ld/testsuite/ld-powerpc/powerpc.exp
@@ -402,6 +402,7 @@  if [ supports_ppc64 ] then {
     run_dump_test "pr23937"
     run_dump_test "callstub-1"
     run_dump_test "callstub-2"
+    run_dump_test "callstub-3"
     run_dump_test "tlsgd"
     run_dump_test "tlsld"
     run_dump_test "tlsie"