SpyKernelImpl.mesa
Last edited by Bruce 14-Feb-81 19:30:51
Last edited by MBrown 16-Aug-81 13:30:07
Last edited by Levin 2-Nov-81 18:16:29
Last edited by Maxwell April 28, 1983 3:23 pm
DIRECTORY
Environment USING [PageNumber, wordsPerPage],
FastBreak USING [FastBreakProc],
Frame USING [GetReturnFrame],
Inline USING [BITAND, BITSHIFT, LongDiv],
IntervalTimer USING [WaitForExpirationInterval],
IntervalTimerFace USING [exists],
Mopcodes,
PageMap USING [GetF, flagsVacant],
PrincOps USING [BytePC, ControlLink, Frame, FrameHandle, GFTIndex, GlobalFrameHandle],
PrincOpsRuntime USING [GFT, GetFrame],
Process
USING [
Detach, GetCurrent, GetPriority, InitializeCondition,
Priority, priorityInterrupt, priorityNormal, SecondsToTicks, SetPriority],
ProcessInternal USING [DisableInterrupts, EnableInterrupts],
ProcessOperations
USING [
IndexToHandle, LongNotify, LongReEnter, LongWait, ReadPSB, ReadPTC],
PSB
USING [
Condition, FaultIndex, NullStateVectorHandle,
PsbHandle, PsbIndex, PsbLink, PDA, PDABase, Priority,
ProcessStateBlock, PsbNull, QueueEmpty, QueueHandle, qPageFault, StartPsb, Ticks],
Rope USING [ROPE],
RTProcess USING [GetTotalPageFaults, StartWatchingFaults, StopWatchingFaults],
RTStorageAccounting USING [AllocatorCallbackProcForSpy],
SafeStorage USING [NWordsAllocated, NWordsReclaimed],
SDDefs USING [SD, sGFTLength],
SpecialSpace
USING [
MakeCodeResident, MakeGlobalFrameResident],
SpyClient USING [],
SpyLog USING [Here, Initialize, logging, spy, Open, WriteData],
SpyOps
USING [
Count, DataType, Frame, LevelData, -- SetSensitiveBreak,
SpyState, Stack, stackHeader, stackLength, StackType],
System USING [GetClockPulses, Pulses, PulsesToMicroseconds],
UserTerminal USING [WaitForScanLine],
Utilities USING [PageFromLongPointer];
SpyKernelImpl:
MONITOR
IMPORTS
PilotFrame: Frame, IntervalTimer, IntervalTimerFace, Inline, PageMap, PrincOpsRuntime, Process, ProcessInternal, ProcessOperations, RTStorageAccounting, RTProcess, SafeStorage, SpecialSpace, SpyLog, System, UserTerminal, Utilities
EXPORTS SpyClient, SpyOps
SHARES PageMap =
BEGIN OPEN PrincOps, PSB, Rope;
-- SpyKernel parameters --
spyState: PUBLIC SpyOps.SpyState ← off;
watching: PUBLIC SpyOps.DataType ← CPU;
justMe: PUBLIC PSB.PsbIndex ← 0;
general statistics
runningTime: PUBLIC System.Pulses; -- total time running
pageFaults: PUBLIC LONG CARDINAL;
active, starts, stops: PUBLIC INTEGER ← 0;
wordsAllocated: PUBLIC LONG CARDINAL;
wordsReclaimed: PUBLIC LONG CARDINAL;
wakeups:
PUBLIC SpyOps.Count ← 0;
-- Number of times that Spy counted something
notScheduled: PUBLIC SpyOps.Count ← 0; -- no interesting process sceduled
skips: PUBLIC SpyOps.Count ← 0; -- counts skipped
code: PUBLIC SpyOps.Count ← 0; -- page faults on code
data: PUBLIC SpyOps.Count ← 0; -- page faults on data
levelData: PUBLIC SpyOps.LevelData ← ALL[0];
****************************************************************************
initializing the Spy and data
****************************************************************************
InitializeSpy:
PUBLIC
ENTRY
PROCEDURE[
dataType: SpyOps.DataType,
process: PSB.PsbIndex]
RETURNS[errorMsg: ROPE] =
BEGIN
SELECT dataType
FROM
spaces => RETURN["Use UserBreaks on SpaceImplA.CreateAny and SpaceImplA.DeleteAny."];
ENDCASE;
DisableSpy[];
InitializeTables[];
IF ~SpyLog.spy THEN ZeroLog[];
watching ← dataType;
justMe ← IF dataType = process THEN process ELSE 0;
EnableSpy[];
END;
SensitiveBreak:
PROCEDURE[words:
CARDINAL] =
BEGIN
IF active = 0 THEN RETURN;
IF watching NOT IN [allocations..wordsAllocated] THEN RETURN;
Record[
psbi: LOOPHOLE[Process.GetCurrent[]],
frame: PilotFrame.GetReturnFrame[],
count: IF watching = wordsAllocated THEN words ELSE 1];
END;
InitializeTables:
PROCEDURE =
BEGIN
runningTime ← [0];
levelData ← ALL[0];
wakeups ← notScheduled ← skips ← code ← data ← 0;
wordsAllocated ← wordsReclaimed ← 0;
END;
Initialize:
PROCEDURE =
BEGIN
dolphin ← System.PulsesToMicroseconds[[1]]#32; -- hack
SpecialSpace.MakeCodeResident[SpyKernelImpl];
SpecialSpace.MakeGlobalFrameResident[SpyKernelImpl];
Process.InitializeCondition[@processDead, 10000];
SetTimeouts[];
END;
SetTimeouts:
PROC =
BEGIN
ticks: PSB.Ticks;
cutoff: PSB.Ticks;
found: BOOLEAN;
frame: PrincOps.FrameHandle;
IF IntervalTimerFace.exists THEN RETURN;
cutoff ← Process.SecondsToTicks[10];
ProcessInternal.DisableInterrupts[]; -- stops the clock. NO PAGE FAULTS ALLOWED!
ticks ← ProcessOperations.ReadPTC[];
FOR psbi: PsbIndex
IN [
PSB.StartPsb..
PSB.StartPsb+
PSB.
PDA.count)
DO
IF PSB.PDA.block[psbi].mds = 0 THEN LOOP;
IF PSB.PDA.block[psbi].mds - ticks > cutoff THEN LOOP; -- not significant
IF
PSB.
PDA.block[psbi].link.vector
THEN frame ← PDA[PSB.PDA.block[psbi].context.state].frame
ELSE frame ← PSB.PDA.block[psbi].context.frame;
found ← FALSE;
FOR i:
CARDINAL
IN [0..index)
DO
IF timeout[i] = [frame.accesslink.gfi, frame.pc] THEN {found ← TRUE; EXIT};
ENDLOOP;
IF found THEN LOOP;
timeout[index] ← [frame.accesslink.gfi, frame.pc];
index ← index + 1;
IF index = maxIndex THEN EXIT;
ENDLOOP;
ProcessInternal.EnableInterrupts[];
ZeroLog:
PUBLIC
PROCEDURE = {
SpyLog.Initialize[NIL, 40, TRUE];
SpyLog.Open[TRUE]};
****************************************************************************
starting and stopping the Spy
****************************************************************************
Note: These procedures need not be resident.
processDead: CONDITION;
spyProcess: PROCESS ← NIL;
oldSpyState: SpyOps.SpyState ← off;
startTime: System.Pulses;
startPageFaults: LONG CARDINAL;
startWordsAllocated: LONG CARDINAL;
startWordsReclaimed: LONG CARDINAL;
StartCounting:
PUBLIC
ENTRY FastBreak.FastBreakProc =
BEGIN
i: CARDINAL;
priority: Process.Priority;
starts ← starts + 1;
IF active = 0 THEN WHILE spyProcess # NIL DO WAIT processDead; ENDLOOP;
IF (active ← active + 1) > 1 THEN RETURN[useOldBreak: FALSE]; -- a psuedo start
IF watching = pagefaults
THEN FOR i IN [0..10) DO RTProcess.StopWatchingFaults[] ENDLOOP
ELSE RTProcess.StartWatchingFaults[];
startPageFaults ← RTProcess.GetTotalPageFaults[];
startWordsAllocated ← SafeStorage.NWordsAllocated[];
startWordsReclaimed ← SafeStorage.NWordsReclaimed[];
startTime ← System.GetClockPulses[];
IF watching = breakProcess THEN justMe ← LOOPHOLE[Process.GetCurrent[]];
IF justMe # 0 THEN justMePSB ← @PSB.PDA[ProcessOperations.IndexToHandle[justMe]];
SetTimeouts[]; -- do it a second time in case the first time missed some
priority ← Process.GetPriority[];
Process.SetPriority[Process.priorityInterrupt];
SELECT watching
FROM
CPU, process, breakProcess => Process.Detach[spyProcess ← FORK Spy[]];
pagefaults => Process.Detach[spyProcess ← FORK PageFaultRecorder[]];
allocations, wordsAllocated => {
RTStorageAccounting.AllocatorCallbackProcForSpy ← SensitiveBreak;
spyState ← on};
ENDCASE => spyState ← on;
Process.SetPriority[priority];
RETURN[useOldBreak: FALSE];
END;
StopCounting:
PUBLIC
ENTRY FastBreak.FastBreakProc =
BEGIN
IF active = 0 THEN RETURN[useOldBreak: FALSE]; -- already stopped
stops ← stops + 1;
IF (active ← active - 1) > 0 THEN RETURN; -- a psuedo stop
spyState ← off;
RTStorageAccounting.AllocatorCallbackProcForSpy ← NIL;
runningTime ← [runningTime + System.GetClockPulses[] - startTime];
pageFaults ← pageFaults + RTProcess.GetTotalPageFaults[] - startPageFaults;
wordsAllocated ← wordsAllocated + SafeStorage.NWordsAllocated[] - startWordsAllocated;
wordsReclaimed ← wordsReclaimed + SafeStorage.NWordsReclaimed[] - startWordsReclaimed;
IF watching = pagefaults
THEN RTProcess.StartWatchingFaults[]
ELSE RTProcess.StopWatchingFaults[];
RETURN[useOldBreak: FALSE];
END;
DisableSpy:
PROCEDURE =
BEGIN
IF spyState = disabled THEN RETURN;
oldSpyState ← spyState;
spyState ← disabled;
END;
EnableSpy: PROCEDURE = {spyState ← oldSpyState};
*********************************************************************
watching CPU
*********************************************************************
All code invoked by this process should be resident
justMePSB: LONG POINTER TO ProcessStateBlock ← NIL;
wait: CARDINAL ← 0; -- used with dolphins
monitor: BOOLEAN ← FALSE; -- measure performance of spy
dolphin: BOOLEAN ← FALSE;
maxStackDepth: CARDINAL = 200;
timeout: ARRAY [0..maxIndex) OF RECORD[gfi: PrincOps.GFTIndex, pc: CARDINAL];
maxIndex: CARDINAL = 20;
index: CARDINAL← 0;
sampleInterval: INT ← 10*1000; -- measured in microseconds
Spy:
PROCEDURE =
BEGIN
top: PsbIndex;
frame: FrameHandle;
myPSB: PsbHandle = ProcessOperations.ReadPSB[];
handleMask: PsbLink = [failed: FALSE, priority: 0, next: LAST[PsbIndex], reserved: 0, vector: FALSE];
NextHandle:
PROCEDURE [link:
UNSPECIFIED]
RETURNS [PsbHandle] =
INLINE
{RETURN[Inline.BITAND[link, handleMask]]};
HandleToIndex:
PROCEDURE [psb: PsbHandle]
RETURNS [PsbIndex] =
INLINE
{RETURN[Inline.BITSHIFT[psb, -2]]};
SearchReadyList:
PROCEDURE =
INLINE
BEGIN
skip, once: BOOLEAN ← FALSE;
headOfReadyList, current: PsbHandle;
ProcessInternal.DisableInterrupts[];
top ← PSB.PsbNull;
headOfReadyList ← NextHandle[PDA.ready];
headOfReadyList ← NextHandle[PDA[headOfReadyList].link]; -- want the SECOND psb.
FOR current ← headOfReadyList, NextHandle[
PDA[current].link]
DO
psb: LONG POINTER TO ProcessStateBlock = @PDA[current];
link: PsbLink = psb.link;
level: Process.Priority = link.priority;
IF current = headOfReadyList THEN IF once THEN EXIT ELSE once ← TRUE;
IF level = 0 THEN LOOP;
IF current = myPSB THEN LOOP;
IF ~link.vector AND PDA.state[level] = NullStateVectorHandle THEN LOOP; -- no SV.
frame ← IF link.vector THEN PDA[psb.context.state].frame ELSE psb.context.frame;
skip over a process that appears in the ready queue because it just timed out.
(The Spy wakes up with all of the other timeouts. Since it is the highest priority,
it will run first. All of the other timeouts will appear on the ready list. Most likely,
they will just check some condition and then go back to sleep. This will mask the
more interesting processes.)
skip ← FALSE;
FOR i:
CARDINAL
IN [0..maxIndex)
DO
IF timeout[i] = [0, 0] THEN EXIT;
IF timeout[i] # [frame.accesslink.gfi, frame.pc] THEN LOOP;
skip ← TRUE; EXIT;
ENDLOOP;
IF skip THEN LOOP;
we have a good process!
top ← HandleToIndex[current];
EXIT; ENDLOOP;
ProcessInternal.EnableInterrupts[];
END;
spyState ← on;
-- MAIN LOOP --
DO
IF IntervalTimerFace.exists
THEN IntervalTimer.WaitForExpirationInterval[sampleInterval]
ELSE UserTerminal.WaitForScanLine[0];
IF active <= 0 THEN EXIT;
IF spyState = disabled THEN LOOP;
IF dolphin AND wait > 0 THEN {wait ← wait - 1; LOOP} ELSE wait ← 8;
IF monitor THEN SpyLog.Here[];
SearchReadyList[];
SELECT
TRUE
FROM
justMe = 0 => Record[top, frame];
justMePSB.link.failed => Record[justMe, NIL, 2]; -- waiting ML
justMePSB.flags.waiting => Record[justMe, NIL, 3]; -- waiting CV
OnQueue[justMe, @
PDA.fault[
PSB.qPageFault].queue] =>
Record[justMe, NIL, 1]; -- waiting pagefault
OnQueue[justMe, @
PDA.fault[
PSB.qPageFault+4].queue] =>
Record[justMe, NIL, 1]; -- waiting pagefault
OnQueue[justMe, @
PSB.
PDA.ready] =>
SELECT
TRUE
FROM
PDA.state[justMePSB.link.priority] = NullStateVectorHandle
AND
~justMePSB.link.vector => Record[justMe, NIL, 5]; -- waiting SV
justMe # top => Record[justMe, NIL, 4]; -- prempted by a higher priority process
ENDCASE => Record[justMe]; -- ready
ENDCASE => Record[justMe, NIL, 6]; -- in some unknown state
IF monitor THEN SpyLog.Here[];
ENDLOOP;
spyProcess ← NIL;
spyState ← off;
NotifyProcessDead[];
Process.SetPriority[Process.priorityNormal];
END;
OnQueue:
PROC[psbi: PsbIndex, queueHandle:
PSB.QueueHandle]
RETURNS[BOOL] = INLINE
BEGIN
tail, prev: PsbIndex;
IF queueHandle^ = PSB.QueueEmpty THEN RETURN[FALSE];
prev ← tail ← queueHandle.tail;
THROUGH [
FIRST[PsbIndex]..
LAST[PsbIndex]+1]
-- garbage protection --
DO next: PsbIndex =
PSB.
PDA.block[prev].link.next;
IF next = psbi THEN RETURN[TRUE];
prev ← next;
IF prev = tail THEN RETURN[FALSE];
ENDLOOP;
RETURN[FALSE] -- actually, the queue is thoroughly mangled! --
END;
*********************************************************************
watching pagefaults
*********************************************************************
recordPageFaulted: BOOLEAN ← FALSE;
Data: TYPE = RECORD[process: CARDINAL, page: INTEGER];
CodeBase: TYPE = LONG POINTER TO PACKED ARRAY [0..0) OF Mopcodes.op;
PageFaultRecorder:
PROCEDURE =
-- stolen from Ben.mesa
BEGIN
fault: Data;
type: CARDINAL;
codePage: INTEGER;
handle: PSB.PsbHandle;
frame: PrincOps.FrameHandle;
pda: PSB.PDABase = PSB.PDA;
qPageFault: PSB.FaultIndex = PSB.qPageFault;
pPageFaultCondition:
LONG
POINTER
TO
PSB.Condition =
@pda.fault[qPageFault].condition;
pPageFaultCONDITION:
LONG
POINTER
TO
CONDITION =
LOOPHOLE[pPageFaultCondition];
recorderLock: MONITORLOCK;
shouldNotifyPilot: BOOLEAN ← FALSE;
NO PAGE FAULTS ALLOWED!
spyState ← on;
DO
wait for a page fault:
ProcessOperations.LongWait[@recorderLock, pPageFaultCONDITION, 1];
UNTIL ProcessOperations.LongReEnter[@recorderLock, pPageFaultCONDITION]
DO
NULL ENDLOOP;
IF pda.fault[qPageFault].queue.tail =
PSB.PsbNull
THEN {
-- timed out
IF shouldNotifyPilot
AND pda.fault[qPageFault].condition.tail ~= PSB.PsbNull THEN {
LongNakedNotify[pPageFaultCONDITION];
shouldNotifyPilot ← FALSE};
IF active <= 0 THEN EXIT;
LOOP}; -- go back and wait again.
figure out who faulted:
fault.process ← pda.block[pda.fault[qPageFault].queue.tail].link.next; -- walk to tail, then to head.
handle ← ProcessOperations.IndexToHandle[fault.process];
fault.page ← Utilities.PageFromLongPointer[pda[pda[handle].context.state].memPointer];
frame ←
IF pda[handle].link.vector
THEN pda[pda[handle].context.state].frame
ELSE pda[handle].context.frame;
codePage ← LOOPHOLE[frame.accesslink.code.longbase+frame.pc/2, INT]/256;
SELECT
TRUE
FROM
ABS[codePage - fault.page] <= 1 => {code ← code + 1; type ← 2};
Xfer[frame.accesslink.code.longbase, frame.pc] => {code ← code + 1; type ← 3};
ENDCASE => {data ← data + 1; type ← 1};
wake up the Pilot fault handler:
ProcessInternal.DisableInterrupts[];
IF pPageFaultCondition^.tail =
PSB.PsbNull
THEN shouldNotifyPilot ← TRUE -- Pilot not ready for this fault yet...
ELSE LongNakedNotify[pPageFaultCONDITION];
ProcessInternal.EnableInterrupts[];
log the fault:
IF active <= 0 THEN EXIT;
IF recordPageFaulted THEN SpyLog.WriteData[CODE[Data], SIZE[Data], @fault];
Record[fault.process, NIL, type];
ENDLOOP;
spyProcess ← NIL;
spyState ← off;
NotifyProcessDead[];
Process.SetPriority[Process.priorityNormal];
END;
Xfer:
PROCEDURE[base: CodeBase, pc: PrincOps.BytePC]
RETURNS[
BOOLEAN] =
INLINE BEGIN -- is the current pc pointing to some sort of xfer?
IF base[pc] IN [Mopcodes.zEFC0..Mopcodes.zKFCB] THEN RETURN[TRUE];
RETURN[FALSE];
END;
LongNakedNotify:
PROCEDURE [pCondition:
LONG
POINTER
TO
CONDITION] =
INLINE {
Used ONLY to notify a condition from a high priority process outside the relevant monitor.
pCond: LONG POINTER TO PSB.Condition = LOOPHOLE[pCondition];
ProcessInternal.DisableInterrupts[];
IF pCond^.tail=
PSB.PsbNull
THEN {pCond^.wakeup ← TRUE; ProcessInternal.EnableInterrupts[]}
ELSE {ProcessInternal.EnableInterrupts[]; ProcessOperations.LongNotify[pCondition]}};
NotifyProcessDead: ENTRY PROCEDURE = {NOTIFY processDead};
***************************************************************************
recording the information
***************************************************************************
stack: SpyOps.Stack;
UserBreak:
PUBLIC FastBreak.FastBreakProc =
TRUSTED BEGIN
count: CARDINAL ← 1;
Parameters:
TYPE =
RECORD[
zone: ZONE,
size: CARDINAL,
filler: ARRAY [0..11) OF CARDINAL];
IF spyState # on THEN RETURN[useOldBreak: FALSE];
IF watching # userDefined THEN RETURN[useOldBreak: FALSE];
IF watching = wordsAllocated THEN count ← LOOPHOLE[sv.stk, Parameters].size;
Record[LOOPHOLE[Process.GetCurrent[]], frame, count];
RETURN[useOldBreak: FALSE];
END;
Record:
PUBLIC
ENTRY
PROCEDURE[psbi: PsbIndex, frame: FrameHandle ←
NIL,
type: SpyOps.StackType ← 0, count: CARDINAL ← 1] =
BEGIN
level: Process.Priority;
psb: LONG POINTER TO ProcessStateBlock;
IF spyState = disabled THEN RETURN;
IF active <= 0 THEN RETURN;
wakeups ← wakeups + count;
IF psbi =
PSB.PsbNull
THEN
IF PDA.fault[qPageFault].queue.tail = PSB.PsbNull
AND
PDA.fault[qPageFault+4].queue.tail =
PSB.PsbNull
THEN {notScheduled ← notScheduled + count; RETURN}
ELSE {skips ← skips + count; RETURN}; -- waiting for a page fault
psb ← @PDA[ProcessOperations.IndexToHandle[psbi]];
level ← psb.link.priority;
IF frame =
NIL
THEN
frame ← IF psb.link.vector THEN PDA[psb.context.state].frame ELSE psb.context.frame;
levelData[level] ← levelData[level] + count;
LogStack[psbi, level, frame, count, type]
END;
LogStack:
INTERNAL
PROC [process:
PSB.PsbIndex, level: Process.Priority, frame: FrameHandle, count, type:
CARDINAL] =
-- Log the stack on the Trace Log --
INLINE BEGIN
sdLength: CARDINAL = SDDefs.SD[SDDefs.sGFTLength];
gfi: GFTIndex ← 0;
gf: GlobalFrameHandle ← NIL;
length: CARDINAL ← 0;
GfiToGF:
PROCEDURE [gfi: GFTIndex]
RETURNS [GlobalFrameHandle] =
INLINE
{RETURN[PrincOpsRuntime.GetFrame[PrincOpsRuntime.GFT[gfi]]]};
ValidFrame:
PROCEDURE [f: ControlLink]
RETURNS [FrameHandle] =
BEGIN OPEN Environment, PageMap;
Note: as a side-effect, this procedure sets 'gf' and 'gfi', which are
used by the other local procedures below and the main loop of IncrementBucket.
IF f.proc
OR f.indirect
OR f.frame =
NIL
OR (gf ← f.frame.accesslink) =
NIL
OR
GetF[Inline.LongDiv[LOOPHOLE[LONG[gf]], wordsPerPage]].flags = flagsVacant OR
(gfi ← gf.gfi) > sdLength OR GfiToGF[gfi] ~= gf THEN RETURN[NIL];
RETURN[f.frame]
END;
IF ~SpyLog.logging THEN RETURN;
stack.type ← type;
stack.count ← count;
stack.level ← level;
stack.process ← process;
frame ← ValidFrame[LOOPHOLE[frame]];
THROUGH [0..maxStackDepth)
UNTIL frame =
NIL
DO
save the current frame
this also saves the last ignored frame
IF length >= SpyOps.stackLength THEN {stack.level ← 7; length ← 0; EXIT};
stack.frame[length] ← [0, gfi, frame.pc];
IF length > 0
AND stack.frame[length] = stack.frame[length-1]
THEN length ← length - 1; -- skip recursive frames
length ← length + 1;
frame ← ValidFrame[frame.returnlink];
ENDLOOP;
IF length = 0
AND stack.level < 7
THEN {notScheduled ← notScheduled + count; RETURN}
ELSE SpyLog.WriteData[SpyOps.Stack.
CODE,
SpyOps.stackHeader + length*SpyOps.Frame.SIZE,
@stack];
END;
Initialize[];
22-Jan-82 Maxwell: Removed cross-partition code; converted to Cedar
4-Feb-82 Maxwell: Added stack logging option
11-Mar-82 Maxwell: Added page fault recorder
October 14, 1982 10:07 am Maxwell: Removed Pilot Spy code
END.
since the Spy AND timeouts are synchronized to the vertical retrace,
we need to skip over processes that have just timed out. The following
[gfi, pc] pairs indicate WAITs on timeouts.
timeout[0] ← [gfi: 100B, pc: 114B];
WAIT harwareVerticalRetrace IN UserTerminalImpl.WaitForVerticalRetrace
timeout[1] ← [gfi: 100B, pc: 143B];
WAIT verticalRetrace IN UserTerminalImpl.WaitForVerticalRetrace
timeout[2] ← [gfi: 401B, pc: 2316B];
Wait[Process.MSecToTicks[5000]] IN InscriptImpl.WaitForEntry
timeout[3] ← [gfi: 401B, pc: 3307B];
WAIT pageDone IN InscriptImpl.MaintainPage
timeout[4] ← [gfi: 1075B, pc: 12265B];
WAIT iIncr IN TEditTypeScriptImpl.GetChar
timeout[5] ← timeout[6] ← timeout[7] ← timeout[8] ← timeout[9] ← [0, 0];