BTreeSimpleImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Taft, January 21, 1984 5:00:25 pm PST
Russ Atkinson (RRA) March 11, 1985 9:48:59 pm PST
DIRECTORY
Ascii USING [Lower],
BTree USING [Compare, CompareEntries, DeleteKey, Entry, EntryFromRecord, EntrySize, EnumerateEntries, GetState, Key, New, NewPathStk, NewRecordFromEntry, Open, PageNumber, PagePtr, PageStorage, PathStk, ReadEntry, ReferencePage, Relation, ReleasePage, SalvageEntries, SetState, SetUpdateInProgress, State, statePage, Tree, UpdateEntry, UpdateType, Validate],
BTreeSimple,
BTreeVM USING [CacheSize, FilePagesPerPage],
FS USING [BytesForPages, GetInfo, OpenFile, Read, SetByteCountAndCreatedTime, SetPageCount, WordsForPages, Write],
PrincOpsUtils USING [GetClockPulses, LongCopy],
Process USING [Detach],
Rope USING [InlineFlatten, TextRep],
SafeStorage USING [CantEstablishFinalization, EnableFinalization, EstablishFinalization, FinalizationQueue, FQNext, NewFQ, ReEstablishFinalization],
VM USING [AddressForPageNumber, Allocate, Free, Interval, PagesForWords, PageNumberForAddress];
BTree representation
KeyFromEntry:
PUBLIC
UNSAFE
PROC [entryKey: EntryKey]
RETURNS [key: InternalKey] =
UNCHECKED {
IF entryKey=NIL THEN RETURN[NIL];
key ← NEW[Rope.TextRep[entryKey.length] ← [text[length: entryKey.length, text: ]]];
PrincOpsUtils.LongCopy[to: BASE[DESCRIPTOR[key.text]], from: BASE[DESCRIPTOR[entryKey.text]], nwords: (entryKey.length+1)/2];
};
ValueFromEntry:
PUBLIC
UNSAFE
PROC [entryValue: EntryValue]
RETURNS [value: Value] =
UNCHECKED {
IF entryValue=NIL THEN RETURN[NIL];
value ← NEW[ValueObject[entryValue.length]];
PrincOpsUtils.LongCopy[to: BASE[DESCRIPTOR[value.words]], from: BASE[DESCRIPTOR[entryValue.words]], nwords: entryValue.length];
};
DefaultCompare: Compare
--[key: InternalKey, entryKey: EntryKey]
RETURNS [Comparison]-- =
UNCHECKED {
FOR i:
CARDINAL
IN [0 ..
MIN[key.length, entryKey.length])
DO
kc: CHAR ← Ascii.Lower[key[i]];
ec: CHAR ← Ascii.Lower[entryKey[i]];
IF kc#ec THEN RETURN[IF kc<ec THEN less ELSE greater];
ENDLOOP;
RETURN[IF key.length=entryKey.length THEN equal
ELSE IF key.length<entryKey.length THEN less ELSE greater];
};
DefaultCompareEntries: CompareEntries
--[entryKey1, entryKey2: EntryKey]
RETURNS [Comparison]-- =
UNCHECKED {
FOR i:
CARDINAL
IN [0 ..
MIN[entryKey1.length, entryKey2.length])
DO
c1: CHAR ← Ascii.Lower[entryKey1[i]];
c2: CHAR ← Ascii.Lower[entryKey2[i]];
IF c1#c2 THEN RETURN[IF c1<c2 THEN less ELSE greater];
ENDLOOP;
RETURN[IF entryKey1.length=entryKey2.length THEN equal
ELSE IF entryKey1.length<entryKey2.length THEN less ELSE greater];
};
defaultCompareProcs: PUBLIC CompareProcs ← [compare: DefaultCompare, compareEntries: DefaultCompareEntries];
BTree global operations
New:
PUBLIC PROC [compareProcs: CompareProcs ← defaultCompareProcs, initialState: State[closed..suspended] ← closed]
RETURNS [tree: Tree] = {
RETURN[BTree.New[repPrim: [compare: LOOPHOLE[compareProcs.compare], compareEntries: LOOPHOLE[compareProcs.compareEntries], entrySize: EntrySize, entryFromRecord: EntryFromRecord, newRecordFromEntry: NewRecordFromEntry], storPrim: [referencePage: ReferencePage, releasePage: ReleasePage], minEntrySize: EntryKeyObject[1].SIZE+ValueObject[0].SIZE, initialState: initialState]];
};
Open:
PUBLIC PROC [tree: Tree, file: FS.OpenFile, filePagesPerPage: FilePagesPerPage ← 1, cacheSize: CacheSize ← 25, initialize:
BOOLEAN ←
FALSE, maintainRecomputableState:
BOOLEAN ←
TRUE] = {
storage: Handle = CacheOpen[file: file, filePagesPerPage: filePagesPerPage, cacheSize: cacheSize];
tree.Open[storage: storage, pageSize: FS.WordsForPages[filePagesPerPage], initialize: initialize, maintainRecomputableState: maintainRecomputableState];
};
SetState:
PUBLIC PROC [tree: Tree, state: State[closed..suspended]] = {
tree.SetState[state];
};
GetState:
PUBLIC PROC [tree: Tree]
RETURNS [state: State, entryCount:
LONG
CARDINAL, greatestPage:
INT, depth:
CARDINAL, file: FS.OpenFile] = {
storage: BTree.PageStorage;
[state: state, entryCount: entryCount, greatestPage: greatestPage, depth: depth, storage: storage] ← tree.GetState[];
file ← NARROW[storage, Handle].file;
};
GetStatistics:
PUBLIC PROC [tree: Tree]
RETURNS [hits, misses, reads, writes, cumChainLength, cumReadWriteTime:
LONG
CARDINAL] = {
RETURN GetCacheStats[NARROW[tree.GetState[].storage]];
};
Validate:
PUBLIC PROC [tree: Tree] = {
tree.Validate[];
};
SetUpdateInProgress:
PUBLIC PROC [tree: Tree, updateInProgress:
BOOLEAN] = {
tree.SetUpdateInProgress[updateInProgress];
};
Higher-level BTree access
ReadRecord:
PUBLIC
PROC [tree: Tree, key: Key, relation: Relation ← equal, pathStk: PathStk ←
NIL, useExistingPath:
BOOLEAN ←
FALSE]
RETURNS [actualKey: InternalKey, value: Value] =
TRUSTED {
EntryProc:
UNSAFE
PROC [key: EntryKey, value: EntryValue] =
UNCHECKED {
IF key=NIL THEN {actualKey ← NIL; returnValue ← NIL}
ELSE {actualKey ← KeyFromEntry[key]; returnValue ← ValueFromEntry[value]};
};
returnValue: Value;
ReadEntry[tree: tree, key: key, relation: relation, pathStk: pathStk, useExistingPath: useExistingPath, Proc: EntryProc];
RETURN [actualKey, returnValue];
};
ReadValue:
PUBLIC PROC [tree: Tree, key: Key, relation: Relation ← equal, pathStk: PathStk ←
NIL, useExistingPath:
BOOLEAN ←
FALSE]
RETURNS [value: Value] =
TRUSTED {
EntryProc:
UNSAFE
PROC [key: EntryKey, value: EntryValue] =
UNCHECKED {
returnValue ← (IF value=NIL THEN NIL ELSE ValueFromEntry[value]);
};
returnValue: Value;
ReadEntry[tree: tree, key: key, relation: relation, pathStk: pathStk, useExistingPath: useExistingPath, Proc: EntryProc];
RETURN [returnValue];
};
EnumerateRecords:
PUBLIC PROC [tree: Tree, key: Key, relation: Relation ← equal, pathStk: PathStk ←
NIL, useExistingPath:
BOOLEAN ←
FALSE, Proc:
PROC [key: InternalKey, value: Value]
RETURNS [continue:
BOOLEAN]]
RETURNS [exhausted:
BOOLEAN] =
TRUSTED {
EntryProc:
UNSAFE
PROC [key: EntryKey, value: EntryValue]
RETURNS [continue:
BOOLEAN] =
UNCHECKED {
RETURN [Proc[key: KeyFromEntry[key], value: ValueFromEntry[value]]] };
exhausted ← EnumerateEntries[tree: tree, key: key, pathStk: pathStk, useExistingPath: useExistingPath, Proc: EntryProc];
};
UpdateRecord:
PUBLIC PROC [tree: Tree, key: Key, value: Value, pathStk: PathStk ←
NIL, useExistingPath:
BOOLEAN ←
FALSE, updateType: UpdateType ← insertOrReplace] =
TRUSTED {
ProduceEntry:
UNSAFE
PROC [value: EntryValue] =
UNCHECKED {
PrincOpsUtils.LongCopy[to: value, from: suppliedValue, nwords: ValueObject[suppliedValue.length].SIZE]};
suppliedValue: LONG POINTER TO ValueObject = LOOPHOLE[value];
UpdateEntry[tree: tree, key: key, pathStk: pathStk, useExistingPath: useExistingPath, valueLength: suppliedValue.length, Proc: ProduceEntry, updateType: updateType];
};
DeleteKey:
PUBLIC PROC [tree: Tree, key: Key, pathStk: PathStk ←
NIL, useExistingPath:
BOOLEAN ←
FALSE]
RETURNS [found:
BOOLEAN] = {
RETURN[tree.DeleteKey[key: key.InlineFlatten[], pathStk: pathStk, useExistingPath: useExistingPath]];
};
Lower-level BTree access
ReadEntry:
PUBLIC
UNSAFE
PROC [tree: Tree, key: Key, relation: Relation ← equal, pathStk: PathStk ←
NIL, useExistingPath:
BOOLEAN ←
FALSE, Proc:
UNSAFE
PROC [key: EntryKey, value: EntryValue]] =
UNCHECKED {
EntryProc:
UNSAFE
PROC [entry: BTree.Entry] =
UNCHECKED {
IF entry=NIL THEN Proc[NIL, NIL]
ELSE Proc[entry, entry+EntryKeyObject[LOOPHOLE[entry, EntryKey].length].SIZE];
};
tree.ReadEntry[key: key.InlineFlatten[], relation: relation, pathStk: pathStk, useExistingPath: useExistingPath, Proc: EntryProc];
};
EnumerateEntries:
PUBLIC UNSAFE
PROC [tree: Tree, key: Key, relation: Relation ← equal, pathStk: PathStk ←
NIL, useExistingPath:
BOOLEAN ←
FALSE, Proc:
UNSAFE
PROC [key: EntryKey, value: EntryValue]
RETURNS [continue:
BOOLEAN]]
RETURNS [exhausted:
BOOLEAN] =
UNCHECKED {
EntryProc:
UNSAFE
PROC [entry: BTree.Entry]
RETURNS [continue:
BOOLEAN] =
UNCHECKED {
RETURN[Proc[entry, entry+EntryKeyObject[LOOPHOLE[entry, EntryKey].length].SIZE]]};
exhausted ← tree.EnumerateEntries[key: key.InlineFlatten[], relation: relation, pathStk: pathStk, useExistingPath: useExistingPath, Proc: EntryProc];
};
UpdateEntry:
PUBLIC UNSAFE
PROC [tree: Tree, key: Key, pathStk: PathStk ←
NIL, useExistingPath:
BOOLEAN ←
FALSE, valueLength:
CARDINAL, Proc:
UNSAFE
PROC [value: EntryValue], updateType: UpdateType ← insertOrReplace] =
UNCHECKED {
EntryProc:
UNSAFE
PROC [entry: BTree.Entry] =
UNCHECKED {
entryKey: EntryKey = LOOPHOLE[entry];
entryValue: EntryValue = LOOPHOLE[entry+entryKeySize];
LOOPHOLE[entryKey, LONG POINTER TO CARDINAL]^ ← internalKey.length; -- entryKey.length ← internalKey.length
PrincOpsUtils.LongCopy[to: BASE[DESCRIPTOR[entryKey.text]], from: BASE[DESCRIPTOR[internalKey.text]], nwords: (internalKey.length+1)/2];
Proc[entryValue];
LOOPHOLE[entryValue, LONG POINTER TO CARDINAL]^ ← valueLength; -- entryValue.length ← valueLength
};
internalKey: InternalKey = key.InlineFlatten[];
entryKeySize: CARDINAL = EntryKeyObject[internalKey.length].SIZE;
tree.UpdateEntry[key: internalKey, pathStk: pathStk, useExistingPath: useExistingPath, words: entryKeySize+ValueObject[valueLength].SIZE, Proc: EntryProc, updateType: updateType];
};
SalvageEntries:
PUBLIC UNSAFE
PROC [tree: Tree, Proc:
UNSAFE
PROC [key: EntryKey, value: EntryValue]
RETURNS [continue:
BOOLEAN]]
RETURNS [exhausted:
BOOLEAN] =
UNCHECKED {
EntryProc:
UNSAFE
PROC [entry: BTree.Entry]
RETURNS [continue:
BOOLEAN] =
UNCHECKED {
RETURN[Proc[entry, entry+EntryKeyObject[LOOPHOLE[entry, EntryKey].length].SIZE]]};
exhausted ← tree.SalvageEntries[Proc: EntryProc];
};
Private
The following are the procedures passed to the BTree package as the RepresentationPrimitives (in addition to the client's Compare and CompareEntries procedures).
EntrySize: BTree.EntrySize
--[entry: BTree.Entry]
RETURNS [words: BTree.EntSize]-- =
UNCHECKED {
words ← EntryKeyObject[LOOPHOLE[entry, EntryKey].length].SIZE;
words ← words + ValueObject[LOOPHOLE[entry+words, EntryValue].length].SIZE;
};
EntryFromRecord: BTree.EntryFromRecord
--[record: BTree.Record]
RETURNS [entry: BTree.Entry]-- =
UNCHECKED {
ERROR Bug[cantGetHere]; -- we never call the BTree procedures that traffic in Records
};
NewRecordFromEntry: BTree.NewRecordFromEntry
--[entry: BTree.Entry]
RETURNS [record: BTree.Record]-- =
UNCHECKED {
ERROR Bug[cantGetHere]; -- we never call the BTree procedures that traffic in Records
};
Storage primitives and BTree cache
This is almost identical to BTreeVMImpl, and perhaps someday most of the code should be shared (in particular, everything to do with VM cache management as opposed to file I/O). The main difference is that the BTree is designated by an FS.OpenFile rather than a File.Handle.
CacheIndex: TYPE = [0 .. LAST[BTreeVM.CacheSize]];
noIndex: CacheIndex = LAST[CacheIndex];
Handle: TYPE = REF VMObject;
VMObject:
TYPE =
MONITORED
RECORD [
file: FS.OpenFile,
filePages: INT,
filePagesPerPage: BTreeVM.FilePagesPerPage,
vmPagesPerPage: CARDINAL,
rover: CacheIndex,
cache: REF Cache,
hits, misses, reads, writes, cumChainLength, cumReadWriteTime: LONG CARDINAL ← 0,
hashTable: PACKED SEQUENCE count: CARDINAL OF CacheIndex
];
Cache: TYPE = RECORD [PACKED SEQUENCE count: CacheIndex OF CacheEntry];
CacheEntry:
TYPE =
RECORD [
pNum: BTree.PageNumber,
ptr: BTree.PagePtr,
referenceCount: [0..256),
hash: CacheIndex,
empty, recentlyUsed, dirty: BOOLEAN
];
CacheOpen:
PROC [file: FS.OpenFile, filePagesPerPage: BTreeVM.FilePagesPerPage,
cacheSize: BTreeVM.CacheSize]
RETURNS [Handle] = {
h: Handle ← NEW[VMObject[2*cacheSize+1]];
h.file ← file;
h.filePages ← 0; -- indicates that we don't know --
h.filePagesPerPage ← filePagesPerPage;
h.vmPagesPerPage ← VM.PagesForWords[FS.WordsForPages[filePagesPerPage]];
h.rover ← 0;
h.cache ← NEW[Cache[cacheSize]];
FOR i: CacheIndex
IN [0 .. h.cache.count)
DO
h.cache[i].ptr ← NIL;
h.cache[i].empty ← TRUE;
ENDLOOP;
FOR c:
CARDINAL
IN [0..h.count)
DO
h.hashTable[c] ← noIndex;
ENDLOOP;
SafeStorage.EnableFinalization[h];
RETURN [h];
};
ReferencePage: BTree.ReferencePage
--[storage: PageStorage, number: PageNumber, type: ReferenceType]
RETURNS [ptr: PagePtr]-- = {
InnerReference:
ENTRY
PROC [h: Handle] =
INLINE {
hash: CARDINAL = HashIndex[h, number];
i: CacheIndex;
FOR i ← h.hashTable[hash], h.cache[i].hash
UNTIL i = noIndex
DO
h.cumChainLength ← h.cumChainLength+1;
IF h.cache[i].pNum = number THEN { h.hits ← h.hits+1; EXIT };
REPEAT
FINISHED => {
-- not found --
h.misses ← h.misses+1;
i ← GetEmptyCacheEntry[h];
h.cache[i].pNum ← number;
h.cache[i].empty ← FALSE;
IF type # new THEN LoadCachePage[h, i];
h.cache[i].hash ← h.hashTable[hash];
h.hashTable[hash] ← i;
};
ENDLOOP;
h.cache[i].recentlyUsed ← TRUE;
h.cache[i].referenceCount ← h.cache[i].referenceCount + 1;
IF type # read THEN h.cache[i].dirty ← TRUE;
IF type = new THEN ExtendFile[h, number];
ptr ← h.cache[i].ptr;
};
InnerReference[NARROW[storage]];
};
ReleasePage: BTree.ReleasePage
--[storage: PageStorage, number: PageNumber, update: UpdateState]-- = {
InnerRelease:
ENTRY
PROC [h: Handle] =
INLINE {
i: CacheIndex;
FOR i ← h.hashTable[HashIndex[h, number]], h.cache[i].hash
UNTIL i = noIndex
DO
IF h.cache[i].pNum = number THEN EXIT;
REPEAT FINISHED => ERROR Bug[pageNotInUse];
ENDLOOP;
IF h.cache[i].referenceCount = 0
THEN ERROR Bug[pageNotInUse]
ELSE h.cache[i].referenceCount ← h.cache[i].referenceCount - 1;
IF update # unchanged
THEN {
IF number = BTree.statePage
THEN {
-- is the statePage; can be either startOfUpdate or endOfUpdate
IF update = startOfUpdate
THEN StoreCachePage[h, i] -- only this page is dirty, so just write it
ELSE WriteDirtyPagesWithStatePageLast[h]; -- many pages possibly dirty
}
ELSE StoreCachePage[h, i]; -- is not the statePage; must be endOfUpdate; only page i is dirty
};
};
InnerRelease[NARROW[storage]];
};
GetCacheStats:
ENTRY
PROC [h: Handle]
RETURNS [hits, misses, reads, writes, cumChainLength, cumReadWriteTime:
LONG
CARDINAL] = {
RETURN [hits: h.hits, misses: h.misses, reads: h.reads, writes: h.writes, cumChainLength: h.cumChainLength, cumReadWriteTime: h.cumReadWriteTime];
};
FlushCache: ENTRY PROC [h: Handle] = {
WriteDirtyPagesWithStatePageLast[h];
FOR i: CacheIndex IN [0 .. h.cache.count) DO
h.cache[i].empty ← TRUE;
ENDLOOP;
FOR c: CARDINAL IN [0..h.count) DO
h.hashTable[c] ← noIndex;
ENDLOOP;
};
FreeBuffers:
ENTRY
PROC [h: Handle] = {
FOR i: CacheIndex
IN [0 .. h.cache.count)
DO
IF h.cache[i].ptr#NIL THEN {ReleaseSpace[h, h.cache[i].ptr]; h.cache[i].ptr ← NIL};
h.cache[i].empty ← TRUE;
ENDLOOP;
FOR c:
CARDINAL
IN [0..h.count)
DO
h.hashTable[c] ← noIndex;
ENDLOOP;
};
HashIndex:
INTERNAL
PROC [h: Handle, pNum: BTree.PageNumber]
RETURNS [
CARDINAL] =
INLINE {
RETURN [pNum MOD h.count];
};
WriteDirtyPagesWithStatePageLast:
INTERNAL
PROC [h: Handle] = {
statePageIndex: CacheIndex ← noIndex;
FOR i: CacheIndex
IN [0 .. h.cache.count)
DO
IF NOT h.cache[i].empty AND h.cache[i].dirty
THEN {
IF h.cache[i].pNum = BTree.statePage
THEN statePageIndex ← i
ELSE StoreCachePage[h, i];
};
ENDLOOP;
IF statePageIndex # noIndex THEN StoreCachePage[h, statePageIndex];
};
GetEmptyCacheEntry:
INTERNAL
PROC [h: Handle]
RETURNS [i: CacheIndex] = {
The cache entry that is returned is empty; it has a valid space and a useCount of zero. It may be assocaited with a page by setting pNum.
FOR cntr:
CARDINAL
IN [0..2*h.cache.count)
DO
i ← (h.rover + cntr) MOD h.cache.count;
SELECT
TRUE
FROM
h.cache[i].empty => {
empty entry
IF h.cache[i].ptr = NIL
THEN h.cache[i].ptr ← GetSpace[h];
h.cache[i].recentlyUsed ← FALSE;
h.cache[i].dirty ← FALSE;
h.cache[i].referenceCount ← 0;
EXIT;
};
h.cache[i].referenceCount = 0 => {
not currently being used
IF h.cache[i].recentlyUsed
THEN h.cache[i].recentlyUsed ← FALSE
ELSE {
-- not even recently used
IF h.cache[i].dirty
THEN
IF h.cache[i].pNum = BTree.statePage
THEN LOOP -- never force out the statePage
ELSE StoreCachePage[h, i];
EXIT;
};
};
ENDCASE;
REPEAT FINISHED => ERROR Bug[noAvailableCacheEntries];
ENDLOOP;
IF
NOT h.cache[i].empty
THEN {
hx: CARDINAL = HashIndex[h, h.cache[i].pNum];
IF h.hashTable[hx] = i
THEN h.hashTable[hx] ← h.cache[i].hash
ELSE
FOR j: CacheIndex ← h.hashTable[hx], h.cache[j].hash
UNTIL j = noIndex
DO
IF h.cache[j].hash = i THEN {h.cache[j].hash ← h.cache[i].hash; EXIT};
REPEAT FINISHED => ERROR Bug[cacheEntryNotInHashTable];
ENDLOOP;
h.cache[i].empty ← TRUE;
};
h.rover ← (i+1) MOD h.cache.count;
};
ExtendFile:
INTERNAL
PROC [h: Handle, n: BTree.PageNumber] = {
fileSizeForNPlus1: INT = (n+1)*h.filePagesPerPage;
IF h.filePages = 0 THEN h.filePages ← h.file.GetInfo[].pages;
IF h.filePages < fileSizeForNPlus1
THEN {
h.filePages ← fileSizeForNPlus1 + 8*h.filePagesPerPage;
h.file.SetPageCount[h.filePages];
h.file.SetByteCountAndCreatedTime[bytes: FS.BytesForPages[h.filePages]];
};
};
LoadCachePage:
INTERNAL
PROC [h: Handle, i: CacheIndex] = {
then: LONG CARDINAL = PrincOpsUtils.GetClockPulses[];
TRUSTED {h.file.Read[from: h.cache[i].pNum*h.filePagesPerPage, nPages: h.filePagesPerPage, to: h.cache[i].ptr]};
h.cumReadWriteTime ← h.cumReadWriteTime + (PrincOpsUtils.GetClockPulses[]-then);
h.reads ← h.reads+1;
};
StoreCachePage:
INTERNAL
PROC [h: Handle, i: CacheIndex] = {
then: LONG CARDINAL = PrincOpsUtils.GetClockPulses[];
TRUSTED {h.file.Write[to: h.cache[i].pNum*h.filePagesPerPage, nPages: h.filePagesPerPage, from: h.cache[i].ptr]};
h.cumReadWriteTime ← h.cumReadWriteTime + (PrincOpsUtils.GetClockPulses[]-then);
h.writes ← h.writes+1;
h.cache[i].dirty ← FALSE;
};
GetSpace:
INTERNAL
PROC [h: Handle]
RETURNS [BTree.PagePtr] =
TRUSTED {
interval: VM.Interval = VM.Allocate[count: h.vmPagesPerPage];
RETURN [VM.AddressForPageNumber[interval.page]];
};
ReleaseSpace:
INTERNAL
PROC [h: Handle, ptr: BTree.PagePtr] =
TRUSTED {
VM.Free[[page: VM.PageNumberForAddress[ptr], count: h.vmPagesPerPage]];
};