-- file SymbolCache.mesa
-- last edited by Satterthwaite, February 18, 1983 4:16 pm
-- last edited by Paul Rovner, 14-May-81 16:19:57

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 [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;


  cachePageLimit: CARDINAL ← 0;

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

  SetCacheSize: PUBLIC ENTRY PROC [pages: CARDINAL] = {
    ENABLE UNWIND => {NULL};
    cachePageLimit ← pages;  TrimCache[cachePageLimit]};


-- statistics

  takingStatistics: BOOL = 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;
	    TrimCache[MAX[cachePageLimit, pages] - pages];
	    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};

  TrimCache: INTERNAL PROC [size: CARDINAL] = {
    WHILE (mappedPages > size OR size = 0) AND unused # free DO
      ClearNode[unused ← unused.prev] ENDLOOP};

  ClearNode: INTERNAL PROC [node: CachePointer] = {
    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/Symbols.HTRecord.SIZE];
    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 ← Symbols.MDIndex.FIRST + p.mdBlock.size;
    base.extLimit ← SymbolSegment.ExtIndex.FIRST + 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[];

  }.