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

417 lines
16 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/. */
#include "avmplus.h"
#ifdef VMCFG_NANOJIT
#include "CodegenLIR.h"
#include "FrameState.h"
#include "exec-osr.h"
/*
OSR Design notes
OSR adds a new compilation threshold parameter to the existing RM_mixed
runmode. I chose not to add a new Runmode, just to keep the configuration
options simpler. There is a ripple effect: Runmode, AvmCore for default
values, ShellCoreSettings for commandline values, plus Flash settings
management. A new osr_threshold setting is added; the default value is 0
which is the existing static heuristic.
Static heuristic means never interpret static initializers, and eagerly
compile all other functions on first invocation. If the JIT fails, the
function reverts to the interpreter forever.
Generally, "invocation" means calls plus loop-backedges; we count both
with a single countdown counter.
New commandline arg -osr=count sets Config.osr_threshold to count. Default
is count=0, which means use the static heuristic.
If osr_threshold is 1 or higher, we compile after that many interpreter
invocations. The value 1 means we JIT compile on the second invocation,
because the first call triggers causes verification and the initial
interpreter invocation.
Limitations
We don't handle methods with exceptions because cloning and setting up
a new ExceptionFrame and jmp_buf seems too complicated. Instead we JIT
these functions eagerly or not-at-all, according to the static heuristic.
The JIT can fail if a method's stack frame is too large. We use a conservative
check for this case in OSR::isSupported(), and don't use OSR compilation if
the JIT could fail. Instead we revert to the static heuristic for such
methods.
Future
The interaction between -Ojit, -Dinterp, and -osr is redundant. If
-osr=1 meant jit-on-first-call, then -Ojit could be expressed in terms of
osr_threshold. Similarly, -Dinterp could be emulated by
forcing OSR::isSupported() to return false for every function.
TODO
- (done) factor out patch to look up FrameState and CodegenLabel by byte* pc, not int pc
- (done) factor out patch to capture activation scopes separately
- (done) combine invocation count and loop count
- (done) commandline configurable thresholds
- (done) count down to zero instead of up to threshold
- (active) move the osr branch earlier in the prolog
- Performance testing
- handle methods with 2+ loop targets. Currently we leak code if we OSR via
one loop, then the other, which is possible if the method has >1 stack
frame active in the interpreter. A weird-but-simple test could repeatedly
do this and cause OOM (TODO: write the test).
- ensure debugEnter/Exit pairing is correct
https://bugzilla.mozilla.org/show_bug.cgi?id=539094#c10
- disable ScopeWriter for subsequent verify passes. (need a new bug)
- add default threshold for -osr with no given threshold.
From testing
- Why is osr repo slower than tr-tip with osr not in use? See sunspider/math-cordic.as,math-partial-sums.as,math-spectral-norm.as,s3d-raytrace.as
- Not sure why osr repo seems faster with -osr not in use on regexp-dna.as?
- With -osr=50 jsbench/typed/FFT.as,LUFact.as,SOR.as are 5-15% slower than without -osr parameter set
- With -osr=50 jsbench/SparseMatmult.as is 20% faster than without -osr parameter set
*/
namespace avmplus
{
int32_t OSR::parseConfig(const char *str)
{
// parse the commandline threshold
int32_t threshold;
if (VMPI_sscanf(str, "%d", &threshold) != 1 ||
threshold < 0)
return -1;
return threshold;
}
OSR::OSR(const uint8_t *osr_pc, Atom* interp_frame)
: osr_pc(osr_pc),
interp_frame(interp_frame),
jit_frame_state(NULL)
{}
void OSR::setFrameState(FrameState *fs)
{
jit_frame_state = fs;
}
const uint8_t* OSR::osrPc() const
{
return osr_pc;
}
// OSR is supported generally only in runmode RM_mixed. We don't support
// methods with try/catch blocks because of the complexity of establishing
// a new ExceptionFrame and jmp_buf. We also don't support methods for which
// a previous compilation attempt failed, or for which failure can be predicted.
//
// We must only OSR methods that will execute with a BugCompatibility object
// such that interpreter/compiler divergences are corrected. Builtin methods
// are invoked with bug compatibility inherited from the innermost non-builtin
// function on the call chain, and thus may vary from call to call. Non-builtins
// should always execute with bug compatibility taken from the AbcEnv to which
// the method belongs, which will thus remain invariant. We can therefore only OSR
// non-builtin methods.
bool OSR::isSupported(const AbcEnv* abc_env, const MethodInfo* m, MethodSignaturep ms)
{
AvmAssert(abc_env->core() == m->pool()->core);
AvmAssert(abc_env->pool() == m->pool());
AvmAssert(abc_env->codeContext() != NULL);
AvmAssert(abc_env->codeContext()->bugCompatibility() != NULL);
return (m->osrEnabled() && // OSR allowed by policy (global or ExecPolicy attribute)
!m->hasExceptions() && // method does not have a try block
!m->hasFailedJit() && // no previous attempt to compile the method has failed
!CodegenLIR::jitWillFail(ms) && // fast-fail predictor says JIT success is possible
!m->pool()->isBuiltin && // the method is not a builtin (ABC baked into application)
abc_env->codeContext()->bugCompatibility()->bugzilla539094); // bug compatibility permits OSR
}
/**
* Compile and finish executing a function.
* We don't actually replace the interpreter frame. Instead, this
* routine JIT-compiles the current method and then calls the result
* of that. The JIT-ted version returns to here and the interpreter
* then immediately exits, returning the result of the JIT-ted version,
* which is returned here.
*/
bool OSR::execute(MethodEnv *env, Atom* interp_frame,
MethodSignaturep ms, const uint8_t* osr_pc, Atom* result)
{
BaseExecMgr* exec = BaseExecMgr::exec(env);
OSR osr(osr_pc, interp_frame);
#ifdef AVMPLUS_VERBOSE
if (env->method->pool()->isVerbose(VB_execpolicy)) {
env->core()->console << "execpolicy jit hot-loop " <<
env->method << " osr_pc=" <<
int(osr_pc - ms->abc_code_start()) <<
"\n";
}
#endif
// compile the method with an OSR entry point
AvmAssert(!env->method->hasFailedJit());
exec->verifyJit(env->method, ms, env->toplevel(), env->abcEnv(), &osr);
env->_implGPR = env->method->_implGPR;
if (env->method->hasFailedJit()) {
// Clean up OSR object explicitly, as there is no destructor.
mmfx_delete(osr.jit_frame_state);
return false;
}
// Save current_osr. It will be set to NULL in adjust_frame(),
// once we have initialized the JIT frame from it.
exec->current_osr = &osr;
int fakeArgc = ms->requiredParamCount();
*result = exec->endCoerce(env, fakeArgc, (uint32_t*)interp_frame, ms);
return true;
}
/**
* This function is called by JIT code if OSR has been requested
* if (exec->current_osr != 0). This function fills the JIT frame locals
* to match the interpreter frame. The JIT code looks like this:
*
* if (*(&exec->current_osr)) {
* adjustFrame(...);
* goto loop_entry
* }
*/
void OSR::adjustFrame(MethodFrame* jitMethodFrame, CallStackNode *callStack,
FramePtr jitFramePointer, uint8_t *jitFrameTags)
{
MethodEnv* env = jitMethodFrame->env();
BaseExecMgr* exec = BaseExecMgr::exec(env);
OSR *osr = exec->current_osr;
AvmAssert(osr && "should not have gotten here");
Atom* interpFramePointer = osr->interp_frame;
MethodSignaturep ms = env->method->getMethodSignature();
int nLocals = ms->local_count();
int stackBase = nLocals + ms->max_scope();
FrameState* frameState = osr->jit_frame_state;
int scopeTop = nLocals + frameState->scopeDepth;
int stackTop = stackBase + frameState->stackDepth;
// OSR has been requested.
#ifdef AVMPLUS_VERBOSE
if (env->method->pool()->isVerbose(VB_interp)) {
env->core()->console <<
"osr-adjust_frame " << env->method->method_id() <<
" " << env->method <<
" scopeTop=" << scopeTop <<
" stackTop=" << stackTop <<
"\n";
}
#endif
// Patch the JIT frame local variable slots and the scope slots in use to match the interpreter state.
for (int i = 0; i < scopeTop; i++)
unboxSlot(frameState, env, interpFramePointer, jitFramePointer, jitFrameTags, i);
// zero out stack area for unused scopes:
if (scopeTop < stackBase) {
void* p = ((char*) jitFramePointer + (scopeTop << VARSHIFT(env->method)));
size_t nbytes = (stackBase - scopeTop) << VARSHIFT(env->method);
VMPI_memset(p, 0, nbytes);
}
// Patch operand stack slots:
for (int i = stackBase; i < stackTop; i++)
unboxSlot(frameState, env, interpFramePointer, jitFramePointer, jitFrameTags, i);
MethodFrame *interpreterMethodFrame = jitMethodFrame->next;
jitMethodFrame->dxns = interpreterMethodFrame->dxns;
jitMethodFrame->next = interpreterMethodFrame->next;
// Clean up non-local OSR parameterization data:
mmfx_delete(frameState);
exec->current_osr = NULL;
#ifdef DEBUGGER
// Call debugEnter if necessary, since the jit code won't.
// We can safely pass NULL for &eip here, because OSR is disabled
// for methods with catch blocks.
if (callStack)
env->debugEnter(jitFrameTags, callStack, jitFramePointer, NULL);
#else
(void) callStack;
#endif
}
#ifdef DEBUG
/**
* Make sure the atom we have meets all the invariants we derived
* at verify time.
*/
static bool isValidAtom(const FrameValue& value, Atom atom)
{
Traits* type = value.traits;
switch (Traits::getBuiltinType(type)) {
case BUILTIN_void:
return atom == undefinedAtom;
case BUILTIN_null:
return AvmCore::isNull(atom);
case BUILTIN_int:
case BUILTIN_uint:
case BUILTIN_number:
case BUILTIN_boolean:
return AvmCore::istype(atom, type);
case BUILTIN_any:
return !value.notNull || !AvmCore::isNullOrUndefined(atom);
default:
return (!value.notNull && AvmCore::isNull(atom)) ||
AvmCore::istype(atom, type);
}
}
#endif
void OSR::unboxSlot(FrameState* frameState,
MethodEnv* env, Atom *interpFramePointer, FramePtr jitFramePointer,
uint8_t* jitFrameTags, int index)
{
const FrameValue& value = frameState->value(index);
Atom atom = interpFramePointer[index];
AvmAssert(isValidAtom(value, atom));
// Unbox the value and store in jit vars[].
AvmAssert(isValidAtom(value, atom));
Atom* addr = (Atom *) ((char *) jitFramePointer + (index << VARSHIFT(env->method)));
Traits* type = value.traits;
BaseExecMgr::unbox1(atom, type, addr);
// Also update the jit's SlotStorageType byte in tags[].
BuiltinType tag = Traits::getBuiltinType(type);
SlotStorageType sst = valueStorageType(tag);
jitFrameTags[index] = (uint8_t) sst;
#ifdef AVMPLUS_VERBOSE
if (env->method->pool()->isVerbose(VB_interp)) {
env->core()->console << "osr-unbox " << index << " type=" << type <<
" notnull=" << frameState->value(index).notNull <<
" value=" << asAtom(atom) <<
"\n";
}
#endif
(void)env;
}
// increment the invocation counter, return true once method is compiled
REALLY_INLINE bool OSR::countInvoke(MethodEnv* env)
{
MethodInfo* m = env->method;
if (--m->_abc.countdown)
return false;
if (m->isInterpreted()) {
#ifdef AVMPLUS_VERBOSE
if (m->pool()->isVerbose(VB_execpolicy))
env->core()->console <<
"execpolicy jit hot-call " << env->method << "\n";
#endif
AvmAssert(!m->hasFailedJit());
BaseExecMgr* exec = BaseExecMgr::exec(env);
exec->verifyJit(m, m->getMethodSignature(),
env->toplevel(), env->abcEnv(), NULL);
if (m->hasFailedJit())
return false;
}
// Method was already compiled; we got here because env->_implGPR
// was not updated. Update it now.
env->_implGPR = env->method->_implGPR;
return true;
}
// intercept call to interpreter increment invocation counter
uintptr_t OSR::osrInterpGPR(MethodEnv* env, int argc, uint32_t *ap)
{
if (countInvoke(env))
return (*env->_implGPR)(env, argc, ap);
return BaseExecMgr::interpGPR(env, argc, ap);
}
// intercept call to interpreter increment invocation counter
double OSR::osrInterpFPR(MethodEnv* env, int argc, uint32_t *ap)
{
if (countInvoke(env))
return (*env->_implFPR)(env, argc, ap);
return BaseExecMgr::interpFPR(env, argc, ap);
}
#ifdef VMCFG_FLOAT
// intercept call to interpreter increment invocation counter
float4_t OSR::osrInterpVECR(MethodEnv* env, int argc, uint32_t *ap)
{
if (countInvoke(env))
return (*env->_implVECR)(env, argc, ap);
return BaseExecMgr::interpVECR(env, argc, ap);
}
#endif
// intercept call to interpreter increment invocation counter
Atom OSR::osrInvokeInterp(MethodEnv* env, int argc, Atom* argv)
{
if (countInvoke(env))
return env->coerceEnter(argc, argv);
return BaseExecMgr::invokeInterp(env, argc, argv);
}
// OSR tramp for invoking a constructor that returns a GPR
uintptr_t OSR::osrInitInterpGPR(MethodEnv* env, int argc, uint32_t *ap)
{
if (countInvoke(env))
return (*env->_implGPR)(env, argc, ap);
return BaseExecMgr::initInterpGPR(env, argc, ap);
}
// OSR tramp for invoking a constructor that returns a FPR
double OSR::osrInitInterpFPR(MethodEnv* env, int argc, uint32_t *ap)
{
if (countInvoke(env))
return (*env->_implFPR)(env, argc, ap);
return BaseExecMgr::initInterpFPR(env, argc, ap);
}
#ifdef VMCFG_FLOAT
// OSR tramp for invoking a constructor that returns a FPR
float4_t OSR::osrInitInterpVECR(MethodEnv* env, int argc, uint32_t *ap)
{
if (countInvoke(env))
return (*env->_implVECR)(env, argc, ap);
return BaseExecMgr::initInterpVECR(env, argc, ap);
}
#endif
// intercept call to interpreter increment invocation counter
Atom OSR::osrInitInvokeInterp(MethodEnv* env, int argc, Atom* argv)
{
if (countInvoke(env))
return env->coerceEnter(argc, argv);
return BaseExecMgr::initInvokeInterp(env, argc, argv);
}
#ifdef DEBUG
// Verify that currentBugCompatibility() is as OSR expected when method is called.
void OSR::checkBugCompatibility(MethodEnv* env)
{
// A method can be OSR'd if it is not a builtin. We ignore builtins here.
if (!env->method->pool()->isBuiltin) {
const BugCompatibility* abcBugCompatibility = env->abcEnv()->codeContext()->bugCompatibility();
const BugCompatibility* dynamicBugCompatibility = env->core()->currentBugCompatibility();
// Verify that currentBugCompatibility() agrees with what the JIT would have assumed.
AvmAssert(dynamicBugCompatibility == abcBugCompatibility);
}
}
#endif
}
#endif // VMCFG_NANOJIT