| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * 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 "vm/Stack.h" |
| |
| #include "mozilla/PodOperations.h" |
| |
| #include "jscntxt.h" |
| |
| #include "gc/Marking.h" |
| #ifdef JS_ION |
| #include "jit/BaselineFrame.h" |
| #include "jit/IonCompartment.h" |
| #endif |
| |
| #include "vm/Interpreter-inl.h" |
| #include "vm/ScopeObject-inl.h" |
| #include "vm/Stack-inl.h" |
| #include "vm/Probes-inl.h" |
| |
| using namespace js; |
| |
| using mozilla::PodCopy; |
| |
| /*****************************************************************************/ |
| |
| void |
| StackFrame::initExecuteFrame(JSContext *cx, JSScript *script, AbstractFramePtr evalInFramePrev, |
| const Value &thisv, JSObject &scopeChain, ExecuteType type) |
| { |
| /* |
| * See encoding of ExecuteType. When GLOBAL isn't set, we are executing a |
| * script in the context of another frame and the frame type is determined |
| * by the context. |
| */ |
| flags_ = type | HAS_SCOPECHAIN | HAS_BLOCKCHAIN; |
| |
| JSObject *callee = NULL; |
| if (!(flags_ & (GLOBAL))) { |
| if (evalInFramePrev) { |
| JS_ASSERT(evalInFramePrev.isFunctionFrame() || evalInFramePrev.isGlobalFrame()); |
| if (evalInFramePrev.isFunctionFrame()) { |
| callee = evalInFramePrev.callee(); |
| flags_ |= FUNCTION; |
| } else { |
| flags_ |= GLOBAL; |
| } |
| } else { |
| ScriptFrameIter iter(cx); |
| JS_ASSERT(iter.isFunctionFrame() || iter.isGlobalFrame()); |
| if (iter.isFunctionFrame()) { |
| callee = iter.callee(); |
| flags_ |= FUNCTION; |
| } else { |
| flags_ |= GLOBAL; |
| } |
| } |
| } |
| |
| Value *dstvp = (Value *)this - 2; |
| dstvp[1] = thisv; |
| |
| if (isFunctionFrame()) { |
| dstvp[0] = ObjectValue(*callee); |
| exec.fun = &callee->as<JSFunction>(); |
| u.evalScript = script; |
| } else { |
| JS_ASSERT(isGlobalFrame()); |
| dstvp[0] = NullValue(); |
| exec.script = script; |
| #ifdef DEBUG |
| u.evalScript = (JSScript *)0xbad; |
| #endif |
| } |
| |
| scopeChain_ = &scopeChain; |
| prev_ = NULL; |
| prevpc_ = NULL; |
| prevsp_ = NULL; |
| blockChain_ = NULL; |
| |
| JS_ASSERT_IF(evalInFramePrev, isDebuggerFrame()); |
| evalInFramePrev_ = evalInFramePrev; |
| |
| #ifdef DEBUG |
| Debug_SetValueRangeToCrashOnTouch(&rval_, 1); |
| hookData_ = (void *)0xbad; |
| #endif |
| } |
| |
| template <StackFrame::TriggerPostBarriers doPostBarrier> |
| void |
| StackFrame::copyFrameAndValues(JSContext *cx, Value *vp, StackFrame *otherfp, |
| const Value *othervp, Value *othersp) |
| { |
| JS_ASSERT(othervp == otherfp->generatorArgsSnapshotBegin()); |
| JS_ASSERT(othersp >= otherfp->slots()); |
| JS_ASSERT(othersp <= otherfp->generatorSlotsSnapshotBegin() + otherfp->script()->nslots); |
| |
| /* Copy args, StackFrame, and slots. */ |
| const Value *srcend = otherfp->generatorArgsSnapshotEnd(); |
| Value *dst = vp; |
| for (const Value *src = othervp; src < srcend; src++, dst++) { |
| *dst = *src; |
| if (doPostBarrier) |
| HeapValue::writeBarrierPost(*dst, dst); |
| } |
| |
| *this = *otherfp; |
| argv_ = vp + 2; |
| unsetPushedSPSFrame(); |
| if (doPostBarrier) |
| writeBarrierPost(); |
| |
| srcend = othersp; |
| dst = slots(); |
| for (const Value *src = otherfp->slots(); src < srcend; src++, dst++) { |
| *dst = *src; |
| if (doPostBarrier) |
| HeapValue::writeBarrierPost(*dst, dst); |
| } |
| |
| if (cx->compartment()->debugMode()) |
| DebugScopes::onGeneratorFrameChange(otherfp, this, cx); |
| } |
| |
| /* Note: explicit instantiation for js_NewGenerator located in jsiter.cpp. */ |
| template |
| void StackFrame::copyFrameAndValues<StackFrame::NoPostBarrier>( |
| JSContext *, Value *, StackFrame *, const Value *, Value *); |
| template |
| void StackFrame::copyFrameAndValues<StackFrame::DoPostBarrier>( |
| JSContext *, Value *, StackFrame *, const Value *, Value *); |
| |
| void |
| StackFrame::writeBarrierPost() |
| { |
| /* This needs to follow the same rules as in StackFrame::mark. */ |
| if (scopeChain_) |
| JSObject::writeBarrierPost(scopeChain_, (void *)&scopeChain_); |
| if (flags_ & HAS_ARGS_OBJ) |
| JSObject::writeBarrierPost(argsObj_, (void *)&argsObj_); |
| if (isFunctionFrame()) { |
| JSFunction::writeBarrierPost(exec.fun, (void *)&exec.fun); |
| if (isEvalFrame()) |
| JSScript::writeBarrierPost(u.evalScript, (void *)&u.evalScript); |
| } else { |
| JSScript::writeBarrierPost(exec.script, (void *)&exec.script); |
| } |
| if (hasReturnValue()) |
| HeapValue::writeBarrierPost(rval_, &rval_); |
| } |
| |
| JSGenerator * |
| StackFrame::maybeSuspendedGenerator(JSRuntime *rt) |
| { |
| /* |
| * A suspended generator's frame is embedded inside the JSGenerator object |
| * and is not currently running. |
| */ |
| if (!isGeneratorFrame() || !isSuspended()) |
| return NULL; |
| |
| /* |
| * Once we know we have a suspended generator frame, there is a static |
| * offset from the frame's snapshot to beginning of the JSGenerator. |
| */ |
| char *vp = reinterpret_cast<char *>(generatorArgsSnapshotBegin()); |
| char *p = vp - offsetof(JSGenerator, stackSnapshot); |
| JSGenerator *gen = reinterpret_cast<JSGenerator *>(p); |
| JS_ASSERT(gen->fp == this); |
| return gen; |
| } |
| |
| bool |
| StackFrame::copyRawFrameSlots(AutoValueVector *vec) |
| { |
| if (!vec->resize(numFormalArgs() + script()->nfixed)) |
| return false; |
| PodCopy(vec->begin(), argv(), numFormalArgs()); |
| PodCopy(vec->begin() + numFormalArgs(), slots(), script()->nfixed); |
| return true; |
| } |
| |
| JSObject * |
| StackFrame::createRestParameter(JSContext *cx) |
| { |
| JS_ASSERT(fun()->hasRest()); |
| unsigned nformal = fun()->nargs - 1, nactual = numActualArgs(); |
| unsigned nrest = (nactual > nformal) ? nactual - nformal : 0; |
| Value *restvp = argv() + nformal; |
| RootedObject obj(cx, NewDenseCopiedArray(cx, nrest, restvp, NULL)); |
| if (!obj) |
| return NULL; |
| |
| RootedTypeObject type(cx, types::GetTypeCallerInitObject(cx, JSProto_Array)); |
| if (!type) |
| return NULL; |
| obj->setType(type); |
| |
| /* Ensure that values in the rest array are represented in the type of the array. */ |
| for (unsigned i = 0; i < nrest; i++) |
| types::AddTypePropertyId(cx, obj, JSID_VOID, restvp[i]); |
| |
| return obj; |
| } |
| |
| static inline void |
| AssertDynamicScopeMatchesStaticScope(JSContext *cx, JSScript *script, JSObject *scope) |
| { |
| #ifdef DEBUG |
| RootedObject enclosingScope(cx, script->enclosingStaticScope()); |
| for (StaticScopeIter i(cx, enclosingScope); !i.done(); i++) { |
| if (i.hasDynamicScopeObject()) { |
| /* |
| * 'with' does not participate in the static scope of the script, |
| * but it does in the dynamic scope, so skip them here. |
| */ |
| while (scope->is<WithObject>()) |
| scope = &scope->as<WithObject>().enclosingScope(); |
| |
| switch (i.type()) { |
| case StaticScopeIter::BLOCK: |
| JS_ASSERT(i.block() == scope->as<ClonedBlockObject>().staticBlock()); |
| scope = &scope->as<ClonedBlockObject>().enclosingScope(); |
| break; |
| case StaticScopeIter::FUNCTION: |
| JS_ASSERT(scope->as<CallObject>().callee().nonLazyScript() == i.funScript()); |
| scope = &scope->as<CallObject>().enclosingScope(); |
| break; |
| case StaticScopeIter::NAMED_LAMBDA: |
| scope = &scope->as<DeclEnvObject>().enclosingScope(); |
| break; |
| } |
| } |
| } |
| |
| /* |
| * Ideally, we'd JS_ASSERT(!scope->is<ScopeObject>()) but the enclosing |
| * lexical scope chain stops at eval() boundaries. See StaticScopeIter |
| * comment. |
| */ |
| #endif |
| } |
| |
| bool |
| StackFrame::initFunctionScopeObjects(JSContext *cx) |
| { |
| CallObject *callobj = CallObject::createForFunction(cx, this); |
| if (!callobj) |
| return false; |
| pushOnScopeChain(*callobj); |
| flags_ |= HAS_CALL_OBJ; |
| return true; |
| } |
| |
| bool |
| StackFrame::prologue(JSContext *cx) |
| { |
| RootedScript script(cx, this->script()); |
| |
| JS_ASSERT(!isGeneratorFrame()); |
| JS_ASSERT(cx->interpreterRegs().pc == script->code); |
| |
| if (isEvalFrame()) { |
| if (script->strict) { |
| CallObject *callobj = CallObject::createForStrictEval(cx, this); |
| if (!callobj) |
| return false; |
| pushOnScopeChain(*callobj); |
| flags_ |= HAS_CALL_OBJ; |
| } |
| Probes::enterScript(cx, script, NULL, this); |
| return true; |
| } |
| |
| if (isGlobalFrame()) { |
| Probes::enterScript(cx, script, NULL, this); |
| return true; |
| } |
| |
| JS_ASSERT(isNonEvalFunctionFrame()); |
| AssertDynamicScopeMatchesStaticScope(cx, script, scopeChain()); |
| |
| if (fun()->isHeavyweight() && !initFunctionScopeObjects(cx)) |
| return false; |
| |
| if (isConstructing()) { |
| RootedObject callee(cx, &this->callee()); |
| JSObject *obj = CreateThisForFunction(cx, callee, useNewType()); |
| if (!obj) |
| return false; |
| functionThis() = ObjectValue(*obj); |
| } |
| |
| Probes::enterScript(cx, script, script->function(), this); |
| return true; |
| } |
| |
| void |
| StackFrame::epilogue(JSContext *cx) |
| { |
| JS_ASSERT(!isYielding()); |
| JS_ASSERT(!hasBlockChain()); |
| |
| RootedScript script(cx, this->script()); |
| Probes::exitScript(cx, script, script->function(), this); |
| |
| if (isEvalFrame()) { |
| if (isStrictEvalFrame()) { |
| JS_ASSERT_IF(hasCallObj(), scopeChain()->as<CallObject>().isForEval()); |
| if (cx->compartment()->debugMode()) |
| DebugScopes::onPopStrictEvalScope(this); |
| } else if (isDirectEvalFrame()) { |
| if (isDebuggerFrame()) |
| JS_ASSERT(!scopeChain()->is<ScopeObject>()); |
| } else { |
| /* |
| * Debugger.Object.prototype.evalInGlobal creates indirect eval |
| * frames scoped to the given global; |
| * Debugger.Object.prototype.evalInGlobalWithBindings creates |
| * indirect eval frames scoped to an object carrying the introduced |
| * bindings. |
| */ |
| if (isDebuggerFrame()) { |
| JS_ASSERT(scopeChain()->is<GlobalObject>() || |
| scopeChain()->enclosingScope()->is<GlobalObject>()); |
| } else { |
| JS_ASSERT(scopeChain()->is<GlobalObject>()); |
| } |
| } |
| return; |
| } |
| |
| if (isGlobalFrame()) { |
| JS_ASSERT(!scopeChain()->is<ScopeObject>()); |
| return; |
| } |
| |
| JS_ASSERT(isNonEvalFunctionFrame()); |
| |
| if (fun()->isHeavyweight()) |
| JS_ASSERT_IF(hasCallObj(), |
| scopeChain()->as<CallObject>().callee().nonLazyScript() == script); |
| else |
| AssertDynamicScopeMatchesStaticScope(cx, script, scopeChain()); |
| |
| if (cx->compartment()->debugMode()) |
| DebugScopes::onPopCall(this, cx); |
| |
| if (isConstructing() && thisValue().isObject() && returnValue().isPrimitive()) |
| setReturnValue(ObjectValue(constructorThis())); |
| } |
| |
| bool |
| StackFrame::pushBlock(JSContext *cx, StaticBlockObject &block) |
| { |
| JS_ASSERT_IF(hasBlockChain(), blockChain_ == block.enclosingBlock()); |
| |
| if (block.needsClone()) { |
| Rooted<StaticBlockObject *> blockHandle(cx, &block); |
| ClonedBlockObject *clone = ClonedBlockObject::create(cx, blockHandle, this); |
| if (!clone) |
| return false; |
| |
| pushOnScopeChain(*clone); |
| |
| blockChain_ = blockHandle; |
| } else { |
| blockChain_ = █ |
| } |
| |
| flags_ |= HAS_BLOCKCHAIN; |
| return true; |
| } |
| |
| void |
| StackFrame::popBlock(JSContext *cx) |
| { |
| JS_ASSERT(hasBlockChain()); |
| |
| if (cx->compartment()->debugMode()) |
| DebugScopes::onPopBlock(cx, this); |
| |
| if (blockChain_->needsClone()) { |
| JS_ASSERT(scopeChain_->as<ClonedBlockObject>().staticBlock() == *blockChain_); |
| popOffScopeChain(); |
| } |
| |
| blockChain_ = blockChain_->enclosingBlock(); |
| } |
| |
| void |
| StackFrame::popWith(JSContext *cx) |
| { |
| if (cx->compartment()->debugMode()) |
| DebugScopes::onPopWith(this); |
| |
| JS_ASSERT(scopeChain()->is<WithObject>()); |
| popOffScopeChain(); |
| } |
| |
| void |
| StackFrame::mark(JSTracer *trc) |
| { |
| /* |
| * Normally we would use MarkRoot here, except that generators also take |
| * this path. However, generators use a special write barrier when the stack |
| * frame is copied to the floating frame. Therefore, no barrier is needed. |
| */ |
| if (flags_ & HAS_SCOPECHAIN) |
| gc::MarkObjectUnbarriered(trc, &scopeChain_, "scope chain"); |
| if (flags_ & HAS_ARGS_OBJ) |
| gc::MarkObjectUnbarriered(trc, &argsObj_, "arguments"); |
| if (isFunctionFrame()) { |
| gc::MarkObjectUnbarriered(trc, &exec.fun, "fun"); |
| if (isEvalFrame()) |
| gc::MarkScriptUnbarriered(trc, &u.evalScript, "eval script"); |
| } else { |
| gc::MarkScriptUnbarriered(trc, &exec.script, "script"); |
| } |
| if (IS_GC_MARKING_TRACER(trc)) |
| script()->compartment()->zone()->active = true; |
| gc::MarkValueUnbarriered(trc, &returnValue(), "rval"); |
| } |
| |
| void |
| StackFrame::markValues(JSTracer *trc, Value *sp) |
| { |
| JS_ASSERT(sp >= slots()); |
| gc::MarkValueRootRange(trc, sp - slots(), slots(), "vm_stack"); |
| if (hasArgs()) { |
| // Mark callee, |this| and arguments. |
| unsigned argc = Max(numActualArgs(), numFormalArgs()); |
| gc::MarkValueRootRange(trc, argc + 2, argv_ - 2, "fp argv"); |
| } |
| } |
| |
| static void |
| MarkInterpreterActivation(JSTracer *trc, InterpreterActivation *act) |
| { |
| for (InterpreterFrameIterator frames(act); !frames.done(); ++frames) { |
| StackFrame *fp = frames.frame(); |
| fp->markValues(trc, frames.sp()); |
| fp->mark(trc); |
| } |
| } |
| |
| void |
| js::MarkInterpreterActivations(JSRuntime *rt, JSTracer *trc) |
| { |
| for (ActivationIterator iter(rt); !iter.done(); ++iter) { |
| Activation *act = iter.activation(); |
| if (act->isInterpreter()) |
| MarkInterpreterActivation(trc, act->asInterpreter()); |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| StackFrame * |
| InterpreterStack::pushInvokeFrame(JSContext *cx, const CallArgs &args, InitialFrameFlags initial, |
| FrameGuard *fg) |
| { |
| LifoAlloc::Mark mark = allocator_.mark(); |
| |
| RootedFunction fun(cx, &args.callee().as<JSFunction>()); |
| RootedScript script(cx, fun->nonLazyScript()); |
| |
| StackFrame::Flags flags = ToFrameFlags(initial); |
| Value *argv; |
| StackFrame *fp = getCallFrame(cx, args, script, &flags, &argv); |
| if (!fp) |
| return NULL; |
| |
| fp->mark_ = mark; |
| fp->initCallFrame(cx, NULL, NULL, NULL, *fun, script, argv, args.length(), flags); |
| fg->setPushed(*this, fp); |
| return fp; |
| } |
| |
| StackFrame * |
| InterpreterStack::pushExecuteFrame(JSContext *cx, HandleScript script, const Value &thisv, |
| HandleObject scopeChain, ExecuteType type, |
| AbstractFramePtr evalInFrame, FrameGuard *fg) |
| { |
| LifoAlloc::Mark mark = allocator_.mark(); |
| |
| unsigned nvars = 2 /* callee, this */ + script->nslots; |
| uint8_t *buffer = allocateFrame(cx, sizeof(StackFrame) + nvars * sizeof(Value)); |
| if (!buffer) |
| return NULL; |
| |
| StackFrame *fp = reinterpret_cast<StackFrame *>(buffer + 2 * sizeof(Value)); |
| fp->mark_ = mark; |
| fp->initExecuteFrame(cx, script, evalInFrame, thisv, *scopeChain, type); |
| fp->initVarsToUndefined(); |
| |
| fg->setPushed(*this, fp); |
| return fp; |
| } |
| |
| /*****************************************************************************/ |
| |
| /* MSVC PGO causes xpcshell startup crashes. */ |
| #if defined(_MSC_VER) |
| # pragma optimize("g", off) |
| #endif |
| |
| void |
| ScriptFrameIter::popActivation() |
| { |
| ++data_.activations_; |
| settleOnActivation(); |
| } |
| |
| void |
| ScriptFrameIter::popInterpreterFrame() |
| { |
| JS_ASSERT(data_.state_ == SCRIPTED); |
| |
| ++data_.interpFrames_; |
| |
| if (data_.interpFrames_.done()) |
| popActivation(); |
| else |
| data_.pc_ = data_.interpFrames_.pc(); |
| } |
| |
| void |
| ScriptFrameIter::settleOnActivation() |
| { |
| while (true) { |
| if (data_.activations_.done()) { |
| data_.state_ = DONE; |
| return; |
| } |
| |
| Activation *activation = data_.activations_.activation(); |
| |
| // If JS_SaveFrameChain was called, stop iterating here (unless |
| // GO_THROUGH_SAVED is set). |
| if (data_.savedOption_ == STOP_AT_SAVED && activation->hasSavedFrameChain()) { |
| data_.state_ = DONE; |
| return; |
| } |
| |
| // Skip activations from another context if needed. |
| JS_ASSERT(activation->cx()); |
| JS_ASSERT(data_.cx_); |
| if (data_.contextOption_ == CURRENT_CONTEXT && activation->cx() != data_.cx_) { |
| ++data_.activations_; |
| continue; |
| } |
| |
| #ifdef JS_ION |
| if (activation->isJit()) { |
| // JS_ION crashes on certain platforms. The fix is to skip the JIT |
| // frames when it will crash due to an empty jit stack. |
| if (NULL == data_.activations_.jitTop()) { |
| ++data_.activations_; |
| continue; |
| } |
| data_.ionFrames_ = jit::IonFrameIterator(data_.activations_); |
| |
| // Stop at the first scripted frame. |
| while (!data_.ionFrames_.isScripted() && !data_.ionFrames_.done()) |
| ++data_.ionFrames_; |
| |
| // It's possible to have an JitActivation with no scripted frames, |
| // for instance if we hit an over-recursion during bailout. |
| if (data_.ionFrames_.done()) { |
| ++data_.activations_; |
| continue; |
| } |
| |
| nextJitFrame(); |
| data_.state_ = JIT; |
| return; |
| } |
| #endif |
| |
| JS_ASSERT(activation->isInterpreter()); |
| |
| InterpreterActivation *interpAct = activation->asInterpreter(); |
| data_.interpFrames_ = InterpreterFrameIterator(interpAct); |
| |
| // If we OSR'ed into JIT code, skip the interpreter frame so that |
| // the same frame is not reported twice. |
| if (data_.interpFrames_.frame()->runningInJit()) { |
| ++data_.interpFrames_; |
| if (data_.interpFrames_.done()) { |
| ++data_.activations_; |
| continue; |
| } |
| } |
| |
| JS_ASSERT(!data_.interpFrames_.frame()->runningInJit()); |
| data_.pc_ = data_.interpFrames_.pc(); |
| data_.state_ = SCRIPTED; |
| return; |
| } |
| } |
| |
| ScriptFrameIter::Data::Data(JSContext *cx, PerThreadData *perThread, SavedOption savedOption, |
| ContextOption contextOption) |
| : perThread_(perThread), |
| cx_(cx), |
| savedOption_(savedOption), |
| contextOption_(contextOption), |
| pc_(NULL), |
| interpFrames_(NULL), |
| activations_(cx->runtime()) |
| #ifdef JS_ION |
| , ionFrames_((uint8_t *)NULL) |
| #endif |
| { |
| } |
| |
| ScriptFrameIter::Data::Data(const ScriptFrameIter::Data &other) |
| : perThread_(other.perThread_), |
| cx_(other.cx_), |
| savedOption_(other.savedOption_), |
| contextOption_(other.contextOption_), |
| state_(other.state_), |
| pc_(other.pc_), |
| interpFrames_(other.interpFrames_), |
| activations_(other.activations_) |
| #ifdef JS_ION |
| , ionFrames_(other.ionFrames_) |
| #endif |
| { |
| } |
| |
| ScriptFrameIter::ScriptFrameIter(JSContext *cx, SavedOption savedOption) |
| : data_(cx, &cx->runtime()->mainThread, savedOption, CURRENT_CONTEXT) |
| #ifdef JS_ION |
| , ionInlineFrames_(cx, (js::jit::IonFrameIterator*) NULL) |
| #endif |
| { |
| settleOnActivation(); |
| } |
| |
| ScriptFrameIter::ScriptFrameIter(JSContext *cx, ContextOption contextOption, SavedOption savedOption) |
| : data_(cx, &cx->runtime()->mainThread, savedOption, contextOption) |
| #ifdef JS_ION |
| , ionInlineFrames_(cx, (js::jit::IonFrameIterator*) NULL) |
| #endif |
| { |
| settleOnActivation(); |
| } |
| |
| ScriptFrameIter::ScriptFrameIter(const ScriptFrameIter &other) |
| : data_(other.data_) |
| #ifdef JS_ION |
| , ionInlineFrames_(other.data_.cx_, |
| data_.ionFrames_.isScripted() ? &other.ionInlineFrames_ : NULL) |
| #endif |
| { |
| } |
| |
| ScriptFrameIter::ScriptFrameIter(const Data &data) |
| : data_(data) |
| #ifdef JS_ION |
| , ionInlineFrames_(data.cx_, data_.ionFrames_.isOptimizedJS() ? &data_.ionFrames_ : NULL) |
| #endif |
| { |
| JS_ASSERT(data.cx_); |
| } |
| |
| #ifdef JS_ION |
| void |
| ScriptFrameIter::nextJitFrame() |
| { |
| if (data_.ionFrames_.isOptimizedJS()) { |
| ionInlineFrames_.resetOn(&data_.ionFrames_); |
| data_.pc_ = ionInlineFrames_.pc(); |
| } else { |
| JS_ASSERT(data_.ionFrames_.isBaselineJS()); |
| data_.ionFrames_.baselineScriptAndPc(NULL, &data_.pc_); |
| } |
| } |
| |
| void |
| ScriptFrameIter::popJitFrame() |
| { |
| JS_ASSERT(data_.state_ == JIT); |
| |
| if (data_.ionFrames_.isOptimizedJS() && ionInlineFrames_.more()) { |
| ++ionInlineFrames_; |
| data_.pc_ = ionInlineFrames_.pc(); |
| return; |
| } |
| |
| ++data_.ionFrames_; |
| while (!data_.ionFrames_.done() && !data_.ionFrames_.isScripted()) |
| ++data_.ionFrames_; |
| |
| if (!data_.ionFrames_.done()) { |
| nextJitFrame(); |
| return; |
| } |
| |
| popActivation(); |
| } |
| #endif |
| |
| ScriptFrameIter & |
| ScriptFrameIter::operator++() |
| { |
| switch (data_.state_) { |
| case DONE: |
| JS_NOT_REACHED("Unexpected state"); |
| case SCRIPTED: |
| if (interpFrame()->isDebuggerFrame() && interpFrame()->evalInFramePrev()) { |
| AbstractFramePtr eifPrev = interpFrame()->evalInFramePrev(); |
| |
| // Eval-in-frame can cross contexts and works across saved frame |
| // chains. |
| ContextOption prevContextOption = data_.contextOption_; |
| SavedOption prevSavedOption = data_.savedOption_; |
| data_.contextOption_ = ALL_CONTEXTS; |
| data_.savedOption_ = GO_THROUGH_SAVED; |
| |
| popInterpreterFrame(); |
| |
| while (isIon() || abstractFramePtr() != eifPrev) { |
| if (data_.state_ == JIT) { |
| #ifdef JS_ION |
| popJitFrame(); |
| #else |
| JS_NOT_REACHED("Invalid state"); |
| #endif |
| } else { |
| popInterpreterFrame(); |
| } |
| } |
| |
| data_.contextOption_ = prevContextOption; |
| data_.savedOption_ = prevSavedOption; |
| data_.cx_ = data_.activations_.activation()->cx(); |
| break; |
| } |
| popInterpreterFrame(); |
| break; |
| case JIT: |
| #ifdef JS_ION |
| popJitFrame(); |
| break; |
| #else |
| JS_NOT_REACHED("Unexpected state"); |
| #endif |
| } |
| return *this; |
| } |
| |
| ScriptFrameIter::Data * |
| ScriptFrameIter::copyData() const |
| { |
| #ifdef JS_ION |
| /* |
| * This doesn't work for optimized Ion frames since ionInlineFrames_ is |
| * not copied. |
| */ |
| JS_ASSERT(data_.ionFrames_.type() != jit::IonFrame_OptimizedJS); |
| #endif |
| return data_.cx_->new_<Data>(data_); |
| } |
| |
| JSCompartment * |
| ScriptFrameIter::compartment() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case SCRIPTED: |
| case JIT: |
| return data_.activations_.activation()->compartment(); |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| return NULL; |
| } |
| |
| bool |
| ScriptFrameIter::isFunctionFrame() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case SCRIPTED: |
| return interpFrame()->isFunctionFrame(); |
| case JIT: |
| #ifdef JS_ION |
| JS_ASSERT(data_.ionFrames_.isScripted()); |
| if (data_.ionFrames_.isBaselineJS()) |
| return data_.ionFrames_.isFunctionFrame(); |
| return ionInlineFrames_.isFunctionFrame(); |
| #else |
| break; |
| #endif |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| return false; |
| } |
| |
| bool |
| ScriptFrameIter::isGlobalFrame() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case SCRIPTED: |
| return interpFrame()->isGlobalFrame(); |
| case JIT: |
| #ifdef JS_ION |
| if (data_.ionFrames_.isBaselineJS()) |
| return data_.ionFrames_.baselineFrame()->isGlobalFrame(); |
| JS_ASSERT(!script()->isForEval()); |
| return !script()->function(); |
| #else |
| break; |
| #endif |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| return false; |
| } |
| |
| bool |
| ScriptFrameIter::isEvalFrame() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case SCRIPTED: |
| return interpFrame()->isEvalFrame(); |
| case JIT: |
| #ifdef JS_ION |
| if (data_.ionFrames_.isBaselineJS()) |
| return data_.ionFrames_.baselineFrame()->isEvalFrame(); |
| JS_ASSERT(!script()->isForEval()); |
| return false; |
| #else |
| break; |
| #endif |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| return false; |
| } |
| |
| bool |
| ScriptFrameIter::isNonEvalFunctionFrame() const |
| { |
| JS_ASSERT(!done()); |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case SCRIPTED: |
| return interpFrame()->isNonEvalFunctionFrame(); |
| case JIT: |
| return !isEvalFrame() && isFunctionFrame(); |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| return false; |
| } |
| |
| bool |
| ScriptFrameIter::isGeneratorFrame() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case SCRIPTED: |
| return interpFrame()->isGeneratorFrame(); |
| case JIT: |
| return false; |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| return false; |
| } |
| |
| bool |
| ScriptFrameIter::isConstructing() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case JIT: |
| #ifdef JS_ION |
| if (data_.ionFrames_.isOptimizedJS()) |
| return ionInlineFrames_.isConstructing(); |
| JS_ASSERT(data_.ionFrames_.isBaselineJS()); |
| return data_.ionFrames_.isConstructing(); |
| #else |
| break; |
| #endif |
| case SCRIPTED: |
| return interpFrame()->isConstructing(); |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| return false; |
| } |
| |
| AbstractFramePtr |
| ScriptFrameIter::abstractFramePtr() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case JIT: |
| #ifdef JS_ION |
| if (data_.ionFrames_.isBaselineJS()) |
| return data_.ionFrames_.baselineFrame(); |
| #endif |
| break; |
| case SCRIPTED: |
| JS_ASSERT(interpFrame()); |
| return AbstractFramePtr(interpFrame()); |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| return NullFramePtr(); |
| } |
| |
| void |
| ScriptFrameIter::updatePcQuadratic() |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case SCRIPTED: { |
| StackFrame *frame = interpFrame(); |
| InterpreterActivation *activation = data_.activations_.activation()->asInterpreter(); |
| |
| // Look for the current frame. |
| data_.interpFrames_ = InterpreterFrameIterator(activation); |
| while (data_.interpFrames_.frame() != frame) |
| ++data_.interpFrames_; |
| |
| // Update the pc. |
| JS_ASSERT(data_.interpFrames_.frame() == frame); |
| data_.pc_ = data_.interpFrames_.pc(); |
| return; |
| } |
| case JIT: |
| #ifdef JS_ION |
| if (data_.ionFrames_.isBaselineJS()) { |
| jit::BaselineFrame *frame = data_.ionFrames_.baselineFrame(); |
| jit::JitActivation *activation = data_.activations_.activation()->asJit(); |
| |
| // ActivationIterator::ionTop_ may be invalid, so create a new |
| // activation iterator. |
| data_.activations_ = ActivationIterator(data_.cx_->runtime()); |
| while (data_.activations_.activation() != activation) |
| ++data_.activations_; |
| |
| // Look for the current frame. |
| data_.ionFrames_ = jit::IonFrameIterator(data_.activations_); |
| while (!data_.ionFrames_.isBaselineJS() || data_.ionFrames_.baselineFrame() != frame) |
| ++data_.ionFrames_; |
| |
| // Update the pc. |
| JS_ASSERT(data_.ionFrames_.baselineFrame() == frame); |
| data_.ionFrames_.baselineScriptAndPc(NULL, &data_.pc_); |
| return; |
| } |
| #endif |
| break; |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| } |
| |
| JSFunction * |
| ScriptFrameIter::callee() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case SCRIPTED: |
| JS_ASSERT(isFunctionFrame()); |
| return &interpFrame()->callee(); |
| case JIT: |
| #ifdef JS_ION |
| if (data_.ionFrames_.isBaselineJS()) |
| return data_.ionFrames_.callee(); |
| JS_ASSERT(data_.ionFrames_.isOptimizedJS()); |
| return ionInlineFrames_.callee(); |
| #else |
| break; |
| #endif |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| return NULL; |
| } |
| |
| Value |
| ScriptFrameIter::calleev() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case SCRIPTED: |
| JS_ASSERT(isFunctionFrame()); |
| return interpFrame()->calleev(); |
| case JIT: |
| #ifdef JS_ION |
| return ObjectValue(*callee()); |
| #else |
| break; |
| #endif |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| return Value(); |
| } |
| |
| unsigned |
| ScriptFrameIter::numActualArgs() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case SCRIPTED: |
| JS_ASSERT(isFunctionFrame()); |
| return interpFrame()->numActualArgs(); |
| case JIT: |
| #ifdef JS_ION |
| if (data_.ionFrames_.isOptimizedJS()) |
| return ionInlineFrames_.numActualArgs(); |
| |
| JS_ASSERT(data_.ionFrames_.isBaselineJS()); |
| return data_.ionFrames_.numActualArgs(); |
| #else |
| break; |
| #endif |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| return 0; |
| } |
| |
| Value |
| ScriptFrameIter::unaliasedActual(unsigned i, MaybeCheckAliasing checkAliasing) const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case SCRIPTED: |
| return interpFrame()->unaliasedActual(i, checkAliasing); |
| case JIT: |
| #ifdef JS_ION |
| JS_ASSERT(data_.ionFrames_.isBaselineJS()); |
| return data_.ionFrames_.baselineFrame()->unaliasedActual(i, checkAliasing); |
| #else |
| break; |
| #endif |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| return NullValue(); |
| } |
| |
| JSObject * |
| ScriptFrameIter::scopeChain() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case JIT: |
| #ifdef JS_ION |
| if (data_.ionFrames_.isOptimizedJS()) |
| return ionInlineFrames_.scopeChain(); |
| return data_.ionFrames_.baselineFrame()->scopeChain(); |
| #else |
| break; |
| #endif |
| case SCRIPTED: |
| return interpFrame()->scopeChain(); |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| return NULL; |
| } |
| |
| CallObject & |
| ScriptFrameIter::callObj() const |
| { |
| JS_ASSERT(callee()->isHeavyweight()); |
| |
| JSObject *pobj = scopeChain(); |
| while (!pobj->is<CallObject>()) |
| pobj = pobj->enclosingScope(); |
| return pobj->as<CallObject>(); |
| } |
| |
| bool |
| ScriptFrameIter::hasArgsObj() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case SCRIPTED: |
| return interpFrame()->hasArgsObj(); |
| case JIT: |
| #ifdef JS_ION |
| JS_ASSERT(data_.ionFrames_.isBaselineJS()); |
| return data_.ionFrames_.baselineFrame()->hasArgsObj(); |
| #else |
| break; |
| #endif |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| return false; |
| } |
| |
| ArgumentsObject & |
| ScriptFrameIter::argsObj() const |
| { |
| JS_ASSERT(hasArgsObj()); |
| |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case JIT: |
| #ifdef JS_ION |
| JS_ASSERT(data_.ionFrames_.isBaselineJS()); |
| return data_.ionFrames_.baselineFrame()->argsObj(); |
| #else |
| break; |
| #endif |
| case SCRIPTED: |
| return interpFrame()->argsObj(); |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| return interpFrame()->argsObj(); |
| } |
| |
| bool |
| ScriptFrameIter::computeThis(JSContext *cx) const |
| { |
| JS_ASSERT(!done()); |
| if (!isIon()) { |
| assertSameCompartment(cx, scopeChain()); |
| return ComputeThis(cx, abstractFramePtr()); |
| } |
| return true; |
| } |
| |
| Value |
| ScriptFrameIter::thisv() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case JIT: |
| #ifdef JS_ION |
| if (data_.ionFrames_.isOptimizedJS()) |
| return ObjectValue(*ionInlineFrames_.thisObject()); |
| return data_.ionFrames_.baselineFrame()->thisValue(); |
| #else |
| break; |
| #endif |
| case SCRIPTED: |
| return interpFrame()->thisValue(); |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| return NullValue(); |
| } |
| |
| Value |
| ScriptFrameIter::returnValue() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case JIT: |
| #ifdef JS_ION |
| if (data_.ionFrames_.isBaselineJS()) |
| return *data_.ionFrames_.baselineFrame()->returnValue(); |
| #endif |
| break; |
| case SCRIPTED: |
| return interpFrame()->returnValue(); |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| return NullValue(); |
| } |
| |
| void |
| ScriptFrameIter::setReturnValue(const Value &v) |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case JIT: |
| #ifdef JS_ION |
| if (data_.ionFrames_.isBaselineJS()) { |
| data_.ionFrames_.baselineFrame()->setReturnValue(v); |
| return; |
| } |
| #endif |
| break; |
| case SCRIPTED: |
| interpFrame()->setReturnValue(v); |
| return; |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| } |
| |
| size_t |
| ScriptFrameIter::numFrameSlots() const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case JIT: { |
| #ifdef JS_ION |
| if (data_.ionFrames_.isOptimizedJS()) |
| return ionInlineFrames_.snapshotIterator().slots() - ionInlineFrames_.script()->nfixed; |
| jit::BaselineFrame *frame = data_.ionFrames_.baselineFrame(); |
| return frame->numValueSlots() - data_.ionFrames_.script()->nfixed; |
| #else |
| break; |
| #endif |
| } |
| case SCRIPTED: |
| JS_ASSERT(data_.interpFrames_.sp() >= interpFrame()->base()); |
| return data_.interpFrames_.sp() - interpFrame()->base(); |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| return 0; |
| } |
| |
| Value |
| ScriptFrameIter::frameSlotValue(size_t index) const |
| { |
| switch (data_.state_) { |
| case DONE: |
| break; |
| case JIT: |
| #ifdef JS_ION |
| if (data_.ionFrames_.isOptimizedJS()) { |
| jit::SnapshotIterator si(ionInlineFrames_.snapshotIterator()); |
| index += ionInlineFrames_.script()->nfixed; |
| return si.maybeReadSlotByIndex(index); |
| } |
| |
| index += data_.ionFrames_.script()->nfixed; |
| return *data_.ionFrames_.baselineFrame()->valueSlot(index); |
| #else |
| break; |
| #endif |
| case SCRIPTED: |
| return interpFrame()->base()[index]; |
| } |
| JS_NOT_REACHED("Unexpected state"); |
| return NullValue(); |
| } |
| |
| #if defined(_MSC_VER) |
| # pragma optimize("", on) |
| #endif |
| |
| /*****************************************************************************/ |
| |
| JSObject * |
| AbstractFramePtr::evalPrevScopeChain(JSContext *cx) const |
| { |
| // Eval frames are not compiled by Ion, though their caller might be. |
| AllFramesIter iter(cx); |
| while (iter.isIon() || iter.abstractFramePtr() != *this) |
| ++iter; |
| ++iter; |
| return iter.scopeChain(); |
| } |
| |
| bool |
| AbstractFramePtr::hasPushedSPSFrame() const |
| { |
| if (isStackFrame()) |
| return asStackFrame()->hasPushedSPSFrame(); |
| #ifdef JS_ION |
| return asBaselineFrame()->hasPushedSPSFrame(); |
| #else |
| JS_NOT_REACHED("Invalid frame"); |
| return false; |
| #endif |
| } |
| |
| #ifdef DEBUG |
| void |
| js::CheckLocalUnaliased(MaybeCheckAliasing checkAliasing, JSScript *script, |
| StaticBlockObject *maybeBlock, unsigned i) |
| { |
| if (!checkAliasing) |
| return; |
| |
| JS_ASSERT(i < script->nslots); |
| if (i < script->nfixed) { |
| JS_ASSERT(!script->varIsAliased(i)); |
| } else { |
| unsigned depth = i - script->nfixed; |
| for (StaticBlockObject *b = maybeBlock; b; b = b->enclosingBlock()) { |
| if (b->containsVarAtDepth(depth)) { |
| JS_ASSERT(!b->isAliased(depth - b->stackDepth())); |
| break; |
| } |
| } |
| } |
| } |
| #endif |
| |
| jit::JitActivation::JitActivation(JSContext *cx, bool firstFrameIsConstructing, bool active) |
| : Activation(cx, Jit), |
| firstFrameIsConstructing_(firstFrameIsConstructing), |
| active_(active) |
| { |
| if (active) { |
| prevIonTop_ = cx->mainThread().ionTop; |
| prevIonJSContext_ = cx->mainThread().ionJSContext; |
| cx->mainThread().ionJSContext = cx; |
| } else { |
| prevIonTop_ = NULL; |
| prevIonJSContext_ = NULL; |
| } |
| } |
| |
| jit::JitActivation::~JitActivation() |
| { |
| if (active_) { |
| cx_->mainThread().ionTop = prevIonTop_; |
| cx_->mainThread().ionJSContext = prevIonJSContext_; |
| } |
| } |
| |
| void |
| jit::JitActivation::setActive(JSContext *cx, bool active) |
| { |
| // Only allowed to deactivate/activate if activation is top. |
| // (Not tested and will probably fail in other situations.) |
| JS_ASSERT(cx->mainThread().activation_ == this); |
| JS_ASSERT(active != active_); |
| active_ = active; |
| |
| if (active) { |
| prevIonTop_ = cx->mainThread().ionTop; |
| prevIonJSContext_ = cx->mainThread().ionJSContext; |
| cx->mainThread().ionJSContext = cx; |
| } else { |
| cx->mainThread().ionTop = prevIonTop_; |
| cx->mainThread().ionJSContext = prevIonJSContext_; |
| } |
| } |
| |
| InterpreterFrameIterator & |
| InterpreterFrameIterator::operator++() |
| { |
| JS_ASSERT(!done()); |
| if (fp_ != activation_->entry_) { |
| pc_ = fp_->prevpc(); |
| sp_ = fp_->prevsp(); |
| fp_ = fp_->prev(); |
| } else { |
| pc_ = NULL; |
| sp_ = NULL; |
| fp_ = NULL; |
| } |
| return *this; |
| } |
| |
| ActivationIterator::ActivationIterator(JSRuntime *rt) |
| : jitTop_(rt->mainThread.ionTop), |
| activation_(rt->mainThread.activation_) |
| { |
| settle(); |
| } |
| |
| ActivationIterator & |
| ActivationIterator::operator++() |
| { |
| JS_ASSERT(activation_); |
| if (activation_->isJit() && activation_->asJit()->isActive()) |
| jitTop_ = activation_->asJit()->prevIonTop(); |
| activation_ = activation_->prev(); |
| settle(); |
| return *this; |
| } |
| |
| void |
| ActivationIterator::settle() |
| { |
| while (!done() && activation_->isJit() && !activation_->asJit()->isActive()) { |
| if (activation_->asJit()->isActive()) |
| jitTop_ = activation_->asJit()->prevIonTop(); |
| activation_ = activation_->prev(); |
| } |
| } |