/*{{{  #includes*/
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include <local/bool.h>

#define OPTMAC_C

#include "keybind.h"
/*}}}  */

#ifndef NO_OPTI
   /*{{{  next_command*/
#   include "codelg.h"

   private TOKEN *next_command(TOKEN *x)
   { if (*x>O_EXE_MACRO || *x<O_NOP) return(x+1);
     switch (cmd_type[*x-O_NOP])
      { case COM_II:
           x++;
        case COM_I:
        case COM_C:
        case COM_A:
           return(x+2);
        case COM_IIP:
           x++;
        case COM_IP:
           x++;
        case COM_P:
           x++;
           while (*x!=M_END_MACRO)
              if (*x==M_INT_STRING) x+=2; else x++;
        case COM:
           return(x+1);
        default:
           fprintf(stderr,"CRASH\n");
           exit(1);
      }

   return(0);
   }
   /*}}}  */
   /*{{{  jmp_dest*/
   private TOKEN *jmp_dest(TOKEN *x)
   { TOKEN *d=x+2+*(x+1);

     if (d!=x) return(d);
     error_po();
     fprintf(stderr,M_CRASH);
     exit(1);
     return(0);
   }
   /*}}}  */
   /*{{{  jumped_on*/
   private bool jumped_on(TOKEN *start,TOKEN *end,TOKEN *from,TOKEN *to)
   { for (;start!=end;)
        switch (*start)
         { case M_JMP:
           case M_JMP_TRUE:
           case M_JMP_FALSE:
           case M_CALL:
            { TOKEN *x=jmp_dest(start);

              if (x>=from && x<to) return(TRUE);
            }
           default:
              start=next_command(start);
         }
     return(FALSE);
   }
   /*}}}  */
   /*{{{  optmac*/
   public TOKEN *opt_mac(TOKEN *start,TOKEN *end,bool no_not_opt)
   { bool changed;
     int loop,s_jmp_cut,s_modified,s_shrink;
     TOKEN buff[macro_lg];

     loop=0;
     s_jmp_cut=s_shrink=s_modified=0;
     do
      /*{{{  one optimisation step*/
      { int jmp_cut,modified,shrink;

        changed=FALSE;
        jmp_cut=shrink=modified=0;
        /*{{{  start->buff, jmp opt*/
        { TOKEN *current,*dest,*next,*jmp;

          for (current=start,dest=buff;current!=end;)
           { bool inverse=FALSE;

             next=next_command(current);
             switch (*current)
              { case M_CALL:
                case M_JMP:
                 /*{{{  cut simple jmp-sequences*/
                 { TOKEN typ= *current;

                   for (jmp=jmp_dest(current);jmp!=end;)
                    { switch (*jmp)
                       { case O_NOP:
                            jmp++;changed=TRUE;jmp_cut++;continue;
                         case M_JMP:
                            jmp=jmp_dest(jmp);changed=TRUE;jmp_cut++;continue;
                         case M_NOT:
                            if (no_not_opt) break;
                            fprintf(stderr,"JMP-NOT optimizer crash\n");
                            no_not_opt=TRUE;
                            current=start;
                            dest=buff;
                            continue;
                         default:
                            break;
                       }
                      break;
                    }
                   dest=generate_jmp(typ,dest,buff+(jmp-start));
                   current=next;
                   continue;
                 }
                 /*}}}  */
                case M_NOT:
                 /*{{{  break, or prefix for jmp-condition*/
                   if (no_not_opt)
                    { *dest++ = *current++;continue; }
                   else
                    { do
                       /*{{{  skip one NOT*/
                       { inverse= !inverse;
                         changed=TRUE;
                         jmp_cut++;
                         *dest++ = O_NOP;
                         current=next;
                         next=next_command(current);
                       }
                       /*}}}  */
                      while (*current==M_NOT && next!=end);
                      if (current!=end && *current!=M_JMP_FALSE && *current!=M_JMP_TRUE)
                       /*{{{  cannot optimize NOT's, this must be a tricky code!*/
                       { fprintf(stderr,"JMP-NOT optimizer crash\n");
                         no_not_opt=TRUE;
                         current=start;
                         dest=buff;
                         continue;
                       }
                       /*}}}  */
                    }
                 /*}}}  */
                case M_JMP_TRUE:
                case M_JMP_FALSE:
                 /*{{{  handle jmp-sequences, value of tag is known*/
                 { TOKEN typ;
                   bool tag_value;

                   tag_value= *current==M_JMP_TRUE;
                   typ=tag_value?(inverse?M_JMP_FALSE:M_JMP_TRUE)
                                :(inverse?M_JMP_TRUE:M_JMP_FALSE);
                   for (jmp=jmp_dest(current);jmp!=end;)
                    { switch (*jmp)
                       { case O_NOP:
                            jmp++;changed=TRUE;jmp_cut++;continue;
                         case M_JMP:
                            jmp=jmp_dest(jmp);changed=TRUE;jmp_cut++;continue;
                         case M_NOT:
                            if (no_not_opt) break;
                            tag_value = !tag_value;
                            jmp+=1;
                            jmp_cut++;
                            changed=TRUE;
                            continue;
                         case M_JMP_FALSE:
                            if (tag_value) jmp+=2; else jmp=jmp_dest(jmp);
                            changed=TRUE;
                            jmp_cut++;
                            continue;
                         case M_JMP_TRUE:
                            if (tag_value) jmp=jmp_dest(jmp); else jmp+=2;
                            changed=TRUE;
                            jmp_cut++;
                            continue;
                         default:
                            break;
                       }
                      break;
                    }
                   dest=generate_jmp(typ,dest,buff+(jmp-start));
                   current=next;
                   continue;
                 }
                 /*}}}  */
                default:
                 /*{{{  copy the code*/
                   for (;current!=next;*dest++ = *current++);
                   continue;
                 /*}}}  */
              }
           }
        }
        /*}}}  */
        /*{{{  buff->start, shrink and modify to better code*/
        { TOKEN *current,*next,*dest,*opt_end;
          bool set_opt_end;

            /*{{{  cut leading O_NOP*/
            while (buff[0]==O_NOP && end!=start)
             { TOKEN *x;

               for (x=buff;x!=(buff+(end-start));*x=*(x+1),x++);
               end--;
               changed=TRUE;
               shrink++;
             }
            /*}}}  */
            /*{{{  mark reachable commands, opt_end= &(good M_END_MACRO)))*/
            for (current=start;current!=end;*current++ = O_NOP);
            set_opt_end=!(end>start && *(buff+(end-start)-1)==M_END_MACRO);
            opt_end=!set_opt_end ? end-1 : 0;
            for (dest=end,start[0]=0;;)
               if (dest==end)
                /*{{{  look for next reached, unhandled command*/
                { for (dest=start;dest!=end;) if (*dest==0) break; else dest++;
                  if (dest==end) break;
                  current=buff+(dest-start);
                  continue;
                }
                /*}}}  */
               else
                /*{{{  handle a reachable, unhandled command*/
                  switch (*current)
                   { case O_NOP:
                        current++;dest++;continue;
                     case M_CALL:
                     case M_JMP:
                     case M_JMP_TRUE:
                     case M_JMP_FALSE:
                      /*{{{  mark jmp-dest and continue in default*/
                      { TOKEN *jmp;

                        jmp=start+(jmp_dest(current)-buff);
                        if (jmp!=end && *jmp!=M_END_MACRO) *jmp=0;
                      }
                      /*}}}  */
                     default:
                      { bool no_cont;

                        /*{{{  set no_cont to correct value, maybe set opt_end!*/
                        switch (*current)
                         { case M_EXIT:
                              set_opt_end=TRUE;
                           case M_END_MACRO:
                           case M_JMP:
                              no_cont=TRUE;
                              break;
                           default:
                              no_cont=FALSE;
                         }
                        /*}}}  */
                        next=next_command(current);
                        for (;current!=next;)
                         { if (*current++==M_END_MACRO && set_opt_end)
                            { opt_end=dest;
                              set_opt_end=FALSE;
                            }
                           *dest++ = M_END_MACRO;
                         }
                        if (no_cont) dest=end;
                        continue;
                      }
                   }
                /*}}}  */
            /*}}}  */
            /*{{{  shrink code*/
            for (current=start,dest=buff;current!=end;)
               if (*current==M_END_MACRO)
                { *current++ = *dest++;continue; }
               else
                { int i,limit,max;

                  limit=current-start;
                  max=end-start;
                  /*{{{  modify jmp pre -> after*/
                  for (i=0;i<limit;)
                     switch (buff[i])
                      { case M_CALL:
                        case M_JMP:
                        case M_JMP_TRUE:
                        case M_JMP_FALSE:
                           if (jmp_dest(buff+i)>(buff+limit))
                            { start[i+1]--;buff[i+1]--; }
                        default:
                           i=next_command(buff+i)-buff;
                      }
                  /*}}}  */
                  /*{{{  modify atfer -> pre*/
                  for (i=limit+1;i!=max;)
                     switch (buff[i])
                      { case M_CALL:
                        case M_JMP:
                        case M_JMP_TRUE:
                        case M_JMP_FALSE:
                           if (jmp_dest(buff+i)<(buff+limit)) buff[i+1]++;
                        default:
                           i=next_command(buff+i)-buff;
                      }
                  /*}}}  */
                  if (opt_end && (opt_end-start)>limit) opt_end--;
                  end--;
                  for (i=limit;i!=max;start[i]=start[i+1],buff[i]=buff[i+1],i++);
                  changed=TRUE;
                  shrink++;
                  continue;
                }
            /*}}}  */
            /*{{{  all jmp's to M_END_MACRO to opt_end!*/
            if (opt_end)
             { for (current=start;current!=end;)
                  switch (*current)
                   { case M_CALL:
                     case M_JMP:
                     case M_JMP_FALSE:
                     case M_JMP_TRUE:
                        next=jmp_dest(current);
                        if (next!=end && next!=opt_end && *next==M_END_MACRO)
                         { modified++;
                           changed=TRUE;
                           generate_jmp(*current,current,opt_end);
                         }
                     default:
                        current=next_command(current);
                        continue;
                   }
             }
            /*}}}  */
            /*{{{  modifiy some OCL-assembler patterns*/
            for (current=start;current!=end;)
             { next=next_command(current);
               switch (*current)
                {
                  /*{{{  M_CALL        ...*/
                  case M_CALL:
                     if
                      /*{{{  M_CALL x;M_END_MACRO -> M_JMP x; M_END_MACRO*/
                      (next!=end && *next==M_END_MACRO)
                      { *current=M_JMP;
                        current=next;
                        goto modif_code;
                      }
                      /*}}}  */
                     else if
                      /*{{{  M_CALL x; .. x:M_END_MACRO -> O_NOP; O_NOP; ..*/
                      (jmp_dest(current)==opt_end)
                      { *current++=O_NOP;
                        *current++=O_NOP;
                        goto modif_code;
                      }
                      /*}}}  */
                     goto skip_code;
                  /*}}}  */
                  /*{{{  M_JMP         ...*/
                  case M_JMP:
                     dest=jmp_dest(current);
                     if
                      /*{{{  M_JMP x; .. x:M_END_MACRO -> M_END_MACRO; O_NOP; ..*/
                      (dest==opt_end)
                      { *current++=M_END_MACRO;
                        *current++=O_NOP;
                        goto modif_code;
                      }
                      /*}}}  */
                     else if
                      /*{{{  M_JMP_X;x: -> O_NOP; O_NOP;*/
                      (dest==next)
                      { *current++=O_NOP;
                        *current++=O_NOP;
                        goto modif_code;
                      }
                      /*}}}  */
                     goto skip_code;
                  /*}}}  */
                  /*{{{  M_JMP_?       ...*/
                  case M_JMP_FALSE:
                  case M_JMP_TRUE:
                     dest=jmp_dest(current);
                     if
                      /*{{{  M_JMP_COND x;M_END_MACRO;x: -> M_JMP_!COND opt_end;O_NOP;x:*/
                      (   next!=end
                       && *next==M_END_MACRO
                       && dest==next_command(next)
                       && opt_end
                       && opt_end!=next
                      )
                      { current=generate_jmp(*current==M_JMP_FALSE?M_JMP_TRUE:M_JMP_FALSE,
                                             current,opt_end);
                        *current++=O_NOP;
                        goto modif_code;
                      }
                      /*}}}  */
                     else if
                      /*{{{  M_JMP_COND x;x: -> O_NOP;O_NOP;*/
                      (dest==next)
                      { *current++=O_NOP;
                        *current++=O_NOP;
                        goto modif_code;
                      }
                      /*}}}  */
                     goto skip_code;
                  /*}}}  */
                  /*{{{  M_PUSH_INT    ...*/
                  case M_PUSH_INT:
                     if (next!=end && !jumped_on(start,end,next,next_command(next)))
                        if
                         /*{{{  PUSH x POP x _> NOP*/
                         (*next==M_POP_INT && *(next+1)==*(current+1))
                         { *current++=O_NOP;
                           *current++=O_NOP;
                           *current++=O_NOP;
                           *current++=O_NOP;
                           goto modif_code;
                         }
                         /*}}}  */
                        else if
                         /*{{{  PUSH x POP y ADD y n -> SET x n SUM x y*/
                         (   *next==M_POP_INT
                          && next+2!=end
                          && *(next+2)==M_ADD_COUNTER
                          && *(next+3)==*(next+1)
                          && !(jumped_on(start,end,next+2,next+4))
                         )
                         { int n= *(next+4);
                           int x= *(next+1);
                           int y= *(current+1);

                           *current++=O_NOP;
                           *current++=M_SET_COUNTER;
                           *current++=(TOKEN)x;
                           *current++=(TOKEN)n;
                           *current++=M_SUM_COUNTER;
                           *current++=(TOKEN)x;
                           *current++=(TOKEN)y;
                           goto modif_code;
                         }
                         /*}}}  */
                        else if
                         /*{{{  PUSH x POP y INV y SUM y z INV y -> PUSH z POP y INV y SUM y x NOP NOP*/
                         (   (current+11)<end
                          && !jumped_on(start,end,current,current+11)
                          && *next==M_POP_INT
                          && *(next+2)==M_INV_COUNTER
                          && *(next+4)==M_SUM_COUNTER
                          && *(next+7)==M_INV_COUNTER
                          && *(next+1)==*(next+3)
                          && *(next+1)==*(next+5)
                          && *(next+1)==*(next+8)
                         )
                         { int x=*(current+1);
                           int y=*(next+1);
                           int z=*(next+6);

                           *current++=M_PUSH_INT;
                           *current++=(TOKEN)z;
                           *current++=M_POP_INT;
                           *current++=(TOKEN)y;
                           *current++=M_INV_COUNTER;
                           *current++=(TOKEN)y;
                           *current++=M_SUM_COUNTER;
                           *current++=(TOKEN)y;
                           *current++=(TOKEN)x;
                           *current++=O_NOP;
                           *current++=O_NOP;
                           goto modif_code;
                         }
                         /*}}}  */
                     goto skip_code;
                  /*}}}  */
                  /*{{{  M_INV         ...*/
                  case M_INV_COUNTER:
                     if (next!=end && !jumped_on(start,end,next,next_command(next)))
                         if
                          /*{{{  INV x INV x -> NOP NOP NOP NOP*/
                          (*next==M_INV_COUNTER && *(current+1)==*(next+1))
                          { *current++=O_NOP;
                            *current++=O_NOP;
                            *current++=O_NOP;
                            *current++=O_NOP;
                            goto modif_code;
                          }
                          /*}}}  */
                         else if
                          /*{{{  INV x ADD x n -> ADD x -n INV x*/
                          ( *next==M_ADD_COUNTER && *(current+1)==*(next+1))
                          { int x= *(current+1);
                            int n= -*(next+2);

                            *current++=M_ADD_COUNTER;
                            *current++=x;
                            *current++=n;
                            *current=M_INV_COUNTER;
                            *(current+1)=x;
                            goto modif_code;
                          }
                          /*}}}  */
                         else if
                          /*{{{  INV dummy M_NULL_COUNTER dummy -> NOP NOP M_NULL_COUNTER*/
                          (   *next==M_NULL_COUNTER
                           && *(next+1)==*(current+1)
                           && isdummy(*(current+1))
                          )
                          { *current++=O_NOP;
                            *current++=O_NOP;
                            goto modif_code;
                          }
                          /*}}}  */
                     goto skip_code;
                  /*}}}  */
                  /*{{{  M_SUM_COUNTER ...*/
                  case M_SUM_COUNTER:
                     if (next!=end && !jumped_on(start,end,next,next_command(next)))
                         if
                          /*{{{  SUM x y ADD x n -> ADD x n SUM x y*/
                          (   *next==M_ADD_COUNTER
                           && *(current+1)==*(next+1)
                           && *(current+1)!=*(current+2)
                          )
                          { int n= *(next+2);
                            int y= *(current+2);

                            *current++=M_ADD_COUNTER;
                            current++;
                            *current++=n;
                            *current++=M_SUM_COUNTER;
                            current++;
                            *current++=y;
                            goto modif_code;
                          }
                          /*}}}  */
                     goto skip_code;
                  /*}}}  */
                  /*{{{  M_SET_COUNTER ...*/
                  case M_SET_COUNTER:
                     if (next!=end && !jumped_on(start,end,next,next_command(next)))
                      { TOKEN *dest1=current+1;
                        TOKEN *dest2=next+1;
                        TOKEN *arg1=current+2;
                        TOKEN *arg2=next+2;

                        if
                         /*{{{  SET x n;INV x -> SET x -n; NOP NOP*/
                         (*next==M_INV_COUNTER && *dest2==*dest1)
                         { *arg1= -*arg1;
                           *next++=O_NOP;
                           *next++=O_NOP;
                           current=next;
                           goto modif_code;
                         }
                         /*}}}  */
                        else if
                         /*{{{  SET x n; SET X m; -> NOP NOP NOP SET x m*/
                         (*next==M_SET_COUNTER && *dest2==*dest1)
                         { *current++=O_NOP;
                           *current++=O_NOP;
                           *current++=O_NOP;
                           goto modif_code;
                         }
                         /*}}}  */
                        else if
                         /*{{{  SET x n; SUM y x; -> SET x n; ADD y n | NOP NOP NOP ADD x n*/
                         (*next==M_SUM_COUNTER && *arg2==*dest1)
                         {  *next=M_ADD_COUNTER;
                            *arg2= *arg1;
                            if (isdummy(*dest1))
                             { *current++=O_NOP;
                               *current++=O_NOP;
                               *current++=O_NOP;
                             }
                            else
                             { current+=3;
                             }
                            goto modif_code;
                         }
                         /*}}}  */
                        else if
                         /*{{{  SET x n; ADD x m; -> NOP NOP NOP SET x n+m*/
                         (*next==M_ADD_COUNTER && *dest2==*dest1)
                         { *arg2+=*arg1;
                           *current++=O_NOP;
                           *current++=O_NOP;
                           *current++=O_NOP;
                           *current=M_SET_COUNTER;
                           goto modif_code;
                         }
                         /*}}}  */
                        else if
                         /*{{{  SET x n ASCII x -> SET X n ; n; | NOP NOP NOP n*/
                         (*next==M_ASCII)
                         { *next++ = *arg1;
                           *next++ = O_NOP;
                           if (isdummy(*dest1))
                            { *current++=O_NOP;
                              *current++=O_NOP;
                              *current++=O_NOP;
                            }
                           current=next;
                           goto modif_code;
                         }
                         /*}}}  */
                        else if
                         /*{{{  SET x n TEST x JMP_cond d -> SET x n NOP NOP JMP d*/
                         (    (    *next==M_NULL_COUNTER
                                || *next==M_POSITIV_COUNTER
                              )
                           && *(next+1)==*(current+1)
                           && (next+2)!=end
                           && (    *(next+2)==M_JMP_TRUE
                                || *(next+2)==M_JMP_FALSE
                              )
                           && !jumped_on(start,end,next+2,next+4)
                         )
                         { bool jmp=(*(current+2)==0);

                           if (*next==M_POSITIV_COUNTER) jmp = !jmp;
                           if (isdummy(*(current+1)))
                            /*{{{  set dummy not needed*/
                            { *current++=O_NOP;*current++=O_NOP;*current++=O_NOP; }
                            /*}}}  */
                           /*{{{  test not needed*/
                           *next++=O_NOP;*next++=O_NOP;
                           /*}}}  */
                           if (*next!=M_JMP_TRUE) jmp = !jmp;
                           if (jmp)
                            /*{{{  jmp_cond -> jmp*/
                            { *next=M_JMP;
                              next+=2;
                            }
                            /*}}}  */
                           else
                            /*{{{  jmp not needed*/
                            { *next++=O_NOP;
                              *next++=O_NOP;
                            }
                            /*}}}  */
                           current=next;
                           goto modif_code;
                         }
                         /*}}}  */
                      }
                     goto skip_code;
                  /*}}}  */
                  /*{{{  M_ADD_COUNTER ...*/
                  case M_ADD_COUNTER:
                     if (next!=end && !jumped_on(start,end,next,next_command(next)))
                      { if
                         /*{{{  ADD x n ADD x m -> NOP NOP NOP ADD X n+m*/
                         (*next==M_ADD_COUNTER && *(next+1)==*(current+1))
                         { *(next+2)+=*(current+2);
                           *current++=O_NOP;
                           *current++=O_NOP;
                           *current++=O_NOP;
                           goto modif_code;
                         }
                         /*}}}  */
                      }
                     goto skip_code;
                  /*}}}  */
                  /*{{{  M_END_MACRO   ...*/
                  case M_END_MACRO:
                     if
                      /*{{{  M_END M_END -> O_NOP M_END*/
                      (next!=end && *next==M_END_MACRO)
                      { *current++=O_NOP;
                        goto modif_code;
                      }
                      /*}}}  */
                     goto skip_code;
                  /*}}}  */
                  default:
                  skip_code:
                   /*{{{  set current to next and loop*/
                     current=next;
                     continue;
                   /*}}}  */
                  modif_code:
                   /*{{{  count modifications and loop*/
                     changed=TRUE;
                     modified++;
                     continue;
                   /*}}}  */
                }
             }
            /*}}}  */
        }
        /*}}}  */
        if (verbose)
         /*{{{  maybe print current optimization strip*/
         { s_jmp_cut+=jmp_cut;
           s_modified+=modified;
           s_shrink+=shrink;
           if (jmp_cut+modified+shrink)
              fprintf(stderr,F_OPT_FORMAT,++loop,jmp_cut,modified,shrink);
         }
         /*}}}  */
      }
      /*}}}  */
     while (changed);
     if (verbose)
      /*{{{  maybe print current optimization strip*/
      if (loop>1 && s_jmp_cut+s_modified+s_shrink)
        fprintf(stderr,F_OPT_S_FORMAT,loop,s_jmp_cut,s_modified,s_shrink);
      /*}}}  */
     return(end);
   }
   /*}}}  */
#endif
