-- file SymbolCache.mesa
-- last edited by Satterthwaite, May 24, 1982 10:31 am
-- last edited by Paul Rovner, February 10, 1983 12:16 pm

DIRECTORY
Environment: TYPE USING [wordsPerPage],
File: TYPE USING [ID, ShowCapability, Unknown],
FileSegment: TYPE USING [Span],
Heap: TYPE USING [systemZone],
Space: TYPE USING [
Activate, Handle, nullHandle, virtualMemory,
Create, CreateUniformSwapUnits, Delete, Error, LongPointer, Map],
Symbols: TYPE USING [HTIndex, HTRecord, MDIndex],
SymbolPack: TYPE USING [
bb, cacheInfo, ctxb, extb, extLimit, fgTable, hashVec, ht, link, ltb,
mainCtx, mdb, mdLimit, notifier, NullNotifier, seb, sourceFile, ssb,
stHandle, tb],
SymbolSegment: TYPE USING [Base, ExtIndex, FGHeader, STHeader],
SymbolTable: TYPE USING [Base, Handle, anySpan];

SymbolCache: MONITOR -- protects the cache of global frames for symbol tables
IMPORTS initial: SymbolPack, File, Heap, Space
EXPORTS SymbolTable = {

-- public interface

Handle: TYPE = SymbolTable.Handle;

Missing: PUBLIC ERROR [Handle] = CODE;
IllegalBase: PUBLIC ERROR [base: SymbolTable.Base] = CODE;


Acquire: PUBLIC ENTRY PROC [handle: Handle] RETURNS [base: SymbolTable.Base] = {
ENABLE UNWIND => {NULL};
node: CachePointer;
IF freeTables = NIL THEN {base ← NEW initial; Bump[@stats.nNew]; START base}
ELSE {base ← freeTables; freeTables ← freeTables.link};
node ← MakeCacheEntry[handle];
base.link ← busyTables; busyTables ← base;
InstallTable[base, node]};

Release: PUBLIC ENTRY PROC [base: SymbolTable.Base] = {
ENABLE UNWIND => {NULL};
cacheNode: CachePointer = base.cacheInfo;
prev: SymbolTable.Base;
Bump[@stats.nRelease];
FOR node: CachePointer ← header.next, node.next UNTIL node = cacheNode DO
IF node = free THEN ERROR IllegalBase[base] ENDLOOP;
prev ← NIL;
FOR table: SymbolTable.Base ← busyTables, table.link UNTIL table = NIL DO
IF table = base THEN {
IF prev # NIL THEN prev.link ← table.link ELSE busyTables ← table.link; EXIT};
prev ← table;
REPEAT
FINISHED => ERROR IllegalBase[base];
ENDLOOP;
FreeCacheEntry[cacheNode];
base.link ← freeTables; freeTables ← base};


Locked: PUBLIC ERROR [handle: SymbolTable.Handle, nLocks: NAT] = CODE;

Forget: PUBLIC ENTRY PROC [handle: SymbolTable.Handle] = {
ENABLE UNWIND => {NULL};

FID: PROC [h: SymbolTable.Handle] RETURNS [File.ID] = INLINE {
RETURN [h.file.ShowCapability[].fID]};

OverLap: PROC [s1, s2: FileSegment.Span] RETURNS [BOOL] = INLINE {
RETURN [IF s1.base <= s2.base
THEN s1.base + s1.pages > s2.base
ELSE s1.base < s2.base + s2.pages]};

fileId: File.ID = FID[handle];
nLocks: NAT ← 0;
prev: CachePointer;
FOR node: CachePointer ← unused.prev, prev UNTIL node = header DO
prev ← node.prev;
IF fileId = FID[node.table] AND (
handle.span = SymbolTable.anySpan OR OverLap[handle.span, node.table.span]) THEN {
IF node.refCount # 0 THEN nLocks ← nLocks + node.refCount
ELSE ReleaseCacheEntry[node]};
ENDLOOP;
IF nLocks # 0 THEN ERROR Locked[handle, nLocks]};


busyTables: SymbolTable.Base ← NIL;
freeTables: SymbolTable.Base ← initial;

symbolMasterSpace: Space.Handle ← Space.nullHandle;
cachePageLimit: CARDINAL ← 0; -- number of pages in symbolMasterSpace

CacheSize: PUBLIC PROC RETURNS [pages: CARDINAL] = {RETURN [cachePageLimit]};

SetCacheSize: PUBLIC ENTRY PROC [pages: CARDINAL] = {
ENABLE UNWIND => NULL;
IF symbolMasterSpace # Space.nullHandle THEN RETURN;
cachePageLimit ← pages;
symbolMasterSpace ← Space.Create[size: pages, parent: Space.virtualMemory];
};


-- statistics

takingStatistics: BOOLEAN = TRUE;
Count: TYPE = LONG CARDINAL ← 0;
stats: RECORD [nAcquire, nNew, nRelease: Count] ← [];

Bump: PROC [p: POINTER TO Count, delta: Count ← 1] = INLINE {
IF takingStatistics THEN p^ ← p^ + delta};


-- internal cache management

initialNodes: NAT = 15;
maxNodes: NAT = 255;

CacheNode: TYPE = RECORD [
prev, next: CachePointer,
table: Handle,
space: Space.Handle ← Space.nullHandle,
refCount: [0..maxNodes] ← 0,
nUses: NAT ← 0];

CachePointer: TYPE = LONG POINTER TO CacheNode;

header, free, unused: CachePointer;
nNodes: [0..maxNodes];
mappedPages: CARDINAL;


-- C A C H E O R G A N I Z A T I O N

-- The cache keeps track of segments in CacheNodes. The CacheNodes are
-- kept in a doubly-linked list and organized in three groups, separated
-- by three pointers: header, free, and unused. Ordered by the link
-- next, the list is organized as follows:
-- (header .. free) nodes attached to frames,
-- [free .. unused) nodes with segment but no frame,
-- [unused .. header) empty and available,

MakeCacheEntry: INTERNAL PROC [handle: Handle] RETURNS [node: CachePointer] = {
FOR node ← header.next, node.next UNTIL node = free DO
IF node.table = handle THEN GO TO allocated;
REPEAT
allocated => NULL;
FINISHED => {
FOR node ← free, node.next UNTIL node = unused DO
IF node.table = handle THEN GO TO unflushed;
REPEAT
unflushed => {
IF node = free THEN free ← node.next; Detach[node]};
FINISHED => {node ← GetEmptyNode[]; node.table ← handle};
ENDLOOP;
IF node.space = Space.nullHandle THEN {
pages: CARDINAL = handle.span.pages;
node.space ← TrimCache[pages];
IF node.space = Space.nullHandle
THEN node.space ← Space.Create[size: pages, parent: Space.virtualMemory];
IF pages > 15 THEN Space.CreateUniformSwapUnits[parent: node.space, size: 8];
node.space.Map[window: [file: node.table.file, base: node.table.span.base]
! Space.Error, File.Unknown => {ERROR Missing[handle]}];
mappedPages ← mappedPages + pages};
Insert[node, free]};
ENDLOOP;
node.refCount ← node.refCount+1; node.nUses ← node.nUses + 1;
node.space.Activate[];
RETURN};

FreeCacheEntry: INTERNAL PROC [node: CachePointer] = {
IF (node.refCount ← node.refCount-1) = 0 THEN {
position: CachePointer;
Detach[node];
FOR position ← free, position.next UNTIL position = unused DO
IF node.nUses >= position.nUses THEN EXIT;
ENDLOOP;
Insert[node, position];
IF position = free THEN free ← node}};

ReleaseCacheEntry: INTERNAL PROC [node: CachePointer] = {
IF node = free THEN free ← node.next;
ClearNode[node];
Detach[node];
Insert[node, unused]; unused ← node};

GetEmptyNode: INTERNAL PROC RETURNS [node: CachePointer] = {
IF free = header OR (unused = header AND nNodes < maxNodes) THEN {
node ← (Heap.systemZone).NEW[CacheNode]; nNodes ← nNodes + 1}
ELSE {
node ← unused;
IF node = header THEN {
IF node = free THEN free ← node.prev;
unused ← node.prev; ClearNode[node]};
unused ← node.next;
IF node = free THEN free ← node.next;
Detach[node]};
RETURN};

Detach: INTERNAL PROC [node: CachePointer] = {
node.prev.next ← node.next; node.next.prev ← node.prev};

Insert: INTERNAL PROC [node, position: CachePointer] = {
node.prev ← position.prev; node.prev.next ← node;
node.next ← position; position.prev ← node};

-- try to create a Space in symbolMasterSpace; clear enough nodes to do so
TrimCache: INTERNAL PROC [pages: CARDINAL]
RETURNS[space: Space.Handle ← Space.nullHandle] = {
DO
space ← Space.Create[size: pages, parent: symbolMasterSpace ! ANY => CONTINUE];
IF space # Space.nullHandle OR unused = free THEN RETURN;
ClearNode[unused ← unused.prev]
ENDLOOP};

ClearNode: INTERNAL PROC [node: CachePointer] = {
found: BOOLFALSE;
IF node.space # Space.nullHandle THEN {
Space.Delete[node.space];
node.space ← Space.nullHandle;
mappedPages ← mappedPages - node.table.span.pages};
node.refCount ← node.nUses ← 0};


-- symbol table setup

InstallTable: INTERNAL PROC [base: SymbolTable.Base, node: CachePointer] = {
SetBases[base, node]; base.notifier ← base.NullNotifier};

SetBases: INTERNAL PROC [base: SymbolTable.Base, node: CachePointer] = {
b: LONG POINTER = node.space.LongPointer[];
tB: SymbolSegment.Base = LOOPHOLE[b];
p: LONG POINTER TO SymbolSegment.STHeader = b;
q: LONG POINTER TO SymbolSegment.FGHeader;
base.cacheInfo ← node;
base.hashVec ← b+p.hvBlock.offset;
base.ht ← DESCRIPTOR[b+p.htBlock.offset, p.htBlock.size/SIZE[Symbols.HTRecord]];
base.ssb ← b + p.ssBlock.offset;
base.seb ← tB + p.seBlock.offset;
base.ctxb ← tB + p.ctxBlock.offset;
base.mdb ← tB + p.mdBlock.offset;
base.bb ← tB + p.bodyBlock.offset;
base.tb ← tB + p.treeBlock.offset;
base.ltb ← tB + p.litBlock.offset;
base.extb ← tB + p.extBlock.offset;
base.mdLimit ← FIRST[Symbols.MDIndex] + p.mdBlock.size;
base.extLimit ← FIRST[SymbolSegment.ExtIndex] + p.extBlock.size;
base.mainCtx ← p.outerCtx; base.stHandle ← p;
IF p.fgRelPgBase = 0 OR node.table.span.pages <= p.fgRelPgBase THEN {
base.sourceFile ← NIL; base.fgTable ← NIL}
ELSE {
offset: CARDINAL = LOOPHOLE[@(NIL[POINTER TO SymbolSegment.FGHeader]).sourceFile];
q ← b + p.fgRelPgBase*Environment.wordsPerPage;
base.sourceFile ← (b + p.fgRelPgBase*Environment.wordsPerPage) + offset;
base.fgTable ← DESCRIPTOR[b + p.fgRelPgBase*Environment.wordsPerPage + q.offset, q.length]}};

-- initialization

Init: ENTRY PROC = {
START initial; initial.link ← NIL;
header ← (Heap.systemZone).NEW[CacheNode];
header.next ← header.prev ← header;
nNodes ← 1;
FOR j: NAT IN [0..initialNodes) DO
node: CachePointer ← (Heap.systemZone).NEW[CacheNode];
Insert[node, header];
nNodes ← nNodes + 1;
ENDLOOP;
free ← unused ← header.next;
mappedPages ← 0};

Init[];

}.