;; Machine description for the PCREL_OPT optimization.
;; Copyright (C) 2020-2024 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
;; .
;; 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 "")) # 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 )
;; (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 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 )
;; (unspec:DI [(symbol_ref:DI )
;; (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 " %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 ? " %0,%1"
: " %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 "")) # 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 "")
;; (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")])