/* -*- 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" #include "Interpreter.h" #ifdef VMCFG_NANOJIT # include "exec-osr.h" #endif #ifdef AVMPLUS_MAC #ifndef __GNUC__ // inline_max_total_size() defaults to 10000. // This module includes so many inline functions that we // exceed this limit and we start getting compile warnings, // so bump up the limit for this file. #pragma inline_max_total_size(26576) #endif #endif // The macro VMCFG_WORDCODE is true if the representation of ABC code is as an array of words and // not an array of bytes. namespace avmplus { // an auto-ptr for managing MethodFrame calls. class EnterMethodEnv { public: inline explicit EnterMethodEnv(AvmCore* core, MethodEnv* env, MethodFrame& frame) : m_core(core), m_frame(frame) { m_frame.enter(m_core, env); } inline ~EnterMethodEnv() { m_frame.exit(m_core); } void setCurrent() { m_core->currentMethodFrame = &m_frame; } private: AvmCore* m_core; MethodFrame& m_frame; }; #ifdef _DEBUG REALLY_INLINE Atom CHECK_INT_ATOM(Atom a) { AvmAssert(atomKind(a) == kIntptrType && atomIsValidIntptrValue(atomGetIntptr(a))); return a; } REALLY_INLINE Atom MAKE_INTEGER(intptr_t i) { AvmAssert(atomIsValidIntptrValue(i)); return (intptr_t(i) << 3) | kIntptrType; } #else #define CHECK_INT_ATOM(a) (a) #define MAKE_INTEGER(v) ((intptr_t(v) << 3) | kIntptrType) #endif #define IS_INTEGER(v) (((v) & 7) == kIntptrType) #define IS_DOUBLE(v) (((v) & 7) == kDoubleType) #define IS_BOOLEAN(v) (((v) & 7) == kBooleanType) #define IS_STRING(v) (((v) & 7) == kStringType) #ifdef VMCFG_FLOAT #define IS_FLOAT(v) ((((v) & 7) == kSpecialBibopType) && ((v)!=AtomConstants::undefinedAtom) && bibopKind(v)==kBibopFloatType) #define IS_FLOAT4(v) ((((v) & 7) == kSpecialBibopType) && ((v)!=AtomConstants::undefinedAtom) && bibopKind(v)==kBibopFloat4Type) #endif // VMCFG_FLOAT // note that the argument to SIGN_EXTEND is expected to be upshifted 3 bits (not a "raw" intptr), // but it doesn't expect or require the tag bits to be set properly. #ifdef AVMPLUS_64BIT // since 64-bit int atoms expect exactly 54 bits of precision, we want to shift bit 54+3 up into the sign bit and back down # define SIGN_EXTEND(v) ((intptr_t(v) << (atomSignExtendShift-AtomConstants::kAtomTypeSize)) >> (atomSignExtendShift-AtomConstants::kAtomTypeSize)) #else # define SIGN_EXTEND(v) (intptr_t(v)) #endif // CLAMP_32 is equivalent to running an int atom thru AvmCore::integer (ie, truncate to int32_t using the right rules), // but, like SIGN_EXTEND, it expects the argument to be upshifted 3 bit. #ifdef AVMPLUS_64BIT # define CLAMP_32(v) ((intptr_t(v) << (32-AtomConstants::kAtomTypeSize)) >> (32-AtomConstants::kAtomTypeSize)) #else # define CLAMP_32(v) (intptr_t(v)) #endif #define INT32_VALUE(v) int32_t(atomGetIntptr(v)) #define UINT32_VALUE(v) uint32_t(atomGetIntptr(v)) #define DOUBLE_VALUE(v) (*(double*)((v) ^ kDoubleType)) #ifdef VMCFG_FLOAT #define FLOAT_VALUE(v) (*(float*)((v) ^ kSpecialBibopType)) #endif #define IS_BOTH_KIND(a,b,k) (((( (a) ^ (k) ) | ( (b) ^ (k) )) & 7) == 0) // less control flow but more registers -- which is better? #define IS_BOTH_INTEGER(a,b) IS_BOTH_KIND(a,b,kIntptrType) #define IS_BOTH_DOUBLE(a,b) IS_BOTH_KIND(a,b,kDoubleType) #ifdef VMCFG_FLOAT #define IS_BOTH_BIBOP(a,b,k) IS_BOTH_KIND(a,b,kSpecialBibopType) && \ ((a)!=AtomConstants::undefinedAtom) && ((b)!=AtomConstants::undefinedAtom) \ && bibopKind(a)==k && bibopKind(b)==k #define IS_BOTH_FLOAT(a,b) IS_BOTH_BIBOP(a,b,kBibopFloatType) #define IS_BOTH_FLOAT4(a,b) IS_BOTH_BIBOP(a,b,kBibopFloat4Type) #endif // VMCFG_FLOAT #ifdef VMCFG_WORDCODE # define WORD_CODE_ONLY(x) x # define ABC_CODE_ONLY(x) #else # define WORD_CODE_ONLY(x) # define ABC_CODE_ONLY(x) x #endif #ifdef VMCFG_WORDCODE_PEEPHOLE # define PEEPHOLE_ONLY(x) x #else # define PEEPHOLE_ONLY(x) #endif #ifdef DEBUGGER # define DEBUGGER_ONLY(x) x # define NONDEBUGGER_ONLY(x) #else # define DEBUGGER_ONLY(x) # define NONDEBUGGER_ONLY(x) x #endif #ifdef DEBUG # define DEBUG_ONLY(x) x #else # define DEBUG_ONLY(x) #endif #ifdef VMCFG_WORDCODE typedef uintptr_t bytecode_t; #else typedef uint8_t bytecode_t; #endif #ifndef VMCFG_WORDCODE inline intptr_t readS24(const uint8_t* pc) { return AvmCore::readS24(pc); } inline uintptr_t readU30(const uint8_t*& pc) { return AvmCore::readU32(pc); } #endif // Direct threading in the interpreter. // // If you have gcc, direct threading should work out of the box and // should provide a nice speedup with many platforms and compiler versions. // // If you are using Microsoft Visual C/C++ then you may turn on direct // threading, and you must select one of the two threading implementations // below. MSVC_X86_ASM_THREADING should "just work" but is likely // to be slower than switch dispatch, unless you're using a compiler // version with little or no optimization. MSVC_X86_REWRITE_THREADING // usually improves the performance over switch dispatch, but requires // a fair amount of manual work if core/Interpreter.cpp has been modified // since core/FastInterpreter.asm was generated. The specific work // needed to regenerate core/FastInterpreter.cpp is described in a comment // at the head of utils/x86rewrite.as, which you will need to run. #ifdef VMCFG_DIRECT_THREADED # ifndef VMCFG_WORDCODE # error "Need word code enabled for this" # endif # if defined __GNUC__ # define GNUC_THREADING # define DIRECT_DISPATCH # elif defined AVMPLUS_WIN32 // Pick one of the two following options //# define MSVC_X86_ASM_THREADING # define MSVC_X86_REWRITE_THREADING # ifdef MSVC_X86_ASM_THREADING # define DIRECT_DISPATCH # endif # ifdef MSVC_X86_REWRITE_THREADING # define SWITCH_DISPATCH # endif # else # error "Threaded code not supported for this platform/compiler" # endif #else # define SWITCH_DISPATCH #endif // compiler/platform vipers' nest static Atom* initMultiname(MethodEnv* env, Multiname &name, Atom* sp); static Atom* initMultinameNoXMLList(MethodEnv* env, Multiname &name, Atom* sp); static Traits* getTraits(const Multiname* name, PoolObject* pool, Toplevel* toplevel, AvmCore* core); #ifdef AVMPLUS_VERBOSE // display contents of current stack frame only static void showState(MethodInfo* info, const bytecode_t *code_start, const bytecode_t *pc, Atom* framep, Atom *spp, int scopeDepth, Atom *scopebasep, int max_scope); #endif /** * on a backwards branch, check if the interrupt flag is enabled. * used by interpreter only. A copy of this is inline-generated * by CodegenLIR at loop headers. */ REALLY_INLINE void branchCheck(AvmCore* core, MethodEnv *env, bool interruptable) { #ifdef DEBUGGER core->sampleCheck(); #endif if (core->interruptCheck(interruptable)) core->handleInterruptMethodEnv(env); } #ifdef _MSC_VER # ifdef MSVC_X86_ASM_THREADING # pragma warning(disable:4740) // "inline assembler suppresses global optimization" # endif #endif // _MSC_VER #ifdef VMCFG_DIRECT_THREADED void** interpGetOpcodeLabels() { // *** NOTE ON THREAD SAFETY *** // // If we ever enable direct threading for a platform where the thread jump targets // cannot be constants in a vector but must be written into a vector the first time // interpGetOpcodeLabels() is called, then the following call must be within a // critical section (or some startup code must make an initial call to make sure // the vector is initialized in a thread safe way). At this time, Visual C++ // requires this, but we're not using direct threading with Visual C++, so there // is no critical section here. // // The startup code to make the initial call can simply call this function and // assign the result to a dummy static global variable; that trick is not portable // but it can be used for many platforms (and is used elsewhere). #if defined MSVC_X86_ASM_THREADING || defined MSVC_X86_REWRITE_THREADING #error "interpGetOpcodeLabels needs a critical section or eager initialization for this compiler / platform combination" #endif return (void**)interpBoxed(NULL, 0, NULL); } #endif // VMCFG_DIRECT_THREADED #ifdef VMCFG_NANOJIT # define OSR(offset) \ if (OSR::countEdge(env, info, ms) && \ OSR::execute(env, framep, ms, pc + (offset), &a1l)) { \ methodFrame.setCurrent(); \ a1 = a1l; \ goto return_value_from_interpreter; \ } #else # define OSR(i1) #endif Atom interpBoxed(register MethodEnv* env, register int _argc, register Atom* _atomv) { #ifdef VMCFG_DIRECT_THREADED // If env is NULL return the jump table. Optionally initialize it here on those // platforms where compile-time initialization is not possible or practical. if (env == NULL) { # if defined GNUC_THREADING # define III(idx, lbl) &&lbl, # define XXX(idx) &&L_illegal_op, static void* opcode_labels[] = { # elif defined MSVC_X86_ASM_THREADING || defined MSVC_X86_REWRITE_THREADING static void* opcode_labels[WOP_LAST+1]; if (opcode_labels[0] == 0) { # define XXX(idx) III(idx, L_illegal_op) # ifdef MSVC_X86_ASM_THREADING # define III(idx, lbl) __asm { \ __asm mov eax, offset opcode_labels \ __asm mov ebx, offset lbl \ __asm mov [eax+4*idx], ebx \ } # else extern bool LLLLABEL(int); # define III(a,b) extern void LLLLABEL ## _ ## a ## _ ## b(); LLLLABEL ## _ ## a ## _ ## b(); # endif # endif // threading discipline # define IIM(a,b) III(a,b) # if defined VMCFG_WORDCODE_PEEPHOLE # define IIP(a,b) III(a,b) # else # define IIP(a,b) XXX(a) # endif # if defined DEBUGGER || !defined VMCFG_WORDCODE # define IID(a,b) III(a,b) # else # define IID(a,b) XXX(a) # endif XXX(0x00) XXX(0x01) /* OP_bkpt */ III(0x02, L_nop) III(0x03, L_throw) III(0x04, L_getsuper) III(0x05, L_setsuper) III(0x06, L_dxns) III(0x07, L_dxnslate) III(0x08, L_kill) XXX(0x09) /* OP_label */ #ifdef VMCFG_FLOAT III(0x0A, L_lf32x4) III(0x0B, L_sf32x4) #else XXX(0x0A) XXX(0x0B) #endif III(0x0C, L_ifnlt) III(0x0D, L_ifnle) III(0x0E, L_ifngt) III(0x0F, L_ifnge) III(0x10, L_jump) III(0x11, L_iftrue) III(0x12, L_iffalse) III(0x13, L_ifeq) III(0x14, L_ifne) III(0x15, L_iflt) III(0x16, L_ifle) III(0x17, L_ifgt) III(0x18, L_ifge) III(0x19, L_ifstricteq) III(0x1A, L_ifstrictne) III(0x1B, L_lookupswitch) III(0x1C, L_pushwith) III(0x1D, L_popscope) III(0x1E, L_nextname) III(0x1F, L_hasnext) III(0x20, L_pushnull) III(0x21, L_pushundefined) #ifdef VMCFG_FLOAT III(0x22, L_pushfloat) #else XXX(0x22) #endif III(0x23, L_nextvalue) XXX(0x24) /* OP_pushbyte */ XXX(0x25) /* OP_pushshort */ III(0x26, L_pushtrue) III(0x27, L_pushfalse) III(0x28, L_pushnan) III(0x29, L_pop) III(0x2A, L_dup) III(0x2B, L_swap) III(0x2C, L_pushstring) XXX(0x2D) /* OP_pushint */ XXX(0x2E) /* OP_pushuint */ III(0x2F, L_pushdouble) III(0x30, L_pushscope) III(0x31, L_pushnamespace) III(0x32, L_hasnext2) XXX(0x33) XXX(0x34) IIM(0x35, L_li8) IIM(0x36, L_li16) IIM(0x37, L_li32) IIM(0x38, L_lf32) IIM(0x39, L_lf64) IIM(0x3A, L_si8) IIM(0x3B, L_si16) IIM(0x3C, L_si32) IIM(0x3D, L_sf32) IIM(0x3E, L_sf64) XXX(0x3F) III(0x40, L_newfunction) III(0x41, L_call) III(0x42, L_construct) III(0x43, L_callmethod) III(0x44, L_callstatic) III(0x45, L_callsuper) III(0x46, L_callproperty) III(0x47, L_returnvoid) III(0x48, L_returnvalue) III(0x49, L_constructsuper) III(0x4A, L_constructprop) XXX(0x4B) /* OP_callsuperid */ III(0x4C, L_callproplex) XXX(0x4D) /* OP_callinterface */ III(0x4E, L_callsupervoid) III(0x4F, L_callpropvoid) IIM(0x50, L_sxi1) IIM(0x51, L_sxi8) IIM(0x52, L_sxi16) III(0x53, L_applytype) #ifdef VMCFG_FLOAT III(0x54, L_pushfloat4) #else XXX(0x54) #endif III(0x55, L_newobject) III(0x56, L_newarray) III(0x57, L_newactivation) III(0x58, L_newclass) III(0x59, L_getdescendants) III(0x5A, L_newcatch) XXX(0x5B) XXX(0x5C) III(0x5D, L_findpropstrict) III(0x5E, L_findproperty) III(0x5F, L_finddef) III(0x60, L_getlex) III(0x61, L_setproperty) III(0x62, L_getlocal) III(0x63, L_setlocal) III(0x64, L_getglobalscope) III(0x65, L_getscopeobject) III(0x66, L_getproperty) III(0x67, L_getouterscope) III(0x68, L_initproperty) XXX(0x69) III(0x6A, L_deleteproperty) XXX(0x6B) III(0x6C, L_getslot) III(0x6D, L_setslot) III(0x6E, L_getglobalslot) III(0x6F, L_setglobalslot) III(0x70, L_convert_s) III(0x71, L_esc_xelem) III(0x72, L_esc_xattr) III(0x73, L_convert_i) III(0x74, L_convert_u) III(0x75, L_convert_d) III(0x76, L_convert_b) III(0x77, L_convert_o) III(0x78, L_checkfilter) #ifdef VMCFG_FLOAT III(0x79, L_convert_f) III(0x7A, L_unplus) III(0x7B, L_convert_f4) #else XXX(0x79) XXX(0x7A) XXX(0x7B) #endif XXX(0x7C) XXX(0x7D) XXX(0x7E) XXX(0x7F) III(0x80, L_coerce) III(0x81, L_convert_b) // coerce_b -> convert_b, they are the same XXX(0x82) /* OP_coerce_a */ III(0x83, L_convert_i) // coerce_i -> convert_i, they are the same III(0x84, L_convert_d) // coerce_d -> convert_d, they are the same III(0x85, L_coerce_s) III(0x86, L_astype) III(0x87, L_astypelate) III(0x88, L_convert_u) // coerce_u -> convert_u, they are the same III(0x89, L_coerce_o) XXX(0x8A) XXX(0x8B) XXX(0x8C) XXX(0x8D) XXX(0x8E) XXX(0x8F) III(0x90, L_negate) III(0x91, L_increment) III(0x92, L_inclocal) III(0x93, L_decrement) III(0x94, L_declocal) III(0x95, L_typeof) III(0x96, L_not) III(0x97, L_bitnot) XXX(0x98) XXX(0x99) XXX(0x9A) XXX(0x9B) XXX(0x9C) XXX(0x9D) XXX(0x9E) XXX(0x9F) III(0xA0, L_add) III(0xA1, L_subtract) III(0xA2, L_multiply) III(0xA3, L_divide) III(0xA4, L_modulo) III(0xA5, L_lshift) III(0xA6, L_rshift) III(0xA7, L_urshift) III(0xA8, L_bitand) III(0xA9, L_bitor) III(0xAA, L_bitxor) III(0xAB, L_equals) III(0xAC, L_strictequals) III(0xAD, L_lessthan) III(0xAE, L_lessequals) III(0xAF, L_greaterthan) III(0xB0, L_greaterequals) III(0xB1, L_instanceof) III(0xB2, L_istype) III(0xB3, L_istypelate) III(0xB4, L_in) XXX(0xB5) XXX(0xB6) XXX(0xB7) XXX(0xB8) XXX(0xB9) XXX(0xBA) XXX(0xBB) XXX(0xBC) XXX(0xBD) XXX(0xBE) XXX(0xBF) III(0xC0, L_increment_i) III(0xC1, L_decrement_i) III(0xC2, L_inclocal_i) III(0xC3, L_declocal_i) III(0xC4, L_negate_i) III(0xC5, L_add_i) III(0xC6, L_subtract_i) III(0xC7, L_multiply_i) XXX(0xC8) XXX(0xC9) XXX(0xCA) XXX(0xCB) XXX(0xCC) XXX(0xCD) XXX(0xCE) XXX(0xCF) III(0xD0, L_getlocal0) III(0xD1, L_getlocal1) III(0xD2, L_getlocal2) III(0xD3, L_getlocal3) III(0xD4, L_setlocal0) III(0xD5, L_setlocal1) III(0xD6, L_setlocal2) III(0xD7, L_setlocal3) XXX(0xD8) XXX(0xD9) XXX(0xDA) XXX(0xDB) XXX(0xDC) XXX(0xDD) XXX(0xDE) XXX(0xDF) XXX(0xE0) XXX(0xE1) XXX(0xE2) XXX(0xE3) XXX(0xE4) XXX(0xE5) XXX(0xE6) XXX(0xE7) XXX(0xE8) XXX(0xE9) XXX(0xEA) XXX(0xEB) XXX(0xEC) XXX(0xED) XXX(0xEE) IID(0xEF, L_debug) IID(0xF0, L_debugline) IID(0xF1, L_debugfile) XXX(0xF2) /* OP_bkptline */ XXX(0xF3) /* OP_timestamp */ XXX(0xF4) XXX(0xF5) XXX(0xF6) XXX(0xF7) XXX(0xF8) XXX(0xF9) XXX(0xFA) XXX(0xFB) XXX(0xFC) XXX(0xFD) XXX(0xFE) XXX(0xFF) /* OP_ext */ XXX(0x100) III(0x101, L_pushbits) III(0x102, L_push_doublebits) IIP(0x103, L_get2locals) IIP(0x104, L_get3locals) IIP(0x105, L_get4locals) IIP(0x106, L_get5locals) IIP(0x107, L_storelocal) IIP(0x108, L_add_ll) IIP(0x109, L_add_set_lll) IIP(0x10A, L_subtract_ll) IIP(0x10B, L_multiply_ll) IIP(0x10C, L_divide_ll) IIP(0x10D, L_modulo_ll) IIP(0x10E, L_bitand_ll) IIP(0x10F, L_bitor_ll) IIP(0x110, L_bitxor_ll) IIP(0x111, L_add_lb) IIP(0x112, L_subtract_lb) IIP(0x113, L_multiply_lb) IIP(0x114, L_divide_lb) IIP(0x115, L_bitand_lb) IIP(0x116, L_bitor_lb) IIP(0x117, L_bitxor_lb) IIP(0x118, L_iflt_ll) IIP(0x119, L_ifnlt_ll) IIP(0x11A, L_ifle_ll) IIP(0x11B, L_ifnle_ll) IIP(0x11C, L_ifgt_ll) IIP(0x11D, L_ifngt_ll) IIP(0x11E, L_ifge_ll) IIP(0x11F, L_ifnge_ll) IIP(0x120, L_ifeq_ll) IIP(0x121, L_ifne_ll) IIP(0x122, L_ifstricteq_ll) IIP(0x123, L_ifstrictne_ll) IIP(0x124, L_iflt_lb) IIP(0x125, L_ifnlt_lb) IIP(0x126, L_ifle_lb) IIP(0x127, L_ifnle_lb) IIP(0x128, L_ifgt_lb) IIP(0x129, L_ifngt_lb) IIP(0x12A, L_ifge_lb) IIP(0x12B, L_ifnge_lb) IIP(0x12C, L_ifeq_lb) IIP(0x12D, L_ifne_lb) IIP(0x12E, L_ifstricteq_lb) IIP(0x12F, L_ifstrictne_lb) IIP(0x130, L_swap_pop) III(0x131, L_findpropglobal) III(0x132, L_findpropglobalstrict) #if defined DEBUGGER && defined VMCFG_WORDCODE IID(0x133, L_debugenter) IID(0x134, L_debugexit) #else XXX(0x133) XXX(0x134) #endif III(0x135, L_lix8) III(0x136, L_lix16) #ifdef VMCFG_FLOAT III(0x137, L_float4) #else XXX(0x137) #endif # if defined GNUC_THREADING }; AvmAssert(opcode_labels[0x18] == &&L_ifge); AvmAssert(opcode_labels[0x97] == &&L_bitnot); AvmAssert(opcode_labels[257] == &&L_pushbits); # ifdef VMCFG_WORDCODE_PEEPHOLE AvmAssert(opcode_labels[48 + 256] == &&L_swap_pop); # endif # elif defined MSVC_X86_ASM_THREADING || defined MSVC_X86_REWRITE_THREADING } // conditional run-time initialization of jump table # endif // threading discipline return (Atom)opcode_labels; } // env == 0? #endif // !VMCFG_DIRECT_THREADED // These are local variables that are allocated to alloca'd memory; // if alloca() is the real alloca() then that makes no difference, but // when alloca() is redirected to the heap it makes stack frames smaller, // which matters on systems with short stacks. // // Storage that is conditionally initialized or rarely used may be moved // into this structure, to balance the benefit (smaller stack frames) // with the costs (more expensive access, slightly larger code). // // Be careful: if a member has a destructor then an auto_ptr like mechanism is // required to invoke the destructor. The ExceptionFrame gets such a mechanism // when the TRY_UNLESS_HEAPMEM macro is used to initialize it. struct InterpreterAuxiliaryFrame { MethodFrame methodFrame; ExceptionFrame ef; Multiname multiname2; }; #ifdef DEBUGGER struct InterpreterAuxiliaryFrameWithCSN : public InterpreterAuxiliaryFrame { inline InterpreterAuxiliaryFrameWithCSN() : cs(CallStackNode::kEmpty) {} CallStackNode cs; }; #endif // OPTIMIZEME - opportunity to compute some information only when needed. // // Some of these, notably cpool_double and envDomain, are cached because // programs that use them tend to be quite a bit slower if they're not cached. (In the // ABC interpreter we should also cache cpool_int and cpool_uint.) But // the caching slows down function-heavy programs that don't use them. It may // be fruitful to have a bit in the info that tells the interpreter whether to // set these up or not. register AvmCore* const core = env->core(); register Toplevel* const toplevel = env->toplevel(); register MethodInfo* const info = env->method; register PoolObject* const pool = info->pool(); #ifdef DEBUGGER const size_t kAuxFrameSize = core->debugger() ? sizeof(InterpreterAuxiliaryFrameWithCSN) : sizeof(InterpreterAuxiliaryFrame); #else const size_t kAuxFrameSize = sizeof(InterpreterAuxiliaryFrame); #endif #ifdef AVMPLUS_VERBOSE if (pool->isVerbose(VB_interp, info)) core->console << "interp " << info << '\n'; #endif // always do a stack check; this not only checks true stack overflows // but also the stack limit is used to gain control for host interrupts. // so this stack check doubles as an interrupt check. // see also: AvmCore::handleStackOverflow(). core->stackCheck(env); register GCList const & cpool_double = pool->cpool_double; register const bool interruptable = !info->isNonInterruptible(); register const DomainEnv* envDomain = env->domainEnv(); // I do *not* like making pc 'volatile'; a smart compiler may handle it well // and only spill to memory across a call, but a dumb compiler may not ever #ifdef VMCFG_STACK_METRICS core->recordStackPointer(); #endif // keep the value in a register at all. MethodSignaturep volatile ms = env->method->getMethodSignature(); #if !defined VMCFG_WORDCODE || defined AVMPLUS_VERBOSE #ifdef VMCFG_WORDCODE register const bytecode_t* volatile codeStart = info->word_code_start(); #else register const bytecode_t* volatile codeStart = ms->abc_code_start(); #endif #endif #ifdef VMCFG_WORDCODE register const bytecode_t* /* NOT VOLATILE */ pc = info->word_code_start(); #else register const bytecode_t* /* NOT VOLATILE */ pc = ms->abc_code_start(); #endif intptr_t volatile expc=0; MMgc::GC::AllocaAutoPtr _framep; #ifdef AVMPLUS_64BIT // Allocation is guaranteed on an 8-byte boundary, but we need 16 for _setjmpex. // So allocate 8 bytes extra, then round up to a 16-byte boundary. register Atom* const framep = (Atom*)avmStackAlloc(core, _framep, sizeof(Atom)*(ms->frame_size()) + 8 + kAuxFrameSize); register InterpreterAuxiliaryFrame* const aux_memory = (InterpreterAuxiliaryFrame*)(((uintptr_t)(framep + ms->frame_size()) + 15) & ~15); #else register Atom* const framep = (Atom*)avmStackAlloc(core, _framep, sizeof(Atom)*(ms->frame_size()) + kAuxFrameSize); union { Atom* fa; InterpreterAuxiliaryFrame* fi; }; fa = framep + ms->frame_size(); register InterpreterAuxiliaryFrame* const aux_memory = (InterpreterAuxiliaryFrame*)fi; #endif // It's essential that the MethodFrame is cleaned up upon normal exit, // to keep core->currentMethodFrame in order. A throw past the frame // will not perform cleanup. A manual call just before returning // is *not* adequate, as we need to be able to call // aux_memory->methodFrame->exit() *after* our TRY/CATCH code has // completed (otherwise, it may attempt to "restore" currentStack // to a bogus value). Using a real dtor here ensures we are called // after any endTry(). EnterMethodEnv methodFrame(core, env, aux_memory->methodFrame); register Atom* const scopeBase = framep + ms->local_count(); register Atom* volatile withBase = NULL; NONDEBUGGER_ONLY( register ) int volatile scopeDepth = 0; register ScopeChain* const scope = env->scope(); // Compute base of operand stack. register Atom* /* NOT VOLATILE */ sp = scopeBase + ms->max_scope() - 1; // We used to assume that scope 0 (the global scope) must be a ScriptObject*, // but this is not the case: it can legitimately be a String* or a primitive. // (Note that no scope can be null or undefined, however.) Atom /* NOT VOLATILE */ globalScopeAtom = (scope->getSize() > 0) ? scope->getScope(0) : nullObjectAtom; // OPTIMIZEME - opportunities for streamlining the function entry code. // // * With unbox/box optimization introduced and alloca removed so // that the parameter are as on the heap, we could overlap the // outgoing parameter area with the incoming locals. This avoids // copying, and will be a win if we don't have to work hard to // figure out when it's applicable. // // * A minor point is that rest / arguments could possibly be // created by a special opcode so that those flags don't have to be // checked here. Unlikely to be a time sink. // // Edwin has suggested that the interp() function should simply take // a boxed argument array always and that the call code should always // pass a boxed argument array. We'd end up with two entry points, // one for boxed entry and one for unboxed entry. For interpreted // code the boxed entry would jump straight into this function, and // the unboxed entry would go through reboxing. For compiled code it // would be the other way around, probably. { // Copy instance and args to local frame const int param_count = ms->param_count(); for (int i=0, n = _argc < param_count ? _argc : param_count; i <= n; i++) framep[i] = _atomv[i]; // Store original value of argc for createRest and createArguments. // argc may be changed by the optional parameter check below. int arguments_argc = _argc; // Set optional param values. these not aliased to arguments[] since arguments[] // only present with traditional prototype functions (no optional args) if (info->hasOptional()) { if (_argc < param_count) { // initialize default values for (int i=_argc+1, o=_argc + ms->optional_count() - param_count, n=param_count; i <= n; i++, o++) framep[i] = ms->getDefaultValue(o); _argc = param_count; } } // Set remaining locals to undefined. Don't have to init scope or stack because // our conservative GC scan knows how to ignore garbage. for (Atom *p = framep + 1 + param_count; p < scopeBase; p++) *p = undefinedAtom; // Capture arguments or rest array. if (info->needRest()) { framep[param_count+1] = env->createRest(_atomv, arguments_argc)->atom(); } else if (info->needArguments()) { // create arguments using atomv[1..argc]. // Even tho E3 says create an Object, E4 says create an Array so thats what we will do. framep[param_count+1] = env->createArguments(_atomv, arguments_argc)->atom(); } #ifdef DEBUGGER // in debugger builds, ensure that non-active scope entries are nulled out // (really only need to do so if there's a debugger active, but not really worth checking for) for (int i = 0; i < ms->max_scope(); ++i) scopeBase[i] = nullObjectAtom; #endif } #ifdef DEBUGGER CallStackNode* volatile callStackNode = NULL; #ifndef VMCFG_WORDCODE if (core->debugger()) { callStackNode = new ((char*)aux_memory + offsetof(InterpreterAuxiliaryFrameWithCSN, cs)) CallStackNode(env, (FramePtr)framep, /*frameTraits*/0, &expc); env->debugEnterInner(); } #endif #endif // NEXT dispatches the next instruction. // // U30ARG picks up a variable-length unsigned integer argument from the instruction // stream and advances the PC. // // U8ARG picks up a fixed-length unsigned byte argument from the instruction stream // and advances the PC. // // S24ARG picks up a fixed-length signed integer argument from the instruction stream // and advances the PC. // // SAVE_EXPC and variants saves the address of the current opcode in the local 'expc'. // Used in the case of exceptions. #ifdef AVMPLUS_VERBOSE # define VERBOSE if (pool->isVerbose(VB_interp, info)) showState(info, codeStart, pc-1, framep, sp, scopeDepth, scopeBase, ms->max_scope()) #else # define VERBOSE #endif #ifdef VMCFG_WORDCODE # if defined VMCFG_DIRECT_THREADED # if defined GNUC_THREADING # define INSTR(op) L_##op: VERBOSE; # define NEXT goto *(*pc++) # elif defined MSVC_X86_REWRITE_THREADING # define INSTR(op) case WOP_##op: L_ ## op: VERBOSE; # define NEXT continue # elif defined MSVC_X86_ASM_THREADING # define INSTR(op) L_ ## op: VERBOSE; # define NEXT __asm { \ __asm mov ebx, pc \ __asm mov eax, [ebx] \ __asm add ebx, 4 \ __asm mov pc, ebx \ __asm jmp eax \ } # endif // threading discipline # else // VMCFG_DIRECT_THREADED # define INSTR(op) case WOP_##op: VERBOSE; # define NEXT continue # endif # define U30ARG (*pc++) # define U8ARG (*pc++) # define S24ARG (intptr_t)(*pc++) # ifdef DEBUGGER // expc is visible outside this function, so make sure it's correct. // It's probably possible to adjust it on demand outside the function too, // because code that accesses it will have access to "info" and can // perform the adjustment. # define SAVE_EXPC expc = pc-1-info->word_code_start() # define SAVE_EXPC_TARGET(off) expc = pc + (off) - info->word_code_start() # else // Adjusted on demand in the CATCH clause. Reduces size of interpreter function // by 2.5KB of object code (x86 / gcc4.0 / -O3). # define SAVE_EXPC expc = (intptr_t)pc # define SAVE_EXPC_TARGET(off) expc = (intptr_t)(pc + (off) + 1) # endif #else // !VMCFG_WORDCODE # define INSTR(op) case OP_##op: VERBOSE; # define NEXT continue # define U30ARG (tmp_pc=pc, tmp_u30 = uint32_t(readU30(tmp_pc)), pc = tmp_pc, tmp_u30) # define U8ARG (*pc++) # define S24ARG (pc+=3, readS24(pc-3)) # define SAVE_EXPC expc = pc-1-codeStart # define SAVE_EXPC_TARGET(off) expc = pc + (off) - codeStart #endif // VMCFG_WORDCODE // The following variables are here for the following purposes: // // - parameter passing between shared bytecode bodies // // - reducing the size of the stack frame on compilers that allocate // individual frame slots to individual block-scoped variables. // Visual C++ is particularly bad. // // Please do not initialize these variables. // // Please do not remove the 'register' qualifier. The variables are so // qualified so that their addresses can't be taken. // // Please do not try to put any of these in a union together. register Atom a1, a2, a3; register Atom* a2p; register intptr_t i1, i2; register uintptr_t u1, u2; register uintptr_t u1t, u2t, u3t; // private to the generic arithmetic macros register double d1, d2; register bool b1; register uint8_t ub2; register ScriptObject *o1; register const Multiname* multiname; register MethodEnv* f; register Traits* t1; uint16_t uh2l; // not register - its address /can/ be taken int32_t i32l; // ditto float f2l; // ditto double d2l; // ditto #ifdef VMCFG_FLOAT float4_t f4l; // ditto #endif Atom a1l; // ditto #ifndef VMCFG_WORDCODE register uint32_t tmp_u30; const bytecode_t* tmp_pc; #endif MainLoop: #ifdef VMCFG_WORDCODE TRY_UNLESS_HEAPMEM((char*)aux_memory + offsetof(InterpreterAuxiliaryFrame, ef), core, !info->word_code_exceptions(), kCatchAction_SearchForActionScriptExceptionHandler) { #else TRY_UNLESS_HEAPMEM((char*)aux_memory + offsetof(InterpreterAuxiliaryFrame, ef), core, !info->abc_exceptions(), kCatchAction_SearchForActionScriptExceptionHandler) { #endif #ifdef DIRECT_DISPATCH NEXT; #else for (;;) { # ifdef SUPERWORD_PROFILING WordcodeTranslator::swprofPC(pc); # endif # ifdef VMCFG_WORDCODE AvmAssert((*pc & 65535) == ((*pc >> 16) & 65535)); switch ((*pc++) & 65535) # else switch (*pc++) # endif { #endif // dispatch INSTR(returnvoid) { a1 = undefinedAtom; goto return_value_from_interpreter; } INSTR(returnvalue) { a1 = *sp; return_value_from_interpreter: #if defined DEBUGGER && !defined VMCFG_WORDCODE if (callStackNode) { env->debugExit(callStackNode); callStackNode->reset(); } #endif SAVE_EXPC; a1 = toplevel->coerce(a1, ms->returnTraits()); #ifdef AVMPLUS_VERBOSE if (pool->isVerbose(VB_interp, info)) core->console << "exit " << info << '\n'; #endif return a1; } INSTR(nop) { NEXT; } #ifndef VMCFG_WORDCODE INSTR(label) { NEXT; } #endif #ifndef VMCFG_WORDCODE INSTR(timestamp) { NEXT; } #endif #ifndef VMCFG_WORDCODE INSTR(bkpt) { NEXT; } #endif #ifndef VMCFG_WORDCODE INSTR(bkptline) { (void)U30ARG; NEXT; } #endif #ifndef VMCFG_WORDCODE INSTR(coerce_a) { // no-op since interpreter only uses atoms NEXT; } #endif #if defined DEBUGGER || !defined VMCFG_WORDCODE INSTR(debugline) { SAVE_EXPC; u1 = U30ARG; DEBUGGER_ONLY( if (core->debugger()) core->debugger()->debugLine((int32_t)u1); ) NEXT; } #endif #if defined DEBUGGER || !defined VMCFG_WORDCODE INSTR(debug) { WORD_CODE_ONLY( pc += 4 ); ABC_CODE_ONLY( pc += AvmCore::calculateInstructionWidth(pc-1) - 1 ); NEXT; } #endif #if defined DEBUGGER || !defined VMCFG_WORDCODE INSTR(debugfile) { SAVE_EXPC; u1 = U30ARG; DEBUGGER_ONLY( if (core->debugger()) core->debugger()->debugFile(pool->getString((int32_t)u1)); ) NEXT; } #endif #if defined DEBUGGER && defined VMCFG_WORDCODE INSTR(debugenter) { AvmAssert(core->debugger() != NULL); AvmAssert(callStackNode == NULL); callStackNode = new ((char*)aux_memory + offsetof(InterpreterAuxiliaryFrameWithCSN, cs)) CallStackNode(env, (FramePtr)framep, /*frameTraits*/0, &expc); env->debugEnterInner(); NEXT; } #endif #if defined DEBUGGER && defined VMCFG_WORDCODE INSTR(debugexit) { AvmAssert(core->debugger() != NULL); AvmAssert(callStackNode != NULL); env->debugExit(callStackNode); callStackNode->reset(); NEXT; } #endif INSTR(jump) { i1 = S24ARG; if (i1 < 0) { SAVE_EXPC_TARGET(i1); branchCheck(core, env, interruptable); OSR(i1); } pc += i1; NEXT; } INSTR(pushnull) { *(++sp) = nullObjectAtom; NEXT; } INSTR(pushundefined) { *(++sp) = undefinedAtom; NEXT; } INSTR(pushstring) { *(++sp) = pool->getString((uint32_t)U30ARG)->atom(); NEXT; } #ifndef VMCFG_WORDCODE INSTR(pushint) { *(++sp) = core->intToAtom(pool->cpool_int[(uint32_t)U30ARG]); NEXT; } #endif #ifndef VMCFG_WORDCODE INSTR(pushuint) { *(++sp) = core->uintToAtom(pool->cpool_uint[(uint32_t)U30ARG]); NEXT; } #endif INSTR(pushdouble) { *(++sp) = kDoubleType|(uintptr_t)cpool_double[(uint32_t)U30ARG]; NEXT; } #ifdef VMCFG_FLOAT INSTR(pushfloat) { AvmAssertMsg( pool->hasFloatSupport(), "float instruction(pushfloat), but float support disabled"); *(++sp) = kSpecialBibopType|(uintptr_t)pool->cpool_float[(uint32_t)U30ARG]; NEXT; } INSTR(pushfloat4) { AvmAssertMsg( pool->hasFloatSupport(), "float instruction(pushfloat4), but float support disabled"); *(++sp) = kSpecialBibopType|(uintptr_t)pool->cpool_float4[(uint32_t)U30ARG]; NEXT; } #endif INSTR(pushnamespace) { *(++sp) = pool->cpool_ns[(uint32_t)U30ARG]->atom(); NEXT; } INSTR(getlocal) { u1 = U30ARG; *(++sp) = framep[u1]; NEXT; } INSTR(getlocal0) { *(++sp) = framep[0]; NEXT; } INSTR(getlocal1) { *(++sp) = framep[1]; NEXT; } INSTR(getlocal2) { *(++sp) = framep[2]; NEXT; } INSTR(getlocal3) { *(++sp) = framep[3]; NEXT; } INSTR(pushtrue) { *(++sp) = trueAtom; NEXT; } INSTR(pushfalse) { *(++sp) = falseAtom; NEXT; } INSTR(pushnan) { *(++sp) = core->kNaN; NEXT; } INSTR(pop) { sp--; NEXT; } INSTR(dup) { sp++; sp[0] = sp[-1]; NEXT; } INSTR(swap) { a1 = sp[0]; sp[0] = sp[-1]; sp[-1] = a1; NEXT; } INSTR(convert_s) { SAVE_EXPC; sp[0] = core->string(sp[0])->atom(); NEXT; } INSTR(esc_xelem) { // ToXMLString will call EscapeElementValue SAVE_EXPC; sp[0] = core->ToXMLString(sp[0])->atom(); NEXT; } INSTR(esc_xattr) { SAVE_EXPC; sp[0] = core->EscapeAttributeValue(sp[0])->atom(); NEXT; } INSTR(convert_d) { ABC_CODE_ONLY( convert_d_impl: ) if (!IS_DOUBLE(sp[0])) { SAVE_EXPC; sp[0] = core->numberAtom(sp[0]); } NEXT; } #ifdef VMCFG_FLOAT INSTR(convert_f) { // Note the instruction may be generated from the verifier. if (!IS_FLOAT(sp[0])) { SAVE_EXPC; sp[0] = core->floatAtom(sp[0]); } NEXT; } INSTR(convert_f4) { // Note the instruction may be generated from the verifier. if (!IS_FLOAT4(sp[0])) { SAVE_EXPC; sp[0] = core->float4Atom(sp[0]); } NEXT; } INSTR(unplus) { // Note the instruction may be generated from the verifier. if (!IS_FLOAT(sp[0]) && !IS_FLOAT4(sp[0])) { SAVE_EXPC; sp[0] = core->numericAtom(sp[0]); } NEXT; } #endif #ifndef VMCFG_WORDCODE /* Jump table forwards to convert_d */ INSTR(coerce_d) { goto convert_d_impl; } #endif INSTR(convert_b) { ABC_CODE_ONLY( convert_b_impl: ) a1 = sp[0]; // boolean value if (IS_BOOLEAN(a1)) ; else if (IS_INTEGER(a1)) sp[0] = a1 == zeroIntAtom ? falseAtom : trueAtom; else sp[0] = AvmCore::booleanAtom(a1); NEXT; } #ifndef VMCFG_WORDCODE /* Jump table forwards to convert_b */ INSTR(coerce_b) { goto convert_b_impl; } #endif INSTR(convert_o) { if (AvmCore::isNullOrUndefined(sp[0])) { SAVE_EXPC; env->nullcheck(sp[0]); } NEXT; } INSTR(negate) { a1 = sp[0]; if (IS_INTEGER(a1) && a1 != zeroIntAtom) { i1 = -atomGetIntptr(a1); // *not* INT32_VALUE if (atomIsValidIntptrValue(i1)) { sp[0] = MAKE_INTEGER(i1); NEXT; } } SAVE_EXPC; #ifdef VMCFG_FLOAT if(pool->hasFloatSupport() ){ SAVE_EXPC; sp[0] = op_negate(core, a1); NEXT; } #endif sp[0] = core->doubleToAtom(-AvmCore::number(a1)); NEXT; } INSTR(negate_i) { // OPTIMIZEME - negate_i SAVE_EXPC; sp[0] = core->intToAtom(-AvmCore::integer(sp[0])); NEXT; } INSTR(kill) { u1 = U30ARG; framep[u1] = undefinedAtom; NEXT; } INSTR(typeof) { *sp = core->_typeof(*sp)->atom(); NEXT; } INSTR(not) { a1 = sp[0]; // boolean value if (IS_BOOLEAN(a1)) ; else if (IS_INTEGER(a1)) a1 = a1 == zeroIntAtom ? falseAtom : trueAtom; else a1 = AvmCore::booleanAtom(a1); sp[0] = a1 ^ (trueAtom ^ falseAtom); NEXT; } INSTR(bitnot) { a1 = sp[0]; if (IS_INTEGER(a1)) { sp[0] = MAKE_INTEGER(~int32_t(atomGetIntptr(a1))); NEXT; } SAVE_EXPC; *sp = core->intToAtom(~AvmCore::integer(a1)); NEXT; } INSTR(setlocal) { u1 = U30ARG; framep[u1] = *(sp--); NEXT; } INSTR(setlocal0) { framep[0] = *(sp--); NEXT; } INSTR(setlocal1) { framep[1] = *(sp--); NEXT; } INSTR(setlocal2) { framep[2] = *(sp--); NEXT; } INSTR(setlocal3) { framep[3] = *(sp--); NEXT; } #define ADD_TWO_VALUES_AND_NEXT(a1, a2, dest) \ if (IS_BOTH_INTEGER(a1, a2)) { \ u1t = a1 ^ kIntptrType; \ u2t = a2 ^ kIntptrType; \ u3t = SIGN_EXTEND(u1t + u2t); \ if ((intptr_t)(u1t ^ u2t) < 0 || (intptr_t)(u1t ^ u3t) >= 0) { \ dest = CHECK_INT_ATOM(u3t | kIntptrType); \ NEXT; \ } \ } \ else if (IS_BOTH_DOUBLE(a1, a2)) { \ dest = core->doubleToAtom(DOUBLE_VALUE(a1) + DOUBLE_VALUE(a2)); \ NEXT; \ } \ FLOAT_ONLY(\ else if (pool->hasFloatSupport() ) { \ if ( IS_BOTH_FLOAT(a1, a2) ) {\ dest = core->floatToAtom(FLOAT_VALUE(a1) + FLOAT_VALUE(a2)); \ NEXT; \ }\ if(IS_BOTH_FLOAT4(a1,a2)){\ float4_decl_v(a1);\ float4_decl_v(a2);\ \ dest = core->float4ToAtom( f4_add(a1v,a2v) ); \ NEXT;\ }\ } else { /* pool doesn't have float support */ \ SAVE_EXPC; \ dest = op_add_nofloat(core, a1, a2);\ NEXT;\ }\ )\ SAVE_EXPC; \ dest = op_add(core, a1, a2);\ NEXT INSTR(increment) { SAVE_EXPC; a2p = sp; increment_impl: a1 = *a2p; if (IS_INTEGER(a1)) { i1 = atomGetIntptr(a1); if (i1 < atomMaxIntValue) *a2p = CHECK_INT_ATOM((intptr_t)a1 + (1 << 3)); else *a2p = core->doubleToAtom((double)i1 + 1.0); NEXT; } if (IS_DOUBLE(a1)) { *a2p = core->doubleToAtom(DOUBLE_VALUE(a1) + 1.0); NEXT; } #ifdef VMCFG_FLOAT if (pool->hasFloatSupport()) { a1 = core->numericAtom(a1); if (IS_FLOAT(a1)) { *a2p = core->floatToAtom(FLOAT_VALUE(a1) + 1.0f); NEXT; } if (IS_FLOAT4(a1)) { float4_t one = {1, 1, 1, 1}; *a2p = core->float4ToAtom(f4_add(AvmCore::atomToFloat4(a1), one)); NEXT; } } else #endif // VMCFG_FLOAT { a1 = core->numberAtom(a1); } *a2p = core->increment_number_d(a1, 1); NEXT; } INSTR(inclocal) { SAVE_EXPC; a2p = framep+U30ARG; goto increment_impl; } INSTR(increment_i) { SAVE_EXPC; a2p = sp; increment_i_impl: a1 = *a2p; if (IS_INTEGER(a1)) { #ifdef AVMPLUS_64BIT // Result always fits 64-bit kIntptrType atom. *a2p = CHECK_INT_ATOM(CLAMP_32((intptr_t)a1 + (1 << 3))); #else i1 = atomGetIntptr(a1); if (i1 < atomMaxIntValue) *a2p = CHECK_INT_ATOM((intptr_t)a1 + (1 << 3)); else *a2p = core->intToAtom((int32_t)i1 + 1); #endif NEXT; } if (IS_DOUBLE(a1)) { *a2p = core->intToAtom((int32_t)core->atomToDouble(a1) + 1); NEXT; } *a2p = core->increment_i(a1, 1); NEXT; } INSTR(inclocal_i) { SAVE_EXPC; a2p = framep+U30ARG; goto increment_i_impl; } INSTR(decrement) { SAVE_EXPC; a2p = sp; decrement_impl: a1 = *a2p; if (IS_INTEGER(a1)) { i1 = atomGetIntptr(a1); if (i1 > atomMinIntValue) *a2p = CHECK_INT_ATOM((intptr_t)a1 - (1 << 3)); else *a2p = core->doubleToAtom((double)i1 - 1.0); NEXT; } if (IS_DOUBLE(a1)) { *a2p = core->doubleToAtom(DOUBLE_VALUE(a1) - 1.0); NEXT; } #ifdef VMCFG_FLOAT if (pool->hasFloatSupport()) { a1 = core->numericAtom(a1); if (IS_FLOAT(a1)) { *a2p = core->floatToAtom(FLOAT_VALUE(a1) - 1.0f); NEXT; } if (IS_FLOAT4(a1)) { float4_t one = {1, 1, 1, 1}; *a2p = core->float4ToAtom(f4_sub(AvmCore::atomToFloat4(a1), one)); NEXT; } } else #endif // VMCFG_FLOAT { a1 = core->numberAtom(a1); } *a2p = core->increment_number_d(a1, -1); NEXT; } INSTR(declocal) { SAVE_EXPC; a2p = framep+U30ARG; goto decrement_impl; } INSTR(decrement_i) { SAVE_EXPC; a2p = sp; decrement_i_impl: a1 = *a2p; if (IS_INTEGER(a1)) { #ifdef AVMPLUS_64BIT // Result always fits 64-bit kIntptrType atom. *a2p = CHECK_INT_ATOM(CLAMP_32((intptr_t)a1 - (1 << 3))); #else i1 = atomGetIntptr(a1); if (i1 > atomMinIntValue) *a2p = CHECK_INT_ATOM((intptr_t)a1 - (1 << 3)); else *a2p = core->intToAtom((int32_t)i1 - 1); #endif NEXT; } if (IS_DOUBLE(a1)) { *a2p = core->intToAtom((int32_t)core->atomToDouble(a1) - 1); NEXT; } *a2p = core->increment_i(a1, -1); NEXT; } INSTR(declocal_i) { SAVE_EXPC; a2p = framep+U30ARG; goto decrement_i_impl; } INSTR(add) { a1 = sp[-1]; a2 = sp[0]; sp--; PEEPHOLE_ONLY( add_two_values_into_tos_impl: ) ADD_TWO_VALUES_AND_NEXT(a1, a2, sp[0]); } INSTR(add_i) { a1 = sp[-1]; a2 = sp[0]; sp--; if (IS_BOTH_INTEGER(a1, a2)) { u1t = a1 ^ kIntptrType; u2t = a2 ^ kIntptrType; u3t = CLAMP_32(u1t + u2t); sp[0] = CHECK_INT_ATOM(u3t | kIntptrType); NEXT; } if (IS_BOTH_DOUBLE(a1, a2)) { i1 = (int32_t)AvmCore::integer_d(DOUBLE_VALUE(a1)); i2 = (int32_t)AvmCore::integer_d(DOUBLE_VALUE(a2)); goto finish_add_i; } SAVE_EXPC; i1 = AvmCore::integer(a1); i2 = AvmCore::integer(a2); finish_add_i: sp[0] = core->intToAtom((int32_t)(i1 + i2)); NEXT; } INSTR(subtract) { a1 = sp[-1]; a2 = sp[0]; sp--; #ifdef VMCFG_WORDCODE_PEEPHOLE sub_two_values_and_next: #endif if (IS_BOTH_INTEGER(a1, a2)) { u1t = a1 ^ kIntptrType; u2t = a2 ^ kIntptrType; u3t = SIGN_EXTEND(u1t - u2t); if ((intptr_t)(u1t ^ u2t) >= 0 || (intptr_t)(u1t ^ u3t) >= 0) { sp[0] = CHECK_INT_ATOM(u3t | kIntptrType); NEXT; } } if (IS_BOTH_DOUBLE(a1, a2)) { sp[0] = core->doubleToAtom(DOUBLE_VALUE(a1) - DOUBLE_VALUE(a2)); NEXT; } SAVE_EXPC; #ifdef VMCFG_FLOAT if (pool->hasFloatSupport()){ a1 = core->numericAtom(a1); a2 = core->numericAtom(a2); if( IS_BOTH_FLOAT(a1, a2) ) { sp[0] = core->floatToAtom(FLOAT_VALUE(a1) - FLOAT_VALUE(a2)); NEXT; } if(AvmCore::isFloat4(a1) || AvmCore::isFloat4(a2)){ float4_decl_v(a1); float4_decl_v(a2); sp[0] = core->float4ToAtom( f4_sub(a1v,a2v) ); NEXT; } } #endif // VMCFG_FLOAT d1 = AvmCore::number(a1); d2 = AvmCore::number(a2); sp[0] = core->doubleToAtom(d1 - d2); NEXT; } INSTR(subtract_i) { a1 = sp[-1]; a2 = sp[0]; sp--; if (IS_BOTH_INTEGER(a1, a2)) { u1t = a1 ^ kIntptrType; u2t = a2 ^ kIntptrType; u3t = CLAMP_32(u1t - u2t); sp[0] = CHECK_INT_ATOM(u3t | kIntptrType); NEXT; } if (IS_BOTH_DOUBLE(a1, a2)) { i1 = (int32_t)AvmCore::integer_d(DOUBLE_VALUE(a1)); i2 = (int32_t)AvmCore::integer_d(DOUBLE_VALUE(a2)); goto finish_subtract_i; } SAVE_EXPC; i1 = AvmCore::integer(a1); i2 = AvmCore::integer(a2); finish_subtract_i: sp[0] = core->intToAtom((int32_t)(i1 - i2)); NEXT; } INSTR(multiply) { a1 = sp[-1]; a2 = sp[0]; sp--; #ifdef VMCFG_WORDCODE_PEEPHOLE mul_two_values_and_next: #endif // OPTIMIZEME - multiplication of small integers might be optimized? if (IS_BOTH_DOUBLE(a1, a2)) { sp[0] = core->doubleToAtom(DOUBLE_VALUE(a1) * DOUBLE_VALUE(a2)); NEXT; } SAVE_EXPC; #ifdef VMCFG_FLOAT if (pool->hasFloatSupport()){ a1 = core->numericAtom(a1); a2 = core->numericAtom(a2); if(IS_BOTH_FLOAT(a1, a2)) { sp[0] = core->floatToAtom(FLOAT_VALUE(a1) * FLOAT_VALUE(a2)); NEXT; } if(AvmCore::isFloat4(a1) || AvmCore::isFloat4(a2)){ float4_decl_v(a1); float4_decl_v(a2); sp[0] = core->float4ToAtom( f4_mul(a1v,a2v) ); NEXT; } } #endif // VMCFG_FLOAT d1 = AvmCore::number(a1); d2 = AvmCore::number(a2); sp[0] = core->doubleToAtom(d1 * d2); NEXT; } INSTR(multiply_i) { // OPTIMIZEME - multiplication of small integers might be optimized? SAVE_EXPC; a1 = sp[-1]; a2 = sp[0]; sp--; i1 = AvmCore::integer(a1); i2 = AvmCore::integer(a2); sp[0] = core->intToAtom((int32_t)(i1 * i2)); NEXT; } INSTR(divide) { // OPTIMIZEME - division of small integers might be optimized? a1 = sp[-1]; a2 = sp[0]; sp--; #ifdef VMCFG_WORDCODE_PEEPHOLE div_two_values_and_next: #endif if (IS_BOTH_DOUBLE(a1, a2)) { sp[0] = core->doubleToAtom(DOUBLE_VALUE(a1) / DOUBLE_VALUE(a2)); NEXT; } SAVE_EXPC; #ifdef VMCFG_FLOAT if(pool->hasFloatSupport()) { a1 = core->numericAtom(a1); a2 = core->numericAtom(a2); if(IS_BOTH_FLOAT(a1, a2)) { sp[0] = core->floatToAtom(FLOAT_VALUE(a1) / FLOAT_VALUE(a2)); NEXT; } if(AvmCore::isFloat4(a1) || AvmCore::isFloat4(a2)){ float4_decl_v(a1); float4_decl_v(a2); sp[0] = core->float4ToAtom( f4_div(a1v, a2v) ); NEXT; } } #endif // VMCFG_FLOAT d1 = AvmCore::number(a1); d2 = AvmCore::number(a2); sp[0] = core->doubleToAtom(d1 / d2); NEXT; } INSTR(modulo) { // FIXME - dodgy optimization? // Can the integer modulo overflow somehow? Is it portable? a1 = sp[-1]; a2 = sp[0]; sp--; #ifdef VMCFG_WORDCODE_PEEPHOLE mod_two_values_and_next: #endif if (IS_BOTH_INTEGER(a1, a2) && a2 != zeroIntAtom) { i1 = INT32_VALUE(a1) % INT32_VALUE(a2); if (atomIsValidIntptrValue(i1)) { sp[0] = MAKE_INTEGER(i1); NEXT; } } else if (IS_BOTH_DOUBLE(a1, a2)) { sp[0] = core->doubleToAtom(MathUtils::mod(DOUBLE_VALUE(a1), DOUBLE_VALUE(a2))); NEXT; } SAVE_EXPC; #ifdef VMCFG_FLOAT if( pool->hasFloatSupport() ) { a1 = core->numericAtom(a1); a2 = core->numericAtom(a2); if( IS_BOTH_FLOAT(a1, a2) ) { sp[0] = core->floatToAtom( (float) MathUtils::mod(FLOAT_VALUE(a1), FLOAT_VALUE(a2))); NEXT; } if(AvmCore::isFloat4(a1) || AvmCore::isFloat4(a2)){ float4_t x; AvmCore::float4(&x, a1); float4_t y; AvmCore::float4(&y, a2); float rx = (float) MathUtils::mod(f4_x(x), f4_x(y)); float ry = (float) MathUtils::mod(f4_y(x), f4_y(y)); float rz = (float) MathUtils::mod(f4_z(x), f4_z(y)); float rw = (float) MathUtils::mod(f4_w(x), f4_w(y)); float4_t res = {rx,ry,rz,rw}; sp[0] = core->float4ToAtom( res ); NEXT; } } #endif // VMCFG_FLOAT d1 = AvmCore::number(a1); d2 = AvmCore::number(a2); sp[0] = core->doubleToAtom(MathUtils::mod(d1, d2)); NEXT; } INSTR(lshift) { a1 = sp[-1]; a2 = sp[0]; sp--; //lshift_two_values_and_next: if (IS_BOTH_INTEGER(a1,a2)) { i1 = INT32_VALUE(a1) << (INT32_VALUE(a2) & 0x1F); if (atomIsValidIntptrValue(i1)) { sp[0] = SIGN_EXTEND(MAKE_INTEGER(i1)); NEXT; } } SAVE_EXPC; i1 = AvmCore::integer(a1); u2 = AvmCore::toUInt32(a2); sp[0] = core->intToAtom( (int32_t)(i1 << (u2 & 0x1F)) ); NEXT; } INSTR(rshift) { a1 = sp[-1]; a2 = sp[0]; sp--; //rshift_two_values_and_next: if (IS_BOTH_INTEGER(a1,a2)) { sp[0] = MAKE_INTEGER(INT32_VALUE(a1) >> (INT32_VALUE(a2) & 0x1F)); NEXT; } SAVE_EXPC; i1 = AvmCore::integer(a1); u2 = AvmCore::toUInt32(a2); sp[0] = core->intToAtom( (int32_t)(i1 >> (u2 & 0x1F)) ); NEXT; } INSTR(urshift) { a1 = sp[-1]; a2 = sp[0]; sp--; //urshift_two_values_and_next: if (IS_BOTH_INTEGER(a1,a2)) { u1 = (UINT32_VALUE(a1) >> (INT32_VALUE(a2) & 0x1F)); if (atomIsValidIntptrValue_u(u1)) { sp[0] = MAKE_INTEGER(u1); NEXT; } } SAVE_EXPC; u1 = AvmCore::toUInt32(a1); u2 = AvmCore::toUInt32(a2); sp[0] = core->uintToAtom( (uint32_t)(u1 >> (u2 & 0x1F)) ); NEXT; } // The OR with tag is only necessary for xor, which passes kIntptrType. The // others pass 0, and we assume the compiler optimizes the OR away. #define BITOP_TWO_VALUES_AND_NEXT(op, a1, a2, dest, tag) \ if (IS_BOTH_INTEGER(a1,a2)) { \ dest = (CLAMP_32((a1) op (a2)) | tag); \ NEXT; \ } \ SAVE_EXPC; \ i1 = AvmCore::integer(a1); \ i2 = AvmCore::integer(a2); \ dest = core->intToAtom((int32_t)(i1 op i2)); \ NEXT INSTR(bitand) { a1 = sp[-1]; a2 = sp[0]; sp--; #ifdef VMCFG_WORDCODE_PEEPHOLE bitand_two_values_and_next: #endif BITOP_TWO_VALUES_AND_NEXT(&, a1, a2, sp[0], 0); } INSTR(bitor) { a1 = sp[-1]; a2 = sp[0]; sp--; #ifdef VMCFG_WORDCODE_PEEPHOLE bitor_two_values_and_next: #endif BITOP_TWO_VALUES_AND_NEXT(|, a1, a2, sp[0], 0); } INSTR(bitxor) { a1 = sp[-1]; a2 = sp[0]; sp--; #ifdef VMCFG_WORDCODE_PEEPHOLE bitxor_two_values_and_next: #endif BITOP_TWO_VALUES_AND_NEXT(^, a1, a2, sp[0], kIntptrType); } INSTR(equals) { // OPTIMIZEME - equals on some classes of values? SAVE_EXPC; sp[-1] = core->equals(sp[-1], sp[0]); sp--; NEXT; } INSTR(strictequals) { // OPTIMIZEME - strictequals on some classes of values? sp[-1] = AvmCore::stricteq(sp[-1], sp[0]); sp--; NEXT; } INSTR(lookupswitch) { #ifdef VMCFG_WORDCODE const uintptr_t* base = pc-1; uint32_t index = AvmCore::integer_i(*(sp--)); intptr_t default_offset = S24ARG; uintptr_t case_count = U30ARG; if (index <= case_count) pc = base + pc[index]; else pc = base + default_offset; #else const uint8_t* base = pc-1; // safe to assume int since verifier checks for int uint32_t index = AvmCore::integer_i(*(sp--)); const uint8_t* switch_pc = pc+3; uint32_t case_count = uint32_t(readU30(switch_pc)) + 1; pc = base+readS24( index < case_count ? (switch_pc + 3*index) : pc ); #endif if (pc <= base) { // if backedge, need an interrupt check SAVE_EXPC; // interrupts should originate from target of backedge branchCheck(core, env, interruptable); } NEXT; } INSTR(iftrue) { a2 = trueAtom; branch_on_boolean: a1 = *(sp--); if (IS_BOOLEAN(a1)) ; else if (IS_INTEGER(a1)) a1 = a1 == zeroIntAtom ? falseAtom : trueAtom; else a1 = AvmCore::booleanAtom(a1); // does not throw or change the XML namespace i1 = S24ARG; if (a1 == a2) { if (i1 < 0) { SAVE_EXPC_TARGET(i1); branchCheck(core, env, interruptable); OSR(i1); } pc += i1; } NEXT; } INSTR(iffalse) { a2 = falseAtom; goto branch_on_boolean; } // The client of IFCMP_TWO_VALUES must save the EXPC before extracting // the operands from the instruction stream because it is used in various // contexts where the instruction sizes and layouts aren't the same. #define GENERATE_FLAG(numeric_cmp, generic_cmp, a1, a2) \ if (IS_BOTH_INTEGER(a1, a2)) \ b1 = a1 numeric_cmp a2; \ else if (IS_BOTH_DOUBLE(a1, a2)) \ b1 = DOUBLE_VALUE(a1) numeric_cmp DOUBLE_VALUE(a2); \ FLOAT_ONLY(\ else if (IS_BOTH_FLOAT(a1, a2) && pool->hasFloatSupport()) \ b1 = FLOAT_VALUE(a1) numeric_cmp FLOAT_VALUE(a2); \ )\ else \ b1 = generic_cmp #define IFCMP_TWO_VALUES(numeric_cmp, generic_cmp, a1, a2, i1) \ GENERATE_FLAG(numeric_cmp, generic_cmp, a1, a2); \ if (b1) \ { \ if (i1 < 0) \ { \ SAVE_EXPC_TARGET(i1); \ branchCheck(core, env, interruptable); \ OSR(i1); \ } \ pc += i1; \ } #define IFNCMP_TWO_VALUES(numeric_cmp, generic_cmp, a1, a2, i1) \ GENERATE_FLAG(numeric_cmp, generic_cmp, a1, a2); \ if (!b1) \ { \ if (i1 < 0) \ { \ SAVE_EXPC_TARGET(i1); \ branchCheck(core, env, interruptable); \ OSR(i1); \ } \ pc += i1; \ } #define LOAD_OFFSET_AND_FETCH_SS(a1,a2,i1) \ SAVE_EXPC; \ a1 = sp[-1]; \ a2 = sp[0]; \ sp -= 2; \ i1 = S24ARG INSTR(ifeq) { LOAD_OFFSET_AND_FETCH_SS(a1,a2,i1); PEEPHOLE_ONLY( compare_eq_and_branch_impl: ) IFCMP_TWO_VALUES(==, core->equals(a1,a2) == trueAtom, a1, a2, i1); NEXT; } INSTR(ifne) { LOAD_OFFSET_AND_FETCH_SS(a1,a2,i1); PEEPHOLE_ONLY( compare_ne_and_branch_impl: ) IFNCMP_TWO_VALUES(==, core->equals(a1,a2) == trueAtom, a1, a2, i1); NEXT; } INSTR(ifstricteq) { LOAD_OFFSET_AND_FETCH_SS(a1,a2,i1); PEEPHOLE_ONLY( compare_stricteq_and_branch_impl: ) IFCMP_TWO_VALUES(==, AvmCore::stricteq(a1,a2) == trueAtom, a1, a2, i1); NEXT; } INSTR(ifstrictne) { LOAD_OFFSET_AND_FETCH_SS(a1,a2,i1); PEEPHOLE_ONLY( compare_strictne_and_branch_impl: ) IFNCMP_TWO_VALUES(==, AvmCore::stricteq(a1,a2) == trueAtom, a1, a2, i1); NEXT; } INSTR(iflt) { LOAD_OFFSET_AND_FETCH_SS(a1,a2,i1); PEEPHOLE_ONLY( compare_lt_and_branch_impl: ) IFCMP_TWO_VALUES(<, AvmCore::compare(a1,a2) == trueAtom, a1, a2, i1); NEXT; } INSTR(ifnlt) { LOAD_OFFSET_AND_FETCH_SS(a1,a2,i1); PEEPHOLE_ONLY( compare_nlt_and_branch_impl: ) IFNCMP_TWO_VALUES(<, AvmCore::compare(a1, a2) == trueAtom, a1, a2, i1); NEXT; } INSTR(ifle) { LOAD_OFFSET_AND_FETCH_SS(a1,a2,i1); PEEPHOLE_ONLY( compare_le_and_branch_impl: ) IFCMP_TWO_VALUES(<=, AvmCore::compare(a2, a1) == falseAtom,a1,a2,i1); NEXT; } INSTR(ifnle) { LOAD_OFFSET_AND_FETCH_SS(a1,a2,i1); PEEPHOLE_ONLY( compare_nle_and_branch_impl: ) IFNCMP_TWO_VALUES(<=, AvmCore::compare(a2, a1) == falseAtom,a1,a2,i1); NEXT; } INSTR(ifgt) { LOAD_OFFSET_AND_FETCH_SS(a1,a2,i1); PEEPHOLE_ONLY( compare_gt_and_branch_impl: ) IFCMP_TWO_VALUES(>, AvmCore::compare(a2, a1) == trueAtom,a1,a2,i1); NEXT; } INSTR(ifngt) { LOAD_OFFSET_AND_FETCH_SS(a1,a2,i1); PEEPHOLE_ONLY( compare_ngt_and_branch_impl: ) IFNCMP_TWO_VALUES(>, AvmCore::compare(a2, a1) == trueAtom,a1,a2,i1); NEXT; } INSTR(ifge) { LOAD_OFFSET_AND_FETCH_SS(a1,a2,i1); PEEPHOLE_ONLY( compare_ge_and_branch_impl: ) IFCMP_TWO_VALUES(>=, AvmCore::compare(a1, a2) == falseAtom,a1,a2,i1); NEXT; } INSTR(ifnge) { LOAD_OFFSET_AND_FETCH_SS(a1,a2,i1); PEEPHOLE_ONLY( compare_nge_and_branch_impl: ) IFNCMP_TWO_VALUES(>=, AvmCore::compare(a1, a2) == falseAtom,a1,a2,i1); NEXT; } #define CMP2(numeric_cmp, generic_cmp) \ a1 = sp[-1]; \ a2 = sp[0]; \ sp--; \ if (IS_BOTH_INTEGER(a1, a2)) \ b1 = a1 numeric_cmp a2; \ else if (IS_BOTH_DOUBLE(a1, a2)) \ b1 = DOUBLE_VALUE(a1) numeric_cmp DOUBLE_VALUE(a2); \ else { \ SAVE_EXPC; \ b1 = generic_cmp; \ } \ sp[0] = b1 ? trueAtom : falseAtom; INSTR(lessthan) { CMP2(<, AvmCore::compare(a1,a2) == trueAtom); NEXT; } INSTR(lessequals) { CMP2(<=, AvmCore::compare(a2, a1) == falseAtom); NEXT; } INSTR(greaterthan) { CMP2(>, AvmCore::compare(a2, a1) == trueAtom); NEXT; } INSTR(greaterequals) { CMP2(>=, AvmCore::compare(a1, a2) == falseAtom); NEXT; } INSTR(newobject) { SAVE_EXPC; i1 = (intptr_t)U30ARG; a1 = env->op_newobject(sp, (int)i1)->atom(); *(sp -= 2*i1-1) = a1; NEXT; } INSTR(newarray) { SAVE_EXPC; i1 = (intptr_t)U30ARG; a1 = avmplus::newarray(toplevel, (int)i1, sp-i1+1)->atom(); *(sp -= i1-1) = a1; NEXT; } // OPTIMIZEME - isRuntime and isRtns are known at translation time. // Using type information in the verifier it may be possible to determine // that isDictionaryLookup is false, too. Should specialize instructions to // handle common cases. // // But does it matter? getproperty, findproperty are heavyweight operations, and // opportunities for early binding are resolved during translation. Still, it may // make a small difference in connection with avoiding multiname copying (now implemented), // since most fields of the multiname data structure will not be accessed. // OPTIMIZEME - inline fast case for toVTable? // Presumably it will very often be a non-null object; now we have to make a call and // a test and a switch to get to that. Should we in-line? // How will RTTI caching affect that? #define GET_MULTINAME_PTR(decl, arg) decl = pool->precomputedMultiname(uint32_t(arg)) INSTR(getlex) { SAVE_EXPC; // findpropstrict + getproperty // stack in: - // stack out: value GET_MULTINAME_PTR(multiname, U30ARG); // only non-runtime names are allowed. but this still includes // wildcard and attribute names. a1 = env->findproperty(scope, scopeBase, scopeDepth, multiname, true, withBase); *(++sp) = toplevel->getproperty(a1, multiname, toplevel->toVTable(a1)); NEXT; } #if 0 // Multiname is neither isRuntime or isRtns INSTR(getproperty_fast) { SAVE_EXPC; GET_MULTINAME_PTR(multiname, U30ARG); sp[0] = toplevel->getproperty(sp[0], multiname, toplevel->toVTable(sp[0])); NEXT; } #endif // get a property using a multiname ref INSTR(getproperty) { SAVE_EXPC; GET_MULTINAME_PTR(multiname, U30ARG); if (!multiname->isRuntime()) { *sp = toplevel->getproperty(*sp, multiname, toplevel->toVTable(*sp)); } else if (!multiname->isRtns() && IS_INTEGER(*sp) && atomCanBeUint32(*sp) && AvmCore::isObject(sp[-1])) { a2 = *(sp--); // key *sp = AvmCore::atomToScriptObject(*sp)->getUintProperty(UINT32_VALUE(a2)); } else if(multiname->isRtns() || !AvmCore::isDictionaryLookup(*sp, *(sp-1))) { aux_memory->multiname2 = *multiname; sp = initMultiname(env, aux_memory->multiname2, sp); *sp = toplevel->getproperty(*sp, &aux_memory->multiname2, toplevel->toVTable(*sp)); } else { a2 = *(sp--); // key *sp = AvmCore::atomToScriptObject(*sp)->getAtomProperty(a2); } NEXT; } // set a property using a multiname ref INSTR(setproperty) { SAVE_EXPC; GET_MULTINAME_PTR(multiname, U30ARG); a1 = *(sp--); // value if (!multiname->isRuntime()) { a3 = *(sp--); // object toplevel->setproperty(a3, multiname, a1, toplevel->toVTable(a3)); } else if (!multiname->isRtns() && IS_INTEGER(*sp) && atomCanBeUint32(*sp) && AvmCore::isObject(sp[-1])) { a2 = *(sp--); // key a3 = *(sp--); // object AvmCore::atomToScriptObject(a3)->setUintProperty(UINT32_VALUE(a2), a1); } else if(multiname->isRtns() || !AvmCore::isDictionaryLookup(*sp, *(sp-1))) { aux_memory->multiname2 = *multiname; sp = initMultiname(env, aux_memory->multiname2, sp); a3 = *(sp--); // object toplevel->setproperty(a3, &aux_memory->multiname2, a1, toplevel->toVTable(a3)); } else { a2 = *(sp--); // key a3 = *(sp--); // object AvmCore::atomToScriptObject(a3)->setAtomProperty(a2, a1); } NEXT; } INSTR(initproperty) { SAVE_EXPC; GET_MULTINAME_PTR(multiname, U30ARG); a1 = *(sp--); // value if (multiname->isRuntime()) { aux_memory->multiname2 = *multiname; sp = initMultiname(env, aux_memory->multiname2, sp); multiname = &aux_memory->multiname2; } a2 = *(sp--); // object env->initproperty(a2, multiname, a1, toplevel->toVTable(a2)); NEXT; } INSTR(getdescendants) { SAVE_EXPC; GET_MULTINAME_PTR(multiname, U30ARG); if (multiname->isRuntime()) { aux_memory->multiname2 = *multiname; sp = initMultiname(env, aux_memory->multiname2, sp); multiname = &aux_memory->multiname2; } sp[0] = env->getdescendants(sp[0], multiname); NEXT; } INSTR(checkfilter) { SAVE_EXPC; env->checkfilter(sp[0]); NEXT; } INSTR(findpropstrict) { b1 = true; goto findproperty_impl; } INSTR(findproperty) { b1 = false; findproperty_impl: SAVE_EXPC; GET_MULTINAME_PTR(multiname, U30ARG); if (multiname->isRuntime()) { aux_memory->multiname2 = *multiname; sp = initMultiname(env, aux_memory->multiname2, sp); multiname = &aux_memory->multiname2; } *(++sp) = env->findproperty(scope, scopeBase, scopeDepth, multiname, b1, withBase); NEXT; } INSTR(finddef) { SAVE_EXPC; u1 = U30ARG; o1 = finddef_cache((ScriptObject**)&(*env->abcEnv()->finddefTable())[(uint32_t)u1], &aux_memory->methodFrame); *(++sp) = o1->atom(); NEXT; } INSTR(nextname) { SAVE_EXPC; a1 = sp[-1]; a2 = sp[0]; sp--; // verifier checks for int sp[0] = env->nextname(a1, AvmCore::integer_i(a2)); NEXT; } INSTR(nextvalue) { SAVE_EXPC; a1 = sp[-1]; a2 = sp[0]; sp--; // verifier checks for int sp[0] = env->nextvalue(a1, AvmCore::integer_i(a2)); NEXT; } INSTR(hasnext) { SAVE_EXPC; a1 = sp[-1]; a2 = sp[0]; sp--; // verifier checks for int sp[0] = core->intToAtom(env->hasnext(a1, AvmCore::integer_i(a2))); NEXT; } INSTR(hasnext2) { SAVE_EXPC; u1 = U30ARG; // objReg u2 = U30ARG; // indexReg a1l = framep[u1]; // objAtom i32l = AvmCore::integer(framep[u2]); // index *(++sp) = env->hasnextproto(a1l, i32l) ? trueAtom : falseAtom; framep[u1] = a1l; framep[u2] = core->intToAtom(i32l); NEXT; } // sign extends -- results always fit into an atom, no need to call intToAtom // since we are downshifting anyway, integrate final upshift-by-3 into downshift // rather than using MAKE_INTEGER macro. INSTR(sxi1) { i1 = AvmCore::integer(sp[0]); sp[0] = CHECK_INT_ATOM(Atom(((i1 << (8*sizeof(Atom)-1)) >> ((8*sizeof(Atom)-1)-3)) | kIntptrType)); NEXT; } INSTR(sxi8) { i1 = AvmCore::integer(sp[0]); sp[0] = CHECK_INT_ATOM(Atom(((i1 << (8*(sizeof(Atom)-1))) >> ((8*(sizeof(Atom)-1))-3)) | kIntptrType)); NEXT; } INSTR(sxi16) { i1 = AvmCore::integer(sp[0]); sp[0] = CHECK_INT_ATOM(Atom(((i1 << (8*(sizeof(Atom)-2))) >> ((8*(sizeof(Atom)-2))-3)) | kIntptrType)); NEXT; } // note that the mops "addr" (offset from globalMemoryBase) is in fact a signed int, so we have to check // for it being < 0 ... but we can get by with a single unsigned compare since all values < 0 will be > size #define MOPS_RANGE_CHECK(addr, type) \ if (uint32_t(addr) > (envDomain->globalMemorySize() - sizeof(type))) { avmplus::mop_rangeCheckFailed(env); } #if defined(VMCFG_UNALIGNED_INT_ACCESS) && defined(VMCFG_LITTLE_ENDIAN) #define MOPS_LOAD_INT(addr, type, call, result) \ MOPS_RANGE_CHECK(addr, type) \ union { const uint8_t* p8; const type* p; }; \ p8 = envDomain->globalMemoryBase() + (addr); \ result = *p; #define MOPS_STORE_INT(addr, type, call, value) \ MOPS_RANGE_CHECK(addr, type) \ union { uint8_t* p8; type* p; }; \ p8 = envDomain->globalMemoryBase() + (addr); \ *p = (type)(value); #else #define MOPS_LOAD_INT(addr, type, call, result) \ MOPS_RANGE_CHECK(addr, type) \ result = (type)avmplus::mop_##call(envDomain->globalMemoryBase() + (addr)); #define MOPS_STORE_INT(addr, type, call, value) \ MOPS_RANGE_CHECK(addr, type) \ avmplus::mop_##call(envDomain->globalMemoryBase() + (addr), value); #endif #if defined(VMCFG_UNALIGNED_FP_ACCESS) && defined(VMCFG_LITTLE_ENDIAN) #define MOPS_LOAD_FP(addr, type, call, result) \ MOPS_RANGE_CHECK(addr, type) \ union { const uint8_t* p8; const type* p; }; \ p8 = envDomain->globalMemoryBase() + (addr); \ result = *p; #define MOPS_STORE_FP(addr, type, call, value) \ MOPS_RANGE_CHECK(addr, type) \ union { uint8_t* p8; type* p; }; \ p8 = envDomain->globalMemoryBase() + (addr); \ *p = (type)(value); #else #define MOPS_LOAD_FP(addr, type, call, result) \ MOPS_RANGE_CHECK(addr, type) \ result = (type)avmplus::mop_##call(envDomain->globalMemoryBase() + (addr)); #define MOPS_STORE_FP(addr, type, call, value) \ MOPS_RANGE_CHECK(addr, type) \ avmplus::mop_##call(envDomain->globalMemoryBase() + (addr), value); #endif #if defined(VMCFG_LITTLE_ENDIAN) // Note, this depends on one of three conditions being true: // - float4_t is a generic structure type that is not mapped directly // to the SIMD type, and ByteArray storage is 4-byte aligned // - SIMD loads/stores to unaligned addresses is OK // - ByteArray storage is 16-byte aligned #define MOPS_LOAD_F4(addr, call, result) \ addr &= ~15; \ MOPS_RANGE_CHECK(addr, float4_t); \ union { const uint8_t* p8; const float4_t* p; }; \ p8 = envDomain->globalMemoryBase() + (addr); \ result = *p; #define MOPS_STORE_F4(addr, call, value) \ addr &= ~15; \ MOPS_RANGE_CHECK(addr, float4_t); \ union { const uint8_t* p8; float4_t* p; }; \ p8 = envDomain->globalMemoryBase() + (addr); \ *p = (value); #else #define MOPS_LOAD_F4(addr, call, result) \ addr &= ~15; \ MOPS_RANGE_CHECK(addr, float4_t); \ avmplus::mop_##call(&result, envDomain->globalMemoryBase() + (addr)); #define MOPS_STORE_F4(addr, call, value) \ addr &= ~15; \ MOPS_RANGE_CHECK(addr, float4_t); \ avmplus::mop_##call(envDomain->globalMemoryBase() + (addr), value); #endif // loads #ifdef VMCFG_WORDCODE INSTR(lix8) { i1 = AvmCore::integer(sp[0]); // i1 = addr MOPS_LOAD_INT(i1, int8_t, lix8, i32l); // i32l = result sp[0] = MAKE_INTEGER(i32l); // always fits in atom NEXT; } INSTR(lix16) { i1 = AvmCore::integer(sp[0]); // i1 = addr MOPS_LOAD_INT(i1, int16_t, lix16, i32l); // i32l = result sp[0] = MAKE_INTEGER(i32l); // always fits in atom NEXT; } #endif INSTR(li8) { i1 = AvmCore::integer(sp[0]); // i1 = addr MOPS_LOAD_INT(i1, uint8_t, liz8, ub2); // ub2 = result sp[0] = MAKE_INTEGER(ub2); // always fits in atom NEXT; } INSTR(li16) { i1 = AvmCore::integer(sp[0]); // i1 = addr MOPS_LOAD_INT(i1, uint16_t, liz16, uh2l); // uh2l = result sp[0] = MAKE_INTEGER(uh2l); // always fits in atom NEXT; } INSTR(li32) { i1 = AvmCore::integer(sp[0]); // i1 = addr MOPS_LOAD_INT(i1, int32_t, li32, i32l); // i32l = result sp[0] = core->intToAtom(i32l); NEXT; } INSTR(lf32) { i1 = AvmCore::integer(sp[0]); // i1 = addr MOPS_LOAD_FP(i1, float, lf32, f2l); // f2l = result sp[0] = core->doubleToAtom(f2l); NEXT; } INSTR(lf64) { i1 = AvmCore::integer(sp[0]); // i1 = addr MOPS_LOAD_FP(i1, double, lf64, d2l); // d2l = result sp[0] = core->doubleToAtom(d2l); NEXT; } #ifdef VMCFG_FLOAT INSTR(lf32x4) { i1 = AvmCore::integer(sp[0]); // i1 = addr MOPS_LOAD_F4(i1, lf32x4, f4l); // f4l = result sp[0] = core->float4ToAtom(f4l); NEXT; } #endif // stores INSTR(si8) { ub2 = (uint8_t)AvmCore::integer(sp[-1]); // u2 = value i1 = AvmCore::integer(sp[0]); // i1 = addr MOPS_STORE_INT(i1, uint8_t, si8, ub2); sp -= 2; NEXT; } INSTR(si16) { uh2l = (uint16_t)AvmCore::integer(sp[-1]); // uh2l = value i1 = AvmCore::integer(sp[0]); // i1 = addr MOPS_STORE_INT(i1, uint16_t, si16, uh2l); sp -= 2; NEXT; } INSTR(si32) { i32l = AvmCore::integer(sp[-1]); // i32l = value i1 = AvmCore::integer(sp[0]); // i1 = addr MOPS_STORE_INT(i1, uint32_t, si32, i32l); sp -= 2; NEXT; } INSTR(sf32) { f2l = (float)AvmCore::number(sp[-1]); // d2l = value i1 = AvmCore::integer(sp[0]); // i1 = addr MOPS_STORE_FP(i1, float, sf32, f2l); sp -= 2; NEXT; } INSTR(sf64) { d2l = AvmCore::number(sp[-1]); i1 = AvmCore::integer(sp[0]); MOPS_STORE_FP(i1, double, sf64, d2l); sp -= 2; NEXT; } #ifdef VMCFG_FLOAT INSTR(sf32x4) { AvmCore::float4(&f4l, sp[-1]); i1 = AvmCore::integer(sp[0]); MOPS_STORE_F4(i1, sf32x4, f4l); sp -= 2; NEXT; } #endif // delete property using multiname INSTR(deleteproperty) { SAVE_EXPC; GET_MULTINAME_PTR(multiname, U30ARG); if (!multiname->isRuntime()) { sp[0] = env->delproperty(sp[0], multiname); } else if (!multiname->isRtns() && IS_INTEGER(*sp) && atomCanBeUint32(*sp) && AvmCore::isObject(sp[-1])) { a2 = *(sp--); // key *sp = AvmCore::atomToScriptObject(*sp)->delUintProperty(UINT32_VALUE(a2)) ? trueAtom : falseAtom; } else if(multiname->isRtns() || !AvmCore::isDictionaryLookup(*sp, *(sp-1))) { aux_memory->multiname2 = *multiname; sp = initMultinameNoXMLList(env, aux_memory->multiname2, sp); sp[0] = env->delproperty(sp[0], &aux_memory->multiname2); } else { a2 = *(sp--); // key sp[0] = AvmCore::atomToScriptObject(sp[0])->deleteAtomProperty(a2) ? trueAtom : falseAtom; } NEXT; } INSTR(setslot) { SAVE_EXPC; a1 = sp[-1]; a2 = sp[0]; sp -= 2; env->nullcheck(a1); u1 = U30ARG-1; // slot_id AvmCore::atomToScriptObject(a1)->coerceAndSetSlotAtom((uint32_t)u1, a2); NEXT; } INSTR(getslot) { SAVE_EXPC; env->nullcheck(sp[0]); // OPTIMIZEME - cleanup after ABC interpreter defenestration. // Perform the -1 adjustment in the bytecode translator, not here every time. sp[0] = AvmCore::atomToScriptObject(sp[0])->getSlotAtom((uint32_t)U30ARG-1); NEXT; } INSTR(setglobalslot) { SAVE_EXPC; // find the global activation scope (object at depth 0 on scope chain) // Note: the global scope can legally be any non-null. non-undefined Atom; // however, the Verifier requires that the argument for setglobalslot // be early-bindable. This precludes the "*" type, and any type defined // in a different ABC block; since primitive (non-ScriptObject) types are // builtin, this prevents user code from using this opcode on such a type. AvmAssert(AvmCore::isObject(globalScopeAtom)); // OPTIMIZEME - cleanup after ABC interpreter defenestration. // Perform the -1 adjustment in the bytecode translator, not here every time. u1 = U30ARG-1; // slot_id a1 = sp[0]; // value sp--; AvmCore::atomToScriptObject(globalScopeAtom)->coerceAndSetSlotAtom((uint32_t)u1, a1); NEXT; } INSTR(getglobalslot) { SAVE_EXPC; // find the global activation scope (object at depth 0 on scope chain) // Note: the global scope can legally be any non-null. non-undefined Atom; // however, the Verifier requires that the argument for getglobalslot // be early-bindable. This precludes the "*" type, and any type defined // in a different ABC block; since primitive (non-ScriptObject) types are // builtin, this prevents user code from using this opcode on such a type. AvmAssert(AvmCore::isObject(globalScopeAtom)); // OPTIMIZMEME - cleanup after ABC interpreter defenestration. // Perform the -1 adjustment in the bytecode translator, not here every time. sp++; sp[0] = AvmCore::atomToScriptObject(globalScopeAtom)->getSlotAtom((uint32_t)(U30ARG-1)); NEXT; } // OPTIMIZEME - presumably there are many ways in which the call opcodes may be specialized // to avoid the full function prologue? INSTR(call) { SAVE_EXPC; i1 = (intptr_t)U30ARG; // argc // stack in: function, receiver, arg1, ... argN // stack out: result a1 = avmplus::op_call(toplevel, sp[-i1-1]/*function*/, (int32_t)i1, sp-i1); *(sp = sp-i1-1) = a1; NEXT; } INSTR(construct) { SAVE_EXPC; i1 = (intptr_t)U30ARG; // argc // stack in: function, arg1, ..., argN // stack out: new instance a1 = toplevel->op_construct(sp[-i1]/*function*/, (int32_t)i1, sp-i1); *(sp = sp-i1) = a1; NEXT; } INSTR(newfunction) { SAVE_EXPC; sp++; MethodInfo *body = pool->getMethodInfo((uint32_t)U30ARG); sp[0] = env->newfunction(body, scopeBase)->atom(); NEXT; } INSTR(newclass) { SAVE_EXPC; u1 = U30ARG; Traits* ctraits = pool->getClassTraits((uint32_t)u1); o1 = (ScriptObject*)(~7 & toplevel->coerce(sp[0], CLASS_TYPE)); sp[0] = env->newclass(ctraits, (ClassClosure*)o1, scope, scopeBase)->atom(); NEXT; } INSTR(callstatic) { SAVE_EXPC; // stack in: receiver, arg1..N // stack out: result u1 = U30ARG; // method_id i2 = (intptr_t)U30ARG; // argc env->nullcheck(sp[-i2]); // ISSUE if arg types were checked in verifier, this coerces again. f = env->abcEnv()->getMethod((uint32_t)u1); // ensure the caller matches receiver sp[-i2] = toplevel->coerce(sp[-i2], f->method->getMethodSignature()->paramTraits(0)); a1 = f->coerceEnter((int32_t)i2, sp-i2); *(sp -= i2) = a1; NEXT; } INSTR(callmethod) { SAVE_EXPC; // stack in: receiver, arg1..N // stack out: result u1 = U30ARG-1; // disp_id i2 = (intptr_t)U30ARG; // argc a2p = sp-i2; // atomv // must be a real class instance for this to be used. primitives that have // methods will only have final bindings and no dispatch table. VTable* vtable = toplevel->toVTable(a2p[0]); // includes null check AvmAssert(u1 < vtable->traits->getTraitsBindings()->methodCount); f = vtable->methods[u1]; // ISSUE if arg types were checked in verifier, this coerces again. a1 = f->coerceEnter((int32_t)i2, a2p); *(sp -= i2) = a1; NEXT; } INSTR(callproperty) { u1 = WORD_CODE_ONLY(WOP_callproperty) ABC_CODE_ONLY(OP_callproperty); callproperty_impl: SAVE_EXPC; GET_MULTINAME_PTR(multiname, U30ARG); i1 = (intptr_t)U30ARG; /* argc */ a2p = sp - i1; /* atomv */ sp = a2p; if (multiname->isRuntime()) { aux_memory->multiname2 = *multiname; sp = initMultiname(env, aux_memory->multiname2, sp); multiname = &aux_memory->multiname2; } a1 = *sp; /* base */ if (u1 == WORD_CODE_ONLY(WOP_callproplex) ABC_CODE_ONLY(OP_callproplex)) a2p[0] = nullObjectAtom; *sp = toplevel->callproperty(a1, multiname, (int32_t)i1, a2p, toplevel->toVTable(a1)); if (u1 == WORD_CODE_ONLY(WOP_callpropvoid) ABC_CODE_ONLY(OP_callpropvoid)) sp--; NEXT; } INSTR(callproplex) { u1 = WORD_CODE_ONLY(WOP_callproplex) ABC_CODE_ONLY(OP_callproplex); goto callproperty_impl; } INSTR(callpropvoid) { u1 = WORD_CODE_ONLY(WOP_callpropvoid) ABC_CODE_ONLY(OP_callpropvoid); goto callproperty_impl; } INSTR(constructprop) { SAVE_EXPC; GET_MULTINAME_PTR(multiname, U30ARG); i1 = (intptr_t)U30ARG; // argc sp -= i1; a2p = sp; // argv if (multiname->isRuntime()) { aux_memory->multiname2 = *multiname; sp = initMultiname(env, aux_memory->multiname2, sp); multiname = &aux_memory->multiname2; a2p[0] = *sp; } *sp = toplevel->constructprop(multiname, (int32_t)i1, a2p, toplevel->toVTable(a2p[0])); NEXT; } INSTR(applytype) { SAVE_EXPC; i1 = (intptr_t)U30ARG; // argc // stack in: factory, arg1, ... argN // stack out: result a1 = op_applytype(env, sp[-i1]/*function*/, (int32_t)i1, sp-i1+1); *(sp = sp-i1) = a1; NEXT; } INSTR(callsuper) { u1 = WORD_CODE_ONLY(WOP_callsuper) ABC_CODE_ONLY(OP_callsuper); callsuper_impl: SAVE_EXPC; GET_MULTINAME_PTR(multiname, U30ARG); i1 = (intptr_t)U30ARG; /* argc */ sp -= i1; a2p = sp; /* atomv */ if (multiname->isRuntime()) { aux_memory->multiname2 = *multiname; sp = initMultiname(env, aux_memory->multiname2, sp); multiname = &aux_memory->multiname2; a2p[0] = *sp; } env->nullcheck(a2p[0]); // ensure the receiver matches supertype a2p[0] = toplevel->coerce(a2p[0], env->method->declaringTraits()->base); *sp = env->callsuper(multiname, (int32_t)i1, a2p); if (u1 == WORD_CODE_ONLY(WOP_callsupervoid) ABC_CODE_ONLY(OP_callsupervoid)) sp--; NEXT; } INSTR(callsupervoid) { u1 = WORD_CODE_ONLY(WOP_callsupervoid) ABC_CODE_ONLY(OP_callsupervoid); goto callsuper_impl; } // An possible optimization is to split instructions that have a runtime multiname // into two: one that initializes aux_memory->multiname2, and another that simply picks // up that value. That way there will be less code in the interpreter, and the // common case will be faster. INSTR(getsuper) { a1 = 0; // not a value - instruction is getsuper getsuper_setsuper_impl: SAVE_EXPC; GET_MULTINAME_PTR(multiname, U30ARG); if (multiname->isRuntime()) { aux_memory->multiname2 = *multiname; sp = initMultiname(env, aux_memory->multiname2, sp); multiname = &aux_memory->multiname2; } a2 = *(sp--); // object env->nullcheck(a2); // ensure the caller matches supertype a2 = toplevel->coerce(a2, env->method->declaringTraits()->base); if (a1 == 0) *(++sp) = env->getsuper(a2, multiname); else env->setsuper(a2, multiname, a1); NEXT; } INSTR(setsuper) { a1 = *(sp--); // value goto getsuper_setsuper_impl; } // obj arg1 arg2 // sp INSTR(constructsuper) { SAVE_EXPC; // stack in: obj arg1..N // stack out: i1 = (intptr_t)U30ARG; // argc env->nullcheck(sp[-i1]); // ensure the receiver matches supertype sp[-i1] = toplevel->coerce(sp[-i1], env->method->declaringTraits()->base); env->super_init()->coerceEnter((int32_t)i1, sp-i1); sp -= i1+1; NEXT; } #ifndef VMCFG_WORDCODE INSTR(pushshort) { // this just pushes an integer since we dont have short atoms *(++sp) = MAKE_INTEGER(((int16_t)U30ARG)); NEXT; } #endif INSTR(astype) { SAVE_EXPC; GET_MULTINAME_PTR(multiname, U30ARG); sp[0] = AvmCore::astype(sp[0], getTraits(multiname, pool, toplevel, core)); NEXT; } INSTR(astypelate) { SAVE_EXPC; a1 = sp[-1]; a2 = sp[0]; sp--; sp[0] = AvmCore::astype(a1, toplevel->toClassITraits(a2)); NEXT; } INSTR(coerce) { SAVE_EXPC; // expects a CONSTANT_Multiname cpool index // this is the ES4 implicit coersion GET_MULTINAME_PTR(multiname, U30ARG); sp[0] = toplevel->coerce(sp[0], getTraits(multiname, pool, toplevel, core)); NEXT; } INSTR(coerce_o) { if (sp[0] == undefinedAtom) sp[0] = nullObjectAtom; NEXT; } INSTR(coerce_s) { a1 = sp[0]; if (!IS_STRING(a1)) { SAVE_EXPC; sp[0] = AvmCore::isNullOrUndefined(a1) ? nullStringAtom : core->string(a1)->atom(); } NEXT; } // OPTIMIZEME - early binding for a2 of 'is'? // (or outright removal in the translator?) INSTR(istype) { SAVE_EXPC; // expects a CONSTANT_Multiname cpool index // used when operator "is" RHS is a compile-time type constant GET_MULTINAME_PTR(multiname, U30ARG); t1 = getTraits(multiname, pool, toplevel, core); // itraits sp[0] = AvmCore::istypeAtom(sp[0], t1); NEXT; } INSTR(istypelate) { SAVE_EXPC; a1 = sp[-1]; a2 = sp[0]; sp--; sp[0] = AvmCore::istypeAtom(a1, toplevel->toClassITraits(a2)); NEXT; } #ifndef VMCFG_WORDCODE INSTR(pushbyte) { *(++sp) = MAKE_INTEGER((int8_t)U8ARG); NEXT; } #endif INSTR(getscopeobject) { u1 = U8ARG; // scope_index *(++sp) = scopeBase[(uint32_t)u1]; NEXT; } INSTR(getouterscope) { u1 = U30ARG; // scope_index *(++sp) = scope->getScope((uint32_t)u1); NEXT; } INSTR(getglobalscope) { AvmAssert(!AvmCore::isNullOrUndefined(globalScopeAtom)); *(++sp) = globalScopeAtom; NEXT; } INSTR(pushscope) { a1 = sp[0]; // scope sp--; if (AvmCore::isNullOrUndefined(a1)) { SAVE_EXPC; env->nullcheck(a1); } if (scopeDepth == 0 && AvmCore::isNullOrUndefined(globalScopeAtom)) { AvmAssert(scope->getSize() == 0); globalScopeAtom = a1; } scopeBase[scopeDepth++] = a1; NEXT; } INSTR(pushwith) { a1 = sp[0]; // scope object sp--; if (AvmCore::isNullOrUndefined(a1)) { SAVE_EXPC; env->nullcheck(a1); } if (!withBase) withBase = scopeBase+scopeDepth; // is it possible to have pushWith for scope 0? not sure, let's do this just in case if (scopeDepth == 0 && AvmCore::isNullOrUndefined(globalScopeAtom)) { AvmAssert(scope->getSize() == 0); globalScopeAtom = a1; } scopeBase[scopeDepth++] = a1; NEXT; } INSTR(newactivation) { SAVE_EXPC; *(++sp) = env->newActivation()->atom(); NEXT; } INSTR(newcatch) { SAVE_EXPC; u1 = U30ARG; // catch_index #ifdef VMCFG_WORDCODE t1 = info->word_code_exceptions()->exceptions[u1].scopeTraits; #else t1 = info->abc_exceptions()->exceptions[u1].scopeTraits; #endif *(++sp) = env->newcatch(t1)->atom(); NEXT; } INSTR(popscope) { if (!--scopeDepth) { if (scope->getSize() == 0) globalScopeAtom = nullObjectAtom; } #ifdef DEBUGGER // in debugger builds, ensure that non-active scope entries are nulled out scopeBase[scopeDepth] = nullObjectAtom; #endif if (withBase >= scopeBase + scopeDepth) withBase = NULL; NEXT; } INSTR(convert_i) { ABC_CODE_ONLY( convert_i_impl: ) a1 = sp[0]; if (!IS_INTEGER(a1)) { SAVE_EXPC; sp[0] = core->intAtom(a1); } else { sp[0] = CLAMP_32(a1); } NEXT; } #ifndef VMCFG_WORDCODE INSTR(coerce_i) { goto convert_i_impl; } #endif INSTR(convert_u) { ABC_CODE_ONLY( convert_u_impl: ) a1 = sp[0]; if (!IS_INTEGER(a1) || !atomCanBeUint32(a1)) { SAVE_EXPC; sp[0] = core->uintAtom(a1); } NEXT; } #ifndef VMCFG_WORDCODE INSTR(coerce_u) { goto convert_u_impl; } #endif INSTR(throw) { SAVE_EXPC; core->throwAtom(*sp--); // unreachable NEXT; } INSTR(instanceof) { SAVE_EXPC; a1 = sp[-1]; a2 = sp[0]; sp--; sp[0] = toplevel->instanceof(a1, a2); NEXT; } INSTR(in) { SAVE_EXPC; a1 = sp[-1]; a2 = sp[0]; sp--; sp[0] = toplevel->in_operator(a1, a2); NEXT; } INSTR(dxns) { AvmAssert(info->setsDxns()); SAVE_EXPC; core->setDxns(&aux_memory->methodFrame, pool->getString((uint32_t)U30ARG)); NEXT; } INSTR(dxnslate) { AvmAssert(info->setsDxns()); SAVE_EXPC; core->setDxnsLate(&aux_memory->methodFrame, *sp); sp--; NEXT; } #if defined(VMCFG_WORDCODE) && !defined(VMCFG_DIRECT_THREADED) // Fleshes out the dispatch table so that it's 0..255, allows // some compilers to generate better code for the switch at the // top, which switches on the low 8 bits. (0 is an illegal // opcode; 255 is OP_ext, for which there's a case below.) case 0: { AvmAssert(false); } #endif #ifdef VMCFG_WORDCODE # ifdef MSVC_X86_REWRITE_THREADING default: // Keep L_illegal_op and L_push_doublebits alive... if ((int)pc > 0x100000) goto L_push_doublebits; break; # endif INSTR(pushbits) { *++sp = *pc++; NEXT; } // OPTIMIZEME - push_doublebits should probably not cons up a new atom every time, // it would be better to keep it in the constant pool. // // OPTIMIZEME - on 64-bit systems we don't need two operand words here // // OPTIMIZEME - we can do better if unaligned int64 loads or unaligned fp loads // are allowed on the platform. INSTR(push_doublebits) { // Native layout in the instruction stream, just copy it over double_overlay d; d.bits32[0] = (uint32_t)*pc++; d.bits32[1] = (uint32_t)*pc++; *++sp = core->doubleToAtom(d.value); NEXT; } # ifdef VMCFG_WORDCODE_PEEPHOLE # define FETCH_LL(a1, a2) \ u1 = *pc++; \ a1 = framep[u1 & 65535];\ a2 = framep[u1 >> 16] # define LOAD_OFFSET_AND_FETCH_LL(a1, a2, i1) \ SAVE_EXPC; \ i1 = (intptr_t)*pc++; \ FETCH_LL(a1, a2) # define FETCH_LB(a1, a2) \ a1 = framep[*pc++]; \ a2 = *pc++ # define LOAD_OFFSET_AND_FETCH_LB(a1, a2, i1) \ SAVE_EXPC; \ i1 = (intptr_t)*pc++; \ FETCH_LB(a1, a2) // Superwords not in the instruction set. These are selected by a table // driven peephole optimizer, see comments and code in core/WordcodeTranslator.cpp. INSTR(get2locals) { u1 = *pc++; *(++sp) = framep[u1 & 65535]; *(++sp) = framep[u1 >> 16]; NEXT; } INSTR(get3locals) { u1 = *pc++; *(++sp) = framep[u1 & 1023]; u1 >>= 10; *(++sp) = framep[u1 & 1023]; *(++sp) = framep[u1 >> 10]; NEXT; } INSTR(get4locals) { u1 = *pc++; *(++sp) = framep[u1 & 255]; u1 >>= 8; *(++sp) = framep[u1 & 255]; u1 >>= 8; *(++sp) = framep[u1 & 255]; *(++sp) = framep[u1 >> 8]; NEXT; } INSTR(get5locals) { u1 = *pc++; *(++sp) = framep[u1 & 63]; u1 >>= 6; *(++sp) = framep[u1 & 63]; u1 >>= 6; *(++sp) = framep[u1 & 63]; u1 >>= 6; *(++sp) = framep[u1 & 63]; *(++sp) = framep[u1 >> 6]; NEXT; } INSTR(storelocal) { framep[*pc++] = *sp; NEXT; } INSTR(add_ll) { FETCH_LL(a1,a2); ++sp; goto add_two_values_into_tos_impl; } INSTR(add_set_lll) { u1 = *pc++; a1=framep[u1 & 1023]; u1 >>= 10; a2=framep[u1 & 1023]; ADD_TWO_VALUES_AND_NEXT(a1, a2, framep[u1 >> 10]); } INSTR(subtract_ll) { FETCH_LL(a1,a2); ++sp; goto sub_two_values_and_next; } INSTR(multiply_ll) { FETCH_LL(a1,a2); ++sp; goto mul_two_values_and_next; } INSTR(divide_ll) { FETCH_LL(a1,a2); ++sp; goto div_two_values_and_next; } INSTR(modulo_ll) { FETCH_LL(a1,a2); ++sp; goto mod_two_values_and_next; } INSTR(bitand_ll) { FETCH_LL(a1,a2); ++sp; goto bitand_two_values_and_next; } INSTR(bitor_ll) { FETCH_LL(a1,a2); ++sp; goto bitor_two_values_and_next; } INSTR(bitxor_ll) { FETCH_LL(a1,a2); ++sp; goto bitxor_two_values_and_next; } // OPTIMIZEME - redundant type check in superword. // As long as ext_pushbits is only used for integer data we know that // a2 is an int in the cases below, so the macros need not check. INSTR(add_lb) { FETCH_LB(a1, a2); ++sp; goto add_two_values_into_tos_impl; } INSTR(subtract_lb) { FETCH_LB(a1, a2); ++sp; goto sub_two_values_and_next; } INSTR(multiply_lb) { FETCH_LB(a1, a2); ++sp; goto mul_two_values_and_next; } INSTR(divide_lb) { FETCH_LB(a1, a2); ++sp; goto div_two_values_and_next; } INSTR(bitand_lb) { FETCH_LB(a1, a2); ++sp; goto bitand_two_values_and_next; } INSTR(bitor_lb) { FETCH_LB(a1, a2); ++sp; goto bitor_two_values_and_next; } INSTR(bitxor_lb) { FETCH_LB(a1, a2); ++sp; goto bitxor_two_values_and_next; } INSTR(iflt_ll) { LOAD_OFFSET_AND_FETCH_LL(a1,a2,i1); goto compare_lt_and_branch_impl; } INSTR(ifnlt_ll) { LOAD_OFFSET_AND_FETCH_LL(a1,a2,i1); goto compare_nlt_and_branch_impl; } INSTR(ifle_ll) { LOAD_OFFSET_AND_FETCH_LL(a1,a2,i1); goto compare_le_and_branch_impl; } INSTR(ifnle_ll) { LOAD_OFFSET_AND_FETCH_LL(a1,a2,i1); goto compare_nle_and_branch_impl; } INSTR(ifgt_ll) { LOAD_OFFSET_AND_FETCH_LL(a1,a2,i1); goto compare_gt_and_branch_impl; } INSTR(ifngt_ll) { LOAD_OFFSET_AND_FETCH_LL(a1,a2,i1); goto compare_ngt_and_branch_impl; } INSTR(ifge_ll) { LOAD_OFFSET_AND_FETCH_LL(a1,a2,i1); goto compare_ge_and_branch_impl; } INSTR(ifnge_ll) { LOAD_OFFSET_AND_FETCH_LL(a1,a2,i1); goto compare_nge_and_branch_impl; } INSTR(ifeq_ll) { LOAD_OFFSET_AND_FETCH_LL(a1,a2,i1); goto compare_eq_and_branch_impl; } INSTR(ifne_ll) { LOAD_OFFSET_AND_FETCH_LL(a1,a2,i1); goto compare_ne_and_branch_impl; } INSTR(ifstricteq_ll) { LOAD_OFFSET_AND_FETCH_LL(a1,a2,i1); goto compare_stricteq_and_branch_impl; } INSTR(ifstrictne_ll) { LOAD_OFFSET_AND_FETCH_LL(a1,a2,i1); goto compare_strictne_and_branch_impl; } INSTR(iflt_lb) { LOAD_OFFSET_AND_FETCH_LB(a1,a2,i1); goto compare_lt_and_branch_impl; } INSTR(ifnlt_lb) { LOAD_OFFSET_AND_FETCH_LB(a1,a2,i1); goto compare_nlt_and_branch_impl; } INSTR(ifle_lb) { LOAD_OFFSET_AND_FETCH_LB(a1,a2,i1); goto compare_le_and_branch_impl; } INSTR(ifnle_lb) { LOAD_OFFSET_AND_FETCH_LB(a1,a2,i1); goto compare_nle_and_branch_impl; } INSTR(ifgt_lb) { LOAD_OFFSET_AND_FETCH_LB(a1,a2,i1); goto compare_gt_and_branch_impl; } INSTR(ifngt_lb) { LOAD_OFFSET_AND_FETCH_LB(a1,a2,i1); goto compare_ngt_and_branch_impl; } INSTR(ifge_lb) { LOAD_OFFSET_AND_FETCH_LB(a1,a2,i1); goto compare_ge_and_branch_impl; } INSTR(ifnge_lb) { LOAD_OFFSET_AND_FETCH_LB(a1,a2,i1); goto compare_nge_and_branch_impl; } INSTR(ifeq_lb) { LOAD_OFFSET_AND_FETCH_LB(a1,a2,i1); goto compare_eq_and_branch_impl; } INSTR(ifne_lb) { LOAD_OFFSET_AND_FETCH_LB(a1,a2,i1); goto compare_ne_and_branch_impl; } INSTR(ifstricteq_lb) { LOAD_OFFSET_AND_FETCH_LB(a1,a2,i1); goto compare_stricteq_and_branch_impl; } INSTR(ifstrictne_lb) { LOAD_OFFSET_AND_FETCH_LB(a1,a2,i1); goto compare_strictne_and_branch_impl; } INSTR(swap_pop) { sp[-1] = sp[0]; --sp; NEXT; } # endif // VMCFG_WORDCODE_PEEPHOLE INSTR(findpropglobal) { b1 = false; // whether to throw or not goto findpropglobal_impl; } INSTR(findpropglobalstrict) { b1 = true; findpropglobal_impl: u1 = *pc++; // multiname_index SAVE_EXPC; GET_MULTINAME_PTR(multiname, u1); AvmAssert(multiname->isBinding()); AvmAssert(!AvmCore::isNullOrUndefined(globalScopeAtom)); a1 = env->findglobalproperty(globalScopeAtom, multiname); // container if (AvmCore::isNullOrUndefined(a1)) { if (b1) toplevel->throwReferenceError(kUndefinedVarError, multiname); *(++sp) = globalScopeAtom; } else { *(++sp) = a1; } NEXT; } #ifdef VMCFG_FLOAT INSTR(float4) { // Does not pop its arguments but replaces the top argument by the result, // the stack is cleaned up by subsequent code. This mess is brought upon // us by Verifier::emitCallpropertySlot(), primarily. float4_t result; result.w = AvmCore::singlePrecisionFloat(sp[0]); result.z = AvmCore::singlePrecisionFloat(sp[-1]); result.y = AvmCore::singlePrecisionFloat(sp[-2]); result.x = AvmCore::singlePrecisionFloat(sp[-3]); sp[0] = core->float4ToAtom(result); NEXT; } #endif #endif // VMCFG_WORDCODE #if defined SWITCH_DISPATCH } // switch // illegal instruction or accidental break goto L_illegal_op; } // for #else goto L_illegal_op; #endif } // End TRY CATCH (Exception *exception) { // find handler; rethrow if no handler. #if defined VMCFG_WORDCODE && !defined DEBUGGER ExceptionHandler *handler = core->findExceptionHandler(info, (uintptr_t*)expc-1-info->word_code_start(), exception); #else ExceptionHandler *handler = core->findExceptionHandler(info, expc, exception); #endif // handler found in current method #ifdef DEBUGGER // This is a little hokey, see https://bugzilla.mozilla.org/show_bug.cgi?id=470954. // // The debugenter instruction sets up core->callStack, we do this lazily to save // time in builds where the debugger is enabled at compile time but not present // at run time. // // The problem is that CATCH restores core->callStack to its old value, saved by TRY. // So we force it to the new value here if there is a new value. Then TRY will save the // value again (the new value this time) which we restore redundantly the next time // there is an exception, if any. The debugexit instruction will take care of restoring // the actual old value. if (callStackNode != NULL) core->callStack = callStackNode; #endif #ifdef VMCFG_WORDCODE pc = info->word_code_start() + handler->target; #else pc = codeStart + handler->target; #endif scopeDepth = 0; #ifdef DEBUGGER // in debugger builds, ensure that non-active scope entries are nulled out for (int i = 0, n = ms->max_scope(); i < n; ++i) scopeBase[i] = nullObjectAtom; #endif globalScopeAtom = (scope->getSize() > 0) ? scope->getScope(0) : nullObjectAtom; sp = scopeBase + ms->max_scope() - 1; *(++sp) = exception->atom; goto MainLoop; } END_CATCH END_TRY // Target of various error cases L_illegal_op: AvmAssert(!"Illegal operation!"); goto L_illegal_op; } // end interpBoxed() // Note, this function is not on the hot path normally, so optimizing it is not urgent. // // OPTIMIZEME - statically knowable if name isRtname or isRtns; exploit this somehow? // OPTIMIZEME - often knowable whether the TOS is an object or something simple; exploit this? Atom* initMultiname(MethodEnv* env, Multiname &name, Atom* sp) { if (name.isRtname()) { Atom index = *(sp--); AvmCore* core = env->core(); // is it a qname? if (AvmCore::isObject(index)) { ScriptObject* i = AvmCore::atomToScriptObject(index); if (i->traits() == core->traits.qName_itraits) { QNameObject* qname = (QNameObject*) i; bool attr = name.isAttr(); qname->getMultiname(name); if (attr) name.setAttr(attr); // Discard runtime namespace if present if (name.isRtns()) sp--; return sp; } } name.setName(core->intern(index)); } if (name.isRtns()) name.setNamespace(env->internRtns(*(sp--))); return sp; } Atom* initMultinameNoXMLList(MethodEnv* env, Multiname &name, Atom* sp) { if (name.isRtname() && AvmCore::isXMLList(sp[0])) { // Error according to E4X spec, section 11.3.1 env->toplevel()->throwTypeError(kDeleteTypeError, env->core()->toErrorString(env->toplevel()->toTraits(sp[0]))); } return initMultiname(env, name, sp); } Traits* getTraits(const Multiname* name, PoolObject* pool, Toplevel* toplevel, AvmCore* core) { // See Verifier::checkTypeName for the canonical code Traits* t = core->domainMgr()->findTraitsInPoolByMultiname(pool, *name); // Verifier::checkTypeName has ensured this is a valid, non-ambiguous binding AvmAssert(t != NULL && t != (Traits*)BIND_AMBIGUOUS); if (name->isParameterizedType()) { const Multiname* param_name; GET_MULTINAME_PTR(param_name, name->getTypeParameter()); core->stackCheck(toplevel); Traits* param_traits = name->getTypeParameter() ? getTraits(param_name, pool, toplevel, core) : NULL; t = pool->resolveParameterizedType(toplevel, t, param_traits); } return t; } #ifdef AVMPLUS_VERBOSE /** * display contents of current stack frame only. */ void showState(MethodInfo* info, const bytecode_t *code_start, const bytecode_t *pc, Atom* framep, Atom *spp, int scopeDepth, Atom *scopebasep, int max_scope) { #ifdef VMCFG_WORDCODE # ifdef VMCFG_DIRECT_THREADED // 'opcode' is really a code pointer. void* address = (void*)*pc; WordOpcode opcode = (WordOpcode)0; void** jumptable = interpGetOpcodeLabels(); for ( int i=0 ; i <= WOP_LAST ; i++ ) { if (jumptable[i] == address) { opcode = (WordOpcode)i; break; } } // this cannot happen due to the structure of the loop above // (and triggers a warning in some exceptionally-picky compilers) //if (opcode < 0 || opcode > WOP_LAST) // opcode = (WordOpcode)0; # else WordOpcode opcode = (WordOpcode) *pc; # endif #else AbcOpcode opcode = (AbcOpcode) *pc; #endif PoolObject* pool = info->pool(); AvmCore* core = pool->core; ptrdiff_t off = pc - code_start; ptrdiff_t sp = spp - framep; ptrdiff_t scopep = scopebasep + scopeDepth - 1 - framep; ptrdiff_t scopeBase = scopebasep - framep; ptrdiff_t stackBase = scopebasep + max_scope - framep; // this is handy for debugging 64-specific interpreter bugs. //#ifdef AVMPLUS_64BIT // core->console << " stack-raw:"; // for (ptrdiff_t i=stackBase; i <= sp; i++) { // core->console << " 0x" << MathUtils::convertDoubleToStringRadix(core, framep[i]>>32, 16) << // MathUtils::convertDoubleToStringRadix(core, framep[i]&0xffffffff, 16); // } // core->console << '\n'; //#endif // locals core->console << " ["; for (ptrdiff_t i=0, n=scopeBase; iconsole << asAtom(framep[i]); if (i+1console << ' '; } // scope chain core->console << "] {"; for (ptrdiff_t i=scopeBase, n=scopep; i<=n; i++) { core->console << asAtom(framep[i]); if (i+1<=n) core->console << ' '; } // stack core->console << "} ("; ptrdiff_t stackStart = stackBase; const size_t stackLimit = 20; // don't display more than this, to reduce verbosity size_t stackDepth = (stackBase <= sp) ? sp - stackBase : 0; if (stackDepth > stackLimit) { stackStart = sp - stackLimit; core->console << "..." << (stackStart-stackBase) << ": "; } for (ptrdiff_t i=stackStart, n=sp; i<=n; i++) { core->console << asAtom(framep[i]); if (i+1<=n) core->console << ' '; } // opcode core->console << ")\n "; #ifdef DEBUGGER if (core->debugger() && core->callStack && core->callStack->filename()) { core->console << '[' << core->callStack->filename() << ':' << (uint32_t)core->callStack->linenum() << "] "; } #endif core->console << (int)off << ':'; // Using largest-possible-pointer here for code_end; this is obviously not correct, // but extracting the proper code_end requires diving into the method's ABC bytecode // (and may not be easily accessible at all for wordcode), and more importantly, // is only used for range-checking of fuzzed opcodes... all of which should have been caught // by Verifier, thus checking should be unnecessary here. const bytecode_t* const code_end = (const bytecode_t*)uintptr_t(-1); #ifdef VMCFG_WORDCODE core->formatOpcode(core->console, pc, code_end, (WordOpcode)((int32_t)opcode&0xffff), off, pool); #else core->formatOpcode(core->console, pc, code_end, opcode, off, pool); #endif core->console << '\n'; } #endif // AVMPLUS_VERBOSE #ifdef VMCFG_HALFMOON Atom interpBoxedAtLocation(MethodEnv* env, int abc_pc, FramePopulator& populator) { AvmAssert(false && "not implemented yet"); (void)env; (void)abc_pc; (void)populator; return unreachableAtom; } Atom interpBoxedAtLocation(int abc_pc, Atom* locals, Atom* operand_stack, Atom* scope_stack, int sp, int scope_height, MethodEnv* env) { AvmAssert(false && "not implemented yet"); (void)abc_pc; (void)locals; (void)operand_stack; (void)scope_stack; (void)sp; (void)scope_height; (void)env; return unreachableAtom; } #endif // VMCFG_HALFMOON } // namespace avmplus