-- CTLDriverImpl.Mesa, last edit May 8, 1983 6:36 pm
-- Mesa 7.0/ Pilot 6.0
-- procedures to load and start modules in a Model

-- can't use PilotLoaderOps since it is not exported by CoPilotDorado.Config
-- may use PilotLoadStateOps, however

-- links:
-- IF gfi > firstdummy, then gfi is index into Import table
--  and ep is index into the export record pared with that import
--  binding is simply to copy control link in the export record
--  into this link
--  IF gfi < firstdummy, then gfi in this link is an index into the config's
--  moduletable. Do not alter the ep

-- spaces:
-- assume there are n modules
-- there will be
-- 1 space in MDS for all the frames (and frame links)
-- 1 space in VM for the Fake Config Bcd for the load state
-- n ReadOnly spaces for Code
--  code and code links are in same space
-- n spaces for the Bcd headers
--  (deleted on UnLoad)

DIRECTORY
Atom: TYPE USING[MakeAtom, PutProp],
AtomsPrivate: TYPE USING[UnsafeMakeAtom],
BcdDefs: TYPE USING [Base, BCD, EPLimit, EXPIndex, EXPRecord,
 FTIndex, FTRecord, GFTIndex, IMPIndex, IMPRecord,
 Link, MTIndex, MTRecord, NameRecord, NullLink, VersionStamp],
BcdOps: TYPE USING [BcdBase, EXPHandle, FTHandle, IMPHandle, MTHandle, NameString,
ProcessExports, ProcessImports, ProcessModules],
CedarExporterImpl: TYPE USING[ProcessPendingEntries, SaveResolvedEntries],
CedarLinkerOps: TYPE USING[FindVariableLink, GetIR, GetPendingList, IR,
 PendingList, SetPendingList],
CS: TYPE USING[EqualRope, MakeTS],
CT: TYPE USING[FrameListRecord, Global, MI, ModuleList, OutsideImports,
 OutsideImportsRecord],
CTFakeBcd: TYPE USING[BuildFakeBcd],
CTLoad: TYPE USING [AllocateInterfaceSeq, BuildFramePtrInterface,
BuildInterface, CloseLinkSpace, ConvertLink, DummyMapSeq,
 FreeLoadInfoSeq, ImpExpSeq,
 InterfaceSeq, InvalidFile, LoadGlobalFrames, LoadIncremental,
 LoadInfoSeq, NSToRope, OpenLinkSpace, ReadLink, ReplaceResult, WriteLink],
Directory: TYPE USING[DeleteFile, Lookup, Error, ignore],
File: TYPE USING [Capability, nullCapability],
IO: TYPE USING[card, Handle, PutF, rope, string, UserAbort, UserAborted],
List: TYPE USING[DReverse],
Loader: TYPE USING[Error],
LoaderPrivate: TYPE USING[FindMappedSpace, GetModuleLink],
PilotLoaderOps: TYPE USING[DestroyMap, IthLink, LinkSegmentLength,
 ReleaseFrames],
PilotLoadStateFormat: TYPE USING[ConfigIndex],
PilotLoadStateOps: TYPE USING [ConfigIndex, GetMap, InputLoadState, Map, NullConfig,
 ReleaseLoadState, ReleaseMap, RemoveConfig],
PrincOps: TYPE USING [ControlLink, GFTIndex, GFTNull, GlobalFrameHandle, NullLink,
UnboundLink],
Process: TYPE USING [Detach],
Rope: TYPE USING[Text],
RTLoader: TYPE USING [AcquireTypesAndLiterals],
Runtime: TYPE USING [ValidateGlobalFrame],
RuntimeInternal: TYPE USING [Codebase],
Space: TYPE USING [Create, Delete, GetHandle, Handle,
 LongPointer, Map, nullHandle, PageFromLongPointer, virtualMemory],
System: TYPE USING [GetClockPulses, PulsesToMicroseconds],
Time: TYPE USING [Current],
TimeStamp: TYPE USING [Stamp];
   
CTLDriverImpl: PROGRAM
IMPORTS Atom, AtomsPrivate, BcdOps, CedarExporterImpl, CedarLinkerOps,
 CTFakeBcd, CTLoad, CS, Directory,
 IO, List, Loader, LoaderPrivate, PilotLoaderOps, PilotLoadStateOps, Process, RTLoader,
 Runtime, RuntimeInternal, Space, System, Time
EXPORTS CT
SHARES CedarExporterImpl, CedarLinkerOps = {

-- no MDS usage!

LoadBcdsAndResolveImports: PUBLIC SAFE PROC[g: CT.Global, tryreplacement: BOOL]
 RETURNS[errors: BOOL] = TRUSTED {

 ENABLE UNWIND => PilotLoadStateOps.ReleaseLoadState[];
 p: LONG CARDINAL;
 time: LONG CARDINAL ← Time.Current[];
 errors ← FALSE;
 g.ttyout.PutF["Loading Phase.\n"];
 -- unload any bcds that may be around from the last invocation
 -- regardless of "replacement", old config in load state is unloaded
 IF g.configindex ~= PilotLoadStateOps.NullConfig THEN
  DeleteLoadStateEntry[g.configindex];
 IF NOT tryreplacement THEN {
  g.moduleExports ← NIL;
  g.outsideImports ← NIL;
  };
  
 g.frameInterfaces ← NIL;

 g.configindex ← PilotLoadStateOps.InputLoadState[];  -- locks the load state
 -- this will build all interface records
 LoadBcds[g ! CTLoad.InvalidFile => GOTO out];
 PutExportsInLoadState[g];
 CTFakeBcd.BuildFakeBcd[g];  -- releases the load state
 IF g.ttyout.UserAbort[] THEN ERROR ABORTED;

 -- now fill in parameters
 p ← System.PulsesToMicroseconds[System.GetClockPulses[]];
 g.dout.PutF["Phase 3: Fill in interface records... \n"];
 ComputeOutsideImports[g];
 -- will lock the load state
 FillInImportsFromLoadState[g, tryreplacement];
 ComingleImportsAndExports[g];
 -- IF Subr.debugflg THEN PrintInterfaceSeqs[spmodel, symbolseq];
 IF g.ttyout.UserAbort[] THEN ERROR ABORTED;
 p ← (System.PulsesToMicroseconds[System.GetClockPulses[]] - p)/1000;
 g.dout.PutF["done (%d millisec).\nPhase 4: Fill in imports (links) ... \n", IO.card[p]];
 IF g.ttyout.UserAbort[] THEN ERROR ABORTED;
 p ← System.PulsesToMicroseconds[System.GetClockPulses[]];
 -- now fill in all the frame links
 ResolveImports[g];
 -- now call Cedar related procedures to finish the loading
 ProcessCedarBcds[g, tryreplacement];
 time ← Time.Current[] - time;
 p ← (System.PulsesToMicroseconds[System.GetClockPulses[]] - p)/1000;
 g.dout.PutF["done (%d millisec).\n", IO.card[p]];
 g.ttyout.PutF["Total time to load: %d seconds.\n", IO.card[time]];
 g.ttyout.PutF["--------------------------------\n"];
 EXITS
 out => NULL;
 };

StartElemRecord: TYPE = RECORD [
 prog: PROGRAM,
 frame: PrincOps.GlobalFrameHandle
 ];

StartAllControlBcds: PUBLIC SAFE PROC[g: CT.Global] = TRUSTED {
 starr: LIST OF REF StartElemRecord;
 p: PROCESS;
 mi: CT.MI;
 prog: PROGRAM;
 dontFork: BOOL ← TRUE;

 g.ttyout.PutF["Starting modules ... \n"];
 FOR l: CT.ModuleList ← g.moduleList, l.rest UNTIL l = NIL DO
  mi ← l.first;
  IF mi.loadInfoSeq = NIL OR NOT mi.controlModule THEN LOOP;
  FOR i: CARDINAL IN [0 .. mi.loadInfoSeq.size) DO
   Runtime.ValidateGlobalFrame[mi.loadInfoSeq[i].frame];
   IF mi.loadInfoSeq[i].frame.started THEN {
    g.ttyout.PutF["Error - %s has already been started.\n",
     IO.rope[mi.bcdFileName]];
    RETURN;
    };
   ENDLOOP;
  g.dout.PutF["Will start %s\n", IO.rope[mi.bcdFileName]];
  prog ← LOOPHOLE[mi.loadInfoSeq.cm];
  starr ← CONS[NEW[StartElemRecord ← [prog: prog, frame: mi.loadInfoSeq[0].frame]],
    starr];
  ENDLOOP;
 starr ← LOOPHOLE[List.DReverse[LOOPHOLE[starr]]];
 IF dontFork THEN
  StartProcedure[g, starr]
 ELSE
  {
  p ← FORK StartProcedure[g, starr];
  Process.Detach[p];
  };
 };

-- always frees the bcdbases stored in our structures
UnLoad: PUBLIC SAFE PROC[g: CT.Global, unloadthebcd: BOOL] = TRUSTED{
 nunl: CARDINAL ← 0;
 mi: CT.MI;

 FOR l: CT.ModuleList ← g.moduleList, l.rest UNTIL l = NIL DO
  mi ← l.first;
  IF mi.loadInfoSeq ~= NIL THEN {
   nunl ← nunl + 1;
   IF unloadthebcd THEN {
    IF nunl = 1 THEN g.ttyout.PutF["Unloading modules.\n"];
    -- remember that a binder bcd loaded by
    -- the modeller will have only ONE mapped
    -- space for all its code
    FOR i: CARDINAL IN [0 .. mi.loadInfoSeq.size) DO
     prog: PROGRAM ← LOOPHOLE[mi.loadInfoSeq[i].frame];
     space: Space.Handle;
     IF RuntimeInternal.Codebase[prog] ~= NIL THEN {
      space ← Space.GetHandle[
       Space.PageFromLongPointer[
        RuntimeInternal.Codebase[prog]]];
      IF space ~= Space.nullHandle
      AND space ~= Space.virtualMemory THEN
       Space.Delete[LoaderPrivate.FindMappedSpace[space]];
      };
     ENDLOOP;
    -- frees the global frames
    PilotLoaderOps.ReleaseFrames[mi.loadInfoSeq.bcdbase, mi.loadInfoSeq.frameList,
     mi.loadInfoSeq.map];
    PilotLoaderOps.DestroyMap[mi.loadInfoSeq.map];
    };
   -- frees the bcdbase space
   mi.loadInfoSeq ← CTLoad.FreeLoadInfoSeq[mi.loadInfoSeq];
   };
  ENDLOOP;
 IF unloadthebcd THEN {
  IF g.configindex ~= PilotLoadStateOps.NullConfig THEN {
   DeleteLoadStateEntry[g.configindex];
   g.configindex ← PilotLoadStateOps.NullConfig;
   };
  IF g.fakebcdspace ~= Space.nullHandle THEN {
   Space.Delete[g.fakebcdspace];
   g.fakebcdspace ← Space.nullHandle;
   Directory.DeleteFile[LOOPHOLE[g.fakeBcdFileName]
    ! Directory.Error => CONTINUE];
   g.fakeBcdFileName ← NIL;
   };
  IF nunl > 0 THEN {
   g.ttyout.PutF["%d Module%s unloaded.\n", IO.card[nunl],
    IO.rope[IF nunl > 1 THEN "s" ELSE ""]];
   g.ttyout.PutF["All code spaces and frames have been freed. DO NOT TRY TO USE THEM!\n"];
   g.ttyout.PutF["--------------------------------\n"];
   };
  };
 };

-- internal procedures

DeleteLoadStateEntry: PROC[index: PilotLoadStateOps.ConfigIndex] = {
 ENABLE UNWIND => PilotLoadStateOps.ReleaseLoadState[];
 map: PilotLoadStateOps.Map;
 [] ← PilotLoadStateOps.InputLoadState[];
 map ← PilotLoadStateOps.GetMap[index];
 PilotLoadStateOps.RemoveConfig[map, index];
 PilotLoadStateOps.ReleaseMap[map];
 PilotLoadStateOps.ReleaseLoadState[];
 };


-- this procedure may be forked forked
StartProcedure: PROC[g: CT.Global, starr: LIST OF REF StartElemRecord] = {
 i: CARDINAL ← 0;
 {
 ENABLE ABORTED, IO.UserAborted => GOTO out;
 FOR l: LIST OF REF StartElemRecord ← starr, l.rest UNTIL l = NIL DO
  i ← i + 1;
  IF l.first.frame.started THEN
   g.ttyout.PutF["Error - element %d of start list has already been started.\n",
    IO.card[i]]
  ELSE
   START l.first.prog;
  ENDLOOP;
 EXITS
 out => NULL;
 };
 g.dout.PutF["All %d modules have been started.\n", IO.card[i]];
 g.ttyout.PutF["--------------------------------\n"];
 };

-- the load state is locked for the entire time
LoadBcds: PROC[g: CT.Global] = {
 ENABLE UNWIND => PilotLoadStateOps.ReleaseLoadState[];
 p: LONG CARDINAL;
 configGfi: PrincOps.GFTIndex ← 1;
 mi: CT.MI;

 p ← System.PulsesToMicroseconds[System.GetClockPulses[]];
 g.dout.PutF["Phase 1: Load in bcd headers... \n"];
 FOR l: CT.ModuleList ← g.moduleList, l.rest UNTIL l = NIL DO
  mi ← l.first;
  FillInCap[mi];
  IF NOT mi.definitions THEN {
   IF NOT mi.bcdValid THEN
    g.ttyout.PutF["Error - can't load %s since its not on the disk.\n",
     IO.rope[mi.bcdFileName]]
   ELSE {
    IF mi.loadInfoSeq = NIL THEN {
     [mi.loadInfoSeq, configGfi] ←
      CTLoad.LoadGlobalFrames[mi.bcdCap, g.configindex, configGfi, g.ttyout
      ! CTLoad.InvalidFile => -- not caught at this level
       g.ttyout.PutF["Error - invalid bcd %s\n", IO.rope[mi.bcdFileName]]
       ];
     SetUpExports[g, mi];
     }
    ELSE IF mi.loadInfoSeq.mustreplace THEN {
     replaceResult: CTLoad.ReplaceResult;
     replaceResult ← CTLoad.LoadIncremental[mi.bcdCap, mi.loadInfoSeq, g.ttyout];
     SELECT replaceResult FROM
     ok => NULL;
     configNotReplaceable =>
      g.ttyout.PutF["Load of %s failed, is a config.\n", IO.rope[mi.bcdFileName]];
     frameTooBig =>
      g.ttyout.PutF["Load of %s failed, frame too big.\n", IO.rope[mi.bcdFileName]];
     ngfiTooBig =>
      g.ttyout.PutF["Load of %s failed, # gfis too big.\n", IO.rope[mi.bcdFileName]];
     checkForMRFailed =>
      g.ttyout.PutF["Load of %s failed, outstanding local frames(?).\n",
       IO.rope[mi.bcdFileName]];
     ENDCASE => ERROR;
     }
    ELSE g.dout.PutF["%s does not need to be reloaded.\n",
     IO.rope[mi.bcdFileName]];
    };
   };
  ENDLOOP;
 p ← (System.PulsesToMicroseconds[System.GetClockPulses[]] - p)/1000;
 g.dout.PutF["done (%d millisec).\n", IO.card[p]];
 };
  
-- fill in exported interface records from the bcd
SetUpExports: PROC[g: CT.Global, mi: CT.MI] = {
 interfaceseq, existingseq: CTLoad.InterfaceSeq;
  
 ForEachExports: PROC[eth: BcdOps.EXPHandle, eti: BcdDefs.EXPIndex]
   RETURNS[stop: BOOL] = {
  interfaceseq ← CTLoad.BuildInterface[mi.loadInfoSeq, eth];
  existingseq ← LookForExp[g, interfaceseq];
  IF existingseq ~= NIL THEN
   PLUSExp[g, existingseq, interfaceseq, mi.bcdFileName]
  ELSE
   AddExp[g, interfaceseq];
  RETURN[FALSE];
  };
  
 IF mi.loadInfoSeq.bcdbase.nModules = 1 THEN {
  -- build interface record for a compiler-produced module
  interfaceseq ← CTLoad.BuildFramePtrInterface[mi.loadInfoSeq.bcdbase, mi.loadInfoSeq[0].frame];
  AddExp[g, interfaceseq];
  };
 [] ← BcdOps.ProcessExports[mi.loadInfoSeq.bcdbase, ForEachExports];
 };

AddExp: PROC[g: CT.Global, interfaceseq: CTLoad.InterfaceSeq] = {
 g.moduleExports ← CONS[interfaceseq, g.moduleExports];
 };

LookForExp: PROC[g: CT.Global, interfaceseq: CTLoad.InterfaceSeq] RETURNS[CTLoad.InterfaceSeq]= {
 FOR l: LIST OF CTLoad.InterfaceSeq ← g.moduleExports, l.rest UNTIL l = NIL DO
  IF l.first.bcdVers = interfaceseq.bcdVers THEN RETURN[l.first];
  ENDLOOP;
 RETURN[NIL];
 };

-- existingseq ← existingseq PLUS interfaceseq
PLUSExp: PROC[g: CT.Global, existingseq, interfaceseq: CTLoad.InterfaceSeq,
 exporterName: Rope.Text] = {
 existingseq.resolved ← TRUE;
 FOR i: CARDINAL IN [0 .. interfaceseq.size) DO
  IF NOT EmptyLink[existingseq[i].clink]
   AND NOT EmptyLink[interfaceseq[i].clink] THEN
    g.ttyout.PutF["Error -- more than one exporter of item #%d in interface %s (%s is an exporter).\n",
      IO.card[i], IO.rope[existingseq.name], IO.rope[exporterName]]
  ELSE IF EmptyLink[existingseq[i].clink] THEN
   existingseq[i] ← interfaceseq[i];
  IF EmptyLink[existingseq[i].clink] THEN
   existingseq.resolved ← FALSE;
  ENDLOOP;
 };

EmptyLink: PROC[link: PrincOps.ControlLink] RETURNS[empty: BOOL] = {
 RETURN[link = PrincOps.UnboundLink OR link = PrincOps.NullLink];
 };
  
ComputeOutsideImports: PROC[g: CT.Global] = {
 bcdbase: BcdOps.BcdBase;
 mi: CT.MI;
  
  ForEachImport: PROC[ith: BcdOps.IMPHandle, iti: BcdDefs.IMPIndex]
   RETURNS[stop: BOOL] = {
  fth: BcdOps.FTHandle;
  stop ← FALSE;
  fth ← @LOOPHOLE[bcdbase + bcdbase.ftOffset, BcdDefs.Base][ith.file];
  FOR l: LIST OF CTLoad.InterfaceSeq ← g.moduleExports, l.rest UNTIL l = NIL DO
   IF l.first.bcdVers = fth.version AND l.first.resolved THEN RETURN;
   ENDLOOP;
  -- not exported by us, must be from outside
  UpdateOutsideImports[g, CTLoad.NSToRope[bcdbase, fth.name], fth.version];
  };
  
 FOR l: CT.ModuleList ← g.moduleList, l.rest UNTIL l = NIL DO
  mi ← l.first;
  IF mi.loadInfoSeq ~= NIL THEN {
   bcdbase ← mi.loadInfoSeq.bcdbase;
   [] ← BcdOps.ProcessImports[mi.loadInfoSeq.bcdbase, ForEachImport];
   };
  ENDLOOP;
 };

UpdateOutsideImports: PROC[g: CT.Global, name: Rope.Text, intvers: TimeStamp.Stamp] = {
 FOR l: LIST OF CT.OutsideImports ← g.outsideImports, l.rest UNTIL l = NIL DO
  IF l.first.bcdVers = intvers THEN RETURN;
  ENDLOOP;
 g.outsideImports ← CONS[NEW[CT.OutsideImportsRecord
   ← [name: name, bcdVers: intvers]], g.outsideImports];
 };

-- locks the load state
FillInImportsFromLoadState: PROC[g: CT.Global, replacement: BOOL] = {
 ENABLE UNWIND => PilotLoadStateOps.ReleaseLoadState[];
 nbcds: CARDINAL;
 p: LONG CARDINAL;
 p ← System.PulsesToMicroseconds[System.GetClockPulses[]];
 g.dout.PutF["Filling in from Pilot load state... "];
 nbcds ← PilotLoadStateOps.InputLoadState[];
 FOR l: LIST OF CT.OutsideImports ← g.outsideImports, l.rest UNTIL l = NIL DO {
  ir: CedarLinkerOps.IR;
  interfaceseq: CTLoad.InterfaceSeq;
  IF replacement AND l.first.interfaceseq ~= NIL THEN LOOP;
  [interface: ir] ← CedarLinkerOps.GetIR[atom: Atom.MakeAtom[l.first.name],
    version: l.first.bcdVers
    ! Loader.Error => {
     IF type = versionMismatch THEN {
      g.ttyout.PutF["Error - version mismatch on %s\n",
       IO.string[LOOPHOLE[message]]];
      PilotLoadStateOps.ReleaseLoadState[];
      GOTO inputForNextIR;
      };
     PilotLoadStateOps.ReleaseLoadState[]; -- reject it
     }];
  IF ir = NIL THEN {
   -- this is one of
   --  1) an imported module from the loadstate
   --  2) an imported interface that is all-INLINES
   --  3) or an imported interface that no one exports (error)
   LOOP;
   }
  ELSE {
   interfaceseq ← CTLoad.AllocateInterfaceSeq[l.first.name, ir.size];
   interfaceseq.bcdVers ← l.first.bcdVers;
   FOR i: CARDINAL IN [0 .. ir.size) DO
    interfaceseq[i] ← [clink: ir[i], blink: BcdDefs.NullLink];
    ENDLOOP;
   interfaceseq.size ← ir.size;
   };
  IF l.first.interfaceseq ~= NIL THEN -- have to plus together
   PLUSExp[g, l.first.interfaceseq, interfaceseq, "Unknown module"]
  ELSE
   l.first.interfaceseq ← interfaceseq;
  EXITS -- only called when there is an unbound import
  inputForNextIR => nbcds ← PilotLoadStateOps.InputLoadState[];
  } ENDLOOP;
 PilotLoadStateOps.ReleaseLoadState[];
 p ← (System.PulsesToMicroseconds[System.GetClockPulses[]] - p)/1000;
 g.dout.PutF["done (%d millisec).\n", IO.card[p]];
 };

LookupFrame: PROC[g: CT.Global, bcdbase: BcdOps.BcdBase, intno: CARDINAL]
 RETURNS[CTLoad.InterfaceSeq] = {
 i: INT ← -1;
 name: Rope.Text;
 version: TimeStamp.Stamp;

  ForEachImport: PROC[ith: BcdOps.IMPHandle, iti: BcdDefs.IMPIndex]
   RETURNS[stop: BOOL] = {
  fth: BcdOps.FTHandle;
  stop ← FALSE;
  i ← i + 1;
  IF i < intno THEN RETURN[FALSE];
  fth ← @LOOPHOLE[bcdbase + bcdbase.ftOffset, BcdDefs.Base][ith.file];
  name ← CTLoad.NSToRope[bcdbase, ith.name];
  version ← fth.version;
  RETURN[TRUE];
  };
  
 {
 interfaceseq: CTLoad.InterfaceSeq;
 clink: PrincOps.ControlLink;
 atom: ATOM;
 old: REF BcdDefs.VersionStamp;
 [] ← BcdOps.ProcessImports[bcdbase, ForEachImport];
 IF name = NIL THEN ERROR;
 FOR l: LIST OF REF CT.FrameListRecord ← g.frameInterfaces, l.rest UNTIL l = NIL DO
  IF l.first.version = version
  AND CS.EqualRope[l.first.name, name]
  THEN RETURN[l.first.interfaceseq];
  ENDLOOP;
 atom ← Atom.MakeAtom[name];
 old ← NEW[BcdDefs.VersionStamp ← version];
 Atom.PutProp[atom, $version, old];
 -- try for imported module, this is very expensive
 clink ← LoaderPrivate.GetModuleLink[atom: atom];
 IF NOT EmptyLink[clink] THEN {
  -- found
  interfaceseq ← CTLoad.AllocateInterfaceSeq[name, 1];
  interfaceseq[0] ← [clink: clink, blink: BcdDefs.NullLink];
  interfaceseq.bcdVers ← version;
  interfaceseq.size ← 1;
  }
 ELSE interfaceseq ← NIL;
 -- caches result so GetModuleLink is only called once per name
 g.frameInterfaces ← CONS[NEW[CT.FrameListRecord ←
  [name: name, version: version, interfaceseq: interfaceseq]], g.frameInterfaces];
 RETURN[interfaceseq];
 }};

-- fill in links
ResolveImports: PROC[g: CT.Global] = {
 mi: CT.MI;
 FOR l: CT.ModuleList ← g.moduleList, l.rest UNTIL l = NIL DO
  mi ← l.first;
  IF mi.loadInfoSeq ~= NIL THEN FillInImports[g, mi];
  ENDLOOP;
 };

-- called for loaded bcd to fill in all its links!
FillInImports: PROC[g: CT.Global, mi: CT.MI] = {
 bcdbase: BcdOps.BcdBase;
 dummymapseq: CTLoad.DummyMapSeq;
 impexpseq: CTLoad.ImpExpSeq;
 mod, imp: CARDINAL ← 0;
 namestring: BcdOps.NameString;

  ForEachImport: PROC[ith: BcdOps.IMPHandle, iti: BcdDefs.IMPIndex]
   RETURNS[stop: BOOL] = {
  fth: BcdOps.FTHandle;
  stop ← FALSE;
  FOR i: CARDINAL IN [0 .. ith.ngfi) DO
   dummymapseq[ith.gfi + i] ← [ind: imp, whichone: i];
   ENDLOOP;
  -- handle funny cases where two instances are imported
  -- of the same interface
  IF ith.gfi = dummymapseq.size THEN
   dummymapseq.size ← dummymapseq.size + ith.ngfi;
  fth ← @LOOPHOLE[bcdbase + bcdbase.ftOffset, BcdDefs.Base][ith.file];
  impexpseq[imp] ← LookUpInterface[g, fth.version];
  -- impexpseq[imp] may be NIL
  IF FALSE AND impexpseq[imp] = NIL THEN {
   -- generates spurious warnings about Inline, etc.
   sym: Rope.Text ← CTLoad.NSToRope[bcdbase, ith.name];
   g.ttyout.PutF["Warning- cannot find exporter of %s anywhere.\n",
    IO.rope[sym]];
   };
  imp ← imp + 1;
  };
  
  ForEachModule: PROC[mth: BcdOps.MTHandle, mti: BcdDefs.MTIndex]
   RETURNS[stop: BOOL] = {
  resolved, bound: BOOL;
  clink: PrincOps.ControlLink;
  stop ← FALSE;
  resolved ← TRUE;
  [] ← CTLoad.OpenLinkSpace[mi.loadInfoSeq[mod].frame, mth, bcdbase];
  FOR i: CARDINAL IN [0 .. PilotLoaderOps.LinkSegmentLength[mth, bcdbase]) DO
   clink ← CTLoad.ReadLink[i];
   [clink, bound] ← NewLink[g: g, blink: PilotLoaderOps.IthLink[mth, i, bcdbase],
    oldclink: clink, mi: mi, frame: mi.loadInfoSeq[mod].frame,
    mth: mth, bcdbase: bcdbase, linkinx: i];
   IF bound THEN
    CTLoad.WriteLink[offset: i, link: clink]
   ELSE resolved ← FALSE;
   ENDLOOP;
  CTLoad.CloseLinkSpace[mi.loadInfoSeq[mod].frame];
  IF NOT resolved THEN mi.loadInfoSeq.linksresolved ← FALSE;
  mod ← mod + 1;
  };
  
 bcdbase ← mi.loadInfoSeq.bcdbase;
 namestring ← LOOPHOLE[bcdbase + bcdbase.ssOffset];
 IF bcdbase.nImports = 0 THEN RETURN; -- no imports
 dummymapseq ← mi.loadInfoSeq.dummymapseq;
 impexpseq ← mi.loadInfoSeq.impexpseq;
 -- the first part of dummymapseq, the map between config gfi's and real gfi's,
 -- has already been computed
 -- set up map between dummygfi's and the import table
 -- IF dummymapseq.size > bcdbase.firstdummy THEN ERROR;
 dummymapseq.size ← bcdbase.firstdummy; -- adjust for dummies to come
 [] ← BcdOps.ProcessImports[bcdbase, ForEachImport];
 impexpseq.size ← imp;
 IF NOT mi.loadInfoSeq.linksresolved THEN {
  mi.loadInfoSeq.linksresolved ← TRUE;
  -- now run thru the frame links looking for imports to fill in
  [] ← BcdOps.ProcessModules[bcdbase, ForEachModule];
  };
 };

NewLink: PROC[g: CT.Global, blink: BcdDefs.Link, oldclink: PrincOps.ControlLink, mi: CT.MI,
 frame: PrincOps.GlobalFrameHandle, mth: BcdOps.MTHandle, bcdbase: BcdOps.BcdBase,
 linkinx: CARDINAL]
 RETURNS[newclink: PrincOps.ControlLink, resolved: BOOL] = {
 loadinfoseq: CTLoad.LoadInfoSeq;

  FindLink: PROC[blink: BcdDefs.Link] RETURNS[PrincOps.ControlLink, BOOL] = {
  IF blink.gfi < loadinfoseq.bcdbase.firstdummy THEN {
   SELECT blink.vtag FROM
   proc0, proc1 => {
    rgfi: PrincOps.GFTIndex;
    rgfi ← loadinfoseq.dummymapseq[blink.gfi].ind;
    newclink ← CTLoad.ConvertLink[blink];
    newclink.gfi ← rgfi +
     loadinfoseq.dummymapseq[blink.gfi].whichone;
    resolved ← rgfi ~= PrincOps.GFTNull;
    };
   var => {
    [link: newclink] ← CedarLinkerOps.FindVariableLink[bcd: loadinfoseq.bcdbase,
      mthLink: blink, rgfi: loadinfoseq.dummymapseq[blink.vgfi].ind];
    resolved ← NOT EmptyLink[newclink];
    };
   ENDCASE => NULL;
   }
  ELSE {
   trueep: CARDINAL;
   intno: CARDINAL;
   interfaceseq: CTLoad.InterfaceSeq;
   intno ← loadinfoseq.dummymapseq[blink.gfi].ind;
   interfaceseq ← loadinfoseq.impexpseq[intno];
   trueep ← blink.ep + (loadinfoseq.dummymapseq[blink.gfi].whichone
    * BcdDefs.EPLimit);
   IF interfaceseq = NIL THEN -- try the module frame table
    interfaceseq ← LookupFrame[g, loadinfoseq.bcdbase, intno];
   -- import not satisfied?
   IF interfaceseq = NIL OR EmptyLink[interfaceseq[trueep].clink] THEN {
    ith: BcdOps.IMPHandle;
    fth: BcdOps.FTHandle;
    sym: Rope.Text;
    ith ← GetImpHandle[loadinfoseq.bcdbase, intno];
    fth ← @LOOPHOLE[loadinfoseq.bcdbase + loadinfoseq.bcdbase.ftOffset, BcdDefs.Base][ith.file];
    sym ← CTLoad.NSToRope[loadinfoseq.bcdbase, ith.name];
    g.ttyout.PutF["Warning - Unable to resolve import of item #%d from interface %s\n\tof %a ",
     IO.card[trueep], IO.rope[sym], CS.MakeTS[fth.version]];
    g.ttyout.PutF["(the %dth import of %s).\n",
     IO.card[intno], IO.rope[mi.bcdFileName]];
    UpdatePendingList[sym, frame, mth, bcdbase, linkinx, trueep];
    RETURN[oldclink, FALSE];
    };
   -- at this point module and variable links are
   -- set to their absolute addresses
   newclink ← interfaceseq[trueep].clink;
   resolved ← TRUE;
   };
  RETURN[newclink, resolved];
  };
  
 loadinfoseq ← mi.loadInfoSeq;
 newclink ← oldclink;
 resolved ← FALSE;
 SELECT blink.vtag FROM
 proc0, proc1 => IF EmptyLink[oldclink] THEN
  [newclink, resolved] ← FindLink[blink];
 var => IF EmptyLink[oldclink] THEN
  [newclink, resolved] ← FindLink[blink];
 ENDCASE => newclink ← LOOPHOLE[blink.typeID];
 };
  
UpdatePendingList: PROC[name: Rope.Text, frame: PrincOps.GlobalFrameHandle,
 mth: BcdOps.MTHandle, bcdbase: BcdOps.BcdBase, linkinx, trueep: CARDINAL] = {
 atom: ATOM ← AtomsPrivate.UnsafeMakeAtom[LOOPHOLE[name]];
 pending: CedarLinkerOps.PendingList ← CedarLinkerOps.GetPendingList[atom];
 -- pendingCount ← pendingCount + 1;
 pending ← CONS[[frame, mth, bcdbase, linkinx, LOOPHOLE[trueep]], pending];
 CedarLinkerOps.SetPendingList[atom, pending];
 };
  
 -- intno starts at 0
GetImpHandle: PROC[bcdbase: BcdOps.BcdBase, intno: CARDINAL]
  RETURNS[ith: BcdOps.IMPHandle] = {
 RETURN[@LOOPHOLE[bcdbase + bcdbase.impOffset, BcdDefs.Base]
  [LOOPHOLE[intno*SIZE[BcdDefs.IMPRecord], BcdDefs.IMPIndex]]];
 };

-- call Paul Rovner's procedure to fixup the Cedar Atoms and Strings section
ProcessCedarBcds: PROC[g: CT.Global, replacement: BOOL] = {
 loadinfoseq: CTLoad.LoadInfoSeq;
 bcdbase: BcdOps.BcdBase;
 mi: CT.MI;
 FOR l: CT.ModuleList ← g.moduleList, l.rest UNTIL l = NIL DO
  mi ← l.first;
  IF mi.loadInfoSeq = NIL THEN LOOP;
  loadinfoseq ← mi.loadInfoSeq;
  bcdbase ← loadinfoseq.bcdbase;
  IF NOT bcdbase.extended THEN LOOP;
  IF replacement AND NOT loadinfoseq.mustreplace THEN LOOP;
  loadinfoseq.mustreplace ← FALSE;
  RTLoader.AcquireTypesAndLiterals[bcd: bcdbase, map: loadinfoseq.map];
  ENDLOOP;
 };

LookUpInterface: PROC[g: CT.Global, bcdVers: TimeStamp.Stamp]
 RETURNS[int: CTLoad.InterfaceSeq] = {
 FOR l: LIST OF CTLoad.InterfaceSeq ← g.moduleExports, l.rest UNTIL l = NIL DO
  IF l.first.bcdVers = bcdVers THEN RETURN[l.first];
  ENDLOOP;
 FOR l: LIST OF CT.OutsideImports ← g.outsideImports, l.rest UNTIL l = NIL DO
  IF l.first.bcdVers = bcdVers THEN RETURN[l.first.interfaceseq];
  ENDLOOP;
 RETURN[NIL]; -- not found
 };

FillInCap: PROC[mi: CT.MI] = {
 IF mi.bcdCap ~= File.nullCapability THEN RETURN;
 mi.bcdCap ← Directory.Lookup[fileName: LOOPHOLE[mi.bcdFileName],
  permissions: Directory.ignore
  ! Directory.Error => {
   mi.bcdValid ← FALSE;
   GOTO out;
   };
  ];
 mi.bcdValid ← TRUE;
 IF mi.srcFileName = NIL THEN { -- object file only, chk if defs
  bcdSpace: Space.Handle;
  bcd: BcdOps.BcdBase;
  bcdSpace ← Space.Create[size: 1, parent: Space.virtualMemory];
 Space.Map[space: bcdSpace, window: [file: mi.bcdCap, base: 1]];
 bcd ← Space.LongPointer[bcdSpace];
  mi.definitions ← bcd.definitions;
  mi.bcdVers ← bcd.version;
  Space.Delete[bcdSpace];
  };
EXITS
 out => NULL;
 };

PutExportsInLoadState: PROC[g: CT.Global] = {
 mi: CT.MI;
 FOR l: CT.ModuleList ← g.moduleList, l.rest UNTIL l = NIL DO
  mi ← l.first;
  IF mi.exportedInterface THEN {
   FOR l: LIST OF CTLoad.InterfaceSeq ← g.moduleExports, l.rest UNTIL l = NIL DO
    IF l.first.bcdVers = mi.bcdVers THEN {
     AddInterfaceToLoadState[g, l.first];
     -- g.ttyout.PutF["%s exported into load state.\n", IO.rope[l.first.name]];
     EXIT;
     };
    REPEAT
    FINISHED => {
     g.ttyout.PutF["Error - cannot find %s exported by this program.\n",
      IO.rope[mi.bcdFileName]];
     };
    ENDLOOP;
   };
  ENDLOOP;
 -- process any pendings we have found
 IF g.toBeProcessed ~= NIL THEN
  CedarExporterImpl.ProcessPendingEntries[g.toBeProcessed];
 };

AddInterfaceToLoadState: PROC[g: CT.Global, interfaceseq: CTLoad.InterfaceSeq] = {
 atom: ATOM ← AtomsPrivate.UnsafeMakeAtom[LOOPHOLE[interfaceseq.name]];
 pending: CedarLinkerOps.PendingList;
 interface: CedarLinkerOps.IR;
 interface ← CedarLinkerOps.GetIR[atom, interfaceseq.bcdVers, interfaceseq.size].interface;
 FOR i: CARDINAL IN [0 .. interfaceseq.size) DO
  IF NOT EmptyLink[interfaceseq[i].clink] THEN
   interface[i] ← interfaceseq[i].clink;
  ENDLOOP;
 -- fill in any importers
 pending ← CedarLinkerOps.GetPendingList[atom];
 IF pending ~= NIL THEN {
  [g.toBeProcessed, pending] ← CedarExporterImpl.SaveResolvedEntries[g.toBeProcessed,
   pending, interface];
  CedarLinkerOps.SetPendingList[atom, pending];
  };
 };

ComingleImportsAndExports: PROC[g: CT.Global] = {
 firstpref, secondpref: CTLoad.InterfaceSeq;
 FOR l: LIST OF CTLoad.InterfaceSeq ← g.moduleExports, l.rest UNTIL l = NIL DO
  IF NOT l.first.resolved THEN {
   FOR r: LIST OF CT.OutsideImports ← g.outsideImports, r.rest UNTIL r = NIL DO
    IF l.first.bcdVers = r.first.bcdVers AND r.first.interfaceseq ~= NIL THEN {
     -- imported from outside and exported
     -- inside.
     -- add things from outside if needed
     firstpref ← l.first;
     secondpref ← r.first.interfaceseq;
     firstpref.resolved ← TRUE;
     FOR i: CARDINAL IN [0 .. firstpref.size) DO
      IF EmptyLink[firstpref[i].clink] THEN
       firstpref[i] ← secondpref[i];
      IF EmptyLink[firstpref[i].clink] THEN
       firstpref.resolved ← FALSE;
      ENDLOOP;
     EXIT;
     };
    ENDLOOP;
   };
  ENDLOOP;
 };

}.