;; Machine description for the PCREL_OPT optimization. ;; Copyright (C) 2020-2021 Free Software Foundation, Inc. ;; Contributed by Michael Meissner (meissner@linux.ibm.com)

;; 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/.

;; Support for the PCREL_OPT optimization. PCREL_OPT looks for instances where ;; an external variable is used only once, either for reading or for writing. ;; ;; If we are optimizing a single read, normally the code would look like: ;; ;; (set (reg:DI ) ;; (symbol_ref:DI “<extern_addr>”)) # is currently dead ;; ;; ... # insns do not need to be adjacent ;; ;; (set (reg:SI ) ;; (mem:SI (reg:DI ))) # dies with this insn ;; ;; We transform this into: ;; ;; (parallel [(set (reg:DI ) ;; (unspec:SI [(symbol_ref:DI <extern_addr>) ;; (const_int )] ;; UNSPEC_PCREL_OPT_LD_ADDR)) ;; (set (reg:DI ) ;; (unspec:DI [(const_int 0)] ;; UNSPEC_PCREL_OPT_LD_DATA))]) ;; ;; ... ;; ;; (parallel [(set (reg:SI ) ;; (unspec:SI [(mem:SI (reg:DI )) ;; (reg:DI ) ;; (const_int )] ;; UNSPEC_PCREL_OPT_LD_RELOC)) ;; (clobber (reg:DI ))]) ;; ;; The marker is an integer constant that links the load of the external ;; address to the load of the actual variable. ;; ;; 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. It doesn‘t matter what mode the register that ;; we will ultimately do the load into, 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. ;; ;; Since we use UNSPEC’s and link both the the register holding the external ;; address and the value being loaded, it should prevent other passes from ;; modifying it. ;; ;; If the register being loaded is the same as the base register, we use an ;; alternate form of the insns. ;; ;; (set (reg:DI <data_ptr>) ;; (unspec:DI [(symbol_ref:DI <extern_addr>) ;; (const_int )] ;; UNSPEC_PCREL_OPT_LD_SAME_REG)) ;; ;; ... ;; ;; (parallel [(set (reg:SI ) ;; (unspec:SI [(mem:SI (reg:DI )) ;; (reg:DI ) ;; (const_int )] ;; UNSPEC_PCREL_OPT_LD_RELOC)) ;; (clobber (reg:DI ))])

(define_c_enum “unspec” [UNSPEC_PCREL_OPT_LD_ADDR UNSPEC_PCREL_OPT_LD_DATA UNSPEC_PCREL_OPT_LD_SAME_REG UNSPEC_PCREL_OPT_LD_RELOC UNSPEC_PCREL_OPT_ST_ADDR UNSPEC_PCREL_OPT_ST_RELOC])

;; Modes that are supported for PCREL_OPT (define_mode_iterator PCRELOPT [QI HI SI DI TI SF DF KF V1TI V2DI V4SI V8HI V16QI V2DF V4SF (TF “TARGET_FLOAT128_TYPE && TARGET_IEEEQUAD”)])

;; Vector modes for PCREL_OPT (define_mode_iterator PCRELOPT_VECT [TI KF V1TI V2DI V4SI V8HI V16QI V2DF V4SF (TF “TARGET_FLOAT128_TYPE && TARGET_IEEEQUAD”)])

;; Insn for loading the external address, where the register being loaded is not ;; the same as the register being loaded with the data. (define_insn “pcrel_opt_ld_addr” [(set (match_operand:DI 0 “base_reg_operand” “=&b,&b”) (unspec:DI [(match_operand:DI 1 “pcrel_external_address”) (match_operand 2 “const_int_operand” “n,n”)] UNSPEC_PCREL_OPT_LD_ADDR)) (set (match_operand:DI 3 “gpc_reg_operand” “=r,wa”) (unspec:DI [(const_int 0)] UNSPEC_PCREL_OPT_LD_DATA))] “TARGET_PCREL_OPT && reg_or_subregno (operands[0]) != reg_or_subregno (operands[3])” “ld %0,%a1\n.Lpcrel%2:” [(set_attr “prefixed” “yes”) (set_attr “type” “load”) (set_attr “loads_external_address” “yes”)])

;; Alternate form of loading up the external address that is the same register ;; as the final load. (define_insn “pcrel_opt_ld_addr_same_reg” [(set (match_operand:DI 0 “base_reg_operand” “=b”) (unspec:DI [(match_operand:DI 1 “pcrel_external_address”) (match_operand 2 “const_int_operand” “n”)] UNSPEC_PCREL_OPT_LD_SAME_REG))] “TARGET_PCREL_OPT” “ld %0,%a1\n.Lpcrel%2:” [(set_attr “prefixed” “yes”) (set_attr “type” “load”) (set_attr “loads_external_address” “yes”)])

;; PCREL_OPT modes that are optimized for loading or storing GPRs. (define_mode_iterator PCRELOPT_GPR [QI HI SI DI SF DF])

(define_mode_attr PCRELOPT_GPR_LD [(QI “lbz”) (HI “lhz”) (SI “lwz”) (SF “lwz”) (DI “ld”) (DF “ld”)])

;; PCREL_OPT load operation of GPRs. Operand 4 (the register used to hold the ;; address of the external symbol) is SCRATCH if the same register is used for ;; the normal load. (define_insn “*pcrel_opt_ld_gpr” [(parallel [(set (match_operand:PCRELOPT_GPR 0 “int_reg_operand” “+r”) (unspec:PCRELOPT_GPR [ (match_operand:PCRELOPT_GPR 1 “d_form_memory” “m”) (match_operand:DI 2 “int_reg_operand” “0”) (match_operand 3 “const_int_operand” “n”)] UNSPEC_PCREL_OPT_LD_RELOC)) (clobber (match_scratch:DI 4 “=bX”))])] “TARGET_PCREL_OPT && (GET_CODE (operands[4]) == SCRATCH || reg_mentioned_p (operands[4], operands[1]))” { output_pcrel_opt_reloc (operands[3]); return “<PCRELOPT_GPR_LD> %0,%1”; } [(set_attr “type” “load”)])

;; PCREL_OPT load with sign/zero extension (define_insn “*pcrel_opt_ldsi__gpr” [(set (match_operand:EXTSI 0 “int_reg_operand” “+r”) (any_extend:EXTSI (unspec:SI [(match_operand:SI 1 “d_form_memory” “m”) (match_operand:DI 2 “int_reg_operand” “0”) (match_operand 3 “const_int_operand” “n”)] UNSPEC_PCREL_OPT_LD_RELOC))) (clobber (match_scratch:DI 4 “=bX”))] “TARGET_PCREL_OPT” { output_pcrel_opt_reloc (operands[3]); return “lw %0,%1”; } [(set_attr “type” “load”)])

(define_insn “*pcrel_opt_ldhi__gpr” [(set (match_operand:EXTHI 0 “int_reg_operand” “+r”) (any_extend:EXTHI (unspec:HI [(match_operand:HI 1 “d_form_memory” “m”) (match_operand:DI 2 “int_reg_operand” “0”) (match_operand 3 “const_int_operand” “n”)] UNSPEC_PCREL_OPT_LD_RELOC))) (clobber (match_scratch:DI 4 “=bX”))] “TARGET_PCREL_OPT” { output_pcrel_opt_reloc (operands[3]); return “lh %0,%1”; } [(set_attr “type” “load”)])

(define_insn “*pcrel_opt_ldqi_u_gpr” [(set (match_operand:EXTQI 0 “int_reg_operand” “+r”) (zero_extend:EXTQI (unspec:QI [(match_operand:QI 1 “d_form_memory” “m”) (match_operand:DI 2 “int_reg_operand” “0”) (match_operand 3 “const_int_operand” “n”)] UNSPEC_PCREL_OPT_LD_RELOC))) (clobber (match_scratch:DI 4 “=bX”))] “TARGET_PCREL_OPT” { output_pcrel_opt_reloc (operands[3]); return “lbz %0,%1”; } [(set_attr “type” “load”)])

;; Scalar types that can be optimized by loading them into floating point ;; or Altivec registers. (define_mode_iterator PCRELOPT_FP [DI DF SF])

;; Load instructions to load up scalar floating point or 64-bit integer values ;; into floating point registers or Altivec registers. (define_mode_attr PCRELOPT_FPR_LD [(DI “lfd”) (DF “lfd”) (SF “lfs”)]) (define_mode_attr PCRELOPT_VMX_LD [(DI “lxsd”) (DF “lxsd”) (SF “lxssp”)])

;; PCREL_OPT load operation of scalar DF/DI/SF into vector registers. (define_insn “*pcrel_opt_ld_vsx” [(set (match_operand:PCRELOPT_FP 0 “vsx_register_operand” “+d,v”) (unspec:PCRELOPT_FP [(match_operand:PCRELOPT_FP 1 “d_form_memory” “m,m”) (match_operand:DI 2 “vsx_register_operand” “0,0”) (match_operand 3 “const_int_operand” “n,n”)] UNSPEC_PCREL_OPT_LD_RELOC)) (clobber (match_operand:DI 4 “base_reg_operand” “=b,b”))] “TARGET_PCREL_OPT” { output_pcrel_opt_reloc (operands[3]); return which_alternative ? “<PCRELOPT_VMX_LD> %0,%1” : “<PCRELOPT_FPR_LD> %0,%1”; } [(set_attr “type” “fpload”)])

;; PCREL_OPT optimization extending SFmode to DFmode via a load. (define_insn “*pcrel_opt_ldsf_df” [(set (match_operand:DF 0 “vsx_register_operand” “+d,v”) (float_extend:DF (unspec:SF [(match_operand:SF 1 “d_form_memory” “m,m”) (match_operand:DI 2 “vsx_register_operand” “0,0”) (match_operand 3 “const_int_operand” “n,n”)] UNSPEC_PCREL_OPT_LD_RELOC))) (clobber (match_operand:DI 4 “base_reg_operand” “=b,b”))] “TARGET_PCREL_OPT” { output_pcrel_opt_reloc (operands[3]); return which_alternative ? “lxssp %0,%1” : “lfs %0,%1”; } [(set_attr “type” “fpload”)])

;; PCREL_OPT load operation of vector/float128 types into vector registers. (define_insn “*pcrel_opt_ld” [(set (match_operand:PCRELOPT_VECT 0 “vsx_register_operand” “+wa”) (unspec:PCRELOPT_VECT [(match_operand:PCRELOPT_VECT 1 “d_form_memory” “m”) (match_operand:DI 2 “vsx_register_operand” “0”) (match_operand 3 “const_int_operand” “n”)] UNSPEC_PCREL_OPT_LD_RELOC)) (clobber (match_operand:DI 4 “base_reg_operand” “=b”))] “TARGET_PCREL_OPT” { output_pcrel_opt_reloc (operands[3]); return “lxv %x0,%1”; } [(set_attr “type” “vecload”)])

;; PCREL_OPT optimization for stores. We need to put the label after the PLD ;; instruction, because the assembler might insert a NOP before the PLD for ;; alignment. ;; ;; If we are optimizing a single write, normally the code would look like: ;; ;; (set (reg:DI ) ;; (symbol_ref:DI “<extern_addr>”)) # must be live here ;; ;; ... # insns do not need to be adjacent ;; ;; (set (mem:SI (reg:DI )) ;; (reg:SI )) # dies with this insn ;; ;; We optimize this to be: ;; ;; (parallel [(set (reg:DI ) ;; (unspec:DI [(symbol_ref:DI “<extern_addr>”) ;; (const_int )] ;; UNSPEC_PCREL_OPT_ST_ADDR)) ;; (use (reg: ))]) ;; ;; ... # insns do not need to be adjacent ;; ;; (parallel [(set (mem: (reg:DI )) ;; (unspec: [(reg: ) ;; (const_int )] ;; UNSPEC_PCREL_OPT_ST_RELOC)) ;; (clobber (reg:DI ))])

(define_insn “*pcrel_opt_st_addr” [(set (match_operand:DI 0 “gpc_reg_operand” “=b”) (unspec:DI [(match_operand:DI 1 “pcrel_external_address”) (match_operand 2 “const_int_operand” “n”)] UNSPEC_PCREL_OPT_ST_ADDR)) (use (match_operand:PCRELOPT 3 “gpc_reg_operand” “rwa”))] “TARGET_PCREL_OPT” “ld %0,%a1\n.Lpcrel%2:” [(set_attr “prefixed” “yes”) (set_attr “type” “load”) (set_attr “loads_external_address” “yes”)])

;; PCREL_OPT stores. (define_insn “*pcrel_opt_st” [(set (match_operand:QHSI 0 “d_form_memory” “=m”) (unspec:QHSI [(match_operand:QHSI 1 “gpc_reg_operand” “r”) (match_operand 2 “const_int_operand” “n”)] UNSPEC_PCREL_OPT_ST_RELOC)) (clobber (match_operand:DI 3 “base_reg_operand” “=b”))] “TARGET_PCREL_OPT” { output_pcrel_opt_reloc (operands[2]); return “st %1,%0”; } [(set_attr “type” “store”)])

(define_insn “*pcrel_opt_stdi” [(set (match_operand:DI 0 “d_form_memory” “=m,m,m”) (unspec:DI [(match_operand:DI 1 “gpc_reg_operand” “r,d,v”) (match_operand 2 “const_int_operand” “n,n,n”)] UNSPEC_PCREL_OPT_ST_RELOC)) (clobber (match_operand:DI 3 “base_reg_operand” “=b,b,b”))] “TARGET_PCREL_OPT && TARGET_POWERPC64” { output_pcrel_opt_reloc (operands[2]); switch (which_alternative) { case 0: return “std %1,%0”; case 1: return “stfd %1,%0”; case 2: return “stxsd %1,%0”; default: gcc_unreachable (); } } [(set_attr “type” “store,fpstore,fpstore”)])

(define_insn “*pcrel_opt_stsf” [(set (match_operand:SF 0 “d_form_memory” “=m,m,m”) (unspec:SF [(match_operand:SF 1 “gpc_reg_operand” “d,v,r”) (match_operand 2 “const_int_operand” “n,n,n”)] UNSPEC_PCREL_OPT_ST_RELOC)) (clobber (match_operand:DI 3 “base_reg_operand” “=b,b,b”))] “TARGET_PCREL_OPT” { output_pcrel_opt_reloc (operands[2]); switch (which_alternative) { case 0: return “stfs %1,%0”; case 1: return “stxssp %1,%0”; case 2: return “stw %1,%0”; default: gcc_unreachable (); } } [(set_attr “type” “fpstore,fpstore,store”)])

(define_insn “*pcrel_opt_stdf” [(set (match_operand:DF 0 “d_form_memory” “=m,m,m”) (unspec:DF [(match_operand:DF 1 “gpc_reg_operand” “d,v,r”) (match_operand 2 “const_int_operand” “n,n,n”)] UNSPEC_PCREL_OPT_ST_RELOC)) (clobber (match_operand:DI 3 “base_reg_operand” “=b,b,b”))] “TARGET_PCREL_OPT && (TARGET_POWERPC64 || vsx_register_operand (operands[1], DFmode))” { output_pcrel_opt_reloc (operands[2]); switch (which_alternative) { case 0: return “stfd %1,%0”; case 1: return “stxsd %1,%0”; case 2: return “std %1,%0”; default: gcc_unreachable (); } } [(set_attr “type” “fpstore,fpstore,store”)])

(define_insn “*pcrel_opt_st” [(set (match_operand:PCRELOPT_VECT 0 “d_form_memory” “=m”) (unspec:PCRELOPT_VECT [(match_operand:PCRELOPT_VECT 1 “gpc_reg_operand” “wa”) (match_operand 2 “const_int_operand” “n”)] UNSPEC_PCREL_OPT_ST_RELOC)) (clobber (match_operand:DI 3 “base_reg_operand” “=b”))] “TARGET_PCREL_OPT” { output_pcrel_opt_reloc (operands[2]); return “stxv %x1,%0”; } [(set_attr “type” “vecstore”)])