-- RTPrefAllocImpl.Mesa
-- last edited On August 30, 1982 10:34 am by Willie-Sue Haugeland
-- last edited On June 27, 1983 2:47 pm by Paul Rovner
-- Last Edited by: Levin, August 8, 1983 5:28 pm

DIRECTORY
Basics USING[BITAND],
DebuggerSwap USING[CallDebugger],
PrincOps USING[StateVector],
PrincOpsUtils USING[ZERO, BITAND, MyLocalFrame],
RTFlags USING[checking, clearing, takingStatistics, useMicrocode],
RTMicrocode USING[ALLOCATEHEAPNODE],
RTQuanta USING[LASTAddress],
RTRefCounts USING[CreateRef, GCMicrocodeExists],
RTStorageAccounting USING[ConsiderCollection, nObjectsCreated],
RTTypesBasicPrivate USING[NumberPackageRefs, FreeCollectibleObject],
SafeStoragePrivate USING[],
SSTraps USING[],
SafeStorage USING
[GetCanonicalType, MemoryExhausted, GetSystemZone, nullType, TypeIndex,
Type, GetPermanentZone],
UnsafeStorage USING[],
TrapSupport USING[BumpPC, GetTrapParam],
RTZones;

RTPrefAllocImpl: MONITOR -- protects zones
LOCKS zn.LOCK USING zn: PZone
IMPORTS Basics, DebuggerSwap, PrincOpsUtils, RTMicrocode, RTQuanta, RTRefCounts,
RTStorageAccounting, RTTypesBasicPrivate, RTZones, SafeStorage,
TrapSupport

EXPORTS RTZones, SafeStoragePrivate, SSTraps, UnsafeStorage
= BEGIN
OPEN RTMicrocode, RTRefCounts, RTTypesBasicPrivate, RTZones, SafeStorage, SafeStoragePrivate;

-- Signals
SuspectPtrSeen: SIGNAL[suspectPtr: LONG POINTER] = CODE; -- hook for debugging
InvalidZone: ERROR[zone: ZONE] = CODE;
SizeTooBig: SIGNAL[size: CARDINAL] = CODE;

-- Global variables and constants

useAllocUCode: BOOLEAN ← RTFlags.useMicrocode AND NOT useSizeToZn; -- XXX
useFreeUCode: BOOLEAN ← RTFlags.useMicrocode;
checking: BOOLEAN = RTFlags.checking;
clearing: BOOLEAN = RTFlags.clearing;
traceAndMarkEnabled: BOOLEAN = FALSE; --NOTE
-- "= FALSE" suppresses compilation of statistics code

suspectPtr: LONG POINTERNIL; -- for debugging

permanentZone: ZONE ← GetPermanentZone[];

-- Statistics
Bump: PROC[p: POINTER TO INT, delta: INT ← 1] = INLINE
BEGIN IF RTFlags.takingStatistics THEN p^ ← p^+delta END;

stats: RECORD
[ nNewUObjectCalls: INT ← 0,
nNewUObjectWords: INT ← 0,
nCreatePrefixedUObjectCalls: INT ← 0,
nCreatePrefixedUObjectWords: INT ← 0
];

-- PUBLIC procedures (i.e. exported)

NewObject: PUBLIC PROC[type: Type, size: CARDINAL, zone: ZONENIL]
RETURNS[REF ANY] =
{ IF zone = NIL THEN zone ← zoneSystem;
IF useSizeToZn THEN type ← GetCanonicalType[type]; -- XXX
RETURN[LOOPHOLE[LOOPHOLE[zone, Zone].new,
PROC[self: ZONE, size: CARDINAL, type: Type] RETURNS[REF ANY]
][self: zone, size: size, type: type]];
};

NewUObject: PUBLIC PROC -- used when NEW won't do, e.g for type-carrying heap objects
[size: CARDINAL,
zone: UNCOUNTED ZONE] RETURNS[ptr: LONG POINTER] =
{ Bump[@stats.nNewUObjectCalls];
Bump[@stats.nNewUObjectWords, size];
WITH zn: LOOPHOLE[zone, LONG POINTER TO PZone]^ SELECT FROM
quantized => ERROR;
prefixed =>
{ triedAgain: BOOLEANFALSE;
IF LOOPHOLE[zn.linkage, heap ZoneLinkage].typeRepresentation THEN ERROR
ELSE
ptr ← CreatePrefixedUObject[size, @zn
! MemoryExhausted => IF NOT triedAgain THEN {triedAgain ← TRUE;RETRY}]};
ENDCASE => ERROR};

NewPrefixedObject: PUBLIC PROC[self: PrefixedZone, size: CARDINAL, type: Type]
RETURNS[ref: REF ANY] =
{ triedAgain: BOOLEANFALSE;

-- IF RTStorageAccounting.AllocatorCallbackProcForSpy # 0
-- THEN LOOPHOLE[RTStorageAccounting.AllocatorCallbackProcForSpy, PROC[CARDINAL]][size];

ref ← LOOPHOLE[CreatePrefixedObject[self, size, type]];
IF checking AND LOOPHOLE[ref, LONG CARDINAL] > RTQuanta.LASTAddress THEN ERROR;

-- TandS: CreateRef OK outside the monitor if it is idempotent and new storage has been cleared
CreateRef[NumberPackageRefs[type], ref];
RTStorageAccounting.nObjectsCreated ← RTStorageAccounting.nObjectsCreated + 1;
IF FALSE --checking
THEN FOR i: CARDINAL IN [0..size)
DO IF (LOOPHOLE[ref, LONG POINTER TO CARDINAL] + i)^ # 0
THEN ERROR
ENDLOOP;

RTStorageAccounting.ConsiderCollection
[size, NodeLength[LOOPHOLE[ref, PNode] - sizeNd]]};

FreePrefixedObject: PUBLIC PROC[self: PrefixedZone, object: REF ANY] =
{FreeCollectibleObject[object]};

NewPrefixedHeapObject: PUBLIC PROC[self: LONG POINTER TO PPrefixedZone, size: CARDINAL]
RETURNS[ptr: LONG POINTER] =
{ triedAgain: BOOLEANFALSE;
ptr ← INLCreatePrefixedUObject[size, self^
! MemoryExhausted => IF NOT triedAgain THEN {triedAgain ← TRUE;RETRY}]};

-- used by ExtendZone
AddBlock: PUBLIC PROC[ptr: LONG POINTER, size: LongNodeSize, pfnPrev: PFreeNode] =
{IF clearing
THEN {WHILE size > LAST[CARDINAL]
DO [] ← PrincOpsUtils.ZERO[ptr, LAST[CARDINAL]];
size ← size - LAST[CARDINAL];
ENDLOOP;
[] ← PrincOpsUtils.ZERO[ptr, size]};
IF checking AND LOOPHOLE[ptr, LONG CARDINAL] > RTQuanta.LASTAddress THEN ERROR;
LinkHeapNode[MakeFreeHeapNode[ptr, size], pfnPrev];
};


-- *** The Prefixed Object Allocator
--********************************************************************************
-- called when the microcode can't handle the allocation
CreatePrefixedObjectTrap: PUBLIC PROC[zn: PPrefixedZone, size: CARDINAL, type: Type]
RETURNS[ptr: LONG POINTER] =
{ state: PrincOps.StateVector;
param: CARDINAL;
state← STATE; -- incantation
param← IF RTRefCounts.GCMicrocodeExists THEN TrapSupport.GetTrapParam[] ELSE 0;

IF param < 2 OR param > 4 THEN useAllocUCode ← FALSE;

IF state.stkptr # 0
THEN DebuggerSwap.CallDebugger["CreatePrefixedObjectTrap: stack not empty"];
ptr ← InternalCreatePrefixedObject[zn, size, type];

TrapSupport.BumpPC[2];
state.dest← LOOPHOLE[PrincOpsUtils.MyLocalFrame[]];
TRANSFER WITH state; -- incantation
};

AllocateHeapNodeSDTrap: PUBLIC PROC[zn: PPrefixedZone, size: CARDINAL, type: Type]
RETURNS[ptr: LONG POINTER] =
{RETURN[InternalCreatePrefixedObject[zn, size, type]]};

InternalCreatePrefixedObject: PROC[zn: PPrefixedZone, size: CARDINAL, type: Type]
RETURNS[ptr: LONG POINTER] =
{ triedAgain: BOOLEAN;
size ← PrincOpsUtils.BITAND[size+1, 177776B]; -- make even

triedAgain ← FALSE;
WHILE (ptr ← AllocateHeapNode[zn, size, type]) = NIL DO
WITH zn.linkage SELECT FROM
collectible =>
LOOPHOLE[zn.linkage, collectible ZoneLinkage].fullProc[LOOPHOLE[zn], size
! MemoryExhausted => -- try once more in case merger made a big enough block
IF NOT triedAgain THEN {triedAgain ← TRUE; LOOP}
];
heap =>
LOOPHOLE[zn.linkage, heap ZoneLinkage].fullProc[LOOPHOLE[LONG[@zn]], size];
ENDCASE => ERROR;
ENDLOOP};

--****************************************************************************

-- XXX arg is the requested size, not including overhead
QuantizedSize: PUBLIC PROC[size: CARDINAL] RETURNS[CARDINAL] =
{RETURN[INLQuantizedSize[size]]};

-- XXX arg is the requested size, not including overhead. See MapSizeToZn.
INLQuantizedSize: PROC[size: CARDINAL] RETURNS[CARDINAL] =
INLINE
{IF size < SIZE[free NodeHeader]-sizeNd THEN size ← SIZE[free NodeHeader]-sizeNd;
size ← SELECT size FROM
<20B => size,
<40B => ((size-1)/4B + 1)*4B,
<100B => ((size-1)/10B + 1)*10B,
<200B => ((size-1)/20B + 1)*20B,
<400B => ((size-1)/40B + 1)*40B,
<1000B => ((size-1)/100B + 1)*100B,
<2000B => ((size-1)/200B + 1)*200B,
<4000B => ((size-1)/400B + 1)*400B,
ENDCASE => size;
RETURN[size];
};

CreatePrefixedObject: PROC[zn: PrefixedZone, size: CARDINAL, type: Type]
RETURNS[ptr: LONG POINTER] =
INLINE
{ IF checking AND size > MaxClientBlockSize THEN SIGNAL SizeTooBig[size];
size ← PrincOpsUtils.BITAND[size+1, 177776B]; -- make even
IF useSizeToZn
AND SizeToZn # NIL
AND zn # LOOPHOLE[permanentZone, PrefixedZone]
-- XXX
THEN {size ← INLQuantizedSize[size];
zn ← MapSizeToZn[size];
};

IF useAllocUCode
THEN ptr ← ALLOCATEHEAPNODE[LOOPHOLE[zn], size, type]
ELSE
{ triedAgain: BOOLEANFALSE;
WHILE (ptr ← AllocateHeapNode[LOOPHOLE[zn], size, type]) = NIL
DO LOOPHOLE[zn.linkage, collectible ZoneLinkage].fullProc[LOOPHOLE[zn], size
! MemoryExhausted => -- try once more in case merger made a big enough block
IF NOT triedAgain THEN {triedAgain ← TRUE; LOOP}];
ENDLOOP};
IF checking THEN IF ptr = suspectPtr THEN SIGNAL SuspectPtrSeen[suspectPtr]};

-- XXX
MapSizeToZn: PROC[size: CARDINAL] RETURNS[zn: PrefixedZone] =
INLINE
{ RETURN[IF size <= maxSizeToZnIndex
THEN LOOPHOLE[SizeToZn[size]]
ELSE IF size >= 4000B -- large buffers, e.g. for Tioga
THEN LOOPHOLE[mediumSizedZone]
ELSE LOOPHOLE[SafeStorage.GetSystemZone[]]
];
};

CreatePrefixedTypedUObject: PUBLIC PROC[type: Type, size: CARDINAL, zn: PPrefixedZone]
RETURNS[ptr: LONG POINTER] =
{ size ← PrincOpsUtils.BITAND[size+1, 177776B]; -- make even
IF useAllocUCode
THEN ptr ← ALLOCATEHEAPNODE[LOOPHOLE[zn], size, type]
ELSE
WHILE (ptr ← AllocateHeapNode[zn, size, type]) = NIL DO
LOOPHOLE[zn.linkage, heap ZoneLinkage].fullProc[LOOPHOLE[LONG[@zn]], size];
ENDLOOP;
IF checking THEN IF ptr = suspectPtr THEN SIGNAL SuspectPtrSeen[suspectPtr]};

CreatePrefixedUObject: PUBLIC PROC[size: CARDINAL, zn: PPrefixedZone]
RETURNS[ptr: LONG POINTER] = {
RETURN[INLCreatePrefixedUObject[size, zn]];
};

INLCreatePrefixedUObject: PROC[size: CARDINAL, zn: PPrefixedZone]
RETURNS[ptr: LONG POINTER] =
INLINE
{ Bump[@stats.nCreatePrefixedUObjectCalls];
Bump[@stats.nCreatePrefixedUObjectWords, size];
size ← PrincOpsUtils.BITAND[size+1, 177776B]; -- make even
IF useAllocUCode
THEN ptr ← ALLOCATEHEAPNODE[LOOPHOLE[zn], size, nullType]
ELSE
WHILE (ptr ← AllocateHeapNode[zn, size, nullType]) = NIL DO
LOOPHOLE[zn.linkage, heap ZoneLinkage].fullProc[LOOPHOLE[LONG[@zn]], size];
ENDLOOP;
IF checking THEN IF ptr = suspectPtr THEN SIGNAL SuspectPtrSeen[suspectPtr]};

AllocateHeapNode: ENTRY PROC[zn: PZone, s: CARDINAL, type: Type]
RETURNS[LONG POINTER] =
BEGIN ENABLE UNWIND => NULL;
size: CARDINAL =
(IF s < SIZE[free NodeHeader]-sizeNd
THEN SIZE[free NodeHeader]
ELSE IF s > (LAST[CARDINAL]-sizeNd) THEN ERROR
ELSE (s+sizeNd));
pfnFirst: PFreeNode ← LOOPHOLE[zn, PPrefixedZone].pfn;
pfn: PFreeNode ← pfnFirst;
length, excess: LongNodeSize;
pType: TypeIndex;
ppzn: PPrefixedZone = LOOPHOLE[zn];

DO
pfnEndpfn: PNode;
IF (length ← NodeLength[pfn]) >= size THEN EXIT;
IF checking AND length = 0 AND pfn # @ppzn.fnd THEN ERROR;

pfnEndpfn ← pfn + length;
IF length > 0 -- 0 indicates the dummy free node header in the zone.
AND LOOPHOLE[pfnEndpfn, LONG CARDINAL] <= RTQuanta.LASTAddress
AND MapPtrZf[pfnEndpfn] = [prefixed[zi: zn.zi]]
THEN WITH pfnEnd: pfnEndpfn SELECT FROM
free => -- next guy is free, merge it with pfn
{ newLength: LongNodeSize = length + NodeLength[pfnEndpfn];
IF checking THEN
{IF pfn.pfnNext.pfnPrev # pfn
OR pfn.pfnPrev.pfnNext # pfn
OR pfnEnd.pfnNext.pfnPrev # @pfnEnd
OR pfnEnd.pfnPrev.pfnNext # @pfnEnd
OR length < MinBlockSize
OR NodeLength[@pfnEnd] < MinBlockSize
THEN ERROR};
pfnEnd.pfnNext.pfnPrev ← pfnEnd.pfnPrev;
pfnEnd.pfnPrev.pfnNext ← pfnEnd.pfnNext;
IF @pfnEnd = pfnFirst THEN pfnFirst ← pfn.pfnPrev;
IF RTFlags.clearing -- keep free objects clear
THEN [] ← PrincOpsUtils.ZERO[pfnEndpfn, SIZE[free NodeHeader]];
pfn.SizeLo ← LOOPHOLE[newLength, MDRLongNodeSize].lnsLo;
pfn.SizeHi ← LOOPHOLE[newLength, MDRLongNodeSize].lnsHi;
ppzn.pfn ← pfn; -- update like ucode does
LOOP};
ENDCASE;
pfn ← pfn.pfnNext;
ppzn.pfn ← pfn; -- update like ucode does
IF pfn = pfnFirst THEN RETURN[NIL] -- no free block large enough
ENDLOOP;

-- Found a large enough free block
pType ← LOOPHOLE[type];
IF checking AND Basics.BITAND[length, 1] # 0 THEN ERROR; -- debugging
IF (excess ← length-size) >= (IF useSizeToZn THEN size ELSE (4 * MinBlockSize))
-- XXX
THEN -- split the block
{pfn.SizeLo← LOOPHOLE[excess, MDRLongNodeSize].lnsLo;
pfn.SizeHi← LOOPHOLE[excess, MDRLongNodeSize].lnsHi;
pfn← LOOPHOLE[pfn + excess];
LOOPHOLE[pfn, PNode]^← [SizeLo: size, body: inuse[type: pType]]}
ELSE -- remove block from free list without splitting it
{pfn.pfnNext.pfnPrev ← pfn.pfnPrev;
ppzn.pfn ← pfn.pfnPrev.pfnNext ← pfn.pfnNext;
pfn.pfnNext ← pfn.pfnPrev ← NIL;
LOOPHOLE[pfn, PNode].body ← inuse[type: pType]};
RETURN[pfn + sizeNd];
END;

MakeFreeHeapNode: PROC[pnp: PNode, size: LongNodeSize] RETURNS[PFreeNode] =
INLINE
{ LOOPHOLE[pnp, PFreeNode]^←
[ SizeLo: LOOPHOLE[size, MDRLongNodeSize].lnsLo,
body: free[SizeHi: LOOPHOLE[size, MDRLongNodeSize].lnsHi,
pfnPrev: NIL, pfnNext: NIL]
];
RETURN[LOOPHOLE[pnp]]};

MakeInuseHeapNode: PROC[pnp: PNode, size: LongNodeSize, type: Type] RETURNS[PNode] =
INLINE
{ pnp^ ← [ SizeLo: LOOPHOLE[size, MDRLongNodeSize].lnsLo,
body: inuse[type: LOOPHOLE[type]]];
RETURN[LOOPHOLE[pnp]]};


-- *** The Prefixed Object Allocator (end)


-- NOTE copied in RTTraceAndSweepImpl, RTReclaimerImpl
LinkHeapNode: PROC[pfn, pfnPrev: PFreeNode] =
INLINE BEGIN
pfnNext: PFreeNode = pfnPrev.pfnNext;
IF checking THEN IF LOOPHOLE[pfn, LONG POINTER] = suspectPtr THEN SIGNAL SuspectPtrSeen[suspectPtr];
pfn.body ← free[pfnPrev: pfnPrev, pfnNext: pfnNext];
pfnNext.pfnPrev ← pfn;
pfnPrev.pfnNext ← pfn;
END;


-- START HERE

useAllocUCode ← useAllocUCode AND RTRefCounts.GCMicrocodeExists;
useFreeUCode ← useFreeUCode AND RTRefCounts.GCMicrocodeExists;

END.