-- MakeImageUtilities.Mesa  Edited by Sandman on July 1, 1980  8:14 AM
-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  AltoDefs USING [PageNumber, PageSize],
  AltoFileDefs USING [CFA, DirFP, eofDA, FA, FP, NullFP, vDA],
  BcdDefs USING [Base, FTSelf, MTIndex],
  BcdMergeOps USING [Finalize, Initialize, MergeBcd, MergeModule, Size, Write],
  BcdOps USING [BcdBase, MTHandle, ProcessModules],
  ControlDefs USING [GFT, GFTIndex, GFTItem, GlobalFrameHandle, NullGlobalFrame],
  DirectoryDefs USING [EnumerateDirectory],
  FrameDefs USING [
    EnumerateGlobalFrames, MakeCodeResident, SwapInCode, SwapOutCode],
  FrameOps USING [CodeHandle],
  ImageDefs USING [FileRequest],
  ImageFormat USING [ImageHeader, MapItem],
  InlineDefs USING [COPY],
  LoadStateFormat USING [
    AltoVersionID, ConfigIndex, LoadState, ModuleTable, NullConfig, NullModule],
  LoadStateOps USING [
    AcquireBcd, EnumerateBcds, GetBcdInfo, GetMap, InputLoadState, Map,
    MapRealToConfig, ReleaseBcd, ReleaseMap],
  MakeImageOps USING [],
  MiscDefs USING [SetBlock, Zero],
  SDDefs USING [SD, sGFTLength],
  SegmentDefs USING [
    AddressFromPage, Append, DataSegmentAddress, DataSegmentHandle, DefaultBase,
    DefaultVersion, DeleteDataSegment, DeleteFileSegment, EnumerateFileSegments,
    FileHandle, FileHint, FileSegmentAddress, FileSegmentHandle, InsertFile,
    JumpToPage, LockFile, NewDataSegment, NewFile, NewFileSegment, Read, SwapIn,
    SwapOut, SwapUp, Unlock, VMtoFileSegment, Write],
  StreamDefs USING [
    DiskHandle, GetIndex, NewWordStream, SetIndex, StreamIndex, WriteBlock],
  StringDefs USING [EquivalentString],
  Storage USING [Pages, FreePages];

MakeImageUtilities: PROGRAM
  IMPORTS
    BcdMergeOps, BcdOps, DirectoryDefs, FrameDefs, FrameOps, InlineDefs,
    LoadStateOps, MiscDefs, SegmentDefs, StreamDefs, StringDefs, Storage
  EXPORTS MakeImageOps
  SHARES SegmentDefs, ControlDefs =PUBLIC

  BEGIN OPEN SegmentDefs, ImageFormat;

  GlobalFrameHandle: TYPE = ControlDefs.GlobalFrameHandle;
  PageNumber: TYPE = AltoDefs.PageNumber;

  -- file requests

  RequestHead: POINTER TO ImageDefs.FileRequest;

  InitFileRequest: PROCEDURE = BEGIN RequestHead ← NIL; END;

  AddFileRequest: PROCEDURE [r: POINTER TO ImageDefs.FileRequest] =
    BEGIN r.link ← RequestHead; RequestHead ← r; END;

  DropFileRequest: PROCEDURE [f: FileHandle] =
    BEGIN
    r, prev: POINTER TO ImageDefs.FileRequest;
    prev ← NIL;
    FOR r ← RequestHead, r.link UNTIL r = NIL DO
      IF r.file = f THEN
	IF prev = NIL THEN RequestHead ← r.link ELSE prev.link ← r.link
      ELSE prev ← r;
      ENDLOOP;
    END;

  ProcessFileRequests: PROCEDURE =
    BEGIN OPEN AltoFileDefs;

    checkone: PROCEDURE [fp: POINTER TO FP, dname: STRING] RETURNS [BOOLEAN] =
      BEGIN
      r: POINTER TO ImageDefs.FileRequest;
      prev: POINTER TO ImageDefs.FileRequest ← NIL;
      FOR r ← RequestHead, r.link UNTIL r = NIL DO
	IF StringDefs.EquivalentString[dname, r.name] THEN
	  BEGIN OPEN SegmentDefs;
	  IF r.file = NIL THEN LockFile[r.file ← InsertFile[fp, r.access]]
	  ELSE r.file.fp ← fp↑;
	  IF prev = NIL THEN RequestHead ← r.link ELSE prev.link ← r.link;
	  END
	ELSE prev ← r;
	ENDLOOP;
      RETURN[RequestHead = NIL]
      END;

    DirectoryDefs.EnumerateDirectory[checkone];
    END;

  -- space allocation

  SpaceHeader: TYPE = RECORD [
    link: POINTER TO SpaceHeader, segment: SegmentDefs.DataSegmentHandle];

  SpaceList: POINTER TO SpaceHeader ← NIL;
  SpacePointer: POINTER TO SpaceHeader;
  SpaceLeft: CARDINAL;

  InitSpace: PROCEDURE = BEGIN SpaceLeft ← 0; END;

  GetSpace: PROCEDURE [n: CARDINAL] RETURNS [p: POINTER] =
    BEGIN
    newseg: DataSegmentHandle;
    IF n > SpaceLeft THEN
      BEGIN
      newseg ← SegmentDefs.NewDataSegment[SegmentDefs.DefaultBase, 1];
      SpacePointer ← SegmentDefs.DataSegmentAddress[newseg];
      SpacePointer.link ← SpaceList;
      SpacePointer.segment ← newseg;
      SpaceList ← SpacePointer;
      SpacePointer ← SpacePointer + SIZE[SpaceHeader];
      SpaceLeft ← AltoDefs.PageSize - SIZE[SpaceHeader];
      END;
    p ← SpacePointer;
    SpacePointer ← SpacePointer + n;
    SpaceLeft ← SpaceLeft - n;
    END;

  GetString: PROCEDURE [oldstring: STRING] RETURNS [newstring: STRING] =
    BEGIN
    i, length: CARDINAL;
    string: TYPE = POINTER TO MACHINE DEPENDENT RECORD [
      length, maxlength: CARDINAL];
    length ← oldstring.length;
    newstring ← GetSpace[(length + 5)/2];
    newstring.length ← length;
    LOOPHOLE[newstring, string].maxlength ← length;
    FOR i IN [0..length) DO newstring[i] ← oldstring[i]; ENDLOOP;
    RETURN;
    END;

  FreeAllSpace: PROCEDURE =
    BEGIN
    next: POINTER TO SpaceHeader;
    UNTIL SpaceList = NIL DO
      next ← SpaceList.link;
      SegmentDefs.DeleteDataSegment[SpaceList.segment];
      SpaceList ← next;
      ENDLOOP;
    END;

  -- image file management


  LockCodeSegment: PROCEDURE [frame: ControlDefs.GlobalFrameHandle] =
    BEGIN OPEN FrameDefs;
    MakeCodeResident[frame];
    SwapInCode[frame];
    SegmentDefs.Unlock[FrameOps.CodeHandle[frame]];
    END;

  UnlockCodeSegment: PROCEDURE [frame: ControlDefs.GlobalFrameHandle] =
    BEGIN SegmentDefs.Unlock[FrameOps.CodeHandle[frame]]; END;

  KDSegment: PROCEDURE RETURNS [FileSegmentHandle] =
    BEGIN OPEN SegmentDefs;
    DiskKDFile: FileHandle = NewFile["DiskDescriptor"L, Read, DefaultVersion];

    FindKD: PROCEDURE [s: FileSegmentHandle] RETURNS [BOOLEAN] =
      BEGIN RETURN[s.file = DiskKDFile]; END;

    RETURN[SegmentDefs.EnumerateFileSegments[FindKD]];
    END;

  DAofPage: PROCEDURE [file: FileHandle, page: AltoDefs.PageNumber]
    RETURNS [next: AltoFileDefs.vDA] =
    BEGIN
    cfa: AltoFileDefs.CFA;
    buf: POINTER = Storage.Pages[1];
    cfa.fp ← file.fp;
    cfa.fa ← AltoFileDefs.FA[file.fp.leaderDA, 0, 0];
    next ← SegmentDefs.JumpToPage[@cfa, page - 1, buf].next;
    Storage.FreePages[buf];
    RETURN
    END;

  FillInCAs: PROCEDURE [
    Image: POINTER TO ImageHeader, mapindex: CARDINAL, ca: POINTER] =
    BEGIN
    i: CARDINAL;
    map: POINTER TO ARRAY [0..0) OF normal MapItem = LOOPHOLE[@Image.map];
    addr: POINTER;
    FOR i IN [0..mapindex) DO
      addr ← SegmentDefs.AddressFromPage[map[i].page];
      THROUGH [0..map[i].count) DO
	ca↑ ← addr; ca ← ca + 1; addr ← addr + AltoDefs.PageSize; ENDLOOP;
      ENDLOOP;
    END;

  SwapOutUnlockedCode: PROCEDURE [f: ControlDefs.GlobalFrameHandle]
    RETURNS [BOOLEAN] =
    BEGIN
    cseg: FileSegmentHandle ← FrameOps.CodeHandle[f];
    IF cseg.swappedin AND cseg.lock = 0 THEN FrameDefs.SwapOutCode[f];
    RETURN[FALSE]
    END;

  SwapOutUnlocked: PROCEDURE [s: FileSegmentHandle] RETURNS [BOOLEAN] =
    BEGIN IF s.lock = 0 THEN SegmentDefs.SwapOut[s]; RETURN[FALSE]; END;

  BashHint: PROCEDURE [s: FileSegmentHandle] RETURNS [BOOLEAN] =
    BEGIN
    WITH s SELECT FROM
      disk => hint ← SegmentDefs.FileHint[da: AltoFileDefs.eofDA, page: 0];
      ENDCASE;
    RETURN[FALSE];
    END;

  BashFile: PROCEDURE [f: FileHandle] RETURNS [BOOLEAN] =
    BEGIN OPEN AltoFileDefs;
    f.open ← f.lengthvalid ← FALSE;
    IF f.fp.serial # DirFP.serial THEN f.fp ← NullFP;
    RETURN[FALSE];
    END;

  PatchUpGFT: PROCEDURE =
    BEGIN OPEN ControlDefs;
    i: GFTIndex;
    gft: POINTER TO ARRAY [0..0) OF GFTItem ← GFT;
    FOR i IN [1..SDDefs.SD[SDDefs.sGFTLength]) DO
      IF gft[i] = [frame: NullGlobalFrame, epbase: 177777B] THEN
	gft[i] ← [frame: NullGlobalFrame, epbase: 0];
      ENDLOOP;
    RETURN
    END;

  ModuleTable: TYPE = LoadStateFormat.ModuleTable;
  BcdBase: TYPE = BcdOps.BcdBase;
  ConfigIndex: TYPE = LoadStateFormat.ConfigIndex;
  ConfigNull: ConfigIndex = LoadStateFormat.NullConfig;

  InitModuleTable: PROCEDURE [
    mt: ModuleTable, merge: BOOLEAN, nbcds: ConfigIndex] =
    BEGIN OPEN ControlDefs;
    rgfi, cgfi: GFTIndex ← 0;
    i: ConfigIndex;
    gft: POINTER TO ARRAY [0..0) OF GFTItem ← GFT;
    gftLength: CARDINAL = SDDefs.SD[SDDefs.sGFTLength];
    MiscDefs.SetBlock[p: BASE[mt], v: LoadStateFormat.NullModule, l: gftLength];
    IF merge THEN
      FOR rgfi IN [1..gftLength) DO
	IF gft[rgfi].frame # NullGlobalFrame THEN
	  mt[rgfi] ← [resolved: TRUE, config: 0, gfi: (cgfi ← cgfi + 1)];
	ENDLOOP
    ELSE
      FOR i IN [0..nbcds) DO
	cgfi ← 0;
	FOR rgfi IN [1..gftLength) DO
	  IF gft[rgfi].frame # NullGlobalFrame AND LoadStateOps.MapRealToConfig[
	    rgfi].config = i THEN
	    mt[rgfi] ← [resolved: TRUE, config: i, gfi: (cgfi ← cgfi + 1)];
	  ENDLOOP;
	ENDLOOP;
    END;

  NumberGFIInConfig: PROCEDURE [mt: ModuleTable, con: ConfigIndex]
    RETURNS [ngfi: ControlDefs.GFTIndex] =
    BEGIN
    i: ControlDefs.GFTIndex;
    ngfi ← 0;
    FOR i IN [0..LENGTH[mt]) DO
      IF mt[i].config = con THEN ngfi ← ngfi + 1; ENDLOOP;
    RETURN
    END;

  -- Bcd Merging Management

  Strings: TYPE = DESCRIPTOR FOR ARRAY OF STRING;

  TableSize: CARDINAL = 15*AltoDefs.PageSize;

  MergeAllBcds: PROCEDURE [mt: ModuleTable, names: Strings] =
    BEGIN OPEN LoadStateOps;

    MergeLoadedBcds: PROCEDURE [config: ConfigIndex] RETURNS [BOOLEAN] =
      BEGIN
      map: Map ← GetMap[config];
      bcd: BcdBase ← AcquireBcd[config];
      BcdMergeOps.MergeBcd[bcd, map, 0, mt, names[config]];
      ReleaseBcd[bcd];
      ReleaseMap[map];
      RETURN[FALSE];
      END;

    MergeCopiedFrames: PROCEDURE [frame: GlobalFrameHandle] RETURNS [BOOLEAN] =
      BEGIN
      copied: GlobalFrameHandle;
      config: ConfigIndex;

      ModuleCopiedFrom: PROCEDURE [f: GlobalFrameHandle] RETURNS [BOOLEAN] =
	BEGIN
	RETURN[
	  f # frame AND f.code.handle = frame.code.handle AND
	    (config ← MapRealToConfig[f.gfi].config) # ConfigNull];
	END;

	IF MapRealToConfig[frame.gfi].config # ConfigNull THEN RETURN[FALSE];
      IF (copied ← FrameDefs.EnumerateGlobalFrames[ModuleCopiedFrom]) #
	ControlDefs.NullGlobalFrame THEN
	BEGIN BcdMergeOps.MergeModule[frame, copied, mt]; RETURN[FALSE]; END;
      RETURN[FALSE];
      END;

    BcdMergeOps.Initialize[TableSize, NumberGFIInConfig[mt, 0]];
    [] ← EnumerateBcds[recentlast, MergeLoadedBcds];
    [] ← FrameDefs.EnumerateGlobalFrames[MergeCopiedFrames];
    [] ← BcdMergeOps.Size[];
    BcdMergeOps.Write[MoveWords];
    BcdMergeOps.Finalize[];
    END;

  MergeABcd: PROCEDURE [config: ConfigIndex, mt: ModuleTable, names: Strings] =
    BEGIN OPEN LoadStateOps;
    map: Map ← GetMap[config];
    bcd: BcdBase ← AcquireBcd[config];
    BcdMergeOps.Initialize[TableSize, NumberGFIInConfig[mt, config]];
    BcdMergeOps.MergeBcd[bcd, map, config, mt, names[config]];
    ReleaseBcd[bcd];
    ReleaseMap[map];
    [] ← BcdMergeOps.Size[];
    BcdMergeOps.Write[MoveWords];
    BcdMergeOps.Finalize[];
    END;

  StreamSegment: PROCEDURE [stream: StreamDefs.DiskHandle, page: PageNumber]
    RETURNS [newpage: PageNumber, seg: FileSegmentHandle] =
    BEGIN OPEN SegmentDefs;
    index: StreamDefs.StreamIndex;
    index ← StreamDefs.GetIndex[stream];
    IF index.byte # 0 THEN
      BEGIN
      index.byte ← 0;
      index.page ← index.page + 1;
      StreamDefs.SetIndex[stream, index];
      END;
    seg ← NewFileSegment[stream.file, page + 1, index.page - page, Read + Write];
    newpage ← index.page;
    RETURN
    END;

  Data: TYPE = RECORD [
    mt: ModuleTable,
    bcdsegs: DESCRIPTOR FOR ARRAY OF FileSegmentHandle,
    strings: Strings,
    initLS: LoadStateFormat.LoadState,
    initlsseg: FileSegmentHandle,
    bcdstream: StreamDefs.DiskHandle];

  data: POINTER TO Data;

  SaveBcd: PROCEDURE [config: ConfigIndex] RETURNS [BOOLEAN] =
    BEGIN
    data.bcdsegs[config] ← SegmentDefs.VMtoFileSegment[
      LoadStateOps.AcquireBcd[config]];
    RETURN[FALSE];
    END;

  ProcessBcds: PROCEDURE [initstateseg: FileSegmentHandle, merge: BOOLEAN]
    RETURNS [maxbcdsize: CARDINAL] =
    BEGIN OPEN SegmentDefs;
    nbcds, page, con: CARDINAL;
    data ← GetSpace[SIZE[Data]];
    SwapIn[initstateseg];
    data.initLS ← FileSegmentAddress[initstateseg];
    data.initlsseg ← initstateseg;
    MiscDefs.Zero[data.initLS, initstateseg.pages*AltoDefs.PageSize];
    data.mt ← DESCRIPTOR[@data.initLS.gft, SDDefs.SD[SDDefs.sGFTLength]];
    data.bcdstream ← StreamDefs.NewWordStream[
      "makeimage.scratch$"L, Read + Write + Append];
    nbcds ← LoadStateOps.InputLoadState[];
    data.strings ← GetBcdFileNames[nbcds];
    nbcds ← IF merge THEN 1 ELSE nbcds;
    data.initLS.nBcds ← nbcds;
    data.bcdsegs ← DESCRIPTOR[GetSpace[nbcds], nbcds];
    page ← 0;
    InitModuleTable[data.mt, merge, nbcds];
    IF merge THEN
      BEGIN
      MergeAllBcds[data.mt, data.strings];
      [page, data.bcdsegs[0]] ← StreamSegment[data.bcdstream, page];
      maxbcdsize ← data.bcdsegs[0].pages;
      END
    ELSE
      BEGIN
      [] ← LoadStateOps.EnumerateBcds[recentlast, SaveBcd];
      maxbcdsize ← 0;
      FOR con IN [0..nbcds) DO
	MergeABcd[con, data.mt, data.strings];
	[page, data.bcdsegs[con]] ← StreamSegment[data.bcdstream, page];
	maxbcdsize ← MAX[maxbcdsize, data.bcdsegs[con].pages];
	ENDLOOP;
      END;
    data.bcdstream.destroy[data.bcdstream];
    IF merge THEN PatchUpGFT[];
    RETURN
    END;

  -- bcd file names


  GetBcdFileNames: PROCEDURE [nbcds: ConfigIndex] RETURNS [names: Strings] =
    BEGIN
    nfound: ConfigIndex ← 0;

    GetNames: PROCEDURE [fp: POINTER TO AltoFileDefs.FP, s: STRING]
      RETURNS [BOOLEAN] =
      BEGIN

      FindBcd: PROCEDURE [config: ConfigIndex] RETURNS [BOOLEAN] =
	BEGIN
	IF fp↑ = LoadStateOps.GetBcdInfo[config].fp↑ THEN
	  BEGIN
	  names[config] ← GetString[s];
	  nfound ← nfound + 1;
	  RETURN[TRUE];
	  END;
	RETURN[FALSE];
	END;

	[] ← LoadStateOps.EnumerateBcds[recentfirst, FindBcd];
      RETURN[nfound = nbcds];
      END;

    names ← DESCRIPTOR[GetSpace[nbcds], nbcds];
    MiscDefs.SetBlock[BASE[names], GetString["(anon)"L], nbcds];
    DirectoryDefs.EnumerateDirectory[GetNames];
    RETURN[names];
    END;

  MoveWords: PUBLIC PROCEDURE [source: POINTER, nwords: CARDINAL] =
    BEGIN
    IF nwords # StreamDefs.WriteBlock[
      stream: data.bcdstream, address: source, words: nwords] THEN ERROR;
    END;

  MapSegmentsInBcd: PROCEDURE [
    mt: ModuleTable, config: ConfigIndex, bcdseg: FileSegmentHandle]
    RETURNS [links, types: BOOLEAN] =
    BEGIN OPEN BcdOps, LoadStateFormat;
    bcd: BcdBase;
    sgb: BcdDefs.Base;

    MapSegments: PROCEDURE [mth: BcdOps.MTHandle, mti: BcdDefs.MTIndex]
      RETURNS [BOOLEAN] =
      BEGIN OPEN s: sgb[mth.code.sgi];
      gftLength: CARDINAL = SDDefs.SD[SDDefs.sGFTLength];
      frame: GlobalFrameHandle;
      rgfi: CARDINAL ← 1;
      WHILE rgfi < gftLength DO
	IF mt[rgfi].config = config AND mt[rgfi].gfi = mth.gfi THEN EXIT;
	rgfi ← rgfi + 1;
	ENDLOOP;
      IF s.file = BcdDefs.FTSelf AND s.class = code THEN
	BEGIN
	frame ← ControlDefs.GFT[rgfi].frame;
	s.base ← FrameOps.CodeHandle[frame].base;
	END;
      RETURN[FALSE];
      END;

    SegmentDefs.SwapIn[bcdseg];
    bcd ← SegmentDefs.FileSegmentAddress[bcdseg];
    sgb ← LOOPHOLE[bcd + bcd.sgOffset];
    [] ← BcdOps.ProcessModules[bcd, MapSegments];
    links ← bcd.nExports # 0;
    types ← bcd.typeExported;
    SegmentDefs.Unlock[bcdseg];
    END;

  InitLoadStates: PROCEDURE [lsseg: FileSegmentHandle] =
    BEGIN OPEN AltoFileDefs;
    i: CARDINAL;
    exports, typeExported: BOOLEAN;
    seg: FileSegmentHandle;
    FOR i IN [0..data.initLS.nBcds) DO
      seg ← data.bcdsegs[i];
      [links: exports, types: typeExported] ← MapSegmentsInBcd[data.mt, i, seg];
      data.initLS.bcds[i] ←
	[exports: exports, typeExported: typeExported, pages: seg.pages,
	  body: alto[fp: NullFP, da: eofDA, base: seg.base]];
      SegmentDefs.DeleteFileSegment[seg];
      ENDLOOP;
    data.initLS.versionident ← LoadStateFormat.AltoVersionID;
    InlineDefs.COPY[
      from: data.initLS, to: SegmentDefs.FileSegmentAddress[lsseg],
      nwords: AltoDefs.PageSize*lsseg.pages];
    lsseg.write ← TRUE;
    SegmentDefs.SwapUp[lsseg];
    lsseg.write ← FALSE;
    data.initlsseg.write ← TRUE;
    SegmentDefs.SwapUp[data.initlsseg];
    data.initlsseg.write ← FALSE;
    RETURN
    END;


  END..