///////////////////////////////////////////////////////////////////////////////
//
//  Microsoft Research Singularity
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
//  File:   MpExecution.cs
//
//  Note:
//          The purpose of this class is to control the execution state of
//  processors on MP systems.  Specifically, to stop processors running
//  while the debugger is active and while a GC is running.
//
//  NB These functions may be called with the debugger
//  communications lock held.  Any calls to DebugStub.Print or
//  its ilk will cause processor to spin on lock forever.

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;

using Microsoft.Singularity.Hal;
using Microsoft.Singularity.Io;
using Microsoft.Singularity.Isal;

namespace Microsoft.Singularity
{
    [CLSCompliant(false)]
    [AccessedByRuntime("referenced from halkd.cpp")]
    public class MpExecution
    {
        // Enumeration of Processor states
        const int Uninitialized = 0x00;   // Processor state uninitialized
        const int Running       = 0x01;   // Processor running normally
        const int TargetFrozen  = 0x02;   // Processor should stop
        const int TargetThaw    = 0x04;   // Processor should continue
        const int FreezeActive  = 0x08;   // Processor is active during freeze
        const int FreezeOwner   = 0x10;   // Processor initiating freeze

        static internal volatile bool FreezeRequested;

        static SpinLock                 freezeLock;
        static volatile int             freezeCount;
        static unsafe ProcessorContext* activeCpuContext;
        static unsafe ProcessorContext* ownerCpuContext;

        [ NoHeapAllocation ]
        static internal unsafe void AddProcessorContext(ProcessorContext* context)
        {
            // Add processor to list of processors in MP system.  Careful
            // to avoid adding processor mid-freeze or without lock.
          start:
            freezeLock.Acquire();
            try {
                if (FreezeRequested)
                    goto start;
                ProcessorContext* head = Processor.processorTable[0].context;

                context->nextProcessorContext = head->nextProcessorContext;
                head->nextProcessorContext = context;
                context->ipiFreeze = Running;
                // From this point on the processor is visible
                // in the debugger
                DebugStub.AddProcessor(context->cpuRecord.id);
            }
            finally {
                freezeLock.Release();
            }
        }
#if NOP
        [ AccessedByRuntime("referenced from halkd.cpp") ]
        [ NoHeapAllocation ]
        static internal unsafe void FreezeAllProcessors()
        {
            freezeLock.Acquire();
            try {
                freezeCount++;
                if (FreezeRequested == true) {
                    // Processors are already frozen
                    return;
                }

                ownerCpuContext  = Processor.GetCurrentProcessorContext();
                activeCpuContext = ownerCpuContext;
                if (activeCpuContext->displacedProcessorContext->nextProcessorContext == activeCpuContext) {
                    return;
                }
                FreezeRequested = true;

                activeCpuContext->displacedProcessorContext->ipiFreeze = FreezeActive | FreezeOwner;
//TODOMP                Platform.FreezeProcessors();

                // Wait until all running processors have gone into freeze
                ProcessorContext* context = activeCpuContext->displacedProcessorContext->nextProcessorContext;
                do
                {
                    if ((context->displacedProcessorContext->ipiFreeze & (TargetFrozen | FreezeActive)) != 0) {
                        context = context->displacedProcessorContext->nextProcessorContext;
                    }
                } while (context->displacedProcessorContext->nextProcessorContext != activeCpuContext);
            }
            finally {
                freezeLock.Release();
            }
        }
#else

        [ AccessedByRuntime("referenced from halkd.cpp") ]
        [ NoHeapAllocation ]
        [ Conditional("SINGULARITY_MP") ]
        static internal unsafe void FreezeAllProcessors()
        {
            // This method is only called on MP systems when the
            // number of running processors is greater than 1.
            freezeLock.Acquire();
            try {
                freezeCount++;
                if (FreezeRequested == true) {
                    // Processors are already frozen
                    return;
                }

                ownerCpuContext  = Processor.GetCurrentProcessorContext();
                activeCpuContext = ownerCpuContext;
                if (activeCpuContext->nextProcessorContext == activeCpuContext) {
                    return;
                }
                FreezeRequested = true;

                activeCpuContext->ipiFreeze = FreezeActive | FreezeOwner;
                // Generate an NMI on all processors (except this one)
                Platform.ThePlatform.FreezeAllCpus();

                // Wait until all running processors have gone into freeze
                ProcessorContext* context = activeCpuContext->nextProcessorContext;
                do
                {
                    if ((context->ipiFreeze & (TargetFrozen | FreezeActive)) != 0) {
                        context = context->nextProcessorContext;
                    }
                } while (context != activeCpuContext);
            }
            finally {
                freezeLock.Release();
            }
        }
#endif

        [ NoHeapAllocation ]
        static internal unsafe void
        FreezeProcessor(ref SpillContext threadContext)
        {
            ProcessorContext* context = Processor.GetCurrentProcessorContext();

            if (context->ipiFreeze == Uninitialized) {
                // Processor was still being initialized when IPI issued
                while (FreezeRequested == true)
                    ; // just spin until thaw occurs
                return;
            }

            context->ipiFreeze = TargetFrozen;

            while (context->ipiFreeze != TargetThaw && FreezeRequested) {
                if ((context->ipiFreeze & FreezeActive) == FreezeActive) {
                    //
                    // This processor has been made the active processor
                    //
                    activeCpuContext = context;

                    // Pass state over to debugger stub
                    if (DebugStub.TrapForProcessorSwitch(ref threadContext)) {
                        // We're returning to Freeze owner, make it active
                        context->ipiFreeze         &= ~FreezeActive;
                        ownerCpuContext->ipiFreeze |= FreezeActive;
                    }
                }
            }

            context->ipiFreeze = Running;
        }

        // Return true maps to ContinueNextProcessor
        // Return false maps to ContinueProcessorReselected
        [ AccessedByRuntime("referenced from halkd.cpp") ]
        [ NoHeapAllocation ]
        static internal unsafe bool SwitchFrozenProcessor(int cpuId)
        {
            ProcessorContext* currentContext = Processor.GetCurrentProcessorContext();
            if (currentContext->cpuRecord.id == cpuId) {
                // No processor to switch to.
                return false;
            }

            ProcessorContext* context = currentContext->nextProcessorContext;
            do
            {
                if (context->cpuRecord.id == cpuId) {
                    currentContext->ipiFreeze &= ~FreezeActive;
                    context->ipiFreeze |= FreezeActive;
                    goto WaitForWakeupOrReselection;
                }
                context = context->nextProcessorContext;
            } while (context != activeCpuContext);

            // cpuId not found so no processor to switch to.
            return false;

          WaitForWakeupOrReselection:
            // If this processor is in the frozen state already, return
            // (it'll fall back into FreezeProcessor() loop). NB Initiating
            // processor can never be in this state.
            if (currentContext->ipiFreeze == TargetFrozen) {
                return true;
            }

            // This processor is the freeze owner, wait to be
            // reselected as the active processor.
            while ((currentContext->ipiFreeze & FreezeActive) != FreezeActive)
                ;

            return false;
        }

        [ AccessedByRuntime("referenced from halkd.cpp") ]
        [ NoHeapAllocation ]
        [ Conditional("SINGULARITY_MP") ]
        static internal unsafe void ThawAllProcessors()
        {
            // This method is only called on MP systems when the
            // number of running processors is greater than 1.
            freezeLock.Acquire();
            try {
                if (FreezeRequested == false) {
                    return;
                }

                freezeCount--;
                if (freezeCount != 0) {
                    return;
                }

                FreezeRequested = false;

                ProcessorContext* context = activeCpuContext;
                do
                {
                    context->ipiFreeze = TargetThaw;
                    context = context->nextProcessorContext;
                } while (context != activeCpuContext);
            }
            finally {
                freezeLock.Release();
            }
        }


         /////////////////////////////////////////////////////////////
        // Initialization function

        internal static void Initialize()
        {
            freezeLock = new SpinLock(SpinLock.Types.MpExecutionFreeze);
        }

        //
        // This is called for a StopTheWorldCollector to ensure CPU's
        // other than the one performing the GC do not service interrupts
        // that can corrupt the heap.
        //
        [NoHeapAllocation]
        static internal unsafe void StopProcessorsForGC()
        {
            ProcessorContext* current = Processor.GetCurrentProcessorContext();

            // Set gcIpiGate to 0 for all processors
            ProcessorContext* context = current;
            do {
                context->gcIpiGate = 0;

                context = context->nextProcessorContext;

            } while (context != current);

            //
            // We don't want to send a broadcast to all but self on a single
            // processor system since the call will fail with no receivers.
            //
            // Note we may have other processors that have not been enabled
            // by the OS and are sitting in SIPI, and won't answer the IPI.
            //
            if (Processor.GetRunningProcessorCount() > 1) {
                Platform.BroadcastFixedIPI((byte)Isal.IX.EVectors.GCSynchronization, false);
            }

            // Wait for ackknowledgement from all CPU's but self
            context = current->nextProcessorContext;

            //DebugStub.WriteLine("StopProcessorsForGC: GC CPU (sender) is {0}\n",
                                //__arglist(current->cpuId));

            while (context != current) {

                //DebugStub.WriteLine("StopProcessorsForGC: Waiting for CPU {0}\n",
                      //__arglist(context->cpuId));

                while (context->gcIpiGate == 0) ;

                //DebugStub.WriteLine("StopProcessorsForGC: CPU {0} Acknowledged\n",
                      //__arglist(context->cpuId));

                context = context->nextProcessorContext;
            }

            return;
        }

        //
        // Called by the StopTheWorldCollector to allow processors
        // to re-enable their interrupts
        //
        [ NoHeapAllocation ]
        static internal unsafe void ResumeProcessorsAfterGC()
        {
            // clear the GC wait flag for each CPU
            ProcessorContext* current = Processor.GetCurrentProcessorContext();

            ProcessorContext* context = current;
            do {
                context->gcIpiGate = 0;

                context = context->nextProcessorContext;

            } while (context != current);

            //DebugStub.WriteLine("ResumeProcessorsAfterGC: All processors resumed\n");

            return;
        }

        //
        // This interrupt occurs when the processor executing the elected
        // GC thread requires all the other processors to stop touching the
        // heap.
        //
        // The GC has ensured that no threads are running through the GC thread
        // synchronization, but interrupts may still occur and enter managed
        // code, and this must be stopped to prevent corruptions on the heap
        // for non-concurrent collectors.
        //
        // The processor with the elected GC thread has already masked off its
        // interrupts and is waiting for the non-GC processors to report
        // that their interrupts are disabled.
        //
        [NoHeapAllocation]
        static internal unsafe void GCSynchronizationInterrupt()
        {
            // - member barriers and other issues with spinwaiting on a variable

            bool en = Processor.DisableInterrupts();

            //DebugStub.WriteLine("Processor {0} received GCSynchronizationInterrupt!\n",
                                 //__arglist(Processor.GetCurrentProcessorId()));


            ProcessorContext* current = Processor.GetCurrentProcessorContext();

            // Write value for this processor stating we ackknowledge the interrupt
            current->gcIpiGate = 1;

            // Question: Are we at a GC safe point? For all GC's?

            //
            // Spinwait until the GC processor indicates its done by clearing
            // this flag.
            //

            while (current->gcIpiGate != 0) {
                //
            }

            Processor.RestoreInterrupts(en);

            //DebugStub.WriteLine("Processor {0} done with GCSynchronizationInterrupt!\n",
                                 //__arglist(Processor.GetCurrentProcessorId()));

            return;
        }

        // Test ..
        internal static void Test()
        {

        }
   }
}
