-- file AllocImpl.mesa
-- last modified by Sweet, 19-Aug-81 12:15:12
-- last modified by Satterthwaite, December 10, 1982 11:56 am
DIRECTORY
Alloc: TYPE USING [
Base, Index, Limit, maxForBits, Notifier, OrderedIndex, pagesForBits,
Selector, TableInfo],
Environment: TYPE USING [PageCount, PageOffset, wordsPerPage],
File: TYPE USING [
Capability, Create, Delete, GetSize, PageCount, PageNumber, SetSize],
FileTypes: TYPE USING [tUntypedFile],
Heap: TYPE USING [Create, Delete],
Inline: TYPE USING [LongDiv],
Runtime: TYPE USING [CallDebugger],
Space: TYPE USING [
Create, Delete, ForceOut, GetAttributes, GetWindow, Handle, LongPointer,
Map, nullHandle, PageCount, PageOffset, virtualMemory, WindowOrigin],
Volume: TYPE USING [systemID];
AllocImpl: MONITOR LOCKS h.LOCK USING h: Handle
IMPORTS File, Heap, Inline, Runtime, Space, Volume EXPORTS Alloc = {
OPEN Alloc;
-- types
Handle: TYPE = LONG POINTER TO InstanceData;
InstanceData: PUBLIC TYPE = MONITORED RECORD [
nTables: CARDINAL,
fileTop: File.PageNumber,
fileSize: CARDINAL,
cap: File.Capability,
z: UNCOUNTED ZONE,
indexBits: CARDINAL,
tileSize: CARDINAL,
notifiers: NotifyChainHandle ← NIL,
bases: LONG POINTER TO BaseSeq,
spaces: LONG POINTER TO SpaceSeq,
chunks: LONG POINTER TO ChunkSeq,
top: SizesHandle,
limit: LONG POINTER TO BoundSeq,
vmPages: SizesHandle];
maxItemSize: NAT = MAX[Alloc.Base.SIZE, Bound.SIZE, Space.Handle.SIZE, ChunkHandle.SIZE];
SeqTag: TYPE = [0 .. CARDINAL.LAST/maxItemSize]; -- for faster indexing
BaseSeq: TYPE = RECORD [SEQUENCE length: SeqTag OF Alloc.Base];
SizeSeq: TYPE = RECORD [SEQUENCE length: SeqTag OF CARDINAL];
Bound: TYPE = LONG CARDINAL;
BoundSeq: TYPE = RECORD [SEQUENCE length: SeqTag OF Bound];
SpaceSeq: TYPE = RECORD [SEQUENCE length: SeqTag OF Space.Handle];
ChunkSeq: TYPE = RECORD [SEQUENCE length: SeqTag OF ChunkHandle];
SizesHandle: TYPE = LONG POINTER TO SizeSeq;
-- signals
Failure: PUBLIC ERROR [h: Handle, table: Selector] = CODE;
Overflow: PUBLIC SIGNAL [h: Handle, table: Selector] RETURNS [extra: CARDINAL] = CODE;
-- stack allocation from subzones
Words: PUBLIC ENTRY PROC [h: Handle, table: Selector, size: CARDINAL] RETURNS [OrderedIndex] = {
RETURN [WordsInternal[h, table, size ! UNWIND => {NULL}]]};
WordsInternal: INTERNAL PROC [h: Handle, table: Selector, size: CARDINAL] RETURNS [OrderedIndex] = {
index: CARDINAL = h.top[table];
newTop: Bound = index.LONG + size; -- could overflow CARDINAL for 16 bit pointers
IF newTop > h.limit[table] THEN {
IF newTop > maxForBits[h.indexBits] THEN ERROR Failure[h, table];
GrowTable[h, table, newTop]};
h.top[table] ← newTop;
RETURN [OrderedIndex.FIRST + index]};
-- linked list allocation
Chunk: TYPE = MACHINE DEPENDENT RECORD [
free(0: 0..0): BOOL,
size(0: 1..15): [0..maxForBits[15]],
fLink(1): CIndex,
bLink(2): CIndex];
CIndex: TYPE = Base RELATIVE POINTER [0..Limit) TO Chunk;
nullChunkIndex: CIndex = CIndex.FIRST;
ChunkHandle: TYPE = LONG POINTER TO ChunkObject;
ChunkObject: TYPE = RECORD [
chunkRover: CIndex,
firstSmall: CARDINAL,
smallLists: SEQUENCE nSmall: CARDINAL OF CIndex];
GetChunk: PUBLIC ENTRY PROC [h: Handle, size: CARDINAL, table: Selector]
RETURNS [Index] = {
ENABLE UNWIND => {NULL};
ch: ChunkHandle = h.chunks[table];
cb: Base = h.bases[table];
q: CIndex;
IF ch = NIL THEN ERROR Failure[h, table];
size ← MAX[size, Chunk.SIZE];
BEGIN
IF size IN [ch.firstSmall..ch.firstSmall+ch.nSmall) THEN {
offset: CARDINAL = size - ch.firstSmall;
q ← ch.smallLists[offset];
IF q # nullChunkIndex THEN {ch.smallLists[offset] ← cb[q].fLink; GO TO found}};
q ← GetRoverChunk[cb, h.top[table], ch, size];
IF q # nullChunkIndex THEN GO TO found;
q ← WordsInternal[h: h, table: table, size: size ! Failure => {GO TO noneAtEnd}];
EXITS
noneAtEnd => {
-- none the right size, no space at the end, and no big ones to split
FOR s: CARDINAL IN [ch.firstSmall.. ch.firstSmall+ch.nSmall) DO
offset: CARDINAL = s - ch.firstSmall;
r: CIndex ← ch.smallLists[offset];
WHILE r # nullChunkIndex DO
next: CIndex = cb[r].fLink;
FreeRoverChunk[cb, ch, r, s];
r ← next;
ENDLOOP;
ch.smallLists[offset] ← nullChunkIndex;
ENDLOOP;
-- now all possible merges of free nodes can happen
q ← GetRoverChunk[cb, h.top[table], ch, size];
IF q = nullChunkIndex THEN ERROR Failure[h, table]};
found => NULL;
END;
h.bases[table][q].free ← FALSE;
RETURN [q]};
GetRoverChunk: INTERNAL PROC [cb: Base, top: CARDINAL, ch: ChunkHandle, size: CARDINAL]
RETURNS [Index] = {
p, q, next: CIndex;
nodeSize: CARDINAL;
n: INTEGER;
BEGIN
IF (p ← ch.chunkRover) = nullChunkIndex THEN GO TO notFound;
-- search for a chunk to allocate
DO
nodeSize ← cb[p].size;
WHILE (next ← p + nodeSize) - CIndex.FIRST # top AND cb[next].free DO
cb[cb[next].bLink].fLink ← cb[next].fLink;
cb[cb[next].fLink].bLink ← cb[next].bLink;
cb[p].size ← nodeSize ← nodeSize + cb[next].size;
ch.chunkRover ← p; -- in case next = chunkRover
ENDLOOP;
SELECT (n ← nodeSize-size) FROM
= 0 => {
IF cb[p].fLink = p THEN ch.chunkRover ← nullChunkIndex
ELSE {
ch.chunkRover ← cb[cb[p].bLink].fLink ← cb[p].fLink;
cb[cb[p].fLink].bLink ← cb[p].bLink};
q ← p;
GO TO found};
>= Chunk.SIZE => {
cb[p].size ← n; ch.chunkRover ← p; q ← p + n; GO TO found};
ENDCASE;
IF (p ← cb[p].fLink) = ch.chunkRover THEN GO TO notFound;
ENDLOOP;
EXITS
found => NULL;
notFound => q ← nullChunkIndex;
END;
RETURN [q]};
FreeChunk: PUBLIC ENTRY PROC [
h: Handle, index: Index, size: CARDINAL, table: Selector] = {
ENABLE UNWIND => {NULL};
ch: ChunkHandle = h.chunks[table];
cb: Base = h.bases[table];
p: CIndex = LOOPHOLE[index];
IF ch = NIL THEN ERROR Failure[h, table];
cb[p].size ← size ← MAX[size, Chunk.SIZE];
IF size IN [ch.firstSmall..ch.firstSmall+ch.nSmall) THEN {
offset: CARDINAL = size - ch.firstSmall;
cb[p].fLink ← ch.smallLists[offset];
ch.smallLists[offset] ← p;
-- don't set cb[p].free ← TRUE; to avoid coalescing nodes
cb[p].bLink ← nullChunkIndex} -- note, only singly linked
ELSE FreeRoverChunk[cb, ch, index, size]};
FreeRoverChunk: INTERNAL PROC [
cb: Base, ch: ChunkHandle, index: Index, size: CARDINAL] = {
p: CIndex = LOOPHOLE[index];
cb[p].size ← size ← MAX[size, Chunk.SIZE];
IF ch.chunkRover = nullChunkIndex THEN ch.chunkRover ← cb[p].fLink ← cb[p].bLink ← p
ELSE {
rover: CIndex = ch.chunkRover;
cb[p].fLink ← cb[rover].fLink;
cb[cb[p].fLink].bLink ← p;
cb[p].bLink ← rover;
cb[rover].fLink ← p};
cb[p].free ← TRUE};
-- queries
Bounds: PUBLIC ENTRY PROC [h: Handle, table: Selector] RETURNS [base: Base, size: CARDINAL] = {
RETURN [h.bases[table], h.top[table]]};
PagesForWords: PROC [words: LONG CARDINAL] RETURNS [CARDINAL] = {
RETURN [Inline.LongDiv[words + Environment.wordsPerPage-1, Environment.wordsPerPage]]};
-- stack allocation from subzones
fileTileSize: CARDINAL = 32; -- must be >= tileSize;
GrowTable: INTERNAL PROC [h: Handle, table: Selector, newTop: Bound] = {
newPages: CARDINAL = PagesForWords[newTop];
IF newPages > h.vmPages[table] THEN {
extra: CARDINAL = SIGNAL Overflow[h, table];
newVM: CARDINAL = MIN[maxForBits[h.indexBits], newPages + extra];
parent: Space.Handle = Space.Create[size: newVM, parent: Space.virtualMemory];
oldSpace: Space.Handle = h.spaces[table];
IF oldSpace # Space.nullHandle THEN {
next: Space.Handle ← oldSpace.GetAttributes.lowestChild;
FOR child: Space.Handle ← next, next UNTIL child = Space.nullHandle DO
window: Space.WindowOrigin = child.GetWindow;
base: Space.PageOffset;
size: Space.PageCount;
newChild: Space.Handle;
[nextSibling: next, base: base, size: size] ← child.GetAttributes;
child.ForceOut[];
Space.Delete[child];
newChild ← Space.Create[size: size, parent: parent, base: base];
newChild.Map[window];
ENDLOOP;
Space.Delete[oldSpace]};
h.spaces[table] ← parent;
h.bases[table] ← LOOPHOLE[parent.LongPointer];
h.vmPages[table] ← newVM;
RunNotifierChain[h]};
DO
AddTile[h, table, h.tileSize];
IF newTop <= h.limit[table] THEN EXIT;
ENDLOOP};
AddTile: PROC [h: Handle, table: Selector, pages: CARDINAL] = {
offset: CARDINAL = PagesForWords[h.limit[table]];
space: Space.Handle;
maxPages: CARDINAL = h.vmPages[table];
IF pages + offset > maxPages THEN pages ← maxPages - offset;
IF pages = 0 THEN ERROR;
h.limit[table] ← h.limit[table] + pages*Environment.wordsPerPage;
space ← Space.Create[size: pages, parent: h.spaces[table], base: offset];
IF h.fileTop + pages > h.fileSize THEN {
IF File.GetSize[h.cap] # h.fileSize THEN Runtime.CallDebugger["SetSize bug"L];
File.SetSize[h.cap, (h.fileSize ← h.fileSize + fileTileSize)];
IF File.GetSize[h.cap] # h.fileSize THEN Runtime.CallDebugger["SetSize bug"L]};
space.Map[[file: h.cap, base: h.fileTop]];
h.fileTop ← h.fileTop + pages};
-- initialization, expansion and termination
Create: PUBLIC PROC [
weights: DESCRIPTOR FOR ARRAY OF TableInfo, indexBits, tileSize: CARDINAL]
RETURNS [h: Handle] = {
az: UNCOUNTED ZONE = Heap.Create[initial: 1];
cnt: CARDINAL = weights.LENGTH;
h ← az.NEW[InstanceData ← [
nTables: cnt, fileTop: 0, fileSize: 0, cap: , indexBits: indexBits, z: az,
tileSize: tileSize, notifiers: NIL,
bases: az.NEW[BaseSeq[cnt]],
spaces: az.NEW[SpaceSeq[cnt]],
chunks: az.NEW[ChunkSeq[cnt]],
top: az.NEW[SizeSeq[cnt]],
limit: az.NEW[BoundSeq[cnt]],
vmPages: az.NEW[SizeSeq[cnt]]]];
IF tileSize >= fileTileSize THEN ERROR;
FOR i: CARDINAL IN [0..cnt) DO
h.fileSize ← h.fileSize + weights[i].initialPages ENDLOOP;
h.cap ← File.Create[Volume.systemID, h.fileSize, FileTypes.tUntypedFile];
FOR i: CARDINAL IN [0..cnt) DO
InitTable[h, i, weights[i]] ENDLOOP};
InitTable: PROC [h: Handle, table: Selector, info: TableInfo] = {
max: CARDINAL = WITH w: info SELECT FROM
TRUE => pagesForBits[h.indexBits],
FALSE => w.initialVMemPages,
ENDCASE => ERROR;
iPages: CARDINAL ← info.initialPages;
IF iPages > max OR max > pagesForBits[h.indexBits] THEN
ERROR Failure[h, table];
h.vmPages[table] ← max;
h.top[table] ← 0; h.limit[table] ← 0;
h.chunks[table] ← NIL;
IF iPages = 0 THEN {
h.spaces[table] ← Space.nullHandle; h.bases[table] ← NIL}
ELSE {
h.spaces[table] ← Space.Create[size: max, parent: Space.virtualMemory];
h.bases[table] ← LOOPHOLE[h.spaces[table].LongPointer]};
WHILE iPages # 0 DO
pages: CARDINAL = MIN[iPages, h.tileSize];
AddTile[h, table, pages];
iPages ← iPages - pages;
ENDLOOP};
ResetTable: PUBLIC ENTRY PROC [h: Handle, table: Selector, info: TableInfo] = {
ENABLE UNWIND => {NULL};
IF h.spaces[table] # Space.nullHandle THEN Space.Delete[h.spaces[table]];
InitTable[h, table, info];
RunNotifierChain[h]};
Destroy: PUBLIC PROC [h: Handle] = {DestroyEntry[h]; Heap.Delete[h.z]};
DestroyEntry: ENTRY PROC [h: Handle] = {
ENABLE UNWIND => {NULL};
FOR i: CARDINAL IN [0..h.nTables) DO
h.bases[i] ← NIL ENDLOOP;
RunNotifierChain[h];
FOR i: CARDINAL IN [0..h.nTables) DO
IF h.spaces[i] # Space.nullHandle THEN Space.Delete[h.spaces[i]]
ENDLOOP;
File.Delete[h.cap]};
Reset: PUBLIC ENTRY PROC [h: Handle] = {
ENABLE UNWIND => {NULL};
FOR i: CARDINAL IN [0..h.nTables) DO
h.top[i] ← 0;
ResetChunkInternal[h, i];
ENDLOOP};
Chunkify: PUBLIC ENTRY PROC [
h: Handle, table: Selector, firstSmall, nSmall: CARDINAL] = {
ENABLE UNWIND => {NULL};
ch: ChunkHandle ← h.chunks[table];
IF ch # NIL THEN ERROR Failure[h, table];
ch ← h.z.NEW[ChunkObject[nSmall]];
ch.firstSmall ← firstSmall;
h.chunks[table] ← ch;
ResetChunkInternal[h, table]};
UnChunkify: PUBLIC ENTRY PROC [h: Handle, table: Selector] = {
ENABLE UNWIND => {NULL};
IF h.chunks[table] # NIL THEN h.z.FREE[@h.chunks[table]]};
Trim: PUBLIC ENTRY PROC [h: Handle, table: Selector, size: CARDINAL] = {
ENABLE UNWIND => {NULL};
IF size <= h.top[table] THEN {h.top[table] ← size; ResetChunkInternal[h, table]}
ELSE ERROR Failure[h, table]};
ResetChunk: PUBLIC ENTRY PROC [h: Handle, table: Selector] = {
ResetChunkInternal[h, table ! UNWIND => {NULL}]};
ResetChunkInternal: INTERNAL PROC [h: Handle, table: Selector] = {
ch: ChunkHandle = h.chunks[table];
IF ch # NIL THEN {
ch.chunkRover ← nullChunkIndex;
FOR i: CARDINAL IN [0..ch.nSmall) DO ch.smallLists[i] ← nullChunkIndex ENDLOOP}};
-- Notifier stuff
NotifyNode: TYPE = RECORD [notifier: Notifier, link: NotifyChainHandle];
NotifyChainHandle: TYPE = LONG POINTER TO NotifyNode;
AddNotify: PUBLIC ENTRY PROC [h: Handle, proc: Notifier] = {
ENABLE UNWIND => {NULL};
p: NotifyChainHandle = h.z.NEW[NotifyNode ← [notifier: proc, link: h.notifiers]];
h.notifiers ← p;
proc[DESCRIPTOR[h.bases↑]]};
DropNotify: PUBLIC ENTRY PROC [h: Handle, proc: Notifier] = {
ENABLE UNWIND => {NULL};
IF h.notifiers # NIL THEN {
p: NotifyChainHandle ← h.notifiers;
IF p.notifier = proc THEN h.notifiers ← p.link
ELSE {
q: NotifyChainHandle;
DO
q ← p;
p ← p.link;
IF p = NIL THEN RETURN;
IF p.notifier = proc THEN EXIT
ENDLOOP;
q.link ← p.link};
h.z.FREE[@p]}};
RunNotifierChain: INTERNAL PROC [h: Handle] = {
FOR p: NotifyChainHandle ← h.notifiers, p.link UNTIL p = NIL DO
p.notifier[DESCRIPTOR[h.bases↑]] ENDLOOP};
}.