-- Copyright (C) 1986  by Xerox Corporation. All rights reserved. 
-- CRuntimeA.mesa
-- NFS		 5-Mar-86 17:37:14
-- MEW		14-Apr-86 13:25:52

DIRECTORY
  BcdOps USING [BcdBase],
  AtomVariants USING [ATRecord],
  BucketAlloc USING [BucketInfo, Initialize],
  CAbort USING [],
  CBasics USING [ArraySpaceHandle, CommonRecPtr],
  CRuntime USING [abortOutcome, FilePtr],
  CRuntimeInternal USING [
    ArraySpace, ConfigEntry, ConfigHandle, DeleteAndLogHeap, DeleteAndLogStream,
    GFEntry, GFIndex, GFList, GFTable, gfTableSize, lock, LogCleanUp, ProcessEntry,
    ProcessIndex, ProcessList, ProcessTable, processTableSize, SHEntry, SHIndex,
    SHList, SHTable, shTableSize, StreamEntry, StreamList],
  Environment USING [PageCount, Word, wordsPerPage],
  File USING [Create, Delete, Error, File, GetSize],
  FileTypes USING [tUntypedFile],
  Frame USING [GetReturnFrame, ReadGlobalLink],
  Heap USING [Create, Delete, Flush, MakeNode],
  Inline USING [LongCOPY, LowHalf],
  LoadState USING [
    GetModuleInfo, LockBcdInfo, LPBcdInfoTable, 
    ModuleInfoSequenceHandle, ModuleInfosOfBcd,
    ModuleInfoRange, UnlockBcdInfo],
  LoadStateFormat USING [ModuleInfo],
  PrincOps USING [GlobalFrameHandle],
  Process USING [GetCurrent],
  Space USING [Kill, MakeReadOnly, Map, SwapUnitSize, Unmap],
  SpecialCRuntime USING [],
  Stream USING [Handle],
  Table USING [Base],
  Volume USING [InsufficientSpace, systemID];

CRuntimeA: MONITOR LOCKS CRuntimeInternal.lock
  IMPORTS
    BucketAlloc, CRuntimeInternal, File, Heap, Inline, Frame, LoadState, Process,
    Space, Volume
  EXPORTS CAbort, CBasics, CRuntime, CRuntimeInternal, SpecialCRuntime =
  {
  OPEN CRuntime, CRuntimeInternal;

  GlobalFrameHandle: TYPE = PrincOps.GlobalFrameHandle;

  ConfigEntry: PUBLIC TYPE = CRuntimeInternal.ConfigEntry;
  ConfigHandle: TYPE = LONG POINTER TO ConfigEntry;

  CEList: PUBLIC ConfigHandle ← NIL;

  z: PUBLIC UNCOUNTED ZONE;

  arrayZone: PUBLIC UNCOUNTED ZONE;

  lock: PUBLIC MONITORLOCK;

  gfTable: PUBLIC LONG POINTER TO GFTable;

  shTable: PUBLIC LONG POINTER TO SHTable;

  processTable: PUBLIC LONG POINTER TO ProcessTable;

  ArraySpace: PUBLIC TYPE = CRuntimeInternal.ArraySpace;

  GFHash: PROCEDURE [gf: GlobalFrameHandle] RETURNS [GFIndex] = INLINE {
    RETURN[(LOOPHOLE[gf, CARDINAL] / 4) MOD gfTableSize]; };

  ModuleNotRegistered: SIGNAL [gfh: GlobalFrameHandle] = CODE;

  ProcessNotRegistered: PUBLIC ERROR = CODE;

  TooMuchGlobalArraySpace: PUBLIC ERROR = CODE;

  arraySpaceThreshold: PUBLIC Environment.PageCount ← 5;

  heapSize: PUBLIC Environment.PageCount ← 10;

  checkUnusedSpace: BOOLEAN ← FALSE;

  dataSwapUnitSize: CARDINAL ← 4;

  codeSwapUnitSize: CARDINAL ← 2;

  GetBcdInfo: PUBLIC PROC RETURNS [numArgs: CARDINAL,
    arraySize: LONG CARDINAL, 
    commonInfo: CBasics.CommonRecPtr] = {
      bcdInfo: LoadState.LPBcdInfoTable;
      frame: PrincOps.GlobalFrameHandle;
      modinfo: LoadStateFormat.ModuleInfo;
      atb: LONG POINTER TO cInfo AtomVariants.ATRecord;
      bcd: BcdOps.BcdBase;
  
      [, bcdInfo] ← LoadState.LockBcdInfo[];
      frame ← Frame.ReadGlobalLink[Frame.GetReturnFrame[]];
      modinfo ← LoadState.GetModuleInfo[frame];
      bcd ← bcdInfo[modinfo.index].base;
      LoadState.UnlockBcdInfo[];
      IF bcd.atLimit = LOOPHOLE[0] THEN 
        RETURN[0,0,NIL];
      atb ← ATBaseFromBcd[bcd];
      numArgs ← atb.numMainArgs;
      arraySize ← atb.arraySpaceSize[0];
      IF bcd.atLimit = LOOPHOLE[SIZE[AtomVariants.ATRecord] 
        + SIZE[LONG CARDINAL]] THEN 
	  RETURN[numArgs, arraySize, NIL];
      commonInfo ← atb + SIZE[AtomVariants.ATRecord] + SIZE[LONG CARDINAL];
      RETURN[numArgs, arraySize, commonInfo]};

  -- ATBaseFromBcd: analogous to BcdOpsExtras.MTBaseFromBcd
  ATBaseFromBcd: PROC [bcd: BcdOps.BcdBase] RETURNS [Table.Base] = INLINE {
    RETURN[LOOPHOLE[bcd + bcd.atOffset]]};

  -- GetCommons called from the driver module, performs the allocation of common space and initializes pointers in the global frame of the driver module to point to the various commons.  

  GetCommons: PUBLIC PROCEDURE [comm: CBasics.CommonRecPtr, 
      comSpace: CBasics.ArraySpaceHandle ] = {
    ptrptr: POINTER TO LONG POINTER ← NIL;
    driversGF: PrincOps.GlobalFrameHandle = Frame.ReadGlobalLink[
      Frame.GetReturnFrame[]];

    ptrptr ← LOOPHOLE[driversGF + comm.commPtrOffset];
    FOR i: CARDINAL IN [0..comm.twiceNumCommons / 2) DO
      ptrptr↑ ← NextArray[comSpace, comm.items[i]];
      ptrptr ← ptrptr + SIZE[LONG POINTER];
      ENDLOOP};  -- GetCommons


  -- GetTotalSize  computes the total ammount of space required for all the commons which are declared in this config.

  GetCommonSize: PUBLIC PROCEDURE [comm: CBasics.CommonRecPtr]
    RETURNS [totSize: LONG CARDINAL ← 0] = {
    FOR i: CARDINAL IN [0..comm.twiceNumCommons / 2) DO
      totSize ← totSize + comm.items[i]; ENDLOOP;
    RETURN[totSize]};  -- GetTotalSize

  ConfigHandleForGF: INTERNAL PROCEDURE [gf: GlobalFrameHandle]
    RETURNS [ConfigHandle] = {
    hashValue: GFIndex = GFHash[gf];
    searcher: GFList ← gfTable[hashValue];
    IF searcher = NIL THEN SIGNAL ModuleNotRegistered[gf];
    UNTIL searcher.gf = gf DO
      searcher ← searcher.link;
      IF searcher = NIL THEN SIGNAL ModuleNotRegistered[gf];
      ENDLOOP;
    RETURN[searcher.config];
    };

  RegisterConfig: PUBLIC ENTRY PROCEDURE [wordsNeeded: LONG CARDINAL] = {
    ENABLE UNWIND => NULL;
    gf: GlobalFrameHandle = Frame.ReadGlobalLink[Frame.GetReturnFrame[]];
    ch: ConfigHandle ← AssociateProcessWithGF[gf];
    IF ch.data = NIL THEN ch.data ← MakeArraySpace[wordsNeeded];
    IF ch.data # NIL THEN {
      ch.data.nextArray ← ch.data.space + ch.data.firstArrayOffset;
      ZeroFill[ch.data.nextArray, ch.data.words];
      };
    };

  RegisterFrame: PUBLIC ENTRY PROCEDURE [wordsNeeded: LONG CARDINAL]
    RETURNS [CBasics.ArraySpaceHandle] = {
    ENABLE UNWIND => NULL;
    gf: GlobalFrameHandle = Frame.ReadGlobalLink[Frame.GetReturnFrame[]];
    gfi: GFIndex;
    ge: GFList;
    ch: ConfigHandle ← AssociateProcessWithGF[gf];
    gfi ← GFHash[gf];
    ge ← gfTable[gfi];
    WHILE ge # NIL DO
      IF ge.gf = gf THEN {
        IF ge.data # NIL THEN {
          ge.data.nextArray ← ge.data.space + ge.data.firstArrayOffset;
          ZeroFill[ge.data.nextArray, ge.data.words];
          };
        RETURN[IF ch.data = NIL THEN ge.data ELSE ch.data];
        };
      ge ← ge.link;
      ENDLOOP;
    gfTable[gfi] ← MakeGFEntry[gfi, gf, wordsNeeded, ch.data = NIL];
    RETURN[IF ch.data = NIL THEN gfTable[gfi].data ELSE ch.data];
    };

  MakeGFEntry: INTERNAL PROCEDURE [
    gfi: GFIndex, gf: GlobalFrameHandle, wordsNeeded: LONG CARDINAL,
    localData: BOOLEAN]
    RETURNS [gfl: GFList] = {
    ch: ConfigHandle ← NIL;
    moduleInfo: LoadStateFormat.ModuleInfo = LoadState.GetModuleInfo[gf];
    ch ← ConfigHandleForConfig[moduleInfo.index, gf];
    IF ch.data = NIL AND ~localData THEN 
      ch.data ← MakeArraySpace[wordsNeeded];
    IF localData THEN gfl ← z.NEW[
      GFEntry ← [gf, ch, gfTable[gfi], MakeArraySpace[wordsNeeded]]]
    ELSE gfl ← z.NEW[GFEntry ← [gf, ch, gfTable[gfi], NIL]];
    };

  MakeArraySpace: PROCEDURE [wordsNeeded: LONG CARDINAL]
    RETURNS [ah: LONG POINTER TO ArraySpace] = {
    IF wordsNeeded > 0 THEN {
      pages: Environment.PageCount ← PagesForWords[wordsNeeded];
      IF pages > arraySpaceThreshold THEN {
	file: File.File;
	ah ← z.NEW[large ArraySpace];
	IF checkUnusedSpace THEN {
	  swapUnits: ARRAY [1..2] OF Space.SwapUnitSize ← [CARDINAL[pages], 1];
	  ah.firstArrayOffset ← CARDINAL[
	    (pages * Environment.wordsPerPage) - wordsNeeded];
	  pages ← pages + 1;
	  file ← File.Create[
	    volume: Volume.systemID, initialSize: pages,
	    type: FileTypes.tUntypedFile !
	    File.Error, Volume.InsufficientSpace =>
	      ERROR TooMuchGlobalArraySpace];
	  ah.space ← Space.Map[
	    window: [file: file, base: 0, count: pages], life: dead,
	    swapUnits: [irregular[DESCRIPTOR[swapUnits]]]].pointer;
	  Space.MakeReadOnly[
	    interval: [
	    pointer: ah.space + ((pages - 1) * Environment.wordsPerPage),
	    count: 1]];
	  ah.nextArray ← ah.space + ah.firstArrayOffset;
	  }
	ELSE {  -- no unused space check
	  file ← File.Create[
	    volume: Volume.systemID, initialSize: pages,
	    type: FileTypes.tUntypedFile !
	    File.Error, Volume.InsufficientSpace =>
	      ERROR TooMuchGlobalArraySpace];
	  ah.space ← ah.nextArray ← Space.Map[
	    window: [file: file, base: 0, count: pages], life: dead,
	    swapUnits: [uniform[size: ]]].pointer;
	  ah.firstArrayOffset ← 0;
	  };
	ah.filePart ← large[file];
	}
      ELSE {
	ah ← z.NEW[small ArraySpace];
	ah.space ← ah.nextArray ← Heap.MakeNode[
	  arrayZone, CARDINAL[wordsNeeded]];
	ah.firstArrayOffset ← 0;
	ah.filePart ← small[];
	};
      ah.words ← wordsNeeded;
      ZeroFill[ah.nextArray, ah.words];
      }
    ELSE ah ← NIL};

  PagesForWords: PROCEDURE [nWords: LONG CARDINAL]
    RETURNS [Environment.PageCount] = INLINE {
    RETURN[(nWords + Environment.wordsPerPage - 1) / Environment.wordsPerPage]; };

  NextArray: PUBLIC PROCEDURE [
    heap: LONG POINTER TO ArraySpace, nWords: LONG CARDINAL]
    RETURNS [next: LONG POINTER] = {
    next ← heap.nextArray; heap.nextArray ← heap.nextArray + nWords; };

  ZeroFill: PROCEDURE [space: LONG POINTER, words: LONG CARDINAL] = {
    -- Initializes global array space to all zeroes.
    largeSpace: BOOLEAN = words > LONG[CARDINAL.LAST];
    words1: CARDINAL = IF largeSpace THEN CARDINAL.LAST ELSE CARDINAL[words];
    IF words = 0 THEN RETURN;
    LOOPHOLE[space, LONG POINTER TO Environment.Word]↑ ← 0;
    Inline.LongCOPY[from: space, nwords: words1 - 1, to: space + 1];
    IF largeSpace THEN {
      words2: CARDINAL = CARDINAL[words - LONG[CARDINAL.LAST]];
      ptr: LONG POINTER = space + CARDINAL.LAST;
      LOOPHOLE[ptr, LONG POINTER TO Environment.Word]↑ ← 0;
      Inline.LongCOPY[from: ptr, nwords: words2 - 1, to: ptr + 1];
      };
    };

  ConfigHandleForConfig: INTERNAL PROCEDURE [
    config: CARDINAL, gf: GlobalFrameHandle] RETURNS [cH: ConfigHandle] = {
    searcher: ConfigHandle ← CEList;
    [] ← LoadState.LockBcdInfo[];
    WHILE searcher # NIL DO
      Validate[searcher];
      IF searcher.config = config THEN {cH ← searcher; GOTO Unlock; };
      searcher ← searcher.link;
      ENDLOOP;
    CEList ← NewConfigEntry[config, gf, NIL, NIL, NIL];
    cH ← CEList;
    GOTO Unlock;
    EXITS Unlock => LoadState.UnlockBcdInfo[];
    };

  NewConfigEntry: INTERNAL PROCEDURE [
    config: CARDINAL, gf: GlobalFrameHandle, stdin, stdout, stderr: Stream.Handle]
    RETURNS [ConfigHandle] = INLINE {
    RETURN[
      z.NEW[
        ConfigEntry ← [
        config: config, gf: gf, heap: NIL, openStreams: NIL, stdin: stdin,
        stdout: stdout, stderr: stderr, link: CEList, data: NIL]]];
    };

  SetConfig: PUBLIC ENTRY PROCEDURE [
    gf: GlobalFrameHandle, stdin, stdout, stderr: Stream.Handle] = {
    ENABLE UNWIND => NULL;
    moduleInfo: LoadStateFormat.ModuleInfo = LoadState.GetModuleInfo[gf];
    CEList ← NewConfigEntry[moduleInfo.index, gf, stdin, stdout, stderr];
    EnterStreamGloballyInternal[stdin];
    EnterStreamGloballyInternal[stdout];
    EnterStreamGloballyInternal[stderr];
    };

  ResetConfig: PUBLIC ENTRY PROCEDURE [
    gf: GlobalFrameHandle, stdin, stdout, stderr: Stream.Handle] = {
    ENABLE UNWIND => NULL;
    cH: ConfigHandle;
    moduleInfo: LoadStateFormat.ModuleInfo = LoadState.GetModuleInfo[gf];
    cH ← ConfigHandleForConfig[moduleInfo.index, gf];
    IF cH.openStreams # NIL THEN FreeOpenStreamList[cH];
    IF cH.heap # NIL THEN Heap.Flush[cH.heap];
    cH.stdin ← stdin;
    cH.stdout ← stdout;
    cH.stderr ← stderr;
    EnterStreamGloballyInternal[stdin];
    EnterStreamGloballyInternal[stdout];
    EnterStreamGloballyInternal[stderr];
    cH.openStreams ← NIL;
    };

  RegisterProcess: PUBLIC ENTRY PROCEDURE = {
    ENABLE UNWIND => NULL;
    [] ← AssociateProcessWithGF[Frame.ReadGlobalLink[Frame.GetReturnFrame[]]];
    };

  AssociateProcessWithGF: INTERNAL PROCEDURE [gf: GlobalFrameHandle]
    RETURNS [cH: ConfigHandle] = {
    cH ← ConfigHandleForConfig[LoadState.GetModuleInfo[gf].index, gf];
    AssociateProcessWithConfig[cH];
    };

  AssociateProcessWithConfig: INTERNAL PROCEDURE [cH: ConfigHandle] = {
    p: PROCESS = Process.GetCurrent[];
    pti: ProcessIndex = PHash[p];
    pl: ProcessList ← processTable[pti];
    <<Check if process already there, and reuse the process entry if it is.>>
    WHILE pl # NIL DO
      IF pl.p = p THEN {pl.config ← cH; RETURN; }; pl ← pl.link; ENDLOOP;
    pl ← z.NEW[ProcessEntry ← [p: p, config: cH, link: processTable[pti]]];
    processTable[pti] ← pl;
    };

  ConfigHandleForProcess: INTERNAL PROCEDURE RETURNS [ConfigHandle] = {
    p: PROCESS = Process.GetCurrent[];
    pl: ProcessList ← processTable[PHash[p]];
    WHILE pl # NIL DO IF pl.p = p THEN RETURN[pl.config]; pl ← pl.link; ENDLOOP;
    ERROR ProcessNotRegistered;
    };

  GetConfigHandle: PUBLIC ENTRY PROCEDURE RETURNS [ConfigHandle] = {
    RETURN[ConfigHandleForProcess[]]; };

  SetConfigHandle: PUBLIC ENTRY PROCEDURE [cH: ConfigHandle] = {
    AssociateProcessWithConfig[cH]; };

  PHash: PROCEDURE [p: PROCESS] RETURNS [ProcessIndex] = {
    RETURN[LOOPHOLE[p, CARDINAL] MOD processTableSize]; };

  FreeOpenStreamList: INTERNAL PROCEDURE [cH: ConfigHandle] = {
    s1, s2: StreamList;
    s1 ← cH.openStreams;
    WHILE s1 # NIL DO s2 ← s1.link; z.FREE[@s1]; s1 ← s2; ENDLOOP;
    };

  RemoveConfig: PUBLIC ENTRY PROC [gf: GlobalFrameHandle] = {
    ENABLE UNWIND => NULL;
    cH: ConfigHandle;
    moduleSeq: LoadState.ModuleInfoSequenceHandle;
    searcher, follower: ConfigHandle;
    cH ← ConfigHandleForGF[gf ! ModuleNotRegistered => GOTO NotRegisterd];
    IF cH.heap # NIL THEN Heap.Delete[z: cH.heap, checkEmpty: FALSE];
    [] ← LoadState.LockBcdInfo[];
    Validate[cH];
    moduleSeq ← LoadState.ModuleInfosOfBcd[cH.config, z];
    -- Remove each gf from gfTable and unmap global array space.
    FOR i: LoadState.ModuleInfoRange IN [0..moduleSeq.length) DO
      RemoveGF[moduleSeq[i].gf]; ENDLOOP;
    -- Remove cH from list of config entries.
    searcher ← CEList;
    follower ← NIL;
    UNTIL searcher = cH DO follower ← searcher; searcher ← searcher.link; ENDLOOP;
    IF follower = NIL THEN CEList ← searcher.link
    ELSE follower.link ← searcher.link;
    IF searcher.data # NIL THEN {  -- if space was mapped
      WITH a: searcher.data↑ SELECT FROM
        large => {[] ← Space.Unmap[pointer: a.space]; File.Delete[a.file]; };
        small => arrayZone.FREE[@a.space];
        ENDCASE;
      z.FREE[@searcher.data];
      };
    z.FREE[@searcher];
    z.FREE[@moduleSeq];
    LoadState.UnlockBcdInfo[];
    EXITS NotRegisterd => NULL;
    };

  RemoveGF: INTERNAL PROCEDURE [gf: GlobalFrameHandle] = {
    gfi: GFIndex = GFHash[gf];
    searcher, follower: GFList;
    searcher ← gfTable[gfi];
    follower ← NIL;
    UNTIL searcher = NIL OR searcher.gf = gf DO
      follower ← searcher; searcher ← searcher.link; ENDLOOP;
    IF searcher = NIL THEN RETURN;
    IF searcher.data # NIL THEN {  -- if space was mapped
      WITH a: searcher.data↑ SELECT FROM
        large => {[] ← Space.Unmap[pointer: a.space]; File.Delete[a.file]; };
        small => arrayZone.FREE[@a.space];
        ENDCASE;
      z.FREE[@searcher.data];
      };
    IF follower = NIL THEN gfTable[gfi] ← searcher.link
    ELSE follower.link ← searcher.link;
    z.FREE[@searcher];
    };

  Validate: PROCEDURE [cH: ConfigHandle] = {
    cH.config ← LoadState.GetModuleInfo[cH.gf].index; };

  GetHeap: PUBLIC ENTRY PROCEDURE RETURNS [UNCOUNTED ZONE] = {
    ENABLE UNWIND => NULL; RETURN[ConfigHandleForProcess[]↑.heap]; };

  SetHeap: PUBLIC ENTRY PROCEDURE [h: UNCOUNTED ZONE] = {
    ENABLE UNWIND => NULL; ConfigHandleForProcess[]↑.heap ← h; };

  SHHash: PROCEDURE [sh: Stream.Handle] RETURNS [SHIndex] = INLINE {
    RETURN[(LOOPHOLE[Inline.LowHalf[sh], CARDINAL] / 4) MOD shTableSize]; };

  EnterStream: PUBLIC ENTRY PROCEDURE [sH: Stream.Handle] RETURNS [FilePtr] = {
    ENABLE UNWIND => NULL;
    cH: ConfigHandle = ConfigHandleForProcess[];
    newStream: StreamList = z.NEW[StreamEntry ← [sH, cH.openStreams]];
    cH.openStreams ← newStream;
    EnterStreamGloballyInternal[sH];
    RETURN[@newStream.sH];
    };

  EnterStreamGlobally: PUBLIC ENTRY PROC [sH: Stream.Handle] = {
    ENABLE UNWIND => NULL; EnterStreamGloballyInternal[sH]; };

  EnterStreamGloballyInternal: INTERNAL PROCEDURE [sH: Stream.Handle] = {
    shi: SHIndex = SHHash[sH];
    shl: SHList;
    shl ← shTable[shi];
    WHILE shl # NIL DO
      IF shl.sh = sH THEN {shl.refCount ← shl.refCount.SUCC; RETURN; };
      shl ← shl.link;
      ENDLOOP;
    shTable[shi] ← z.NEW[SHEntry ← [sh: sH, refCount: 1, link: shTable[shi]]];
    };

  RemoveStream: PUBLIC ENTRY PROCEDURE [sH: Stream.Handle]
    RETURNS [canDeleteStream: BOOLEAN] = {
    ENABLE UNWIND => NULL;
    cH: ConfigHandle;  
    -- OK if sH not in list .  Might not have been opened with fopen.
    searcher: StreamList;
    IF sH = NIL THEN RETURN [FALSE];
    cH ← ConfigHandleForProcess[];
    searcher ← cH.openStreams;
    UNTIL searcher = NIL OR searcher.sH = sH DO searcher ← searcher.link; ENDLOOP;
    IF searcher # NIL THEN searcher.sH ← NIL;
    -- check if sH is a standard stream
    SELECT sH FROM
      cH.stdin => cH.stdin ← NIL;
      cH.stdout => cH.stdout ← NIL;
      cH.stderr => cH.stderr ← NIL;
      ENDCASE;
    canDeleteStream ← RemoveStreamGlobally[sH];  --called even if not in list
    };

  RemoveStreamGlobally: INTERNAL PROCEDURE [sH: Stream.Handle]
    RETURNS [canDeleteStream: BOOLEAN] = {
    -- decrements ref. count and removes entry if ref. count becomes 0.
    searcher, follower: SHList;
    shi: SHIndex ← SHHash[sH];
    searcher ← shTable[shi];
    follower ← NIL;
    UNTIL searcher = NIL OR searcher.sh = sH DO
      follower ← searcher; searcher ← searcher.link; ENDLOOP;
    IF searcher = NIL THEN RETURN[TRUE];
    searcher.refCount ← searcher.refCount.PRED;
    IF searcher.refCount = 0 THEN {
      canDeleteStream ← TRUE;
      IF follower = NIL THEN shTable[shi] ← searcher.link
      ELSE follower.link ← searcher.link;
      z.FREE[@searcher];
      }
    ELSE canDeleteStream ← FALSE;
    };

  CanDelete: PUBLIC ENTRY PROC [sh: Stream.Handle] RETURNS [BOOLEAN] = {
    -- Returns true if sh not in global stream table.
    ENABLE UNWIND => NULL;
    shi: SHIndex = SHHash[sh];
    shl: SHList;
    shl ← shTable[shi];
    WHILE shl # NIL DO IF shl.sh = sh THEN RETURN[FALSE]; shl ← shl.link; ENDLOOP;
    RETURN[TRUE];
    };

  ProgramExited: PUBLIC ERROR [status: INTEGER] = CODE;

  exit: PUBLIC PROCEDURE [status: INTEGER] RETURNS [INTEGER ← 0] = {
    CleanUp[]; ERROR ProgramExited[status]; };

  abort: PUBLIC PROCEDURE RETURNS [INTEGER ← 0] = {
    CleanUp[]; ERROR ProgramExited[abortOutcome]; };

  CleanUp: PUBLIC ENTRY PROCEDURE = {
    -- Deletes heap and closes open streams.
    ENABLE UNWIND => NULL; CleanUpConfig[ConfigHandleForProcess[]]; };

  CleanUpConfig: INTERNAL PROCEDURE [cH: ConfigHandle] = {
    sl: StreamList;
    IF ~LogCleanUp[cH].cleanUpNeeded THEN RETURN;
    -- delete heap
    IF cH.heap # NIL THEN {DeleteAndLogHeap[z: cH.heap]; cH.heap ← NIL; };
    -- close standard streams
    IF cH.stdin # NIL AND RemoveStreamGlobally[cH.stdin].canDeleteStream THEN
      DeleteAndLogStream[cH.stdin];
    IF cH.stdout # NIL AND RemoveStreamGlobally[cH.stdout].canDeleteStream THEN
      DeleteAndLogStream[cH.stdout];
    IF cH.stderr # NIL AND RemoveStreamGlobally[cH.stderr].canDeleteStream THEN
      DeleteAndLogStream[cH.stderr];
    -- close other open files
    sl ← cH.openStreams;
    WHILE sl # NIL DO
      nextsl: StreamList ← sl.link;
      IF sl.sH # cH.stdin AND sl.sH # cH.stdout AND sl.sH # cH.stderr
        AND sl.sH # NIL THEN {
        IF RemoveStreamGlobally[sl.sH].canDeleteStream THEN
          DeleteAndLogStream[sl.sH];
        };
      z.FREE[@sl];
      sl ← nextsl;
      ENDLOOP;
    cH.openStreams ← NIL;
    cH.stdin ← cH.stdout ← cH.stderr ← NIL;
    IF cH.data # NIL THEN WITH ah: cH.data SELECT FROM
      large =>
	Space.Kill[
	  interval: [pointer: cH.data.space, count: File.GetSize[ah.file]]];
      ENDCASE;
    };

  GetStdin: PUBLIC ENTRY PROCEDURE RETURNS [FilePtr] = {
    ENABLE UNWIND => NULL; RETURN[@(ConfigHandleForProcess[].stdin)]; };

  GetStdout: PUBLIC ENTRY PROCEDURE RETURNS [FilePtr] = {
    ENABLE UNWIND => NULL; RETURN[@(ConfigHandleForProcess[].stdout)]; };

  GetStderr: PUBLIC ENTRY PROCEDURE RETURNS [FilePtr] = {
    ENABLE UNWIND => NULL; RETURN[@(ConfigHandleForProcess[].stderr)]; };

  SetStdin: PUBLIC ENTRY PROCEDURE [fp: FilePtr] = {
    ENABLE UNWIND => NULL;
    cH: ConfigHandle = ConfigHandleForProcess[];
    cH.stdin ← IF fp = NIL THEN NIL ELSE fp↑;
    };

  SetStdout: PUBLIC ENTRY PROCEDURE [fp: FilePtr] = {
    ENABLE UNWIND => NULL;
    cH: ConfigHandle = ConfigHandleForProcess[];
    cH.stdout ← IF fp = NIL THEN NIL ELSE fp↑;
    };

  SetStderr: PUBLIC ENTRY PROCEDURE [fp: FilePtr] = {
    ENABLE UNWIND => NULL;
    cH: ConfigHandle = ConfigHandleForProcess[];
    cH.stderr ← IF fp = NIL THEN NIL ELSE fp↑;
    };

  SetUnusedSpaceCheck: PUBLIC ENTRY PROCEDURE [checkSpace: BOOLEAN]
    RETURNS [oldCheckSpace: BOOLEAN] = {
    oldCheckSpace ← checkUnusedSpace; checkUnusedSpace ← checkSpace; };

  GetUnusedSpaceCheck: PUBLIC ENTRY PROCEDURE RETURNS [checkSpace: BOOLEAN] = {
    checkSpace ← checkUnusedSpace; };

  SetDataSwapUnitSize:PUBLIC ENTRY PROCEDURE[size:CARDINAL] 
    RETURNS [oldSize:CARDINAL] = {
    oldSize ← dataSwapUnitSize;
    dataSwapUnitSize ← size;
    };
  
  SetCodeSwapUnitSize:PUBLIC ENTRY PROCEDURE[size:CARDINAL] 
    RETURNS [oldSize:CARDINAL] = {
    oldSize ← codeSwapUnitSize;
    codeSwapUnitSize ← size;
    };
  
  GetDataSwapUnitSize:PUBLIC ENTRY PROCEDURE RETURNS [size:CARDINAL] = {
    size ← dataSwapUnitSize;
    };
  
  GetCodeSwapUnitSize:PUBLIC ENTRY PROCEDURE RETURNS [size:CARDINAL] = {
    size ← codeSwapUnitSize;
    };
  

  Init: PROCEDURE = {
    buckets: ARRAY [0..8) OF BucketAlloc.BucketInfo ← [
      [6, 4, 2], [10, 4, 2], [12, 4, 1], [15, 4, 2],  -- SIZE[CIOLib.ClientDataObject]
      [20, 3, 0], [25, 2, 0], [32, 2, 0], [45, 2, 2]  -- SIZE[CFormatIO$StringStreamObject]
      ];
    z ← Heap.Create[
      initial: 15, increment: 10,
      largeNodeThreshold:
      Environment.wordsPerPage * CARDINAL[arraySpaceThreshold]];
    arrayZone ← Heap.Create[
      initial: 25, increment: 50,
      largeNodeThreshold:
      Environment.wordsPerPage * CARDINAL[arraySpaceThreshold]];
    gfTable ← z.NEW[GFTable];
    shTable ← z.NEW[SHTable];
    processTable ← z.NEW[ProcessTable];
    BucketAlloc.Initialize[z, DESCRIPTOR[buckets]];
    };

  Init[];

  }.