{
    Copyright (c) 2000-2006 by Florian Klaempfl and Jonas Maebe

    Code generation for add nodes on the PowerPC (32 and 64 bit generic)

    This program 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 2 of the License, or
    (at your option) any later version.

    This program 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 this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

 ****************************************************************************
}

unit ngppcadd;


{$i fpcdefs.inc}

interface

    uses
       node,nadd,ncgadd,cpubase;

    type
       tgenppcaddnode = class(tcgaddnode)
          function pass_1: tnode; override;
         protected
          procedure pass_left_and_right;
          procedure load_left_right(cmpop, load_constants: boolean);
          function  getresflags : tresflags;
          procedure emit_compare(unsigned: boolean); virtual; abstract;
          procedure second_addfloat;override;
          procedure second_addboolean;override;
          procedure second_addsmallset;override;
       end;


implementation

{*****************************************************************************
                                  Pass 1
*****************************************************************************}

    uses
      globtype,systems,
      cutils,verbose,globals,
      symconst,symdef,paramgr,
      aasmbase,aasmtai,aasmdata,aasmcpu,defutil,htypechk,
      cgbase,cpuinfo,pass_1,pass_2,regvars,
      cpupara,cgcpu,cgutils,procinfo,
      ncon,nset,
      ncgutil,tgobj,rgobj,rgcpu,cgobj;


{*****************************************************************************
                                  Pass 1
*****************************************************************************}

    function tgenppcaddnode.pass_1: tnode;
      begin
        typecheckpass(left);
        if (nodetype in [equaln,unequaln]) and
           (left.resultdef.typ = orddef) and
           is_64bit(left.resultdef) then
          begin
            result := nil;
            firstpass(left);
            firstpass(right);
            expectloc := LOC_FLAGS;
            exit;
          end;
        result := inherited pass_1;
      end;

{*****************************************************************************
                                  Helpers
*****************************************************************************}

    procedure tgenppcaddnode.pass_left_and_right;
      begin
        { calculate the operator which is more difficult }
        firstcomplex(self);

        { in case of constant put it to the left }
        if (left.nodetype=ordconstn) then
         swapleftright;

        secondpass(left);
        secondpass(right);
      end;


    procedure tgenppcaddnode.load_left_right(cmpop, load_constants: boolean);

      procedure load_node(var n: tnode);
        begin
          case n.location.loc of
            LOC_REGISTER,
            LOC_CREGISTER:
              ;
            LOC_REFERENCE,LOC_CREFERENCE:
              location_force_reg(current_asmdata.CurrAsmList,n.location,def_cgsize(n.resultdef),false);
            LOC_CONSTANT:
              begin
                if load_constants then
                  location_force_reg(current_asmdata.CurrAsmList,n.location,def_cgsize(n.resultdef),false);
              end;
            else
              location_force_reg(current_asmdata.CurrAsmList,n.location,def_cgsize(n.resultdef),false);
          end;
        end;

      begin
        load_node(left);
        load_node(right);
        if not(cmpop) then
          begin
            location.register := cg.getintregister(current_asmdata.CurrAsmList,OS_INT);
{$ifndef cpu64bitalu}
            if is_64bit(resultdef) then
              location.register64.reghi := cg.getintregister(current_asmdata.CurrAsmList,OS_INT);
{$endif not cpu64bitalu}
         end;
      end;


    function tgenppcaddnode.getresflags : tresflags;
      begin
        if (left.resultdef.typ <> floatdef) then
          result.cr := RS_CR0
        else
          result.cr := RS_CR1;
        case nodetype of
          equaln : result.flag:=F_EQ;
          unequaln : result.flag:=F_NE;
        else
          if nf_swapped in flags then
            case nodetype of
              ltn : result.flag:=F_GT;
              lten : result.flag:=F_GE;
              gtn : result.flag:=F_LT;
              gten : result.flag:=F_LE;
            end
          else
            case nodetype of
              ltn : result.flag:=F_LT;
              lten : result.flag:=F_LE;
              gtn : result.flag:=F_GT;
              gten : result.flag:=F_GE;
            end;
        end
      end;


{*****************************************************************************
                                AddBoolean
*****************************************************************************}

    procedure tgenppcaddnode.second_addboolean;
      var
        cgop      : TOpCg;
        cgsize  : TCgSize;
        cmpop,
        isjump  : boolean;
        otl,ofl : tasmlabel;
      begin
        { calculate the operator which is more difficult }
        firstcomplex(self);

        cmpop:=false;
        if (torddef(left.resultdef).ordtype in [pasbool,bool8bit]) or
           (torddef(right.resultdef).ordtype in [pasbool,bool8bit]) then
         cgsize:=OS_8
        else
          if (torddef(left.resultdef).ordtype=bool16bit) or
             (torddef(right.resultdef).ordtype=bool16bit) then
           cgsize:=OS_16
        else
           cgsize:=OS_32;

        if ((cs_full_boolean_eval in current_settings.localswitches) and
            not(nf_short_bool in flags)) or
           (nodetype in [unequaln,ltn,lten,gtn,gten,equaln,xorn]) then
          begin
            if left.nodetype in [ordconstn,realconstn] then
             swapleftright;

            isjump:=(left.expectloc=LOC_JUMP);
            if isjump then
              begin
                 otl:=current_procinfo.CurrTrueLabel;
                 current_asmdata.getjumplabel(current_procinfo.CurrTrueLabel);
                 ofl:=current_procinfo.CurrFalseLabel;
                 current_asmdata.getjumplabel(current_procinfo.CurrFalseLabel);
              end;
            secondpass(left);
            if left.location.loc in [LOC_FLAGS,LOC_JUMP] then
             location_force_reg(current_asmdata.CurrAsmList,left.location,cgsize,false);
            if isjump then
             begin
               current_procinfo.CurrTrueLabel:=otl;
               current_procinfo.CurrFalseLabel:=ofl;
             end
            else if left.location.loc=LOC_JUMP then
              internalerror(2003122901);

            isjump:=(right.expectloc=LOC_JUMP);
            if isjump then
              begin
                 otl:=current_procinfo.CurrTrueLabel;
                 current_asmdata.getjumplabel(current_procinfo.CurrTrueLabel);
                 ofl:=current_procinfo.CurrFalseLabel;
                 current_asmdata.getjumplabel(current_procinfo.CurrFalseLabel);
              end;
            secondpass(right);
            if right.location.loc in [LOC_FLAGS,LOC_JUMP] then
             location_force_reg(current_asmdata.CurrAsmList,right.location,cgsize,false);
            if isjump then
             begin
               current_procinfo.CurrTrueLabel:=otl;
               current_procinfo.CurrFalseLabel:=ofl;
             end
            else if right.location.loc=LOC_JUMP then
              internalerror(200312292);

            cmpop := nodetype in [ltn,lten,gtn,gten,equaln,unequaln];

            { set result location }
            if not cmpop then
              location_reset(location,LOC_REGISTER,def_cgsize(resultdef))
             else
              location_reset(location,LOC_FLAGS,OS_NO);

            load_left_right(cmpop,false);

            if (left.location.loc = LOC_CONSTANT) then
              swapleftright;

            { compare the }
            case nodetype of
              ltn,lten,gtn,gten,
              equaln,unequaln :
                begin
                  if (right.location.loc <> LOC_CONSTANT) then
                    current_asmdata.CurrAsmList.concat(taicpu.op_reg_reg(A_CMPLW,
                      left.location.register,right.location.register))
                  else
                    current_asmdata.CurrAsmList.concat(taicpu.op_reg_const(A_CMPLWI,
                      left.location.register,longint(right.location.value)));
                  location.resflags := getresflags;
                end;
              else
                begin
                  case nodetype of
                    xorn :
                      cgop:=OP_XOR;
                    orn :
                      cgop:=OP_OR;
                    andn :
                      cgop:=OP_AND;
                    else
                      internalerror(200203247);
                  end;

                  if right.location.loc <> LOC_CONSTANT then
                    cg.a_op_reg_reg_reg(current_asmdata.CurrAsmList,cgop,OS_INT,
                      left.location.register,right.location.register,
                      location.register)
                  else
                    cg.a_op_const_reg_reg(current_asmdata.CurrAsmList,cgop,OS_INT,
                      right.location.value,left.location.register,
                      location.register);
                end;
            end;
         end
        else
          inherited second_addboolean;
      end;


{*****************************************************************************
                                AddFloat
*****************************************************************************}

    procedure tgenppcaddnode.second_addfloat;
      var
        op    : TAsmOp;
        cmpop,
        singleprec : boolean;
      begin
        pass_left_and_right;

        cmpop:=false;
        singleprec:=tfloatdef(left.resultdef).floattype=s32real;
        case nodetype of
          addn :
            if singleprec then
              op:=A_FADDS
            else
              op:=A_FADD;
          muln :
            if singleprec then
              op:=A_FMULS
            else
            op:=A_FMUL;
          subn :
            if singleprec then
              op:=A_FSUBS
            else
              op:=A_FSUB;
          slashn :
            if singleprec then
              op:=A_FDIVS
            else
             op:=A_FDIV;
          ltn,lten,gtn,gten,
          equaln,unequaln :
            begin
              op:=A_FCMPO;
              cmpop:=true;
            end;
          else
            internalerror(200403182);
        end;

        // get the operands in the correct order, there are no special cases
        // here, everything is register-based
        if nf_swapped in flags then
          swapleftright;

        // put both operands in a register
        location_force_fpureg(current_asmdata.CurrAsmList,right.location,true);
        location_force_fpureg(current_asmdata.CurrAsmList,left.location,true);

        // initialize de result
        if not cmpop then
          begin
            location_reset(location,LOC_FPUREGISTER,def_cgsize(resultdef));
            location.register := cg.getfpuregister(current_asmdata.CurrAsmList,location.size);
          end
        else
         begin
           location_reset(location,LOC_FLAGS,OS_NO);
           location.resflags := getresflags;
         end;

        // emit the actual operation
        if not cmpop then
          begin
            current_asmdata.CurrAsmList.concat(taicpu.op_reg_reg_reg(op,
              location.register,left.location.register,
              right.location.register))
          end
        else
          begin
            current_asmdata.CurrAsmList.concat(taicpu.op_reg_reg_reg(op,
              newreg(R_SPECIALREGISTER,location.resflags.cr,R_SUBNONE),left.location.register,right.location.register))
          end;
      end;


{*****************************************************************************
                                AddSmallSet
*****************************************************************************}

    procedure tgenppcaddnode.second_addsmallset;
      var
        cgop   : TOpCg;
        setbase: aint;
        tmpreg : tregister;
        opdone,
        cmpop  : boolean;
      begin
        pass_left_and_right;

        { when a setdef is passed, it has to be a smallset }
        if (not(nf_swapped in flags) and
            not is_smallset(left.resultdef) or
            (not is_smallset(right.resultdef) and
             (right.nodetype<>setelementn))) or
           ((nf_swapped in flags) and
            not is_smallset(right.resultdef) or
            (not is_smallset(left.resultdef) and
             (left.nodetype<>setelementn))) then
         internalerror(200203359);

        opdone := false;
        cmpop:=nodetype in [equaln,unequaln,lten,gten];

        { set result location }
        if not cmpop then
          location_reset(location,LOC_REGISTER,def_cgsize(resultdef))
         else
          location_reset(location,LOC_FLAGS,OS_NO);

        load_left_right(cmpop,false);

        if not(cmpop) then
          location.register := cg.getintregister(current_asmdata.CurrAsmList,OS_INT);

        if (left.resultdef.typ=setdef) then
          setbase:=tsetdef(left.resultdef).setbase
        else
          setbase:=tsetdef(right.resultdef).setbase;
        case nodetype of
          addn :
            begin
              if (nf_swapped in flags) and (left.nodetype=setelementn) then
                swapleftright;
              { are we adding set elements ? }
              if right.nodetype=setelementn then
                begin
                  { no range support for smallsets! }
                  if assigned(tsetelementnode(right).right) then
                   internalerror(43244);
                  if (right.location.loc = LOC_CONSTANT) then
                    cg.a_op_const_reg_reg(current_asmdata.CurrAsmList,OP_OR,OS_INT,
                      aint((aword(1) shl (resultdef.size*8-1)) shr aword(right.location.value-setbase)),
                      left.location.register,location.register)
                  else
                    begin
                      tmpreg := cg.getintregister(current_asmdata.CurrAsmList,OS_INT);
                      cg.a_load_const_reg(current_asmdata.CurrAsmList,OS_INT,aint((aword(1) shl (resultdef.size*8-1))),tmpreg);
                      register_maybe_adjust_setbase(current_asmdata.CurrAsmList,right.location,setbase);
                      cg.a_op_reg_reg(current_asmdata.CurrAsmList,OP_SHR,OS_INT,
                        right.location.register,tmpreg);
                      if left.location.loc <> LOC_CONSTANT then
                        cg.a_op_reg_reg_reg(current_asmdata.CurrAsmList,OP_OR,OS_INT,tmpreg,
                          left.location.register,location.register)
                      else
                        cg.a_op_const_reg_reg(current_asmdata.CurrAsmList,OP_OR,OS_INT,
                          left.location.value,tmpreg,location.register);
                    end;
                  opdone := true;
                end
              else
                cgop := OP_OR;
            end;
          symdifn :
            cgop:=OP_XOR;
          muln :
            cgop:=OP_AND;
          subn :
            begin
              cgop:=OP_AND;
              if (not(nf_swapped in flags)) then
                if (right.location.loc=LOC_CONSTANT) then
                  right.location.value := not(right.location.value)
                else
                  opdone := true
              else if (left.location.loc=LOC_CONSTANT) then
                left.location.value := not(left.location.value)
              else
                 begin
                   swapleftright;
                   opdone := true;
                 end;
              if opdone then
                begin
                  if left.location.loc = LOC_CONSTANT then
                    begin
                      tmpreg := cg.getintregister(current_asmdata.CurrAsmList,OS_INT);
                      cg.a_load_const_reg(current_asmdata.CurrAsmList,OS_INT,
                        left.location.value,tmpreg);
                      current_asmdata.CurrAsmList.concat(taicpu.op_reg_reg_reg(A_ANDC,
                        location.register,tmpreg,right.location.register));
                    end
                  else
                    current_asmdata.CurrAsmList.concat(taicpu.op_reg_reg_reg(A_ANDC,
                      location.register,left.location.register,
                      right.location.register));
                end;
            end;
          equaln,
          unequaln :
            begin
              emit_compare(true);
              opdone := true;
            end;
          lten,gten:
            begin
              If (not(nf_swapped in flags) and
                  (nodetype = lten)) or
                 ((nf_swapped in flags) and
                  (nodetype = gten)) then
                swapleftright;
              // now we have to check whether left >= right
              tmpreg := cg.getintregister(current_asmdata.CurrAsmList,OS_INT);
              if left.location.loc = LOC_CONSTANT then
                begin
                  cg.a_op_const_reg_reg(current_asmdata.CurrAsmList,OP_AND,OS_INT,
                    not(left.location.value),right.location.register,tmpreg);
                  current_asmdata.CurrAsmList.concat(taicpu.op_reg_const(A_CMPWI,tmpreg,0));
                  // the two instructions above should be folded together by
                  // the peepholeoptimizer
                end
              else
                begin
                  if right.location.loc = LOC_CONSTANT then
                    begin
                      cg.a_load_const_reg(current_asmdata.CurrAsmList,OS_INT,
                        right.location.value,tmpreg);
                      current_asmdata.CurrAsmList.concat(taicpu.op_reg_reg_reg(A_ANDC_,tmpreg,
                        tmpreg,left.location.register));
                    end
                  else
                    current_asmdata.CurrAsmList.concat(taicpu.op_reg_reg_reg(A_ANDC_,tmpreg,
                      right.location.register,left.location.register));
                end;
              location.resflags.cr := RS_CR0;
              location.resflags.flag := F_EQ;
              opdone := true;
            end;
          else
            internalerror(2002072701);
        end;

        if not opdone then
          begin
            // these are all commutative operations
            if (left.location.loc = LOC_CONSTANT) then
              swapleftright;
            if (right.location.loc = LOC_CONSTANT) then
              cg.a_op_const_reg_reg(current_asmdata.CurrAsmList,cgop,OS_INT,
                right.location.value,left.location.register,
                location.register)
            else
              cg.a_op_reg_reg_reg(current_asmdata.CurrAsmList,cgop,OS_INT,
                right.location.register,left.location.register,
                location.register);
          end;
      end;

end.
