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 POINTER ← NIL,
pc: PrincOps.BytePC ← [0],
inst: Basics.BYTE ← 0,
data: LONG POINTER ← NIL,
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: PROC ← NIL;
fastBreaksLeft: NAT ← maxFastBreaks;
inUseChain: FastBreakEntryPtr ← NIL;
freeChain: FastBreakEntryPtr ← NIL;
firstTime: BOOL ← TRUE;
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: BOOL ← FALSE;
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: BOOL ← TRUE;
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: BOOL ← FALSE] = 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: BOOL ← TRUE] 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 CARDINAL ← LOOPHOLE[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.