-- BootLoader.mesa
-- Last Modified by Sandman,  October 7, 1980  9:04 AM    
-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  AltoDefs USING [PageCount, PageSize],
  BcdDefs USING [
    Base, BCD, CTIndex, CTNull, EPIndex, EVNull, Link, MTIndex, UnboundLink,
    VarLimit, VersionID],
  BcdOps USING [CTHandle, MTHandle, NameString, ProcessConfigs, ProcessModules],
  BootmesaOps,
  CacheOps USING [CopyWrite, READ, WRITE],
  ControlDefs USING [
    AV, ControlModule, EPRange, GFTIndex, GFTItem, GlobalFrame, GlobalFrameHandle,
    NullControl, NullGlobalFrame, ControlLink, NullLink, UnboundLink],
  FrameOps USING [MakeFsi],
  IODefs USING [
    CR, NumberFormat, SP, WriteChar, WriteNumber, WriteOctal, WriteString],
  LoaderOps USING [
    CloseLinkSpace, DestroyMap, FindCode, FindFiles, FileNotFound,
    FinalizeUtilities, InitializeMap, InitializeUtilities, OpenLinkSpace,
    WriteLink],
  LoadStateFormat USING [
    AltoVersionID, LoadState, LoadStateObject, ModuleInfo, NullModule],
  LoadStateOps USING [EnterModule, GetModule, Map],
  MiscDefs USING [Zero],
  SegmentDefs USING [
    Append, DefaultVersion, DeleteFileSegment, FileHandle, FileSegmentAddress,
    FileSegmentHandle, MoveFileSegment, NewFile, NewFileSegment, OldFileOnly,
    Read, SwapIn, SwapOut, Unlock, Write],
  SegOps USING [DefaultBase, NewSeg, Seg, CodeClass],
  StreamDefs USING [
    DisplayHandle, GetDefaultDisplayStream, NewByteStream, StreamHandle],
  String USING [AppendString],
  Storage USING [Words, FreeWords, PagesForWords];

BootLoader: PROGRAM
  IMPORTS
    CacheOps, BootmesaOps, FrameOps, SegOps, IODefs, BcdOps, MiscDefs, LoaderOps,
    LoadStateOps, SegmentDefs, StreamDefs, String, Storage
  EXPORTS BootmesaOps, LoadStateOps =
  PUBLIC
  
  BEGIN OPEN ControlDefs, SegOps, SegmentDefs, BcdDefs;
  
  data: POINTER TO BootmesaOps.BootData ← @BootmesaOps.dataObject;
  
  BcdBase: TYPE = POINTER TO BcdDefs.BCD;
  MTHandle: TYPE = BcdOps.MTHandle;
  Seg: TYPE = SegOps.Seg;
  GlobalFrameHandle: TYPE = ControlDefs.GlobalFrameHandle;
  ControlModule: TYPE = ControlDefs.ControlModule;
  GFTIndex: TYPE = ControlDefs.GFTIndex;
  
  loadee: BcdBase;
  lsseg, initlsseg, fakebcdseg: PUBLIC Seg;
  
  InitializeLoading: PUBLIC PROCEDURE [name: STRING] =
    BEGIN OPEN data, ControlDefs, SegmentDefs, SegOps;
    bcdseg: FileSegmentHandle ← LoadBcd[
      NewFile[name, Read, OldFileOnly] !
      InvalidBcd =>
	BEGIN OPEN IODefs;
	WriteString["Invalid file "L];
	WriteString[name];
	SIGNAL BootmesaOps.BootAbort;
	END];
    fakebcdseg ← NewSeg[bcdseg.file, bcdseg.base, bcdseg.pages];
    fakebcdseg.link2 ← bcdseg;
    fakebcdseg.resident ← FALSE;
    fakebcdseg.data ← FALSE;
    InitLoadStates[];
    loadee ← data.bcd ← FileSegmentAddress[bcdseg];
    LoaderOps.InitializeUtilities[data.bcd];
    SetupModuleTable[];
    LoaderOps.FindFiles[
      data.bcd !
      LoaderOps.FileNotFound =>
	BEGIN OPEN IODefs;
	WriteString["Cant find a code file "L];
	WriteString[name];
	SIGNAL BootmesaOps.BootAbort;
	END];
    RETURN
    END;
    
  InvalidBcd: SIGNAL [file: FileHandle] = CODE;
  
  LoadBcd: PROCEDURE [bcdfile: FileHandle] RETURNS [bcdseg: FileSegmentHandle] =
    BEGIN OPEN data, SegmentDefs;
    pages: AltoDefs.PageCount;
    bcd: BcdBase;
    bcdseg ← NewFileSegment[bcdfile, 1, 1, Read];
    BootmesaOps.MakeResident[bcdseg];
    bcd ← FileSegmentAddress[bcdseg];
    IF (pages ← bcd.nPages) # 1 THEN
      BEGIN
      Unlock[bcdseg];
      MoveFileSegment[bcdseg, 1, pages];
      BootmesaOps.MakeResident[bcdseg];
      bcd ← FileSegmentAddress[bcdseg];
      END;
    IF bcd.versionIdent # BcdDefs.VersionID OR bcd.definitions THEN
      ERROR InvalidBcd[
	bcdfile ! UNWIND => BEGIN Unlock[bcdseg]; DeleteFileSegment[bcdseg]; END];
    END;
    
  FinishLoading: PUBLIC PROCEDURE =
    BEGIN
    map: LoadStateOps.Map;
    map ← LoaderOps.InitializeMap[data.bcd];
    LoaderOps.FindCode[data.bcd, map];
    BootmesaOps.LookUpModules[];
    ClassifyCodeSegments[];
    AssignControlModules[];
    RelocateOnly[data.bcd, map];
    LoaderOps.FinalizeUtilities[];
    LoaderOps.DestroyMap[map];
    SegmentDefs.Unlock[lsseg.link2];
    SegmentDefs.SwapOut[lsseg.link2];
    END;
    
  RelocateOnly: PROCEDURE [bcd: BcdBase, map: LoadStateOps.Map] =
    BEGIN
    ModuleSearch: PROCEDURE [mth: MTHandle, mti: MTIndex] RETURNS [BOOLEAN] =
      BEGIN OPEN data, ControlDefs;
      i: CARDINAL;
      gfi: GFTIndex = mth.gfi;
      frame: GlobalFrameHandle ← moduleTable[gfi].frame;
      resolved: BOOLEAN ← TRUE;
      link: Link;
      info: LoadStateFormat.ModuleInfo;
      GetVariableLink: PROCEDURE [link: Link] RETURNS [ControlLink] =
	BEGIN
	ep: EPIndex;
	evb: Base ← LOOPHOLE[bcd + bcd.evOffset];
	gfi: GFTIndex ← link.vgfi;
	FindModule: PROCEDURE [mth: MTHandle, mti: MTIndex] RETURNS [BOOLEAN] =
	  BEGIN
	  IF gfi IN [mth.gfi..mth.gfi + mth.ngfi) THEN
	    BEGIN ep ← VarLimit*(gfi - mth.gfi); RETURN[TRUE] END;
	  RETURN[FALSE]
	  END;
	mth: MTHandle ← BcdOps.ProcessModules[bcd, FindModule].mth;
	f: GlobalFrameHandle ← moduleTable[gfi].frame;
	IF mth = NIL THEN RETURN[ControlDefs.NullLink];
	IF (ep ← ep + link.var) = 0 THEN RETURN[LOOPHOLE[f]];
	IF mth.variables = EVNull THEN RETURN[ControlDefs.NullLink];
	RETURN[LOOPHOLE[f + evb[mth.variables].offsets[ep]]];
	END;
      IF mth.frame.length # 0 THEN
	BEGIN
	LoaderOps.OpenLinkSpace[frame, mth];
	FOR i IN [0..mth.frame.length) DO
	  link ← mth.frame.frag[i];
	  SELECT link.vtag FROM
	    proc0, proc1 =>
	      IF link.gfi >= bcd.firstdummy THEN
		BEGIN
		resolved ← FALSE;
		LoaderOps.WriteLink[offset: i, link: ControlDefs.UnboundLink]
		END
	      ELSE LoaderOps.WriteLink[offset: i, link: ConvertLink[link]];
	    var =>
	      IF link.gfi >= bcd.firstdummy THEN
		BEGIN
		resolved ← FALSE;
		LoaderOps.WriteLink[offset: i, link: ControlDefs.NullLink]
		END
	      ELSE LoaderOps.WriteLink[offset: i, link: GetVariableLink[link]];
	    type => LoaderOps.WriteLink[offset: i, link: ConvertLink[link]]
	    ENDCASE;
	  ENDLOOP;
	LoaderOps.CloseLinkSpace[frame];
	END;
      FOR i IN [gfi..gfi + mth.ngfi) DO
	info ← LoadStateOps.GetModule[i];
	info.resolved ← resolved;
	LoadStateOps.EnterModule[i, info];
	ENDLOOP;
      RETURN[FALSE];
      END;
    [] ← BcdOps.ProcessModules[bcd, ModuleSearch];
    RETURN
    END;
    
  ConvertLink: PROCEDURE [bl: Link] RETURNS [cl: ControlLink] =
    BEGIN
    IF bl = BcdDefs.UnboundLink THEN RETURN[ControlDefs.UnboundLink];
    SELECT bl.vtag FROM
      proc0, proc1 => cl ← [procedure[gfi: bl.gfi, ep: bl.ep, tag: procedure]];
      var => cl ← [procedure[gfi: bl.vgfi, ep: bl.var, tag: frame]];
      type => cl ← LOOPHOLE[bl.typeID]
      ENDCASE;
    RETURN
    END;
    
  LoadModules: PUBLIC PROCEDURE [which: BootmesaOps.LoadClass] =
    BEGIN
    ssb: BcdOps.NameString ← LOOPHOLE[loadee + loadee.ssOffset];
    ModuleSearch: PROCEDURE [mth: MTHandle, mti: MTIndex] RETURNS [BOOLEAN] =
      BEGIN OPEN data, SegmentDefs;
      i: CARDINAL;
      frame: GlobalFrameHandle;
      framelinks: BOOLEAN;
      gf: GlobalFrame;
      IF moduleTable[mth.gfi].whenLoaded # which THEN RETURN[FALSE];
      framelinks ← mth.links = frame OR ~mth.code.linkspace;
      frame ← BootmesaOps.AllocGlobalFrame[
	mth.framesize, mth.frame.length, framelinks];
      SetGFTEntry[frame, mth.gfi, mth.ngfi];
      gf ←
	[gfi: mth.gfi, unused: 0, copied: FALSE, alloced: FALSE, shared: FALSE,
	  started: FALSE, trapxfers: FALSE, codelinks: ~framelinks, global:,
	  code: [offset[offset: mth.code.offset, highHalf: NIL]]];
      gf.code.out ← TRUE;
      CacheOps.CopyWrite[from: @gf, to: frame, size: SIZE[GlobalFrame]];
      CacheOps.WRITE[@frame.global[0], ControlDefs.NullControl];
      FOR i IN [mth.gfi..mth.gfi + mth.ngfi) DO
	moduleTable[i].frame ← frame; ENDLOOP;
      IF Loadmap # NIL THEN
	BEGIN OPEN IODefs;
	WriteString["New: g = "L];
	WriteNumber[frame, NumberFormat[8, FALSE, TRUE, 6]];
	WriteChar[SP];
	FOR i IN [mth.name..mth.name + ssb.size[mth.name]) DO
	  WriteChar[ssb.string.text[i]]; ENDLOOP;
	WriteChar[SP];
	WriteChar['[];
	WriteOctal[ControlLink[procedure[gfi: mth.gfi, ep: 0, tag: frame]]];
	WriteChar[']];
	WriteChar[CR];
	END;
      RETURN[FALSE];
      END;
    [] ← BcdOps.ProcessModules[loadee, ModuleSearch];
    END;
    
  ClassifyCodeSegments: PUBLIC PROCEDURE =
    BEGIN
    class: SegOps.CodeClass;
    MungeFrame: PROCEDURE [gfi: GFTIndex] RETURNS [BOOLEAN] =
      BEGIN
      i: CARDINAL;
      segState: SegOps.CodeClass;
      FOR i IN [gfi..gfi + data.moduleTable[gfi].mth.ngfi) DO
	IF (segState ← data.moduleTable[i].code.class) < class THEN
	  data.moduleTable[i].class ← segState
	ELSE data.moduleTable[i].class ← data.moduleTable[i].code.class ← class;
	ENDLOOP;
      RETURN[FALSE];
      END;
    class ← resident;
    [] ← BootmesaOps.EnumerateResidentModules[MungeFrame];
    class ← in;
    [] ← BootmesaOps.EnumerateSwappedInModules[MungeFrame];
    RETURN
    END;
    
  SetupModuleTable: PROCEDURE =
    BEGIN
    mt: DESCRIPTOR FOR ARRAY OF BootmesaOps.ModuleInfo;
    ModuleSearch: PROCEDURE [mth: MTHandle, mti: MTIndex] RETURNS [BOOLEAN] =
      BEGIN
      i: CARDINAL;
      FOR i IN [mth.gfi..mth.gfi + mth.ngfi) DO
	mt[i] ← [mth, NIL, NIL, other, FALSE, resident]; ENDLOOP;
      RETURN[FALSE];
      END;
    mt ← data.moduleTable ← DESCRIPTOR[
      Storage.Words[loadee.firstdummy*SIZE[BootmesaOps.ModuleInfo]],
	loadee.firstdummy];
    mt[0] ← [NIL, NIL, NIL, other, FALSE, notLoaded];
    [] ← BcdOps.ProcessModules[loadee, ModuleSearch];
    RETURN
    END;
    
  CMMapItem: TYPE = RECORD [cti: CTIndex, cm: ControlModule, level: CARDINAL];

  AssignControlModules: PROCEDURE =
    BEGIN OPEN BcdOps, data, ControlDefs;
    ctb: Base ← LOOPHOLE[loadee+loadee.ctOffset];
    mtb: Base ← LOOPHOLE[loadee+loadee.mtOffset];
    mt: DESCRIPTOR FOR ARRAY OF BootmesaOps.ModuleInfo = moduleTable;
    mapIndex, maxLevel: CARDINAL ← 0;
    cti: CTIndex;
    i: CARDINAL;
    cmMap: POINTER TO ARRAY [0..0) OF CMMapItem;
    cm: ControlModule;
    MapControls: PROCEDURE [cth: CTHandle, cti: CTIndex] RETURNS [BOOLEAN] =
      BEGIN OPEN ControlDefs;
      cm: ControlModule;
      c: CTIndex;
      level: CARDINAL ← 0;
      IF cth.nControls = 0 THEN cm ← NullControl
      ELSE {
	cm.list ← Alloc[FrameOps.MakeFsi[cth.nControls + 1 + 1]];
	CacheOps.WRITE[@cm.list.nModules, cth.nControls + 1];
	FOR i: CARDINAL IN [0..cth.nControls) DO
	  CacheOps.WRITE[
	    @cm.list.frames[i+1], mt[mtb[cth.controls[i]].gfi].frame];
	  ENDLOOP;
	FOR c ← ctb[cti].config, ctb[c].config UNTIL c = CTNull DO
	  level ← level + 1; ENDLOOP;
	cm.multiple ← TRUE};
      cmMap[mapIndex] ← [cti: cti, cm: cm, level: level];
      mapIndex ← mapIndex + 1;
      maxLevel ← MAX[maxLevel, level];
      RETURN [FALSE];
      END;
    GetControl: PROCEDURE [mth: MTHandle, mti: MTIndex] RETURNS [BOOLEAN] =
      BEGIN OPEN ControlDefs;
      frame: GlobalFrameHandle ← mt[mth.gfi].frame;
      IF mth.config # cti THEN RETURN[FALSE];
      IF CacheOps.READ[@frame.global[0]] = NullControl THEN
	CacheOps.WRITE[@frame.global[0], GetLink[cm]];
      RETURN [FALSE];
      END;
    IF bcd.nModules = 1 THEN
      BEGIN
      frame: GlobalFrameHandle ← mt[1].frame;
      CacheOps.WRITE[@frame.global[0], NullControl];
      RETURN
      END;
    cmMap ← Storage.Words[bcd.nConfigs*SIZE[CMMapItem]];
    [] ← BcdOps.ProcessConfigs[bcd, MapControls];
    FOR level: CARDINAL DECREASING IN [0..maxLevel] DO
      FOR index: CARDINAL IN [0..mapIndex) DO
	list, listHead: ControlModule;
	IF cmMap[index].level # level OR (cm ← cmMap[index].cm) = NullControl THEN LOOP;
	list ← cm;
	list.multiple ← FALSE;
	cti ← cmMap[index].cti;
	listHead ← SetLink[cm, CacheOps.READ[@list.list.frames[1]]];
	CacheOps.WRITE[@list.list.frames[1], listHead];
	FOR i: CARDINAL IN [2..ctb[cti].nControls+1) DO
	  CacheOps.WRITE[
	    @list.list.frames[i],
	    SetLink[GetLink[listHead], CacheOps.READ[@list.list.frames[i]]]];
	  ENDLOOP;
        [] ← BcdOps.ProcessModules[bcd, GetControl];
	ENDLOOP;
      ENDLOOP;
    FOR index: CARDINAL IN [0..mapIndex) DO
      parent: CARDINAL;
      list: ControlModule;
      IF (list ← cmMap[index].cm) = NullControl THEN LOOP;
      list.multiple ← FALSE;
      IF (cti ← ctb[cmMap[index].cti].config) = CTNull THEN cm ← NullControl
      ELSE {
	FOR parent IN [0..mapIndex) DO IF cmMap[parent].cti = cti THEN EXIT; ENDLOOP;
	cm ← GetLink[cmMap[parent].cm]};
      CacheOps.WRITE[@list.list.frames[0], cm];
      ENDLOOP;
    FOR i IN [0..mapIndex) DO
      IF ctb[cmMap[i].cti].config = CTNull THEN {
	cm ← GetLink[cmMap[i].cm]; EXIT};
      ENDLOOP;
    Storage.FreeWords[cmMap];
    END;

  Alloc: PROCEDURE [index: CARDINAL] RETURNS [p: POINTER] =
    BEGIN
    DO
      p ←  CacheOps.READ[ControlDefs.AV+index];
      IF LOOPHOLE[p, CARDINAL] MOD 4 = 0 THEN EXIT;
      index ← LOOPHOLE[p, CARDINAL]/4;
      ENDLOOP;
    CacheOps.WRITE[ControlDefs.AV+index, CacheOps.READ[p]];
    RETURN
    END;

  SetLink: PROCEDURE [
    cm: ControlModule, frame: GlobalFrameHandle] RETURNS [ControlModule] = {
    t: ControlModule = CacheOps.READ[@frame.global[0]];
    CacheOps.WRITE[@frame.global[0], cm];
    RETURN[IF t = ControlDefs.NullControl THEN [frame[frame]] ELSE t]};

  GetLink: PROCEDURE [cm: ControlModule] RETURNS [ControlModule] = {
    list: ControlModule;
    DO
      IF ~cm.multiple THEN RETURN[cm];
      list ← cm;
      list.multiple ← FALSE;
      cm ← CacheOps.READ[@list.list.frames[1]];
      ENDLOOP};

  loadstate: LoadStateFormat.LoadState;
  
  InitLoadStates: PROCEDURE =
    BEGIN OPEN data, SegmentDefs, SegOps, LoadStateFormat;
    seg: FileSegmentHandle;
    pages, i: CARDINAL;
    swatee: FileHandle;
    pages ← Storage.PagesForWords[
      SIZE[LoadStateObject] + LENGTH[gft]*SIZE[ModuleInfo]];
    swatee ← SegmentDefs.NewFile["swatee", Read + Write + Append, DefaultVersion];
    lsseg ← NewSeg[swatee, DefaultBase, pages, TRUE];
    initlsseg ← NewSeg[swatee, lsseg.base + pages, pages, TRUE];
    lsseg.data ← initlsseg.data ← FALSE;
    seg ← lsseg.link2 ← NewFileSegment[swatee, DefaultBase, pages, Read + Write];
    SwapIn[seg];
    loadstate ← FileSegmentAddress[seg];
    MiscDefs.Zero[loadstate, pages*AltoDefs.PageSize];
    loadstate.versionident ← AltoVersionID;
    loadstate.nBcds ← 1;
    FOR i IN [0..LENGTH[gft]) DO loadstate.gft[i] ← NullModule; ENDLOOP;
    RETURN
    END;
    
  EnterModule: PROCEDURE [rgfi: GFTIndex, module: LoadStateFormat.ModuleInfo] =
    BEGIN loadstate.gft[rgfi] ← module; RETURN END;
    
  GetModule: PROCEDURE [rgfi: GFTIndex]
    RETURNS [module: LoadStateFormat.ModuleInfo] =
    BEGIN RETURN[loadstate.gft[rgfi]]; END;
    
  -- global frame table management
  
  gft: PRIVATE DESCRIPTOR FOR ARRAY OF GFTItem;
  
  InitializeGFT: PROCEDURE [p: POINTER, l: CARDINAL] =
    BEGIN gft ← DESCRIPTOR[p, l]; RETURN END;
    
  EnumerateGlobalFrames: PROCEDURE [
    proc: PROCEDURE [GlobalFrameHandle] RETURNS [BOOLEAN]]
    RETURNS [GlobalFrameHandle] =
    BEGIN
    i: GFTIndex;
    frame: GlobalFrameHandle;
    FOR i IN [1..LENGTH[gft]) DO
      frame ← CacheOps.READ[@gft[i].frame];
      IF frame # NullGlobalFrame AND CacheOps.READ[@gft[i].epbase] = 0 AND proc[
	frame] THEN RETURN[frame];
      ENDLOOP;
    RETURN[NullGlobalFrame]
    END;
    
  gftrover: PRIVATE CARDINAL ← 0;
  
  SetGFTEntry: PROCEDURE [
    frame: GlobalFrameHandle, gfi: GFTIndex, ngfi: CARDINAL] =
    BEGIN
    i, epoffset: CARDINAL;
    IF gfi + ngfi >= LENGTH[gft] THEN BootmesaOps.BootmesaError["GFT Full"L];
    epoffset ← 0;
    FOR i IN [gfi..gfi + ngfi) DO
      LoadStateOps.EnterModule[
	rgfi: i, module: [resolved: FALSE, config: 0, gfi: i]];
      CacheOps.WRITE[@gft[i].frame, frame];
      CacheOps.WRITE[@gft[i].epbase, epoffset];
      epoffset ← epoffset + EPRange;
      ENDLOOP;
    RETURN
    END;
    
  -- loadmap management
  
  Loadmap: PRIVATE StreamDefs.StreamHandle ← NIL;
  
  DisplayChar: PRIVATE PROCEDURE [StreamDefs.StreamHandle, CHARACTER];
  
  OpenLoadmap: PROCEDURE [root: STRING] =
    BEGIN OPEN String;
    default: StreamDefs.DisplayHandle ← StreamDefs.GetDefaultDisplayStream[];
    name: STRING ← [40];
    AppendString[name, root];
    AppendString[name, ".Loadmap"L];
    Loadmap ← StreamDefs.NewByteStream[name, Write + Append];
    DisplayChar ← default.put;
    default.put ← LMput;
    RETURN
    END;
    
  CloseLoadmap: PROCEDURE =
    BEGIN
    default: StreamDefs.DisplayHandle ← StreamDefs.GetDefaultDisplayStream[];
    IF default.put # LMput THEN ERROR;
    default.put ← DisplayChar;
    Loadmap.destroy[Loadmap];
    RETURN
    END;
    
  LMput: PRIVATE PROCEDURE [s: StreamDefs.StreamHandle, c: CHARACTER] =
    BEGIN DisplayChar[s, c]; Loadmap.put[Loadmap, c]; RETURN END;
    
  
  END....