(root)/
gcc-13.2.0/
gcc/
m2/
gm2-libs-iso/
COROUTINES.mod
(* COROUTINES.mod implement the ISO COROUTINES specification.

Copyright (C) 2002-2023 Free Software Foundation, Inc.
Contributed by Gaius Mulley <gaius.mulley@southwales.ac.uk>.

This file is part of GNU Modula-2.

GNU Modula-2 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 3, or (at your option)
any later version.

GNU Modula-2 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.

Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.

You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
<http://www.gnu.org/licenses/>.  *)

IMPLEMENTATION MODULE COROUTINES ;

FROM RTco IMPORT init, initThread, transfer, initSemaphore,
                 wait, signal, currentThread, turnInterrupts,
                 currentInterruptLevel ;

FROM RTExceptions IMPORT EHBlock, InitExceptionBlock,
                         SetExceptionBlock, GetExceptionBlock,
                         SetExceptionState, IsInExceptionState,
                         SetExceptionSource, GetExceptionSource ;

FROM SYSTEM IMPORT ADDRESS, ADR ;
FROM EXCEPTIONS IMPORT ExceptionSource ;
FROM RTint IMPORT Listen, AttachVector, IncludeVector, ExcludeVector ;
FROM Storage IMPORT ALLOCATE ;
FROM Assertion IMPORT Assert ;
FROM M2RTS IMPORT Halt ;
FROM libc IMPORT printf ;
FROM Processes IMPORT displayProcesses ;

IMPORT RTint ;


CONST
   MinStack  = 16 * 1024 * 1024 ;
   Debugging = FALSE ;

TYPE
   Status = (suspended, ready, new, running) ;

   COROUTINE = POINTER TO RECORD
                             context   : INTEGER ;
                             ehblock   : EHBlock ;
                             inexcept  : BOOLEAN ;
                             source    : ExceptionSource ;
                             wspace    : SYSTEM.ADDRESS ;
                             nLocs     : CARDINAL ;
                             status    : Status ;
                             attached  : SourceList ;
                             next      : COROUTINE ;
                          END ;

   SourceList = POINTER TO RECORD
                              next     : SourceList ;    (* next in the list of vectors which are      *)
                                                         (* attached to this coroutine.                *)
                              vec      : INTERRUPTSOURCE ;  (* the interrupt vector (source)           *)
                              curco    : COROUTINE ;     (* the coroutine which is waiting on this vec *)
                              chain    : SourceList ;    (* the next coroutine waiting on this vec     *)
                              ptrToTo,
                              ptrToFrom: POINTER TO COROUTINE ;
                           END ;


VAR
   freeList         : SourceList ;
   head             : COROUTINE ;
   previous,
   currentCoRoutine : COROUTINE ;
   illegalFinish    : ADDRESS ;
   initMain,
   initCo           : BOOLEAN ;
   lock             : INTEGER ;    (* semaphore protecting module data structures.  *)


PROCEDURE NEWCOROUTINE (procBody: PROC;
                        workspace: SYSTEM.ADDRESS;
                        size: CARDINAL;
                        VAR cr: COROUTINE;
                        [initProtection: PROTECTION]);

  (* Creates a new coroutine whose body is given by procBody, and
     returns the identity of the coroutine in cr. workspace is a
     pointer to the work space allocated to the coroutine; size
     specifies the size of this workspace in terms of SYSTEM.LOC.

     The optarg, initProtection, may contain a single parameter
     which specifies the initial protection level of the coroutine.
  *)
VAR
   tp : INTEGER ;
   old: PROTECTION ;
BEGIN
   localInit ;
   old := TurnInterrupts (MAX (PROTECTION)) ;
   IF initProtection = UnassignedPriority
   THEN
      initProtection := PROT ()
   END ;
   tp := initThread (procBody, size, initProtection) ;
   IF tp = -1
   THEN
      Halt ('unable to create a new thread', __FILE__, __FUNCTION__, __LINE__)
   END ;
   NEW (cr) ;
   WITH cr^ DO
      context    := tp ;
      ehblock    := InitExceptionBlock () ;
      inexcept   := FALSE ;
      source     := NIL ;
      wspace     := workspace ;
      nLocs      := size ;
      status     := new ;
      attached   := NIL ;
      next       := head
   END ;
   head := cr ;
   old := TurnInterrupts (old)
END NEWCOROUTINE ;


PROCEDURE TRANSFER (VAR from: COROUTINE; to: COROUTINE);
  (* Returns the identity of the calling coroutine in from, and
     transfers control to the coroutine specified by to.
  *)
VAR
   old: PROTECTION ;
BEGIN
   localInit ;
   old := TurnInterrupts (MAX (PROTECTION)) ;
   (* wait (lock) ; *)
   Transfer (from, to) ;
   (* signal (lock) ; *)
   old := TurnInterrupts (old)
END TRANSFER ;


(*
   Transfer -
*)

PROCEDURE Transfer (VAR from: COROUTINE; to: COROUTINE) ;
BEGIN
   IF Debugging
   THEN
      printf ("TRANSFER\n");
      printf ("current coroutine is: %d\n", currentCoRoutine^.context);
      IF previous # NIL
      THEN
         printf ("previous coroutine is: %d\n", previous^.context)
      END ;
      printf ("wishes to context switch to: %d\n", to^.context);
   END ;
   previous := currentCoRoutine ;
   from := currentCoRoutine ;
   IF to^.context = from^.context
   THEN
      Halt ('error when attempting to context switch to the same process',
            __FILE__, __FUNCTION__, __LINE__)
   END ;
   from^.inexcept := SetExceptionState (to^.inexcept) ;
   from^.source := GetExceptionSource () ;
   currentCoRoutine := to ;
   SetExceptionBlock (currentCoRoutine^.ehblock) ;
   SetExceptionSource (currentCoRoutine^.source) ;
   transfer (from^.context, to^.context)
END Transfer ;


(*
   localMain - creates the holder for the main process.
*)

PROCEDURE localMain ;
VAR
   old: PROTECTION ;
BEGIN
   IF NOT initMain
   THEN
      initMain := TRUE ;
      lock := initSemaphore (1) ;
      wait (lock) ;
      NEW (currentCoRoutine) ;
      WITH currentCoRoutine^ DO
         context    := currentThread () ;
         ehblock    := GetExceptionBlock () ;
         inexcept   := IsInExceptionState () ;
         source     := GetExceptionSource () ;
         wspace     := NIL ;
         nLocs      := 0 ;
         status     := running ;
         attached   := NIL ;
         next       := head
      END ;
      head := currentCoRoutine ;
      old := turnInterrupts (MAX (PROTECTION)) ;    (* was UnassignedPriority *)
      signal (lock)
   END
END localMain ;


(*
   localInit - checks to see whether we need to initialize our interface to pthreads.
*)

PROCEDURE localInit ;
BEGIN
   IF NOT initCo
   THEN
      Init ;
      IF init () # 0
      THEN
         Halt ('failed to initialize RTco',
               __FILE__, __FUNCTION__, __LINE__)
      END ;
      RTint.Init ;
      initCo := TRUE
   END ;
   localMain
END localInit ;


PROCEDURE IOTRANSFER (VAR from: COROUTINE; to: COROUTINE);
  (* Returns the identity of the calling coroutine in from and
     transfers control to the coroutine specified by to.  On
     occurrence of an interrupt, associated with the caller, control
     is transferred back to the caller, and the identity of the
     interrupted coroutine is returned in from.  The calling coroutine
     must be associated with a source of interrupts.
  *)
VAR
   prev,
   l   : SourceList ;
   old : PROTECTION ;
BEGIN
   localInit ;
   old := TurnInterrupts (MAX (PROTECTION)) ;
   IF from = to
   THEN
      Halt ("error IOTRANSFER cannot transfer control to the running COROUTINE",
            __FILE__, __FUNCTION__, __LINE__)
   END ;
   wait (lock) ;
   l := currentCoRoutine^.attached ;
   IF l=NIL
   THEN
      printf ("no source of interrupts associated with coroutine\n")
   END ;
   WHILE l # NIL DO
      WITH l^ DO
         ptrToFrom := ADR (from) ;
         ptrToTo   := ADR (to) ;
         curco := currentCoRoutine ;
         Assert (currentCoRoutine # NIL) ;
         prev := AttachVector (vec, l) ;
         Assert (from # to) ;
         IF (prev # NIL) AND (prev # l)
         THEN
            printf ("not expecting multiple COROUTINES to be waiting on a single interrupt source\n")
         END ;
         IncludeVector (vec)
      END ;
      l := l^.next
   END ;
   signal (lock) ;
   Transfer (from, to) ;
   from := previous ;
   old := TurnInterrupts (old)
END IOTRANSFER ;


(*
   New - assigns, l, to a new SourceList.
*)

PROCEDURE New (VAR l: SourceList) ;
BEGIN
   IF freeList=NIL
   THEN
      NEW (l)
   ELSE
      l := freeList ;
      freeList := freeList^.next
   END
END New ;


(*
   Dispose - returns, l, to the freeList.
*)

PROCEDURE Dispose (l: SourceList) ;
BEGIN
   l^.next := freeList ;
   freeList := l
END Dispose ;


PROCEDURE ATTACH (source: INTERRUPTSOURCE);
  (* Associates the specified source of interrupts with the calling
     coroutine. *)
VAR
   l: SourceList ;
BEGIN
   localInit ;
   wait (lock) ;
   l := currentCoRoutine^.attached ;
   WHILE l#NIL DO
      IF l^.vec = source
      THEN
         l^.curco := currentCoRoutine ;
         signal (lock) ;
         RETURN
      ELSE
         l := l^.next
      END
   END ;
   New (l) ;
   WITH l^ DO
      next := currentCoRoutine^.attached ;
      vec := source ;
      curco := currentCoRoutine ;
      chain := NIL ;
   END ;
   currentCoRoutine^.attached := l ;
   IF AttachVector (source, l) # NIL
   THEN
      printf ("ATTACH implementation restriction only one coroutine may be attached to a specific interrupt source\n")
   END ;
   signal (lock)
END ATTACH ;


PROCEDURE DETACH (source: INTERRUPTSOURCE);
  (* Dissociates the specified source of interrupts from the calling
     coroutine. *)
VAR
   l, prev: SourceList ;
BEGIN
   localInit ;
   wait (lock) ;
   l := currentCoRoutine^.attached ;
   prev := NIL ;
   WHILE l # NIL DO
      IF l^.vec = source
      THEN
         IF prev = NIL
         THEN
            Assert (l = currentCoRoutine^.attached) ;
            currentCoRoutine^.attached := currentCoRoutine^.attached^.next ;
         ELSE
            prev^.next := l^.next
         END ;
         Dispose (l) ;
         signal (lock) ;
         RETURN
      ELSE
         prev := l ;
         l := l^.next
      END
   END ;
   signal (lock)
END DETACH ;


(*
   getAttached - returns the first COROUTINE associated with, source.
                 It returns NIL is no COROUTINE is associated with, source.
*)

PROCEDURE getAttached (source: INTERRUPTSOURCE) : COROUTINE ;
VAR
   l: SourceList ;
   c: COROUTINE ;
BEGIN
   localInit ;
   c := head ;
   WHILE c # NIL DO
      l := c^.attached ;
      WHILE l#NIL DO
         IF l^.vec = source
         THEN
            RETURN c
         ELSE
            l := l^.next
         END
      END ;
      c := c^.next
   END ;
   RETURN NIL
END getAttached ;


PROCEDURE IsATTACHED (source: INTERRUPTSOURCE): BOOLEAN;
  (* Returns TRUE if and only if the specified source of interrupts is
     currently associated with a coroutine; otherwise returns FALSE.
  *)
VAR
   result: BOOLEAN ;
BEGIN
   localInit ;
   wait (lock) ;
   result := getAttached (source) # NIL ;
   signal (lock) ;
   RETURN result
END IsATTACHED ;


PROCEDURE HANDLER (source: INTERRUPTSOURCE) : COROUTINE;
  (* Returns the coroutine, if any, that is associated with the source
     of interrupts. The result is undefined if IsATTACHED(source) =
     FALSE.
  *)
VAR
   co: COROUTINE ;
BEGIN
   localInit ;
   wait (lock) ;
   co := getAttached (source) ;
   signal (lock) ;
   RETURN co
END HANDLER ;


PROCEDURE CURRENT () : COROUTINE ;
  (* Returns the identity of the calling coroutine. *)
BEGIN
   localInit ;
   RETURN currentCoRoutine
END CURRENT ;


PROCEDURE LISTEN (p: PROTECTION) ;
  (* Momentarily changes the protection of the calling coroutine to p. *)
BEGIN
   localInit ;
   Listen (FALSE, IOTransferHandler, p)
END LISTEN ;


(*
   ListenLoop - should be called instead of users writing:

                LOOP
                   LISTEN
                END

                It performs the same function but yields
                control back to the underlying operating system.
                It also checks for deadlock.
                This function returns when an interrupt occurs.
                (File descriptor becomes ready or time event expires).
*)

PROCEDURE ListenLoop ;
BEGIN
   localInit ;
   Listen (TRUE, IOTransferHandler, MIN (PROTECTION))
END ListenLoop ;


(*
   removeAttached - removes all sources of interrupt from COROUTINE, c.
*)

PROCEDURE removeAttached (c: COROUTINE) ;
VAR
   l: SourceList ;
BEGIN
   localInit ;
   l := c^.attached ;
   WHILE l#NIL DO
      ExcludeVector (l^.vec) ;
      l := l^.next
   END
END removeAttached ;


(*
   IOTransferHandler - handles interrupts related to a pending IOTRANSFER.
*)

PROCEDURE IOTransferHandler (InterruptNo: CARDINAL;
                             Priority: CARDINAL ;
                             l: SourceList) ;
VAR
   ourself: SourceList ;
BEGIN
   IF Debugging
   THEN
      printf ("IOTransferHandler called\n") ;
      displayProcesses ("IOTransferHandler") ;
      printf ("IOTransferHandler vec %d coroutine: %d\n", l^.vec, l^.curco^.context);
      printf ("localInit\n");
   END ;
   localInit ;
   IF l = NIL
   THEN
      Halt ('no coroutine attached to this interrupt vector which was initiated by IOTRANSFER',
            __FILE__, __FUNCTION__, __LINE__)
   ELSE
      IF Debugging
      THEN
         printf ("IOTransferHandler called\n");
         printf ("before wait (lock)\n");
      END ;
      wait (lock) ;
      IF Debugging
      THEN
         printf ("IOTransferHandler vec %d coroutine 0x%x\n", l^.vec, l^.curco);
         printf ("current coroutine is: %d\n", currentCoRoutine^.context);
         IF previous # NIL
         THEN
            printf ("previous coroutine is: %d\n", previous^.context)
         END ;
         printf ("handler wants to context switch to:  %d\n", l^.curco^.context);
         displayProcesses ("IOTransferHandler")
      END ;
      WITH l^ DO
         (*
         ourself := AttachVector (InterruptNo, chain) ;
         IF ourself # l
         THEN
            Halt ('inconsistancy of return result',
                  __FILE__, __FUNCTION__, __LINE__)
         END ;
         IF chain = NIL
         THEN
            removeAttached (curco)
         ELSE
            printf ('odd vector has been chained\n')
         END ;
         *)
         removeAttached (curco) ;   (* remove all sources of interrupt for l^.curco.  *)
         ptrToFrom^ := currentCoRoutine ;
         previous := currentCoRoutine ;
         previous^.inexcept := SetExceptionState (curco^.inexcept) ;
         previous^.source := GetExceptionSource () ;
         currentCoRoutine := curco ;
         SetExceptionBlock (currentCoRoutine^.ehblock) ;
         SetExceptionSource (currentCoRoutine^.source) ;
         signal (lock) ;
         transfer (previous^.context, currentCoRoutine^.context)
      END
   END
END IOTransferHandler ;


PROCEDURE PROT () : PROTECTION;
  (* Returns the protection of the calling coroutine. *)
BEGIN
   localInit ;
   RETURN currentInterruptLevel ()
END PROT ;


(*
   TurnInterrupts - switches processor interrupts to the protection
                    level, to.  It returns the old value.
*)

PROCEDURE TurnInterrupts (to: PROTECTION) : PROTECTION ;
VAR
   old: PROTECTION ;
BEGIN
   localInit ;
   old := turnInterrupts (to) ;
   Listen (FALSE, IOTransferHandler, to) ;
   RETURN old
END TurnInterrupts ;


(*
   Init - initialize the global data structures.
*)

PROCEDURE Init ;
BEGIN
   freeList := NIL ;
   initMain := FALSE ;
   currentCoRoutine := NIL
END Init ;


END COROUTINES.