REF PROCESS                                         John Gibson Jul 1995

        COPYRIGHT University of Sussex 1995. All Rights Reserved.

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<<                             >>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<<         PROCESSES           >>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<<                             >>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

A process  in Poplog  is a  data  structure that  records the  state  of
execution of a piece of  Poplog program (see also HELP * PROCESS).  This
file describes processes,  and procedures  to construct  and operate  on
them.

         CONTENTS - (Use <ENTER> g to access required sections)

  1   Introduction

  2   Predicates on Processes

  3   Constructing Processes

  4   Running, Suspending and Resuming

  5   Generic Data Structure Procedures on Processes

  6   Miscellaneous

  7   Example



---------------
1  Introduction
---------------

A process  in Poplog  is a  data  structure that  records the  state  of
execution of  a piece  of Poplog  program. The  information stored  in a
process record comprises the sequence of procedure calls (stack  frames)
that the  process is  currently inside  (including the  values of  local
variables of those procedures), and the state of the user stack.

A process is constructed initially in two ways:

    # from a procedure with consproc, in  which case, on running it  for
      the first time with runproc  (or resume), the procedure is  called
      in the normal way;

    # from  part   of  the   currently  active   procedure  calls   with
      consproc_to, in which case execution continues inside the  process
      after the call to consproc_to.

Thereafter, the process may  suspend itself at any  time, e.g. by  using
suspend. This causes the current state of execution (all procedure calls
upto runproc and the  user stack) to be  stored in the original  process
record -- the  process is then  'swapped out'. The  process may then  be
re-activated  with  runproc  and  will  continue  execution  immediately
following the suspend call, after the stored state of execution has been
reinstated.

    A process can also cause itself to be swapped out by calling  resume
to resume  another process  in its  place (see  below). There  are  also
versions of suspend and resume  (ksuspend and kresume) which 'kill'  the
current process. This means that the  process' state is swapped out  but
not stored,  the process  record  being marked  as 'dead'.  The  process
cannot then be run again. (A process is also killed if it does a  normal
procedure exit to runproc.)

    Note that a process always has its own user stack, which is separate
from the stack of any  other process or from  the normal stack when  not
running inside a  process. Thus all  arguments passed into  or out  of a
process have to  be explicitly  declared in calls  of runproc,  suspend,
etc. The exception  to this is  when a process  does a normal  procedure
exit to  runproc, or  exits  abnormally through  runproc with  chain  or
related procedures: in this case all  values on the process's stack  are
passed up as results (thus if you leave items on the stack in a  process
which you  do  not want  passed  back on  normal  or chained  exit,  use
clearstack to clear the stack first).

    Processes can be used hierarchically, i.e. one process can be run as
a sub-process of another. While procedures like suspend which  suspend a
process normally apply only to the current process, they can all take an
optional  process  argument  specifying   any  running  process  to   be
(k)suspended. This means that all processes up to and including the  one
specified are either killed (ksuspend and kresume), or suspended in such
a way that on  running or resuming the  outer process the whole  process
chain is  reactivated,  and  control  returns  from  the  original  call
(suspend and resume). A process chain  like this is also constructed  by
consproc_to if the calling sequence to the target procedure includes one
or  more  processes;  when  the  constructed  process  is  run  it  will
reactivate the whole chain.

    Sub-processes that get  suspended when suspending  an outer  process
are said to  be 'subsumed'. Subsumed  processes cannot be  run again  in
their own  right,  but only  by  reactivating the  outer  process  which
subsumed them.




--------------------------
2  Predicates on Processes
--------------------------

isprocess(item) -> bool                                      [procedure]
        Returns true if item is a process, false if not.


isliveprocess(item) -> result                                [procedure]
        Returns a  true result  if item  is a  live process,  and  false
        otherwise (i.e. if not a process,  or a dead one). In the  first
        case, the return is item itself  if item is a currently  running
        process, or true if the process is suspended. Thus

                    isliveprocess(proc) == proc

        can be used to test whether a process proc is running.




-------------------------
3  Constructing Processes
-------------------------

A process is either volatile  or non-volatile. With a volatile  process,
the saved state held in the process  record is lost when the process  is
run, so that  the record  is effectively empty  until such  time as  the
process suspends again. With a non-volatile process, the saved state (as
it was at the  time of running)  is retained until  the process is  next
suspended.

    Not retaining the  saved state  means that  data structures  which a
running process was using at the time of its being run, but which it  no
longer requires, will be garbage collected -- thus a volatile process is
more garbage-collection  efficient.  On the  other  hand, if  a  running
process is abnormally exited due to  an error condition, the system  has
no choice but to  kill that process unless  it retains a sensible  saved
state;  thus  a  volatile  process  will  always  be  killed  in   these
circumstances, but a non-volatile one will survive (providing the  error
does not actually occur  in the middle of  the process being  suspended,
since starting to suspend corrupts the old saved state).

    Therefore, use a non-volatile process only if you require to  retain
the old state while the process is running.


consproc(item1, ..., itemN, N, p)           -> proc          [procedure]
consproc(item1, ..., itemN, N, p, volatile) -> proc
        The result of  this procedure  is a process  constructed on  the
        procedure p, with its initial user stack set to contain N  items
        passed from the  current stack.  When the process  is first  run
        (with runproc,  resume  or kresume),  the  procedure p  will  be
        called with  those items  on the  stack, i.e.  the process  will
        commence with:

                    p(item1, ..., itemN)

        The new process will die when the given procedure returns.

        volatile is an optional boolean argument specifying whether  the
        process is volatile  or not  (see above). If  not specified  the
        default is true, i.e. volatile.


consproc_to(item1, ..., itemN, N, target_p) -> proc          [procedure]
consproc_to(item1, ..., itemN, N, target_p, volatile) -> proc
        Makes the current  calling sequence upto  and including the  the
        most recent  call  of the  procedure  target_p into  a  process,
        returning the process record proc for the new (running) process.
        This  procedure  effectively   'inserts'  a   call  of   runproc
        immediately above the call of target_p, does a suspend, and then
        immediately runs the process again with proc passed as argument.

        The  implicit  suspend  automatically  subsumes  any   processes
        running below the  call of target_p;  these will be  reactivated
        when proc is run again.

        The user stack  of proc is  then set to  contain N items  passed
        from the current stack (i.e. the stack as it is AFTER suspending
        intervening  processes).   To   allow   computation   (in   that
        environment) of the number of items to be passed, the argument N
        may also be a  procedure which returns the  number, i.e. N()  is
        evaluated after suspending intervening processes.

        The argument volatile (and its default) are as for consproc.


saveproc() -> proc                                           [procedure]
        Makes a copy of the state of the current process; the result  is
        a process record proc which when run will exit from the call  of
        saveproc with false  as result  instead of  the process  record.
        Thus saveproc should be used in something like

            if saveproc() ->> proc then
                ;;; copy returned, running in original
            else
                ;;; running in copy
            endif

        Saving the  state involves  suspending the  current process  and
        then copying it using copy (see Generic Datastructure Procedures
        on Processes below).  The original  is then run  again with  the
        copy passed  as argument.  To  allow the  copy  to be  run  with
        arguments, saveproc is defined roughly as follows:

           define saveproc();
               lconstant mark = 'mark';
               suspend_chain(pop_current_process, 1,
                         procedure(proc);
                             lvars proc;
                             chain(copy(proc), mark, 2, proc, runproc)
                         endprocedure);
               if stacklength() /== 0 and dup() == mark then
                   ;;; running original
                   ;;; erase mark leaving copy on stack as result
                   ->
               else
                   ;;; running copy
                   ;;; return false above any other arguments
                   false
               endif
           enddefine;




-----------------------------------
4  Running, Suspending and Resuming
-----------------------------------

pop_current_process -> proc_or_false                          [variable]
proc_or_false -> pop_current_process
        Always contains the  current process, or  false if no  processes
        are running. You can thus use

                pop_current_process == proc

        to test if proc is the current process.


runproc(item1, ..., itemN, N, proc)                          [procedure]
        Runs the process proc as a subprocess of the current process (or
        from outside any process), passing N items from the current user
        stack to the process  user stack. (If  runproc is used  inside a
        process, the calling process is not 'swapped out'. All calls  of
        the outer process  remain in  the calling chain,  BUT the  outer
        process user stack  is saved (somewhere)  so the called  process
        still runs with its own stack.)

        Since runproc is the class_apply of processes, this can also  be
        called as

              proc(item1, ..., itemN, N)


suspend(item1, ..., itemN, N)                                [procedure]
suspend(item1, ..., itemN, N, sus_proc)
        Suspends the current process (first form), or all processes upto
        and including the  process sus_proc (second  form), and  returns
        from the  call of  runproc which  ran the  suspended  process. N
        items are passed back as results from the current user stack.

        When the suspended process is run again (with runproc, resume or
        kresume), the call of suspend will return with whatever items on
        the stack were passed to it by the initiating procedure.


suspend_chain(item1, ..., itemN, N, chain_p)                 [procedure]
suspend_chain(item1, ..., itemN, N, sus_proc, chain_p)
        Same as suspend, but chains  the procedure chain_p out the  call
        of runproc which ran the process, instead of just returning from
        it. (suspend is  thus equivalent to  suspend_chain with  identfn
        for the chain_p argument.)


resume(item1, ..., itemN, N, res_proc)                       [procedure]
resume(item1, ..., itemN, N, sus_proc, res_proc)
        Suspends the current process (first form), or all processes upto
        and including the process sus_proc (second form), and then  runs
        the process  res_proc  inside  the call  of  runproc  which  was
        running the suspended  process (thus  the newly-resumed  process
        replaces the suspended one). N items are passed from the current
        user stack to the resumed process user stack.

        When the suspended process is run again (with runproc, resume or
        kresume), the call of resume will return with whatever items  on
        the stack were passed to it by the initiating procedure.


ksuspend(item1, ..., itemN, N)                               [procedure]
ksuspend(item1, ..., itemN, N, kill_proc)
        Kills the current  process (first form),  or all processes  upto
        and including the process  kill_proc (second form), and  returns
        from the call  of runproc  which ran  the process.  N items  are
        passed back as results from the current user stack.


ksuspend_chain(item1, ..., itemN, N, chain_p)                [procedure]
ksuspend_chain(item1, ..., itemN, N, kill_proc, chain_p)
        Same as ksuspend, but chains the procedure chain_p out the  call
        of runproc which ran the process, instead of just returning from
        it. (ksuspend is thus equivalent to ksuspend_chain with  identfn
        for the chain_p argument.)


kresume(item1, ..., itemN, N, res_proc)                      [procedure]
kresume(item1, ..., itemN, N, kill_proc, res_proc)
        Kills the current  process (first form),  or all processes  upto
        and including kill_proc (second form), and then runs the process
        res_proc inside the call of runproc which was running the killed
        process (thus  the  newly-resumed process  replaces  the  killed
        one). N items  are passed  from the  current user  stack to  the
        resumed process user stack.




-------------------------------------------------
5  Generic Data Structure Procedures on Processes
-------------------------------------------------

copy can be used to copy a process, but only if it has a runnable  saved
state. This  means that  a  volatile process  can  be copied  only  when
suspended; a non-volatile process can  be copied either while  suspended
or running (in the  latter case the  copy reflects the  state as it  was
when the process was run, i.e. the last time it was suspended).

After copying,  the  copy and  the  original process  become  completely
independent of each other.

(At least, this should be the  case. Currently, the system is  deficient
in that type-3 local lexical  variables of procedures forming a  process
are not copied,  and may  therefore give rise  to unwanted  interactions
between  the  copy  and  the  original  in  programs  using  these  (see
REF * VMCODE for a description of type-3 lvars); this bug will be  fixed
in a future release of the system. Another current bug is that copying a
suspended process which  has subsumed  sub-processes does  not copy  the
subsumed processes, which it should.)




----------------
6  Miscellaneous
----------------

process_key -> key                                            [constant]
        This constant holds the key  structure for process records  (see
        REF * KEYS).




----------
7  Example
----------

The following example creates a process  proc which each time it is  run
returns the next integer, starting from an initial value n:

        define next(n);
            lvars n;
            repeat
                suspend(n, 1);
                n+1 -> n
            endrepeat
        enddefine;

        vars proc = consproc(23, 1, next);

        runproc(0, proc)=>
        ** 23
        runproc(0, proc)=>
        ** 24



--- C.all/ref/process
--- Copyright University of Sussex 1995. All rights reserved.
