//////////////////////////////////////////////////////////////////////////////////////////////////
//
// Microsoft Research Singularity
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// This file defines an architecture-neutral encapsulation of the state which is saved
// during a thread context switch.

using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

#if ISA_IX
using Microsoft.Singularity.Isal.IX;
#elif ISA_ARM
using Microsoft.Singularity.Isal.Arm;
#endif

namespace Microsoft.Singularity.Isal
{
    // SpillContext holds the CPU state which is local to a thread.  Note this
    // is a subset of the entire CPU context, as some of the context does not
    // change from thread to thread.

    [NoCCtor]
    [AccessedByRuntime("referenced in c++", AllFields = true)]
    [CLSCompliant(false)]
    [StructLayout(LayoutKind.Sequential)]
    [StructAlign(16)]
    public struct SpillContext
    {
#if ISA_IX

        ///////////////////////////////////////////////////////////////////////////
        // ISA_IX86 Context

        //
        // Integer registers
        //

        public UIntPtr  ax;
        public UIntPtr  bx;
        public UIntPtr  cx;
        public UIntPtr  dx;
        public UIntPtr  di;
        public UIntPtr  si;

#if ISA_IX64

        public UIntPtr  r8;
        public UIntPtr  r9;
        public UIntPtr  r10;
        public UIntPtr  r11;
        public UIntPtr  r12;
        public UIntPtr  r13;
        public UIntPtr  r14;
        public UIntPtr  r15;

#endif

        //
        // Control registers
        //

        public UIntPtr  sp;
        public UIntPtr  ip;
        public UIntPtr  bp;
        public UIntPtr  fl;

        //
        // Mmx context.  Note that this must be 16-byte aligned.
        //

        internal MmxContext    mmx;

        // We always store cs even though it never changes in non-paging builds,
        // since it simplifies the context switching code.  Also note we
        // don't store any other segments because they either the same as cs (ss, ds, es) or
        // never change (fs, gs)
        public UIntPtr  cs;

#if PAGING

        //
        // Page table base
        //

        public UIntPtr  cr3;

#endif

#elif ISA_ARM

        ///////////////////////////////////////////////////////////////////////////
        // Arm Context

        //
        // Unbanked integer and control registers.
        //

        public uint     r0;
        public uint     r1;
        public uint     r2;
        public uint     r3;
        public uint     r4;
        public uint     r5;
        public uint     r6;
        public uint     r7;
        public uint     r8;
        public uint     r9;
        public uint     r10;
        public uint     r11;
        public uint     r12;
        public uint     pc; // aka r15

        //
        // Banked control Registers
        //

        public uint     sp; // aka r13
        public uint     lr; // aka r14

        //
        // Processor status register.
        //
        public uint     cpsr;

#endif

        //
        // Extra (unrestored) context information from InterruptContext.
        //

#if ISA_ARM

        public uint     instruction;

#endif

        ///////////////////////////////////////////////////////////////////////////
        // Common fields

        public const int ContentsSpilled            = 0x00000001;

        // Stack limit is stored as part of the spill context.
        public UIntPtr stackLimit;

        // Field for storing various flags about the state of the spill.
        public int spillFlags;

        ///////////////////////////////////////////////////////////////////////////
        // Properties - these are ISA-neutral ways to access common aspects of the
        // spill context.

        public UIntPtr InstructionPointer
        {
            [NoHeapAllocation]
            get {
#if ISA_IX
                return ip;
#elif ISA_ARM
                return pc;
#endif
            }
            [NoHeapAllocation]
            set {
#if ISA_IX
                ip = value;
#elif ISA_ARM
                pc = (uint)value;
#endif
            }
        }

        public UIntPtr StackPointer
        {
            [NoHeapAllocation]
            get {
                return sp;
            }
        }

        public UIntPtr FramePointer
        {
            [NoHeapAllocation]
            get {
#if ISA_IX
                return bp;
#elif ISA_ARM
                return r11; // ?
#endif
            }
        }

        public UIntPtr Flags
        {
            [NoHeapAllocation]
            get {
#if ISA_IX
                return fl;
#elif ISA_ARM
                return cpsr;
#endif
            }
        }

        // IsSpilled indicates whether or not the context is in a spilled state - that is,
        // whether the context represents a swapped-out thread (as opposed to the state
        // where the thread is running.)
        //
        // This flag is set every time we save the context, and cleared when it is restored.

        public bool IsSpilled
        {
            [NoHeapAllocation]
            get {
                return (spillFlags & ContentsSpilled) != 0;
            }
        }

        [NoHeapAllocation]
        public unsafe void Trace()
        {
#if ISA_IX86
            Tracing.Log(Tracing.Debug, "eax={0:x8} ebx={1:x8} ecx={2:x8} edx={3:x8}\n",
                        ax, bx, cx, dx);

            Tracing.Log(Tracing.Debug, "esp={0:x8} ebp={1:x8} esi={2:x8} edi={3:x8}\n",
                        sp, bp, si, di);

            Tracing.Log(Tracing.Debug, "eip={0:x8} efl={1:x8}\n", ip, fl);
#elif ISA_IX64
            Tracing.Log(Tracing.Debug, "rax={0:x16} rbx={1:x16} rcx={2:x16} rdx={3:x16}\n",
                        ax, bx, cx, dx);

            Tracing.Log(Tracing.Debug, "rsp={0:x16} rbp={1:x16} rsi={2:x16} rdi={3:x16}\n",
                        sp, bp, si, di);

            Tracing.Log(Tracing.Debug, "r8={0:x16} r9={1:x16} r10={2:x16} r11={3:x16}\n",
                        r8, r9, r10, r11);

            Tracing.Log(Tracing.Debug, "r12={0:x16} r13={1:x16} r14={2:x16} r15={3:x16}\n",
                        r12, r13, r14, r15);

            Tracing.Log(Tracing.Debug, "rip={0:x16} rfl={1:x16}\n",
                        ip, fl);
#elif ISA_ARM

            Tracing.Log(Tracing.Debug, "r0={0:x8} r1={1:x8} r2={2:x8} r3={3:x8}\n",
                        (UIntPtr) r0, (UIntPtr) r1, (UIntPtr) r2, (UIntPtr) r3);

            Tracing.Log(Tracing.Debug, "r4={0:x8} r5={1:x8} r6={2:x8} r7={3:x8}\n",
                        (UIntPtr) r4, (UIntPtr) r5, (UIntPtr) r6, (UIntPtr) r7);

            Tracing.Log(Tracing.Debug, "r8={0:x8} r9={1:x8} r10={2:x8} r11={3:x8}\n",
                        (UIntPtr) r8, (UIntPtr) r9, (UIntPtr) r10, (UIntPtr) r11);

            Tracing.Log(Tracing.Debug, "r12={0:x8} sp={1:x8} lr={2:x8} pc={3:x8}\n",
                        (UIntPtr) r12, (UIntPtr) sp, (UIntPtr) lr, (UIntPtr) pc);

            Tracing.Log(Tracing.Debug, "cpsr={0:x8}\n", (UIntPtr) cpsr);

#endif
        }

        [NoHeapAllocation]
        public unsafe void Display()
        {
#if ISA_IX86
            DebugStub.WriteLine("  eax={0:x8} ebx={1:x8} ecx={2:x8} edx={3:x8}",
                                __arglist(ax, bx, cx, dx));

            DebugStub.WriteLine("  esp={0:x8} ebp={1:x8} esi={2:x8} edi={3:x8}",
                                __arglist(sp, bp, si, di));

            DebugStub.WriteLine("  eip={0:x8} efl={1:x8}",
                                __arglist(ip, fl));
#elif ISA_IX64
            DebugStub.WriteLine("  rax={0:x16} rbx={1:x16} rcx={2:x16} rdx={3:x16}",
                                __arglist(ax, bx, cx, dx));

            DebugStub.WriteLine("  rsp={0:x16} rbp={1:x16} rsi={2:x16} rdi={3:x16}",
                                __arglist(sp, bp, si, di));

            DebugStub.WriteLine("   r8={0:x16}  r9={1:x16} r10={2:x16} r11={3:x16}",
                                __arglist(r8, r9, r10, r11));

            DebugStub.WriteLine("  r12={0:x16} r13={1:x16} r14={2:x16} r15={3:x16}",
                                __arglist(r12, r13, r14, r15));

            DebugStub.WriteLine("  rip={0:x16} rfl={1:x16}",
                                __arglist(ip, fl));
#elif ISA_ARM

            DebugStub.WriteLine("  r0={0:x8}  r1={1:x8}  r2={2:x8}  r3={3:x8}",
                                __arglist(r0, r1, r2, r3));

            DebugStub.WriteLine("  r4={0:x8}  r5={1:x8}  r6={2:x8}  r7={3:x8}",
                                __arglist(r4, r5, r6, r7));

            DebugStub.WriteLine("  r8={0:x8}  r9={1:x8} r10={2:x8} r11={3:x8}",
                                __arglist(r8, r9, r10, r11));

            DebugStub.WriteLine(" r12={0:x8} sp={1:x8}  lr={2:x8}  pc={3:x8}",
                                __arglist(r12, sp, lr, pc));

            DebugStub.WriteLine(" psr={0:x8}", __arglist(cpsr));
#endif
        }

#if SINGULARITY_KERNEL

        ///////////////////////////////////////////////////////////////////////////
        // Methods

        [AccessedByRuntime("defined in asm")]
        [CLSCompliant(false)]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [GCAnnotation(GCOption.NOGC)]
        [StackBound(32)]
        [NoHeapAllocation]
        public extern unsafe bool Save();

        [AccessedByRuntime("defined in asm")]
        [CLSCompliant(false)]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [GCAnnotation(GCOption.NOGC)]
        [StackBound(32)]
        [NoHeapAllocation]
        public extern unsafe bool Save(InterruptContext *interrupt);

        [AccessedByRuntime("defined in asm")]
        [CLSCompliant(false)]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [GCAnnotation(GCOption.NOGC)]
        [StackBound(32)]
        [NoHeapAllocation]
        public extern unsafe void Resume();

        [AccessedByRuntime("called from C++")]
        [CLSCompliant(false)]
        [NoHeapAllocation]
        public unsafe void Initialize(UIntPtr stack, UIntPtr stackLimit,
                                      UIntPtr start, int threadIndex, UIntPtr cr3)
        {
#if ISA_IX
            UIntPtr *sp = (UIntPtr *) stack;
            *--sp = 0;
            *--sp = 0;
            this.bp = (uint) (UIntPtr) sp;
            this.sp = (uint) (UIntPtr) sp;
            this.ip = (uint) start;
            this.cs  = (uint) Isa.GetCs();
            this.cx = (uint) threadIndex;
            this.fl = EFlags.IF;

            // If we are allowed (i.e. the kernel is ring 0), set IO priveleges in the context
            if ((Isa.GetCs() & 0x3) == 0) {
                this.fl |= EFlags.IOPL; // TODO: get rid of IOPL
            }

            this.fl = EFlags.IF;
            this.mmx.fcw = 0x037f;
            this.mmx.ftw = 0;
            // mmx.mxcsr = 0x1f80;
            this.stackLimit = stackLimit;
#if PAGING
            this.cr3 = cr3;
#endif
#elif ISA_ARM
            this.sp = (uint) stack;
            this.r11 = (uint) stack;
            this.pc = (uint) start;
            this.r0 = (uint) threadIndex;
            this.cpsr = ProcessorMode.System;
            this.stackLimit = stackLimit;
#endif
            // Mark thread as having valid contents
            this.spillFlags = ContentsSpilled;
        }

        [AccessedByRuntime("defined in asm")]
        [CLSCompliant(false)]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [GCAnnotation(GCOption.NOGC)]
        [StackBound(32)]
        [NoHeapAllocation]
        public extern unsafe static void ResetCurrent();

#endif // SINGULARITY_KERNEL

    };
}
