1 //
2 // Copyright (c) 2010-2025 Antmicro
3 // Copyright (c) 2011-2015 Realtime Embedded
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 using System;
9 using System.Collections.Concurrent;
10 using System.Collections.Generic;
11 using System.Diagnostics;
12 using System.IO;
13 using System.Linq;
14 using System.Reflection;
15 using System.Runtime.ExceptionServices;
16 using System.Runtime.InteropServices;
17 using System.Text;
18 using System.Threading;
19 using Antmicro.Migrant;
20 using Antmicro.Migrant.Hooks;
21 using Antmicro.Renode.Core;
22 using Antmicro.Renode.Debugging;
23 using Antmicro.Renode.Exceptions;
24 using Antmicro.Renode.Logging;
25 using Antmicro.Renode.Logging.Profiling;
26 using Antmicro.Renode.Peripherals.Bus;
27 using Antmicro.Renode.Peripherals.CPU.Disassembler;
28 using Antmicro.Renode.Peripherals.CPU.Registers;
29 using Antmicro.Renode.Time;
30 using Antmicro.Renode.Utilities;
31 using Antmicro.Renode.Utilities.Binding;
32 using ELFSharp.ELF;
33 using ELFSharp.UImage;
34 using Machine = Antmicro.Renode.Core.Machine;
35 
36 namespace Antmicro.Renode.Peripherals.CPU
37 {
38     /// <summary>
39     /// <see cref="BaseCPU"/> implements <see cref="ICluster{T}"/> interface
40     /// to seamlessly handle either cluster or CPU as a parameter to different methods.
41     /// </summary>
42     public abstract class BaseCPU : CPUCore, ICluster<BaseCPU>, ICPU, IDisposable, ITimeSink, IInitableCPU
43     {
BaseCPU(uint id, string cpuType, IMachine machine, Endianess endianness, CpuBitness bitness = CpuBitness.Bits32)44         protected BaseCPU(uint id, string cpuType, IMachine machine, Endianess endianness, CpuBitness bitness = CpuBitness.Bits32)
45             : base(id)
46         {
47             if(cpuType == null)
48             {
49                 throw new ConstructionException("cpuType was null");
50             }
51 
52             Endianness = endianness;
53             PerformanceInMips = 100;
54             this.Model = cpuType;
55             this.machine = machine;
56             this.bitness = bitness;
57             isPaused = true;
58 
59             singleStepSynchronizer = new Synchronizer();
60             EmulationManager.Instance.CurrentEmulation.SingleStepBlockingChanged += () => UpdateHaltedState();
61 
62             Clustered = new BaseCPU[] { this };
63         }
64 
InitFromElf(IELF elf)65         public virtual void InitFromElf(IELF elf)
66         {
67             if(elf.GetBitness() > (int)bitness)
68             {
69                 throw new RecoverableException($"Unsupported ELF format - trying to load a {elf.GetBitness()}-bit ELF on a {(int)bitness}-bit machine");
70             }
71 
72             this.Log(LogLevel.Info, "Setting PC value to 0x{0:X}.", elf.GetEntryPoint());
73             SetPCFromEntryPoint(elf.GetEntryPoint());
74         }
75 
InitFromUImage(UImage uImage)76         public virtual void InitFromUImage(UImage uImage)
77         {
78             this.Log(LogLevel.Info, "Setting PC value to 0x{0:X}.", uImage.EntryPoint);
79             SetPCFromEntryPoint(uImage.EntryPoint);
80         }
81 
Step(int count = 1)82         public ulong Step(int count = 1)
83         {
84             if(IsHalted)
85             {
86                 this.Log(LogLevel.Warning, "Ignoring stepping on a halted CPU");
87                 return PC;
88             }
89 
90             lock(singleStepSynchronizer.Guard)
91             {
92                 ExecutionMode = ExecutionMode.SingleStep;
93 
94                 // Starting emulation has to be done after changing ExecutionMode. Otherwise, continuous CPUs won't be waiting for step command.
95                 var emulation = EmulationManager.Instance.CurrentEmulation;
96                 if(!emulation.IsStarted)
97                 {
98                     var emulationCPUs = emulation.Machines.SelectMany(x => x.SystemBus.GetCPUs());
99                     var continuousCPUs = emulationCPUs.Where(x => x.ExecutionMode == ExecutionMode.Continuous);
100                     if(continuousCPUs.Any())
101                     {
102                         this.Log(LogLevel.Info, "Automatically starting all CPUs due to '{0}' including CPUs with {1} {2}: {3}", nameof(Step),
103                             ExecutionMode.Continuous, nameof(ExecutionMode), Misc.PrettyPrintCollection(continuousCPUs, x => x.GetName())
104                             );
105                     }
106                     emulation.StartAll();
107                 }
108                 else
109                 {
110                     Resume();
111                 }
112 
113                 this.Log(LogLevel.Noisy, "Stepping {0} step(s)", count);
114 
115                 var th = TimeHandle;
116                 if(th != null)
117                 {
118                     th.DeferredEnabled = true;
119                 }
120 
121                 // Invoking this to allow virtual time to be granted, without setting currentHaltedState to false
122                 if(!IsSingleStepBlocking)
123                 {
124                     UpdateHaltedState(ignoreExecutionMode: true);
125                 }
126 
127                 singleStepSynchronizer.CommandStep(count);
128                 singleStepSynchronizer.WaitForStepFinished();
129 
130                 UpdateHaltedState();
131 
132                 return PC;
133             }
134         }
135 
SkipTime(TimeInterval amountOfTime)136         public void SkipTime(TimeInterval amountOfTime)
137         {
138             var instructions = amountOfTime.ToCPUCycles(PerformanceInMips, out var residuum);
139             if(residuum > 0)
140             {
141                 // We want to execute instructions for at least required amount of time, so we should add
142                 // instructions += ceil(residuum / TicksPerMicrosecond) * PerformanceInMips
143                 // As residuum < TicksPerMicrosecond by definition, ceiling of it will be always 1
144                 instructions += PerformanceInMips;
145                 var newInterval = TimeInterval.FromCPUCycles(instructions, PerformanceInMips, out var _);
146                 this.Log(LogLevel.Warning, "Conversion from time to instructions is not exact, real time skipped: {0}", newInterval);
147             }
148             SkipInstructions += instructions;
149         }
150 
Dispose()151         public virtual void Dispose()
152         {
153             DisposeInner();
154         }
155 
Reset()156         public virtual void Reset()
157         {
158             isAborted = false;
159             Pause();
160             EmulationState = EmulationCPUState.InReset;
161         }
162 
SyncTime()163         public virtual void SyncTime()
164         {
165             // by default do nothing
166         }
167 
ExecuteInstructions(ulong numberOfInstructionsToExecute, out ulong numberOfExecutedInstructions)168         public abstract ExecutionResult ExecuteInstructions(ulong numberOfInstructionsToExecute, out ulong numberOfExecutedInstructions);
169 
170         public abstract string Architecture { get; }
171 
172         public Endianess Endianness { get; }
173 
174         public IBusController Bus => machine.SystemBus;
175 
176         public IEnumerable<ICluster<BaseCPU>> Clusters { get; } = new List<ICluster<BaseCPU>>(0);
177 
178         public IEnumerable<BaseCPU> Clustered { get; }
179 
180         public string Model { get; }
181 
182         public uint PerformanceInMips
183         {
184             get => performanceInMips.Value;
185             set => performanceInMips.Value = value;
186         }
187 
188         //The debug mode disables interrupt handling in the emulated CPU
189         //Additionally, some instructions, suspending execution, until an interrupt arrives (e.g. HLT on x86 or WFI on ARM) are treated as NOP
190         public virtual bool ShouldEnterDebugMode
191         {
192             get => shouldEnterDebugMode;
193             set
194             {
195                 if(value == true && !(DebuggerConnected && IsSingleStepMode))
196                 {
197                     this.Log(LogLevel.Warning, "The debug mode now has no effect - connect a debugger, and switch to stepping mode.");
198                 }
199                 shouldEnterDebugMode = value;
200             }
201         }
202 
203         public bool OnPossessedThread
204         {
205             get
206             {
207                 var cpuThreadLocal = cpuThread;
208                 return cpuThreadLocal != null && Thread.CurrentThread.ManagedThreadId == cpuThreadLocal.ManagedThreadId;
209             }
210         }
211 
212         public bool DebuggerConnected { get; set; }
213 
214         public override bool IsHalted
215         {
216             get
217             {
218                 return isHaltedRequested;
219             }
220             set
221             {
222                 this.Trace();
223                 if(value == isHaltedRequested)
224                 {
225                     return;
226                 }
227 
228                 lock(pauseLock)
229                 {
230                     this.Trace();
231                     isHaltedRequested = value;
232                     UpdateHaltedState();
233 
234                     if(value)
235                     {
236                         if(started && !isPaused)
237                         {
238                             wasRunningWhenHalted = true;
239                             Pause(new HaltArguments(HaltReason.Pause, this), checkPauseGuard: false);
240                         }
241                     }
242                     else
243                     {
244                         if(EmulationState == EmulationCPUState.InReset)
245                         {
246                             EmulationState = EmulationCPUState.Running;
247                         }
248 
249                         if(wasRunningWhenHalted)
250                         {
251                             Resume();
252                         }
253                     }
254                 }
255             }
256         }
257 
258         /// <remarks><c>StateChanged</c> is invoked when the value gets changed.</remarks>
259         public EmulationCPUState EmulationState
260         {
261             get => state;
262 
263             private set
264             {
265                 var oldState = state;
266                 if(oldState == value)
267                 {
268                     return;
269                 }
270                 state = value;
271                 if(oldState == EmulationCPUState.InReset)
272                 {
273                     OnLeavingResetState();
274                 }
275                 StateChanged?.Invoke(this, oldState, value);
276             }
277         }
278 
279         public TimeHandle TimeHandle
280         {
281             get
282             {
283                 return timeHandle;
284             }
285             set
286             {
287                 this.Trace("Setting a new time handle");
288                 timeHandle?.Dispose();
289                 lock(haltedLock)
290                 {
291                     timeHandle = value;
292                     timeHandle.Enabled = !currentHaltedState;
293                     timeHandle.PauseRequested += RequestPause;
294                     timeHandle.StartRequested += StartCPUThreadTimeHandle;
295                 }
296             }
297         }
298 
299         public ulong SkippedInstructions { get; private set; }
300 
301         public virtual ExecutionMode ExecutionMode
302         {
303             get
304             {
305                 return executionMode;
306             }
307 
308             set
309             {
310                 lock(singleStepSynchronizer.Guard)
311                 {
312                     if(executionMode == value)
313                     {
314                         return;
315                     }
316 
317                     executionMode = value;
318 
319                     singleStepSynchronizer.Enabled = IsSingleStepMode;
320                     UpdateHaltedState();
321                 }
322             }
323         }
324 
325         public event Action<HaltArguments> Halted;
326 
327         /// <remarks>The arguments passed are: <c>StateChanged(cpu, oldState, newState)</c>.</remarks>
328         public event Action<ICPU, EmulationCPUState, EmulationCPUState> StateChanged;
329 
330         public abstract ulong ExecutedInstructions { get; }
331         public abstract RegisterValue PC { get; set; }
332 
333         public bool IsPaused => isPaused;
334 
InnerPause(bool onCpuThread, bool checkPauseGuard)335         protected virtual void InnerPause(bool onCpuThread, bool checkPauseGuard)
336         {
337             RequestPause();
338 
339             if(!onCpuThread)
340             {
341                 bool success = false;
342                 do
343                 {
344                     const int startPauseDeadlockResolveTimeoutMs = 100;
345                     TimeHandle.Interrupt(ref success, startPauseDeadlockResolveTimeoutMs);
346                     if(!success)
347                     {
348                         /// We weren't able to interrupt the <see cref="TimeHandle"/>.
349                         /// Check marker to distinguish a usual timeout from a deadlock.
350                         if(pauseLockTimeHandleMarker)
351                         {
352                             /// We have a deadlock with other thread in <see cref="StartCPUThreadTimeHandle"/>.
353                             /// Release <see cref="pauseLock"/> and try <see cref="TimeHandle.Interrupt"/> again later after the deadlock is resolved.
354                             /// In rare cases we expect a deadlock as a result of starting and pausing cpu from different threads.
355                             /// We use <see cref="Monitor.Pulse"/> on <see cref="pauseLock"/> only in <see cref="StartCPUThreadTimeHandle"/>
356                             /// which is a verified case of the race between time source and Monitor thread.
357                             /// Clear marker before releasing lock to select short path in <see cref="StartCPUThreadTimeHandle"/>.
358                             pauseLockTimeHandleMarker = false;
359                             Monitor.Wait(pauseLock);
360                             /// We should get control back very soon due to short path in <see cref="StartCPUThreadTimeHandle"/>.
361                             DebugHelper.Assert(pauseLockTimeHandleMarker);
362                             pauseLockTimeHandleMarker = false;
363                         }
364                         else
365                         {
366                             /// We have a usual timeout. Try <see cref="TimeHandle.Interrupt"/> again.
367                             /// Leave a warning here, as this may require special attention performance-wise.
368                             this.Log(LogLevel.Warning, "Trying to stop CPU execution, but it is taking longer than expected...");
369                         }
370                     }
371                 }
372                 while(!success);
373             }
374         }
375 
Pause(HaltArguments haltArgs, bool checkPauseGuard)376         protected virtual void Pause(HaltArguments haltArgs, bool checkPauseGuard)
377         {
378             if(isAborted || isPaused)
379             {
380                 // cpu is already paused or aborted
381                 return;
382             }
383 
384             lock(pauseLock)
385             {
386                 // cpuThread can get null as a result of `InnerPause` call
387                 var cpuThreadCopy = cpuThread;
388                 var onCpuThread = (cpuThreadCopy != null && Thread.CurrentThread.ManagedThreadId == cpuThreadCopy.ManagedThreadId);
389 
390                 InnerPause(onCpuThread, checkPauseGuard);
391 
392                 if(!onCpuThread)
393                 {
394                     singleStepSynchronizer.Enabled = false;
395                     this.NoisyLog("Waiting for thread to pause.");
396                     cpuThreadCopy?.Join();
397                     this.NoisyLog("Paused.");
398                 }
399 
400                 isPaused = true;
401             }
402 
403             InvokeHalted(haltArgs);
404         }
405 
ReportProgress(ulong instructions)406         protected void ReportProgress(ulong instructions)
407         {
408             if(instructions > 0)
409             {
410                 instructionsLeftThisRound -= instructions;
411                 instructionsExecutedThisRound += instructions;
412                 // CPU is `executedResiduum` instructions ahead of the reported time and this value is smaller than the smallest positive possible amount to report,
413                 // so we report sum of currently executed/skipped instructions and residuum from previously reported progress.
414                 var intervalToReport = TimeInterval.FromCPUCycles(instructions + executedResiduum, PerformanceInMips, out executedResiduum);
415                 TimeHandle.ReportProgress(intervalToReport);
416             }
417         }
418 
OnLeavingResetState()419         protected virtual void OnLeavingResetState()
420         {
421             // Intentionally left blank.
422         }
423 
OnResume()424         protected override void OnResume()
425         {
426             if(EmulationState == EmulationCPUState.InReset && !currentHaltedState)
427             {
428                 EmulationState = EmulationCPUState.Running;
429             }
430             singleStepSynchronizer.Enabled = IsSingleStepMode;
431             StartCPUThread();
432         }
433 
OnPause()434         protected override void OnPause()
435         {
436             Pause(new HaltArguments(HaltReason.Pause, this), checkPauseGuard: true);
437         }
438 
RequestPause()439         protected virtual void RequestPause()
440         {
441             lock(pauseLock)
442             {
443                 isPaused = true;
444                 this.Trace("Requesting pause");
445                 sleeper.Interrupt();
446             }
447         }
448 
DisposeInner(bool silent = false)449         protected virtual void DisposeInner(bool silent = false)
450         {
451             // Take a copy of the CPU thread because it will be cleared at the end of its body
452             var cpuThreadCopy = cpuThread;
453             disposing = true;
454             if(!silent)
455             {
456                 this.NoisyLog("About to dispose CPU.");
457             }
458             started = false;
459             Pause(new HaltArguments(HaltReason.Abort, this), checkPauseGuard: false);
460             singleStepSynchronizer.Enabled = false;
461             cpuThreadCopy?.Join();
462         }
463 
InvokeHalted(HaltArguments arguments)464         protected void InvokeHalted(HaltArguments arguments)
465         {
466             var halted = Halted;
467             if(halted != null)
468             {
469                 halted(arguments);
470             }
471         }
472 
CpuThreadBody()473         protected virtual void CpuThreadBody()
474         {
475             var isLocked = false;
476             try
477             {
478 #if DEBUG
479                 using(this.TraceRegion("CPU loop"))
480 #endif
481                 using(var activityTracker = (DisposableWrapper)this.ObtainSinkActiveState())
482                 using(TimeDomainsManager.Instance.RegisterCurrentThread(() => new TimeStamp(TimeHandle.TotalElapsedTime, TimeHandle.TimeSource.Domain)))
483                 {
484                     try
485                     {
486 restart:
487                         while(!isPaused && !isAborted)
488                         {
489                             var singleStep = false;
490                             // locking here is to ensure that execution mode does not change
491                             // before calling `WaitForStepCommand` method
492                             lock(singleStepSynchronizer.Guard)
493                             {
494                                 singleStep = IsSingleStepMode;
495                                 if(singleStep)
496                                 {
497                                     // we become incactive as we wait for step command
498                                     using(this.ObtainSinkInactiveState())
499                                     {
500                                         this.Log(LogLevel.Noisy, "Waiting for a step instruction (PC=0x{0:X8}).", PC.RawValue);
501                                         InvokeHalted(new HaltArguments(HaltReason.Step, this));
502                                         if(!singleStepSynchronizer.WaitForStepCommand())
503                                         {
504                                             this.Trace();
505                                             continue;
506                                         }
507                                         this.Trace();
508                                     }
509                                 }
510                             }
511 
512                             var cpuResult = CpuThreadBodyInner(singleStep);
513 
514                             if(singleStep)
515                             {
516                                 switch(cpuResult)
517                                 {
518                                     case CpuResult.NothingExecuted:
519                                         break;
520                                     case CpuResult.MmuFault:
521                                         this.Trace("Interrupting stepping due to the external MMU fault");
522                                         singleStepSynchronizer.StepInterrupted();
523                                         break;
524                                     default:
525                                         this.Trace();
526                                         singleStepSynchronizer.StepFinished();
527                                         break;
528                                 }
529                             }
530                         }
531 
532                         this.Trace();
533                         lock(cpuThreadBodyLock)
534                         {
535                             if(dispatcherRestartRequested)
536                             {
537                                 dispatcherRestartRequested = false;
538                                 this.Trace();
539                                 goto restart;
540                             }
541 
542                             this.Trace();
543                             // the `locker` is re-acquired here to
544                             // make sure that dispose-related code of all usings
545                             // is executed before setting `dispatcherThread` to
546                             // null (what allows to start new dispatcher thread);
547                             // otherwise there could be a race condition when
548                             // new thread enters usings (e.g., activates sink side)
549                             // and then the old one exits them (deactivating sink
550                             // side as a result)
551                             Monitor.Enter(cpuThreadBodyLock, ref isLocked);
552                         }
553                     }
554                     catch(Exception)
555                     {
556                         // being here means we are in trouble anyway,
557                         // so we don't have to care about the time framework
558                         // protocol that much;
559                         // without disabling activity tracker
560                         // it will try to disable the time handle
561                         // which might in turn crash with it's own
562                         // exception (hiding the original one)
563                         activityTracker.Disable();
564                         throw;
565                     }
566                 }
567             }
568             finally
569             {
570                 cpuThread = null;
571                 if(isLocked)
572                 {
573                     this.Trace();
574                     Monitor.Exit(cpuThreadBodyLock);
575                 }
576                 this.Trace();
577             }
578         }
579 
ExecutionFinished(ExecutionResult result)580         protected virtual bool ExecutionFinished(ExecutionResult result)
581         {
582             return false;
583         }
584 
CpuThreadBodyInner(bool singleStep)585         protected CpuResult CpuThreadBodyInner(bool singleStep)
586         {
587             if(!TimeHandle.RequestTimeInterval(out var interval))
588             {
589                 this.Trace();
590                 return CpuResult.NothingExecuted;
591             }
592 
593             using(performanceInMips.Seal())
594             {
595                 this.Trace($"CPU thread body running... granted {interval.Ticks} ticks");
596                 var mmuFaultThrown = false;
597                 var initialExecutedResiduum = executedResiduum;
598                 var initialTotalElapsedTime = TimeHandle.TotalElapsedTime;
599                 TimeInterval virtualTimeAhead;
600 
601                 var instructionsToExecuteThisRound = interval.ToCPUCycles(PerformanceInMips, out ulong ticksResiduum);
602                 if(instructionsToExecuteThisRound <= executedResiduum)
603                 {
604                     this.Trace("not enough time granted, reporting continue");
605                     TimeHandle.ReportBackAndContinue(interval);
606                     return CpuResult.NothingExecuted;
607                 }
608                 instructionsLeftThisRound = Math.Min(instructionsToExecuteThisRound - executedResiduum, singleStep ? 1 : ulong.MaxValue);
609                 instructionsExecutedThisRound = executedResiduum;
610 
611                 while(!isPaused && !currentHaltedState && instructionsLeftThisRound > 0)
612                 {
613                     this.Trace($"CPU thread body in progress; {instructionsLeftThisRound} instructions left...");
614 
615                     // this puts a limit on instructions to execute in one round
616                     // and makes timers update independent of the current quantum
617                     var toExecute = Math.Min(InstructionsToNearestLimit(), instructionsLeftThisRound);
618 
619                     if(skipInstructions > 0)
620                     {
621                         var amountOfInstructions = Math.Min(skipInstructions, toExecute);
622                         this.Trace($"Skipping {amountOfInstructions} instructions");
623 
624                         toExecute -= amountOfInstructions;
625                         skipInstructions -= amountOfInstructions;
626                         SkippedInstructions += amountOfInstructions;
627                         ReportProgress(amountOfInstructions);
628                         // We have to update progress immidietely, as we could potentially
629                         // call SyncTime during ExecuteInstructions
630                     }
631 
632                     // set upper limit on instructions to execute to `int.MaxValue`
633                     // as otherwise it would overflow further down in ExecuteInstructions
634                     toExecute = Math.Min(toExecute, int.MaxValue);
635                     var result = ExecutionResult.Ok;
636                     if(toExecute > 0)
637                     {
638                         this.Trace($"Asking CPU to execute {toExecute} instructions");
639 
640                         result = ExecuteInstructions(toExecute, out var executed);
641                         this.Trace($"CPU executed {executed} instructions and returned {result}");
642                         machine.Profiler?.Log(new InstructionEntry(machine.SystemBus.GetCPUSlot(this), ExecutedInstructions));
643                         ReportProgress(executed);
644                     }
645                     if(ExecutionFinished(result))
646                     {
647                         break;
648                     }
649 
650                     if(result == ExecutionResult.WaitingForInterrupt)
651                     {
652                         if(!InDebugMode && !neverWaitForInterrupt)
653                         {
654                             this.Trace();
655                             var instructionsToSkip = Math.Min(InstructionsToNearestLimit(), instructionsLeftThisRound);
656 
657                             virtualTimeAhead = machine.LocalTimeSource.ElapsedVirtualHostTimeDifference;
658                             if(!machine.LocalTimeSource.AdvanceImmediately && virtualTimeAhead.Ticks > 0 && instructionsToSkip > 0)
659                             {
660                                 // Don't fall behind realtime by sleeping
661                                 var intervalToSleep = TimeInterval.FromCPUCycles(instructionsToSkip, PerformanceInMips, out var cyclesResiduum).WithTicksMin(virtualTimeAhead.Ticks);
662                                 sleeper.Sleep(intervalToSleep.ToTimeSpan(out var nsResiduum), out var intervalSlept);
663                                 // If we have a CPU of less than 10 MIPS, it might be the case that the interval slept (which is in units of 100 ns)
664                                 // is less than 1 instruction. Just round up it in this case.
665                                 instructionsToSkip = Math.Max(TimeInterval.FromTimeSpan(intervalSlept, nsResiduum).ToCPUCycles(PerformanceInMips, out var _) + cyclesResiduum, 1);
666                             }
667 
668                             ReportProgress(instructionsToSkip);
669                         }
670                     }
671                     else if(result == ExecutionResult.ExternalMmuFault)
672                     {
673                         this.Trace(result.ToString());
674                         mmuFaultThrown = true;
675                         break;
676                     }
677                     else if(result == ExecutionResult.Aborted)
678                     {
679                         this.Trace(result.ToString());
680                         isAborted = true;
681                         break;
682                     }
683                     else if(result == ExecutionResult.Interrupted || result == ExecutionResult.StoppedAtWatchpoint)
684                     {
685                         this.Trace(result.ToString());
686                         break;
687                     }
688                 }
689 
690                 // If AdvanceImmediately is not enabled, and virtual time has surpassed host time,
691                 // sleep to make up the difference.
692                 // However, if pause was requested, we want to exit as soon as possible without sleeping,
693                 // because it was requested from interactive context (e.g. "pause" monitor command).
694                 virtualTimeAhead = machine.LocalTimeSource.ElapsedVirtualHostTimeDifference;
695                 if(!machine.LocalTimeSource.AdvanceImmediately && virtualTimeAhead.Ticks > 0 && !isPaused)
696                 {
697                     // Ignore the return value, if the sleep is interrupted we'll make up any extra
698                     // remaining difference next time. Preserve the interrupt request so that if this
699                     // extra sleep is interrupted due to a CPU pause, it will be picked up by the WFI
700                     // handling above.
701                     sleeper.Sleep(virtualTimeAhead.ToTimeSpan(), out var _, preserveInterruptRequest: true);
702                 }
703 
704                 this.Trace("CPU thread body finished");
705 
706                 if(isAborted)
707                 {
708                     this.Trace("aborted, reporting continue");
709                     TimeHandle.ReportBackAndContinue(TimeInterval.Empty);
710                     executedResiduum = 0;
711                     EmulationState = EmulationCPUState.Aborted;
712                     return CpuResult.Aborted;
713                 }
714                 else if(currentHaltedState)
715                 {
716                     this.Trace("halted, reporting continue");
717                     TimeHandle.ReportBackAndContinue(TimeInterval.Empty);
718                     executedResiduum = 0;
719                 }
720                 else
721                 {
722                     var instructionsLeft = instructionsToExecuteThisRound - instructionsExecutedThisRound;
723                     // instructionsExecutedThisRound = reportedInstructions + executedResiduum
724                     // reportedInstructions + executedResiduum + instructionsLeft = instructionsToExecuteThisRound
725                     // reportedInstructions is divisible by instructionsPerTick and instructionsToExecuteThisRound is divisible by instructionsPerTick
726                     // so instructionsLeft + executedResiduum is divisible by instructionsPerTick and residuum is 0
727                     var timeLeft = TimeInterval.FromCPUCycles(instructionsLeft + executedResiduum, PerformanceInMips, out var residuum) + TimeInterval.FromMicroseconds(ticksResiduum);
728                     DebugHelper.Assert(residuum == 0);
729                     if(instructionsLeft > 0)
730                     {
731                         this.Trace("reporting break");
732                         TimeHandle.ReportBackAndBreak(timeLeft);
733                     }
734                     else
735                     {
736                         DebugHelper.Assert(executedResiduum == 0);
737                         // executedResiduum < instructionsPerTick so timeLeft is 0 + ticksResiduum
738                         this.Trace("finished, reporting continue");
739                         TimeHandle.ReportBackAndContinue(timeLeft);
740                     }
741                 }
742 
743                 if(mmuFaultThrown)
744                 {
745                     return CpuResult.MmuFault;
746                 }
747                 else if(executedResiduum == initialExecutedResiduum && TimeHandle.TotalElapsedTime == initialTotalElapsedTime)
748                 {
749                     return CpuResult.NothingExecuted;
750                 }
751                 return CpuResult.ExecutedInstructions;
752             }
753         }
754 
StartCPUThreadTimeHandle()755         private void StartCPUThreadTimeHandle()
756         {
757             this.Trace();
758             pauseLockTimeHandleMarker = true;
759             lock(pauseLock)
760             {
761                 if(!pauseLockTimeHandleMarker)
762                 {
763                     /// Marker value got changed by other thread in <see cref="InnerPause"/>.
764                     /// We escaped from a deadlock and we are going to be interrupted by <see cref="TimeHandle.Interrupt"/>.
765                     /// It means there is a race between starting and pausing cpu from different threads.
766                     /// We allow pausing to win in such case because pausing cpu is more "explicit" operation
767                     /// than starting cpu as part of <see cref="TimeHandle.StartRequested"/>.
768                     /// We were allowed to enter this critical region guarded by <see cref="pauseLock"/> due to special circumstances
769                     /// despite <see cref="pauseLock"/> having previously been held by <see cref="Pause"/> + <see cref="InnerPause"/>.
770                     /// Signal that we escaped deadlock condition and return control to the other thread waiting in <see cref="InnerPause"/>.
771                     pauseLockTimeHandleMarker = true;
772                     Monitor.Pulse(pauseLock);
773                     return;
774                 }
775                 pauseLockTimeHandleMarker = false; // clear marker
776                 StartCPUThreadInner();
777             }
778         }
779 
StartCPUThread()780         protected void StartCPUThread()
781         {
782             this.Trace();
783             lock(pauseLock)
784             {
785                 StartCPUThreadInner();
786             }
787         }
788 
StartCPUThreadInner()789         private void StartCPUThreadInner()
790         {
791             lock(cpuThreadBodyLock)
792             {
793                 if(isAborted)
794                 {
795                     return;
796                 }
797                 if(cpuThread == null)
798                 {
799                     this.Trace();
800                     cpuThread = new Thread(CpuThreadBody)
801                     {
802                         IsBackground = true,
803                         Name = this.GetCPUThreadName(machine)
804                     };
805                     cpuThread.Start();
806                 }
807                 else
808                 {
809                     this.Trace();
810                     dispatcherRestartRequested = true;
811                 }
812             }
813         }
814 
CheckIfOnSynchronizedThread()815         protected void CheckIfOnSynchronizedThread()
816         {
817             if(Thread.CurrentThread.ManagedThreadId != cpuThread.ManagedThreadId)
818             {
819                 this.Log(LogLevel.Warning, "An interrupt from the unsynchronized thread.");
820             }
821         }
822 
823         [Conditional("DEBUG")]
CheckCpuThreadId()824         protected void CheckCpuThreadId()
825         {
826             if(Thread.CurrentThread != cpuThread)
827             {
828                 throw new ArgumentException(
829                     string.Format("Method called from a wrong thread. Expected {0}, but got {1}",
830                                   cpuThread.ManagedThreadId, Thread.CurrentThread.ManagedThreadId));
831             }
832         }
833 
UpdateHaltedState(bool ignoreExecutionMode = false)834         protected virtual bool UpdateHaltedState(bool ignoreExecutionMode = false)
835         {
836             var shouldBeHalted = isHaltedRequested || (IsSingleStepMode && !IsSingleStepBlocking && !ignoreExecutionMode);
837 
838             if(shouldBeHalted == currentHaltedState)
839             {
840                 return false;
841             }
842 
843             lock(pauseLock)
844             {
845                 this.Trace();
846                 currentHaltedState = shouldBeHalted;
847                 if(TimeHandle != null)
848                 {
849                     this.Trace();
850                     TimeHandle.DeferredEnabled = !shouldBeHalted;
851                 }
852             }
853 
854             return true;
855         }
856 
857         public virtual ulong SkipInstructions
858         {
859             get => skipInstructions;
860             protected set => skipInstructions = value;
861         }
862 
863         protected static bool IsSingleStepBlocking => EmulationManager.Instance.CurrentEmulation.SingleStepBlocking;
864 
865         protected bool InDebugMode => DebuggerConnected && ShouldEnterDebugMode && IsSingleStepMode;
866         protected bool IsSingleStepMode => executionMode == ExecutionMode.SingleStep;
867 
868         protected bool shouldEnterDebugMode;
869         protected bool neverWaitForInterrupt;
870         protected bool dispatcherRestartRequested;
871         protected bool isHaltedRequested;
872         protected bool currentHaltedState;
873 
874         [Transient]
875         protected ExecutionMode executionMode;
876 
877         [Transient]
878         protected bool disposing;
879 
880         [Constructor]
881         protected readonly Synchronizer singleStepSynchronizer;
882 
883         protected readonly Sleeper sleeper = new Sleeper();
884         protected readonly CpuBitness bitness;
885         protected readonly IMachine machine;
886 
887         protected enum CpuResult
888         {
889             ExecutedInstructions = 0,
890             NothingExecuted = 1,
891             MmuFault = 2,
892             Aborted = 3,
893         }
894 
895         protected class RegisterAttribute : Attribute
896         {
897         }
898 
InstructionsToNearestLimit()899         private ulong InstructionsToNearestLimit()
900         {
901             var nearestLimitIn = ((BaseClockSource)machine.ClockSource).NearestLimitIn;
902             var instructionsToNearestLimit = nearestLimitIn.ToCPUCycles(PerformanceInMips, out var unused);
903             // the limit must be reached or surpassed for limit's owner to execute
904             if(instructionsToNearestLimit <= executedResiduum)
905             {
906                 return 1;
907             }
908             instructionsToNearestLimit -= executedResiduum;
909             if(instructionsToNearestLimit != ulong.MaxValue && (nearestLimitIn.Ticks == 0 || unused > 0))
910             {
911                 // we must check for `ulong.MaxValue` as otherwise it would overflow
912                 instructionsToNearestLimit++;
913             }
914             return instructionsToNearestLimit;
915         }
916 
SetPCFromEntryPoint(ulong entryPoint)917         private void SetPCFromEntryPoint(ulong entryPoint)
918         {
919             var what = machine.SystemBus.WhatIsAt(entryPoint, this);
920             if(what != null)
921             {
922                 if(((what.Peripheral as IMemory) == null) && ((what.Peripheral as Redirector) != null))
923                 {
924                     var redirector = what.Peripheral as Redirector;
925                     var newValue = redirector.TranslateAbsolute(entryPoint);
926                     this.Log(LogLevel.Info, "Fixing PC address from 0x{0:X} to 0x{1:X}", entryPoint, newValue);
927                     entryPoint = newValue;
928                 }
929             }
930             PC = entryPoint;
931         }
932 
933         [Transient]
934         private Thread cpuThread;
935 
936         private EmulationCPUState state = EmulationCPUState.InReset;
937         private TimeHandle timeHandle;
938 
939         private bool wasRunningWhenHalted;
940         private ulong executedResiduum;
941         private ulong instructionsLeftThisRound;
942         private ulong instructionsExecutedThisRound;
943         private ulong skipInstructions;
944 
945         [Transient]
946         private volatile bool pauseLockTimeHandleMarker;
947 
948         private readonly object cpuThreadBodyLock = new object();
949         private readonly SealableValue<uint> performanceInMips = new SealableValue<uint>();
950     }
951 }
952