Files
dmcallis 65a0592776 Initial source code drop
Source code drop for the Actionscript virtual machine in the Adobe
Flash Player for December 2013
2013-12-02 16:04:04 -08:00

644 lines
23 KiB
C++

/* -*- 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__