diff interps/c-intercal/src/unravel.c @ 996:859f9b4339e6

<Gregor> tar xf egobot.tar.xz
author HackBot
date Sun, 09 Dec 2012 19:30:08 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/interps/c-intercal/src/unravel.c	Sun Dec 09 19:30:08 2012 +0000
@@ -0,0 +1,825 @@
+/*****************************************************************************
+
+NAME
+    unravel.c -- multithreading and backtracking support for C-INTERCAL
+
+LICENSE TERMS
+    Copyright (C) 2006 Alex Smith
+
+    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.
+
+***************************************************************************/
+
+/* Notes about authorship. This entire file was written by Alex Smith (AIS),
+   but with reference to an earlier implementation of Threaded INTERCAL written
+   by Malcom Ryan. I have not reused any of his C code (two of his INTERCAL
+   programs are in the pit), but it was a useful source of inspiration, even
+   though I did it a completely different and less efficient way (I think we
+   handled mutiple COME FROM recognition in much the same way). This file only
+   contains functions necessary for multithreading; some of the code is stored
+   in feh2.c (stored in strings), or in perpet.c (again stored in strings).
+   The information about the required syntax is in parser.y and lexer.l.
+   A small amount of the multithread code is in ick-wrap.c, although as that
+   file is copied almost verbatim (although it's quite a big 'almost') into
+   all ick output, it's guarded there by #if...#endif. The whole multithread
+   idea started on alt.lang.intercal when it was noticed that multiple COME
+   FROMs aiming at the same line were hard to interpret, but I would like
+   to thank Malcom Ryan for drafting the Threaded Intercal standard and a
+   reference compiler, and for turning the idea into a concrete set of rules.
+   (The standard itself carries a note saying that a revision (use of AGAIN)
+   was inspired by Kyle Dean, so I'd like to mention that here too, and I also
+   revised it (so that ABSTAIN/REINSTATE were preserved through backtracking).)
+   In terms of the quality of the code, Malcom Ryan's code was clearly better
+   for multithread programs, but required rewriting most of the compiler. As
+   clearly it would be unfair to subject the INTERCAL community at large to
+   the risk of silent multithreading by default, I decided multithreading
+   should be off by default (but ONCE/AGAIN should be legal). So this code is
+   designed to interfere as little as possible with non-multithreaded output.
+   There are two versions of libick compiled by the Makefile in this
+   distribution: libick.a, the non-multithread version, and libickmt.a, the
+   multithread version. The only difference between them is this file,
+   unravel.c, which is multithread only, so it seemed a good place for this
+   comment. To see what the multithreading looks like in the object code,
+   see degenerated code or feh2.c. */
+
+/* This file also implements Backtracking INTERCAL, which is turned on with
+   the same command-line option (-m). This implementation differs from
+   Malcom Ryan's standard in that abstention stati are not restored upon
+   GOING BACK. As far as I know, this is the only Backtracking INTERCAL
+   implementation currently available, although the code depends on the
+   multithreading code (which is why -m is neccesary). */
+
+/* Note about control structures: Crazy INTERCAL control structures call for
+   crazy C control structures. setjmp/longjmp is probably C's weirdest
+   control structure (it's sort of like a time-reversed computed COME FROM).
+   I've used them to implement both multithreading and computed COME FROMming.
+
+   Worried that this code might actually be copied by a casual C programmer, I
+   have the following warning:
+
+   setjmp/longjmp is worse than goto in terms of spaghettification of code.
+   It is strictly to be used only when neccesary, or when writing INTERCAL
+   compilers. They also lead to weird portability problems when you don't obey
+   all the restrictions put on them by the C standard. For instance,
+   if(setjmp(ick_cjb)!=0) puts("Jumped"); is portable, but
+   if(setjmp(ick_cjb)) puts("Jumped"); is not, despite seeming to mean exactly the
+   same thing. Also, the address of setjmp can't be taken. Semantically,
+   setjmp/longjmp lets you do the same crazy things like goto, like jump into
+   the middle of a loop, except in this case the loop can be in a different
+   function, or even in a different file. unravel.c has no qualms about
+   jumping straight into the middle of an if statement in a degenerated C
+   program, even if said degenerated C program didn't exist when libickmt.a was
+   compiled. */
+
+/* LINTLIBRARY */
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <setjmp.h>
+#include <assert.h>
+
+#define MULTITHREAD 1
+
+#include "sizes.h"
+#include "abcess.h"
+#include "ick_lose.h"
+
+int gonebackto; /* Is the choicepoint reached by GOING BACK? */
+static int choicing = 0; /* Is a multithread function being used to do
+			    backtracking processing? */
+jmp_buf btjb; /* The backtracking jmp_buf */
+/* About the annotation 'dependent': topchoice is really 'owned', but the owner keeps
+   on changing. So Splint gets less confused if we simply never tell it who the owner
+   is. */
+/*@null@*/ /*@dependent@*/ static ickthread* topchoice; /* Top of the choicepoint stack */
+extern int ick_lineno; /* Keep track of error-message lines */
+
+extern int ick_printflow; /* from arrgghh.c; a debug option */
+
+int weaving=0; /* Weave newly created threads? */
+
+/* Printflow debugging output */
+static void fluputs(const char* s)
+{
+  fprintf(stderr,"%s",s);
+  (void) fflush(stderr);
+}
+
+/**********************************************************************
+ *
+ * This functions deal with choicepoints, which are implemented as
+ * ickthread objects. choicepoint creates a choicepoint or marks a
+ * choicepoint as stale; choiceahead eliminates a choicepoint;
+ * choiceback eliminates a stale choicepoint or returns to a fresh
+ * choicepoint.
+ *
+ *********************************************************************/
+
+void choicepoint(void)
+{
+  ickthread* oldprev, *oldtc;
+  int oldweave;
+  if(gonebackto)
+  { /* Create a stale choicepoint */
+    if(ick_printflow) fluputs("(back)");
+    oldtc = topchoice;
+    /* Suppress the onlytrans warning because we're allocating a member
+       of a linked list, which confuses Splint no matter what the list
+       is flagged as (because malloc returns 'only' data, but yet that
+       data has to be pointed to by other 'only' data in the same list). */
+    /*@-onlytrans@*/
+    topchoice = (ickthread*) malloc(sizeof(ickthread));
+    /*@=onlytrans@*/
+    if(!topchoice) ick_lose(IE991, ick_lineno, (const char*) NULL);
+    topchoice->choicepoint = oldtc;
+    topchoice->stale = 1;
+    topchoice->refcount = 1; /* At the moment, this is the only
+				thread looking at this choicepoint */
+    topchoice->dsi=topchoice;
+    topchoice->usesthis=0;
+    /* topchoice needn't be completely defined if it's stale */
+    /*@-compdef@*/
+    return;
+    /*@=compdef@*/
+  }
+  else
+  { /* Create a new choicepoint */
+    if(ick_printflow) fluputs("(maybe)");
+    oldprev = ickmt_prev;
+    choicing = 1;
+    oldweave = weaving;
+    weaving = 0;
+    (void) multicome1(ick_lineno,btjb); /* Duplicate data */
+    weaving = oldweave;
+    choicing = 0;
+    oldprev->ick_next = ickmt_cur;
+    ickmt_prev->choicepoint = topchoice;
+    topchoice = ickmt_prev;
+    ickmt_prev = oldprev;
+    topchoice->stale = 0;
+    topchoice->refcount = 1;
+    /* So in other words, we've duplicated the current execution
+       environment, except for the choicepoint stack, changed the
+       duplicate from a thread into a choicepoint, and pushed it
+       on top of the choicepoint stack. Its pc is the point in the
+       degenerated program where choicepoint was called. */
+  }
+}
+
+void choiceahead(void)
+{
+  ickthread* tempthread;
+  jmp_buf fakepc;
+
+  if(!topchoice) ick_lose(IE404, ick_lineno, (const char*) NULL); /* That's what IE404's for */
+  /* GO AHEAD with multithreading requires care. The choicepoint should only
+     be removed from this thread. topchoice = topchoice->ick_next almost works, but
+     is a memory leak. So most of this is garbage-collection. */
+
+  /* If other threads are using the choicepoint, don't free it. */
+  if(topchoice->refcount > 1)
+  {
+    if(ick_printflow) fluputs("(refahead)");
+    topchoice->refcount--;
+    topchoice = topchoice->choicepoint;
+    return;
+  }
+
+  /* The top choicepoint is not being used by other threads; free it. */
+
+  /* Freeing a stale choicepoint (which contains no data) is easy. */
+  if(topchoice->stale)
+  {
+    if(ick_printflow) fluputs("(destale)");
+    tempthread = topchoice;
+    topchoice = topchoice->choicepoint;
+    /*@-dependenttrans@*/ /* because it's really owned, not dependent */
+    free(tempthread);
+    /*@=dependenttrans@*/
+    return;
+  }
+
+  if(ick_printflow) fluputs("(ahead)");
+
+  /* This code works by converting topchoice back to a thread and placing it
+     just before the current thread, and then killing it. First, the data from
+     this thread, apart from the choicepoint stack, must be saved. */
+  choicing = 1;
+  if(setjmp(fakepc) == 0)
+  {
+    memcpy((void*)ickmt_cur->pc,(const void*)fakepc,sizeof(jmp_buf));
+    nextthread(fakepc, -1, 5);
+  }
+  choicing = 0;
+  /* That's saved the current thread's data. Now to convert topchoice to a
+     thread. */
+  tempthread = topchoice->choicepoint;
+  ickmt_prev->ick_next = topchoice;
+  topchoice->ick_next = ickmt_cur;
+  ickmt_cur = topchoice;
+  topchoice = tempthread;
+  /* Let's load the backtracking data... */
+  choicing = 1;
+  if(setjmp(fakepc) == 0)
+  {
+    memcpy((void *)ickmt_cur->pc,(const void *)fakepc,sizeof(jmp_buf));
+    nextthread(fakepc, -1, 6);
+  }
+  /* only to destroy it! Mwahahahahah! */
+  if(setjmp(fakepc) == 0)
+  {
+    memcpy((void *)ickmt_cur->ick_next->pc,(const void *)fakepc,sizeof(jmp_buf));
+    killthread();
+  }
+  choicing = 0;
+  /* So we've reloaded the original current thread, the original previous
+     thread is still correct, topchoice has become topchoice->choicepoint,
+     and the original topchoice has disappeared. Mission accomplished. */
+}
+
+void choiceback(void)
+{
+  if(!topchoice) ick_lose(IE404, ick_lineno, (const char *) NULL);
+  if(topchoice->stale)
+  {
+    if(ick_printflow) fluputs("(back=)");
+    choiceahead();
+    return;
+  }
+  /* That's two simple cases out of the way (at least once choiceahead's
+     been implemented). What we need to do to backtrack is to change
+     topchoice to a thread after the current thread (rather than before
+     as in the previous two functions), and then kill the current thread.
+     (It amuses me that destroying a choicepoint, as in choiceahead(),
+     is more complicated than either visiting a choicepoint or creating a
+     choicepoint. That's how much work it can take to avoid a memory leak.)
+     In this case, choiceback won't return in the normal fashion. */
+  if(topchoice->refcount > 1)
+  {
+    /* The Threaded INTERCAL standard states that if other threads are using
+       the choicepoint, a GO BACK should cause the thread it's in to be
+       killed. (Of course, if it's stale, it should just have been removed.)*/
+    if(ick_printflow) fluputs("(desplit)");
+    killthread();
+    return;
+  }
+  topchoice->ick_next = ickmt_cur->ick_next;
+  ickmt_cur->ick_next = topchoice;
+  topchoice = topchoice->choicepoint;
+  if(ickmt_cur==ickmt_prev) ickmt_prev = ickmt_cur->ick_next;
+  choicing = 2; /* Tells killthread to set it back to 0 */
+  killthread();
+}
+
+/**********************************************************************
+ *
+ * This function is called when two COME FROMs reference the same
+ * line at runtime. multicome1 is used in a multithread
+ * program; it forks the program. For ick_multicome0, see cesspool.c.
+ *
+ *********************************************************************/
+
+int multicome1(int errlineno, jmp_buf pc)
+{
+  /* Make a new thread just before the current one. Fake a PC in the
+     current thread within this function, change to the new thread, then
+     call nextthread. The upshot of all this is that all this thread's
+     data is stored in the new thread's state. Then, we have to copy
+     all this thread's current data to new locations. */
+  ickthread* newthread;
+  jmp_buf fakepc;
+  ick_stashbox *isb, *isb2, *isbprev;
+  void* tvp;
+  ick_array* a;
+  int prod, i, j;
+  newthread = malloc(sizeof(ickthread));
+  if(!newthread) ick_lose(IE991, errlineno, (const char *) NULL);
+  ickmt_prev->ick_next = newthread;
+  newthread->ick_next = ickmt_cur;
+  ickmt_cur = newthread;
+  newthread->dsi=newthread;
+  newthread->usesthis=0;
+
+  if(ick_printflow && !choicing) fluputs("(fork)");
+
+  if(setjmp(fakepc) == 0)
+  {
+    memcpy((void *)newthread->ick_next->pc,(const void *)fakepc,sizeof(jmp_buf));
+    nextthread(pc, -1, 1);
+  }
+  /* So on the previous line: Save the value of pc as the program
+     counter of the new thread, and give a fake value for the
+     program value of the current thread (so it returns here,
+     not anywhere in the degenerated program). All that remains
+     is to duplicate all the data stored through pointers in the 'old'
+     thread (nextthread has changed ickmt_cur to point at the 'old'
+     thread). The original memory pointed to by these pointers is in
+     use storing the values in the 'new' thread, so the 'old' thread
+     needs new copies that it can modify independently. */
+
+  if(!weaving)
+  {
+    /* duplicate variables, forget indicators */
+    i = onespotcount;
+    tvp = ick_onespots;
+    ick_onespots = malloc(i * sizeof *ick_onespots);
+    if(!ick_onespots) ick_lose(IE991, errlineno, (const char *) NULL);
+    memcpy(ick_onespots, tvp, i * sizeof *ick_onespots);
+    if(ick_oo_onespots)
+    {
+      tvp = ick_oo_onespots;
+      ick_oo_onespots = malloc(i * sizeof *ick_oo_onespots);
+      if(!ick_oo_onespots) ick_lose(IE991, errlineno, (const char *) NULL);
+      memcpy(ick_oo_onespots, tvp, i * sizeof *ick_oo_onespots);
+    }
+    i = twospotcount;
+    tvp = ick_twospots;
+    ick_twospots = malloc(i * sizeof *ick_twospots);
+    if(!ick_twospots) ick_lose(IE991, errlineno, (const char *) NULL);
+    memcpy(ick_twospots, tvp, i * sizeof *ick_twospots);
+    if(ick_oo_twospots)
+    {
+      tvp = ick_oo_twospots;
+      ick_oo_twospots = malloc(i * sizeof *ick_oo_twospots);
+      if(!ick_oo_twospots) ick_lose(IE991, errlineno, (const char *) NULL);
+      memcpy(ick_oo_twospots, tvp, i * sizeof *ick_oo_twospots);
+    }
+    i = tailcount;
+    tvp = ick_tails;
+    ick_tails = malloc(i * sizeof *ick_tails);
+    if(!ick_tails) ick_lose(IE991, errlineno, (const char *) NULL);
+    memcpy(ick_tails, tvp, i * sizeof *ick_tails);
+    i = hybridcount;
+    tvp = ick_hybrids;
+    ick_hybrids = malloc(i * sizeof *ick_hybrids);
+    if(!ick_hybrids) ick_lose(IE991, errlineno, (const char *) NULL);
+    memcpy(ick_hybrids, tvp, i * sizeof *ick_hybrids);
+    i = onespotcount;
+    tvp = ick_oneforget;
+    ick_oneforget = malloc(i * sizeof *ick_oneforget);
+    if(!ick_oneforget) ick_lose(IE991, errlineno, (const char *) NULL);
+    memcpy(ick_oneforget, tvp, i * sizeof *ick_oneforget);
+    i = twospotcount;
+    tvp = ick_twoforget;
+    ick_twoforget = malloc(i * sizeof *ick_twoforget);
+    if(!ick_twoforget) ick_lose(IE991, errlineno, (const char *) NULL);
+    memcpy(ick_twoforget, tvp, i * sizeof *ick_twoforget);
+    i = tailcount;
+    tvp = ick_tailforget;
+    ick_tailforget = malloc(i * sizeof *ick_tailforget);
+    if(!ick_tailforget) ick_lose(IE991, errlineno, (const char *) NULL);
+    memcpy(ick_tailforget, tvp, i * sizeof *ick_tailforget);
+    i = hybridcount;
+    tvp = ick_hyforget;
+    ick_hyforget = malloc(i * sizeof *ick_hyforget);
+    if(!ick_hyforget) ick_lose(IE991, errlineno, (const char *) NULL);
+    memcpy(ick_hyforget, tvp, i * sizeof *ick_hyforget);
+
+  /* duplicate data stored in arrays */
+    j = tailcount;
+    tvp = NULL;
+    while(j--)
+    {
+      a = ick_tails+j; /* &(ick_tails[j]) */
+      if(!a->rank||!a->dims) continue; /* don't duplicate a deallocated ick_array */
+      tvp = a->dims;
+      /* Much of this code is paraphrased from the ick_stashbox-copying code below,
+	 which was in turn paraphrased from a section in cesspool.c I didn't
+	 write. So any errors in this code are probably mine, but the algorithm
+	 isn't. */
+      a->dims = malloc(a->rank * sizeof *(a->dims));
+      if(a->dims == NULL) ick_lose(IE991, errlineno, (const char *) NULL);
+      memcpy(a->dims, tvp, a->rank * sizeof *(a->dims));
+      prod = (int)!!a->rank;
+      i = (int)a->rank;
+      while(i--) prod *= a->dims[i];
+      /*@-mustfreeonly@*/ /* how on earth did a->dims become only? */
+      tvp = a->data.tail;
+      /*@=mustfreeonly@*/
+      a->data.tail = malloc(prod * sizeof(ick_type16));
+      if(!a->data.tail) ick_lose(IE991, errlineno, (const char *) NULL);
+      memcpy(a->data.tail, tvp, prod * sizeof(ick_type16));
+      /*@-mustfreeonly@*/ /* likewise. This isn't only, honest! */
+      tvp = NULL;
+      /*@=mustfreeonly@*/
+    }
+    j = hybridcount;
+    while(j--)
+    {
+      a = ick_hybrids+j; /* &(ick_hybrids[j]) */
+      if(!a->rank||!a->dims) continue; /* don't duplicate a deallocated ick_array */
+      tvp = a->dims;
+      /* Much of this code is paraphrased from the ick_stashbox-copying code below,
+	 which was in turn paraphrased from a section in cesspool.c I didn't
+	 write. So any errors in this code are probably mine, but the algorithm
+	 isn't. */
+      a->dims = malloc(a->rank * sizeof(*(a->dims)));
+      if(!a->dims) ick_lose(IE991, errlineno, (const char *) NULL);
+      memcpy(a->dims, tvp, a->rank * sizeof(*(a->dims)));
+      prod = (int)!!a->rank;
+      i = (int)a->rank;
+      while(i--) prod *= a->dims[i];
+      /*@-mustfreeonly@*/
+      tvp = a->data.hybrid;
+      /*@=mustfreeonly@*/
+      a->data.hybrid = malloc(prod * sizeof(ick_type32));
+      if(!a->data.hybrid) ick_lose(IE991, errlineno, (const char *) NULL);
+      memcpy(a->data.hybrid, tvp, prod * sizeof(ick_type32));
+      /*@-mustfreeonly@*/
+      tvp=NULL;
+      /*@=mustfreeonly@*/
+    }
+
+    /* duplicate ick_stashbox */
+    isb2 = ick_first;
+    isbprev = (ick_stashbox*)NULL;
+    while(isb2)
+    {
+      isb = (ick_stashbox*)malloc(sizeof(ick_stashbox));
+      if(!isb) ick_lose(IE991, errlineno, (const char *) NULL);
+      memcpy(isb,isb2,sizeof(ick_stashbox));
+      if(isbprev) isbprev->ick_next = isb;
+      isbprev = isb;
+      if(isb2==ick_first) ick_first = isb; /* change ick_first only the ick_first time round */
+      if(isb->type == ick_ONESPOT || isb->type == ick_TWOSPOT)
+      { /* all copying already done */
+	isb2 = isb->ick_next;
+	continue;
+      }
+      /* Copy the stashed ick_array. Much of this code is paraphrased from some
+	 code in cesspool.c. In fact, it's the same, with a few idioms added
+	 and variable names changed. So, although it's in this file, I can't
+	 take the credit for it. */
+      isb->save.a = (ick_array*)malloc(sizeof(ick_array));
+      if(!isb->save.a) ick_lose(IE991, errlineno, (const char *) NULL);
+      assert(isb2 != NULL); /* we already said that in the while condition,
+			       so it's surprising that Splint needs this hint */
+      isb->save.a->rank = isb2->save.a->rank;
+      isb->save.a->dims = malloc(isb2->save.a->rank *
+				 sizeof(*(isb2->save.a->dims)));
+      if(!isb->save.a->dims) ick_lose(IE991, errlineno, (const char *) NULL);
+      memcpy(isb->save.a->dims, isb2->save.a->dims,
+	     isb2->save.a->rank * sizeof(*(isb2->save.a->dims)));
+      prod = (int)!!isb2->save.a->rank;
+      /* I use this idiom often enough in the code produced by my optimizer that I
+	 may as well use it here. */
+      i = (int)isb2->save.a->rank;
+      while(i--) prod *= isb2->save.a->dims[i];
+      if(isb2->type == ick_TAIL)
+      {
+	isb->save.a->data.tail = (ick_type16*)malloc(prod * sizeof(ick_type16));
+	if(!isb->save.a->data.tail) ick_lose(IE991, errlineno, (const char *) NULL);
+	memcpy(isb->save.a->data.tail, isb2->save.a->data.tail,
+	       prod * sizeof(ick_type16));
+      }
+      else
+      {
+	isb->save.a->data.hybrid = (ick_type32*)malloc(prod * sizeof(ick_type32));
+	if(!isb->save.a->data.hybrid) ick_lose(IE991, errlineno, (const char *) NULL);
+	memcpy(isb->save.a->data.hybrid, isb2->save.a->data.hybrid,
+	       prod * sizeof(ick_type32));
+      }
+      isb2 = isb->ick_next;
+    }
+  }
+  else /* we are weaving, reference the old current thread */
+  {
+    ickthread* tempthread;
+    if(ick_printflow) fluputs("(weave)");
+    /* Sanity check to make sure that the threads are arranged correctly */
+    if(newthread->ick_next!=ickmt_cur) ick_lose(IE778, errlineno, (const char *) NULL);
+    tempthread=newthread->ick_next; /* the old current thread */
+    while(tempthread->usesthis) tempthread=tempthread->usesthis;
+    newthread->dsi=tempthread->dsi;
+    tempthread->usesthis=newthread;
+  }
+  /* duplicate NEXT stack */
+  tvp = ick_next;
+  ick_next = malloc(ick_MAXNEXT * sizeof *ick_next);
+  if(!ick_next) ick_lose(IE991, errlineno, (const char *) NULL);
+  memcpy(ick_next, tvp, ick_MAXNEXT * sizeof *ick_next);
+
+  /* allow for multithreading with choicepoints on the stack */
+  if(!choicing && topchoice != NULL)
+  {
+    ickthread* icktp = topchoice;
+    while(icktp)
+    {
+      icktp->refcount++;
+      icktp = icktp->choicepoint; /* ick_next choicepoint on the stack */
+    }
+    /* The old thread's and new thread's choicepoint stack share
+       memory. This is pretty much necessary to handle backtracking
+       past a fork correctly. */
+  }
+
+  /* The nullstate annotation is because Splint doesn't notice that all
+     the mallocs are guarded by lines that error out permanently if they
+     fail. The mustfreefresh annotation is because Splint doesn't realise
+     that I've stored newthread in a pointer buried in the linked list of
+     threads, and so Splint assumes that it's memory-leaking. */
+  /*@-nullstate@*/ /*@-mustfreefresh@*/
+  return 1; /* Tell the degenerated program to look for yet
+	       another COME FROM */
+  /*@=nullstate@*/ /*@=mustfreefresh@*/
+}
+
+/**********************************************************************
+ *
+ * These functions do the multithreading, using setjmp and longjmp
+ * to save the program counter. ickmtinit sets up the ick_first
+ * thread, and nextthread changes to the ick_next thread in the
+ * sequence. (Note that nextthread rarely actually returns). The
+ * code makes each command atomic, so that ONCE and AGAIN appear
+ * to the user to be atomic operations.
+ *
+ *********************************************************************/
+
+/*@partial@*/ /*@dependent@*/ ickthread* ickmt_cur; /* define ickmt_cur */
+/*@partial@*/ /*@dependent@*/ ickthread* ickmt_prev; /* define ickmt_prev */
+
+void ickmtinit(void)
+{
+  /* Splint linked list problems again; the list is marked as 'dependent',
+     which is correct for everything except adding and removing items in the
+     list. (All annotations are incorrect in some respects.) */
+  /*@-onlytrans@*/
+  ickmt_cur = malloc(sizeof(ickthread));
+  /*@=onlytrans@*/
+  if(!ickmt_cur) ick_lose(IE991, 1, (const char *) NULL);
+  ickmt_prev = ickmt_cur;
+  ickmt_cur->ick_next = ickmt_cur;
+  topchoice = (ickthread*) NULL; /* No choicepoints */
+  ickmt_cur->dsi=ickmt_cur;
+  ickmt_cur->usesthis=0;
+}
+
+/* Destroys the current thread, and switches to the ick_next thread.
+   If there are no threads left, terminates the program using exit(0). */
+void killthread(void)
+{
+  static jmp_buf dummy;
+  ick_stashbox* isb, *isbi;
+  int i;
+
+  if(ick_printflow&&!choicing) fluputs("(kill)");
+
+  if(!choicing) while(topchoice) choiceahead();
+  /* The above line will mark each of the choicepoints as no longer being
+     used by this thread, and free them if neccesary. This has to be done
+     ick_first while this thread's pointers are still valid due to the way that
+     choiceahead works. */
+
+  /* If this thread uses another, let the other know about the change */
+  if(ickmt_cur->dsi!=ickmt_cur)
+  {
+    ickthread* temp=ickmt_cur->dsi;
+    if(ick_printflow) fluputs("(deweave)");
+    while(!temp||temp->usesthis!=ickmt_cur)
+    {
+      if(!temp) ick_lose(IE778, -1, (const char *) NULL);
+      temp=temp->usesthis;
+    }
+    temp->usesthis=ickmt_cur->usesthis;
+  }
+  /* If this thread is holding data for others, move it somewhere safe */
+  if(ickmt_cur->usesthis != NULL && ickmt_cur->usesthis->dsi==ickmt_cur)
+  {
+    ickthread* newuses=ickmt_cur->usesthis;
+    ickthread* temp=ickmt_cur->usesthis;
+    if(ick_printflow) fluputs("(shift)");
+    while(temp)
+    {
+      temp->dsi=newuses;
+      temp=temp->usesthis;
+    }
+    ickmt_cur->dsi=newuses; /* so the data will be transferred later */
+  }
+
+  if(ickmt_cur->dsi==ickmt_cur)
+  {
+    /* We aren't storing data for another thread, and we have data of our own,
+       or dsi would point somewhere else (either naturally or because it was
+       changed higher up). */
+    if(ick_printflow) fluputs("(free)");
+    /* Deallocate the current thread's data */
+    i=tailcount;
+    while(i--)
+    { /* free tail data */
+      if(!ick_tails[i].rank||!ick_tails[i].dims) continue; /* already free */
+      free(ick_tails[i].dims);
+      free(ick_tails[i].data.tail);
+    }
+    i=hybridcount;
+    while(i--)
+    { /* free hybrid data */
+      if(!ick_hybrids[i].rank||!ick_hybrids[i].dims) continue; /* already free */
+      free(ick_hybrids[i].dims);
+      free(ick_hybrids[i].data.hybrid);
+    }
+    /* unqualifiedtrans because although they aren't always only, they're only at the moment;
+       compdestroy because we have just deallocated tail and hybrid data */
+    /*@-unqualifiedtrans@*/ /*@-compdestroy@*/
+    free(ick_onespots); free(ick_twospots); free(ick_tails); free(ick_hybrids);
+    free(ick_oneforget); free(ick_twoforget); free(ick_tailforget); free(ick_hyforget);
+    if(ick_oo_onespots) free(ick_oo_onespots); if(ick_oo_twospots) free(ick_oo_twospots);
+    /*@=unqualifiedtrans@*/ /*@=compdestroy@*/
+
+    isbi = ick_first;
+    while(isbi) /* Free ick_stash */
+    {
+      isb=isbi->ick_next;
+      if(isbi->type == ick_TAIL || isbi->type == ick_HYBRID)
+      {
+	free(isbi->save.a->dims);
+	if(isbi->type == ick_TAIL)
+	  free(isbi->save.a->data.tail);
+	else
+	  free(isbi->save.a->data.hybrid);
+      }
+      free(isbi);
+      isbi=isb;
+    }
+  }
+  else
+  {
+    /* We still need to save our variables for the benefit of woven threads. */
+    /* save variables */
+    ickmt_cur->dsi->varforget[0] = ick_onespots;
+    ickmt_cur->dsi->varforget[1] = ick_twospots;
+    ickmt_cur->dsi->varforget[2] = ick_tails;
+    ickmt_cur->dsi->varforget[3] = ick_hybrids;
+    ickmt_cur->dsi->varforget[4] = ick_oneforget;
+    ickmt_cur->dsi->varforget[5] = ick_twoforget;
+    ickmt_cur->dsi->varforget[6] = ick_tailforget;
+    ickmt_cur->dsi->varforget[7] = ick_hyforget;
+    ickmt_cur->dsi->varforget[8] = ick_oo_onespots;
+    ickmt_cur->dsi->varforget[9] = ick_oo_twospots;
+
+    /* save ick_stashbox */
+    /*@-unqualifiedtrans@*/ /* the linked list problem again */
+    ickmt_cur->dsi->sb = ick_first;
+    /*@=unqualifiedtrans@*/
+    /*@-branchstate@*/ /* it's reference-counted */
+  }
+  /*@=branchstate@*/
+  /*@-unqualifiedtrans@*/ /* it is only at this point */
+  free(ick_next); /* Free NEXT stack */
+  /*@=unqualifiedtrans@*/
+
+  ickmt_prev->ick_next = ickmt_cur->ick_next;
+  if(ickmt_cur->ick_next == ickmt_cur)
+  {
+    /*@-dependenttrans@*/ /* because it isn't really dependent */
+    free(ickmt_cur);
+    /*@=dependenttrans@*/
+    exit(0);
+  }
+  else
+  {
+    /* We need to run about half of nextthread. So we pass in a 2 for
+       flags and tell it to skip the ick_first half. */
+    /*@-dependenttrans@*/ /* because it isn't really dependent */
+    free(ickmt_cur);
+    /*@=dependenttrans@*/
+    ickmt_cur = ickmt_prev;
+    nextthread(dummy, -1, 2); /* dummy is not used by nextthread */
+    ick_lose(IE778, -1, (const char *) NULL); /* nextthread does not return */
+  }
+}
+
+/* This function does not return in the normal fashion, but by a nonlocal
+   goto to a mysterious location, possibly in multicome1 but possibly in
+   the degenerated code. From the point of view of libickmt.a, we're going
+   to a piece of code that doesn't even exist yet! The general ugliness of
+   INTERCAL multithreading pulls out all the stops when it comes to using
+   unusual and confusing C control structures. It is important to remember
+   that longjmp clobbers all auto variables that have changed since the
+   corresponding setjmp. */
+
+/* In this Threaded INTERCAL implementation, data is saved in a linked ring
+   of threads. A thread runs for a command, then all the data pointers are
+   changed, saving the old ones. This is an improvement on an earlier attempt
+   of mine in which the values themselves were copied, but is still less
+   efficient than Malcom Ryan's method (which didn't involve copying things
+   about except when forking). However, I justify this by saying that it leaves
+   as much existing code as possible untouched, which is helpful for single-
+   thread compatibility, modifying the code in feh.c without an understanding
+   of multithread issues, and because I'm lazy. It also makes the rest of the
+   program shorter. */
+void nextthread(jmp_buf pc, int errlineno, int flags)
+{ /* flags | 1 means save this thread.
+     flags | 2 means load the ick_next thread.
+     flags | 4 means don't change thread. */
+  if(errlineno > -1 && ickmt_cur->ick_next == ickmt_cur && !choicing) longjmp(pc,1);
+  /* If we only have 1 thread, just continue with it. Otherwise: */
+
+  if(!(flags&1)) goto advancethread;
+  /* OK, so I've spaghettified this procedure slightly by using goto
+     instead of if. But I was so deep into figuring out setjmp/longjmp,
+     a goto seemed crystal-clear by comparison. */
+
+  /* save variables */
+  ickmt_cur->dsi->varforget[0] = ick_onespots;
+  ickmt_cur->dsi->varforget[1] = ick_twospots;
+  ickmt_cur->dsi->varforget[2] = ick_tails;
+  ickmt_cur->dsi->varforget[3] = ick_hybrids;
+  ickmt_cur->dsi->varforget[4] = ick_oneforget;
+  ickmt_cur->dsi->varforget[5] = ick_twoforget;
+  ickmt_cur->dsi->varforget[6] = ick_tailforget;
+  ickmt_cur->dsi->varforget[7] = ick_hyforget;
+  ickmt_cur->dsi->varforget[8] = ick_oo_onespots;
+  ickmt_cur->dsi->varforget[9] = ick_oo_twospots;
+
+  /* save NEXT stack */
+  /*@-unqualifiedtrans@*/ /* because nextstack isn't only really */
+  ickmt_cur->nextstack = ick_next;
+  /*@=unqualifiedtrans@*/
+  ickmt_cur->nextpointer = ick_nextindex;
+
+  /* save ick_stashbox */
+  /*@-unqualifiedtrans@*/ /* ditto */
+  ickmt_cur->dsi->sb = ick_first;
+  /*@=unqualifiedtrans@*/
+
+  /* save choicepoints */
+  if(!choicing) ickmt_cur->choicepoint = topchoice;
+
+  /* save comefrom information */
+  memcpy((void*)(ickmt_cur->ick_cjb), (const void*)ick_cjb, sizeof(jmp_buf));
+  ickmt_cur->ick_ccfc = ick_ccfc;
+  ickmt_cur->ick_skipto = ick_skipto;
+
+  /* save program counter */
+  memcpy((void*)(ickmt_cur->pc), (const void*)pc, sizeof(jmp_buf));
+  /* And another thing about setjmp/longjmp. A jmp_buf
+     acts like a structure that passes itself around by
+     reference. However, it cannot be assigned, although
+     just about everything else in C can be, although it
+     can be copied with memcpy (what I'm doing in the line
+     above - remember it passes itself around by reference).
+     Generally speaking, it's some sort of ick_array,
+     even though some implementors use a 1-element ick_array.
+     The exact representation of jmp_buf is one of the most
+     implementation-dependent things in C (I've seen both
+     a 1-element ick_array of structure and an int[12].) */
+
+ advancethread:
+  /* change thread */
+  if(!(flags&4))
+  {
+    ickmt_prev = ickmt_cur;
+    ickmt_cur = ickmt_cur->ick_next;
+  }
+
+  if(!(flags&2)) goto returnjmp;
+
+  /* load variables */
+  ick_onespots    = ickmt_cur->dsi->varforget[0];
+  ick_twospots    = ickmt_cur->dsi->varforget[1];
+  ick_tails       = ickmt_cur->dsi->varforget[2];
+  ick_hybrids     = ickmt_cur->dsi->varforget[3];
+  ick_oneforget   = ickmt_cur->dsi->varforget[4];
+  ick_twoforget   = ickmt_cur->dsi->varforget[5];
+  ick_tailforget  = ickmt_cur->dsi->varforget[6];
+  ick_hyforget    = ickmt_cur->dsi->varforget[7];
+  ick_oo_onespots = ickmt_cur->dsi->varforget[8];
+  ick_oo_twospots = ickmt_cur->dsi->varforget[9];
+
+  /* load NEXT stack */
+  /*@-onlytrans@*/ /* the nextstack shouldn't be only */
+  ick_next = ickmt_cur->nextstack;
+  /*@=onlytrans@*/
+  ick_nextindex = ickmt_cur->nextpointer;
+
+  /* load choicepoints */
+  if(!choicing) topchoice = ickmt_cur->choicepoint;
+
+  /* load ick_stashbox */
+  /*@-onlytrans@*/ /* ditto */
+  ick_first = ickmt_cur->dsi->sb;
+  /*@=onlytrans@*/
+
+  /* load comefrom information */
+  memcpy((void*)ick_cjb, (const void*)(ickmt_cur->ick_cjb), sizeof(jmp_buf));
+  ick_ccfc = ickmt_cur->ick_ccfc;
+  ick_skipto = ickmt_cur->ick_skipto;
+
+ returnjmp:
+  /* return to the new current thread's program counter */
+  if(choicing==2) choicing = 0;
+  longjmp(ickmt_cur->pc, 1);
+}