-- MesaRuntime>Processes.mesa  (August 26, 1982 11:22 am by Levin)
DIRECTORY
  CPSwapDefs USING [ProcessState],
  Environment USING [Long, PageCount, PageNumber, wordsPerPage],
  Frame USING [Alloc, Free, GetReturnFrame, MyLocalFrame],
  Inline USING [BITAND, BITNOT, BITOR, BITSHIFT, COPY],
  PrincOps USING [ControlLink, Frame,
        FrameHandle, FrameSizeIndex, NullFrame, NullLink, StateVector],
  Process USING [Milliseconds, Priority, Ticks],
  ProcessInternal USING [DisableInterrupts, EnableInterrupts],
  ProcessOperations USING [
    Broadcast, EnableAndRequeue, Enter, Exit, HandleToIndex, IndexToHandle,
    Notify, ReadPSB, ReEnter, Requeue, Wait, WritePSB, WriteWDC],
  ProcessPriorities USING [
    priorityClient, priorityClientHigh, priorityClientLow, priorityRealTime],
  ProcessorFace USING [millisecondsPerTick, reservedNakedNotifyMask],
  PSB USING [
    Condition, ConditionVariable, FaultIndex, InterruptItem, Monitor, NoTimeout,
    NullPsbHandle, NullStateVectorHandle, PDA, PDABase, Priority,
    ProcessDataArea, ProcessStateBlock, PsbHandle, PsbIndex,
    PsbNull, Queue, StartPsb, StateVectorHandle, Ticks, UnlockedEmpty],
  RuntimeInternal USING [FrameSize],
  RuntimePrograms USING [],
  SDDefs USING [SD, sFork, sJoin, sProcessTrap],
  StartList USING [Base],
  StoragePrograms USING [tableBase];

Processes: MONITOR
  LOCKS processLock
  IMPORTS Frame, Inline, ProcessInternal, ProcessorFace,
        ProcessOperations, RuntimeInternal, StoragePrograms
  EXPORTS Process, ProcessInternal, RuntimePrograms =

  BEGIN OPEN PSB, ProcessOperations;

  priorityBackground: PUBLIC Process.Priority ← ProcessPriorities.priorityClientLow;
  priorityForeground: PUBLIC Process.Priority ← ProcessPriorities.priorityClientHigh;
  priorityInterrupt: PUBLIC Process.Priority ← ProcessPriorities.priorityRealTime;
  priorityNormal: PUBLIC Process.Priority ← ProcessPriorities.priorityClient;

  ConditionVariable: TYPE = PSB.ConditionVariable;
  FrameSizeIndex: TYPE = PrincOps.FrameSizeIndex;

  --PrincOps.--FsiFrame: TYPE = MACHINE DEPENDENT RECORD [
    fsi (0): FrameSizeIndex,  -- must be at 3 MOD 4 boundary.
    frame (1): local PrincOps.Frame];

  assert1: [FIRST[Process.Priority]..FIRST[Process.Priority]] =
        FIRST[PSB.Priority];
  assert2: [LAST[Process.Priority]..LAST[Process.Priority]] =
        LAST[PSB.Priority];
  assert3: [FIRST[Process.Ticks]..FIRST[Process.Ticks]] = FIRST[PSB.Ticks];
-- assert4: [LAST[Process.Ticks]..LAST[Process.Ticks]] = LAST[PSB.Ticks];  ++ waiting on AR6834.

  assert5: [PSB.NoTimeout..PSB.NoTimeout] = 0;  -- (see next statement.)
  collisionTimeout: PSB.Ticks = PSB.NoTimeout+1;  -- value to use if client
                -- wants timeout numerically equal to NoTimeout.

  ProcessState: TYPE = CPSwapDefs.ProcessState;

  ProcState:  -- for access to ProcessState field.
    PROCEDURE [psbh: PsbHandle] RETURNS [ProcessState] =
      INLINE { RETURN[LOOPHOLE[pda[psbh].flags.available, ProcessState]] };

  NakedNotifyLevel: TYPE = [0..16);

  DyingFrameHandle: TYPE = POINTER TO dying PrincOps.Frame;

  InvalidProcess: PUBLIC ERROR [process: PsbIndex] = CODE;
  TooManyProcesses: PUBLIC ERROR = CODE;

  -- Queues of processes which are..
  rebirth: CONDITION;  -- ..unused.
  frameReady: CONDITION;  -- ..parents waiting to rejoin their child.
  frameTaken: CONDITION;  -- ..children waiting to rejoin their parent.
  dead: CONDITION;  -- ..parents waiting for their child to clean up.

  pda: PSB.PDABase = PSB.PDA;
  processLock: MONITORLOCK;
  busyLevels: WORD; -- bit-mask of busy naked-notify levels
                    --   (numbered right-to-left)

  deadFrame: DyingFrameHandle ← NIL;  -- the top-level frame of a
        -- detached process which needs to be freed.

  Bug: PRIVATE ERROR [type: BugType] = CODE;
  BugType: TYPE =
    {insufficientMappedPDA, noMoreNakedNotifyLevels, noSuchCondition};

  --~~~~~~~~~~~~ Initialization ~~~~~~~~~~~~

  --RuntimePrograms.--InitializeProcesses: PUBLIC PROCEDURE 
        [pagePDA: Environment.PageNumber, countPDA: Environment.PageCount] =
    -- (pagePDA is not used.)
    BEGIN
    AlignUp: PROC [unaligned, modulus: UNSPECIFIED] RETURNS [aligned: UNSPECIFIED] =
        INLINE { RETURN[ ((unaligned + modulus-1) / modulus) * modulus] };
    AlignDown: PROC [unaligned, modulus: UNSPECIFIED] RETURNS [aligned: UNSPECIFIED] =
        INLINE { RETURN[unaligned  - unaligned MOD modulus] };
    rAlloc: PDABase RELATIVE POINTER TO UNSPECIFIED;  -- allocation pointer.
    DisableTimeout[@dead];
    DisableTimeout[@frameReady];
    DisableTimeout[@frameTaken];
    DisableTimeout[@rebirth];
    SDDefs.SD[SDDefs.sProcessTrap] ← ProcessTrap;
    SDDefs.SD[SDDefs.sFork] ← Fork;
    SDDefs.SD[SDDefs.sJoin] ← Join;

    -- We will start at the end of the PDA and allocate downwards:
    rAlloc ← LOOPHOLE[countPDA*Environment.wordsPerPage];

    -- Allocate StateVector pool:
    BEGIN
    alignmentStateVector: CARDINAL = 4;  -- (a D0 requirement)
    sizeStateVector: CARDINAL
        = AlignUp[MAX[SIZE[PrincOps.StateVector],
                      StoragePrograms.tableBase.stateVectorSize],
                  alignmentStateVector];
    rState: PSB.StateVectorHandle;
    rState ← AlignDown[rAlloc, alignmentStateVector];
    FOR pri: PSB.Priority IN PSB.Priority DO
      pda.state[pri] ← PSB.NullStateVectorHandle;
      FOR k: CARDINAL IN
          [0..StoragePrograms.tableBase.stateVectorCounts[pri]) DO
        IF LOOPHOLE[rState, CARDINAL]<sizeStateVector THEN
          ERROR Bug[insufficientMappedPDA];
        rState ← rState - sizeStateVector;
        pda[rState].stk[0] ← pda.state[pri];  -- chain onto list..
        pda.state[pri] ← rState;
        ENDLOOP;
      ENDLOOP;
    rAlloc ← rState;
    END;

    BEGIN
    -- Find number of {PSB, timeout words} that will fit in space remaining:
    --   (considering TimeoutVector alignment and unused TimeoutVector
    --    positions.)
    sizePSBTimeout: CARDINAL  -- (does not include alignment filler)
        = SIZE[ProcessStateBlock] + --interruptVectorWord--SIZE[Ticks];
    alignmentTimeoutVector: CARDINAL = 16;  -- (PrincOps requirement)
    rStartPsb: PDABase RELATIVE POINTER TO UNSPECIFIED
        = LOOPHOLE[IndexToHandle[StartPsb]];
    totalPsbs: CARDINAL;
    pda.count ← 0;
    FOR nUseful: CARDINAL
        DECREASING IN [0..(rAlloc - rStartPsb)/sizePSBTimeout]
        --UNTIL nUseful Psbs will fit in available space-- DO
      totalPsbs ← StartPsb + nUseful;
      pda.timeout ← AlignDown[rAlloc-totalPsbs, alignmentTimeoutVector];
      IF   (LOOPHOLE[pda.timeout, CARDINAL]+StartPsb  -- (first used word
                                                -- of timeout vector)
              - LOOPHOLE[rStartPsb, CARDINAL])
           / SIZE[ProcessStateBlock]  >=  nUseful THEN
        {pda.count ← nUseful; EXIT};  -- that many Psbs fit in avail space.
      REPEAT
        FINISHED => ERROR Bug[insufficientMappedPDA];
      ENDLOOP;
    IF pda.count <= 1 THEN ERROR Bug[insufficientMappedPDA];
    END;

    -- Initialize TimeoutVector:
    --   (Note: TimeoutVector unused portions overlay Psbs)
    FOR psb: PsbIndex IN [StartPsb..StartPsb+pda.count) DO
      pda[pda.timeout][psb] ← NoTimeout;
      ENDLOOP;

    -- Initialize first PSB to describe self - detached:
    pda.block[StartPsb] ← ProcessStateBlock[
      link: [failed: FALSE, priority: FIRST[Priority], next: StartPsb,
             vector: NULL],
      flags: [available: LOOPHOLE[ProcessState[state: alive, detached: TRUE]],
              cleanup: PsbNull, waiting: FALSE, abort: FALSE],
      context: NULL,
      -- TEMP until microcode uses timeout vector:
   -- mds: Inline.HighHalf[LONG[LOOPHOLE[1, POINTER]]] ];
      mds: NoTimeout ];
    WritePSB[IndexToHandle[StartPsb]];  -- tell the processor.
    -- TEMP until microcode takes current Psb off readyList:
 -- pda.ready ← Queue[tail: PsbNull];
    pda.ready ← Queue[tail: StartPsb];

    -- Finish making self be detached:
    -- Note that someone must have previously set the return link of the topmost frame to PrincOps.NullFrame.
    FOR root: PrincOps.FrameHandle ← Frame.MyLocalFrame[],
        root.returnlink.frame DO
      IF root.returnlink.frame = PrincOps.NullFrame THEN
        { root.returnlink ←
              LOOPHOLE[End, procedure PrincOps.ControlLink];
          EXIT };
      ENDLOOP;

    -- Put rest of PSBs into free pool:  (chained off "rebirth" condition)
    -- The free list is set so that each successive new process created
    -- will have a lower PsbIndex, thus causing the most-recently-created
    -- processes to be listed first by CoPilot.
    BEGIN
    firstFree: PsbIndex ← StartPsb+1;
    lastFree: PsbIndex ← StartPsb+pda.count-1;
    LOOPHOLE[rebirth, ConditionVariable].condition.tail ← PsbNull;
    FOR psb: PsbIndex IN [firstFree..lastFree] DO
      pda.block[psb].link.next ← IF psb=firstFree THEN lastFree ELSE psb-1;
      -- Tell CoPilot that process is not in use:
      LOOPHOLE[pda.block[psb].flags.available, ProcessState].state ← dead;
      -- TEMP until microcode uses timeout vector:
      pda.block[psb].mds ← NoTimeout;
      REPEAT
        FINISHED =>
          LOOPHOLE[rebirth, ConditionVariable].condition.tail ← firstFree;
      ENDLOOP;
    END;

    -- Initialize FaultVector:
    FOR flt: FaultIndex IN FaultIndex DO
      pda.fault[flt] ←
        [queue: [tail: PsbNull],
         condition: [tail: PsbNull, abortable: FALSE, wakeup: FALSE] ];
      ENDLOOP;

    -- Initialize naked-notify allocator:
    busyLevels ← ProcessorFace.reservedNakedNotifyMask;

    ProcessOperations.WriteWDC[0];  -- start interrupts.
    END;

  --~~~~~~~~~~ EXTERNAL Procedures ~~~~~~~~~~

  DisableAborts: PUBLIC --EXTERNAL--
    PROCEDURE [pCondition: LONG POINTER TO CONDITION] =
      { LOOPHOLE[pCondition↑, ConditionVariable]
                .condition.abortable ← FALSE };

  DisableTimeout: PUBLIC --EXTERNAL--
    PROCEDURE [pCondition: LONG POINTER TO CONDITION] =
      { pCondition.timeout ← NoTimeout };

  EnableAborts: PUBLIC --EXTERNAL--
    PROCEDURE [pCondition: LONG POINTER TO CONDITION] =
      { LOOPHOLE[pCondition↑, ConditionVariable]
                .condition.abortable ← TRUE};

  GetCurrent: PUBLIC --EXTERNAL-- PROCEDURE RETURNS [psbHandle: PROCESS] =
    { RETURN[ LOOPHOLE[HandleToIndex[ReadPSB[]], PROCESS] ] };

  GetPriority: PUBLIC --EXTERNAL since atomic action-- SAFE PROCEDURE []
                RETURNS [priority: Process.Priority] = TRUSTED {
    RETURN[pda[ReadPSB[]].link.priority]};

  InitializeCondition: PUBLIC --EXTERNAL--
    PROCEDURE [condition: LONG POINTER TO CONDITION, ticks: Process.Ticks] =
    { LOOPHOLE[condition↑, ConditionVariable] ←
      [ condition: [tail: PsbNull, abortable: FALSE, wakeup: FALSE],
        timeout: IF ticks=NoTimeout THEN collisionTimeout ELSE ticks ] };

  InitializeMonitor: PUBLIC --EXTERNAL--
    PROCEDURE [pMonitor: LONG POINTER TO MONITORLOCK] =
      { LOOPHOLE[pMonitor↑, PSB.Monitor] ← UnlockedEmpty };

  MsecToTicks: PUBLIC --EXTERNAL-- SAFE PROCEDURE [ms: Process.Milliseconds]
                RETURNS [Process.Ticks] = TRUSTED
    { RETURN[
      ( IF ms >= LAST[Process.Milliseconds]-
                                 (ProcessorFace.millisecondsPerTick-1)
            THEN LAST[Process.Milliseconds]  -- (avoid overflow)
            ELSE ms + ProcessorFace.millisecondsPerTick-1 )
      / ProcessorFace.millisecondsPerTick ] };

  ProcessTrap: --EXTERNAL-- PROCEDURE RETURNS [BOOLEAN] =
    -- (called when an aborted process attempts to reenter its monitor.)
    BEGIN

    Enter: PROCEDURE [a: POINTER TO MONITORLOCK] RETURNS [BOOLEAN] =
      LOOPHOLE[ProcessOperations.Enter];

    LongEnter: PROCEDURE [a,b: --LONG POINTER TO MONITORLOCK--UNSPECIFIED]
        RETURNS [BOOLEAN] =
      LOOPHOLE[ProcessOperations.Enter];

    abortee: PrincOps.FrameHandle;
    state: RECORD [filler: UNSPECIFIED, v: PrincOps.StateVector];
    state.v ← STATE;
    -- Acquire abortee's monitor lock:
    UNTIL
        (IF state.v.stkptr = 4 THEN
             LongEnter[state.v.stk[0], state.v.stk[1]]
         ELSE Enter[LOOPHOLE[state.v.stk[0], POINTER TO MONITORLOCK]] )
      DO ENDLOOP;
    abortee ← Frame.GetReturnFrame[];
    abortee.pc ← [abortee.pc + 1];  -- step past ME(L) instruction.
    pda[ReadPSB[]].flags.abort ← FALSE;
    ERROR ABORTED;
    -- if ABORTED is made resumable, we should return [monitorEntered: TRUE]
    -- as the result of the MonitorEnter instruction that got us here.
    END;

  SetPriority: PUBLIC --EXTERNAL-- PROCEDURE [p: Process.Priority] =
    BEGIN
    h: PsbHandle;
    h ← ReadPSB[];
    pda[h].link.priority ← p;
    ProcessInternal.DisableInterrupts[];  -- (to cancel subsequent enable)
    -- move to appropriate spot in ready queue:
    EnableAndRequeue[@pda.ready, @pda.ready, h];
    END;

  SecondsToTicks: PUBLIC --EXTERNAL--
      SAFE PROCEDURE [sec: CARDINAL] RETURNS [Process.Ticks] = TRUSTED
    BEGIN
    ticks: Environment.Long = [lc[
      (LONG[sec]*LONG[1000] + ProcessorFace.millisecondsPerTick-1)
      / ProcessorFace.millisecondsPerTick]];
    RETURN[IF ticks.highbits ~= 0 THEN LAST[Ticks] ELSE ticks.lowbits]
    END;

  SetTimeout: PUBLIC --EXTERNAL-- PROCEDURE
        [condition: LONG POINTER TO CONDITION, ticks: Process.Ticks] =
    {condition.timeout ← IF ticks=NoTimeout THEN collisionTimeout ELSE ticks};

  TicksToMsec: PUBLIC --EXTERNAL-- SAFE PROCEDURE [ticks: Ticks]
        RETURNS [Process.Milliseconds] = TRUSTED
    {RETURN[
      IF ticks > LAST[Process.Milliseconds]/ProcessorFace.millisecondsPerTick
      THEN LAST[Process.Milliseconds]
      ELSE ticks*ProcessorFace.millisecondsPerTick ] };

  Yield: PUBLIC --EXTERNAL-- SAFE PROCEDURE = TRUSTED
    { ProcessInternal.DisableInterrupts[];  -- (to cancel subsequent enable)
      EnableAndRequeue[@pda.ready, @pda.ready, ReadPSB[]] };

  --~~~~~~~~~~~~ ENTRY Procedures ~~~~~~~~~~~~

  Abort: PUBLIC ENTRY PROCEDURE [process: PsbIndex] =
    BEGIN
    h: PsbHandle;
    IF (h ← ValidProcess[process]) = NullPsbHandle THEN
        RETURN WITH ERROR InvalidProcess[process];
    ProcessInternal.DisableInterrupts[];  -- (also stops
                                -- ProcessTimeoutCounter from ticking.)
    IF ProcState[h].state = alive THEN {
      pda[h].flags.abort ← TRUE;
      IF pda[h].flags.waiting THEN  -- Wake the victim up..--
      { pda[h].flags.waiting ← FALSE;
        pda[pda.timeout][HandleToIndex[h]] ← NoTimeout;
        -- TEMP until microcode uses timeout vector:
        pda[h].--timeout--mds ← NoTimeout;
        Requeue[NIL, @pda.ready, h] }};
    ProcessInternal.EnableInterrupts[];
    END;

  --ProcessInternal.--AllocateNakedCondition: PUBLIC ENTRY PROC []
        RETURNS [cv: LONG POINTER TO CONDITION, mask: WORD] =
    BEGIN
    level: NakedNotifyLevel;
    FOR level IN NakedNotifyLevel DO
      mask ← Inline.BITSHIFT[1, LAST[NakedNotifyLevel] - level];
      IF Inline.BITAND[mask, busyLevels] = 0 THEN
        BEGIN
        busyLevels ← Inline.BITOR[busyLevels, mask];
        cv ← LOOPHOLE[@pda.interrupt[level]];
        RETURN;
        END;
      ENDLOOP;
    ERROR Bug[noMoreNakedNotifyLevels];
    END;

  --ProcessInternal.--DeallocateNakedCondition:
        PUBLIC PROC [cv: LONG POINTER TO CONDITION] =
    BEGIN
    level: NakedNotifyLevel;
    FOR level IN NakedNotifyLevel DO
      mask: WORD ← Inline.BITSHIFT[1, LAST[NakedNotifyLevel] - level];
      IF cv = LOOPHOLE[@pda.interrupt[level], LONG POINTER TO CONDITION] THEN
        BEGIN
        busyLevels ← Inline.BITAND[busyLevels, Inline.BITNOT[mask]];
        pda.interrupt[level].condition.tail ← PsbNull;
        RETURN;
        END;
      ENDLOOP;
    ERROR Bug[noSuchCondition];
    END;

  Detach: PUBLIC ENTRY PROCEDURE [process: PROCESS] =
    BEGIN
    h: PsbHandle;
    IF (h ← ValidProcess[process]) = NullPsbHandle THEN
        RETURN WITH ERROR InvalidProcess[LOOPHOLE[process, UNSPECIFIED]];
    LOOPHOLE[pda[h].flags.available, ProcessState].detached ← TRUE;
    BROADCAST frameTaken;  -- wake child if waiting to JOIN.
    END;

  End: --"ENTRY"-- PROCEDURE =
    -- When the top context of a process "returns", it Xfers to
    --   this procedure with its results on the stack.
    BEGIN
    sv: RECORD [filler: UNSPECIFIED, results: PrincOps.StateVector];
    frame: DyingFrameHandle;
    h: PsbHandle;
    sv.results ← STATE;  -- save stack containing returned results.
    WHILE ~Enter[@processLock] DO NULL ENDLOOP;
    frame ← LOOPHOLE[Frame.MyLocalFrame[]];
    frame.state ← alive;
    h ← ReadPSB[];
    LOOPHOLE[pda[h].flags.available, ProcessState].state ← frameReady;
    pda[h].flags.abort ← FALSE; -- too late for Aborts: they no-op
    Broadcast[@frameReady];  -- wake any parent process waiting to Join.
    -- Wait till this process is Detached or Joined:
    UNTIL ProcState[h].state = frameTaken
        OR ProcState[h].detached DO
      Wait[@processLock, @frameTaken,
           LOOPHOLE[frameTaken, ConditionVariable].timeout];
      WHILE ~ReEnter[@processLock, @frameTaken] DO NULL ENDLOOP;
      ENDLOOP;
    -- Free any frame left over from a previous dead detached process:
    IF deadFrame ~= NIL THEN {Frame.Free[deadFrame]; deadFrame ← NIL};
    IF ProcState[h].detached THEN
        deadFrame ← frame;  -- If detached, leave our frame for freeing.
    frame.state ← dead;  -- tell Joiner that we're done.
    LOOPHOLE[pda[h].flags.available, ProcessState].state ← dead;
    Broadcast[@dead];  -- tell parent our frame has been left for freeing.
    Wait[@processLock, @rebirth,
         LOOPHOLE[rebirth, ConditionVariable].timeout];
    -- This process is dead.  Its PSB sits in the rebirth queue until
    -- it is recycled into a new process by Fork.
    -- Our current frame however, has one of two fates:
    -- (a) If this process was detached, the frame will simply be freed
    -- by the next process that finishes ("deadFrame").
    -- (b) if this process is being Joined, the parent process will
    -- have acquired a pointer to our frame.  The JOIN code will Xfer
    -- to our frame and the code below will be executed
    -- BY THE PARENT PROCESS.  The parent process therefore
    -- MUST BE RUNNING IN THE SAME MDS as the child process!
    WHILE ~ReEnter[@processLock, @rebirth] DO NULL ENDLOOP;
    sv.results.dest ← LOOPHOLE[frame.returnlink, PrincOps.ControlLink];  -- (frame.returnlink was set by Join[] to point to the context of JOIN.)
    sv.results.source ← PrincOps.NullLink;
    Exit[@processLock];
    -- Reload returned results into stack, return to parent:
    RETURN WITH sv.results;
    END;

  Fork: --"ENTRY"-- PROCEDURE [--argsForRoot,-- root: PrincOps.ControlLink]
        RETURNS [childPsb: PsbIndex] =
    BEGIN
    PForkFrame: TYPE = POINTER TO FRAME[Fork];

    ChildBuilder: PROC [--parent's locals--] RETURNS [--MUST BE NULL!--] =
      BEGIN
      pChild: LONG POINTER TO ProcessStateBlock ← @pda.block[childPsb];
      parentFrame: PForkFrame = LOOPHOLE[Frame.GetReturnFrame[]];
      fsi: FrameSizeIndex = LOOPHOLE[
          (parentFrame-1), POINTER TO FsiFrame].fsi;
      childFrame: PForkFrame = Frame.Alloc[fsi];
      Inline.COPY[from: parentFrame-1, to: childFrame-1,
          nwords: RuntimeInternal.FrameSize[fsi] + SIZE[FrameSizeIndex] ];
      childFrame.identity ← child;
      pChild.link.failed ← FALSE;
      pChild.link.priority ← pda[ReadPSB[]].link.priority;
      pChild.flags ←
        [available: LOOPHOLE[ProcessState[state: alive, detached: FALSE]],
        cleanup: PsbNull, waiting: FALSE, abort: FALSE];
      pChild.context ← [frame[LOOPHOLE[childFrame, PrincOps.FrameHandle]]];
      -- TEMP until microcode uses timeout vector:
   -- pChild.mds ← Inline.HighHalf[LONG[LOOPHOLE[1, POINTER]]];
      pChild.mds ← NoTimeout;
      pda[pda.timeout][childPsb] ← NoTimeout;
      Notify[@rebirth];  -- starts the new process executing.  Its PC
        -- is set to begin execution at the instruction after the call
        -- to ChildBuilder.  Its stack is empty.  Therefore,
        -- ChildBuilder MUST NOT RETURN ANY RESULTS!
      END;

    identity: {parent, child};
    argsForChild: PrincOps.StateVector;
    -- ("root" is automatically popped off the stack first.)
    argsForChild ← STATE;  -- must be first!
    identity ← parent;
    WHILE ~Enter[@processLock] DO NULL ENDLOOP;
    IF LOOPHOLE[rebirth, ConditionVariable].condition.tail = PsbNull THEN
      { Exit[@processLock]; ERROR TooManyProcesses };
    childPsb ← pda.block[LOOPHOLE[rebirth, ConditionVariable].condition.tail]
        .link.next;  -- walk to tail, then to head.
    [] ← ChildBuilder[--my local vars--];
    -- Both parent and child processes will execute the following code:
    SELECT identity FROM
      parent =>
        { Exit[@processLock];
          RETURN[childPsb] };  -- return child handle to FORKing parent.
      child =>
        BEGIN
        -- To simulate a procedure call, we must also store dest and
        -- source links *above* the stack since ReturnWithState doesn't.
        -- (stack pointer is *not* incremented.)
        argsForChild.stk[argsForChild.stkptr] ← root;
        argsForChild.dest ← root;
        -- Set child's top context to call End when it returns:
        argsForChild.stk[argsForChild.stkptr+1] ← End;
        argsForChild.source ← LOOPHOLE[End, PrincOps.ControlLink];
        RETURN WITH argsForChild;  -- "call" root procedure of child.
        END;
      ENDCASE;
    END;

  Join: ENTRY PROCEDURE [process: PsbIndex]
                RETURNS [loadResults: PrincOps.FrameHandle] =
    BEGIN
    h: PsbHandle;
    frame: DyingFrameHandle;
    self: PrincOps.FrameHandle = Frame.MyLocalFrame[];
    IF (h ← ValidProcess[process]) = NullPsbHandle THEN
        RETURN WITH ERROR InvalidProcess[process];
    -- Wait till process ready to be joined:
    WHILE ProcState[h].state ~= frameReady DO
        WAIT frameReady ENDLOOP;
    -- Guaranteed to be a dying frame by the time we get here.
    frame ← LOOPHOLE[pda[h].context.frame, DyingFrameHandle];
    LOOPHOLE[pda[h].flags.available, ProcessState].state ← frameTaken;
    BROADCAST frameTaken;  -- tell child process we've got his frame.
    -- Wait till he has finished cleaning up:
    WHILE frame.state ~= dead DO WAIT dead ENDLOOP;
    -- At this point, we (the parent process)  have acquired responsibility
    -- for the child's frame.  IT MUST BE IN THE SAME MDS AS THE PARENT. 
    frame.returnlink ← self.returnlink; -- We use the child frame's
        -- return link as a mailbox to pass to the child's frame the address
        -- of the JOINer's frame, which the child should return to.
    RETURN[frame];  -- JOINer will next Xfer to "frame", which will reload the
        -- results into the stack and return them to the JOINer.
    END;

  Pause: PUBLIC ENTRY SAFE PROC [ticks: Process.Ticks] = TRUSTED
    BEGIN  ENABLE  ABORTED => GO TO Aborted;
    c: CONDITION;
    SetTimeout[@c, ticks];
    EnableAborts[@c];
    WAIT c;
    EXITS  Aborted => RETURN WITH ERROR ABORTED;
    END;

  ValidateProcess: PUBLIC ENTRY PROCEDURE [p: PsbIndex] =
    { IF ValidProcess[p] = NullPsbHandle THEN
        RETURN WITH ERROR InvalidProcess[p] };

  --~~~~~~~~~~~~ INTERNAL Procedures ~~~~~~~~~~~~

  ValidProcess: INTERNAL PROCEDURE [p: --PsbIndex--UNSPECIFIED]
                         RETURNS [h: PsbHandle] = INLINE
    -- returns NullPsbHandle if invalid.
    { RETURN[ IF ~(LOOPHOLE[p, PsbIndex] IN [StartPsb..StartPsb+pda.count))
              OR ProcState[h ← IndexToHandle[LOOPHOLE[p, PsbIndex]]].state
                    IN [frameTaken..dead]
              THEN NullPsbHandle ELSE h ] };

  END.

LOG
(For earlier log entries see Pilot 4.0 archive version.)

April 29, 1980  5:53 PM   Forrest   Drop PSB's crossing pages on floor; move Initialize Timeout machinery to ProcessorHead.

May 3, 1980  10:46 AM   Forrest   Mesa 6.0 Conversion.

May 14, 1980  6:19 PM   McJones   Use OISProcessorFace.reservedNakedNotifyMask; start interrupts at end of initialization (as before).

June 23, 1980  5:34 PM   McJones   OISProcessorFace=>ProcessorFace.

August 5, 1980  5:54 PM   Sandman   New PSB format.

September 29, 1980  10:30 AM   Johnsson   New ProcessTrap.

January 21, 1981  9:45 AM   Knutsen   New PDA layout: state vector pool, timeout vector.  New Fork.  Fix bugs. InitializeProcesses[].  Use waiting bit in Abort.

January 27, 1981  8:17 AM   Knutsen   Use stateVectorCount from StartList.  Forking requires storing controlLinks above the stack.

February 4, 1981  1:25 PM   Knutsen   Export priorities to Process.  Enable ABORTED on Pause.  PrincOps fields changed names.

February 25, 1981  2:03 PM   Knutsen   Use stateVectorSize from StartList.  Pause[0] must not wait forever.

March 20, 1981  1:45 PM   Fay/Sandman/Knutsen   Fix AR 7493 by changing Abort procedure.

March 26, 1981  5:39 PM   Luniewski/McJones   Abort must clear waiting flag when doing requeue.

August 3, 1982 4:16 pm	Levin	Correct all occurrences of ~IN.

August 26, 1982 11:22 am	Levin	Make things SAFE.