-- ExternalLoadState.mesa
-- Edited by:
-- Bruce, July 2, 1980 4:56 PM
-- Sandman, January 22, 1979 1:48 PM
-- Barbara, November 7, 1978 11:18 AM

DIRECTORY
AltoFileDefs USING [eofDA, FP, NullSN],
BcdOps USING [BcdBase],
ControlDefs USING [GFTIndex],
DebugOps USING [ShortREAD],
Event USING [AddNotifier, Item, Masks, Notifier],
LoadStateFormat USING [AltoVersionID, BcdObject, ConfigIndex, LoadState, ModuleInfo, ModuleTable, NullConfig],
LoadStateOps USING [EnumerationDirection, Map],
MiscDefs USING [Zero],
SDDefs USING [SD, sGFTLength],
SegmentDefs USING [DefaultMDSBase, DeleteFileSegment, FileHint, FileSegmentAddress, FileSegmentHandle, HardUp, InsertFile, MakeSwappedIn, NewFileSegment, Read, SwapOut, SwapUp, Unlock, VMtoFileSegment],
Storage USING [Free, Node];

ExternalLoadState: PROGRAM
IMPORTS DebugOps, Event, MiscDefs, SegmentDefs, Storage
EXPORTS LoadStateOps = PUBLIC

BEGIN OPEN LoadStateFormat;

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

state: PUBLIC FileSegmentHandle ← NIL;
loadstate: LoadState ← NIL;
gft: ModuleTable;
dirty: BOOLEAN ← FALSE;

LoadStateInvalid: SIGNAL = CODE;

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

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

ReleaseLoadState: PROCEDURE =
BEGIN OPEN SegmentDefs;
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: LoadStateOps.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: LoadStateOps.Map] =
BEGIN
IF BASE[map] # NIL THEN Storage.Free[BASE[map]];
END;


AcquireBcd: PROCEDURE [config: ConfigIndex] RETURNS [bcd: BcdOps.BcdBase] =
BEGIN OPEN SegmentDefs;
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 LoadStateInvalid;
fp ← IF b.fp.serial = AltoFileDefs.NullSN THEN state.file.fp ELSE b.fp;
seg ← NewFileSegment[InsertFile[@fp, Read], b.base, b.pages, Read];
IF b.da # AltoFileDefs.eofDA THEN
WITH s: seg SELECT FROM
disk => s.hint ← SegmentDefs.FileHint[b.da, b.base];
ENDCASE;
AddSeg[config: config, seg: seg];
END;
SwapIn[seg];
RETURN[FileSegmentAddress[seg]];
END;

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

DeleteSeg: PROCEDURE [config: ConfigIndex, seg: FileSegmentHandle] =
BEGIN OPEN SegmentDefs;
[] ← InputLoadState[];
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 b.base = seg.base THEN
WITH s: seg SELECT FROM
disk => BEGIN b.da ← s.hint.da; dirty ← TRUE END;
ENDCASE;
END;
ENDCASE => ERROR LoadStateInvalid[];
ENDLOOP;
ReleaseLoadState[];
DeleteFileSegment[seg];
END;

EnumerateModules: PROCEDURE [
proc: PROCEDURE [GFTIndex, ModuleInfo] RETURNS [BOOLEAN]]
RETURNS [GFTIndex] =
BEGIN
i: GFTIndex;
FOR i IN [0..LENGTH[gft]) DO
IF proc[i, gft[i]] THEN RETURN[i];
ENDLOOP;
RETURN[0]
END;

EnumerateBcds: PROCEDURE [dir: LoadStateOps.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;

SetAltoLoadState: PROCEDURE [stateseg: FileSegmentHandle] =
BEGIN
state ← stateseg;
END;

GetAltoLoadState: PROCEDURE RETURNS [FileSegmentHandle] =
BEGIN
RETURN[state];
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
SegmentDefs.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;

Event.AddNotifier[@notifyItem];

END..