diff -r 000000000000 -r 6474c204b198 js/src/vm/Stack.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/src/vm/Stack.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1747 @@ +/* -*- 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-inl.h" + +#include "mozilla/PodOperations.h" + +#include "jscntxt.h" + +#include "gc/Marking.h" +#ifdef JS_ION +#include "jit/AsmJSModule.h" +#include "jit/BaselineFrame.h" +#include "jit/JitCompartment.h" +#endif +#include "js/GCAPI.h" +#include "vm/Opcodes.h" + +#include "jit/JitFrameIterator-inl.h" +#include "vm/Interpreter-inl.h" +#include "vm/Probes-inl.h" +#include "vm/ScopeObject-inl.h" + +using namespace js; + +using mozilla::PodCopy; + +/*****************************************************************************/ + +void +InterpreterFrame::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; + + JSObject *callee = nullptr; + if (!(flags_ & (GLOBAL))) { + if (evalInFramePrev) { + JS_ASSERT(evalInFramePrev.isFunctionFrame() || evalInFramePrev.isGlobalFrame()); + if (evalInFramePrev.isFunctionFrame()) { + callee = evalInFramePrev.callee(); + flags_ |= FUNCTION; + } else { + flags_ |= GLOBAL; + } + } else { + FrameIter iter(cx); + JS_ASSERT(iter.isFunctionFrame() || iter.isGlobalFrame()); + JS_ASSERT(!iter.isAsmJS()); + 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(); + u.evalScript = script; + } else { + JS_ASSERT(isGlobalFrame()); + dstvp[0] = NullValue(); + exec.script = script; +#ifdef DEBUG + u.evalScript = (JSScript *)0xbad; +#endif + } + + scopeChain_ = &scopeChain; + prev_ = nullptr; + prevpc_ = nullptr; + prevsp_ = nullptr; + + JS_ASSERT_IF(evalInFramePrev, isDebuggerFrame()); + evalInFramePrev_ = evalInFramePrev; + +#ifdef DEBUG + Debug_SetValueRangeToCrashOnTouch(&rval_, 1); + hookData_ = (void *)0xbad; +#endif +} + +template +void +InterpreterFrame::copyFrameAndValues(JSContext *cx, Value *vp, InterpreterFrame *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, InterpreterFrame, 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); + } +} + +/* Note: explicit instantiation for js_NewGenerator located in jsiter.cpp. */ +template +void InterpreterFrame::copyFrameAndValues( + JSContext *, Value *, InterpreterFrame *, const Value *, Value *); +template +void InterpreterFrame::copyFrameAndValues( + JSContext *, Value *, InterpreterFrame *, const Value *, Value *); + +void +InterpreterFrame::writeBarrierPost() +{ + /* This needs to follow the same rules as in InterpreterFrame::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_); +} + +bool +InterpreterFrame::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 * +InterpreterFrame::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; + JSObject *obj = NewDenseCopiedArray(cx, nrest, restvp, nullptr); + if (!obj) + return nullptr; + types::FixRestArgumentsType(cx, obj); + return obj; +} + +static inline void +AssertDynamicScopeMatchesStaticScope(JSContext *cx, JSScript *script, JSObject *scope) +{ +#ifdef DEBUG + RootedObject enclosingScope(cx, script->enclosingStaticScope()); + for (StaticScopeIter i(enclosingScope); !i.done(); i++) { + if (i.hasDynamicScopeObject()) { + switch (i.type()) { + case StaticScopeIter::BLOCK: + JS_ASSERT(&i.block() == scope->as().staticScope()); + scope = &scope->as().enclosingScope(); + break; + case StaticScopeIter::WITH: + JS_ASSERT(&i.staticWith() == scope->as().staticScope()); + scope = &scope->as().enclosingScope(); + break; + case StaticScopeIter::FUNCTION: + JS_ASSERT(scope->as().callee().nonLazyScript() == i.funScript()); + scope = &scope->as().enclosingScope(); + break; + case StaticScopeIter::NAMED_LAMBDA: + scope = &scope->as().enclosingScope(); + break; + } + } + } + + /* + * Ideally, we'd JS_ASSERT(!scope->is()) but the enclosing + * lexical scope chain stops at eval() boundaries. See StaticScopeIter + * comment. + */ +#endif +} + +bool +InterpreterFrame::initFunctionScopeObjects(JSContext *cx) +{ + CallObject *callobj = CallObject::createForFunction(cx, this); + if (!callobj) + return false; + pushOnScopeChain(*callobj); + flags_ |= HAS_CALL_OBJ; + return true; +} + +bool +InterpreterFrame::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; + } + return probes::EnterScript(cx, script, nullptr, this); + } + + if (isGlobalFrame()) + return probes::EnterScript(cx, script, nullptr, this); + + 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() ? SingletonObject : GenericObject); + if (!obj) + return false; + functionThis() = ObjectValue(*obj); + } + + return probes::EnterScript(cx, script, script->functionNonDelazifying(), this); +} + +void +InterpreterFrame::epilogue(JSContext *cx) +{ + JS_ASSERT(!isYielding()); + + RootedScript script(cx, this->script()); + probes::ExitScript(cx, script, script->functionNonDelazifying(), hasPushedSPSFrame()); + + if (isEvalFrame()) { + if (isStrictEvalFrame()) { + JS_ASSERT_IF(hasCallObj(), scopeChain()->as().isForEval()); + if (MOZ_UNLIKELY(cx->compartment()->debugMode())) + DebugScopes::onPopStrictEvalScope(this); + } else if (isDirectEvalFrame()) { + if (isDebuggerFrame()) + JS_ASSERT(!scopeChain()->is()); + } 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() || + scopeChain()->enclosingScope()->is()); + } else { + JS_ASSERT(scopeChain()->is()); + } + } + return; + } + + if (isGlobalFrame()) { + JS_ASSERT(!scopeChain()->is()); + return; + } + + JS_ASSERT(isNonEvalFunctionFrame()); + + if (fun()->isHeavyweight()) + JS_ASSERT_IF(hasCallObj(), + scopeChain()->as().callee().nonLazyScript() == script); + else + AssertDynamicScopeMatchesStaticScope(cx, script, scopeChain()); + + if (MOZ_UNLIKELY(cx->compartment()->debugMode())) + DebugScopes::onPopCall(this, cx); + + if (isConstructing() && thisValue().isObject() && returnValue().isPrimitive()) + setReturnValue(ObjectValue(constructorThis())); +} + +bool +InterpreterFrame::pushBlock(JSContext *cx, StaticBlockObject &block) +{ + JS_ASSERT (block.needsClone()); + + Rooted blockHandle(cx, &block); + ClonedBlockObject *clone = ClonedBlockObject::create(cx, blockHandle, this); + if (!clone) + return false; + + pushOnScopeChain(*clone); + + return true; +} + +void +InterpreterFrame::popBlock(JSContext *cx) +{ + JS_ASSERT(scopeChain_->is()); + popOffScopeChain(); +} + +void +InterpreterFrame::popWith(JSContext *cx) +{ + if (MOZ_UNLIKELY(cx->compartment()->debugMode())) + DebugScopes::onPopWith(this); + + JS_ASSERT(scopeChain()->is()); + popOffScopeChain(); +} + +void +InterpreterFrame::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().address(), "rval"); +} + +void +InterpreterFrame::markValues(JSTracer *trc, unsigned start, unsigned end) +{ + if (start < end) + gc::MarkValueRootRange(trc, end - start, slots() + start, "vm_stack"); +} + +void +InterpreterFrame::markValues(JSTracer *trc, Value *sp, jsbytecode *pc) +{ + JS_ASSERT(sp >= slots()); + + NestedScopeObject *staticScope; + + staticScope = script()->getStaticScope(pc); + while (staticScope && !staticScope->is()) + staticScope = staticScope->enclosingNestedScope(); + + size_t nfixed = script()->nfixed(); + size_t nlivefixed; + + if (staticScope) { + StaticBlockObject &blockObj = staticScope->as(); + nlivefixed = blockObj.localOffset() + blockObj.numVariables(); + } else { + nlivefixed = script()->nfixedvars(); + } + + if (nfixed == nlivefixed) { + // All locals are live. + markValues(trc, 0, sp - slots()); + } else { + // Mark operand stack. + markValues(trc, nfixed, sp - slots()); + + // Clear dead locals. + while (nfixed > nlivefixed) + unaliasedLocal(--nfixed, DONT_CHECK_ALIASING).setUndefined(); + + // Mark live locals. + markValues(trc, 0, nlivefixed); + } + + if (hasArgs()) { + // Mark callee, |this| and arguments. + unsigned argc = Max(numActualArgs(), numFormalArgs()); + gc::MarkValueRootRange(trc, argc + 2, argv_ - 2, "fp argv"); + } else { + // Mark callee and |this| + gc::MarkValueRootRange(trc, 2, ((Value *)this) - 2, "stack callee and this"); + } +} + +static void +MarkInterpreterActivation(JSTracer *trc, InterpreterActivation *act) +{ + for (InterpreterFrameIterator frames(act); !frames.done(); ++frames) { + InterpreterFrame *fp = frames.frame(); + fp->markValues(trc, frames.sp(), frames.pc()); + 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()); + } + +} + +/*****************************************************************************/ + +// Unlike the other methods of this calss, this method is defined here so that +// we don't have to #include jsautooplen.h in vm/Stack.h. +void +InterpreterRegs::setToEndOfScript() +{ + JSScript *script = fp()->script(); + sp = fp()->base(); + pc = script->codeEnd() - JSOP_RETRVAL_LENGTH; + JS_ASSERT(*pc == JSOP_RETRVAL); +} + +/*****************************************************************************/ + +InterpreterFrame * +InterpreterStack::pushInvokeFrame(JSContext *cx, const CallArgs &args, InitialFrameFlags initial) +{ + LifoAlloc::Mark mark = allocator_.mark(); + + RootedFunction fun(cx, &args.callee().as()); + RootedScript script(cx, fun->nonLazyScript()); + + InterpreterFrame::Flags flags = ToFrameFlags(initial); + Value *argv; + InterpreterFrame *fp = getCallFrame(cx, args, script, &flags, &argv); + if (!fp) + return nullptr; + + fp->mark_ = mark; + fp->initCallFrame(cx, nullptr, nullptr, nullptr, *fun, script, argv, args.length(), flags); + return fp; +} + +InterpreterFrame * +InterpreterStack::pushExecuteFrame(JSContext *cx, HandleScript script, const Value &thisv, + HandleObject scopeChain, ExecuteType type, + AbstractFramePtr evalInFrame) +{ + LifoAlloc::Mark mark = allocator_.mark(); + + unsigned nvars = 2 /* callee, this */ + script->nslots(); + uint8_t *buffer = allocateFrame(cx, sizeof(InterpreterFrame) + nvars * sizeof(Value)); + if (!buffer) + return nullptr; + + InterpreterFrame *fp = reinterpret_cast(buffer + 2 * sizeof(Value)); + fp->mark_ = mark; + fp->initExecuteFrame(cx, script, evalInFrame, thisv, *scopeChain, type); + fp->initVarsToUndefined(); + + return fp; +} + +/*****************************************************************************/ + +/* MSVC PGO causes xpcshell startup crashes. */ +#if defined(_MSC_VER) +# pragma optimize("g", off) +#endif + +void +FrameIter::popActivation() +{ + ++data_.activations_; + settleOnActivation(); +} + +void +FrameIter::popInterpreterFrame() +{ + JS_ASSERT(data_.state_ == INTERP); + + ++data_.interpFrames_; + + if (data_.interpFrames_.done()) + popActivation(); + else + data_.pc_ = data_.interpFrames_.pc(); +} + +void +FrameIter::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; + } + + // If the caller supplied principals, only show activations which are subsumed (of the same + // origin or of an origin accessible) by these principals. + if (data_.principals_) { + if (JSSubsumesOp subsumes = data_.cx_->runtime()->securityCallbacks->subsumes) { + JS::AutoAssertNoGC nogc; + if (!subsumes(data_.principals_, activation->compartment()->principals)) { + ++data_.activations_; + continue; + } + } + } + +#ifdef JS_ION + if (activation->isJit()) { + data_.jitFrames_ = jit::JitFrameIterator(data_.activations_); + + // Stop at the first scripted frame. + while (!data_.jitFrames_.isScripted() && !data_.jitFrames_.done()) + ++data_.jitFrames_; + + // It's possible to have an JitActivation with no scripted frames, + // for instance if we hit an over-recursion during bailout. + if (data_.jitFrames_.done()) { + ++data_.activations_; + continue; + } + + nextJitFrame(); + data_.state_ = JIT; + return; + } + + if (activation->isAsmJS()) { + data_.asmJSFrames_ = AsmJSFrameIterator(data_.activations_->asAsmJS()); + + if (data_.asmJSFrames_.done()) { + ++data_.activations_; + continue; + } + + data_.state_ = ASMJS; + return; + } + + // ForkJoin activations don't contain iterable frames, so skip them. + if (activation->isForkJoin()) { + ++data_.activations_; + continue; + } +#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_ = INTERP; + return; + } +} + +FrameIter::Data::Data(JSContext *cx, SavedOption savedOption, ContextOption contextOption, + JSPrincipals *principals) + : cx_(cx), + savedOption_(savedOption), + contextOption_(contextOption), + principals_(principals), + pc_(nullptr), + interpFrames_(nullptr), + activations_(cx->runtime()) +#ifdef JS_ION + , jitFrames_((uint8_t *)nullptr, SequentialExecution) + , ionInlineFrameNo_(0) + , asmJSFrames_(nullptr) +#endif +{ +} + +FrameIter::Data::Data(const FrameIter::Data &other) + : cx_(other.cx_), + savedOption_(other.savedOption_), + contextOption_(other.contextOption_), + principals_(other.principals_), + state_(other.state_), + pc_(other.pc_), + interpFrames_(other.interpFrames_), + activations_(other.activations_) +#ifdef JS_ION + , jitFrames_(other.jitFrames_) + , ionInlineFrameNo_(other.ionInlineFrameNo_) + , asmJSFrames_(other.asmJSFrames_) +#endif +{ +} + +FrameIter::FrameIter(JSContext *cx, SavedOption savedOption) + : data_(cx, savedOption, CURRENT_CONTEXT, nullptr) +#ifdef JS_ION + , ionInlineFrames_(cx, (js::jit::JitFrameIterator*) nullptr) +#endif +{ + // settleOnActivation can only GC if principals are given. + JS::AutoAssertNoGC nogc; + settleOnActivation(); +} + +FrameIter::FrameIter(JSContext *cx, ContextOption contextOption, + SavedOption savedOption, JSPrincipals *principals) + : data_(cx, savedOption, contextOption, principals) +#ifdef JS_ION + , ionInlineFrames_(cx, (js::jit::JitFrameIterator*) nullptr) +#endif +{ + settleOnActivation(); +} + +FrameIter::FrameIter(const FrameIter &other) + : data_(other.data_) +#ifdef JS_ION + , ionInlineFrames_(other.data_.cx_, + data_.jitFrames_.isIonJS() ? &other.ionInlineFrames_ : nullptr) +#endif +{ +} + +FrameIter::FrameIter(const Data &data) + : data_(data) +#ifdef JS_ION + , ionInlineFrames_(data.cx_, data_.jitFrames_.isIonJS() ? &data_.jitFrames_ : nullptr) +#endif +{ + JS_ASSERT(data.cx_); + +#ifdef JS_ION + if (data_.jitFrames_.isIonJS()) { + while (ionInlineFrames_.frameNo() != data.ionInlineFrameNo_) + ++ionInlineFrames_; + } +#endif +} + +#ifdef JS_ION +void +FrameIter::nextJitFrame() +{ + if (data_.jitFrames_.isIonJS()) { + ionInlineFrames_.resetOn(&data_.jitFrames_); + data_.pc_ = ionInlineFrames_.pc(); + } else { + JS_ASSERT(data_.jitFrames_.isBaselineJS()); + data_.jitFrames_.baselineScriptAndPc(nullptr, &data_.pc_); + } +} + +void +FrameIter::popJitFrame() +{ + JS_ASSERT(data_.state_ == JIT); + + if (data_.jitFrames_.isIonJS() && ionInlineFrames_.more()) { + ++ionInlineFrames_; + data_.pc_ = ionInlineFrames_.pc(); + return; + } + + ++data_.jitFrames_; + while (!data_.jitFrames_.done() && !data_.jitFrames_.isScripted()) + ++data_.jitFrames_; + + if (!data_.jitFrames_.done()) { + nextJitFrame(); + return; + } + + popActivation(); +} + +void +FrameIter::popAsmJSFrame() +{ + JS_ASSERT(data_.state_ == ASMJS); + + ++data_.asmJSFrames_; + if (data_.asmJSFrames_.done()) + popActivation(); +} +#endif + +FrameIter & +FrameIter::operator++() +{ + switch (data_.state_) { + case DONE: + MOZ_ASSUME_UNREACHABLE("Unexpected state"); + case INTERP: + if (interpFrame()->isDebuggerFrame() && interpFrame()->evalInFramePrev()) { + AbstractFramePtr eifPrev = interpFrame()->evalInFramePrev(); + MOZ_ASSERT(!eifPrev.isRematerializedFrame()); + + // 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 + MOZ_ASSUME_UNREACHABLE("Invalid state"); +#endif + } else { + popInterpreterFrame(); + } + } + + data_.contextOption_ = prevContextOption; + data_.savedOption_ = prevSavedOption; + data_.cx_ = data_.activations_->cx(); + break; + } + popInterpreterFrame(); + break; +#ifdef JS_ION + case JIT: + popJitFrame(); + break; + case ASMJS: + popAsmJSFrame(); + break; +#else + default: + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +#endif + } + return *this; +} + +FrameIter::Data * +FrameIter::copyData() const +{ + Data *data = data_.cx_->new_(data_); +#ifdef JS_ION + JS_ASSERT(data_.state_ != ASMJS); + if (data && data_.jitFrames_.isIonJS()) + data->ionInlineFrameNo_ = ionInlineFrames_.frameNo(); +#endif + return data; +} + +AbstractFramePtr +FrameIter::copyDataAsAbstractFramePtr() const +{ + AbstractFramePtr frame; + if (Data *data = copyData()) + frame.ptr_ = uintptr_t(data); + return frame; +} + +JSCompartment * +FrameIter::compartment() const +{ + switch (data_.state_) { + case DONE: + break; + case INTERP: + case JIT: + case ASMJS: + return data_.activations_->compartment(); + } + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +bool +FrameIter::isFunctionFrame() const +{ + switch (data_.state_) { + case DONE: + break; + case INTERP: + return interpFrame()->isFunctionFrame(); + case JIT: +#ifdef JS_ION + JS_ASSERT(data_.jitFrames_.isScripted()); + if (data_.jitFrames_.isBaselineJS()) + return data_.jitFrames_.isFunctionFrame(); + return ionInlineFrames_.isFunctionFrame(); +#else + break; +#endif + case ASMJS: + return true; + } + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +bool +FrameIter::isGlobalFrame() const +{ + switch (data_.state_) { + case DONE: + break; + case INTERP: + return interpFrame()->isGlobalFrame(); + case JIT: +#ifdef JS_ION + if (data_.jitFrames_.isBaselineJS()) + return data_.jitFrames_.baselineFrame()->isGlobalFrame(); + JS_ASSERT(!script()->isForEval()); + return !script()->functionNonDelazifying(); +#else + break; +#endif + case ASMJS: + return false; + } + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +bool +FrameIter::isEvalFrame() const +{ + switch (data_.state_) { + case DONE: + break; + case INTERP: + return interpFrame()->isEvalFrame(); + case JIT: +#ifdef JS_ION + if (data_.jitFrames_.isBaselineJS()) + return data_.jitFrames_.baselineFrame()->isEvalFrame(); + JS_ASSERT(!script()->isForEval()); + return false; +#else + break; +#endif + case ASMJS: + return false; + } + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +bool +FrameIter::isNonEvalFunctionFrame() const +{ + JS_ASSERT(!done()); + switch (data_.state_) { + case DONE: + break; + case INTERP: + return interpFrame()->isNonEvalFunctionFrame(); + case JIT: + return !isEvalFrame() && isFunctionFrame(); + case ASMJS: + return true; + } + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +bool +FrameIter::isGeneratorFrame() const +{ + switch (data_.state_) { + case DONE: + break; + case INTERP: + return interpFrame()->isGeneratorFrame(); + case JIT: + return false; + case ASMJS: + return false; + } + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +JSAtom * +FrameIter::functionDisplayAtom() const +{ + JS_ASSERT(isNonEvalFunctionFrame()); + + switch (data_.state_) { + case DONE: + break; + case INTERP: + case JIT: + return callee()->displayAtom(); + case ASMJS: { +#ifdef JS_ION + return data_.asmJSFrames_.functionDisplayAtom(); +#else + break; +#endif + } + } + + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +ScriptSource * +FrameIter::scriptSource() const +{ + switch (data_.state_) { + case DONE: + break; + case INTERP: + case JIT: + return script()->scriptSource(); + case ASMJS: +#ifdef JS_ION + return data_.activations_->asAsmJS()->module().scriptSource(); +#else + break; +#endif + } + + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +const char * +FrameIter::scriptFilename() const +{ + switch (data_.state_) { + case DONE: + break; + case INTERP: + case JIT: + return script()->filename(); + case ASMJS: +#ifdef JS_ION + return data_.activations_->asAsmJS()->module().scriptSource()->filename(); +#else + break; +#endif + } + + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +unsigned +FrameIter::computeLine(uint32_t *column) const +{ + switch (data_.state_) { + case DONE: + break; + case INTERP: + case JIT: + return PCToLineNumber(script(), pc(), column); + case ASMJS: +#ifdef JS_ION + return data_.asmJSFrames_.computeLine(column); +#else + break; +#endif + } + + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +JSPrincipals * +FrameIter::originPrincipals() const +{ + switch (data_.state_) { + case DONE: + break; + case INTERP: + case JIT: + return script()->originPrincipals(); + case ASMJS: { +#ifdef JS_ION + return data_.activations_->asAsmJS()->module().scriptSource()->originPrincipals(); +#else + break; +#endif + } + } + + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +bool +FrameIter::isConstructing() const +{ + switch (data_.state_) { + case DONE: + case ASMJS: + break; + case JIT: +#ifdef JS_ION + if (data_.jitFrames_.isIonJS()) + return ionInlineFrames_.isConstructing(); + JS_ASSERT(data_.jitFrames_.isBaselineJS()); + return data_.jitFrames_.isConstructing(); +#else + break; +#endif + case INTERP: + return interpFrame()->isConstructing(); + } + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +bool +FrameIter::ensureHasRematerializedFrame() +{ +#ifdef JS_ION + MOZ_ASSERT(isIon()); + return !!activation()->asJit()->getRematerializedFrame(activation()->cx(), data_.jitFrames_); +#else + return true; +#endif +} + +bool +FrameIter::hasUsableAbstractFramePtr() const +{ + switch (data_.state_) { + case DONE: + case ASMJS: + return false; + case JIT: +#ifdef JS_ION + if (data_.jitFrames_.isBaselineJS()) + return true; + + MOZ_ASSERT(data_.jitFrames_.isIonJS()); + return !!activation()->asJit()->lookupRematerializedFrame(data_.jitFrames_.fp(), + ionInlineFrames_.frameNo()); +#endif + break; + case INTERP: + return true; + } + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +AbstractFramePtr +FrameIter::abstractFramePtr() const +{ + MOZ_ASSERT(hasUsableAbstractFramePtr()); + switch (data_.state_) { + case DONE: + case ASMJS: + break; + case JIT: { +#ifdef JS_ION + if (data_.jitFrames_.isBaselineJS()) + return data_.jitFrames_.baselineFrame(); + + MOZ_ASSERT(data_.jitFrames_.isIonJS()); + return activation()->asJit()->lookupRematerializedFrame(data_.jitFrames_.fp(), + ionInlineFrames_.frameNo()); +#endif + break; + } + case INTERP: + JS_ASSERT(interpFrame()); + return AbstractFramePtr(interpFrame()); + } + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +void +FrameIter::updatePcQuadratic() +{ + switch (data_.state_) { + case DONE: + case ASMJS: + break; + case INTERP: { + InterpreterFrame *frame = interpFrame(); + InterpreterActivation *activation = data_.activations_->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_.jitFrames_.isBaselineJS()) { + jit::BaselineFrame *frame = data_.jitFrames_.baselineFrame(); + jit::JitActivation *activation = data_.activations_->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_.jitFrames_ = jit::JitFrameIterator(data_.activations_); + while (!data_.jitFrames_.isBaselineJS() || data_.jitFrames_.baselineFrame() != frame) + ++data_.jitFrames_; + + // Update the pc. + JS_ASSERT(data_.jitFrames_.baselineFrame() == frame); + data_.jitFrames_.baselineScriptAndPc(nullptr, &data_.pc_); + return; + } +#endif + break; + } + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +JSFunction * +FrameIter::callee() const +{ + switch (data_.state_) { + case DONE: + case ASMJS: + break; + case INTERP: + JS_ASSERT(isFunctionFrame()); + return &interpFrame()->callee(); + case JIT: +#ifdef JS_ION + if (data_.jitFrames_.isBaselineJS()) + return data_.jitFrames_.callee(); + JS_ASSERT(data_.jitFrames_.isIonJS()); + return ionInlineFrames_.callee(); +#else + break; +#endif + } + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +Value +FrameIter::calleev() const +{ + switch (data_.state_) { + case DONE: + case ASMJS: + break; + case INTERP: + JS_ASSERT(isFunctionFrame()); + return interpFrame()->calleev(); + case JIT: +#ifdef JS_ION + return ObjectValue(*callee()); +#else + break; +#endif + } + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +unsigned +FrameIter::numActualArgs() const +{ + switch (data_.state_) { + case DONE: + case ASMJS: + break; + case INTERP: + JS_ASSERT(isFunctionFrame()); + return interpFrame()->numActualArgs(); + case JIT: +#ifdef JS_ION + if (data_.jitFrames_.isIonJS()) + return ionInlineFrames_.numActualArgs(); + + JS_ASSERT(data_.jitFrames_.isBaselineJS()); + return data_.jitFrames_.numActualArgs(); +#else + break; +#endif + } + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +unsigned +FrameIter::numFormalArgs() const +{ + return script()->functionNonDelazifying()->nargs(); +} + +Value +FrameIter::unaliasedActual(unsigned i, MaybeCheckAliasing checkAliasing) const +{ + return abstractFramePtr().unaliasedActual(i, checkAliasing); +} + +JSObject * +FrameIter::scopeChain() const +{ + switch (data_.state_) { + case DONE: + case ASMJS: + break; + case JIT: +#ifdef JS_ION + if (data_.jitFrames_.isIonJS()) + return ionInlineFrames_.scopeChain(); + return data_.jitFrames_.baselineFrame()->scopeChain(); +#else + break; +#endif + case INTERP: + return interpFrame()->scopeChain(); + } + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +CallObject & +FrameIter::callObj() const +{ + JS_ASSERT(callee()->isHeavyweight()); + + JSObject *pobj = scopeChain(); + while (!pobj->is()) + pobj = pobj->enclosingScope(); + return pobj->as(); +} + +bool +FrameIter::hasArgsObj() const +{ + return abstractFramePtr().hasArgsObj(); +} + +ArgumentsObject & +FrameIter::argsObj() const +{ + MOZ_ASSERT(hasArgsObj()); + return abstractFramePtr().argsObj(); +} + +bool +FrameIter::computeThis(JSContext *cx) const +{ + JS_ASSERT(!done() && !isAsmJS()); + assertSameCompartment(cx, scopeChain()); + return ComputeThis(cx, abstractFramePtr()); +} + +Value +FrameIter::computedThisValue() const +{ + return abstractFramePtr().thisValue(); +} + +Value +FrameIter::thisv() const +{ + switch (data_.state_) { + case DONE: + case ASMJS: + break; + case JIT: +#ifdef JS_ION + if (data_.jitFrames_.isIonJS()) + return ionInlineFrames_.thisValue(); + return data_.jitFrames_.baselineFrame()->thisValue(); +#else + break; +#endif + case INTERP: + return interpFrame()->thisValue(); + } + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +Value +FrameIter::returnValue() const +{ + switch (data_.state_) { + case DONE: + case ASMJS: + break; + case JIT: +#ifdef JS_ION + if (data_.jitFrames_.isBaselineJS()) + return data_.jitFrames_.baselineFrame()->returnValue(); +#endif + break; + case INTERP: + return interpFrame()->returnValue(); + } + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +void +FrameIter::setReturnValue(const Value &v) +{ + switch (data_.state_) { + case DONE: + case ASMJS: + break; + case JIT: +#ifdef JS_ION + if (data_.jitFrames_.isBaselineJS()) { + data_.jitFrames_.baselineFrame()->setReturnValue(v); + return; + } +#endif + break; + case INTERP: + interpFrame()->setReturnValue(v); + return; + } + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +size_t +FrameIter::numFrameSlots() const +{ + switch (data_.state_) { + case DONE: + case ASMJS: + break; + case JIT: { +#ifdef JS_ION + if (data_.jitFrames_.isIonJS()) { + return ionInlineFrames_.snapshotIterator().numAllocations() - + ionInlineFrames_.script()->nfixed(); + } + jit::BaselineFrame *frame = data_.jitFrames_.baselineFrame(); + return frame->numValueSlots() - data_.jitFrames_.script()->nfixed(); +#else + break; +#endif + } + case INTERP: + JS_ASSERT(data_.interpFrames_.sp() >= interpFrame()->base()); + return data_.interpFrames_.sp() - interpFrame()->base(); + } + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +Value +FrameIter::frameSlotValue(size_t index) const +{ + switch (data_.state_) { + case DONE: + case ASMJS: + break; + case JIT: +#ifdef JS_ION + if (data_.jitFrames_.isIonJS()) { + jit::SnapshotIterator si(ionInlineFrames_.snapshotIterator()); + index += ionInlineFrames_.script()->nfixed(); + return si.maybeReadAllocByIndex(index); + } + + index += data_.jitFrames_.script()->nfixed(); + return *data_.jitFrames_.baselineFrame()->valueSlot(index); +#else + break; +#endif + case INTERP: + return interpFrame()->base()[index]; + } + MOZ_ASSUME_UNREACHABLE("Unexpected state"); +} + +#if defined(_MSC_VER) +# pragma optimize("", on) +#endif + +#ifdef DEBUG +bool +js::SelfHostedFramesVisible() +{ + static bool checked = false; + static bool visible = false; + if (!checked) { + checked = true; + char *env = getenv("MOZ_SHOW_ALL_JS_FRAMES"); + visible = !!env; + } + return visible; +} +#endif + +void +NonBuiltinFrameIter::settle() +{ + if (!SelfHostedFramesVisible()) { + while (!done() && hasScript() && script()->selfHosted()) + FrameIter::operator++(); + } +} + +void +NonBuiltinScriptFrameIter::settle() +{ + if (!SelfHostedFramesVisible()) { + while (!done() && script()->selfHosted()) + ScriptFrameIter::operator++(); + } +} + +/*****************************************************************************/ + +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 (isInterpreterFrame()) + return asInterpreterFrame()->hasPushedSPSFrame(); +#ifdef JS_ION + return asBaselineFrame()->hasPushedSPSFrame(); +#else + MOZ_ASSUME_UNREACHABLE("Invalid frame"); +#endif +} + +#ifdef DEBUG +void +js::CheckLocalUnaliased(MaybeCheckAliasing checkAliasing, JSScript *script, uint32_t i) +{ + if (!checkAliasing) + return; + + JS_ASSERT(i < script->nfixed()); + if (i < script->bindings.numVars()) { + JS_ASSERT(!script->varIsAliased(i)); + } else { + // FIXME: The callers of this function do not easily have the PC of the + // current frame, and so they do not know the block scope. + } +} +#endif + +jit::JitActivation::JitActivation(JSContext *cx, bool firstFrameIsConstructing, bool active) + : Activation(cx, Jit), + firstFrameIsConstructing_(firstFrameIsConstructing), + active_(active) +#ifdef JS_ION + , rematerializedFrames_(cx) +#endif +{ + if (active) { + prevIonTop_ = cx->mainThread().ionTop; + prevJitJSContext_ = cx->mainThread().jitJSContext; + cx->mainThread().jitJSContext = cx; + } else { + prevIonTop_ = nullptr; + prevJitJSContext_ = nullptr; + } +} + +jit::JitActivation::~JitActivation() +{ + if (active_) { + cx_->mainThread().ionTop = prevIonTop_; + cx_->mainThread().jitJSContext = prevJitJSContext_; + } + +#ifdef JS_ION + clearRematerializedFrames(); +#endif +} + +// setActive() is inlined in GenerateFFIIonExit() with explicit masm instructions so +// changes to the logic here need to be reflected in GenerateFFIIonExit() in the enable +// and disable activation instruction sequences. +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; + prevJitJSContext_ = cx->mainThread().jitJSContext; + cx->mainThread().jitJSContext = cx; + } else { + cx->mainThread().ionTop = prevIonTop_; + cx->mainThread().jitJSContext = prevJitJSContext_; + } +} + +#ifdef JS_ION + +void +jit::JitActivation::freeRematerializedFramesInVector(RematerializedFrameVector &frames) +{ + for (size_t i = 0; i < frames.length(); i++) { + RematerializedFrame *f = frames[i]; + f->RematerializedFrame::~RematerializedFrame(); + js_free(f); + } + frames.clear(); +} + +void +jit::JitActivation::removeRematerializedFrame(uint8_t *top) +{ + if (!rematerializedFrames_.initialized()) + return; + + if (RematerializedFrameTable::Ptr p = rematerializedFrames_.lookup(top)) { + freeRematerializedFramesInVector(p->value()); + rematerializedFrames_.remove(p); + } +} + +void +jit::JitActivation::clearRematerializedFrames() +{ + if (!rematerializedFrames_.initialized()) + return; + + for (RematerializedFrameTable::Enum e(rematerializedFrames_); !e.empty(); e.popFront()) { + freeRematerializedFramesInVector(e.front().value()); + e.removeFront(); + } +} + +jit::RematerializedFrame * +jit::JitActivation::getRematerializedFrame(JSContext *cx, JitFrameIterator &iter, + size_t inlineDepth) +{ + MOZ_ASSERT(iter.activation() == this); + MOZ_ASSERT(iter.isIonJS()); + + if (!rematerializedFrames_.initialized() && !rematerializedFrames_.init()) + return nullptr; + + // The unit of rematerialization is an uninlined frame and its inlined + // frames. Since inlined frames do not exist outside of snapshots, it is + // impossible to synchronize their rematerialized copies to preserve + // identity. Therefore, we always rematerialize an uninlined frame and all + // its inlined frames at once. + + uint8_t *top = iter.fp(); + RematerializedFrameTable::AddPtr p = rematerializedFrames_.lookupForAdd(top); + if (!p) { + RematerializedFrameVector empty(cx); + if (!rematerializedFrames_.add(p, top, Move(empty))) + return nullptr; + + InlineFrameIterator inlineIter(cx, &iter); + if (!p->value().resize(inlineIter.frameCount())) + return nullptr; + + while (true) { + size_t frameNo = inlineIter.frameNo(); + p->value()[frameNo] = RematerializedFrame::New(cx, top, inlineIter); + if (!p->value()[frameNo]) + return nullptr; + + if (!inlineIter.more()) + break; + ++inlineIter; + } + } + + return p->value()[inlineDepth]; +} + +jit::RematerializedFrame * +jit::JitActivation::lookupRematerializedFrame(uint8_t *top, size_t inlineDepth) +{ + if (!rematerializedFrames_.initialized()) + return nullptr; + if (RematerializedFrameTable::Ptr p = rematerializedFrames_.lookup(top)) + return inlineDepth < p->value().length() ? p->value()[inlineDepth] : nullptr; + return nullptr; +} + +void +jit::JitActivation::markRematerializedFrames(JSTracer *trc) +{ + if (!rematerializedFrames_.initialized()) + return; + for (RematerializedFrameTable::Enum e(rematerializedFrames_); !e.empty(); e.popFront()) { + RematerializedFrameVector &frames = e.front().value(); + for (size_t i = 0; i < frames.length(); i++) + frames[i]->mark(trc); + } +} + +#endif // JS_ION + +AsmJSActivation::AsmJSActivation(JSContext *cx, AsmJSModule &module) + : Activation(cx, AsmJS), + module_(module), + errorRejoinSP_(nullptr), + profiler_(nullptr), + resumePC_(nullptr), + exitSP_(nullptr) +{ + if (cx->runtime()->spsProfiler.enabled()) { + // Use a profiler string that matches jsMatch regex in + // browser/devtools/profiler/cleopatra/js/parserWorker.js. + // (For now use a single static string to avoid further slowing down + // calls into asm.js.) + profiler_ = &cx->runtime()->spsProfiler; + profiler_->enterNative("asm.js code :0", this); + } + + prevAsmJS_ = cx_->runtime()->mainThread.asmJSActivationStack_; + + JSRuntime::AutoLockForInterrupt lock(cx_->runtime()); + cx_->runtime()->mainThread.asmJSActivationStack_ = this; + + (void) errorRejoinSP_; // squelch GCC warning +} + +AsmJSActivation::~AsmJSActivation() +{ + if (profiler_) + profiler_->exitNative(); + + JS_ASSERT(cx_->runtime()->mainThread.asmJSActivationStack_ == this); + + JSRuntime::AutoLockForInterrupt lock(cx_->runtime()); + cx_->runtime()->mainThread.asmJSActivationStack_ = prevAsmJS_; +} + +InterpreterFrameIterator & +InterpreterFrameIterator::operator++() +{ + JS_ASSERT(!done()); + if (fp_ != activation_->entryFrame_) { + pc_ = fp_->prevpc(); + sp_ = fp_->prevsp(); + fp_ = fp_->prev(); + } else { + pc_ = nullptr; + sp_ = nullptr; + fp_ = nullptr; + } + 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() +{ + // Stop at the next active activation. No need to update jitTop_, since + // we don't iterate over an active jit activation. + while (!done() && activation_->isJit() && !activation_->asJit()->isActive()) + activation_ = activation_->prev(); +}