-- XDLoadState.mesa
-- Edited by:
-- Bruce, October 8, 1980 6:53 PM
-- Sandman, July 18, 1980 12:07 PM

DIRECTORY
AltoFileDefs USING [CFA, eofDA, FA, FP, NullSN],
BcdOps USING [BcdBase],
ControlDefs USING [GFTIndex],
DebugOps USING [ShortREAD],
DiskDefs USING [UnrecoverableDiskError],
Event USING [AddNotifier, Item, Masks, Notifier],
LoadStateFormat USING [
AltoVersionID, BcdObject, ConfigIndex, LoadState, ModuleInfo, ModuleTable, NullConfig],
DLoadState USING [EnumerationDirection, Map],
MiscDefs USING [Zero],
SDDefs USING [SD, sGFTLength],
SegmentDefs USING [
DefaultMDSBase, DeleteFileSegment, FileHandle, FileHint, FileSegmentAddress,
FileSegmentHandle, HardUp, InsertFile, InvalidFP, InvalidSegmentSize,
MakeSwappedIn, NewFileSegment,
Read, SetFileSegmentDA, SwapOut, SwapUp, Unlock, LongVMtoFileSegment],
State USING [GetGS, GSHandle],
Storage USING [Free, Node];

XDLoadState: PROGRAM
IMPORTS DebugOps, DiskDefs, Event, MiscDefs, SegmentDefs, State, Storage
EXPORTS DLoadState = PUBLIC

BEGIN OPEN LoadStateFormat, SegmentDefs;

GFTIndex: TYPE = ControlDefs.GFTIndex;
FileSegmentHandle: TYPE = SegmentDefs.FileSegmentHandle;

state: PUBLIC FileSegmentHandle ← NIL;
offset: PUBLIC CARDINAL;

loadstate: LoadState ← NIL;
gft: ModuleTable;
dirty: BOOLEAN ← FALSE;

Invalid: SIGNAL = CODE;

SwapIn: PROC [seg: FileSegmentHandle] = INLINE
BEGIN
MakeSwappedIn[seg,DefaultMDSBase,HardUp]
END;

Acquire: PROCEDURE RETURNS [ConfigIndex] =
BEGIN OPEN ControlDefs, SDDefs;
IF state = NIL THEN SIGNAL Invalid;
SwapIn[state];
loadstate ← FileSegmentAddress[state];
IF loadstate.versionident # AltoVersionID THEN
ERROR Invalid[ ! UNWIND => Unlock[state]];
gft ← DESCRIPTOR[@loadstate.gft, DebugOps.ShortREAD[SD+sGFTLength]];
RETURN[loadstate.nBcds]
END;

Release: PROCEDURE =
BEGIN
IF ~state.swappedin THEN RETURN;
Unlock[state];
IF state.lock = 0 THEN
BEGIN
IF dirty THEN
BEGIN
state.write ← TRUE;
SegmentDefs.SwapUp[state];
dirty ← state.write ← FALSE;
END;
loadstate ← NIL;
END;
state.inuse ← TRUE;
END;

MapConfigToReal: PROCEDURE [cgfi: GFTIndex, config: ConfigIndex]
RETURNS [rgfi: GFTIndex] =
BEGIN
IF cgfi = 0 THEN RETURN[0];
FOR rgfi IN [0..LENGTH[gft]) DO
IF gft[rgfi].config = config AND gft[rgfi].gfi = cgfi THEN
RETURN [rgfi];
ENDLOOP;
RETURN[0];
END;

MapRealToConfig: PROCEDURE [rgfi: GFTIndex] RETURNS [GFTIndex, ConfigIndex] =
BEGIN
RETURN[gft[rgfi].gfi, gft[rgfi].config];
END;

GetMap: PROCEDURE [config: ConfigIndex] RETURNS [map: DLoadState.Map] =
BEGIN
max: CARDINAL ← 0;
i: GFTIndex;
FOR i IN [0..LENGTH[gft]) DO
IF gft[i].config = config THEN max ← MAX[max, gft[i].gfi];
ENDLOOP;
max ← max+1;
map ← DESCRIPTOR[Storage.Node[max], max];
MiscDefs.Zero[BASE[map], max];
FOR i IN [0..LENGTH[gft]) DO
IF gft[i].config = config THEN map[gft[i].gfi] ← i;
ENDLOOP;
END;

ReleaseMap: PROCEDURE [map: DLoadState.Map] =
BEGIN
IF BASE[map] # NIL THEN Storage.Free[BASE[map]];
END;


AcquireBcd: PROCEDURE [config: ConfigIndex] RETURNS [bcd: BcdOps.BcdBase] =
BEGIN
b: POINTER TO alto BcdObject;
fp: AltoFileDefs.FP;
seg: FileSegmentHandle;
seg ← GetSeg[config];
IF seg = NIL THEN
BEGIN
WITH object: loadstate.bcds[config] SELECT FROM
alto => b ← @object;
ENDCASE => ERROR Invalid;
fp ← IF b.fp.serial = AltoFileDefs.NullSN THEN state.file.fp ELSE b.fp;
seg ← NewFileSegment[InsertFile[@fp, Read], offset + b.base, b.pages, Read];
IF b.da # AltoFileDefs.eofDA THEN
WITH s: seg SELECT FROM
disk => s.hint ← SegmentDefs.FileHint[b.da, offset + b.base];
ENDCASE;
AddSeg[config: config, seg: seg];
END;
SwapIn[seg];
RETURN[FileSegmentAddress[seg]];
END;

ReleaseBcd: PROCEDURE [bcd: BcdOps.BcdBase] =
BEGIN
seg: FileSegmentHandle ← LongVMtoFileSegment[bcd];
IF seg = NIL THEN RETURN;
Unlock[seg];
seg.inuse ← TRUE;
END;

DeleteSeg: PROCEDURE [config: ConfigIndex, seg: FileSegmentHandle] =
BEGIN
[] ← Acquire[];
FOR config IN [0..loadstate.nBcds) DO
WITH b: loadstate.bcds[config] SELECT FROM
alto =>
BEGIN
IF b.fp # seg.file.fp THEN LOOP;
IF b.da = AltoFileDefs.eofDA AND offset + b.base = seg.base THEN
WITH s: seg SELECT FROM
disk => BEGIN b.da ← s.hint.da; dirty ← TRUE END;
ENDCASE;
END;
ENDCASE => ERROR Invalid[];
ENDLOOP;
Release[];
DeleteFileSegment[seg];
END;

Enumerate: PROCEDURE [dir: DLoadState.EnumerationDirection,
proc: PROCEDURE [ConfigIndex] RETURNS [BOOLEAN]]
RETURNS [config: ConfigIndex] =
BEGIN
SELECT dir FROM
recentfirst =>
FOR config DECREASING IN [0..loadstate.nBcds) DO
IF proc[config] THEN RETURN[config];
ENDLOOP;
recentlast =>
FOR config IN [0..loadstate.nBcds) DO
IF proc[config] THEN RETURN[config];
ENDLOOP;
ENDCASE;
RETURN[NullConfig]
END;

-- Bcd Cache

notifyItem: Event.Item ← [
link:,
eventMask: Event.Masks[newSession] +
Event.Masks[resumeSession] + Event.Masks[abortSession],
eventProc: LoadStateNotifier];

LoadStateNotifier: Event.Notifier =
BEGIN
SELECT why FROM
resumeSession, abortSession => IF state # NIL THEN SwapOut[state];
newSession => FlushBcdCache[];
ENDCASE;
RETURN
END;

CacheItem: TYPE = RECORD [
config: ConfigIndex,
seg: FileSegmentHandle];

CacheSize: CARDINAL = 8;
CacheIndex: TYPE = CARDINAL[0..CacheSize);
cache: ARRAY CacheIndex OF CacheItem;
entries: [0..CacheSize] ← 0;

FlushBcdCache: PROCEDURE =
BEGIN
i: CacheIndex;
FOR i IN [0..entries) DO
DeleteSeg[config: cache[i].config, seg: cache[i].seg];
ENDLOOP;
entries ← 0;
RETURN
END;

GetSeg: PROCEDURE [config: ConfigIndex] RETURNS [seg: FileSegmentHandle] =
BEGIN
i, j: CacheIndex;
item: CacheItem;
FOR i IN [0..entries) DO
IF cache[i].config = config THEN
BEGIN
item ← cache[i];
FOR j DECREASING IN [1..i] DO cache[j] ← cache[j-1]; ENDLOOP;
cache[0] ← item;
RETURN[item.seg];
END;
ENDLOOP;
RETURN[NIL];
END;

AddSeg: PROCEDURE [config: ConfigIndex, seg: FileSegmentHandle] =
BEGIN
i: CacheIndex;
IF entries < CacheSize THEN
BEGIN
FOR i DECREASING IN [1..entries] DO cache[i] ← cache[i-1]; ENDLOOP;
cache[0] ← [config: config, seg: seg];
entries ← entries + 1;
RETURN
END;
DeleteSeg[
config: cache[LAST[CacheIndex]].config,
seg: cache[LAST[CacheIndex]].seg];
FOR i DECREASING IN [1..CacheSize) DO
cache[i] ← cache[i-1];
ENDLOOP;
cache[0] ← [config: config, seg: seg];
END;

Init: PUBLIC PROCEDURE =
BEGIN
data: State.GSHandle ← State.GetGS[];
cfa: POINTER TO AltoFileDefs.CFA ← @data.ESV.loadstateCFA;
pages: CARDINAL ← data.ESV.lspages;
file: FileHandle;
offset ← 0;
IF state # NIL THEN {
UNTIL state.lock = 0 DO Unlock[state]; ENDLOOP;
state.write ← FALSE;
DeleteFileSegment[state]};
file ← InsertFile[@cfa.fp, Read];
state ← FindLoadState[file, @cfa.fa, pages !
InvalidFP, InvalidSegmentSize, DiskDefs.UnrecoverableDiskError => {
state ← NIL; CONTINUE}];
END;

FindLoadState: PROCEDURE [
file: FileHandle, fa: POINTER TO AltoFileDefs.FA, pages: CARDINAL]
RETURNS [seg: FileSegmentHandle] =
BEGIN
seg ← NewFileSegment[file, fa.page, pages, Read];
SetFileSegmentDA[seg, fa.da ! UNWIND => DeleteFileSegment[seg]];
SwapIn[seg ! UNWIND => DeleteFileSegment[seg]];
Unlock[seg];
RETURN
END;

Event.AddNotifier[@notifyItem];

END..