--file GSortImpl.mesa (converted from GPsort 3-Mar-81) --last modified by: -- Evans on 30-Dec-80 9:03:08 -- Sweet on 23-Mar-81 14:30:30 -- translated from Ed McCreight's BCPL by Jim Frandeen DIRECTORY Heap, GSort, Inline USING [LongCOPY], Segments, Streams; GSortImpl: PROGRAM IMPORTS Heap, Inline, Segments, Streams EXPORTS GSort = BEGIN Fd: TYPE = RECORD [ dh: Streams.Handle ← NIL, endOfRun: BOOLEAN ← FALSE, dummyRuns, totalRuns: CARDINAL ← NULL, head, tail: CARDINAL ← NULL, len: CARDINAL ← NULL, buffSize: CARDINAL, buffer: ItemHandle ← NIL, record: ItemHandle ← NIL]; FdHandle: TYPE = POINTER TO Fd; WordsInPage: CARDINAL = 256; Item: TYPE = RECORD [SEQUENCE COMPUTED CARDINAL OF UNSPECIFIED]; -- the item being sorted ItemHandle: TYPE = LONG POINTER TO Item; ItemHeader: TYPE = RECORD [SEQUENCE len: CARDINAL OF UNSPECIFIED]; ItemHeaderHandle: TYPE = LONG POINTER TO ItemHeader; SortHeap: TYPE = RECORD [ SEQUENCE COMPUTED CARDINAL [0..LAST[CARDINAL]/SIZE[ItemHeaderHandle]) -- to avoid multiply OF ItemHeaderHandle]; EOR: CARDINAL = LAST[CARDINAL]; -- end of run NFiles: CARDINAL = 3; -- number of scratch files RecordTooLong: PUBLIC ERROR = CODE; systemZone: MDSZone = Heap.systemMDSZone; Sort: PUBLIC PROCEDURE [ get: GSort.GetProcType, put: GSort.PutProcType, compare: GSort.CompareProcType, expectedItemSize: CARDINAL ← 30, maxItemSize: CARDINAL ← 1000, pagesInHeap: CARDINAL ← 100] = BEGIN filesArray: ARRAY [0..NFiles] OF FdHandle; firstFreeEnt: CARDINAL ← 1; -- 1 + end of unsorted part of heap vector heapArray: LONG POINTER TO SortHeap; heapSize: CARDINAL; -- end of heap-sorted part of heap vector inputFinished: BOOLEAN ← FALSE; itemIsLeftOver: BOOLEAN ← FALSE; leftoverItem: ItemHandle ← NIL; leftoverItemLen: CARDINAL; level: CARDINAL; maxHeapSize: CARDINAL; maxItemWords: CARDINAL; occItemWords: CARDINAL; recordSize: CARDINAL; f: FdHandle; -- declared out here to be sorted with locals (compiler shortcoming) item: ItemHeaderHandle; fid: STRING = "SORT.SCRATCH0$"; lastChar: CARDINAL ← fid.length - 2; sortZone: UNCOUNTED ZONE ← Heap.Create[ initial: MAX[pagesInHeap, 10], swapUnit: 10, increment: 0]; Initialize: PROCEDURE [expected, max: CARDINAL] = BEGIN blockSize: CARDINAL = MAX[pagesInHeap, 10]*WordsInPage; bufferSize: CARDINAL = (blockSize)/NFiles - 100; recordSize ← IF bufferSize > max THEN max ELSE bufferSize; maxHeapSize ← (blockSize - recordSize)/(expected + 3); maxItemWords ← blockSize - maxHeapSize - recordSize; FOR i: CARDINAL IN [1..NFiles] DO filesArray[i] ← systemZone.NEW[Fd ← [buffSize: bufferSize]]; ENDLOOP; RETURN; END; FreeAllocatedStuff: PROCEDURE = BEGIN i: CARDINAL; FOR i IN [1..NFiles] DO IF filesArray[i] = NIL THEN LOOP; IF filesArray[i].buffer # NIL THEN sortZone.FREE[@filesArray[i].buffer]; IF filesArray[i].dh # NIL THEN DeleteFile[filesArray[i].dh]; systemZone.FREE[@filesArray[i]]; ENDLOOP; IF heapArray # NIL THEN sortZone.FREE[@heapArray]; IF leftoverItem # NIL THEN sortZone.FREE[@leftoverItem]; Heap.Delete[sortZone]; RETURN; END; BuildHeap: PROCEDURE = BEGIN L: CARDINAL; heapSize ← 0; MaintainHeap[]; heapSize ← firstFreeEnt - 1; L ← (heapSize/2) + 1; WHILE L > 1 DO L ← L - 1; SiftDown[L, heapArray[L]]; ENDLOOP; RETURN; END; BuildRuns: PROCEDURE = BEGIN --Continue reading and sorting, alternating in Fibonacci sequence, until the input is exhausted. A: CARDINAL; files: POINTER TO ARRAY [0..NFiles] OF FdHandle = @filesArray; item: ItemHeaderHandle; i: CARDINAL; j: CARDINAL ← 1; LFile: FdHandle; NT: CARDINAL; level ← 1; DO f: FdHandle ← files[j]; IF level > 1 THEN Streams.PutWord[f.dh, EOR]; -- end-of-run marker FOR item ← GetHeap[], GetHeap[] UNTIL item = NIL DO Streams.PutWord[f.dh, item.len]; [] ← Streams.PutBlock[f.dh, @item[0], item.len]; occItemWords ← occItemWords - SIZE[ItemHeader [item.len]] - Heap.minimumNodeSize; sortZone.FREE[@item]; ENDLOOP; f.dummyRuns ← f.dummyRuns - 1; IF inputFinished AND (firstFreeEnt = 1) THEN EXIT; IF f.dummyRuns < files[j + 1].dummyRuns THEN {j ← j + 1; f ← files[j]} ELSE BEGIN j ← 1; f ← files[1]; IF f.dummyRuns = 0 THEN BEGIN level ← level + 1; A ← files[1].totalRuns; FOR i IN [1..NFiles - 1] DO LFile ← files[i]; NT ← A + files[i + 1].totalRuns; LFile.dummyRuns ← NT - LFile.totalRuns; LFile.totalRuns ← NT; ENDLOOP; END; END; BuildHeap[]; ENDLOOP; FOR i IN [1..NFiles - 1] DO f: FdHandle = files[i]; Streams.PutWord[f.dh, EOR]; Streams.SetIndex[f.dh, 0]; ENDLOOP; RETURN; END; GetHeap: PROCEDURE RETURNS [itemHP: ItemHeaderHandle] = BEGIN heap: LONG POINTER TO SortHeap = heapArray; IF heapSize = 0 THEN RETURN[NIL]; MaintainHeap[]; itemHP ← heap[1]; SiftDown[1, heap[heapSize]]; firstFreeEnt ← firstFreeEnt - 1; heap[heapSize] ← heap[firstFreeEnt]; heapSize ← heapSize - 1; RETURN; END; MaintainHeap: PROCEDURE = BEGIN -- Fill the heap as full as possible itemHP: ItemHeaderHandle; heap: LONG POINTER TO SortHeap = heapArray; IF inputFinished THEN RETURN; WHILE firstFreeEnt <= maxHeapSize DO -- Try adding another heap element IF NOT itemIsLeftOver THEN BEGIN leftoverItemLen ← get[leftoverItem]; IF leftoverItemLen > recordSize THEN ERROR RecordTooLong; IF leftoverItemLen = 0 THEN BEGIN inputFinished ← TRUE; EXIT; END; END; IF occItemWords >= maxItemWords THEN BEGIN itemIsLeftOver ← TRUE; EXIT; END; itemHP ← sortZone.NEW[ ItemHeader [leftoverItemLen] ! Heap.Error => -- IF type = insufficientSpace THEN BEGIN maxItemWords ← occItemWords - 100; itemIsLeftOver ← TRUE; EXIT; END]; occItemWords ← occItemWords + SIZE[ItemHeader [leftoverItemLen]] + Heap.minimumNodeSize; Inline.LongCOPY[ from: leftoverItem, nwords: leftoverItemLen, to: @itemHP[0]]; heap[firstFreeEnt] ← heap[heapSize + 1]; firstFreeEnt ← firstFreeEnt + 1; heap[heapSize + 1] ← itemHP; itemIsLeftOver ← FALSE; IF heapSize > 0 AND compare[@itemHP[0], @heap[1][0]] > 0 THEN BEGIN heapSize ← heapSize + 1; SiftUp[]; END; ENDLOOP; RETURN; END; MergePass: PROCEDURE = BEGIN files: POINTER TO ARRAY [0..NFiles] OF FdHandle = @filesArray; dummiesThisPass: CARDINAL; lastFile: FdHandle; OFile: FdHandle; runNo: CARDINAL; runsThisPass: CARDINAL; OFile ← files[NFiles]; lastFile ← files[NFiles - 1]; runsThisPass ← lastFile.totalRuns; dummiesThisPass ← lastFile.dummyRuns; -- FOR i IN[1..NFiles-2] dummiesThisPass ← MIN[dummiesThisPass, files[1].dummyRuns]; OFile.totalRuns ← runsThisPass; OFile.dummyRuns ← dummiesThisPass; -- FOR i IN[1..NFiles-2] files[1].totalRuns ← files[1].totalRuns - runsThisPass; files[1].dummyRuns ← files[1].dummyRuns - dummiesThisPass; FOR runNo IN [dummiesThisPass + 1..runsThisPass] DO MergeRun[OFile]; ENDLOOP; IF level > 1 THEN BEGIN fd: FdHandle; i: CARDINAL; FlushBuffer[OFile]; FOR i IN [NFiles - 1..NFiles] DO f: FdHandle = files[i]; Streams.SetIndex[f.dh, 0]; f.head ← 0; f.tail ← 0; ENDLOOP; fd ← files[NFiles]; FOR i DECREASING IN (1..NFiles] DO files[i] ← files[i - 1]; ENDLOOP; files[1] ← fd; END; RETURN; END; MergeRun: PROCEDURE [OFile: FdHandle] = BEGIN -- Process a run. Fill up the applicable records. files: POINTER TO ARRAY [0..NFiles] OF FdHandle = @filesArray; i: CARDINAL; SR: CARDINAL; FOR i IN [1..NFiles - 1] DO f: FdHandle = files[i]; IF f.dummyRuns = 0 THEN [] ← ReadRecord[f] ELSE BEGIN f.dummyRuns ← f.dummyRuns - 1; f.endOfRun ← TRUE; END; ENDLOOP; DO SR ← 0; -- selected record (which file is it from) FOR i IN [1..NFiles - 1] DO f: FdHandle = files[i]; IF (NOT f.endOfRun) AND (SR = 0 OR compare[f.record, files[SR].record] < 0) THEN SR ← i; ENDLOOP; IF SR = 0 THEN EXIT; -- come back and fix this IF level = 1 THEN put[files[SR].record, files[SR].len] ELSE WriteRecord[OFile, files[SR].len, files[SR].record]; files[SR].record ← NIL; [] ← ReadRecord[files[SR]]; ENDLOOP; IF level > 1 THEN WriteRecord[OFile, EOR, NIL]; -- end-of-run marker RETURN; END; SiftDown: PROCEDURE [L: CARDINAL, K: ItemHeaderHandle] = BEGIN heap: LONG POINTER TO SortHeap = heapArray; J: CARDINAL ← L; I: CARDINAL; DO I ← J; J ← I*2; IF J > heapSize THEN EXIT; IF J < heapSize THEN IF compare[@heap[J][0], @heap[J + 1][0]] > 0 THEN J ← J + 1; IF compare[@K[0], @heap[J][0]] <= 0 THEN EXIT; heap[I] ← heap[J]; ENDLOOP; heap[I] ← K; RETURN; END; SiftUp: PROCEDURE = BEGIN heap: LONG POINTER TO SortHeap = heapArray; i: CARDINAL; j: CARDINAL ← heapSize; k: ItemHeaderHandle ← heap[heapSize]; i ← j/2; WHILE i > 0 DO IF compare[@heap[i][0], @k[0]] <= 0 THEN EXIT; heap[j] ← heap[i]; j ← i; i ← j/2; ENDLOOP; heap[j] ← k; RETURN; END; Initialize[expected: expectedItemSize, max: maxItemSize]; BEGIN ENABLE UNWIND => FreeAllocatedStuff[]; heapArray ← sortZone.NEW[SortHeap [maxHeapSize]]; leftoverItem ← sortZone.NEW[Item [recordSize]]; occItemWords ← SIZE[SortHeap [maxHeapSize]] + SIZE[Item [recordSize]]; itemIsLeftOver ← FALSE; inputFinished ← FALSE; -- First, fill up the heap as much as possible and sort it. BuildHeap[]; IF inputFinished THEN THROUGH [1..heapSize] DO item ← GetHeap[]; put[@item[0], item.len]; ENDLOOP ELSE BEGIN FOR i: CARDINAL IN [1..NFiles - 1] DO f ← filesArray[i]; fid[lastChar] ← fid[lastChar] + 1; f.dh ← Streams.NewStream[fid, Streams.AllAccess]; f.totalRuns ← 1; f.dummyRuns ← 1; ENDLOOP; filesArray[NFiles].totalRuns ← 0; filesArray[NFiles].dummyRuns ← 0; BuildRuns[]; -- Put runs on input files 1..NFiles-1 so that they have Fibonacci relationship sortZone.FREE[@leftoverItem]; sortZone.FREE[@heapArray]; IF level > 1 THEN BEGIN fid[lastChar] ← fid[lastChar] + 1; filesArray[NFiles].dh ← Streams.NewStream[fid, Streams.AllAccess]; END; FOR i: CARDINAL IN [1..NFiles] DO -- seems like not needed for N if level = 1 f ← filesArray[i]; f.buffer ← sortZone.NEW[Item [f.buffSize]]; f.head ← 0; f.tail ← 0; ENDLOOP; -- Now carry out merge passes until the level has returned to zero. UNTIL level = 0 DO MergePass[]; -- also cycles the files afterward if level>1 level ← level - 1; IF level = 1 THEN { DeleteFile[filesArray[NFiles].dh]; filesArray[NFiles].dh ← NIL}; -- Output will go to the putItemParam routine ENDLOOP; FOR i: CARDINAL IN [1..NFiles - 1] DO DeleteFile[filesArray[i].dh]; filesArray[i].dh ← NIL; ENDLOOP; END; END; -- enable block FreeAllocatedStuff[]; RETURN; END; ReadRecord: PROCEDURE [f: FdHandle] RETURNS [BOOLEAN] = BEGIN itemLen: CARDINAL; headIndex: CARDINAL; IF f.head = f.tail THEN FillBuffer[f]; headIndex ← f.head; itemLen ← f.buffer[headIndex]; f.head ← headIndex ← headIndex + 1; IF itemLen = EOR THEN BEGIN f.endOfRun ← TRUE; RETURN[FALSE]; END; IF headIndex + itemLen > f.tail THEN FillBuffer[f]; headIndex ← f.head; f.record ← @f.buffer[headIndex]; f.head ← headIndex + itemLen; f.len ← itemLen; f.endOfRun ← FALSE; RETURN[TRUE]; END; FillBuffer: PROCEDURE [f: FdHandle] = BEGIN cnt: CARDINAL; f.tail ← f.tail - f.head; IF f.tail > 0 THEN Inline.LongCOPY[from: @f.buffer[f.head], nwords: f.tail, to: f.buffer]; cnt ← Streams.GetBlock[f.dh, @f.buffer[f.tail], f.buffSize - f.tail]; f.head ← 0; f.tail ← f.tail + cnt; RETURN; END; WriteRecord: PROCEDURE [f: FdHandle, itemLen: CARDINAL, itemPtr: ItemHandle] = BEGIN buffer: ItemHandle ← f.buffer; tailIndex: CARDINAL ← f.tail; IF tailIndex + (IF itemLen = EOR THEN 1 ELSE itemLen + 1) > f.buffSize THEN BEGIN FlushBuffer[f]; tailIndex ← f.tail; END; buffer[tailIndex] ← itemLen; tailIndex ← tailIndex + 1; IF itemLen # EOR THEN BEGIN Inline.LongCOPY[itemPtr, itemLen, @buffer[tailIndex]]; tailIndex ← tailIndex + itemLen; END; f.tail ← tailIndex; RETURN; END; FlushBuffer: PROCEDURE [f: FdHandle] = BEGIN [] ← Streams.PutBlock[f.dh, f.buffer, f.tail]; f.tail ← 0; END; DeleteFile: PROCEDURE [dh: Streams.Handle] = BEGIN fh: Segments.FHandle ← Streams.FileFromStream[dh]; Segments.LockFile[fh]; Streams.Destroy[dh]; Segments.UnlockFile[fh]; Segments.DestroyFile[fh]; END; END...