-- File LupineTimeTestDriver.mesa.
-- Last edited by BZM on  2-Mar-82 21:12:38.
-- Last edited by Andrew Birrell on January 26, 1983 6:08 pm

-- This program is derived from Liaison's Mesa6 ParamTestDriver.


DIRECTORY
  Heap USING [systemZone],
  LupineTimeTest, --USING [ALL]
  String USING [AppendLongNumber, AppendString, InvalidNumber, StringToDecimal],
  Mopcodes USING [zMISC],
  Process USING [Detach, Yield],
  Runtime USING [CallDebugger],
  SpyClient USING [StartSpy, StopSpy],
  System USING [GetClockPulses, Microseconds, Pulses, PulsesToMicroseconds],
  Time USING [Append, Current, Unpack],
  TTY USING [
	Create, Destroy, GetLine, Handle, LineOverflow,
	NumberFormat, PutChar, PutCR, PutDecimal, PutLine,
	PutLongDecimal, PutLongNumber, PutString, Rubout,
	ResetUserAbort, UserAbort ];


LupineTimeTestDriver: PROGRAM
  IMPORTS
	TimeTest: LupineTimeTest, Heap, Process, Runtime,
	SpyClient, ShortString: String, System, Time, TTY
  = BEGIN


-- Compiletime parameters:

  StandardNumberOfTrials: INTEGER = 1;
  StandardIterationsPerTrial: INTEGER = 3000;


-- Runtime parameters:

  TestParameters: TYPE = MACHINE DEPENDENT RECORD [
    useDoradoClock (0): BOOLEAN ← FALSE,
    countOnlyEmulatorCycles (1): BOOLEAN ← FALSE,
    checkResults (2): BOOLEAN ← TRUE,
    trials (3): INTEGER ← StandardNumberOfTrials,
    iterationsPerTrial (4): INTEGER ← StandardIterationsPerTrial,
    spying (5:0..0): BOOLEAN ← FALSE,
    spyOnProcs (5:1..1): BOOLEAN ← FALSE,
    showSpyData (5:2..2): {afterEachTest, afterAllTests} ← afterAllTests,
    filler (5:3..15): BOOLEAN ← NULL ];

  DefaultTestParameters: TestParameters = [];


-- Runtime inconsistency found:

  ParamsDisagree: SIGNAL = CODE;  -- An echo test failed.


-- This top-level driver routine is run in a separate process.

  user: TTY.Handle;
  String: TYPE = STRING;

  TestProcess: PROCEDURE =
    BEGIN
    logFileString: STRING = [100];
    logFile: String ← UniqueName[
      root: "LupineTimeTest"L, suffix: ".log"L,
      nameString: logFileString ];
    user ← TTY.Create[name: logFile];
    TestRoutine[logFile: logFile ! ABORTED => CONTINUE ];
    TTY.Destroy[user];
    END;

  UniqueName: PROCEDURE [root, suffix, nameString: String]
      RETURNS [rootSuffix: String] =
    BEGIN
    rootSuffix ← nameString;
    rootSuffix.length ← 0;
    ShortString.AppendString[to: rootSuffix, from: root];
    ShortString.AppendLongNumber [
      s: rootSuffix, n: System.GetClockPulses[], radix: 10 ];
    ShortString.AppendString[to: rootSuffix, from: suffix];
    END;


-- For periodically causing glitches (eg, NIL) that Lupine claims to handle.

  Periodically: PROC [testNumber: INTEGER] RETURNS [--yes:-- BOOLEAN] =
    --INLINE-- {RETURN[ (testNumber MOD 31) = 0 ]};


-- Main Timing Test.

  tp: TestParameters;  -- Global values.

  TestRoutine: PROCEDURE[logFile: String] =
    BEGIN  -- The entire test is in a loop.

    InitPrecisionTimings;

    DO ENABLE BEGIN
	AbortTest => CONTINUE;
	UNWIND => FinishPrecisionTimings;
	END;

      -- Get Test Parameters.

      tp ← DefaultTestParameters;
      
      TTY.PutCR[user];
      TTY.PutLine[user, "Lupine RPC Timing Test of 24 February 1982."L];
      TTY.PutString[user, "Results appear in file "L];
      TTY.PutString[user, logFile];  TimeStamp[" of "L];
      TTY.PutCR[user];
      TTY.PutString[user, "Your options are:
  D    Don't check results of remote calls for correctness.
  E    Exclude nonemulator cycles in timings (Dorados only).
  G    Go (type this last).
  H    High precision timings (Dorados only).
  P    Procedure-level spying (via Copilot).
  R    Reset test parameters to default values.
  S    Spy after all tests are over.
  T    Spy at the conclusion of each test.
  Q    Quit this program immediately.
  --   This line is a comment.
  1-9  Perform # trials of each test.
  ↑DEL To abort at any time."L ];
      TTY.PutCR[user];  TTY.PutCR[user];

      DO OPEN tp, TTY;
	BEGIN
	reply: STRING = [100];
	PutString[user, "Type one of D,E,G,H,P,R,S,T,Q,--,1-9 [R1]: "L];
	reply[0] ← 0C;
	CheckAbort;
	GetLine[user, reply ! Rubout, LineOverflow => GOTO TryAgain];
	CheckAbort;
	SELECT reply[0] FROM
	  'd, 'D => {checkResults ← FALSE};
	  'e, 'E => {useDoradoClock ← countOnlyEmulatorCycles ← TRUE};
	  'g, 'G => {EXIT};
	  'h, 'H => {useDoradoClock ← TRUE};
	  'p, 'P => {spying ← spyOnProcs ← TRUE};
	  'r, 'R => {tp ← DefaultTestParameters};
	  's, 'S => {spying ← TRUE; showSpyData ← afterAllTests};
	  't, 'T => {spying ← TRUE; showSpyData ← afterEachTest};
	  'q, 'Q => {GOTO QuitTestProgram};
	  '-     => {NULL};
	  IN ['1..'9] => {trials ← ShortString.StringToDecimal[
			    reply ! ShortString.InvalidNumber => GOTO TryAgain ]};
	  ENDCASE => GOTO TryAgain;
	EXITS
	  TryAgain => PutLine[user, " ???"L];
	END;
	ENDLOOP;

      -- Call debugger as needed for spying.
      IF tp.spying THEN CallSpy[
          IF tp.spyOnProcs THEN startAndWatchDetails ELSE start ];

      -- Eliminating timing transients before timings start.
      TimeTest.Null[];  TimeTest.Null[];  TimeTest.Null[]; 

      -- Perform testing.
      NullTests;
      SimpleParameterTests;
      SignalTests;
      SmallArrayTests;
    --BigArrayTests;

      -- Call debugger as needed for spying.
      IF tp.spying THEN CallSpy[
	(SELECT tp.showSpyData FROM
	    afterEachTest => stop,
	    afterAllTests => stopAndDisplayStats,
	    ENDCASE => ERROR) ];

      REPEAT
	QuitTestProgram => NULL;
      ENDLOOP; -- Test Loop.

    FinishPrecisionTimings;

    END;  -- TestRoutine.


  NullTests: PROC =
    BEGIN

    -- Timing overhead.

    StartTest[testName: "per call timing overhead"L, skipSpy: TRUE];
    THROUGH [0..tp.trials) DO
      StampTest;
      FOR test: INTEGER IN [0..tp.iterationsPerTrial) DO
	--DelayTest;
	StartPrecisionTiming;
	NULL;
	StopPrecisionTiming;
	ENDLOOP;
      ENDLOOP;
    StopTest[skipSpy: TRUE];

    -- Null Procedure

    StartTest["the null procedure"L];
    THROUGH [0..tp.trials) DO
      StampTest;
      FOR test: INTEGER IN [0..tp.iterationsPerTrial) DO
	DelayTest;
	StartPrecisionTiming;
	TimeTest.Null[];
	StopPrecisionTiming;
	ENDLOOP;
      ENDLOOP;
    StopTest;

    END;  -- NullTests.


  SimpleParameterTests: PROC =
    BEGIN

    a,b,c,d,e,f,g,h,i,j: INTEGER;

    StartTest["a 1 parameter procedure"L];
    THROUGH [0..tp.trials) DO
      StampTest;
      FOR test: INTEGER IN [0..tp.iterationsPerTrial) DO
	DelayTest;
	StartPrecisionTiming;
	a ← TimeTest.One[test];
	StopPrecisionTiming;
	IF tp.checkResults AND a#test THEN SIGNAL ParamsDisagree;
	ENDLOOP;
      ENDLOOP;
    StopTest;

    StartTest["a 2 parameter procedure"L];
    THROUGH [0..tp.trials) DO
      StampTest;
      FOR test: INTEGER IN [0..tp.iterationsPerTrial) DO
	DelayTest;
	StartPrecisionTiming;
	[a,b] ← TimeTest.Two[test+0,test+1];
	StopPrecisionTiming;
	IF tp.checkResults AND (a#test+0 OR b#test+1) THEN SIGNAL ParamsDisagree;
	ENDLOOP;
      ENDLOOP;
    StopTest;

    StartTest["a 4 parameter procedure"L];
    THROUGH [0..tp.trials) DO
      StampTest;
      FOR test: INTEGER IN [0..tp.iterationsPerTrial) DO
	DelayTest;
	StartPrecisionTiming;
	[a,b,c,d] ← TimeTest.Four[test+0,test+1,test+2,test+3];
	StopPrecisionTiming;
	IF tp.checkResults AND (a#test+0 OR b#test+1 OR c#test+2 OR d#test+3) 
	  THEN SIGNAL ParamsDisagree;
	ENDLOOP;
      ENDLOOP;
    StopTest;

    StartTest["a 10 parameter procedure"L];
    THROUGH [0..tp.trials) DO
      StampTest;
      FOR test: INTEGER IN [0..tp.iterationsPerTrial) DO
	DelayTest;
	StartPrecisionTiming;
	[a,b,c,d,e,f,g,h,i,j] ← TimeTest.Ten[
	    test+0,test+1,test+2,test+3,test+4,
	    test+5,test+6,test+7,test+8,test+9 ];
	StopPrecisionTiming;
	IF tp.checkResults AND
	    (a#test+0 OR b#test+1 OR c#test+2 OR d#test+3 OR e#test+4
	     OR f#test+5 OR g#test+6 OR h#test+7 OR i#test+8 OR j#test+9)
	  THEN SIGNAL ParamsDisagree;
	ENDLOOP;
      ENDLOOP;
    StopTest;

    END; -- SimpleParameterTests.


  SignalTests: PROC =
    BEGIN
    
    out: INTEGER;

    StartTest["signal test (no signalling)"L];
    THROUGH [0..tp.trials) DO
      StampTest;
      FOR test: INTEGER IN [0..tp.iterationsPerTrial) DO
	DelayTest;
	StartPrecisionTiming;
	out ← TimeTest.SignalTest[ in: test, action: neither
	  ! TimeTest.Signal => {SIGNAL ParamsDisagree; CONTINUE} ];
	StopPrecisionTiming;
	IF tp.checkResults AND out#test THEN SIGNAL ParamsDisagree;
	ENDLOOP;
      ENDLOOP;
    StopTest;

    StartTest["signal test (SIGNAL & RESUME)"L];
    THROUGH [0..tp.trials) DO
      StampTest;
      FOR test: INTEGER IN [0..tp.iterationsPerTrial) DO
	DelayTest;
	StartPrecisionTiming;
	out ← TimeTest.SignalTest[ in: test, action: signal
	  ! TimeTest.Signal => {
	      IF tp.checkResults AND in#test THEN SIGNAL ParamsDisagree;
	      RESUME[out: 2*in] } ];
	StopPrecisionTiming;
	IF tp.checkResults AND out#2*test THEN SIGNAL ParamsDisagree;
	ENDLOOP;
      ENDLOOP;
    StopTest;

    StartTest["signal test (ERROR & UNWIND)"L];
    THROUGH [0..tp.trials) DO
      StampTest;
      FOR test: INTEGER IN [0..tp.iterationsPerTrial) DO
	DelayTest;
	StartPrecisionTiming;
	out ← TimeTest.SignalTest[ in: test, action: error
	  ! TimeTest.Signal => {
	      IF tp.checkResults AND in#test THEN SIGNAL ParamsDisagree;
	      CONTINUE } ];
	StopPrecisionTiming;
	--Checking is never valid after an ERROR since out is undefined.
	ENDLOOP;
      ENDLOOP;
    StopTest;

    END;  -- SignalTests.


  SmallArrayTests: PROC =
    BEGIN

    one: TimeTest.Array1 = ALL[1];
    four: TimeTest.Array4 = ALL[4];
    ten: TimeTest.Array10 = ALL[10];
    forty: TimeTest.Array40 = ALL [40];
    hundred: TimeTest.Array100 = ALL[100];

    array1: TimeTest.Array1;
    array4: TimeTest.Array4;
    array10: TimeTest.Array10;
    array40: TimeTest.Array40;
    array100: TimeTest.Array100;

    StartTest["1 word array procedure"L];
    THROUGH [0..tp.trials) DO
      StampTest;
      FOR test: INTEGER IN [0..tp.iterationsPerTrial) DO
	DelayTest;
	StartPrecisionTiming;
	array1 ← TimeTest.OneArray[one];
	StopPrecisionTiming;
	IF tp.checkResults AND one#array1 THEN SIGNAL ParamsDisagree;
	ENDLOOP;
      ENDLOOP;
    StopTest;

    StartTest["4 word array procedure"L];
    THROUGH [0..tp.trials) DO
      StampTest;
      FOR test: INTEGER IN [0..tp.iterationsPerTrial) DO
	DelayTest;
	StartPrecisionTiming;
	array4 ← TimeTest.FourArray[four];
	StopPrecisionTiming;
	IF tp.checkResults AND four#array4 THEN SIGNAL ParamsDisagree;
	ENDLOOP;
      ENDLOOP;
    StopTest;

    StartTest["10 word array procedure"L];
    THROUGH [0..tp.trials) DO
      StampTest;
      FOR test: INTEGER IN [0..tp.iterationsPerTrial) DO
	DelayTest;
	StartPrecisionTiming;
	array10 ← TimeTest.TenArray[ten];
	StopPrecisionTiming;
	IF tp.checkResults AND ten#array10 THEN SIGNAL ParamsDisagree;
	ENDLOOP;
      ENDLOOP;
    StopTest;

    StartTest["40 word array procedure"L];
    THROUGH [0..tp.trials) DO
      StampTest;
      FOR test: INTEGER IN [0..tp.iterationsPerTrial) DO
	DelayTest;
	StartPrecisionTiming;
	array40 ← TimeTest.FortyArray[forty];
	StopPrecisionTiming;
	IF tp.checkResults AND forty#array40 THEN SIGNAL ParamsDisagree;
	ENDLOOP;
      ENDLOOP;
    StopTest;

    StartTest["100 word array procedure"L];
    THROUGH [0..tp.trials) DO
      StampTest;
      FOR test: INTEGER IN [0..tp.iterationsPerTrial) DO
	DelayTest;
	StartPrecisionTiming;
	array100 ← TimeTest.HundredArray[hundred];
	StopPrecisionTiming;
	IF tp.checkResults AND hundred#array100 THEN SIGNAL ParamsDisagree;
	ENDLOOP;
      ENDLOOP;
    StopTest;

    END;  -- SmallArrayTests.



--  Primary time logging routines that use Pilot's timer.

  StartTest: PROC [testName: String, skipSpy: BOOLEAN←FALSE] =
    BEGIN OPEN TTY;
    CheckAbort;
    PutCR[user]; PutCR[user];
    PutString[user, "Timing "L]; PutDecimal[user, tp.trials];
    PutString[user, " trials of "L];
    PutString[user, testName]; TimeStamp[" at "L];
    PreparePrecisionTimings;
    IF ~skipSpy AND tp.spying THEN CallSpy[startSpy];
    StartTimer[reset:TRUE];
    END;
    
  StampTest: PROC = INLINE {StopTimer;  DoStampTest;  StartTimer};

  DoStampTest: PROC =
    BEGIN OPEN TTY;
    PutString[user, "Doing "L];
    PutLongDecimal[user, tp.iterationsPerTrial];
    TimeStamp[" calls at "L];
    END;

  DelayTest: PROC =
    INLINE BEGIN
    StopTimer;
    CheckAbort;
    Delay[1000];
    StartTimer;
    END;

  StopTest: PROC [skipSpy: BOOLEAN←FALSE]  =
    BEGIN OPEN TTY;
    ms, tenths: LONG INTEGER;
    StopTimer[];
    IF ~skipSpy AND tp.spying THEN CallSpy[stopSpy];
    CheckAbort;
    [ms, tenths] ←
      ConvertTimer[ReadTimer[].elapsed/(tp.trials*LONG[tp.iterationsPerTrial])];
    PutString[user, "The average call time (any checking included) was "L];
    PutLongDecimal[user, ms]; PutChar[user, '.];
    PutLongDecimal[user, tenths]; TimeStamp[" ms at "L];
    PrintPrecisionTimings;
    IF ~skipSpy AND tp.spying AND tp.showSpyData=afterEachTest
      THEN CallSpy[displayStats];
    END;

  TimeStamp: PROC [herald: String] =
    BEGIN OPEN TTY;
    timeString: STRING = [100];
    CheckAbort;
    Time.Append[timeString, Time.Unpack[Time.Current[]]];
    PutString[user, herald];
    PutString[user, timeString];
    PutLine[user, "."L];
    END;

  AbortTest: PRIVATE ERROR = CODE;
    -- Raised by CheckAbort and caught in TestRoutine.

  CheckAbort: PROC  =
    BEGIN OPEN TTY;
    IF ~UserAbort[] THEN RETURN;
    ResetUserAbort;
    PutCR[user];
    PutLine[user, "Test aborted..."L];
    ERROR AbortTest;
    END;


-- Pilot Timing Routines.

  SystemTime: TYPE = LONG CARDINAL;  -- Same as System.Pulses.
  Microseconds: TYPE = LONG CARDINAL;

  ReadPilotClock: PROC RETURNS [SystemTime] =
    INLINE {RETURN[ LOOPHOLE[System.GetClockPulses[]] ]};

  PilotPrecisionRead: PROC RETURNS [--SystemTime-- PrecisionTime] =
    INLINE { RETURN[ ReadPilotClock[] ] };

  PilotPrecisionConvert: PROC [pt: --SystemTime-- PrecisionTime]
      RETURNS [--microseconds:-- Microseconds] =
    ----INLINE---- {RETURN[ SystemTimeToUsec[pt] ]};


  hrStartTime, hrEventTime, hrElapsedTime: SystemTime ← 0;

  StartTimer: PROC [reset: BOOLEAN←FALSE] = 
    INLINE BEGIN
    IF reset THEN hrElapsedTime ← 0;
    hrStartTime ← ReadPilotClock[];
    END;

  StopTimer: PROC =
    INLINE BEGIN
    hrEventTime ← ReadPilotClock[] - hrStartTime;
    hrElapsedTime ← hrElapsedTime + hrEventTime;
    END;

  ReadTimer: PROC RETURNS [event, elapsed: SystemTime] =
    ----INLINE---- BEGIN
    RETURN[hrEventTime, hrElapsedTime]
    END;

  ConvertTimer: PROC [time: SystemTime] RETURNS [ms,tenthsOfMs: LONG INTEGER] =
    BEGIN
    time ← (SystemTimeToUsec[time]+50)/100;
    ms ← time/10;
    tenthsOfMs ← time MOD 10;
    END;

  SystemTimeToUsec: PROC [time: SystemTime] RETURNS [Microseconds] =
    INLINE BEGIN
    RETURN[ System.PulsesToMicroseconds[System.Pulses[time]] ]
    END;


  lastDelayFinished: SystemTime ← 0;

  Delay: PROC [interval: --Microseconds-- LONG INTEGER] =
    -- Spin at least "interval" between executions.  There is a startup glitch.
    BEGIN
    LastIntegerTime: SystemTime = LAST[LONG INTEGER];
    DO
      grossWait: SystemTime =
        SystemTimeToUsec[ReadPilotClock[]-lastDelayFinished];
      waitTime: LONG INTEGER =
        IF grossWait < LastIntegerTime THEN grossWait ELSE LastIntegerTime;
      SELECT interval-waitTime FROM
	< 0    => EXIT;
	< 50  => NULL;
	ENDCASE => Process.Yield;
      ENDLOOP;
    lastDelayFinished ← ReadPilotClock[];
    END;


--  Individual call high precision timing routines.

  PrecisionTime: TYPE = LONG CARDINAL;

  RingBufferIndex: TYPE = INTEGER[0..StandardIterationsPerTrial);
  SortBufferIndex: TYPE = INTEGER[0..LAST[RingBufferIndex]+2);
  RingBuffer: TYPE = ARRAY RingBufferIndex OF PrecisionTime;
  SortBuffer: TYPE = ARRAY SortBufferIndex OF PrecisionTime;

  ptRingIndex: RingBufferIndex ← LAST[RingBufferIndex];
  ptRingBuffer: LONG DESCRIPTOR FOR RingBuffer ← DESCRIPTOR [NIL, 0];
  ptSortBuffer: LONG DESCRIPTOR FOR SortBuffer ← DESCRIPTOR [NIL, 0];


  ptStartTime: PrecisionTime;

  StartPrecisionTiming: PROC  =
    INLINE BEGIN
    ptStartTime ← PrecisionRead[];
    END;

  StopPrecisionTiming: PROC  =
    INLINE BEGIN
    ptStopTime: PrecisionTime = PrecisionRead[];
    ptRingBuffer[(ptRingIndex ← (ptRingIndex+1) MOD (LAST[RingBufferIndex]+1))]
	  ← (ptStopTime - ptStartTime);
    END;

  PrecisionRead: PROC RETURNS [--pt:-- PrecisionTime] =
    INLINE BEGIN
    RETURN[IF tp.useDoradoClock
	     THEN DoradoPrecisionRead[] ELSE PilotPrecisionRead[] ];
    END;

  PrecisionConvert: PROC [pt: PrecisionTime]
      RETURNS [--microseconds:-- Microseconds] =
    ----INLINE---- BEGIN
    RETURN[IF tp.useDoradoClock
	     THEN DoradoPrecisionConvert[pt] ELSE PilotPrecisionConvert[pt] ];
    END;


--  Precision timing routines.

  InitPrecisionTimings: PROC =
    BEGIN
    -- A compiler LENGTH computation bug makes this ugly:
    bufferPtr: LONG POINTER TO SortBuffer ← Heap.systemZone.NEW[SortBuffer];
    ptSortBuffer ← DESCRIPTOR[bufferPtr, LENGTH[bufferPtr↑]];
    ptRingBuffer ← DESCRIPTOR [ 
      @ptSortBuffer[FIRST[SortBufferIndex]+1],
      LENGTH[LOOPHOLE[NIL, POINTER TO RingBuffer]↑] ];
    END;

  FinishPrecisionTimings: PROC =
    BEGIN
    bufferPtr: LONG POINTER TO SortBuffer ← BASE[ptSortBuffer];
    Heap.systemZone.FREE[@bufferPtr];
    ptSortBuffer ← NIL;
    ptRingBuffer ← NIL;
    END;

  PreparePrecisionTimings: PROC =
    BEGIN
    IF BASE[ptRingBuffer] = NIL THEN ERROR;
    ptRingIndex ← LAST[RingBufferIndex];
    FOR i: RingBufferIndex IN RingBufferIndex DO ptRingBuffer[i] ← 0 ENDLOOP;
    IF tp.useDoradoClock THEN StartDoradoCounters[EmuCycles];
    END;

  PrintPrecisionTimings: PROC =
    BEGIN OPEN TTY;
    Range: INTEGER = 10;
    PrintRange: PROC [herald: String, startIndex: RingBufferIndex]  =
      BEGIN
      PutString[user, herald];
      FOR i: RingBufferIndex IN [startIndex..startIndex+Range) DO
	  IF ptRingBuffer[i] > 99999
	    THEN PutString[user, " *****"L]
	    ELSE PutLongNumber[ h: user, n: ptRingBuffer[i],
	      format: [base: 10, zerofill: FALSE, unsigned: TRUE, columns: 6] ];
	  ENDLOOP;
      PutCR[user];
      END;  -- PrintRange.
    FOR i: RingBufferIndex IN RingBufferIndex DO
      ptRingBuffer[i] ← PrecisionConvert[ptRingBuffer[i]];
      ENDLOOP;
    QuickSort[ptSortBuffer];
    PutString[user, "Some instantaneous call times (microseconds) from a "L];
    PutDecimal[user, LAST[RingBufferIndex]-FIRST[RingBufferIndex]+1];
    PutLine[user, " element ring buffer:"L];
      PrintRange["  Fastest times:"L, FIRST[RingBufferIndex]];
      PrintRange["  Median times: "L,
	  (LAST[RingBufferIndex]-FIRST[RingBufferIndex]-Range)/2];
      PrintRange["  Slowest times:"L, LAST[RingBufferIndex]-Range];
    END;

  
  -- Dorado 64ns precision counter routines.
  -- Modified from [Ivy]<McDaniel>Measurements>Measure.mesa.

  doradoCounters: CounterVector ← ZeroCounterVector;

  DoradoPrecisionRead: PROC RETURNS [PrecisionTime] =
    INLINE BEGIN
    ReadDoradoCounters[@doradoCounters];
    RETURN[IF tp.countOnlyEmulatorCycles
	     THEN doradoCounters.A.low ELSE doradoCounters.B.low ];
    END;

  DoradoPrecisionConvert: PROC [pt: PrecisionTime]
      RETURNS [--microseconds:-- Microseconds] =
    ----INLINE---- BEGIN
    -- Measured clock time was 2*32.2 ns => 644/10000 = 161/2500 usec/cycle
    -- Compute slowly to avoid overflow and do rounding.
    RETURN[
	  (LONG[161]*(pt/LONG[2500]))
	+ ( LONG[161]*(pt MOD LONG[2500])+LONG[2500/2] )/LONG[2500] ];
    END;

  StartDoradoCounters: PROCEDURE [CounterDescriptor] =
    MACHINE CODE BEGIN Mopcodes.zMISC, 240B END;

  ReadDoradoCounters: PROCEDURE [CounterVectorPtr] =
    MACHINE CODE BEGIN Mopcodes.zMISC, 241B END;

  StopDoradoCounters: PROCEDURE =
    MACHINE CODE BEGIN Mopcodes.zMISC, 242B END;

  CounterVector: TYPE = MACHINE DEPENDENT RECORD [
    A(0): MACHINE DEPENDENT RECORD [low(0): LONG CARDINAL, high(2): CARDINAL],
    B(3): MACHINE DEPENDENT RECORD [low(0): LONG CARDINAL, high(2): CARDINAL] ];

  ZeroCounterVector: CounterVector = [[0,0],[0,0]];

  CounterVectorPtr: TYPE = LONG POINTER TO CounterVector;

  ASelection: TYPE = MACHINE DEPENDENT {
    true, hold, procRef, ifuJump, miss, bpa, bpc, bpe };

  BSelection: TYPE = MACHINE DEPENDENT {
    true, hold, ifuRef, ifuNrdy, miss, bpb, bpc, bpd };

  allA, allB, doA, doB: BOOLEAN = TRUE;
  emuOnlyA, emuOnlyB, notIS: BOOLEAN = FALSE;

  CounterDescriptor: TYPE = MACHINE DEPENDENT RECORD  [
    instrSet: BOOLEAN,
    ignoreBits123: [0..7],
    enableA, enableB: BOOLEAN,
    ignoreBits67: [0..3],
    aSelects: ASelection,
    allTasksA: BOOLEAN,
    bSelects: BSelection,
    allTasksB: BOOLEAN  ];

  EmuCycles: CounterDescriptor = [notIS,0,doA,doB,0,true,emuOnlyA,true,allB];


-- This Quicksort routine is from Sedgewick's thesis.
-- It is a transliteration of [Maxc2]<Guibas>STSRT.SAI.

  QuickSort: PROC [table: LONG DESCRIPTOR FOR ARRAY OF PrecisionTime]  =
    BEGIN
    first: INTEGER = 0;
    last: INTEGER = LENGTH[table]-1;
    M: INTEGER = 9;
    PP, L, R, I, J: INTEGER;
    V, TK: PrecisionTime;
    STACK: ARRAY [0..50) OF INTEGER;
    --Originally: STACK: ARRAY [0:2*(LOG(256/(M+2)) DIV 1)+1] OF INTEGER;

    table[first] ← FIRST[PrecisionTime];  table[last] ← LAST[PrecisionTime];
    PP ← 0; L ← first+1; R ← last-1;

    WHILE PP >=0 DO
      I ← L; J ← R+1; V ← table[L];
      WHILE I < J DO
	 I←I+1; WHILE table[I]<V DO I ← I+1 ENDLOOP;
	 J←J-1; WHILE table[J]>V DO J ← J-1 ENDLOOP;
	 TK←table[J]; table[J]←table[I]; table[I]←TK;
	 ENDLOOP;
      table[I]←table[J]; table[J]←table[L]; table[L]←TK;
      SELECT TRUE FROM
	 R-J > J-L =>
	  SELECT TRUE FROM
	     R-J <= M => NULL;
	     J-L <= M => {L←J+1; LOOP};
	     ENDCASE => BEGIN
	       PP←PP+2; STACK[PP]←J+1; STACK[PP+1]←R; R←J-1;
	       LOOP;
	       END;
	 J-L <= M => NULL;
	 R-J <= M => {R←J-1; LOOP};
	 ENDCASE => BEGIN
	   PP ← PP+2; STACK[PP]←L; STACK[PP+1]←J-1; L←J+1;
	   LOOP;
	   END;
      L←STACK[PP];
      R←STACK[PP+1];
      PP←PP-2;
      REPEAT
	FINISHED =>
	  FOR I IN [2..last) DO
	    V←table[I]; J←I-1;
	    WHILE table[J]>V DO table[J+1]←table[J]; J←J-1 ENDLOOP;
	    table[J+1]←V;
	    ENDLOOP;
      ENDLOOP;
    END;


-- General spy routine, in the hope that the real procedural interface improves.

  CallSpy: PROCEDURE[operation: {
			start, startAndWatchDetails,
			startSpy, stopSpy, displayStats,
			stop, stopAndDisplayStats } ] =
    BEGIN
    SELECT operation FROM
      start =>
	Runtime.CallDebugger["Start Spy..."L];
      startAndWatchDetails =>
	Runtime.CallDebugger["Start Spy and finger specific procs..."L];
      startSpy =>
	SpyClient.StartSpy;
      stopSpy =>
	SpyClient.StopSpy;
      displayStats =>
	Runtime.CallDebugger["Examine Spy statistics..."L];
      stop =>
	Runtime.CallDebugger["Stop Spy..."L];
      stopAndDisplayStats =>
	Runtime.CallDebugger["Stop Spy and examine statistics..."L];
      ENDCASE => ERROR;
    END;


-- Module Initialization.

  Process.Detach[FORK TestProcess[]];

END.  -- LupineTimeTestDriver.



  BigArrayTests: PROC =
    BEGIN

    fourHundred: TimeTest.Array400 = ALL[400];
    thousand: TimeTest.Array1000 = ALL[1000];

    array400: TimeTest.Array400;
    array1000: TimeTest.Array1000;

    StartTest["400 word array procedure"L];
    THROUGH [0..tp.trials) DO
      StampTest;
      FOR test: INTEGER IN [0..tp.iterationsPerTrial) DO
	DelayTest;
	StartPrecisionTiming;
	array400 ← TimeTest.FourHundredArray[fourHundred];
	StopPrecisionTiming;
	IF tp.checkResults AND fourHundred#array400 THEN SIGNAL ParamsDisagree;
	ENDLOOP;
      ENDLOOP;
    StopTest;

    StartTest["1000 word array procedure"L];
    THROUGH [0..tp.trials) DO
      StampTest;
      FOR test: INTEGER IN [0..tp.iterationsPerTrial) DO
	DelayTest;
	StartPrecisionTiming;
	array1000 ← TimeTest.ThousandArray[thousand];
	StopPrecisionTiming;
	IF tp.checkResults AND thousand#array1000 THEN SIGNAL ParamsDisagree;
	ENDLOOP;
      ENDLOOP;
    StopTest;

    END;  -- BigArrayTests.



    -- String and array descriptor test.

    Alphabet: TYPE = PACKED ARRAY [0..26) OF CHARACTER;
    string1: String = "AbCdEfGhIjKlMnOpQrStUvWxYz"L;
    string2: String = "12345678901234567890123456"L;
    text: Alphabet;
    textDescriptor: TimeTest.RESULTTextDescriptor = DESCRIPTOR[text];

    StartTest["26 character string to sequence procedure"L];
    THROUGH [0..tp.trials) DO
      StampTest;
      FOR test: INTEGER IN [0..tp.iterationsPerTrial) DO
	string: String = IF test MOD 2 = 0 THEN string1 ELSE string2;
	DelayTest;
	StartPrecisionTiming;
	TimeTest.StringDescriptor[string, textDescriptor];
	StopPrecisionTiming;
	IF tp.checkResults AND
		LOOPHOLE[@string.text, LONG POINTER TO Alphabet]↑
	      # LOOPHOLE[BASE[textDescriptor], LONG POINTER TO Alphabet]↑
	  THEN SIGNAL ParamsDisagree;
	ENDLOOP;
      ENDLOOP;
    StopTest;