#!/usr/bin/perl # Generate fusion.md # # Copyright (C) 2020-2025 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/>. use warnings; use strict; print <<'EOF'; ;; Generated automatically by genfusion.pl ;; Copyright (C) 2020-2025 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/>. EOF sub mode_to_ldst_char { my ($mode) = @_; my %x = (DI => 'd', SI => 'w', HI => 'h', QI => 'b'); return $x{$mode} if exists $x{$mode}; return '?'; } sub gen_ld_cmpi_p10_one { my ($lmode, $result, $ccmode) = @_; my $np = "NON_PREFIXED_D"; my $mempred = "non_update_memory_operand"; my $extend; # We need to special case lwa. The prefixed_load_p function in rs6000.cc # (which determines if a load instruction is prefixed) uses the fact that the # register mode is different from the memory mode, and that the sign_extend # attribute is set to use DS-form rules for the address instead of D-form. # If the register size is the same, prefixed_load_p assumes we are doing a # lwz. We change to use an lwz and word compare if we don't need to sign # extend the SImode value. Otherwise if we need the value, we need to # make sure the insn is marked as ds-form. my $cmp_size_char = ($lmode eq "SI" && $ccmode eq "CC" && $result !~ /^EXT|^DI$/) ? "w" : "d"; if ($ccmode eq "CC") { # ld and lwa are both DS-FORM. ($lmode eq "DI") and $np = "NON_PREFIXED_DS"; ($lmode eq "SI" && $cmp_size_char eq "d") and $np = "NON_PREFIXED_DS"; } else { if ($lmode eq "DI") { # ld is DS-form, but lwz is not. $np = "NON_PREFIXED_DS"; } } my $cmpl = ($ccmode eq "CC") ? "" : "l"; my $echr = ($ccmode eq "CC" && $cmp_size_char eq "d") ? "a" : "z"; if ($lmode eq "DI") { $echr = ""; } my $constpred = ($ccmode eq "CC") ? "const_m1_to_1_operand" : "const_0_to_1_operand"; # For clobber, we need a SI/DI reg in case we # split because we have to sign/zero extend. my $clobbermode = ($lmode =~ /^[QH]I$/) ? "GPR" : $lmode; if ($result =~ /^EXT/ || $result eq "GPR" || $clobbermode eq "GPR") { # We always need extension if result > lmode. $extend = ($ccmode eq "CC") ? "sign" : "zero"; } else { # Result of SI/DI does not need sign extension. $extend = "none"; } my $ldst = mode_to_ldst_char($lmode); # DS-form addresses need YZ, and not m. my $constraint = ($np eq "NON_PREFIXED_DS") ? "YZ" : "m"; print <<HERE; ;; load-cmpi fusion pattern generated by gen_ld_cmpi_p10 ;; load mode is $lmode result mode is $result compare mode is $ccmode extend is $extend (define_insn_and_split "*l${ldst}${echr}_cmp${cmpl}${cmp_size_char}i_cr0_${lmode}_${result}_${ccmode}_${extend}" [(set (match_operand:${ccmode} 2 "cc_reg_operand" "=x") (compare:${ccmode} (match_operand:${lmode} 1 "${mempred}" "${constraint}") HERE print " " if $ccmode eq "CCUNS"; print <<HERE; (match_operand:${lmode} 3 "${constpred}" "n"))) HERE if ($result eq "clobber") { print <<HERE; (clobber (match_scratch:${clobbermode} 0 "=r"))] HERE } elsif ($result eq $lmode) { print <<HERE; (set (match_operand:${result} 0 "gpc_reg_operand" "=r") (match_dup 1))] HERE } else { print <<HERE; (set (match_operand:${result} 0 "gpc_reg_operand" "=r") (${extend}_extend:${result} (match_dup 1)))] HERE } print <<HERE; "(TARGET_P10_FUSION)" "l${ldst}${echr}%X1 %0,%1\\;cmp${cmpl}${cmp_size_char}i %2,%0,%3" "&& reload_completed && (cc_reg_not_cr0_operand (operands[2], CCmode) || !address_is_non_pfx_d_or_x (XEXP (operands[1], 0), ${lmode}mode, ${np}))" HERE if ($extend eq "none") { print " [(set (match_dup 0) (match_dup 1))\n"; } elsif ($result eq "clobber") { print " [(set (match_dup 0) (${extend}_extend:${clobbermode} (match_dup 1)))\n"; } else { print " [(set (match_dup 0) (${extend}_extend:${result} (match_dup 1)))\n"; } print <<HERE; (set (match_dup 2) (compare:${ccmode} (match_dup 0) (match_dup 3)))] "" [(set_attr "type" "fused_load_cmpi") (set_attr "cost" "8") HERE if ($lmode eq "SI" && $ccmode eq "CC" && $cmp_size_char eq "d") { # prefixed_load_p needs the sign_extend attribute to validate lwa as a # DS-form instruction instead of D-form. print " (set_attr \"sign_extend\" \"yes\")\n"; } print <<HERE (set_attr "length" "8")]) HERE } sub gen_ld_cmpi_p10 { foreach my $lmode (qw/DI SI HI QI/) { foreach my $result ("clobber", $lmode, "EXT$lmode") { # EXTDI does not exist, and we cannot directly produce HI/QI results. next if $result =~ /^(QI|HI|EXTDI)$/; # Don't allow EXTQI because that would allow HI result which we can't do. $result = "GPR" if $result eq "EXTQI"; foreach my $ccmode (qw/CC CCUNS/) { # We do not have signed single-byte loads. next if ($lmode eq "QI" and $ccmode eq "CC"); gen_ld_cmpi_p10_one($lmode, $result, $ccmode); } } } } sub gen_logical_addsubf { my @logicals = ( "and", "andc", "eqv", "nand", "nor", "or", "orc", "xor" ); my %logicals_addsub = ( "and"=>1, "nand"=>1, "nor"=>1, "or"=>1 ); my @addsub = ( "add", "subf" ); my %isaddsub = ( "add"=>1, "subf"=>1 ); my %complement = ( "and"=> 0, "andc"=> 1, "eqv"=> 0, "nand"=> 3, "nor"=> 3, "or"=> 0, "orc"=> 1, "xor"=> 0, "add"=> 0, "subf"=> 0 ); my %invert = ( "and"=> 0, "andc"=> 0, "eqv"=> 1, "nand"=> 0, "nor"=> 0, "or"=> 0, "orc"=> 0, "xor"=> 0, "add"=> 0, "subf"=> 0 ); my %commute2 = ( "and"=> 1, "andc"=> 0, "eqv"=> 1, "nand"=> 0, "nor"=> 0, "or"=> 1, "orc"=> 0, "xor"=> 1 ); my %rtlop = ( "and"=>"and", "andc"=>"and", "eqv"=>"xor", "nand"=>"ior", "nor"=>"and", "or"=>"ior", "orc"=>"ior", "xor"=>"xor", "add"=>"plus", "subf"=>"minus" ); my ($kind, $vchr, $mode, $pred, $constraint, $cr, $outer, @outer_ops, $outer_op, $outer_comp, $outer_inv, $outer_rtl, $inner, @inner_ops, $inner_comp, $inner_inv, $inner_rtl, $inner_op, $both_commute, $c4, $bc, $inner_arg0, $inner_arg1, $inner_exp, $outer_arg2, $outer_exp, $ftype, $insn, $is_subf, $is_rsubf, $outer_32, $outer_42,$outer_name, $fuse_type); KIND: foreach $kind ('scalar','vector') { @outer_ops = @logicals; if ( $kind eq 'vector' ) { $vchr = "v"; $mode = "VM"; $pred = "altivec_register_operand"; $constraint = "v"; $fuse_type = "fused_vector"; } else { $vchr = ""; $mode = "GPR"; $pred = "gpc_reg_operand"; $constraint = "r"; $fuse_type = "fused_arith_logical"; push (@outer_ops, @addsub); push (@outer_ops, ( "rsubf" )); } $c4 = "${constraint},${constraint},${constraint},${constraint}"; OUTER: foreach $outer ( @outer_ops ) { $outer_name = "${vchr}${outer}"; $is_subf = ( $outer eq "subf" ); $is_rsubf = ( $outer eq "rsubf" ); if ( $is_rsubf ) { $outer = "subf"; } $outer_op = "${vchr}${outer}"; $outer_comp = $complement{$outer}; $outer_inv = $invert{$outer}; $outer_rtl = $rtlop{$outer}; @inner_ops = @logicals; $ftype = "logical-logical"; if ( exists $isaddsub{$outer} ) { @inner_ops = sort keys %logicals_addsub; $ftype = "logical-add"; } elsif ( $kind ne 'vector' && exists $logicals_addsub{$outer} ) { push (@inner_ops, @addsub); } INNER: foreach $inner ( @inner_ops ) { if ( exists $isaddsub{$inner} ) { $ftype = "add-logical"; } $inner_comp = $complement{$inner}; $inner_inv = $invert{$inner}; $inner_rtl = $rtlop{$inner}; $inner_op = "${vchr}${inner}"; # If both ops commute then we can specify % on operand 1 # so the pattern will let operands 1 and 2 interchange. $both_commute = ($inner eq $outer) && ($commute2{$inner} == 1); $bc = ""; if ( $both_commute ) { $bc = "%"; } $inner_arg0 = "(match_operand:${mode} 0 \"${pred}\" \"${c4}\")"; $inner_arg1 = "(match_operand:${mode} 1 \"${pred}\" \"${bc}${c4}\")"; if ( ($inner_comp & 1) == 1 ) { $inner_arg0 = "(not:${mode} $inner_arg0)"; } if ( ($inner_comp & 2) == 2 ) { $inner_arg1 = "(not:${mode} $inner_arg1)"; } $inner_exp = "(${inner_rtl}:${mode} ${inner_arg0} ${inner_arg1})"; if ( $inner_inv == 1 ) { $inner_exp = "(not:${mode} $inner_exp)"; } $outer_arg2 = "(match_operand:${mode} 2 \"${pred}\" \"${c4}\")"; if ( ($outer_comp & 1) == 1 ) { $outer_arg2 = "(not:${mode} $outer_arg2)"; } if ( ($outer_comp & 2) == 2 ) { $inner_exp = "(not:${mode} $inner_exp)"; } if ( $is_subf ) { $outer_32 = "%2,%3"; $outer_42 = "%2,%4"; } else { $outer_32 = "%3,%2"; $outer_42 = "%4,%2"; } if ( $is_rsubf == 1 ) { $outer_exp = "(${outer_rtl}:${mode} ${outer_arg2} ${inner_exp})"; } else { $outer_exp = "(${outer_rtl}:${mode} ${inner_exp} ${outer_arg2})"; } if ( $outer_inv == 1 ) { $outer_exp = "(not:${mode} $outer_exp)"; } $insn = <<"EOF"; ;; $ftype fusion pattern generated by gen_logical_addsubf ;; $kind $inner_op -> $outer_name (define_insn "*fuse_${inner_op}_${outer_name}" [(set (match_operand:${mode} 3 "${pred}" "=&0,&1,&${constraint},${constraint}") ${outer_exp}) (clobber (match_scratch:${mode} 4 "=X,X,X,&${constraint}"))] "(TARGET_P10_FUSION)" "@ ${inner_op} %3,%1,%0\\;${outer_op} %3,${outer_32} ${inner_op} %3,%1,%0\\;${outer_op} %3,${outer_32} ${inner_op} %3,%1,%0\\;${outer_op} %3,${outer_32} ${inner_op} %4,%1,%0\\;${outer_op} %3,${outer_42}" [(set_attr "type" "$fuse_type") (set_attr "cost" "6") (set_attr "length" "8")]) EOF print $insn; } } } } sub gen_addadd { my ($kind, $vchr, $op, $type, $mode, $pred, $constraint); foreach $kind ('scalar','vector') { if ( $kind eq 'vector' ) { $vchr = "v"; $op = "vaddudm"; $type = "fused_vector"; $mode = "V2DI"; $pred = "altivec_register_operand"; $constraint = "v"; } else { $vchr = ""; $op = "add"; $type = "fused_arith_logical"; $mode = "GPR"; $pred = "gpc_reg_operand"; $constraint = "r"; } my $c4 = "${constraint},${constraint},${constraint},${constraint}"; print <<"EOF"; ;; ${op}-${op} fusion pattern generated by gen_addadd (define_insn "*fuse_${op}_${op}" [(set (match_operand:${mode} 3 "${pred}" "=&0,&1,&${constraint},${constraint}") (plus:${mode} (plus:${mode} (match_operand:${mode} 0 "${pred}" "${c4}") (match_operand:${mode} 1 "${pred}" "%${c4}")) (match_operand:${mode} 2 "${pred}" "${c4}"))) (clobber (match_scratch:${mode} 4 "=X,X,X,&${constraint}"))] "(TARGET_P10_FUSION)" "@ ${op} %3,%1,%0\\;${op} %3,%3,%2 ${op} %3,%1,%0\\;${op} %3,%3,%2 ${op} %3,%1,%0\\;${op} %3,%3,%2 ${op} %4,%1,%0\\;${op} %3,%4,%2" [(set_attr "type" "${type}") (set_attr "cost" "6") (set_attr "length" "8")]) EOF } } gen_ld_cmpi_p10(); gen_logical_addsubf(); gen_addadd;