/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sw=4 et tw=79: * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released * May 28, 2008. * * The Initial Developer of the Original Code is * Brendan Eich * * Contributor(s): * Andreas Gal * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include #include "nanojit/avmplus.h" #include "nanojit/nanojit.h" #include "jsarray.h" #include "jsbool.h" #include "jstracer.h" #include "jscntxt.h" #include "jsscript.h" #include "jsprf.h" #include "jsinterp.h" #include "jsscope.h" using namespace avmplus; using namespace nanojit; static GC gc = GC(); static avmplus::AvmCore* core = new (&gc) avmplus::AvmCore(); Tracker::Tracker() { pagelist = 0; } Tracker::~Tracker() { clear(); } long Tracker::getPageBase(const void* v) const { return ((long)v) & (~(NJ_PAGE_SIZE-1)); } struct Tracker::Page* Tracker::findPage(const void* v) const { long base = getPageBase(v); struct Tracker::Page* p = pagelist; while (p) { if (p->base == base) { return p; } p = p->next; } return 0; } struct Tracker::Page* Tracker::addPage(const void* v) { long base = getPageBase(v); struct Tracker::Page* p = (struct Tracker::Page*) GC::Alloc(sizeof(struct Tracker::Page) + (NJ_PAGE_SIZE >> 2) * sizeof(LInsp)); p->base = base; p->next = pagelist; pagelist = p; return p; } void Tracker::clear() { while (pagelist) { Page* p = pagelist; pagelist = pagelist->next; GC::Free(p); } } LIns* Tracker::get(const void* v) const { struct Tracker::Page* p = findPage(v); JS_ASSERT(p != 0); /* we must have a page for the slot we are looking for */ LIns* i = p->map[(((long)v) & 0xfff) >> 2]; JS_ASSERT(i != 0); return i; } void Tracker::set(const void* v, LIns* ins) { struct Tracker::Page* p = findPage(v); if (!p) p = addPage(v); p->map[(((long)v) & 0xfff) >> 2] = ins; } #define LO ARGSIZE_LO #define F ARGSIZE_F #define Q ARGSIZE_Q #ifdef DEBUG #define NAME(op) ,#op #else #define NAME(op) #endif #define builtin_DoubleToECMAInt32 js_DoubleToECMAInt32 #define builtin_DoubleToECMAUint32 js_DoubleToECMAUint32 #define builtin_CompareStrings js_CompareStrings jsint builtin_StringLength(JSString* s) { return JSSTRING_LENGTH(s); } jsdouble builtin_dmod(jsdouble a, jsdouble b) { if (b == 0.0) { jsdpun u; u.s.hi = JSDOUBLE_HI32_EXPMASK | JSDOUBLE_HI32_MANTMASK; u.s.lo = 0xffffffff; return u.d; } jsdouble r; #ifdef XP_WIN /* Workaround MS fmod bug where 42 % (1/0) => NaN, not 42. */ if (!(JSDOUBLE_IS_FINITE(a) && JSDOUBLE_IS_INFINITE(b))) r = a; else #endif r = fmod(a, b); return r; } #define BUILTIN1(op, at0, atr, tr, t0, cse, fold) \ { (intptr_t)&builtin_##op, (at0 << 2) | atr, cse, fold NAME(op) }, #define BUILTIN2(op, at0, at1, atr, tr, t0, t1, cse, fold) \ { (intptr_t)&builtin_##op, (at0 << 4) | (at1 << 2) | atr, cse, fold NAME(op) }, #define BUILTIN3(op, at0, at1, at2, atr, tr, t0, t1, t2, cse, fold) \ { (intptr_t)&builtin_##op, (at0 << 6) | (at1 << 4) | (at2 << 2) | atr, cse, fold NAME(op) }, static struct CallInfo builtins[] = { #include "builtins.tbl" }; #undef NAME #undef BUILTIN1 #undef BUILTIN2 #undef BUILTIN3 static void buildTypeMap(JSStackFrame* entryFrame, JSStackFrame* fp, JSFrameRegs& regs, char* m); TraceRecorder::TraceRecorder(JSContext* cx, Fragmento* fragmento, Fragment* _fragment) { this->cx = cx; this->fragment = _fragment; entryFrame = cx->fp; entryRegs.pc = entryFrame->regs->pc; entryRegs.sp = entryFrame->regs->sp; printf("entryRegs.pc=%p opcode=%d\n", entryRegs.pc, *entryRegs.pc); fragment->calldepth = 0; lirbuf = new (&gc) LirBuffer(fragmento, builtins); fragment->lirbuf = lirbuf; lir = lir_buf_writer = new (&gc) LirBufWriter(lirbuf); #ifdef DEBUG lirbuf->names = new (&gc) LirNameMap(&gc, builtins, fragmento->labels); lir = verbose_filter = new (&gc) VerboseWriter(&gc, lir, lirbuf->names); #endif lir = cse_filter = new (&gc) CseFilter(lir, &gc); lir = expr_filter = new (&gc) ExprFilter(lir); lir->ins0(LIR_trace); /* generate the entry map and stash it in the trace */ entryNativeFrameSlots = nativeFrameSlots(entryFrame, entryRegs); maxNativeFrameSlots = entryNativeFrameSlots; LIns* data = lir_buf_writer->skip(sizeof(VMFragmentInfo) + entryNativeFrameSlots * sizeof(char)); fragmentInfo = (VMFragmentInfo*)data->payload(); buildTypeMap(entryFrame, entryFrame, entryRegs, fragmentInfo->typeMap); fragmentInfo->nativeStackBase = nativeFrameOffset(&cx->fp->spbase[0]); fragment->vmprivate = fragmentInfo; fragment->param0 = lir->insImm8(LIR_param, Assembler::argRegs[0], 0); fragment->param1 = lir->insImm8(LIR_param, Assembler::argRegs[1], 0); fragment->sp = lir->insLoadi(fragment->param0, offsetof(InterpState, sp)); cx_ins = lir->insLoadi(fragment->param0, offsetof(InterpState, cx)); #ifdef DEBUG lirbuf->names->addName(fragment->param0, "state"); lirbuf->names->addName(fragment->sp, "sp"); lirbuf->names->addName(cx_ins, "cx"); #endif JSStackFrame* fp = cx->fp; unsigned n; for (n = 0; n < fp->argc; ++n) import(&fp->argv[n], "arg", n); for (n = 0; n < fp->nvars; ++n) import(&fp->vars[n], "var", n); for (n = 0; n < (unsigned)(fp->regs->sp - fp->spbase); ++n) import(&fp->spbase[n], "stack", n); } TraceRecorder::~TraceRecorder() { #ifdef DEBUG delete lirbuf->names; delete verbose_filter; #endif delete cse_filter; delete expr_filter; delete lir_buf_writer; } /* Determine the current call depth (starting with the entry frame.) */ unsigned TraceRecorder::calldepth() const { JSStackFrame* fp = cx->fp; unsigned depth = 0; while (fp != entryFrame) { ++depth; fp = fp->down; } return depth; } /* Find the frame that this address belongs to (if any). */ JSStackFrame* TraceRecorder::findFrame(void* p) const { JSStackFrame* fp = cx->fp; while (1) { if ((p >= &fp->argv[0] && p < &fp->argv[fp->argc]) || (p >= &fp->vars[0] && p < &fp->vars[fp->nvars]) || (p >= &fp->spbase[0] && p < &fp->spbase[fp->script->depth])) return fp; if (fp == entryFrame) return NULL; fp = fp->down; } } /* Determine whether an address is part of a currently active frame. */ bool TraceRecorder::onFrame(void* p) const { return findFrame(p) != NULL; } /* Calculate the total number of native frame slots we need from this frame all the way back to the entry frame, including the current stack usage. */ unsigned TraceRecorder::nativeFrameSlots(JSStackFrame* fp, JSFrameRegs& regs) const { unsigned size = 0; while (1) { size += fp->argc + fp->nvars + (regs.sp - fp->spbase); if (fp == entryFrame) return size; fp = fp->down; } } /* Determine the offset in the native frame (marshal) for an address that is part of a currently active frame. */ unsigned TraceRecorder::nativeFrameOffset(void* p) const { JSStackFrame* fp = findFrame(p); JS_ASSERT(fp != NULL); // must be on the frame somewhere unsigned offset = 0; if (p >= &fp->argv[0] && p < &fp->argv[fp->argc]) offset = unsigned((jsval*)p - &fp->argv[0]); else if (p >= &fp->vars[0] && p < &fp->vars[fp->nvars]) offset = (fp->argc + unsigned((jsval*)p - &fp->vars[0])); else { JS_ASSERT((p >= &fp->spbase[0] && p < &fp->spbase[fp->script->depth])); offset = (fp->argc + fp->nvars + unsigned((jsval*)p - &fp->spbase[0])); } if (fp != entryFrame) offset += nativeFrameSlots(fp->down, *fp->regs); return offset * sizeof(double); } /* Track the maximum number of native frame slots we need during execution. */ void TraceRecorder::trackNativeFrameUse(unsigned slots) { if (slots > maxNativeFrameSlots) maxNativeFrameSlots = slots; } /* Return the tag of a jsval. Doubles are checked whether they actually represent an int, in which case we treat them as JSVAL_INT. */ static inline int getType(jsval v) { if (JSVAL_IS_INT(v)) return JSVAL_INT; jsint i; if (JSVAL_IS_DOUBLE(v) && JSDOUBLE_IS_INT(*JSVAL_TO_DOUBLE(v), i)) return JSVAL_INT; return JSVAL_TAG(v); } static inline bool isInt(jsval v) { return getType(v) == JSVAL_INT; } static inline bool isDouble(jsval v) { return (getType(v) == JSVAL_DOUBLE) && !isInt(v); } static inline jsint asInt(jsval v) { JS_ASSERT(isInt(v)); if (JSVAL_IS_DOUBLE(v)) return js_DoubleToECMAInt32(*JSVAL_TO_DOUBLE(v)); return JSVAL_TO_INT(v); } /* Write out a type map for the current scopes and all outer scopes, up until the entry scope. */ static void buildTypeMap(JSStackFrame* entryFrame, JSStackFrame* fp, JSFrameRegs& regs, char* m) { if (fp != entryFrame) buildTypeMap(entryFrame, fp->down, *fp->down->regs, m); for (unsigned n = 0; n < fp->argc; ++n) *m++ = getType(fp->argv[n]); for (unsigned n = 0; n < fp->nvars; ++n) *m++ = getType(fp->vars[n]); for (jsval* sp = fp->spbase; sp < regs.sp; ++sp) *m++ = getType(*sp); } /* Make sure that all loop-carrying values have a stable type. */ static bool verifyTypeStability(JSStackFrame* entryFrame, JSStackFrame* fp, JSFrameRegs& regs, char* m) { if (fp != entryFrame) verifyTypeStability(entryFrame, fp->down, *fp->down->regs, m); for (unsigned n = 0; n < fp->argc; ++n) if (*m++ != getType(fp->argv[n])) return false; for (unsigned n = 0; n < fp->nvars; ++n) if (*m++ != getType(fp->vars[n])) return false; for (jsval* sp = fp->spbase; sp < regs.sp; ++sp) if (*m++ != getType(*sp)) return false; return true; } /* Unbox a jsval into a slot. Slots are wide enough to hold double values directly (instead of storing a pointer to them). */ static bool unbox_jsval(jsval v, int t, double* slot) { if (t != getType(v)) return false; switch (t) { case JSVAL_BOOLEAN: *(bool*)slot = JSVAL_TO_BOOLEAN(v); break; case JSVAL_INT: *(jsint*)slot = asInt(v); break; case JSVAL_DOUBLE: *(jsdouble*)slot = *JSVAL_TO_DOUBLE(v); break; case JSVAL_STRING: *(JSString**)slot = JSVAL_TO_STRING(v); break; default: JS_ASSERT(JSVAL_IS_GCTHING(v)); *(void**)slot = JSVAL_TO_GCTHING(v); } return true; } /* Box a value from the native stack back into the jsval format. Integers that are too large to fit into a jsval are automatically boxed into heap-allocated doubles. */ static bool box_jsval(JSContext* cx, jsval* vp, int t, double* slot) { switch (t) { case JSVAL_BOOLEAN: *vp = BOOLEAN_TO_JSVAL(*(bool*)slot); break; case JSVAL_INT: jsint i = *(jsint*)slot; if (INT_FITS_IN_JSVAL(i)) *vp = INT_TO_JSVAL(i); else return js_NewDoubleInRootedValue(cx, (jsdouble)i, vp); break; case JSVAL_DOUBLE: return js_NewDoubleInRootedValue(cx, *slot, vp); case JSVAL_STRING: *vp = STRING_TO_JSVAL(*(JSString**)slot); break; default: JS_ASSERT(t == JSVAL_OBJECT); *vp = OBJECT_TO_JSVAL(*(JSObject**)slot); break; } return true; } /* Attempt to unbox the given JS frame into a native frame, checking along the way that the supplied typemap holds. */ static bool unbox(JSStackFrame* fp, JSFrameRegs& regs, char* m, double* native) { jsval* vp; for (vp = fp->argv; vp < fp->argv + fp->argc; ++vp) if (!unbox_jsval(*vp, (JSType)*m++, native++)) return false; for (vp = fp->vars; vp < fp->vars + fp->nvars; ++vp) if (!unbox_jsval(*vp, (JSType)*m++, native++)) return false; for (vp = fp->spbase; vp < regs.sp; ++vp) if (!unbox_jsval(*vp, (JSType)*m++, native++)) return false; return true; } /* Attempt to unbox the given JS frame into a native frame, checking along the way that the supplied typemap holds. */ static bool box(JSContext* cx, JSStackFrame* fp, JSFrameRegs& regs, char* m, double* native) { jsval* vp; for (vp = fp->argv; vp < fp->argv + fp->argc; ++vp) if (!box_jsval(cx, vp, (JSType)*m++, native++)) return false; for (vp = fp->vars; vp < fp->vars + fp->nvars; ++vp) if (!box_jsval(cx, vp, (JSType)*m++, native++)) return false; for (vp = fp->spbase; vp < regs.sp; ++vp) if (!box_jsval(cx, vp, (JSType)*m++, native++)) return false; return true; } /* Emit load instructions onto the trace that read the initial stack state. */ void TraceRecorder::import(jsval* p, char *prefix, int index) { JS_ASSERT(onFrame(p)); LIns *ins = lir->insLoad(isDouble(*p) ? LIR_ldq : LIR_ld, fragment->sp, -fragmentInfo->nativeStackBase + nativeFrameOffset(p) + 8); tracker.set(p, ins); #ifdef DEBUG if (prefix) { char name[16]; JS_ASSERT(strlen(prefix) < 10); JS_snprintf(name, sizeof name, "$%s%d", prefix, index); lirbuf->names->addName(ins, name); } #endif } /* Update the tracker. If the value is part of any argv/vars/stack of any currently active frame (onFrame), then issue a write back store. */ void TraceRecorder::set(void* p, LIns* i) { tracker.set(p, i); if (onFrame(p)) lir->insStorei(i, fragment->sp, -fragmentInfo->nativeStackBase + nativeFrameOffset(p) + 8); } LIns* TraceRecorder::get(void* p) { return tracker.get(p); } SideExit* TraceRecorder::snapshot() { /* generate the entry map and stash it in the trace */ unsigned slots = nativeFrameSlots(cx->fp, *cx->fp->regs); trackNativeFrameUse(slots); LIns* data = lir_buf_writer->skip(sizeof(VMSideExitInfo) + slots * sizeof(char)); VMSideExitInfo* si = (VMSideExitInfo*)data->payload(); buildTypeMap(entryFrame, cx->fp, *cx->fp->regs, si->typeMap); /* setup side exit structure */ memset(&exit, 0, sizeof(exit)); #ifdef DEBUG exit.from = fragment; #endif exit.calldepth = calldepth(); exit.sp_adj = (cx->fp->regs->sp - entryRegs.sp) * sizeof(double); exit.ip_adj = cx->fp->regs->pc - entryRegs.pc; exit.vmprivate = si; return &exit; } void TraceRecorder::guard(bool expected, LIns* cond) { lir->insGuard(expected ? LIR_xf : LIR_xt, cond, snapshot()); } void TraceRecorder::closeLoop(Fragmento* fragmento) { if (!verifyTypeStability(entryFrame, entryFrame, entryRegs, fragmentInfo->typeMap)) { #ifdef DEBUG printf("Trace rejected: unstable loop variables.\n"); #endif return; } fragment->lastIns = lir->ins0(LIR_loop); ((VMFragmentInfo*)fragment->vmprivate)->maxNativeFrameSlots = maxNativeFrameSlots; compile(fragmento->assm(), fragment); } bool TraceRecorder::loopEdge(JSContext* cx) { if (cx->fp->regs->pc == entryRegs.pc) { closeLoop(JS_TRACE_MONITOR(cx).fragmento); return false; /* done recording */ } return false; /* abort recording */ } void js_DeleteRecorder(JSContext* cx) { JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); delete tm->recorder; tm->recorder = NULL; } bool js_LoopEdge(JSContext* cx) { JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); /* is the recorder currently active? */ if (tm->recorder) { if (tm->recorder->loopEdge(cx)) return true; /* keep recording */ js_DeleteRecorder(cx); return false; /* done recording */ } if (!tm->fragmento) { Fragmento* fragmento = new (&gc) Fragmento(core); #ifdef DEBUG fragmento->labels = new (&gc) LabelMap(core, NULL); #endif fragmento->assm()->setCallTable(builtins); tm->fragmento = fragmento; } InterpState state; state.ip = (FOpcodep)cx->fp->regs->pc; Fragment* f = tm->fragmento->getLoop(state); if (!f->code()) { tm->recorder = new (&gc) TraceRecorder(cx, tm->fragmento, f); return true; /* start recording */ } /* execute previously recorded race */ VMFragmentInfo* fi = (VMFragmentInfo*)f->vmprivate; double native[fi->maxNativeFrameSlots+1]; #ifdef DEBUG *(uint64*)&native[fi->maxNativeFrameSlots] = 0xdeadbeefdeadbeefLL; #endif unbox(cx->fp, *cx->fp->regs, fi->typeMap, native); double* entry_sp = &native[fi->nativeStackBase/sizeof(double) + (cx->fp->regs->sp - cx->fp->spbase - 1)]; state.sp = (void*)entry_sp; state.rp = NULL; state.f = NULL; state.cx = cx; union { NIns *code; GuardRecord* (FASTCALL *func)(InterpState*, Fragment*); } u; u.code = f->code(); GuardRecord* lr = u.func(&state, NULL); cx->fp->regs->sp += (((double*)state.sp - entry_sp)); cx->fp->regs->pc = (jsbytecode*)state.ip; box(cx, cx->fp, *cx->fp->regs, ((VMSideExitInfo*)lr->vmprivate)->typeMap, native); #ifdef DEBUG JS_ASSERT(*(uint64*)&native[fi->maxNativeFrameSlots] == 0xdeadbeefdeadbeefLL); #endif return false; /* continue with regular interpreter */ } void js_AbortRecording(JSContext* cx, const char* reason) { #ifdef DEBUG printf("Abort recording: %s.\n", reason); #endif JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx); JS_ASSERT(tm->recorder != NULL); js_DeleteRecorder(cx); } jsval& TraceRecorder::argval(unsigned n) const { JS_ASSERT((n >= 0) && (n <= cx->fp->argc)); return cx->fp->argv[n]; } jsval& TraceRecorder::varval(unsigned n) const { JS_ASSERT((n >= 0) && (n <= cx->fp->nvars)); return cx->fp->vars[n]; } jsval& TraceRecorder::stackval(int n) const { JS_ASSERT((cx->fp->regs->sp + n < cx->fp->spbase + cx->fp->script->depth) && (cx->fp->regs->sp + n >= cx->fp->spbase)); return cx->fp->regs->sp[n]; } LIns* TraceRecorder::arg(unsigned n) { return get(&argval(n)); } void TraceRecorder::arg(unsigned n, LIns* i) { set(&argval(n), i); } LIns* TraceRecorder::var(unsigned n) { return get(&varval(n)); } void TraceRecorder::var(unsigned n, LIns* i) { set(&varval(n), i); } LIns* TraceRecorder::stack(int n) { return get(&stackval(n)); } void TraceRecorder::stack(int n, LIns* i) { set(&stackval(n), i); } bool TraceRecorder::inc(jsval& v, jsint incr, bool pre) { if (isInt(v)) { LIns* before = get(&v); LIns* after = lir->ins2i(LIR_add, before, incr); guard(false, lir->ins1(LIR_ov, after)); set(&v, after); stack(0, pre ? after : before); return true; } return false; } bool TraceRecorder::cmp(LOpcode op, bool negate) { jsval& r = stackval(-1); jsval& l = stackval(-2); if (isInt(l) && isInt(r)) { LIns* x = lir->ins2(op, get(&l), get(&r)); if (negate) x = lir->ins2i(LIR_eq, x, 0); bool cond; switch (op) { case LIR_lt: cond = asInt(l) < asInt(r); break; case LIR_gt: cond = asInt(l) > asInt(r); break; case LIR_le: cond = asInt(l) <= asInt(r); break; case LIR_ge: cond = asInt(l) >= asInt(r); break; default: JS_ASSERT(cond == LIR_eq); cond = asInt(l) == asInt(r); break; } /* The interpreter fuses comparisons and the following branch, so we have to do that here as well. */ if (cx->fp->regs->pc[1] == ::JSOP_IFEQ) guard(!cond, x); else if (cx->fp->regs->pc[1] == ::JSOP_IFNE) guard(cond, x); /* We update the stack after the guard. This is safe since the guard bails out at the comparison and the interpreter will this re-execute the comparison. This way the value of the condition doesn't have to be calculated and saved on the stack in most cases. */ set(&l, x); return true; } return false; } bool TraceRecorder::ibinary(LOpcode op, bool ov) { jsval& r = stackval(-1); jsval& l = stackval(-2); if (isInt(l) && isInt(r)) { LIns* result = lir->ins2(op, get(&l), get(&r)); if (ov) guard(false, lir->ins1(LIR_ov, result)); set(&l, result); return true; } return false; } bool TraceRecorder::map_is_native(JSObjectMap* map, LIns* map_ins) { LIns* ops = lir->insLoadi(map_ins, offsetof(JSObjectMap, ops)); if (map->ops == &js_ObjectOps) { guard(true, lir->ins2i(LIR_eq, ops, (long)&js_ObjectOps)); return true; } LIns* n = lir->insLoadi(ops, offsetof(JSObjectOps, newObjectMap)); if (map->ops->newObjectMap == js_ObjectOps.newObjectMap) { guard(true, lir->ins2i(LIR_eq, n, (long)js_ObjectOps.newObjectMap)); return true; } return false; } LIns* TraceRecorder::loadObjectClass(LIns* objld) { return lir->ins2(LIR_and, lir->insLoadi(objld, offsetof(JSObject, fslots[JSSLOT_CLASS])), lir->insImmPtr((void *)~3)); } bool TraceRecorder::JSOP_INTERRUPT() { return false; } bool TraceRecorder::JSOP_PUSH() { return false; } bool TraceRecorder::JSOP_POPV() { return false; } bool TraceRecorder::JSOP_ENTERWITH() { return false; } bool TraceRecorder::JSOP_LEAVEWITH() { return false; } bool TraceRecorder::JSOP_RETURN() { return false; } bool TraceRecorder::JSOP_GOTO() { return false; } bool TraceRecorder::JSOP_IFEQ() { return false; } bool TraceRecorder::JSOP_IFNE() { return false; } bool TraceRecorder::JSOP_ARGUMENTS() { return false; } bool TraceRecorder::JSOP_FORARG() { return false; } bool TraceRecorder::JSOP_FORVAR() { return false; } bool TraceRecorder::JSOP_DUP() { return false; } bool TraceRecorder::JSOP_DUP2() { return false; } bool TraceRecorder::JSOP_SETCONST() { return false; } bool TraceRecorder::JSOP_BITOR() { return ibinary(LIR_or); } bool TraceRecorder::JSOP_BITXOR() { return ibinary(LIR_xor); } bool TraceRecorder::JSOP_BITAND() { return ibinary(LIR_and); } bool TraceRecorder::JSOP_EQ() { return cmp(LIR_eq); } bool TraceRecorder::JSOP_NE() { return cmp(LIR_eq, true); } bool TraceRecorder::JSOP_LT() { return cmp(LIR_lt); } bool TraceRecorder::JSOP_LE() { return cmp(LIR_le); } bool TraceRecorder::JSOP_GT() { return cmp(LIR_gt); } bool TraceRecorder::JSOP_GE() { return cmp(LIR_ge); } bool TraceRecorder::JSOP_LSH() { return ibinary(LIR_lsh, false); } bool TraceRecorder::JSOP_RSH() { return ibinary(LIR_rsh, false); } bool TraceRecorder::JSOP_URSH() { return ibinary(LIR_ush, false); } bool TraceRecorder::JSOP_ADD() { jsval& r = stackval(-1); jsval& l = stackval(-2); if (isInt(l) && isInt(r)) { LIns* result = lir->ins2(LIR_add, get(&l), get(&r)); guard(false, lir->ins1(LIR_ov, result)); set(&l, result); return true; } return false; } bool TraceRecorder::JSOP_SUB() { return ibinary(LIR_sub, true); } bool TraceRecorder::JSOP_MUL() { return ibinary(LIR_mul, true); } bool TraceRecorder::JSOP_DIV() { return false; } bool TraceRecorder::JSOP_MOD() { return false; } bool TraceRecorder::JSOP_NOT() { return false; } bool TraceRecorder::JSOP_BITNOT() { return false; } bool TraceRecorder::JSOP_NEG() { jsval& v = stackval(-1); set(&v, lir->ins1(LIR_neg, get(&v))); return true; } bool TraceRecorder::JSOP_NEW() { return false; } bool TraceRecorder::JSOP_DELNAME() { return false; } bool TraceRecorder::JSOP_DELPROP() { return false; } bool TraceRecorder::JSOP_DELELEM() { return false; } bool TraceRecorder::JSOP_TYPEOF() { return false; } bool TraceRecorder::JSOP_VOID() { return false; } bool TraceRecorder::JSOP_INCNAME() { return false; } bool TraceRecorder::JSOP_INCPROP() { return false; } bool TraceRecorder::JSOP_INCELEM() { return false; } bool TraceRecorder::JSOP_DECNAME() { return false; } bool TraceRecorder::JSOP_DECPROP() { return false; } bool TraceRecorder::JSOP_DECELEM() { return false; } bool TraceRecorder::JSOP_NAMEINC() { return false; } bool TraceRecorder::JSOP_PROPINC() { return false; } bool TraceRecorder::JSOP_ELEMINC() { return false; } bool TraceRecorder::JSOP_NAMEDEC() { return false; } bool TraceRecorder::JSOP_PROPDEC() { return false; } bool TraceRecorder::JSOP_ELEMDEC() { return false; } bool TraceRecorder::JSOP_GETPROP() { return false; } bool TraceRecorder::JSOP_SETPROP() { return false; } bool TraceRecorder::guardAndLoadDenseArray(jsval& aval, jsval& ival, LIns*& objld, LIns*& dslotsld) { JS_ASSERT(!JSVAL_IS_PRIMITIVE(aval)); JSObject *obj = JSVAL_TO_OBJECT(aval); objld = get(&aval); dslotsld = lir->insLoadi(objld, offsetof(JSObject, dslots)); // guard(OBJ_GET_CLASS(obj) == &js_ArrayClass); guard(true, lir->ins2(LIR_eq, loadObjectClass(objld), lir->insImmPtr(&js_ArrayClass))); jsint i = asInt(ival); jsuint capacity = ARRAY_DENSE_LENGTH(obj); jsint length = obj->fslots[JSSLOT_ARRAY_LENGTH]; if ((jsuint)i >= capacity || i >= length) return false; // load lengths LIns* lengthld = lir->insLoadi(objld, offsetof(JSObject, fslots[JSSLOT_ARRAY_LENGTH])); // guard(i < length); guard(true, lir->ins2(LIR_lt, get(&ival), lengthld)); // guard(i < capacity) guard(false, lir->ins_eq0(dslotsld)); guard(true, lir->ins2(LIR_lt, get(&ival), lir->insLoadi(dslotsld, sizeof(jsval) * -1))); return true; } bool TraceRecorder::JSOP_GETELEM() { jsval& r = stackval(-1); jsval& l = stackval(-2); if (!isInt(r)) return false; jsint i = asInt(r); if (!JSVAL_IS_PRIMITIVE(l)) { JSObject* obj = JSVAL_TO_OBJECT(l); if (OBJ_IS_DENSE_ARRAY(cx, obj)) { LInsp objld, dslotsld; if (!guardAndLoadDenseArray(l, r, objld, dslotsld)) return false; LIns* slotoff = lir->ins2(LIR_mul, get(&r), lir->insImm(sizeof(jsval))); // XXX LIR_ld only accepts constant displacements! return false; LIns* val = lir->insLoad(LIR_ld, dslotsld, slotoff); if (obj->dslots[i] == JSVAL_HOLE) return false; guard(false, lir->ins2i(LIR_eq, val, JSVAL_HOLE)); set(&l, val); return true; } } return false; } bool TraceRecorder::JSOP_SETELEM() { jsval& v = stackval(-1); jsval& r = stackval(-2); jsval& l = stackval(-3); if (!isInt(r)) return false; jsint i = asInt(r); if (!JSVAL_IS_PRIMITIVE(l)) { JSObject* obj = JSVAL_TO_OBJECT(l); if (OBJ_IS_DENSE_ARRAY(cx, obj)) { LInsp objld, dslotsld; if (!guardAndLoadDenseArray(l, r, objld, dslotsld)) return false; LIns* slotoff = lir->ins2(LIR_mul, get(&r), lir->insImm(sizeof(jsval))); // XXX LIR_ld only accepts constant displacements! return false; LIns* oldval = lir->insLoad(LIR_ld, dslotsld, slotoff); if (obj->dslots[i] == JSVAL_HOLE) return false; guard(false, lir->ins2i(LIR_eq, oldval, JSVAL_HOLE)); lir->insStore(get(&v), dslotsld, slotoff); set(&l, get(&v)); return true; } } return false; } bool TraceRecorder::JSOP_CALLNAME() { return false; } bool TraceRecorder::JSOP_CALL() { return false; } bool TraceRecorder::JSOP_NAME() { return false; } bool TraceRecorder::JSOP_DOUBLE() { return false; } bool TraceRecorder::JSOP_STRING() { return false; } bool TraceRecorder::JSOP_ZERO() { stack(0, lir->insImm(0)); return true; } bool TraceRecorder::JSOP_ONE() { stack(0, lir->insImm(1)); return true; } bool TraceRecorder::JSOP_NULL() { stack(0, lir->insImm(0)); return true; } bool TraceRecorder::JSOP_THIS() { return false; } bool TraceRecorder::JSOP_FALSE() { stack(0, lir->insImm(0)); return true; } bool TraceRecorder::JSOP_TRUE() { stack(0, lir->insImm(1)); return true; } bool TraceRecorder::JSOP_OR() { return false; } bool TraceRecorder::JSOP_AND() { return false; } bool TraceRecorder::JSOP_TABLESWITCH() { return false; } bool TraceRecorder::JSOP_LOOKUPSWITCH() { return false; } bool TraceRecorder::JSOP_STRICTEQ() { return false; } bool TraceRecorder::JSOP_STRICTNE() { return false; } bool TraceRecorder::JSOP_CLOSURE() { return false; } bool TraceRecorder::JSOP_EXPORTALL() { return false; } bool TraceRecorder::JSOP_EXPORTNAME() { return false; } bool TraceRecorder::JSOP_IMPORTALL() { return false; } bool TraceRecorder::JSOP_IMPORTPROP() { return false; } bool TraceRecorder::JSOP_IMPORTELEM() { return false; } bool TraceRecorder::JSOP_OBJECT() { return false; } bool TraceRecorder::JSOP_POP() { return false; } bool TraceRecorder::JSOP_POS() { return false; } bool TraceRecorder::JSOP_TRAP() { return false; } bool TraceRecorder::JSOP_GETARG() { stack(0, arg(GET_ARGNO(cx->fp->regs->pc))); return true; } bool TraceRecorder::JSOP_SETARG() { arg(GET_ARGNO(cx->fp->regs->pc), stack(-1)); return true; } bool TraceRecorder::JSOP_GETVAR() { stack(0, var(GET_VARNO(cx->fp->regs->pc))); return true; } bool TraceRecorder::JSOP_SETVAR() { var(GET_VARNO(cx->fp->regs->pc), stack(-1)); return true; } bool TraceRecorder::JSOP_UINT16() { stack(0, lir->insImm((jsint) GET_UINT16(cx->fp->regs->pc))); return true; } bool TraceRecorder::JSOP_NEWINIT() { return false; } bool TraceRecorder::JSOP_ENDINIT() { return false; } bool TraceRecorder::JSOP_INITPROP() { return false; } bool TraceRecorder::JSOP_INITELEM() { return false; } bool TraceRecorder::JSOP_DEFSHARP() { return false; } bool TraceRecorder::JSOP_USESHARP() { return false; } bool TraceRecorder::JSOP_INCARG() { return inc(argval(GET_ARGNO(cx->fp->regs->pc)), 1, true); } bool TraceRecorder::JSOP_INCVAR() { return inc(varval(GET_VARNO(cx->fp->regs->pc)), 1, true); } bool TraceRecorder::JSOP_DECARG() { return inc(argval(GET_ARGNO(cx->fp->regs->pc)), -1, true); } bool TraceRecorder::JSOP_DECVAR() { return inc(varval(GET_VARNO(cx->fp->regs->pc)), -1, true); } bool TraceRecorder::JSOP_ARGINC() { return inc(argval(GET_ARGNO(cx->fp->regs->pc)), 1, false); } bool TraceRecorder::JSOP_VARINC() { return inc(varval(GET_VARNO(cx->fp->regs->pc)), 1, false); } bool TraceRecorder::JSOP_ARGDEC() { return inc(argval(GET_ARGNO(cx->fp->regs->pc)), -1, false); } bool TraceRecorder::JSOP_VARDEC() { return inc(varval(GET_VARNO(cx->fp->regs->pc)), -1, false); } bool TraceRecorder::JSOP_ITER() { return false; } bool TraceRecorder::JSOP_FORNAME() { return false; } bool TraceRecorder::JSOP_FORPROP() { return false; } bool TraceRecorder::JSOP_FORELEM() { return false; } bool TraceRecorder::JSOP_POPN() { return false; } bool TraceRecorder::JSOP_BINDNAME() { /* BINDNAME is a no-op for the recorder. We wait until we hit the SETNAME/SETPROP that uses it. This is safe because the interpreter calculates here the scope we will use and we will use that value to guard against in SETNAME/SETPROP. */ return true; } bool TraceRecorder::JSOP_SETNAME() { return false; } bool TraceRecorder::JSOP_THROW() { return false; } bool TraceRecorder::JSOP_IN() { return false; } bool TraceRecorder::JSOP_INSTANCEOF() { return false; } bool TraceRecorder::JSOP_DEBUGGER() { return false; } bool TraceRecorder::JSOP_GOSUB() { return false; } bool TraceRecorder::JSOP_RETSUB() { return false; } bool TraceRecorder::JSOP_EXCEPTION() { return false; } bool TraceRecorder::JSOP_LINENO() { return false; } bool TraceRecorder::JSOP_CONDSWITCH() { return false; } bool TraceRecorder::JSOP_CASE() { return false; } bool TraceRecorder::JSOP_DEFAULT() { return false; } bool TraceRecorder::JSOP_EVAL() { return false; } bool TraceRecorder::JSOP_ENUMELEM() { return false; } bool TraceRecorder::JSOP_GETTER() { return false; } bool TraceRecorder::JSOP_SETTER() { return false; } bool TraceRecorder::JSOP_DEFFUN() { return false; } bool TraceRecorder::JSOP_DEFCONST() { return false; } bool TraceRecorder::JSOP_DEFVAR() { return false; } bool TraceRecorder::JSOP_ANONFUNOBJ() { return false; } bool TraceRecorder::JSOP_NAMEDFUNOBJ() { return false; } bool TraceRecorder::JSOP_SETLOCALPOP() { return false; } bool TraceRecorder::JSOP_GROUP() { return true; // no-op } bool TraceRecorder::JSOP_SETCALL() { return false; } bool TraceRecorder::JSOP_TRY() { return false; } bool TraceRecorder::JSOP_FINALLY() { return false; } bool TraceRecorder::JSOP_NOP() { return false; } bool TraceRecorder::JSOP_ARGSUB() { return false; } bool TraceRecorder::JSOP_ARGCNT() { return false; } bool TraceRecorder::JSOP_DEFLOCALFUN() { return false; } bool TraceRecorder::JSOP_GOTOX() { return false; } bool TraceRecorder::JSOP_IFEQX() { return false; } bool TraceRecorder::JSOP_IFNEX() { return false; } bool TraceRecorder::JSOP_ORX() { return false; } bool TraceRecorder::JSOP_ANDX() { return false; } bool TraceRecorder::JSOP_GOSUBX() { return false; } bool TraceRecorder::JSOP_CASEX() { return false; } bool TraceRecorder::JSOP_DEFAULTX() { return false; } bool TraceRecorder::JSOP_TABLESWITCHX() { return false; } bool TraceRecorder::JSOP_LOOKUPSWITCHX() { return false; } bool TraceRecorder::JSOP_BACKPATCH() { return false; } bool TraceRecorder::JSOP_BACKPATCH_POP() { return false; } bool TraceRecorder::JSOP_THROWING() { return false; } bool TraceRecorder::JSOP_SETRVAL() { return false; } bool TraceRecorder::JSOP_RETRVAL() { return false; } bool TraceRecorder::JSOP_GETGVAR() { return false; } bool TraceRecorder::JSOP_SETGVAR() { return false; } bool TraceRecorder::JSOP_INCGVAR() { return false; } bool TraceRecorder::JSOP_DECGVAR() { return false; } bool TraceRecorder::JSOP_GVARINC() { return false; } bool TraceRecorder::JSOP_GVARDEC() { return false; } bool TraceRecorder::JSOP_REGEXP() { return false; } bool TraceRecorder::JSOP_DEFXMLNS() { return false; } bool TraceRecorder::JSOP_ANYNAME() { return false; } bool TraceRecorder::JSOP_QNAMEPART() { return false; } bool TraceRecorder::JSOP_QNAMECONST() { return false; } bool TraceRecorder::JSOP_QNAME() { return false; } bool TraceRecorder::JSOP_TOATTRNAME() { return false; } bool TraceRecorder::JSOP_TOATTRVAL() { return false; } bool TraceRecorder::JSOP_ADDATTRNAME() { return false; } bool TraceRecorder::JSOP_ADDATTRVAL() { return false; } bool TraceRecorder::JSOP_BINDXMLNAME() { return false; } bool TraceRecorder::JSOP_SETXMLNAME() { return false; } bool TraceRecorder::JSOP_XMLNAME() { return false; } bool TraceRecorder::JSOP_DESCENDANTS() { return false; } bool TraceRecorder::JSOP_FILTER() { return false; } bool TraceRecorder::JSOP_ENDFILTER() { return false; } bool TraceRecorder::JSOP_TOXML() { return false; } bool TraceRecorder::JSOP_TOXMLLIST() { return false; } bool TraceRecorder::JSOP_XMLTAGEXPR() { return false; } bool TraceRecorder::JSOP_XMLELTEXPR() { return false; } bool TraceRecorder::JSOP_XMLOBJECT() { return false; } bool TraceRecorder::JSOP_XMLCDATA() { return false; } bool TraceRecorder::JSOP_XMLCOMMENT() { return false; } bool TraceRecorder::JSOP_XMLPI() { return false; } bool TraceRecorder::JSOP_CALLPROP() { return false; } bool TraceRecorder::JSOP_GETFUNNS() { return false; } bool TraceRecorder::JSOP_UNUSED186() { return false; } bool TraceRecorder::JSOP_DELDESC() { return false; } bool TraceRecorder::JSOP_UINT24() { stack(0, lir->insImm((jsint) GET_UINT24(cx->fp->regs->pc))); return true; } bool TraceRecorder::JSOP_INDEXBASE() { return false; } bool TraceRecorder::JSOP_RESETBASE() { return false; } bool TraceRecorder::JSOP_RESETBASE0() { return false; } bool TraceRecorder::JSOP_STARTXML() { return false; } bool TraceRecorder::JSOP_STARTXMLEXPR() { return false; } bool TraceRecorder::JSOP_CALLELEM() { return false; } bool TraceRecorder::JSOP_STOP() { return false; } bool TraceRecorder::JSOP_GETXPROP() { return false; } bool TraceRecorder::JSOP_CALLXMLNAME() { return false; } bool TraceRecorder::JSOP_TYPEOFEXPR() { return false; } bool TraceRecorder::JSOP_ENTERBLOCK() { return false; } bool TraceRecorder::JSOP_LEAVEBLOCK() { return false; } bool TraceRecorder::JSOP_GETLOCAL() { return false; } bool TraceRecorder::JSOP_SETLOCAL() { return false; } bool TraceRecorder::JSOP_INCLOCAL() { return false; } bool TraceRecorder::JSOP_DECLOCAL() { return false; } bool TraceRecorder::JSOP_LOCALINC() { return false; } bool TraceRecorder::JSOP_LOCALDEC() { return false; } bool TraceRecorder::JSOP_FORLOCAL() { return false; } bool TraceRecorder::JSOP_FORCONST() { return false; } bool TraceRecorder::JSOP_ENDITER() { return false; } bool TraceRecorder::JSOP_GENERATOR() { return false; } bool TraceRecorder::JSOP_YIELD() { return false; } bool TraceRecorder::JSOP_ARRAYPUSH() { return false; } bool TraceRecorder::JSOP_UNUSED213() { return false; } bool TraceRecorder::JSOP_ENUMCONSTELEM() { return false; } bool TraceRecorder::JSOP_LEAVEBLOCKEXPR() { return false; } bool TraceRecorder::JSOP_GETTHISPROP() { return false; } bool TraceRecorder::JSOP_GETARGPROP() { return false; } bool TraceRecorder::JSOP_GETVARPROP() { return false; } bool TraceRecorder::JSOP_GETLOCALPROP() { return false; } bool TraceRecorder::JSOP_INDEXBASE1() { return false; } bool TraceRecorder::JSOP_INDEXBASE2() { return false; } bool TraceRecorder::JSOP_INDEXBASE3() { return false; } bool TraceRecorder::JSOP_CALLGVAR() { return false; } bool TraceRecorder::JSOP_CALLVAR() { return false; } bool TraceRecorder::JSOP_CALLARG() { return false; } bool TraceRecorder::JSOP_CALLLOCAL() { return false; } bool TraceRecorder::JSOP_INT8() { stack(0, lir->insImm(GET_INT8(cx->fp->regs->pc))); return true; } bool TraceRecorder::JSOP_INT32() { stack(0, lir->insImm(GET_INT32(cx->fp->regs->pc))); return true; } bool TraceRecorder::JSOP_LENGTH() { return false; } bool TraceRecorder::JSOP_NEWARRAY() { return false; } bool TraceRecorder::JSOP_HOLE() { return false; }