-- Copyright (C) 1986  by Xerox Corporation. All rights reserved. 
-- CRuntimeB.mesa
-- NFS		22-Jan-86 15:59:48
-- MEW		27-May-86 17:29:44

DIRECTORY
  BcdDefs USING [CTIndex, GFIndex, MTIndex],
  BcdOps USING [BcdBase, MTHandle],
  BcdOpsExtras USING [CTBaseFromBcd, GfiFromMti, MTBaseFromBcd, MthFromGfi],
  CBasics USING [MainProc],
  CRuntime USING [normalOutcome, ProgramExited, z],
  CRuntimeInternal USING [GlobalFrameHandle, ResetConfig, SetConfig],
  CString USING [CString],
  Frame USING [GetReturnFrame, ReadCodebaseLow, ReadGlobalLink, ReadGlobalWord, WriteCodebaseLow, WriteGlobalWord],
  LoadState USING [
    GetModuleInfo, LockBcdInfo, LPBcdInfoTable, ModuleInfoRange,
    ModuleInfoSequenceHandle, ModuleInfosOfBcd, UnlockBcdInfo],
  LoadStateFormat USING [ModuleInfo],
  PrincOps USING [GlobalCodebase, GlobalFrameHandle, GlobalWord],
  Process USING [GetCurrent],
  SpecialRuntime USING [ProgramFromGlobalFrame],
  Stream USING [Handle],
  Table USING [Base];

CRuntimeB: MONITOR  -- not CRuntimeInternal.lock
  IMPORTS
    BcdOpsExtras, CRuntime, CRuntimeInternal, Frame, LoadState, Process,
    SpecialRuntime
  EXPORTS CBasics, CRuntime, CRuntimeInternal = {
  OPEN CRuntime, CRuntimeInternal;

  GlobalFrameHandle: TYPE = PrincOps.GlobalFrameHandle;

  moduleTable: LONG POINTER TO ModuleTable;
  ModuleTable: TYPE = RECORD [
    seq: SEQUENCE COMPUTED CARDINAL OF PrincOps.GlobalFrameHandle];
  ControlList: TYPE = LONG POINTER TO ModuleRecord;
  ModuleRecord: TYPE = RECORD [gfi: BcdDefs.GFIndex, link: ControlList];

  MainArgsNeeded: PUBLIC SIGNAL
    RETURNS [argC: CARDINAL, argV: LONG POINTER TO CString.CString] = CODE;

  CallMain: PUBLIC PROCEDURE [main: CBasics.MainProc] = {
    <<Note:Programs that have main procs. that take arc or argv parameters or that
   return values other than 0, must be invoked by
   the procedures Start or Restart, below.  Otherwise there will be an
   uncaught signal.>>
    argc: CARDINAL;
    argv: LONG POINTER TO CString.CString;
    retval: INTEGER ← 0;
    IF main.args # neither THEN [argc, argv] ← SIGNAL MainArgsNeeded;
    WITH m: main SELECT FROM
      neither => retval ← m.main0[];
      justArgC => retval ← m.main1[argc];
      both => retval ← m.main2[argc, argv];
      ENDCASE => ERROR;
    IF retval # normalOutcome THEN ERROR ProgramExited[retval];
    };

  Start: PUBLIC PROCEDURE [
    gf: GlobalFrameHandle, argc: CARDINAL, argv: LONG POINTER TO CString.CString,
    stdin, stdout, stderr: Stream.Handle]
    RETURNS [outcome: INTEGER ← normalOutcome] = {
    ENABLE {
      ProgramExited => {outcome ← status; CONTINUE; };
      MainArgsNeeded => RESUME [argc, argv];
      };
    p: PROGRAM = SpecialRuntime.ProgramFromGlobalFrame[gf];
    SetConfig[gf, stdin, stdout, stderr];
    ResetUserAbort[];
    START p;
    };
    
  StartConfig: PUBLIC PROCEDURE = {
    gf: GlobalFrameHandle = Frame.ReadGlobalLink[Frame.GetReturnFrame[]];
    moduleInfo: LoadStateFormat.ModuleInfo = LoadState.GetModuleInfo[gf];
    moduleSeq: LoadState.ModuleInfoSequenceHandle;
    controlModules: ControlList;
    bcdInfo: LoadState.LPBcdInfoTable;
    bcd: BcdOps.BcdBase;
    mth: BcdOps.MTHandle;
    moduleSeq ← LoadState.ModuleInfosOfBcd[moduleInfo.index, z];
    moduleTable ← LOOPHOLE[z.NEW[ModuleTable [moduleSeq.length]]];
    bcdInfo ← LoadState.LockBcdInfo[].bcdInfo;
    bcd ← bcdInfo[moduleInfo.index].base;
    FOR i: LoadState.ModuleInfoRange IN [0..moduleSeq.length) DO
      gf: GlobalFrameHandle = moduleSeq[i].gf;
      moduleTable[moduleSeq[i].cgfi - 1] ← gf;
      mth ← BcdOpsExtras.MthFromGfi[bcd, moduleSeq[i].cgfi];
      IF mth.tableCompiled THEN LOOP;  -- no gf[0] to erase!!
      gf[0] ← NIL;  -- so start trap doesn't think there is a control list.
      ENDLOOP;
    controlModules ← GetControlModules[bcd];
    LoadState.UnlockBcdInfo[];
    StartModules[controlModules, gf ! 
      ProgramExited => {CONTINUE; }];
    z.FREE[@moduleSeq];
    z.FREE[@moduleTable];
    };

  Restart: PUBLIC PROCEDURE [
    gf: GlobalFrameHandle, argc: CARDINAL, argv: LONG POINTER TO CString.CString,
    stdin, stdout, stderr: Stream.Handle]
    RETURNS [outcome: INTEGER ← normalOutcome] = {
    moduleInfo: LoadStateFormat.ModuleInfo = LoadState.GetModuleInfo[gf];
    moduleSeq: LoadState.ModuleInfoSequenceHandle;
    p: PROGRAM = SpecialRuntime.ProgramFromGlobalFrame[gf];
    BEGIN ENABLE {
      MainArgsNeeded => RESUME [argc, argv];
      ProgramExited => {outcome ← status; CONTINUE; };
      };
    ResetConfig[gf, stdin, stdout, stderr];
    -- Reset started and out flag of every module in configuration, and build
    -- correspondence between gfi and GFrame.
    moduleSeq ← LoadState.ModuleInfosOfBcd[moduleInfo.index, z];
    FOR i: LoadState.ModuleInfoRange IN [0..moduleSeq.length) DO
      globalCodeBase: PrincOps.GlobalCodebase;
      gf: GlobalFrameHandle = moduleSeq[i].gf;
      globalWord: PrincOps.GlobalWord ← Frame.ReadGlobalWord[gf];
      globalWord.started ← FALSE;
      Frame.WriteGlobalWord[word: globalWord, gf: gf];
      globalCodeBase.offset ← Frame.ReadCodebaseLow[gf];
      globalCodeBase.out ← TRUE;
      Frame.WriteCodebaseLow[gf: gf, u: globalCodeBase.offset];
      ENDLOOP;
    gf[0] ← NIL;  -- so start trap doesn't think there is a control list.
    ResetUserAbort[];
    START p;
    END;
    z.FREE[@moduleSeq];
    };

  GetControlModules: PROCEDURE [bcd: BcdOps.BcdBase]
    RETURNS [control: ControlList ← NIL] = {

    TraverseControls: PROCEDURE [cti: BcdDefs.CTIndex] = {
      FOR i: NATURAL IN [0..ctb[cti].nControls) DO
        WITH c: ctb[cti].controls[i] SELECT FROM
          config => TraverseControls[c.cti];
          module => AttachControl[BcdOpsExtras.GfiFromMti[c.mti]];
          ENDCASE => ERROR;
        ENDLOOP;
      };

    AttachControl: PROCEDURE [gfi: BcdDefs.GFIndex] = {
      newModule: ControlList = LOOPHOLE[z.NEW[ModuleRecord ← [gfi, NIL]]];
      IF control = NIL THEN control ← last ← newModule
      ELSE {last.link ← newModule; last ← newModule; };
      };

    ctb: Table.Base = BcdOpsExtras.CTBaseFromBcd[bcd];
    mtb: Table.Base = BcdOpsExtras.MTBaseFromBcd[bcd];
    last: ControlList ← NIL;
    IF bcd.nConfigs = 0 THEN {
      control ← LOOPHOLE[z.NEW[
        ModuleRecord ← [BcdOpsExtras.GfiFromMti[BcdDefs.MTIndex.FIRST], NIL]]];
      RETURN;
      };
    TraverseControls[BcdDefs.CTIndex.FIRST];
    };

  StartModules: PROCEDURE [
    controls: ControlList, driver: GlobalFrameHandle ← NIL] = {
    WHILE controls # NIL DO
      ENABLE UNWIND => z.FREE[@controls];
      next: ControlList = controls.link;
      gf: GlobalFrameHandle = moduleTable[controls.gfi - 1];
      IF NOT Frame.ReadGlobalWord[gf].started AND gf # driver THEN {
        p: PROGRAM = SpecialRuntime.ProgramFromGlobalFrame[gf]; START p; };
      z.FREE[@controls];
      controls ← next;
      ENDLOOP;
    };

  -- monitored data and entry procs.

  processesAborted: PUBLIC ProcessList ← NIL;
  ProcessList: TYPE = LONG POINTER TO ProcessEntry;
  ProcessEntry: PUBLIC TYPE = RECORD [p: PROCESS, link: ProcessList];

  NoteAbortedProcess: PUBLIC ENTRY PROCEDURE [p: PROCESS] = {
    ENABLE UNWIND => NULL;
    -- Don't add if process already has pending abort.
    pl: ProcessList ← processesAborted;
    WHILE pl # NIL DO IF pl.p = p THEN RETURN; pl ← pl.link; ENDLOOP;
    pl ← z.NEW[ProcessEntry];
    pl.link ← processesAborted;
    pl.p ← p;
    processesAborted ← pl;
    };

  StopIfCurrentProcessAborted: PUBLIC ENTRY PROCEDURE = {
    ENABLE UNWIND => NULL;
    searcher, follower: ProcessList;
    p: PROCESS ← Process.GetCurrent[];
    searcher ← processesAborted;
    follower ← NIL;
    WHILE searcher # NIL DO
      IF searcher.p = p THEN {
        IF follower = NIL THEN processesAborted ← searcher.link
        ELSE follower.link ← searcher.link;
        z.FREE[@searcher];
        ERROR ABORTED;
        };
      follower ← searcher;
      searcher ← searcher.link;
      ENDLOOP;
    };

  ResetUserAbort: PROCEDURE = {
    -- Removes process from aborted process.
    StopIfCurrentProcessAborted[ ! ABORTED => CONTINUE]; };

  }.