-- SMLoadImpl.mesa
-- last edit by Schmidt, May 13, 1983 2:58 pm
-- last edit by Satterthwaite, August 15, 1983 11:44 am
-- Mesa 7.0/ Pilot 6.0
-- procedures to load and start modules in the new modeller

-- 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

DIRECTORY
  Atom: TYPE USING [MakeAtom],
  BcdDefs: TYPE USING [
    Base, FTIndex, FTSelf, GFTIndex, Link, MTIndex, NameRecord,
    UnboundLink, VersionID],
  BcdOps: TYPE USING [
    BcdBase, EXPHandle, FTHandle, MTHandle, NameString, ProcessModules],
  CedarLinkerOps: TYPE USING [FindVariableLink],
  Directory: TYPE USING [UpdateDates],
  Environment: TYPE USING [PageCount, PageNumber],
  File: TYPE USING [Capability, PageCount, PageNumber, read, write],
  Frame: TYPE USING [GetReturnLink],
  Inline: TYPE USING [BITAND],
  IO: TYPE USING [card, Handle, PutF, rope],
  LoaderPrivate: TYPE USING[
    AssignCodeToFrames, AssignControlModules, CreateGlobalFrames,
    FindMappedSpace, NextMultipleOfFour],
  PilotLoaderOps: TYPE USING [
    DestroyMap, InitializeMap, IthLink, LinkSegmentLength],
  PilotLoadStateOps: TYPE USING [ConfigIndex],
  PrincOps: TYPE USING [
    ControlLink, ControlModule, Frame, GFTIndex, GlobalFrameHandle,
    NullLink, StateVector, UnboundLink],
  PrincOpsRuntime: TYPE USING [GetFrame, GFT],
  Rope: TYPE USING [Flatten, FromProc, ROPE, Text],
  RTOS: TYPE USING [CheckForModuleReplacement],
  Runtime: TYPE USING [ValidateGlobalFrame],
  RuntimeInternal: TYPE USING [Codebase],
  SDDefs: TYPE USING [SD, sStart, sSwapTrap],
  SMLoad: TYPE USING [
    GfiMap, GfiMapSeq, InterfaceRecordRep, IR, IRSeq, IRSeqRecord,
    LoadInfo, LoadInfoRecord, ReplaceResult],
  Space: TYPE USING [
    Create, Delete, Error, GetHandle, GetWindow, Handle, LongPointer, MakeReadOnly,
    MakeWritable, Map, PageFromLongPointer, virtualMemory],
  TimeStamp: TYPE USING [Stamp],
  Trap: TYPE USING [ReadOTP];

SMLoadImpl: MONITOR 
      IMPORTS
	Atom, BcdOps, CedarLinkerOps, Directory, Frame, Inline, IO, LoaderPrivate,
	PilotLoaderOps, PrincOpsRuntime, Rope, RTOS, Runtime, RuntimeInternal, Space, Trap
      EXPORTS SMLoad
      SHARES File ~ {

 -- MDS Usage!
  waitCodeTrapCV: CONDITION;
 -- link space data
  links: LONG POINTER TO ARRAY[0 .. 0) OF PrincOps.ControlLink ← NIL;
  writeable: BOOL ← FALSE;
  long: BOOL ← FALSE;
 -- end of MDS usage

  LoadGlobalFrames: PUBLIC PROC[
	cap: File.Capability, config: PilotLoadStateOps.ConfigIndex,
	oldConfigGfi: PrincOps.GFTIndex, out: IO.Handle] 
      RETURNS[loadInfo: SMLoad.LoadInfo, newConfigGfi: PrincOps.GFTIndex] ~ {
    bcdBase: BcdOps.BcdBase;
    mod: NAT ← 0;
    gfiMap: SMLoad.GfiMap;
	
    ForEachModule: PROC[mth: BcdOps.MTHandle, mti: BcdDefs.MTIndex]
	RETURNS[stop: BOOL←FALSE] ~ {
      gfi: NAT ~ loadInfo.map[mth.gfi];
      frame: PrincOps.GlobalFrameHandle ~ 
        PrincOpsRuntime.GetFrame[PrincOpsRuntime.GFT[gfi]];
      loadInfo[mod] ← [frame~frame, frameSize~mth.framesize, nGfi~mth.ngfi];
      newConfigGfi ← newConfigGfi + mth.ngfi;
      -- now store in cgfi to rgfi map
      FOR i: NAT IN [0 .. mth.ngfi) DO
	gfiMap[mth.gfi+i] ← [index~gfi, whichOne~i];
	ENDLOOP;
      gfiMap.size ← gfiMap.size + mth.ngfi;
      mod ← mod + 1;
      out.PutF["Load %s .. gfi = %bB\n", IO.rope[NSToRope[bcdBase, mth.name]], IO.card[gfi]];
      };
			
    cap ← Directory.UpdateDates[cap, File.read];
    bcdBase ← LoadUpBcd[cap, 1].bcd;
    loadInfo ← AllocateLoadInfo[bcdBase];
    loadInfo.configGfi ← oldConfigGfi;
    newConfigGfi ← oldConfigGfi;
    gfiMap ← loadInfo.gfiMap;
    gfiMap[0] ← [0, 0];
    gfiMap.size ← 1;	-- dummy module indices start at 1, not 0
    loadInfo.map ← PilotLoaderOps.InitializeMap[bcdBase];
    -- map is filled in by CreateGlobalFrames
    loadInfo.frameList ← LoaderPrivate.CreateGlobalFrames[bcdBase, loadInfo.map, config, FALSE];
    [] ← BcdOps.ProcessModules[bcdBase, ForEachModule];
    LoaderPrivate.AssignCodeToFrames[bcdBase, cap, 1, loadInfo.map];
    SetLinksToNull[bcdBase, loadInfo];
    loadInfo.cm ← LoaderPrivate.AssignControlModules[bcdBase, loadInfo.map]};
	
  WaitForBroadcast: ENTRY PROC[frame: PrincOps.GlobalFrameHandle] ~ {
    WHILE frame.code.out DO WAIT waitCodeTrapCV ENDLOOP};

 -- can only be called for modules (not configs)
  LoadIncremental: PUBLIC ENTRY PROC[
	bcdcap: File.Capability, loadInfo: SMLoad.LoadInfo, out: IO.Handle] 
      RETURNS[SMLoad.ReplaceResult] ~ {
    saveModellerCode: PROC ← NIL;
    codeTrapFrame: PrincOps.GlobalFrameHandle ← NIL;
      {
      ENABLE
        UNWIND => {
          IF saveModellerCode ~= NIL THEN
            SDDefs.SD[SDDefs.sSwapTrap] ← saveModellerCode;
          };
	
      ModellerCodeTrap: PROC ~ { 
	start: PROC[PrincOps.ControlModule];
	dest: PrincOps.ControlLink;
	state: PrincOps.StateVector;
	frame: PrincOps.GlobalFrameHandle;
	state ← STATE;
	dest ← Trap.ReadOTP[];
	state.dest ← Frame.GetReturnLink[];
	DO
	  IF dest.proc THEN {
	    frame ← PrincOpsRuntime.GetFrame[PrincOpsRuntime.GFT[dest.gfi]]; 
	    EXIT}
	  ELSE IF dest.indirect THEN dest ← dest.link↑
	  ELSE {frame ← dest.frame.accesslink; EXIT}; -- frame
	  ENDLOOP;
	IF frame = codeTrapFrame THEN {
	  -- this halts outside process until my procedure is finished
	  WaitForBroadcast[frame];
	  RETURN};
	IF ~frame.started THEN {
	  start ← LOOPHOLE[SDDefs.SD[SDDefs.sStart]];
	  start[[frame[frame]]]};
	frame.code.out ← FALSE;
	RETURN WITH state};

      n: CARDINAL;
      mname: Rope.Text;
      gfi: CARDINAL;
      bcdBase: BcdOps.BcdBase;
      mth: BcdOps.MTHandle;
      frame: PrincOps.GlobalFrameHandle ~ loadInfo[0].frame;
      IF loadInfo.size ~= 1 THEN RETURN[$configNotReplaceable];
      bcdBase ← LoadUpBcd[bcdcap, 1].bcd;
      loadInfo.bcdBase ← bcdBase;
      loadInfo.cm ← LOOPHOLE[frame];
      -- recompute these since bcdBase.firstdummy, bcdBase.nDummies, and bcdBase.nImports
      -- may have changed
      ReSizeMaps[loadInfo];
      mth ← @LOOPHOLE[bcdBase+bcdBase.mtOffset, BcdDefs.Base][BcdDefs.MTIndex.FIRST];
      -- checking
      IF LOOPHOLE[frame+mth.framesize, CARDINAL] > 
       LOOPHOLE[LoaderPrivate.NextMultipleOfFour[frame+loadInfo[0].frameSize], CARDINAL] THEN 
	RETURN[$frameTooBig];
      IF mth.ngfi > loadInfo[0].nGfi THEN RETURN[$ngfiTooBig];

      -- now think of monitor:
      -- set lock by setting code trap, then call check procedure
      -- then do replacement, then release lock
      -- this is to avoid the case where some local frames
      -- are created after the call on RTOS.CheckForModuleReplacemeent
      -- but before I swap the code
      codeTrapFrame ← frame;
      saveModellerCode ← SDDefs.SD[SDDefs.sSwapTrap];
      SDDefs.SD[SDDefs.sSwapTrap] ← ModellerCodeTrap;
      frame.code.out ← TRUE;	-- force code trap
      IF ~RTOS.CheckForModuleReplacement[frame] THEN {
	SDDefs.SD[SDDefs.sSwapTrap] ← saveModellerCode;
	RETURN[$checkForMRFailed]};
      PilotLoaderOps.DestroyMap[loadInfo.map];		-- old map
      loadInfo.map ← PilotLoaderOps.InitializeMap[bcdBase];
      -- the dummy bcd #'s start at 1
      FOR i: CARDINAL IN [0 .. bcdBase.firstdummy) DO
	loadInfo.map[i] ← loadInfo.gfiMap[i].index;
	ENDLOOP;
      LoaderPrivate.AssignCodeToFrames[bcdBase, bcdcap, 1, loadInfo.map];
      -- havinge set the code base, we can now release the lock
      frame.code.out ← FALSE;	
      BROADCAST waitCodeTrapCV;
      SDDefs.SD[SDDefs.sSwapTrap] ← saveModellerCode;
      saveModellerCode ← NIL;

      SetLinksToNull[bcdBase, loadInfo];
      mname ← NSToRope[bcdBase, mth.name];
      n ← mth.framesize;
      gfi ← frame.gfi;
      out.PutF["Load %s .. gfi = %bB\n", IO.rope[mname], IO.card[gfi]];
      loadInfo.rtStarted ← FALSE;	-- force AcquireTypesAndLiterals to be called
      RETURN[$ok]};
    };

-- old contents are preserved
  ReSizeMaps: PROC[loadInfo: SMLoad.LoadInfo] ~ {
    newNGfi: NAT ~ loadInfo.bcdBase.firstdummy+loadInfo.bcdBase.nDummies;
    IF newNGfi ~= loadInfo.gfiMap.maxsize THEN {
      oldGfiMap: SMLoad.GfiMap ← loadInfo.gfiMap;
      minSize: NAT ~ MIN[oldGfiMap.size, newNGfi];
      loadInfo.gfiMap ← NEW[SMLoad.GfiMapSeq[newNGfi]];
      FOR i: NAT IN [0 .. minSize) DO loadInfo.gfiMap[i] ← oldGfiMap[i] ENDLOOP;
      loadInfo.gfiMap.size ← minSize;
      FREE[@oldGfiMap]};
    IF loadInfo.bcdBase.nImports ~= loadInfo.imports.size THEN {
      oldImports: SMLoad.IRSeq ← loadInfo.imports;
      minSize: NAT ~ MIN[oldImports.size, loadInfo.bcdBase.nImports];
      loadInfo.imports ← NEW[SMLoad.IRSeqRecord[loadInfo.bcdBase.nImports]];
      FOR i: NAT IN [0 .. minSize) DO loadInfo.imports[i] ← oldImports[i] ENDLOOP;
      FREE[@oldImports]};
    };

  SetLinksToNull: PROC[bcdBase: BcdOps.BcdBase, loadInfo: SMLoad.LoadInfo] ~ {

    ForEachModule: PROC[mth: BcdOps.MTHandle, mti: BcdDefs.MTIndex]
	RETURNS[stop: BOOL←FALSE] ~ {
      -- set all the links to null
      frame: PrincOps.GlobalFrameHandle ~
	PrincOpsRuntime.GetFrame[
	  PrincOpsRuntime.GFT[loadInfo.gfiMap[mth.gfi].index]];
      Runtime.ValidateGlobalFrame[frame];
      [] ← OpenLinkSpace[frame, mth, bcdBase];
      FOR i: CARDINAL IN [0..PilotLoaderOps.LinkSegmentLength[mth, bcdBase]) DO
	WriteLink[
	  offset~i,
	  link~SELECT PilotLoaderOps.IthLink[mth, i, bcdBase].vtag FROM
	          $var, $type => PrincOps.NullLink, 
	          ENDCASE => PrincOps.UnboundLink];
	ENDLOOP;
      CloseLinkSpace[frame]};
	
    [] ← BcdOps.ProcessModules[bcdBase, ForEachModule];
    loadInfo.linksResolved ← FALSE};

  BuildInterface: PUBLIC PROC[loadInfo: SMLoad.LoadInfo, eth: BcdOps.EXPHandle]
      RETURNS[ir: SMLoad.IR] ~ {
    bcdBase: BcdOps.BcdBase ~ loadInfo.bcdBase;
    name: ATOM ~ Atom.MakeAtom[NSToRope[bcdBase, eth.name]];
    fth: BcdOps.FTHandle ~ @LOOPHOLE[bcdBase + bcdBase.ftOffset, BcdDefs.Base][eth.file];
    IF eth.size = 0 THEN RETURN[NIL];
    ir ← AllocateIR[name, eth.size];
    ir.stamp ← fth.version;
    ir.resolved ← TRUE;
    FOR i: CARDINAL IN [0 .. eth.size) DO
      clink: PrincOps.ControlLink;
      cgfi: PrincOps.GFTIndex;	-- dummy
      SELECT eth.links[i].vtag FROM
	$var => {
	  [link~clink] ← CedarLinkerOps.FindVariableLink[
			bcd~loadInfo.bcdBase, 
			mthLink~eth.links[i],
			rgfi~loadInfo.gfiMap[eth.links[i].vgfi].index];
	  ir[i] ← [link~clink]};
	$proc0, $proc1 => {
	  realgfi: PrincOps.GFTIndex ~ loadInfo.gfiMap[eth.links[i].gfi].index;
	  [clink, cgfi] ← ConvertDummyLinkToControlLink[
			eth.links[i], realgfi, bcdBase, loadInfo];
	  ir[i] ← [link~clink]};
	$type => 	-- means no checking for exported type mismatches!!!
	  ir[i] ← [link~PrincOps.NullLink];
	ENDCASE => ERROR;
      IF EmptyLink[ir[i].link] THEN ir.resolved ← FALSE;
      ENDLOOP;
    };
	
  EmptyLink: PROC[link: PrincOps.ControlLink] RETURNS[BOOL] ~ {
    RETURN[link = PrincOps.UnboundLink OR link = PrincOps.NullLink]};
		
 -- can't be used for configs
  BuildFramePtrInterface: PUBLIC PROC[
	bcdBase: BcdOps.BcdBase, frame: PrincOps.GlobalFrameHandle] 
      RETURNS[ir: SMLoad.IR] ~ {
    name: ATOM;
    mth: BcdOps.MTHandle;
    IF bcdBase.nModules ~= 1 THEN ERROR;
    mth ← @LOOPHOLE[bcdBase+bcdBase.mtOffset, BcdDefs.Base][BcdDefs.MTIndex.FIRST];
    name ← Atom.MakeAtom[NSToRope[bcdBase, mth.name]];
    ir ← AllocateIR[name, 1];
    IF mth.file = BcdDefs.FTSelf THEN
      ir.stamp ← bcdBase.version
    ELSE {
      fth: BcdOps.FTHandle ~ @LOOPHOLE[bcdBase+bcdBase.ftOffset, BcdDefs.Base][mth.file];
      ir.stamp ← fth.version};
    ir[0] ← [link~LOOPHOLE[frame]]};
	
 -- only works for exported BcdDefs.Links in the export table
  ConvertDummyLinkToControlLink: PROC[
	bl: BcdDefs.Link, realgfi: CARDINAL,
	bcdBase: BcdOps.BcdBase, loadInfo: SMLoad.LoadInfo] 
      RETURNS[cl: PrincOps.ControlLink, newcgfi: PrincOps.GFTIndex] ~ {
    cgfi: PrincOps.GFTIndex;

    ForEachModule: PROC [mth: BcdOps.MTHandle, mti: BcdDefs.MTIndex] 
	RETURNS [BOOL] ~ {
      mgfi: PrincOps.GFTIndex ~ mth.gfi;
      IF cgfi IN [mth.gfi..mgfi + mth.ngfi) THEN  {
	    	newcgfi ← newcgfi + (cgfi - mgfi);
		realgfi ← realgfi + (cgfi - mgfi); 
		RETURN[TRUE];
		};
      RETURN[FALSE]};

    newcgfi ← loadInfo.configGfi;
    IF bl = BcdDefs.UnboundLink THEN RETURN[PrincOps.UnboundLink, newcgfi];
    SELECT bl.vtag FROM
      $var => {
	cgfi ← bl.vgfi;
	IF BcdOps.ProcessModules[bcdBase, ForEachModule].mth = NIL THEN
	  RETURN[PrincOps.NullLink, newcgfi];
	cl ← [procedure[gfi~realgfi, ep~bl.var, tag~FALSE]]};
      $proc0, $proc1 => {
	cgfi ← bl.gfi;
	IF BcdOps.ProcessModules[bcdBase, ForEachModule].mth = NIL THEN
	  RETURN[PrincOps.UnboundLink, newcgfi];
	cl ← [procedure[gfi~realgfi, ep~bl.ep, tag~TRUE]]};
      $type => cl ← LOOPHOLE[bl.typeID];
      ENDCASE;
    };

  NSToRope: PUBLIC PROC[bcdBase: BcdOps.BcdBase, name: BcdDefs.NameRecord] 
      RETURNS[Rope.Text] ~ {
    namestring: BcdOps.NameString ~ LOOPHOLE[bcdBase+bcdBase.ssOffset];
    i: CARDINAL ← 0;
    
    EachChar: SAFE PROC RETURNS[c: CHAR] ~ TRUSTED {
      c ← namestring.string.text[name+i]; i ← i+1; RETURN};
      
    RETURN[Rope.FromProc[namestring.size[name], EachChar].Flatten[]]};

  AllocateLoadInfo: PUBLIC PROC[bcdBase: BcdOps.BcdBase] 
      RETURNS[loadInfo: SMLoad.LoadInfo] ~ {
    loadInfo ← NEW[SMLoad.LoadInfoRecord[bcdBase.nModules]];
    loadInfo.bcdBase ← bcdBase;
    loadInfo.gfiMap ← NEW[SMLoad.GfiMapSeq[bcdBase.firstdummy +bcdBase.nDummies]];
    loadInfo.imports ← NEW[SMLoad.IRSeqRecord[bcdBase.nImports]]};

  FreeLoadInfo: PUBLIC PROC[loadInfo: SMLoad.LoadInfo] RETURNS[SMLoad.LoadInfo] ~ {
    IF loadInfo = NIL THEN RETURN[NIL];
    IF loadInfo.bcdBase ~= NIL THEN
	Space.Delete[Space.GetHandle[Space.PageFromLongPointer[loadInfo.bcdBase]]
	  ! Space.Error => {CONTINUE}];
    loadInfo.imports ← NIL;
    loadInfo.gfiMap ← NIL;
    RETURN[NIL]};

  AllocateIR: PUBLIC PROC[name: ATOM, size: NAT] RETURNS[ir: SMLoad.IR] ~ {
    ir ← NEW[SMLoad.InterfaceRecordRep[size] ← [name~name, body~]];
    FOR i: NAT IN [0 .. size) DO ir[i] ← [PrincOps.NullLink] ENDLOOP};

  FreeIR: PUBLIC PROC[ir: SMLoad.IR] RETURNS[SMLoad.IR] ~ {
    IF ir # NIL THEN {ir.name ← NIL; FREE[@ir]};
    RETURN[NIL]};


  InvalidFile: PUBLIC ERROR [File.Capability] ~ CODE;

  LoadUpBcd: PROC [file: File.Capability, offset: File.PageCount] 
      RETURNS [bcd: BcdOps.BcdBase, pages: CARDINAL] ~ {
    bcdSpaceBase: File.PageNumber ~ offset;
    bcdSpace: Space.Handle ← Space.Create[size~1, parent~Space.virtualMemory];
    bcdSpace.Map[window~[file~file, base~bcdSpaceBase]];
    bcd ← bcdSpace.LongPointer;  pages ← bcd.nPages;
    IF bcd.versionIdent # BcdDefs.VersionID OR bcd.definitions 
     OR (~bcd.tableCompiled AND ~bcd.spare1) THEN
      ERROR InvalidFile[file ! UNWIND => {Space.Delete[bcdSpace]}];
    IF pages > 1 THEN {
      Space.Delete[bcdSpace];
      bcdSpace ← Space.Create[size~pages, parent~Space.virtualMemory];
      bcdSpace.Map[window~[file~file, base~bcdSpaceBase]];
      bcd ← bcdSpace.LongPointer};
    bcdSpace.MakeReadOnly;	-- make bcd header spaces ReadOnly, as the code is already
    RETURN};
  
-- link space operations

  OpenLinkSpace: PUBLIC PROC [
	frame: PrincOps.GlobalFrameHandle, mth: BcdOps.MTHandle, bcd: BcdOps.BcdBase←NIL] 
      RETURNS[LONG POINTER] ~ {
    IF frame.codelinks THEN {
      long ← TRUE; 
      links ← RuntimeInternal.Codebase[LOOPHOLE[frame]];
      IF links = NIL THEN ERROR}
    ELSE {
      long ← FALSE; 
      links ← LOOPHOLE[LONG[frame]]};
    links ← links - PilotLoaderOps.LinkSegmentLength[mth, bcd];
    writeable ← FALSE;
    RETURN[links]};

  ReadLink: PUBLIC PROC [offset: CARDINAL] RETURNS [link: PrincOps.ControlLink] ~ {
    RETURN[links[offset]]};

 WriteLink: PUBLIC PROC [offset: CARDINAL, link: PrincOps.ControlLink] ~ {
    IF long AND ~writeable THEN {
      space: Space.Handle ~ LoaderPrivate.FindMappedSpace[
	Space.GetHandle[Space.PageFromLongPointer[links]]];
      cap: File.Capability ← space.GetWindow.file;
      writeable ← TRUE;
      -- questionable????
      -- IF Inline.BITAND[cap.permissions, File.write] = 0 THEN    
      cap.permissions ← cap.permissions + File.write;
      space.MakeWritable[cap]};
    links[offset] ← link};

  CloseLinkSpace: PUBLIC PROC [frame: PrincOps.GlobalFrameHandle] ~ {
    IF long AND writeable THEN
      Space.MakeReadOnly[LoaderPrivate.FindMappedSpace[
        Space.GetHandle[Space.PageFromLongPointer[links]]]]
    };



  ConvertLink: PUBLIC PROC [bl: BcdDefs.Link] RETURNS [PrincOps.ControlLink] ~ {
    RETURN [IF bl = BcdDefs.UnboundLink
      THEN PrincOps.UnboundLink
      ELSE SELECT bl.vtag FROM
	$var => [procedure[gfi~bl.vgfi, ep~bl.var, tag~FALSE]],
	$proc0, $proc1 => [procedure[gfi~bl.gfi, ep~bl.ep, tag~TRUE]],
	$type => LOOPHOLE[bl.typeID],
	ENDCASE => ERROR]
    };

  }.