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