DIRECTORY BasicTime USING [GetClockPulses, Now, Pulses, PulsesToSeconds], Buttons USING [Button, Create, ReLabel, SetDisplayStyle], Containers USING [ChildXBound, ChildYBound, Create], Convert USING [Base, CardFromDecimalLiteral, CardFromHexLiteral, CardFromOctalLiteral, Error, IntFromRope], DragOpsCross USING [Byte, FourBytes, Inst, OnesWord, ProcessorRegister, Word, ZerosWord], DragOpsCrossUtils USING [AddDelta, BytePCToWordAddress, IntToWord, WordToCard, WordToInt], HandCodingSupport USING [Area, GetCurrentArea], HandCodingUtil USING [GetInstArray, GetRegArray, NameArray, RegNameArray, ToStream], InputFocus USING [], IO USING [Close, EndOfStream, Error, GetCedarTokenRope, GetIndex, PutF, PutFR, PutRope, RIS, RopeFromROS, ROS, STREAM, TokenKind], IOUtils USING [CopyPFProcs, PFCodeProc, PFProcs, SetPFCodeProc, SetPFProcs], Labels USING [Create, Label, Set], LizardCache USING [CacheBase, FlushCache, NewBase, NewCache, SharedBase], LizardHeart USING [ChangeLogger, ChangeLoggerRep, Control, InstDoneProc, InstructionExecute, InstStartProc, IOChangeProc, MemChangeProc, NewProcessor, OutsideEnvelope, Processor, ProcessorStats, RegChangeProc], LizardToolOutput, Menus USING [AppendMenuEntry, CreateEntry, CreateMenu, Menu, MenuProc], MessageWindow USING [Append, Blink], NumberLabels USING [CreateNumber, NumberLabel, NumberLabelUpdate], Process USING [Detach, MsecToTicks, priorityBackground, SetPriority, SetTimeout], Real USING [Round], Rope USING [Equal, Fetch, ROPE], SparseMemory USING [Base, Fetch], TypeScript USING [ChangeLooks, Create], VFonts USING [], ViewerClasses USING [Viewer], ViewerIO USING [CreateViewerStreams], ViewerOps USING [CreateViewer, OpenIcon], ViewerTools USING [GetContents, GetSelectionContents, SetContents], VTables USING [Create, Install, SetRowsAndColumns, SetTableEntry, VTable], WriteSparseMemory USING [TestAbort, ToStream]; LizardToolDriver: CEDAR MONITOR LOCKS data USING data: ToolData IMPORTS BasicTime, Buttons, Containers, Convert, DragOpsCrossUtils, HandCodingSupport, IO, IOUtils, Labels, LizardCache, LizardHeart, LizardToolOutput, HandCodingUtil, Menus, MessageWindow, NumberLabels, Process, Real, Rope, SparseMemory, TypeScript, ViewerIO, ViewerOps, ViewerTools, VTables, WriteSparseMemory = BEGIN Area: TYPE = HandCodingSupport.Area; Base: TYPE = SparseMemory.Base; Button: TYPE = Buttons.Button; Byte: TYPE = DragOpsCross.Byte; ChangeLogger: TYPE = LizardHeart.ChangeLogger; ChangeLoggerRep: TYPE = LizardHeart.ChangeLoggerRep; Control: TYPE = LizardHeart.Control; Inst: TYPE = DragOpsCross.Inst; Label: TYPE = Labels.Label; NumberLabel: TYPE = NumberLabels.NumberLabel; Processor: TYPE = LizardHeart.Processor; ProcessorRegister: TYPE = DragOpsCross.ProcessorRegister; ProcessorStats: TYPE = LizardHeart.ProcessorStats; ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; TestAbort: TYPE = WriteSparseMemory.TestAbort; Viewer: TYPE = ViewerClasses.Viewer; VTable: TYPE = VTables.VTable; Word: TYPE = DragOpsCross.Word; ZerosWord: Word = DragOpsCross.ZerosWord; OnesWord: Word = DragOpsCross.OnesWord; ToolData: TYPE = REF ToolDataRep; ToolDataRep: TYPE = MONITORED RECORD [ refreshMillis: CARDINAL _ 1000, processors: ProcessorSequence _ NIL, area: Area _ NIL, logger: ChangeLogger _ NIL, logTS: Viewer _ NIL, log: STREAM _ NIL, traceButton, baseButton: Buttons.Button _ NIL, vTab: VTable _ NIL, pauseCondText: Viewer _ NIL, pauseCondTable: PauseCondTable _ NIL, statusLabel: Label _ NIL, refresh: CONDITION, controlChange: CONDITION, internalControl: InternalControl _ pause, initPulses: BasicTime.Pulses _ BasicTime.GetClockPulses[], dumpStart: INT _ 0, dumpWords: INT _ LAST[INT], live: BOOL _ FALSE, requestReset, requestReschedule: BOOL _ FALSE, requestDump: DumpRequest _ none, requestBiasStats: BOOL _ FALSE, stopPrint: BOOL _ FALSE, tracing: BOOL _ FALSE, useHex: BOOL _ TRUE, -- will be inverted at setup time to Octal instTable: InstCountTable _ NIL, lastInstTable: LastInstTable _ NIL, newStats, baseStats: ToolStats _ [], biasStats: LizardToolOutput.StatSnapshot ]; ToolStats: TYPE = RECORD [ samplePulses: BasicTime.Pulses _ 0, pStats: ProcessorStats _ [] ]; ProcessorSequence: TYPE = REF ProcessorSequenceRep; ProcessorSequenceRep: TYPE = RECORD [ SEQUENCE num: NAT OF Processor ]; InternalControl: TYPE = {run, step, pause, stop}; DumpRequest: TYPE = {none, words, bytes, regs, stats, ring}; PauseCondTable: TYPE = REF PauseCondTableRep; PauseCondTableRep: TYPE = RECORD [ instArray: PACKED ARRAY Inst OF BOOL _ ALL[FALSE], pcRanges: LIST OF PCRange _ NIL, instCountStop: LIST OF INT _ NIL ]; InstCountTable: TYPE = REF InstCountTableRep; InstCountTableRep: TYPE = LizardToolOutput.InstCountTableRep; PCRange: TYPE = RECORD [firstPC,lastPC: LONG CARDINAL]; LastInstTable: TYPE = REF LastInstTableRep; LastInstTableRep: TYPE = LizardToolOutput.LastInstTableRep; LastInstMod: NAT = LizardToolOutput.LastInstMod; instCacheLines: NAT _ 64; dataCacheLines: NAT _ 64; SampleStats: ENTRY PROC [data: ToolData] RETURNS [ToolStats] = { RETURN [data.newStats]; }; UpdateStats: ENTRY PROC [data: ToolData, stats: ToolStats, updateTime: BOOL _ TRUE] = { IF updateTime THEN stats.samplePulses _ BasicTime.GetClockPulses[]; data.newStats _ stats; }; SetBaseState: ENTRY PROC [data: ToolData] = { data.baseStats _ data.newStats; data.baseStats.samplePulses _ BasicTime.GetClockPulses[]; }; GetDeltaFromBase: ENTRY PROC [data: ToolData] RETURNS [stats: ToolStats] = { stats.pStats.instructions _ data.newStats.pStats.instructions - data.baseStats.pStats.instructions; stats.pStats.cycles _ data.newStats.pStats.cycles - data.baseStats.pStats.cycles; stats.pStats.euFetches _ data.newStats.pStats.euFetches - data.baseStats.pStats.euFetches; stats.pStats.euStores _ data.newStats.pStats.euStores - data.baseStats.pStats.euStores; stats.pStats.instBytes _ data.newStats.pStats.instBytes - data.baseStats.pStats.instBytes; stats.samplePulses _ data.newStats.samplePulses - data.baseStats.samplePulses; }; Start: ENTRY PROC [data: ToolData] = { data.newStats _ [samplePulses: BasicTime.GetClockPulses[]]; data.live _ TRUE; BROADCAST data.refresh; }; Stop: ENTRY PROC [data: ToolData] = { vTab: VTables.VTable _ data.vTab; IF vTab # NIL THEN {data.vTab _ NIL; VTables.SetRowsAndColumns[vTab, 0, 0]}; IF data.log # NIL THEN { IO.Close[data.log ! IO.Error => CONTINUE]; data.log _ NIL; data.logTS _ NIL}; data.processors _ NIL; data.vTab _ NIL; data.pauseCondText _ NIL; data.pauseCondTable _ NIL; data.traceButton _ NIL; data.baseButton _ NIL; data.statusLabel _ NIL; data.live _ FALSE; data.instTable _ NIL; }; SetInternalControl: ENTRY PROC [data: ToolData, internalControl: InternalControl] = TRUSTED { data.internalControl _ internalControl; BROADCAST data.controlChange; }; WaitForControlChange: ENTRY PROC [data: ToolData] RETURNS [InternalControl] = TRUSTED { Process.SetTimeout[@data.controlChange, Process.MsecToTicks[data.refreshMillis]]; WAIT data.controlChange; RETURN [data.internalControl]; }; WaitForRefresh: ENTRY PROC [data: ToolData] = TRUSTED { Process.SetTimeout[@data.refresh, Process.MsecToTicks[data.refreshMillis]]; WAIT data.refresh; }; CauseRefresh: ENTRY PROC [data: ToolData] = TRUSTED { BROADCAST data.refresh; }; InvertTracing: ENTRY PROC [data: ToolData] = TRUSTED { data.tracing _ NOT data.tracing; Buttons.SetDisplayStyle[data.traceButton, IF data.tracing THEN $WhiteOnBlack ELSE $BlackOnWhite]; BROADCAST data.refresh; BROADCAST data.controlChange; }; InvertOutputBase: ENTRY PROC [data: ToolData] = TRUSTED { useHex: BOOL _ data.useHex _ NOT data.useHex; SetOutputBaseW[data.log, useHex]; Buttons.ReLabel[data.baseButton, IF useHex THEN " Hex " ELSE "Octal"]; BROADCAST data.refresh; BROADCAST data.controlChange; }; SetOutputBaseW: PROC [st: STREAM, useHex: BOOL] = TRUSTED { pfProcs: IOUtils.PFProcs = IOUtils.CopyPFProcs[st]; [] _ IOUtils.SetPFCodeProc[ pfProcs, 'w, IF useHex THEN CodeWHexProc ELSE CodeWOctalProc]; [] _ IOUtils.SetPFProcs[st, pfProcs]; }; CodeWHexProc: IOUtils.PFCodeProc = { IO.PutF[stream, "%xH", val]; }; CodeWOctalProc: IOUtils.PFCodeProc = { IO.PutF[stream, "%bB", val]; }; StopMenuProc: Menus.MenuProc = TRUSTED { SetInternalControl[NARROW[clientData], stop]; }; PauseMenuProc: Menus.MenuProc = TRUSTED { SetInternalControl[NARROW[clientData], pause]; }; RunMenuProc: Menus.MenuProc = TRUSTED { td: ToolData = NARROW[clientData]; IF shift AND td.tracing THEN InvertTracing[td]; SetInternalControl[td, run]; }; StepMenuProc: Menus.MenuProc = TRUSTED { SetInternalControl[NARROW[clientData], step]; }; ResetMenuProc: Menus.MenuProc = TRUSTED { td: ToolData = NARROW[clientData]; td.requestReset _ TRUE; }; ReschedMenuProc: Menus.MenuProc = TRUSTED { td: ToolData = NARROW[clientData]; td.requestReschedule _ TRUE; }; StopPrintMenuProc: Menus.MenuProc = TRUSTED { td: ToolData = NARROW[clientData]; td.stopPrint _ TRUE; }; DumpMemMenuProc: Menus.MenuProc = TRUSTED { td: ToolData = NARROW[clientData]; IF shift THEN td.requestDump _ bytes ELSE td.requestDump _ words; }; DumpRegsMenuProc: Menus.MenuProc = TRUSTED { td: ToolData = NARROW[clientData]; td.requestDump _ regs; }; DumpStatsMenuProc: Menus.MenuProc = TRUSTED { td: ToolData = NARROW[clientData]; td.requestDump _ stats; }; DumpRingMenuProc: Menus.MenuProc = TRUSTED { td: ToolData = NARROW[clientData]; td.requestDump _ ring; }; BiasStatsMenuProc: Menus.MenuProc = TRUSTED { td: ToolData = NARROW[clientData]; td.requestBiasStats _ TRUE; }; SamplePauseCondProc: Menus.MenuProc = TRUSTED { ShowPauseCond[NARROW[clientData]] }; SetPauseCondProc: Menus.MenuProc = TRUSTED { td: ToolData = NARROW[clientData]; newPauseData: PauseCondTable _ NEW[PauseCondTableRep _ []]; oldPauseData: PauseCondTable = td.pauseCondTable; st: STREAM = IO.RIS[ViewerTools.GetContents[td.pauseCondText]]; buffer: REF TEXT _ NEW[TEXT[64]]; -- buffer for scanning opNameTable: HandCodingUtil.NameArray = HandCodingUtil.GetInstArray[]; startPC: LONG CARDINAL; haveStart: BOOL _ FALSE; haveDots: BOOL _ FALSE; instCount: BOOL _ FALSE; IF shift AND oldPauseData # NIL THEN newPauseData^ _ oldPauseData^; DO tokenKind: IO.TokenKind; token: ROPE _ NIL; [tokenKind: tokenKind, token: token] _ IO.GetCedarTokenRope[st ! IO.EndOfStream => {tokenKind _ tokenEOF; CONTINUE}; IO.Error => {tokenKind _ tokenERROR; CONTINUE}]; {endPC: LONG CARDINAL _ startPC; SELECT tokenKind FROM tokenID => { IF haveDots THEN GO TO oops; FOR inst: Inst IN Inst DO IF Rope.Equal[token, opNameTable[inst]] THEN { newPauseData.instArray[inst] _ TRUE; EXIT; }; ENDLOOP; IF NOT haveStart THEN LOOP; -- No lagging PC to enter }; tokenSINGLE => { IF Rope.Fetch[token, 0] = '# THEN instCount _ TRUE; LOOP; }; tokenDOUBLE => { IF haveDots OR NOT haveStart THEN GO TO oops; IF NOT Rope.Equal[token, ".."] THEN GO TO oops; haveDots _ TRUE; LOOP; }; tokenDECIMAL, tokenOCTAL, tokenHEX => { ENABLE Convert.Error => GO TO oops; thisPC: LONG CARDINAL _ 0; SELECT tokenKind FROM tokenDECIMAL => thisPC _ Convert.CardFromDecimalLiteral[token]; tokenOCTAL => thisPC _ Convert.CardFromOctalLiteral[token]; tokenHEX => thisPC _ Convert.CardFromHexLiteral[token]; ENDCASE => ERROR; SELECT TRUE FROM instCount => { newPauseData.instCountStop _ CONS[thisPC, newPauseData.instCountStop]; instCount _ FALSE; LOOP; }; haveDots => { endPC _ thisPC; }; ENDCASE => { IF haveStart THEN { endPC _ startPC; newPauseData.pcRanges _ CONS[[startPC, endPC], newPauseData.pcRanges]; }; haveStart _ TRUE; startPC _ thisPC; LOOP; }; }; tokenEOF => { IF NOT haveStart THEN EXIT; IF haveDots THEN GO TO oops; haveStart _ FALSE; }; ENDCASE => GO TO oops; newPauseData.pcRanges _ CONS[[startPC, endPC], newPauseData.pcRanges]; haveStart _ haveDots _ instCount _ FALSE; EXITS oops => { MessageWindow.Append[ IO.PutFR["near pos %g, scanning error for pause condition", [integer[IO.GetIndex[st]]]], TRUE]; MessageWindow.Blink[]; RETURN; }; }; ENDLOOP; td.pauseCondTable _ newPauseData; ShowPauseCond[td]; }; ShowPauseCond: PROC [td: ToolData] = { oldPauseData: PauseCondTable = td.pauseCondTable; ros: STREAM = IO.ROS[]; opNameTable: HandCodingUtil.NameArray = HandCodingUtil.GetInstArray[]; SetOutputBaseW[ros, td.useHex]; IF oldPauseData # NIL THEN { FOR inst: Inst IN Inst DO IF oldPauseData.instArray[inst] THEN { IO.PutRope[ros, opNameTable[inst]]; IO.PutRope[ros, " "]; }; ENDLOOP; FOR each: LIST OF PCRange _ oldPauseData.pcRanges, each.rest WHILE each # NIL DO pcr: PCRange _ each.first; IF pcr.firstPC # pcr.lastPC THEN IO.PutF[ros, "%w..%w ", [cardinal[pcr.firstPC]], [cardinal[pcr.lastPC]] ] ELSE IO.PutF[ros, "%w ", [cardinal[pcr.firstPC]]]; ENDLOOP; FOR each: LIST OF INT _ oldPauseData.instCountStop, each.rest WHILE each # NIL DO IO.PutF[ros, "#%g ", [cardinal[each.first]]]; ENDLOOP; }; ViewerTools.SetContents[td.pauseCondText, IO.RopeFromROS[ros]]; }; TraceButtonProc: Menus.MenuProc = TRUSTED { InvertTracing[NARROW[clientData]]; }; BaseButtonProc: Menus.MenuProc = TRUSTED { InvertOutputBase[NARROW[clientData]]; }; SetDumpStartButtonProc: Menus.MenuProc = TRUSTED { data: ToolData = NARROW[clientData]; data.dumpStart _ NumberFromSelection[data.dumpStart]; }; SetDumpWordsButtonProc: Menus.MenuProc = TRUSTED { data: ToolData = NARROW[clientData]; data.dumpWords _ NumberFromSelection[data.dumpWords]; }; NumberFromSelection: PROC [default: INT] RETURNS [INT] = { selection: ROPE = ViewerTools.GetSelectionContents[]; RETURN [Convert.IntFromRope[selection ! Convert.Error => CONTINUE]]; MessageWindow.Append["Selection not a number!", TRUE]; MessageWindow.Blink[]; RETURN [default]; }; LogRegChange: LizardHeart.RegChangeProc = { td: ToolData = NARROW[data]; IF td.tracing THEN { log: STREAM = td.log; IO.PutRope[log, " "]; IO.PutRope[log, HandCodingUtil.GetRegArray[][reg]]; PutAsBoth[log, ", old: %w (%d)", old]; PutAsBoth[log, ", new: %w (%d)\n", new]; }; }; LogMemChange: LizardHeart.MemChangeProc = { td: ToolData = NARROW[data]; IF td.tracing THEN { log: STREAM = td.log; PutAsCard[log, " word addr: %w", addr]; PutAsBoth[log, ", old: %w (%g)", old]; PutAsBoth[log, ", new: %w (%g)\n", new]; }; }; LogIOChange: LizardHeart.IOChangeProc = { td: ToolData = NARROW[data]; IF td.tracing THEN { log: STREAM = td.log; PutAsCard[log, " IO addr: %w", addr]; PutAsBoth[log, ", old: %w (%g)", old]; PutAsBoth[log, ", new: %w (%g)\n", new]; }; }; LogInstStart: LizardHeart.InstStartProc = { td: ToolData = NARROW[data]; thisOne: LONG CARDINAL = DragOpsCrossUtils.WordToCard[thisPC]; instTable: InstCountTable = td.instTable; last: [0..LastInstMod) _ td.lastInstTable.last; td.lastInstTable.last _ last _ IF last = LastInstMod-1 THEN 0 ELSE last + 1; instTable[inst] _ instTable[inst] + 1; td.lastInstTable.pcArray[last] _ thisPC; {pauseTable: PauseCondTable = td.pauseCondTable; IF pauseTable # NIL THEN { IF pauseTable.instArray[inst] THEN GO TO setPause; FOR each: LIST OF PCRange _ pauseTable.pcRanges, each.rest WHILE each # NIL DO pcr: PCRange _ each.first; IF thisOne IN [pcr.firstPC..pcr.lastPC] THEN GO TO setPause; ENDLOOP; FOR each: LIST OF INT _ pauseTable.instCountStop, each.rest WHILE each # NIL DO IF processor.stats.instructions = each.first THEN GO TO setPause; ENDLOOP; }; SELECT inst FROM x000b, LAST[Inst] => GO TO setPause; ENDCASE; EXITS setPause => { IF NOT td.tracing THEN InvertTracing[td]; SetInternalControl[td, pause]; }; }; IF td.tracing THEN { IO.PutF[td.log, "Inst %g start, cycle: %g, pc: %w, opcode: %w", [integer[processor.stats.instructions]], [integer[processor.stats.cycles]], [cardinal[thisOne]], [cardinal[LOOPHOLE[inst, CARDINAL]]] ]; IF processor.userMode THEN IO.PutRope[td.log, " (user"] ELSE IO.PutRope[td.log, " (kernel"]; IF processor.trapsEnabled THEN IO.PutRope[td.log, ", traps enabled"] ELSE IO.PutRope[td.log, ", traps disabled"]; IF processor.rescheduleRequested THEN IO.PutRope[td.log, ", reschedule pending"]; IO.PutRope[td.log, ")\n "]; HandCodingUtil.ToStream[td.log, inst, rest, thisPC]; IO.PutRope[td.log, "\n"]; }; }; LogInstDone: LizardHeart.InstDoneProc = { td: ToolData = NARROW[data]; td.newStats.pStats _ processor.stats; td.newStats.samplePulses _ BasicTime.GetClockPulses[]; IF td.tracing THEN { log: STREAM = td.log; ctrl: ROPE = SELECT control FROM nextInst => "nextInst", doCall => "doCall", doReturn => "doReturn", doSwitch => "doSwitch", doAbort => "doAbort", ENDCASE => "??"; PutAsCard[log, " Inst done. newPC: %w", newPC]; PutAsCard[log, ", rtnPC: %w", rtnPC]; PutAsCard[log, ", regS: %w", processor.regs[ifuS]]; PutAsCard[log, ", regL: %w", processor.regs[ifuL]]; IO.PutF[log, ", control: %g\n\n", [rope[ctrl]] ]; }; }; GetInstAndRest: PROC [mem: SparseMemory.Base, pc: Word] RETURNS [inst: Inst, rest: Word] = { lastAddr: Word _ ZerosWord; bytes: DragOpsCross.FourBytes _ LOOPHOLE[ZerosWord]; instLen: NAT; word: Word; FetchByte: PROC [offset: NAT] RETURNS [Byte] = { wordAddr: Word; byteIndex: [0..3]; [wordAddr, byteIndex] _ DragOpsCrossUtils.BytePCToWordAddress[[DragOpsCrossUtils.AddDelta[offset, pc]]]; IF lastAddr # wordAddr THEN word _ SparseMemory.Fetch[mem, lastAddr _ wordAddr]; RETURN [LOOPHOLE[word, DragOpsCross.FourBytes][byteIndex]]; }; inst _ LOOPHOLE[FetchByte[0]]; instLen _ LOOPHOLE[inst, CARDINAL] / 64; IF instLen = 0 THEN instLen _ IF LOOPHOLE[inst, CARDINAL] < 40B THEN 1 ELSE 5; SELECT instLen FROM 2 => bytes[3] _ FetchByte[1]; 3 => { bytes[2] _ FetchByte[1]; bytes[3] _ FetchByte[2]; }; 5 => { bytes[0] _ FetchByte[1]; bytes[1] _ FetchByte[2]; bytes[2] _ FetchByte[3]; bytes[3] _ FetchByte[4]; }; ENDCASE; RETURN [inst, LOOPHOLE[bytes]]; }; PutAsBoth: PROC [log: STREAM, format: ROPE, word: Word] = { IO.PutF[log, format, [cardinal[DragOpsCrossUtils.WordToCard[word]]], [integer[DragOpsCrossUtils.WordToInt[word]]]]; }; PutAsCard: PROC [log: STREAM, format: ROPE, word: Word] = { IO.PutF[log, format, [cardinal[DragOpsCrossUtils.WordToCard[word]]]]; }; StandardNumberLabel: PROC [parent: Viewer, chars: NAT] RETURNS [NumberLabel] = { RETURN [NumberLabels.CreateNumber[ info: [parent: parent, border: FALSE], chars: chars, paint: FALSE]]; }; LizardToolWatcher: PROC [data: ToolData] = { NewMenuEntry: PROC [name: ROPE, proc: Menus.MenuProc, line: INTEGER _ 0] = { Menus.AppendMenuEntry[ menu, Menus.CreateEntry[name: name, proc: proc, clientData: data], line]; }; StandardButton: PROC [name: ROPE, proc: Menus.MenuProc, x,y: INTEGER, w: INTEGER _ 0] RETURNS [button: Buttons.Button] = { button _ Buttons.Create[ info: [parent: topViewer, name: name, border: TRUE, wx: x, wy: y, ww: w], proc: proc, clientData: data, paint: FALSE]; nextX _ button.wx+button.ww-1; nextY _ button.wy+button.wh-1; }; nextX: INTEGER _ 1; nextY: INTEGER _ 1; menu: Menus.Menu = Menus.CreateMenu[2]; topViewer: Viewer = Containers.Create[ info: [name: "LizardTool", iconic: TRUE, column: left, scrollable: FALSE], paint: FALSE]; traceButton: Buttons.Button _ data.traceButton _ StandardButton[ " Trace ", TraceButtonProc, 1, 1]; baseButton: Buttons.Button _ data.baseButton _ StandardButton[ " Octal ", BaseButtonProc, nextX, 1]; setDumpStartButton: Buttons.Button = StandardButton[ " Set Dump Start ", SetDumpStartButtonProc, nextX, 1]; setDumpWordsButton: Buttons.Button = StandardButton[ " Set Dump Words ", SetDumpWordsButtonProc, nextX, 1]; setPauseCondButton: Button = StandardButton[ " SetPauseCond ", SetPauseCondProc, 1, nextY]; samplePauseCondButton: Button = StandardButton[ " Sample ", SamplePauseCondProc, 1, nextY, setPauseCondButton.ww]; pauseCondText: Viewer = ViewerOps.CreateViewer[ flavor: $Text, info: [ wx: nextX, wy: setPauseCondButton.wy, ww: setPauseCondButton.ww, wh: nextY+1 - setPauseCondButton.wy, parent: topViewer], paint: FALSE]; vTab: VTable = VTables.Create[columns: 10, rows: 3, parent: topViewer, x: 1, y: nextY]; statusLabel: Label _ data.statusLabel _ Labels.Create[ info: [name: "Initializing", parent: vTab, border: FALSE], paint: FALSE]; instCountLabel: NumberLabel = StandardNumberLabel[vTab, 10]; instRateLabel: NumberLabel = StandardNumberLabel[vTab, 7]; cycleCountLabel: NumberLabel = StandardNumberLabel[vTab, 10]; cycleRateLabel: NumberLabel = StandardNumberLabel[vTab, 7]; iBytesCountLabel: NumberLabel = StandardNumberLabel[vTab, 10]; iBytesRateLabel: NumberLabel = StandardNumberLabel[vTab, 7]; readCountLabel: NumberLabel = StandardNumberLabel[vTab, 10]; readRateLabel: NumberLabel = StandardNumberLabel[vTab, 7]; writeCountLabel: NumberLabel = StandardNumberLabel[vTab, 10]; writeRateLabel: NumberLabel = StandardNumberLabel[vTab, 7]; DoRefresh: PROC [data: ToolData] = { newStats: ToolStats = SampleStats[data]; deltaStats: ToolStats = GetDeltaFromBase[data]; IF deltaStats.samplePulses # 0 THEN { secs: REAL = BasicTime.PulsesToSeconds[deltaStats.samplePulses]; invSecs: REAL = 1/secs; IF deltaStats.pStats.instructions # 0 THEN { NumberLabels.NumberLabelUpdate[instCountLabel, newStats.pStats.instructions]; NumberLabels.NumberLabelUpdate[instRateLabel, Real.Round[(deltaStats.pStats.instructions) * invSecs]]; }; IF deltaStats.pStats.cycles # 0 THEN { NumberLabels.NumberLabelUpdate[cycleCountLabel, newStats.pStats.cycles]; NumberLabels.NumberLabelUpdate[cycleRateLabel, Real.Round[(deltaStats.pStats.cycles) * invSecs]]; }; IF deltaStats.pStats.instBytes # 0 THEN { NumberLabels.NumberLabelUpdate[iBytesCountLabel, newStats.pStats.instBytes]; NumberLabels.NumberLabelUpdate[iBytesRateLabel, Real.Round[(deltaStats.pStats.instBytes) * invSecs]]; }; IF deltaStats.pStats.euFetches # 0 THEN { NumberLabels.NumberLabelUpdate[readCountLabel, newStats.pStats.euFetches]; NumberLabels.NumberLabelUpdate[readRateLabel, Real.Round[(deltaStats.pStats.euFetches) * invSecs]]; }; IF deltaStats.pStats.euStores # 0 THEN { NumberLabels.NumberLabelUpdate[writeCountLabel, newStats.pStats.euStores]; NumberLabels.NumberLabelUpdate[writeRateLabel, Real.Round[(deltaStats.pStats.euStores) * invSecs]]; }; }; }; topViewer.menu _ menu; NewMenuEntry["Stop!", StopMenuProc]; NewMenuEntry["Pause", PauseMenuProc]; NewMenuEntry["Run", RunMenuProc]; NewMenuEntry["Step", StepMenuProc]; NewMenuEntry["Reset", ResetMenuProc]; NewMenuEntry["Resched", ReschedMenuProc]; NewMenuEntry["StopPrint!", StopPrintMenuProc]; NewMenuEntry["DumpMem", DumpMemMenuProc, 1]; NewMenuEntry["DumpRegs", DumpRegsMenuProc, 1]; NewMenuEntry["DumpStats", DumpStatsMenuProc, 1]; NewMenuEntry["DumpRing", DumpRingMenuProc, 1]; NewMenuEntry["BiasStats", BiasStatsMenuProc, 1]; VTables.SetTableEntry[ table: vTab, row: 0, column: 0, flavor: $Viewer, clientData: statusLabel]; VTables.SetTableEntry[table: vTab, row: 1, column: 0, name: "count"]; VTables.SetTableEntry[table: vTab, row: 2, column: 0, name: "rate"]; VTables.SetTableEntry[table: vTab, row: 0, column: 1, name: "inst"]; VTables.SetTableEntry[ table: vTab, row: 1, column: 1, flavor: $Viewer, clientData: instCountLabel]; VTables.SetTableEntry[ table: vTab, row: 2, column: 1, flavor: $Viewer, clientData: instRateLabel]; VTables.SetTableEntry[table: vTab, row: 0, column: 2, name: "cycle"]; VTables.SetTableEntry[ table: vTab, row: 1, column: 2, flavor: $Viewer, clientData: cycleCountLabel]; VTables.SetTableEntry[ table: vTab, row: 2, column: 2, flavor: $Viewer, clientData: cycleRateLabel]; VTables.SetTableEntry[table: vTab, row: 0, column: 3, name: "instBytes"]; VTables.SetTableEntry[ table: vTab, row: 1, column: 3, flavor: $Viewer, clientData: iBytesCountLabel]; VTables.SetTableEntry[ table: vTab, row: 2, column: 3, flavor: $Viewer, clientData: iBytesRateLabel]; VTables.SetTableEntry[table: vTab, row: 0, column: 4, name: "read"]; VTables.SetTableEntry[ table: vTab, row: 1, column: 4, flavor: $Viewer, clientData: readCountLabel]; VTables.SetTableEntry[ table: vTab, row: 2, column: 4, flavor: $Viewer, clientData: readRateLabel]; VTables.SetTableEntry[table: vTab, row: 0, column: 5, name: "write"]; VTables.SetTableEntry[ table: vTab, row: 1, column: 5, flavor: $Viewer, clientData: writeCountLabel]; VTables.SetTableEntry[ table: vTab, row: 2, column: 5, flavor: $Viewer, clientData: writeRateLabel]; VTables.Install[table: vTab, paint: FALSE]; data.logTS _ TypeScript.Create[ info: [parent: topViewer, wx: vTab.wx, wy: vTab.wy+vTab.wh, border: FALSE], paint: FALSE]; TypeScript.ChangeLooks[data.logTS, 'f]; -- fixed width font for alignment TypeScript.ChangeLooks[data.logTS, 's]; -- small for compactness data.vTab _ vTab; data.pauseCondText _ pauseCondText; data.log _ ViewerIO.CreateViewerStreams[ name: "LizardTool.log", viewer: data.logTS, backingFile: "LizardTool.log", editedStream: FALSE].out; Containers.ChildXBound[container: topViewer, child: data.logTS]; Containers.ChildYBound[container: topViewer, child: data.logTS]; Containers.ChildXBound[container: topViewer, child: pauseCondText]; InvertOutputBase[data]; -- forces the initial base to be Octal ViewerOps.OpenIcon[topViewer]; Start[data]; WHILE data.live DO DoRefresh[data]; WaitForRefresh[data]; IF NOT data.live OR topViewer.destroyed THEN EXIT; ENDLOOP; }; ExecBase: PROC [nProcessors: NAT, area: HandCodingSupport.Area] = { data: ToolData = NEW[ToolDataRep]; processors: ProcessorSequence = NEW[ProcessorSequenceRep[nProcessors]]; logger: ChangeLogger = NEW[ChangeLoggerRep _ [ data, LogRegChange, LogMemChange, LogIOChange, LogInstStart, LogInstDone]]; cycles: INT _ 0; processor: Processor _ NIL; lagControl: InternalControl _ run; statusLabel: Label _ NIL; mem: SparseMemory.Base = NARROW[area.data]; shared: LizardCache.SharedBase = LizardCache.NewBase[mem]; instTable: InstCountTable _ NEW[InstCountTableRep _ ALL[0]]; logger.data _ data; data.instTable _ instTable; data.lastInstTable _ NEW[LastInstTableRep]; data.lastInstTable.last _ 0; data.lastInstTable.pcArray _ ALL[ZerosWord]; FOR i: NAT IN [0..nProcessors) DO ifuCache: LizardCache.CacheBase = LizardCache.NewCache[shared, instCacheLines]; euCache: LizardCache.CacheBase = LizardCache.NewCache[shared, dataCacheLines]; processors[i] _ LizardHeart.NewProcessor[ifuCache, euCache, logger]; ENDLOOP; TRUSTED {Process.Detach[FORK LizardToolWatcher[data]]}; UNTIL data.live DO WaitForRefresh[data]; ENDLOOP; statusLabel _ data.statusLabel; IO.PutF[data.log, "\nStarting interpreter at %g\n\n", [time[BasicTime.Now[]]] ]; Labels.Set[statusLabel, "Pausing"]; processor _ processors[0]; Process.SetPriority[Process.priorityBackground]; WHILE data.live AND NOT data.vTab.destroyed DO internalControl: InternalControl _ data.internalControl; SELECT TRUE FROM data.requestReset => { processor.resetRequested _ TRUE; IO.PutRope[data.log, "Note: reset requested by user.\n\n"]; data.requestReset _ FALSE; UpdateStats[data, SampleStats[data]]; LOOP; }; data.requestReschedule => { IO.PutRope[data.log, "Note: reschedule requested by user.\n\n"]; processor.rescheduleRequested _ FALSE; LOOP; }; data.requestDump # none => { ServiceDumpRequest[data, processor, mem, data.requestDump]; data.requestDump _ none; LOOP; }; data.requestBiasStats => { ServiceBiasStatsRequest[data, processor]; data.requestBiasStats _ FALSE; LOOP; }; ENDCASE; IF internalControl # lagControl THEN { SELECT internalControl FROM run => Labels.Set[statusLabel, "Running"]; step => Labels.Set[statusLabel, "Stepping"]; pause => Labels.Set[statusLabel, "Pausing"]; stop => Labels.Set[statusLabel, "Stopping"]; ENDCASE => ERROR; lagControl _ internalControl; SetBaseState[data]; CauseRefresh[data]; }; SELECT internalControl FROM run, step => { }; pause => { UpdateStats[data, SampleStats[data]]; [] _ WaitForControlChange[data]; LOOP}; stop => EXIT; ENDCASE => ERROR; LizardHeart.InstructionExecute[processor ! LizardHeart.OutsideEnvelope => { data.log.PutF["Warning: In instruction %g processor outside defined envelope -\n %g\n\n", [integer[processor.stats.instructions]], [rope[explanation]]]; internalControl _ step; RESUME } ]; IF internalControl = step THEN { SetInternalControl[data, pause]; CauseRefresh[data]; }; ENDLOOP; IO.PutF[data.log, "\nStopping interpreter at %g\n\n", [time[BasicTime.Now[]]] ! IO.Error => CONTINUE]; Stop[data]; IF NOT statusLabel.destroyed THEN Labels.Set[statusLabel, "Stopped"]; }; ServiceDumpRequest: PROC [data: ToolData, processor: Processor, mem: SparseMemory.Base, kind: DumpRequest] = { regNameArray: HandCodingUtil.RegNameArray = HandCodingUtil.GetRegArray[]; instNameArray: HandCodingUtil.NameArray = HandCodingUtil.GetInstArray[]; log: STREAM = data.log; testAbort: WriteSparseMemory.TestAbort = {RETURN [data.stopPrint]}; data.stopPrint _ FALSE; IO.PutRope[data.log, "Note: start of dump requested by user.\n"]; SELECT kind FROM words, bytes => { useBytes: BOOL = kind = bytes; IO.PutRope[log, "\n"]; LizardCache.FlushCache[processor.euCache]; WriteSparseMemory.ToStream[ data.log, mem, DragOpsCrossUtils.IntToWord[data.dumpStart], data.dumpWords, useBytes, useBytes, testAbort]; }; regs => LizardToolOutput.ShowRegisters[log, processor, testAbort]; ring => LizardToolOutput.ShowRing[log, data.lastInstTable, mem, LastInstMod, testAbort]; stats => LizardToolOutput.ShowStats[log, processor, data.instTable, testAbort]; ENDCASE; IO.PutRope[data.log, "\nNote: end of dump requested by user.\n\n"]; data.stopPrint _ FALSE; }; ServiceBiasStatsRequest: PROC [data: ToolData, processor: LizardHeart.Processor] = { testAbort: WriteSparseMemory.TestAbort = {RETURN [data.stopPrint]}; newBias: LizardToolOutput.StatSnapshot _ [pStats: processor.stats, discarded: processor.instBuffer.bytesDiscarded, -- not in stats!!! forcedEmpty: processor.instBuffer.forcedEmpty, -- not in stats!!! ifuStats: processor.ifuCache.stats, euStats: processor.euCache.stats, iStats: data.instTable^]; LizardToolOutput.ShowBiasedStats[data.log, data.biasStats, newBias]; data.biasStats _ newBias; IO.PutRope[data.log, "\nNote: Statistics biased.\n\n"]; }; Exec: PROC [nProcessors: NAT _ 1] = TRUSTED { area: HandCodingSupport.Area = HandCodingSupport.GetCurrentArea[]; Process.Detach[FORK ExecBase[nProcessors, area]]; }; Exec1: PROC = { Exec[1]; }; Exec2: PROC = { Exec[2]; }; Exec3: PROC = { Exec[3]; }; Exec4: PROC = { Exec[4]; }; END. nLizardToolDriver.mesa Copyright Ó 1984, 1985, 1986, 1987 by Xerox Corporation. All rights reserved. Ed McCreight, January 8, 1986 12:09:58 pm PST Russ Atkinson (RRA) March 19, 1987 10:57:57 am PST Peter Kessler November 11, 1985 3:09:29 pm PST ENTRY procs to manipulate the tool state atomically Call this proc to stop execution for good. We go around storing NIL to assist the garbage collector. Menu procs to respond to mouse clicks [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] if shift was held, add to old stuff This should be an instruction name. Of course we may have a pending startPC as well. Look up the instruction name and set the appropriate bits We must have a startPC to accept this token We must have a startPC to accept this token This should be the start of a PC range When we get this far we have a new PC range to add [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] Change logging procs [data: REF, processor: Processor, reg: ProcessorRegister, old,new: Word] [data: REF, processor: Processor, addr: Word, old,new: Word] [data: REF, processor: Processor, addr: Word, old,new: Word] [data: REF, processor: Processor, thisPC: Word, inst: Inst, rest: Word] Check for instructions that must pause Check the PC ranges Check the instruction count [data: REF, processor: Processor, newPC, rtnPC: Word, control: Control, cycles: INT] Utility procs A slow but simple routine to fetch instructions and the rest given the PC. Main procs Now we are off and running! Wait for the watcher to start up For now, we only support one processor When we are really cranking along in the instruction stream we don't want to hog the machine. Cedar does not really handle competing processes at the same priority level as well as we would like. Now start interpreting instructions until told to stop (currently we only support one processor). Show the new status to make the memory agree with what is in the cache Dump the last N instructions Dump the current statistics Snapshot the statistics display the statistics save the bias The following procedures are for the convenience of the Quad command. Ê#1˜codešœ™KšœN™NKšœ-™-K™2K™.—˜šÏk ˜ Kšœ œ0˜?Kšœœ,˜9Kšœ œ$˜4Kšœœ^˜kKšœ œG˜YKšœœC˜ZKšœœ˜/Kšœœ@˜TKšœ œ˜Kš œœPœœœ ˜‚Kšœœ?˜LKšœœ˜"Kšœ œ8˜IKšœ œÁ˜ÒK˜Kšœœ<˜GKšœœ˜$Kšœ œ0˜BKšœœD˜QKšœœ ˜Kšœœœ˜ Kšœ œ˜!Kšœ œ˜'Kšœœ˜Kšœœ ˜Kšœ œ˜%Kšœ œ˜)Kšœ œ2˜CKšœœ=˜JKšœœ˜.——headšœœ˜Kšœœ˜KšœPœÞ˜·Kšœ˜K˜Kšœœ˜$Kšœœ˜Kšœœ˜Kšœœ˜Kšœœ˜.Kšœœ˜4Kšœ œ˜$Kšœœ˜Kšœœ˜Kšœ œ˜-Kšœ œ˜(Kšœœ"˜9Kšœœ˜2Kšœœœ˜Kšœœœœ˜Kšœ œ˜.Kšœœ˜$Kšœœ˜šœœ˜Kšœ)˜)Kšœ'˜'—K˜Kšœ œœ ˜!šœ œ œœ˜&Kšœœ˜Kšœ œ˜$Kšœ œ˜Kšœœ˜Kšœœ˜Kšœœœ˜Kšœ*œ˜.Kšœœ˜Kšœœ˜Kšœ!œ˜%Kšœœ˜Kšœ œ˜Kšœ œ˜Kšœ)˜)K˜:Kšœ œ˜Kšœ œœœ˜Kšœœœ˜Kšœ!œœ˜.Kšœ ˜ Kšœœœ˜Kšœ œœ˜Kšœ œœ˜KšœœœÏc*˜@Kšœœ˜ Kšœœ˜#K˜$K˜(K˜K˜—šœ œœ˜Kšœ#˜#Kšœ˜K˜K˜—Kšœœœ˜3šœœœ˜%Kšœœœ ˜K˜K˜—Kšœœ˜1K˜Kšœ œ+˜—Kšœ%˜%K˜K˜—šŸ œ˜$Kšœ˜K˜K˜—šŸœ˜&Kšœ˜K˜K˜——šœ%™%K˜šŸ œœ˜(Kšœ œœ,œ™NKšœœ˜-K˜K˜—šŸ œœ˜)Kšœ œœ,œ™NKšœœ˜.K˜K˜—šŸ œœ˜'Kšœ œœ,œ™NKšœœ ˜"Kšœœ œ˜/Kšœ˜K˜K˜—šŸ œœ˜(Kšœ œœ,œ™NKšœœ˜-K˜K˜—šŸ œœ˜)Kšœ œœ,œ™NKšœœ ˜"Kšœœ˜K˜K˜—šŸœœ˜+Kšœ œœ,œ™NKšœœ ˜"Kšœœ˜K˜K˜—šŸœœ˜-Kšœ œœ,œ™NKšœœ ˜"Kšœœ˜K˜K˜—šŸœœ˜+Kšœ œœ,œ™NKšœœ ˜"Kšœœœ˜AK˜K˜—šŸœœ˜,Kšœ œœ,œ™NKšœœ ˜"Kšœ˜K˜K˜—šŸœœ˜-Kšœ œœ,œ™NKšœœ ˜"Kšœ˜K˜K˜—šŸœœ˜,Kšœ œœ,œ™NKšœœ ˜"Kšœ˜K˜K˜—šŸœœ˜-Kšœ œœ,œ™NKšœœ ˜"Kšœœ˜K˜K˜—šŸœœ˜/Kšœ œœ,œ™NKšœœ ˜!K˜K˜—šŸœœ˜,Kšœ œœ,œ™NKšœœ ˜"Kšœœ˜;Kšœ1˜1Kšœœœœ,˜?Kš œœœœœž˜9KšœF˜FKšœ œœ˜Kšœ œœ˜Kšœ œœ˜Kšœ œœ˜K˜šœœœœ˜CKšœ#™#—K˜š˜Kšœ œ ˜Kšœœœ˜šœ$˜$šœœ˜šœœ'œ˜5Kšœ#œ˜0———šœœœ ˜ šœ ˜˜ KšœU™UKšœ œœœ˜K˜Kšœ9™9šœ œ˜šœ&œ˜.Kšœœ˜$Kšœ˜K˜—Kšœ˜K˜—Kš œœ œœž˜6K˜—šœ˜Kšœ+™+Kšœœ œ˜3Kšœ˜K˜—šœ˜Kšœ+™+Kš œ œœ œœœ˜-Kš œœœœœ˜/Kšœ œ˜Kšœ˜K˜—šœ'˜'Kšœ&™&Kšœœœ˜#Kšœœœ˜šœ ˜Kšœ?˜?Kšœ;˜;Kšœ7˜7Kšœœ˜—šœœ˜šœ˜Kšœœ%˜FKšœ œ˜Kšœ˜K˜—šœ ˜ Kšœ˜K˜—šœ˜ šœ œ˜Kšœ˜Kšœœ*˜FK˜—Kšœ œ˜Kšœ˜Kšœ˜K˜——K˜—šœ ˜ Kšœœ œœ˜Kšœ œœœ˜Kšœ œ˜Kšœ˜—Kšœœœ˜—K˜Kšœ2™2Kšœœ*˜FKšœ#œ˜)K˜šœ ˜šœ˜šœ9˜;Kšœ œ˜—Kšœ˜—K˜Kšœ˜K˜—K˜—Kšœ˜—K˜Kšœ!˜!Kšœ˜K˜K˜—šŸ œœ˜&Kšœ1˜1Kšœœœœ˜KšœF˜FKšœ˜K˜šœœœ˜šœ œ˜šœœ˜&Kšœ!˜#Kšœ˜K˜—Kšœ˜—š œœœ,œœ˜PKšœ˜šœ˜KšœœG˜NKšœœ+˜2—Kšœ˜—š œœœœ)œœ˜QKšœ+˜-Kšœ˜—K˜—Kšœ*œ˜?K˜K˜—šŸœœ˜+Kšœ œœ,œ™NKšœœ˜"K˜K˜—šŸœœ˜*Kšœ œœ,œ™NKšœœ˜%K˜K˜—šŸœœ˜2Kšœ œœ,œ™NKšœœ ˜$Kšœ5˜5K˜K˜—šŸœœ˜2Kšœ œœ,œ™NKšœœ ˜$Kšœ5˜5K˜K˜—š Ÿœœ œœœ˜:Kšœ œ&˜5Kšœ3œ˜DKšœ0œ˜6K˜Kšœ ˜K˜K˜——šœ™K˜šŸ œ˜+Kšœœ>™HKšœœ˜šœ œ˜Kšœœ ˜Kšœ˜Kšœ1˜3Kšœ&˜&Kšœ(˜(K˜—K˜K˜—šŸ œ˜+Kšœœ2™Kšœ)˜)Kšœ/˜/Kšœœœœ ˜LK˜Kšœ&˜&Kšœ(˜(K˜šœ0˜0K˜šœœœ˜K˜Kšœ&™&Kšœœœœ ˜2K˜Kšœ™š œœœ*œœ˜NKšœ˜Kš œ œœœœ ˜˜>Kšœ%˜%—šœ4˜4Kšœ6˜6—šœ4˜4Kšœ6˜6—šœ,˜,Kšœ.˜.—šœ/˜/KšœB˜B—šœ/˜/Kšœ˜šœ˜Kšœ ˜ Kšœ˜Kšœ˜Kšœ$˜$Kšœ˜—Kšœœ˜—KšœW˜Wšœ6˜6Kšœ3œ˜:Kšœœ˜—Kšœ<˜˜>Kšœ<˜˜@Kšœ œ˜&Kšœ˜Kšœ˜—šœ˜Kšœ;˜;Kšœ˜Kšœ˜Kšœ˜—šœ˜Kšœ)˜)Kšœœ˜Kšœ˜Kšœ˜—K˜Kšœ˜—K˜šœœ˜&Kšœ™šœ˜Kšœ*˜*Kšœ,˜,Kšœ,˜,Kšœ,˜,Kšœœ˜—Kšœ˜Kšœ˜Kšœ˜K˜—šœ˜šœ˜Kšœ˜—šœ ˜ Kšœ%˜%Kšœ ˜ Kšœ˜—Kšœœ˜ Kšœœ˜—šœ*˜*šœ ˜ šœ[˜[Kšœ>˜>—Kšœ˜Kš˜Kšœ˜—šœ˜K˜——šœœ˜ Kšœ ˜ Kšœ˜K˜K˜—Kšœ˜—K˜šœK˜MKšœœ œ˜—K˜ Kšœœœ$˜EK˜K˜—šŸœœV˜nKšœI˜IKšœH˜HKšœœ ˜Kšœ*œ˜CKšœœ˜Kšœ?˜Ašœ˜šœ˜Kšœ œ˜Kšœ˜šœ*˜*Kšœ2™2—šœ˜Kšœ;˜;Kšœ/˜/—K˜—˜Kšœ:˜:—˜Kšœ™KšœP˜P—šœ˜Kšœ™KšœF˜F—Kšœ˜—KšœA˜CKšœœ˜K˜—K˜šŸœœ7˜TKšŸ œ!œ˜CK™Kšœsžœ/žœ_˜§K™K˜DK™ Kšœ˜Kšœ5˜7K˜K˜—K˜—šœE™EK˜šŸœœœœ˜-KšœB˜BKšœœ˜1K˜K˜—šŸœœ˜Kšœ˜K˜—K˜šŸœœ˜Kšœ˜K˜—K˜šŸœœ˜Kšœ˜K˜—K˜šŸœœ˜Kšœ˜K˜——K˜Kšœ˜K˜—…—tð¥