-- file CoreCom.Mesa (See also Core*.mesa.)
-- Edited by Kierr, February 14, 1978 1:43 PM.
-- Edited by Brotz, August 15, 1980 11:17 AM
-- Edited by Levin, June 1, 1978 9:03 AM

DIRECTORY
AltoFileDefs: FROM "AltoFileDefs",
BFSDefs: FROM "BFSDefs",
crD: FROM "CoreDefs",
crID: FROM "CoreImpDefs",
DiskDefs: FROM "DiskDefs",
DiskKDDefs: FROM "DiskKDDefs",
exD: FROM "ExceptionDefs",
gsD: FROM "GlobalStorageDefs",
ovD: FROM "OverviewDefs",
SystemDefs: FROM "SystemDefs";

CoreCom: PROGRAM
IMPORTS BFSDefs, DiskDefs, DiskKDDefs, exD, gsD, SystemDefs
EXPORTS crD, crID
SHARES crD = PUBLIC

BEGIN

OPEN crD, crID;


-- INTERNAL MODULE: AltoCore.Mesa
-- Last edited by Horning, December 20, 1977 10:49 AM.
-- Last edited by Kierr, February 8, 1978 11:37 AM.
-- Last edited by Wegbreit, November 29, 1977 3:34 PM.


-- ErrorCodes from ovD:
ok: ovD.ErrorCode=ovD.ok;
diskError: ovD.ErrorCode=ovD.diskError;
diskCorrupted: ovD.ErrorCode=ovD.diskCorrupted;
diskFull: ovD.ErrorCode=ovD.diskFull;
fileInUse: ovD.ErrorCode=ovD.fileInUse;
illegalFilename: ovD.ErrorCode=ovD.illegalFilename;
fileNotFound: ovD.ErrorCode=ovD.fileNotFound;

-- Note: all of the following operations require a UFileHandle for a file which is in open
-- state. They have no way of checking the validity of the UFileHandle. Terrible things
-- will happen if they are given an invalid UFileHandle.


GetVDA: PROCEDURE [targetPage: AltoPageNumber, aFH: AltoFileHandle]
RETURNS [erc: ovD.ErrorCode, vDA: VDA] =
-- Get the VDA for the target page, either from the table or, if necessary, by reading the
-- disk.
-- ErrorCodes: DiskError.
BEGIN
erc ← ok;
vDA ← TableGetVDA[aFH.vDATable, targetPage];
IF vDA = AltoFileDefs.fillinDA
THEN [erc, vDA] ← GetVDAByDiskReads[targetPage, aFH];
END; -- of GetVDA --


GetVDAByDiskReads: PROCEDURE [targetPage: AltoPageNumber, aFH: AltoFileHandle]
RETURNS [errorCode: ovD.ErrorCode, targetVDA: VDA] =
-- Reads the disk to find the vDA of targetPage. Fills in the vDATable along the way.
-- Returns the targetVDA if successful.
-- Error Codes: diskError
BEGIN
i, goodVDAIndex: AltoPageNumber;
scratchBuffer: gsD.MemoryPagePtr;

GetVDAInChunk: PROCEDURE [firstP, lastP: AltoPageNumber, aFH: AltoFileHandle,
vDAPtr: VDAsPtr] RETURNS [erc: ovD.ErrorCode] =
-- Globals from GetVDAByDiskReads: scratchBuffer
-- Reads the disk to get from firstP to lastP, filling in entries in the VDATable along the
-- way. firstP, lastP must be existing pages in the file described by aFH. scratchBuffer
-- is used to hold the unwanted data.
BEGIN
endPage: AltoPageNumber;
uDR: update DiskDefs.DiskRequest ←
[ca: scratchBuffer,
da: LOOPHOLE[vDAPtr, POINTER TO VDA],
firstPage: firstP,
lastPage: lastP,
fp: @aFH.handle.fp,
fixedCA: TRUE,
action: ReadD,
lastAction: ReadD,
signalCheckError: FALSE,
option: update[cleanup: BFSDefs.GetNextDA]];
erc ← ok;
[endPage, ] ← BFSDefs.ActOnPages[@uDR !
-- Catch phrases:
DiskDefs.UnrecoverableDiskError => BEGIN erc ← diskError; CONTINUE; END];
END; -- of GetVDAInChunk --

-- Strategy: This implements only the strategy of going forward from largest known vDA
-- which is smaller than targetPage. It might be profitable to later add a strategy of going
-- backward from a known vDA.

-- Find the largest known vDA that is smaller than targetPage.
FOR i DECREASING IN [0 .. targetPage] DO
IF TableGetVDA[aFH.vDATable, i] # AltoFileDefs.fillinDA THEN GOTO found;
REPEAT
found => goodVDAIndex ← i;
FINISHED => exD.SysBug[];
ENDLOOP;

scratchBuffer ← gsD.GetMemoryPages[1];
errorCode ← CallProcedureForChunks[goodVDAIndex, targetPage, aFH, GetVDAInChunk];
gsD.ReturnMemoryPages[1, scratchBuffer];
targetVDA ← TableGetVDA[aFH.vDATable, targetPage];
END; -- of GetVDAByDiskReads --


MakeFileIndexCanonical: PROCEDURE [page: UPageNumber, byte: CARDINAL]
RETURNS [UPageNumber, CARDINAL[0 .. 512)] =
-- All FileIndexes (e.g., page,byte) have to property that the corresponding CharIndex is
-- (page*512 + byte). But canonical FileIndexes also have the property that byte is IN
-- [0 .. 512).
BEGIN
delta: UPageNumber = byte/512;
RETURN [page+delta, (byte MOD 512)];
END; -- of MakeFileIndexCanonical --


MapUToAltoPage: PROCEDURE [page: UPageNumber] RETURNS [AltoPageNumber] =
BEGIN
RETURN [page+1];
END; -- of MapUToAltoPage --


MapAltoToUPage: PROCEDURE [page: AltoPageNumber] RETURNS [UPageNumber] =
BEGIN
RETURN [page-1];
END; -- of MapAltoToUPage --


-- END of AltoCore: INTERNAL MODULE;


-- VDATable: INTERNAL MODULE =
-- The vDA table is represented as a set of chunks, indexed by a descriptor block.


TableGetVDA: PROCEDURE [vDATable: VDATablePtr, page: AltoPageNumber]
RETURNS [vDA: VDA]=
-- Look in vDATable for page. Return the contents of the entry if present; otherwise,
-- return fillinDA.
BEGIN
base: VDAsPtr;
base ← vDATable↑[page/chunkSpan]; -- Address of chunk.
vDA ← (IF base=NIL THEN AltoFileDefs.fillinDA ELSE base↑[page]);
END; -- of TableGetVDA --


TablePutVDA: PROCEDURE [vDATable: VDATablePtr, page: AltoPageNumber, vDA: VDA] =
-- Puts the pair (page, vDA) in vDATable. The auxilliary entries which overlap between
-- chunks are also stored. Adds a new chunk if necessary.
BEGIN
firstInChunk, lastInChunk: AltoPageNumber;
vDAs, shadowVDAs: VDAsPtr;
cIndex: CARDINAL;

[vDAs, firstInChunk, lastInChunk, cIndex] ← GetVDAChunk[page, vDATable];
vDAs↑[page] ← vDA; -- First store in normal spot.
-- Now store in (any) auxilliary spot.
IF page IN [firstInChunk .. firstInChunk+upperPad) AND
(shadowVDAs←vDATable↑[cIndex-1])#NIL THEN
shadowVDAs↑[page] ← vDA; -- Store at end of previous chunk.
IF page IN (lastInChunk-lowerPad .. lastInChunk] AND
(shadowVDAs←vDATable↑[cIndex+1])#NIL THEN
shadowVDAs↑[page] ← vDA; -- Store at begining of next chunk.
END; -- of TablePutVDA --


GetVDAChunk: PROCEDURE [page: AltoPageNumber, vDATable: VDATablePtr]
RETURNS [base: VDAsPtr, firstInChunk,lastInChunk: AltoPageNumber,
chunkIndex: CARDINAL[0 .. numberOfChunks)] =
-- The chunk which contains "page" is returned: in the form address of the chunk,
-- inclusive limits of the chunk, and index of the chunk. The extra array entries on
-- either side of the chunk (as required by ActOnPages, etc.) are not included in the
-- limits.
BEGIN
-- The chunk is allocated if need be. An allocated chunk is initialized to "fillinDA". Then
-- if the previous and/or next chunks are there, the first upperPad and/or last lowerPad
-- entries are initialized from them.

-- Logically, we should write: i: [FIRST[AltoPageNumber]-1 .. LAST[AltoPageNumber]+1];
-- but Mesa makes us write:
i: INTEGER;
prev, next: VDAsPtr; -- Before and after "base".

chunkIndex ← page/chunkSpan;
firstInChunk ← chunkIndex*chunkSpan;
lastInChunk ← firstInChunk+chunkSpan-1;
base ← vDATable↑[chunkIndex];
IF base=NIL THEN
BEGIN -- Allocate a new chunk.
base ← SystemDefs.AllocateHeapNode[chunkSize]-firstInChunk+lowerPad;
vDATable↑[chunkIndex] ← base;
-- The 3 FOR loops below cannot be written as "i IN [range]" because
-- Mesa can’t compile the proper condition tests. This is because AltoPageNumber
-- is really [0..77777B], and hence i is [177777B..100000B], and neither a signed nor
-- an unsigned test will work for both end-conditions. Sigh..... (RL)
FOR i ← firstInChunk-lowerPad, i+1 UNTIL i = lastInChunk+upperPad+1 DO
base↑[i] ← AltoFileDefs.fillinDA; ENDLOOP;
IF (prev←vDATable↑[chunkIndex-1])#NIL THEN
FOR i ← firstInChunk-lowerPad, i+1 UNTIL i = firstInChunk+upperPad DO
base↑[i] ← prev↑[i]; ENDLOOP;
IF (next←vDATable↑[chunkIndex+1])#NIL THEN
FOR i ← lastInChunk-lowerPad+1, i+1 UNTIL i = lastInChunk+upperPad+1 DO
base↑[i] ← next↑[i]; ENDLOOP;
END; -- of Allocate a new chunk.
END; -- of GetVDAChunk --


CallProcedureForChunks: PROCEDURE [firstPage, lastPage: AltoPageNumber,
aFH: AltoFileHandle,
proc: PROCEDURE
[firstP, lastP: AltoPageNumber, aFH: AltoFileHandle, vDAPtr: VDAsPtr]
RETURNS [erc: ovD.ErrorCode]]
RETURNS [erc: ovD.ErrorCode] =
-- The proc is called for each included chunk of the vDA array, unless it asks to be stopped
-- via returning a non-ok ErrorCode. At each call to "proc", the vDAs array and its limits
-- are given as args.
-- The ErrorCode returned is the one returned by proc.
BEGIN
-- Logically, we should write: i: [FIRST[AltoPageNumber]-1 .. LAST[AltoPageNumber]+1];
-- but Mesa makes us write:
i: INTEGER;
page, firstInChunk, lastInChunk, firstP, lastP: AltoPageNumber;
vDAs, shadowVDAs: VDAsPtr;
cIndex: CARDINAL[0..numberOfChunks);

erc←ok; -- Assume ok, even for empty loop, i.e. lastPage > firstPage.
page ← firstPage;
UNTIL page > lastPage DO
[vDAs, firstInChunk, lastInChunk, cIndex] ← GetVDAChunk[page, aFH.vDATable];
firstP ← MAX[page,firstInChunk];
lastP ← MIN[lastPage,lastInChunk];

erc ← proc[firstP, lastP, aFH, vDAs];

IF erc # ok THEN EXIT;

-- Propagate possible new information from the ends of this
-- chunk to the the shadow values in the previous and next
-- chunks.
--
-- Note: The limits on the FOR loops below are tricky. They
--assume several things about the world. (1) It is always safe
--to copy VDA’s into adjacent chunks, e.g. you never end up
--copying fillinDA on top of a real VDA. (2) The upperPad and
--lowerPad are >= 1. The reasons why first-1 (rather than
--firstInChunk-lowerPad), in the first FOR, and lastP+1
--(rather than lastInChunk+1), in the second FOR, are used as
--limits are (a) Why bother to copy data that hasn’t been
--touched? and (b) Why bother to do the FOR loop at all if
--nothing on this end of the chunk has been touched?. (3) The
--lowest VDA that can be changed in a call to a chunk proc
--is one below the range (i.e. firstP-1), and the highest is
--one above (i.e. lastP+1).
-- I feel somewhat shakey about assumption 1. In case of bugs
--I feel it better to enforce the assumption than to relax
--it, but a final determination will depend upon the bug.
--/Robert
--
--All of the above is reasonable, as far as I can tell, but we still have
--the CARDINAL/INTEGER problem for the loop index. Unfortunately,
-- we can’t fix it with the same hack used in GetVDAChunk, since these
-- loops may need to be executed zero times. I’ve been forced to include an
-- explicit check for the this case.
--Roy
--
IF (shadowVDAs←aFH.vDATable↑[cIndex-1])#NIL AND
firstP <= AltoPageNumber[firstInChunk+upperPad] THEN -- copy.
FOR i ← firstP-1, i+1 UNTIL i = firstInChunk+upperPad DO
shadowVDAs↑[i]←vDAs↑[i]; ENDLOOP;
IF (shadowVDAs←aFH.vDATable↑[cIndex+1])#NIL AND
AltoPageNumber[lastInChunk-lowerPad] <= lastP THEN -- copy.
FOR i ← lastInChunk-lowerPad+1, i+1 UNTIL i = lastP+1+1 DO
shadowVDAs↑[i]←vDAs↑[i]; ENDLOOP;

page ← lastInChunk+1;
ENDLOOP;
END; -- of CallProcedureForChunks --


-- END of VDATable: INTERNAL MODULE;


-- FreePageMonitor: INTERNAL MODULE;
-- This module is responsible for maintaining a free page count and announcing changes to
-- the client.

-- Last edited by Kierr, January 27, 1978 11:57 AM.

-- Some storage for this module (FPC=FreePageCount):
currentFPC, previousFPC: CARDINAL;
notifyWithFPC: PROCEDURE [CARDINAL];
notifyWithFPCIsArmed: BOOLEAN←FALSE;


SetAltoFreePageMonitor: PROCEDURE [proc: PROCEDURE [count: CARDINAL]] =
-- The arg, proc, is called immediately and whenever the tally of Alto free pages changes.
-- (Note that this tally is only a hint.) To inhibit the function, call
-- InhibitAltoFreePageMonitor.
BEGIN
notifyWithFPC←proc;
notifyWithFPCIsArmed←TRUE;
currentFPC←previousFPC←CountAltoFreePages[];
notifyWithFPC[currentFPC];
END; -- of SetAltoFreePageMonitor --


InhibitAltoFreePageMonitor: PROCEDURE =
-- Any free page monitoring set up by SetAltoFreePageMonitor is discontinued.
BEGIN
notifyWithFPCIsArmed←FALSE;
END; -- of InhibitAltoFreePageMonitor --


CountAltoFreePages: PROCEDURE RETURNS [freePages: CARDINAL] =
-- Returns the number of pages available on the Alto dsik pack. This number is a real
-- count, not just a hint.
BEGIN
freePages←DiskKDDefs.CountFreeDiskPages[];
END; -- of AltoFreePageCount --


UpdateFreePageCount: PROCEDURE =
-- This is called internally to set up (and announce if necessary and enabled) the free page
-- tally by actually counting whenever currentFPC might be wrong.
BEGIN
currentFPC←CountAltoFreePages[];
BroadcastFreePageCount[];
END; -- of UpdateFreePageCount --


DecrementFreePageCount: PROCEDURE [deltaFPC: CARDINAL] =
-- This is called internally to change (and announce if necessary and enabled) the free page
-- tally by subtracting deltaFPC from the current total.
BEGIN
currentFPC←currentFPC-deltaFPC;
BroadcastFreePageCount[];
END; -- of DecrementFreePageCount --


BroadcastFreePageCount: PRIVATE PROCEDURE =
-- If the FPC has changed, the client is notified through his FreePageMonitor procedure set
-- up by SetAltoFreePageMonitor.
BEGIN
IF currentFPC#previousFPC AND notifyWithFPCIsArmed THEN
notifyWithFPC[previousFPC←currentFPC];
END; -- of BroadcastFreePageCount --


-- END of FreePageMonitor: INTERNAL MODULE;


END. -- of CoreCom --