-- RTProcessPrivateImpl.Mesa
-- last edited December 6, 1982 12:49 pm by Paul Rovner

DIRECTORY
Environment USING[Long, wordsPerPage],
Inline USING[LongCOPY],
PrincOps USING [FrameHandle, GlobalFrameHandle, Frame, LargeReturnSlot,
FrameVec, NullFrame, stackDepth],
Process USING[Priority, SetPriority, GetPriority, ValidateProcess, InvalidProcess],
ProcessInternal USING[DisableInterrupts],
ProcessOperations USING[IndexToHandle, HandleToIndex, ReadPSB,
EnableAndRequeue],
PSB USING[PsbHandle, PsbIndex, PDA, StateVectorHandle],
RTBasic USING [Address],
RTProcessPrivate USING [NewCedarProcessRegistered, InvalidateCedarProcess],
RTCommon USING [ShortenLongCardinal],
RTOS USING[GetCurrent, MyLocalFrame, End],
RTSponge USING[spongePriority, StartSponge, StopSponge],
Runtime USING[GlobalFrame],
Space USING[nullHandle, PageNumber, Create, virtualMemory, Handle,
Delete, Map, LongPointer, Kill],
SpecialSpace USING[MakeResident, MakeSwappable, MakeCodeResident, MakeCodeSwappable,
MakeGlobalFrameResident--, MakeGlobalFrameSwappable--];

RTProcessPrivateImpl: MONITOR -- Protects the Cedar process registry
IMPORTS Inline, Process, ProcessInternal, ProcessOperations, RTCommon, RTOS, RTProcessPrivate, RTSponge, Runtime,
Space, SpecialSpace
EXPORTS RTOS
= BEGIN


PageSize: CARDINAL = Environment.wordsPerPage;

--Public signals
FrameCopySpaceTooSmall: PUBLIC SIGNAL = CODE;
TooManyCedarProcesses: PUBLIC SIGNAL = CODE;

-- Variables
-- pinned frame body buffer for the stack scan
fbpNext: LONG POINTERNIL;
fbp: LONG POINTERNIL; -- init by a call on AllocateFrameBodyBuffer from RTRefCountsImpl
fbpMaxNext: LONG POINTER;
codeLockCount: CARDINAL ← 0;
rtProcessStarted: BOOLEANFALSE;
frameBodyMasterPages: CARDINAL ← 60;
frameBodyMasterSpace: Space.Handle ← Space.nullHandle;
frameBodyBufferSpace: Space.Handle ← Space.nullHandle;

thisModule: PROGRAM = Runtime.GlobalFrame[RegisterCedarProcess];

-- NOTE resident
CedarProcessRegistry: PACKED ARRAY PSB.PsbIndex OF BOOLALL[FALSE];

nCedarProcesses: CARDINAL ← 0;

maxCedarPSBI: PSB.PsbIndex ← 0;

CedarFrameChainRegistry: ARRAY [0..CedarFrameChainRegistrySize) OF DESCRIPTOR FOR ARRAY OF POINTER TO PrincOps.FrameHandle;
CedarFrameChainRegistrySize: CARDINAL = 20;
fchNext: CARDINAL ← 0;


-- ************ Process registry maintenance (Begin)

UnregisterCedarProcess: PUBLIC PROC[psbi: PSB.PsbIndex] =
{ IF DoUnregisterCedarProcess[psbi] AND rtProcessStarted
THEN RTProcessPrivate.InvalidateCedarProcess[psbi]};

DoUnregisterCedarProcess: ENTRY PROC[psbi: PSB.PsbIndex]
RETURNS[deregistered: BOOLEAN] =
{ ENABLE UNWIND => NULL;
IF CedarProcessRegistry[psbi]
THEN {CedarProcessRegistry[psbi] ← FALSE;
IF psbi = maxCedarPSBI THEN maxCedarPSBI ← maxCedarPSBI - 1;
nCedarProcesses ← nCedarProcesses - 1;
RETURN[TRUE]}
ELSE RETURN[FALSE]};

RegisterCedarProcess: PUBLIC PROC[psbi: PSB.PsbIndex] =
{IF DoRegisterCedarProcess[psbi].found THEN RETURN;
IF rtProcessStarted THEN RTProcessPrivate.NewCedarProcessRegistered[psbi]};

DoRegisterCedarProcess: ENTRY PROC[psbi: PSB.PsbIndex] RETURNS[found: BOOL] =
{ ENABLE UNWIND => NULL;
IF CedarProcessRegistry[psbi] THEN RETURN[TRUE]; -- awreddy registered.

-- whiz up the dynamic call chain until I find the top
-- and stuff RTOS.End there
FOR root: PrincOps.FrameHandle ← RTOS.MyLocalFrame[], root.returnlink.frame
DO IF root.returnlink.proc THEN {root.returnlink ← LOOPHOLE[RTOS.End]; EXIT} ENDLOOP;

-- now register it.
CedarProcessRegistry[psbi] ← TRUE;
IF psbi > maxCedarPSBI THEN maxCedarPSBI ← psbi;
nCedarProcesses ← nCedarProcesses + 1;
RETURN[FALSE]};

RegisterFrameChains: PUBLIC ENTRY PROC[d: DESCRIPTOR FOR ARRAY OF POINTER TO PrincOps.FrameHandle] =
{ ENABLE UNWIND => NULL;
FOR i: CARDINAL IN [0..fchNext)
DO IF CedarFrameChainRegistry[i] = DESCRIPTOR[NIL, 0]
THEN {CedarFrameChainRegistry[i] ← d; RETURN}; -- found an empty slot
ENDLOOP;

-- not enough slots; here to extend the registry (someday)
IF fchNext > CedarFrameChainRegistrySize THEN ERROR; -- ERROR TooManyCedarFrameChains;

CedarFrameChainRegistry[fchNext] ← d;
fchNext ← fchNext + 1};

UnregisterFrameChains: PUBLIC ENTRY PROC[d: DESCRIPTOR FOR ARRAY OF POINTER TO PrincOps.FrameHandle] =
{ ENABLE UNWIND => NULL;
FOR i: CARDINAL IN [0..fchNext)
DO IF CedarFrameChainRegistry[i] = d
THEN {CedarFrameChainRegistry[i] ← DESCRIPTOR[NIL, 0]; RETURN}; -- found it
ENDLOOP;
ERROR};


-- ************ Process registry maintenance (End)


--**************Stoppable process stuff (Begin)

-- After this returns there will be no cedar client activity until ReleaseProcesses returns
-- StopProcesses can't use FrameImpl for its return
-- (i.e it must be an inline), because someone at a
-- lower priority may be in FrameImpl, holding the lock
StopProcesses: PROC RETURNS[oldPriority: Process.Priority] =
INLINE
{ oldPriority ← Process.GetPriority[]; -- remember the caller's priority
IF oldPriority >= RTSponge.spongePriority THEN ERROR;
Process.SetPriority[RTSponge.spongePriority];
RTSponge.StartSponge[];
RETURN[oldPriority]};

Stopped: PROC[ph: PSB.PsbHandle] RETURNS[BOOLEAN] =
INLINE
{RETURN[PSB.PDA[ph].link.priority < RTSponge.spongePriority]};

ReleaseProcesses: PROC[oldPriority: Process.Priority] =
INLINE
{ -- allow cedar client processes to continue
RTSponge.StopSponge[];
Process.SetPriority[oldPriority]};

--**************Stoppable process stuff (End)


-- Collector invariant guarantees that only one guy at a time will be in here.
SnapshotTheFrameHeap: PUBLIC ENTRY PROC =
{ ENABLE UNWIND => NULL;
myPriority: Process.Priority;
success: BOOLEAN;

SpecialSpace.MakeResident[frameBodyBufferSpace];
fbpNext ← fbp;
-- If fbp storage is too small, FrameCopySpaceTooSmall will be raised AFTER
-- ReleaseProcesses is done
LockThisCode[];
myPriority ← StopProcesses[]; -- an INLINE. No use of FrameImpl for return, cause
-- someone at a lower priority may be in it, holding its lock.
success ← DoSnapshotTheFrameHeap[];
ReleaseProcesses[myPriority];
UnLockThisCode[];
IF NOT success
THEN {Space.Kill[frameBodyBufferSpace];
SpecialSpace.MakeSwappable[frameBodyBufferSpace];
ERROR FrameCopySpaceTooSmall};
SpecialSpace.MakeSwappable[frameBodyBufferSpace]};

DoSnapshotTheFrameHeap: INTERNAL PROC RETURNS[success: BOOLEAN] = INLINE
{ myPSBI: PSB.PsbIndex = RTOS.GetCurrent[];
FOR i: CARDINAL IN [0..fchNext)
DO FOR j: CARDINAL IN [0..LENGTH[CedarFrameChainRegistry[i]])
DO IF NOT WalkForCopy[CedarFrameChainRegistry[i][j]^] THEN RETURN[FALSE]
ENDLOOP;
ENDLOOP;
FOR psbi: PSB.PsbIndex IN [1..maxCedarPSBI]
DO IF CedarProcessRegistry[psbi] THEN
{ ph: PSB.PsbHandle = ProcessOperations.IndexToHandle[psbi];
f: PrincOps.FrameHandle ← NIL;
IF myPSBI # psbi
THEN UNTIL Stopped[ph] DO Yield[] ENDLOOP;
-- here with control, which is retained AT THIS PRIORITY til end of FOR iteration
IF myPSBI = psbi
THEN f ← RTOS.MyLocalFrame[].returnlink.frame -- caller of SnapshotTheFrameHeap
ELSE IF PSB.PDA[ph].link.vector
THEN -- there's a state vector here
{ svh: PSB.StateVectorHandle = PSB.PDA[ph].context.state;
IF NOT CopyFrameBody[LOOPHOLE[@PSB.PDA[svh].stk,
LONG POINTER TO RTBasic.Address],
MIN[PSB.PDA[svh].stkptr + 1, PrincOps.stackDepth]]
THEN RETURN[FALSE]; -- + 1: one word of a REF may have been popped
f ← PSB.PDA[svh].frame}
ELSE f ← PSB.PDA[ph].context.frame;
IF NOT WalkForCopy[f] THEN RETURN[FALSE];
};-- end IF CedarProcessRegistry[psbi] THEN
ENDLOOP;
RETURN[TRUE]};

CheckForModuleReplacement: PUBLIC ENTRY PROC[gfh: PrincOps.GlobalFrameHandle]
RETURNS[ok: BOOLEAN] =
{ ENABLE UNWIND => NULL;
myPriority: Process.Priority;
IF gfh.copied OR gfh.shared THEN RETURN[FALSE];
LockThisCode[];
myPriority ← StopProcesses[]; -- an INLINE. No use of FrameImpl for return
ok ← LookForGFH[gfh];
ReleaseProcesses[myPriority];
UnLockThisCode[]};

WalkForCopy: INTERNAL PROC[f: PrincOps.FrameHandle] RETURNS[success: BOOLEAN ← TRUE] =
INLINE
{WHILE ValidFrame[f] DO
IF NOT CopyFrameBody[LOOPHOLE[LONG[@f.local]],
GetFrameSize[f]-SIZE[local PrincOps.Frame]]
THEN RETURN[FALSE];
f ← f.returnlink.frame;
ENDLOOP};

WalkForLook: INTERNAL PROC[f: PrincOps.FrameHandle, gfh: PrincOps.GlobalFrameHandle]
RETURNS[found: BOOLEAN ← FALSE] =
INLINE
{WHILE ValidFrame[f]
DO IF f.accesslink = gfh THEN RETURN[TRUE];
f ← f.returnlink.frame;
ENDLOOP};

LookForGFH: INTERNAL PROC[gfh: PrincOps.GlobalFrameHandle]
RETURNS[notFound: BOOLEANTRUE] =
INLINE
{ myPSBI: PSB.PsbIndex = RTOS.GetCurrent[];
FOR i: CARDINAL IN [0..fchNext)
DO FOR j: CARDINAL IN [0..LENGTH[CedarFrameChainRegistry[i]])
DO IF WalkForLook[CedarFrameChainRegistry[i][j]^, gfh] THEN RETURN[FALSE]
ENDLOOP;
ENDLOOP;
FOR psbi: PSB.PsbIndex IN [1..maxCedarPSBI]
DO IF CedarProcessRegistry[psbi] THEN
{ ph: PSB.PsbHandle = ProcessOperations.IndexToHandle[psbi];
f: PrincOps.FrameHandle ← NIL;
IF myPSBI # psbi
THEN UNTIL Stopped[ph] DO Yield[] ENDLOOP;
-- here with control, which is retained AT THIS PRIORITY til end of FOR iteration
IF myPSBI = psbi
THEN f ← RTOS.MyLocalFrame[].returnlink.frame
-- caller of SnapshotTheFrameHeap
ELSE IF PSB.PDA[ph].link.vector
THEN -- there's a state vector here
{ svh: PSB.StateVectorHandle = PSB.PDA[ph].context.state;
f ← PSB.PDA[svh].frame}
ELSE f ← PSB.PDA[ph].context.frame;
IF WalkForLook[f, gfh] THEN RETURN[FALSE]};
ENDLOOP};

LockThisCode: INTERNAL PROC =
INLINE
{SpecialSpace.MakeGlobalFrameResident[RTProcessPrivateImpl];
SpecialSpace.MakeCodeResident[thisModule]};

UnLockThisCode: INTERNAL PROC =
INLINE
{SpecialSpace.MakeCodeSwappable[thisModule];
--SpecialSpace.MakeGlobalFrameSwappable[RTProcessPrivateImpl]--};

MapRCFrameBodies: PUBLIC PROC[conservativeScanner:
PROC[pa: LONG POINTER TO RTBasic.Address,
nWords: CARDINAL]] =
{conservativeScanner
[fbp,
RTCommon.ShortenLongCardinal[LOOPHOLE[fbpNext, LONG CARDINAL]
- LOOPHOLE[fbp, LONG CARDINAL]]];
Space.Kill[frameBodyBufferSpace];
};

-- called by the collector: only one guy at a time can be in
-- here, and exclusive of SnapshotTheFrameHeap
AllocateFrameBodyBuffer: PUBLIC PROC[nPages: CARDINAL] =
{ IF frameBodyBufferSpace # Space.nullHandle THEN Space.Delete[frameBodyBufferSpace];
IF nPages > frameBodyMasterPages THEN
{ frameBodyMasterPages ← nPages;
Space.Delete[frameBodyMasterSpace];
frameBodyMasterSpace ← Space.Create[size: frameBodyMasterPages,
parent: Space.virtualMemory]};

frameBodyBufferSpace ← Space.Create[nPages, frameBodyMasterSpace];
Space.Map[frameBodyBufferSpace];
fbp ← fbpNext ← Space.LongPointer[frameBodyBufferSpace];
fbpMaxNext ← fbp + nPages * PageSize - 1};

CopyFrameBody: INTERNAL PROC[pa: LONG POINTER, nWords: CARDINAL]
RETURNS[success: BOOLEAN] =
INLINE
{ dest: LONG POINTER = fbpNext;
fbpNext ← fbpNext + nWords;
IF LOOPHOLE[fbpNext, LONG CARDINAL] > LOOPHOLE[fbpMaxNext, LONG CARDINAL]
THEN RETURN[FALSE]
ELSE {Inline.LongCOPY[pa, nWords, dest]; RETURN[TRUE]}};

GetFrameSize: PROC[f: PrincOps.FrameHandle] RETURNS [CARDINAL] =
INLINE
{ FramePrefix: TYPE = MACHINE DEPENDENT RECORD[ size: CARDINAL, fsi: CARDINAL];
prefix: POINTER TO FramePrefix = LOOPHOLE[f - SIZE[FramePrefix]];
RETURN[IF prefix.fsi = PrincOps.LargeReturnSlot
THEN prefix.size - 1 -- the fsi word is included in the size
ELSE PrincOps.FrameVec[prefix.fsi]]};

ValidFrame: PROC[f: PrincOps.FrameHandle]
RETURNS[BOOLEAN] =
INLINE
{RETURN[f # PrincOps.NullFrame AND LOOPHOLE[f, CARDINAL] MOD 4 = 0]};

InvalidProcess: PROC[px: PSB.PsbIndex] RETURNS[BOOLEAN] =
{ Process.ValidateProcess[px ! Process.InvalidProcess => GOTO ng];
RETURN[FALSE];
EXITS ng => RETURN[TRUE]};


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

Yield: PROC =
INLINE
{ ProcessInternal.DisableInterrupts[]; -- (to cancel subsequent enable)
ProcessOperations.EnableAndRequeue[@PSB.PDA.ready,
@PSB.PDA.ready,
ProcessOperations.ReadPSB[]] };

RTProcessStarted: PUBLIC ENTRY PROC =
{ENABLE UNWIND => NULL;
rtProcessStarted ← TRUE;
FOR psbi: PSB.PsbIndex IN [1..maxCedarPSBI]
DO IF CedarProcessRegistry[psbi] THEN RTProcessPrivate.NewCedarProcessRegistered[psbi];
ENDLOOP};


-- START HERE

-- pre-allocate space for the frameheap snapshot 'cause collection maybe for VMExhausted
frameBodyMasterSpace ← Space.Create[size: frameBodyMasterPages, parent: Space.virtualMemory];

END.