FastBreakImpl.mesa
Russ Atkinson, September 23, 1983 4:49 pm
Birrell, August 2, 1983 11:02 am
DIRECTORY
AMEvents USING [CallDebugger],
Basics USING [BYTE],
FastBreak USING [FastBreakData, FastBreakId, FastBreakProc],
PrincOps USING [
BytePC, Frame, FrameHandle, GlobalFrameHandle, InstWord, NullLink, StateVector, zBRK],
PrincOpsUtils USING [GetReturnFrame, ReadXTS, WriteXTS],
VM USING [Allocate, Free, Interval, AddressForPageNumber, Pin, Unpin, wordsPerPage],
WorldVM USING [ClearBreak, LocalWorld, Read, SetBreak];
FastBreakImpl: MONITOR
IMPORTS AMEvents, PrincOpsUtils, VM, WorldVM
EXPORTS FastBreak
= BEGIN OPEN FastBreak;
FastBreakEntryPtr: TYPE = LONG POINTER TO FastBreakEntry;
FastBreakEntry: TYPE = RECORD [
count: INT ← 0,
next: FastBreakEntryPtr ← NIL,
code: LONG POINTERNIL,
pc: PrincOps.BytePC ← [0],
inst: Basics.BYTE ← 0,
data: LONG POINTERNIL,
proc: FastBreakProc ← NIL
];
fastBreakPages: CARDINAL = 4;
maxFastBreaks: CARDINAL =
(fastBreakPages*VM.wordsPerPage) / SIZE[FastBreakEntry];
FastBreakArrayPtr: TYPE = LONG POINTER TO FastBreakArray;
FastBreakArray: TYPE = ARRAY [0..maxFastBreaks) OF FastBreakEntry;
arrayPtr: FastBreakArrayPtr ← NIL;
space: VM.Interval; -- valid iff arrayPtr # NIL
oldBreakTrap: PROCNIL;
fastBreaksLeft: NAT ← maxFastBreaks;
inUseChain: FastBreakEntryPtr ← NIL;
freeChain: FastBreakEntryPtr ← NIL;
firstTime: BOOLTRUE;
SpecifyDefaultBreakHandler: PUBLIC PROC [old: PROC] = {
SpecifyDefaultBreakHandler[old] specifies the old breakpoint handler to be used when a non-fast break is encountered. It should be called before FastBreakHandler is installed.
oldBreakTrap ← old;
};
FastBreakHandler: PUBLIC PROC = {
Executed by (non-worry) BRK instruction. Examines our wonder little resident data structure to determine whether or not to take a FAST break. If not, then we pass control to the old break handler (whatever it is). There can be races between this procedure and the setting/cearing procedures, but it is not easy to remove these races due to atomicity considerations.
state: RECORD [
padding: ARRAY [0..2) OF UNSPECIFIED,
v: PrincOps.StateVector];
fp: PrincOps.FrameHandle;
state.v ← STATE;
fp ← PrincOpsUtils.GetReturnFrame[];
IF inUseChain # NIL THEN {
pc: PrincOps.BytePC ← fp.pc;
gf: PrincOps.GlobalFrameHandle ← fp.accesslink;
code: LONG POINTER ← gf.code.longbase;
ep: FastBreakEntryPtr ← inUseChain;
inst: Basics.BYTE ← PrincOps.zBRK;
useOld: BOOLFALSE;
WHILE ep # NIL DO
entry: FastBreakEntry ← ep^; -- copy in one burst to reduce races
IF entry.code = code AND entry.pc = pc THEN {
inst ← entry.inst;
ep.count ← entry.count + 1;
IF entry.proc # NIL THEN
call the user's routine
IF entry.proc[entry.data, fp, @state.v] THEN useOld ← TRUE;
};
ep ← entry.next;
ENDLOOP;
IF inst # PrincOps.zBRK THEN {
We have handled a fast break. Now we must clean up our act to allow continuation. Notice that we do not allow upper-level breakpoints AND fast breaks, although we allow multiple fast breaks.
IF PrincOpsUtils.ReadXTS[] = on THEN PrincOpsUtils.WriteXTS[skip1];
IF useOld THEN {
We have handled a fast break, but we have been requested to make this into a pseudo-break event. The easiest way is just to call the debugger.
AMEvents.CallDebugger["FastBreak proc requested a break."];
};
state.v.source ← PrincOps.NullLink;
state.v.dest ← LOOPHOLE[fp];
state.v.instbyte ← inst;
RETURN WITH state.v;
};
};
At this point we have NOT handled a fast break, so we have to go to the old break handler in the hopes that it knows what to do.
IF PrincOpsUtils.ReadXTS[] = on THEN PrincOpsUtils.WriteXTS[skip1];
state.v.source ← LOOPHOLE[fp];
state.v.dest ← LOOPHOLE[oldBreakTrap];
RETURN WITH state.v;
};
FastBreaksLeft: PUBLIC PROC RETURNS [NAT] = TRUSTED {
FastBreaksLeft[] returns the number of fast break slots remaining in the table.
RETURN [fastBreaksLeft];
};
SetFastBreak: PUBLIC ENTRY PROC
[code: LONG POINTER, pc: PrincOps.BytePC,
proc: FastBreakProc ← NIL, data: FastBreakData ← NIL]
RETURNS [id: FastBreakId] = TRUSTED {
SetFastBreak[code, pc, proc, data] adds a fast break at the specified location. The pointer returned is used to distinguish which break to clear when clearing the break. If NIL is returned, then the breakpoint could not be set (due to the table being full).
ENABLE UNWIND => NULL;
ep: FastBreakEntryPtr ← freeChain;
inst: Basics.BYTE ← PrincOps.zBRK;
needToSet: BOOLTRUE;
IF arrayPtr = NIL THEN {
Make a new array, and make sure that it is resident.
fb: FastBreakEntryPtr;
IF firstTime THEN {
this is the latest time that we can make ourselves resident and still be safe about it
--SpecialSpace.MakeGlobalFrameResident[FastBreakImpl];
--SpecialSpace.MakeCodeResident[FastBreakImpl];
firstTime ← FALSE;
};
space ← VM.Allocate[fastBreakPages];
VM.Pin[space];
arrayPtr ← LOOPHOLE[VM.AddressForPageNumber[space.page]];
fastBreaksLeft ← maxFastBreaks;
arrayPtr^ ← ALL[FastBreakEntry[]];
ep ← freeChain ← fb ← @arrayPtr[0];
FOR i: CARDINAL IN [0..maxFastBreaks-1) DO
next: FastBreakEntryPtr ← fb + SIZE[FastBreakEntry];
fb.next ← next;
fb ← next;
ENDLOOP;
};
IF ep = NIL THEN RETURN [NIL];
inst ← ReadCodeByte[code, pc];
IF inst = PrincOps.zBRK
THEN {
There is already a breakpoint here, so let's check our inUseChain for other instances.
FOR eep: FastBreakEntryPtr ← inUseChain, eep.next WHILE eep # NIL DO
IF eep.code = code AND eep.pc = pc THEN {
inst ← eep.inst;
needToSet ← FALSE;
EXIT};
ENDLOOP;
IF inst = PrincOps.zBRK THEN
There is already a break at this location, so punt.
RETURN [NIL];
};
id ← @ep.count;
Remove the entry from the free chain
freeChain ← ep.next;
Fill in the entry
ep^ ← [count: 0, next: inUseChain, code: code, pc: pc, inst: inst, data: data, proc: proc];
Put the entry on the inUseChain
inUseChain ← ep;
IF needToSet THEN
ep.inst ← WorldVM.SetBreak[WorldVM.LocalWorld[], LOOPHOLE[code], pc];
};
ClearFastBreak: PUBLIC ENTRY PROC
[id: FastBreakId, proc: FastBreakProc ← NIL, data: FastBreakData ← NIL]
RETURNS [found: BOOLFALSE] = TRUSTED {
ClearFastBreak[id, proc, data] clears the specified fast break, provided that the parameters agree with an active fast break. TRUE is returned iff such a break was found.
ENABLE UNWIND => NULL;
ep: FastBreakEntryPtr ← inUseChain;
lag: FastBreakEntryPtr ← NIL;
WHILE ep # NIL DO
IF LOOPHOLE[id, FastBreakEntryPtr] = ep AND ep.proc = proc AND ep.data = data THEN {
ClearInternal[ep, lag];
RETURN [TRUE];
};
lag ← ep;
ep ← ep.next;
ENDLOOP;
};
ClearAllFastBreaks: PUBLIC ENTRY PROC
[releaseResources: BOOLTRUE] RETURNS [cleared: NAT ← 0] = TRUSTED {
ClearAllFastBreaks[] clears all fast breaks. It also releases system resources used by fast breaks if releaseResources = TRUE. It returns the number of fast breaks removed.
ENABLE UNWIND => NULL;
WHILE inUseChain # NIL DO
Clear out one break
ClearInternal[inUseChain];
cleared ← cleared + 1;
ENDLOOP;
IF releaseResources AND arrayPtr # NIL THEN {
arrayPtr ← NIL;
-- SpecialSpace.MakeCodeSwappable[FastBreakImpl];
VM.Unpin[space];
VM.Free[space];
fastBreaksLeft ← maxFastBreaks;
};
};
ClearInternal: INTERNAL PROC [ep: FastBreakEntryPtr, lag: FastBreakEntryPtr ← NIL] = {
SELECT CountBreaksAtLocation[ep.code, ep.pc] FROM
0 => RETURN;
1 => {
Time to restore the old instruction
WorldVM.ClearBreak[WorldVM.LocalWorld[], LOOPHOLE[ep.code], ep.pc, ep.inst];
};
ENDCASE;
Now, remove the entry from the inUseChain
IF lag = NIL THEN inUseChain ← ep.next ELSE lag.next ← ep.next;
Put the block back on the free chain
ep.next ← freeChain;
freeChain ← ep;
fastBreaksLeft ← fastBreaksLeft + 1;
};
CountBreaksAtLocation: INTERNAL PROC
[code: LONG POINTER, pc: PrincOps.BytePC] RETURNS [count: CARDINAL ← 0] = {
ep: FastBreakEntryPtr ← inUseChain;
WHILE ep # NIL DO
IF ep.code = code AND ep.pc = pc THEN count ← count + 1;
ep ← ep.next;
ENDLOOP;
};
ReadCodeByte: PROC
[code: LONG POINTER, pc: PrincOps.BytePC] RETURNS [byte: Basics.BYTE] = {
addr: LONG CARDINALLOOPHOLE[code];
word: PrincOps.InstWord ←
LOOPHOLE[WorldVM.Read[WorldVM.LocalWorld[], addr+pc/2]];
IF pc MOD 2 = 1 THEN byte ← word.oddbyte ELSE byte ← word.evenbyte;
};
END.