1 //
2 // Copyright (c) 2010-2025 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 using System;
8 using System.Collections.Generic;
9 using System.Diagnostics;
10 using System.Linq;
11 using System.Threading;
12 using Antmicro.Renode.Core;
13 using Antmicro.Renode.Debugging;
14 using Antmicro.Renode.Logging;
15 using Antmicro.Renode.Utilities;
16 using Antmicro.Migrant;
17 
18 namespace Antmicro.Renode.Time
19 {
20     /// <summary>
21     /// Provides common base for <see cref="ITimeSource"> implementations.
22     /// </summary>
23     public abstract class TimeSourceBase : IdentifiableObject, ITimeSource, IDisposable
24     {
25         /// <summary>
26         /// Creates new instance of time source.
27         /// </summary>
TimeSourceBase()28         public TimeSourceBase()
29         {
30             virtualTimeSyncLock = new object();
31             isOnSyncPhaseThreadLock = new object();
32 
33             blockingEvent = new ManualResetEvent(true);
34             delayedActions = new SortedSet<DelayedTask>();
35             handles = new HandlesCollection();
36             stopwatch = new Stopwatch();
37 
38             hostTicksElapsed = new TimeVariantValue(10);
39             virtualTicksElapsed = new TimeVariantValue(10);
40 
41             sync = new PrioritySynchronizer();
42 
43             Quantum = DefaultQuantum;
44 
45             this.Trace();
46         }
47 
48         /// <summary>
49         /// Disposes this instance.
50         /// </summary>
Dispose()51         public virtual void Dispose()
52         {
53             delayedActions.Clear();
54 
55             stopwatch.Stop();
56             BlockHook          = null;
57             StopRequested      = null;
58             SyncHook           = null;
59             TimePassed         = null;
60             SinksReportedHook  = null;
61             using(sync.HighPriority)
62             {
63                 handles.LatchAllAndCollectGarbage();
64                 handles.UnlatchAll();
65 
66                 foreach(var slave in handles.All)
67                 {
68                     slave.Dispose();
69                 }
70             }
71         }
72 
73         /// <summary>
74         /// Activates sources of the time source and provides an object that deactivates them on dispose.
75         /// </summary>
ObtainSourceActiveState()76         protected IDisposable ObtainSourceActiveState()
77         {
78             using(sync.HighPriority)
79             {
80                 foreach(var slave in handles.All)
81                 {
82                     slave.SourceSideActive = true;
83                     slave.RequestStart();
84                 }
85             }
86 
87             var result = new DisposableWrapper();
88             result.RegisterDisposeAction(() =>
89             {
90                 using(sync.HighPriority)
91                 {
92                     foreach(var slave in handles.All)
93                     {
94                         slave.SourceSideActive = false;
95                     }
96                 }
97             });
98             return result;
99         }
100 
101         /// <summary>
102         /// Starts the time source and provides an object that stops it on dispose.
103         /// </summary>
ObtainStartedState()104         protected IDisposable ObtainStartedState()
105         {
106             Start();
107             return new DisposableWrapper().RegisterDisposeAction(() => Stop());
108         }
109 
110         /// <summary>
111         /// Starts this time source and activates all associated slaves.
112         /// </summary>
113         /// <returns>False it the handle has already been started.</returns>
Start()114         protected bool Start()
115         {
116             if(isStarted)
117             {
118                 this.Trace("Already started");
119                 return false;
120             }
121 
122             using(sync.HighPriority)
123             {
124                 if(isStarted)
125                 {
126                     this.Trace("Already started");
127                     return false;
128                 }
129 
130                 stopwatch.Start();
131                 isStarted = true;
132                 return true;
133             }
134         }
135 
136         /// <summary>
137         /// Requests start of all registered slaves.
138         /// </summary>
139         /// <remark>
140         /// The method should be called after activating this source using <cref="ActivateSlavesSourceSize">,
141         /// otherwise a race condition situation might happen.
142         /// </remark>
RequestSlavesStart()143         protected void RequestSlavesStart()
144         {
145             using(sync.HighPriority)
146             {
147                 foreach(var slave in handles.All)
148                 {
149                     slave.RequestStart();
150                 }
151             }
152         }
153 
154         /// <summary>
155         /// Calls <see cref="StopRequested"/> event.
156         /// </summary>
RequestStop()157         protected void RequestStop()
158         {
159             StopRequested?.Invoke();
160         }
161 
162         /// <summary>
163         /// Stops this time source and deactivates all associated slaves.
164         /// </summary>
Stop()165         protected void Stop()
166         {
167             RequestStop();
168             using(sync.HighPriority)
169             {
170                 if(!isStarted)
171                 {
172                     this.Trace("Not started");
173                     return;
174                 }
175 
176                 stopwatch.Stop();
177                 isStarted = false;
178                 sync.Pulse();
179                 blockingEvent.Set();
180             }
181         }
182 
183         /// <summary>
184         /// Queues an action to execute in the nearest synced state.
185         /// </summary>
186         /// <param name="executeImmediately">Flag indicating if the action should be executed immediately when executed in already synced context or should it wait for the next synced state.</param>
ExecuteInNearestSyncedState(Action<TimeStamp> what, bool executeImmediately = false)187         public void ExecuteInNearestSyncedState(Action<TimeStamp> what, bool executeImmediately = false)
188         {
189             if(IsOnSyncPhaseThread && executeImmediately)
190             {
191                 what(new TimeStamp(ElapsedVirtualTime, Domain));
192                 return;
193             }
194             lock(delayedActions)
195             {
196                 delayedActions.Add(new DelayedTask(what, new TimeStamp(), ++delayedTaskId));
197             }
198         }
199 
200         /// <summary>
201         /// Queues an action to execute in the nearest synced state after <paramref name="when"> time point.
202         /// </summary>
203         /// <remarks>
204         /// If the <see cref="when"> time stamp comes from other time domain it will be executed in the nearest synced state.
205         /// </remarks>
206         /// <returns>
207         /// The ID of the action, which can be used to cancel it with <see cref="CancelActionToExecuteInSyncedState">.
208         /// </returns>
ExecuteInSyncedState(Action<TimeStamp> what, TimeStamp when)209         public ulong ExecuteInSyncedState(Action<TimeStamp> what, TimeStamp when)
210         {
211             lock(delayedActions)
212             {
213                 var id = ++delayedTaskId;
214                 delayedActions.Add(new DelayedTask(what, when.Domain != Domain ? new TimeStamp() : when, id));
215                 return id;
216             }
217         }
218 
219         /// <summary>
220         /// Removes a queued action to execute in the synced state by ID.
221         /// </summary>
222         /// <param name="actionId">The ID of the action to remove.</param>
223         /// <returns>
224         /// True if the action was successfully removed, otherwise false.
225         /// </returns>
CancelActionToExecuteInSyncedState(ulong actionId)226         public bool CancelActionToExecuteInSyncedState(ulong actionId)
227         {
228             lock(delayedActions)
229             {
230                 return delayedActions.RemoveWhere(action => action.Id == actionId) == 1;
231             }
232         }
233 
234         /// <see cref="ITimeSource.RegisterSink">
RegisterSink(ITimeSink sink)235         public void RegisterSink(ITimeSink sink)
236         {
237             using(sync.HighPriority)
238             {
239                 var handle = new TimeHandle(this, sink) { SourceSideActive = isStarted };
240                 StopRequested += handle.RequestPause;
241                 handles.Add(handle);
242 #if DEBUG
243                 this.Trace($"Registering sink ({(sink as IIdentifiable)?.GetDescription()}) in source ({this.GetDescription()}) via handle ({handle.GetDescription()})");
244 #endif
245                 // assigning TimeHandle to a sink must be done when everything is configured, otherwise a race condition might happen (dispatcher starts its execution when time source and handle are not yet ready)
246                 sink.TimeHandle = handle;
247             }
248         }
249 
250         public IEnumerable<ITimeSink> Sinks { get { using(sync.HighPriority) { return handles.Select(x => x.TimeSink); } } }
251 
252         /// <see cref="ITimeSource.ReportHandleActive">
ReportHandleActive()253         public void ReportHandleActive()
254         {
255             blockingEvent.Set();
256         }
257 
258         /// <see cref="ITimeSource.ReportTimeProgress">
ReportTimeProgress()259         public void ReportTimeProgress()
260         {
261             SynchronizeVirtualTime();
262         }
263 
SynchronizeVirtualTime()264         private void SynchronizeVirtualTime()
265         {
266             lock(virtualTimeSyncLock)
267             {
268                 if(!handles.TryGetCommonElapsedTime(out var currentCommonElapsedTime))
269                 {
270                     return;
271                 }
272 
273                 if(currentCommonElapsedTime == ElapsedVirtualTime)
274                 {
275                     return;
276                 }
277 
278                 DebugHelper.Assert(currentCommonElapsedTime > ElapsedVirtualTime, $"A slave reports time from the past! The current virtual time is {ElapsedVirtualTime}, but {currentCommonElapsedTime} has been reported");
279 
280                 var timeDiff = currentCommonElapsedTime - ElapsedVirtualTime;
281                 this.Trace($"Reporting time passed: {timeDiff}");
282                 // this will update ElapsedVirtualTime
283                 UpdateTime(timeDiff);
284                 TimePassed?.Invoke(timeDiff);
285             }
286         }
287 
ToString()288         public override string ToString()
289         {
290             return string.Join("\n",
291                 $"Elapsed Virtual Time: {ElapsedVirtualTime}",
292                 $"Elapsed Host Time: {ElapsedHostTime}",
293                 $"Current load: {CurrentLoad}",
294                 $"Cumulative load: {CumulativeLoad}",
295                 $"State: {State}",
296                 $"Advance immediately: {AdvanceImmediately}",
297                 $"Quantum: {Quantum}");
298         }
299 
300         /// <see cref="ITimeSource.Domain">
301         public abstract ITimeDomain Domain { get; }
302 
303         // TODO: this name does not give a lot to a user - maybe we should rename it?
304         /// <summary>
305         /// Gets or sets flag indicating if the time flow should be slowed down to reflect real time or be as fast as possible.
306         /// </summary>
307         /// <remarks>
308         /// Setting this flag to True has the same effect as setting <see cref="Performance"> to a very high value.
309         /// </remarks>
310         public bool AdvanceImmediately { get; set; }
311 
312         /// <summary>
313         /// Gets current state of this time source.
314         /// </summary>
315         public TimeSourceState State { get; private set; }
316 
317         // TODO: do not allow to set Quantum of 0
318         /// <see cref="ITimeSource.Quantum">
319         public TimeInterval Quantum
320         {
321             get => quantum;
322             set
323             {
324                 if(quantum == value)
325                 {
326                     return;
327                 }
328 
329                 var oldQuantum = quantum;
330                 quantum = value;
331                 QuantumChanged?.Invoke(oldQuantum, quantum);
332             }
333         }
334 
335         /// <summary>
336         /// Gets the value representing current load, i.e., value indicating how much time the emulation spends sleeping in order to match the expected <see cref="Performance">.
337         /// </summary>
338         /// <remarks>
339         /// Value 1 means that there is no sleeping, i.e., it is not possible to execute faster. Value > 1 means that the execution is slower than expected. Value < 1 means that increasing <see cref="Performance"> will lead to faster execution.
340         /// This value is calculated as an average of 10 samples.
341         /// </remarks>
342         public double CurrentLoad { get { lock(hostTicksElapsed) { return hostTicksElapsed.AverageValue * 1.0 / virtualTicksElapsed.AverageValue; } } }
343 
344         /// <summary>
345         /// Gets the value representing load (see <see cref="CurrentLoad">) calculated from all samples.
346         /// </summary>
347         public double CumulativeLoad { get { lock(hostTicksElapsed) { return hostTicksElapsed.CumulativeValue * 1.0 / virtualTicksElapsed.CumulativeValue; } } }
348 
349         /// <summary>
350         /// Gets the amount of virtual time elapsed from the perspective of this time source.
351         /// </summary>
352         /// <remarks>
353         /// This is a minimum value of all associated <see cref="TimeHandle.TotalElapsedTime">.
354         /// </remarks>
355         public TimeInterval ElapsedVirtualTime { get { return TimeInterval.FromTicks(virtualTicksElapsed.CumulativeValue); } }
356 
357         /// <summary>
358         /// Gets the amount of host time elapsed from the perspective of this time source.
359         /// </summary>
360         public TimeInterval ElapsedHostTime { get { return TimeInterval.FromTicks(hostTicksElapsed.CumulativeValue); } }
361 
362         /// <summary>
363         /// Gets the amount that the virtual time is ahead of the host time from the perspective of this time source,
364         /// or 0 if the virtual time is behind the host time.
365         /// </summary>
366         public TimeInterval ElapsedVirtualHostTimeDifference
367         {
368             get
369             {
370                 lock(hostTicksElapsed)
371                 {
372                     var hostTicks = hostTicksElapsed.CumulativeValue;
373                     var virtualTicks = virtualTicksElapsed.CumulativeValue;
374                     if(virtualTicks <= hostTicks)
375                     {
376                         return TimeInterval.Empty;
377                     }
378                     return TimeInterval.FromTicks(virtualTicks - hostTicks);
379                 }
380             }
381         }
382 
383         /// <summary>
384         /// Gets the virtual time point of the nearest synchronization of all associated <see cref="ITimeHandle">.
385         /// </summary>
386         public TimeInterval NearestSyncPoint { get; private set; }
387 
388         /// <summary>
389         /// Gets the number of synchronizations points reached so far.
390         /// </summary>
391         public long NumberOfSyncPoints { get; private set; }
392 
393         /// <summary>
394         /// Gets or sets the flag indicating if the current thread is a safe thread executing sync phase.
395         /// </summary>
396         public bool IsOnSyncPhaseThread
397         {
398             get
399             {
400                 lock(isOnSyncPhaseThreadLock)
401                 {
402                     return executeThreadId == Thread.CurrentThread.ManagedThreadId;
403                 }
404             }
405 
406             private set
407             {
408                 lock(isOnSyncPhaseThreadLock)
409                 {
410                     executeThreadId = value ? Thread.CurrentThread.ManagedThreadId : (int?)null;
411                 }
412             }
413         }
414 
415         /// <summary>
416         /// Forces the execution phase of time sinks to be done in serial.
417         /// </summary>
418         /// <remarks>
419         /// Using this option might reduce the performance of the execution, but ensures the determinism.
420         /// </remarks>
421         public bool ExecuteInSerial { get; set; }
422 
423         /// <summary>
424         /// Action to be executed on every synchronization point.
425         /// </summary>
426         public event Action<TimeInterval> SyncHook;
427 
428         /// <summary>
429         /// An event called when the time source is blocked by at least one of the sinks.
430         /// </summary>
431         public event Action BlockHook;
432 
433         /// <summary>
434         /// An event called when no sink is in progress.
435         /// </summary>
436         public event Action SinksReportedHook;
437 
438         /// <summary>
439         /// An event informing about the amount of passed virtual time. Might be called many times between two consecutive synchronization points.
440         /// </summary>
441         public event Action<TimeInterval> TimePassed;
442 
443         /// <summary>
444         /// An event called when the Quantum is changed.
445         /// </summary>
446         public event Action<TimeInterval, TimeInterval> QuantumChanged;
447 
448         /// <summary>
449         /// Execute one iteration of time-granting loop.
450         /// </summary>
451         /// <remarks>
452         /// The steps are as follows:
453         /// (1) remove and forget all slave handles that requested detaching
454         /// (2) check if there are any blocked slaves; if so DO NOT grant a time interval
455         /// (2.1) if there are no blocked slaves grant a new time interval to every slave
456         /// (3) wait for all slaves that are relevant in this execution (it can be either all slaves or just blocked ones) until they report back
457         /// (4) update elapsed virtual time
458         /// (5) execute sync hook and delayed actions if any
459         /// </remarks>
460         /// <param name="virtualTimeElapsed">Contains the amount of virtual time that passed during execution of this method. It is the minimal value reported by a slave (i.e, some slaves can report higher/lower values).</param>
461         /// <param name="timeLimit">Maximum amount of virtual time that can pass during the execution of this method. If not set, current <see cref="Quantum"> is used.</param>
462         /// <returns>
463         /// True if sync point has just been reached or False if the execution has been blocked.
464         /// </returns>
InnerExecute(out TimeInterval virtualTimeElapsed, TimeInterval? timeLimit = null)465         protected bool InnerExecute(out TimeInterval virtualTimeElapsed, TimeInterval? timeLimit = null)
466         {
467             if(updateNearestSyncPoint)
468             {
469                 NearestSyncPoint += timeLimit.HasValue ? TimeInterval.Min(timeLimit.Value, Quantum) : Quantum;
470                 updateNearestSyncPoint = false;
471                 this.Trace($"Updated NearestSyncPoint to: {NearestSyncPoint}");
472             }
473             DebugHelper.Assert(NearestSyncPoint.Ticks >= ElapsedVirtualTime.Ticks, $"Nearest sync point set in the past: EVT={ElapsedVirtualTime} NSP={NearestSyncPoint}");
474 
475             isBlocked = false;
476             var quantum = NearestSyncPoint - ElapsedVirtualTime;
477             this.Trace($"Starting a loop with #{quantum.Ticks} ticks");
478 
479             SynchronizeVirtualTime();
480             var elapsedVirtualTimeAtStart = ElapsedVirtualTime;
481 
482             using(sync.LowPriority)
483             {
484                 handles.LatchAllAndCollectGarbage();
485                 var shouldGrantTime = handles.AreAllReadyForNewGrant;
486 
487                 this.Trace($"Iteration start: slaves left {handles.ActiveCount}; will we try to grant time? {shouldGrantTime}");
488 
489                 if(handles.ActiveCount > 0)
490                 {
491                     var executor = new PhaseExecutor<LinkedListNode<TimeHandle>>();
492 
493                     if(!shouldGrantTime)
494                     {
495                         if(ExecuteInSerial)
496                         {
497                             // We only test in serial execution to ensure determinism
498                             executor.RegisterTestPhase(ExecuteReadyForUnblockTestPhase);
499                         }
500                         executor.RegisterPhase(ExecuteUnblockPhase);
501                         executor.RegisterPhase(ExecuteWaitPhase);
502                     }
503                     else if(quantum != TimeInterval.Empty)
504                     {
505                         executor.RegisterPhase(s => ExecuteGrantPhase(s, quantum));
506                         executor.RegisterPhase(ExecuteWaitPhase);
507                     }
508 
509                     if(ExecuteInSerial)
510                     {
511                         executor.ExecuteInSerial(handles.WithLinkedListNode);
512                     }
513                     else
514                     {
515                         executor.ExecuteInParallel(handles.WithLinkedListNode);
516                     }
517 
518                     SynchronizeVirtualTime();
519                     virtualTimeElapsed = ElapsedVirtualTime - elapsedVirtualTimeAtStart;
520                 }
521                 else
522                 {
523                     this.Trace($"There are no slaves, updating VTE by {quantum.Ticks}");
524                     // if there are no slaves just make the time pass
525                     virtualTimeElapsed = quantum;
526 
527                     UpdateTime(quantum);
528                     // here we must trigger `TimePassed` manually as no handles has been updated so they won't reflect the passed time
529                     TimePassed?.Invoke(quantum);
530                 }
531 
532                 handles.UnlatchAll();
533             }
534 
535             SinksReportedHook?.Invoke();
536             if(!isBlocked)
537             {
538                 ExecuteSyncPhase();
539                 updateNearestSyncPoint = true;
540             }
541             else
542             {
543                 BlockHook?.Invoke();
544             }
545 
546             State = TimeSourceState.Idle;
547 
548             this.Trace($"The end of {nameof(InnerExecute)} with result={!isBlocked}");
549             return !isBlocked;
550         }
551 
UpdateTime(TimeInterval virtualTimeElapsed)552         private void UpdateTime(TimeInterval virtualTimeElapsed)
553         {
554             lock(hostTicksElapsed)
555             {
556                 // Converting to TimeInterval truncates to whole microseconds. Convert before saving the value to
557                 // elapsedAtLastUpdate, as otherwise we would lose this decimal part.
558                 var currentTimestamp = TimeInterval.FromTimeSpan(stopwatch.Elapsed);
559                 var elapsedThisTime = currentTimestamp - elapsedAtLastUpdate;
560                 elapsedAtLastUpdate = currentTimestamp;
561 
562                 this.Trace($"Updating virtual time by {virtualTimeElapsed.TotalMicroseconds} us");
563                 this.virtualTicksElapsed.Update(virtualTimeElapsed.Ticks);
564                 this.hostTicksElapsed.Update(elapsedThisTime.Ticks);
565             }
566         }
567 
568         /// <summary>
569         /// Activates all slaves from source side perspective, i.e., tells them that there will be time granted in the nearest future.
570         /// </summary>
ActivateSlavesSourceSide(bool state = true)571         protected void ActivateSlavesSourceSide(bool state = true)
572         {
573             using(sync.HighPriority)
574             {
575                 foreach(var slave in handles.All)
576                 {
577                     slave.SourceSideActive = state;
578                     if(state)
579                     {
580                         slave.RequestStart();
581                     }
582                 }
583             }
584         }
585 
586         /// <summary>
587         /// Deactivates all slaves from  source side perspective, i.e., tells them that there will be no grants in the nearest future.
588         /// </summary>
DeactivateSlavesSourceSide()589         protected void DeactivateSlavesSourceSide()
590         {
591             ActivateSlavesSourceSide(false);
592         }
593 
594         /// <summary>
595         /// Suspends an execution of the calling thread if blocking event is set.
596         /// </summary>
597         /// <remarks>
598         /// This is just to improve performance of the emulation - avoid spinning when any of the sinks is blocking.
599         /// </remarks>
WaitIfBlocked()600         protected void WaitIfBlocked()
601         {
602             // this 'if' statement and 'canBeBlocked' variable are here for performance only
603             // calling `WaitOne` in every iteration can cost a lot of time;
604             // waiting on 'blockingEvent' is not required for the time framework to work properly,
605             // but decreases cpu usage when any handle is known to be blocking
606             if(isBlocked)
607             {
608                 // value of 'isBlocked' will be reevaluated in 'ExecuteInner' method
609                 blockingEvent.WaitOne(10);
610                 // this parameter here is kind of a hack:
611                 // in theory we could use an overload without timeout,
612                 // but there is a bug and sometimes it blocks forever;
613                 // this is just a simple workaround
614             }
615         }
616 
617         /// <summary>
618         /// Forces value of elapsed virtual time and nearest sync point.
619         /// </summary>
620         /// <remarks>
621         /// It is called when attaching a new time handle to synchronize the initial value of virtual time.
622         /// </remarks>
ResetVirtualTime(TimeInterval interval)623         protected void ResetVirtualTime(TimeInterval interval)
624         {
625             lock(hostTicksElapsed)
626             {
627                 DebugHelper.Assert(ElapsedVirtualTime <= interval, $"Couldn't reset back in time from {ElapsedVirtualTime} to {interval}.");
628 
629                 virtualTicksElapsed.Reset(interval.Ticks);
630                 NearestSyncPoint = interval;
631 
632                 using(sync.HighPriority)
633                 {
634                     foreach(var handle in handles.All)
635                     {
636                         handle.Reset();
637                     }
638                 }
639             }
640         }
641 
642         /// <summary>
643         /// Grants time interval to a single handle.
644         /// </summary>
ExecuteGrantPhase(LinkedListNode<TimeHandle> handle, TimeInterval quantum)645         private void ExecuteGrantPhase(LinkedListNode<TimeHandle> handle, TimeInterval quantum)
646         {
647             State = TimeSourceState.ReportingElapsedTime;
648             handle.Value.GrantTimeInterval(quantum);
649         }
650 
651         /// <summary>
652         /// Unblocks a single handle allowing it to continue
653         /// execution of the previously granted time interval.
654         /// </summary>
ExecuteUnblockPhase(LinkedListNode<TimeHandle> handle)655         private void ExecuteUnblockPhase(LinkedListNode<TimeHandle> handle)
656         {
657             handle.Value.UnblockHandle();
658         }
659 
660         /// <summary>
661         /// Tests the given handle for readiness to be unblocked.
662         /// If the handle is not ready the execution is blocked.
663         /// </summary>
ExecuteReadyForUnblockTestPhase(LinkedListNode<TimeHandle> handle)664         private bool ExecuteReadyForUnblockTestPhase(LinkedListNode<TimeHandle> handle)
665         {
666             var isReady = handle.Value.IsReadyToBeUnblocked;
667             isBlocked |= !isReady;
668             return isReady;
669         }
670 
671         /// <summary>
672         /// Waits until the handle finishes its execution.
673         /// </summary>
674         /// <remarks>
675         /// This method must be called with a <see cref="sync"/> locked.
676         /// </remarks>
ExecuteWaitPhase(LinkedListNode<TimeHandle> handle)677         private void ExecuteWaitPhase(LinkedListNode<TimeHandle> handle)
678         {
679             State = TimeSourceState.WaitingForReportBack;
680             var result = handle.Value.WaitUntilDone(out var usedInterval);
681             if(!result.IsDone)
682             {
683                 EnterBlockedState(!handle.Value.SinkSideActive);
684             }
685 
686             using(sync.HighPriority)
687             {
688                 handles.UpdateHandle(handle);
689             }
690         }
691 
692         /// <summary>
693         /// Sets blocking event to true.
694         /// </summary>
695         /// <param name="waitForEvent">Describes if we should block until external event</param>
EnterBlockedState(bool waitForEvent)696         private void EnterBlockedState(bool waitForEvent)
697         {
698             isBlocked = true;
699             // The blocking event is by default in the `set` state.
700             // It enters the `unset` state only temporarily between calls to `EnterBlockedState` (with the wait for event flag set) and `ReportHandleActive`.
701             if(waitForEvent)
702             {
703                 blockingEvent.Reset();
704             }
705         }
706 
707         /// <summary>
708         /// Executes sync phase actions in a safe state.
709         /// </summary>
ExecuteSyncPhase()710         private void ExecuteSyncPhase()
711         {
712             this.Trace($"Before syncpoint, EVT={ElapsedVirtualTime.Ticks}, NSP={NearestSyncPoint.Ticks}");
713             // if no slave returned blocking state, sync point should be reached
714             DebugHelper.Assert(ElapsedVirtualTime == NearestSyncPoint);
715             this.Trace($"We are at the sync point #{NumberOfSyncPoints}");
716 
717             State = TimeSourceState.ExecutingSyncHook;
718 
719             DelayedTask[] tasksAsArray;
720             TimeStamp timeNow;
721             lock(delayedActions)
722             {
723                 IsOnSyncPhaseThread = true;
724                 SyncHook?.Invoke(ElapsedVirtualTime);
725 
726                 State = TimeSourceState.ExecutingDelayedActions;
727                 timeNow = new TimeStamp(ElapsedVirtualTime, Domain);
728                 // we are not incrementing delayedTaskId here because DelayedTask object is only created temporarily for comparison,
729                 // all operations on delayedActions are in the lock() blocks and delayedTaskId is never decremented so its current value
730                 // is greater or equal to every currently existing DelayedTask object which is what we care for in this comparison
731                 var tasksToExecute = delayedActions.GetViewBetween(DelayedTask.Zero, new DelayedTask(null, timeNow, delayedTaskId));
732                 tasksAsArray = tasksToExecute.ToArray();
733                 tasksToExecute.Clear();
734             }
735 
736             foreach(var task in tasksAsArray)
737             {
738                 task.What(timeNow);
739             }
740             IsOnSyncPhaseThread = false;
741             NumberOfSyncPoints++;
742         }
743 
744         // This value is dropped because it should always be false after deserialization in order to start the emulation properly,
745         // otherwise starting stopwatch is omitted in TimeSourceBase.Start() method.
746         // If it wasn't marked as transient it could be true when the emulation wasn't paused before serialization because it was already in a safe state.
747         [Transient]
748         protected volatile bool isStarted;
749         protected bool isPaused;
750 
751         protected readonly HandlesCollection handles;
752         protected readonly Stopwatch stopwatch;
753         // we use special object for locking as it was observed that idle dispatcher thread can starve other threads when using simple lock(object)
754         protected readonly PrioritySynchronizer sync;
755 
756         /// <summary>
757         /// Used to request a pause on sinks before trying to acquire their locks.
758         /// </summary>
759         /// <remarks>
760         /// Triggering this event can improve pausing efficiency by interrupting the sink execution in the middle of a quant.
761         /// </remarks>
762         private event Action StopRequested;
763 
764         [Antmicro.Migrant.Constructor(true)]
765         private ManualResetEvent blockingEvent;
766 
767         private TimeInterval elapsedAtLastUpdate;
768         private bool isBlocked;
769         private bool updateNearestSyncPoint;
770         private int? executeThreadId;
771         private ulong delayedTaskId;
772         private TimeInterval quantum;
773 
774         private readonly TimeVariantValue virtualTicksElapsed;
775         private readonly TimeVariantValue hostTicksElapsed;
776         private readonly SortedSet<DelayedTask> delayedActions;
777         private readonly object virtualTimeSyncLock;
778         private readonly object isOnSyncPhaseThreadLock;
779 
780         private static readonly TimeInterval DefaultQuantum = TimeInterval.FromMicroseconds(100);
781 
782         /// <summary>
783         /// Allows locking without starvation.
784         /// </summary>
785         protected class PrioritySynchronizer : IdentifiableObject, IDisposable
786         {
PrioritySynchronizer()787             public PrioritySynchronizer()
788             {
789                 innerLock = new object();
790             }
791 
792             /// <summary>
793             /// Used to obtain lock with low priority.
794             /// </summary>
795             /// <remarks>
796             /// Any thread already waiting on the lock with high priority is guaranteed to obtain it prior to this one.
797             /// There are no guarantees for many threads with the same priority.
798             /// </remarks>
799             public PrioritySynchronizer LowPriority
800             {
801                 get
802                 {
803                     // here we assume that `highPriorityRequestPending` will be reset soon,
804                     // so there is no point of using more complicated synchronization methods
805                     while(highPriorityRequestPendingCounter > 0) ;
806                     Monitor.Enter(innerLock);
807 
808                     return this;
809                 }
810             }
811 
812             /// <summary>
813             /// Used to obtain lock with high priority.
814             /// </summary>
815             /// <remarks>
816             /// It is guaranteed that the thread wanting to lock with high priority will not wait indefinitely if all other threads lock with low priority.
817             /// There are no guarantees for many threads with the same priority.
818             /// </remarks>
819             public PrioritySynchronizer HighPriority
820             {
821                 get
822                 {
823                     Interlocked.Increment(ref highPriorityRequestPendingCounter);
824                     Monitor.Enter(innerLock);
825                     Interlocked.Decrement(ref highPriorityRequestPendingCounter);
826                     return this;
827                 }
828             }
829 
Dispose()830             public void Dispose()
831             {
832                 Monitor.Exit(innerLock);
833             }
834 
WaitWhile(Func<bool> condition, string reason)835             public void WaitWhile(Func<bool> condition, string reason)
836             {
837                 innerLock.WaitWhile(condition, reason);
838             }
839 
Pulse()840             public void Pulse()
841             {
842                 Monitor.PulseAll(innerLock);
843             }
844 
845             private readonly object innerLock;
846             private volatile int highPriorityRequestPendingCounter;
847         }
848 
849         /// <summary>
850         /// Represents a time-variant value.
851         /// </summary>
852         private class TimeVariantValue
853         {
TimeVariantValue(int size)854             public TimeVariantValue(int size)
855             {
856                 buffer = new ulong[size];
857             }
858 
859             /// <summary> <summary>
860             /// Resets the value and clears the internal buffer.
861             /// </summary>
Reset(ulong value = 0)862             public void Reset(ulong value = 0)
863             {
864                 position = 0;
865                 CumulativeValue = 0;
866                 partialSum = 0;
867                 Array.Clear(buffer, 0, buffer.Length);
868 
869                 Update(value);
870             }
871 
872             /// <summary>
873             /// Updates the <see cref="RawValue">.
874             /// </summary>
Update(ulong value)875             public void Update(ulong value)
876             {
877                 RawValue = value;
878                 CumulativeValue += value;
879 
880                 partialSum += value;
881                 partialSum -= buffer[position];
882                 buffer[position] = value;
883                 position = (position + 1) % buffer.Length;
884             }
885 
886             public ulong RawValue { get; private set; }
887 
888             /// <summary>
889             /// Returns average of <see cref="RawValues"> over the last <see cref="size"> samples.
890             /// </summary>
891             public ulong AverageValue { get { return  (ulong)(partialSum / (ulong)buffer.Length); } }
892 
893             /// <summary>
894             /// Returns total sum of all <see cref="RawValues"> so far.
895             /// </summary>
896             public ulong CumulativeValue { get; private set; }
897 
898             private readonly ulong[] buffer;
899             private int position;
900             private ulong partialSum;
901         }
902 
903         /// <summary>
904         /// Represents a task that is scheduled for execution in the future.
905         /// </summary>
906         private struct DelayedTask : IComparable<DelayedTask>
907         {
DelayedTaskAntmicro.Renode.Time.TimeSourceBase.DelayedTask908             static DelayedTask()
909             {
910                 Zero = new DelayedTask();
911             }
912 
DelayedTaskAntmicro.Renode.Time.TimeSourceBase.DelayedTask913             public DelayedTask(Action<TimeStamp> what, TimeStamp when, ulong id) : this()
914             {
915                 What = what;
916                 When = when;
917                 Id = id;
918             }
919 
CompareToAntmicro.Renode.Time.TimeSourceBase.DelayedTask920             public int CompareTo(DelayedTask other)
921             {
922                 var result = When.TimeElapsed.CompareTo(other.When.TimeElapsed);
923                 return result != 0 ? result : Id.CompareTo(other.Id);
924             }
925 
926             public Action<TimeStamp> What { get; private set; }
927 
928             public TimeStamp When { get; private set; }
929 
930             public static DelayedTask Zero { get; private set; }
931 
932             public ulong Id { get; }
933         }
934 
935         /// <summary>
936         /// Allows to execute registered actions in serial or in parallel.
937         /// </summary>
938         private class PhaseExecutor<T>
939         {
PhaseExecutor()940             public PhaseExecutor()
941             {
942                 testPhases = new List<Func<T, Boolean>>();
943                 phases = new List<Action<T>>();
944             }
945 
RegisterPhase(Action<T> action)946             public void RegisterPhase(Action<T> action)
947             {
948                 phases.Add(action);
949             }
950 
RegisterTestPhase(Func<T, Boolean> predicate)951             public void RegisterTestPhase(Func<T, Boolean> predicate)
952             {
953                 testPhases.Add(predicate);
954             }
955 
ExecuteInSerial(IEnumerable<T> targets)956             public void ExecuteInSerial(IEnumerable<T> targets)
957             {
958                 if(phases.Count == 0)
959                 {
960                     return;
961                 }
962                 if(!ExecuteTestPhase(targets))
963                 {
964                     return;
965                 }
966 
967                 foreach(var target in targets)
968                 {
969                     foreach(var phase in phases)
970                     {
971                         phase(target);
972                     }
973                 }
974             }
975 
ExecuteInParallel(IEnumerable<T> targets)976             public void ExecuteInParallel(IEnumerable<T> targets)
977             {
978                 if(!ExecuteTestPhase(targets))
979                 {
980                     return;
981                 }
982                 foreach(var phase in phases)
983                 {
984                     foreach(var target in targets)
985                     {
986                         phase(target);
987                     }
988                 }
989             }
990 
ExecuteTestPhase(IEnumerable<T> targets)991             private bool ExecuteTestPhase(IEnumerable<T> targets)
992             {
993                 foreach(var test in testPhases)
994                 {
995                     foreach(var target in targets)
996                     {
997                         if(!test(target))
998                         {
999                             return false;
1000                         }
1001                     }
1002                 }
1003                 return true;
1004             }
1005 
1006             private readonly List<Func<T, Boolean>> testPhases;
1007             private readonly List<Action<T>> phases;
1008         }
1009     }
1010 }
1011