]> git.ipfire.org Git - thirdparty/gcc.git/blob - gcc/config/rs6000/rs6000-pcrel-opt.cc
Update copyright years.
[thirdparty/gcc.git] / gcc / config / rs6000 / rs6000-pcrel-opt.cc
1 /* Subroutines used support the pc-relative linker optimization.
2 Copyright (C) 2020-2024 Free Software Foundation, Inc.
3
4 This file is part of GCC.
5
6 GCC is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published
8 by the Free Software Foundation; either version 3, or (at your
9 option) any later version.
10
11 GCC is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
14 License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GCC; see the file COPYING3. If not see
18 <http://www.gnu.org/licenses/>. */
19
20 /* This file implements a RTL pass that looks for pc-relative loads of the
21 address of an external variable using the PCREL_GOT relocation and a single
22 load that uses that external address. If that is found we create the
23 PCREL_OPT relocation to possibly convert:
24
25 pld addr_reg,var@pcrel@got
26
27 <possibly other insns that do not use 'addr_reg' or 'data_reg'>
28
29 lwz data_reg,0(addr_reg)
30
31 into:
32
33 plwz data_reg,var@pcrel
34
35 <possibly other insns that do not use 'addr_reg' or 'data_reg'>
36
37 nop
38
39 Of course it would be nice to be able to put the plwz in this example in
40 place of the lwz but the linker cannot easily replace a 4-byte instruction
41 with an 8-byte one.
42
43 If the variable is not defined in the main program or the code using it is
44 not in the main program, the linker puts the address in the .got section and
45 generates:
46
47 .section .got
48 .Lvar_got:
49 .dword var
50
51 At the point where it is referenced, we have:
52
53 .section .text
54 pld addr_reg,.Lvar_got@pcrel
55
56 <possibly other insns that do not use 'addr_reg' or 'data_reg'>
57
58 lwz data_reg,0(addr_reg)
59
60 We look for a single usage in the basic block where this external
61 address is loaded, and convert it to a PCREL_OPT relocation so the
62 linker can convert it to a single plwz in this case. Multiple uses
63 or references in another basic block will force us to not use the
64 PCREL_OPT relocation.
65
66 We also optimize stores to the address of an external variable using the
67 PCREL_GOT relocation and a single store that uses that external address. If
68 that is found we create the PCREL_OPT relocation to possibly convert:
69
70 pld addr_reg,var@pcrel@got
71
72 <possibly other insns that do not use 'addr_reg' or 'data_reg'>
73
74 stw data_reg,0(addr_reg)
75
76 into:
77
78 pstw data_reg,var@pcrel
79
80 <possibly other insns that do not use 'addr_reg' or 'data_reg'>
81
82 nop
83
84 If the variable is not defined in the main program or the code using it is
85 not in the main program, the linker puts the address in the .got section and
86 generates:
87
88 .section .got
89 .Lvar_got:
90 .dword var
91
92 And at our point of reference we have:
93
94 .section .text
95 pld addr_reg,.Lvar_got@pcrel
96
97 <possibly other insns that do not use 'addr_reg' or 'data_reg'>
98
99 stw data_reg,0(addr_reg)
100
101 We only look for a single usage in the basic block where the external
102 address is loaded. Multiple uses or references in another basic block will
103 force us to not use the PCREL_OPT relocation. */
104
105 #define IN_TARGET_CODE 1
106
107 #include "config.h"
108 #include "system.h"
109 #include "coretypes.h"
110 #include "backend.h"
111 #include "rtl.h"
112 #include "tree.h"
113 #include "memmodel.h"
114 #include "expmed.h"
115 #include "optabs.h"
116 #include "recog.h"
117 #include "df.h"
118 #include "tm_p.h"
119 #include "ira.h"
120 #include "print-tree.h"
121 #include "varasm.h"
122 #include "explow.h"
123 #include "expr.h"
124 #include "output.h"
125 #include "tree-pass.h"
126 #include "rtx-vector-builder.h"
127 #include "print-rtl.h"
128 #include "insn-attr.h"
129 #include "insn-codes.h"
130
131 /* Various counters. */
132 static struct {
133 unsigned long extern_addrs;
134 unsigned long loads;
135 unsigned long adjacent_loads;
136 unsigned long failed_loads;
137 unsigned long stores;
138 unsigned long adjacent_stores;
139 unsigned long failed_stores;
140 } counters;
141
142 /* Unique integer that is appended to .Lpcrel to make a pcrel_opt label. */
143 static unsigned int pcrel_opt_next_num;
144
145 \f
146 /* Optimize a PC-relative load address to be used in a load. Before it calls
147 this function, pcrel_opt_address () uses DF to make sure that it is safe
148 to do the PCREL_OPT optimization on these insns.
149
150 Convert insns of the form:
151
152 (set (reg:DI addr)
153 (symbol_ref:DI "ext_symbol"))
154
155 ...
156
157 (set (reg:<MODE> value)
158 (mem:<MODE> (reg:DI addr)))
159
160 into:
161
162 (parallel [(set (reg:DI addr)
163 (unspec:<MODE> [(symbol_ref:DI "ext_symbol")
164 (const_int label_num)]
165 UNSPEC_PCREL_OPT_LD_ADDR))
166 (set (reg:DI data)
167 (unspec:DI [(const_int 0)]
168 UNSPEC_PCREL_OPT_LD_DATA))])
169
170 ...
171
172 (parallel [(set (reg:<MODE>)
173 (unspec:<MODE> [(mem:<MODE> (reg:DI addr))
174 (reg:DI data)
175 (const_int label_num)]
176 UNSPEC_PCREL_OPT_LD_RELOC))
177 (clobber (reg:DI addr))])
178
179 Because PCREL_OPT will move the actual location of the load from the second
180 insn to the first, we need to have the register for the load data be live
181 starting at the first insn.
182
183 If the destination register for the data being loaded is the same register
184 used to hold the extern address, we generate this insn instead:
185
186 (set (reg:DI data)
187 (unspec:DI [(symbol_ref:DI "ext_symbol")
188 (const_int label_num)]
189 UNSPEC_PCREL_OPT_LD_SAME_REG))
190
191 In the first insn, we set both the address of the external variable, and mark
192 that the variable being loaded both are created in that insn, and are
193 consumed in the second insn. The mode used in the first insn for the data
194 register that will be loaded in the second insn doesn't matter in the end so
195 we use DImode. We just need to mark that both registers may be set in the
196 first insn, and will be used in the second insn.
197
198 The UNSPEC_PCREL_OPT_LD_ADDR insn will generate the load address plus
199 a definition of a label (.Lpcrel<n>), while the UNSPEC_PCREL_OPT_LD_RELOC
200 insn will generate the .reloc to tell the linker to tie the load address and
201 load using that address together.
202
203 pld b,ext_symbol@got@pcrel
204 .Lpcrel1:
205
206 ...
207
208 .reloc .Lpcrel1-8,R_PPC64_PCREL_OPT,.-(.Lpcrel1-8)
209 lwz r,0(b)
210
211 If ext_symbol is defined in another object file in the main program and we
212 are linking the main program, the linker will convert the above instructions
213 to:
214
215 plwz r,ext_symbol@got@pcrel
216
217 ...
218
219 nop
220
221 ADDR_INSN is the insn that is loading the address.
222 LOAD_INSN is the insn that uses the address to load the actual data. */
223
224 static void
225 pcrel_opt_load (rtx_insn *addr_insn, rtx_insn *load_insn)
226 {
227 rtx addr_set = PATTERN (addr_insn);
228 gcc_assert (GET_CODE (addr_set) == SET);
229
230 rtx addr_reg = SET_DEST (addr_set);
231 gcc_assert (base_reg_operand (addr_reg, Pmode));
232
233 rtx addr_symbol = SET_SRC (addr_set);
234 gcc_assert (pcrel_external_address (addr_symbol, Pmode));
235
236 rtx load_set = PATTERN (load_insn);
237 gcc_assert (GET_CODE (load_set) == SET);
238
239 /* Make sure there are no references to the register being loaded
240 between the two insns. */
241 rtx reg = SET_DEST (load_set);
242 if (reg_used_between_p (reg, addr_insn, load_insn)
243 || reg_set_between_p (reg, addr_insn, load_insn))
244 return;
245
246 rtx mem = SET_SRC (load_set);
247 machine_mode reg_mode = GET_MODE (reg);
248 machine_mode mem_mode = GET_MODE (mem);
249 rtx mem_inner = mem;
250 unsigned int reg_regno = reg_or_subregno (reg);
251
252 /* Handle the fact that LWA is a DS format instruction, but LWZ is a D format
253 instruction. If the mem load is a signed SImode (i.e. LWA would be used)
254 we set mem_mode to DImode so that pcrel_opt_valid_mem_p() will check that
255 the address will work for a DS-form instruction. If it won't work, we skip
256 the optimization. The float loads are all indexed so there are no problems
257 there. */
258
259 if (GET_CODE (mem) == SIGN_EXTEND && GET_MODE (XEXP (mem, 0)) == SImode)
260 {
261 if (!INT_REGNO_P (reg_regno))
262 return;
263
264 mem_inner = XEXP (mem, 0);
265 mem_mode = DImode;
266 }
267
268 else if (GET_CODE (mem) == SIGN_EXTEND
269 || GET_CODE (mem) == ZERO_EXTEND
270 || GET_CODE (mem) == FLOAT_EXTEND)
271 {
272 mem_inner = XEXP (mem, 0);
273 mem_mode = GET_MODE (mem_inner);
274 }
275
276 if (!MEM_P (mem_inner))
277 return;
278
279 /* Can we do PCREL_OPT for this reference? */
280 if (!pcrel_opt_valid_mem_p (reg, mem_mode, mem_inner))
281 return;
282
283 /* Allocate a new PC-relative label, and update the load external address
284 insn.
285
286 If the register being loaded is different from the address register, we
287 need to indicate both registers are set at the load of the address.
288
289 (parallel [(set (reg load)
290 (unspec [(symbol_ref addr_symbol)
291 (const_int label_num)]
292 UNSPEC_PCREL_OPT_LD_ADDR))
293 (set (reg addr)
294 (unspec [(const_int 0)]
295 UNSPEC_PCREL_OPT_LD_DATA))])
296
297 If the register being loaded is the same as the address register, we use
298 an alternate form:
299
300 (set (reg load)
301 (unspec [(symbol_ref addr_symbol)
302 (const_int label_num)]
303 UNSPEC_PCREL_OPT_LD_SAME_REG)) */
304 unsigned int addr_regno = reg_or_subregno (addr_reg);
305 rtx label_num = GEN_INT (++pcrel_opt_next_num);
306 rtx reg_di = gen_rtx_REG (DImode, reg_regno);
307 rtx addr_pattern;
308
309 /* Create the load address, either using the pattern with an explicit clobber
310 if the address register is not the same as the register being loaded, or
311 using the pattern that requires the address register to be the address
312 loaded. */
313 if (addr_regno != reg_regno)
314 addr_pattern = gen_pcrel_opt_ld_addr (addr_reg, addr_symbol, label_num,
315 reg_di);
316 else
317 addr_pattern = gen_pcrel_opt_ld_addr_same_reg (addr_reg, addr_symbol,
318 label_num);
319
320 validate_change (addr_insn, &PATTERN (addr_insn), addr_pattern, false);
321
322 /* Update the load insn. If the mem had a sign/zero/float extend, add that
323 also after doing the UNSPEC. Add an explicit clobber of the external
324 address register just to make it clear that the address register dies.
325
326 (parallel [(set (reg:<MODE> data)
327 (unspec:<MODE> [(mem (addr_reg)
328 (reg:DI data)
329 (const_int label_num)]
330 UNSPEC_PCREL_OPT_LD_RELOC))
331 (clobber (reg:DI addr_reg))]) */
332 rtvec v_load = gen_rtvec (3, mem_inner, reg_di, label_num);
333 rtx new_load = gen_rtx_UNSPEC (GET_MODE (mem_inner), v_load,
334 UNSPEC_PCREL_OPT_LD_RELOC);
335
336 if (GET_CODE (mem) != GET_CODE (mem_inner))
337 new_load = gen_rtx_fmt_e (GET_CODE (mem), reg_mode, new_load);
338
339 rtx new_load_set = gen_rtx_SET (reg, new_load);
340 rtx load_clobber = gen_rtx_CLOBBER (VOIDmode,
341 (addr_regno == reg_regno
342 ? gen_rtx_SCRATCH (Pmode)
343 : addr_reg));
344 rtx new_load_pattern
345 = gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, new_load_set, load_clobber));
346
347 validate_change (load_insn, &PATTERN (load_insn), new_load_pattern, false);
348
349 /* Attempt to apply the changes: */
350 if (!apply_change_group ())
351 {
352 /* PCREL_OPT load optimization did not succeed. */
353 counters.failed_loads++;
354 if (dump_file)
355 fprintf (dump_file,
356 "PCREL_OPT load failed (addr insn = %d, use insn = %d).\n",
357 INSN_UID (addr_insn),
358 INSN_UID (load_insn));
359 return;
360 }
361
362 /* PCREL_OPT load optimization succeeded. */
363 counters.loads++;
364 if (next_nonnote_insn (addr_insn) == load_insn)
365 counters.adjacent_loads++;
366
367 if (dump_file)
368 fprintf (dump_file,
369 "PCREL_OPT load (addr insn = %d, use insn = %d).\n",
370 INSN_UID (addr_insn),
371 INSN_UID (load_insn));
372
373 /* Because we have set DF_DEFER_INSN_RESCAN, we have to explicitly do it
374 after we have made changes to the insns. */
375 df_analyze ();
376
377 }
378 \f
379 /* Optimize a PC-relative load address to be used in a store. Before calling
380 this function, pcrel_opt_address () uses DF to make sure it is safe to do
381 the PCREL_OPT optimization.
382
383 Convert insns of the form:
384
385 (set (reg:DI addr)
386 (symbol_ref:DI "ext_symbol"))
387
388 ...
389
390 (set (mem:<MODE> (reg:DI addr))
391 (reg:<MODE> value))
392
393 into:
394
395 (parallel [(set (reg:DI addr)
396 (unspec:DI [(symbol_ref:DI "ext_symbol")
397 (const_int label_num)]
398 UNSPEC_PCREL_OPT_ST_ADDR))
399 (use (reg:<MODE> value))])
400
401 ...
402
403 (parallel [(set (mem:<MODE> (reg:DI addr))
404 (unspec:<MODE> [(reg:<MODE>)
405 (const_int label_num)]
406 UNSPEC_PCREL_OPT_ST_RELOC))
407 (clobber (reg:DI addr))])
408
409 The UNSPEC_PCREL_OPT_ST_ADDR insn will generate the load address plus a
410 definition of a label (.Lpcrel<n>), while the UNSPEC_PCREL_OPT_ST_RELOC insn
411 will generate the .reloc to tell the linker to tie the load address and load
412 using that address together.
413
414 pld b,ext_symbol@got@pcrel
415 .Lpcrel1:
416
417 ...
418
419 .reloc .Lpcrel1-8,R_PPC64_PCREL_OPT,.-(.Lpcrel1-8)
420 stw r,0(b)
421
422 If ext_symbol is defined in another object file in the main program and we
423 are linking the main program, the linker will convert the above instructions
424 to:
425
426 pstwz r,ext_symbol@got@pcrel
427
428 ...
429
430 nop */
431
432 static void
433 pcrel_opt_store (rtx_insn *addr_insn, /* insn loading address. */
434 rtx_insn *store_insn) /* insn using address. */
435 {
436 rtx addr_old_set = PATTERN (addr_insn);
437 gcc_assert (GET_CODE (addr_old_set) == SET);
438
439 rtx addr_reg = SET_DEST (addr_old_set);
440 gcc_assert (base_reg_operand (addr_reg, Pmode));
441
442 rtx addr_symbol = SET_SRC (addr_old_set);
443 gcc_assert (pcrel_external_address (addr_symbol, Pmode));
444
445 rtx store_set = PATTERN (store_insn);
446 gcc_assert (GET_CODE (store_set) == SET);
447
448 rtx mem = SET_DEST (store_set);
449 if (!MEM_P (mem))
450 return;
451
452 machine_mode mem_mode = GET_MODE (mem);
453 rtx reg = SET_SRC (store_set);
454
455 /* Don't allow storing the address of the external variable. */
456 if (reg_or_subregno (reg) == reg_or_subregno (addr_reg))
457 return;
458
459 /* Can we do PCREL_OPT for this reference? */
460 if (!pcrel_opt_valid_mem_p (reg, mem_mode, mem))
461 return;
462
463 /* Allocate a new PC-relative label, and update the load address insn.
464
465 (parallel [(set (reg addr)
466 (unspec [(symbol_ref symbol)
467 (const_int label_num)]
468 UNSPEC_PCREL_OPT_ST_ADDR))
469 (use (reg store))])
470 */
471 rtx label_num = GEN_INT (++pcrel_opt_next_num);
472 rtvec v_addr = gen_rtvec (2, addr_symbol, label_num);
473 rtx addr_unspec = gen_rtx_UNSPEC (Pmode, v_addr,
474 UNSPEC_PCREL_OPT_ST_ADDR);
475 rtx addr_new_set = gen_rtx_SET (addr_reg, addr_unspec);
476 rtx addr_use = gen_rtx_USE (VOIDmode, reg);
477 rtx addr_new_pattern
478 = gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, addr_new_set, addr_use));
479
480 validate_change (addr_insn, &PATTERN (addr_insn), addr_new_pattern, false);
481
482 /* Update the store insn. Add an explicit clobber of the external address
483 register just to be sure there are no additional uses of the address
484 register.
485
486 (parallel [(set (mem (addr_reg)
487 (unspec:<MODE> [(reg)
488 (const_int label_num)]
489 UNSPEC_PCREL_OPT_ST_RELOC))
490 (clobber (reg:DI addr_reg))]) */
491 rtvec v_store = gen_rtvec (2, reg, label_num);
492 rtx new_store = gen_rtx_UNSPEC (mem_mode, v_store,
493 UNSPEC_PCREL_OPT_ST_RELOC);
494
495 rtx new_store_set = gen_rtx_SET (mem, new_store);
496 rtx store_clobber = gen_rtx_CLOBBER (VOIDmode, addr_reg);
497 rtx new_store_pattern
498 = gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, new_store_set, store_clobber));
499
500 validate_change (store_insn, &PATTERN (store_insn), new_store_pattern, false);
501
502 /* Attempt to apply the changes: */
503 if (!apply_change_group ())
504 {
505 /* PCREL_OPT store failed. */
506 counters.failed_stores++;
507 if (dump_file)
508 fprintf (dump_file,
509 "PCREL_OPT store failed (addr insn = %d, use insn = %d).\n",
510 INSN_UID (addr_insn),
511 INSN_UID (store_insn));
512 return;
513 }
514
515 /* PCREL_OPT store succeeded. */
516 counters.stores++;
517 if (next_nonnote_insn (addr_insn) == store_insn)
518 counters.adjacent_stores++;
519
520 if (dump_file)
521 fprintf (dump_file,
522 "PCREL_OPT store (addr insn = %d, use insn = %d).\n",
523 INSN_UID (addr_insn),
524 INSN_UID (store_insn));
525
526 /* Because we have set DF_DEFER_INSN_RESCAN, we have to explicitly do it
527 after we have made changes to the insns. */
528 df_analyze();
529
530 }
531
532 /* Return the register used as the base register of MEM, if the instruction has
533 a pc-relative form. We look for BSWAP to rule out LFIWAX/LFIWZX/STFIWX, and
534 ROTATE/VEC_SELECT are RTX_EXTRA not RTX_UNARY which rules out lxvd2x. This
535 excludes instructions that do not have a pc-relative form. */
536
537 static rtx
538 get_mem_base_reg (rtx mem)
539 {
540 const char * fmt;
541
542 while (!MEM_P (mem))
543 {
544 if (GET_RTX_CLASS (GET_CODE (mem)) != RTX_UNARY
545 || GET_CODE (mem) == BSWAP)
546 return NULL_RTX;
547 fmt = GET_RTX_FORMAT (GET_CODE (mem));
548 if (fmt[0] != 'e')
549 return NULL_RTX;
550 mem = XEXP (mem, 0);
551 if (mem == NULL_RTX )
552 return NULL_RTX;
553 }
554
555 if (!MEM_SIZE_KNOWN_P (mem))
556 return NULL_RTX;
557
558 rtx addr_rtx = (XEXP (mem, 0));
559 if (GET_CODE (addr_rtx) == PRE_MODIFY)
560 addr_rtx = XEXP (addr_rtx, 1);
561
562 while (GET_CODE (addr_rtx) == PLUS
563 && CONST_INT_P (XEXP (addr_rtx, 1)))
564 addr_rtx = XEXP (addr_rtx, 0);
565
566 if (!REG_P (addr_rtx))
567 return NULL_RTX;
568
569 return addr_rtx;
570 }
571
572 /* Check whether INSN contains a reference to REGNO that will inhibit the
573 PCREL_OPT optimization. If TYPE is a load or store instruction, return true
574 if there is a definition of REGNO. If TYPE is a load instruction, then
575 return true of there is a use of REGNO. */
576
577 static bool
578 insn_references_regno_p (rtx_insn *insn, unsigned int regno,
579 enum attr_type type)
580 {
581 struct df_insn_info *insn_info = DF_INSN_INFO_GET (insn);
582 df_ref ref;
583
584 /* Return true if there is a definition of REGNO. */
585 for (ref = DF_INSN_INFO_DEFS (insn_info); ref; ref = DF_REF_NEXT_LOC (ref))
586 if (DF_REF_REGNO (ref) == regno)
587 return true;
588
589 /* If type is a load, return true if there is a use of REGNO. */
590 if (type == TYPE_LOAD
591 || type == TYPE_FPLOAD
592 || type == TYPE_VECLOAD)
593 for (ref = DF_INSN_INFO_USES (insn_info); ref; ref = DF_REF_NEXT_LOC (ref))
594 if (DF_REF_REGNO (ref) == regno)
595 return true;
596
597 return false;
598 }
599
600 /* Given an insn that loads up a base register with the address of an
601 external symbol, see if we can optimize it with the PCREL_OPT
602 optimization.
603
604 DF is used to make sure that there is exactly one definition and one
605 non-debug use of the address register defined by the insn. The use insn must
606 be a non-prefix insn, and must also be in the same basic block as the address
607 insn.
608
609 ADDR_INSN is the insn that loads the external symbol address. */
610
611 static void
612 pcrel_opt_address (rtx_insn *addr_insn)
613 {
614 counters.extern_addrs++;
615
616 /* Do some basic validation. */
617 rtx addr_set = PATTERN (addr_insn);
618 if (GET_CODE (addr_set) != SET)
619 return;
620
621 rtx addr_reg = SET_DEST (addr_set);
622 rtx addr_symbol = SET_SRC (addr_set);
623
624 if (!base_reg_operand (addr_reg, Pmode)
625 || !pcrel_external_address (addr_symbol, Pmode))
626 return;
627
628 /* The address register must have exactly one definition. */
629 struct df_insn_info *insn_info = DF_INSN_INFO_GET (addr_insn);
630 if (!insn_info)
631 return;
632
633 df_ref def = df_single_def (insn_info);
634 if (!def)
635 return;
636
637 /* Make sure there is at least one use. */
638 df_link *chain = DF_REF_CHAIN (def);
639 if (!chain || !chain->ref)
640 return;
641
642 /* Get the insn of the possible load or store. */
643 rtx_insn *use_insn = DF_REF_INSN (chain->ref);
644
645 /* Ensure there are no other uses. */
646 for (chain = chain->next; chain; chain = chain->next)
647 if (chain->ref && DF_REF_INSN_INFO (chain->ref))
648 {
649 gcc_assert (DF_REF_INSN (chain->ref));
650 if (NONDEBUG_INSN_P (DF_REF_INSN (chain->ref)))
651 return;
652 }
653
654 /* The use instruction must be a single non-prefixed instruction. */
655 if (get_attr_length (use_insn) != 4)
656 return;
657
658 /* The address and the memory operation must be in the same basic block. */
659 if (BLOCK_FOR_INSN (use_insn) != BLOCK_FOR_INSN (addr_insn))
660 return;
661
662 /* If this isn't a simple SET, skip doing the optimization. */
663 if (GET_CODE (PATTERN (use_insn)) != SET)
664 return;
665
666 enum attr_type use_insn_type = get_attr_type (use_insn);
667 unsigned int use_regno;
668
669 /* Make sure the use_insn is using addr_reg as its base register
670 for the load or store, and determine the regno for the register
671 used in the use_insn. */
672 rtx use_dest, use_src;
673 switch (use_insn_type)
674 {
675 case TYPE_LOAD:
676 case TYPE_FPLOAD:
677 case TYPE_VECLOAD:
678 /* Make sure our address register is the same register used in the
679 base address of the load. */
680 if (addr_reg != get_mem_base_reg (SET_SRC (PATTERN (use_insn))))
681 return;
682 /* Make sure we are setting a register before we look at REGNO. */
683 use_dest = SET_DEST (PATTERN (use_insn));
684 if (!register_operand (use_dest, GET_MODE (use_dest)))
685 return;
686 use_regno = REGNO (use_dest);
687 break;
688 case TYPE_STORE:
689 case TYPE_FPSTORE:
690 case TYPE_VECSTORE:
691 /* Make sure our address register is the same register used in the
692 base address of the store. */
693 if (addr_reg != get_mem_base_reg (SET_DEST (PATTERN (use_insn))))
694 return;
695 /* Make sure this is a register before we look at REGNO. */
696 use_src = SET_SRC (PATTERN (use_insn));
697 if (!register_operand (use_src, GET_MODE (use_src)))
698 return;
699 use_regno = REGNO (use_src);
700 break;
701 default:
702 /* We can only optimize loads and stores. Ignore everything else. */
703 return;
704 }
705
706 rtx_insn *insn;
707 for (insn = NEXT_INSN (addr_insn);
708 insn != use_insn;
709 insn = NEXT_INSN (insn))
710 {
711 /* If we see a call, do not do the PCREL_OPT optimization. */
712 if (CALL_P (insn))
713 return;
714
715 /* Skip debug insns. */
716 if (!NONDEBUG_INSN_P (insn))
717 continue;
718
719 /* See if it is a load or store. */
720 if (GET_CODE (PATTERN (insn)) != USE
721 && GET_CODE (PATTERN (insn)) != CLOBBER)
722 {
723 switch (get_attr_type (insn))
724 {
725 case TYPE_LOAD:
726 /* While load of the external address is a 'load' for scheduling
727 purposes, it should be safe to allow loading other external
728 addresses between the load of the external address we are
729 currently looking at and the load or store using that
730 address. */
731 if (get_attr_loads_external_address (insn)
732 == LOADS_EXTERNAL_ADDRESS_YES)
733 break;
734 /* fall through */
735
736 case TYPE_FPLOAD:
737 case TYPE_VECLOAD:
738 /* Don't do the PCREL_OPT store optimization if there is a load
739 operation. For example, the load might be trying to load the
740 value being stored in between getting the address and doing
741 the store. */
742 if (use_insn_type == TYPE_STORE
743 || use_insn_type == TYPE_FPSTORE
744 || use_insn_type == TYPE_VECSTORE)
745 return;
746 break;
747
748 case TYPE_STORE:
749 case TYPE_FPSTORE:
750 case TYPE_VECSTORE:
751 /* Don't do the PCREL_OPT load optimization if there is a store
752 operation. Perhaps the store might be to the global variable
753 through a pointer. */
754 return;
755
756 case TYPE_LOAD_L:
757 case TYPE_STORE_C:
758 case TYPE_HTM:
759 case TYPE_HTMSIMPLE:
760 /* Don't do the optimization through atomic operations. */
761 return;
762
763 default:
764 break;
765 }
766 }
767
768 /* Check for invalid references of the non-address register that is
769 used in the load or store instruction. */
770 if (insn_references_regno_p (insn, use_regno, use_insn_type))
771 return;
772 }
773
774 /* Is this a load or a store? */
775 switch (use_insn_type)
776 {
777 case TYPE_LOAD:
778 case TYPE_FPLOAD:
779 case TYPE_VECLOAD:
780 pcrel_opt_load (addr_insn, use_insn);
781 break;
782
783 case TYPE_STORE:
784 case TYPE_FPSTORE:
785 case TYPE_VECSTORE:
786 pcrel_opt_store (addr_insn, use_insn);
787 break;
788
789 default:
790 gcc_unreachable ();
791 }
792 }
793
794 /* Optimize pcrel external variable references. */
795
796 static unsigned int
797 pcrel_opt_pass (function *fun)
798 {
799 basic_block bb;
800 rtx_insn *insn, *curr_insn = 0;
801
802 memset (&counters, 0, sizeof (counters));
803
804 /* Dataflow analysis for use-def chains. However we have to specify both UD
805 and DU as otherwise when we make changes to insns for the PCREL_OPT there
806 will be dangling references. */
807 df_set_flags (DF_RD_PRUNE_DEAD_DEFS);
808 df_chain_add_problem (DF_DU_CHAIN + DF_UD_CHAIN);
809 df_note_add_problem ();
810 df_analyze ();
811
812 /* Set the defer flag as our pattern of operation will be to modify two insns,
813 then call df_analyze (). */
814 df_set_flags (DF_DEFER_INSN_RESCAN | DF_LR_RUN_DCE);
815
816 if (dump_file)
817 fprintf (dump_file, "\n");
818
819 /* Look at each basic block to see if there is a load of an external
820 variable's external address, and a single load/store using that external
821 address. */
822 FOR_ALL_BB_FN (bb, fun)
823 {
824 FOR_BB_INSNS_SAFE (bb, insn, curr_insn)
825 {
826 if (NONJUMP_INSN_P (insn)
827 && single_set (insn)
828 && get_attr_loads_external_address (insn)
829 == LOADS_EXTERNAL_ADDRESS_YES)
830 pcrel_opt_address (insn);
831 }
832 }
833
834 if (dump_file)
835 {
836 fprintf (dump_file,
837 "\n# of loads of an address of an external symbol = %lu\n",
838 counters.extern_addrs);
839
840 fprintf (dump_file, "# of PCREL_OPT loads = %lu (adjacent %lu)\n",
841 counters.loads, counters.adjacent_loads);
842
843 if (counters.failed_loads)
844 fprintf (dump_file, "# of failed PCREL_OPT loads = %lu\n",
845 counters.failed_loads);
846
847 fprintf (dump_file, "# of PCREL_OPT stores = %lu (adjacent %lu)\n",
848 counters.stores, counters.adjacent_stores);
849
850 if (counters.failed_stores)
851 fprintf (dump_file, "# of failed PCREL_OPT stores = %lu\n",
852 counters.failed_stores);
853
854 fprintf (dump_file, "\n");
855 }
856
857 df_remove_problem (df_chain);
858 df_process_deferred_rescans ();
859 df_set_flags (DF_RD_PRUNE_DEAD_DEFS | DF_LR_RUN_DCE);
860 df_analyze ();
861 return 0;
862 }
863 \f
864 /* Optimize pc-relative references for the new PCREL_OPT pass. */
865 const pass_data pass_data_pcrel_opt =
866 {
867 RTL_PASS, /* type. */
868 "pcrel_opt", /* name. */
869 OPTGROUP_NONE, /* optinfo_flags. */
870 TV_NONE, /* tv_id. */
871 0, /* properties_required. */
872 0, /* properties_provided. */
873 0, /* properties_destroyed. */
874 0, /* todo_flags_start. */
875 TODO_df_finish, /* todo_flags_finish. */
876 };
877
878 /* Pass data structures. */
879 class pcrel_opt : public rtl_opt_pass
880 {
881 public:
882 pcrel_opt (gcc::context *ctxt)
883 : rtl_opt_pass (pass_data_pcrel_opt, ctxt)
884 {}
885
886 ~pcrel_opt (void)
887 {}
888
889 /* opt_pass methods: */
890 virtual bool gate (function *)
891 {
892 return (TARGET_PCREL && TARGET_PCREL_OPT && optimize);
893 }
894
895 virtual unsigned int execute (function *fun)
896 {
897 return pcrel_opt_pass (fun);
898 }
899
900 opt_pass *clone ()
901 {
902 return new pcrel_opt (m_ctxt);
903 }
904 };
905
906 rtl_opt_pass *
907 make_pass_pcrel_opt (gcc::context *ctxt)
908 {
909 return new pcrel_opt (ctxt);
910 }