DIRECTORY Basics USING [BITSHIFT, LongNumber], MesaRuntimeInit USING [StateVectorCounts], PrincOps USING [ Condition, ConditionVariable, ControlLink, FaultIndex, Frame, FrameHandle, FrameSizeIndex, InterruptItem, NoTimeout, NullFrame, NullLink, NullPsbHandle, NullStateVectorHandle, PDA, PDABase, Priority, ProcessDataArea, ProcessState, ProcessStateBlock, PsbHandle, PsbIndex, PsbLink, PsbNull, Queue, SD, sFork, sJoin, sProcessTrap, StartPsb, StateVector, StateVectorHandle, Ticks], Process USING [ DisableTimeout, EnableAborts, Milliseconds, Priority, priorityFaultHandlers, SetPriority, SetTimeout, Ticks], ProcessExtras USING [], PrincOpsUtils USING [ Alloc, Broadcast, COPY, DisableInterrupts, EnableAndRequeue, EnableInterrupts, Enter, Exit, FrameSize, Free, GetReturnFrame, MyLocalFrame, Notify, PsbHandleToIndex, PsbIndexToHandle, ReadPSB, ReEnter, Requeue, Wait, WritePSB, WriteWDC]; ProcessImpl: MONITOR [ pdaWords: CARDINAL, svCounts: MesaRuntimeInit.StateVectorCounts, wordsPerSV: CARDINAL, reservedNakedNotifyMask: WORD, millisecondsPerTick: CARDINAL] LOCKS processLock IMPORTS Basics, PrincOpsUtils, Process EXPORTS MesaRuntimeInit, PrincOpsUtils, Process, ProcessExtras = BEGIN OPEN PrincOps; FsiFrame: TYPE = MACHINE DEPENDENT RECORD [ fsi (0): FrameSizeIndex, -- must be at 3 MOD 4 boundary. frame (1): local Frame]; NakedNotifyLevel: TYPE = [0..--VM.bitsPerWord--16); DyingFrameHandle: TYPE = POINTER TO dying Frame; processLock: MONITORLOCK; rebirth: CONDITION; -- queue of unused processes. frameReady: CONDITION; -- queue of parents waiting to rejoin their child. frameTaken: CONDITION; -- queue of children waiting to rejoin their parent. dead: CONDITION; -- queue of parents waiting for their child to clean up. deadFrame: DyingFrameHandle _ NIL; -- the top-level frame of a detached process which needs to be freed. busyLevels: PACKED ARRAY NakedNotifyLevel OF BOOL; MsecToTicks: PUBLIC SAFE PROC [ms: Process.Milliseconds] RETURNS [Process.Ticks] = TRUSTED { RETURN[ (IF ms >= Process.Milliseconds.LAST-(millisecondsPerTick-1) THEN Process.Milliseconds.LAST -- (avoid overflow) ELSE ms + millisecondsPerTick-1) / millisecondsPerTick ] }; SecondsToTicks: PUBLIC SAFE PROC [sec: CARDINAL] RETURNS [Process.Ticks] = TRUSTED { ticks: Basics.LongNumber = [lc[(sec.LONG*1000 + millisecondsPerTick-1) / millisecondsPerTick]]; RETURN[IF ticks.highbits ~= 0 THEN Ticks.LAST ELSE ticks.lowbits] }; TicksToMsec: PUBLIC SAFE PROC [ticks: Ticks] RETURNS [Process.Milliseconds] = TRUSTED { RETURN[ IF ticks > Process.Milliseconds.LAST/millisecondsPerTick THEN Process.Milliseconds.LAST ELSE ticks*millisecondsPerTick ] }; Detach: PUBLIC ENTRY PROCEDURE [process: PROCESS] = BEGIN h: PsbHandle; IF (h _ ValidProcess[process]) = NullPsbHandle THEN RETURN WITH ERROR InvalidProcess[LOOPHOLE[process, UNSPECIFIED]]; PDA[h].flags.processState.detached _ TRUE; BROADCAST frameTaken; -- wake child if waiting to JOIN. END; Abort: PUBLIC ENTRY PROCEDURE [process: PsbIndex] = { h: PsbHandle; IF (h _ ValidProcess[process]) = NullPsbHandle THEN RETURN WITH ERROR InvalidProcess[process]; IF h = PrincOpsUtils.ReadPSB[] THEN { PDA[h].flags.abort _ FALSE; RETURN WITH ERROR ABORTED } ELSE { PrincOpsUtils.DisableInterrupts[]; -- (also stops ProcessTimeoutCounter from ticking.) IF ProcState[h].state = alive THEN { PDA[h].flags.abort _ TRUE; IF PDA[h].flags.waiting THEN { PDA[h].flags.waiting _ FALSE; PDA[PDA.timeout][PrincOpsUtils.PsbHandleToIndex[h]] _ NoTimeout; PDA[h].--timeout--mds _ NoTimeout; PrincOpsUtils.Requeue[NIL, @PDA.ready, h]; }; }; PrincOpsUtils.EnableInterrupts[]; }; }; CheckForAbort: PUBLIC ENTRY SAFE PROCEDURE = TRUSTED { h: PsbHandle = PrincOpsUtils.ReadPSB[]; IF PDA[h].flags.abort THEN { PDA[h].flags.abort _ FALSE; RETURN WITH ERROR ABORTED }; }; Pause: PUBLIC ENTRY SAFE PROC [ticks: Process.Ticks] = TRUSTED { ENABLE ABORTED => GO TO Aborted; c: CONDITION; Process.SetTimeout[@c, ticks]; Process.EnableAborts[@c]; WAIT c; EXITS Aborted => RETURN WITH ERROR ABORTED; }; SetTimeSlice: PUBLIC ENTRY SAFE PROC [ticks: Process.Ticks] = TRUSTED { IF pSchedulingInterval = NIL THEN RETURN; pSchedulingInterval^.timeout _ ticks; NOTIFY pSchedulingInterval^; }; ValidateProcess: PUBLIC ENTRY PROC [p: PsbIndex] = { IF ValidProcess[p] = NullPsbHandle THEN RETURN WITH ERROR InvalidProcess[p]; }; InvalidProcess: PUBLIC ERROR [process: PsbIndex] = CODE; AllocateNakedCondition: PUBLIC ENTRY PROC RETURNS [cv: LONG POINTER TO CONDITION, mask: WORD] = BEGIN FOR level: NakedNotifyLevel IN NakedNotifyLevel DO IF ~busyLevels[level] THEN { busyLevels[level] _ TRUE; RETURN[ cv: LOOPHOLE[@PDA.interrupt[level]], mask: Basics.BITSHIFT[1, NakedNotifyLevel.LAST - level] ]; }; ENDLOOP; ERROR END; DeallocateNakedCondition: PUBLIC ENTRY PROC [cv: LONG POINTER TO CONDITION] = { FOR level: NakedNotifyLevel IN NakedNotifyLevel DO IF busyLevels[level] AND cv = LOOPHOLE[@PDA.interrupt[level], LONG POINTER TO CONDITION] THEN { busyLevels[level] _ FALSE; PDA.interrupt[level].condition.tail _ PsbNull; RETURN; }; ENDLOOP; ERROR; }; ProcState: PROC [psbh: PsbHandle] RETURNS [ProcessState] = INLINE { RETURN[PDA[psbh].flags.processState] }; ProcessTrap: PROC RETURNS [BOOL] = BEGIN LongEnter: PROC [a,b: --LONG POINTER TO MONITORLOCK--UNSPECIFIED] RETURNS [BOOLEAN] = LOOPHOLE[PrincOpsUtils.Enter]; abortee: FrameHandle; state: RECORD [filler: UNSPECIFIED, v: StateVector]; state.v _ STATE; UNTIL (IF state.v.stkptr = 4 THEN LongEnter[state.v.stk[0], state.v.stk[1]] ELSE PrincOpsUtils.Enter[LOOPHOLE[state.v.stk[0], POINTER TO MONITORLOCK]]) DO ENDLOOP; abortee _ PrincOpsUtils.GetReturnFrame[]; abortee.pc _ [abortee.pc + 1]; -- step past ME(L) instruction. PDA[PrincOpsUtils.ReadPSB[]].flags.abort _ FALSE; ERROR ABORTED; END; ValidProcess: INTERNAL PROC [p: --PsbIndex--UNSPECIFIED] RETURNS [h: PsbHandle] = INLINE { RETURN[ IF ~(LOOPHOLE[p, PsbIndex] IN [StartPsb..StartPsb+PDA.count)) OR ProcState[h _ PrincOpsUtils.PsbIndexToHandle[LOOPHOLE[p, PsbIndex]]].state IN [frameTaken..dead] THEN NullPsbHandle ELSE h ] }; Fork: PROC [--argsForRoot,-- root: ControlLink] RETURNS [childPsb: PsbIndex] = { PForkFrame: TYPE = POINTER TO FRAME[Fork]; ChildBuilder: PROC [--parent's locals--] RETURNS [--MUST BE NULL!--] = { pChild: LONG POINTER TO ProcessStateBlock _ @PDA.block[childPsb]; parentFrame: PForkFrame = LOOPHOLE[PrincOpsUtils.GetReturnFrame[]]; fsi: FrameSizeIndex = LOOPHOLE[parentFrame-1, POINTER TO FsiFrame].fsi; childFrame: PForkFrame = PrincOpsUtils.Alloc[fsi]; PrincOpsUtils.COPY[ from: parentFrame, to: childFrame, nwords: PrincOpsUtils.FrameSize[fsi]]; childFrame.identity _ child; pChild.link.failed _ FALSE; pChild.link.priority _ PDA[PrincOpsUtils.ReadPSB[]].link.priority; pChild.flags _ [ processState: [state: alive, detached: FALSE], cleanup: PsbNull, waiting: FALSE, abort: FALSE ]; pChild.context _ [frame[LOOPHOLE[childFrame, FrameHandle]]]; pChild.mds _ NoTimeout; PDA[PDA.timeout][childPsb] _ NoTimeout; PrincOpsUtils.Notify[@rebirth]; }; identity: {parent, child}; argsForChild: StateVector; argsForChild _ STATE; -- must be first! ("root" is automatically popped off the stack first.) identity _ parent; UNTIL PrincOpsUtils.Enter[@processLock] DO NULL ENDLOOP; WHILE LOOPHOLE[rebirth, ConditionVariable].condition.tail = PsbNull DO PrincOpsUtils.Wait[@processLock, @dead, LOOPHOLE[dead, ConditionVariable].timeout]; UNTIL PrincOpsUtils.ReEnter[@processLock, @dead] DO NULL ENDLOOP; ENDLOOP; childPsb _ PDA.block[LOOPHOLE[rebirth, ConditionVariable].condition.tail].link.next; -- walk to tail, then to head. [] _ ChildBuilder[--my local vars--]; SELECT identity FROM parent => {PrincOpsUtils.Exit[@processLock]; RETURN[childPsb]}; -- return child handle to FORKing parent. child => { argsForChild.stk[argsForChild.stkptr] _ root; argsForChild.dest _ root; argsForChild.stk[argsForChild.stkptr+1] _ End; argsForChild.source _ LOOPHOLE[End, ControlLink]; RETURN WITH argsForChild; -- "call" root procedure of child. }; ENDCASE; }; End: PROC = { sv: RECORD [filler: UNSPECIFIED, results: StateVector]; frame: DyingFrameHandle; h: PsbHandle; sv.results _ STATE; -- save stack containing returned results. UNTIL PrincOpsUtils.Enter[@processLock] DO NULL ENDLOOP; frame _ LOOPHOLE[PrincOpsUtils.MyLocalFrame[]]; frame.state _ alive; h _ PrincOpsUtils.ReadPSB[]; PDA[h].flags.processState.state _ frameReady; PDA[h].flags.abort _ FALSE; -- too late for Aborts: they no-op PrincOpsUtils.Broadcast[@frameReady]; -- wake any parent process waiting to Join. UNTIL ProcState[h].state = frameTaken OR ProcState[h].detached DO PrincOpsUtils.Wait[@processLock, @frameTaken, LOOPHOLE[frameTaken, ConditionVariable].timeout]; WHILE ~PrincOpsUtils.ReEnter[@processLock, @frameTaken] DO ENDLOOP; ENDLOOP; IF deadFrame ~= NIL THEN {PrincOpsUtils.Free[deadFrame]; deadFrame _ NIL}; IF ProcState[h].detached THEN deadFrame _ frame; -- If detached, leave our frame for freeing. frame.state _ dead; -- tell Joiner that we're done. PDA[h].flags.processState.state _ dead; PrincOpsUtils.Broadcast[@dead]; -- tell parent our frame has been left for freeing. PrincOpsUtils.Wait[@processLock, @rebirth, LOOPHOLE[rebirth, ConditionVariable].timeout]; UNTIL PrincOpsUtils.ReEnter[@processLock, @rebirth] DO ENDLOOP; sv.results.dest _ LOOPHOLE[frame.returnlink, ControlLink]; -- (frame.returnlink was set by Join[] to point to the context of JOIN.) sv.results.source _ NullLink; PrincOpsUtils.Exit[@processLock]; RETURN WITH sv.results }; Join: ENTRY PROC [process: PsbIndex] RETURNS [loadResults: FrameHandle] = { h: PsbHandle; frame: DyingFrameHandle; self: FrameHandle = PrincOpsUtils.MyLocalFrame[]; IF (h _ ValidProcess[process]) = NullPsbHandle THEN RETURN WITH ERROR InvalidProcess[process]; WHILE ProcState[h].state ~= frameReady DO WAIT frameReady ENDLOOP; frame _ LOOPHOLE[PDA[h].context.frame, DyingFrameHandle]; PDA[h].flags.processState.state _ frameTaken; BROADCAST frameTaken; -- tell child process we've got his frame. WHILE frame.state ~= dead DO WAIT dead ENDLOOP; frame.returnlink _ self.returnlink; RETURN[frame] -- JOINer will next Xfer to "frame", which will reload the results into the stack and return them to the JOINer. }; defaultInterval: Process.Milliseconds = 100; schedulerPriority: Process.Priority = Process.priorityFaultHandlers; pSchedulingInterval: LONG POINTER TO CONDITION _ NIL; Schedule: ENTRY PROCEDURE = { QueueToPsbHandle: PROCEDURE [queue: Queue] RETURNS [PsbHandle] = INLINE {RETURN[PrincOpsUtils.PsbIndexToHandle[queue.tail]]}; schedulingLevels: PACKED ARRAY Process.Priority OF BOOLEAN _ [FALSE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE]; schedulingInterval: CONDITION _ [timeout: MsecToTicks[defaultInterval]]; pSchedulingInterval _ @schedulingInterval; -- enable use of SetTimeSlice Process.SetPriority[schedulerPriority]; DO WAIT schedulingInterval; FOR priority: Process.Priority DECREASING IN [FIRST[Process.Priority]..schedulerPriority) DO IF schedulingLevels[priority] THEN { psb: PsbHandle; PrincOpsUtils.DisableInterrupts[]; psb _ PrincOpsUtils.PsbIndexToHandle[PDA[QueueToPsbHandle[PDA.ready]].link.next]; DO SELECT PDA[psb].link.priority FROM = priority => {PrincOpsUtils.Requeue[@PDA.ready, @PDA.ready, psb]; EXIT}; < priority => EXIT; ENDCASE => IF psb = QueueToPsbHandle[PDA.ready] THEN EXIT; psb _ PrincOpsUtils.PsbIndexToHandle[PDA[psb].link.next]; ENDLOOP; PrincOpsUtils.EnableInterrupts[]; }; ENDLOOP; ENDLOOP; }; Initialize: PROC = { AlignUp: PROC [unaligned, modulus: UNSPECIFIED] RETURNS [aligned: UNSPECIFIED] = INLINE {RETURN[ ((unaligned + modulus-1) / modulus) * modulus]}; AlignDown: PROC [unaligned, modulus: UNSPECIFIED] RETURNS [aligned: UNSPECIFIED] = INLINE {RETURN[unaligned - unaligned MOD modulus]}; rAlloc: PDABase RELATIVE POINTER TO UNSPECIFIED _ LOOPHOLE[pdaWords]; -- allocation pointer. Process.DisableTimeout[@dead]; Process.DisableTimeout[@frameReady]; Process.DisableTimeout[@frameTaken]; Process.DisableTimeout[@rebirth]; SD[sProcessTrap] _ ProcessTrap; SD[sFork] _ Fork; SD[--sCedarFork-- 153B] _ Fork; -- temporary until Compiler is fixed SD[sJoin] _ Join; { alignmentStateVector: CARDINAL = 4; -- (a D0 requirement) sizeStateVector: CARDINAL = AlignUp[MAX[SIZE[StateVector], wordsPerSV], alignmentStateVector]; rState: StateVectorHandle _ AlignDown[rAlloc, alignmentStateVector]; FOR pri: Priority IN Priority DO PDA.state[pri] _ NullStateVectorHandle; FOR k: CARDINAL IN [0..svCounts[pri]) DO IF LOOPHOLE[rState, CARDINAL] < sizeStateVector THEN ERROR; rState _ rState - sizeStateVector; PDA[rState].stk[0] _ PDA.state[pri]; -- chain onto list. PDA.state[pri] _ rState; ENDLOOP; ENDLOOP; rAlloc _ rState; }; { sizePSBTimeout: CARDINAL = SIZE[ProcessStateBlock] + SIZE[Ticks]; alignmentTimeoutVector: CARDINAL = 16; -- (PrincOps requirement) rStartPsb: PDABase RELATIVE POINTER TO UNSPECIFIED = LOOPHOLE[PrincOpsUtils.PsbIndexToHandle[StartPsb]]; totalPsbs: CARDINAL; PDA.count _ 0; FOR nUseful: CARDINAL DECREASING IN [0..(rAlloc - rStartPsb)/sizePSBTimeout] DO totalPsbs _ StartPsb + nUseful; PDA.timeout _ AlignDown[rAlloc - totalPsbs, alignmentTimeoutVector]; IF (LOOPHOLE[PDA.timeout, CARDINAL] + StartPsb -- (first used word of timeout vector) - LOOPHOLE[rStartPsb, CARDINAL]) / SIZE[ProcessStateBlock] >= nUseful THEN {PDA.count _ nUseful; EXIT}; -- that many Psbs fit in available space. REPEAT FINISHED => ERROR; ENDLOOP; IF PDA.count <= 1 THEN ERROR; }; FOR psb: PsbIndex IN [StartPsb..StartPsb+PDA.count) DO PDA[PDA.timeout][psb] _ NoTimeout; ENDLOOP; PDA.block[StartPsb] _ ProcessStateBlock[ link: [failed: FALSE, priority: FIRST[Priority], next: StartPsb, vector: NULL], flags: [ processState: [state: alive, detached: TRUE], cleanup: PsbNull, waiting: FALSE, abort: FALSE], context: NULL, mds: NoTimeout ]; FOR root: FrameHandle _ PrincOpsUtils.MyLocalFrame[], root.returnlink.frame DO IF root.returnlink.frame = NullFrame THEN {root.returnlink _ LOOPHOLE[End, procedure ControlLink]; EXIT}; ENDLOOP; PrincOpsUtils.WritePSB[PrincOpsUtils.PsbIndexToHandle[StartPsb]]; -- tell the processor. PDA.ready _ Queue[tail: StartPsb]; { firstFree: PsbIndex _ StartPsb+1; lastFree: PsbIndex _ StartPsb+PDA.count-1; LOOPHOLE[rebirth, ConditionVariable].condition.tail _ PsbNull; FOR psb: PsbIndex IN [firstFree..lastFree] DO PDA.block[psb].link.next _ IF psb=firstFree THEN lastFree ELSE psb-1; PDA.block[psb].flags.processState.state _ dead; PDA.block[psb].mds _ NoTimeout; REPEAT FINISHED => LOOPHOLE[rebirth, ConditionVariable].condition.tail _ firstFree; ENDLOOP; }; FOR fi: FaultIndex IN FaultIndex DO PDA.fault[fi] _ [ queue: [tail: PsbNull], condition: [tail: PsbNull, abortable: FALSE, wakeup: FALSE]]; ENDLOOP; busyLevels _ LOOPHOLE[reservedNakedNotifyMask]; PrincOpsUtils.WriteWDC[0]; -- start interrupts. Detach[FORK Schedule[]]; }; Initialize[]; END. ΈProcessImpl.mesa last edited by Levin on September 28, 1983 12:03 pm Last edited by Andrew Birrell on August 25, 1983 4:29 pm Types and Related Declarations Global Variables (protected by processLock) This really should be protected by a completely separate monitor from the above, but it isn't worth the trouble. Exported to Process Initializing monitors and condition variables Detaching processes Aborting a process Wake the victim up. TEMP until microcode uses timeout vector: Control of Scheduling Process validation Exported to PrincOpsUtils Internal Procedures This procedure is invoked by microcode when an aborted process attempts to reenter its monitor. Acquire abortee's monitor lock: if ABORTED is made resumable, we should return [monitorEntered: TRUE] as the result of the MonitorEnter instruction that got us here. This procedure returns NullPsbHandle if "p" is invalid. Implementation of Language Constructs (FORK and JOIN) TEMP until microcode uses timeout vector: pChild.mds _ Inline.HighHalf[LONG[LOOPHOLE[1, POINTER]]]; The following starts the new process executing. Its PC is set to begin execution at the instruction after the call to ChildBuilder. Its stack is empty. Therefore, ChildBuilder MUST NOT RETURN ANY RESULTS! Both parent and child processes will execute the following code: To simulate a procedure call, we must also store dest and source links *above* the stack since ReturnWithState doesn't. (stack pointer is *not* incremented.) Set child's top context to call End when it returns: When the top context of a process "returns", it Xfers to this procedure with its results on the stack. Wait till this process is Detached or Joined: Free any frame left over from a previous dead detached process: This process is dead. Its PSB sits in the rebirth queue until it is recycled into a new process by Fork. Our current frame however, has one of two fates: (a) If this process was detached, the frame will simply be freed by the next process that finishes ("deadFrame"). (b) if this process is being Joined, the parent process will have acquired a pointer to our frame. The JOIN code will Xfer to our frame and the code below will be executed BY THE PARENT PROCESS. The parent process therefore MUST BE RUNNING IN THE SAME MDS as the child process! Reload returned results into stack, return to parent: Wait till process ready to be joined: Guaranteed to be a dying frame by the time we get here. Wait till he has finished cleaning up: At this point, we (the parent process) have acquired responsibility for the child's frame. IT MUST BE IN THE SAME MDS AS THE PARENT. We use the child frame's return link as a mailbox to pass to the child's frame the address of the JOINer's frame, which the child should return to. Time Slicer The following is intended to be altered only by the debugger. Note: this procedure only touches resident structures and does not call any procedures. It therefore can never consume a state vector and consequently does not need a reserved one, even though it is executing at the reserved priority level. Note that readyList can't be empty, since we are running! Initialization This procedure is implicitly parameterized by the module parameters. Initialize monitored variables and SD Allocate StateVector pool: Find number of {PSB, timeout words} that will fit in space remaining, considering TimeoutVector alignment and unused TimeoutVector positions. Initialize TimeoutVector: (Note: TimeoutVector unused portions overlay Psbs) Set up the executing context as a legitimate process (detached) in the first Psb: TEMP until microcode uses timeout vector: mds: Inline.HighHalf[LONG[LOOPHOLE[1, POINTER]]] ]; Note: the following assumes that someone has previously set the return link of the topmost frame to NullFrame. Initialize the ready list Put rest of PSBs into free pool: (chained off "rebirth" condition) TEMP until microcode uses timeout vector: Initialize FaultVector: Initialize naked-notify allocator: Start the time slicer Κ„– "Cedar" style˜Jšœ™J™3J™8J˜šΟk ˜ Jšœœœ˜$Jšœœ˜*šœ œ˜Jšœ°œuœO˜ω—šœœ˜Jšœm˜m—Jšœœ˜šœœ˜JšœœΦ˜μ——J˜šœ œ˜Jšœ œ;œ˜VJšœœœ˜=Jšœ ˜Jšœ˜&Jšœ9˜@J˜—J˜Jš˜J˜Jšœ ˜J˜Jšœ™™š œ œœ œœ˜+JšœΟc˜9J˜J˜—Jšœœžœ˜3J˜Jšœœœœ ˜0J˜J™—Jšž+™+˜Jšœ  œ˜J˜Jšœ  œž˜2Jšœ  œž2˜JJšœ  œž4˜LJšœ œž8˜JJ™JšœœžE˜iJ˜š œ œœœœ˜2Jšœp™pJ˜—J™—Jšœ™™Jšœ-™-J˜š Οn œœœœœœ˜\šœ˜šœœœ˜@Jšœœž˜.—Jšœ˜ J˜J˜—˜J˜——šŸœœœœœœœ˜Tšœ˜Jšœ œ7˜D—Jš œœœœœ˜AJ˜J˜—š Ÿ œœœœœœ˜Wšœ˜šœœ˜=Jšœ˜—Jšœ˜Jšœ˜—Jšœ˜—J˜Jšœ™J™š Ÿœœœ œ œ˜3Jš˜J˜ šœ-˜3Jš œœœœ  œ˜A—Jšœ"œ˜*Jš œž!˜8Jšœ˜—J˜Jšœ™J™šŸœœœ œ˜5J˜ šœ-˜3Jšœœœ˜*—šœœ˜%Jšœœ˜Jšœ˜Jšœ˜—šœ˜Jšœ$ž3˜Wšœœ˜$Jšœœ˜šœœœ˜Jšœ™Jšœœ˜Jšœœ9˜@Jšœ)™)Jšœž œ˜"Jšœœœ ˜*Jšœ˜—Jšœ˜—Jšœ!˜!J˜—Jšœ˜—J˜š Ÿ œœœœ œœ˜6Jšœ'˜'šœœœ˜Jšœœ˜Jšœ˜Jšœ˜—Jšœ˜J˜—Jšœ™J™š Ÿœœœœœœ˜@Jšœœœœ ˜ Jšœ œ˜ J˜J˜Jšœ˜š˜Jš œ œœœœ˜%—Jšœ˜—J˜š Ÿ œœœœœœ˜GJšœœœœ˜)J˜%Jšœ˜J˜J˜—Jšœ™J˜šŸœœœœ˜4Jš œ!œœœœ˜LJšœ˜—J˜Jšœœœœ˜8J˜Jšœ™J˜šŸœœœ˜)Jš œœœœ œœ˜5Jš˜šœœ˜2šœœ˜Jšœœ˜šœ˜Jšœœœ˜$Jšœ œœ ˜7Jšœ˜—Jšœ˜—Jšœ˜—Jš˜Jšœ˜—J˜šŸœœ œœœœ œ˜Ošœœ˜2šœ˜Jšœœœœœœ œœ˜FJšœœ˜Jšœ+˜.Jšœ˜Jšœ˜—Jšœ˜—Jšœ˜Jšœ˜—J˜—J™™šŸ œœœœ˜CJšœœ˜$Jšœ˜—J˜šŸ œœœœ˜"Jšœ_™_Jš˜šŸ œœž œ˜AJšœœœ˜2—J˜Jšœœ  œ˜4Jšœ œ˜Jšœ™š˜Jšœœœ*˜EJš œœœœ œ˜KJšœœ˜ —Jšœ)˜)Jšœ ž˜?Jšœ(œ˜1Jšœœ˜JšœE™EJšœ?™?Jšœ˜—J˜š Ÿ œœœž  œœœ˜ZJšœ7™7šœ˜š œœœœ ˜@Jšœ-œœ˜`—Jšœœ˜Jšœ˜—Jšœ˜—J™—J™5˜šŸœœžœœ˜PJš œ œœœœ˜*š Ÿ œœžœœžœ˜HJš œœœœœ˜AJšœœ!˜CJšœœœœ˜GJ˜2šœœ˜JšœI˜I—J˜Jšœœ˜Jšœœ(˜B˜Jšœ'œ˜.Jšœœ ˜.Jšœ˜—Jšœœ˜Jšœ'ž+˜RJšœ-™-šœ!œ˜AJšœ.œ)˜_Jšœ3œœ˜CJšœ˜—Jšœ?™?Jšœœœ-œ˜JJšœœž,˜^Jšœž˜4Jšœ$˜'Jšœ!ž3˜TJšœ* œ&˜Yšœ›™›Jšœq™qJšœ—™——Jšœ/œœ˜?Jšœœ"žH˜„J˜Jšœ!˜!Jšœ5™5Jšœœ ˜Jšœ˜—J˜šŸœœœœ˜KJ˜ J˜Jšœ1˜1šœ-˜3Jšœœœ˜*—Jšœ%™%Jšœ"œ œ˜BJšœ7™7Jšœœœ%˜9Jšœ*˜-Jš œž*˜AJšœ&™&Jšœœœœ˜/Jšœˆžœ{™›Jšœ#˜#Jšœ žp˜Jšœ˜—J™—J™ J˜J˜,J˜DJ˜Jš œœœœ œœ˜5J˜šŸœœ œ˜šŸœ œœ˜GJšœœ.˜5—Jšœ=™=š œœœœœ˜šœœ˜-Jšœœœ œ˜EJšœ,˜/Jšœ)™)Jšœ˜š˜Jšœ œ8˜L—Jšœ˜—Jšœ˜J˜Jšœ™šœœ ˜#šœ˜Jšœ>œ œ˜U—Jšœ˜J˜—Jšœ"™"Jšœ œ˜/J˜Jšœž˜0J™J™Jšœœ ˜Jšœ˜—J˜J˜ J˜—šœ˜J˜——…—:]B