Files
avmplus/core/CodegenLIR.cpp
T
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

9044 lines
371 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 "exec-osr.h"
#if defined(WIN32) && defined(AVMPLUS_ARM)
#include <intrin.h>
#endif
// Workaround GCC 4.6 offsetof bug
#if (__GNUC__ == 4) && (__GNUC_MINOR__ == 6)
#undef offsetof
#define offsetof(a,b) ((int)(&(((a*)(0))->b)))
#endif
#ifdef VMCFG_VTUNE
namespace vtune {
using namespace avmplus;
void* vtuneInit(String*);
void vtuneCleanup(void*);
}
using namespace vtune;
#endif // VMCFG_VTUNE
// Sparc, ARM and SH4 have buggy LIR_d2i support (bug 613189)
#if (defined(NANOJIT_SPARC) || defined(NANOJIT_SH4) || defined(NANOJIT_ARM))
# undef NJ_F2I_SUPPORTED
# define NJ_F2I_SUPPORTED 0
#endif
#ifdef _MSC_VER
#if !defined (AVMPLUS_ARM) || defined(UNDER_RT)
extern "C"
{
int __cdecl _setjmp3(jmp_buf jmpbuf, int arg);
}
#else
#include <setjmp.h>
#undef setjmp
extern "C"
{
int __cdecl setjmp(jmp_buf jmpbuf);
}
#endif // AVMPLUS_ARM
#endif // _MSC_VER
#ifdef AVMPLUS_ARM
#ifdef _MSC_VER
#define RETURN_METHOD_PTR(_class, _method) \
return *((int*)&_method);
#else
#define RETURN_METHOD_PTR(_class, _method) \
union { \
int (_class::*bar)(); \
int foo[2]; \
}; \
bar = _method; \
return foo[0];
#endif
#elif defined __GNUC__
#define RETURN_METHOD_PTR(_class, _method) \
union { \
int (_class::*bar)(); \
intptr_t foo; \
}; \
bar = _method; \
return foo;
#else
#define RETURN_METHOD_PTR(_class, _method) \
return *((intptr_t*)&_method);
#endif
#ifdef PERFM
#define DOPROF
#endif /* PERFM */
//#define DOPROF
#include "../vprof/vprof.h"
// Profiling generated code.
#ifdef DOPROF
#define JIT_EVENT(id) \
do { \
static void* id; \
_jnvprof_init(&id, #id, NULL); \
callIns(FUNCTIONID(jitProfileEvent), 1, InsConstPtr(id)); \
} while (0)
#define JIT_VALUE(id, val) \
do { \
static void* id; \
_jnvprof_init(&id, #id, NULL); \
callIns(FUNCTIONID(jitProfileValue32), 2, InsConstPtr(id), val); \
} while (0)
#define JIT_TAGVAL(id, val) \
do { \
static void* id; \
_jnhprof_init(&id, #id, 8, 1, 2, 3, 4, 5, 6, 7, 8); \
LIns* jit_tagval_tag = p2i(andp((val), 7)); \
callIns(FUNCTIONID(jitProfileHist32), 2, InsConstPtr(id), jit_tagval_tag); \
} while (0)
#else
#define JIT_EVENT(id) do { } while (0)
#define JIT_VALUE(id, val) do { } while (0)
#define JIT_TAGVAL(id, val) do { } while (0)
#endif
#ifdef AVMPLUS_64BIT
#define PTR_SCALE 3
#else
#define PTR_SCALE 2
#endif
#define IS_ALIGNED(x, size) ((uintptr_t(x) & ((size)-1)) == 0)
namespace avmplus
{
#define COREADDR(f) coreAddr((int (AvmCore::*)())(&f))
#define GCADDR(f) gcAddr((int (MMgc::GC::*)())(&f))
#define ENVADDR(f) envAddr((int (MethodEnv::*)())(&f))
#define ARRAYADDR(f) arrayAddr((int (ArrayObject::*)())(&f))
#define STRINGADDR(f) stringAddr((int (String::*)())(&f))
#define VECTORINTADDR(f) vectorIntAddr((int (IntVectorObject::*)())(&f))
#define VECTORUINTADDR(f) vectorUIntAddr((int (UIntVectorObject::*)())(&f))
#define VECTORDOUBLEADDR(f) vectorDoubleAddr((int (DoubleVectorObject::*)())(&f))
#define VECTORFLOATADDR(f) vectorFloatAddr((int (FloatVectorObject::*)())(&f))
#define VECTORFLOAT4ADDR(f) vectorFloat4Addr((int (Float4VectorObject::*)())(&f))
#define VECTOROBJADDR(f) vectorObjAddr((int (ObjectVectorObject::*)())(&f))
#define EFADDR(f) efAddr((int (ExceptionFrame::*)())(&f))
#define DEBUGGERADDR(f) debuggerAddr((int (Debugger::*)())(&f))
#define FUNCADDR(addr) (uintptr_t)addr
intptr_t coreAddr( int (AvmCore::*f)() )
{
RETURN_METHOD_PTR(AvmCore, f);
}
intptr_t gcAddr( int (MMgc::GC::*f)() )
{
RETURN_METHOD_PTR(MMgc::GC, f);
}
intptr_t envAddr( int (MethodEnv::*f)() )
{
RETURN_METHOD_PTR(MethodEnv, f);
}
#ifdef DEBUGGER
intptr_t debuggerAddr( int (Debugger::*f)() )
{
RETURN_METHOD_PTR(Debugger, f);
}
#endif /* DEBUGGER */
intptr_t arrayAddr(int (ArrayObject::*f)())
{
RETURN_METHOD_PTR(ArrayObject, f);
}
intptr_t stringAddr(int (String::*f)())
{
RETURN_METHOD_PTR(String, f);
}
intptr_t vectorIntAddr(int (IntVectorObject::*f)())
{
RETURN_METHOD_PTR(IntVectorObject, f);
}
intptr_t vectorUIntAddr(int (UIntVectorObject::*f)())
{
RETURN_METHOD_PTR(UIntVectorObject, f);
}
intptr_t vectorDoubleAddr(int (DoubleVectorObject::*f)())
{
RETURN_METHOD_PTR(DoubleVectorObject, f);
}
#ifdef VMCFG_FLOAT
intptr_t vectorFloatAddr(int (FloatVectorObject::*f)())
{
RETURN_METHOD_PTR(FloatVectorObject, f);
}
intptr_t vectorFloat4Addr(int (Float4VectorObject::*f)())
{
RETURN_METHOD_PTR(Float4VectorObject, f);
}
#endif // VMCFG_FLOAT
intptr_t vectorObjAddr(int (ObjectVectorObject::*f)())
{
RETURN_METHOD_PTR(ObjectVectorObject, f);
}
intptr_t efAddr( int (ExceptionFrame::*f)() )
{
RETURN_METHOD_PTR(ExceptionFrame, f);
}
using namespace MMgc;
using namespace nanojit;
#if defined _MSC_VER && !defined AVMPLUS_ARM
# define SETJMP ((uintptr_t)_setjmp3)
#elif defined AVMPLUS_MAC_CARBON
# define SETJMP setjmpAddress
#else
# define SETJMP ((uintptr_t)VMPI_setjmpNoUnwind)
#endif // _MSC_VER
} // end namespace avmplus
// include this outside any namespace to appease Eclipse CDT indigo C++ indexer.
#include "../core/jit-calls.h"
namespace avmplus
{
#if NJ_EXPANDED_LOADSTORE_SUPPORTED && defined(VMCFG_UNALIGNED_INT_ACCESS) && defined(VMCFG_LITTLE_ENDIAN)
#define VMCFG_MOPS_USE_EXPANDED_LOADSTORE_INT
#endif
#if NJ_EXPANDED_LOADSTORE_SUPPORTED && defined(VMCFG_UNALIGNED_FP_ACCESS) && defined(VMCFG_LITTLE_ENDIAN)
#define VMCFG_MOPS_USE_EXPANDED_LOADSTORE_FP
#endif
struct MopsInfo
{
uint32_t size;
LOpcode op;
const CallInfo* call;
};
static const MopsInfo kMopsLoadInfo[7] = {
{ 1, LIR_ldc2i, FUNCTIONID(mop_lix8) },
{ 2, LIR_lds2i, FUNCTIONID(mop_lix16) },
{ 1, LIR_lduc2ui, FUNCTIONID(mop_liz8) },
{ 2, LIR_ldus2ui, FUNCTIONID(mop_liz16) },
{ 4, LIR_ldi, FUNCTIONID(mop_li32) },
{ 4, LIR_ldf2d, FUNCTIONID(mop_lf32) },
{ 8, LIR_ldd, FUNCTIONID(mop_lf64) }
};
static const MopsInfo kMopsStoreInfo[5] = {
{ 1, LIR_sti2c, FUNCTIONID(mop_si8) },
{ 2, LIR_sti2s, FUNCTIONID(mop_si16) },
{ 4, LIR_sti, FUNCTIONID(mop_si32) },
{ 4, LIR_std2f, FUNCTIONID(mop_sf32) },
{ 8, LIR_std, FUNCTIONID(mop_sf64) }
};
class MopsRangeCheckFilter: public LirWriter
{
private:
LirWriter* const prolog_out;
LIns* const env_domainenv;
LIns* curMemBase;
LIns* curMemSize;
LIns* curMopAddr;
LIns* curRangeCheckLHS;
LIns* curRangeCheckRHS;
int32_t curRangeCheckMinValue;
int32_t curRangeCheckMaxValue;
private:
void clearMemBaseAndSize();
static void extractConstantDisp(LIns*& mopAddr, int32_t& curDisp);
LIns* safeIns2(LOpcode op, LIns*, int32_t);
void safeRewrite(LIns* ins, int32_t);
public:
MopsRangeCheckFilter(LirWriter* out, LirWriter* prolog_out, LIns* env_domainenv);
LIns* emitRangeCheck(LIns*& mopAddr, int32_t const size, int32_t* disp, LIns*& br);
void flushRangeChecks();
// overrides from LirWriter
LIns* ins0(LOpcode v);
LIns* insCall(const CallInfo* call, LIns* args[]);
};
inline MopsRangeCheckFilter::MopsRangeCheckFilter(LirWriter* out, LirWriter* prolog_out, LIns* env_domainenv) :
LirWriter(out),
prolog_out(prolog_out),
env_domainenv(env_domainenv),
curMemBase(NULL),
curMemSize(NULL),
curMopAddr(NULL),
curRangeCheckLHS(NULL),
curRangeCheckRHS(NULL),
curRangeCheckMinValue(int32_t(0x7fffffff)),
curRangeCheckMaxValue(int32_t(0x80000000))
{
clearMemBaseAndSize();
}
void MopsRangeCheckFilter::clearMemBaseAndSize()
{
curMemBase = curMemSize = NULL;
}
void MopsRangeCheckFilter::flushRangeChecks()
{
AvmAssert((curRangeCheckLHS != NULL) == (curRangeCheckRHS != NULL));
if (curRangeCheckLHS)
{
curRangeCheckLHS = curRangeCheckRHS = curMopAddr = NULL;
curRangeCheckMinValue = int32_t(0x7fffffff);
curRangeCheckMaxValue = int32_t(0x80000000);
// but don't clearMemBaseAndSize()!
}
else
{
AvmAssert(curMopAddr == NULL);
AvmAssert(curRangeCheckMinValue == int32_t(0x7fffffff));
AvmAssert(curRangeCheckMaxValue == int32_t(0x80000000));
}
}
static bool sumFitsInInt32(int32_t a, int32_t b)
{
return int64_t(a) + int64_t(b) == int64_t(a + b);
}
/*static*/ void MopsRangeCheckFilter::extractConstantDisp(LIns*& mopAddr, int32_t& curDisp)
{
// mopAddr is an int (an offset from globalMemoryBase) on all archs.
// if mopAddr is an expression of the form
// expr+const
// const+expr
// expr-const
// (but not const-expr)
// then try to pull the constant out and return it as a displacement to
// be used in the instruction as an addressing-mode offset.
// (but only if caller requests it that way.)
for (;;)
{
LOpcode const op = mopAddr->opcode();
if (op != LIR_addi && op != LIR_subi)
break;
int32_t imm;
LIns* nonImm;
if (mopAddr->oprnd2()->isImmI())
{
imm = mopAddr->oprnd2()->immI();
nonImm = mopAddr->oprnd1();
if (op == LIR_subi)
imm = -imm;
}
else if (mopAddr->oprnd1()->isImmI())
{
// don't try to optimize const-expr
if (op == LIR_subi)
break;
imm = mopAddr->oprnd1()->immI();
nonImm = mopAddr->oprnd2();
}
else
{
break;
}
if (!sumFitsInInt32(curDisp, imm))
break;
curDisp += imm;
mopAddr = nonImm;
}
}
LIns* MopsRangeCheckFilter::emitRangeCheck(LIns*& mopAddr, int32_t const size, int32_t* disp, LIns*& br)
{
int32_t offsetMin = 0;
if (disp != NULL)
{
*disp = 0;
extractConstantDisp(mopAddr, *disp);
offsetMin = *disp;
}
int32_t offsetMax = offsetMin + size;
AvmAssert((curRangeCheckLHS != NULL) == (curRangeCheckRHS != NULL));
AvmAssert(mopAddr != NULL);
if (curRangeCheckLHS != NULL && curMopAddr == mopAddr)
{
int32_t n_curRangeCheckMin = curRangeCheckMinValue;
if (n_curRangeCheckMin > offsetMin)
n_curRangeCheckMin = offsetMin;
int32_t n_curRangeCheckMax = curRangeCheckMaxValue;
if (n_curRangeCheckMax < offsetMax)
n_curRangeCheckMax = offsetMax;
if ((n_curRangeCheckMax - n_curRangeCheckMin) <= DomainEnv::GLOBAL_MEMORY_MIN_SIZE)
{
if (curRangeCheckMinValue != n_curRangeCheckMin)
safeRewrite(curRangeCheckLHS, curRangeCheckMinValue);
if ((n_curRangeCheckMax - n_curRangeCheckMin) != (curRangeCheckMaxValue - curRangeCheckMinValue))
safeRewrite(curRangeCheckRHS, curRangeCheckMaxValue - curRangeCheckMinValue);
curRangeCheckMinValue = n_curRangeCheckMin;
curRangeCheckMaxValue = n_curRangeCheckMax;
}
else
{
// if collapsed ranges get too large, pre-emptively flush, so that the
// range-checking code can always assume the range is within minsize
flushRangeChecks();
}
}
else
{
flushRangeChecks();
}
if (!curMemBase)
{
//AvmAssert(curMemSize == NULL);
curMemBase = out->insLoad(LIR_ldp, env_domainenv, offsetof(DomainEnv,m_globalMemoryBase), ACCSET_OTHER, LOAD_VOLATILE);
curMemSize = out->insLoad(LIR_ldi, env_domainenv, offsetof(DomainEnv,m_globalMemorySize), ACCSET_OTHER, LOAD_VOLATILE);
}
AvmAssert((curRangeCheckLHS != NULL) == (curRangeCheckRHS != NULL));
if (!curRangeCheckLHS)
{
AvmAssert(!curMopAddr);
curMopAddr = mopAddr;
curRangeCheckMinValue = offsetMin;
curRangeCheckMaxValue = offsetMax;
AvmAssert(env_domainenv != NULL);
// we want to pass range-check if
//
// (curMopAddr+curRangeCheckMin >= 0 && curMopAddr+curRangeCheckMax <= mopsMemorySize)
//
// which is the same as
//
// (curMopAddr >= -curRangeCheckMin && curMopAddr <= mopsMemorySize - curRangeCheckMax)
//
// which is the same as
//
// (curMopAddr >= -curRangeCheckMin && curMopAddr < mopsMemorySize - curRangeCheckMax + 1)
//
// and since (x >= min && x < max) is equivalent to (unsigned)(x-min) < (unsigned)(max-min)
//
// (unsigned(curMopAddr + curRangeCheckMin) < unsigned(mopsMemorySize - curRangeCheckMax + 1 + curRangeCheckMin))
//
// from there, you'd think you could do
//
// (curMopAddr < mopsMemorySize - curRangeCheckMax + 1))
//
// but that is only valid if you are certain that curMopAddr>0, due to the unsigned casting...
// and curMopAddr could be anything, which is really the point of this whole exercise. Instead, settle for
//
// (unsigned(curMopAddr + curRangeCheckMin) <= unsigned(mopsMemorySize - (curRangeCheckMax-curRangeCheckMin)))
//
AvmAssert(curRangeCheckMaxValue > curRangeCheckMinValue);
AvmAssert(curRangeCheckMaxValue - curRangeCheckMinValue <= DomainEnv::GLOBAL_MEMORY_MIN_SIZE);
curRangeCheckLHS = safeIns2(LIR_addi, curMopAddr, curRangeCheckMinValue);
curRangeCheckRHS = safeIns2(LIR_subi, curMemSize, curRangeCheckMaxValue - curRangeCheckMinValue);
LIns* cond = this->ins2(LIR_leui, curRangeCheckLHS, curRangeCheckRHS);
br = this->insBranch(LIR_jf, cond, NULL);
}
return curMemBase;
}
// workaround for WE2569232: don't let these adds get specialized or CSE'd.
LIns* MopsRangeCheckFilter::safeIns2(LOpcode op, LIns* lhs, int32_t rhsConst)
{
LIns* rhs = prolog_out->insImmI(rhsConst);
LIns* ins = out->ins2(op, lhs, rhs);
AvmAssert(ins->isop(op) && ins->oprnd1() == lhs && ins->oprnd2() == rhs);
return ins;
}
// rewrite the instruction with a new rhs constant
void MopsRangeCheckFilter::safeRewrite(LIns* ins, int32_t rhsConst)
{
LIns* rhs = prolog_out->insImmI(rhsConst);
AvmAssert(ins->isop(LIR_addi) || ins->isop(LIR_subi));
ins->initLInsOp2(ins->opcode(), ins->oprnd1(), rhs);
}
LIns* MopsRangeCheckFilter::ins0(LOpcode v)
{
if (v == LIR_label)
{
flushRangeChecks();
clearMemBaseAndSize();
}
return LirWriter::ins0(v);
}
LIns* MopsRangeCheckFilter::insCall(const CallInfo *ci, LIns* args[])
{
// calls could potentially resize globalMemorySize, so we
// can't collapse range checks across them
if (!ci->_isPure)
{
flushRangeChecks();
clearMemBaseAndSize();
}
return LirWriter::insCall(ci, args);
}
/**
* ---------------------------------
* Instruction convenience functions
* ---------------------------------
*/
LIns* CodegenLIR::localCopy(int i)
{
switch (bt(state->value(i).traits)) {
#ifdef VMCFG_FLOAT
case BUILTIN_float:
return localGetf(i);
case BUILTIN_float4:
return localGetf4(i);
#endif
case BUILTIN_number:
return localGetd(i);
case BUILTIN_boolean:
case BUILTIN_int:
case BUILTIN_uint:
return localGet(i);
default:
return localGetp(i);
}
}
void CodegenLIR::localSet(int i, LIns* o, Traits* type)
{
BuiltinType tag = bt(type);
SlotStorageType sst = valueStorageType(tag);
#ifdef DEBUG
jit_sst[i] = uint16_t(1 << sst);
#endif
lirout->insStore(o, vars, i << VARSHIFT(info) , ACCSET_VARS);
lirout->insStore(LIR_sti2c, InsConst(sst), tags, i, ACCSET_TAGS);
}
LIns* CodegenLIR::atomToNativeRep(int i, LIns* atom)
{
return atomToNativeRep(state->value(i).traits, atom);
}
LIns* CodegenLIR::ptrToNativeRep(Traits*t, LIns* ptr)
{
return t->isMachineType() ? addp(ptr, kObjectType) : ptr;
}
#ifdef _DEBUG
bool CodegenLIR::isPointer(int i) {
return !state->value(i).traits->isMachineType();
}
#endif
LIns* CodegenLIR::loadAtomRep(int i)
{
return nativeToAtom(localCopy(i), state->value(i).traits);
}
LIns* CodegenLIR::storeAtomArgs(int count, int index)
{
LIns* ap = insAlloc(sizeof(Atom)*count);
for (int i=0; i < count; i++)
stp(loadAtomRep(index++), ap, i * sizeof(Atom), ACCSET_OTHER);
return ap;
}
LIns* CodegenLIR::storeAtomArgs(LIns* receiver, int count, int index)
{
#ifdef NJ_VERBOSE
if (verbose())
core->console << " store args\n";
#endif
LIns* ap = insAlloc(sizeof(Atom)*(count+1));
stp(receiver, ap, 0, ACCSET_OTHER);
for (int i=1; i <= count; i++)
{
LIns* v = loadAtomRep(index++);
stp(v, ap, sizeof(Atom)*i, ACCSET_OTHER);
}
return ap;
}
CodegenLIR::CodegenLIR(MethodInfo* i, MethodSignaturep ms, Toplevel* toplevel,
OSR* osr_state, JITNoise* noise) :
LirHelper(i->pool()),
info(i),
ms(ms),
toplevel(toplevel),
pool(i->pool()),
osr(osr_state),
driver(NULL),
state(NULL),
mopsRangeCheckFilter(NULL),
restArgc(NULL),
restLocal(-1),
interruptable(true),
npe_label("npe"),
upe_label("upe"),
interrupt_label("interrupt"),
mop_rangeCheckFailed_label("mop_rangeCheckFailed"),
catch_label("catch"),
call_error_label("call_error"),
inlineFastpath(false),
call_cache_builder(*alloc1, *initCodeMgr(pool)),
get_cache_builder(*alloc1, *pool->codeMgr),
set_cache_builder(*alloc1, *pool->codeMgr),
prolog(NULL),
skip_ins(NULL),
specializedCallHashMap(NULL),
builtinFunctionOptimizerHashMap(NULL),
blockLabels(NULL),
cseFilter(NULL),
noise(noise),
jit_debug_info(NULL)
DEBUGGER_ONLY(, haveDebugger(core->debugger() != NULL) )
{
#ifdef AVMPLUS_MAC_CARBON
setjmpInit();
#endif
verbose_only(
if (pool->isVerbose(VB_jit, i)) {
core->console << "codegen " << i;
core->console <<
" required=" << ms->requiredParamCount() <<
" optional=" << (ms->param_count() - ms->requiredParamCount()) << "\n";
})
}
CodegenLIR::~CodegenLIR() {
cleanup();
}
void CodegenLIR::cleanup()
{
LirHelper::cleanup();
}
#ifdef AVMPLUS_MAC_CARBON
int CodegenLIR::setjmpAddress = 0;
extern "C" int __setjmp();
asm int CodegenLIR::setjmpDummy(jmp_buf buf)
{
b __setjmp;
}
void CodegenLIR::setjmpInit()
{
// CodeWarrior defies all reasonable efforts to get
// the address of __vec_setjmp. So, we resort to
// a crude hack: We'll search the actual code
// of setjmpDummy for the branch instruction.
if (setjmpAddress == 0)
{
setjmpAddress = *((int*)&setjmpDummy);
}
}
#endif
void CodegenLIR::suspendCSE()
{
if (cseFilter) cseFilter->suspend();
}
void CodegenLIR::resumeCSE()
{
if (cseFilter) cseFilter->resume();
}
LIns* CodegenLIR::atomToNativeRep(Traits* t, LIns* atom)
{
return atomToNative(bt(t), atom);
}
bool isNullable(Traits* t) {
BuiltinType bt = Traits::getBuiltinType(t);
return bt != BUILTIN_int && bt != BUILTIN_uint && bt != BUILTIN_boolean && bt != BUILTIN_number
FLOAT_ONLY(&& bt != BUILTIN_float && bt != BUILTIN_float4);
}
/**
* Eliminates redundant loads within a block, and tracks the nullability of pointers
* within blocks and across edges. CodegenLIR will inform VarTracker that a
* pointer is not null by calling setNotNull(ptr, type) either when the Verifier's
* FrameState.FrameValue is not null, in localGetp(), or after a null check in emitNullCheck().
*
* Within a block, we track nullability of references to instructions; when references
* are copied, we know the copies are not null.
*
* At block boundaries, different values may flow together, so we track nullability
* in variable slots instead of specific instruction references.
*/
class VarTracker: public LirWriter
{
Allocator &alloc; // Allocator for the lifetime of this filter
LIns** varTracker; // remembers the last value stored in each var
LIns** tagTracker; // remembers the last tag stored in each var
HashMap<LIns*, bool> *checked; // pointers we know are not null.
nanojit::BitSet *notnull; // stack locations we know are not null
LIns* vars; // LIns that defines the vars[] array
LIns* tags; // LIns that defines the tags[] array
const int nvar; // this method's frame size.
const int varShift; // the size of a frame variable in bytes (8 or 16)
const int scopeBase; // index of first local scope
const int stackBase; // index of first stack slot
int restLocal; // -1 or, if it's lazily allocated, the local holding the rest array
// false after an unconditional control flow instruction (jump, throw, return),
// true from the start and after we start a block via trackLabel()
bool reachable;
// true if any backedges exist in LIR, false otherwise.
bool has_backedges;
#ifdef DEBUGGER
bool haveDebugger; // true if debugger is currently enabled
#else
static const bool haveDebugger = false;
#endif
#ifdef AVMPLUS_VERBOSE
bool verbose; // true when generating verbose output
#else
static const bool verbose = false;
#endif
public:
VarTracker(MethodInfo* info, Allocator& alloc, LirWriter *out,
int nvar, int scopeBase, int stackBase, int restLocal,
uint32_t code_len)
: LirWriter(out), alloc(alloc),
vars(NULL), tags(NULL), nvar(nvar),
varShift(VARSHIFT(info)), scopeBase(scopeBase),
stackBase(stackBase), restLocal(restLocal),
reachable(true), has_backedges(false)
#ifdef DEBUGGER
, haveDebugger(info->pool()->core->debugger() != NULL)
#endif
#ifdef AVMPLUS_VERBOSE
, verbose(info->pool()->isVerbose(VB_jit, info))
#endif
{
(void) info; // suppress warning if !DEBUGGER && !AVMPLUS_VERBOSE
varTracker = new (alloc) LIns*[nvar];
tagTracker = new (alloc) LIns*[nvar];
// allocate a large value until https://bugzilla.mozilla.org/show_bug.cgi?id=565489 is resolved
checked = new (alloc) InsSet(alloc, code_len < 16700 ? code_len : 16700);
notnull = new (alloc) nanojit::BitSet(alloc, nvar);
clearState();
}
void init(LIns *vars, LIns* tags) {
this->vars = vars;
this->tags = tags;
}
bool hasBackedges() const {
return has_backedges;
}
void setNotNull(LIns* ins, Traits* t) {
if (isNullable(t))
checked->put(ins, true);
}
bool isNotNull(LIns* ins) {
return checked->containsKey(ins);
}
void initNotNull(const FrameState* state) {
syncNotNull(notnull, state);
}
// We're at the start of an AS3 basic block; synchronize our
// notnull bits for that block with ones from the driver.
void syncNotNull(nanojit::BitSet* bits, const FrameState* state) {
int scopeTop = scopeBase + state->scopeDepth;
int stackTop = stackBase + state->stackDepth;
if (state->targetOfBackwardsBranch || state->targetOfExceptionBranch) {
// Clear any notNull bits that are not set in FrameState.
// This is done at both targets of backward branches and at exception handlers,
// as we cannot be assured of having seen all branches, thus the varTracker info
// may not be complete. This limitation arises from the one-pass algorithm.
for (int i=0, n=nvar; i < n; i++) {
const FrameValue& v = state->value(i);
bool stateNotNull = v.notNull && isNullable(v.traits);
if (!stateNotNull || (i >= scopeTop && i < stackBase) || (i >= stackTop))
bits->clear(i);
else
bits->set(i);
}
printNotNull(bits, "loop label", state);
} else {
// Set any notNull bits that are set in FrameState.
// If we are tracking an expression for a non-null variable,
// add it to the set of checked expressions.
for (int i=0, n=nvar; i < n; i++) {
const FrameValue& v = state->value(i);
bool stateNotNull = v.notNull && isNullable(v.traits);
if ((i >= scopeTop && i < stackBase) || (i >= stackTop)) {
bits->clear(i);
} else if (stateNotNull) {
bits->set(i);
if (varTracker[i] && (i != restLocal))
checked->put(varTracker[i], true);
} else if (bits->get(i) && varTracker[i] && (i != restLocal))
checked->put(varTracker[i], true);
}
printNotNull(bits, "forward label", state);
}
}
// Model a control flow edge by merging our current state with the state
// saved at the target. Used for forward, non-exceptional branches only.
void trackForwardEdge(CodegenLabel& target) {
AvmAssert(target.labelIns == NULL); // illegal to call trackForwardEdge on backedge
// Merge varTracker/tagTracker state with state at target label.
// Due to hidden internal control flow in exception dispatch, state may not be
// propagated across exception edges. Thus, if a label may be reached from such
// an edge, we cannot determine accurate state at the label, and must clear it.
// The state will remain cleared due to the monotonicity of the merge.
if (!target.varTracker) {
// Allocate state vectors for target label upon first encounter.
target.varTracker = new (alloc) LIns*[nvar];
target.tagTracker = new (alloc) LIns*[nvar];
VMPI_memcpy(target.varTracker, varTracker, nvar*sizeof(LIns*));
VMPI_memcpy(target.tagTracker, tagTracker, nvar*sizeof(LIns*));
} else {
for (int i=0, n=nvar; i < n; i++) {
if (varTracker[i] != target.varTracker[i])
target.varTracker[i] = NULL;
if (tagTracker[i] != target.tagTracker[i])
target.tagTracker[i] = NULL;
}
}
for (int i=0, n=nvar; i < n; i++) {
if (varTracker[i]) {
if (checked->containsKey(varTracker[i]))
notnull->set(i);
else
AvmAssert((i == restLocal) || (!notnull->get(i)));
}
}
if (!target.notnull) {
//printf("save state\n");
target.notnull = new (alloc) nanojit::BitSet(alloc, nvar);
target.notnull->setFrom(*notnull);
} else {
// target.notnull &= notnull
for (int i=0, n=nvar; i < n; i++)
if (!notnull->get(i))
target.notnull->clear(i);
}
}
#ifdef AVMPLUS_VERBOSE
void printNotNull(nanojit::BitSet* bits, const char* title,
const FrameState* state) {
if (0 && verbose) {
if (bits) {
int max_scope = scopeBase + state->scopeDepth - 1;
int max_stack = stackBase + state->stackDepth;
printf("%s [0-%d,%d-%d] notnull = ", title, max_scope+1, stackBase, max_stack-1);
for (int i=0; i < max_stack; i = i != max_scope ? i + 1 : stackBase)
if (bits->get(i))
printf("%d ", i);
printf("\n");
} else {
printf("%s notnull = null\n", title);
}
}
}
#else
void printNotNull(nanojit::BitSet*, const char*, const FrameState*)
{}
#endif
void checkBackEdge(CodegenLabel& target, const FrameState* state) {
has_backedges = true;
#ifdef DEBUG
AvmAssert(target.labelIns != NULL);
if (target.notnull) {
printNotNull(notnull, "current", state);
printNotNull(target.notnull, "target", state);
int scopeTop = scopeBase + state->scopeDepth;
int stackTop = stackBase + state->stackDepth;
// make sure our notnull bits at the target of the backedge were safe.
for (int i=0, n=nvar; i < n; i++) {
if ((i >= scopeTop && i < stackBase) || i >= stackTop)
continue; // skip empty locations
if (!isNullable(state->value(i).traits))
continue; // skip non-nullable types in current state
// current target assert(!target || current)
// ------- ------ ------
// false false true
// false true false (assertion fires)
// true false true
// true true true
bool currentNotNull = (varTracker[i] ? isNotNull(varTracker[i]) : false) || notnull->get(i);
AvmAssert(!target.notnull->get(i) || currentNotNull);
}
}
#else
(void) target;
(void) state;
#endif // DEBUG
}
// starts a new block. if the new label is reachable from here,
// merge our state with it. then initialize from the new merged state.
void trackLabel(CodegenLabel& label, const FrameState* state) {
if (reachable)
trackForwardEdge(label); // model the fall-through path as an edge
clearState();
label.labelIns = out->ins0(LIR_label);
// Load varTracker/tagTracker state accumulated from forward branches.
// Do not load if there are any backward branches, as the tracker state may
// not be accurate. Just switch the pointers -- no need to copy the arrays.
// Exception branches are assumed to be backward, as lowering to LIR may have
// introduced backward control flow that was not present in the original ABC.
if (!state->targetOfBackwardsBranch && label.varTracker && !state->targetOfExceptionBranch) {
varTracker = label.varTracker;
tagTracker = label.tagTracker;
}
// load state saved at label
if (label.notnull) {
syncNotNull(label.notnull, state);
notnull->setFrom(*label.notnull);
printNotNull(notnull, "merged label", state);
} else {
syncNotNull(notnull, state);
printNotNull(notnull, "first-time label", state);
}
reachable = true;
}
// Clear the var and tag expression states, but do not clear the nullability
// state. Called around debugger safe points to ensure that we reload values
// that are possibly modified by the debugger. Clearing the nullability state
// correctly must be done at the verifier level, and at that level, it must always
// be done or never be done (can't be conditional on debugging).
// FIXME: bug 544238: clearing only the var state has questionable validity
void clearVarState() {
VMPI_memset(varTracker, 0, nvar*sizeof(LIns*));
VMPI_memset(tagTracker, 0, nvar*sizeof(LIns*));
}
// clear all nullability and var/tag tracking state at branch targets
void clearState() {
clearVarState();
checked->clear();
notnull->reset();
}
REALLY_INLINE int varOffsetToIndex(int offset) {
AvmAssert(IS_ALIGNED(offset, 1 << varShift));
return offset >> varShift;
}
// keep track of the value stored in var d and update notnull
void trackVarStore(LIns *value, int i) {
varTracker[i] = value;
if (checked->containsKey(value))
notnull->set(i);
else
notnull->clear(i);
}
// keep track of the tag stored in var i.
void trackTagStore(LIns *value, int i) {
tagTracker[i] = value;
}
// The first time we see a load from variable i, remember it,
// and if we know that slot is nonnull, add the load instruction to the nonnull set.
void trackVarLoad(LIns* value, int i) {
varTracker[i] = value;
if (notnull->get(i) && (i != restLocal))
checked->put(value, true);
}
// first time we read a tag for variable i, remember it.
void trackTagLoad(LIns* value, int i) {
tagTracker[i] = value;
}
// monitor loads emitted by the LIR generator, track access to vars and tags
LIns *insLoad(LOpcode op, LIns *base, int32_t d, AccSet accSet, LoadQual loadQual) {
if (base == vars) {
int i = varOffsetToIndex(d);
LIns *val = varTracker[i];
if (!val) {
val = out->insLoad(op, base, d, accSet, loadQual);
FLOAT_ONLY(AvmAssert(!val->isF4() || varShift == 4));
trackVarLoad(val, i);
}
return val;
}
if (base == tags) {
int i = d; // 1 byte per tag
LIns *tag = tagTracker[i];
if (!tag) {
tag = out->insLoad(op, base, d, accSet, loadQual);
trackTagLoad(tag, i);
}
return tag;
}
return out->insLoad(op, base, d, accSet, loadQual);
}
// monitor all stores emitted by LIR generator, update our tracking state
// when we see stores to vars or tags.
LIns *insStore(LOpcode op, LIns *value, LIns *base, int32_t d, AccSet accSet) {
if (base == vars)
{
FLOAT_ONLY(AvmAssert(!value->isF4() || varShift == 4));
trackVarStore(value, varOffsetToIndex(d));
}
else if (base == tags)
trackTagStore(value, d);
return out->insStore(op, value, base, d, accSet);
}
// we expect the frontend to use CodegenLabels and call trackLabel for all
// LIR label creation. Assert to prevent unknown label generation.
LIns *ins0(LOpcode op) {
AvmAssert(op != LIR_label); // trackState must be called directly to generate a label.
return out->ins0(op);
}
// set reachable = false after return instructions
LIns* ins1(LOpcode op, LIns* a) {
if (isRetOpcode(op))
reachable = false;
return out->ins1(op, a);
}
// set reachable = false after unconditional jumps
LIns *insBranch(LOpcode v, LIns* cond, LIns* to) {
if (v == LIR_j)
reachable = false;
return out->insBranch(v, cond, to);
}
// set reachable = false after LIR_jtbl which has explicit targets for all cases
LIns *insJtbl(LIns* index, uint32_t size) {
reachable = false;
return out->insJtbl(index, size);
}
// assume any non-pure function can throw an exception, and that pure functions cannot.
bool canThrow(const CallInfo* call)
{
return !call->_isPure;
}
// if debugging is attached, clear our tracking state when calling side-effect
// fucntions, which are effectively debugger safe points.
// also set reachable = false if the function is known to always throw, and never return.
LIns *insCall(const CallInfo *call, LIns* args[]) {
if (haveDebugger && canThrow(call))
clearVarState(); // debugger might have modified locals, so make sure we reload after call.
if (neverReturns(call))
reachable = false;
if (call->_address == (uintptr_t)&restargHelper) {
// That helper has a by-reference argument which points into the vars array
AvmAssert(restLocal != -1);
varTracker[restLocal] = 0;
}
return out->insCall(call, args);
}
};
LIns* CodegenLIR::localGet(int i) {
#ifdef DEBUG
const FrameValue& v = state->value(i);
AvmAssert((v.sst_mask == (1 << SST_int32) && v.traits == INT_TYPE) ||
(v.sst_mask == (1 << SST_uint32) && v.traits == UINT_TYPE) ||
(v.sst_mask == (1 << SST_bool32) && v.traits == BOOLEAN_TYPE));
#endif
return lirout->insLoad(LIR_ldi, vars, i << VARSHIFT(info) , ACCSET_VARS);
}
LIns* CodegenLIR::localGetf(int i) {
#ifdef VMCFG_FLOAT
#ifdef DEBUG
const FrameValue& v = state->value(i);
AvmAssert(v.sst_mask == (1<<SST_float) && v.traits == FLOAT_TYPE);
#endif
return lirout->insLoad(LIR_ldf, vars, i << VARSHIFT(info), ACCSET_VARS);
#else
toplevel->throwError(kNotImplementedError); (void)i;
return NULL;
#endif
}
LIns* CodegenLIR::localGetd(int i) {
#ifdef DEBUG
const FrameValue& v = state->value(i);
AvmAssert((v.sst_mask == (1<<SST_double) && v.traits == NUMBER_TYPE));
#endif
return lirout->insLoad(LIR_ldd, vars, i << VARSHIFT(info) , ACCSET_VARS);
}
#ifdef VMCFG_FLOAT
LIns* CodegenLIR::localGetf4(int i) {
#ifdef DEBUG
const FrameValue& v = state->value(i);
AvmAssert(v.sst_mask == (1<<SST_float4) && v.traits == FLOAT4_TYPE);
AvmAssert(VARSHIFT(info) == 4);
#endif
return lirout->insLoad(LIR_ldf4, vars, i << 4, ACCSET_VARS);
}
LIns* CodegenLIR::localGetf4Addr(int i) {
#ifdef DEBUG
const FrameValue& v = state->value(i);
AvmAssert(v.sst_mask == (1<<SST_float4) && v.traits == FLOAT4_TYPE);
AvmAssert(VARSHIFT(info) == 4);
#endif
return lea(i << 4, vars);
}
#endif
// Load a pointer-sized var, and update null tracking state if the driver
// informs us that it is not null via FrameState.value.
LIns* CodegenLIR::localGetp(int i)
{
const FrameValue& v = state->value(i);
LIns* ins;
if (exactlyOneBit(v.sst_mask)) {
// pointer or atom
AvmAssert(!(v.sst_mask == (1 << SST_int32) && v.traits == INT_TYPE) &&
!(v.sst_mask == (1 << SST_uint32) && v.traits == UINT_TYPE) &&
!(v.sst_mask == (1 << SST_bool32) && v.traits == BOOLEAN_TYPE) &&
FLOAT_ONLY( !(v.sst_mask == (1 << SST_float) && v.traits == FLOAT_TYPE) &&
!(v.sst_mask == (1 << SST_float4) && v.traits == FLOAT4_TYPE) && )
!(v.sst_mask == (1 << SST_double) && v.traits == NUMBER_TYPE));
ins = lirout->insLoad(LIR_ldp, vars, i << VARSHIFT(info) , ACCSET_VARS);
} else {
// more than one representation is possible: convert to atom using tag found at runtime.
AvmAssert(bt(v.traits) == BUILTIN_any || bt(v.traits) == BUILTIN_object);
LIns* tag = lirout->insLoad(LIR_lduc2ui, tags, i, ACCSET_TAGS);
LIns* varAddr = lea( i << VARSHIFT(info) , vars);
ins = callIns(FUNCTIONID(makeatom), 3, coreAddr, varAddr, tag);
}
// A sentinel null value may be stored in the rest local slot even though the
// verifier believes the slot is non-null. This is valid from the ABC standpoint,
// as the rest arg array will be created on demand if the slot is accessed, and
// the null sentinel can never be observed. We do not want to infer that 'ins' is
// itself non-null in this case, however, as it may be used elsewhere.
if (v.notNull && (i != restLocal))
varTracker->setNotNull(ins, v.traits);
return ins;
}
LIns* CodegenLIR::callIns(const CallInfo *ci, uint32_t argc, ...)
{
// A LIR_call that throws an exception may not appear in the same position
// with respect to the handler as did the ABC instruction that notionally
// performed the throw. As a result, the backwards-branch information
// collected by the verifier is not reliable for exception edges. We thus
// do not attempt to model exceptions in the VarTracker, but conservatively
// treat all exception handlers as if they were backward branch targets.
va_list ap;
va_start(ap, argc);
LIns* ins = LirHelper::vcallIns(ci, argc, ap);
va_end(ap);
return ins;
}
#if defined(DEBUGGER) && defined(_DEBUG)
// The AS debugger requires type information for variables contained
// in the AS frame regions (i.e. 'vars'). In the interpreter this
// is not an issues, since the region contains box values (i.e. Atoms)
// and so the type information is self-contained. With the jit, this is
// not the case, and thus 'tags' is used to track the type of each
// variable in 'vars'.
// This filter watches stores to 'vars' and 'tags' and upon encountering
// debugline (i.e. place where debugger can halt), it ensures that the
// tags entry is consistent with the value stored in 'vars'
class DebuggerCheck : public LirWriter
{
AvmCore* core;
LIns** varTracker;
LIns** tagTracker;
LIns *vars;
LIns *tags;
int nvar;
int varShift;
public:
DebuggerCheck(AvmCore* core, Allocator& alloc, LirWriter *out, int nvar, int varShift)
: LirWriter(out), core(core), vars(NULL), tags(NULL), nvar(nvar), varShift(varShift)
{
varTracker = new (alloc) LIns*[nvar];
tagTracker = new (alloc) LIns*[nvar];
clearState();
}
void init(LIns *vars, LIns *tags) {
this->vars = vars;
this->tags = tags;
}
void trackVarStore(LIns *value, int d) {
AvmAssert(IS_ALIGNED(d, 1 << varShift));
FLOAT_ONLY(AvmAssert(varShift == 4 || !value->isF4());)
int i = d >> varShift;
if (i >= nvar)
return;
varTracker[i] = value;
}
void trackTagStore(LIns *value, int d) {
int i = d; // 1 byte per tag
if (i >= nvar)
return;
tagTracker[i] = value;
checkValid(i);
tagTracker[i] = (LIns*)((intptr_t)value|1); // lower bit => validated;
}
void clearState() {
VMPI_memset(varTracker, 0, nvar * sizeof(LIns*));
VMPI_memset(tagTracker, 0, nvar * sizeof(LIns*));
}
void checkValid(int i) {
// @pre tagTracker[i] has been previously filled
LIns* val = varTracker[i];
LIns* tra = tagTracker[i];
NanoAssert(val && tra);
switch ((SlotStorageType) tra->immI()) {
case SST_double:
AvmAssert(val->isQorD());
break;
#ifdef VMCFG_FLOAT
case SST_float:
AvmAssert(val->isF());
break;
case SST_float4:
AvmAssert(val->isF4());
break;
#endif
case SST_int32:
case SST_uint32:
case SST_bool32:
AvmAssert(val->isI());
break;
default:
AvmAssert(val->isP());
break;
}
}
void checkState() {
for (int i=0; i < this->nvar; i++) {
LIns* val = varTracker[i];
LIns* tra = tagTracker[i];
AvmAssert(val && tra);
// isValid should have already been called on everything
AvmAssert(((intptr_t)tra&0x1) == 1);
}
}
LIns *insCall(const CallInfo *call, LIns* args[]) {
if (call == FUNCTIONID(debugLine))
checkState();
return out->insCall(call,args);
}
LIns *insStore(LOpcode op, LIns *value, LIns *base, int32_t d, AccSet accSet) {
if (base == vars)
trackVarStore(value, d);
else if (base == tags)
trackTagStore(value, d);
return out->insStore(op, value, base, d, accSet);
}
};
#endif
// writer for the prolog. instructions written here dominate code in the
// body, and so are added to the body's CseFilter
class PrologWriter : public LirWriter
{
public:
LIns* lastIns;
LIns* env_scope;
LIns* env_vtable;
LIns* env_abcenv;
LIns* env_domainenv;
LIns* env_toplevel;
LIns* env_finddef_table;
PrologWriter(LirWriter *out):
LirWriter(out),
lastIns(NULL),
env_scope(NULL),
env_vtable(NULL),
env_abcenv(NULL),
env_domainenv(NULL),
env_toplevel(NULL),
env_finddef_table(NULL)
{}
virtual LIns* ins0(LOpcode v) {
return lastIns = out->ins0(v);
}
virtual LIns* ins1(LOpcode v, LIns* a) {
return lastIns = out->ins1(v, a);
}
virtual LIns* ins2(LOpcode v, LIns* a, LIns* b) {
return lastIns = out->ins2(v, a, b);
}
virtual LIns* ins3(LOpcode v, LIns* a, LIns* b, LIns* c) {
return lastIns = out->ins3(v, a, b, c);
}
virtual LIns* ins4(LOpcode v, LIns* a, LIns* b, LIns* c, LIns* d) {
return lastIns = out->ins4(v, a, b, c, d);
}
virtual LIns* insGuard(LOpcode v, LIns *c, GuardRecord *gr) {
return lastIns = out->insGuard(v, c, gr);
}
virtual LIns* insBranch(LOpcode v, LIns* condition, LIns* to) {
return lastIns = out->insBranch(v, condition, to);
}
// arg: 0=first, 1=second, ...
// kind: 0=arg 1=saved-reg
virtual LIns* insParam(int32_t arg, int32_t kind) {
return lastIns = out->insParam(arg, kind);
}
virtual LIns* insImmI(int32_t imm, bool tainted) {
return lastIns = out->insImmI(imm, tainted);
}
#ifdef AVMPLUS_64BIT
virtual LIns* insImmQ(uint64_t imm, bool tainted) {
return lastIns = out->insImmQ(imm, tainted);
}
#endif
virtual LIns* insImmD(double d, bool tainted) {
return lastIns = out->insImmD(d, tainted);
}
virtual LIns* insImmF(float f, bool tainted) {
return lastIns = out->insImmF(f, tainted);
}
virtual LIns* insImmF4(const float4_t& f, bool tainted) {
return lastIns = out->insImmF4(f, tainted);
}
virtual LIns* insLoad(LOpcode op, LIns* base, int32_t d, AccSet accSet, LoadQual loadQual) {
return lastIns = out->insLoad(op, base, d, accSet, loadQual);
}
virtual LIns* insStore(LOpcode op, LIns* value, LIns* base, int32_t d, AccSet accSet) {
return lastIns = out->insStore(op, value, base, d, accSet);
}
// args[] is in reverse order, ie. args[0] holds the rightmost arg.
virtual LIns* insCall(const CallInfo *call, LIns* args[]) {
return lastIns = out->insCall(call, args);
}
virtual LIns* insAlloc(int32_t size) {
NanoAssert(size != 0);
return lastIns = out->insAlloc(size);
}
virtual LIns* insJtbl(LIns* index, uint32_t size) {
return lastIns = out->insJtbl(index, size);
}
virtual LIns* insSwz(LIns* a, uint8_t mask) {
return lastIns = out->insSwz(a, mask);
}
};
#ifdef DEBUG
FUNCTION(FUNCADDR(OSR::checkBugCompatibility), SIG1(V, P), osr_check_bugcompatibility)
#endif
// Generate the prolog for a function with this C++ signature:
//
// <return-type> f(MethodEnv* env, int argc, void* args)
//
// argc is the number of arguments, not counting the receiver
// (aka "this"). args points to the arguments in memory:
//
// [receiver] [arg1, arg2, ... ]
//
// the arguments in memory are typed according to the AS3 method
// signature. types * and Object are represented as Atom, and all
// other types are native pointers or values. return-type is whatever
// the native type is for the AS3 return type; one of double, int32_t,
// uint32_t, ScriptObject*, String*, Namespace*, Atom, or void.
//
// The stack frame layout of a jit-compiled function is determined by
// the jit backend. Stack-allocated structs are declared in LIR with
// a LIR_allocp instruction. Incoming parameters are declared with LIR_paramp
// instructions, and any other local variables with function-body scope
// and lifetime are declared with the expressions that compute them.
// The backend will also allocate additional stack space for spilled values
// and callee-saved registers. The VM and LIR do not currently depend on how
// the backend organizes the stack frame.
//
// Incoming parameters:
//
// env_param (LIR_paramp, MethodEnv*) is the incoming MethodEnv* parameter
// that provides access to the environment for this function and all vm services.
//
// argc_param (LIR_paramp, int32_t) the # of arguments that follow. Ignored
// when the # of args is fixed, but otherwise used for optional arg processing
// and/or creating the rest[] or arguments[] arrays for undeclared varargs.
//
// ap_param (LIR_paramp, uint32_t*) pointer to (argc+1) incoming arguments.
// arguments are packed. doubles are sizeof(double), everything else is sizeof(Atom).
//
// Distinguished locals:
//
// methodFrame (LIR_allocp, MethodFrame*) is the current MethodFrame. in the prolog
// we push this onto the call stack pointed to by AvmCore::currentMethodFrame, and
// in the epilog we pop it back off.
//
// coreAddr (LIR_immi|LIR_immq) constant address of AvmCore*. used in lots of places.
// undefConst (LIR_immi|LIR_immq) constant value = undefinedAtom. used all over.
//
// vars (LIR_allocp) storage for ABC stack frame variables. 8 bytes per variable,
// always, laid out according to ABC param/local var numbering. The total number
// is local_count + scope_depth + stack_depth, i.e. enough for the whole ABC frame.
// values at any given point in the jit code are are represented according to the
// statically known type of the variable at that point in the code. (the type and
// representation may change at different points. CodegenDriver maintains
// the known static types of variables and exposes them via FrameState.
//
// tags (LIR_allocp) SlotStorageType of each var in vars, one byte per variable.
//
// The contents of vars+tags are up-to-date at all labels and debugging safe points.
// Inbetween those points, the contents are stale; the JIT optimizes away
// stores and loads in straightline code. Additional dead stores
// are elided by deadvars_analyze() and deadvars_kill().
//
// Locals for Debugger use, only present when Debugger is in use:
//
// csn (LIR_allocp, CallStackNode). extra information about this call frame
// used by the debugger and also used for constructing human-readable stack traces.
//
// Locals for Exception-handling, only present when method has try/catch blocks:
//
// _save_eip (LIR_allocp, intptr_t) storage for the current ABC-based "pc", used by exception
// handling to determine which catch blocks are in scope. The value is an ABC
// instruction offset, which is how catch handler records are indexed.
//
// _ef (LIR_allocp, ExceptionFrame) an instance of struct ExceptionFrame, including
// a jmp_buf holding our setjmp() state, a pointer to the next outer ExceptionFrame,
// and other junk.
//
// setjmpResult (LIR_call, int) result from calling setjmp; feeds a conditional branch
// that surrounds the whole function body; logic to pick a catch handler and jump to it
// is compiled after the function body. if setjmp returns a nonzero result then we
// jump forward, pick a catch block, then jump backwards to the catch block.
//
void CodegenLIR::writePrologue(const FrameState* state, const uint8_t* pc,
CodegenDriver* driver)
{
this->state = state;
this->driver = driver;
this->code_pos = pc;
this->try_from = driver->getTryFrom();
this->try_to = driver->getTryTo();
framesize = ms->frame_size();
int32_t codeLength = info->parse_code_length();
// Enable inline fastpath optimizations for sufficiently small methods.
//
// An overwhelming majority of methods in the Brightspot collection
// are less than 50K bytes of ABC in length, and we see that JIT
// times for methods of this length consistently fall in the 10ms
// or less range with no outliers to suggest algorithmic pathologies.
// The framesize limit is rather arbitrary, and is set to keep within
// the boundaries of available brightspot data. Note that large methods
// appear more common in Alchemy-generated code, with a 500KB method
// in Quake, for example. We are admittedly conservative here.
// We see only a modest increase in native code size with the present
// suite of inlining optimizations and opcode mix in the code we examined.
//
// The issue we are attempting to guard against is non-linear blowup of
// analysis algorithms such as deadvars() that are stressed by the introduction
// of many new labels and branches. Our heuristic seeks to confine the fastpath
// optimization to an "envelope" for we have good empirical data, without
// attempting to establish its true outer limits. Additionally, from first
// principles, we know that specific characteristics of the flow graph are
// more relevant than length alone, and even a count of the labels and branches
// would likely give a more precise result than overall code lenth.
// The preferred long-term solution is to perform actual acconting for JIT
// resource consumption (memory and time), and aborting when reasonable limits
// are exceeded.
//
if (core->config.jitconfig.opt_inline) {
if (codeLength < 50000 && framesize < 1000) {
inlineFastpath = true;
} else {
//core->console << "disabling inline fastpaths, frame size " << framesize << " code length " << codeLength << "\n";
}
}
#if defined(NANOJIT_ARM) && defined(VMCFG_FLOAT)
// The LIR instructions for float and float4 are not supported by nanojit
// for soft float, and we are planning to rip out soft float entirely.
// While this is really a nanojit issue, we've already removed VMCFG_FLOAT
// from nanojit, so we put the check here.
AvmAssert(!core->config.njconfig.soft_float);
#endif
if (info->needRestOrArguments() && info->lazyRest())
restLocal = ms->param_count()+1;
frag = new (*lir_alloc) Fragment(pc verbose_only(, 0));
LirBuffer *prolog_buf = frag->lirbuf = new (*lir_alloc) LirBuffer(*lir_alloc);
prolog_buf->abi = ABI_CDECL;
lirout = new (*alloc1) LirBufWriter(prolog_buf, core->config.njconfig);
verbose_only(
vbNames = 0;
if (verbose()) {
vbNames = new (*lir_alloc) LInsPrinter(*lir_alloc, TR_NUM_USED_ACCS);
vbNames->addrNameMap->addAddrRange(pool->core, sizeof(AvmCore), 0, "core");
prolog_buf->printer = vbNames;
}
)
debug_only(
lirout = validate2 = new (*alloc1) ValidateWriter(lirout, prolog_buf->printer,
"writePrologue(prologue)");
)
verbose_only(
vbWriter = 0;
if (verbose())
lirout = vbWriter = new (*alloc1) VerboseWriter(*alloc1, lirout, vbNames, &pool->codeMgr->log, "PROLOG");
)
prolog = new (*alloc1) PrologWriter(lirout);
redirectWriter = lirout = new (*lir_alloc) LirWriter(prolog);
if (core->config.njconfig.cseopt)
lirout = cseFilter = new (*alloc1) CseFilter(lirout, TR_NUM_USED_ACCS, *alloc1, core->config.njconfig);
#if defined(NANOJIT_ARM)
if (core->config.njconfig.soft_float)
lirout = new (*alloc1) SoftFloatFilter(lirout);
#endif
lirout = new (*alloc1) ExprFilter(lirout);
#ifdef DEBUGGER
dbg_framesize = ms->local_count() + ms->max_scope();
#ifdef DEBUG
DebuggerCheck *checker = NULL;
if (haveDebugger) {
checker = new (*alloc1) DebuggerCheck(core, *alloc1, lirout, dbg_framesize, VARSHIFT(info));
lirout = checker;
}
#endif // DEBUG
#endif // DEBUGGER
emitStart(*alloc1, prolog_buf, lirout);
// add the VarTracker filter last because we want it to be first in line.
lirout = varTracker = new (*alloc1) VarTracker(info, *alloc1, lirout,
framesize, ms->scope_base(), ms->stack_base(), restLocal, codeLength);
// last pc value that we generated a store for
lastPcSave = NULL;
//
// generate lir to define incoming method arguments. Stack
// frame allocations follow.
//
env_param = lirout->insParam(0, 0);
argc_param = lirout->insParam(1, 0);
#ifdef AVMPLUS_64BIT
argc_param = lirout->ins1(LIR_q2i, argc_param);
#endif
ap_param = lirout->insParam(2, 0);
// allocate room for a MethodFrame structure
methodFrame = insAlloc(sizeof(MethodFrame));
verbose_only( if (vbNames) {
vbNames->lirNameMap->addName(methodFrame, "methodFrame");
})
coreAddr = InsConstPtr(core);
#ifdef VMCFG_TELEMETRY_SAMPLER
// Emit the code to take a sample of the MethodFrame stack. This is only
// emitted when the sampler is enabled so there is no performance impact
// otherwise.
if(core->samplerEnabled) {
LIns* sampleTicks = loadIns(LIR_ldi, offsetof(AvmCore,sampleTicks), coreAddr, ACCSET_OTHER);
CodegenLabel &afterTakeSample_label = createLabel("afterTakeSample");
branchToLabel(LIR_jf, sampleTicks, afterTakeSample_label);
callIns(FUNCTIONID(takeSampleWrapper), 1, coreAddr);
emitLabel(afterTakeSample_label);
}
#endif
// replicate MethodFrame ctor inline
LIns* currentMethodFrame = loadIns(LIR_ldp, offsetof(AvmCore,currentMethodFrame), coreAddr, ACCSET_OTHER);
// save env in MethodFrame.envOrCodeContext
// explicitly leave IS_EXPLICIT_CODECONTEXT clear
// explicitly leave DXNS_NOT_NULL clear, dxns is effectively null without doing the store here.
stp(env_param, methodFrame, offsetof(MethodFrame,envOrCodeContext), ACCSET_OTHER);
stp(currentMethodFrame, methodFrame, offsetof(MethodFrame,next), ACCSET_OTHER);
stp(methodFrame, coreAddr, offsetof(AvmCore,currentMethodFrame), ACCSET_OTHER);
#ifdef _DEBUG
// poison MethodFrame.dxns since it's uninitialized by default
stp(InsConstPtr((void*)(uintptr_t)0xdeadbeef), methodFrame, offsetof(MethodFrame,dxns), ACCSET_OTHER);
#endif
#ifdef DEBUG
// Check that the BugCompatibility that would be used to OSR this function,
// whether or not we actually did so, agrees with the value returned from
// currentBugCompatibility() every time the function is executed. Note that
// it would be preferable to modify currentBugCompatibility() such that it is
// structurally impossible for this test to fail.
callIns(FUNCTIONID(osr_check_bugcompatibility), 1, env_param);
#endif
// allocate room for our local variables
vars = insAlloc(framesize << VARSHIFT(info)); // room for double|Atom|int|pointer, and float4 if used
tags = insAlloc(framesize); // one tag byte per var
prolog_buf->sp = vars;
varTracker->init(vars, tags);
verbose_only( if (prolog_buf->printer) {
prolog_buf->printer->lirNameMap->addName(env_param, "env");
prolog_buf->printer->lirNameMap->addName(argc_param, "argc");
prolog_buf->printer->lirNameMap->addName(ap_param, "ap");
prolog_buf->printer->lirNameMap->addName(vars, "vars");
prolog_buf->printer->lirNameMap->addName(tags, "tags");
})
debug_only(
void** extras = new (*alloc1) void*[2];
extras[0] = vars;
extras[1] = tags;
validate1->setCheckAccSetExtras(extras);
validate2->setCheckAccSetExtras(extras);
)
// stack overflow check - use methodFrame address as comparison
LIns *d = loadIns(LIR_ldp, offsetof(AvmCore, minstack), coreAddr, ACCSET_OTHER);
LIns *c = binaryIns(LIR_ltup, methodFrame, d);
CodegenLabel &begin_label = createLabel("begin");
branchToLabel(LIR_jf, c, begin_label);
callIns(FUNCTIONID(handleStackOverflowMethodEnv), 1, env_param);
emitLabel(begin_label);
// we emit the undefined constant here since we use it so often and
// to ensure it dominates all uses.
undefConst = InsConstAtom(undefinedAtom);
// whether this sequence is interruptable or not.
interruptable = ! info->isNonInterruptible();
// then space for the exception frame, be safe if its an init stub
if (driver->hasReachableExceptions()) {
// [_save_eip][ExceptionFrame]
// offsets of local vars, rel to current ESP
_save_eip = insAlloc(sizeof(intptr_t));
_ef = insAlloc(sizeof(ExceptionFrame));
verbose_only( if (vbNames) {
vbNames->lirNameMap->addName(_save_eip, "_save_eip");
vbNames->lirNameMap->addName(_ef, "_ef");
})
} else {
_save_eip = NULL;
_ef = NULL;
}
#ifdef DEBUGGER
if (haveDebugger) {
// tell the sanity checker about vars and tags
debug_only( checker->init(vars, tags); )
// Allocate space for the call stack
csn = insAlloc(sizeof(CallStackNode));
verbose_only( if (vbNames) {
vbNames->lirNameMap->addName(csn, "csn");
})
}
#ifdef VMCFG_STACK_METRICS
callIns(FUNCTIONID(recordStackPointerWrapper), 1, coreAddr);
#endif
#endif
#ifdef DEBUG
jit_sst = new (*alloc1) uint16_t[framesize];
memset(jit_sst, 0, framesize * sizeof(uint16_t));
#endif
//
// copy args to local frame
//
// copy required args, and initialize optional args.
// this whole section only applies to functions that actually
// have arguments.
const int param_count = ms->param_count();
const int optional_count = ms->optional_count();
const int required_count = param_count - optional_count;
LIns* apArg = ap_param;
if (info->hasOptional())
{
// compute offset of first optional arg
int offset = 0;
for (int i=0, n=required_count; i <= n; i++)
offset += argSize(ms, i);
// now copy the default optional values
LIns* argcarg = argc_param;
for (int i=0, n=optional_count; i < n; i++)
{
// first set the local[p+1] = defaultvalue
int param = i + required_count; // 0..N
int loc = param+1;
LIns* defaultVal = InsConstAtom(ms->getDefaultValue(i), /*tainted*/true);
defaultVal = atomToNativeRep(loc, defaultVal);
localSet(loc, defaultVal, state->value(loc).traits);
// then generate: if (argc > p) local[p+1] = arg[p+1]
LIns* cmp = binaryIns(LIR_lei, argcarg, InsConst(param));
CodegenLabel& optional_label = createLabel("param_", i);
branchToLabel(LIR_jt, cmp, optional_label); // will patch
copyParam(loc, offset);
emitLabel(optional_label);
}
}
else
{
// !info->hasOptional()
AvmAssert(optional_count == 0);
}
// now set up the required args (we can ignore argc)
// for (int i=0, n=param_count; i <= n; i++)
// framep[i] = argv[i];
int offset = 0;
for (int i=0, n=required_count; i <= n; i++)
copyParam(i, offset);
if (info->unboxThis())
{
localSet(0, atomToNativeRep(0, localGet(0)), state->value(0).traits);
}
int firstLocal = 1+param_count;
// Capture remaining args.
//
// Optimized ...rest and 'arguments':
//
// We avoid constructing the rest array if possible. An analysis in the
// verifier sets the _lazyRest bit if access patterns to the rest argument
// or the arguments array are only OBJ.length or OBJ[prop] for arbitrary
// propery; see comments in Verifier::verify. The former pattern
// results in an OP_restargc instruction, while the latter results in an
// OP_restarg instruction. Those instructions will access an unconsed
// rest array or arguments array when possible, and otherwise access a
// constructed rest array. (For example, if prop turns out to be "slice"
// we must construct the array. This will almost never happen.) They're
// implemented via helper functions restargcHelper and restargHelper.
//
// The unconsed rest or arguments array, the argument count, the consed array,
// and the flag that determines whether to use the unconsed or the consed array,
// are represented as follows:
//
// - The unconsed array restArg is represented indirectly via ap_param and
// rest_offset.
// - The argument count restArgc is a LIR expression of type uint32 computed from
// argc_param and param_count.
// - The rest parameter local is an array, it is either null (no rest array
// consed yet) or a raw array pointer, so it doubles as the flag. The offset
// of this variable is 1+param_count.
//
// The rest parameter local is passed by reference to restargHelper, which
// may update it.
//
// The difference between a ...rest argument and an arguments array are that in
// the former case,
//
// restArgc = MAX(argc_param - param_count, 0)
// restArg = is ap_param + rest_offset
//
// while in the latter case
//
// restArgc = argc_param
// restArg = ap_param + 1
//
// restArg is computed in the code generation case for OP_restarg.
if (info->needRest())
{
if (info->lazyRest())
{
LIns* x0 = binaryIns(LIR_subi, argc_param, InsConst(param_count));
LIns* x1 = binaryIns(LIR_lti, x0, InsConst(0));
restArgc = lirout->insChoose(x1, InsConst(0), x0, use_cmov);
// Store a NULL array pointer
localSet(firstLocal, InsConstPtr(0), ARRAY_TYPE);
}
else
{
//framep[info->param_count+1] = createRest(env, argv, argc);
// use csop so if rest value never used, we don't bother creating array
LIns* rest = callIns(FUNCTIONID(createRestHelper), 3,
env_param, argc_param, apArg);
localSet(firstLocal, rest, ARRAY_TYPE);
}
firstLocal++;
}
else if (info->needArguments())
{
if (info->lazyRest())
{
restArgc = argc_param;
// Store a NULL array pointer
localSet(firstLocal, InsConstPtr(0), ARRAY_TYPE);
}
else {
//framep[info->param_count+1] = createArguments(env, argv, argc);
// use csop so if arguments never used, we don't create it
LIns* arguments = callIns(FUNCTIONID(createArgumentsHelper), 3,
env_param, argc_param, apArg);
localSet(firstLocal, arguments, ARRAY_TYPE);
}
firstLocal++;
}
// set remaining locals to undefined
for (int i=firstLocal, n = ms->local_count(); i < n; i++) {
AvmAssert(state->value(i).traits == NULL);
localSet(i, undefConst, NULL); // void would be more precise
}
/// SWITCH PIPELINE FROM PROLOG TO BODY
verbose_only( if (vbWriter) { vbWriter->flush();} )
// we have written the prolog to prolog_buf, now create a new
// LirBuffer to hold the body, and redirect further output to the body.
LirBuffer *body_buf = new (*lir_alloc) LirBuffer(*lir_alloc);
LirWriter *body = new (*alloc1) LirBufWriter(body_buf, core->config.njconfig);
skip_ins = body->insSkip(prolog->lastIns);
debug_only(
body = validate3 = new (*alloc1) ValidateWriter(body, vbNames, "writePrologue(body)");
validate3->setCheckAccSetExtras(extras);
)
verbose_only(
if (verbose()) {
AvmAssert(vbNames != NULL);
body_buf->printer = vbNames;
body = vbWriter = new (*alloc1) VerboseWriter(*alloc1, body, vbNames, &pool->codeMgr->log);
}
)
redirectWriter->out = body;
/// END SWITCH CODE
varTracker->initNotNull(state);
if (osr)
emitOsrBranch();
// Generate code to initialize the object, if we are compiling an initializer.
// This is intentionally before debugEnter(), to match interpreter behavior.
if (info->isConstructor())
emitInitializers();
if (haveDebugger)
emitDebugEnter();
if (driver->hasReachableExceptions()) {
// _ef.beginTry(core);
callIns(FUNCTIONID(beginTry), 2, _ef, coreAddr);
// Exception* setjmpResult = setjmp(_ef.jmpBuf);
// ISSUE this needs to be a cdecl call
LIns* jmpbuf = lea(offsetof(ExceptionFrame, jmpbuf), _ef);
setjmpResult = callIns(FUNCTIONID(fsetjmp), 2, jmpbuf, InsConst(0));
// If (setjmp() != 0) goto catch dispatcher, which we generate in the epilog.
// Note that register contents following setjmp return via longjmp are not predictable.
branchToLabel(LIR_jf, eqi0(setjmpResult), catch_label);
}
verbose_only( if (vbWriter) { vbWriter->flush();} )
}
FUNCTION(FUNCADDR(OSR::adjustFrame), SIG4(B, P, P, P, P), osr_adjust_frame)
// Emit code to call OSR::adjust_frame and conditionally enter loop.
// Note that adjust_frame will call debugEnter if necessary, since
// the loop-branch will skip the normal call to debugEnter.
void CodegenLIR::emitOsrBranch()
{
// Compiling an OSR entry point; save FrameState at the OSR loop header,
// for later use by adjust_frame().
FrameState* osr_state = mmfx_new(FrameState(ms, info));
const FrameState* loop_state = driver->getFrameState(osr->osrPc());
AvmAssert(loop_state->targetOfBackwardsBranch);
osr_state->init(loop_state);
osr->setFrameState(osr_state);
// if (exec->current_osr) {
// adjustFrame(...);
// goto osr_pc;
// }
BaseExecMgr* exec = (BaseExecMgr*) core->exec;
// It is safe to emit &exec->current_osr as a constant here because
// it never changes during runtime.
LIns* current_osr = ldp(InsConstPtr(&exec->current_osr), 0, ACCSET_OTHER);
CodegenLabel &no_osr_label = createLabel("no_osr");
suspendCSE();
branchToLabel(LIR_jt, eqp0(current_osr), no_osr_label);
callIns(FUNCTIONID(osr_adjust_frame), 4,
methodFrame, haveDebugger ? csn : InsConstPtr(0), vars, tags);
branchToAbcPos(LIR_j, NULL, osr->osrPc());
emitLabel(no_osr_label);
resumeCSE();
}
void CodegenLIR::emitInitializers()
{
struct JitInitVisitor: public InitVisitor {
CodegenLIR *jit;
JitInitVisitor(CodegenLIR *jit) : jit(jit) {}
virtual ~JitInitVisitor() {}
void defaultVal(Atom value, uint32_t slot, Traits* slotType) {
#ifdef NJ_VERBOSE
if (jit->verbose()) {
jit->vbWriter->flush();
jit->core->console << "init [" << slot << "] = " << asAtom(value) << "\n";
}
#endif
LIns* defaultVal = jit->InsConstAtom(value, /*tainted*/true);
defaultVal = jit->atomToNativeRep(slotType, defaultVal);
jit->emitSetslot(OP_setslot, slot, 0, defaultVal);
}
};
JitInitVisitor visitor(this);
Traits* t = info->declaringTraits();
const TraitsBindings *tb = t->getTraitsBindings();
t->visitInitBody(&visitor, toplevel, tb);
}
void CodegenLIR::emitDebugEnter()
{
#ifdef DEBUGGER
for (int i = ms->scope_base(), n = ms->stack_base(); i < n; ++i)
localSet(i, undefConst, VOID_TYPE);
callIns(FUNCTIONID(debugEnter), 5,
env_param,
tags,
csn,
vars,
driver->hasReachableExceptions() ? _save_eip : InsConstPtr(0));
#endif // DEBUGGER
}
void CodegenLIR::copyParam(int i, int& offset) {
LIns* apArg = ap_param;
Traits* type = ms->paramTraits(i);
LIns *arg;
switch (bt(type)) {
#ifdef VMCFG_FLOAT
case BUILTIN_float:
arg = loadIns(LIR_ldf, offset, apArg, ACCSET_OTHER, LOAD_CONST);
offset += sizeof(Atom); // float is 4 bytes; but on x64, it takes up 8 bytes too, just like any int.
break;
case BUILTIN_float4:
// float4 is passed inline [i.e. not as a pointer to the value !! ]
arg = loadIns(LIR_ldf4, offset, apArg, ACCSET_OTHER, LOAD_CONST);
offset += sizeof(float4_t);
break;
#endif // VMCFG_FLOAT
case BUILTIN_number:
arg = loadIns(LIR_ldd, offset, apArg, ACCSET_OTHER, LOAD_CONST);
offset += sizeof(double);
break;
case BUILTIN_int:
case BUILTIN_uint:
case BUILTIN_boolean:
// in the args these are widened to intptr_t or uintptr_t, so truncate here.
arg = p2i(loadIns(LIR_ldp, offset, apArg, ACCSET_OTHER, LOAD_CONST));
offset += sizeof(Atom);
break;
default:
arg = loadIns(LIR_ldp, offset, apArg, ACCSET_OTHER, LOAD_CONST);
offset += sizeof(Atom);
break;
}
localSet(i, arg, type);
}
void CodegenLIR::emitCopy(int src, int dest) {
localSet(dest, localCopy(src), state->value(src).traits);
}
void CodegenLIR::emitGetscope(int scope_index, int dest)
{
Traits* t = info->declaringScope()->getScopeTraitsAt(scope_index);
LIns* scope = loadEnvScope();
LIns* scopeobj = loadIns(LIR_ldp, offsetof(ScopeChain,_scopes) + scope_index*sizeof(Atom), scope, ACCSET_OTHER, LOAD_CONST);
localSet(dest, atomToNativeRep(t, scopeobj), t);
}
void CodegenLIR::emitSwap(int i, int j) {
LIns* t = localCopy(i);
localSet(i, localCopy(j), state->value(j).traits);
localSet(j, t, state->value(i).traits);
}
void CodegenLIR::emitKill(int i)
{
localSet(i, undefConst, NULL);
}
#ifdef VMCFG_FLOAT
void CodegenLIR::emitNumericOp2(int32_t op1, int32_t op2, const LIREmitter& emitIns)
{
static_assert(kIntptrType == 6, "You can't change kIntptrType just like that");
static_assert(kDoubleType == 7, "You can't change kDoubleType just like that");
const FrameValue& v1 = state->value(op1); (void) v1;
const FrameValue& v2 = state->value(op2); (void) v2;
AvmAssert (v1.traits == OBJECT_TYPE && v2.traits == OBJECT_TYPE && v1.notNull && v2.notNull);
CodegenLabel slow_path("_numeric2_slow_path_");
CodegenLabel both_double("_numeric2_both_double_");
CodegenLabel done("_numeric2_done_");
CodegenLabel int_op_number("_numeric2_int_op_number_");
CodegenLabel v1_is_int("_numeric2_v1_is_int_");
LIns* val1 = loadAtomRep(op1);
LIns* val2 = loadAtomRep(op2);
LIns* result = insAlloc(sizeof(intptr_t));
LIns* tag = andp( binaryIns(LIR_andp, val1, val2), AtomConstants::kAtomTypeMask);
// kIntptrType
suspendCSE();
/* NOTE: to make the fast path faster, we're playing with the bits to quickly determine if
"both are double", "at least one is not Number" and "both are int"
We rely on the Atom TAG scheme, i.e. "kIntPtrType is 6" and "kDoubleType is 7",
see static asserts above.
*/
branchToLabel(LIR_jt, eqp(tag, AtomConstants::kDoubleType), both_double);
branchToLabel(LIR_jf, eqp(tag, AtomConstants::kIntptrType), slow_path);
LIns* xormask = andp( binaryIns(LIR_xorp,val1,val2), AtomConstants::kAtomTypeMask );
branchToLabel(LIR_jf, eqp0(xormask), int_op_number);
// both_integers:
stp( nativeToAtom(
emitIns(BUILTIN_number, p2dIns(rshp(val1, AtomConstants::kAtomTypeSize))
, p2dIns(rshp(val2, AtomConstants::kAtomTypeSize)) ),
NUMBER_TYPE ),
result, 0, ACCSET_OTHER);
JIT_EVENT(jit_numeric2_fast_int);
branchToLabel(LIR_j, NULL, done);
// int_op_number:
emitLabel(int_op_number);
tag = andp (val1, AtomConstants::kAtomTypeMask);
branchToLabel(LIR_jt, eqp(tag, AtomConstants::kIntptrType),v1_is_int );
// v2_is_int: (v1 is number!)
stp( nativeToAtom(
emitIns(BUILTIN_number, ldd(subp(val1, AtomConstants::kDoubleType), 0, ACCSET_OTHER)
, p2dIns(rshp(val2, AtomConstants::kAtomTypeSize)) ),
NUMBER_TYPE ),
result, 0, ACCSET_OTHER);
JIT_EVENT(jit_numeric2_dbl_int);
branchToLabel(LIR_j, NULL, done);
// v1_is_int: (v2 is number!)
emitLabel(v1_is_int);
stp( nativeToAtom(
emitIns(BUILTIN_number, p2dIns(rshp(val1, AtomConstants::kAtomTypeSize))
, ldd(subp(val2, AtomConstants::kDoubleType), 0, ACCSET_OTHER) ),
NUMBER_TYPE ),
result, 0, ACCSET_OTHER);
JIT_EVENT(jit_numeric2_dbl_int);
branchToLabel(LIR_j, NULL, done);
// slow_path:
emitLabel(slow_path);
stp( emitIns(BUILTIN_any, val1, val2) , result, 0, ACCSET_OTHER);
JIT_EVENT(jit_numeric2_slow);
branchToLabel(LIR_j, NULL, done);
// both_double:
emitLabel(both_double);
stp( nativeToAtom(
emitIns(BUILTIN_number, ldd(subp(val1, AtomConstants::kDoubleType), 0, ACCSET_OTHER)
, ldd(subp(val2, AtomConstants::kDoubleType), 0, ACCSET_OTHER))
, NUMBER_TYPE),
result, 0, ACCSET_OTHER);
JIT_EVENT(jit_numeric2_fast_double);
// done:
emitLabel(done);
resumeCSE();
localSet( op1, ldp(result, 0, ACCSET_OTHER), OBJECT_TYPE);
}
void CodegenLIR::emitNumericOp1(int32_t op1, const LIREmitter &emitIns)
{
const FrameValue& v = state->value(op1); (void)v;
AvmAssert(v.traits == OBJECT_TYPE && v.notNull);
CodegenLabel not_intptr("_not_intptr_");
CodegenLabel not_double("_not_double_");
CodegenLabel done("_done_");
LIns* val = loadAtomRep(op1); // load Atom.
LIns* result = insAlloc(sizeof(intptr_t));
LIns* tag = andp(val, AtomConstants::kAtomTypeMask);
// kIntptrType
suspendCSE();
branchToLabel(LIR_jf, eqp(tag, AtomConstants::kIntptrType), not_intptr);
stp( nativeToAtom(
emitIns(BUILTIN_number, p2dIns(rshp(val, AtomConstants::kAtomTypeSize)) )
, NUMBER_TYPE
) , result, 0, ACCSET_OTHER);
JIT_EVENT(jit_numeric1_fast_int);
branchToLabel(LIR_j, NULL, done);
emitLabel(not_intptr);
// kDoubleType
branchToLabel(LIR_jf, eqp(tag, AtomConstants::kDoubleType), not_double);
stp( nativeToAtom(
emitIns(BUILTIN_number, ldd(val, -AtomConstants::kDoubleType, ACCSET_OTHER) )
, NUMBER_TYPE
) , result, 0, ACCSET_OTHER);
JIT_EVENT(jit_numeric1_fast_number);
branchToLabel(LIR_j, NULL, done);
emitLabel(not_double);
stp( emitIns(BUILTIN_any, val) , result, 0, ACCSET_OTHER);
JIT_EVENT(jit_numeric1_slow);
emitLabel(done);
resumeCSE();
localSet( op1, ldp(result, 0, ACCSET_OTHER), OBJECT_TYPE);
}
#endif // VMCFG_FLOAT
#ifdef VMCFG_FASTPATH_ADD
#ifdef VMCFG_FASTPATH_ADD_INLINE
// Emit code for the fastpath for int + intptr => intptr addition.
// Branch to fallback label if rhs is not intptr, or result cannot be so represented due to overflow.
//
// 64-bit:
//
// FAST_ADD_INT_TO_ATOM(lhs, rhs, result, fallback)
// if ((rhs & kAtomTypeMask) != kIntptrType) goto fallback; # punt if atom argument rhs is not intptr
// intptr_t lhsExtended = i; # extend int argument lhs to atom size
// intptr_t lhsShifted = lhsExtended << (kAtomTypeSize+7); # align lhs with 54-bit payload of rhs (see next step)
// intptr_t rhsShifted = rhs << 7; # left-justify 54-bit payload of rhs (note rhs is tagged)
// intptr_t sumShifted = lhsShifted + rhsShifted; # add aligned values, producing 57-bit tagged sum, left-justified
// if (OVERFLOW) goto fallback; # punt on overflow, as sum will not fit in 57-bit field allotted
// intptr_t sum = sumShifted >> 7; # right-justify 57-bit tagged value (54 payload bits + 3 tag bits)
// result = sum; # result is intptr atom, properly tagged
//
// 32-bit:
//
// FAST_ADD_INT_TO_ATOM(lhs, rhs, result, fallback)
// if ((rhs & kAtomTypeMask) != kIntptrType) goto fallback; # punt if atom argument rhs is not intptr
// intptr_t lhsExtended = i; # extend int argument lhs to atom size (a nop on 32-bit platforms)
// intptr_t lhsShifted = lhsExtended << kAtomTypeSize; # left-justify 29-bit payload
// intptr_t lhsRestored = lhsShifted >> kAtomTypeSize; # restore
// if (lhsRestored != lhsExtended) goto fallback; # high-order bits were lost, value will not fit in 29-bit field allotted
// intptr_t sum = lhsShifted + rhs; # add left-justified lhs to tagged rhs, resulting in tagged result
// if (OVERFLOW) goto fallback; # punt on overflow, as sum will not fit in 29-bit field allotted
// result = sum; # result is intptr atom, properly tagged
//
// Note: If lhs will not fit in 29 bits, the fastpath will fail even if the result may fit. We expect this to be an
// uncommon case. Generating code as we do avoids extra tag manipulations that would be required if we did a full
// 32-bit addition followed by a range check.
void CodegenLIR::emitIntPlusAtomFastpath(int i, Traits* type, LIns* lhs, LIns* rhs, CodegenLabel &fallback)
{
LIns* tag = andp(rhs, AtomConstants::kAtomTypeMask);
branchToLabel(LIR_jf, eqp(tag, AtomConstants::kIntptrType), fallback);
LIns* lhsExtended = i2p(lhs);
#ifdef AVMPLUS_64BIT
// int argument is guaranteed to fit, but must restrict intptr result to 54 bit range
// TODO: Consider maintaining 54-bit intptrs in pre-shifted form.
LIns* lhsShifted = lshp(lhsExtended, atomSignExtendShift);
LIns* rhsShifted = lshp(rhs, atomSignExtendShift-AtomConstants::kAtomTypeSize);
LIns* sumShifted = branchJovToLabel(LIR_addjovp, lhsShifted, rhsShifted, fallback);
LIns* sum = rshp(sumShifted, atomSignExtendShift-AtomConstants::kAtomTypeSize);
#else
// verify that int value will fit in intptr
LIns* lhsShifted = lshp(lhsExtended, AtomConstants::kAtomTypeSize);
LIns* lhsRestored = rshp(lhsShifted, AtomConstants::kAtomTypeSize);
branchToLabel(LIR_jf, binaryIns(LIR_eqp, lhsRestored, lhsExtended), fallback);
LIns* sum = branchJovToLabel(LIR_addjovp, lhsShifted, rhs, fallback);
#endif
localSet(i, sum, type);
}
#endif /* VMCFG_FASTPATH_ADD_INLINE */
// Emit code for int + atom => atom addition.
// The usual helper function call may be bypassed with a fastpath for the int + intptr => intptr case:
//
// intptr_t rhsa = CONVERT_TO_ATOM(rhs); # box atom if needed
// FAST_ADD_INT_TO_ATOM(lhs, rhsa, result, fallback); # handle the fastpath, branching to fallback label on failure
// goto done; # fastpath succeeded
// fallback:
// result = op_add_a_ia(coreAddr, lhs, rhsa); # fastpath failed, fall back to helper function
// done:
void CodegenLIR::emitAddIntToAtom(int i, int j, Traits* type)
{
LIns* rhs = loadAtomRep(j);
LIns* lhs = localGet(i);
FLOAT_ONLY(const bool floatSupport = pool->hasFloatSupport();)
const CallInfo* addFunction = IFFLOAT(floatSupport ? FUNCTIONID(op_add_a_ia) : FUNCTIONID(op_add_a_ia_nofloat), FUNCTIONID(op_add_a_ia));
#ifdef VMCFG_FASTPATH_ADD_INLINE
if (inlineFastpath) {
CodegenLabel fallback("fallback");
CodegenLabel done("done");
suspendCSE();
emitIntPlusAtomFastpath(i, type, lhs, rhs, fallback);
JIT_EVENT(jit_add_a_ia_fast_intptr);
branchToLabel(LIR_j, NULL, done);
emitLabel(fallback);
LIns* out = callIns(addFunction, 3, coreAddr, lhs, rhs);
localSet(i, out, type);
JIT_EVENT(jit_add_a_ia_slow);
emitLabel(done);
resumeCSE();
return;
}
#endif
LIns* out = callIns(addFunction, 3, coreAddr, lhs, rhs);
localSet(i, out, type);
JIT_EVENT(jit_add_a_ia);
}
// Emit code for double + atom => atom addition.
void CodegenLIR::emitAddDoubleToAtom(int i, int j, Traits* type)
{
LIns* rhs = loadAtomRep(j);
LIns* lhs = localGetd(i);
#ifdef VMCFG_FLOAT
const bool floatSupport = pool->hasFloatSupport();
const CallInfo* addFunction = floatSupport ? FUNCTIONID(op_add_a_da) : FUNCTIONID(op_add_a_da_nofloat);
#else
const CallInfo* addFunction = FUNCTIONID(op_add_a_da);
#endif // VMCFG_FLOAT
LIns* out = callIns(addFunction, 3, coreAddr, lhs, rhs);
localSet(i, out, type);
JIT_EVENT(jit_add_a_da);
}
// Emit code for atom + int => atom addition.
// The usual helper function call may be bypassed with a fastpath for the intptr + int => intptr case:
//
// intptr_t lhsa = CONVERT_TO_ATOM(lhs); # box atom if needed
// FAST_ADD_INT_TO_ATOM(rhs, lhsa, result, fallback); # handle the fastpath, note it is OK to commute arguments here
// goto done; # fastpath succeeded
// fallback:
// result = op_add_a_ai(coreAddr, lhsa, rhs); # fastpath failed, fall back to helper function
// done:
void CodegenLIR::emitAddAtomToInt(int i, int j, Traits* type)
{
LIns* lhs = loadAtomRep(i);
LIns* rhs = localGet(j);
#ifdef VMCFG_FLOAT
const bool floatSupport = pool->hasFloatSupport();
const CallInfo* addFunction = floatSupport ? FUNCTIONID(op_add_a_ai) : FUNCTIONID(op_add_a_ai_nofloat);
#else
const CallInfo* addFunction = FUNCTIONID(op_add_a_ai);
#endif // VMCFG_FLOAT
#ifdef VMCFG_FASTPATH_ADD_INLINE
if (inlineFastpath) {
CodegenLabel fallback("fallback");
CodegenLabel done("done");
suspendCSE();
emitIntPlusAtomFastpath(i, type, rhs, lhs, fallback);
JIT_EVENT(jit_add_a_ai_fast_intptr);
branchToLabel(LIR_j, NULL, done);
emitLabel(fallback);
LIns* out = callIns(addFunction, 3, coreAddr, lhs, rhs);
localSet(i, out, type);
JIT_EVENT(jit_add_a_ai_slow);
emitLabel(done);
resumeCSE();
return;
}
#endif
LIns* out = callIns(addFunction, 3, coreAddr, lhs, rhs);
localSet(i, out, type);
JIT_EVENT(jit_add_a_ai);
}
// Emit code for atom + double => atom addition.
void CodegenLIR::emitAddAtomToDouble(int i, int j, Traits* type)
{
LIns* lhs = loadAtomRep(i);
LIns* rhs = localGetd(j);
#ifdef VMCFG_FLOAT
const bool floatSupport = pool->hasFloatSupport();
const CallInfo* addFunction = floatSupport ? FUNCTIONID(op_add_a_ad) : FUNCTIONID(op_add_a_ad_nofloat);
#else
const CallInfo* addFunction = FUNCTIONID(op_add_a_ad);
#endif // VMCFG_FLOAT
LIns* out = callIns(addFunction, 3, coreAddr, lhs, rhs);
localSet(i, out, type);
JIT_EVENT(jit_add_a_ad);
}
// Emit code for atom + atom => atom addition.
//
// We implement a fastpath for the intptr + intptr => intptr case:
//
// 64-bit:
//
// intptr_t lhsa = CONVERT_TO_ATOM(lhs); # box atom if needed
// intptr_t rhsa = CONVERT_TO_ATOM(rhs); # box atom if needed
// if (((lhsa ^ kIntptrType) | (rhsa ^ kIntptrType)) & kAtomTypeMask) goto fallback;
// # both arguments are intptr atoms
// intptr_t lhsStripped = lhsa - kIntptrType; # zero out tag bits on lhs
// intptr_t lhsShifted = lhsStripped << 7; # left-justify 54-bit payload (followed by 0s in tag position)
// intptr_t rhsShifted = rhsa << 7; # align rhs payload and tag with left-justified lhs
// intptr_t sumShifted = lhsShifted + rhsShifted; # add aligned values, producing 54-bit left-justified sum followed by tag
// if (OVERFLOW) goto fallback;
// result = sumShifted >> 7; # right-justify tagged sum (54-bit payload + 3-bit tag)
// goto done;
// fallback:
// result = op_add_a_aa(coreAddr, lhsa, rhsa); # handle the general case out-of-line
// done:
//
// 32-bit:
//
// intptr_t lhsa = CONVERT_TO_ATOM(lhs); # box atom if needed
// intptr_t rhsa = CONVERT_TO_ATOM(rhs); # box atom if needed
// if (((lhsa ^ kIntptrType) | (rhsa ^ kIntptrType)) & kAtomTypeMask) goto fallback;
// # both arguments are intptr atoms
// intptr_t lhsStripped = lhsa - kIntptrType; # zero out tag bits on lhs (note rhs retains its tag)
// intptr_t sum = lhsStripped + rhs; # add, producing 29-bit sum followed by 3-bit tag
// if (OVERFLOW) goto fallback;
// result = sum;
// goto done;
// fallback:
// result = op_add_a_aa(coreAddr, lhsa, rhsa); # handle the general case out-of-line
// done:
void CodegenLIR::emitAddAtomToAtom(int i, int j, Traits* type)
{
LIns* lhs = loadAtomRep(i);
LIns* rhs = loadAtomRep(j);
#ifdef VMCFG_FLOAT
const bool floatSupport = pool->hasFloatSupport();
const CallInfo* addFunction = floatSupport ? FUNCTIONID(op_add_a_aa) : FUNCTIONID(op_add_a_aa_nofloat);
#else
const CallInfo* addFunction = FUNCTIONID(op_add_a_aa);
#endif // VMCFG_FLOAT
#ifdef VMCFG_FASTPATH_ADD_INLINE
if (inlineFastpath) {
CodegenLabel fallback("fallback");
CodegenLabel done("done");
// intptr + intptr fastpath
suspendCSE();
LIns* t0 = xorp(lhs, AtomConstants::kIntptrType);
LIns* t1 = xorp(rhs, AtomConstants::kIntptrType);
LIns* t2 = binaryIns(LIR_orp, t0, t1);
LIns* t3 = andp(t2, AtomConstants::kAtomTypeMask);
branchToLabel(LIR_jf, eqp0(t3), fallback);
LIns* lhsStripped = subp(lhs, AtomConstants::kIntptrType);
#ifdef AVMPLUS_64BIT
// restrict range of intptr result to 54 bits
// since 64-bit int atoms expect exactly 54 bits of precision, shift bit 54+3 up into the sign bit
LIns* lhsShifted = lshp(lhsStripped, atomSignExtendShift-AtomConstants::kAtomTypeSize);
LIns* rhsShifted = lshp(rhs, atomSignExtendShift-AtomConstants::kAtomTypeSize);
LIns* sumShifted = branchJovToLabel(LIR_addjovp, lhsShifted, rhsShifted, fallback);
LIns* sum = rshp(sumShifted, atomSignExtendShift-AtomConstants::kAtomTypeSize);
#else
LIns* sum = branchJovToLabel(LIR_addjovp, lhsStripped, rhs, fallback);
#endif
localSet(i, sum, type);
JIT_EVENT(jit_add_a_aa_fast_intptr);
branchToLabel(LIR_j, NULL, done);
emitLabel(fallback);
LIns* out = callIns(addFunction, 3, coreAddr, lhs, rhs);
localSet(i, out, type);
JIT_EVENT(jit_add_a_aa_slow);
emitLabel(done);
resumeCSE();
return;
}
#endif
LIns* out = callIns(addFunction, 3, coreAddr, lhs, rhs);
localSet(i, atomToNativeRep(type, out), type);
JIT_EVENT(jit_add_a_aa);
}
#else /* VMCFG_FASTPATH_ADD */
void CodegenLIR::emitAddAtomToAtom(int i, int j, Traits* type)
{
LIns* lhs = loadAtomRep(i);
LIns* rhs = loadAtomRep(j);
#ifdef VMCFG_FLOAT
const bool floatSupport = pool->hasFloatSupport();
const CallInfo* addFunction = floatSupport ? FUNCTIONID(op_add) : FUNCTIONID(op_add_nofloat);
#else
const CallInfo* addFunction = FUNCTIONID(op_add);
#endif // VMCFG_FLOAT
LIns* out = callIns(addFunction, 3, coreAddr, lhs, rhs);
localSet(i, atomToNativeRep(type, out), type);
JIT_EVENT(jit_add);
}
#endif /* VMCFG_FASTPATH_ADD */
void CodegenLIR::emitAdd(int i, int j, Traits* type)
{
const FrameValue& val1 = state->value(i);
const FrameValue& val2 = state->value(j);
if ((val1.traits == STRING_TYPE && val1.notNull) || (val2.traits == STRING_TYPE && val2.notNull)) {
// string concatenation
AvmAssert(type == STRING_TYPE);
LIns* lhs = convertToString(i, true);
LIns* rhs = convertToString(j, true);
LIns* out = callIns(FUNCTIONID(concatStrings), 3, coreAddr, lhs, rhs);
localSet(i, out, type);
JIT_EVENT(jit_add_a_ss);
} else if (val1.traits && val2.traits && val1.traits->isNumeric() && val2.traits->isNumeric()) {
// numeric + numeric
// TODO: The tests for isNumeric() above could be isNumericOrBool(),
// but a corresponding change would be needed in the verifier, resulting
// in a slight change to the verifier's type inference algorithm.
AvmAssert(type == NUMBER_TYPE FLOAT_ONLY(|| type==FLOAT_TYPE || type== FLOAT4_TYPE) );
#ifdef VMCFG_FLOAT
if(!pool->hasFloatSupport())
type = NUMBER_TYPE;
else if(val1.traits== FLOAT4_TYPE || val2.traits == FLOAT4_TYPE)
type = FLOAT4_TYPE;
if (type == FLOAT4_TYPE) {
LIns* lhs = coerceToFloat4(i);
LIns* rhs = coerceToFloat4(j);
localSet(i, binaryIns(LIR_addf4, lhs, rhs), type);
JIT_EVENT(jit_add_a_f4f4);
} else if(type==FLOAT_TYPE){
AvmAssert(val1.traits == FLOAT_TYPE && val2.traits == FLOAT_TYPE);
LIns* lhs = coerceToFloat(i);
LIns* rhs = coerceToFloat(j);
localSet(i, binaryIns(LIR_addf, lhs, rhs), type);
JIT_EVENT(jit_add_a_ff);
} else
#endif // VMCFG_FLOAT
{
LIns* lhs = coerceToNumber(i);
LIns* rhs = coerceToNumber(j);
localSet(i, binaryIns(LIR_addd, lhs, rhs), type);
JIT_EVENT(jit_add_a_nn);
}
#ifdef VMCFG_FASTPATH_ADD
// If we arrive here, at least one argument is not known to be of a numeric type.
// Thus, having determined one argument to be of a known numeric type, we will coerce
// the other to an atom. We speculate that the other argument will already be an atom
// of type kIntptrType or kDoubleType, checking for these cases first.
} else if (val1.traits == INT_TYPE) {
// integer + atom
AvmAssert(type == OBJECT_TYPE);
emitAddIntToAtom(i, j, type);
} else if (val1.traits == NUMBER_TYPE) {
// double + atom
AvmAssert(type == OBJECT_TYPE);
emitAddDoubleToAtom(i, j, type);
} else if (val2.traits == INT_TYPE) {
// atom + integer
AvmAssert(type == OBJECT_TYPE);
emitAddAtomToInt(i, j, type);
} else if (val2.traits == NUMBER_TYPE) {
// atom + double
AvmAssert(type == OBJECT_TYPE);
emitAddAtomToDouble(i, j, type);
#endif
} else {
// Neither argument is known to be of a numeric type, so coerce both to atoms.
AvmAssert(type == OBJECT_TYPE);
emitAddAtomToAtom(i, j, type);
}
}
void CodegenLIR::writeBlockStart(const FrameState* state)
{
this->state = state;
// get the saved label for our block start and tie it to this location
CodegenLabel& label = getCodegenLabel(state->abc_pc);
emitLabel(label);
emitSetPc(state->abc_pc);
#ifdef DEBUG
memset(jit_sst, 0, framesize * sizeof(uint16_t));
#endif
// If this is the target of a backwards branch, generate an interrupt check.
#if defined (VMCFG_INTERRUPT_SAFEPOINT_POLL) && defined (NJ_SAFEPOINT_POLLING_SUPPORTED)
// Always poll for safepoints, regardless of config settings.
if (state->targetOfBackwardsBranch) {
AvmAssert(AvmCore::NotInterrupted == 0);
LIns* interrupted = loadIns(LIR_ldi, offsetof(AvmCore,interrupted), coreAddr, ACCSET_OTHER, LOAD_VOLATILE);
branchAndSavePC(interrupted, interrupt_label);
}
#else
if (interruptable && core->config.interrupts && state->targetOfBackwardsBranch) {
LIns* interrupted = loadIns(LIR_ldi, offsetof(AvmCore,interrupted),
coreAddr, ACCSET_OTHER, LOAD_VOLATILE);
LIns* cond = binaryIns(LIR_eqi, interrupted, InsConst(AvmCore::NotInterrupted));
branchToLabel(LIR_jf, cond, interrupt_label);
}
#endif
}
void CodegenLIR::writeOpcodeVerified(const FrameState* state, const uint8_t*, AbcOpcode)
{
verbose_only( if (vbWriter) { vbWriter->flush();} )
#ifdef DEBUG
this->state = NULL; // prevent access to stale state
int scopeTop = ms->scope_base() + state->scopeDepth;
for (int i=0, n=state->sp()+1; i < n; i++) {
if (i >= scopeTop && i < ms->stack_base())
continue;
const FrameValue& v = state->value(i);
AvmAssert(!jit_sst[i] || jit_sst[i] == v.sst_mask);
}
#else
(void)state;
#endif
}
// this is a no-op for the JIT because we do all label patching in emitLabel().
void CodegenLIR::writeFixExceptionsAndLabels(const FrameState*, const uint8_t*)
{}
void CodegenLIR::write(const FrameState* state, const uint8_t* pc, AbcOpcode opcode, Traits *type)
{
//AvmLog("CodegenLIR::write %x\n", opcode);
this->state = state;
emitSetPc(pc);
const uint8_t* nextpc = pc;
unsigned int imm30=0, imm30b=0;
int imm8=0, imm24=0;
AvmCore::readOperands(nextpc, imm30, imm24, imm30b, imm8);
int sp = state->sp();
switch (opcode) {
case OP_nop:
case OP_pop:
case OP_label:
// do nothing
break;
case OP_getlocal0:
case OP_getlocal1:
case OP_getlocal2:
case OP_getlocal3:
imm30 = opcode-OP_getlocal0;
// hack imm30 and fall through
case OP_getlocal:
emitCopy(imm30, sp+1);
break;
case OP_setlocal0:
case OP_setlocal1:
case OP_setlocal2:
case OP_setlocal3:
imm30 = opcode-OP_setlocal0;
// hack imm30 and fall through
case OP_setlocal:
emitCopy(sp, imm30);
break;
case OP_pushtrue:
AvmAssert(type == BOOLEAN_TYPE);
emitTaintedIntConst(sp+1, 1, type);
break;
case OP_pushfalse:
AvmAssert(type == BOOLEAN_TYPE);
emitTaintedIntConst(sp+1, 0, type);
break;
case OP_pushnull:
// Null is coercible to numeric 0, which I don't think
// is exploitable as an untainted value, but I'd give it
// a bit more scrutiny if it were possible to do this
// coercion at compile-time. As it stands, conversions
// of non-numeric to numeric values is done out-of-line.
AvmAssert(type == NULL_TYPE);
emitPtrConst(sp+1, 0, type);
break;
case OP_pushundefined:
// Through a chain of coercions undefined->false->0, there
// is a plausible way in which an untainted 0 might be generated.
// The comment above on OP_pushnull applies here as well; we are
// relying on the fact that such conversions are done out-of-line
// and will not reduce to constants.
AvmAssert(type == VOID_TYPE);
emitPtrConst(sp+1, (void*)undefinedAtom, type);
break;
case OP_pushshort:
AvmAssert(type == INT_TYPE);
emitTaintedIntConst(sp+1, (signed short)imm30, type);
break;
case OP_pushbyte:
AvmAssert(type == INT_TYPE);
emitTaintedIntConst(sp+1, (signed char)imm8, type);
break;
case OP_pushstring:
AvmAssert(type == STRING_TYPE);
emitPtrConst(sp+1, pool->getString(imm30), type);
break;
case OP_pushnamespace:
AvmAssert(type == NAMESPACE_TYPE);
emitPtrConst(sp+1, pool->cpool_ns[imm30], type);
break;
case OP_pushint:
AvmAssert(type == INT_TYPE);
emitTaintedIntConst(sp+1, pool->cpool_int[imm30], type);
break;
case OP_pushuint:
AvmAssert(type == UINT_TYPE);
emitTaintedIntConst(sp+1, pool->cpool_uint[imm30], type);
break;
case OP_pushdouble:
AvmAssert(type == NUMBER_TYPE);
emitTaintedDoubleConst(sp+1, &pool->cpool_double[imm30]->value);
break;
#ifdef VMCFG_FLOAT
case OP_pushfloat:
AvmAssert(type == FLOAT_TYPE);
emitTaintedFloatConst(sp+1, pool->cpool_float[imm30]->value);
break;
case OP_pushfloat4:
AvmAssert(type == FLOAT4_TYPE);
emitTaintedFloat4Const(sp+1, (const float4_t*) pool->cpool_float4[imm30] );
break;
#endif
case OP_pushnan:
AvmAssert(type == NUMBER_TYPE);
emitDoubleConst(sp+1, (double*)atomPtr(core->kNaN));
break;
case OP_lookupswitch:
emit(opcode, uintptr_t(pc + imm24), imm30b /*count*/);
break;
case OP_throw:
case OP_returnvalue:
case OP_returnvoid:
emit(opcode, sp);
break;
case OP_debugfile: {
if (haveDebugger || haveVTune || jit_observer) {
String* filename = pool->getString(imm30); (void)filename;
#ifdef DEBUGGER
if (haveDebugger) {
LIns* debugger = loadIns(LIR_ldp, offsetof(AvmCore, _debugger),
coreAddr, ACCSET_OTHER, LOAD_CONST);
callIns(FUNCTIONID(debugFile), 2,
debugger,
InsConstPtr(filename));
}
#endif // DEBUGGER
#ifdef VMCFG_VTUNE
Ins(LIR_file, InsConstPtr(filename));
#endif /* VMCFG_VTUNE */
if (jit_observer) {
JITDebugInfo* jdi = initJitDebugInfo();
JITDebugInfo::Info* info = new (*lir_alloc) JITDebugInfo::Info(JITDebugInfo::kFile);
info->file = imm30;
jdi->add(info);
Ins(LIR_pc, InsConstPtr(&info->pc));
}
}
break;
}
case OP_debugline: {
if (haveDebugger || haveVTune || jit_observer) {
// we actually do generate code for these, in debugger mode
#ifdef DEBUGGER
if (haveDebugger) {
LIns* debugger = loadIns(LIR_ldp, offsetof(AvmCore, _debugger),
coreAddr, ACCSET_OTHER, LOAD_CONST);
callIns(FUNCTIONID(debugLine), 2, debugger, InsConst(imm30));
}
#endif // DEBUGGER
#ifdef VMCFG_VTUNE
Ins(LIR_line, InsConst(imm30));
#endif /* VMCFG_VTUNE */
if (jit_observer) {
JITDebugInfo* jdi = initJitDebugInfo();
JITDebugInfo::Info* info = new (*lir_alloc) JITDebugInfo::Info(JITDebugInfo::kLine);
info->line = imm30; // filename pool index
jdi->add(info);
Ins(LIR_pc, InsConstPtr(&info->pc));
}
}
break;
}
case OP_dxns:
{
Stringp str = pool->getString(imm30); // assume been checked already
emit(opcode, (uintptr_t)str);
break;
}
case OP_dxnslate:
// codgen will call intern on the input atom.
emit(opcode, sp);
break;
case OP_kill:
emitKill(imm30);
break;
case OP_inclocal:
case OP_declocal:
AvmAssert(type==NUMBER_TYPE FLOAT_ONLY(|| type == FLOAT_TYPE|| type == FLOAT4_TYPE|| type == OBJECT_TYPE) );
emit(opcode, imm30, opcode==OP_inclocal ? 1 : -1, type);
break;
case OP_inclocal_i:
case OP_declocal_i:
emit(opcode, imm30, opcode==OP_inclocal_i ? 1 : -1, INT_TYPE);
break;
case OP_lessthan:
case OP_greaterthan:
case OP_lessequals:
case OP_greaterequals:
emit(opcode, 0, 0, BOOLEAN_TYPE);
break;
case OP_getdescendants:
{
const Multiname *name = pool->precomputedMultiname(imm30);
emit(opcode, (uintptr_t)name, 0, NULL);
break;
}
case OP_checkfilter:
emit(opcode, sp, 0, NULL);
break;
case OP_deleteproperty:
{
const Multiname *name = pool->precomputedMultiname(imm30);
emit(opcode, (uintptr_t)name, 0, BOOLEAN_TYPE);
break;
}
case OP_astype:
{
emit(OP_astype, (uintptr_t)type, sp, type && type->isMachineType() ? OBJECT_TYPE : type);
break;
}
case OP_astypelate:
{
emit(OP_astypelate, 0, 0, type);
break;
}
#ifdef VMCFG_FLOAT
case OP_unplus:
AvmAssert(type && (type == OBJECT_TYPE || type->isNumeric()));
/* Don't do anything; the coercion already happened */
break;
#endif
case OP_coerce:
case OP_coerce_b:
case OP_convert_b:
case OP_coerce_o:
case OP_coerce_a:
case OP_convert_i:
case OP_coerce_i:
case OP_convert_u:
case OP_coerce_u:
case OP_convert_d:
case OP_coerce_d:
case OP_coerce_s:
#ifdef VMCFG_FLOAT
case OP_convert_f:
case OP_convert_f4:
#endif
AvmAssert( FLOAT_ONLY(
(opcode == OP_convert_f && type == FLOAT_TYPE) ||
(opcode == OP_convert_f4 && type == FLOAT4_TYPE) ||)
(opcode == OP_coerce && type != NULL) ||
(opcode == OP_coerce_b && type == BOOLEAN_TYPE) ||
(opcode == OP_convert_b && type == BOOLEAN_TYPE) ||
(opcode == OP_coerce_o && type == OBJECT_TYPE) ||
(opcode == OP_coerce_a && type == NULL) ||
(opcode == OP_convert_i && type == INT_TYPE) ||
(opcode == OP_coerce_i && type == INT_TYPE) ||
(opcode == OP_convert_u && type == UINT_TYPE) ||
(opcode == OP_coerce_u && type == UINT_TYPE) ||
(opcode == OP_convert_d && type == NUMBER_TYPE) ||
(opcode == OP_coerce_d && type == NUMBER_TYPE) ||
(opcode == OP_coerce_s && type == STRING_TYPE) );
emitCoerce(sp, type);
break;
case OP_istype:
{
// used when operator "is" RHS is a compile-time type constant
//sp[0] = istype(sp[0], itraits);
LIns* obj = loadAtomRep(sp);
LIns* out = callIns(FUNCTIONID(istype), 2, obj, InsConstPtr(type));
localSet(sp, out, BOOLEAN_TYPE);
break;
}
case OP_istypelate:
{
// null check for the type value T in (x is T). This also preserves
// any side effects from loading T, even if we end up inlining T.itraits() as a const.
Traits* class_type = state->value(sp).traits;
emitCheckNull(localCopy(sp), class_type);
LIns* obj = loadAtomRep(sp-1);
LIns* istype_result;
if (class_type && class_type->base == CLASS_TYPE) {
// (x is T) where T is a class object: get T.itraits as constant.
istype_result = callIns(FUNCTIONID(istype), 2, obj, InsConstPtr(class_type->itraits));
} else {
// RHS is unknown, call general istype
istype_result = callIns(FUNCTIONID(istypelate), 3, env_param, obj, loadAtomRep(sp));
}
localSet(sp-1, istype_result, BOOLEAN_TYPE);
break;
}
case OP_convert_o:
// NOTE check null has already been done
break;
case OP_applytype:
// * is ok for the type, as Vector classes have no statics
// when we implement type parameters fully, we should do something here.
emit(opcode, imm30/*argc*/, 0, NULL);
break;
case OP_newobject:
emit(opcode, imm30, 0, OBJECT_TYPE);
break;
case OP_newarray:
emit(opcode, imm30, 0, ARRAY_TYPE);
break;
case OP_newactivation:
emit(opcode, 0, 0, info->activationTraits());
break;
case OP_newcatch:
{
ExceptionHandler* handler = &info->abc_exceptions()->exceptions[imm30];
emit(opcode, 0, 0, handler->scopeTraits);
break;
}
case OP_popscope:
if (haveDebugger)
emitKill(ms->local_count()/*scopeBase*/ + state->scopeDepth);
break;
case OP_getslot:
{
const FrameValue& obj = state->peek(1);
int index = imm30-1;
Traits* slotTraits = obj.traits ? obj.traits->getTraitsBindings()->getSlotTraits(index) : NULL;
emitGetslot(index, sp, slotTraits);
break;
}
case OP_setslot:
emitSetslot(OP_setslot, imm30-1, sp-1);
break;
case OP_dup:
emitCopy(sp, sp+1);
break;
case OP_swap:
emitSwap(sp, sp-1);
break;
case OP_add:
emitAdd(sp-1, sp, type);
break;
case OP_equals:
case OP_strictequals:
case OP_instanceof:
case OP_in:
emit(opcode, 0, 0, BOOLEAN_TYPE);
break;
case OP_not:
AvmAssert(type == BOOLEAN_TYPE);
emit(opcode, sp, 0, type);
break;
case OP_modulo:
case OP_subtract:
case OP_divide:
case OP_multiply:
AvmAssert(type==NUMBER_TYPE FLOAT_ONLY(|| type == OBJECT_TYPE || type == FLOAT_TYPE || type == FLOAT4_TYPE));
emit(opcode, 0, 0, type);
break;
case OP_increment:
case OP_decrement:
AvmAssert(type==NUMBER_TYPE FLOAT_ONLY(|| type == OBJECT_TYPE || type == FLOAT_TYPE || type == FLOAT4_TYPE));
emit(opcode, sp, opcode == OP_increment ? 1 : -1, type);
break;
case OP_increment_i:
case OP_decrement_i:
emit(opcode, sp, opcode == OP_increment_i ? 1 : -1, INT_TYPE);
break;
case OP_add_i:
case OP_subtract_i:
case OP_multiply_i:
emit(opcode, 0, 0, INT_TYPE);
break;
case OP_negate:
AvmAssert(type==NUMBER_TYPE FLOAT_ONLY(|| type == OBJECT_TYPE || type == FLOAT_TYPE || type == FLOAT4_TYPE));
emit(opcode, sp, 0, type);
break;
case OP_negate_i:
emit(opcode, sp, 0, INT_TYPE);
break;
case OP_bitand:
case OP_bitor:
case OP_bitxor:
emit(opcode, 0, 0, INT_TYPE);
break;
case OP_lshift:
case OP_rshift:
emit(opcode, 0, 0, INT_TYPE);
break;
case OP_urshift:
emit(opcode, 0, 0, UINT_TYPE);
break;
case OP_bitnot:
emit(opcode, sp, 0, INT_TYPE);
break;
case OP_typeof:
emit(opcode, sp, 0, STRING_TYPE);
break;
case OP_nextvalue:
case OP_nextname:
emit(opcode, 0, 0, NULL);
break;
case OP_hasnext:
emit(opcode, 0, 0, INT_TYPE);
break;
case OP_hasnext2:
emit(opcode, imm30, imm30b, BOOLEAN_TYPE);
break;
// sign extends
case OP_sxi1:
case OP_sxi8:
case OP_sxi16:
emit(opcode, sp, 0, INT_TYPE);
break;
// loads
case OP_lix8:
case OP_lix16:
case OP_li8:
case OP_li16:
case OP_li32:
case OP_lf32:
case OP_lf64:
{
Traits* result = (opcode == OP_lf32 || opcode == OP_lf64) ? NUMBER_TYPE : INT_TYPE;
emit(opcode, sp, 0, result);
break;
}
#ifdef VMCFG_FLOAT
case OP_lf32x4:
{
emit(opcode, sp, 0, FLOAT4_TYPE);
break;
}
#endif
// stores
case OP_si8:
case OP_si16:
case OP_si32:
case OP_sf32:
case OP_sf64:
{
emit(opcode, 0, 0, VOID_TYPE);
break;
}
#ifdef VMCFG_FLOAT
case OP_sf32x4:
{
emit(opcode, 0, 0, FLOAT4_TYPE);
break;
}
#endif
case OP_getglobalscope:
emitGetGlobalScope(sp+1);
break;
case OP_convert_s:
localSet(sp, convertToString(sp, false), STRING_TYPE);
break;
case OP_esc_xelem:
case OP_esc_xattr:
emit(opcode, sp, 0, STRING_TYPE);
break;
case OP_debug:
// ignored
break;
case OP_restargc:
{
// See documentation in writePrologue regarding rest arguments
AvmAssert(info->needRestOrArguments() && info->lazyRest());
LIns* out = callIns(FUNCTIONID(restargcHelper),
2,
localGetp(restLocal),
restArgc);
localSet(sp, out, UINT_TYPE);
break;
}
default:
AvmAssertMsg(false, "unhandled opcode in CodegenLIR::write()");
break;
}
}
// coerce parameter types, starting at firstArg.
void CodegenLIR::coerceArgs(MethodSignaturep mms, int argc, int firstArg)
{
int sp = state->sp();
for (int arg = argc, n = 1; arg >= firstArg; arg--, n++) {
Traits* target = (arg <= mms->param_count()) ? mms->paramTraits(arg) : NULL;
int index = sp - (n - 1);
emitCoerce(index, target);
}
}
// Coerce parameter types, but not receiver. The object in the reciever
// position is the class or function object and we haven't called newInstance yet.
// In this case, emitCall() will generate a call to newInstance, producing the
// new object, then call its init function with the coerced arguments.
void CodegenLIR::emitConstructCall(intptr_t method_id, int argc, LIns* ctor, Traits* ctraits)
{
Traits* itraits = ctraits->itraits;
MethodInfo* m = itraits->init;
MethodSignaturep mms = m->getMethodSignature();
AvmAssert(mms->argcOk(argc)); // caller must check this before early binding to ctor
coerceArgs(mms, argc, 1);
emitCall(OP_construct, method_id, argc, ctor, ctraits, itraits, mms);
}
/**
* emitCoerceCall is used when the jit finds an opportunity to early bind that the
* driver did not. It does the coersions using the signature of the callee, and
* does not mutate FrameState.
*/
void CodegenLIR::emitCoerceCall(AbcOpcode opcode, intptr_t method_id, int argc, MethodSignaturep mms)
{
AvmAssert(state->value(state->sp() - argc).notNull); // make sure null check happened
AvmAssert(opcode != OP_construct);
AvmAssert(mms->argcOk(argc));
coerceArgs(mms, argc, 0);
emitCall(opcode, method_id, argc, mms->returnTraits(), mms);
}
void CodegenLIR::emitGetGlobalScope(int dest)
{
const ScopeTypeChain* scope = info->declaringScope();
int captured_depth = scope->size;
if (captured_depth > 0)
{
// enclosing scope
emitGetscope(0, dest);
}
else
{
// local scope
AvmAssert(state->scopeDepth > 0); // verifier checked.
emitCopy(ms->scope_base(), dest);
}
}
void CodegenLIR::writeOp1(const FrameState* state, const uint8_t *pc, AbcOpcode opcode, uint32_t opd1, Traits *type)
{
this->state = state;
emitSetPc(pc);
switch (opcode) {
case OP_iflt:
case OP_ifle:
case OP_ifnlt:
case OP_ifnle:
case OP_ifgt:
case OP_ifge:
case OP_ifngt:
case OP_ifnge:
case OP_ifeq:
case OP_ifstricteq:
case OP_ifne:
case OP_ifstrictne:
{
int32_t offset = (int32_t) opd1;
int lhs = state->sp()-1;
emitIf(opcode, pc+4/*size*/+offset, lhs, lhs+1);
break;
}
case OP_iftrue:
case OP_iffalse:
{
int32_t offset = (int32_t) opd1;
int sp = state->sp();
emitIf(opcode, pc+4/*size*/+offset, sp, 0);
break;
}
case OP_jump:
{
int32_t offset = (int32_t) opd1;
emit(opcode, uintptr_t(pc+4/*size*/+offset));
break;
}
case OP_getslot:
emitGetslot(opd1, state->sp(), type);
break;
case OP_getglobalslot: {
int32_t dest_index = state->sp(); // driver already incremented it
uint32_t slot = opd1;
emitGetGlobalScope(dest_index);
emitGetslot(slot, dest_index, type /* slot type */);
break;
}
case OP_setglobalslot:
emitSetslot(OP_setglobalslot, opd1, 0 /* computed or ignored */);
break;
case OP_call:
emit(opcode, opd1 /*argc*/, 0, NULL);
break;
case OP_construct:
{
const uint32_t argc = opd1;
int ctor_index = state->sp() - argc;
Traits* ctraits = state->value(ctor_index).traits;
LIns* ctor = localCopy(ctor_index);
emitConstruct(argc, ctor, ctraits);
break;
}
case OP_getouterscope:
emitGetscope(opd1, state->sp()+1);
break;
case OP_getscopeobject:
emitCopy(opd1 + ms->scope_base(), state->sp()+1);
break;
case OP_newfunction:
AvmAssert(pool->getMethodInfo(opd1)->declaringTraits() == type);
emit(opcode, opd1, state->sp()+1, type);
break;
case OP_pushscope:
case OP_pushwith:
emitCopy(state->sp(), opd1);
break;
case OP_findpropstrict:
case OP_findproperty:
{
const Multiname *name = pool->precomputedMultiname(opd1);
emit(opcode, (uintptr_t)name, 0, OBJECT_TYPE);
break;
}
case OP_findpropglobalstrict:
{
// NOTE opcode not supported, deoptimizing
const Multiname *name = pool->precomputedMultiname(opd1);
emit(OP_findpropstrict, (uintptr_t)name, 0, OBJECT_TYPE);
break;
}
case OP_findpropglobal:
{
// NOTE opcode not supported, deoptimizing
const Multiname *name = pool->precomputedMultiname(opd1);
emit(OP_findproperty, (uintptr_t)name, 0, OBJECT_TYPE);
break;
}
case OP_newclass:
{
Traits* ctraits = pool->getClassTraits(opd1);
AvmAssert(ctraits == type);
emit(opcode, (uintptr_t)(void*)ctraits, state->sp(), type);
break;
}
case OP_finddef:
{
// opd1=name index
// type=script->declaringTraits
uint32_t name_id = opd1;
AvmAssert(pool->precomputedMultiname(name_id)->isBinding());
int32_t dest_index = state->sp() + 1;
LIns* table = loadEnvFinddefTable();
// We always call out here because inlining the fast path inhibits
// dead code elimination when this finddef isn't used.
LIns* obj_ptr = addp(table, offsetof(FinddefTable, elements) + sizeof(ScriptObject*) * name_id);
LIns* obj = callIns(FUNCTIONID(finddef_cache), 2,
obj_ptr, methodFrame);
localSet(dest_index, ptrToNativeRep(type, obj), type);
break;
}
case OP_restarg:
{
// See documentation in writePrologue regarding rest arguments
AvmAssert(info->needRestOrArguments() && info->lazyRest());
const Multiname *multiname = pool->precomputedMultiname(opd1);
// The by-reference parameter &restLocal is handled specially for this
// helper function in VarTracker::insCall and in CodegenLIR::analyze_call.
LIns* out = callIns(FUNCTIONID(restargHelper),
6,
loadEnvToplevel(),
InsConstPtr(multiname),
loadAtomRep(state->sp()),
lea(restLocal << VARSHIFT(info) , vars),
restArgc,
(info->needRest() ?
binaryIns(LIR_addp, ap_param, InsConstPtr((void*)(ms->rest_offset()))) :
binaryIns(LIR_addp, ap_param, InsConstPtr((void*)sizeof(Atom)))));
localSet(state->sp()-1, out, type);
break;
}
default:
// writeOp1() called with an improper opcode.
AvmAssert(false);
break;
}
}
LIns* CodegenLIR::coerceToString(int index)
{
const FrameValue& value = state->value(index);
Traits* in = value.traits;
switch (bt(in)) {
case BUILTIN_null:
case BUILTIN_string:
// fine to just load the pointer
return localGetp(index);
case BUILTIN_int:
return callIns(FUNCTIONID(intToString), 2, coreAddr, localGet(index));
case BUILTIN_uint:
return callIns(FUNCTIONID(uintToString), 2, coreAddr, localGet(index));
#ifdef VMCFG_FLOAT
case BUILTIN_float:
return callIns(FUNCTIONID(floatToString),2,coreAddr,localGetf(index));
case BUILTIN_float4:
return callIns(FUNCTIONID(float4ToString), 2, coreAddr, localGetf4Addr(index));
#endif
case BUILTIN_number:
return callIns(FUNCTIONID(doubleToString), 2, coreAddr, localGetd(index));
case BUILTIN_boolean: {
// load "true" or "false" string constant from AvmCore.booleanStrings[]
LIns *offset = binaryIns(LIR_lshp, i2p(localGet(index)), InsConst(PTR_SCALE));
LIns *arr = InsConstPtr(&core->booleanStrings);
return loadIns(LIR_ldp, 0, binaryIns(LIR_addp, arr, offset), ACCSET_OTHER, LOAD_CONST);
}
default:
if (value.notNull) {
// not eligible for CSE, and we know it's not null/undefined
return emitStringCall(index, FUNCTIONID(string), true); // call string
}
return emitStringCall(index, FUNCTIONID(coerce_s), true); // call coerce_s
}
}
/** emit code for * -> Number conversion */
LIns* CodegenLIR::coerceToNumber(int index)
{
const FrameValue& value = state->value(index);
Traits* in = value.traits;
/* can't promote "Numeric" to "Number", we don't know the exact type at compile time */
if (in && (in->isNumeric() || in == BOOLEAN_TYPE)) {
return promoteNumberIns(in, index);
} else {
// * -> Number
// Note that another version of this code exists in InvokerCompiler::downcast_and_store, if we make
// changes here they might need to be propagated there too.
#ifdef VMCFG_FASTPATH_FROMATOM
if (inlineFastpath) {
// double result;
// intptr_t val = CONVERT_TO_ATOM(arg);
// if ((val & kAtomTypeMask) != kIntptrType) goto not_intptr; # test for kIntptrType tag
// # kIntptrType
// result = double(val >> kAtomTypeSize); # extract integer value and convert to double
// goto done;
// not_intptr:
// if ((val & kAtomTypeMask) != kDoubleType) goto not_double; # test for kDoubleType tag
// # kDoubleType
// result = *(val - kDoubleType); # remove tag and dereference
// goto done;
// not_double:
// result = number(val); # slow path -- call helper
// done:
// return result;
CodegenLabel not_intptr;
CodegenLabel not_double;
CodegenLabel done;
suspendCSE();
LIns* val = loadAtomRep(index);
LIns* result = insAlloc(sizeof(double));
LIns* tag = andp(val, AtomConstants::kAtomTypeMask);
// kIntptrType
branchToLabel(LIR_jf, eqp(tag, AtomConstants::kIntptrType), not_intptr);
// Note that this works on 64bit platforms only if we are careful
// to restrict the range of intptr values to those that fit within
// the integer range of the double type.
std(p2dIns(rshp(val, AtomConstants::kAtomTypeSize)), result, 0, ACCSET_OTHER);
JIT_EVENT(jit_atom2double_fast_intptr);
branchToLabel(LIR_j, NULL, done);
emitLabel(not_intptr);
// kDoubleType
branchToLabel(LIR_jf, eqp(tag, AtomConstants::kDoubleType), not_double);
std(ldd(subp(val, AtomConstants::kDoubleType), 0, ACCSET_OTHER), result, 0, ACCSET_OTHER);
JIT_EVENT(jit_atom2double_fast_double);
branchToLabel(LIR_j, NULL, done);
emitLabel(not_double);
std(callIns(FUNCTIONID(number), 1, val), result, 0, ACCSET_OTHER);
JIT_EVENT(jit_atom2double_slow);
emitLabel(done);
resumeCSE();
return ldd(result, 0, ACCSET_OTHER);
}
#endif
return callIns(FUNCTIONID(number), 1, loadAtomRep(index));
}
}
#ifdef VMCFG_FLOAT
/** emit code for * -> Float conversion */
LIns* CodegenLIR::coerceToFloat(int index)
{
const FrameValue& value = state->value(index);
Traits* in = value.traits;
if (in && (in->isNumeric() || in == BOOLEAN_TYPE)) {
return promoteFloatIns(in, index);
} else {
// * -> Float
return callIns(FUNCTIONID(singlePrecisionFloat), 1, loadAtomRep(index));
}
}
/** emit code for * -> Float4 conversion */
LIns* CodegenLIR::coerceToFloat4(int index)
{
const FrameValue& value = state->value(index);
Traits* in = value.traits;
return promoteFloat4Ins(in, index);
}
/** emit code for * -> Float|Float4|Number conversion */
LIns* CodegenLIR::coerceToNumeric(int index)
{
const FrameValue& value = state->value(index);
Traits* in = value.traits;
if( in && (in->isNumeric() || in == BOOLEAN_TYPE))
return loadAtomRep(index); // already numeric, just load the atom.
else{
CodegenLabel already_numeric("_coercenum_already_numeric_");
CodegenLabel done("_coercenum_done_");
CodegenLabel non_numeric("_coercenum_non_numeric_");
LIns* val = loadAtomRep(index);
LIns* tag = andp(val, AtomConstants::kAtomTypeMask);
LIns* result = insAlloc(sizeof(intptr_t));
suspendCSE();
LIns* tagmask = binaryIns(LIR_lshi, InsConst(1),p2i(tag));
const int32_t numericMask = ( (1<<AtomConstants::kIntptrType) | (1<< AtomConstants::kDoubleType) | (1 << AtomConstants::kSpecialBibopType) );
branchToLabel(LIR_jt, binaryIns(LIR_eqi, binaryIns(LIR_andi, tagmask, InsConst(numericMask)) , InsConst(0) ), non_numeric);
branchToLabel(LIR_jf, eqp(val, AtomConstants::undefinedAtom), already_numeric); // we still need to test for "undefined" before we conclude it's already a numeric type
/* If we got here, the input was "undefined" - just return NaN */
stp(InsConstAtom(core->kNaN),result,0,ACCSET_OTHER );
branchToLabel(LIR_j, NULL, done);
emitLabel(non_numeric);
stp(callIns(FUNCTIONID(numericAtom), 2, coreAddr, val) , result,0,ACCSET_OTHER);
JIT_EVENT(jit_atom2numeric_dbl);
branchToLabel(LIR_j, NULL, done);
emitLabel(already_numeric);
#ifdef DEBUG
// Above we assume that every bibop type is numeric.
// Add a DEBUG guard in case we introduce future non-numeric bibop types.
CodegenLabel really_numeric("_really_numeric_");
branchToLabel(LIR_jf, eqp(tag, AtomConstants::kSpecialBibopType), really_numeric);
LIns* bibopTag = lirout->insLoad(LIR_lduc2ui, andp(val, (Atom)MMgc::GCHeap::kBlockMask), 0, ACCSET_OTHER);
branchToLabel(LIR_jt, binaryIns(LIR_eqi, bibopTag, InsConst(AtomConstants::kBibopFloatType)), really_numeric);
branchToLabel(LIR_jt, binaryIns(LIR_eqi, bibopTag, InsConst(AtomConstants::kBibopFloat4Type)), really_numeric);
callIns(FUNCTIONID(upe), 1, env_param);
emitLabel(really_numeric);
#endif
stp(val,result,0, ACCSET_OTHER);
JIT_EVENT(jit_atom2numeric_flt);
emitLabel(done);
resumeCSE();
return ldp(result,0,ACCSET_OTHER);
}
}
#endif // VMCFG_FLOAT
LIns *CodegenLIR::emitStringCall(int index, const CallInfo *stringCall, bool preserveNull)
{
LIns* val = loadAtomRep(index);
if (inlineFastpath) {
// Inline fast path for string conversion.
// if preserveNull == false:
// if ((input & kAtomTypeMask) != kStringType) || (input == kStringType))
// output = stringCall (input);
// else
// output = input ^ kStringType;
//
// if preserveNull == true:
// if (input & kAtomTypeMask) != kStringType)
// output = stringCall (input);
// else
// output = input ^ kStringType;
CodegenLabel not_stringptr;
CodegenLabel done;
suspendCSE();
LIns* result = insAlloc(sizeof(intptr_t));
LIns* tag = andp(val, AtomConstants::kAtomTypeMask);
// kStringType
branchToLabel(LIR_jf, eqp(tag, AtomConstants::kStringType), not_stringptr);
if (!preserveNull) {
// If our value is equal to kStringType, we have a null String ptr
branchToLabel(LIR_jt, eqp(val, AtomConstants::kStringType), not_stringptr);
}
stp(xorp(val, AtomConstants::kStringType), result, 0, ACCSET_OTHER);
branchToLabel(LIR_j, NULL, done);
emitLabel(not_stringptr);
stp(callIns(stringCall, 2, coreAddr, val), result, 0, ACCSET_OTHER);
emitLabel(done);
resumeCSE();
return ldp(result, 0, ACCSET_OTHER);
}
return callIns(stringCall, 2, coreAddr, val);
}
// OP_convert_s needs a null String ptr to be converted to "null"
// while our other usage prior to concatStrings handles null ptrs
// correctly in AvmCore::concatStrings. This function is different
// than coerceToString in how undefinedAtom is handled:
// convert: undefinedAtom -> "undefined"
// coerce: undefinedAtom -> "null" (see coerce_s)
LIns* CodegenLIR::convertToString(int index, bool preserveNull)
{
const FrameValue& value = state->value(index);
Traits* in = value.traits;
Traits* stringType = STRING_TYPE;
if (in != stringType || (!preserveNull && !value.notNull)) {
if (in && (value.notNull || in->isNumeric() || in == BOOLEAN_TYPE)) {
// convert is the same as coerce
return coerceToString(index);
} else {
// explicitly convert to string
return emitStringCall(index, FUNCTIONID(string), preserveNull);
}
}
// already String*
return localGetp(index);
}
void CodegenLIR::writeNip(const FrameState* state, const uint8_t *pc, uint32_t count)
{
this->state = state;
emitSetPc(pc);
emitCopy(state->sp(), state->sp() - count);
}
void CodegenLIR::writeMethodCall(const FrameState* state, const uint8_t *pc, AbcOpcode opcode, MethodInfo* m, uintptr_t disp_id, uint32_t argc, Traits *type)
{
this->state = state;
emitSetPc(pc);
switch (opcode) {
case OP_callproperty:
case OP_callproplex:
case OP_callpropvoid:
AvmAssert(m->declaringTraits()->isInterface());
emitTypedCall(OP_callinterface, ImtHolder::getIID(m), argc, type, m);
break;
case OP_callmethod:
emitTypedCall(OP_callmethod, disp_id, argc, type, m);
break;
default:
AvmAssert(false);
break;
}
}
void CodegenLIR::writeOp2(const FrameState* state, const uint8_t *pc, AbcOpcode opcode, uint32_t opd1, uint32_t opd2, Traits *type)
{
this->state = state;
emitSetPc(pc);
int sp = state->sp();
switch (opcode) {
case OP_constructsuper:
{
Traits* base = info->declaringTraits()->base;
// opd1=unused, opd2=argc
if (base == OBJECT_TYPE && base->init->isTrivial()) {
AvmAssert(opd2 == 0); // The verifier should have caught a non-zero argc
break;
}
emitTypedCall(OP_constructsuper, 0, opd2, VOID_TYPE, base->init);
break;
}
case OP_setsuper:
{
const uint32_t index = opd1;
const uint32_t n = opd2;
Traits* base = type;
int32_t ptrIndex = sp-(n-1);
const Multiname* name = pool->precomputedMultiname(index);
Binding b = toplevel->getBinding(base, name);
Traits* propType = Traits::readBinding(base, b);
const TraitsBindingsp basetd = base->getTraitsBindings();
if (AvmCore::isSlotBinding(b)) {
if (!AvmCore::isVarBinding(b)) {
// else, ignore write to readonly accessor
break;
}
int slot_id = AvmCore::bindingToSlotId(b);
LIns* value = coerceToType(sp, propType);
emitSetslot(OP_setslot, slot_id, ptrIndex, value);
break;
}
if (AvmCore::isAccessorBinding(b)) {
if (!AvmCore::hasSetterBinding(b)) {
// ignore write to readonly accessor
break;
}
// Invoke the setter
int disp_id = AvmCore::bindingToSetterId(b);
MethodSignaturep mms = basetd->getMethod(disp_id)->getMethodSignature();
if (mms->argcOk(1)) {
emitCoerceCall(OP_callsuperid, disp_id, 1, mms);
break;
}
}
// generic late bound case
emit(opcode, (uintptr_t)name);
break;
}
case OP_getsuper:
{
const uint32_t index = opd1;
const uint32_t n = opd2;
Traits* base = type;
const Multiname* name = pool->precomputedMultiname(index);
Binding b = toplevel->getBinding(base, name);
Traits* propType = Traits::readBinding(base, b);
if (AvmCore::isSlotBinding(b)) {
int slot_id = AvmCore::bindingToSlotId(b);
emitGetslot(slot_id, state->sp()-(n-1), propType);
break;
}
if (AvmCore::hasGetterBinding(b)) {
// Invoke the getter
int disp_id = AvmCore::bindingToGetterId(b);
const TraitsBindingsp basetd = base->getTraitsBindings();
MethodSignaturep mms = basetd->getMethod(disp_id)->getMethodSignature();
if (mms->argcOk(0)) {
emitCoerceCall(OP_callsuperid, disp_id, 0, mms);
break;
}
}
// generic late-bound case
emit(opcode, (uintptr_t)name, 0, propType);
break;
}
case OP_callsuper:
case OP_callsupervoid:
{
const uint32_t index = opd1;
const uint32_t argc = opd2;
Traits* base = type;
const TraitsBindingsp basetd = base->getTraitsBindings();
const Multiname *name = pool->precomputedMultiname(index);
Binding b = toplevel->getBinding(base, name);
if (AvmCore::isMethodBinding(b)) {
int disp_id = AvmCore::bindingToMethodId(b);
MethodSignaturep mms = basetd->getMethod(disp_id)->getMethodSignature();
if (mms->argcOk(argc)) {
emitCoerceCall(OP_callsuperid, disp_id, argc, mms);
break;
}
}
// generic late bound case
emit(opcode, (uintptr_t)name, argc, NULL);
break;
}
case OP_constructprop:
{
const uint32_t argc = opd2;
const Multiname* name = pool->precomputedMultiname(opd1);
const FrameValue& obj = state->peek(argc+1); // object
Binding b = toplevel->getBinding(obj.traits, name);
if (AvmCore::isSlotBinding(b))
{
int slot_id = AvmCore::bindingToSlotId(b);
int ctor_index = state->sp() - argc;
LIns* ctor = loadFromSlot(ctor_index, slot_id, type);
emitConstruct(argc, ctor, type);
}
else
{
emit(opcode, (uintptr_t)name, argc, NULL);
}
break;
}
case OP_getproperty:
{
// NOTE opd2 is the stack offset to the reciever
const Multiname* name = pool->precomputedMultiname(opd1);
const FrameValue& obj = state->peek(opd2); // object
Binding b = toplevel->getBinding(obj.traits, name);
// early bind accessor
if (AvmCore::hasGetterBinding(b))
{
MethodInfo* getter_info = obj.traits->getTraitsBindings()->getMethod(AvmCore::bindingToGetterId(b));
if (getter_info->pool() == core->builtinPool)
{
// Inline certain blessed and possibly-hot accessors.
//
// - Optimize access to vector's "length", which is final.
//
// The verifier has computed that the return type of the "length"
// getter is uint, so we can just load "len" here: it is a uint32_t.
//
// - Optimize access to float4's x, y, z, and w elements.
//
// These are statically known to produce float, so a simple load
// will suffice.
//
// - Optimize access to float4 swizzlers.
//
// These are statically known to produce float4, the swizzling compiles
// down to a single LIR instruction.
int32_t arrayDataOffset = -1;
int32_t lenOffset = -1;
#ifdef VMCFG_FLOAT
uint8_t shuffle_mask = 0;
if (matchShuffler(getter_info, &shuffle_mask)) {
NanoAssert(bt(type) == BUILTIN_float4);
localSet(sp, lirout->insSwz(localGetf4(sp), shuffle_mask), type);
break;
}
LOpcode f4_getter = LIR_skip;
#endif
switch (getter_info->method_id())
{
case avmplus::NativeID::__AS3___vec_Vector_object_length_get:
arrayDataOffset = int32_t(offsetof(ObjectVectorObject, m_list.m_data));
lenOffset = int32_t(offsetof(AtomListHelper::LISTDATA, len));
goto vector_length;
case avmplus::NativeID::__AS3___vec_Vector_double_length_get:
arrayDataOffset = int32_t(offsetof(DoubleVectorObject, m_list.m_data));
lenOffset = int32_t(offsetof(DataListHelper<double>::LISTDATA, len));
goto vector_length;
case avmplus::NativeID::__AS3___vec_Vector_int_length_get:
arrayDataOffset = int32_t(offsetof(IntVectorObject, m_list.m_data));
lenOffset = int32_t(offsetof(DataListHelper<int32_t>::LISTDATA, len));
goto vector_length;
case avmplus::NativeID::__AS3___vec_Vector_uint_length_get:
arrayDataOffset = int32_t(offsetof(UIntVectorObject, m_list.m_data));
lenOffset = int32_t(offsetof(DataListHelper<uint32_t>::LISTDATA, len));
goto vector_length;
#ifdef VMCFG_FLOAT
case avmplus::NativeID::float4_x_get:
f4_getter = LIR_f4x;
goto float4_element;
case avmplus::NativeID::float4_y_get:
f4_getter = LIR_f4y;
goto float4_element;
case avmplus::NativeID::float4_z_get:
f4_getter = LIR_f4z;
goto float4_element;
case avmplus::NativeID::float4_w_get:
f4_getter = LIR_f4w;
goto float4_element;
#endif
default:
goto invoke_getter;
}
#ifdef VMCFG_FLOAT
float4_element: {
NanoAssert(bt(type) == BUILTIN_float);
localSet(sp, lirout->ins1(f4_getter, localGetf4(sp)), type);
break;
}
#endif
vector_length: {
LIns *arrayData = loadIns(LIR_ldp, arrayDataOffset, localGetp(sp), ACCSET_OTHER, LOAD_NORMAL);
LIns *arrayLen = loadIns(LIR_ldi, lenOffset, arrayData, ACCSET_OTHER, LOAD_NORMAL);
localSet(sp, arrayLen, type);
break;
}
} // inlining blessed getters
invoke_getter:
// Default case: Invoke the getter
int disp_id = AvmCore::bindingToGetterId(b);
const TraitsBindingsp objtd = obj.traits->getTraitsBindings();
MethodInfo *f = objtd->getMethod(disp_id);
AvmAssert(f != NULL);
if (!obj.traits->isInterface()) {
emitTypedCall(OP_callmethod, disp_id, 0, type, f);
}
else {
emitTypedCall(OP_callinterface, ImtHolder::getIID(f), 0, type, f);
}
AvmAssert(type == f->getMethodSignature()->returnTraits());
}
else {
emit(OP_getproperty, opd1, 0, type);
}
break;
}
case OP_setproperty:
case OP_initproperty:
{
// opd2=n the stack offset to the reciever
const Multiname *name = pool->precomputedMultiname(opd1);
const FrameValue& obj = state->peek(opd2); // object
Binding b = toplevel->getBinding(obj.traits, name);
// early bind accessor
if (AvmCore::hasSetterBinding(b))
{
// invoke the setter
int disp_id = AvmCore::bindingToSetterId(b);
const TraitsBindingsp objtd = obj.traits->getTraitsBindings();
MethodInfo *f = objtd->getMethod(disp_id);
AvmAssert(f != NULL);
if (!obj.traits->isInterface()) {
emitTypedCall(OP_callmethod, disp_id, 1, type, f);
}
else {
emitTypedCall(OP_callinterface, ImtHolder::getIID(f), 1, type, f);
}
}
else {
emit(opcode, (uintptr_t)name);
}
break;
}
case OP_setslot:
emitSetslot(OP_setslot, opd1, opd2);
break;
case OP_callproperty:
case OP_callproplex:
case OP_callpropvoid:
{
emit(opcode, opd1, opd2, NULL);
break;
}
case OP_callstatic: {
uint32_t method_id = opd1;
uint32_t argc = opd2;
emitTypedCall(OP_callstatic, method_id, argc, type, pool->getMethodInfo(method_id));
break;
}
default:
AvmAssert(false);
break;
}
}
void CodegenLIR::emitIntConst(int index, int32_t c, Traits* type)
{
localSet(index, lirout->insImmI(c), type);
}
void CodegenLIR::emitTaintedIntConst(int index, int32_t c, Traits* type)
{
localSet(index, lirout->insImmI(c, true), type);
}
void CodegenLIR::emitPtrConst(int index, void* c, Traits* type)
{
localSet(index, lirout->insImmP(c), type);
}
void CodegenLIR::emitDoubleConst(int index, const double* pd)
{
localSet(index, lirout->insImmD(*pd), NUMBER_TYPE);
}
void CodegenLIR::emitTaintedDoubleConst(int index, const double* pd)
{
localSet(index, lirout->insImmD(*pd, true), NUMBER_TYPE);
}
#ifdef VMCFG_FLOAT
void CodegenLIR::emitTaintedFloatConst(int index, const float f)
{
localSet(index, lirout->insImmF(f, true), FLOAT_TYPE);
}
void CodegenLIR::emitTaintedFloat4Const(int index, const float4_t* f4)
{
localSet(index, insImmF4(*(const float4_t*)f4, true), FLOAT4_TYPE);
}
/**
* If m is a float4 shuffle getter, e.g. xyxy(), then return true and
* the shuffle mask to use with the LIR_swzf4 instruction.
*/
bool CodegenLIR::matchShuffler(MethodInfo* m, uint8_t* mask)
{
using namespace avmplus::NativeID;
AvmAssert(m->pool() == core->builtinPool);
uint32_t id = m->method_id();
if (id < float4_xxxx_get || id > float4_wwww_get)
return false;
id -= float4_xxxx_get;
int x = (id >> 6) & 3;
int y = (id >> 4) & 3;
int z = (id >> 2) & 3;
int w = (id >> 0) & 3;
*mask = uint8_t(w << 6 | z << 4 | y << 2 | x << 0);
return true;
// We are counting on xxxx, xxxy, ... wwww being in order.
NanoStaticAssert(float4_xxxy_get - float4_xxxx_get == 1);
// todo: put the rest of them here.
NanoStaticAssert(float4_wwww_get - float4_xxxx_get == 255);
}
#endif // VMCFG_FLOAT
void CodegenLIR::writeCoerceToNumeric(const FrameState* state, uint32_t loc)
{
this->state = state;
emitSetPc(state->abc_pc);
localSet(loc, IFFLOAT(coerceToNumeric(loc), coerceToNumber(loc)), OBJECT_TYPE);
}
void CodegenLIR::writeCoerceToFloat4(const FrameState* state, uint32_t index)
{
#ifdef VMCFG_FLOAT
AvmAssert(index >= 3);
this->state = state;
emitSetPc(state->abc_pc);
LIns* x = coerceToFloat(index-3);
LIns* y = coerceToFloat(index-2);
LIns* z = coerceToFloat(index-1);
LIns* w = coerceToFloat(index);
LIns* val = lirout->ins4(LIR_ffff2f4, x, y, z, w);
localSet(index, val, FLOAT4_TYPE);
#else
(void) index; (void) state;
AvmAssert(!"coerceToFloat4 encountered in CodegenLIR, with VMCFG_FLOAT turned off!!!");
#endif
}
void CodegenLIR::writeCoerce(const FrameState* state, uint32_t loc, Traits* result)
{
this->state = state;
emitSetPc(state->abc_pc);
emitCoerce(loc, result);
}
void CodegenLIR::emitCoerce(uint32_t loc, Traits* result)
{
localSet(loc, coerceToType(loc, result), result);
}
// If we have already generated this specialized function, return our
// prior entry instead of re-specializing the same function.
LIns* CodegenLIR::getSpecializedCall(LIns* origCall)
{
if (!specializedCallHashMap)
return NULL;
return specializedCallHashMap->get(origCall);
}
// Track any specialized function so if we try to specialize the same function
// again we can re-use the original one.
LIns * CodegenLIR::addSpecializedCall(LIns* origCall, LIns* specializedCall)
{
if (!specializedCallHashMap)
specializedCallHashMap = new (*alloc1) HashMap<LIns *, LIns *>(*alloc1);
specializedCallHashMap->put(origCall, specializedCall);
return specializedCall;
}
// Attempt to replace a call with a call to another function, using the
// mapping given by 'specs'. The intent is that the replacement function
// yield equivalent semantics in the context in which it will appear, but
// more efficiently. A single replacement is generated for each call LIns*,
// and subquent requests to perform the same specializatoin will return
// the previously-constructed replacement LIns*. If an attempt is made to
// specialize a function call for which no replacement is given in 'specs', we
// return NULL. It is assumed that the replacement function will return
// an int32 value.
LIns* CodegenLIR::specializeIntCall(LIns* call, const Specialization* specs)
{
LIns *priorCall = getSpecializedCall(call);
if (priorCall)
return priorCall;
const CallInfo *ci = call->callInfo();
int i = 0;
while (specs[i].oldFunc != NULL) {
if (specs[i].oldFunc == ci) {
const CallInfo* nci = specs[i].newFunc;
AvmAssert(nci->returnType() == ARGTYPE_I);
LIns* specialization = callIns(nci, 2, call->arg(1), call->arg(0), INT_TYPE);
addSpecializedCall(call, specialization);
return specialization;
}
i++;
}
return NULL;
}
// Return true if we are promoting an int or uint to a double
bool CodegenLIR::isPromote(LOpcode op)
{
return op == LIR_ui2d || op == LIR_i2d;
}
// Return non-null LIns* if input is a constant that fits into a int32_t
LIns* CodegenLIR::imm2Int(LIns* imm)
{
if (imm->isImmI())
; // just use imm
else if (imm->isImmD()) {
double val = imm->immD();
double cvt = (int32_t)val;
if (val == 0 || val == cvt)
imm = InsConst((int32_t)cvt, imm->isTainted());
else
imm = 0; // can't convert
} else {
imm = 0; // non-imm
}
return imm;
}
static const Specialization coerceDoubleToInt[] = {
{ FUNCTIONID(String_charCodeAtDI), FUNCTIONID(String_charCodeAtIU) },
{ FUNCTIONID(String_charCodeAtDU), FUNCTIONID(String_charCodeAtIU) },
{ FUNCTIONID(String_charCodeAtDD), FUNCTIONID(String_charCodeAtID) },
{ 0, 0 }
};
// Perform coercion from a double to integer.
// Try various optimization to avoid double math if possible
// and specialize charCodeAt to an faster integer version.
LIns* CodegenLIR::coerceNumberToInt(int loc)
{
LIns *arg = localGetd(loc);
LOpcode op = arg->opcode();
switch (op) {
case LIR_ui2d:
case LIR_i2d:
return arg->oprnd1();
case LIR_addd:
case LIR_subd:
case LIR_muld: {
LIns *a = arg->oprnd1();
a = isPromote(a->opcode()) ? a->oprnd1() : imm2Int(a);
if (a) {
LIns *b = arg->oprnd2();
b = isPromote(b->opcode()) ? b->oprnd1() : imm2Int(b);
if (b)
return lirout->ins2(arithOpcodeD2I(op), a, b);
}
break;
}
// Optimize integer division when divisor is non-zero constant integer.
// We cannot use LIR_divi if divisor is non-constant, as we need to generate a NaN on division by zero.
case LIR_divd: {
LIns *a = arg->oprnd1();
LOpcode aOpcode = a->opcode();
// We should never arrive here at all if both operands are constants, as the LIR_divd should
// have been folded previously. Furthermore, Nanojit does not permit both arguments of LIR_divi
// to be constant, and will assert if this occurs, so let's be absolutely sure it will not by
// specifically not attempting to optimize that case here.
AvmAssert(!imm2Int(a) || !imm2Int(arg->oprnd2()));
if (isPromote(aOpcode)) {
a = a->oprnd1();
LIns *b = imm2Int(arg->oprnd2());
if (b) {
int32_t intConst = b->immI();
if (intConst) {
// use faster unsigned right shift if our arg is unsigned and
// we have just one bit set in the divisor.
if (aOpcode == LIR_ui2d && intConst >= 0 && exactlyOneBit(intConst)) {
return lirout->ins2(LIR_rshui, a, lirout->insImmI(msbSet32(intConst)));
}
#if NJ_DIVI_SUPPORTED
else if (aOpcode == LIR_i2d) {
return lirout->ins2(LIR_divi, a, b);
}
#endif // NJ_DIVI_SUPPORTED
}
}
}
break;
}
#ifdef AVMPLUS_64BIT
case LIR_immq:
#endif // AVMPLUS_64BIT
case LIR_immd:
// const fold
return InsConst(AvmCore::integer_d(arg->immD()), arg->isTainted());
// Try to replace a call returning a double, which will then be
// coerced to an integer, with a call that will produce an equivalent
// integer value directly.
case LIR_calld: {
LIns* specialized = specializeIntCall(arg, coerceDoubleToInt);
if (specialized)
return specialized;
}
default:
break;
}
// For SSE capable machines, inline our double to integer conversion
// using the CVTTSD2SI instruction. If we get a 0x80000000 return
// value, our double is outside the valid integer range we fallback
// to calling doubleToInt32.
if (haveSSE2()) {
suspendCSE();
CodegenLabel skip_label("goodint");
LIns* intResult = insAlloc(sizeof(int32_t));
LIns* fastd2i = lirout->ins1(LIR_d2i, arg);
sti(fastd2i, intResult, 0, ACCSET_STORE_ANY); // int32_t index
LIns *c = binaryIns(LIR_eqi, fastd2i, InsConst(0x80000000));
branchToLabel(LIR_jf, c, skip_label);
LIns *funcCall = callIns(FUNCTIONID(doubleToInt32), 1, arg);
sti(funcCall, intResult, 0, ACCSET_STORE_ANY); // int32_t index
emitLabel(skip_label);
LIns *result = loadIns(LIR_ldi, 0, intResult, ACCSET_LOAD_ANY);
resumeCSE();
return result;
}
return callIns(FUNCTIONID(integer_d), 1, arg);
}
#ifdef VMCFG_FLOAT
LIns* CodegenLIR::coerceFloatToInt(int loc)
{
LIns *arg = localGetf(loc);
if (arg->isop(LIR_immf))
return InsConst(AvmCore::integer_d((double)arg->immF()), arg->isTainted());
// fixme: Bug 707644; emperically, calling the helper is fastest.
const bool use_cvttss2si = false;
// For SSE capable machines, inline our float to integer conversion
// using the CVTTSS2SI instruction. If we get a 0x80000000 return
// value, our float is outside the valid integer range we fallback
// to calling doubleToInt32.
if (use_cvttss2si && haveSSE2()) {
suspendCSE();
CodegenLabel skip_label("goodint");
LIns* intResult = insAlloc(sizeof(int32_t));
LIns* fastf2i = lirout->ins1(LIR_f2i, arg);
sti(fastf2i, intResult, 0, ACCSET_STORE_ANY); // int32_t index
LIns *c = binaryIns(LIR_eqi, fastf2i, InsConst(0x80000000));
branchToLabel(LIR_jf, c, skip_label);
LIns *funcCall = callIns(FUNCTIONID(doubleToInt32), 1, f2dIns(arg));
sti(funcCall, intResult, 0, ACCSET_STORE_ANY); // int32_t index
emitLabel(skip_label);
LIns *result = loadIns(LIR_ldi, 0, intResult, ACCSET_LOAD_ANY);
resumeCSE();
return result;
}
// fallback: promote float->double, then convert double->int
return callIns(FUNCTIONID(integer_d), 1, f2dIns(arg));
}
#endif
LIns* CodegenLIR::coerceToType(int loc, Traits* result)
{
const FrameValue& value = state->value(loc);
Traits* in = value.traits;
LIns* expr;
if (result == NULL)
{
// coerce to * is simple, we just save the atom rep.
expr = loadAtomRep(loc);
}
else if (result == OBJECT_TYPE)
{
if (in == NULL || in == VOID_TYPE)
{
// value already boxed but we need to coerce undefined->null
if (!value.notNull) {
// v == undefinedAtom ? nullObjectAtom : v;
LIns *v = localGetp(loc);
expr = choose(eqp(v, undefConst), nullObjectAtom, v);
} else {
expr = loadAtomRep(loc);
}
}
else
{
// value cannot be undefined so just box it
expr = loadAtomRep(loc);
}
}
else if (!result->isMachineType() && in == NULL_TYPE)
{
// it's fine to coerce null to a pointer type, just load the value
expr = localGetp(loc);
}
else if (result == NUMBER_TYPE)
{
expr = coerceToNumber(loc);
}
#ifdef VMCFG_FLOAT
else if (result == FLOAT_TYPE)
{
expr = coerceToFloat(loc);
}
else if (result == FLOAT4_TYPE)
{
expr = coerceToFloat4(loc);
}
#endif // VMCFG_FLOAT
else if (result == INT_TYPE)
{
if (in == UINT_TYPE || in == BOOLEAN_TYPE || in == INT_TYPE)
{
// just load the value
expr = localGet(loc);
}
else if (in == NUMBER_TYPE)
{
// narrowing conversion number->int
expr = coerceNumberToInt(loc);
}
#ifdef VMCFG_FLOAT
else if (in == FLOAT_TYPE)
{
expr = coerceFloatToInt(loc);
}
#endif
else
{
// * -> int
expr = callIns(FUNCTIONID(integer), 1, loadAtomRep(loc));
}
}
else if (result == UINT_TYPE)
{
if (in == INT_TYPE || in == BOOLEAN_TYPE || in == UINT_TYPE)
{
// just load the value
expr = localGet(loc);
}
else if (in == NUMBER_TYPE)
{
expr = coerceNumberToInt(loc);
}
#ifdef VMCFG_FLOAT
else if (in == FLOAT_TYPE)
{
expr = coerceFloatToInt(loc);
}
#endif
else
{
// * -> uint
expr = callIns(FUNCTIONID(toUInt32), 1, loadAtomRep(loc));
}
}
else if (result == BOOLEAN_TYPE)
{
if (in == BOOLEAN_TYPE)
{
expr = localGet(loc);
}
else if (in == NUMBER_TYPE FLOAT_ONLY(|| in ==FLOAT_TYPE) )
{
LIns* ins = FLOAT_ONLY( in == FLOAT_TYPE ? f2dIns(localGetf(loc)):)
localGetd(loc);
expr = callIns(FUNCTIONID(doubleToBool), 1, ins);
}
else if (in == INT_TYPE || in == UINT_TYPE)
{
// int to bool: b = (i==0) == 0
expr = eqi0(eqi0(localGet(loc)));
}
else if (in && !in->hasComplexEqualityRules())
{
// ptr to bool: b = (p==0) == 0
expr = eqi0(eqp0(localGetp(loc)));
}
else
{
// * -> Boolean
expr = callIns(FUNCTIONID(boolean), 1, loadAtomRep(loc));
}
}
else if (result == STRING_TYPE)
{
expr = coerceToString(loc);
}
else if (in && !in->isMachineType() && !result->isMachineType()
&& in != STRING_TYPE && in != NAMESPACE_TYPE)
{
if (!Traits::canAssign(result, in)) {
// coerceobj_obj() is void, but we mustn't optimize it out; we only call it when required
callIns(FUNCTIONID(coerceobj_obj), 3,
env_param, localGetp(loc), InsConstPtr(result));
}
// the input pointer has now been checked but it's still the same value.
expr = localGetp(loc);
}
else if (!result->isMachineType() && result != NAMESPACE_TYPE)
{
// result is a ScriptObject based type.
expr = downcast_obj(loadAtomRep(loc), env_param, result);
}
else if (result == NAMESPACE_TYPE && in == NAMESPACE_TYPE)
{
expr = localGetp(loc);
}
else
{
LIns* value = loadAtomRep(loc);
// resultValue = coerce(caller_env, inputValue, traits)
LIns* out = callIns(FUNCTIONID(coerce), 3,
env_param, value, InsConstPtr(result));
// store the result
expr = atomToNativeRep(result, out);
}
return expr;
}
void CodegenLIR::writeCheckNull(const FrameState* state, uint32_t index)
{
this->state = state;
emitSetPc(state->abc_pc);
emitCheckNull(localCopy(index), state->value(index).traits);
}
void CodegenLIR::emitCheckNull(LIns* ptr, Traits* t)
{
// The result is either unchanged or an exception is thrown, so
// we don't save the result. This is the null pointer check.
if (!isNullable(t) || varTracker->isNotNull(ptr))
return;
_nvprof("nullcheck",1);
BuiltinType ty = bt(t);
if (valueStorageType(ty) == SST_atom) {
_nvprof("nullcheck atom", 1);
if (ty != BUILTIN_object) {
// If we know atom value is an object, it cannot be undefined.
branchToLabel(LIR_jt, eqp(ptr, undefConst), upe_label); // if (p == undefined) throw undefined pointer error
}
branchToLabel(LIR_jt, ltup(ptr, undefConst), npe_label); // if (p < undefined) throw null pointer error
} else {
_nvprof("nullcheck ptr", 1);
branchToLabel(LIR_jt, eqp0(ptr), npe_label);
}
varTracker->setNotNull(ptr, t);
}
// Save our current PC location for the catch finder later.
void CodegenLIR::emitSetPc(const uint8_t* pc)
{
AvmAssert(state->abc_pc == pc);
// update bytecode ip if necessary
if (_save_eip && lastPcSave != pc) {
// We do not blind the saved virtual pc.
stp(InsConstPtr((void*)(pc - code_pos)), _save_eip, 0, ACCSET_OTHER);
lastPcSave = pc;
}
}
// This is for VTable->createInstanceProc which is called by OP_construct
FASTFUNCTION(CALL_INDIRECT, SIG2(P,P,P), createInstanceProc)
void CodegenLIR::emitIsNaN(Traits* result)
{
int op1 = state->sp();
/* In practice, floats will be coerced to double before calling isNaN - so "singlePrecision" should be always false for now.
But this is implemented as if isNaN was polymorphic - there's no reason why it won't eventually be polymorphic */
bool singlePrecision = IFFLOAT(state->value(op1).traits == FLOAT_TYPE, false);
LIns *f = singlePrecision? localGetf(op1):localGetd(op1);
if (isPromote(f->opcode())) {
// Promoting an integer to a double cannot result in a NaN.
localSet(op1-1, InsConst(0), result);
}
else {
#ifdef VMCFG_FLOAT
if (f->opcode()==LIR_f2d) {
/* handle the case when isNan() was called on a float */
f = f->oprnd1();
singlePrecision = true;
}
LOpcode eqop = singlePrecision ? LIR_eqf : LIR_eqd;
#else
LOpcode eqop = LIR_eqd;
#endif // VMCFG_FLOAT
// LIR is required to follow IEEE floating point semantics, thus x is a NaN iff x != x.
localSet(op1-1, binaryIns(LIR_eqi, binaryIns(eqop, f, f), InsConst(0)), result);
}
}
void CodegenLIR::emitIntMathMin(Traits* result)
{
// if (x < y)
// return x
// else
// return y
int op1 = state->sp();
int op2 = state->sp() - 1;
LIns *x = getSpecializedArg(op1, BUILTIN_int);
LIns *y = getSpecializedArg(op2, BUILTIN_int);
LIns *s1 = binaryIns(LIR_lti, x, y);
LIns *s2 = lirout->insChoose(s1, x, y, use_cmov);
// coerceNumberToInt will remove a d2i/i2d roundtrip
localSet(op1-2, i2dIns(s2), result);
}
void CodegenLIR::emitIntMathMax(Traits* result)
{
// if (x > y)
// return x
// else
// return y
int op1 = state->sp();
int op2 = state->sp() - 1;
LIns *x = getSpecializedArg(op1, BUILTIN_int);
LIns *y = getSpecializedArg(op2, BUILTIN_int);
LIns *s1 = binaryIns(LIR_gti, x, y);
LIns *s2 = lirout->insChoose(s1, x, y, use_cmov);
// coerceNumberToInt will remove a d2i/i2d roundtrip
localSet(op1-2, i2dIns(s2), result);
}
void CodegenLIR::emitStringLength(Traits* result)
{
int op1 = state->sp();
LIns * ptr = loadIns(LIR_ldi, offsetof(String, m_length), localGetp(op1), ACCSET_OTHER, LOAD_CONST);
localSet(op1, ptr, result);
}
#ifdef VMCFG_FLOAT
void CodegenLIR::emitFloatUnary(Traits* result, LOpcode op) {
int sp = state->sp();
localSet(sp - 1, lirout->ins1(op, localGetf(sp)), result);
}
void CodegenLIR::emitFloatAbs(Traits* result) { emitFloatUnary(result, LIR_absf); }
void CodegenLIR::emitFloatReciprocal(Traits* result) { emitFloatUnary(result, LIR_recipf); }
void CodegenLIR::emitFloatRsqrt(Traits* result) { emitFloatUnary(result, LIR_rsqrtf); }
void CodegenLIR::emitFloatSqrt(Traits* result) { emitFloatUnary(result, LIR_sqrtf); }
void CodegenLIR::emitFloat4unary(Traits* result, LOpcode op) {
int sp = state->sp();
localSet(sp - 1, lirout->ins1(op, localGetf4(sp)), result);
}
void CodegenLIR::emitFloat4abs(Traits* result) { emitFloat4unary(result, LIR_absf4); }
void CodegenLIR::emitFloat4reciprocal(Traits* result) { emitFloat4unary(result, LIR_recipf4); }
void CodegenLIR::emitFloat4rsqrt(Traits* result) { emitFloat4unary(result, LIR_rsqrtf4); }
void CodegenLIR::emitFloat4sqrt(Traits* result) { emitFloat4unary(result, LIR_sqrtf4); }
// Helper to expand dotf4, dotf3, dotf2
LIns* CodegenLIR::emitDot(LOpcode dot_op, LIns* a, LIns* b) {
if (core->config.njconfig.i386_sse41)
return lirout->ins2(dot_op, a, b);
LIns* c = lirout->ins2(LIR_mulf4, a, b);
LIns* sum = lirout->ins2(LIR_addf, lirout->ins1(LIR_f4x, c), lirout->ins1(LIR_f4y, c));
if (dot_op == LIR_dotf3 || dot_op == LIR_dotf4)
sum = lirout->ins2(LIR_addf, sum, lirout->ins1(LIR_f4z, c));
if (dot_op == LIR_dotf4)
sum = lirout->ins2(LIR_addf, sum, lirout->ins1(LIR_f4w, c));
return sum;
}
// magnitudeK(x) = sqrt(dotK(x, x))
LIns* CodegenLIR::magnitude(LOpcode dot_op, LIns* x) {
return lirout->ins1(LIR_sqrtf, emitDot(dot_op, x, x));
}
/** normalize(x:float4) = x / magnitude(x) = x / sqrt(dot(x,x)) */
void CodegenLIR::emitFloat4normalize(Traits* result) {
int sp = state->sp();
LIns* x = localGetf4(sp);
LIns* mag4 = lirout->ins1(LIR_f2f4, magnitude(LIR_dotf4, x));
LIns* normalize = lirout->ins2(LIR_divf4, x, mag4);
localSet(sp - 1, normalize, result);
}
/** cross(a:float4, b:float4) = a.yzx0 * b.zxy0 - a.zxy0 * b.yzx0
We implement the equivalent:a.yzxw * b.zxyw - a.zxyw * b.yzxw
*/
void CodegenLIR::emitFloat4cross(Traits* result) {
int sp = state->sp();
LIns* x = localGetf4(sp - 1);
LIns* y = localGetf4(sp);
LIns* xs1 = lirout->insSwz(x,0xD8);
LIns* ys1 = lirout->insSwz(y,0xE1);
LIns* m1 = lirout->ins2(LIR_mulf4,xs1,ys1);
LIns* xs2 = lirout->insSwz(x,0xE1);
LIns* ys2 = lirout->insSwz(y,0xD8);
LIns* m2 = lirout->ins2(LIR_mulf4,xs2,ys2);
LIns* cross = lirout->ins2(LIR_subf4, m1,m2);
localSet(sp - 2, cross , result);
}
void CodegenLIR::emitFloat4binary(Traits* result, LOpcode op) {
int sp = state->sp();
LIns* x = localGetf4(sp - 1);
LIns* y = localGetf4(sp);
localSet(sp - 2, lirout->ins2(op, x, y), result);
}
void CodegenLIR::emitFloat4max(Traits* result) { emitFloat4binary(result, LIR_maxf4); }
void CodegenLIR::emitFloat4min(Traits* result) { emitFloat4binary(result, LIR_minf4); }
void CodegenLIR::emitFloat4Greater(Traits* result) { emitFloat4binary(result, LIR_cmpgtf4); }
void CodegenLIR::emitFloat4GreaterOrEqual(Traits* result) { emitFloat4binary(result, LIR_cmpgef4); }
void CodegenLIR::emitFloat4Less(Traits* result) { emitFloat4binary(result, LIR_cmpltf4); }
void CodegenLIR::emitFloat4LessOrEqual(Traits* result) { emitFloat4binary(result, LIR_cmplef4); }
void CodegenLIR::emitFloat4Equal(Traits* result) { emitFloat4binary(result, LIR_cmpeqf4); }
void CodegenLIR::emitFloat4NotEqual(Traits* result) { emitFloat4binary(result, LIR_cmpnef4); }
void CodegenLIR::emitFloat4dot(Traits* result, LOpcode op) {
int sp = state->sp();
LIns* x = localGetf4(sp - 1);
LIns* y = localGetf4(sp);
localSet(sp - 2, emitDot(op, x, y), result);
}
void CodegenLIR::emitFloat4dot(Traits* result) { emitFloat4dot(result, LIR_dotf4); }
void CodegenLIR::emitFloat4dot2(Traits* result) { emitFloat4dot(result, LIR_dotf2); }
void CodegenLIR::emitFloat4dot3(Traits* result) { emitFloat4dot(result, LIR_dotf3); }
void CodegenLIR::emitMagnitude(Traits* result, LOpcode dot_op) {
int sp = state->sp();
LIns* x = localGetf4(sp);
localSet(sp - 1, magnitude(dot_op, x), result);
}
void CodegenLIR::emitFloat4magnitude(Traits* result) { emitMagnitude(result, LIR_dotf4); }
void CodegenLIR::emitFloat4magnitude2(Traits* result) { emitMagnitude(result, LIR_dotf2); }
void CodegenLIR::emitFloat4magnitude3(Traits* result) { emitMagnitude(result, LIR_dotf3); }
// distanceK(x,y) = magnitudeK(x - y)
void CodegenLIR::emitDistance(Traits* result, LOpcode dot_op) {
int sp = state->sp();
LIns* x = localGetf4(sp - 1);
LIns* y = localGetf4(sp);
LIns* diff = lirout->ins2(LIR_subf4, x, y);
localSet(sp - 2, magnitude(dot_op, diff), result);
}
void CodegenLIR::emitFloat4distance(Traits* result) { emitDistance(result, LIR_dotf4); }
void CodegenLIR::emitFloat4distance2(Traits* result) { emitDistance(result, LIR_dotf2); }
void CodegenLIR::emitFloat4distance3(Traits* result) { emitDistance(result, LIR_dotf3); }
#endif
// Determine a mask for our argument that indicates to which types it may be trivially converted
// without insertion of additional instructions and without loss. This information may allow us
// to substitute a numeric operation on a more specific type than that inferred by the verifier.
// For example:
// A number that has been promoted from an integer will be marked as a number|int.
// A constant number that is an unsigned integer will be marked as a number|uint|int
int32_t CodegenLIR::determineBuiltinMaskForArg (int argOffset)
{
BuiltinType bt = this->bt(state->value(argOffset).traits);
int32_t btMask = 1 << bt;
if (bt == BUILTIN_number) {
LIns *arg = localGetd(argOffset);
if (arg->isImmD()) {
int32_t intVal = (int32_t) arg->immD();
if ((double) intVal == arg->immD() && !MathUtils::isNegZero(arg->immD())) {
if (intVal >= 0)
btMask |= 1 << BUILTIN_uint;
btMask |= 1 << BUILTIN_int;
}
#ifdef VMCFG_FLOAT
float fVal = (float) arg->immD();
if((double)fVal == arg->immD() || MathUtils::isNaN(arg->immD()))
btMask |= 1 << BUILTIN_float;
#endif // VMCFG_FLOAT
}
else if (arg->opcode() == LIR_i2d)
btMask |= 1 << BUILTIN_int;
else if (arg->opcode() == LIR_ui2d)
btMask |= 1 << BUILTIN_uint;
#ifdef VMCFG_FLOAT
else if (arg->opcode() == LIR_f2d)
btMask |= 1<< BUILTIN_float;
}
else if (bt == BUILTIN_float) {
LIns *arg = localGetf(argOffset);
btMask |= 1 << BUILTIN_number;
if (arg->isImmF()) {
int32_t intVal = (int32_t) arg->immF();
if ((float) intVal == arg->immF() && !MathUtils::isNegZero(arg->immF())) {
if (intVal >= 0)
btMask |= 1 << BUILTIN_uint;
btMask |= 1 << BUILTIN_int;
}
}
else if (arg->opcode() == LIR_i2f)
btMask |= 1 << BUILTIN_int;
else if (arg->opcode() == LIR_ui2f)
btMask |= 1 << BUILTIN_uint;
#endif // VMCFG_FLOAT
}
else if (bt == BUILTIN_int) {
LIns *arg = localGet(argOffset);
if (arg->isImmI() && arg->immI() >= 0)
btMask |= 1 << BUILTIN_uint;
}
return btMask;
}
// Given an argument and a builtin type to which it may be trivially converted,
// return a LIns that represents the argument as a value of the correct machine type.
LIns* CodegenLIR::getSpecializedArg (int argOffset, BuiltinType newBt)
{
BuiltinType oldBt = this->bt(state->value(argOffset).traits);
if (oldBt == BUILTIN_number) {
LIns *arg = localGetd(argOffset);
if (newBt == BUILTIN_int) {
if (arg->isImmD())
return InsConst((int32_t)arg->immD(), arg->isTainted());
else if (arg->opcode() == LIR_i2d)
return arg->oprnd1();
else
AvmAssert(0);
}
else if (newBt == BUILTIN_uint) {
if (arg->isImmD())
return InsConst((int32_t)arg->immD(), arg->isTainted());
else if (arg->opcode() == LIR_ui2d)
return arg->oprnd1();
else
AvmAssert(0);
}
#ifdef VMCFG_FLOAT
else if (newBt == BUILTIN_float) { // not used right now, but maybe soon...
if (arg->isImmD())
return InsConstFlt((float)arg->immD(), arg->isTainted());
else if (arg->opcode() == LIR_f2d)
return arg->oprnd1();
else
AvmAssert(0);
}
AvmAssert(newBt==BUILTIN_number);
return arg;
}
else if (oldBt == BUILTIN_float) {
LIns *arg = localGetf(argOffset);
if (newBt == BUILTIN_int) {
if (arg->isImmF())
return InsConst((int32_t)arg->immF(), arg->isTainted());
else if (arg->opcode() == LIR_i2f)
return arg->oprnd1();
else
AvmAssert(0);
}
else if (newBt == BUILTIN_uint) {
if (arg->isImmD())
return InsConst((int32_t)arg->immD(), arg->isTainted());
else if (arg->opcode() == LIR_ui2d)
return arg->oprnd1();
else
AvmAssert(0);
}
else if (newBt == BUILTIN_number) { // not used right now, but maybe soon...
if (arg->isImmF())
return lirout->insImmD(arg->immF(), arg->isTainted());
else
return f2dIns(arg);
}
AvmAssert(newBt==BUILTIN_float);
return arg;
#endif // VMCFG_FLOAT
}
/* See determineBuiltinMaskForArg - so far we can have:
- for Number/float - newBt may be any of float, int, uint, number
- for int - newBt may also be uint
- for others: newBt must be identical with oldBt
That's why the switch below works.
*/
switch (newBt) {
#ifdef VMCFG_FLOAT
case BUILTIN_number:
AvmAssertMsg(false, "Case should be already handled; this means oldBt is not number, but newBt is Number!");
return localGetd(argOffset);
case BUILTIN_float:
AvmAssertMsg(false, "Case should be already handled; this means oldBt is not float, but newBt is float!");
return localGetf(argOffset);
case BUILTIN_float4:
AvmAssertMsg(false, "Case should be already handled; this means oldBt is not float4, but newBt is float4!");
return localGetf4(argOffset);
#else
case BUILTIN_number:
return localGetd(argOffset);
#endif // VMCFG_FLOAT
case BUILTIN_boolean:
case BUILTIN_int:
case BUILTIN_uint:
return localGet(argOffset);
default:
return localGetp(argOffset);
}
}
// For a given builtin function id and argument count, matching of argument lists occurs in the order in which the
// entries appear in the table below. More specialized variants that are expected to execute faster should be listed
// prior to the more general cases. If no argument list matches, specialization is not performed.
const CodegenLIR::FunctionMatch CodegenLIR::specializedFunctions[] =
{
{ 0, /* dummy entry so 0 can be treated as HashMap miss*/ 1, {}, 0, 0},
{ avmplus::NativeID::native_script_function_isNaN, 1, {BUILTIN_number, BUILTIN_none}, 0, &CodegenLIR::emitIsNaN },
/*could theoretically also add { avmplus::NativeID::native_script_function_isNaN, 1, {BUILTIN_float, BUILTIN_none}, 0, &CodegenLIR::emitIsNaN }, */
{ avmplus::NativeID::String_AS3_charCodeAt, 1, {BUILTIN_uint, BUILTIN_none}, FUNCTIONID(String_charCodeAtDU), 0},
{ avmplus::NativeID::String_AS3_charCodeAt, 1, {BUILTIN_int, BUILTIN_none}, FUNCTIONID(String_charCodeAtDI), 0},
{ avmplus::NativeID::String_AS3_charCodeAt, 1, {BUILTIN_number, BUILTIN_none}, FUNCTIONID(String_charCodeAtDD), 0},
{ avmplus::NativeID::String_AS3_charAt, 1, {BUILTIN_uint, BUILTIN_none}, FUNCTIONID(String_charAtU), 0},
{ avmplus::NativeID::String_AS3_charAt, 1, {BUILTIN_int, BUILTIN_none}, FUNCTIONID(String_charAtI), 0},
{ avmplus::NativeID::String_AS3_charAt, 1, {BUILTIN_number, BUILTIN_none}, FUNCTIONID(String_charAtD), 0},
{ avmplus::NativeID::String_length_get, 0, {BUILTIN_none, BUILTIN_none}, 0, &CodegenLIR::emitStringLength},
{ avmplus::NativeID::Math_acos, 1, {BUILTIN_number, BUILTIN_none}, FUNCTIONID(Math_acos), 0},
{ avmplus::NativeID::Math_asin, 1, {BUILTIN_number, BUILTIN_none}, FUNCTIONID(Math_asin), 0},
{ avmplus::NativeID::Math_atan, 1, {BUILTIN_number, BUILTIN_none}, FUNCTIONID(Math_atan), 0},
{ avmplus::NativeID::Math_ceil, 1, {BUILTIN_number, BUILTIN_none}, FUNCTIONID(Math_ceil), 0},
{ avmplus::NativeID::Math_cos, 1, {BUILTIN_number, BUILTIN_none}, FUNCTIONID(Math_cos), 0},
{ avmplus::NativeID::Math_exp, 1, {BUILTIN_number, BUILTIN_none}, FUNCTIONID(Math_exp), 0},
{ avmplus::NativeID::Math_floor, 1, {BUILTIN_number, BUILTIN_none}, FUNCTIONID(Math_floor), 0},
{ avmplus::NativeID::Math_log, 1, {BUILTIN_number, BUILTIN_none}, FUNCTIONID(Math_log), 0},
{ avmplus::NativeID::Math_round, 1, {BUILTIN_number, BUILTIN_none}, FUNCTIONID(Math_round), 0},
{ avmplus::NativeID::Math_sin, 1, {BUILTIN_number, BUILTIN_none}, FUNCTIONID(Math_sin), 0},
{ avmplus::NativeID::Math_sqrt, 1, {BUILTIN_number, BUILTIN_none}, FUNCTIONID(Math_sqrt), 0},
{ avmplus::NativeID::Math_tan, 1, {BUILTIN_number, BUILTIN_none}, FUNCTIONID(Math_tan), 0},
{ avmplus::NativeID::Math_atan2, 2, {BUILTIN_number, BUILTIN_number}, FUNCTIONID(Math_atan2), 0},
{ avmplus::NativeID::Math_pow, 2, {BUILTIN_number, BUILTIN_number}, FUNCTIONID(Math_pow), 0},
// We would like to inline abs(), but the obvious implementation does not work, as both 0.0 and -0.0 compare as the same.
{ avmplus::NativeID::Math_abs, 1, {BUILTIN_number, BUILTIN_none}, FUNCTIONID(Math_abs), 0},
#if defined VMCFG_FLOAT && defined VMCFG_IA32
// float4
{ avmplus::NativeID::float4_abs, 1, {BUILTIN_float4, BUILTIN_none}, 0, &CodegenLIR::emitFloat4abs},
{ avmplus::NativeID::float4_max, 2, {BUILTIN_float4, BUILTIN_float4}, 0, &CodegenLIR::emitFloat4max},
{ avmplus::NativeID::float4_min, 2, {BUILTIN_float4, BUILTIN_float4}, 0, &CodegenLIR::emitFloat4min},
{ avmplus::NativeID::float4_reciprocal, 1, {BUILTIN_float4, BUILTIN_none}, 0, &CodegenLIR::emitFloat4reciprocal},
{ avmplus::NativeID::float4_rsqrt, 1, {BUILTIN_float4, BUILTIN_none}, 0, &CodegenLIR::emitFloat4rsqrt},
{ avmplus::NativeID::float4_sqrt, 1, {BUILTIN_float4, BUILTIN_none}, 0, &CodegenLIR::emitFloat4sqrt},
{ avmplus::NativeID::float4_cross, 2, {BUILTIN_float4, BUILTIN_float4}, 0, &CodegenLIR::emitFloat4cross},
{ avmplus::NativeID::float4_normalize, 1, {BUILTIN_float4, BUILTIN_none}, 0, &CodegenLIR::emitFloat4normalize},
{ avmplus::NativeID::float4_dot, 2, {BUILTIN_float4, BUILTIN_float4}, 0, &CodegenLIR::emitFloat4dot},
{ avmplus::NativeID::float4_dot2, 2, {BUILTIN_float4, BUILTIN_float4}, 0, &CodegenLIR::emitFloat4dot2},
{ avmplus::NativeID::float4_dot3, 2, {BUILTIN_float4, BUILTIN_float4}, 0, &CodegenLIR::emitFloat4dot3},
{ avmplus::NativeID::float4_magnitude, 1, {BUILTIN_float4, BUILTIN_none}, 0, &CodegenLIR::emitFloat4magnitude},
{ avmplus::NativeID::float4_magnitude3, 1, {BUILTIN_float4, BUILTIN_none}, 0, &CodegenLIR::emitFloat4magnitude3},
{ avmplus::NativeID::float4_magnitude2, 1, {BUILTIN_float4, BUILTIN_none}, 0, &CodegenLIR::emitFloat4magnitude2},
{ avmplus::NativeID::float4_distance, 2, {BUILTIN_float4, BUILTIN_float4}, 0, &CodegenLIR::emitFloat4distance},
{ avmplus::NativeID::float4_distance2, 2, {BUILTIN_float4, BUILTIN_float4}, 0, &CodegenLIR::emitFloat4distance2},
{ avmplus::NativeID::float4_distance3, 2, {BUILTIN_float4, BUILTIN_float4}, 0, &CodegenLIR::emitFloat4distance3},
{ avmplus::NativeID::float4_isGreater, 2, {BUILTIN_float4, BUILTIN_float4}, 0, &CodegenLIR::emitFloat4Greater},
{ avmplus::NativeID::float4_isLess, 2, {BUILTIN_float4, BUILTIN_float4}, 0, &CodegenLIR::emitFloat4Less},
{ avmplus::NativeID::float4_isGreaterOrEqual, 2, {BUILTIN_float4, BUILTIN_float4}, 0, &CodegenLIR::emitFloat4GreaterOrEqual},
{ avmplus::NativeID::float4_isLessOrEqual, 2, {BUILTIN_float4, BUILTIN_float4}, 0, &CodegenLIR::emitFloat4LessOrEqual},
{ avmplus::NativeID::float4_isEqual, 2, {BUILTIN_float4, BUILTIN_float4}, 0, &CodegenLIR::emitFloat4Equal},
{ avmplus::NativeID::float4_isNotEqual, 2, {BUILTIN_float4, BUILTIN_float4}, 0, &CodegenLIR::emitFloat4NotEqual},
#endif
{ avmplus::NativeID::Math_min, 2, {BUILTIN_int, BUILTIN_int}, 0, &CodegenLIR::emitIntMathMin},
{ avmplus::NativeID::Math_max, 2, {BUILTIN_int, BUILTIN_int}, 0, &CodegenLIR::emitIntMathMax},
{ avmplus::NativeID::Math_private__min, 2, {BUILTIN_int, BUILTIN_int}, 0, &CodegenLIR::emitIntMathMin},
{ avmplus::NativeID::Math_private__max, 2, {BUILTIN_int, BUILTIN_int}, 0, &CodegenLIR::emitIntMathMax},
// We do not inline the non-integral cases of min and max due to the complexity of handling -0 correctly.
// See MathClass::_min() and MathClass::_max().
// Unsupported because it uses MathClass::seed
//{ avmplus::NativeID::Math_random, 0, {BUILTIN_none, BUILTIN_none}, FUNCTIONID(Math_random), 0},
#ifdef VMCFG_FLOAT
// float
{ avmplus::NativeID::float_acos, 1, {BUILTIN_float, BUILTIN_none}, FUNCTIONID(float_acos), 0},
{ avmplus::NativeID::float_asin, 1, {BUILTIN_float, BUILTIN_none}, FUNCTIONID(float_asin), 0},
{ avmplus::NativeID::float_atan, 1, {BUILTIN_float, BUILTIN_none}, FUNCTIONID(float_atan), 0},
{ avmplus::NativeID::float_ceil, 1, {BUILTIN_float, BUILTIN_none}, FUNCTIONID(float_ceil), 0},
{ avmplus::NativeID::float_cos, 1, {BUILTIN_float, BUILTIN_none}, FUNCTIONID(float_cos), 0},
{ avmplus::NativeID::float_exp, 1, {BUILTIN_float, BUILTIN_none}, FUNCTIONID(float_exp), 0},
{ avmplus::NativeID::float_floor, 1, {BUILTIN_float, BUILTIN_none}, FUNCTIONID(float_floor), 0},
{ avmplus::NativeID::float_log, 1, {BUILTIN_float, BUILTIN_none}, FUNCTIONID(float_log), 0},
{ avmplus::NativeID::float_sin, 1, {BUILTIN_float, BUILTIN_none}, FUNCTIONID(float_sin), 0},
{ avmplus::NativeID::float_tan, 1, {BUILTIN_float, BUILTIN_none}, FUNCTIONID(float_tan), 0},
{ avmplus::NativeID::float_atan2, 2, {BUILTIN_float, BUILTIN_float}, FUNCTIONID(float_atan2), 0},
{ avmplus::NativeID::float_pow, 2, {BUILTIN_float, BUILTIN_float}, FUNCTIONID(float_pow), 0},
// We would like to inline this on x86, but need SSE 4.1 to use ROUNDSS. A CVTSS2SI; CVTSI2SS sequence should work for SSE2,
// but only with the correct rounding mode, which we cannot guarantee. It would be possible to inline the MathUtils::roundf
// function, which seems the best we can do.
{ avmplus::NativeID::float_round, 1, {BUILTIN_float, BUILTIN_none}, FUNCTIONID(float_round), 0},
// We would like to inline these on x86, but the MINSS and MAXSS instructions do not handle signed zero as required.
{ avmplus::NativeID::float_max, 2, {BUILTIN_float, BUILTIN_float}, FUNCTIONID(float_max2), 0},
{ avmplus::NativeID::float_min, 2, {BUILTIN_float, BUILTIN_float}, FUNCTIONID(float_min2), 0},
#ifdef VMCFG_IA32
{ avmplus::NativeID::float_abs, 1, {BUILTIN_float, BUILTIN_none}, 0, &CodegenLIR::emitFloatAbs},
{ avmplus::NativeID::float_reciprocal, 1, {BUILTIN_float, BUILTIN_none}, 0, &CodegenLIR::emitFloatReciprocal},
{ avmplus::NativeID::float_rsqrt, 1, {BUILTIN_float, BUILTIN_none}, 0, &CodegenLIR::emitFloatRsqrt},
{ avmplus::NativeID::float_sqrt, 1, {BUILTIN_float, BUILTIN_none}, 0, &CodegenLIR::emitFloatSqrt},
#else
{ avmplus::NativeID::float_abs, 1, {BUILTIN_float, BUILTIN_none}, FUNCTIONID(float_abs), 0},
{ avmplus::NativeID::float_reciprocal, 1, {BUILTIN_float, BUILTIN_none}, FUNCTIONID(float_reciprocal), 0},
{ avmplus::NativeID::float_rsqrt, 1, {BUILTIN_float, BUILTIN_none}, FUNCTIONID(float_rsqrt), 0},
{ avmplus::NativeID::float_sqrt, 1, {BUILTIN_float, BUILTIN_none}, FUNCTIONID(float_sqrt), 0},
#endif
#endif
};
static uint32_t genFunctionKey (int32_t methodId, int32_t argCount)
{
return uint32_t(methodId | (argCount << 16));
}
// Our builtinFunctionOptimizerHashMap maps from a [nativeId, argCount] key into the
// starting index in our specializedFunctions table. The inlineBuiltinFunction function
// will then iterate over our specializedFunctions table for all matching [nativeId, argCount]
// pairs looking for a match to argument types.
void CodegenLIR::genBuiltinFunctionOptimizerHashMap()
{
builtinFunctionOptimizerHashMap = new (*alloc1) HashMap<uint32_t, uint32_t>(*alloc1, 100);
uint32_t funcKey = 0;
for (uint32_t i = 0; i < sizeof(specializedFunctions) / sizeof(FunctionMatch); i++) {
uint32_t newFuncKey = genFunctionKey (specializedFunctions[i].methodId, specializedFunctions[i].argCount);
if (newFuncKey != funcKey) {
funcKey = newFuncKey;
builtinFunctionOptimizerHashMap->put(funcKey, i);
}
}
}
bool CodegenLIR::inlineBuiltinFunction(AbcOpcode, intptr_t, int argc, Traits* result, MethodInfo* mi)
{
if (haveDebugger)
return false;
if (mi->pool() != core->builtinPool || !mi->isFinal())
return false;
if (!builtinFunctionOptimizerHashMap)
genBuiltinFunctionOptimizerHashMap();
uint32_t funcKey = genFunctionKey(mi->method_id(), argc);
uint32_t startingIndex = builtinFunctionOptimizerHashMap->get(funcKey);
if (!startingIndex)
return false;
int count = sizeof(specializedFunctions) / sizeof(FunctionMatch);
for (int i = startingIndex; i < count; i++) {
if (mi->method_id() != specializedFunctions[i].methodId)
return false;
if (argc != (int) specializedFunctions[i].argCount)
return false;
// matching identifier, matching arg count, try to match argument types
bool bMatch = true;
for (int32_t argindex = 0; argindex < argc; argindex++) {
int32_t btMask = determineBuiltinMaskForArg(state->sp() - argindex);
if (!(btMask & (1 << specializedFunctions[i].argType[argindex]))) {
bMatch = false;
break;
}
}
if (!bMatch)
continue;
if (specializedFunctions[i].newFunction) {
int32_t sp = state->sp();
// No 'this' param
if (specializedFunctions[i].newFunction->count_args() == (uint32_t) argc) {
if (argc == 1)
localSet(sp-1, callIns(specializedFunctions[i].newFunction, 1,
getSpecializedArg(sp, specializedFunctions[i].argType[0])), result);
else if (argc == 2)
localSet(sp-2, callIns(specializedFunctions[i].newFunction, 2,
getSpecializedArg(sp-1, specializedFunctions[i].argType[1]),
getSpecializedArg(sp, specializedFunctions[i].argType[0])), result);
}
else {
if (argc == 1)
localSet(sp-1, callIns(specializedFunctions[i].newFunction, 2, localGetp(sp-1),
getSpecializedArg(sp, specializedFunctions[i].argType[0])), result);
else if (argc == 2)
localSet(sp-2, callIns(specializedFunctions[i].newFunction, 3, localGetp(sp-2),
getSpecializedArg(sp-1, specializedFunctions[i].argType[1]),
getSpecializedArg(sp, specializedFunctions[i].argType[0])), result);
}
}
else {
// Invoke emitFunction as member function pointer.
EmitMethod emitter = specializedFunctions[i].emitFunction;
(this->*emitter)(result);
}
return true;
}
return false;
}
#ifdef DEBUG
/**
* emitTypedCall is used when the Verifier has found an opportunity to early bind,
* and has already coerced arguments from whatever native type is discovered, to
* the required types. emitTypedCall() then just double-checks (via assert) that
* the arg types are already correct.
*/
void CodegenLIR::emitTypedCall(AbcOpcode opcode, intptr_t method_id, int argc, Traits* result, MethodInfo* mi)
{
AvmAssert(opcode != OP_construct);
AvmAssert(state->value(state->sp() - argc).notNull); // make sure null check happened
MethodSignaturep ms = mi->getMethodSignature();
AvmAssert(ms->argcOk(argc));
int objDisp = state->sp() - argc;
for (int arg = 0; arg <= argc && arg <= ms->param_count(); arg++)
AvmAssert(Traits::canAssign(state->value(objDisp+arg).traits, ms->paramTraits(arg)));
for (int arg = ms->param_count()+1; arg <= argc; arg++) {
BuiltinType t = bt(state->value(objDisp+arg).traits);
AvmAssert(valueStorageType(t) == SST_atom);
}
if (inlineBuiltinFunction(opcode, method_id, argc, result, mi))
return;
emitCall(opcode, method_id, argc, result, ms);
}
#else
REALLY_INLINE void CodegenLIR::emitTypedCall(AbcOpcode opcode, intptr_t method_id, int argc, Traits* result, MethodInfo* mi)
{
if (inlineBuiltinFunction(opcode, method_id, argc, result, mi))
return;
emitCall(opcode, method_id, argc, result, mi->getMethodSignature());
}
#endif
void CodegenLIR::emitCall(AbcOpcode opcode, intptr_t method_id, int argc, Traits* result, MethodSignaturep ms)
{
int objDisp = state->sp() - argc;
LIns* obj = localCopy(objDisp);
Traits* objType = state->value(objDisp).traits;
emitCall(opcode, method_id, argc, obj, objType, result, ms);
}
void CodegenLIR::emitCall(AbcOpcode opcode, intptr_t method_id, int argc, LIns* obj, Traits* objType, Traits* result, MethodSignaturep ms)
{
int sp = state->sp();
int dest = sp-argc;
int objDisp = dest;
LIns *method = NULL;
LIns *iid = NULL;
switch (opcode)
{
case OP_constructsuper:
{
// env->vtable->base->init->enter32v(argc, ...);
LIns* vtable = loadEnvVTable();
LIns* base = loadIns(LIR_ldp, offsetof(VTable,base), vtable, ACCSET_OTHER, LOAD_CONST);
method = loadIns(LIR_ldp, offsetof(VTable,init), base, ACCSET_OTHER, LOAD_CONST);
break;
}
case OP_callmethod:
{
// stack in: obj arg1..N
// stack out: result
// sp[-argc] = callmethod(disp_id, argc, ...);
// method_id is disp_id of virtual method
LIns* vtable = loadVTable(obj, objType);
method = loadIns(LIR_ldp, int32_t(offsetof(VTable,methods)+sizeof(MethodEnv*)*method_id), vtable, ACCSET_OTHER, LOAD_CONST);
break;
}
case OP_callsuperid:
{
// stack in: obj arg1..N
// stack out: result
// method_id is disp_id of super method
LIns* declvtable = loadEnvVTable();
LIns* basevtable = loadIns(LIR_ldp, offsetof(VTable, base), declvtable, ACCSET_OTHER, LOAD_CONST);
method = loadIns(LIR_ldp, int32_t(offsetof(VTable,methods)+sizeof(MethodEnv*)*method_id), basevtable, ACCSET_OTHER, LOAD_CONST);
break;
}
case OP_callstatic:
{
// stack in: obj arg1..N
// stack out: result
LIns* abcenv = loadEnvAbcEnv();
method = loadIns(LIR_ldp, int32_t(offsetof(AbcEnv,m_methods)+sizeof(MethodEnv*)*method_id), abcenv, ACCSET_OTHER, LOAD_CONST);
break;
}
case OP_callinterface:
{
// method_id is pointer to interface method name (multiname)
uint32_t index = ImtHolder::hashIID(method_id);
LIns* vtable = loadVTable(obj, objType);
// note, could be MethodEnv* or ImtThunkEnv*
method = loadIns(LIR_ldp, offsetof(VTable, imt.entries) + index * sizeof(ImtThunkEnv*), vtable, ACCSET_OTHER, LOAD_CONST);
// We don't need to blind method_id, as it is a MethodInfo* pointer, not a method index.
iid = InsConstPtr((void*)method_id);
break;
}
case OP_construct:
{
// stack in: ctor arg1..N
// stack out: newinstance
LIns* vtable = loadVTable(obj, objType);
LIns* ivtable = loadIns(LIR_ldp, offsetof(VTable, ivtable), vtable, ACCSET_OTHER, LOAD_CONST);
method = loadIns(LIR_ldp, offsetof(VTable, init), ivtable, ACCSET_OTHER, LOAD_CONST);
LIns* createInstanceProc = loadIns(LIR_ldp, offsetof(VTable, createInstanceProc), ivtable, ACCSET_OTHER);
obj = callIns(FUNCTIONID(createInstanceProc), 2, createInstanceProc, obj);
objType = result;
// the call below to the init function is void; the expression result we want
// is the new object, not the result from the init function. save it now.
localSet(dest, obj, result);
break;
}
default:
AvmAssert(false);
}
// store args for the call
LIns* ap = insAlloc(sizeof(Atom)); // we will update this size, below
int disp = 0;
int pad = 0;
int param_count = ms->param_count();
// LIR_allocp of any size >= 8 is always 8-aligned.
// if the first double arg would be unaligned, add padding to align it.
#if !defined AVMPLUS_64BIT
for (int i=0; i <= argc && i <= param_count; i++) {
if (ms->paramTraits(i) == NUMBER_TYPE) {
if ((disp&7) != 0) {
// this double would be unaligned, so add some padding
pad = 8-(disp&7); // should be 4
}
break;
}
else {
disp += sizeof(Atom);
}
}
#endif
disp = pad;
for (int i=0, index=objDisp; i <= argc; i++, index++) {
Traits* paramType = i <= param_count ? ms->paramTraits(i) : NULL;
LIns* v;
switch (bt(paramType)) {
#ifdef VMCFG_FLOAT
case BUILTIN_float:
v = (i == 0) ? obj : lirout->insLoad(LIR_ldf, vars, index << VARSHIFT(info), ACCSET_VARS);
stf(v, ap, disp, ACCSET_OTHER);
disp += sizeof(intptr_t); // only 4 bytes needed, but on X64 we use at least 8 bytes anyway - same as "int" and "uint"
break;
case BUILTIN_float4:
v = (i == 0) ? obj : lirout->insLoad(LIR_ldf4, vars, index << VARSHIFT(info), ACCSET_VARS);
stf4(v, ap, disp, ACCSET_OTHER);
disp += sizeof(float4_t);
break;
#endif
case BUILTIN_number:
v = (i == 0) ? obj : lirout->insLoad(LIR_ldd, vars, index << VARSHIFT(info), ACCSET_VARS);
std(v, ap, disp, ACCSET_OTHER);
disp += sizeof(double);
break;
case BUILTIN_int:
v = (i == 0) ? obj : lirout->insLoad(LIR_ldi, vars, index << VARSHIFT(info), ACCSET_VARS);
stp(i2p(v), ap, disp, ACCSET_OTHER);
disp += sizeof(intptr_t);
break;
case BUILTIN_uint:
case BUILTIN_boolean:
v = (i == 0) ? obj : lirout->insLoad(LIR_ldi, vars, index << VARSHIFT(info), ACCSET_VARS);
stp(ui2p(v), ap, disp, ACCSET_OTHER);
disp += sizeof(uintptr_t);
break;
default:
v = (i == 0) ? obj : lirout->insLoad(LIR_ldp, vars, index << VARSHIFT(info), ACCSET_VARS);
stp(v, ap, disp, ACCSET_OTHER);
disp += sizeof(void*);
break;
}
}
// patch the size to what we actually need
ap->setSize(disp);
LIns* target = loadIns(LIR_ldp, offsetof(MethodEnvProcHolder,_implGPR), method, ACCSET_OTHER);
LIns* apAddr = lea(pad, ap);
LIns *out;
BuiltinType rbt = bt(result);
if (!iid) {
const CallInfo *fid;
switch (rbt) {
#ifdef VMCFG_FLOAT
case BUILTIN_float:
fid = FUNCTIONID(fcalli);
break;
case BUILTIN_float4:
fid = FUNCTIONID(vfcalli);
break;
#endif
case BUILTIN_number:
fid = FUNCTIONID(dcalli);
break;
case BUILTIN_int: case BUILTIN_uint: case BUILTIN_boolean:
fid = FUNCTIONID(icalli);
break;
default:
fid = FUNCTIONID(acalli);
break;
}
// It is doubtful that a tainted 'argc' can be usefully exploited due to the
// density of surrounding code not under attacker control, but we expect
// the values to be small enough that we'll not blind them anyhow, so why
// take the risk?
out = callIns(fid, 4, target, method, InsConst(argc, /*tainted*/true), apAddr);
} else {
const CallInfo *fid;
switch (rbt) {
#ifdef VMCFG_FLOAT
case BUILTIN_float:
fid = FUNCTIONID(fcallimt);
break;
case BUILTIN_float4:
fid = FUNCTIONID(vfcallimt);
break;
#endif
case BUILTIN_number:
fid = FUNCTIONID(dcallimt);
break;
case BUILTIN_int: case BUILTIN_uint: case BUILTIN_boolean:
fid = FUNCTIONID(icallimt);
break;
default:
fid = FUNCTIONID(acallimt);
break;
}
out = callIns(fid, 5, target, method, InsConst(argc, /*tainted*/true), apAddr, iid);
}
// ensure the stack-allocated args are live until after the call
liveAlloc(ap);
if (opcode != OP_constructsuper && opcode != OP_construct)
localSet(dest, out, result);
}
LIns* CodegenLIR::loadFromSlot(int ptr_index, int slot, Traits* slotType)
{
Traits *t = state->value(ptr_index).traits;
LIns *ptr = localGetp(ptr_index);
AvmAssert(state->value(ptr_index).notNull);
AvmAssert(isPointer((int)ptr_index)); // obj
AvmAssert(t->isResolved());
const TraitsBindingsp tb = t->getTraitsBindings();
int offset = tb->getSlotOffset(slot);
// get
LOpcode op;
switch (bt(slotType)) {
#ifdef VMCFG_FLOAT
case BUILTIN_float4: op = LIR_ldf4; break;
case BUILTIN_float: op = LIR_ldf; break;
#endif
case BUILTIN_number: op = LIR_ldd; break;
case BUILTIN_int:
case BUILTIN_uint:
case BUILTIN_boolean: op = LIR_ldi; break;
default: op = LIR_ldp; break;
}
return loadIns(op, offset, ptr, ACCSET_OTHER);
}
void CodegenLIR::emitGetslot(int slot, int ptr_index, Traits *slotType)
{
localSet(ptr_index, loadFromSlot(ptr_index, slot, slotType), slotType);
}
void CodegenLIR::emitSetslot(AbcOpcode opcode, int slot, int ptr_index)
{
emitSetslot(opcode, slot, ptr_index, localCopy(state->sp()));
}
void CodegenLIR::emitSetslot(AbcOpcode opcode, int slot, int ptr_index, LIns* value)
{
Traits* t;
LIns* ptr;
if (opcode == OP_setslot)
{
t = state->value(ptr_index).traits;
ptr = localGetp(ptr_index);
AvmAssert(state->value(ptr_index).notNull);
AvmAssert(isPointer((int)ptr_index)); // obj
}
else
{
// setglobalslot
const ScopeTypeChain* scopeTypes = info->declaringScope();
if (scopeTypes->size == 0)
{
// no captured scopes, so global is local scope 0
ptr_index = ms->scope_base();
t = state->value(ptr_index).traits;
ptr = localGetp(ptr_index);
AvmAssert(state->value(ptr_index).notNull);
AvmAssert(isPointer((int)ptr_index)); // obj
}
else
{
// global is outer scope 0
t = scopeTypes->getScopeTraitsAt(0);
LIns* scope = loadEnvScope();
LIns* scopeobj = loadIns(LIR_ldp, offsetof(ScopeChain,_scopes) + 0*sizeof(Atom), scope, ACCSET_OTHER);
ptr = atomToNativeRep(t, scopeobj);
}
}
AvmAssert(t->isResolved());
const TraitsBindingsp tb = t->getTraitsBindings();
int offset = tb->getSlotOffset(slot);
LIns *unoffsetPtr = ptr;
// if storing to a pointer-typed slot, inline a WB
Traits* slotType = tb->getSlotTraits(slot);
if (!slotType || !slotType->isMachineType() || slotType == OBJECT_TYPE)
{
// slot type is Atom (for *, Object) or RCObject* (String, Namespace, or other user types)
const CallInfo *wbAddr = FUNCTIONID(privateWriteBarrierRC);
if (slotType == NULL || slotType == OBJECT_TYPE) {
// use fast atom wb
wbAddr = FUNCTIONID(atomWriteBarrier);
}
callIns(wbAddr, 4,
InsConstPtr(core->GetGC()),
unoffsetPtr,
lea(offset, ptr),
value);
}
#ifdef VMCFG_FLOAT
else if (slotType == FLOAT_TYPE) {
// slot type is double or int
stf(value, ptr, offset, ACCSET_OTHER);
}
else if (slotType == FLOAT4_TYPE) {
// slot type is double or int
stf4(value, ptr, offset, ACCSET_OTHER);
}
#endif
else if (slotType == NUMBER_TYPE) {
// slot type is double or int
std(value, ptr, offset, ACCSET_OTHER);
} else {
AvmAssert(slotType == INT_TYPE || slotType == UINT_TYPE || slotType == BOOLEAN_TYPE);
sti(value, ptr, offset, ACCSET_OTHER);
}
}
/**
* Emit a constructor call, or a late bound constructor call.
* Early binding is possible when we know the constructor (class) being
* used, and we know it doesn't override ClassClosure::construct(),
* as indicated by the itraits->hasCustomConstruct flag.
*/
void CodegenLIR::emitConstruct(int argc, LIns* ctor, Traits* ctraits)
{
// Attempt to early bind to constructor method.
Traits* itraits = NULL;
if (ctraits && (itraits = ctraits->itraits) != NULL &&
!ctraits->hasCustomConstruct) {
// Inline the body of ClassClosure::construct() and early bind the call
// to the constructor method, if it's resolved and argc is legal.
// Cannot resolve signatures now because that could cause a premature verification failure,
// one that should occur in the class's script-init.
// If it's already resolved then we're good to go.
if (itraits->init && itraits->init->isResolved() && itraits->init->getMethodSignature()->argcOk(argc)) {
// The explicit null check will throw a different exception than
// the generic call to op_construct below, or to similar paths through
// interpreted code!
emitCheckNull(ctor, ctraits);
emitConstructCall(0, argc, ctor, ctraits);
return;
}
}
// Generic path: could not early bind to a constructor method.
// stack in: ctor-object arg1..N
// sp[-argc] = construct(env, sp[-argc], argc, null, arg1..N)
int ctor_index = state->sp() - argc;
LIns* func = nativeToAtom(ctor, ctraits);
LIns* args = storeAtomArgs(InsConstAtom(nullObjectAtom), argc, ctor_index+1);
LIns* newobj = callIns(FUNCTIONID(op_construct), 4, env_param, func, InsConst(argc, /*tainted*/true), args);
liveAlloc(args);
localSet(ctor_index, atomToNativeRep(itraits, newobj), itraits);
}
static const CallInfo* getArrayHelpers[VI_SIZE] =
{ FUNCTIONID(ArrayObject_getUintProperty), FUNCTIONID(ArrayObject_getIntProperty), FUNCTIONID(ArrayObject_getDoubleProperty) };
static const CallInfo* getObjectVectorHelpers[VI_SIZE] =
{ FUNCTIONID(ObjectVectorObject_getUintProperty), FUNCTIONID(ObjectVectorObject_getIntProperty), FUNCTIONID(ObjectVectorObject_getDoubleProperty) };
static const CallInfo* getIntVectorNativeHelpers[VI_SIZE] =
{ FUNCTIONID(IntVectorObject_getNativeUintProperty), FUNCTIONID(IntVectorObject_getNativeIntProperty), FUNCTIONID(IntVectorObject_getNativeDoubleProperty) };
static const CallInfo* getIntVectorHelpers[VI_SIZE] =
{ FUNCTIONID(IntVectorObject_getUintProperty), FUNCTIONID(IntVectorObject_getIntProperty), FUNCTIONID(IntVectorObject_getDoubleProperty) };
static const CallInfo* getUIntVectorNativeHelpers[VI_SIZE] =
{ FUNCTIONID(UIntVectorObject_getNativeUintProperty), FUNCTIONID(UIntVectorObject_getNativeIntProperty), FUNCTIONID(UIntVectorObject_getNativeDoubleProperty) };
static const CallInfo* getUIntVectorHelpers[VI_SIZE] =
{ FUNCTIONID(UIntVectorObject_getUintProperty), FUNCTIONID(UIntVectorObject_getIntProperty), FUNCTIONID(UIntVectorObject_getDoubleProperty) };
static const CallInfo* getDoubleVectorNativeHelpers[VI_SIZE] =
{ FUNCTIONID(DoubleVectorObject_getNativeUintProperty), FUNCTIONID(DoubleVectorObject_getNativeIntProperty), FUNCTIONID(DoubleVectorObject_getNativeDoubleProperty) };
static const CallInfo* getDoubleVectorHelpers[VI_SIZE] =
{ FUNCTIONID(DoubleVectorObject_getUintProperty), FUNCTIONID(DoubleVectorObject_getIntProperty), FUNCTIONID(DoubleVectorObject_getDoubleProperty) };
#ifdef VMCFG_FLOAT
static const CallInfo* getFloatVectorNativeHelpers[VI_SIZE] =
{ FUNCTIONID(FloatVectorObject_getNativeUintProperty), FUNCTIONID(FloatVectorObject_getNativeIntProperty), FUNCTIONID(FloatVectorObject_getNativeDoubleProperty) };
static const CallInfo* getFloatVectorHelpers[VI_SIZE] =
{ FUNCTIONID(FloatVectorObject_getUintProperty), FUNCTIONID(FloatVectorObject_getIntProperty), FUNCTIONID(FloatVectorObject_getDoubleProperty) };
static const CallInfo* getFloat4VectorNativeHelpers[VI_SIZE] =
{ FUNCTIONID(Float4VectorObject_getNativeUintProperty), FUNCTIONID(Float4VectorObject_getNativeIntProperty), FUNCTIONID(Float4VectorObject_getNativeDoubleProperty) };
static const CallInfo* getFloat4VectorHelpers[VI_SIZE] =
{ FUNCTIONID(Float4VectorObject_getUintProperty), FUNCTIONID(Float4VectorObject_getIntProperty), FUNCTIONID(Float4VectorObject_getDoubleProperty) };
#endif
static const CallInfo* getGenericHelpers[VI_SIZE] =
{ FUNCTIONID(getpropertylate_u), FUNCTIONID(getpropertylate_i), FUNCTIONID(getpropertylate_d) };
LIns* CodegenLIR::emitInlineVectorRead(int objIndexOnStack,
LIns* index,
size_t arrayDataOffset, size_t lenOffset, size_t entriesOffset, int scale, LOpcode load_item,
const CallInfo* helper)
{
CodegenLabel &begin_label = createLabel("arrayoutofbounds");
// index is "int" or "uint".
//
// The basic code is this:
//
// arrayData = *(array+arrayDataOffset) ; array->data
// arrayLen = *(arrayData + lenOffset) ; array->data->len
// if ((unsigned)index >= (unsigned)arrayLen)
// call(helper, array, index)
// value = *(arrayData + (index<<scale) + entriesOffset) ; value = array->data->entries[index]
//
// In the case of float4 the (arrayData + entriesOffset) has to be rounded up to a 16-byte
// boundary at run-time to conform to alignment restrictions.
// It would be better to have a different vector representation and a guarantee about
// alignment from the storage manager, so that we could avoid the adjustment, but that's
// a future improvement.
//
// The "(unsigned)index" compare trick works because vectors are shorter than 2^31 elements,
// that fact is verified statically in the vector code, see eg checkReadIndex_i.
//
// We also depend on vectors being shorter than 2^32 bytes in computing the scaled offset for
// the vector. The 4GB object limit is enforced by MMgc as of September 2011.
LIns* arrayData = loadIns(LIR_ldp, int32_t(arrayDataOffset), localGetp(objIndexOnStack), ACCSET_OTHER, LOAD_NORMAL);
LIns* arrayLen = loadIns(LIR_ldi, int32_t(lenOffset), arrayData, ACCSET_OTHER, LOAD_NORMAL);
LIns* cmp = binaryIns(LIR_geui, index, arrayLen);
suspendCSE();
branchToLabel(LIR_jf, cmp, begin_label);
if(load_item == LIR_ldf4)
callIns(helper, 3, localGetp(objIndexOnStack), InsConstPtr(NULL), index); // we don't really need the returned arg, hence we pass null.
else
callIns(helper, 2, localGetp(objIndexOnStack), index);
emitLabel(begin_label);
resumeCSE();
switch (load_item)
{
case LIR_ldf4:
{
LIns* base = binaryIns(LIR_addp, arrayData, InsConstPtr((const void*)(uintptr_t(entriesOffset) + 15))); // arrayData + entries + 15
LIns* baseRounded = binaryIns(LIR_andp, base, InsConstPtr((const void*)~uintptr_t(15))); // & ~15
LIns* element = ui2p(binaryIns(LIR_lshi, index, InsConst(scale)));
LIns* effectiveAddress = binaryIns(LIR_addp, baseRounded, element);
return loadIns(load_item, 0, effectiveAddress, ACCSET_OTHER, LOAD_NORMAL);
}
#ifdef VMCFG_64BIT
case LIR_ldp:
#endif
case LIR_ldd:
case LIR_ldi:
case LIR_ldf:
{
LIns* valOffset = binaryIns(LIR_addp, arrayData, ui2p(binaryIns(LIR_lshi, index, InsConst(scale))));
return loadIns(load_item, int32_t(entriesOffset), valOffset, ACCSET_OTHER, LOAD_NORMAL);
}
default:
AvmAssert(!"Bad inline vector access");
return NULL;
}
}
LIns* CodegenLIR::emitInlineSpeculativeArrayRead(int objIndexOnStack,
LIns* index,
const CallInfo* helper)
{
CodegenLabel &nonsimpleBeginLabel = createLabel("nonsimplearray");
CodegenLabel &joinLabel = createLabel("joinarraygetprop");
// index is "int" or "uint"
// Emitted speculative fast path for simple-marked array:
// ``return ((unsigned)index < m_lengthIfSimple) ?
// m_denseArray.get(index) : getUintProperty(index));''
size_t lenIfSimpleOffset = offsetof(ArrayObject, m_lengthIfSimple);
size_t denseArrayOffset = offsetof(ArrayObject, m_denseArray);
typedef ListData<Atom, 0> LISTDATA;
size_t entriesOffset = offsetof(LISTDATA, entries);
int scale = (sizeof(void*) == 8) ? 3 : 2;
LIns* arrayPtr = localGetp(objIndexOnStack);
LIns* arraySimpleLen =
loadIns(LIR_ldi, int32_t(lenIfSimpleOffset), arrayPtr,
ACCSET_OTHER, LOAD_NORMAL);
LIns* cmp = binaryIns(LIR_geui, index, arraySimpleLen);
suspendCSE();
LIns* result = insAlloc(sizeof(Atom));
branchToLabel(LIR_jt, cmp, nonsimpleBeginLabel);
LIns* arrayData = loadIns(LIR_ldp, int32_t(denseArrayOffset), arrayPtr,
ACCSET_OTHER, LOAD_NORMAL );
LIns* idxScaled = ui2p(binaryIns(LIR_lshi, index, InsConst(scale)));
LIns* valOffset = binaryIns(LIR_addp, arrayData, idxScaled);
LIns* loadValue = loadIns(LIR_ldp, int32_t(entriesOffset), valOffset,
ACCSET_OTHER, LOAD_NORMAL);
stp(loadValue, result, 0, ACCSET_OTHER);
branchToLabel(LIR_j, NULL, joinLabel);
emitLabel(nonsimpleBeginLabel);
LIns* callValue = callIns(helper, 2, localGetp(objIndexOnStack), index);
stp(callValue, result, 0, ACCSET_OTHER);
emitLabel(joinLabel);
resumeCSE();
return ldp(result, 0, ACCSET_OTHER);
}
// Generate code for get obj[index] where index is a signed or unsigned integer type, or double type.
LIns* CodegenLIR::emitGetIndexedProperty(int objIndexOnStack, LIns* index, Traits* result, IndexKind idxKind)
{
Traits* objType = state->value(objIndexOnStack).traits;
const CallInfo* getter = NULL;
bool valIsAtom = true;
if (objType == ARRAY_TYPE) {
getter = getArrayHelpers[idxKind];
if (idxKind == VI_INT || idxKind == VI_UINT) {
LIns* value = emitInlineSpeculativeArrayRead(objIndexOnStack,
index,
getter);
return atomToNativeRep(result, value);
}
}
else if (objType != NULL && objType->subtypeof(VECTOROBJ_TYPE)) {
if (idxKind == VI_INT || idxKind == VI_UINT) {
typedef ListData<Atom, 0> STORAGE;
return atomToNativeRep(result, emitInlineVectorRead(objIndexOnStack,
index,
offsetof(ObjectVectorObject, m_list.m_data),
offsetof(AtomListHelper::LISTDATA, len),
offsetof(STORAGE, entries),
sizeof(void*) == 8 ? 3 : 2,
LIR_ldp,
getObjectVectorHelpers[idxKind]));
}
getter = getObjectVectorHelpers[idxKind];
}
else if (objType == VECTORINT_TYPE) {
if (result == INT_TYPE) {
if (idxKind == VI_INT || idxKind == VI_UINT) {
typedef ListData<int32_t, 0> STORAGE;
return emitInlineVectorRead(objIndexOnStack,
index,
offsetof(IntVectorObject, m_list.m_data),
offsetof(DataListHelper<int32_t>::LISTDATA, len),
offsetof(STORAGE, entries),
2,
LIR_ldi,
getIntVectorNativeHelpers[idxKind]);
}
getter = getIntVectorNativeHelpers[idxKind];
valIsAtom = false;
}
else {
getter = getIntVectorHelpers[idxKind];
}
}
else if (objType == VECTORUINT_TYPE) {
if (result == UINT_TYPE) {
if (idxKind == VI_INT || idxKind == VI_UINT) {
typedef ListData<uint32_t, 0> STORAGE;
return emitInlineVectorRead(objIndexOnStack,
index,
offsetof(UIntVectorObject, m_list.m_data),
offsetof(DataListHelper<uint32_t>::LISTDATA, len),
offsetof(STORAGE, entries),
2,
LIR_ldi,
getUIntVectorNativeHelpers[idxKind]);
}
getter = getUIntVectorNativeHelpers[idxKind];
valIsAtom = false;
}
else {
getter = getUIntVectorHelpers[idxKind];
}
}
else if (objType == VECTORDOUBLE_TYPE) {
if (result == NUMBER_TYPE) {
if (idxKind == VI_INT || idxKind == VI_UINT) {
typedef ListData<double, 0> STORAGE;
return emitInlineVectorRead(objIndexOnStack,
index,
offsetof(DoubleVectorObject, m_list.m_data),
offsetof(DataListHelper<double>::LISTDATA, len),
offsetof(STORAGE, entries),
3,
LIR_ldd,
getDoubleVectorNativeHelpers[idxKind]);
}
getter = getDoubleVectorNativeHelpers[idxKind];
valIsAtom = false;
}
else {
getter = getDoubleVectorHelpers[idxKind];
}
}
#ifdef VMCFG_FLOAT
else if (objType == VECTORFLOAT_TYPE) {
if (result == FLOAT_TYPE) {
if (idxKind == VI_INT || idxKind == VI_UINT) {
typedef ListData<float, 0> STORAGE;
return emitInlineVectorRead(objIndexOnStack,
index,
offsetof(FloatVectorObject, m_list.m_data),
offsetof(DataListHelper<float>::LISTDATA, len),
offsetof(STORAGE, entries),
2,
LIR_ldf,
getFloatVectorNativeHelpers[idxKind]);
}
getter = getFloatVectorNativeHelpers[idxKind];
valIsAtom = false;
}
else {
getter = getFloatVectorHelpers[idxKind];
}
}
else if (objType == VECTORFLOAT4_TYPE) {
if (result == FLOAT4_TYPE) {
if (idxKind == VI_INT || idxKind == VI_UINT) {
typedef ListData<float4_t, 16> STORAGE;
typedef DataListHelper<float4_t, 16>::LISTDATA LISTDATA;
return emitInlineVectorRead(objIndexOnStack,
index,
offsetof(Float4VectorObject, m_list.m_data),
offsetof(LISTDATA, len),
offsetof(STORAGE, entries),
4,
LIR_ldf4,
getFloat4VectorNativeHelpers[idxKind]);
}
/* handle here the case of native helpers, it is different than all the others */
LIns* retval = lirout->insAlloc(sizeof(float4_t));
callIns(getFloat4VectorNativeHelpers[idxKind], 3, localGetp(objIndexOnStack), lea(0, retval), index);
return ldf4(retval, 0, ACCSET_OTHER);
}
else {
getter = getFloat4VectorHelpers[idxKind];
}
}
#endif
if (getter) {
LIns* value = callIns(getter, 2, localGetp(objIndexOnStack), index);
return valIsAtom ? atomToNativeRep(result, value) : value;
}
else {
LIns* value = callIns(getGenericHelpers[idxKind], 3, env_param, loadAtomRep(objIndexOnStack), index);
return atomToNativeRep(result, value);
}
}
static const CallInfo* setArrayHelpers[VI_SIZE] =
{ FUNCTIONID(ArrayObject_setUintProperty), FUNCTIONID(ArrayObject_setIntProperty), FUNCTIONID(ArrayObject_setDoubleProperty) };
static const CallInfo* setObjectVectorHelpers[VI_SIZE] =
{ FUNCTIONID(ObjectVectorObject_setUintProperty), FUNCTIONID(ObjectVectorObject_setIntProperty), FUNCTIONID(ObjectVectorObject_setDoubleProperty) };
static const CallInfo* setKnownTypeObjectVectorHelpers[VI_SIZE] =
{ FUNCTIONID(ObjectVectorObject_setKnownUintProperty), FUNCTIONID(ObjectVectorObject_setKnownIntProperty), FUNCTIONID(ObjectVectorObject_setKnownDoubleProperty) };
static const CallInfo* setKnownPointerTypeObjectVectorHelpers[VI_SIZE] =
{ FUNCTIONID(ObjectVectorObject_setKnownUintPropertyWithPointer), FUNCTIONID(ObjectVectorObject_setKnownIntPropertyWithPointer), FUNCTIONID(ObjectVectorObject_setKnownDoublePropertyWithPointer) };
static const CallInfo* setIntVectorNativeHelpers[VI_SIZE] =
{ FUNCTIONID(IntVectorObject_setNativeUintProperty), FUNCTIONID(IntVectorObject_setNativeIntProperty), FUNCTIONID(IntVectorObject_setNativeDoubleProperty) };
static const CallInfo* setIntVectorHelpers[VI_SIZE] =
{ FUNCTIONID(IntVectorObject_setUintProperty), FUNCTIONID(IntVectorObject_setIntProperty), FUNCTIONID(IntVectorObject_setDoubleProperty) };
static const CallInfo* setUIntVectorNativeHelpers[VI_SIZE] =
{ FUNCTIONID(UIntVectorObject_setNativeUintProperty), FUNCTIONID(UIntVectorObject_setNativeIntProperty), FUNCTIONID(UIntVectorObject_setNativeDoubleProperty) };
static const CallInfo* setUIntVectorHelpers[VI_SIZE] =
{ FUNCTIONID(UIntVectorObject_setUintProperty), FUNCTIONID(UIntVectorObject_setIntProperty), FUNCTIONID(UIntVectorObject_setDoubleProperty) };
static const CallInfo* setDoubleVectorNativeHelpers[VI_SIZE] =
{ FUNCTIONID(DoubleVectorObject_setNativeUintProperty), FUNCTIONID(DoubleVectorObject_setNativeIntProperty), FUNCTIONID(DoubleVectorObject_setNativeDoubleProperty) };
static const CallInfo* setDoubleVectorHelpers[VI_SIZE] =
{ FUNCTIONID(DoubleVectorObject_setUintProperty), FUNCTIONID(DoubleVectorObject_setIntProperty), FUNCTIONID(DoubleVectorObject_setDoubleProperty) };
#ifdef VMCFG_FLOAT
static const CallInfo* setFloatVectorNativeHelpers[VI_SIZE] =
{ FUNCTIONID(FloatVectorObject_setNativeUintProperty), FUNCTIONID(FloatVectorObject_setNativeIntProperty), FUNCTIONID(FloatVectorObject_setNativeDoubleProperty) };
static const CallInfo* setFloatVectorHelpers[VI_SIZE] =
{ FUNCTIONID(FloatVectorObject_setUintProperty), FUNCTIONID(FloatVectorObject_setIntProperty), FUNCTIONID(FloatVectorObject_setDoubleProperty) };
static const CallInfo* setFloat4VectorNativeHelpers[VI_SIZE] =
{ FUNCTIONID(Float4VectorObject_setNativeUintProperty), FUNCTIONID(Float4VectorObject_setNativeIntProperty), FUNCTIONID(Float4VectorObject_setNativeDoubleProperty) };
static const CallInfo* setFloat4VectorHelpers[VI_SIZE] =
{ FUNCTIONID(Float4VectorObject_setUintProperty), FUNCTIONID(Float4VectorObject_setIntProperty), FUNCTIONID(Float4VectorObject_setDoubleProperty) };
#endif
static const CallInfo* setGenericHelpers[VI_SIZE] =
{ FUNCTIONID(setpropertylate_u), FUNCTIONID(setpropertylate_i), FUNCTIONID(setpropertylate_d) };
// Not for values that require a write barrier!
void CodegenLIR::emitInlineVectorWrite(int objIndexOnStack,
LIns* index,
LIns* value,
size_t arrayDataOffset, size_t lenOffset, size_t entriesOffset, int scale, LOpcode store_item,
const CallInfo* helper)
{
CodegenLabel &begin_label = createLabel("arrayoutofbounds");
CodegenLabel &end_label = createLabel("traprecovered");
// index is "int" or "uint".
//
// The basic code is this:
//
// arrayData = *(array+arrayDataOffset) ; array->data
// arrayLen = *(arrayData + lenOffset) ; array->data->len
// if ((unsigned)index >= (unsigned)arrayLen)
// call(helper, array, index, value)
// else
// *(arrayData + (index<<scale) + entriesOffset) = value ; array->data->entries[index] = value
//
// In the case of float4 the (arrayData + entriesOffset) has to be rounded up to a 16-byte
// boundary at run-time to conform to alignment restrictions.
// It would be better to have a different vector representation and a guarantee about
// alignment from the storage manager, so that we could avoid the adjustment, but that's
// a future improvement.
//
// The "(unsigned)index" compare trick works because vectors are shorter than 2^31 elements,
// that fact is verified statically in the vector code, see eg checkReadIndex_i.
//
// We also depend on vectors being shorter than 2^32 bytes in computing the scaled offset for
// the vector. The 4GB object limit is enforced by MMgc as of September 2011.
LIns* arrayData = loadIns(LIR_ldp, int32_t(arrayDataOffset), localGetp(objIndexOnStack), ACCSET_OTHER, LOAD_NORMAL);
LIns* arrayLen = loadIns(LIR_ldi, int32_t(lenOffset), arrayData, ACCSET_OTHER, LOAD_NORMAL);
LIns* cmp = binaryIns(LIR_geui, index, arrayLen);
suspendCSE();
branchToLabel(LIR_jf, cmp, begin_label);
callIns(helper, 3, localGetp(objIndexOnStack), index, value);
branchToLabel(LIR_j, NULL, end_label);
emitLabel(begin_label);
switch (store_item)
{
#ifdef VMCFG_FLOAT
case LIR_stf4:
{
LIns* base = binaryIns(LIR_addp, arrayData, InsConstPtr((const void*)(uintptr_t(entriesOffset) + 15))); // arrayData + entries + 15
LIns* baseRounded = binaryIns(LIR_andp, base, InsConstPtr((const void*)~uintptr_t(15))); // & ~15
LIns* element = ui2p(binaryIns(LIR_lshi, index, InsConst(scale)));
LIns* effectiveAddress = binaryIns(LIR_addp, baseRounded, element);
if( (value->opcode() == LIR_addp) && (value->oprnd1() == vars) ){
AvmAssert(value->oprnd2()->isImmP());
int32_t v_offset = (int32_t) (intptr_t) value->oprnd2()->immP();
value = ldf4(vars, v_offset, ACCSET_VARS);
} else {
value = ldf4(value, 0 , ACCSET_OTHER);
}
storeIns(store_item, value, 0, effectiveAddress, ACCSET_OTHER);
break;
}
#endif
#ifdef VMCFG_64BIT
case LIR_stp:
#endif
case LIR_std:
case LIR_sti:
case LIR_stf:
{
LIns *valOffset = binaryIns(LIR_addp, arrayData, ui2p(binaryIns(LIR_lshi, index, InsConst(scale))));
storeIns(store_item, value, int32_t(entriesOffset), valOffset, ACCSET_OTHER);
break;
}
default:
AvmAssert(!"Bad inline vector access");
}
emitLabel(end_label);
resumeCSE();
}
// Generate code for 'obj[index] = value' where index is a signed or unsigned integer type, or a double.
void CodegenLIR::emitSetIndexedProperty(int objIndexOnStack, int valIndexOnStack, LIns* index, IndexKind idxKind)
{
Traits* valueType = state->value(valIndexOnStack).traits;
Traits* objType = state->value(objIndexOnStack).traits;
const CallInfo* setter = NULL;
LIns* value = NULL;
if (objType == ARRAY_TYPE) {
value = loadAtomRep(valIndexOnStack);
setter = setArrayHelpers[idxKind];
}
else if (objType != NULL && objType->subtypeof(VECTOROBJ_TYPE)) {
value = loadAtomRep(valIndexOnStack);
// Optimization: avoid coercion if the stored value is known to be a subtype
// of the vector parameter type.
Traits* paramType = objType->m_paramTraits;
if (paramType != NULL && valueType != NULL && valueType->subtypeof(paramType)) {
// Optimization: avoid atomWriteBarrier if the vector parameter type is
// known to be an RCObject; atomWriteBarrier has a bunch of run-time tests
// that try to determine that fact.
if (isRCObjectSlot(valueStorageType(Traits::getBuiltinType(paramType))))
setter = setKnownPointerTypeObjectVectorHelpers[idxKind];
else
setter = setKnownTypeObjectVectorHelpers[idxKind];
}
else
setter = setObjectVectorHelpers[idxKind];
}
else if (objType == VECTORINT_TYPE) {
if (valueType == INT_TYPE) {
if (idxKind == VI_INT || idxKind == VI_UINT) {
typedef ListData<int32_t, 0> STORAGE;
emitInlineVectorWrite(objIndexOnStack,
index,
localGet(valIndexOnStack),
offsetof(IntVectorObject, m_list.m_data),
offsetof(DataListHelper<int32_t>::LISTDATA, len),
offsetof(STORAGE, entries),
2,
LIR_sti,
setIntVectorNativeHelpers[idxKind]);
return;
}
value = localGet(valIndexOnStack);
setter = setIntVectorNativeHelpers[idxKind];
}
else {
value = loadAtomRep(valIndexOnStack);
setter = setIntVectorHelpers[idxKind];
}
}
else if (objType == VECTORUINT_TYPE) {
if (valueType == UINT_TYPE) {
if (idxKind == VI_INT || idxKind == VI_UINT) {
typedef ListData<uint32_t, 0> STORAGE;
emitInlineVectorWrite(objIndexOnStack,
index,
localGet(valIndexOnStack),
offsetof(UIntVectorObject, m_list.m_data),
offsetof(DataListHelper<uint32_t>::LISTDATA, len),
offsetof(STORAGE, entries),
2,
LIR_sti,
setUIntVectorNativeHelpers[idxKind]);
return;
}
value = localGet(valIndexOnStack);
setter = setUIntVectorNativeHelpers[idxKind];
}
else {
value = loadAtomRep(valIndexOnStack);
setter = setUIntVectorHelpers[idxKind];
}
}
else if (objType == VECTORDOUBLE_TYPE) {
if (valueType == NUMBER_TYPE) {
if (idxKind == VI_INT || idxKind == VI_UINT) {
typedef ListData<double, 0> STORAGE;
emitInlineVectorWrite(objIndexOnStack,
index,
localGetd(valIndexOnStack),
offsetof(DoubleVectorObject, m_list.m_data),
offsetof(DataListHelper<double>::LISTDATA, len),
offsetof(STORAGE, entries),
3,
LIR_std,
setDoubleVectorNativeHelpers[idxKind]);
return;
}
value = localGetd(valIndexOnStack);
setter = setDoubleVectorNativeHelpers[idxKind];
}
else {
value = loadAtomRep(valIndexOnStack);
setter = setDoubleVectorHelpers[idxKind];
}
}
#ifdef VMCFG_FLOAT
else if (objType == VECTORFLOAT_TYPE) {
if (valueType == FLOAT_TYPE) {
if (idxKind == VI_INT || idxKind == VI_UINT) {
typedef ListData<float, 0> STORAGE;
emitInlineVectorWrite(objIndexOnStack,
index,
localGetf(valIndexOnStack),
offsetof(FloatVectorObject, m_list.m_data),
offsetof(DataListHelper<float>::LISTDATA, len),
offsetof(STORAGE, entries),
2,
LIR_stf,
setFloatVectorNativeHelpers[idxKind]);
return;
}
value = localGetf(valIndexOnStack);
setter = setFloatVectorNativeHelpers[idxKind];
}
else {
value = loadAtomRep(valIndexOnStack);
setter = setFloatVectorHelpers[idxKind];
}
}
else if (objType == VECTORFLOAT4_TYPE) {
if (valueType == FLOAT4_TYPE) {
if (idxKind == VI_INT || idxKind == VI_UINT) {
typedef ListData<float4_t, 16> STORAGE;
typedef DataListHelper<float4_t, 16>::LISTDATA LISTDATA;
emitInlineVectorWrite(objIndexOnStack,
index,
localGetf4Addr(valIndexOnStack),
offsetof(Float4VectorObject, m_list.m_data),
offsetof(LISTDATA, len),
offsetof(STORAGE, entries),
4,
LIR_stf4,
setFloat4VectorNativeHelpers[idxKind]);
}
value = localGetf4Addr(valIndexOnStack);
setter = setFloat4VectorNativeHelpers[idxKind];
}
else {
value = loadAtomRep(valIndexOnStack);
setter = setFloat4VectorHelpers[idxKind];
}
}
#endif // VMCFG_FLOAT
if (setter) {
callIns(setter, 3, localGetp(objIndexOnStack), index, value);
} else {
value = loadAtomRep(valIndexOnStack);
callIns(setGenericHelpers[idxKind], 4, env_param, loadAtomRep(objIndexOnStack), index, value);
}
}
// Try to optimize our input argument to the fastest possible type.
// Non-negative integer constants can be considered unsigned.
// Promotions from int/uint to number can be used as integers.
// Double constants that are actually integers can be used as integers.
LIns *CodegenLIR::optimizeIndexArgumentType(int32_t sp, Traits** indexType)
{
LIns *index = NULL;
if (*indexType == INT_TYPE) {
index = localGet(sp);
if (index->isImmI() && index->immI() > 0) {
*indexType = UINT_TYPE;
}
return index;
}
else if (*indexType == UINT_TYPE) {
return localGet(sp);
}
// Convert Number expression to int or uint if it is a promotion
// from int or uint, or if it is a constant in range for int or uint.
else if (*indexType == NUMBER_TYPE FLOAT_ONLY(|| *indexType == FLOAT_TYPE)) {
bool singlePrecision = IFFLOAT(*indexType == FLOAT_TYPE,false);
index = singlePrecision ? localGetf(sp) : localGetd(sp);
if (index->opcode() == LIR_i2d FLOAT_ONLY(|| index->opcode()== LIR_i2f)) {
FLOAT_ONLY(AvmAssert(singlePrecision == (index->opcode()==LIR_i2f)) );
*indexType = INT_TYPE;
return index->oprnd1();
}
else if (index->opcode() == LIR_ui2d FLOAT_ONLY(|| index->opcode()== LIR_ui2f)) {
FLOAT_ONLY(AvmAssert(singlePrecision == (index->opcode()==LIR_ui2f)) );
*indexType = UINT_TYPE;
return index->oprnd1();
}
else if (index->isImmD() FLOAT_ONLY(|| index->isImmF()) )
{
double d = IFFLOAT( index->isImmD() ? index->immD(): (double)index->immF(), index->immD());
// Convert to uint if possible.
uint32_t u = uint32_t(d);
if (double(u) == d) {
*indexType = UINT_TYPE;
return InsConst(u, index->isTainted());
}
// Failing that, it may still be possible to
// convert to some negative values to int.
int32_t i = int32_t(d);
if (double(i) == d) {
*indexType = INT_TYPE;
return InsConst(i, index->isTainted());
}
}
}
return index;
}
void CodegenLIR::emit(AbcOpcode opcode, uintptr_t op1, uintptr_t op2, Traits* result)
{
int sp = state->sp();
switch (opcode)
{
// sign extends
case OP_sxi1:
case OP_sxi8:
case OP_sxi16:
{
// straightforward shift based sign extension
static const uint8_t kShiftAmt[3] = { 31, 24, 16 };
int32_t index = (int32_t) op1;
LIns* val = localGet(index);
if ((opcode == OP_sxi8 && val->opcode() == LIR_ldc2i) ||
(opcode == OP_sxi16 && val->opcode() == LIR_lds2i))
{
// if we are sign-extending the result of a load-and-sign-extend
// instruction, no need to do anything.
break;
}
LIns* sh = InsConst(kShiftAmt[opcode - OP_sxi1]);
LIns* shl = binaryIns(LIR_lshi, val, sh);
LIns* res = binaryIns(LIR_rshi, shl, sh);
localSet(index, res, result);
break;
}
// loads
case OP_lix8:
case OP_lix16:
case OP_li8:
case OP_li16:
case OP_li32:
{
int32_t index = (int32_t) op1;
LIns* mopAddr = localGet(index);
const MopsInfo& mi = kMopsLoadInfo[opcode-OP_lix8];
#if defined (VMCFG_INTERRUPT_SAFEPOINT_POLL) && defined (NJ_SAFEPOINT_POLLING_SUPPORTED)
Ins(LIR_memfence);
#endif
#ifdef VMCFG_MOPS_USE_EXPANDED_LOADSTORE_INT
int32_t disp = 0;
LIns* realAddr = mopAddrToRangeCheckedRealAddrAndDisp(mopAddr, mi.size, &disp);
LIns* i2 = loadIns(mi.op, disp, realAddr, ACCSET_OTHER);
#else
LIns* realAddr = mopAddrToRangeCheckedRealAddrAndDisp(mopAddr, mi.size, NULL);
LIns* i2 = callIns(mi.call, 1, realAddr);
#endif
localSet(index, i2, result);
break;
}
case OP_lf32:
case OP_lf64:
{
int32_t index = (int32_t) op1;
LIns* mopAddr = localGet(index);
const MopsInfo& mi = kMopsLoadInfo[opcode-OP_lix8];
#if defined (VMCFG_INTERRUPT_SAFEPOINT_POLL) && defined (NJ_SAFEPOINT_POLLING_SUPPORTED)
Ins(LIR_memfence);
#endif
#ifdef VMCFG_MOPS_USE_EXPANDED_LOADSTORE_FP
int32_t disp = 0;
LIns* realAddr = mopAddrToRangeCheckedRealAddrAndDisp(mopAddr, mi.size, &disp);
LIns* i2 = loadIns(mi.op, disp, realAddr, ACCSET_OTHER);
#else
LIns* realAddr = mopAddrToRangeCheckedRealAddrAndDisp(mopAddr, mi.size, NULL);
LIns* i2 = callIns(mi.call, 1, realAddr);
#endif
localSet(index, i2, result);
break;
}
#ifdef VMCFG_FLOAT
case OP_lf32x4:
{
#if defined (VMCFG_INTERRUPT_SAFEPOINT_POLL) && defined (NJ_SAFEPOINT_POLLING_SUPPORTED)
Ins(LIR_memfence);
#endif
// TODO: inlining. The appropriate condition is probably *not* VMCFG_MOPS_USE_EXPANDED_LOADSTORE_FP.
int32_t index = (int32_t) op1;
LIns* mopAddr = binaryIns(LIR_andi, localGet(index), InsConst(~15U));
LIns* realAddr = mopAddrToRangeCheckedRealAddrAndDisp(mopAddr, sizeof(float4_t), NULL);
LIns* retval = insAlloc(sizeof(float4_t));
callIns(FUNCTIONID(mop_lf32x4), 2, retval, realAddr);
localSet(index, ldf4(retval,0,ACCSET_OTHER), result);
break;
}
#endif
// stores
case OP_si8:
case OP_si16:
case OP_si32:
{
LIns* svalue = localGet(sp-1);
LIns* mopAddr = localGet(sp);
const MopsInfo& mi = kMopsStoreInfo[opcode-OP_si8];
#if defined (VMCFG_INTERRUPT_SAFEPOINT_POLL) && defined (NJ_SAFEPOINT_POLLING_SUPPORTED)
Ins(LIR_memfence);
#endif
#ifdef VMCFG_MOPS_USE_EXPANDED_LOADSTORE_INT
int32_t disp = 0;
LIns* realAddr = mopAddrToRangeCheckedRealAddrAndDisp(mopAddr, mi.size, &disp);
lirout->insStore(mi.op, svalue, realAddr, disp, ACCSET_OTHER);
#else
LIns* realAddr = mopAddrToRangeCheckedRealAddrAndDisp(mopAddr, mi.size, NULL);
callIns(mi.call, 2, realAddr, svalue);
#endif
break;
}
case OP_sf32:
case OP_sf64:
{
bool singlePrecision = IFFLOAT(state->value(sp-1).traits == FLOAT_TYPE, false);
LIns* svalue = singlePrecision ? localGetf(sp-1) : localGetd(sp-1);
LIns* mopAddr = localGet(sp);
const MopsInfo& mi = kMopsStoreInfo[opcode-OP_si8];
#if defined (VMCFG_INTERRUPT_SAFEPOINT_POLL) && defined (NJ_SAFEPOINT_POLLING_SUPPORTED)
Ins(LIR_memfence);
#endif
#ifdef VMCFG_MOPS_USE_EXPANDED_LOADSTORE_FP
int32_t disp = 0;
LIns* realAddr = mopAddrToRangeCheckedRealAddrAndDisp(mopAddr, mi.size, &disp);
lirout->insStore(mi.op, svalue, realAddr, disp, ACCSET_OTHER);
#else
LIns* realAddr = mopAddrToRangeCheckedRealAddrAndDisp(mopAddr, mi.size, NULL);
callIns(mi.call, 2, realAddr, svalue);
#endif
break;
}
#ifdef VMCFG_FLOAT
case OP_sf32x4:
{
#if defined (VMCFG_INTERRUPT_SAFEPOINT_POLL) && defined (NJ_SAFEPOINT_POLLING_SUPPORTED)
Ins(LIR_memfence);
#endif
// TODO: inlining. The appropriate condition is probably *not* VMCFG_MOPS_USE_EXPANDED_LOADSTORE_FP.
LIns* svalue = localGetf4Addr(sp-1);
LIns* mopAddr = binaryIns(LIR_andi, localGet(sp), InsConst(~15U));
LIns* realAddr = mopAddrToRangeCheckedRealAddrAndDisp(mopAddr, sizeof(float4_t), NULL);
callIns(FUNCTIONID(mop_sf32x4), 2, realAddr, svalue);
break;
}
#endif
case OP_jump:
{
// spill everything first
const uint8_t* target = (const uint8_t*) op1;
#ifdef DEBUGGER
Sampler* s = core->get_sampler();
if (s && s->sampling() && target < state->abc_pc)
{
emitSampleCheck();
}
#endif
branchToAbcPos(LIR_j, 0, target);
break;
}
case OP_lookupswitch:
{
//int index = integer(*(sp--));
//pc += readS24(index < readU16(pc+4) ?
// (pc+6+3*index) : // matched case
// (pc+1)); // default
int count = int(1 + op2);
const uint8_t* targetpc = (const uint8_t*) op1;
AvmAssert(state->value(sp).traits == INT_TYPE);
AvmAssert(count >= 0);
// Compute address of jump table
const uint8_t* pc = 4 + state->abc_pc;
AvmCore::readU32(pc); // skip count
// Delete any trailing table entries that == default case (effective for asc output)
while (count > 0 && targetpc == (state->abc_pc + AvmCore::readS24(pc+3*(count-1))))
count--;
if (count > 0) {
LIns* index = localGet(sp);
LIns* cmp = binaryIns(LIR_ltui, index, InsConst(count));
branchToAbcPos(LIR_jf, cmp, targetpc);
// count == 1 is equivalent to if (case) else (default), so don't bother with jtbl
if (NJ_JTBL_SUPPORTED && count > 1) {
// Backend supports LIR_jtbl for jump tables
LIns* jtbl = lirout->insJtbl(index, count);
for (int i=0; i < count; i++) {
const uint8_t* target = state->abc_pc + AvmCore::readS24(pc+3*i);
patchLater(jtbl, target, i);
}
} else {
// Backend doesn't support jump tables, use cascading if's
for (int i=0; i < count; i++) {
const uint8_t* target = state->abc_pc + AvmCore::readS24(pc+3*i);
branchToAbcPos(LIR_jt, binaryIns(LIR_eqi, index, InsConst(i)), target);
}
}
}
else {
// switch collapses into a single target
branchToAbcPos(LIR_j, 0, targetpc);
}
break;
}
case OP_returnvoid:
case OP_returnvalue:
{
// ISSUE if a method has multiple returns this causes some bloat
#ifdef DEBUGGER
if (haveDebugger) {
callIns(FUNCTIONID(debugExit), 2,
env_param, csn);
// now we toast the cse and restore contents in order to
// ensure that any variable modifications made by the debugger
// will be pulled in.
//firstCse = ip;
}
#endif // DEBUGGER
if (driver->hasReachableExceptions())
{
// _ef.endTry();
callIns(FUNCTIONID(endTry), 1, _ef);
}
#ifdef VMCFG_TELEMETRY_SAMPLER
// Emit the code to take a sample of the MethodFrame stack. This is only
// emitted when the sampler is enabled so there is no performance impact
// otherwise.
if(core->samplerEnabled) {
LIns* sampleTicks = loadIns(LIR_ldi, offsetof(AvmCore,sampleTicks), coreAddr, ACCSET_OTHER);
CodegenLabel &afterTakeSampleExit_label = createLabel("afterTakeSampleExit");
branchToLabel(LIR_jf, sampleTicks, afterTakeSampleExit_label);
callIns(FUNCTIONID(takeSampleWrapper), 1, coreAddr);
emitLabel(afterTakeSampleExit_label);
}
#endif
// replicate MethodFrame dtor inline -- must come after endTry call (if any)
LIns* nextMethodFrame = loadIns(LIR_ldp, offsetof(MethodFrame,next), methodFrame, ACCSET_OTHER);
stp(nextMethodFrame, coreAddr, offsetof(AvmCore,currentMethodFrame), ACCSET_OTHER);
Traits* t = ms->returnTraits();
LIns* retvalue;
if (opcode == OP_returnvalue)
{
// already coerced to required native type
// use localCopy() to sniff type and use appropriate load instruction
int32_t index = (int32_t) op1;
retvalue = localCopy(index);
}
else
{
retvalue = undefConst;
if (t && t != VOID_TYPE)
{
// implicitly coerce undefined to the return type
retvalue = callIns(FUNCTIONID(coerce), 3,
env_param, retvalue, InsConstPtr(t));
retvalue = atomToNativeRep(t, retvalue);
}
}
switch (bt(t)) {
#ifdef VMCFG_FLOAT
case BUILTIN_float:
Ins(LIR_retf, retvalue);
break;
case BUILTIN_float4:
Ins(LIR_retf4, retvalue);
break;
#endif
case BUILTIN_number:
Ins(LIR_retd, retvalue);
break;
case BUILTIN_int:
retp(i2p(retvalue));
break;
case BUILTIN_uint:
case BUILTIN_boolean:
retp(ui2p(retvalue));
break;
default:
retp(retvalue);
break;
}
break;
}
case OP_typeof:
{
//sp[0] = typeof(sp[0]);
int32_t index = (int32_t) op1;
LIns* value = loadAtomRep(index);
LIns* i3 = callIns(FUNCTIONID(typeof), 2,
coreAddr, value);
AvmAssert(result == STRING_TYPE);
localSet(index, i3, result);
break;
}
case OP_not:
{
int32_t index = (int32_t) op1;
AvmAssert(state->value(index).traits == BOOLEAN_TYPE && result == BOOLEAN_TYPE);
LIns* value = localGet(index); // 0 or 1
LIns* i3 = eqi0(value); // 1 or 0
localSet(index, i3, result);
break;
}
case OP_negate: {
int32_t index = (int32_t) op1;
#ifdef VMCFG_FLOAT
const Traits* vt = state->value(index).traits;
AvmAssert ( vt == result );
if(vt == NUMBER_TYPE)
localSet(index, Ins(LIR_negd, localGetd(index)), result);
else if(vt == FLOAT_TYPE)
localSet(index, Ins(LIR_negf, localGetf(index)), result);
else if(vt == FLOAT4_TYPE)
localSet(index, Ins(LIR_negf4, localGetf4(index)), result);
else {
NanoAssert( vt == OBJECT_TYPE && state->value(index).notNull );
emitNumericOp1( index, BasicLIREmitter(this, LIR_negd, FUNCTIONID(op_negate)) );
}
#else
localSet(index, Ins(LIR_negd, localGetd(index)),result);
#endif // VMCFG_FLOAT
break;
}
case OP_negate_i: {
//framep[op1] = -framep[op1]
int32_t index = (int32_t) op1;
AvmAssert(state->value(index).traits == INT_TYPE);
localSet(index, Ins(LIR_negi, localGet(index)), result);
break;
}
case OP_increment:
case OP_decrement:
case OP_inclocal:
case OP_declocal: {
int32_t index = (int32_t) op1;
int32_t incr = (int32_t) op2; // 1 or -1
LIns* addIns = NULL;
// We do not blind the increment/decrement value, as it is always 1 or -1.
AvmAssert(incr == 1 || incr == -1);
#ifdef VMCFG_FLOAT
const Traits* vt = state->value(index).traits;
AvmAssert(result == vt);
if(vt == NUMBER_TYPE)
addIns = binaryIns(LIR_addd, localGetd(index), i2dIns(InsConst(incr, /*tainted*/false)));
else if(vt == FLOAT_TYPE)
addIns = binaryIns(LIR_addf, localGetf(index), InsConstFlt((float)incr, /*tainted*/false));
else if(vt == FLOAT4_TYPE)
addIns = binaryIns(LIR_addf4, localGetf4(index), Ins(LIR_f2f4, InsConstFlt((float)incr, /*tainted*/false)) );
else {
// NUMERIC - i.e., unknown
AvmAssert(vt == OBJECT_TYPE && state->value(index).notNull);
emitNumericOp1(index, IncrementLIREmitter(this, incr<0));
JIT_EVENT(jit_add);
break; // skip the localSet, emitNumericOp1 already does it
}
#else
addIns = binaryIns(LIR_addd, localGetd(index), i2dIns(InsConst(incr, /*tainted*/false)));
#endif // VMCFG_FLOAT
localSet(index, addIns,result);
break;
}
case OP_inclocal_i:
case OP_declocal_i:
case OP_increment_i:
case OP_decrement_i: {
int32_t index = (int32_t) op1;
int32_t incr = (int32_t) op2;
AvmAssert(state->value(index).traits == INT_TYPE);
// We do not blind the increment/decrement value, as it is always 1 or -1.
AvmAssert(incr == 1 || incr == -1);
localSet(index, binaryIns(LIR_addi, localGet(index), InsConst(incr, /*tainted*/false)), result);
break;
}
case OP_bitnot: {
// *sp = core->intToAtom(~integer(*sp));
int32_t index = (int32_t) op1;
AvmAssert(state->value(index).traits == INT_TYPE);
localSet(index, lirout->ins1(LIR_noti, localGet(index)), result);
break;
}
case OP_modulo: {
#ifdef VMCFG_FLOAT
if(result ==FLOAT_TYPE){
AvmAssert(state->value(sp-1).traits == FLOAT_TYPE);
AvmAssert(state->value(sp).traits == FLOAT_TYPE);
LIns* get1 = f2dIns(localGetf(sp-1));
LIns* get2 = f2dIns(localGetf(sp));
LIns* out = callIns(FUNCTIONID(mod), 2,get1,get2);
localSet(sp-1, d2fIns(out), result);
} else if(result == FLOAT4_TYPE){
AvmAssert(state->value(sp-1).traits == FLOAT4_TYPE);
AvmAssert(state->value(sp).traits == FLOAT4_TYPE);
LIns* f4 = localGetf4(sp-1), *g4 = localGetf4(sp);
LIns* f4x = f2dIns(lirout->ins1(LIR_f4x, f4)), *g4x = f2dIns(lirout->ins1(LIR_f4x, g4));
LIns* ox = callIns(FUNCTIONID(mod), 2,f4x,g4x);
LIns* f4y = f2dIns(lirout->ins1(LIR_f4y, f4)), *g4y = f2dIns(lirout->ins1(LIR_f4y, g4));
LIns* oy = callIns(FUNCTIONID(mod), 2,f4y,g4y);
LIns* f4z = f2dIns(lirout->ins1(LIR_f4z, f4)), *g4z = f2dIns(lirout->ins1(LIR_f4z, g4));
LIns* oz = callIns(FUNCTIONID(mod), 2,f4z,g4z);
LIns* f4w = f2dIns(lirout->ins1(LIR_f4w, f4)), *g4w = f2dIns(lirout->ins1(LIR_f4w, g4));
LIns* ow = callIns(FUNCTIONID(mod), 2,f4w,g4w);
localSet(sp - 1, lirout->ins4(LIR_ffff2f4,d2fIns(ox), d2fIns(oy), d2fIns(oz), d2fIns(ow)), result);
} else if(result == NUMBER_TYPE){
AvmAssert(state->value(sp-1).traits == NUMBER_TYPE);
AvmAssert(state->value(sp).traits == NUMBER_TYPE);
LIns* get1= localGetd(sp-1);
LIns* get2 = localGetd(sp);
LIns* out = callIns(FUNCTIONID(mod), 2,get1,get2);
localSet(sp-1, out, result);
} else {
AvmAssert(result == OBJECT_TYPE && state->value(sp).traits == OBJECT_TYPE && state->value(sp-1).traits == OBJECT_TYPE && state->value(sp).notNull && state->value(sp-1).notNull);
emitNumericOp2( sp-1, sp, ModuloLIREmitter(this) );
}
#else
LIns* out = callIns(FUNCTIONID(mod), 2, localGetd(sp-1), localGetd(sp));
localSet(sp-1, out, result);
#endif // VMCFG_FLOAT
break;
}
case OP_divide:
case OP_multiply:
case OP_subtract: {
#ifdef VMCFG_FLOAT
LOpcode opf,opf4,opd;
const CallInfo* interpCall = NULL;
AvmAssert(state->value(sp-1).traits==result && state->value(sp-1).traits ==result);
switch(opcode){
default: AvmAssertMsg(false,"Can only handle OP_divide/OP_multiply/OP_subtract");
case OP_divide: opf= LIR_divf; opf4= LIR_divf4; opd = LIR_divd; interpCall= FUNCTIONID(op_divide); break;
case OP_multiply: opf= LIR_mulf; opf4= LIR_mulf4; opd = LIR_muld; interpCall= FUNCTIONID(op_multiply); break;
case OP_subtract: opf= LIR_subf; opf4= LIR_subf4; opd = LIR_subd; interpCall= FUNCTIONID(op_subtract); break;
}
if(result ==FLOAT_TYPE){
localSet(sp-1, binaryIns(opf, localGetf(sp-1), localGetf(sp)), result);
} else if(result == NUMBER_TYPE){
localSet(sp-1, binaryIns(opd, localGetd(sp-1), localGetd(sp)), result);
} else if(result == FLOAT4_TYPE){
localSet(sp-1, binaryIns(opf4, localGetf4(sp-1), localGetf4(sp)), result);
} else {
AvmAssert(result == OBJECT_TYPE && state->value(sp).traits == OBJECT_TYPE && state->value(sp-1).traits == OBJECT_TYPE && state->value(sp).notNull && state->value(sp-1).notNull);
emitNumericOp2(sp-1, sp, BasicLIREmitter(this, opd, interpCall) );
}
#else
LOpcode op;
switch (opcode) {
default:
case OP_divide: op = LIR_divd; break;
case OP_multiply: op = LIR_muld; break;
case OP_subtract: op = LIR_subd; break;
}
localSet(sp-1, binaryIns(op, localGetd(sp-1), localGetd(sp)), result);
#endif // VMCFG_FLOAT
break;
}
case OP_subtract_i:
case OP_add_i:
case OP_multiply_i:
case OP_lshift:
case OP_rshift:
case OP_urshift:
case OP_bitand:
case OP_bitor:
case OP_bitxor:
{
LOpcode op;
switch (opcode) {
default:
case OP_bitxor: op = LIR_xori; break;
case OP_bitor: op = LIR_ori; break;
case OP_bitand: op = LIR_andi; break;
case OP_urshift: op = LIR_rshui; break;
case OP_rshift: op = LIR_rshi; break;
case OP_lshift: op = LIR_lshi; break;
case OP_multiply_i: op = LIR_muli; break;
case OP_add_i: op = LIR_addi; break;
case OP_subtract_i: op = LIR_subi; break;
}
LIns* lhs = localGet(sp-1);
LIns* rhs = localGet(sp);
LIns* out = binaryIns(op, lhs, rhs);
localSet(sp-1, out, result);
break;
}
case OP_throw:
{
//throwAtom(*sp--);
int32_t index = (int32_t) op1;
callIns(FUNCTIONID(throwAtom), 2, coreAddr, loadAtomRep(index));
break;
}
case OP_getsuper:
{
// stack in: obj [ns [name]]
// stack out: value
// sp[0] = env->getsuper(sp[0], multiname)
int objDisp = sp;
LIns* multi = initMultiname((Multiname*)op1, objDisp);
AvmAssert(state->value(objDisp).notNull);
LIns* obj = loadAtomRep(objDisp);
LIns* i3 = callIns(FUNCTIONID(getsuper), 3,
env_param, obj, multi);
liveAlloc(multi);
i3 = atomToNativeRep(result, i3);
localSet(objDisp, i3, result);
break;
}
case OP_setsuper:
{
// stack in: obj [ns [name]] value
// stack out: nothing
// core->setsuper(sp[-1], multiname, sp[0], env->vtable->base)
int objDisp = sp-1;
LIns* multi = initMultiname((Multiname*)op1, objDisp);
AvmAssert(state->value(objDisp).notNull);
LIns* obj = loadAtomRep(objDisp);
LIns* value = loadAtomRep(sp);
callIns(FUNCTIONID(setsuper), 4,
env_param, obj, multi, value);
liveAlloc(multi);
break;
}
case OP_nextname:
case OP_nextvalue:
{
// sp[-1] = next[name|value](sp[-1], sp[0]);
LIns* obj = loadAtomRep(sp-1);
AvmAssert(state->value(sp).traits == INT_TYPE);
LIns* index = localGet(sp);
LIns* i1 = callIns((opcode == OP_nextname) ? FUNCTIONID(nextname) : FUNCTIONID(nextvalue), 3,
env_param, obj, index);
localSet(sp-1, atomToNativeRep(result, i1), result);
break;
}
case OP_hasnext:
{
// sp[-1] = hasnext(sp[-1], sp[0]);
LIns* obj = loadAtomRep(sp-1);
AvmAssert(state->value(sp).traits == INT_TYPE);
LIns* index = localGet(sp);
LIns* i1 = callIns(FUNCTIONID(hasnext), 3,
env_param, obj, index);
AvmAssert(result == INT_TYPE);
localSet(sp-1, i1, result);
break;
}
case OP_hasnext2:
{
// fixme - if obj is already Atom, or index is already int,
// easier to directly reference space in vars.
int32_t obj_index = (int32_t) op1;
int32_t index_index = (int32_t) op2;
LIns* obj = insAlloc(sizeof(Atom));
LIns* index = insAlloc(sizeof(int32_t));
stp(loadAtomRep(obj_index), obj, 0, ACCSET_STORE_ANY); // Atom obj
sti(localGet(index_index), index, 0, ACCSET_STORE_ANY); // int32_t index
LIns* i1 = callIns(FUNCTIONID(hasnextproto), 3,
env_param, obj, index);
localSet(obj_index, loadIns(LIR_ldp, 0, obj, ACCSET_LOAD_ANY), OBJECT_TYPE); // Atom obj
localSet(index_index, loadIns(LIR_ldi, 0, index, ACCSET_LOAD_ANY), INT_TYPE); // int32_t index
AvmAssert(result == BOOLEAN_TYPE);
localSet(sp+1, i1, result);
break;
}
case OP_newfunction:
{
uint32_t function_id = (uint32_t) op1;
int32_t index = (int32_t) op2;
//sp[0] = core->newfunction(env, body, _scopeBase, scopeDepth);
MethodInfo* func = pool->getMethodInfo(function_id);
int extraScopes = state->scopeDepth;
// prepare scopechain args for call
LIns* ap = storeAtomArgs(extraScopes, ms->scope_base());
LIns* i3 = callIns(FUNCTIONID(newfunction), 3,
env_param, InsConstPtr(func), ap);
liveAlloc(ap);
AvmAssert(!result->isMachineType());
localSet(index, i3, result);
break;
}
case OP_call:
{
// stack in: function obj arg1..N
int argc = int(op1);
int funcDisp = sp - argc - 1;
int dest = funcDisp;
// convert args to Atom[] for the call
LIns *result_ins, *ap;
switch (bt(state->value(funcDisp).traits)) {
case BUILTIN_function:
case BUILTIN_methodClosure: {
// call through pointer in object
// ptr = f->m_call_ptr
// if (!ptr) goto nfe_label
// sp[-argc-1] = (*ptr)(f, argc, obj, ...)
LIns* func = localGetp(funcDisp);
branchToLabel(LIR_jt, eqp0(func), call_error_label);
ap = storeAtomArgs(loadAtomRep(funcDisp+1), argc, funcDisp+2);
LIns* call_ptr = ldp(func, offsetof(FunctionObject, m_call_ptr),
ACCSET_OTHER);
result_ins = callIns(FUNCTIONID(call_ptr), 4, call_ptr, func, InsConst(argc, /*tainted*/true), ap);
break;
}
default:
// generic call
// sp[-argc-1] = op_call(env, sp[-argc], argc, ...)
LIns* func = loadAtomRep(funcDisp);
ap = storeAtomArgs(loadAtomRep(funcDisp+1), argc, funcDisp+2);
result_ins = callIns(FUNCTIONID(op_call_atom), 4, env_param, func, InsConst(argc, /*tainted*/true), ap);
break;
}
liveAlloc(ap);
localSet(dest, atomToNativeRep(result, result_ins), result);
break;
}
case OP_callproperty:
case OP_callproplex:
case OP_callpropvoid:
{
// stack in: obj [ns [name]] arg1..N
// stack out: result
// obj = sp[-argc]
//tempAtom = callproperty(env, name, toVTable(obj), argc, ...);
// *(sp -= argc) = tempAtom;
int argc = int(op2);
int argv = sp-argc+1;
int baseDisp = sp-argc;
const Multiname* name = pool->precomputedMultiname((int)op1);
LIns* multi = initMultiname(name, baseDisp);
AvmAssert(state->value(baseDisp).notNull);
// convert args to Atom[] for the call
LIns* base = loadAtomRep(baseDisp);
LIns* receiver = opcode == OP_callproplex ? InsConstAtom(nullObjectAtom) : base;
LIns* ap = storeAtomArgs(receiver, argc, argv);
Traits* baseTraits = state->value(baseDisp).traits;
Binding b = toplevel->getBinding(baseTraits, name);
LIns* out;
if (AvmCore::isSlotBinding(b)) {
// can early bind call to closure in slot
Traits* slotType = Traits::readBinding(baseTraits, b);
// todo if funcValue is already a ScriptObject then don't box it, use a different helper.
LIns* funcValue = loadFromSlot(baseDisp, AvmCore::bindingToSlotId(b), slotType);
LIns* funcAtom = nativeToAtom(funcValue, slotType);
out = callIns(FUNCTIONID(op_call_atom), 4, env_param, funcAtom, InsConst(argc, /*tainted*/true), ap);
}
else if (!name->isRuntime()) {
// use inline cache for late bound call
// cache contains: [handler, vtable, [data], Multiname*]
// and we call (*cache->handler)(cache, obj, argc, args*, MethodEnv*)
CallCache* cache = call_cache_builder.allocateCacheSlot(name);
LIns* cacheAddr = InsConstPtr(cache);
LIns* handler = loadIns(LIR_ldp, offsetof(CallCache, call_handler), cacheAddr, ACCSET_OTHER);
out = callIns(FUNCTIONID(call_cache_handler), 6,
handler, cacheAddr, base, InsConst(argc, /*tainted*/true), ap, env_param);
}
else {
// generic late bound call to anything
out = callIns(FUNCTIONID(callprop_late), 5, env_param, base, multi, InsConst(argc, /*tainted*/true), ap);
liveAlloc(multi);
}
liveAlloc(ap);
localSet(baseDisp, atomToNativeRep(result, out), result);
break;
}
case OP_constructprop:
{
// stack in: obj [ns [name]] arg1..N
// stack out: result
int argc = int(op2);
// obj = sp[-argc]
//tempAtom = callproperty(env, name, toVTable(obj), argc, ...);
// *(sp -= argc) = tempAtom;
int argv = sp-argc+1;
int objDisp = sp-argc;
LIns* multi = initMultiname((Multiname*)op1, objDisp);
AvmAssert(state->value(objDisp).notNull);
// convert args to Atom[] for the call
LIns* ap = storeAtomArgs(loadAtomRep(objDisp), argc, argv);
LIns* i3 = callIns(FUNCTIONID(construct_late), 4,
env_param, multi, InsConst(argc, /*tainted*/true), ap);
liveAlloc(multi);
liveAlloc(ap);
localSet(objDisp, atomToNativeRep(result, i3), result);
break;
}
case OP_callsuper:
case OP_callsupervoid:
{
// stack in: obj [ns [name]] arg1..N
// stack out: result
// null check must have already happened.
// tempAtom = callsuper(multiname, obj, sp-argc+1, argc, vtable->base);
int argc = int(op2);
int argv = sp - argc + 1;
int objDisp = sp - argc;
LIns* multi = initMultiname((Multiname*)op1, objDisp);
AvmAssert(state->value(objDisp).notNull);
// convert args to Atom[] for the call
LIns* obj = loadAtomRep(objDisp);
LIns* ap = storeAtomArgs(obj, argc, argv);
LIns* i3 = callIns(FUNCTIONID(callsuper), 4,
env_param, multi, InsConst(argc, /*tainted*/true), ap);
liveAlloc(multi);
liveAlloc(ap);
localSet(objDisp, atomToNativeRep(result, i3), result);
break;
}
case OP_applytype:
{
// stack in: method arg1..N
// sp[-argc] = applytype(env, sp[-argc], argc, null, arg1..N)
int argc = int(op1);
int funcDisp = sp - argc;
int dest = funcDisp;
int arg0 = sp - argc + 1;
LIns* func = loadAtomRep(funcDisp);
// convert args to Atom[] for the call
LIns* ap = storeAtomArgs(argc, arg0);
LIns* i3 = callIns(FUNCTIONID(op_applytype), 4,
env_param, func, InsConst(argc, /*tainted*/true), ap);
liveAlloc(ap);
localSet(dest, atomToNativeRep(result, i3), result);
break;
}
case OP_newobject:
{
// result = env->op_newobject(sp, argc)
int argc = int(op1);
int dest = sp - (2*argc-1);
int arg0 = dest;
// convert args to Atom for the call[]
LIns* ap = storeAtomArgs(2*argc, arg0);
LIns* i3 = callIns(FUNCTIONID(op_newobject), 3,
env_param, lea(sizeof(Atom)*(2*argc-1), ap), InsConst(argc, /*tainted*/true));
liveAlloc(ap);
localSet(dest, ptrToNativeRep(result, i3), result);
break;
}
case OP_newactivation:
{
// result = env->newActivation()
LIns* activation = callIns(FUNCTIONID(newActivation), 1, env_param);
localSet(sp+1, ptrToNativeRep(result, activation), result);
break;
}
case OP_newcatch:
{
// result = core->newObject(env->activation, NULL);
int dest = sp+1;
LIns* activation = callIns(FUNCTIONID(newcatch), 2,
env_param, InsConstPtr(result));
localSet(dest, ptrToNativeRep(result, activation), result);
break;
}
case OP_newarray:
{
// sp[-argc+1] = env->toplevel()->arrayClass->newarray(sp-argc+1, argc)
int argc = int(op1);
int arg0 = sp - 1*argc+1;
// convert array elements to Atom[]
LIns* ap = storeAtomArgs(argc, arg0);
LIns* i3 = callIns(FUNCTIONID(newarray), 3, env_param, InsConst(argc, /*tainted*/true), ap);
liveAlloc(ap);
AvmAssert(!result->isMachineType());
localSet(arg0, i3, result);
break;
}
case OP_newclass:
{
// sp[0] = core->newclass(env, ctraits, scopeBase, scopeDepth, base)
Traits* ctraits = (Traits*) op1;
int localindex = int(op2);
int extraScopes = state->scopeDepth;
LIns* outer = loadEnvScope();
LIns* base = localGetp(localindex);
// prepare scopechain args for call
LIns* ap = storeAtomArgs(extraScopes, ms->scope_base());
LIns* i3 = callIns(FUNCTIONID(newclass), 5,
env_param, InsConstPtr(ctraits), base, outer, ap);
liveAlloc(ap);
AvmAssert(!result->isMachineType());
localSet(localindex, i3, result);
break;
}
case OP_getdescendants:
{
// stack in: obj [ns [name]]
// stack out: value
//sp[0] = core->getdescendants(sp[0], name);
int objDisp = sp;
Multiname* multiname = (Multiname*) op1;
LIns* multi = initMultiname(multiname, objDisp);
LIns* obj = loadAtomRep(objDisp);
AvmAssert(state->value(objDisp).notNull);
LIns* out = callIns(FUNCTIONID(getdescendants), 3,
env_param, obj, multi);
liveAlloc(multi);
localSet(objDisp, atomToNativeRep(result, out), result);
break;
}
case OP_checkfilter: {
int32_t index = (int32_t) op1;
callIns(FUNCTIONID(checkfilter), 2, env_param, loadAtomRep(index));
break;
}
case OP_findpropstrict:
case OP_findproperty:
{
// stack in: [ns [name]]
// stack out: obj
// sp[1] = env->findproperty(scopeBase, scopedepth, name, strict)
int dest = sp;
LIns* multi = initMultiname((Multiname*)op1, dest);
dest++;
int extraScopes = state->scopeDepth;
// prepare scopechain args for call
LIns* ap = storeAtomArgs(extraScopes, ms->scope_base());
LIns* outer = loadEnvScope();
LIns* withBase;
if (state->withBase == -1)
{
withBase = InsConstPtr(0);
}
else
{
withBase = lea(state->withBase*sizeof(Atom), ap);
}
// return env->findproperty(outer, argv, extraScopes, name, strict);
LIns* i3 = callIns(FUNCTIONID(findproperty), 7,
env_param, outer, ap, InsConst(extraScopes), multi,
InsConst((int32_t)(opcode == OP_findpropstrict)),
withBase);
liveAlloc(multi);
liveAlloc(ap);
localSet(dest, atomToNativeRep(result, i3), result);
break;
}
case OP_getproperty:
{
// stack in: obj [ns] [name]
// stack out: value
// obj=sp[0]
//sp[0] = env->getproperty(obj, multiname);
const Multiname* multiname = pool->precomputedMultiname((int)op1);
bool attr = multiname->isAttr();
Traits* indexType = state->value(sp).traits;
LIns* index = NULL;
bool maybeIntegerIndex = !attr && multiname->isRtname() && multiname->containsAnyPublicNamespace();
if (maybeIntegerIndex)
index = optimizeIndexArgumentType(sp, &indexType);
if (maybeIntegerIndex && indexType == INT_TYPE) {
LIns *value = emitGetIndexedProperty(sp-1, index, result, VI_INT);
localSet(sp-1, value, result);
}
else if (maybeIntegerIndex && indexType == UINT_TYPE) {
LIns *value = emitGetIndexedProperty(sp-1, index, result, VI_UINT);
localSet(sp-1, value, result);
}
else if (maybeIntegerIndex && indexType == NUMBER_TYPE) {
bool bGeneratedFastPath = false;
#ifdef VMCFG_FASTPATH_ADD_INLINE
if (inlineFastpath) {
// TODO: Since a helper must be called anyhow, I conjecture that fused add-and-index helpers
// may be more efficient than the inlining done here. On the other hand, it is plausible that
// we would go the other way and completely inline the getNativeXXXProperty cases, in which
// case we'd want to retain this for the sake of more thorough inlining.
LOpcode op = index->opcode();
if (op == LIR_addd || op == LIR_subd) {
CodegenLabel slow_path("slow");
CodegenLabel done_path("done");
LIns* tempResult = insAllocForTraits(result);
suspendCSE();
LIns* a = index->oprnd1();
LIns* b = index->oprnd2();
// Our addjovi only works with signed integers so don't use isPromote.
a = (a->opcode() == LIR_i2d) ? a->oprnd1() : imm2Int(a);
b = (b->opcode() == LIR_i2d) ? b->oprnd1() : imm2Int(b);
if (a && b) {
// Inline fast path for index generated from integer add
// if (a+b) does not overflow && ((a+b) >= 0)
// call getUintProperty(obj, a + b)
// else
// call getprop_index_add (env, obj, multiname, a, b)
// (for subtraction, we emit (a-b) and call getprop_index_subtract)
LIns* addOp = branchJovToLabel((op == LIR_addd) ? LIR_addjovi : LIR_subjovi, a, b, slow_path);
branchToLabel(LIR_jt, binaryIns(LIR_lti, addOp, InsConst(0)), slow_path);
LIns* value0 = emitGetIndexedProperty(sp-1, addOp, result, VI_UINT);
stForTraits(result, value0, tempResult, 0, ACCSET_STORE_ANY);
branchToLabel(LIR_j, NULL, done_path);
emitLabel(slow_path);
LIns* value1 = emitGetIndexedProperty(sp-1, index, result, VI_DOUBLE);
stForTraits(result, value1, tempResult, 0, ACCSET_STORE_ANY);
emitLabel(done_path);
localSet(sp-1, ldForTraits(result, tempResult, 0, ACCSET_LOAD_ANY), result);
bGeneratedFastPath = true;
}
resumeCSE();
}
}
#endif // VMCFG_FASTPATH_ADD_INLINE
if (!bGeneratedFastPath) {
LIns *value = emitGetIndexedProperty(sp-1, index, result, VI_DOUBLE);
localSet(sp-1, value, result);
}
}
else if (maybeIntegerIndex && indexType != STRING_TYPE) {
LIns* multi = InsConstPtr(multiname); // inline ptr to precomputed name
LIns* index = loadAtomRep(sp);
AvmAssert(state->value(sp-1).notNull);
LIns* obj = loadAtomRep(sp-1);
LIns* value = callIns(FUNCTIONID(getprop_index), 4,
env_param, obj, multi, index);
localSet(sp-1, atomToNativeRep(result, value), result);
}
else {
int objDisp = sp;
LIns* multi = initMultiname(multiname, objDisp);
AvmAssert(state->value(objDisp).notNull);
LIns* value;
LIns* obj = loadAtomRep(objDisp);
if (multiname->isRuntime()) {
//return getprop_late(obj, name);
value = callIns(FUNCTIONID(getprop_late), 3, env_param, obj, multi);
liveAlloc(multi);
} else {
// static name, use property cache
GetCache* cache = get_cache_builder.allocateCacheSlot(multiname);
LIns* cacheAddr = InsConstPtr(cache);
LIns* handler = loadIns(LIR_ldp, offsetof(GetCache, get_handler), cacheAddr, ACCSET_OTHER);
value = callIns(FUNCTIONID(get_cache_handler), 4, handler, cacheAddr, env_param, obj);
}
localSet(objDisp, atomToNativeRep(result, value), result);
}
break;
}
case OP_initproperty:
case OP_setproperty:
{
// stack in: obj [ns] [name] value
// stack out:
// obj = sp[-1]
//env->setproperty(obj, multiname, sp[0], toVTable(obj));
const Multiname* multiname = (const Multiname*)op1;
bool attr = multiname->isAttr();
Traits* indexType = state->value(sp-1).traits;
LIns* index = NULL;
bool maybeIntegerIndex = !attr && multiname->isRtname() && multiname->containsAnyPublicNamespace();
if (maybeIntegerIndex)
index = optimizeIndexArgumentType(sp-1, &indexType);
if (maybeIntegerIndex && indexType == INT_TYPE) {
emitSetIndexedProperty(sp-2, sp, index, VI_INT);
}
else if (maybeIntegerIndex && indexType == UINT_TYPE) {
emitSetIndexedProperty(sp-2, sp, index, VI_UINT);
}
else if (maybeIntegerIndex && indexType == NUMBER_TYPE) {
bool bGeneratedFastPath = false;
#ifdef VMCFG_FASTPATH_ADD_INLINE
if (inlineFastpath) {
LOpcode op = index->opcode();
if (op == LIR_addd || op == LIR_subd) {
CodegenLabel slow_path("slow");
CodegenLabel done_path("done");
suspendCSE();
LIns *a = index->oprnd1();
LIns *b = index->oprnd2();
// Our addjovi only works with signed integers.
a = (a->opcode() == LIR_i2d) ? a->oprnd1() : imm2Int(a);
b = (b->opcode() == LIR_i2d) ? b->oprnd1() : imm2Int(b);
if (a && b) {
// Inline fast path for index generated from integer add
// if (a + b) does not overflow && ((a+b) >= 0)
// call setProperty(obj, value, a + b)
// else
// call set/initprop_index_add(env, obj, name, value, a, b)
// (for subtraction, we emit (a-b) and call set/initprop_index_subtract)
LIns* addOp = branchJovToLabel((op == LIR_addd) ? LIR_addjovi : LIR_subjovi, a, b, slow_path);
branchToLabel(LIR_jt, binaryIns(LIR_lti, addOp, InsConst(0)), slow_path);
emitSetIndexedProperty(sp-2, sp, addOp, VI_UINT);
branchToLabel(LIR_j, NULL, done_path);
emitLabel(slow_path);
emitSetIndexedProperty(sp-2, sp, index, VI_DOUBLE);
emitLabel(done_path);
bGeneratedFastPath = true;
}
resumeCSE();
}
}
#endif // VMCFG_FASTPATH_ADD_INLINE
if (!bGeneratedFastPath) {
emitSetIndexedProperty(sp-2, sp, index, VI_DOUBLE);
}
}
else if (maybeIntegerIndex) {
LIns* name = InsConstPtr(multiname); // precomputed multiname
LIns* value = loadAtomRep(sp);
LIns* index = loadAtomRep(sp-1);
AvmAssert(state->value(sp-2).notNull);
LIns* obj = loadAtomRep(sp-2);
const CallInfo* func = (opcode == OP_setproperty) ? FUNCTIONID(setprop_index) : FUNCTIONID(initprop_index);
callIns(func, 5, env_param, obj, name, value, index);
}
else {
int objDisp = sp-1;
LIns* value = loadAtomRep(sp);
LIns* multi = initMultiname(multiname, objDisp);
AvmAssert(state->value(objDisp).notNull);
LIns* obj = loadAtomRep(objDisp);
if (opcode == OP_setproperty) {
if (!multiname->isRuntime()) {
// use inline cache for dynamic setproperty access
SetCache* cache = set_cache_builder.allocateCacheSlot(multiname);
LIns* cacheAddr = InsConstPtr(cache);
LIns* handler = loadIns(LIR_ldp, offsetof(SetCache, set_handler), cacheAddr, ACCSET_OTHER);
callIns(FUNCTIONID(set_cache_handler), 5, handler, cacheAddr, obj, value, env_param);
} else {
// last resort slow path for OP_setproperty
callIns(FUNCTIONID(setprop_late), 4, env_param, obj, multi, value);
liveAlloc(multi);
}
}
else {
// initproplate is rare in jit code because we typically interpret static
// initializers, and constructor initializers tend to early-bind successfully.
callIns(FUNCTIONID(initprop_late), 4, env_param, obj, multi, value);
liveAlloc(multi);
}
}
break;
}
case OP_deleteproperty:
{
// stack in: obj [ns] [name]
// stack out: Boolean
//sp[0] = delproperty(sp[0], multiname);
int objDisp = sp;
Multiname *multiname = (Multiname*)op1;
if(!multiname->isRtname()) {
LIns* multi = initMultiname(multiname, objDisp, true);
LIns* obj = loadAtomRep(objDisp);
LIns* i3 = callIns(FUNCTIONID(delproperty), 3,
env_param, obj, multi);
liveAlloc(multi);
localSet(objDisp, atomToNativeRep(result, i3), result);
} else {
LIns* _tempname = copyMultiname(multiname);
LIns* index = loadAtomRep(objDisp--);
if( !multiname->isRtns() )
{
// copy the compile-time namespace to the temp multiname
LIns* mSpace = InsConstPtr(multiname->ns);
stp(mSpace, _tempname, offsetof(Multiname, ns), ACCSET_OTHER);
}
else
{
// intern the runtime namespace and copy to the temp multiname
LIns* nsAtom = loadAtomRep(objDisp--);
LIns* internNs = callIns(FUNCTIONID(internRtns), 2,
env_param, nsAtom);
stp(internNs, _tempname, offsetof(Multiname,ns), ACCSET_OTHER);
}
liveAlloc(_tempname);
AvmAssert(state->value(objDisp).notNull);
LIns* obj = loadAtomRep(objDisp);
LIns* value = callIns(FUNCTIONID(delpropertyHelper), 4,
env_param, obj, _tempname, index);
localSet(objDisp, atomToNativeRep(result, value), result);
}
break;
}
case OP_esc_xelem: // ToXMLString will call EscapeElementValue
{
//sp[0] = core->ToXMLString(sp[0]);
int32_t index = (int32_t) op1;
LIns* value = loadAtomRep(index);
LIns* i3 = callIns(FUNCTIONID(ToXMLString), 2,
coreAddr, value);
AvmAssert(result == STRING_TYPE);
localSet(index, i3, result);
break;
}
case OP_esc_xattr:
{
//sp[0] = core->EscapeAttributeValue(sp[0]);
int32_t index = (int32_t) op1;
LIns* value = loadAtomRep(index);
LIns* i3 = callIns(FUNCTIONID(EscapeAttributeValue), 2,
coreAddr, value);
AvmAssert(result == STRING_TYPE);
localSet(index, i3, result);
break;
}
case OP_astype:
{
// sp[0] = AvmCore::astype(sp[0], traits)
Traits *type = (Traits*) op1;
int32_t index = (int32_t) op2;
LIns* obj = loadAtomRep(index);
LIns* i1 = callIns(FUNCTIONID(astype), 2, obj, InsConstPtr(type));
i1 = atomToNativeRep(result, i1);
localSet(index, i1, result);
break;
}
case OP_astypelate:
{
//sp[-1] = astype_late(env, sp[-1], sp[0]);
LIns* type = loadAtomRep(sp);
LIns* obj = loadAtomRep(sp-1);
LIns* i3 = callIns(FUNCTIONID(astype_late), 3, env_param, obj, type);
i3 = atomToNativeRep(result, i3);
localSet(sp-1, i3, result);
break;
}
case OP_strictequals:
{
AvmAssert(result == BOOLEAN_TYPE);
localSet(sp-1, cmpEq(FUNCTIONID(stricteq), sp-1, sp), result);
break;
}
case OP_equals:
{
AvmAssert(result == BOOLEAN_TYPE);
localSet(sp-1, cmpEq(FUNCTIONID(equals), sp-1, sp), result);
break;
}
case OP_lessthan:
{
AvmAssert(result == BOOLEAN_TYPE);
localSet(sp-1, cmpLt(sp-1, sp), result);
break;
}
case OP_lessequals:
{
AvmAssert(result == BOOLEAN_TYPE);
localSet(sp-1, cmpLe(sp-1, sp), result);
break;
}
case OP_greaterthan:
{
AvmAssert(result == BOOLEAN_TYPE);
localSet(sp-1, cmpLt(sp, sp-1), result);
break;
}
case OP_greaterequals:
{
AvmAssert(result == BOOLEAN_TYPE);
localSet(sp-1, cmpLe(sp, sp-1), result);
break;
}
case OP_instanceof:
{
LIns* lhs = loadAtomRep(sp-1);
LIns* rhs = loadAtomRep(sp);
LIns* out = callIns(FUNCTIONID(instanceof), 3, env_param, lhs, rhs);
out = atomToNativeRep(result, out);
localSet(sp-1, out, result);
break;
}
case OP_in:
{
// sp[-1] = env->in_operator(sp[-1], sp[0])
int lhsDisp = sp-1;
LIns *lhs;
LIns *rhs = loadAtomRep(sp);
LIns *out;
Traits *lhsType = state->value(lhsDisp).traits;
switch (bt(lhsType)) {
case BUILTIN_uint:
lhs = localGet(lhsDisp);
out = callIns(FUNCTIONID(haspropertylate_u), 3, env_param, rhs, lhs);
break;
case BUILTIN_int:
lhs = localGet(lhsDisp);
out = callIns(FUNCTIONID(haspropertylate_i), 3, env_param, rhs, lhs);
break;
default:
lhs = loadAtomRep(lhsDisp);
out = callIns(FUNCTIONID(op_in), 3, env_param, lhs, rhs);
out = atomToNativeRep(result, out);
break;
}
localSet(sp-1, out, result);
break;
}
case OP_dxns:
{
LIns* uri = InsConstPtr((String*)op1); // namespace uri from string pool
callIns(FUNCTIONID(setDxns), 3, coreAddr, methodFrame, uri);
break;
}
case OP_dxnslate:
{
int32_t index = (int32_t) op1;
LIns* atom = loadAtomRep(index);
callIns(FUNCTIONID(setDxnsLate), 3, coreAddr, methodFrame, atom);
break;
}
default:
{
AvmAssert(false); // unsupported
}
}
} // emit()
void CodegenLIR::emitIf(AbcOpcode opcode, const uint8_t* target, int a, int b)
{
#ifdef DEBUGGER
Sampler* s = core->get_sampler();
if (s && s->sampling() && target < state->abc_pc)
{
emitSampleCheck();
}
#endif
//
// compile instructions that cannot throw exceptions before we add exception handling logic
//
// op1 = abc opcode target
// op2 = what local var contains condition
LIns* cond;
LOpcode br;
switch (opcode)
{
case OP_iftrue:
NanoAssert(state->value(a).traits == BOOLEAN_TYPE);
br = LIR_jf;
cond = eqi0(localGet(a));
break;
case OP_iffalse:
NanoAssert(state->value(a).traits == BOOLEAN_TYPE);
br = LIR_jt;
cond = eqi0(localGet(a));
break;
case OP_iflt:
br = LIR_jt;
cond = cmpLt(a, b);
break;
case OP_ifnlt:
br = LIR_jf;
cond = cmpLt(a, b);
break;
case OP_ifle:
br = LIR_jt;
cond = cmpLe(a, b);
break;
case OP_ifnle:
br = LIR_jf;
cond = cmpLe(a, b);
break;
case OP_ifgt: // a>b === b<a
br = LIR_jt;
cond = cmpLt(b, a);
break;
case OP_ifngt: // !(a>b) === !(b<a)
br = LIR_jf;
cond = cmpLt(b, a);
break;
case OP_ifge: // a>=b === b<=a
br = LIR_jt;
cond = cmpLe(b, a);
break;
case OP_ifnge: // !(a>=b) === !(a<=b)
br = LIR_jf;
cond = cmpLe(b, a);
break;
case OP_ifeq:
br = LIR_jt;
cond = cmpEq(FUNCTIONID(equals), a, b);
break;
case OP_ifne:
br = LIR_jf;
cond = cmpEq(FUNCTIONID(equals), a, b);
break;
case OP_ifstricteq:
br = LIR_jt;
cond = cmpEq(FUNCTIONID(stricteq), a, b);
break;
case OP_ifstrictne:
br = LIR_jf;
cond = cmpEq(FUNCTIONID(stricteq), a, b);
break;
default:
AvmAssert(false);
return;
}
if (cond->isImmI()) {
if ((br == LIR_jt && cond->immI()) || (br == LIR_jf && !cond->immI())) {
// taken
br = LIR_j;
cond = 0;
}
else {
// not taken = no-op
return;
}
}
branchToAbcPos(br, cond, target);
} // emitIf()
// Try to optimize comparison of a function call yielding NUMBER_TYPE with another expression
// of INT_TYPE. We may be able to simplify the function and/or the comparison based on the
// type or value, if constant, of the other argument. The function call is normally presumed
// to be the left-hand argument. The swap parameter, if true, reverses this convention.
static const Specialization intCmpWithNumber[] = {
{ FUNCTIONID(String_charCodeAtDI), FUNCTIONID(String_charCodeAtIU) },
{ FUNCTIONID(String_charCodeAtDU), FUNCTIONID(String_charCodeAtIU) },
{ FUNCTIONID(String_charCodeAtDD), FUNCTIONID(String_charCodeAtID) },
{ 0, 0 }
};
LIns *CodegenLIR::optimizeIntCmpWithNumberCall(int callIndex, int otherIndex, LOpcode icmp, bool swap)
{
LIns* numSide = localGetd(callIndex);
const CallInfo *ci = numSide->callInfo();
// Try to optimize charCodeAt to return an integer if possible. Because it can return NaN for
// out of bounds access, we need to limit our support to constant integer values that generate
// the same results for both NaN (floating point result) and zero (NaN cast to integer result).
// These are the six possibilities:
// String.CharCodeAt == int - any constant integer but zero
// String.CharCodeAt < int - zero or any negative integer constant
// String.CharCodeAt <= int - any negative integer constant
// int == String.CharCodeAt - any constant integer but zero
// int < String.CharCodeAt - zero or any positive integer constant
// int <= String.CharCodeAt - any positive integer constant
if (ci == FUNCTIONID(String_charCodeAtDI) || ci == FUNCTIONID(String_charCodeAtDU) || ci == FUNCTIONID(String_charCodeAtDD)) {
AvmAssert(numSide->opcode() == LIR_calld);
LIns* intSide = localGet(otherIndex);
if (!intSide->isImmI())
return NULL;
int32_t intVal = intSide->immI();
if ((icmp == LIR_eqi && intVal != 0) ||
(icmp == LIR_lti && (swap ? intVal >= 0 : intVal <= 0)) ||
(icmp == LIR_lei && (swap ? intVal > 0 : intVal < 0))) {
numSide = specializeIntCall(numSide, intCmpWithNumber);
AvmAssert(numSide != NULL);
if (swap) {
return binaryIns(icmp, intSide, numSide);
} else {
return binaryIns(icmp, numSide, intSide);
}
}
}
return NULL;
}
static const Specialization stringCmpWithString[] = {
{ FUNCTIONID(String_charAtI), FUNCTIONID(String_charCodeAtII) },
{ FUNCTIONID(String_charAtU), FUNCTIONID(String_charCodeAtIU) },
{ FUNCTIONID(String_charAtD), FUNCTIONID(String_charCodeAtID) },
{ 0, 0 }
};
LIns *CodegenLIR::optimizeStringCmpWithStringCall(int callIndex, int otherIndex, LOpcode icmp, bool swap)
{
LIns* callSide = localGetp(callIndex);
const CallInfo *ci = callSide->callInfo();
if (ci == FUNCTIONID(String_charAtI) || ci == FUNCTIONID(String_charAtU) || ci == FUNCTIONID(String_charAtD)) {
LIns* strSide = localGetp(otherIndex);
if (!strSide->isImmP())
return NULL;
String *s = (String *) strSide->immP();
// If we have a single character constant string that is not the null
// character, we can use charCodeAt which is about 2x faster.
if (s->length() == 1 && s->charAt(0) != 0) {
int32_t firstChar = s->charAt(0);
// NOTE: Reconsider whether blinding is really needed here.
strSide = InsConst(firstChar, /*tainted*/true);
callSide = specializeIntCall(callSide, stringCmpWithString);
AvmAssert(callSide != NULL);
if (swap) {
return binaryIns(icmp, strSide, callSide);
} else {
return binaryIns(icmp, callSide, strSide);
}
}
}
return NULL;
}
// Faster compares for int, uint, double, boolean
LIns* CodegenLIR::cmpOptimization( int lhsi, int rhsi, LOpcode icmp, LOpcode ucmp, LOpcode fcmp, bool strictOperation /*=false*/ )
{
Traits* lht = state->value(lhsi).traits;
Traits* rht = state->value(rhsi).traits;
if (lht == rht && (lht == INT_TYPE || lht == BOOLEAN_TYPE))
{
LIns* lhs = localGet(lhsi);
LIns* rhs = localGet(rhsi);
return binaryIns(icmp, lhs, rhs);
}
else if (lht == rht && lht == UINT_TYPE)
{
LIns* lhs = localGet(lhsi);
LIns* rhs = localGet(rhsi);
return binaryIns(ucmp, lhs, rhs);
}
#ifdef VMCFG_FLOAT
else if (lht == rht && lht == FLOAT_TYPE)
{
LIns* lhs = localGetf(lhsi);
LIns* rhs = localGetf(rhsi);
return binaryIns(getCmpFOpcode(fcmp), lhs, rhs);
}
else if (lht == FLOAT4_TYPE || rht == FLOAT4_TYPE)
{
if (fcmp == LIR_eqd) {
// TODO: This is not the best way to optimize the mixed-mode cases
// if SIMD instructions are not available on the target platform.
if (lht == rht) {
// float4 ==/=== float4
LIns* lhs = localGetf4(lhsi);
LIns* rhs = localGetf4(rhsi);
return binaryIns(LIR_eqf4, lhs, rhs);
} else if (lht == FLOAT_TYPE) {
// float ==/=== float4
if (strictOperation)
return InsConst(0);
LIns* lhs = Ins(LIR_f2f4, localGetf(lhsi));
LIns* rhs = localGetf4(rhsi);
return binaryIns(LIR_eqf4, lhs, rhs);
} else if (rht == FLOAT_TYPE) {
// float4 ==/=== float
if (strictOperation)
return InsConst(0);
LIns* lhs = localGetf4(lhsi);
LIns* rhs = Ins(LIR_f2f4, localGetf(rhsi));
return binaryIns(LIR_eqf4, lhs, rhs);
} else
// Punt to out-of-line handler.
return NULL;
} else
// Comparison with NaN, always false.
return InsConst(0);
}
#endif // VMCFG_FLOAT
else if (lht && lht->isNumeric() && rht && rht->isNumeric())
{
// Comparing the result of a call returning a Number to another int value.
if (lht == NUMBER_TYPE && rht == INT_TYPE && localGetd(lhsi)->opcode() == LIR_calld) {
LIns* result = optimizeIntCmpWithNumberCall(lhsi, rhsi, icmp, false);
if (result)
return result;
}
if (rht == NUMBER_TYPE && lht == INT_TYPE && localGetd(rhsi)->opcode() == LIR_calld) {
LIns* result = optimizeIntCmpWithNumberCall(rhsi, lhsi, icmp, true);
if (result)
return result;
}
// Note: no optimizations needed above for "float" or "numeric".
// Those optimize String.charCodeAt(), which returns a Number by language definition.
// If we're comparing a uint to an int and the int is a non-negative
// integer constant, don't promote to doubles for the compare
if ((lht == UINT_TYPE) && (rht == INT_TYPE))
{
LIns* lhs = localGet(lhsi);
LIns* rhs = localGet(rhsi);
#ifdef AVMPLUS_64BIT
// 32-bit signed and unsigned values fit in 64-bit registers
// so we can promote and simply do a signed 64bit compare
LOpcode qcmp = cmpOpcodeI2Q(icmp);
NanoAssert((icmp == LIR_eqi && qcmp == LIR_eqq) ||
(icmp == LIR_lti && qcmp == LIR_ltq) ||
(icmp == LIR_lei && qcmp == LIR_leq));
return binaryIns(qcmp, ui2p(lhs), i2p(rhs));
#else
if (rhs->isImmI() && rhs->immI() >= 0)
return binaryIns(ucmp, lhs, rhs);
#endif
}
else if ((lht == INT_TYPE) && (rht == UINT_TYPE))
{
LIns* lhs = localGet(lhsi);
LIns* rhs = localGet(rhsi);
#ifdef AVMPLUS_64BIT
// 32-bit signed and unsigned values fit in 64-bit registers
// so we can promote and simply do a signed 64bit compare
LOpcode qcmp = cmpOpcodeI2Q(icmp);
NanoAssert((icmp == LIR_eqi && qcmp == LIR_eqq) ||
(icmp == LIR_lti && qcmp == LIR_ltq) ||
(icmp == LIR_lei && qcmp == LIR_leq));
return binaryIns(qcmp, i2p(lhs), ui2p(rhs));
#else
if (lhs->isImmI() && lhs->immI() >= 0)
return binaryIns(ucmp, lhs, rhs);
#endif
}
if(!strictOperation && (lht->isNumberType() || rht->isNumberType()) ){
// NOTE: This assumes that comparisons between two floats works just as
// well performed on doubles. For float vs. number, we can't do strict
// equality tests this way, since the types are assumed to be different.
LIns* lhs = promoteNumberIns(lht, lhsi);
LIns* rhs = promoteNumberIns(rht, rhsi);
return binaryIns(fcmp, lhs, rhs);
}
}
if (lht == STRING_TYPE && rht == STRING_TYPE) {
if (localGetp(lhsi)->opcode() == LIR_calli) {
LIns* result = optimizeStringCmpWithStringCall(lhsi, rhsi, icmp, false);
if (result)
return result;
}
else if (localGetp(rhsi)->opcode() == LIR_calli) {
LIns* result = optimizeStringCmpWithStringCall(rhsi, lhsi, icmp, true);
if (result)
return result;
}
}
return NULL;
}
// set cc's for < operator
LIns* CodegenLIR::cmpLt(int lhsi, int rhsi)
{
LIns *result = cmpOptimization(lhsi, rhsi, LIR_lti, LIR_ltui, LIR_ltd);
if (result)
return result;
// TODO numeric comparisons can be optimized by inlining the fastpath (int/int, number/number)
AvmAssert(trueAtom == 13);
AvmAssert(falseAtom == 5);
AvmAssert(undefinedAtom == 4);
LIns* lhs = loadAtomRep(lhsi);
LIns* rhs = loadAtomRep(rhsi);
LIns* atom = callIns(FUNCTIONID(compare), 2, lhs, rhs);
// caller will use jt for (a<b) and jf for !(a<b)
// compare ^8 <8
// true 1101 0101 y
// false 0101 1101 n
// undefined 0100 1100 n
LIns* c = InsConst(8);
return binaryIns(LIR_lti, binaryIns(LIR_xori, p2i(atom), c), c);
}
LIns* CodegenLIR::cmpLe(int lhsi, int rhsi)
{
LIns *result = cmpOptimization(lhsi, rhsi, LIR_lei, LIR_leui, LIR_led);
if (result)
return result;
// TODO numeric comparisons can be optimized by inlining the fastpath (int/int, number/number)
LIns* lhs = loadAtomRep(lhsi);
LIns* rhs = loadAtomRep(rhsi);
LIns* atom = callIns(FUNCTIONID(compare), 2, rhs, lhs);
// assume caller will use jt for (a<=b) and jf for !(a<=b)
// compare ^1 <=4
// true 1101 1100 n
// false 0101 0100 y
// undefined 0100 0101 n
LIns* c2 = InsConst(1);
LIns* c4 = InsConst(4);
return binaryIns(LIR_lei, binaryIns(LIR_xori, p2i(atom), c2), c4);
}
LIns* CodegenLIR::cmpEq(const CallInfo *fid, int lhsi, int rhsi)
{
bool isStrict = fid == FUNCTIONID(stricteq);
LIns *result = cmpOptimization(lhsi, rhsi, LIR_eqi, LIR_eqi, LIR_eqd, isStrict);
if (result )
return result;
Traits* lht = state->value(lhsi).traits;
Traits* rht = state->value(rhsi).traits;
// There are various conditions we can check for that simplify our equality check down
// to a ptr comparison:
// - null and a type that does not require complex equality checks
// - null and a string - no string comparison is performed, just ptrs
// - both types do not have complex equality checks (non builtin derived Object types)
// This does not work for various other types (not a complete list) such as:
// string vs string - performs string comparison
// number vs string - type conversion is performed
// XML types - complex equality checks
// OBJECT_TYPE - this can mean even Number and String
if (((lht == NULL_TYPE) && (rht && (!rht->hasComplexEqualityRules() || rht == STRING_TYPE))) ||
((rht == NULL_TYPE) && (lht && (!lht->hasComplexEqualityRules() || lht == STRING_TYPE))) ||
((rht && !rht->hasComplexEqualityRules()) && (lht && !lht->hasComplexEqualityRules()))) {
LIns* lhs = localGetp(lhsi);
LIns* rhs = localGetp(rhsi);
return binaryIns(LIR_eqp, lhs, rhs);
}
// TODO numeric comparisons can be optimized by inlining the fastpath (int/int, number/number)
if ((lht == rht) && (lht == STRING_TYPE)) {
LIns* lhs = localGetp(lhsi);
LIns* rhs = localGetp(rhsi);
return callIns(FUNCTIONID(String_equals), 2, lhs, rhs, result);
}
LIns* lhs = loadAtomRep(lhsi);
LIns* rhs = loadAtomRep(rhsi);
LIns* out;
if (isStrict)
out = callIns(fid, 2, lhs, rhs);
else
out = callIns(fid, 3, coreAddr, lhs, rhs);
result = binaryIns(LIR_eqp, out, InsConstAtom(trueAtom));
return result;
}
void CodegenLIR::writeEpilogue(const FrameState *state)
{
this->state = state;
this->labelCount = driver->getBlockCount();
// LIR_regfence instructions are used below to reduce register pressure.
// The cost of reloading is not an issue for these exceptional cases.
// A notable exception is the 'catch_label' block, where correctness
// requires a regfence for an unrelated reason.
if (mop_rangeCheckFailed_label.unpatchedEdges) {
emitLabel(mop_rangeCheckFailed_label);
Ins(LIR_regfence);
callIns(FUNCTIONID(mop_rangeCheckFailed), 1, env_param);
}
if (npe_label.unpatchedEdges) {
emitLabel(npe_label);
Ins(LIR_regfence);
callIns(FUNCTIONID(npe), 1, env_param);
}
if (upe_label.unpatchedEdges) {
emitLabel(upe_label);
Ins(LIR_regfence);
callIns(FUNCTIONID(upe), 1, env_param);
}
if (call_error_label.unpatchedEdges) {
emitLabel(call_error_label);
Ins(LIR_regfence);
callIns(FUNCTIONID(op_call_error), 1, env_param);
}
if (interrupt_label.unpatchedEdges) {
emitLabel(interrupt_label);
#if defined (VMCFG_INTERRUPT_SAFEPOINT_POLL) && defined (NJ_SAFEPOINT_POLLING_SUPPORTED)
Ins(LIR_pushstate);
#endif
Ins(LIR_regfence);
callIns(FUNCTIONID(handleInterruptMethodEnv), 1, env_param);
#if defined (VMCFG_INTERRUPT_SAFEPOINT_POLL) && defined (NJ_SAFEPOINT_POLLING_SUPPORTED)
Ins(LIR_popstate);
Ins(LIR_restorepc);
#endif
}
if (driver->hasReachableExceptions()) {
// exception case
emitLabel(catch_label);
// This regfence is necessary for correctness,
// as register contents after a longjmp are unpredictable.
Ins(LIR_regfence);
// _ef.beginCatch()
int stackBase = ms->stack_base();
LIns* pc = loadIns(LIR_ldp, 0, _save_eip, ACCSET_OTHER);
LIns* slotAddr = lea(stackBase << VARSHIFT(info) , vars);
LIns* tagAddr = lea(stackBase, tags);
LIns* handler_ordinal = callIns(FUNCTIONID(beginCatch), 6, coreAddr, _ef, InsConstPtr(info), pc, slotAddr, tagAddr);
int handler_count = info->abc_exceptions()->exception_count;
// Jump to catch handler
// Find last handler, to optimize branches generated below.
int i;
for (i = handler_count-1; i >= 0; i--) {
ExceptionHandler* h = &info->abc_exceptions()->exceptions[i];
const uint8_t* handler_pc = code_pos + h->target;
if (driver->hasFrameState(handler_pc)) break;
}
int last_ordinal = i;
// There should be at least one reachable handler.
AvmAssert(last_ordinal >= 0);
// Do a compare & branch to each possible target.
for (int j = 0; j <= last_ordinal; j++) {
ExceptionHandler* h = &info->abc_exceptions()->exceptions[j];
const uint8_t* handler_pc = code_pos + h->target;
if (driver->hasFrameState(handler_pc)) {
CodegenLabel& label = getCodegenLabel(handler_pc);
AvmAssert(label.labelIns != NULL);
if (j == last_ordinal) {
lirout->insBranch(LIR_j, NULL, label.labelIns);
} else {
LIns* cond = binaryIns(LIR_eqi, handler_ordinal, InsConst(j));
branchToLabel(LIR_jt, cond, label);
}
}
}
livep(_ef);
livep(_save_eip);
}
if (prolog->env_scope) livep(prolog->env_scope);
if (prolog->env_vtable) livep(prolog->env_vtable);
if (prolog->env_abcenv) livep(prolog->env_abcenv);
if (prolog->env_domainenv) livep(prolog->env_domainenv);
if (prolog->env_toplevel) livep(prolog->env_toplevel);
if (prolog->env_finddef_table) livep(prolog->env_finddef_table);
if (restArgc) {
lirout->ins1(LIR_livei, restArgc);
livep(ap_param);
}
#ifdef DEBUGGER
if (haveDebugger)
livep(csn);
#endif
// extend live range of critical stuff
// fixme -- this should be automatic based on live analysis
livep(methodFrame);
livep(env_param);
frag->lastIns = livep(coreAddr);
skip_ins->overwriteWithSkip(prolog->lastIns);
// After CodeWriter::writeEpilogue() is called, driver is invalid
// and could be destructed. Null out our pointer as a precaution.
this->driver = NULL;
}
// emit code to create a stack-allocated copy of the given multiname.
// this helper only initializes Multiname.flags and Multiname.next_index
LIns* CodegenLIR::copyMultiname(const Multiname* multiname)
{
LIns* name = insAlloc(sizeof(Multiname));
sti(InsConst(multiname->ctFlags()), name, offsetof(Multiname, flags), ACCSET_OTHER);
sti(InsConst(multiname->next_index), name, offsetof(Multiname, next_index), ACCSET_OTHER);
return name;
}
LIns* CodegenLIR::initMultiname(const Multiname* multiname, int& csp, bool isDelete /*=false*/)
{
if (!multiname->isRuntime()) {
// use the precomputed multiname
return InsConstPtr(multiname);
}
// create an initialize a copy of the given multiname
LIns* _tempname = copyMultiname(multiname);
// then initialize its name and ns|nsset fields.
LIns* nameAtom = NULL;
if (multiname->isRtname())
{
nameAtom = loadAtomRep(csp--);
}
else
{
// copy the compile-time name to the temp name
LIns* mName = InsConstPtr(multiname->name);
stp(mName, _tempname, offsetof(Multiname,name), ACCSET_OTHER);
}
if (multiname->isRtns())
{
// intern the runtime namespace and copy to the temp multiname
LIns* nsAtom = loadAtomRep(csp--);
LIns* internNs = callIns(FUNCTIONID(internRtns), 2,
env_param, nsAtom);
stp(internNs, _tempname, offsetof(Multiname,ns), ACCSET_OTHER);
}
else
{
// copy the compile-time namespace to the temp multiname
LIns* mSpace = InsConstPtr(multiname->ns);
stp(mSpace, _tempname, offsetof(Multiname, ns), ACCSET_OTHER);
}
// Call initMultinameLate as the last step, since if a runtime
// namespace is present, initMultinameLate may clobber it if a
// QName is provided as index.
if (nameAtom)
{
if (isDelete)
{
callIns(FUNCTIONID(initMultinameLateForDelete), 3,
env_param, _tempname, nameAtom);
}
else
{
callIns(FUNCTIONID(initMultinameLate), 3,
coreAddr, _tempname, nameAtom);
}
}
return _tempname;
}
LIns* CodegenLIR::mopAddrToRangeCheckedRealAddrAndDisp(LIns* mopAddr, int32_t const size, int32_t* disp)
{
AvmAssert(size > 0); // it's signed to help make the int promotion correct
if (!mopsRangeCheckFilter) {
// add a MopsRangeCheckFilter to the back end of the lirout pipeline, just after CseFilter.
// fixme bug Bug 554030: We must put this after CseFilter and ExprFilter so that
// the range-check expression using LIR_addi/LIR_subi are not modified (by ExprFilter)
// and no not become referenced by other unrelated code (by CseFilter).
AvmAssert(lirout == varTracker);
mopsRangeCheckFilter = new (*alloc1) MopsRangeCheckFilter(redirectWriter->out, prolog, loadEnvDomainEnv());
redirectWriter->out = mopsRangeCheckFilter;
}
// note, mopAddr and disp are both in/out parameters
LIns* br = NULL;
LIns* mopsMemoryBase = mopsRangeCheckFilter->emitRangeCheck(mopAddr, size, disp, br);
if (br)
patchLater(br, mop_rangeCheckFailed_label);
// if mopAddr is a compiletime constant, we still have to do the range-check above
// (since globalMemorySize can vary at runtime), but we might be able to encode
// the entire address into the displacement (if any)...
if (mopAddr->isImmI() && disp != NULL && sumFitsInInt32(*disp, mopAddr->immI()))
{
*disp += mopAddr->immI();
return mopsMemoryBase;
}
// (yes, i2p, not u2p... it might legitimately be negative due to the
// displacement optimization in emitCheck().)
return binaryIns(LIR_addp, mopsMemoryBase, i2p(mopAddr));
}
LIns* CodegenLIR::loadEnvScope()
{
LIns* scope = prolog->env_scope;
if (!scope)
{
prolog->env_scope = scope = prolog->insLoad(LIR_ldp, env_param, offsetof(MethodEnv, _scope), ACCSET_OTHER, LOAD_CONST);
verbose_only( if (vbNames) {
vbNames->lirNameMap->addName(scope, "env_scope");
})
verbose_only( if (vbWriter) { vbWriter->flush(); } )
}
return scope;
}
LIns* CodegenLIR::loadEnvVTable()
{
LIns* vtable = prolog->env_vtable;
if (!vtable)
{
LIns* scope = loadEnvScope();
prolog->env_vtable = vtable = prolog->insLoad(LIR_ldp, scope, offsetof(ScopeChain, _vtable), ACCSET_OTHER, LOAD_CONST);
verbose_only( if (vbNames) {
vbNames->lirNameMap->addName(vtable, "env_vtable");
})
verbose_only( if (vbWriter) { vbWriter->flush(); } )
}
return vtable;
}
LIns* CodegenLIR::loadEnvAbcEnv()
{
LIns* abcenv = prolog->env_abcenv;
if (!abcenv)
{
LIns* scope = loadEnvScope();
prolog->env_abcenv = abcenv = prolog->insLoad(LIR_ldp, scope, offsetof(ScopeChain, _abcEnv), ACCSET_OTHER, LOAD_CONST);
verbose_only( if (vbNames) {
vbNames->lirNameMap->addName(abcenv, "env_abcenv");
})
verbose_only( if (vbWriter) { vbWriter->flush(); } )
}
return abcenv;
}
LIns* CodegenLIR::loadEnvDomainEnv()
{
LIns* domainenv = prolog->env_domainenv;
if (!domainenv)
{
LIns* abcenv = loadEnvAbcEnv();
prolog->env_domainenv = domainenv = prolog->insLoad(LIR_ldp, abcenv, offsetof(AbcEnv, m_domainEnv), ACCSET_OTHER, LOAD_CONST);
verbose_only( if (vbNames) {
vbNames->lirNameMap->addName(domainenv, "env_domainenv");
})
verbose_only( if (vbWriter) { vbWriter->flush(); } )
}
return domainenv;
}
LIns* CodegenLIR::loadEnvToplevel()
{
LIns* toplevel = prolog->env_toplevel;
if (!toplevel)
{
LIns* vtable = loadEnvVTable();
prolog->env_toplevel = toplevel = prolog->insLoad(LIR_ldp, vtable, offsetof(VTable, _toplevel), ACCSET_OTHER, LOAD_CONST);
verbose_only( if (vbNames) {
vbNames->lirNameMap->addName(toplevel, "env_toplevel");
})
verbose_only( if (vbWriter) { vbWriter->flush(); } )
}
return toplevel;
}
LIns* CodegenLIR::loadEnvFinddefTable()
{
LIns* table = prolog->env_finddef_table;
if (!table) {
LIns* abc = loadEnvAbcEnv();
prolog->env_finddef_table = table = prolog->insLoad(LIR_ldp, abc, offsetof(AbcEnv, m_finddef_table), ACCSET_OTHER, LOAD_CONST);
verbose_only( if (vbNames) {
vbNames->lirNameMap->addName(table, "env_finddef_table");
})
verbose_only( if (vbWriter) { vbWriter->flush(); } )
}
return table;
}
/**
* given an object and type, produce code that loads the VTable for
* the object. Handles all types: primitive vables get loaded from
* Toplevel, Object and * vtables get loaded by calling the toVTable() helper.
* ScriptObject* vtables are loaded from the ScriptObject.
*/
LIns* CodegenLIR::loadVTable(LIns* obj, Traits* t)
{
if (t && !t->isMachineType() && t != STRING_TYPE && t != NAMESPACE_TYPE && t != NULL_TYPE)
{
// must be a pointer to a scriptobject, and we've done the n
// all other types are ScriptObject, and we've done the null check
return loadIns(LIR_ldp, offsetof(ScriptObject, vtable), obj, ACCSET_OTHER, LOAD_CONST);
}
LIns* toplevel = loadEnvToplevel();
int offset;
if (t == NAMESPACE_TYPE) offset = offsetof(Toplevel, _namespaceClass);
else if (t == STRING_TYPE) offset = offsetof(Toplevel, _stringClass);
else if (t == BOOLEAN_TYPE) offset = offsetof(Toplevel, _booleanClass);
else if (t == NUMBER_TYPE) offset = offsetof(Toplevel, _numberClass);
#ifdef VMCFG_FLOAT
else if (t == FLOAT_TYPE) offset = offsetof(Toplevel, _floatClass);
else if (t == FLOAT4_TYPE) offset = offsetof(Toplevel, _float4Class );
#endif
else if (t == INT_TYPE) offset = offsetof(Toplevel, _intClass);
else if (t == UINT_TYPE) offset = offsetof(Toplevel, _uintClass);
else
{
// *, Object or Void
LIns* atom = nativeToAtom(obj, t);
return callIns(FUNCTIONID(toVTable), 2, toplevel, atom);
}
// now offset != -1 and we are returning a primitive vtable
LIns* cc = loadIns(LIR_ldp, offset, toplevel, ACCSET_OTHER, LOAD_CONST);
LIns* cvtable = loadIns(LIR_ldp, offsetof(ClassClosure, vtable), cc, ACCSET_OTHER, LOAD_CONST);
return loadIns(LIR_ldp, offsetof(VTable, ivtable), cvtable, ACCSET_OTHER, LOAD_CONST);
}
LIns* CodegenLIR::promoteNumberIns(Traits* t, int i)
{
if (t == NUMBER_TYPE)
return localGetd(i);
if (t == INT_TYPE || t == BOOLEAN_TYPE)
return i2dIns(localGet(i));
if (t == UINT_TYPE)
return ui2dIns(localGet(i));
#ifdef VMCFG_FLOAT
if(t == FLOAT_TYPE)
return f2dIns(localGetf(i));
AvmAssert(t == FLOAT4_TYPE);
#else
AvmAssert(!"Unhandled type in promoteNumberIns");
#endif
return InsConstDbl(MathUtils::kNaN);
}
#ifdef VMCFG_FLOAT
LIns* CodegenLIR::promoteFloatIns(Traits* t, int i)
{
if (t == FLOAT_TYPE)
{
return localGetf(i);
}
if (t == FLOAT4_TYPE)
{
return InsConstFlt(AvmCore::atomToFloat(core->kFltNaN));
}
if(t == NUMBER_TYPE)
return d2fIns(localGetd(i));
if (t == INT_TYPE || t == BOOLEAN_TYPE)
{
return i2fIns(localGet(i));
}
AvmAssert(t == UINT_TYPE);
return ui2fIns(localGet(i));
}
LIns* CodegenLIR::promoteFloat4Ins(Traits* t, int i)
{
// This function hadles * -> Float4, too.
// That is because if any operand is float4, the other needs to be coerced to float4
switch(bt(t)){
case BUILTIN_float:
case BUILTIN_number:
case BUILTIN_int:
case BUILTIN_uint:
case BUILTIN_boolean:
return Ins(LIR_f2f4, promoteFloatIns(t,i));
case BUILTIN_float4:
return localGetf4(i);
default:
{
LIns* retval = lirout->insAlloc(sizeof(float4_t));
callIns(FUNCTIONID(float4), 2, lea(0, retval), loadAtomRep(i));
return ldf4(retval,0,ACCSET_OTHER);
}
}
}
#endif // VMCFG_FLOAT
/// set position of a label and patch all pending jumps to point here.
void CodegenLIR::emitLabel(CodegenLabel& label)
{
// specialized calls shouldn't be re-used across labels,
// otherwise we might use one that isn't valid for a given
// code path. simplest fix is to just clear the map when
// we see a label.
if (specializedCallHashMap)
specializedCallHashMap->clear();
varTracker->trackLabel(label, state);
// patch all unpatched branches to this label
LIns* labelIns = label.labelIns;
bool jtbl_forward_target = false;
for (Seq<InEdge>* p = label.unpatchedEdges; p != NULL; p = p->tail) {
InEdge& patch = p->head;
LIns* branchIns = patch.branchIns;
if (branchIns->isop(LIR_jtbl)) {
jtbl_forward_target = true;
branchIns->setTarget(patch.index, labelIns);
} else {
AvmAssert(branchIns->isBranch() && patch.index == 0);
branchIns->setTarget(labelIns);
}
}
if (jtbl_forward_target) {
// A jtbl (switch) jumps forward to here, creating a situation our
// register allocator cannot handle; force regs to be loaded at the
// start of this block.
Ins(LIR_regfence);
}
#ifdef NJ_VERBOSE
if (vbNames && label.name)
vbNames->lirNameMap->addName(label.labelIns, label.name);
#endif
}
#ifdef DEBUGGER
void CodegenLIR::emitSampleCheck()
{
/* @todo inline the sample check code, help! */
callIns(FUNCTIONID(sampleCheck), 1, coreAddr);
}
#endif
#ifdef NJ_VERBOSE
bool CodegenLIR::verbose()
{
return pool->isVerbose(VB_jit, info);
}
#endif
// emit a conditional branch to the given label. If we have already emitted
// code for that label then the branch is complete. If not then add a patch
// record to the label, which will patch the branch when the label position
// is reached. cond == NULL for unconditional branches (LIR_j).
void CodegenLIR::branchToLabel(LOpcode op, LIns *cond, CodegenLabel& label) {
if (cond && !cond->isCmp()) {
// branching on a non-condition expression, so test (v==0)
// and invert the sense of the branch.
cond = eqi0(cond);
op = invertCondJmpOpcode(op);
}
LIns* labelIns = label.labelIns;
LIns* br = lirout->insBranch(op, cond, labelIns);
if (br != NULL) {
if (labelIns != NULL) {
varTracker->checkBackEdge(label, state);
} else {
label.unpatchedEdges = new (*alloc1) Seq<InEdge>(InEdge(br), label.unpatchedEdges);
varTracker->trackForwardEdge(label);
}
} else {
// branch was optimized away. do nothing.
}
}
void CodegenLIR::branchAndSavePC(LIns *flag, CodegenLabel& label) {
LIns* labelIns = label.labelIns;
LIns* br = lirout->insBranch(LIR_brsavpc, flag, labelIns);
AvmAssert(br != NULL && labelIns == NULL);
label.unpatchedEdges = new (*alloc1) Seq<InEdge>(InEdge(br), label.unpatchedEdges);
varTracker->trackForwardEdge(label);
}
LIns* CodegenLIR::branchJovToLabel(LOpcode op, LIns *a, LIns *b, CodegenLabel& label) {
LIns* labelIns = label.labelIns;
LIns* result = lirout->insBranchJov(op, a, b, labelIns);
NanoAssert(result);
if (result->isop(op)) {
if (labelIns != NULL) {
varTracker->checkBackEdge(label, state);
} else {
label.unpatchedEdges = new (*alloc1) Seq<InEdge>(InEdge(result), label.unpatchedEdges);
varTracker->trackForwardEdge(label);
}
} else {
// The root operator of the expression has been eliminated via
// constant folding or other simplification. This is only valid
// if no overflow is possible, in which case the branch is not needed.
// Do nothing.
}
return result;
}
// emit a relative branch to the given ABC pc-offset by mapping pc
// to a corresponding CodegenLabel, and creating a new one if necessary
void CodegenLIR::branchToAbcPos(LOpcode op, LIns *cond, const uint8_t* pc) {
CodegenLabel& label = getCodegenLabel(pc);
branchToLabel(op, cond, label);
}
CodegenLabel& CodegenLIR::createLabel(const char* name) {
return *(new (*alloc1) CodegenLabel(name));
}
CodegenLabel& CodegenLIR::createLabel(const char* prefix, int id) {
CodegenLabel* label = new (*alloc1) CodegenLabel();
#ifdef NJ_VERBOSE
if (vbNames) {
char *name = new (*lir_alloc) char[VMPI_strlen(prefix)+16];
VMPI_sprintf(name, "%s%d", prefix, id);
label->name = name;
}
#else
(void) prefix;
(void) id;
#endif
return *label;
}
CodegenLabel& CodegenLIR::getCodegenLabel(const uint8_t* pc) {
AvmAssert(driver->hasFrameState(pc));
if (!blockLabels)
blockLabels = new (*alloc1) HashMap<const uint8_t*,CodegenLabel*>(*alloc1, driver->getBlockCount());
CodegenLabel* label = blockLabels->get(pc);
if (!label) {
label = new (*alloc1) CodegenLabel();
blockLabels->put(pc, label);
}
#ifdef NJ_VERBOSE
if (!label->name && vbNames) {
char *name = new (*lir_alloc) char[16];
VMPI_sprintf(name, "B%d", int(pc - code_pos));
label->name = name;
}
#endif
return *label;
}
/// connect to a label for one entry of a switch
void CodegenLIR::patchLater(LIns* jtbl, const uint8_t* pc, uint32_t index) {
CodegenLabel& target = getCodegenLabel(pc);
if (target.labelIns != 0) {
jtbl->setTarget(index, target.labelIns); // backward edge
varTracker->checkBackEdge(target, state);
} else {
target.unpatchedEdges = new (*alloc1) Seq<InEdge>(InEdge(jtbl, index), target.unpatchedEdges);
varTracker->trackForwardEdge(target);
}
}
void CodegenLIR::patchLater(LIns *br, CodegenLabel &target) {
if (!br) return; // occurs if branch was unconditional and thus never emitted.
if (target.labelIns != 0) {
br->setTarget(target.labelIns); // backwards edge
varTracker->checkBackEdge(target, state);
} else {
target.unpatchedEdges = new (*alloc1) Seq<InEdge>(InEdge(br), target.unpatchedEdges);
varTracker->trackForwardEdge(target);
}
}
LIns* CodegenLIR::insAlloc(int32_t size) {
return lirout->insAlloc(size >= 4 ? size : 4);
}
LIns* CodegenLIR::insAllocForTraits(Traits *t)
{
switch (bt(t)) {
#ifdef VMCFG_FLOAT
case BUILTIN_float:
return insAlloc(sizeof(float));
case BUILTIN_float4:
return insAlloc(sizeof(float4_t));
#endif // VMCFG_FLOAT
case BUILTIN_number:
return insAlloc(sizeof(double));
case BUILTIN_int:
case BUILTIN_uint:
case BUILTIN_boolean:
return insAlloc(sizeof(int32_t));
default:
return insAlloc(sizeof(intptr_t));
}
}
CodeMgr::CodeMgr(nanojit::Config* config) : codeAlloc(config), bindingCaches(NULL), jit_mgr(NULL)
{
verbose_only( log.lcbits = 0; )
}
void CodeMgr::flushBindingCaches()
{
// this clears vtable so all kObjectType receivers are invalidated.
// of course, this field is also "tag" for primitive receivers,
// but 0 is never a legal value there (and this is asserted when the tag is set)
// so this should safely invalidate those as well (though we don't really need to invalidate them)
for (BindingCache* b = bindingCaches; b != NULL; b = b->next)
b->vtable = NULL;
}
void analyze_edge(LIns* label, nanojit::BitSet &livein,
LabelBitSet& labels, InsList* looplabels)
{
nanojit::BitSet *lset = labels.get(label);
if (lset) {
livein.setFrom(*lset);
} else {
AvmAssertMsg(looplabels != NULL, "Unexpected back-edge");
looplabels->add(label);
}
}
REALLY_INLINE void update_var_liveness(nanojit::BitSet* varlivein, nanojit::BitSet* taglivein, int varIdx, const bool isVarStore)
{
if(isVarStore) {
varlivein->clear(varIdx);
if(taglivein) taglivein->clear(varIdx);
} else {
varlivein->set(varIdx);
if(taglivein) taglivein->set(varIdx);
}
}
// Treat the calculated address of addp(vars, const) as the target
// of a load(or store) from/to the variable pointed to, as well as its associated tag (if a tag bitset is passed).
bool analyze_addp(LIns* ins, LIns* vars, nanojit::BitSet* varlivein, nanojit::BitSet* taglivein, const size_t varShift, const bool isStore)
{
AvmAssert(ins->isop(LIR_addp));
if (ins->oprnd1() == vars && ins->oprnd2()->isImmP()) {
AvmAssert(IS_ALIGNED(ins->oprnd2()->immP(), 1 << varShift));
int d = int(uintptr_t(ins->oprnd2()->immP()) >> varShift);
update_var_liveness(varlivein, taglivein, d, isStore);
return true;
}
return false;
}
void analyze_call(LIns* ins, LIns* catcher, LIns* vars, DEBUGGER_ONLY(bool haveDebugger, int dbg_framesize,)
const size_t varShift,
nanojit::BitSet& varlivein, LabelBitSet& varlabels,
nanojit::BitSet& taglivein, LabelBitSet& taglabels)
{
typedef struct FuncLiveInfo{
const CallInfo* callinfo;
int32_t var_argument; /* NOTE! arguments are counted from the end! */
bool isStore;
bool tagAccess;
bool mustBeVarAccess;
} FuncLiveInfo;
static const FuncLiveInfo varPtrFunctions[] = {
// beginCatch(core, ef, info, pc, &vars[i], &tags[i]) => store to &vars[i] and &tag[i]
{ FUNCTIONID(beginCatch), 1, true, true, true },
// makeatom(core, &vars[index], tag[index]) => treat as load from &vars[index]
{ FUNCTIONID(makeatom), 1, false, true, true },
// restargHelper(Toplevel*, Multiname*, Atom, ArrayObject**, uint32_t, Atom*) => arrayObject** might be reference to a var
{ FUNCTIONID(restargHelper), 2, false, false, false },
#ifdef VMCFG_FLOAT
// float4(float4_t* result, Atom val) => result might point to a local var.
{ FUNCTIONID(float4), 1, true, false, false },
// Float4VectorObject::_getNativeUintProperty(this, result, index) => result might point to a local var.
{ FUNCTIONID(Float4VectorObject_getNativeUintProperty), 1, true, false, false },
// Float4VectorObject::_getNativeIntProperty(this, result, index) => result might point to a local var.
{ FUNCTIONID(Float4VectorObject_getNativeIntProperty), 1, true, false, false },
// Float4VectorObject::_getNativeDoubleProperty(this, result, index) => result might point to a local var.
{ FUNCTIONID(Float4VectorObject_getNativeDoubleProperty),1, true, false, false },
// Float4VectorObject::_setFloat4UintProperty(this, index, value) => value might point to a local var.
{ FUNCTIONID(Float4VectorObject_setNativeUintProperty), 0, false, false, false },
// Float4VectorObject::_setFloat4IntProperty(this, index, value) => value might point to a local var.
{ FUNCTIONID(Float4VectorObject_setNativeIntProperty), 0, false, false, false },
// Float4VectorObject::_setFloat4DoubleProperty(this, index, value) => value might point to a local var.
{ FUNCTIONID(Float4VectorObject_setNativeDoubleProperty),0, false, false, false },
#endif
};
const int varPtrFunctionsNum = (int) (sizeof(varPtrFunctions) / sizeof(FuncLiveInfo));
const CallInfo* ci = ins->callInfo();
for(int i=0;i < varPtrFunctionsNum; i++ ) {
if(ci == varPtrFunctions[i].callinfo) {
uint32_t argIdx = varPtrFunctions[i].var_argument;
bool updated = false;
LIns* varPtrArg = ins->arg(argIdx); // varPtrArg == vars, OR addp(vars, index)
if (varPtrArg == vars) {
update_var_liveness(&varlivein, varPtrFunctions[i].tagAccess? &taglivein : NULL, 0, varPtrFunctions[i].isStore);
updated = true;
} else
if (varPtrArg->isop(LIR_addp)) {
updated = analyze_addp(varPtrArg, vars, &varlivein, varPtrFunctions[i].tagAccess? &taglivein : NULL, varShift, varPtrFunctions[i].isStore);
}
AvmAssert(updated || !varPtrFunctions[i].mustBeVarAccess);
return; // don't continue, we handled the call.
}
}
if (!ins->callInfo()->_isPure) {
if (catcher) {
// non-cse call is like a conditional forward branch to the catcher label.
// this could be made more precise by checking whether this call
// can really throw, and only processing edges to the subset of
// reachable catch blocks. If we haven't seen the catch label yet then
// the call is to an exception handling helper (eg beginCatch())
// that won't throw.
nanojit::BitSet *varlset = varlabels.get(catcher);
if (varlset)
varlivein.setFrom(*varlset);
nanojit::BitSet *taglset = taglabels.get(catcher);
if (taglset)
taglivein.setFrom(*taglset);
}
#ifdef DEBUGGER
if (haveDebugger) {
// all vars and scopes must be considered "read" by any call
// the debugger can stop in. The debugger also will access tags[].
for (int i = 0, n = dbg_framesize; i < n; i++) {
varlivein.set(i);
taglivein.set(i);
}
}
#endif
}
}
// Analyze a LIR_label. We're at the top of a block, save livein for
// this block so it can be propagated to predecessors.
bool analyze_label(LIns* i, Allocator& alloc, bool again,
int framesize, InsList* looplabels,
nanojit::BitSet& varlivein, LabelBitSet& varlabels,
nanojit::BitSet& taglivein, LabelBitSet& taglabels)
{
nanojit::BitSet *var_lset = varlabels.get(i);
if (!var_lset) {
var_lset = new (alloc) nanojit::BitSet(alloc, framesize);
varlabels.put(i, var_lset);
}
if (var_lset->setFrom(varlivein) && !again) {
for (Seq<LIns*>* p = looplabels->get(); p != NULL; p = p->tail) {
if (p->head == i) {
again = true;
break;
}
}
}
nanojit::BitSet *tag_lset = taglabels.get(i);
if (!tag_lset) {
tag_lset = new (alloc) nanojit::BitSet(alloc, framesize);
taglabels.put(i, tag_lset);
}
if (tag_lset->setFrom(taglivein) && !again) {
for (Seq<LIns*>* p = looplabels->get(); p != NULL; p = p->tail) {
if (p->head == i) {
again = true;
break;
}
}
}
return again;
}
void CodegenLIR::deadvars_analyze(Allocator& alloc,
nanojit::BitSet& varlivein, LabelBitSet& varlabels,
nanojit::BitSet& taglivein, LabelBitSet& taglabels)
{
LIns *catcher = this->catch_label.labelIns;
InsList looplabels(alloc);
verbose_only(int iter = 0;)
bool again;
do {
again = false;
varlivein.reset();
taglivein.reset();
LirReader in(frag->lastIns);
for (LIns *i = in.read(); !i->isop(LIR_start); i = in.read()) {
LOpcode op = i->opcode();
switch (op) {
case LIR_reti:
CASE64(LIR_retq:)
case LIR_retd:
case LIR_retf:
case LIR_retf4:
varlivein.reset();
taglivein.reset();
break;
CASE64(LIR_stq:)
case LIR_sti:
case LIR_std:
case LIR_std2f:
case LIR_stf:
case LIR_stf4:
case LIR_sti2c:
if (i->oprnd2() == vars) {
AvmAssert( IS_ALIGNED(i->disp(), (1 << VARSHIFT(info))) );
int d = i->disp() >> VARSHIFT(info);
varlivein.clear(d);
} else if (i->oprnd2() == tags) {
int d = i->disp(); // 1 byte per tag
taglivein.clear(d);
}
break;
case LIR_addp:
// treat pointer calculations into vars as a read from vars
// FIXME: Bug 569677
// This is extremely fragile. There is no reason to suppose
// that the address will be used for a read rather than a store,
// other than that to do so would break the deadvars analysis
// because of the dodgy assumption made here.
analyze_addp(i, vars, &varlivein, NULL, VARSHIFT(info), false);
break;
CASE64(LIR_ldq:)
case LIR_ldi:
case LIR_ldd:
case LIR_ldf2d:
case LIR_ldf:
case LIR_ldf4:
case LIR_lduc2ui: case LIR_ldc2i:
if (i->oprnd1() == vars) {
AvmAssert( IS_ALIGNED(i->disp(), (1 << VARSHIFT(info))) );
int d = i->disp() >> VARSHIFT(info);
varlivein.set(d);
}
else if (i->oprnd1() == tags) {
int d = i->disp(); // 1 byte per tag
taglivein.set(d);
}
break;
case LIR_label:
again |= analyze_label(i, alloc, again, framesize, &looplabels,
varlivein, varlabels, taglivein, taglabels);
break;
case LIR_j:
// the fallthrough path is unreachable, clear it.
varlivein.reset();
taglivein.reset();
// fall through to other branch cases
case LIR_jt:
case LIR_jf:
// merge the LiveIn sets from each successor: the fall
// through case (livein) and the branch case (lset).
analyze_edge(i->getTarget(), varlivein, varlabels, &looplabels);
analyze_edge(i->getTarget(), taglivein, taglabels, &looplabels);
break;
case LIR_jtbl:
varlivein.reset(); // fallthrough path is unreachable, clear it.
taglivein.reset(); // fallthrough path is unreachable, clear it.
for (uint32_t j=0, n=i->getTableSize(); j < n; j++) {
analyze_edge(i->getTarget(j), varlivein, varlabels, &looplabels);
analyze_edge(i->getTarget(j), taglivein, taglabels, &looplabels);
}
break;
CASE64(LIR_callq:)
case LIR_calli:
case LIR_calld:
case LIR_callf:
case LIR_callf4:
case LIR_callv:
analyze_call(i, catcher, vars, DEBUGGER_ONLY(haveDebugger, dbg_framesize,) VARSHIFT(info),
varlivein, varlabels, taglivein, taglabels);
break;
default:
break;
}
}
verbose_only(iter++;)
}
while (again);
// now make a final pass, modifying LIR to delete dead stores (make them LIR_neartramps)
verbose_only( if (pool->isVerbose(LC_Liveness, info))
AvmLog("killing dead stores after %d LA iterations.\n",iter);
)
}
// Erase the instruction by rewriting it as a skip.
// TODO this can go away if we turn this kill pass into a LirReader
// and do the work inline with the assembly pass.
void CodegenLIR::eraseIns(LIns* ins, LIns* prevIns)
{
#ifdef NJ_VERBOSE
LInsPrinter *printer = frag->lirbuf->printer;
bool verbose = printer && pool->isVerbose(LC_AfterDCE, info);
if (verbose) {
InsBuf b;
AvmLog("- %s\n", printer->formatIns(&b, ins));
}
#endif
ins->overwriteWithSkip(prevIns);
}
void CodegenLIR::deadvars_kill(Allocator& alloc,
nanojit::BitSet& varlivein, LabelBitSet& varlabels,
nanojit::BitSet& taglivein, LabelBitSet& taglabels)
{
#ifdef NJ_VERBOSE
LInsPrinter *printer = frag->lirbuf->printer;
bool verbose = printer && pool->isVerbose(LC_AfterDCE, info);
InsBuf b;
#endif
LIns *catcher = this->catch_label.labelIns;
varlivein.reset();
taglivein.reset();
bool tags_touched = false;
bool vars_touched = false;
LirReader in(frag->lastIns);
for (LIns *i = in.read(); !i->isop(LIR_start); i = in.read()) {
LOpcode op = i->opcode();
switch (op) {
case LIR_reti:
CASE64(LIR_retq:)
case LIR_retd:
case LIR_retf:
case LIR_retf4:
varlivein.reset();
taglivein.reset();
break;
CASE64(LIR_stq:)
case LIR_sti:
case LIR_std:
case LIR_std2f:
case LIR_stf:
case LIR_stf4:
case LIR_sti2c:
if (i->oprnd2() == vars) {
AvmAssert( IS_ALIGNED(i->disp(), (1 << VARSHIFT(info))) );
int d = i->disp() >> VARSHIFT(info);
if (!varlivein.get(d)) {
eraseIns(i, in.peek());
continue;
} else {
varlivein.clear(d);
vars_touched = true;
}
}
else if (i->oprnd2() == tags) {
int d = i->disp(); // 1 byte per tag
if (!taglivein.get(d)) {
eraseIns(i, in.peek());
continue;
} else {
// keep the store
taglivein.clear(d);
tags_touched = true;
}
}
break;
case LIR_addp:
// treat pointer calculations into vars as a read from vars
analyze_addp(i, vars, &varlivein, NULL, VARSHIFT(info), false);
break;
CASE64(LIR_ldq:)
case LIR_ldi:
case LIR_ldd:
case LIR_ldf2d:
case LIR_ldf:
case LIR_ldf4:
case LIR_lduc2ui: case LIR_ldc2i:
if (i->oprnd1() == vars) {
AvmAssert( IS_ALIGNED(i->disp(), (1 << VARSHIFT(info))) );
int d = i->disp() >> VARSHIFT(info);
varlivein.set(d);
}
else if (i->oprnd1() == tags) {
int d = i->disp(); // 1 byte per tag
taglivein.set(d);
}
break;
case LIR_label:
analyze_label(i, alloc, true, framesize, 0,
varlivein, varlabels, taglivein, taglabels);
break;
case LIR_j:
// the fallthrough path is unreachable, clear it.
varlivein.reset();
taglivein.reset();
// fall through to other branch cases
case LIR_jt:
case LIR_jf:
// merge the LiveIn sets from each successor: the fall
// through case (live) and the branch case (lset).
analyze_edge(i->getTarget(), varlivein, varlabels, 0);
analyze_edge(i->getTarget(), taglivein, taglabels, 0);
break;
case LIR_jtbl:
varlivein.reset();
taglivein.reset();
for (uint32_t j = 0, n = i->getTableSize(); j < n; j++) {
analyze_edge(i->getTarget(j), varlivein, varlabels, 0);
analyze_edge(i->getTarget(j), taglivein, taglabels, 0);
}
break;
CASE64(LIR_callq:)
case LIR_calli:
case LIR_calld:
case LIR_callf:
case LIR_callf4:
case LIR_callv:
analyze_call(i, catcher, vars, DEBUGGER_ONLY(haveDebugger, dbg_framesize,) VARSHIFT(info),
varlivein, varlabels, taglivein, taglabels);
break;
default:
break;
}
verbose_only(if (verbose) {
AvmLog(" %s\n", printer->formatIns(&b, i));
})
}
// if we have not removed all stores to the tags array, mark it live
// so its live range will span loops.
if (tags_touched)
livep(tags);
if (vars_touched)
livep(vars);
}
/*
* this is iterative live variable analysis. We walk backwards through
* the code. when we see a load, we mark the variable live, and when
* we see a store, we mark it dead. Dead stores are dropped, not returned
* by read().
*
* at labels, we save the liveIn set associated with that label.
*
* at branches, we merge the liveIn sets from the fall through case (which
* is the current set) and the branch case (which was saved with the label).
* this filter can be run multiple times, which is required to pick up
* loop-carried live variables.
*
* once the live sets are stable, the DeadVars.kill flag is set to cause the filter
* to not only drop dead stores, but overwrite them as tramps so they'll be
* ignored by any later passes even without using this filter.
*/
void CodegenLIR::deadvars()
{
// allocator used only for duration of this phase. no exceptions are
// thrown while this phase runs, hence no try/catch is necessary.
Allocator dv_alloc;
// map of label -> bitset, tracking what is livein at each label.
// populated by deadvars_analyze, then used by deadvars_kill.
// Estimated number of required buckets is based on the driver's block count,
// which is slightly below the actual # of labels in LIR. Being slightly low
// is okay for a bucket hashtable. note: labelCount is 0 for simple 1-block
// methods, so use labelCount+1 as the estimate to ensure we have >0 buckets.
LabelBitSet varlabels(dv_alloc, labelCount + 1);
LabelBitSet taglabels(dv_alloc, labelCount + 1);
// scratch bitset used by both dv_analyze and dv_kill. Each resets
// the bitset before using it. creating it here saves one allocation.
nanojit::BitSet varlivein(dv_alloc, framesize);
nanojit::BitSet taglivein(dv_alloc, framesize);
// If catch_label.labelIns is non-null, we emitted an exception
// handler dispatcher, which generates back-edges into the method.
if (varTracker->hasBackedges() || catch_label.labelIns)
deadvars_analyze(dv_alloc, varlivein, varlabels, taglivein, taglabels);
deadvars_kill(dv_alloc, varlivein, varlabels, taglivein, taglabels);
}
#ifdef NJ_VERBOSE
void listing(const char* title, AvmLogControl &log, Fragment* frag)
{
LirReader reader(frag->lastIns);
Allocator lister_alloc;
ReverseLister lister(&reader, lister_alloc, frag->lirbuf->printer, &log, title);
for (LIns* ins = lister.read(); !ins->isop(LIR_start); ins = lister.read())
{}
lister.finish();
}
// build a filename based on path/title
static const char* filenameFor(const char* path, const char* name, const char* ext, Allocator& alloc)
{
char* filename = new (alloc) char[1+VMPI_strlen(name)+VMPI_strlen(path)+VMPI_strlen(ext)];
VMPI_strcpy(filename, path);
VMPI_strcat(filename, "/"); // sorry windows
char* dst = &filename[VMPI_strlen(filename)];
for(const char* s = name; *s; s++) {
char c = *s;
c = VMPI_isalnum(*s) ? c : '.'; // convert non-alpha to dots
c = ( c == '.' && dst[-1] == '.' ) ? 0 : c; // dont print multiple dots
if (c)
*dst++ = c;
}
dst = dst[-1] == '.' ? dst-1 : dst; // rm trailing dot
VMPI_strcpy(dst, ext);
return filename;
}
// write out a control flow graph of LIR instructions
static void lircfg(FILE* f, const Fragment* frag, InsSet* ignore, Allocator& alloc, CfgLister::CfgMode mode)
{
LirReader reader(frag->lastIns);
CfgLister cfg(&reader, alloc, mode);
for (LIns* ins = cfg.read(); !ins->isop(LIR_start); ins = cfg.read())
{}
cfg.printGmlCfg(f, frag->lirbuf->printer, ignore);
fclose(f);
}
#endif
JITDebugInfo* CodegenLIR::initJitDebugInfo()
{
// allocate JITDebugInfo for any JITObserver
if (!jit_debug_info) {
jit_debug_info = new (*lir_alloc) JITDebugInfo();
jit_debug_info->info = 0;
}
return jit_debug_info;
}
// return pointer to generated code on success, NULL on failure (frame size too large)
GprMethodProc CodegenLIR::emitMD()
{
deadvars(); // deadvars_kill() will add livep(vars) or livep(tags) if necessary
// do this very last so it's after livep(vars)
frag->lastIns = livep(undefConst);
PERFM_NTPROF_BEGIN("compile");
mmfx_delete( alloc1 );
alloc1 = NULL;
CodeMgr *mgr = pool->codeMgr;
#ifdef NJ_VERBOSE
if (pool->isVerbose(LC_ReadLIR, info)) {
StringBuffer sb(core);
sb << info;
core->console << "Final LIR " << info;
listing(sb.c_str(), mgr->log, frag);
}
if (pool->isVerbose(LC_Liveness, info)) {
Allocator live_alloc;
LirReader in(frag->lastIns);
nanojit::live(&in, live_alloc, frag, &mgr->log);
}
if (pool->isVerbose(LC_AfterDCE | LC_Native, info)) {
StringBuffer sb(core);
sb << info;
mgr->log.printf("jit-assembler %s\n", sb.c_str());
}
if (pool->isVerbose(VB_lircfg, info)) {
// For the control-flow graph :
// The list of instructions that we don't want to show explicit
// edges for are added to the 'ignore' set.
Allocator alloc;
bool hideCommonEdges = true;
InsSet ignore(alloc);
if (hideCommonEdges)
{
ignore.put(npe_label.labelIns, true);
ignore.put(upe_label.labelIns, true);
ignore.put(interrupt_label.labelIns, true);
ignore.put(mop_rangeCheckFailed_label.labelIns, true);
ignore.put(catch_label.labelIns, true);
ignore.put(call_error_label.labelIns, true);
}
// type of cfg graph to produce
CfgLister::CfgMode mode = pool->isVerbose(VB_lircfg_ins, info) ? CfgLister::CFG_INS
: pool->isVerbose(VB_lircfg_bb, info) ? CfgLister::CFG_BB
: CfgLister::CFG_EBB;
StringBuffer sb(core);
sb << info; // method name
const char* filename = filenameFor(".", sb.c_str(), ".gml", alloc);
FILE* f = fopen(filename, "w");
lircfg(f, frag, &ignore, alloc, mode);
fclose(f);
}
#endif
// Use the 'active' log if we are in verbose output mode otherwise sink the output
LogControl* log = &(mgr->log);
verbose_only(
SinkLogControl sink;
log = pool->isVerbose(VB_jit,info) ? log : &sink;
)
Assembler *assm = new (*lir_alloc) Assembler(mgr->codeAlloc, mgr->allocator, *lir_alloc, log, core->config.njconfig);
#ifdef VMCFG_VTUNE
assm->vtuneHandle = vtuneInit(info->getMethodName());
#endif /* VMCFG_VTUNE */
assm->setNoiseGenerator(noise);
verbose_only(
StringList asmOutput(*lir_alloc);
if (!pool->isVerbose(VB_raw, info))
assm->_outputCache = &asmOutput;
);
assm->beginAssembly(frag);
LirReader reader(frag->lastIns);
assm->assemble(frag, &reader);
assm->endAssembly(frag);
PERFM_NTPROF_END("compile");
verbose_only(
assm->_outputCache = 0;
for (Seq<char*>* p = asmOutput.get(); p != NULL; p = p->tail) {
assm->outputf("%s", p->head);
}
);
PERFM_NVPROF("IR-bytes", frag->lirbuf->byteCount());
PERFM_NVPROF("IR", frag->lirbuf->insCount());
GprMethodProc code;
bool keep = !assm->error();
if (keep) {
// save pointer to generated code
code = (GprMethodProc) frag->code();
PERFM_NVPROF("JIT method bytes", CodeAlloc::size(assm->codeList));
if (jit_observer)
jit_observer->notifyMethodJITed(info, assm->codeList, jit_debug_info);
} else {
verbose_only (if (pool->isVerbose(VB_execpolicy))
AvmLog("execpolicy revert to interp (%d) compiler error %d \n", info->unique_method_id(), assm->error());
)
// assm puked, or we did something untested, so interpret.
code = NULL;
PERFM_NVPROF("lir-error",1);
}
#ifdef VMCFG_VTUNE
if (assm->vtuneHandle) {
vtuneCleanup(assm->vtuneHandle);
}
#endif /* VMCFG_VTUNE */
return code;
}
BindingCache::BindingCache(const Multiname* name, BindingCache* next)
: name(name), next(next)
{}
CallCache::CallCache(const Multiname* name, BindingCache* next)
: BindingCache(name, next), call_handler(callprop_miss)
{}
GetCache::GetCache(const Multiname* name, BindingCache* next)
: BindingCache(name, next), get_handler(getprop_miss)
{}
SetCache::SetCache(const Multiname* name, BindingCache* next)
: BindingCache(name, next), set_handler(setprop_miss)
{}
template class CacheBuilder<CallCache>;
template class CacheBuilder<GetCache>;
template class CacheBuilder<SetCache>;
}
#endif // VMCFG_NANOJIT