-- Cedar Remote Debugging: word-level implementation

-- WVMImpl.mesa

-- Andrew Birrell March 3, 1983 12:42 pm

DIRECTORY
Inline   USING[ LongCOPY ],
Mopcodes  USING[ op, zBRK ],
PageMap  USING[ flagsVacant, GetF, SetF, Value ],
PrincOps  USING[ BytePC, InstWord ],
ProcessInternal USING[ DisableInterrupts, EnableInterrupts],
Space   USING[ PageFromLongPointer, PageNumber ],
SpecialSpace USING[ MakeGlobalFrameResident, MakeProcedureResident ],
VMMapLog  USING[ PatchTable, PatchTableEntry, PatchTableEntryPointer ],
WorldVM  USING[ Address, LocalWorld, World ],
WVMPrivate USING[ GetPage, PageData, PageHandle, PageNumber, pageSize, PatchTable, ReleasePage, WriteAndReleasePage ];

WVMImpl: MONITOR--protects the patch table--
IMPORTS Inline, PageMap, ProcessInternal, Space, SpecialSpace, WorldVM, WVMPrivate
EXPORTS WorldVM
SHARES PageMap =

BEGIN

OPEN WorldVM;

local: WorldVM.World = WorldVM.LocalWorld[];

Read: PUBLIC PROC[world: World, addr: Address] RETURNS[ i: CARDINAL ] =
BEGIN
n: WVMPrivate.PageNumber;
w: [0..WVMPrivate.pageSize);
h: WVMPrivate.PageHandle;
d: REF WVMPrivate.PageData;
IF world = local
THEN RETURN[LOOPHOLE[addr, LONG POINTER TO CARDINAL]^];
[n, w, h, d] ← PageFromAddr[world, addr];
i ← d[w];
WVMPrivate.ReleasePage[h];
END;

Write: PUBLIC PROC[world: World, addr: Address, value: CARDINAL] =
BEGIN
n: WVMPrivate.PageNumber;
w: [0..WVMPrivate.pageSize);
h: WVMPrivate.PageHandle;
d: REF WVMPrivate.PageData;
IF world = local
THEN { LOOPHOLE[addr, LONG POINTER TO CARDINAL]^ ← value; RETURN };
[n, w, h, d] ← PageFromAddr[world, addr];
d[w] ← value;
WVMPrivate.WriteAndReleasePage[h];
END;

LongRead: PUBLIC PROC[world: World, addr: Address] RETURNS[ i: LONG CARDINAL ] =
BEGIN
n: WVMPrivate.PageNumber;
w: [0..WVMPrivate.pageSize);
h: WVMPrivate.PageHandle;
d: REF WVMPrivate.PageData;
IF world = local
THEN RETURN[LOOPHOLE[addr, LONG POINTER TO LONG CARDINAL]^];
[n, w, h, d] ← PageFromAddr[world, addr];
IF w = WVMPrivate.pageSize-1
THEN { WVMPrivate.ReleasePage[h]; CopyRead[world,addr,SIZE[LONG CARDINAL],@i] }
ELSE BEGIN
i ← LOOPHOLE[@d[w],LONG POINTER TO LONG CARDINAL]^;
WVMPrivate.ReleasePage[h];
END;
END;

LongWrite: PUBLIC PROC[world: World, addr: Address, value: LONG CARDINAL] =
BEGIN
n: WVMPrivate.PageNumber;
w: [0..WVMPrivate.pageSize);
h: WVMPrivate.PageHandle;
d: REF WVMPrivate.PageData;
IF world = local
THEN { LOOPHOLE[addr, LONG POINTER TO LONG CARDINAL]^ ← value; RETURN };
[n, w, h, d] ← PageFromAddr[world, addr];
IF w = WVMPrivate.pageSize-1
THEN { WVMPrivate.ReleasePage[h];
CopyWrite[world,@value,SIZE[LONG CARDINAL],addr] }
ELSE BEGIN
LOOPHOLE[@d[w],LONG POINTER TO LONG CARDINAL]^ ← value;
WVMPrivate.WriteAndReleasePage[h];
END;
END;

CopyRead: PUBLIC PROC[world: World, from: Address, nwords: INT,
to: LONG POINTER] =
BEGIN
n: WVMPrivate.PageNumber;
w: [0..WVMPrivate.pageSize);
h: WVMPrivate.PageHandle;
d: REF WVMPrivate.PageData;
IF world = local
THEN { Inline.LongCOPY[from: LOOPHOLE[from], nwords: nwords, to: to]; RETURN };
WHILE nwords > 0
DO amount: (0..WVMPrivate.pageSize];
[n, w, h, d] ← PageFromAddr[world, from];
amount ← MIN[nwords,WVMPrivate.pageSize-w];
Inline.LongCOPY[from: @d[w], to: to, nwords: amount];
nwords ← nwords - amount; from ← from + amount; to ← to + amount;
WVMPrivate.ReleasePage[h];
ENDLOOP;
END;

CopyWrite: PUBLIC PROC[world: World, from: LONG POINTER, nwords: INT,
to: Address] =
BEGIN
n: WVMPrivate.PageNumber;
w: [0..WVMPrivate.pageSize);
h: WVMPrivate.PageHandle;
d: REF WVMPrivate.PageData;
IF world = local
THEN { Inline.LongCOPY[from: from, nwords: nwords, to: LOOPHOLE[to]]; RETURN };
WHILE nwords > 0
DO amount: [0..WVMPrivate.pageSize);
[n, w, h, d] ← PageFromAddr[world, to];
amount ← MIN[nwords,WVMPrivate.pageSize-w];
Inline.LongCOPY[from: from, to: @d[w], nwords: amount];
nwords ← nwords - amount; from ← from + amount; to ← to + amount;
WVMPrivate.WriteAndReleasePage[h];
ENDLOOP;
END;

PageFromAddr: PROC[world: WorldVM.World, addr: WorldVM.Address,
coreOnly: BOOLEANFALSE]
RETURNS[n: WVMPrivate.PageNumber, w: [0..WVMPrivate.pageSize),
h: WVMPrivate.PageHandle, d: REF WVMPrivate.PageData] =
BEGIN
n ← addr / WVMPrivate.pageSize;
w ← addr - n * LONG[WVMPrivate.pageSize];
[d, h] ← WVMPrivate.GetPage[world, n, coreOnly];
END;

SetBreak: PUBLIC PROC[world: World, addr: Address, offset: PrincOps.BytePC]
RETURNS[ oldByte: Mopcodes.op ] =
{ oldByte ← Patch[world, addr+offset/2, offset MOD 2, Mopcodes.zBRK] };

ClearBreak: PUBLIC PROC[world: World, addr: Address,
offset: PrincOps.BytePC,
oldByte: Mopcodes.op] =
{ [] ← Patch[world, addr+offset/2, offset MOD 2, oldByte] };

PatchTableFull: ERROR = CODE;
IllegalPatch: PUBLIC ERROR = CODE;

Patch: ENTRY PROC[world: World, addr: Address,
offset: [0..1], byte: Mopcodes.op]
RETURNS[oldByte: Mopcodes.op] =
BEGIN
ENABLE UNWIND => NULL;
value: PrincOps.InstWord; -- new memory word contents --
patch: Address = WVMPrivate.PatchTable[world];
header: VMMapLog.PatchTable;
base: Address = patch + SIZE[VMMapLog.PatchTable];
free: CARDINAL;
pti: CARDINAL;
entry: VMMapLog.PatchTableEntry;
CopyRead[world: world, from: patch, to: @header,
nwords: SIZE[VMMapLog.PatchTable]];
free ← LOOPHOLE[header.limit];
FOR pti ← 0, pti + SIZE[VMMapLog.PatchTableEntry]
UNTIL pti = LOOPHOLE[header.limit, CARDINAL]
DO CopyRead[world: world, from: base+pti, to: @entry,
nwords: SIZE[VMMapLog.PatchTableEntry]];
IF entry.address = NIL THEN free ← pti;
IF LOOPHOLE[entry.address, Address] = addr THEN EXIT;
REPEAT FINISHED => { pti ← free; entry.address ← NIL }
ENDLOOP;
IF pti = LOOPHOLE[header.limit, CARDINAL] AND header.limit = header.maxLimit
THEN ERROR PatchTableFull[];
value ← IF LOOPHOLE[entry.address, Address] = addr
THEN entry.value
ELSE LOOPHOLE[Read[world, addr]];
oldByte ← IF offset=0 THEN value.evenbyte ELSE value.oddbyte;
IF oldByte # Mopcodes.zBRK AND byte # Mopcodes.zBRK THEN ERROR IllegalPatch[];
IF oldByte = Mopcodes.zBRK AND byte = Mopcodes.zBRK THEN ERROR IllegalPatch[];
IF offset=0 THEN value.evenbyte ← byte ELSE value.oddbyte ← byte;
entry.value ← value;
IF value.evenbyte # Mopcodes.zBRK AND value.oddbyte # Mopcodes.zBRK
THEN entry.address ← NIL -- no patching needed --
ELSE entry.address ← LOOPHOLE[addr];
IF world = local
THEN LocalEntry[from: entry, to: LOOPHOLE[base, LONG POINTER] + pti]
ELSE CopyWrite[world: world, from: @entry, to: base+pti,
nwords: SIZE[VMMapLog.PatchTableEntry]];
IF pti = LOOPHOLE[header.limit, CARDINAL]
THEN -- extend table --
BEGIN
header.limit ← LOOPHOLE[pti + SIZE[VMMapLog.PatchTableEntry]];
CopyWrite[world: world, from: @header, to: patch,
nwords: SIZE[VMMapLog.PatchTable]];
END;
-- Now we've done the patch table. Next, fix real memory. --
IF world = local
THEN LocalPatch[LOOPHOLE[addr, LONG POINTER], entry.value]
ELSE BEGIN
n: WVMPrivate.PageNumber;
w: [0..WVMPrivate.pageSize);
h: WVMPrivate.PageHandle;
d: REF WVMPrivate.PageData;
[n, w, h, d] ← PageFromAddr[world, addr, --coreOnly:-- TRUE];
IF h # NIL
THEN -- swapped in, so patch it --
BEGIN
d[w] ← entry.value;
WVMPrivate.WriteAndReleasePage[h--, clean--];
END;
END;
END;

LocalEntry: INTERNAL PROC[from: VMMapLog.PatchTableEntry,
to: LONG POINTER TO VMMapLog.PatchTableEntry] =
BEGIN
-- Unfortunately, we can't synchronize with the swapper.
ProcessInternal.DisableInterrupts[];
to^ ← from; -- assume our local frame and patch table are resident.
ProcessInternal.EnableInterrupts[];
END;

LocalPatch: INTERNAL PROC[addr: LONG POINTER, value: CARDINAL] =
BEGIN
-- patch given address in our real memory, cleanly.
page: Space.PageNumber = Space.PageFromLongPointer[addr];
oldVal, newVal: PageMap.Value;
UNTIL addr^ = value -- in practice, only once!
DO ProcessInternal.DisableInterrupts[];
newVal ← oldVal ← PageMap.GetF[page];
IF oldVal.flags # PageMap.flagsVacant
THEN BEGIN
newVal.flags.writeProtected ← FALSE;
[] ← PageMap.SetF[page, newVal];
addr^ ← value; --write the word!
[] ← PageMap.SetF[page, oldVal];
END;
ProcessInternal.EnableInterrupts[];
ENDLOOP;
END;

SpecialSpace.MakeGlobalFrameResident[WVMImpl];
SpecialSpace.MakeProcedureResident[LocalPatch];
SpecialSpace.MakeProcedureResident[LocalEntry];

END.