/* -*- 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 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 verifyFunctionQueue; GCList 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__