-- RTOSImpl.Mesa
-- last edit by Russ Atkinson, 18-Mar-81 11:44:26
-- last edit by Willie-Sue Haugeland, 9-Sep-81 15:08:44
-- last edit by Paul Rovner, November 1, 1982 10:18 am

DIRECTORY
CachedSpace USING[Handle],
CedarSnapshot USING[Register, After],
Directory USING[Lookup],
Environment USING[wordsPerPage, Long],
File USING [read, write, Capability, nullCapability, GetSize],
Heap USING [Create],
PrincOps USING [GlobalFrameHandle, NullGlobalFrame, FrameCodeBase],
PrincOpsRuntime USING [GetFrame, GFT, GFTItem],
RTFlags USING [checking],
RTOS USING [EVRange, CodeMatch],
RTOSExtras USING [], -- EXPORT of EstablishCedarVMFile only. NOTE INTERIM
RTQuanta USING[PagesPerQuantum, QuantumCount, QuantumIndex],
RTSponge USING [Enable, Disable],
RTZones USING [MapPtrZf, mzVacant],
SDDefs USING [SD, sGFTLength],
Space USING [Create, CreateUniformSwapUnits, nullHandle, InsufficientSpace,
defaultWindow, Delete, GetWindow, GetHandle, Handle, Kill,
LongPointer, Map, PageNumber, PageCount, PageFromLongPointer,
mds, virtualMemory, GetAttributes, WindowOrigin, DeleteSwapUnits],
SpecialSpace: TYPE USING [CreateForCode, MakeResident, CreateAligned],
UnsafeStorage USING [NewUZone],
Volume USING[InsufficientSpace];

RTOSImpl: MONITOR
IMPORTS CedarSnapshot, Directory, File, Heap, PrincOpsRuntime, RTSponge,
RTZones, Space, SpecialSpace, UnsafeStorage, Volume

EXPORTS RTOS, RTOSExtras
= BEGIN OPEN RTQuanta;

PageSize: CARDINAL = Environment.wordsPerPage;
GlobalFrameHandle: TYPE = PrincOps.GlobalFrameHandle;
NullGlobalFrame: GlobalFrameHandle = PrincOps.NullGlobalFrame;
EVRange: TYPE = RTOS.EVRange;

--Public signals
InsufficientVM: PUBLIC SIGNAL = CODE;
--Public variables
UZHandle: TYPE = LONG POINTER TO UZObject;

UZObject: TYPE = MACHINE DEPENDENT RECORD [
procs(0:0..31): LONG POINTER TO UZProcs
];

UZProcs: TYPE = MACHINE DEPENDENT RECORD [
new(0): PROC[self: UZHandle, size: CARDINAL] RETURNS[LONG POINTER],
free(1): PROC[self: UZHandle, object: LONG POINTER]
];

DummySequenceType: TYPE = RECORD[seq: SEQUENCE length: CARDINAL OF UNSPECIFIED];
shzProcs: UZProcs ← [new: SHZNew, free: SHZFree];
shzObject: UZObject ← [procs: @shzProcs];
SHZNew: PROC[self: UZHandle, size: CARDINAL] RETURNS[LONG POINTER] = {
size ← size - SIZE[DummySequenceType];
IF allocStarted
THEN RETURN[cedarHeapZone.NEW[DummySequenceType[size]]]
ELSE RETURN[pilotHeapZone.NEW[DummySequenceType[size]]];
};

SHZFree: ENTRY PROC[self: UZHandle, object: LONG POINTER] =
{IF RTZones.MapPtrZf[object] = RTZones.mzVacant
THEN pilotHeapZone.FREE[@object]
ELSE cedarHeapZone.FREE[@object]};

fszProcs: UZProcs ← [new: FSZNew, free: FSZFree];
fszObject: UZObject ← [procs: @fszProcs];
FSZNew: PROC[self: UZHandle, size: CARDINAL] RETURNS[LONG POINTER] = {
RETURN[GetDataPagesFromNewSpace[(size + PageSize - 1)/PageSize]];
};


FSZFree: ENTRY PROC[self: UZHandle, object: LONG POINTER] =
{space: Space.Handle;
WHILE checkPointing DO WAIT finishedCheckpointing ENDLOOP;
space ← UnRavelUSUs[Space.GetHandle[Space.PageFromLongPointer[object]]];
IF SpaceInCedarVM[space]
THEN RemoveFromCedarVMSpaces[space]
ELSE {Space.Kill[space];
Space.Delete[space];
nSpaces ← nSpaces - 1}};

ppzProcs: UZProcs ← [new: PPZNew, free: NIL];
ppzObject: UZObject ← [procs: @ppzProcs];
PPZNew: PROC[self: UZHandle, size: CARDINAL] RETURNS[LONG POINTER] = {
RETURN[GetPermanentDataPages[(size + PageSize - 1)/PageSize]];
};

FreeableSpaceZone: PUBLIC UNCOUNTED ZONELOOPHOLE[LONG[@fszObject]];
PermanentPageZone: PUBLIC UNCOUNTED ZONELOOPHOLE[LONG[@ppzObject]];

PrivateHeapZone: PUBLIC UNCOUNTED ZONELOOPHOLE[LONG[@shzObject]];

pilotHeapZone: UNCOUNTED ZONE;
cedarHeapZone: UNCOUNTED ZONE;

--Other parameters
nSpaces: CARDINAL ← 0; -- # of spaces used thru this module
SwapUnitSize: CARDINAL = 4; -- for data quanta
largeSwapUnitSize: CARDINAL = 16; -- for larger stuff, e.g. the reference-count table
allocStarted: BOOLEANFALSE;

discardedSpaces: ARRAY [1..maxNDiscardedSpaces] OF Space.Handle ← ALL[Space.nullHandle];
maxNDiscardedSpaces: CARDINAL = 30;
nDiscarded: [0..maxNDiscardedSpaces] ← 0;
spaceLost: BOOLEANFALSE;

nextCedarVMFilePage: CARDINAL ← 0;
cedarVMFilePages: LONG INTEGER ← 0;
cedarVMFile: File.Capability ← File.nullCapability;

cedarVMSpaces: SDHandle ← NIL;
cedarVMSpaceCache: SDHandle ← NIL;
SDHandle: TYPE = LONG POINTER TO SDRec;
SDRec: TYPE = RECORD[space: Space.Handle, next: SDHandle, firstCedarVMPage, nPages: NAT];

checkPointing: BOOLEANFALSE;
finishedCheckpointing: CONDITION;

--Public procedures

UnRavelUSUs: PUBLIC PROC[space: Space.Handle] RETURNS[Space.Handle] =
{ DO parent: Space.Handle;
mapped: BOOLEAN;
IF space = Space.mds OR space = Space.virtualMemory THEN ERROR;
[parent: parent, mapped: mapped] ← Space.GetAttributes[space];
IF mapped THEN EXIT;
space ← parent;
ENDLOOP;
RETURN[space]};

NotifyAllocatorReady: PUBLIC PROC =
{cedarHeapZone ← UnsafeStorage.NewUZone[initialSize: 100000B--words--];
allocStarted ← TRUE};

IsAllocatorReady: PUBLIC PROC RETURNS[BOOLEAN] =
{RETURN[allocStarted]};


-- PROCs for allocating blocks of data pages

SpaceInCedarVM: PUBLIC PROC[sh: Space.Handle] RETURNS[BOOLEAN] =
{file: File.Capability;
sh ← UnRavelUSUs[sh];
file ← Space.GetWindow[sh].file;
RETURN[cedarVMFilePages>0 AND file = cedarVMFile]};

GetPermanentDataPages: PUBLIC ENTRY PROC[nPages: CARDINAL,
createUniformSwapUnits: BOOLEANTRUE]
RETURNS[LONG POINTER] =
{ ENABLE UNWIND => NULL;
spH: Space.Handle;
WHILE checkPointing DO WAIT finishedCheckpointing ENDLOOP;
nPages ← ((nPages+PagesPerQuantum-1)/PagesPerQuantum)*PagesPerQuantum;

spH ← FindCedarVMSpace[nPages];
IF spH = Space.nullHandle
THEN {spH ← SpecialSpace.CreateAligned[nPages, Space.virtualMemory, PagesPerQuantum];
IF cedarVMFilePages>0 AND nPages <= (cedarVMFilePages-nextCedarVMFilePage)
THEN {window: Space.WindowOrigin = [file: cedarVMFile,
base: nextCedarVMFilePage];
nextCedarVMFilePage ← nextCedarVMFilePage + nPages;
Space.Map[spH, window ! UNWIND => Space.Delete[spH]];
InsertInCedarVMSpaces[spH, window.base, nPages]}
ELSE Space.Map[spH, Space.defaultWindow ! UNWIND => Space.Delete[spH]];
nSpaces ← nSpaces + 1};
IF createUniformSwapUnits AND nPages > largeSwapUnitSize
THEN Space.CreateUniformSwapUnits[largeSwapUnitSize, spH ! UNWIND => Space.Delete[spH]];
RETURN[Space.LongPointer[spH]]};

GetCollectibleQuanta: PUBLIC ENTRY PROC[desired, needed: QuantumCount]
RETURNS[qi: QuantumIndex, qc: QuantumCount] =
{ OPEN Space;
ENABLE UNWIND => NULL;
space: Handle;

WHILE checkPointing DO WAIT finishedCheckpointing ENDLOOP;

space ← FindCedarVMSpace[needed*PagesPerQuantum, TRUE];

IF space = Space.nullHandle THEN
{qc ← desired;
WHILE cedarVMFilePages>0
AND desired*PagesPerQuantum > (cedarVMFilePages-nextCedarVMFilePage)
AND needed*PagesPerQuantum <= (cedarVMFilePages-nextCedarVMFilePage)
DO qc ← desired ← MAX[needed, desired/2] ENDLOOP;

{space ← SpecialSpace.CreateAligned[size: qc*PagesPerQuantum,
parent: virtualMemory,
alignment: PagesPerQuantum
! Space.InsufficientSpace =>
IF INTEGER[available] >= INTEGER[needed*PagesPerQuantum]
THEN {qc ← available/PagesPerQuantum; RETRY}
ELSE GOTO insufficientVM];
EXITS insufficientVM => RETURN WITH ERROR InsufficientVM};

IF qc*PagesPerQuantum > SwapUnitSize
THEN CreateUniformSwapUnits[SwapUnitSize, space];
IF cedarVMFilePages>0
AND qc*PagesPerQuantum <= (cedarVMFilePages-nextCedarVMFilePage)
THEN {window: Space.WindowOrigin = [file: cedarVMFile, base: nextCedarVMFilePage];
nextCedarVMFilePage ← nextCedarVMFilePage + qc*PagesPerQuantum;
Space.Map[space, window
! Volume.InsufficientSpace => {Space.Delete[space];
ERROR InsufficientVM}];
InsertInCedarVMSpaces[space, window.base, qc*PagesPerQuantum]}
ELSE Space.Map[space, Space.defaultWindow
! Volume.InsufficientSpace => {Space.Delete[space]; ERROR InsufficientVM}];
nSpaces ← nSpaces + 1}
ELSE -- space # Space.nullHandle
qc ← Space.GetAttributes[space].size/PagesPerQuantum;

IF RTFlags.checking
AND LOOPHOLE[space, CachedSpace.Handle].page MOD PagesPerQuantum # 0
THEN ERROR;
qi ← LOOPHOLE[space, CachedSpace.Handle].page/PagesPerQuantum;
RETURN[qi, qc]};

GetDataPagesFromNewSpace: PUBLIC ENTRY PROC[nPages: CARDINAL,
createUniformSwapUnits: BOOLEANTRUE]
RETURNS[LONG POINTER] =
{ ENABLE UNWIND => NULL;
spH: Space.Handle;

WHILE checkPointing DO WAIT finishedCheckpointing ENDLOOP;
nPages ← ((nPages+PagesPerQuantum-1)/PagesPerQuantum)*PagesPerQuantum;

spH ← FindCedarVMSpace[nPages];
IF spH = Space.nullHandle
THEN {spH ← SpecialSpace.CreateAligned[size: nPages,
parent: Space.virtualMemory,
alignment: PagesPerQuantum];
IF cedarVMFilePages>0 AND nPages <= (cedarVMFilePages-nextCedarVMFilePage)
THEN {window: Space.WindowOrigin = [file: cedarVMFile, base: nextCedarVMFilePage];
nextCedarVMFilePage ← nextCedarVMFilePage + nPages;
Space.Map[spH, window ! UNWIND => Space.Delete[spH]];
InsertInCedarVMSpaces[spH, window.base, nPages]}
ELSE Space.Map[spH, Space.defaultWindow ! UNWIND => Space.Delete[spH]];
nSpaces ← nSpaces + 1};
IF createUniformSwapUnits AND nPages > largeSwapUnitSize
THEN Space.CreateUniformSwapUnits[largeSwapUnitSize, spH ! UNWIND => Space.Delete[spH]];
RETURN[Space.LongPointer[spH]]};

-- used in RTBasesImpl for GetSubspaceQuanta (for NewCollectibleSubspace)
GetQuantaFromNewSpace: PUBLIC ENTRY PROC[nQ: QuantumCount,
createUniformSwapUnits: BOOLEANTRUE]
RETURNS[qi: QuantumIndex] =
{ OPEN Space;
ENABLE UNWIND => NULL;
space: Handle;

WHILE checkPointing DO WAIT finishedCheckpointing ENDLOOP;
space ← SpecialSpace.CreateAligned[nQ*PagesPerQuantum, virtualMemory, PagesPerQuantum
! Space.InsufficientSpace => ERROR InsufficientVM];
IF createUniformSwapUnits AND nQ*PagesPerQuantum > SwapUnitSize
THEN CreateUniformSwapUnits[SwapUnitSize, space];
Map[space, defaultWindow
! Volume.InsufficientSpace => {Space.Delete[space]; ERROR InsufficientVM}];
nSpaces ← nSpaces + 1;
qi ← LOOPHOLE[space, CachedSpace.Handle].page/PagesPerQuantum;
RETURN[qi]};

GetDataPagesForGCTables: PUBLIC ENTRY PROC RETURNS[LONG POINTER] =
-- get it on a 64K boundary
{ OPEN Space;
np: PageCount ← 256;
spHs: Handle; --swappable space
spHp: Handle; --pinned space
spH: Handle;

WHILE checkPointing DO WAIT finishedCheckpointing ENDLOOP;
spH ← SpecialSpace.CreateForCode[np, virtualMemory
! InsufficientSpace => ERROR InsufficientVM];
spHp← Space.Create[1, spH, 0]; -- first page is a space by itself
spHs← Space.Create[np-1, spH, 1]; -- the rest
IF cedarVMFilePages>0 AND 1 <= (cedarVMFilePages-nextCedarVMFilePage)
THEN {window: Space.WindowOrigin = [file: cedarVMFile, base: nextCedarVMFilePage];
nextCedarVMFilePage ← nextCedarVMFilePage + 1;
Space.Map[spHp, window
! Volume.InsufficientSpace => {Space.Delete[spH];
ERROR InsufficientVM}];
InsertInCedarVMSpaces[spHp, window.base, 1]}
ELSE Space.Map[spHp, Space.defaultWindow
! Volume.InsufficientSpace => {Space.Delete[spH];
ERROR InsufficientVM}];

SpecialSpace.MakeResident[spHp];

IF np-1 > largeSwapUnitSize
THEN Space.CreateUniformSwapUnits[largeSwapUnitSize, spHs
! UNWIND => Space.Delete[spH]];
IF cedarVMFilePages>0 AND np-1 <= (cedarVMFilePages-nextCedarVMFilePage)
THEN {window: Space.WindowOrigin = [file: cedarVMFile, base: nextCedarVMFilePage];
nextCedarVMFilePage ← nextCedarVMFilePage + np-1;
Space.Map[spHs, window
! Volume.InsufficientSpace =>
{Space.Delete[spH];
ERROR InsufficientVM}];
InsertInCedarVMSpaces[spHs, window.base, np-1]}
ELSE Space.Map[spHs, Space.defaultWindow
! Volume.InsufficientSpace => {Space.Delete[spH];
ERROR InsufficientVM}];
nSpaces ← nSpaces + 3;
RETURN[LongPointer[spH]]};

FindCedarVMSpace: INTERNAL PROC[np: Space.PageCount, minimum: BOOLEANFALSE]
RETURNS[Space.Handle] =
{ prev: SDHandle ← NIL;
FOR p: SDHandle ← cedarVMSpaceCache, p.next UNTIL p = NIL
DO IF (IF minimum THEN (np <= p.nPages) ELSE (np = p.nPages))
THEN {p.space ← SpecialSpace.CreateAligned[size: p.nPages,
parent: Space.virtualMemory,
alignment: PagesPerQuantum
! ANY => CONTINUE];
IF p.space = Space.nullHandle THEN RETURN[Space.nullHandle];
nSpaces ← nSpaces + 1;
IF prev = NIL
THEN cedarVMSpaceCache ← p.next
ELSE prev.next ← p.next;
p.next ← cedarVMSpaces;
cedarVMSpaces ← p;
Space.Map[p.space, [file: cedarVMFile, base: p.firstCedarVMPage]];
Space.Kill[p.space];
RETURN[p.space]}
ELSE prev ← p;
ENDLOOP;
RETURN[Space.nullHandle]
};

InsertInCedarVMSpaces: INTERNAL PROC[space: Space.Handle, firstPage, nPages: NAT] =
{ space ← UnRavelUSUs[space];
Space.Kill[space];
cedarVMSpaces ← pilotHeapZone.NEW[SDRec ← [space: space,
next: cedarVMSpaces,
firstCedarVMPage: firstPage,
nPages: nPages]]};

CheckpointCedarVM: ENTRY PROC[p: PROC[Space.Handle]] =
{ ENABLE UNWIND => NULL;
FOR sdh: SDHandle ← cedarVMSpaces, sdh.next UNTIL sdh = NIL
DO p[sdh.space] ENDLOOP;
RTSponge.Disable[]; --acquire the sponge.
checkPointing ← TRUE};

RollbackCedarVM: ENTRY PROC[after: CedarSnapshot.After] =
{ RTSponge.Enable[]; -- release the sponge.
checkPointing ← FALSE;
BROADCAST finishedCheckpointing};

RemoveFromCedarVMSpaces: INTERNAL PROC[space: Space.Handle] =
{ prev: SDHandle ← NIL;
FOR p: SDHandle ← cedarVMSpaces, p.next UNTIL p = NIL
DO IF p.space = space
THEN {IF prev = NIL
THEN cedarVMSpaces ← p.next
ELSE prev.next ← p.next;
Space.DeleteSwapUnits[space];
p.next ← cedarVMSpaceCache;
cedarVMSpaceCache ← p;
nSpaces ← nSpaces - 1;
Space.Kill[p.space];
Space.Delete[p.space];
p.space ← Space.nullHandle;
RETURN}
ELSE prev ← p;
ENDLOOP;
ERROR};

DeleteSpace: PUBLIC ENTRY PROC[space: Space.Handle] =
{ ENABLE UNWIND => NULL;
WHILE checkPointing DO WAIT finishedCheckpointing ENDLOOP;
IF SpaceInCedarVM[space]
THEN RemoveFromCedarVMSpaces[UnRavelUSUs[space]]
ELSE {nSpaces ← nSpaces - 1;
Space.Kill[space];
Space.Delete[space]}};

-- PROCs for dealing with frames
EnumerateGlobalFrames: PUBLIC PROC[proc: PROC[GlobalFrameHandle] RETURNS[BOOLEAN]]
RETURNS[GlobalFrameHandle] =
{ FOR i: CARDINAL IN [1..SDDefs.SD[SDDefs.sGFTLength]] DO
item: PrincOpsRuntime.GFTItem ← PrincOpsRuntime.GFT[LOOPHOLE[i]];
frame: GlobalFrameHandle ← PrincOpsRuntime.GetFrame[item];
IF frame # NullGlobalFrame AND item.epbias = 0 THEN
{IF proc[frame] THEN RETURN[frame]};
ENDLOOP;
RETURN[NullGlobalFrame]};


-- PROCs for dealing with RTProcesses

SameCode: PUBLIC PROC [f1, f2: GlobalFrameHandle] RETURNS [RTOS.CodeMatch] =
{ fcb1, fcb2: PrincOps.FrameCodeBase;
fcb1 ← f1.code;
fcb2 ← f2.code;
fcb1.out ← fcb2.out ← FALSE;
RETURN[IF fcb1 = fcb2 THEN identical ELSE different]};

EstablishCedarVMFile: PUBLIC PROC[fc: File.Capability] =
{ IF cedarVMFile # File.nullCapability THEN ERROR;
cedarVMFile ← fc;
IF cedarVMFile # File.nullCapability
THEN cedarVMFilePages ← File.GetSize[cedarVMFile]};

-- shortcuts

PageFromLongPointer: PROC[lp: LONG POINTER] RETURNS [page: Space.PageNumber] =
INLINE
{ OPEN LOOPHOLE[lp, num Environment.Long];
RETURN[highbits*Environment.wordsPerPage+lowbits/Environment.wordsPerPage]};



-- START HERE


EstablishCedarVMFile[Directory.Lookup[fileName: "CedarVM.DontDeleteMe",
permissions: File.read + File.write
! ANY => CONTINUE]];

pilotHeapZone ← Heap.Create[initial: 10--pages--,
parent: Space.Create[100, Space.virtualMemory],
increment: 10,
swapUnit: 1];

CedarSnapshot.Register[CheckpointCedarVM, RollbackCedarVM];

END.