From 1c370170289dd77193ae4de8ea3a785d072a2c2a Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 11 Dec 2009 19:10:36 -0800 Subject: [PATCH] Lazily import stack and global slots (bug 515749, original patch and r=gal). --- js/src/jsrecursion.cpp | 2 +- js/src/jstracer.cpp | 248 ++++++++++++++++++++++++++++------------- js/src/jstracer.h | 25 ++++- 3 files changed, 196 insertions(+), 79 deletions(-) diff --git a/js/src/jsrecursion.cpp b/js/src/jsrecursion.cpp index 2ad62fdc407..2ea643d9c3c 100644 --- a/js/src/jsrecursion.cpp +++ b/js/src/jsrecursion.cpp @@ -600,7 +600,7 @@ TraceRecorder::downRecursion() lirbuf->rp = lir->ins2(LIR_piadd, lirbuf->rp, lir->insImmWord(sizeof(FrameInfo*))); lir->insStorei(lirbuf->rp, lirbuf->state, offsetof(InterpState, rp)); --callDepth; - clearFrameSlotsFromCache(); + clearFrameSlotsFromTracker(nativeFrameTracker); /* * If the callee and caller have identical call sites, this is a down- diff --git a/js/src/jstracer.cpp b/js/src/jstracer.cpp index 3f605744405..e31909da657 100644 --- a/js/src/jstracer.cpp +++ b/js/src/jstracer.cpp @@ -2235,6 +2235,15 @@ public: } }; +void +TypeMap::set(unsigned stackSlots, unsigned ngslots, + const JSTraceType* stackTypeMap, const JSTraceType* globalTypeMap) +{ + setLength(ngslots + stackSlots); + memcpy(data(), stackTypeMap, stackSlots * sizeof(JSTraceType)); + memcpy(data() + stackSlots, globalTypeMap, ngslots * sizeof(JSTraceType)); +} + /* * Capture the type map for the selected slots of the global object and currently pending * stack frames. @@ -2373,6 +2382,7 @@ TraceRecorder::TraceRecorder(JSContext* cx, VMSideExit* anchor, VMFragment* frag eos_ins(NULL), eor_ins(NULL), loopLabel(NULL), + importTypeMap(&tempAlloc()), lirbuf(new (tempAlloc()) LirBuffer(tempAlloc())), mark(*traceMonitor->traceAlloc), numSideExitsBefore(tree->sideExits.length()), @@ -2688,14 +2698,20 @@ TraceRecorder::p2i(nanojit::LIns* ins) #endif } +ptrdiff_t +TraceRecorder::nativeGlobalSlot(jsval* p) const +{ + JS_ASSERT(isGlobal(p)); + if (size_t(p - globalObj->fslots) < JS_INITIAL_NSLOTS) + return ptrdiff_t(p - globalObj->fslots); + return ptrdiff_t((p - globalObj->dslots) + JS_INITIAL_NSLOTS); +} + /* Determine the offset in the native global frame for a jsval we track. */ ptrdiff_t TraceRecorder::nativeGlobalOffset(jsval* p) const { - JS_ASSERT(isGlobal(p)); - if (size_t(p - globalObj->fslots) < JS_INITIAL_NSLOTS) - return size_t(p - globalObj->fslots) * sizeof(double); - return ((p - globalObj->dslots) + JS_INITIAL_NSLOTS) * sizeof(double); + return nativeGlobalSlot(p) * sizeof(double); } /* Determine whether a value is a global stack slot. */ @@ -2730,6 +2746,13 @@ TraceRecorder::nativeStackOffset(jsval* p) const } return offset; } + +JS_REQUIRES_STACK ptrdiff_t +TraceRecorder::nativeStackSlot(jsval* p) const +{ + return nativeStackOffset(p) / sizeof(double); +} + /* * Return the offset, from InterpState:sp, for the given jsval. Shorthand for: * -TreeFragment::nativeStackBase + nativeStackOffset(p). @@ -2828,9 +2851,10 @@ ValueToNative(JSContext* cx, jsval v, JSTraceType type, double* slot) #endif return; } + default: + JS_NOT_REACHED("unexpected type"); + break; } - - JS_NOT_REACHED("unexpected type"); } void @@ -3001,6 +3025,9 @@ js_NativeToValue(JSContext* cx, jsval& v, JSTraceType type, double* slot) #endif break; } + default: + JS_NOT_REACHED("unexpected type"); + break; } return true; } @@ -3628,38 +3655,6 @@ public: } }; -class ImportUnboxedStackSlotVisitor : public SlotVisitorBase -{ - TraceRecorder &mRecorder; - LIns *mBase; - ptrdiff_t mStackOffset; - JSTraceType *mTypemap; - JSStackFrame *mFp; -public: - ImportUnboxedStackSlotVisitor(TraceRecorder &recorder, - LIns *base, - ptrdiff_t stackOffset, - JSTraceType *typemap) : - mRecorder(recorder), - mBase(base), - mStackOffset(stackOffset), - mTypemap(typemap) - {} - - JS_REQUIRES_STACK JS_ALWAYS_INLINE bool - visitStackSlots(jsval *vp, size_t count, JSStackFrame* fp) { - for (size_t i = 0; i < count; ++i) { - if (*mTypemap != TT_JSVAL) { - mRecorder.import(mBase, mStackOffset, vp++, *mTypemap, - stackSlotKind(), i, fp); - } - mTypemap++; - mStackOffset += sizeof(double); - } - return true; - } -}; - JS_REQUIRES_STACK void TraceRecorder::import(TreeFragment* tree, LIns* sp, unsigned stackSlots, unsigned ngslots, unsigned callDepth, JSTraceType* typeMap) @@ -3692,26 +3687,24 @@ TraceRecorder::import(TreeFragment* tree, LIns* sp, unsigned stackSlots, unsigne (JSTraceType*)alloca(sizeof(JSTraceType) * length)); } JS_ASSERT(ngslots == tree->nGlobalTypes()); - ptrdiff_t offset = -tree->nativeStackBase; /* * Check whether there are any values on the stack we have to unbox and do * that first before we waste any time fetching the state from the stack. */ if (!anchor || anchor->exitType != RECURSIVE_SLURP_FAIL_EXIT) { - ImportBoxedStackSlotVisitor boxedStackVisitor(*this, sp, offset, typeMap); + ImportBoxedStackSlotVisitor boxedStackVisitor(*this, sp, -tree->nativeStackBase, typeMap); VisitStackSlots(boxedStackVisitor, cx, callDepth); } - ImportGlobalSlotVisitor globalVisitor(*this, eos_ins, globalTypeMap); - VisitGlobalSlots(globalVisitor, cx, globalObj, ngslots, - tree->globalSlots->data()); - if (!anchor || anchor->exitType != RECURSIVE_SLURP_FAIL_EXIT) { - ImportUnboxedStackSlotVisitor unboxedStackVisitor(*this, sp, offset, - typeMap); - VisitStackSlots(unboxedStackVisitor, cx, callDepth); - } + /* + * Remember the import type map so we can lazily import later whatever + * we need. + */ + importTypeMap.set(importStackSlots = stackSlots, + importGlobalSlots = ngslots, + typeMap, globalTypeMap); } JS_REQUIRES_STACK bool @@ -3738,13 +3731,41 @@ TraceRecorder::isValidSlot(JSScope* scope, JSScopeProperty* sprop) return true; } +/* Lazily import a global slot if we don't already have it in the tracker. */ +JS_REQUIRES_STACK void +TraceRecorder::importGlobalSlot(unsigned slot) +{ + JS_ASSERT(slot == uint16(slot)); + JS_ASSERT(STOBJ_NSLOTS(globalObj) <= MAX_GLOBAL_SLOTS); + + jsval* vp = &STOBJ_GET_SLOT(globalObj, slot); + JS_ASSERT(!known(vp)); + + /* Add the slot to the list of interned global slots. */ + JSTraceType type; + int index = tree->globalSlots->offsetOf(slot); + if (index == -1) { + type = getCoercedType(*vp); + if (type == TT_INT32 && oracle.isGlobalSlotUndemotable(cx, slot)) + type = TT_DOUBLE; + index = (int)tree->globalSlots->length(); + tree->globalSlots->add(slot); + tree->typeMap.add(type); + SpecializeTreesToMissingGlobals(cx, globalObj, tree); + JS_ASSERT(tree->nGlobalTypes() == tree->globalSlots->length()); + } else { + type = importTypeMap[importStackSlots + index]; + JS_ASSERT(type != TT_IGNORE); + } + import(eos_ins, slot * sizeof(double), vp, type, "global", index, NULL); +} + /* Lazily import a global slot if we don't already have it in the tracker. */ JS_REQUIRES_STACK bool TraceRecorder::lazilyImportGlobalSlot(unsigned slot) { if (slot != uint16(slot)) /* we use a table of 16-bit ints, bail out if that's not enough */ return false; - /* * If the global object grows too large, alloca in ExecuteTree might fail, * so abort tracing on global objects with unreasonably many slots. @@ -3754,17 +3775,7 @@ TraceRecorder::lazilyImportGlobalSlot(unsigned slot) jsval* vp = &STOBJ_GET_SLOT(globalObj, slot); if (known(vp)) return true; /* we already have it */ - unsigned index = tree->globalSlots->length(); - - /* Add the slot to the list of interned global slots. */ - JS_ASSERT(tree->nGlobalTypes() == tree->globalSlots->length()); - tree->globalSlots->add(slot); - JSTraceType type = getCoercedType(*vp); - if (type == TT_INT32 && oracle.isGlobalSlotUndemotable(cx, slot)) - type = TT_DOUBLE; - tree->typeMap.add(type); - import(eos_ins, slot*sizeof(double), vp, type, "global", index, NULL); - SpecializeTreesToMissingGlobals(cx, globalObj, tree); + importGlobalSlot(slot); return true; } @@ -3787,7 +3798,6 @@ JS_REQUIRES_STACK void TraceRecorder::set(jsval* p, LIns* i, bool initializing, bool demote) { JS_ASSERT(i != NULL); - JS_ASSERT(initializing || known(p)); checkForGlobalObjectReallocation(); tracker.set(p, i); @@ -3829,8 +3839,22 @@ TraceRecorder::set(jsval* p, LIns* i, bool initializing, bool demote) JS_REQUIRES_STACK LIns* TraceRecorder::get(jsval* p) { - JS_ASSERT(known(p)); checkForGlobalObjectReallocation(); + LIns* x = tracker.get(p); + if (x) + return x; + if (isGlobal(p)) { + unsigned slot = nativeGlobalSlot(p); + JS_ASSERT(tree->globalSlots->offsetOf(slot) != -1); + importGlobalSlot(slot); + } else { + unsigned slot = nativeStackSlot(p); + JSTraceType type = importTypeMap[slot]; + JS_ASSERT(type != TT_IGNORE); + import(lirbuf->sp, -tree->nativeStackBase + slot * sizeof(jsdouble), + p, type, "stack", slot, cx->fp); + } + JS_ASSERT(known(p)); return tracker.get(p); } @@ -4003,9 +4027,17 @@ JS_REQUIRES_STACK JSTraceType TraceRecorder::determineSlotType(jsval* vp) { JSTraceType m; - LIns* i = get(vp); if (isNumber(*vp)) { - m = isPromoteInt(i) ? TT_INT32 : TT_DOUBLE; + LIns* i = tracker.get(vp); + if (i) { + m = isPromoteInt(i) ? TT_INT32 : TT_DOUBLE; + } else if (isGlobal(vp)) { + int offset = tree->globalSlots->offsetOf(nativeGlobalSlot(vp)); + JS_ASSERT(offset != -1); + m = importTypeMap[importStackSlots + offset]; + } else { + m = importTypeMap[nativeStackSlot(vp)]; + } } else if (JSVAL_IS_OBJECT(*vp)) { if (JSVAL_IS_NULL(*vp)) m = TT_NULL; @@ -4513,7 +4545,21 @@ class SlotMap : public SlotVisitorBase JS_REQUIRES_STACK JS_ALWAYS_INLINE void addSlot(jsval* vp) { - slots.add(SlotInfo(vp, isPromoteInt(mRecorder.get(vp)))); + bool promoteInt = false; + if (isNumber(*vp)) { + if (LIns* i = mRecorder.tracker.get(vp)) { + promoteInt = isPromoteInt(i); + } else if (mRecorder.isGlobal(vp)) { + int offset = mRecorder.tree->globalSlots->offsetOf(mRecorder.nativeGlobalSlot(vp)); + JS_ASSERT(offset != -1); + promoteInt = mRecorder.importTypeMap[mRecorder.importStackSlots + offset] == + TT_INT32; + } else { + promoteInt = mRecorder.importTypeMap[mRecorder.nativeStackSlot(vp)] == + TT_INT32; + } + } + slots.add(SlotInfo(vp, promoteInt)); } JS_REQUIRES_STACK JS_ALWAYS_INLINE void @@ -5156,16 +5202,66 @@ TraceRecorder::emitTreeCall(TreeFragment* inner, VMSideExit* exit, LIns* inner_s JS_ASSERT(map[i] != TT_JSVAL); #endif + /* + * Flush values from the tracker which could have been invalidated by the + * inner tree. This means variables local to this frame and global slots. + * It's safe to keep the offset cache around, we just want to force reloads. + */ + clearFrameSlotsFromTracker(tracker); + SlotList& gslots = *tree->globalSlots; + for (unsigned i = 0; i < gslots.length(); i++) { + unsigned slot = gslots[i]; + jsval* vp = &STOBJ_GET_SLOT(globalObj, slot); + tracker.set(vp, NULL); + } + + /* + * Begin the complicated process of building a usable typemap by looking + * at all stack slots. + */ + TypeMap stackTypeMap(NULL); + stackTypeMap.setLength(NativeStackSlots(cx, callDepth)); + CaptureTypesVisitor visitor(cx, stackTypeMap.data()); + VisitStackSlots(visitor, cx, callDepth); + JS_ASSERT(stackTypeMap.length() >= exit->numStackSlots); + + /* + * The import map layout looks like: + * ------------- + * Initial Frame (callDepth=0) + * ... + * ... + * ... + * Current Frame (callDepth=N) + * ------------- + * + * Anything in between the innermost and outermost frames must be in the + * tracker at this point, so it's kind of pointless to make sure it's + * in the typemap. It'd be a pain to have separate typemaps for each + * "hole" though, so make one big one knowing that the holes won't be + * read. + * + * NOTE: It's important that setLength() doesn't mess up stuff below the + * current frame. It has to shrink safely. + */ + importTypeMap.setLength(stackTypeMap.length()); + unsigned startOfInnerFrame = stackTypeMap.length() - exit->numStackSlots; +#ifdef DEBUG + for (unsigned i = importStackSlots; i < startOfInnerFrame; i++) + stackTypeMap[i] = TT_IGNORE; +#endif + for (unsigned i = 0; i < exit->numStackSlots; i++) + importTypeMap[startOfInnerFrame + i] = exit->stackTypeMap()[i]; + /* * Bug 502604 - It is illegal to extend from the outer typemap without * first extending from the inner. Make a new typemap here. */ - TypeMap fullMap(NULL); - fullMap.add(exit->stackTypeMap(), exit->numStackSlots); - BuildGlobalTypeMapFromInnerTree(fullMap, exit); + BuildGlobalTypeMapFromInnerTree(importTypeMap, exit); - import(inner, inner_sp_ins, exit->numStackSlots, fullMap.length() - exit->numStackSlots, - exit->calldepth, fullMap.data()); + importStackSlots = stackTypeMap.length(); + importGlobalSlots = importTypeMap.length() - importStackSlots; + JS_ASSERT(importGlobalSlots == tree->globalSlots->length()); /* Restore sp and rp to their original values (we still have them in a register). */ if (callDepth > 0) { @@ -9646,7 +9742,7 @@ TraceRecorder::guardNotGlobalObject(JSObject* obj, LIns* obj_ins) } JS_REQUIRES_STACK void -TraceRecorder::clearFrameSlotsFromCache() +TraceRecorder::clearFrameSlotsFromTracker(Tracker& which) { /* * Clear out all slots of this frame in the nativeFrameTracker. Different @@ -9668,13 +9764,13 @@ TraceRecorder::clearFrameSlotsFromCache() vp = &fp->argv[-2]; vpstop = &fp->argv[argSlots(fp)]; while (vp < vpstop) - nativeFrameTracker.set(vp++, (LIns*)0); - nativeFrameTracker.set(&fp->argsobj, (LIns*)0); + which.set(vp++, (LIns*)0); + which.set(&fp->argsobj, (LIns*)0); } vp = &fp->slots[0]; vpstop = &fp->slots[fp->script->nslots]; while (vp < vpstop) - nativeFrameTracker.set(vp++, (LIns*)0); + which.set(vp++, (LIns*)0); } /* @@ -9908,7 +10004,7 @@ TraceRecorder::record_JSOP_RETURN() debug_only_printf(LC_TMTracer, "returning from %s\n", js_AtomToPrintableString(cx, cx->fp->fun->atom)); - clearFrameSlotsFromCache(); + clearFrameSlotsFromTracker(nativeFrameTracker); return ARECORD_CONTINUE; } @@ -14259,7 +14355,7 @@ TraceRecorder::record_JSOP_STOP() } else { rval_ins = INS_CONST(JSVAL_TO_SPECIAL(JSVAL_VOID)); } - clearFrameSlotsFromCache(); + clearFrameSlotsFromTracker(nativeFrameTracker); return ARECORD_CONTINUE; } diff --git a/js/src/jstracer.h b/js/src/jstracer.h index 8d439de564c..1986641ab57 100644 --- a/js/src/jstracer.h +++ b/js/src/jstracer.h @@ -156,6 +156,16 @@ public: T* data() const { return _data; } + + int offsetOf(T slot) { + T* p = _data; + unsigned n = 0; + for (n = 0; n < _len; ++n) + if (*p++ == slot) + return n; + return -1; + } + }; /* @@ -337,7 +347,8 @@ enum JSTraceType_ TT_STRING = 4, /* pointer to JSString */ TT_NULL = 5, /* null */ TT_PSEUDOBOOLEAN = 6, /* true, false, or undefined (0, 1, or 2) */ - TT_FUNCTION = 7 /* pointer to JSObject whose class is js_FunctionClass */ + TT_FUNCTION = 7, /* pointer to JSObject whose class is js_FunctionClass */ + TT_IGNORE = 8 } #if defined(__GNUC__) && defined(USE_TRACE_TYPE_ENUM) __attribute__((packed)) @@ -362,6 +373,8 @@ typedef Queue SlotList; class TypeMap : public Queue { public: TypeMap(nanojit::Allocator* alloc) : Queue(alloc) {} + void set(unsigned stackSlots, unsigned ngslots, + const JSTraceType* stackTypeMap, const JSTraceType* globalTypeMap); JS_REQUIRES_STACK void captureTypes(JSContext* cx, JSObject* globalObj, SlotList& slots, unsigned callDepth); JS_REQUIRES_STACK void captureMissingGlobalTypes(JSContext* cx, JSObject* globalObj, SlotList& slots, unsigned stackSlots); @@ -941,6 +954,11 @@ class TraceRecorder nanojit::LIns* const eor_ins; nanojit::LIns* const loopLabel; + /* Lazy slot import state. */ + unsigned importStackSlots; + unsigned importGlobalSlots; + TypeMap importTypeMap; + /* * The LirBuffer used to supply memory to our LirWriter pipeline. Also contains the most recent * instruction for {sp, rp, state}. Also contains names for debug JIT spew. Should be split. @@ -1040,8 +1058,10 @@ class TraceRecorder JS_REQUIRES_STACK nanojit::GuardRecord* createGuardRecord(VMSideExit* exit); bool isGlobal(jsval* p) const; + ptrdiff_t nativeGlobalSlot(jsval *p) const; ptrdiff_t nativeGlobalOffset(jsval* p) const; JS_REQUIRES_STACK ptrdiff_t nativeStackOffset(jsval* p) const; + JS_REQUIRES_STACK ptrdiff_t nativeStackSlot(jsval* p) const; JS_REQUIRES_STACK ptrdiff_t nativespOffset(jsval* p) const; JS_REQUIRES_STACK void import(nanojit::LIns* base, ptrdiff_t offset, jsval* p, JSTraceType t, const char *prefix, uintN index, JSStackFrame *fp); @@ -1051,6 +1071,7 @@ class TraceRecorder JS_REQUIRES_STACK bool isValidSlot(JSScope* scope, JSScopeProperty* sprop); JS_REQUIRES_STACK bool lazilyImportGlobalSlot(unsigned slot); + JS_REQUIRES_STACK void importGlobalSlot(unsigned slot); JS_REQUIRES_STACK void guard(bool expected, nanojit::LIns* cond, ExitType exitType); JS_REQUIRES_STACK void guard(bool expected, nanojit::LIns* cond, VMSideExit* exit); @@ -1266,7 +1287,7 @@ class TraceRecorder ExitType exitType); JS_REQUIRES_STACK RecordingStatus guardNotGlobalObject(JSObject* obj, nanojit::LIns* obj_ins); - void clearFrameSlotsFromCache(); + void clearFrameSlotsFromTracker(Tracker& which); JS_REQUIRES_STACK void putArguments(); JS_REQUIRES_STACK RecordingStatus guardCallee(jsval& callee); JS_REQUIRES_STACK JSStackFrame *guardArguments(JSObject *obj, nanojit::LIns* obj_ins,