/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */
/* vi: set ts=4 sw=4 expandtab: (add to ~/.vimrc: set modeline modelines=5) */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef __avmplus_exec__
#define __avmplus_exec__

#include "exec-osr.h"

namespace nanojit {
  class CodeList; // Forward declaration for JITObserver
}

namespace avmplus {

/**
 *  Execution manager pure virtual interface.  An execution manager implementation
 *  is responsible for all aspects of AS3 execution, including invocation,
 *  policy decisions for how to execute, when to verify, and when to translate,
 *  if needed.
 *
 *  Although some configurations may only have one concrete implementation,
 *  we use ordinary polymorphism instead of build-time polymorphism, to keep
 *  things simple.
 */
class ExecMgr
{
public:
    virtual ~ExecMgr() {}

    /** Called when a MethodInfo is allocated. */
    virtual void init(MethodInfo*, const NativeMethodInfo*) = 0;

    /** Called when a MethodEnv is allocated. */
    virtual void init(MethodEnv*) = 0;

    /** Called after MethodInfo::resolveSignatures() completes */
    virtual void notifyMethodResolved(MethodInfo*, MethodSignaturep) = 0;

    /** Called after VTable::resolveSignatures() completes. */
    virtual void notifyVTableResolved(VTable*) = 0;

    /** Called after prepareActionPool completes. */
    virtual void notifyAbcPrepared(Toplevel*, AbcEnv*) = 0;

    /** Invoke a function apply-style, by unpacking arguments from an array */
    virtual Atom apply(MethodEnv*, Atom thisArg, ArrayObject* a) = 0;

    /** Invoke a function call-style, with thisArg passed explicitly */
    virtual Atom call(MethodEnv*, Atom thisArg, int32_t argc, Atom* argv) = 0;

#ifdef VMCFG_HALFMOON
    /** revert compiled method to interpreted state */
    // TODO: this only makes sense for compiled methods
    virtual void deoptimize(MethodEnv* env) = 0;
#endif
    
};

/**
 * Execution Engine run modes.  All are defined, but support depends
 * on build-time and run-time flags.
 */
enum Runmode {
    RM_mixed,       /** Hybrid mode, governed by OSR threshold */
    RM_jit_all,     /** Always JIT */
    RM_interp_all   /** Never JIT */
};

// Signature for method invocation when caller coerces arguments
// and boxes results.  Typically the caller is JIT code.
typedef uintptr_t (*GprMethodProc)(MethodEnv*, int32_t, uint32_t *);
typedef double (*FprMethodProc)(MethodEnv*, int32_t, uint32_t *);
#ifdef VMCFG_FLOAT
typedef float4_t (*VecrMethodProc)(MethodEnv*, int32_t, uint32_t *);
#endif

// Signature for invocation when callee coerces & boxes;
// the caller is calling an unknown function with an unknown signature.
typedef Atom (*AtomMethodProc)(MethodEnv*, int, Atom*);

// Signature for invoking a method early bound via an interface
// type reference.  JIT code passes in the IID of the interface method
// to enable searching for the correct concrete method.
typedef uintptr_t (*GprImtThunkProc)(class ImtThunkEnv*, int argc, uint32_t* args, uintptr_t idd);
typedef double (*FprImtThunkProc)(class ImtThunkEnv*, int argc, uint32_t* args, uintptr_t idd);

// Signature for calling a function closure (OP_call)
typedef Atom (*FunctionProc)(FunctionObject*, int argc, Atom* args);

/**
 * Size of a variable in a JIT stack frame, in bytes.  VARSIZE is large
 * enough to hold double, int, pointers, or Atom on 32-bit or 64-bit cpus.
 * This is an aspect of the JIT implementation, but is defined here because
 * the debugger boxing/unboxing code in MethodInfo needs to know it.
 * Note: for VMCFG_FLOAT we use a function call, to decide the size of a variable
 * per method (if method uses float4, then 16 bytes; otherwise, 8 bytes)
 * Also: instead of actually defining VARSIZE, we define VARSHIFT (either 3 or 4)
 * because shifts are faster than multiplications/divisions, when the second 
 * operand is a variable (and it's trivial to derive 'size' from 'shift', and 
 * we only actually need VARSIZE in asserts - everywhere else we just shift).
 */
#ifdef VMCFG_FLOAT
#define VARSHIFT(ptr) ptr->varShift()
#else
#define VARSHIFT(ptr) 3
#endif

#ifdef VMCFG_GENERIC_FLOAT4
typedef float4_t (*VecrThunkProc)(void* thunk, MethodEnv* env, int32_t argc, uint32_t* argv);

#ifdef DEBUGGER
extern const VecrMethodProc debugEnterVECR_adapter;
#endif
extern const VecrMethodProc verifyEnterVECR_adapter;
extern const VecrThunkProc thunkEnterVECR_adapter;
#endif
    
/**
 * Compute number of bytes needed for the unboxed representation
 * of this argument value when passed on the stack.
 */
int32_t argSize(Traits*);

class MethodRecognizer;

#ifdef VMCFG_NANOJIT

/**
 * Associates debugfile/debugline information with locations in JITted code
 */
struct JITDebugInfo
{
    enum Kind {
        kLine, kFile
    };
    struct Info {
        Info(Kind kind) : kind(kind) {}
        const void* pc; // location in native code.
        Kind kind;      // kLine or kFile
        union {
            int line;
            int file;   // pool index of filename string
        };
        Info* next; // next record.
    };

    JITDebugInfo() : info(0), last(0) {}

    void add(Info* r) {
        r->next = 0;
        if (last)
            last->next = r;
        else
            info = r;
        last = r;
    }

    Info* info;
    Info* last;
};

/** JITObserver is an interface that is notified for each JITted method */
class JITObserver
{
public:
    virtual ~JITObserver() {}
    /**
     * Notify the observer that the given method was just compiled.
     * codeInfo and debugInfo contain code and debug symbol data about the
     * method.  After notifyMethodJITted is called, the codeInfo and debugInfo
     * buffers may be recycled.  The observer must copy out any required info.
     */
    virtual void notifyMethodJITed(MethodInfo* method,
                                   const nanojit::CodeList* code,
                                   JITDebugInfo *debugInfo) = 0;

    virtual void notifyInvokerJITed(MethodInfo* method,
                                    const nanojit::CodeList* code) = 0;
};

/**
 * The JITNoise class implements a source of random numbers for JIT hardening.
 */
class JITNoise : public nanojit::Noise
{
public :
    JITNoise();
    int32_t getValue(int32_t maxValue);
    uint32_t getValue32();
private:
    TRandomFast randomSeed;
};

#endif // VMCFG_NANOJIT

/**
 * BaseExecMgr implements for all policies, and encapsulates
 * jit+abc, abc-only, and wordcode-only mechanisms.  This could be improved
 * by factoring into multiple implementations.
 *
 * Extends GCFinalizedObject because instances contain GC object references
 * and have a destructor that needs to run.
 */
class BaseExecMgr: public MMgc::GCFinalizedObject
    , /* implements */ public ExecMgr
{
public:
    BaseExecMgr(AvmCore*);
    virtual ~BaseExecMgr();

    // ExecMgr methods:
    void init(MethodInfo*, const NativeMethodInfo*);
    void init(MethodEnv*);
    void notifyMethodResolved(MethodInfo*, MethodSignaturep);
    void notifyVTableResolved(VTable*);
    void notifyAbcPrepared(Toplevel*, AbcEnv*);
    Atom apply(MethodEnv*, Atom thisArg, ArrayObject* a);
    Atom call(MethodEnv*, Atom thisArg, int32_t argc, Atom* argv);
#ifdef VMCFG_HALFMOON
    void deoptimize(MethodEnv* env);
#endif

private:
    // Helpers to simply return the current implementation:
    static BaseExecMgr* exec(VTable*);
    static BaseExecMgr* exec(MethodEnv*);

    /** True if method's _isInterpImpl flag is set. */
    static bool isInterpreted(MethodEnv*);

    // Trampolines that verify on first call:
    static uintptr_t verifyEnterGPR(MethodEnv*, int32_t argc, uint32_t* args);
    static double verifyEnterFPR(MethodEnv*, int32_t argc, uint32_t* args);
#ifdef VMCFG_FLOAT
public:
    static float4_t verifyEnterVECR(MethodEnv*, int32_t argc, uint32_t* args);
    static float4_t debugEnterExitWrapperV(MethodEnv* env, int32_t argc, uint32_t* argv);
    static float4_t interpVECR(MethodEnv* method, int argc, uint32_t *ap);
    static float4_t initInterpVECR(MethodEnv*, int, uint32_t*);
private:    
#endif
    static Atom verifyInvoke(MethodEnv*, int32_t argc, Atom* args);
    static void verifyOnCall(MethodEnv*); // helper called by verify trampolines

    // Trampolines to call debugEnter/Exit around native methods:
    static uintptr_t debugEnterExitWrapper32(MethodEnv* env, int32_t argc, uint32_t* argv);
    static double debugEnterExitWrapperN(MethodEnv* env, int32_t argc, uint32_t* argv);

    // Trampoline to set MethodEnv->impl to MethodInfo->impl on first call.
    static uintptr_t delegateInvoke(MethodEnv* env, int32_t argc, uint32_t* ap);

    // Interpreter invocation when called by JIT code.  C++ and Interpreter
    // calls to the interpreter go through one of the invoke_interp variants.
    static uintptr_t interpGPR(MethodEnv* method, int argc, uint32_t *ap);
    static double interpFPR(MethodEnv* method, int argc, uint32_t *ap);

    /** General purpose interpreter invocation. */
    static Atom invokeInterp(MethodEnv* env, int32_t argc, Atom* argv);

    /**
     * Invoke the interpreter for a method that does not need to coerce
     * any arguments, either because there are none, or they're all type *.
     **/
    static Atom invokeInterpNoCoerce(MethodEnv* env, int32_t argc, Atom* argv);

    // Stubs used for invoking interpreted constructor methods; these
    // initialize default values of the new object before invoking the
    // interpreter.  See initObj() in exec.cpp.
    static uintptr_t initInterpGPR(MethodEnv*, int, uint32_t*);
    static double initInterpFPR(MethodEnv*, int, uint32_t*);
    static Atom initInvokeInterp(MethodEnv*, int, Atom*);
    static Atom initInvokeInterpNoCoerce(MethodEnv*, int, Atom*);

    /** Set an object's fields to default values. Called by init stubs. */
    static void initObj(MethodEnv* env, ScriptObject* obj);

    /**
     * Generic interpretive invoker for JIT and native methods that
     * iterates over argument types and coerces each one.
     */
    static Atom invokeGeneric(MethodEnv* env, int32_t argc, Atom* argv);

    /** Invoke a native or jit-compiled method and then box the return value. */
    static Atom endCoerce(MethodEnv*, int32_t argc, uint32_t *ap,
                          MethodSignaturep ms);

    /** Check argc and compute the space required for rest args. */
    static size_t startCoerce(MethodEnv*, int32_t argc, MethodSignaturep ms);

    /** Unbox and coerce arguments for ordinary invocation. */
    static void unboxCoerceArgs(MethodEnv*, int32_t argc, Atom* in,
                                uint32_t *ap, MethodSignaturep ms);

    /** Unbox and coerce arguments for invocation via Function.apply(). */
    static void unboxCoerceArgs(MethodEnv*, Atom thisArg, ArrayObject *a,
                                uint32_t *argv, MethodSignaturep ms);

    /** Unbox and coerce arguments for invocation via Function.call(). */
    static void unboxCoerceArgs(MethodEnv*, Atom thisArg, int32_t argc,
                                Atom* in, uint32_t *argv, MethodSignaturep ms);

    /** Coerce and unbox a single argument. */
    static Atom* FASTCALL coerceUnbox1(MethodEnv*, Atom atom, Traits* t, Atom* args);

    /** Just unbox a single argument that is known to be the correct type already. */
    static Atom* FASTCALL unbox1(Atom atom, Traits* t, Atom* args);

    /**
     * Set trampolines and flags for the interpreter, possibly including an
     * initializer trampoline, and possibly counting invocations for OSR.
     */
    void setInterp(MethodInfo*, MethodSignaturep, bool isOsr);

    /** Set trampolines and flags for a native method. */
    void setNative(MethodInfo*, GprMethodProc p);

    /**
     * Verify any kind of method, by sniffing what kind it is and dispatching
     * to the appropriate case.  Called on or before the first invocation of the
     * target method.  Each subcase is responsible for setting up CodeWriters and
     * then ultimately running the verifier by calling verifyCommon().
     */
    void verifyMethod(MethodInfo*, Toplevel*, AbcEnv*);

    /** "Verify" a native method by installing trampolines and flags. */
    void verifyNative(MethodInfo*, MethodSignaturep);

    /** Verify a method and install interpreter trampolines. */
    void verifyInterp(MethodInfo*, MethodSignaturep, Toplevel*, AbcEnv*);

    /**
     * Actually run the verifier with the given CodeWriter chain, and
     * clean up if it throws a VerifyError.
     */
    void verifyCommon(MethodInfo*, MethodSignaturep, Toplevel*, AbcEnv*, CodeWriter*);

    //
    // Support for verifyall mode mainly consists of hooking into the
    // code loading mechanism and verifying early instead of on the first
    // call.  This includes installing trampolines and doing jit-compiltion,
    // if applicable.  verifyall mode supports a "verifyonly" sub-mode whereby
    // no code is actually executed.
    //
    // Verifying early is done in a semi breadth-first order by maintaining
    // two queues:  verifyFunctionQueue, containing MethodInfo's that are
    // resolved and ready to verify, and verifyTraitsQueue, containing types
    // that contain read-to-verify functions
    //
    // Traits are queued when their corresponding OP_newclass or OP_newactivation
    // is encountered while verifying another method.  Functions are added when
    // we encounter OP_newfunction, or when the declaring traits reaches the head
    // of the queue.
    //
    // Methods have their _isVerifyPending flag set when queued, then changed
    // to _isVerified once verified.
    //
    // In verifyall mode we never install the verify-on-first-call trampolines.
    // In verifyonly mode, all execution is stubbed out by a single stub that
    // just returns undefined.
    //

    friend class VerifyallWriter;
    void enqFunction(MethodInfo* f);
    void enqTraits(Traits* t);
    void verifyEarly(Toplevel* toplevel, AbcEnv* abc_env);
    bool isVerified(const MethodInfo*) const;
    bool isVerifyPending(const MethodInfo*) const;
    void setVerified(MethodInfo*) const;
    void setVerifyPending(MethodInfo*) const;

    //
    // Support for JIT Compilation:
    //
    void setupJit(AvmCore*);

    /** Return true if we should eagerly JIT.  False means use interpreter. */
    bool shouldJitFirst(const AbcEnv*, const MethodInfo*, MethodSignaturep) const;

    /** True if the JIT is enabled */
    bool isJitEnabled() const;

    /** Run the verifier with the JIT attached. */
    void verifyJit(MethodInfo*, MethodSignaturep, Toplevel*, AbcEnv*,
                   OSR *osr_state);

    /** Install JIT code pointers and set MethodInfo::_isJitImpl. */
    void setJit(MethodInfo*, GprMethodProc p);

    /**
     * Invoker called on the first invocation then calls invoke_generic,
     * installs jitInvokerNow yielding a 1-call delay before we try to
     * compile the invoker itself.
     */
    static Atom jitInvokerNext(MethodEnv*, int argc, Atom* args);

    /* Compile now then invoke the compiled invoker. */
    static Atom jitInvokerNow(MethodEnv*, int argc, Atom* args);

    // Support for interface method tables (IMTs).  These enable fast
    // dispatching of an interface method when invoked via an interface-
    // typed reference, when we know the method signature but not the
    // vtable index.  Only applies to call sites in JIT code.
    //
    // Each VTable contains a fixed sized array called the interface
    // method table, each entry points to a concrete MethodEnv, resolveImt(),
    // or dispatchImt().  Initially all entries point to resolveImt().
    //
    // Resolution: All interface methods that the concrete type implements
    // are hashed into the IMT by a unique interface method id (IID).  If an IMT slot
    // has no collisions, then the concrete MethodEnv* for the method implementing
    // that interface method is installed.  Otherwise we install dispatchImt().
    //
    // Dispatching: JIT'd call sites pass the IID of the method they early bound
    // to.  If the called slot contains just a concrete MethodEnv, the IID parameter
    // is ignored.  Otherwise we search a table for the correct concrete
    // method and then invoke it.
    //
    // IIDs are simply the pointer to the interface method's MethodInfo.
    // The IMT itself is declared in class ImtHolder, below.  It is private
    // to the execution mechanism but must be allocated in each VTable instance.
    //

    /**
     * Analyze the implemented types then build the ImtEntry table and
     * install dispatchImt() or a concrete MethodEnv*.
     */
    static class ImtThunkEnv* resolveImtSlot(class ImtThunkEnv*, uintptr_t iid);

    // Helpers for resolveImtSlot():
    void resolveImtSlotFromBase(VTable*, uint32_t slot);
    bool resolveImtSlotSelf(VTable*, uint32_t slot);
    void resolveImtSlotFull(VTable*, uint32_t slot);
    static class ImtEntry* buildImtEntries(VTable* vtable, uint32_t slot, uint32_t& count);

    /** Trampoline to resolve this IMT slot then invoke the proper handler. */
    static uintptr_t resolveImt(class ImtThunkEnv* ite, int argc, uint32_t* ap, uintptr_t iid);

    /** Trampoline which searches for the method with a matching IID. */
    static uintptr_t dispatchImt(class ImtThunkEnv* ite, int argc, uint32_t* ap, uintptr_t iid);

#ifdef VMCFG_COMPILEPOLICY
    /**
     * The policy rule objects utilize MethodRecognizers
     * to determine the policy on per method basis.
     */
    typedef UnmanagedPointerList<MethodRecognizer*> PolicyRuleSet;

    /**
     * This RuleSet object houses two sets of rules, one for jit'ing
     * and one for interp.  No attempt is made to determine if the
     * rules overlap and/or conflict in any manner.
     */
    class JitInterpRuleSet : public MMgc::GCFinalizedObject
    {
    public:
        PolicyRuleSet jit;
        PolicyRuleSet interp;

        JitInterpRuleSet(MMgc::GC* gc);
        ~JitInterpRuleSet();
    };

    bool ruleMatch(PolicyRuleSet* rules, const MethodInfo* m) const;
    bool prepPolicyRules();
#endif

#ifdef VMCFG_HALFMOON
public:
    static void resetMethodInvokers(MethodEnv*);
    static void setRecompileWithProfileData(MethodEnv* env);
    static void setRecompileHotMethod(MethodEnv* env);
    static Atom jitInvokerProfiler(MethodEnv* env, int argc, Atom* args);
    static Atom jitInvokerWithProfileData(MethodEnv* env, int argc, Atom* args);
    static Atom executeMethod(MethodEnv*, int argc, Atom* args);
    static void freeJitCompiledCode(MethodInfo*);
    bool verifyOptimizeJit(MethodInfo* m, MethodSignaturep ms,
                           Toplevel*, AbcEnv*, OSR*);
    void verifyProfilingJit(MethodInfo*, MethodSignaturep, Toplevel*, AbcEnv*);
#endif

private:
    AvmCore* core;
    const struct Config& config;
#ifdef VMCFG_COMPILEPOLICY
    JitInterpRuleSet* _ruleSet;
#endif
#ifdef VMCFG_VERIFYALL
    GCList<MethodInfo> verifyFunctionQueue;
    GCList<Traits> verifyTraitsQueue;
#endif
#ifdef VMCFG_NANOJIT
    friend class OSR;
    friend class CodegenLIR;
    friend class halfmoon::JitFriend;
    friend class LirHelper;
    OSR *current_osr;
    JITObserver *jit_observer; // Current JITObserver or NULL if not profiling.
    JITNoise noise;  // Random number source for JIT hardening
#endif
};

/**
 * CodeWriter instance to hook into opcodes OP_newfunction, OP_newclass, and
 * OP_newactivation so we can populate verifyFunctionQueue and
 * verifyTraitsQueue in verifyall mode.
 */
class VerifyallWriter : public NullWriter
{
    PoolObject* pool;
    BaseExecMgr* exec;
public:
    VerifyallWriter(MethodInfo*, BaseExecMgr*, CodeWriter*);
    void write(const FrameState*, const uint8_t *pc, AbcOpcode, Traits*);
    void writeOp1(const FrameState*, const uint8_t *pc, AbcOpcode, uint32_t opd1, Traits*);
};

/**
 * Base class for MethodInfo which contains invocation pointers.  These
 * pointers are private to the ExecMgr instance and hence declared here.
 */
class GC_CPP_EXACT(MethodInfoProcHolder, MMgc::GCTraceableObject)
{
    friend class ImtThunkEnv;
    friend class InvokerCompiler;
    friend class BaseExecMgr;
    friend class MethodEnv;
    friend class OSR;

protected:
    MethodInfoProcHolder();

    GC_DATA_BEGIN(MethodInfoProcHolder)

private:
    union {
        GprMethodProc  _implGPR;
        FprMethodProc  _implFPR;
        FLOAT_ONLY(VecrMethodProc _implVECR;)
    };
    /** pointer to invoker used when callee must coerce args. */
    AtomMethodProc _invoker;

    GC_DATA_END(MethodInfoProcHolder)
};

/**
 * Base class for MethodEnv and ImtThunkEnv, containing copies of the
 * trampoline from MethodInfo.  In JIT configurations this saves one
 * load along the call fast path.  Calls from C++ or the Interpreter
 * always go through MethodInfo._invoker.
 */
class GC_CPP_EXACT(MethodEnvProcHolder, MMgc::GCTraceableObject)
{
    friend class CodegenLIR;
    friend class BaseExecMgr;
    friend class OSR;
    friend class halfmoon::JitFriend;

protected:
    MethodEnvProcHolder();

    GC_NO_DATA(MethodEnvProcHolder)

private:
    union {
        GprMethodProc   _implGPR;
        FprMethodProc   _implFPR;
        FLOAT_ONLY(VecrMethodProc  _implVECR;)
        GprImtThunkProc _implImtGPR;
    };

#ifdef VMCFG_AOT
public:
    inline GprMethodProc implGPR() const { return _implGPR; }
    inline FprMethodProc implFPR() const { return _implFPR; }
#endif

};

/**
 * Holder for interface method dispatch table within class VTable,
 * declared here because it is private to the ExecMgr implementation.
 */
class ImtHolder : public MMgc::GCInlineObject
{
    friend class BaseExecMgr;
    friend class CodegenLIR;
    friend class halfmoon::JitFriend;

    // IMT_SIZE should be a number that is relatively prime to sizeof(MethodInfo)/8
    // since we use the MethodInfo pointer as the interface method id (IID).
    // smaller = dense table, few large conflict tables
    // larger  = sparse table, many small conflict tables

    #ifdef _DEBUG
    static const uint32_t IMT_SIZE = 3; // Good for testing all code paths.
    #else
    static const uint32_t IMT_SIZE = 7; // Marginally better for speed/size tradeoff.
    #endif

    static uintptr_t getIID(MethodInfo*);    // Return a full IID for the given interface method.
    static uint32_t hashIID(uintptr_t iid);  // Hash the IID into an IMT slot number.
    static uint32_t hashIID(MethodInfo*);    // Hash the method's IID into an IMT slot number.
    class ImtThunkEnv* entries[IMT_SIZE];

public:
    void gcTrace(MMgc::GC* gc) {
        gc->TraceLocations((void**)entries, IMT_SIZE);
    }
};

#if defined(_MSC_VER) && defined(AVMPLUS_IA32)
// These macros are used for dynamically aligning our
// stack before calling into our JITed code.  By aligning our
// stack to an 8 byte boundary before calling into our JITed
// code, we will keep all double access aligned and improve performance.
#define STACKADJUST() \
    __asm \
    { \
        __asm push ecx \
        __asm mov ecx, esp \
        __asm and ecx, 0x4 \
        __asm sub esp, ecx \
        __asm push ecx \
    }

#define STACKRESTORE() \
    __asm \
    { \
        __asm pop ecx \
        __asm add esp, ecx \
        __asm pop ecx \
    }
#else
#define STACKADJUST()
#define STACKRESTORE()
#endif

}
#endif // __avmplus_exec__
