
    <<InternalSymbolCache.mesa>>
        <<Copyright © 1985 by Xerox Corporation.  All rights reserved.>>
        <<Satterthwaite, June 18, 1986 10:19:50 am PDT>>
        <<Rovner, November 17, 1983 2:23 pm>>
        <<Russ Atkinson (RRA) February 11, 1985 12:32:12 pm PST>>

    DIRECTORY
        BcdDefs: TYPE USING [PageSize],
        FS: TYPE USING [Read, OpenFile, SameFile],
        FileSegment: TYPE USING [Span],
        Symbols: TYPE USING [HTIndex, MDIndex, MDFirst],
        SymbolPack: TYPE USING [bb, cacheInfo, ctxb, extb, extLimit, fgTable, hashVec, htb, link, ltb, mainCtx, mdb, mdLimit, notifier, 
        NullNotifier, seb, sourceFile, ssb, stHandle, stb, tb],
        SymbolSegment: TYPE USING [Base, FGHeader, STHeader, biases, ExtFirst, bodyType, ctxType, extType, htType, ltType, mdType, seType, 
        ssType, stType, treeType],
        SymbolTable: TYPE USING [Base, Handle, anySpan],
        VM: TYPE USING [wordsPerPage, Free, Allocate, AddressForPageNumber, Interval, CantAllocate];

    SymbolCache: MONITOR  -- protects the cache of global frames for symbol tables
        IMPORTS initial: SymbolPack, FS, VM
        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;
            interval: VM.Interval;
            {
                node _ MakeCacheEntry[handle
                    ! VM.CantAllocate => {interval _ bestInterval; GOTO cantAllocate}];
                IF freeTables = NIL THEN {base _ NEW initial; START base; Bump[@stats.nNew]}
                ELSE {base _ freeTables; freeTables _ freeTables.link};
                base.link _ busyTables;  busyTables _ base;
                InstallTable[base, node];
                EXITS
                    cantAllocate => RETURN WITH ERROR VM.CantAllocate[interval];
            };
            Bump[@stats.nAcquire]};

        Release: PUBLIC ENTRY PROC[base: SymbolTable.Base] = {
            ENABLE UNWIND => {NULL};
            cacheNode: CachePointer = LOOPHOLE[base.cacheInfo, CachePointer];  -- XXX BEWARE
            prev: SymbolTable.Base;
            FOR node: CachePointer _ header.next, node.next UNTIL node = cacheNode DO
                IF node = free THEN RETURN WITH 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 => RETURN WITH ERROR IllegalBase[base];
                ENDLOOP;
            FreeCacheEntry[cacheNode];
            base.link _ freeTables;  freeTables _ base;  Bump[@stats.nRelease]};


        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 [FS.OpenFile] = INLINE {RETURN[[h.file]]};
            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]
                };

            nLocks: INT _ 0;
            prev: CachePointer;
            FOR node: CachePointer _ unused.prev, prev UNTIL node = header DO
                prev _ node.prev;
                IF FS.SameFile[FID[handle], 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 RETURN WITH ERROR Locked[handle, nLocks]};

        BaseToHandle: PUBLIC ENTRY PROC[base: SymbolTable.Base]
              RETURNS[SymbolTable.Handle] = {
            ENABLE UNWIND => NULL;
            cacheNode: CachePointer = LOOPHOLE[base.cacheInfo, CachePointer];  -- XXX BEWARE
            RETURN[cacheNode.table]};

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

        cachePageLimit: INT _ 0;  -- number of pages for symbol tables

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

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

    <<statistics>>

        takingStatistics: BOOL = TRUE;
        Count: TYPE = INT _ 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: VM.Interval_[0, 0],
            refCount: INT_0,
            nUses: INT_0];

        CachePointer: TYPE = REF CacheNode;

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


        <<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,>>

        VMPages: PROC[bcdPages: NAT] RETURNS[vmPages: INT] = {
            RETURN[IF (BcdDefs.PageSize MOD VM.wordsPerPage) = 0
                THEN bcdPages*(BcdDefs.PageSize/VM.wordsPerPage)
                ELSE (bcdPages*BcdDefs.PageSize + (VM.wordsPerPage-1))/VM.wordsPerPage]
            };

        MakeCacheEntry: INTERNAL PROC[handle: Handle] RETURNS[node: CachePointer] = {
            FOR node _ header.next, node.next UNTIL node = free DO
                IF node.table = handle THEN GOTO allocated;
                REPEAT
                    allocated =>  NULL;
                    FINISHED => {
                        FOR node _ free, node.next UNTIL node = unused DO
                            IF node.table = handle THEN GOTO unflushed;
                            REPEAT
                                unflushed => {IF node = free THEN free _ node.next;  Detach[node]};
                                FINISHED => {node _ GetEmptyNode[];  node.table _ handle};
                            ENDLOOP;
                        IF node.space = [0, 0] THEN {
                            pages: INT = VMPages[handle.span.pages];
                            node.space _ TrimCache[pages];
                            FS.Read[
                                file: [node.table.file], 
                                from: node.table.span.base,
                                nPages: pages,
                                to: VM.AddressForPageNumber[node.space.page]];
                            };
                        Insert[node, free]};
                ENDLOOP;
            node.refCount _ node.refCount+1;  node.nUses _ node.nUses + 1;
            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 _ 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[pages: INT] RETURNS[space: VM.Interval _ [0, 0]] = {  
            UNTIL mappedPages <= cachePageLimit DO
                IF unused = free THEN EXIT;
                ClearNode[unused _ unused.prev];
            ENDLOOP;
            space _ VM.Allocate[count: pages ! VM.CantAllocate => CONTINUE];
            IF space = [0, 0] THEN {
                UNTIL unused = free DO ClearNode[unused _ unused.prev] ENDLOOP;
                space _ VM.Allocate[count: pages]};
            mappedPages _ mappedPages + pages};

        ClearNode: INTERNAL PROC[node: CachePointer] = {
            IF node.space # [0, 0] THEN {
                mappedPages _ mappedPages - node.space.count;
                VM.Free[node.space];
                node.space _ [0, 0]};
            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] = {
            OPEN SymbolSegment;
            b: LONG POINTER = VM.AddressForPageNumber[node.space.page];
            tB: SymbolSegment.Base = LOOPHOLE[b];
            p: LONG POINTER TO SymbolSegment.STHeader = b;
            q: LONG POINTER TO SymbolSegment.FGHeader;
            base.cacheInfo _ LOOPHOLE[node, LONG POINTER];  -- XXX BEWARE
            base.hashVec _ b+p.hvBlock.offset;
            base.htb _ tB+p.htBlock.offset - biases[htType];
            base.ssb _ b + p.ssBlock.offset - biases[ssType];
            base.seb _ tB + p.seBlock.offset - biases[seType];
            base.ctxb _ tB + p.ctxBlock.offset - biases[ctxType];
            base.mdb _ tB + p.mdBlock.offset - biases[mdType];
            base.bb _ tB + p.bodyBlock.offset - biases[bodyType];
            base.tb _ tB + p.treeBlock.offset - biases[treeType];
            base.ltb _ tB + p.litBlock.offset - biases[ltType];
            base.stb _ tB + p.sLitBlock.offset - biases[stType];
            base.extb _ tB + p.extBlock.offset - biases[extType];
            base.mdLimit _ Symbols.MDFirst + p.mdBlock.size;
            base.extLimit _ SymbolSegment.ExtFirst + 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 {
                FGPointer: TYPE = POINTER TO SymbolSegment.FGHeader;
                offset: CARDINAL = LOOPHOLE[@(FGPointer.NIL).sourceFile];
                q _ b + p.fgRelPgBase*BcdDefs.PageSize;
                base.sourceFile _ (b + p.fgRelPgBase*BcdDefs.PageSize) + offset;
                base.fgTable _ DESCRIPTOR[b + p.fgRelPgBase*BcdDefs.PageSize + q.offset, q.length]}
            };

    <<initialization>>

        Init: ENTRY PROC = {
            START initial;  initial.link _ NIL;
            header _ NEW[CacheNode];
            header.next _ header.prev _ header;
            nNodes _ 1;
            THROUGH [1..initialNodes] DO
                node: CachePointer _ NEW[CacheNode];
                Insert[node, header];
                nNodes _ nNodes + 1;
                ENDLOOP;
            free _ unused _ header.next;
            mappedPages _ 0};

        Init[];

        }.


