/***** spin: dstep.c *****/

/*
* This file is part of the public release of Spin. It is subject to the
* terms in the LICENSE file that is included in this source directory.
* Tool documentation is available at http://spinroot.com
*/

#include <assert.h>
#include "spin.h"
#include "y.tab.h"

#define MAXDSTEP        2048    /* was 512 */

char    *NextLab[64];   /* must match value in pangen2.c:41 */
int     Level=0, GenCode=0, IsGuard=0, TestOnly=0;

static int      Tj=0, Jt=0, LastGoto=0;
static int      Tojump[MAXDSTEP], Jumpto[MAXDSTEP], Special[MAXDSTEP];
static void     putCode(FILE *, Element *, Element *, Element *, int);

extern int      Pid, separate, OkBreak;

static void
Sourced(int n, int special)
{       int i;
       for (i = 0; i < Tj; i++)
               if (Tojump[i] == n)
                       return;
       if (Tj >= MAXDSTEP)
               fatal("d_step sequence too long", (char *)0);
       Special[Tj] = special;
       Tojump[Tj++] = n;
}

static void
Dested(int n)
{       int i;
       for (i = 0; i < Tj; i++)
               if (Tojump[i] == n)
                       return;
       for (i = 0; i < Jt; i++)
               if (Jumpto[i] == n)
                       return;
       if (Jt >= MAXDSTEP)
               fatal("d_step sequence too long", (char *)0);
       Jumpto[Jt++] = n;
       LastGoto = 1;
}

static void
Mopup(FILE *fd)
{       int i, j;

       for (i = 0; i < Jt; i++)
       {       for (j = 0; j < Tj; j++)
                       if (Tojump[j] == Jumpto[i])
                               break;
               if (j == Tj)
               {       char buf[16];
                       if (Jumpto[i] == OkBreak)
                       {       if (!LastGoto)
                               fprintf(fd, "S_%.3d_0:  /* break-dest */\n",
                                       OkBreak);
                       } else {
                               sprintf(buf, "S_%.3d_0", Jumpto[i]);
                               non_fatal("goto %s breaks from d_step seq", buf);
       }       }       }
       for (j = 0; j < Tj; j++)
       {       for (i = 0; i < Jt; i++)
                       if (Tojump[j] == Jumpto[i])
                               break;
#ifdef DEBUG
               if (i == Jt && !Special[i])
                       fprintf(fd, "\t\t/* no goto's to S_%.3d_0 */\n",
                       Tojump[j]);
#endif
       }
       for (j = i = 0; j < Tj; j++)
               if (Special[j])
               {       if (i >= MAXDSTEP)
                       {       fatal("cannot happen (dstep.c)", (char *)0);
                       }
                       Tojump[i] = Tojump[j];
                       Special[i] = 2;
                       i++;
               }
       Tj = i; /* keep only the global exit-labels */
       Jt = 0;
}

static int
FirstTime(int n)
{       int i;
       for (i = 0; i < Tj; i++)
               if (Tojump[i] == n)
                       return (Special[i] <= 1);
       return 1;
}

static void
illegal(Element *e, char *str)
{
       printf("illegal operator in 'd_step:' '");
       comment(stdout, e->n, 0);
       printf("'\n");
       fatal("'%s'", str);
}

static void
filterbad(Element *e)
{
       switch (e->n->ntyp) {
       case ASSERT:
       case PRINT:
       case 'c':
               /* run cannot be completely undone
                * with sv_save-sv_restor
                */
               if (any_oper(e->n->lft, RUN))
                       illegal(e, "run operator in d_step");

               /* remote refs inside d_step sequences
                * would be okay, but they cannot always
                * be interpreted by the simulator the
                * same as by the verifier (e.g., for an
                * error trail)
                */
               if (any_oper(e->n->lft, 'p'))
                       illegal(e, "remote reference in d_step");
               break;
       case '@':
               illegal(e, "process termination");
               break;
       case D_STEP:
               illegal(e, "nested d_step sequence");
               break;
       case ATOMIC:
               illegal(e, "nested atomic sequence");
               break;
       default:
               break;
       }
}

static int
CollectGuards(FILE *fd, Element *e, int inh)
{       SeqList *h; Element *ee;

       for (h = e->sub; h; h = h->nxt)
       {       ee = huntstart(h->this->frst);
               filterbad(ee);
               switch (ee->n->ntyp) {
               case NON_ATOMIC:
                       inh += CollectGuards(fd, ee->n->sl->this->frst, inh);
                       break;
               case  IF:
                       inh += CollectGuards(fd, ee, inh);
                       break;
               case '.':
                       if (ee->nxt->n->ntyp == DO)
                               inh += CollectGuards(fd, ee->nxt, inh);
                       break;
               case ELSE:
                       if (inh++ > 0) fprintf(fd, " || ");
/* 4.2.5 */             if (!pid_is_claim(Pid))
                               fprintf(fd, "(boq == -1 /* else */)");
                       else
                               fprintf(fd, "(1 /* else */)");
                       break;
               case 'R':
                       if (inh++ > 0) fprintf(fd, " || ");
                       fprintf(fd, "("); TestOnly=1;
                       putstmnt(fd, ee->n, ee->seqno);
                       fprintf(fd, ")"); TestOnly=0;
                       break;
               case 'r':
                       if (inh++ > 0) fprintf(fd, " || ");
                       fprintf(fd, "("); TestOnly=1;
                       putstmnt(fd, ee->n, ee->seqno);
                       fprintf(fd, ")"); TestOnly=0;
                       break;
               case 's':
                       if (inh++ > 0) fprintf(fd, " || ");
                       fprintf(fd, "("); TestOnly=1;
/* 4.2.1 */             if (!pid_is_claim(Pid)) fprintf(fd, "(boq == -1) && ");
                       putstmnt(fd, ee->n, ee->seqno);
                       fprintf(fd, ")"); TestOnly=0;
                       break;
               case 'c':
                       if (inh++ > 0) fprintf(fd, " || ");
                       fprintf(fd, "("); TestOnly=1;
                       if (!pid_is_claim(Pid))
                               fprintf(fd, "(boq == -1 && ");
                       putstmnt(fd, ee->n->lft, e->seqno);
                       if (!pid_is_claim(Pid))
                               fprintf(fd, ")");
                       fprintf(fd, ")"); TestOnly=0;
                       break;
       }       }
       return inh;
}

int
putcode(FILE *fd, Sequence *s, Element *nxt, int justguards, int ln, int seqno)
{       int isg=0;
       static char buf[64];

       NextLab[0] = "continue";
       filterbad(s->frst);

       switch (s->frst->n->ntyp) {
       case UNLESS:
               non_fatal("'unless' inside d_step - ignored", (char *) 0);
               return putcode(fd, s->frst->n->sl->this, nxt, 0, ln, seqno);
       case NON_ATOMIC:
               (void) putcode(fd, s->frst->n->sl->this, ZE, 1, ln, seqno);
               if (justguards) return 0;       /* 6.2.5 */
               break;
       case IF:
               fprintf(fd, "if (!(");
               if (!CollectGuards(fd, s->frst, 0))     /* what about boq? */
                       fprintf(fd, "1");
               fprintf(fd, "))\n\t\t\tcontinue;");
               isg = 1;
               break;
       case '.':
               if (s->frst->nxt->n->ntyp == DO)
               {       fprintf(fd, "if (!(");
                       if (!CollectGuards(fd, s->frst->nxt, 0))
                               fprintf(fd, "1");
                       fprintf(fd, "))\n\t\t\tcontinue;");
                       isg = 1;
               }
               break;
       case 'R': /* <- can't really happen (it's part of a 'c') */
               fprintf(fd, "if (!("); TestOnly=1;
               putstmnt(fd, s->frst->n, s->frst->seqno);
               fprintf(fd, "))\n\t\t\tcontinue;"); TestOnly=0;
               break;
       case 'r':
               fprintf(fd, "if (!("); TestOnly=1;
               putstmnt(fd, s->frst->n, s->frst->seqno);
               fprintf(fd, "))\n\t\t\tcontinue;"); TestOnly=0;
               break;
       case 's':
               fprintf(fd, "if (");
#if 1
/* 4.2.1 */     if (!pid_is_claim(Pid)) fprintf(fd, "(boq != -1) || ");
#endif
               fprintf(fd, "!("); TestOnly=1;
               putstmnt(fd, s->frst->n, s->frst->seqno);
               fprintf(fd, "))\n\t\t\tcontinue;"); TestOnly=0;
               break;
       case 'c':
               fprintf(fd, "if (!(");
               if (!pid_is_claim(Pid)) fprintf(fd, "boq == -1 && ");
               TestOnly=1;
               putstmnt(fd, s->frst->n->lft, s->frst->seqno);
               fprintf(fd, "))\n\t\t\tcontinue;"); TestOnly=0;
               break;
       case ELSE:
               fprintf(fd, "if (boq != -1 || (");
               if (separate != 2) fprintf(fd, "trpt->");
               fprintf(fd, "o_pm&1))\n\t\t\tcontinue;");
               {       extern FILE *th;
                       fprintf(th, "#ifndef ELSE_IN_GUARD\n");
                       fprintf(th, "   #define ELSE_IN_GUARD\n");
                       fprintf(th, "#endif\n");
               }
               break;
       case ASGN:      /* new 3.0.8 */
               fprintf(fd, "IfNotBlocked");
               break;
       default:
               fprintf(fd, "/* default %d */\n\t\t", s->frst->n->ntyp);
       }

       /* 6.2.5 : before TstOnly */
       fprintf(fd, "\n\n\t\treached[%d][%d] = 1;\n\t\t", Pid, seqno);
       fprintf(fd, "reached[%d][t->st] = 1;\n\t\t", Pid); /* next state */
       fprintf(fd, "reached[%d][tt] = 1;\n", Pid);     /* current state */

       /* 6.2.5 : before sv_save() */
       if (s->frst->n->ntyp != NON_ATOMIC)
       fprintf(fd, "\n\t\tif (TstOnly) return 1;\n"); /* if called from enabled() */

       if (justguards) return 0;

       fprintf(fd, "\n\t\tsv_save();\n\t\t");
       sprintf(buf, "Uerror(\"block in d_step seq, line %d\")", ln);
       NextLab[0] = buf;
       putCode(fd, s->frst, s->extent, nxt, isg);

       if (nxt)
       {       extern Symbol *Fname;
               extern int lineno;

               if (FirstTime(nxt->Seqno)
               && (!(nxt->status & DONE2) || !(nxt->status & D_ATOM)))
               {       fprintf(fd, "S_%.3d_0: /* 1 */\n", nxt->Seqno);
                       nxt->status |= DONE2;
                       LastGoto = 0;
               }
               Sourced(nxt->Seqno, 1);
               lineno = ln;
               Fname = nxt->n->fn;
               Mopup(fd);
       }
       unskip(s->frst->seqno);
       return LastGoto;
}

static void
putCode(FILE *fd, Element *f, Element *last, Element *next, int isguard)
{       Element *e, *N;
       SeqList *h; int i;
       char NextOpt[64];
       static int bno = 0;

       for (e = f; e; e = e->nxt)
       {       if (e->status & DONE2)
                       continue;
               e->status |= DONE2;

               if (!(e->status & D_ATOM))
               {       if (!LastGoto)
                       {       fprintf(fd, "\t\tgoto S_%.3d_0;\n",
                                       e->Seqno);
                               Dested(e->Seqno);
                       }
                       break;
               }
               fprintf(fd, "S_%.3d_0: /* 2 */\n", e->Seqno);
               LastGoto = 0;
               Sourced(e->Seqno, 0);

               if (!e->sub)
               {       filterbad(e);
                       switch (e->n->ntyp) {
                       case NON_ATOMIC:
                               h = e->n->sl;
                               putCode(fd, h->this->frst,
                                       h->this->extent, e->nxt, 0);
                               break;
                       case BREAK:
                               if (LastGoto) break;
                               if (e->nxt)
                               {       i = target( huntele(e->nxt,
                                               e->status, -1))->Seqno;
                                       fprintf(fd, "\t\tgoto S_%.3d_0; ", i);
                                       fprintf(fd, "/* 'break' */\n");
                                       Dested(i);
                               } else
                               {       if (next)
                                       {       fprintf(fd, "\t\tgoto S_%.3d_0;",
                                                       next->Seqno);
                                               fprintf(fd, " /* NEXT */\n");
                                               Dested(next->Seqno);
                                       } else
                                       fatal("cannot interpret d_step", 0);
                               }
                               break;
                       case GOTO:
                               if (LastGoto) break;
                               i = huntele( get_lab(e->n,1),
                                       e->status, -1)->Seqno;
                               fprintf(fd, "\t\tgoto S_%.3d_0; ", i);
                               fprintf(fd, "/* 'goto' */\n");
                               Dested(i);
                               break;
                       case '.':
                               if (LastGoto) break;
                               if (e->nxt && (e->nxt->status & DONE2))
                               {       i = e->nxt->Seqno;
                                       fprintf(fd, "\t\tgoto S_%.3d_0;", i);
                                       fprintf(fd, " /* '.' */\n");
                                       Dested(i);
                               }
                               break;
                       default:
                               putskip(e->seqno);
                               GenCode = 1; IsGuard = isguard;
                               fprintf(fd, "\t\t");
                               putstmnt(fd, e->n, e->seqno);
                               fprintf(fd, ";\n");
                               GenCode = IsGuard = isguard = LastGoto = 0;
                               break;
                       }
                       i = e->nxt?e->nxt->Seqno:0;
                       if (e->nxt && (e->nxt->status & DONE2) && !LastGoto)
                       {       fprintf(fd, "\t\tgoto S_%.3d_0; ", i);
                               fprintf(fd, "/* ';' */\n");
                               Dested(i);
                               break;
                       }
               } else
               {       for (h = e->sub, i=1; h; h = h->nxt, i++)
                       {       sprintf(NextOpt, "goto S_%.3d_%d",
                                       e->Seqno, i);
                               NextLab[++Level] = NextOpt;
                               N = (e->n && e->n->ntyp == DO) ? e : e->nxt;
                               putCode(fd, h->this->frst,
                                       h->this->extent, N, 1);
                               Level--;
                               fprintf(fd, "%s: /* 3 */\n", &NextOpt[5]);
                               LastGoto = 0;
                       }
                       if (!LastGoto)
                       {       fprintf(fd, "\t\tUerror(\"blocking sel ");
                               fprintf(fd, "in d_step (nr.%d, near line %d)\");\n",
                               bno++, (e->n)?e->n->ln:0);
                               LastGoto = 0;
                       }
               }
               if (e == last)
               {       if (!LastGoto && next)
                       {       fprintf(fd, "\t\tgoto S_%.3d_0;\n",
                                       next->Seqno);
                               Dested(next->Seqno);
                       }
                       break;
       }       }
}