| /* Subroutines used support the pc-relative linker optimization. |
| Copyright (C) 2020-2022 Free Software Foundation, Inc. |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify it |
| under the terms of the GNU General Public License as published |
| by the Free Software Foundation; either version 3, or (at your |
| option) any later version. |
| |
| GCC is distributed in the hope that it will be useful, but WITHOUT |
| ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
| or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public |
| License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GCC; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| /* This file implements a RTL pass that looks for pc-relative loads of the |
| address of an external variable using the PCREL_GOT relocation and a single |
| load that uses that external address. If that is found we create the |
| PCREL_OPT relocation to possibly convert: |
| |
| pld addr_reg,var@pcrel@got |
| |
| <possibly other insns that do not use 'addr_reg' or 'data_reg'> |
| |
| lwz data_reg,0(addr_reg) |
| |
| into: |
| |
| plwz data_reg,var@pcrel |
| |
| <possibly other insns that do not use 'addr_reg' or 'data_reg'> |
| |
| nop |
| |
| Of course it would be nice to be able to put the plwz in this example in |
| place of the lwz but the linker cannot easily replace a 4-byte instruction |
| with an 8-byte one. |
| |
| If the variable is not defined in the main program or the code using it is |
| not in the main program, the linker puts the address in the .got section and |
| generates: |
| |
| .section .got |
| .Lvar_got: |
| .dword var |
| |
| At the point where it is referenced, we have: |
| |
| .section .text |
| pld addr_reg,.Lvar_got@pcrel |
| |
| <possibly other insns that do not use 'addr_reg' or 'data_reg'> |
| |
| lwz data_reg,0(addr_reg) |
| |
| We look for a single usage in the basic block where this external |
| address is loaded, and convert it to a PCREL_OPT relocation so the |
| linker can convert it to a single plwz in this case. Multiple uses |
| or references in another basic block will force us to not use the |
| PCREL_OPT relocation. |
| |
| We also optimize stores to the address of an external variable using the |
| PCREL_GOT relocation and a single store that uses that external address. If |
| that is found we create the PCREL_OPT relocation to possibly convert: |
| |
| pld addr_reg,var@pcrel@got |
| |
| <possibly other insns that do not use 'addr_reg' or 'data_reg'> |
| |
| stw data_reg,0(addr_reg) |
| |
| into: |
| |
| pstw data_reg,var@pcrel |
| |
| <possibly other insns that do not use 'addr_reg' or 'data_reg'> |
| |
| nop |
| |
| If the variable is not defined in the main program or the code using it is |
| not in the main program, the linker puts the address in the .got section and |
| generates: |
| |
| .section .got |
| .Lvar_got: |
| .dword var |
| |
| And at our point of reference we have: |
| |
| .section .text |
| pld addr_reg,.Lvar_got@pcrel |
| |
| <possibly other insns that do not use 'addr_reg' or 'data_reg'> |
| |
| stw data_reg,0(addr_reg) |
| |
| We only look for a single usage in the basic block where the external |
| address is loaded. Multiple uses or references in another basic block will |
| force us to not use the PCREL_OPT relocation. */ |
| |
| #define IN_TARGET_CODE 1 |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "backend.h" |
| #include "rtl.h" |
| #include "tree.h" |
| #include "memmodel.h" |
| #include "expmed.h" |
| #include "optabs.h" |
| #include "recog.h" |
| #include "df.h" |
| #include "tm_p.h" |
| #include "ira.h" |
| #include "print-tree.h" |
| #include "varasm.h" |
| #include "explow.h" |
| #include "expr.h" |
| #include "output.h" |
| #include "tree-pass.h" |
| #include "rtx-vector-builder.h" |
| #include "print-rtl.h" |
| #include "insn-attr.h" |
| #include "insn-codes.h" |
| |
| /* Various counters. */ |
| static struct { |
| unsigned long extern_addrs; |
| unsigned long loads; |
| unsigned long adjacent_loads; |
| unsigned long failed_loads; |
| unsigned long stores; |
| unsigned long adjacent_stores; |
| unsigned long failed_stores; |
| } counters; |
| |
| /* Unique integer that is appended to .Lpcrel to make a pcrel_opt label. */ |
| static unsigned int pcrel_opt_next_num; |
| |
| |
| /* Optimize a PC-relative load address to be used in a load. Before it calls |
| this function, pcrel_opt_address () uses DF to make sure that it is safe |
| to do the PCREL_OPT optimization on these insns. |
| |
| Convert insns of the form: |
| |
| (set (reg:DI addr) |
| (symbol_ref:DI "ext_symbol")) |
| |
| ... |
| |
| (set (reg:<MODE> value) |
| (mem:<MODE> (reg:DI addr))) |
| |
| into: |
| |
| (parallel [(set (reg:DI addr) |
| (unspec:<MODE> [(symbol_ref:DI "ext_symbol") |
| (const_int label_num)] |
| UNSPEC_PCREL_OPT_LD_ADDR)) |
| (set (reg:DI data) |
| (unspec:DI [(const_int 0)] |
| UNSPEC_PCREL_OPT_LD_DATA))]) |
| |
| ... |
| |
| (parallel [(set (reg:<MODE>) |
| (unspec:<MODE> [(mem:<MODE> (reg:DI addr)) |
| (reg:DI data) |
| (const_int label_num)] |
| UNSPEC_PCREL_OPT_LD_RELOC)) |
| (clobber (reg:DI addr))]) |
| |
| Because PCREL_OPT will move the actual location of the load from the second |
| insn to the first, we need to have the register for the load data be live |
| starting at the first insn. |
| |
| If the destination register for the data being loaded is the same register |
| used to hold the extern address, we generate this insn instead: |
| |
| (set (reg:DI data) |
| (unspec:DI [(symbol_ref:DI "ext_symbol") |
| (const_int label_num)] |
| UNSPEC_PCREL_OPT_LD_SAME_REG)) |
| |
| In the first insn, we set both the address of the external variable, and mark |
| that the variable being loaded both are created in that insn, and are |
| consumed in the second insn. The mode used in the first insn for the data |
| register that will be loaded in the second insn doesn't matter in the end so |
| we use DImode. We just need to mark that both registers may be set in the |
| first insn, and will be used in the second insn. |
| |
| The UNSPEC_PCREL_OPT_LD_ADDR insn will generate the load address plus |
| a definition of a label (.Lpcrel<n>), while the UNSPEC_PCREL_OPT_LD_RELOC |
| insn will generate the .reloc to tell the linker to tie the load address and |
| load using that address together. |
| |
| pld b,ext_symbol@got@pcrel |
| .Lpcrel1: |
| |
| ... |
| |
| .reloc .Lpcrel1-8,R_PPC64_PCREL_OPT,.-(.Lpcrel1-8) |
| lwz r,0(b) |
| |
| If ext_symbol is defined in another object file in the main program and we |
| are linking the main program, the linker will convert the above instructions |
| to: |
| |
| plwz r,ext_symbol@got@pcrel |
| |
| ... |
| |
| nop |
| |
| ADDR_INSN is the insn that is loading the address. |
| LOAD_INSN is the insn that uses the address to load the actual data. */ |
| |
| static void |
| pcrel_opt_load (rtx_insn *addr_insn, rtx_insn *load_insn) |
| { |
| rtx addr_set = PATTERN (addr_insn); |
| gcc_assert (GET_CODE (addr_set) == SET); |
| |
| rtx addr_reg = SET_DEST (addr_set); |
| gcc_assert (base_reg_operand (addr_reg, Pmode)); |
| |
| rtx addr_symbol = SET_SRC (addr_set); |
| gcc_assert (pcrel_external_address (addr_symbol, Pmode)); |
| |
| rtx load_set = PATTERN (load_insn); |
| gcc_assert (GET_CODE (load_set) == SET); |
| |
| /* Make sure there are no references to the register being loaded |
| between the two insns. */ |
| rtx reg = SET_DEST (load_set); |
| if (reg_used_between_p (reg, addr_insn, load_insn) |
| || reg_set_between_p (reg, addr_insn, load_insn)) |
| return; |
| |
| rtx mem = SET_SRC (load_set); |
| machine_mode reg_mode = GET_MODE (reg); |
| machine_mode mem_mode = GET_MODE (mem); |
| rtx mem_inner = mem; |
| unsigned int reg_regno = reg_or_subregno (reg); |
| |
| /* Handle the fact that LWA is a DS format instruction, but LWZ is a D format |
| instruction. If the mem load is a signed SImode (i.e. LWA would be used) |
| we set mem_mode to DImode so that pcrel_opt_valid_mem_p() will check that |
| the address will work for a DS-form instruction. If it won't work, we skip |
| the optimization. The float loads are all indexed so there are no problems |
| there. */ |
| |
| if (GET_CODE (mem) == SIGN_EXTEND && GET_MODE (XEXP (mem, 0)) == SImode) |
| { |
| if (!INT_REGNO_P (reg_regno)) |
| return; |
| |
| mem_inner = XEXP (mem, 0); |
| mem_mode = DImode; |
| } |
| |
| else if (GET_CODE (mem) == SIGN_EXTEND |
| || GET_CODE (mem) == ZERO_EXTEND |
| || GET_CODE (mem) == FLOAT_EXTEND) |
| { |
| mem_inner = XEXP (mem, 0); |
| mem_mode = GET_MODE (mem_inner); |
| } |
| |
| if (!MEM_P (mem_inner)) |
| return; |
| |
| /* Can we do PCREL_OPT for this reference? */ |
| if (!pcrel_opt_valid_mem_p (reg, mem_mode, mem_inner)) |
| return; |
| |
| /* Allocate a new PC-relative label, and update the load external address |
| insn. |
| |
| If the register being loaded is different from the address register, we |
| need to indicate both registers are set at the load of the address. |
| |
| (parallel [(set (reg load) |
| (unspec [(symbol_ref addr_symbol) |
| (const_int label_num)] |
| UNSPEC_PCREL_OPT_LD_ADDR)) |
| (set (reg addr) |
| (unspec [(const_int 0)] |
| UNSPEC_PCREL_OPT_LD_DATA))]) |
| |
| If the register being loaded is the same as the address register, we use |
| an alternate form: |
| |
| (set (reg load) |
| (unspec [(symbol_ref addr_symbol) |
| (const_int label_num)] |
| UNSPEC_PCREL_OPT_LD_SAME_REG)) */ |
| unsigned int addr_regno = reg_or_subregno (addr_reg); |
| rtx label_num = GEN_INT (++pcrel_opt_next_num); |
| rtx reg_di = gen_rtx_REG (DImode, reg_regno); |
| rtx addr_pattern; |
| |
| /* Create the load address, either using the pattern with an explicit clobber |
| if the address register is not the same as the register being loaded, or |
| using the pattern that requires the address register to be the address |
| loaded. */ |
| if (addr_regno != reg_regno) |
| addr_pattern = gen_pcrel_opt_ld_addr (addr_reg, addr_symbol, label_num, |
| reg_di); |
| else |
| addr_pattern = gen_pcrel_opt_ld_addr_same_reg (addr_reg, addr_symbol, |
| label_num); |
| |
| validate_change (addr_insn, &PATTERN (addr_insn), addr_pattern, false); |
| |
| /* Update the load insn. If the mem had a sign/zero/float extend, add that |
| also after doing the UNSPEC. Add an explicit clobber of the external |
| address register just to make it clear that the address register dies. |
| |
| (parallel [(set (reg:<MODE> data) |
| (unspec:<MODE> [(mem (addr_reg) |
| (reg:DI data) |
| (const_int label_num)] |
| UNSPEC_PCREL_OPT_LD_RELOC)) |
| (clobber (reg:DI addr_reg))]) */ |
| rtvec v_load = gen_rtvec (3, mem_inner, reg_di, label_num); |
| rtx new_load = gen_rtx_UNSPEC (GET_MODE (mem_inner), v_load, |
| UNSPEC_PCREL_OPT_LD_RELOC); |
| |
| if (GET_CODE (mem) != GET_CODE (mem_inner)) |
| new_load = gen_rtx_fmt_e (GET_CODE (mem), reg_mode, new_load); |
| |
| rtx new_load_set = gen_rtx_SET (reg, new_load); |
| rtx load_clobber = gen_rtx_CLOBBER (VOIDmode, |
| (addr_regno == reg_regno |
| ? gen_rtx_SCRATCH (Pmode) |
| : addr_reg)); |
| rtx new_load_pattern |
| = gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, new_load_set, load_clobber)); |
| |
| validate_change (load_insn, &PATTERN (load_insn), new_load_pattern, false); |
| |
| /* Attempt to apply the changes: */ |
| if (!apply_change_group ()) |
| { |
| /* PCREL_OPT load optimization did not succeed. */ |
| counters.failed_loads++; |
| if (dump_file) |
| fprintf (dump_file, |
| "PCREL_OPT load failed (addr insn = %d, use insn = %d).\n", |
| INSN_UID (addr_insn), |
| INSN_UID (load_insn)); |
| return; |
| } |
| |
| /* PCREL_OPT load optimization succeeded. */ |
| counters.loads++; |
| if (next_nonnote_insn (addr_insn) == load_insn) |
| counters.adjacent_loads++; |
| |
| if (dump_file) |
| fprintf (dump_file, |
| "PCREL_OPT load (addr insn = %d, use insn = %d).\n", |
| INSN_UID (addr_insn), |
| INSN_UID (load_insn)); |
| |
| /* Because we have set DF_DEFER_INSN_RESCAN, we have to explicitly do it |
| after we have made changes to the insns. */ |
| df_analyze (); |
| |
| } |
| |
| /* Optimize a PC-relative load address to be used in a store. Before calling |
| this function, pcrel_opt_address () uses DF to make sure it is safe to do |
| the PCREL_OPT optimization. |
| |
| Convert insns of the form: |
| |
| (set (reg:DI addr) |
| (symbol_ref:DI "ext_symbol")) |
| |
| ... |
| |
| (set (mem:<MODE> (reg:DI addr)) |
| (reg:<MODE> value)) |
| |
| into: |
| |
| (parallel [(set (reg:DI addr) |
| (unspec:DI [(symbol_ref:DI "ext_symbol") |
| (const_int label_num)] |
| UNSPEC_PCREL_OPT_ST_ADDR)) |
| (use (reg:<MODE> value))]) |
| |
| ... |
| |
| (parallel [(set (mem:<MODE> (reg:DI addr)) |
| (unspec:<MODE> [(reg:<MODE>) |
| (const_int label_num)] |
| UNSPEC_PCREL_OPT_ST_RELOC)) |
| (clobber (reg:DI addr))]) |
| |
| The UNSPEC_PCREL_OPT_ST_ADDR insn will generate the load address plus a |
| definition of a label (.Lpcrel<n>), while the UNSPEC_PCREL_OPT_ST_RELOC insn |
| will generate the .reloc to tell the linker to tie the load address and load |
| using that address together. |
| |
| pld b,ext_symbol@got@pcrel |
| .Lpcrel1: |
| |
| ... |
| |
| .reloc .Lpcrel1-8,R_PPC64_PCREL_OPT,.-(.Lpcrel1-8) |
| stw r,0(b) |
| |
| If ext_symbol is defined in another object file in the main program and we |
| are linking the main program, the linker will convert the above instructions |
| to: |
| |
| pstwz r,ext_symbol@got@pcrel |
| |
| ... |
| |
| nop */ |
| |
| static void |
| pcrel_opt_store (rtx_insn *addr_insn, /* insn loading address. */ |
| rtx_insn *store_insn) /* insn using address. */ |
| { |
| rtx addr_old_set = PATTERN (addr_insn); |
| gcc_assert (GET_CODE (addr_old_set) == SET); |
| |
| rtx addr_reg = SET_DEST (addr_old_set); |
| gcc_assert (base_reg_operand (addr_reg, Pmode)); |
| |
| rtx addr_symbol = SET_SRC (addr_old_set); |
| gcc_assert (pcrel_external_address (addr_symbol, Pmode)); |
| |
| rtx store_set = PATTERN (store_insn); |
| gcc_assert (GET_CODE (store_set) == SET); |
| |
| rtx mem = SET_DEST (store_set); |
| if (!MEM_P (mem)) |
| return; |
| |
| machine_mode mem_mode = GET_MODE (mem); |
| rtx reg = SET_SRC (store_set); |
| |
| /* Don't allow storing the address of the external variable. */ |
| if (reg_or_subregno (reg) == reg_or_subregno (addr_reg)) |
| return; |
| |
| /* Can we do PCREL_OPT for this reference? */ |
| if (!pcrel_opt_valid_mem_p (reg, mem_mode, mem)) |
| return; |
| |
| /* Allocate a new PC-relative label, and update the load address insn. |
| |
| (parallel [(set (reg addr) |
| (unspec [(symbol_ref symbol) |
| (const_int label_num)] |
| UNSPEC_PCREL_OPT_ST_ADDR)) |
| (use (reg store))]) |
| */ |
| rtx label_num = GEN_INT (++pcrel_opt_next_num); |
| rtvec v_addr = gen_rtvec (2, addr_symbol, label_num); |
| rtx addr_unspec = gen_rtx_UNSPEC (Pmode, v_addr, |
| UNSPEC_PCREL_OPT_ST_ADDR); |
| rtx addr_new_set = gen_rtx_SET (addr_reg, addr_unspec); |
| rtx addr_use = gen_rtx_USE (VOIDmode, reg); |
| rtx addr_new_pattern |
| = gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, addr_new_set, addr_use)); |
| |
| validate_change (addr_insn, &PATTERN (addr_insn), addr_new_pattern, false); |
| |
| /* Update the store insn. Add an explicit clobber of the external address |
| register just to be sure there are no additional uses of the address |
| register. |
| |
| (parallel [(set (mem (addr_reg) |
| (unspec:<MODE> [(reg) |
| (const_int label_num)] |
| UNSPEC_PCREL_OPT_ST_RELOC)) |
| (clobber (reg:DI addr_reg))]) */ |
| rtvec v_store = gen_rtvec (2, reg, label_num); |
| rtx new_store = gen_rtx_UNSPEC (mem_mode, v_store, |
| UNSPEC_PCREL_OPT_ST_RELOC); |
| |
| rtx new_store_set = gen_rtx_SET (mem, new_store); |
| rtx store_clobber = gen_rtx_CLOBBER (VOIDmode, addr_reg); |
| rtx new_store_pattern |
| = gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, new_store_set, store_clobber)); |
| |
| validate_change (store_insn, &PATTERN (store_insn), new_store_pattern, false); |
| |
| /* Attempt to apply the changes: */ |
| if (!apply_change_group ()) |
| { |
| /* PCREL_OPT store failed. */ |
| counters.failed_stores++; |
| if (dump_file) |
| fprintf (dump_file, |
| "PCREL_OPT store failed (addr insn = %d, use insn = %d).\n", |
| INSN_UID (addr_insn), |
| INSN_UID (store_insn)); |
| return; |
| } |
| |
| /* PCREL_OPT store succeeded. */ |
| counters.stores++; |
| if (next_nonnote_insn (addr_insn) == store_insn) |
| counters.adjacent_stores++; |
| |
| if (dump_file) |
| fprintf (dump_file, |
| "PCREL_OPT store (addr insn = %d, use insn = %d).\n", |
| INSN_UID (addr_insn), |
| INSN_UID (store_insn)); |
| |
| /* Because we have set DF_DEFER_INSN_RESCAN, we have to explicitly do it |
| after we have made changes to the insns. */ |
| df_analyze(); |
| |
| } |
| |
| /* Return the register used as the base register of MEM, if the instruction has |
| a pc-relative form. We look for BSWAP to rule out LFIWAX/LFIWZX/STFIWX, and |
| ROTATE/VEC_SELECT are RTX_EXTRA not RTX_UNARY which rules out lxvd2x. This |
| excludes instructions that do not have a pc-relative form. */ |
| |
| static rtx |
| get_mem_base_reg (rtx mem) |
| { |
| const char * fmt; |
| |
| while (!MEM_P (mem)) |
| { |
| if (GET_RTX_CLASS (GET_CODE (mem)) != RTX_UNARY |
| || GET_CODE (mem) == BSWAP) |
| return NULL_RTX; |
| fmt = GET_RTX_FORMAT (GET_CODE (mem)); |
| if (fmt[0] != 'e') |
| return NULL_RTX; |
| mem = XEXP (mem, 0); |
| if (mem == NULL_RTX ) |
| return NULL_RTX; |
| } |
| |
| if (!MEM_SIZE_KNOWN_P (mem)) |
| return NULL_RTX; |
| |
| rtx addr_rtx = (XEXP (mem, 0)); |
| if (GET_CODE (addr_rtx) == PRE_MODIFY) |
| addr_rtx = XEXP (addr_rtx, 1); |
| |
| while (GET_CODE (addr_rtx) == PLUS |
| && CONST_INT_P (XEXP (addr_rtx, 1))) |
| addr_rtx = XEXP (addr_rtx, 0); |
| |
| if (!REG_P (addr_rtx)) |
| return NULL_RTX; |
| |
| return addr_rtx; |
| } |
| |
| /* Check whether INSN contains a reference to REGNO that will inhibit the |
| PCREL_OPT optimization. If TYPE is a load or store instruction, return true |
| if there is a definition of REGNO. If TYPE is a load instruction, then |
| return true of there is a use of REGNO. */ |
| |
| static bool |
| insn_references_regno_p (rtx_insn *insn, unsigned int regno, |
| enum attr_type type) |
| { |
| struct df_insn_info *insn_info = DF_INSN_INFO_GET (insn); |
| df_ref ref; |
| |
| /* Return true if there is a definition of REGNO. */ |
| for (ref = DF_INSN_INFO_DEFS (insn_info); ref; ref = DF_REF_NEXT_LOC (ref)) |
| if (DF_REF_REGNO (ref) == regno) |
| return true; |
| |
| /* If type is a load, return true if there is a use of REGNO. */ |
| if (type == TYPE_LOAD |
| || type == TYPE_FPLOAD |
| || type == TYPE_VECLOAD) |
| for (ref = DF_INSN_INFO_USES (insn_info); ref; ref = DF_REF_NEXT_LOC (ref)) |
| if (DF_REF_REGNO (ref) == regno) |
| return true; |
| |
| return false; |
| } |
| |
| /* Given an insn that loads up a base register with the address of an |
| external symbol, see if we can optimize it with the PCREL_OPT |
| optimization. |
| |
| DF is used to make sure that there is exactly one definition and one |
| non-debug use of the address register defined by the insn. The use insn must |
| be a non-prefix insn, and must also be in the same basic block as the address |
| insn. |
| |
| ADDR_INSN is the insn that loads the external symbol address. */ |
| |
| static void |
| pcrel_opt_address (rtx_insn *addr_insn) |
| { |
| counters.extern_addrs++; |
| |
| /* Do some basic validation. */ |
| rtx addr_set = PATTERN (addr_insn); |
| if (GET_CODE (addr_set) != SET) |
| return; |
| |
| rtx addr_reg = SET_DEST (addr_set); |
| rtx addr_symbol = SET_SRC (addr_set); |
| |
| if (!base_reg_operand (addr_reg, Pmode) |
| || !pcrel_external_address (addr_symbol, Pmode)) |
| return; |
| |
| /* The address register must have exactly one definition. */ |
| struct df_insn_info *insn_info = DF_INSN_INFO_GET (addr_insn); |
| if (!insn_info) |
| return; |
| |
| df_ref def = df_single_def (insn_info); |
| if (!def) |
| return; |
| |
| /* Make sure there is at least one use. */ |
| df_link *chain = DF_REF_CHAIN (def); |
| if (!chain || !chain->ref) |
| return; |
| |
| /* Get the insn of the possible load or store. */ |
| rtx_insn *use_insn = DF_REF_INSN (chain->ref); |
| |
| /* Ensure there are no other uses. */ |
| for (chain = chain->next; chain; chain = chain->next) |
| if (chain->ref && DF_REF_INSN_INFO (chain->ref)) |
| { |
| gcc_assert (DF_REF_INSN (chain->ref)); |
| if (NONDEBUG_INSN_P (DF_REF_INSN (chain->ref))) |
| return; |
| } |
| |
| /* The use instruction must be a single non-prefixed instruction. */ |
| if (get_attr_length (use_insn) != 4) |
| return; |
| |
| /* The address and the memory operation must be in the same basic block. */ |
| if (BLOCK_FOR_INSN (use_insn) != BLOCK_FOR_INSN (addr_insn)) |
| return; |
| |
| /* If this isn't a simple SET, skip doing the optimization. */ |
| if (GET_CODE (PATTERN (use_insn)) != SET) |
| return; |
| |
| enum attr_type use_insn_type = get_attr_type (use_insn); |
| unsigned int use_regno; |
| |
| /* Make sure the use_insn is using addr_reg as its base register |
| for the load or store, and determine the regno for the register |
| used in the use_insn. */ |
| rtx use_dest, use_src; |
| switch (use_insn_type) |
| { |
| case TYPE_LOAD: |
| case TYPE_FPLOAD: |
| case TYPE_VECLOAD: |
| /* Make sure our address register is the same register used in the |
| base address of the load. */ |
| if (addr_reg != get_mem_base_reg (SET_SRC (PATTERN (use_insn)))) |
| return; |
| /* Make sure we are setting a register before we look at REGNO. */ |
| use_dest = SET_DEST (PATTERN (use_insn)); |
| if (!register_operand (use_dest, GET_MODE (use_dest))) |
| return; |
| use_regno = REGNO (use_dest); |
| break; |
| case TYPE_STORE: |
| case TYPE_FPSTORE: |
| case TYPE_VECSTORE: |
| /* Make sure our address register is the same register used in the |
| base address of the store. */ |
| if (addr_reg != get_mem_base_reg (SET_DEST (PATTERN (use_insn)))) |
| return; |
| /* Make sure this is a register before we look at REGNO. */ |
| use_src = SET_SRC (PATTERN (use_insn)); |
| if (!register_operand (use_src, GET_MODE (use_src))) |
| return; |
| use_regno = REGNO (use_src); |
| break; |
| default: |
| /* We can only optimize loads and stores. Ignore everything else. */ |
| return; |
| } |
| |
| rtx_insn *insn; |
| for (insn = NEXT_INSN (addr_insn); |
| insn != use_insn; |
| insn = NEXT_INSN (insn)) |
| { |
| /* If we see a call, do not do the PCREL_OPT optimization. */ |
| if (CALL_P (insn)) |
| return; |
| |
| /* Skip debug insns. */ |
| if (!NONDEBUG_INSN_P (insn)) |
| continue; |
| |
| /* See if it is a load or store. */ |
| if (GET_CODE (PATTERN (insn)) != USE |
| && GET_CODE (PATTERN (insn)) != CLOBBER) |
| { |
| switch (get_attr_type (insn)) |
| { |
| case TYPE_LOAD: |
| /* While load of the external address is a 'load' for scheduling |
| purposes, it should be safe to allow loading other external |
| addresses between the load of the external address we are |
| currently looking at and the load or store using that |
| address. */ |
| if (get_attr_loads_external_address (insn) |
| == LOADS_EXTERNAL_ADDRESS_YES) |
| break; |
| /* fall through */ |
| |
| case TYPE_FPLOAD: |
| case TYPE_VECLOAD: |
| /* Don't do the PCREL_OPT store optimization if there is a load |
| operation. For example, the load might be trying to load the |
| value being stored in between getting the address and doing |
| the store. */ |
| if (use_insn_type == TYPE_STORE |
| || use_insn_type == TYPE_FPSTORE |
| || use_insn_type == TYPE_VECSTORE) |
| return; |
| break; |
| |
| case TYPE_STORE: |
| case TYPE_FPSTORE: |
| case TYPE_VECSTORE: |
| /* Don't do the PCREL_OPT load optimization if there is a store |
| operation. Perhaps the store might be to the global variable |
| through a pointer. */ |
| return; |
| |
| case TYPE_LOAD_L: |
| case TYPE_STORE_C: |
| case TYPE_HTM: |
| case TYPE_HTMSIMPLE: |
| /* Don't do the optimization through atomic operations. */ |
| return; |
| |
| default: |
| break; |
| } |
| } |
| |
| /* Check for invalid references of the non-address register that is |
| used in the load or store instruction. */ |
| if (insn_references_regno_p (insn, use_regno, use_insn_type)) |
| return; |
| } |
| |
| /* Is this a load or a store? */ |
| switch (use_insn_type) |
| { |
| case TYPE_LOAD: |
| case TYPE_FPLOAD: |
| case TYPE_VECLOAD: |
| pcrel_opt_load (addr_insn, use_insn); |
| break; |
| |
| case TYPE_STORE: |
| case TYPE_FPSTORE: |
| case TYPE_VECSTORE: |
| pcrel_opt_store (addr_insn, use_insn); |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Optimize pcrel external variable references. */ |
| |
| static unsigned int |
| pcrel_opt_pass (function *fun) |
| { |
| basic_block bb; |
| rtx_insn *insn, *curr_insn = 0; |
| |
| memset (&counters, 0, sizeof (counters)); |
| |
| /* Dataflow analysis for use-def chains. However we have to specify both UD |
| and DU as otherwise when we make changes to insns for the PCREL_OPT there |
| will be dangling references. */ |
| df_set_flags (DF_RD_PRUNE_DEAD_DEFS); |
| df_chain_add_problem (DF_DU_CHAIN + DF_UD_CHAIN); |
| df_note_add_problem (); |
| df_analyze (); |
| |
| /* Set the defer flag as our pattern of operation will be to modify two insns, |
| then call df_analyze (). */ |
| df_set_flags (DF_DEFER_INSN_RESCAN | DF_LR_RUN_DCE); |
| |
| if (dump_file) |
| fprintf (dump_file, "\n"); |
| |
| /* Look at each basic block to see if there is a load of an external |
| variable's external address, and a single load/store using that external |
| address. */ |
| FOR_ALL_BB_FN (bb, fun) |
| { |
| FOR_BB_INSNS_SAFE (bb, insn, curr_insn) |
| { |
| if (NONJUMP_INSN_P (insn) |
| && single_set (insn) |
| && get_attr_loads_external_address (insn) |
| == LOADS_EXTERNAL_ADDRESS_YES) |
| pcrel_opt_address (insn); |
| } |
| } |
| |
| if (dump_file) |
| { |
| fprintf (dump_file, |
| "\n# of loads of an address of an external symbol = %lu\n", |
| counters.extern_addrs); |
| |
| fprintf (dump_file, "# of PCREL_OPT loads = %lu (adjacent %lu)\n", |
| counters.loads, counters.adjacent_loads); |
| |
| if (counters.failed_loads) |
| fprintf (dump_file, "# of failed PCREL_OPT loads = %lu\n", |
| counters.failed_loads); |
| |
| fprintf (dump_file, "# of PCREL_OPT stores = %lu (adjacent %lu)\n", |
| counters.stores, counters.adjacent_stores); |
| |
| if (counters.failed_stores) |
| fprintf (dump_file, "# of failed PCREL_OPT stores = %lu\n", |
| counters.failed_stores); |
| |
| fprintf (dump_file, "\n"); |
| } |
| |
| df_remove_problem (df_chain); |
| df_process_deferred_rescans (); |
| df_set_flags (DF_RD_PRUNE_DEAD_DEFS | DF_LR_RUN_DCE); |
| df_analyze (); |
| return 0; |
| } |
| |
| /* Optimize pc-relative references for the new PCREL_OPT pass. */ |
| const pass_data pass_data_pcrel_opt = |
| { |
| RTL_PASS, /* type. */ |
| "pcrel_opt", /* name. */ |
| OPTGROUP_NONE, /* optinfo_flags. */ |
| TV_NONE, /* tv_id. */ |
| 0, /* properties_required. */ |
| 0, /* properties_provided. */ |
| 0, /* properties_destroyed. */ |
| 0, /* todo_flags_start. */ |
| TODO_df_finish, /* todo_flags_finish. */ |
| }; |
| |
| /* Pass data structures. */ |
| class pcrel_opt : public rtl_opt_pass |
| { |
| public: |
| pcrel_opt (gcc::context *ctxt) |
| : rtl_opt_pass (pass_data_pcrel_opt, ctxt) |
| {} |
| |
| ~pcrel_opt (void) |
| {} |
| |
| /* opt_pass methods: */ |
| virtual bool gate (function *) |
| { |
| return (TARGET_PCREL && TARGET_PCREL_OPT && optimize); |
| } |
| |
| virtual unsigned int execute (function *fun) |
| { |
| return pcrel_opt_pass (fun); |
| } |
| |
| opt_pass *clone () |
| { |
| return new pcrel_opt (m_ctxt); |
| } |
| }; |
| |
| rtl_opt_pass * |
| make_pass_pcrel_opt (gcc::context *ctxt) |
| { |
| return new pcrel_opt (ctxt); |
| } |