| /* -*- 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 "jit/BaselineJIT.h" |
| |
| #include "builtin/Eval.h" |
| #include "jit/BaselineCompiler.h" |
| #include "jit/BaselineHelpers.h" |
| #include "jit/BaselineIC.h" |
| #include "jit/IonLinker.h" |
| #include "jit/IonSpewer.h" |
| #include "jit/VMFunctions.h" |
| |
| #include "builtin/Iterator-inl.h" |
| #include "jit/IonFrames-inl.h" |
| #include "vm/Interpreter-inl.h" |
| |
| namespace js { |
| namespace jit { |
| |
| #ifdef DEBUG |
| void |
| FallbackICSpew(JSContext *cx, ICFallbackStub *stub, const char *fmt, ...) |
| { |
| if (IonSpewEnabled(IonSpew_BaselineICFallback)) { |
| RootedScript script(cx, GetTopIonJSScript(cx)); |
| jsbytecode *pc = stub->icEntry()->pc(script); |
| |
| char fmtbuf[100]; |
| va_list args; |
| va_start(args, fmt); |
| vsnprintf(fmtbuf, 100, fmt, args); |
| va_end(args); |
| |
| IonSpew(IonSpew_BaselineICFallback, |
| "Fallback hit for (%s:%d) (pc=%d,line=%d,uses=%d,stubs=%d): %s", |
| script->filename(), |
| script->lineno, |
| (int) (pc - script->code), |
| PCToLineNumber(script, pc), |
| script->getUseCount(), |
| (int) stub->numOptimizedStubs(), |
| fmtbuf); |
| } |
| } |
| |
| void |
| TypeFallbackICSpew(JSContext *cx, ICTypeMonitor_Fallback *stub, const char *fmt, ...) |
| { |
| if (IonSpewEnabled(IonSpew_BaselineICFallback)) { |
| RootedScript script(cx, GetTopIonJSScript(cx)); |
| jsbytecode *pc = stub->icEntry()->pc(script); |
| |
| char fmtbuf[100]; |
| va_list args; |
| va_start(args, fmt); |
| vsnprintf(fmtbuf, 100, fmt, args); |
| va_end(args); |
| |
| IonSpew(IonSpew_BaselineICFallback, |
| "Type monitor fallback hit for (%s:%d) (pc=%d,line=%d,uses=%d,stubs=%d): %s", |
| script->filename(), |
| script->lineno, |
| (int) (pc - script->code), |
| PCToLineNumber(script, pc), |
| script->getUseCount(), |
| (int) stub->numOptimizedMonitorStubs(), |
| fmtbuf); |
| } |
| } |
| |
| #else |
| #define FallbackICSpew(...) |
| #define TypeFallbackICSpew(...) |
| #endif |
| |
| |
| ICFallbackStub * |
| ICEntry::fallbackStub() const |
| { |
| return firstStub()->getChainFallback(); |
| } |
| |
| |
| ICStubConstIterator & |
| ICStubConstIterator::operator++() |
| { |
| JS_ASSERT(currentStub_ != NULL); |
| currentStub_ = currentStub_->next(); |
| return *this; |
| } |
| |
| |
| ICStubIterator::ICStubIterator(ICFallbackStub *fallbackStub, bool end) |
| : icEntry_(fallbackStub->icEntry()), |
| fallbackStub_(fallbackStub), |
| previousStub_(NULL), |
| currentStub_(end ? fallbackStub : icEntry_->firstStub()), |
| unlinked_(false) |
| { } |
| |
| ICStubIterator & |
| ICStubIterator::operator++() |
| { |
| JS_ASSERT(currentStub_->next() != NULL); |
| if (!unlinked_) |
| previousStub_ = currentStub_; |
| currentStub_ = currentStub_->next(); |
| unlinked_ = false; |
| return *this; |
| } |
| |
| void |
| ICStubIterator::unlink(Zone *zone) |
| { |
| JS_ASSERT(currentStub_->next() != NULL); |
| JS_ASSERT(currentStub_ != fallbackStub_); |
| JS_ASSERT(!unlinked_); |
| fallbackStub_->unlinkStub(zone, previousStub_, currentStub_); |
| |
| // Mark the current iterator position as unlinked, so operator++ works properly. |
| unlinked_ = true; |
| } |
| |
| |
| void |
| ICStub::markCode(JSTracer *trc, const char *name) |
| { |
| IonCode *stubIonCode = ionCode(); |
| MarkIonCodeUnbarriered(trc, &stubIonCode, name); |
| } |
| |
| void |
| ICStub::updateCode(IonCode *code) |
| { |
| // Write barrier on the old code. |
| #ifdef JSGC_INCREMENTAL |
| IonCode::writeBarrierPre(ionCode()); |
| #endif |
| stubCode_ = code->raw(); |
| } |
| |
| /* static */ void |
| ICStub::trace(JSTracer *trc) |
| { |
| markCode(trc, "baseline-stub-ioncode"); |
| |
| // If the stub is a monitored fallback stub, then mark the monitor ICs hanging |
| // off of that stub. We don't need to worry about the regular monitored stubs, |
| // because the regular monitored stubs will always have a monitored fallback stub |
| // that references the same stub chain. |
| if (isMonitoredFallback()) { |
| ICTypeMonitor_Fallback *lastMonStub = toMonitoredFallbackStub()->fallbackMonitorStub(); |
| for (ICStubConstIterator iter = lastMonStub->firstMonitorStub(); !iter.atEnd(); iter++) { |
| JS_ASSERT_IF(iter->next() == NULL, *iter == lastMonStub); |
| iter->markCode(trc, "baseline-monitor-stub-ioncode"); |
| } |
| } |
| |
| if (isUpdated()) { |
| for (ICStubConstIterator iter = toUpdatedStub()->firstUpdateStub(); !iter.atEnd(); iter++) { |
| JS_ASSERT_IF(iter->next() == NULL, iter->isTypeUpdate_Fallback()); |
| iter->markCode(trc, "baseline-update-stub-ioncode"); |
| } |
| } |
| |
| switch (kind()) { |
| case ICStub::Call_Scripted: { |
| ICCall_Scripted *callStub = toCall_Scripted(); |
| MarkScript(trc, &callStub->calleeScript(), "baseline-callscripted-callee"); |
| break; |
| } |
| case ICStub::Call_Native: { |
| ICCall_Native *callStub = toCall_Native(); |
| MarkObject(trc, &callStub->callee(), "baseline-callnative-callee"); |
| break; |
| } |
| case ICStub::GetElem_Native: { |
| ICGetElem_Native *getElemStub = toGetElem_Native(); |
| MarkShape(trc, &getElemStub->shape(), "baseline-getelem-native-shape"); |
| gc::MarkValue(trc, &getElemStub->idval(), "baseline-getelem-native-idval"); |
| break; |
| } |
| case ICStub::GetElem_NativePrototype: { |
| ICGetElem_NativePrototype *getElemStub = toGetElem_NativePrototype(); |
| MarkShape(trc, &getElemStub->shape(), "baseline-getelem-nativeproto-shape"); |
| gc::MarkValue(trc, &getElemStub->idval(), "baseline-getelem-nativeproto-idval"); |
| MarkObject(trc, &getElemStub->holder(), "baseline-getelem-nativeproto-holder"); |
| MarkShape(trc, &getElemStub->holderShape(), "baseline-getelem-nativeproto-holdershape"); |
| break; |
| } |
| case ICStub::GetElem_Dense: { |
| ICGetElem_Dense *getElemStub = toGetElem_Dense(); |
| MarkShape(trc, &getElemStub->shape(), "baseline-getelem-dense-shape"); |
| break; |
| } |
| case ICStub::GetElem_TypedArray: { |
| ICGetElem_TypedArray *getElemStub = toGetElem_TypedArray(); |
| MarkShape(trc, &getElemStub->shape(), "baseline-getelem-typedarray-shape"); |
| break; |
| } |
| case ICStub::SetElem_Dense: { |
| ICSetElem_Dense *setElemStub = toSetElem_Dense(); |
| MarkShape(trc, &setElemStub->shape(), "baseline-getelem-dense-shape"); |
| MarkTypeObject(trc, &setElemStub->type(), "baseline-setelem-dense-type"); |
| break; |
| } |
| case ICStub::SetElem_DenseAdd: { |
| ICSetElem_DenseAdd *setElemStub = toSetElem_DenseAdd(); |
| MarkTypeObject(trc, &setElemStub->type(), "baseline-setelem-denseadd-type"); |
| |
| JS_STATIC_ASSERT(ICSetElem_DenseAdd::MAX_PROTO_CHAIN_DEPTH == 4); |
| |
| switch (setElemStub->protoChainDepth()) { |
| case 0: setElemStub->toImpl<0>()->traceShapes(trc); break; |
| case 1: setElemStub->toImpl<1>()->traceShapes(trc); break; |
| case 2: setElemStub->toImpl<2>()->traceShapes(trc); break; |
| case 3: setElemStub->toImpl<3>()->traceShapes(trc); break; |
| case 4: setElemStub->toImpl<4>()->traceShapes(trc); break; |
| default: JS_NOT_REACHED("Invalid proto stub."); |
| } |
| break; |
| } |
| case ICStub::SetElem_TypedArray: { |
| ICSetElem_TypedArray *setElemStub = toSetElem_TypedArray(); |
| MarkShape(trc, &setElemStub->shape(), "baseline-setelem-typedarray-shape"); |
| break; |
| } |
| case ICStub::TypeMonitor_SingleObject: { |
| ICTypeMonitor_SingleObject *monitorStub = toTypeMonitor_SingleObject(); |
| MarkObject(trc, &monitorStub->object(), "baseline-monitor-singleobject"); |
| break; |
| } |
| case ICStub::TypeMonitor_TypeObject: { |
| ICTypeMonitor_TypeObject *monitorStub = toTypeMonitor_TypeObject(); |
| MarkTypeObject(trc, &monitorStub->type(), "baseline-monitor-typeobject"); |
| break; |
| } |
| case ICStub::TypeUpdate_SingleObject: { |
| ICTypeUpdate_SingleObject *updateStub = toTypeUpdate_SingleObject(); |
| MarkObject(trc, &updateStub->object(), "baseline-update-singleobject"); |
| break; |
| } |
| case ICStub::TypeUpdate_TypeObject: { |
| ICTypeUpdate_TypeObject *updateStub = toTypeUpdate_TypeObject(); |
| MarkTypeObject(trc, &updateStub->type(), "baseline-update-typeobject"); |
| break; |
| } |
| case ICStub::Profiler_PushFunction: { |
| ICProfiler_PushFunction *pushFunStub = toProfiler_PushFunction(); |
| MarkScript(trc, &pushFunStub->script(), "baseline-profilerpushfunction-stub-script"); |
| break; |
| } |
| case ICStub::GetName_Global: { |
| ICGetName_Global *globalStub = toGetName_Global(); |
| MarkShape(trc, &globalStub->shape(), "baseline-global-stub-shape"); |
| break; |
| } |
| case ICStub::GetName_Scope0: |
| static_cast<ICGetName_Scope<0>*>(this)->traceScopes(trc); |
| break; |
| case ICStub::GetName_Scope1: |
| static_cast<ICGetName_Scope<1>*>(this)->traceScopes(trc); |
| break; |
| case ICStub::GetName_Scope2: |
| static_cast<ICGetName_Scope<2>*>(this)->traceScopes(trc); |
| break; |
| case ICStub::GetName_Scope3: |
| static_cast<ICGetName_Scope<3>*>(this)->traceScopes(trc); |
| break; |
| case ICStub::GetName_Scope4: |
| static_cast<ICGetName_Scope<4>*>(this)->traceScopes(trc); |
| break; |
| case ICStub::GetName_Scope5: |
| static_cast<ICGetName_Scope<5>*>(this)->traceScopes(trc); |
| break; |
| case ICStub::GetName_Scope6: |
| static_cast<ICGetName_Scope<6>*>(this)->traceScopes(trc); |
| break; |
| case ICStub::GetIntrinsic_Constant: { |
| ICGetIntrinsic_Constant *constantStub = toGetIntrinsic_Constant(); |
| gc::MarkValue(trc, &constantStub->value(), "baseline-getintrinsic-constant-value"); |
| break; |
| } |
| case ICStub::GetProp_String: { |
| ICGetProp_String *propStub = toGetProp_String(); |
| MarkShape(trc, &propStub->stringProtoShape(), "baseline-getpropstring-stub-shape"); |
| break; |
| } |
| case ICStub::GetProp_Native: { |
| ICGetProp_Native *propStub = toGetProp_Native(); |
| MarkShape(trc, &propStub->shape(), "baseline-getpropnative-stub-shape"); |
| break; |
| } |
| case ICStub::GetProp_NativePrototype: { |
| ICGetProp_NativePrototype *propStub = toGetProp_NativePrototype(); |
| MarkShape(trc, &propStub->shape(), "baseline-getpropnativeproto-stub-shape"); |
| MarkObject(trc, &propStub->holder(), "baseline-getpropnativeproto-stub-holder"); |
| MarkShape(trc, &propStub->holderShape(), "baseline-getpropnativeproto-stub-holdershape"); |
| break; |
| } |
| case ICStub::GetProp_CallDOMProxyNative: |
| case ICStub::GetProp_CallDOMProxyWithGenerationNative: { |
| ICGetPropCallDOMProxyNativeStub *propStub; |
| if (kind() == ICStub::GetProp_CallDOMProxyNative) |
| propStub = toGetProp_CallDOMProxyNative(); |
| else |
| propStub = toGetProp_CallDOMProxyWithGenerationNative(); |
| MarkShape(trc, &propStub->shape(), "baseline-getproplistbasenative-stub-shape"); |
| if (propStub->expandoShape()) { |
| MarkShape(trc, &propStub->expandoShape(), |
| "baseline-getproplistbasenative-stub-expandoshape"); |
| } |
| MarkObject(trc, &propStub->holder(), "baseline-getproplistbasenative-stub-holder"); |
| MarkShape(trc, &propStub->holderShape(), "baseline-getproplistbasenative-stub-holdershape"); |
| MarkObject(trc, &propStub->getter(), "baseline-getproplistbasenative-stub-getter"); |
| break; |
| } |
| case ICStub::GetProp_DOMProxyShadowed: { |
| ICGetProp_DOMProxyShadowed *propStub = toGetProp_DOMProxyShadowed(); |
| MarkShape(trc, &propStub->shape(), "baseline-getproplistbaseshadowed-stub-shape"); |
| MarkString(trc, &propStub->name(), "baseline-getproplistbaseshadowed-stub-name"); |
| break; |
| } |
| case ICStub::GetProp_CallScripted: { |
| ICGetProp_CallScripted *callStub = toGetProp_CallScripted(); |
| MarkShape(trc, &callStub->shape(), "baseline-getpropcallscripted-stub-shape"); |
| MarkObject(trc, &callStub->holder(), "baseline-getpropcallscripted-stub-holder"); |
| MarkShape(trc, &callStub->holderShape(), "baseline-getpropcallscripted-stub-holdershape"); |
| MarkObject(trc, &callStub->getter(), "baseline-getpropcallscripted-stub-getter"); |
| break; |
| } |
| case ICStub::GetProp_CallNative: { |
| ICGetProp_CallNative *callStub = toGetProp_CallNative(); |
| MarkShape(trc, &callStub->shape(), "baseline-getpropcallnative-stub-shape"); |
| MarkObject(trc, &callStub->holder(), "baseline-getpropcallnative-stub-holder"); |
| MarkShape(trc, &callStub->holderShape(), "baseline-getpropcallnative-stub-holdershape"); |
| MarkObject(trc, &callStub->getter(), "baseline-getpropcallnative-stub-getter"); |
| break; |
| } |
| case ICStub::SetProp_Native: { |
| ICSetProp_Native *propStub = toSetProp_Native(); |
| MarkShape(trc, &propStub->shape(), "baseline-setpropnative-stub-shape"); |
| MarkTypeObject(trc, &propStub->type(), "baseline-setpropnative-stub-type"); |
| break; |
| } |
| case ICStub::SetProp_NativeAdd: { |
| ICSetProp_NativeAdd *propStub = toSetProp_NativeAdd(); |
| MarkTypeObject(trc, &propStub->type(), "baseline-setpropnativeadd-stub-type"); |
| MarkShape(trc, &propStub->newShape(), "baseline-setpropnativeadd-stub-newshape"); |
| JS_STATIC_ASSERT(ICSetProp_NativeAdd::MAX_PROTO_CHAIN_DEPTH == 4); |
| switch (propStub->protoChainDepth()) { |
| case 0: propStub->toImpl<0>()->traceShapes(trc); break; |
| case 1: propStub->toImpl<1>()->traceShapes(trc); break; |
| case 2: propStub->toImpl<2>()->traceShapes(trc); break; |
| case 3: propStub->toImpl<3>()->traceShapes(trc); break; |
| case 4: propStub->toImpl<4>()->traceShapes(trc); break; |
| default: JS_NOT_REACHED("Invalid proto stub."); |
| } |
| break; |
| } |
| case ICStub::SetProp_CallScripted: { |
| ICSetProp_CallScripted *callStub = toSetProp_CallScripted(); |
| MarkShape(trc, &callStub->shape(), "baseline-setpropcallscripted-stub-shape"); |
| MarkObject(trc, &callStub->holder(), "baseline-setpropcallscripted-stub-holder"); |
| MarkShape(trc, &callStub->holderShape(), "baseline-setpropcallscripted-stub-holdershape"); |
| MarkObject(trc, &callStub->setter(), "baseline-setpropcallscripted-stub-setter"); |
| break; |
| } |
| case ICStub::SetProp_CallNative: { |
| ICSetProp_CallNative *callStub = toSetProp_CallNative(); |
| MarkShape(trc, &callStub->shape(), "baseline-setpropcallnative-stub-shape"); |
| MarkObject(trc, &callStub->holder(), "baseline-setpropcallnative-stub-holder"); |
| MarkShape(trc, &callStub->holderShape(), "baseline-setpropcallnative-stub-holdershape"); |
| MarkObject(trc, &callStub->setter(), "baseline-setpropcallnative-stub-setter"); |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| void |
| ICFallbackStub::unlinkStub(Zone *zone, ICStub *prev, ICStub *stub) |
| { |
| JS_ASSERT(stub->next()); |
| |
| // If stub is the last optimized stub, update lastStubPtrAddr. |
| if (stub->next() == this) { |
| JS_ASSERT(lastStubPtrAddr_ == stub->addressOfNext()); |
| if (prev) |
| lastStubPtrAddr_ = prev->addressOfNext(); |
| else |
| lastStubPtrAddr_ = icEntry()->addressOfFirstStub(); |
| *lastStubPtrAddr_ = this; |
| } else { |
| if (prev) { |
| JS_ASSERT(prev->next() == stub); |
| prev->setNext(stub->next()); |
| } else { |
| JS_ASSERT(icEntry()->firstStub() == stub); |
| icEntry()->setFirstStub(stub->next()); |
| } |
| } |
| |
| JS_ASSERT(numOptimizedStubs_ > 0); |
| numOptimizedStubs_--; |
| |
| if (zone->needsBarrier()) { |
| // We are removing edges from ICStub to gcthings. Perform one final trace |
| // of the stub for incremental GC, as it must know about those edges. |
| stub->trace(zone->barrierTracer()); |
| } |
| |
| if (ICStub::CanMakeCalls(stub->kind()) && stub->isMonitored()) { |
| // This stub can make calls so we can return to it if it's on the stack. |
| // We just have to reset its firstMonitorStub_ field to avoid a stale |
| // pointer when purgeOptimizedStubs destroys all optimized monitor |
| // stubs (unlinked stubs won't be updated). |
| ICTypeMonitor_Fallback *monitorFallback = toMonitoredFallbackStub()->fallbackMonitorStub(); |
| stub->toMonitoredStub()->resetFirstMonitorStub(monitorFallback); |
| } |
| |
| #ifdef DEBUG |
| // Poison stub code to ensure we don't call this stub again. However, if this |
| // stub can make calls, a pointer to it may be stored in a stub frame on the |
| // stack, so we can't touch the stubCode_ or GC will crash when marking this |
| // pointer. |
| if (!ICStub::CanMakeCalls(stub->kind())) |
| stub->stubCode_ = (uint8_t *)0xbad; |
| #endif |
| } |
| |
| void |
| ICFallbackStub::unlinkStubsWithKind(JSContext *cx, ICStub::Kind kind) |
| { |
| for (ICStubIterator iter = beginChain(); !iter.atEnd(); iter++) { |
| if (iter->kind() == kind) |
| iter.unlink(cx->zone()); |
| } |
| } |
| |
| void |
| ICTypeMonitor_Fallback::resetMonitorStubChain(Zone *zone) |
| { |
| if (zone->needsBarrier()) { |
| // We are removing edges from monitored stubs to gcthings (IonCode). |
| // Perform one final trace of all monitor stubs for incremental GC, |
| // as it must know about those edges. |
| if (hasFallbackStub_) { |
| for (ICStub *s = firstMonitorStub_; !s->isTypeMonitor_Fallback(); s = s->next()) |
| s->trace(zone->barrierTracer()); |
| } |
| } |
| |
| firstMonitorStub_ = this; |
| numOptimizedMonitorStubs_ = 0; |
| |
| if (hasFallbackStub_) { |
| lastMonitorStubPtrAddr_ = NULL; |
| |
| // Reset firstMonitorStub_ field of all monitored stubs. |
| for (ICStubConstIterator iter = mainFallbackStub_->beginChainConst(); |
| !iter.atEnd(); iter++) |
| { |
| if (!iter->isMonitored()) |
| continue; |
| iter->toMonitoredStub()->resetFirstMonitorStub(this); |
| } |
| } else { |
| icEntry_->setFirstStub(this); |
| lastMonitorStubPtrAddr_ = icEntry_->addressOfFirstStub(); |
| } |
| } |
| |
| ICMonitoredStub::ICMonitoredStub(Kind kind, IonCode *stubCode, ICStub *firstMonitorStub) |
| : ICStub(kind, ICStub::Monitored, stubCode), |
| firstMonitorStub_(firstMonitorStub) |
| { |
| // If the first monitored stub is a ICTypeMonitor_Fallback stub, then |
| // double check that _its_ firstMonitorStub is the same as this one. |
| JS_ASSERT_IF(firstMonitorStub_->isTypeMonitor_Fallback(), |
| firstMonitorStub_->toTypeMonitor_Fallback()->firstMonitorStub() == |
| firstMonitorStub_); |
| } |
| |
| bool |
| ICMonitoredFallbackStub::initMonitoringChain(JSContext *cx, ICStubSpace *space) |
| { |
| JS_ASSERT(fallbackMonitorStub_ == NULL); |
| |
| ICTypeMonitor_Fallback::Compiler compiler(cx, this); |
| ICTypeMonitor_Fallback *stub = compiler.getStub(space); |
| if (!stub) |
| return false; |
| fallbackMonitorStub_ = stub; |
| return true; |
| } |
| |
| bool |
| ICMonitoredFallbackStub::addMonitorStubForValue(JSContext *cx, HandleScript script, HandleValue val) |
| { |
| return fallbackMonitorStub_->addMonitorStubForValue(cx, script, val); |
| } |
| |
| bool |
| ICUpdatedStub::initUpdatingChain(JSContext *cx, ICStubSpace *space) |
| { |
| JS_ASSERT(firstUpdateStub_ == NULL); |
| |
| ICTypeUpdate_Fallback::Compiler compiler(cx); |
| ICTypeUpdate_Fallback *stub = compiler.getStub(space); |
| if (!stub) |
| return false; |
| |
| firstUpdateStub_ = stub; |
| return true; |
| } |
| |
| IonCode * |
| ICStubCompiler::getStubCode() |
| { |
| IonCompartment *ion = cx->compartment()->ionCompartment(); |
| |
| // Check for existing cached stubcode. |
| uint32_t stubKey = getKey(); |
| IonCode *stubCode = ion->getStubCode(stubKey); |
| if (stubCode) |
| return stubCode; |
| |
| // Compile new stubcode. |
| MacroAssembler masm; |
| #ifdef JS_CPU_ARM |
| masm.setSecondScratchReg(BaselineSecondScratchReg); |
| #endif |
| |
| AutoFlushCache afc("ICStubCompiler::getStubCode", cx->runtime()->ionRuntime()); |
| if (!generateStubCode(masm)) |
| return NULL; |
| Linker linker(masm); |
| Rooted<IonCode *> newStubCode(cx, linker.newCode(cx, JSC::BASELINE_CODE)); |
| if (!newStubCode) |
| return NULL; |
| |
| // After generating code, run postGenerateStubCode() |
| if (!postGenerateStubCode(masm, newStubCode)) |
| return NULL; |
| |
| // All barriers are emitted off-by-default, enable them if needed. |
| if (cx->zone()->needsBarrier()) |
| newStubCode->togglePreBarriers(true); |
| |
| // Cache newly compiled stubcode. |
| if (!ion->putStubCode(stubKey, newStubCode)) |
| return NULL; |
| |
| JS_ASSERT(entersStubFrame_ == ICStub::CanMakeCalls(kind)); |
| |
| return newStubCode; |
| } |
| |
| bool |
| ICStubCompiler::tailCallVM(const VMFunction &fun, MacroAssembler &masm) |
| { |
| IonCompartment *ion = cx->compartment()->ionCompartment(); |
| IonCode *code = ion->getVMWrapper(fun); |
| if (!code) |
| return false; |
| |
| uint32_t argSize = fun.explicitStackSlots() * sizeof(void *); |
| EmitTailCallVM(code, masm, argSize); |
| return true; |
| } |
| |
| bool |
| ICStubCompiler::callVM(const VMFunction &fun, MacroAssembler &masm) |
| { |
| IonCompartment *ion = cx->compartment()->ionCompartment(); |
| IonCode *code = ion->getVMWrapper(fun); |
| if (!code) |
| return false; |
| |
| EmitCallVM(code, masm); |
| return true; |
| } |
| |
| bool |
| ICStubCompiler::callTypeUpdateIC(MacroAssembler &masm, uint32_t objectOffset) |
| { |
| IonCompartment *ion = cx->compartment()->ionCompartment(); |
| IonCode *code = ion->getVMWrapper(DoTypeUpdateFallbackInfo); |
| if (!code) |
| return false; |
| |
| EmitCallTypeUpdateIC(masm, code, objectOffset); |
| return true; |
| } |
| |
| void |
| ICStubCompiler::enterStubFrame(MacroAssembler &masm, Register scratch) |
| { |
| EmitEnterStubFrame(masm, scratch); |
| #ifdef DEBUG |
| entersStubFrame_ = true; |
| #endif |
| } |
| |
| void |
| ICStubCompiler::leaveStubFrame(MacroAssembler &masm, bool calledIntoIon) |
| { |
| JS_ASSERT(entersStubFrame_); |
| EmitLeaveStubFrame(masm, calledIntoIon); |
| } |
| |
| void |
| ICStubCompiler::guardProfilingEnabled(MacroAssembler &masm, Register scratch, Label *skip) |
| { |
| // This should only be called from the following stubs. |
| JS_ASSERT(kind == ICStub::Call_Scripted || kind == ICStub::Call_AnyScripted || |
| kind == ICStub::Call_Native || kind == ICStub::GetProp_CallScripted || |
| kind == ICStub::GetProp_CallNative || kind == ICStub::GetProp_CallDOMProxyNative || |
| kind == ICStub::Call_ScriptedApplyArguments || |
| kind == ICStub::GetProp_CallDOMProxyWithGenerationNative || |
| kind == ICStub::GetProp_DOMProxyShadowed || |
| kind == ICStub::SetProp_CallScripted || kind == ICStub::SetProp_CallNative); |
| |
| // Guard on bit in frame that indicates if the SPS frame was pushed in the first |
| // place. This code is expected to be called from within a stub that has already |
| // entered a stub frame. |
| JS_ASSERT(entersStubFrame_); |
| masm.loadPtr(Address(BaselineFrameReg, 0), scratch); |
| masm.branchTest32(Assembler::Zero, |
| Address(scratch, BaselineFrame::reverseOffsetOfFlags()), |
| Imm32(BaselineFrame::HAS_PUSHED_SPS_FRAME), |
| skip); |
| |
| // Check if profiling is enabled |
| uint32_t *enabledAddr = cx->runtime()->spsProfiler.addressOfEnabled(); |
| masm.branch32(Assembler::Equal, AbsoluteAddress(enabledAddr), Imm32(0), skip); |
| } |
| |
| #ifdef JSGC_GENERATIONAL |
| inline bool |
| ICStubCompiler::emitPostWriteBarrierSlot(MacroAssembler &masm, Register obj, Register scratch, |
| GeneralRegisterSet saveRegs) |
| { |
| Nursery &nursery = cx->runtime()->gcNursery; |
| |
| Label skipBarrier; |
| Label isTenured; |
| masm.branchPtr(Assembler::Below, obj, ImmWord(nursery.start()), &isTenured); |
| masm.branchPtr(Assembler::Below, obj, ImmWord(nursery.heapEnd()), &skipBarrier); |
| masm.bind(&isTenured); |
| |
| // void PostWriteBarrier(JSRuntime *rt, JSObject *obj); |
| #if defined(JS_CPU_ARM) || defined(JS_CPU_MIPS) |
| saveRegs.add(BaselineTailCallReg); |
| #endif |
| saveRegs = GeneralRegisterSet::Intersect(saveRegs, GeneralRegisterSet::Volatile()); |
| masm.PushRegsInMask(saveRegs); |
| masm.setupUnalignedABICall(2, scratch); |
| masm.movePtr(ImmWord(cx->runtime()), scratch); |
| masm.passABIArg(scratch); |
| masm.passABIArg(obj); |
| masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, PostWriteBarrier)); |
| masm.PopRegsInMask(saveRegs); |
| |
| masm.bind(&skipBarrier); |
| return true; |
| } |
| #endif // JSGC_GENERATIONAL |
| |
| // |
| // UseCount_Fallback |
| // |
| static bool |
| IsTopFrameConstructing(JSContext *cx) |
| { |
| JS_ASSERT(cx->currentlyRunningInJit()); |
| JitActivationIterator activations(cx->runtime()); |
| IonFrameIterator iter(activations); |
| JS_ASSERT(iter.type() == IonFrame_Exit); |
| |
| ++iter; |
| JS_ASSERT(iter.type() == IonFrame_BaselineStub); |
| |
| ++iter; |
| JS_ASSERT(iter.isBaselineJS()); |
| |
| return iter.isConstructing(); |
| } |
| |
| static bool |
| EnsureCanEnterIon(JSContext *cx, ICUseCount_Fallback *stub, BaselineFrame *frame, |
| HandleScript script, jsbytecode *pc, void **jitcodePtr) |
| { |
| JS_ASSERT(jitcodePtr); |
| JS_ASSERT(!*jitcodePtr); |
| |
| bool isLoopEntry = (JSOp(*pc) == JSOP_LOOPENTRY); |
| |
| bool isConstructing = IsTopFrameConstructing(cx); |
| MethodStatus stat; |
| if (isLoopEntry) { |
| IonSpew(IonSpew_BaselineOSR, " Compile at loop entry!"); |
| stat = CanEnterAtBranch(cx, script, frame, pc, isConstructing); |
| } else if (frame->isFunctionFrame()) { |
| IonSpew(IonSpew_BaselineOSR, " Compile function from top for later entry!"); |
| stat = CompileFunctionForBaseline(cx, script, frame, isConstructing); |
| } else { |
| return true; |
| } |
| |
| if (stat == Method_Error) { |
| IonSpew(IonSpew_BaselineOSR, " Compile with Ion errored!"); |
| return false; |
| } |
| |
| if (stat == Method_CantCompile) |
| IonSpew(IonSpew_BaselineOSR, " Can't compile with Ion!"); |
| else if (stat == Method_Skipped) |
| IonSpew(IonSpew_BaselineOSR, " Skipped compile with Ion!"); |
| else if (stat == Method_Compiled) |
| IonSpew(IonSpew_BaselineOSR, " Compiled with Ion!"); |
| else |
| JS_NOT_REACHED("Invalid MethodStatus!"); |
| |
| // Failed to compile. Reset use count and return. |
| if (stat != Method_Compiled) { |
| // TODO: If stat == Method_CantCompile, insert stub that just skips the useCount |
| // entirely, instead of resetting it. |
| bool bailoutExpected = script->hasIonScript() && script->ionScript()->bailoutExpected(); |
| if (stat == Method_CantCompile || bailoutExpected) { |
| IonSpew(IonSpew_BaselineOSR, " Reset UseCount cantCompile=%s bailoutExpected=%s!", |
| stat == Method_CantCompile ? "yes" : "no", |
| bailoutExpected ? "yes" : "no"); |
| script->resetUseCount(); |
| } |
| return true; |
| } |
| |
| if (isLoopEntry) { |
| IonSpew(IonSpew_BaselineOSR, " OSR possible!"); |
| IonScript *ion = script->ionScript(); |
| *jitcodePtr = ion->method()->raw() + ion->osrEntryOffset(); |
| } |
| |
| return true; |
| } |
| |
| // |
| // The following data is kept in a temporary heap-allocated buffer, stored in |
| // IonRuntime (high memory addresses at top, low at bottom): |
| // |
| // +=================================+ -- <---- High Address |
| // | | | |
| // | ...Locals/Stack... | | |
| // | | | |
| // +---------------------------------+ | |
| // | | | |
| // | ...StackFrame... | |-- Fake StackFrame |
| // | | | |
| // +----> +---------------------------------+ | |
| // | | | | |
| // | | ...Args/This... | | |
| // | | | | |
| // | +=================================+ -- |
| // | | Padding(Maybe Empty) | |
| // | +=================================+ -- |
| // +------|-- stackFrame | |-- IonOsrTempData |
| // | jitcode | | |
| // +=================================+ -- <---- Low Address |
| // |
| // A pointer to the IonOsrTempData is returned. |
| |
| struct IonOsrTempData |
| { |
| void *jitcode; |
| uint8_t *stackFrame; |
| }; |
| |
| static IonOsrTempData * |
| PrepareOsrTempData(JSContext *cx, ICUseCount_Fallback *stub, BaselineFrame *frame, |
| HandleScript script, jsbytecode *pc, void *jitcode) |
| { |
| // Calculate the (numLocals + numStackVals), and the number of formal args. |
| size_t numLocalsAndStackVals = frame->numValueSlots(); |
| size_t numFormalArgs = frame->isFunctionFrame() ? frame->numFormalArgs() : 0; |
| |
| // Calculate the amount of space to allocate: |
| // StackFrame space: |
| // (sizeof(Value) * (numLocals + numStackVals)) |
| // + sizeof(StackFrame) |
| // + (sizeof(Value) * (numFormalArgs + 1)) // +1 for ThisV |
| // |
| // IonOsrTempData space: |
| // sizeof(IonOsrTempData) |
| |
| size_t stackFrameSpace = (sizeof(Value) * numLocalsAndStackVals) + sizeof(StackFrame) |
| + (sizeof(Value) * (numFormalArgs + 1)); |
| size_t ionOsrTempDataSpace = sizeof(IonOsrTempData); |
| |
| size_t totalSpace = AlignBytes(stackFrameSpace, sizeof(Value)) + |
| AlignBytes(ionOsrTempDataSpace, sizeof(Value)); |
| |
| IonOsrTempData *info = (IonOsrTempData *)cx->runtime()->getIonRuntime(cx)->allocateOsrTempData(totalSpace); |
| if (!info) |
| return NULL; |
| |
| memset(info, 0, totalSpace); |
| |
| info->jitcode = jitcode; |
| |
| uint8_t *stackFrameStart = (uint8_t *)info + AlignBytes(ionOsrTempDataSpace, sizeof(Value)); |
| info->stackFrame = stackFrameStart + (numFormalArgs * sizeof(Value)) + sizeof(Value); |
| |
| // |
| // Initialize the fake StackFrame. |
| // |
| |
| // Copy formal args and thisv. |
| memcpy(stackFrameStart, frame->argv() - 1, (numFormalArgs + 1) * sizeof(Value)); |
| |
| // Initialize ScopeChain, Exec, and Flags fields in StackFrame struct. |
| uint8_t *stackFrame = info->stackFrame; |
| *((JSObject **) (stackFrame + StackFrame::offsetOfScopeChain())) = frame->scopeChain(); |
| if (frame->isFunctionFrame()) { |
| // Store the function in exec field, and StackFrame::FUNCTION for flags. |
| *((JSFunction **) (stackFrame + StackFrame::offsetOfExec())) = frame->fun(); |
| *((uint32_t *) (stackFrame + StackFrame::offsetOfFlags())) = StackFrame::FUNCTION; |
| } else { |
| *((JSScript **) (stackFrame + StackFrame::offsetOfExec())) = frame->script(); |
| *((uint32_t *) (stackFrame + StackFrame::offsetOfFlags())) = 0; |
| } |
| |
| // Do locals and stack values. Note that in the fake StackFrame, these go from |
| // low to high addresses, while on the C stack, they go from high to low addresses. |
| // So we can't use memcpy on this, but must copy the values in reverse order. |
| Value *stackFrameLocalsStart = (Value *) (stackFrame + sizeof(StackFrame)); |
| for (size_t i = 0; i < numLocalsAndStackVals; i++) |
| stackFrameLocalsStart[i] = *(frame->valueSlot(i)); |
| |
| IonSpew(IonSpew_BaselineOSR, "Allocated IonOsrTempData at %p", (void *) info); |
| IonSpew(IonSpew_BaselineOSR, "Jitcode is %p", info->jitcode); |
| |
| // All done. |
| return info; |
| } |
| |
| static bool |
| DoUseCountFallback(JSContext *cx, ICUseCount_Fallback *stub, BaselineFrame *frame, |
| IonOsrTempData **infoPtr) |
| { |
| JS_ASSERT(infoPtr); |
| *infoPtr = NULL; |
| |
| // A TI OOM will disable TI and Ion. |
| if (!jit::IsIonEnabled(cx)) |
| return true; |
| |
| RootedScript script(cx, frame->script()); |
| jsbytecode *pc = stub->icEntry()->pc(script); |
| bool isLoopEntry = JSOp(*pc) == JSOP_LOOPENTRY; |
| |
| FallbackICSpew(cx, stub, "UseCount(%d)", isLoopEntry ? int(pc - script->code) : int(-1)); |
| |
| if (!script->canIonCompile()) { |
| // TODO: ASSERT that ion-compilation-disabled checker stub doesn't exist. |
| // TODO: Clear all optimized stubs. |
| // TODO: Add a ion-compilation-disabled checker IC stub |
| script->resetUseCount(); |
| return true; |
| } |
| |
| JS_ASSERT(!script->isIonCompilingOffThread()); |
| |
| // If Ion script exists, but PC is not at a loop entry, then Ion will be entered for |
| // this script at an appropriate LOOPENTRY or the next time this function is called. |
| if (script->hasIonScript() && !isLoopEntry) { |
| IonSpew(IonSpew_BaselineOSR, "IonScript exists, but not at loop entry!"); |
| // TODO: ASSERT that a ion-script-already-exists checker stub doesn't exist. |
| // TODO: Clear all optimized stubs. |
| // TODO: Add a ion-script-already-exists checker stub. |
| return true; |
| } |
| |
| // Ensure that Ion-compiled code is available. |
| IonSpew(IonSpew_BaselineOSR, |
| "UseCount for %s:%d reached %d at pc %p, trying to switch to Ion!", |
| script->filename(), script->lineno, (int) script->getUseCount(), (void *) pc); |
| void *jitcode = NULL; |
| if (!EnsureCanEnterIon(cx, stub, frame, script, pc, &jitcode)) |
| return false; |
| |
| // Jitcode should only be set here if not at loop entry. |
| JS_ASSERT_IF(!isLoopEntry, !jitcode); |
| if (!jitcode) |
| return true; |
| |
| // Prepare the temporary heap copy of the fake StackFrame and actual args list. |
| IonSpew(IonSpew_BaselineOSR, "Got jitcode. Preparing for OSR into ion."); |
| IonOsrTempData *info = PrepareOsrTempData(cx, stub, frame, script, pc, jitcode); |
| if (!info) |
| return false; |
| *infoPtr = info; |
| |
| return true; |
| } |
| |
| typedef bool (*DoUseCountFallbackFn)(JSContext *, ICUseCount_Fallback *, BaselineFrame *frame, |
| IonOsrTempData **infoPtr); |
| static const VMFunction DoUseCountFallbackInfo = |
| FunctionInfo<DoUseCountFallbackFn>(DoUseCountFallback); |
| |
| bool |
| ICUseCount_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| // enterStubFrame is going to clobber the BaselineFrameReg, save it in R0.scratchReg() |
| // first. |
| masm.movePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| enterStubFrame(masm, R1.scratchReg()); |
| |
| Label noCompiledCode; |
| // Call DoUseCountFallback to compile/check-for Ion-compiled function |
| { |
| // Push IonOsrTempData pointer storage |
| masm.subPtr(Imm32(sizeof(void *)), BaselineStackReg); |
| masm.push(BaselineStackReg); |
| |
| // Push IonJSFrameLayout pointer. |
| masm.loadBaselineFramePtr(R0.scratchReg(), R0.scratchReg()); |
| masm.push(R0.scratchReg()); |
| |
| // Push stub pointer. |
| masm.push(BaselineStubReg); |
| |
| if (!callVM(DoUseCountFallbackInfo, masm)) |
| return false; |
| |
| // Pop IonOsrTempData pointer. |
| masm.pop(R0.scratchReg()); |
| |
| leaveStubFrame(masm); |
| |
| // If no IonCode was found, then skip just exit the IC. |
| masm.branchPtr(Assembler::Equal, R0.scratchReg(), ImmWord((void*) NULL), &noCompiledCode); |
| } |
| |
| // Get a scratch register. |
| GeneralRegisterSet regs(availableGeneralRegs(0)); |
| Register osrDataReg = R0.scratchReg(); |
| regs.take(osrDataReg); |
| regs.takeUnchecked(OsrFrameReg); |
| |
| Register scratchReg = regs.takeAny(); |
| |
| // At this point, stack looks like: |
| // +-> [...Calling-Frame...] |
| // | [...Actual-Args/ThisV/ArgCount/Callee...] |
| // | [Descriptor] |
| // | [Return-Addr] |
| // +---[Saved-FramePtr] <-- BaselineFrameReg points here. |
| // [...Baseline-Frame...] |
| |
| // Restore the stack pointer to point to the saved frame pointer. |
| masm.movePtr(BaselineFrameReg, BaselineStackReg); |
| |
| // Discard saved frame pointer, so that the return address is on top of |
| // the stack. |
| masm.pop(scratchReg); |
| |
| // Jump into Ion. |
| masm.loadPtr(Address(osrDataReg, offsetof(IonOsrTempData, jitcode)), scratchReg); |
| masm.loadPtr(Address(osrDataReg, offsetof(IonOsrTempData, stackFrame)), OsrFrameReg); |
| masm.jump(scratchReg); |
| |
| // No jitcode available, do nothing. |
| masm.bind(&noCompiledCode); |
| EmitReturnFromIC(masm); |
| return true; |
| } |
| |
| // |
| // ICProfile_Fallback |
| // |
| |
| static bool |
| DoProfilerFallback(JSContext *cx, BaselineFrame *frame, ICProfiler_Fallback *stub) |
| { |
| RootedScript script(cx, frame->script()); |
| RootedFunction func(cx, frame->maybeFun()); |
| mozilla::DebugOnly<ICEntry *> icEntry = stub->icEntry(); |
| |
| FallbackICSpew(cx, stub, "Profiler"); |
| |
| SPSProfiler *profiler = &cx->runtime()->spsProfiler; |
| |
| // Manually enter SPS this time. |
| JS_ASSERT(profiler->enabled()); |
| if (!cx->runtime()->spsProfiler.enter(cx, script, func)) |
| return false; |
| frame->setPushedSPSFrame(); |
| |
| // Unlink any existing PushFunction stub (which may hold stale 'const char *' to |
| // the profile string. |
| JS_ASSERT_IF(icEntry->firstStub() != stub, |
| icEntry->firstStub()->isProfiler_PushFunction() && |
| icEntry->firstStub()->next() == stub); |
| stub->unlinkStubsWithKind(cx, ICStub::Profiler_PushFunction); |
| JS_ASSERT(icEntry->firstStub() == stub); |
| |
| // Generate the string to use to identify this stack frame. |
| const char *string = profiler->profileString(cx, script, func); |
| if (string == NULL) |
| return false; |
| |
| IonSpew(IonSpew_BaselineIC, " Generating Profiler_PushFunction stub for %s:%d", |
| script->filename(), script->lineno); |
| |
| // Create a new optimized stub. |
| ICProfiler_PushFunction::Compiler compiler(cx, string, script); |
| ICStub *optStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!optStub) |
| return false; |
| stub->addNewStub(optStub); |
| |
| return true; |
| } |
| |
| typedef bool (*DoProfilerFallbackFn)(JSContext *, BaselineFrame *frame, ICProfiler_Fallback *); |
| static const VMFunction DoProfilerFallbackInfo = |
| FunctionInfo<DoProfilerFallbackFn>(DoProfilerFallback); |
| |
| bool |
| ICProfiler_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| EmitRestoreTailCallReg(masm); |
| |
| masm.push(BaselineStubReg); // Push stub. |
| masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); // Push frame. |
| |
| return tailCallVM(DoProfilerFallbackInfo, masm); |
| } |
| |
| bool |
| ICProfiler_PushFunction::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| |
| Register scratch = R0.scratchReg(); |
| Register scratch2 = R1.scratchReg(); |
| |
| // Profiling should be enabled if we ever reach here. |
| #ifdef DEBUG |
| Label spsEnabled; |
| uint32_t *enabledAddr = cx->runtime()->spsProfiler.addressOfEnabled(); |
| masm.branch32(Assembler::NotEqual, AbsoluteAddress(enabledAddr), Imm32(0), &spsEnabled); |
| masm.breakpoint(); |
| masm.bind(&spsEnabled); |
| #endif |
| |
| // Push SPS entry. |
| masm.spsPushFrame(&cx->runtime()->spsProfiler, |
| Address(BaselineStubReg, ICProfiler_PushFunction::offsetOfStr()), |
| Address(BaselineStubReg, ICProfiler_PushFunction::offsetOfScript()), |
| scratch, |
| scratch2); |
| |
| // Mark frame as having profiler entry pushed. |
| Address flagsOffset(BaselineFrameReg, BaselineFrame::reverseOffsetOfFlags()); |
| masm.or32(Imm32(BaselineFrame::HAS_PUSHED_SPS_FRAME), flagsOffset); |
| |
| EmitReturnFromIC(masm); |
| |
| return true; |
| } |
| |
| // |
| // TypeMonitor_Fallback |
| // |
| |
| bool |
| ICTypeMonitor_Fallback::addMonitorStubForValue(JSContext *cx, HandleScript script, HandleValue val) |
| { |
| bool wasDetachedMonitorChain = lastMonitorStubPtrAddr_ == NULL; |
| JS_ASSERT_IF(wasDetachedMonitorChain, numOptimizedMonitorStubs_ == 0); |
| |
| if (numOptimizedMonitorStubs_ >= MAX_OPTIMIZED_STUBS) { |
| // TODO: if the TypeSet becomes unknown or has the AnyObject type, |
| // replace stubs with a single stub to handle these. |
| return true; |
| } |
| |
| if (val.isPrimitive()) { |
| JS_ASSERT(!val.isMagic()); |
| JSValueType type = val.isDouble() ? JSVAL_TYPE_DOUBLE : val.extractNonDoubleType(); |
| |
| // Check for existing TypeMonitor stub. |
| ICTypeMonitor_PrimitiveSet *existingStub = NULL; |
| for (ICStubConstIterator iter = firstMonitorStub(); !iter.atEnd(); iter++) { |
| if (iter->isTypeMonitor_PrimitiveSet()) { |
| existingStub = iter->toTypeMonitor_PrimitiveSet(); |
| if (existingStub->containsType(type)) |
| return true; |
| } |
| } |
| |
| ICTypeMonitor_PrimitiveSet::Compiler compiler(cx, existingStub, type); |
| ICStub *stub = existingStub ? compiler.updateStub() |
| : compiler.getStub(compiler.getStubSpace(script)); |
| if (!stub) |
| return false; |
| |
| IonSpew(IonSpew_BaselineIC, " %s TypeMonitor stub %p for primitive type %d", |
| existingStub ? "Modified existing" : "Created new", stub, type); |
| |
| if (!existingStub) { |
| JS_ASSERT(!hasStub(TypeMonitor_PrimitiveSet)); |
| addOptimizedMonitorStub(stub); |
| } |
| |
| } else if (val.toObject().hasSingletonType()) { |
| RootedObject obj(cx, &val.toObject()); |
| |
| // Check for existing TypeMonitor stub. |
| for (ICStubConstIterator iter = firstMonitorStub(); !iter.atEnd(); iter++) { |
| if (iter->isTypeMonitor_SingleObject() && |
| iter->toTypeMonitor_SingleObject()->object() == obj) |
| { |
| return true; |
| } |
| } |
| |
| ICTypeMonitor_SingleObject::Compiler compiler(cx, obj); |
| ICStub *stub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!stub) |
| return false; |
| |
| IonSpew(IonSpew_BaselineIC, " Added TypeMonitor stub %p for singleton %p", |
| stub, obj.get()); |
| |
| addOptimizedMonitorStub(stub); |
| |
| } else { |
| RootedTypeObject type(cx, val.toObject().type()); |
| |
| // Check for existing TypeMonitor stub. |
| for (ICStubConstIterator iter = firstMonitorStub(); !iter.atEnd(); iter++) { |
| if (iter->isTypeMonitor_TypeObject() && |
| iter->toTypeMonitor_TypeObject()->type() == type) |
| { |
| return true; |
| } |
| } |
| |
| ICTypeMonitor_TypeObject::Compiler compiler(cx, type); |
| ICStub *stub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!stub) |
| return false; |
| |
| IonSpew(IonSpew_BaselineIC, " Added TypeMonitor stub %p for TypeObject %p", |
| stub, type.get()); |
| |
| addOptimizedMonitorStub(stub); |
| } |
| |
| bool firstMonitorStubAdded = wasDetachedMonitorChain && (numOptimizedMonitorStubs_ > 0); |
| |
| if (firstMonitorStubAdded) { |
| // Was an empty monitor chain before, but a new stub was added. This is the |
| // only time that any main stubs' firstMonitorStub fields need to be updated to |
| // refer to the newly added monitor stub. |
| ICStub *firstStub = mainFallbackStub_->icEntry()->firstStub(); |
| for (ICStubConstIterator iter = firstStub; !iter.atEnd(); iter++) { |
| // Non-monitored stubs are used if the result has always the same type, |
| // e.g. a StringLength stub will always return int32. |
| if (!iter->isMonitored()) |
| continue; |
| |
| // Since we just added the first optimized monitoring stub, any |
| // existing main stub's |firstMonitorStub| MUST be pointing to the fallback |
| // monitor stub (i.e. this stub). |
| JS_ASSERT(iter->toMonitoredStub()->firstMonitorStub() == this); |
| iter->toMonitoredStub()->updateFirstMonitorStub(firstMonitorStub_); |
| } |
| } |
| |
| return true; |
| } |
| |
| static bool |
| DoTypeMonitorFallback(JSContext *cx, BaselineFrame *frame, ICTypeMonitor_Fallback *stub, |
| HandleValue value, MutableHandleValue res) |
| { |
| RootedScript script(cx, frame->script()); |
| jsbytecode *pc = stub->icEntry()->pc(script); |
| TypeFallbackICSpew(cx, stub, "TypeMonitor"); |
| |
| uint32_t argument; |
| if (stub->monitorsThis()) { |
| JS_ASSERT(pc == script->code); |
| types::TypeScript::SetThis(cx, script, value); |
| } else if (stub->monitorsArgument(&argument)) { |
| JS_ASSERT(pc == script->code); |
| types::TypeScript::SetArgument(cx, script, argument, value); |
| } else { |
| types::TypeScript::Monitor(cx, script, pc, value); |
| } |
| |
| if (!stub->addMonitorStubForValue(cx, script, value)) |
| return false; |
| |
| // Copy input value to res. |
| res.set(value); |
| return true; |
| } |
| |
| typedef bool (*DoTypeMonitorFallbackFn)(JSContext *, BaselineFrame *, ICTypeMonitor_Fallback *, |
| HandleValue, MutableHandleValue); |
| static const VMFunction DoTypeMonitorFallbackInfo = |
| FunctionInfo<DoTypeMonitorFallbackFn>(DoTypeMonitorFallback); |
| |
| bool |
| ICTypeMonitor_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| JS_ASSERT(R0 == JSReturnOperand); |
| |
| // Restore the tail call register. |
| EmitRestoreTailCallReg(masm); |
| |
| masm.pushValue(R0); |
| masm.push(BaselineStubReg); |
| masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| return tailCallVM(DoTypeMonitorFallbackInfo, masm); |
| } |
| |
| bool |
| ICTypeMonitor_PrimitiveSet::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label success; |
| if ((flags_ & TypeToFlag(JSVAL_TYPE_INT32)) && !(flags_ & TypeToFlag(JSVAL_TYPE_DOUBLE))) |
| masm.branchTestInt32(Assembler::Equal, R0, &success); |
| |
| if (flags_ & TypeToFlag(JSVAL_TYPE_DOUBLE)) |
| masm.branchTestNumber(Assembler::Equal, R0, &success); |
| |
| if (flags_ & TypeToFlag(JSVAL_TYPE_UNDEFINED)) |
| masm.branchTestUndefined(Assembler::Equal, R0, &success); |
| |
| if (flags_ & TypeToFlag(JSVAL_TYPE_BOOLEAN)) |
| masm.branchTestBoolean(Assembler::Equal, R0, &success); |
| |
| if (flags_ & TypeToFlag(JSVAL_TYPE_STRING)) |
| masm.branchTestString(Assembler::Equal, R0, &success); |
| |
| // Currently, we will never generate primitive stub checks for object. However, |
| // when we do get to the point where we want to collapse our monitor chains of |
| // objects and singletons down (when they get too long) to a generic "any object" |
| // in coordination with the typeset doing the same thing, this will need to |
| // be re-enabled. |
| /* |
| if (flags_ & TypeToFlag(JSVAL_TYPE_OBJECT)) |
| masm.branchTestObject(Assembler::Equal, R0, &success); |
| */ |
| JS_ASSERT(!(flags_ & TypeToFlag(JSVAL_TYPE_OBJECT))); |
| |
| if (flags_ & TypeToFlag(JSVAL_TYPE_NULL)) |
| masm.branchTestNull(Assembler::Equal, R0, &success); |
| |
| EmitStubGuardFailure(masm); |
| |
| masm.bind(&success); |
| EmitReturnFromIC(masm); |
| return true; |
| } |
| |
| bool |
| ICTypeMonitor_SingleObject::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| // Guard on the object's identity. |
| Register obj = masm.extractObject(R0, ExtractTemp0); |
| Address expectedObject(BaselineStubReg, ICTypeMonitor_SingleObject::offsetOfObject()); |
| masm.branchPtr(Assembler::NotEqual, expectedObject, obj, &failure); |
| |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICTypeMonitor_TypeObject::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| // Guard on the object's TypeObject. |
| Register obj = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(obj, JSObject::offsetOfType()), R1.scratchReg()); |
| |
| Address expectedType(BaselineStubReg, ICTypeMonitor_TypeObject::offsetOfType()); |
| masm.branchPtr(Assembler::NotEqual, expectedType, R1.scratchReg(), &failure); |
| |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICUpdatedStub::addUpdateStubForValue(JSContext *cx, HandleScript script, HandleObject obj, |
| HandleId id, HandleValue val) |
| { |
| if (numOptimizedStubs_ >= MAX_OPTIMIZED_STUBS) { |
| // TODO: if the TypeSet becomes unknown or has the AnyObject type, |
| // replace stubs with a single stub to handle these. |
| return true; |
| } |
| |
| if (!obj->getType(cx)) |
| return false; |
| |
| types::EnsureTrackPropertyTypes(cx, obj, id); |
| |
| if (val.isPrimitive()) { |
| JSValueType type = val.isDouble() ? JSVAL_TYPE_DOUBLE : val.extractNonDoubleType(); |
| |
| // Check for existing TypeUpdate stub. |
| ICTypeUpdate_PrimitiveSet *existingStub = NULL; |
| for (ICStubConstIterator iter = firstUpdateStub_; !iter.atEnd(); iter++) { |
| if (iter->isTypeUpdate_PrimitiveSet()) { |
| existingStub = iter->toTypeUpdate_PrimitiveSet(); |
| if (existingStub->containsType(type)) |
| return true; |
| } |
| } |
| |
| ICTypeUpdate_PrimitiveSet::Compiler compiler(cx, existingStub, type); |
| ICStub *stub = existingStub ? compiler.updateStub() |
| : compiler.getStub(compiler.getStubSpace(script)); |
| if (!stub) |
| return false; |
| if (!existingStub) { |
| JS_ASSERT(!hasTypeUpdateStub(TypeUpdate_PrimitiveSet)); |
| addOptimizedUpdateStub(stub); |
| } |
| |
| IonSpew(IonSpew_BaselineIC, " %s TypeUpdate stub %p for primitive type %d", |
| existingStub ? "Modified existing" : "Created new", stub, type); |
| |
| } else if (val.toObject().hasSingletonType()) { |
| RootedObject obj(cx, &val.toObject()); |
| |
| // Check for existing TypeUpdate stub. |
| for (ICStubConstIterator iter = firstUpdateStub_; !iter.atEnd(); iter++) { |
| if (iter->isTypeUpdate_SingleObject() && |
| iter->toTypeUpdate_SingleObject()->object() == obj) |
| { |
| return true; |
| } |
| } |
| |
| ICTypeUpdate_SingleObject::Compiler compiler(cx, obj); |
| ICStub *stub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!stub) |
| return false; |
| |
| IonSpew(IonSpew_BaselineIC, " Added TypeUpdate stub %p for singleton %p", stub, obj.get()); |
| |
| addOptimizedUpdateStub(stub); |
| |
| } else { |
| RootedTypeObject type(cx, val.toObject().type()); |
| |
| // Check for existing TypeUpdate stub. |
| for (ICStubConstIterator iter = firstUpdateStub_; !iter.atEnd(); iter++) { |
| if (iter->isTypeUpdate_TypeObject() && |
| iter->toTypeUpdate_TypeObject()->type() == type) |
| { |
| return true; |
| } |
| } |
| |
| ICTypeUpdate_TypeObject::Compiler compiler(cx, type); |
| ICStub *stub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!stub) |
| return false; |
| |
| IonSpew(IonSpew_BaselineIC, " Added TypeUpdate stub %p for TypeObject %p", |
| stub, type.get()); |
| |
| addOptimizedUpdateStub(stub); |
| } |
| |
| return true; |
| } |
| |
| // |
| // TypeUpdate_Fallback |
| // |
| static bool |
| DoTypeUpdateFallback(JSContext *cx, BaselineFrame *frame, ICUpdatedStub *stub, HandleValue objval, |
| HandleValue value) |
| { |
| FallbackICSpew(cx, stub->getChainFallback(), "TypeUpdate(%s)", |
| ICStub::KindString(stub->kind())); |
| |
| RootedScript script(cx, frame->script()); |
| RootedObject obj(cx, &objval.toObject()); |
| RootedId id(cx); |
| |
| switch(stub->kind()) { |
| case ICStub::SetElem_Dense: |
| case ICStub::SetElem_DenseAdd: { |
| JS_ASSERT(obj->isNative()); |
| id = JSID_VOID; |
| types::AddTypePropertyId(cx, obj, id, value); |
| break; |
| } |
| case ICStub::SetProp_Native: |
| case ICStub::SetProp_NativeAdd: { |
| JS_ASSERT(obj->isNative()); |
| jsbytecode *pc = stub->getChainFallback()->icEntry()->pc(script); |
| if (*pc == JSOP_SETALIASEDVAR) |
| id = NameToId(ScopeCoordinateName(cx, script, pc)); |
| else |
| id = NameToId(script->getName(pc)); |
| types::AddTypePropertyId(cx, obj, id, value); |
| break; |
| } |
| default: |
| JS_NOT_REACHED("Invalid stub"); |
| return false; |
| } |
| |
| return stub->addUpdateStubForValue(cx, script, obj, id, value); |
| } |
| |
| typedef bool (*DoTypeUpdateFallbackFn)(JSContext *, BaselineFrame *, ICUpdatedStub *, HandleValue, |
| HandleValue); |
| const VMFunction DoTypeUpdateFallbackInfo = |
| FunctionInfo<DoTypeUpdateFallbackFn>(DoTypeUpdateFallback); |
| |
| bool |
| ICTypeUpdate_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| // Just store false into R1.scratchReg() and return. |
| masm.move32(Imm32(0), R1.scratchReg()); |
| EmitReturnFromIC(masm); |
| return true; |
| } |
| |
| bool |
| ICTypeUpdate_PrimitiveSet::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label success; |
| if ((flags_ & TypeToFlag(JSVAL_TYPE_INT32)) && !(flags_ & TypeToFlag(JSVAL_TYPE_DOUBLE))) |
| masm.branchTestInt32(Assembler::Equal, R0, &success); |
| |
| if (flags_ & TypeToFlag(JSVAL_TYPE_DOUBLE)) |
| masm.branchTestNumber(Assembler::Equal, R0, &success); |
| |
| if (flags_ & TypeToFlag(JSVAL_TYPE_UNDEFINED)) |
| masm.branchTestUndefined(Assembler::Equal, R0, &success); |
| |
| if (flags_ & TypeToFlag(JSVAL_TYPE_BOOLEAN)) |
| masm.branchTestBoolean(Assembler::Equal, R0, &success); |
| |
| if (flags_ & TypeToFlag(JSVAL_TYPE_STRING)) |
| masm.branchTestString(Assembler::Equal, R0, &success); |
| |
| // Currently, we will never generate primitive stub checks for object. However, |
| // when we do get to the point where we want to collapse our monitor chains of |
| // objects and singletons down (when they get too long) to a generic "any object" |
| // in coordination with the typeset doing the same thing, this will need to |
| // be re-enabled. |
| /* |
| if (flags_ & TypeToFlag(JSVAL_TYPE_OBJECT)) |
| masm.branchTestObject(Assembler::Equal, R0, &success); |
| */ |
| JS_ASSERT(!(flags_ & TypeToFlag(JSVAL_TYPE_OBJECT))); |
| |
| if (flags_ & TypeToFlag(JSVAL_TYPE_NULL)) |
| masm.branchTestNull(Assembler::Equal, R0, &success); |
| |
| EmitStubGuardFailure(masm); |
| |
| // Type matches, load true into R1.scratchReg() and return. |
| masm.bind(&success); |
| masm.mov(Imm32(1), R1.scratchReg()); |
| EmitReturnFromIC(masm); |
| |
| return true; |
| } |
| |
| bool |
| ICTypeUpdate_SingleObject::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| // Guard on the object's identity. |
| Register obj = masm.extractObject(R0, R1.scratchReg()); |
| Address expectedObject(BaselineStubReg, ICTypeUpdate_SingleObject::offsetOfObject()); |
| masm.branchPtr(Assembler::NotEqual, expectedObject, obj, &failure); |
| |
| // Identity matches, load true into R1.scratchReg() and return. |
| masm.mov(Imm32(1), R1.scratchReg()); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICTypeUpdate_TypeObject::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| // Guard on the object's TypeObject. |
| Register obj = masm.extractObject(R0, R1.scratchReg()); |
| masm.loadPtr(Address(obj, JSObject::offsetOfType()), R1.scratchReg()); |
| |
| Address expectedType(BaselineStubReg, ICTypeUpdate_TypeObject::offsetOfType()); |
| masm.branchPtr(Assembler::NotEqual, expectedType, R1.scratchReg(), &failure); |
| |
| // Type matches, load true into R1.scratchReg() and return. |
| masm.mov(Imm32(1), R1.scratchReg()); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // This_Fallback |
| // |
| |
| static bool |
| DoThisFallback(JSContext *cx, ICThis_Fallback *stub, HandleValue thisv, MutableHandleValue ret) |
| { |
| FallbackICSpew(cx, stub, "This"); |
| |
| ret.set(thisv); |
| bool modified; |
| if (!BoxNonStrictThis(cx, ret, &modified)) |
| return false; |
| return true; |
| } |
| |
| typedef bool (*DoThisFallbackFn)(JSContext *, ICThis_Fallback *, HandleValue, MutableHandleValue); |
| static const VMFunction DoThisFallbackInfo = FunctionInfo<DoThisFallbackFn>(DoThisFallback); |
| |
| bool |
| ICThis_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| JS_ASSERT(R0 == JSReturnOperand); |
| |
| // Restore the tail call register. |
| EmitRestoreTailCallReg(masm); |
| |
| masm.pushValue(R0); |
| masm.push(BaselineStubReg); |
| |
| return tailCallVM(DoThisFallbackInfo, masm); |
| } |
| |
| // |
| // NewArray_Fallback |
| // |
| |
| static bool |
| DoNewArray(JSContext *cx, ICNewArray_Fallback *stub, uint32_t length, |
| HandleTypeObject type, MutableHandleValue res) |
| { |
| FallbackICSpew(cx, stub, "NewArray"); |
| |
| JSObject *obj = NewInitArray(cx, length, type); |
| if (!obj) |
| return false; |
| |
| res.setObject(*obj); |
| return true; |
| } |
| |
| typedef bool(*DoNewArrayFn)(JSContext *, ICNewArray_Fallback *, uint32_t, HandleTypeObject, |
| MutableHandleValue); |
| static const VMFunction DoNewArrayInfo = FunctionInfo<DoNewArrayFn>(DoNewArray); |
| |
| bool |
| ICNewArray_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| EmitRestoreTailCallReg(masm); |
| |
| masm.push(R1.scratchReg()); // type |
| masm.push(R0.scratchReg()); // length |
| masm.push(BaselineStubReg); // stub. |
| |
| return tailCallVM(DoNewArrayInfo, masm); |
| } |
| |
| // |
| // NewObject_Fallback |
| // |
| |
| static bool |
| DoNewObject(JSContext *cx, ICNewObject_Fallback *stub, HandleObject templateObject, |
| MutableHandleValue res) |
| { |
| FallbackICSpew(cx, stub, "NewObject"); |
| |
| JSObject *obj = NewInitObject(cx, templateObject); |
| if (!obj) |
| return false; |
| |
| res.setObject(*obj); |
| return true; |
| } |
| |
| typedef bool(*DoNewObjectFn)(JSContext *, ICNewObject_Fallback *, HandleObject, |
| MutableHandleValue); |
| static const VMFunction DoNewObjectInfo = FunctionInfo<DoNewObjectFn>(DoNewObject); |
| |
| bool |
| ICNewObject_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| EmitRestoreTailCallReg(masm); |
| |
| masm.push(R0.scratchReg()); // template |
| masm.push(BaselineStubReg); // stub. |
| |
| return tailCallVM(DoNewObjectInfo, masm); |
| } |
| |
| // |
| // Compare_Fallback |
| // |
| |
| static bool |
| DoCompareFallback(JSContext *cx, BaselineFrame *frame, ICCompare_Fallback *stub, HandleValue lhs, |
| HandleValue rhs, MutableHandleValue ret) |
| { |
| jsbytecode *pc = stub->icEntry()->pc(frame->script()); |
| JSOp op = JSOp(*pc); |
| |
| FallbackICSpew(cx, stub, "Compare(%s)", js_CodeName[op]); |
| |
| // Case operations in a CONDSWITCH are performing strict equality. |
| if (op == JSOP_CASE) |
| op = JSOP_STRICTEQ; |
| |
| // Don't pass lhs/rhs directly, we need the original values when |
| // generating stubs. |
| RootedValue lhsCopy(cx, lhs); |
| RootedValue rhsCopy(cx, rhs); |
| |
| // Perform the compare operation. |
| JSBool out; |
| switch(op) { |
| case JSOP_LT: |
| if (!LessThan(cx, &lhsCopy, &rhsCopy, &out)) |
| return false; |
| break; |
| case JSOP_LE: |
| if (!LessThanOrEqual(cx, &lhsCopy, &rhsCopy, &out)) |
| return false; |
| break; |
| case JSOP_GT: |
| if (!GreaterThan(cx, &lhsCopy, &rhsCopy, &out)) |
| return false; |
| break; |
| case JSOP_GE: |
| if (!GreaterThanOrEqual(cx, &lhsCopy, &rhsCopy, &out)) |
| return false; |
| break; |
| case JSOP_EQ: |
| if (!LooselyEqual<true>(cx, &lhsCopy, &rhsCopy, &out)) |
| return false; |
| break; |
| case JSOP_NE: |
| if (!LooselyEqual<false>(cx, &lhsCopy, &rhsCopy, &out)) |
| return false; |
| break; |
| case JSOP_STRICTEQ: |
| if (!StrictlyEqual<true>(cx, &lhsCopy, &rhsCopy, &out)) |
| return false; |
| break; |
| case JSOP_STRICTNE: |
| if (!StrictlyEqual<false>(cx, &lhsCopy, &rhsCopy, &out)) |
| return false; |
| break; |
| default: |
| JS_ASSERT(!"Unhandled baseline compare op"); |
| return false; |
| } |
| |
| ret.setBoolean(out); |
| |
| // Check to see if a new stub should be generated. |
| if (stub->numOptimizedStubs() >= ICCompare_Fallback::MAX_OPTIMIZED_STUBS) { |
| // TODO: Discard all stubs in this IC and replace with inert megamorphic stub. |
| // But for now we just bail. |
| return true; |
| } |
| |
| JSScript *script = frame->script(); |
| |
| // Try to generate new stubs. |
| if (lhs.isInt32() && rhs.isInt32()) { |
| IonSpew(IonSpew_BaselineIC, " Generating %s(Int32, Int32) stub", js_CodeName[op]); |
| ICCompare_Int32::Compiler compiler(cx, op); |
| ICStub *int32Stub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!int32Stub) |
| return false; |
| |
| stub->addNewStub(int32Stub); |
| return true; |
| } |
| |
| if (!cx->runtime()->jitSupportsFloatingPoint && (lhs.isNumber() || rhs.isNumber())) |
| return true; |
| |
| if (lhs.isNumber() && rhs.isNumber()) { |
| IonSpew(IonSpew_BaselineIC, " Generating %s(Number, Number) stub", js_CodeName[op]); |
| |
| // Unlink int32 stubs, it's faster to always use the double stub. |
| stub->unlinkStubsWithKind(cx, ICStub::Compare_Int32); |
| |
| ICCompare_Double::Compiler compiler(cx, op); |
| ICStub *doubleStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!doubleStub) |
| return false; |
| |
| stub->addNewStub(doubleStub); |
| return true; |
| } |
| |
| if ((lhs.isNumber() && rhs.isUndefined()) || |
| (lhs.isUndefined() && rhs.isNumber())) |
| { |
| IonSpew(IonSpew_BaselineIC, " Generating %s(%s, %s) stub", js_CodeName[op], |
| rhs.isUndefined() ? "Number" : "Undefined", |
| rhs.isUndefined() ? "Undefined" : "Number"); |
| ICCompare_NumberWithUndefined::Compiler compiler(cx, op, lhs.isUndefined()); |
| ICStub *doubleStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!stub) |
| return false; |
| |
| stub->addNewStub(doubleStub); |
| return true; |
| } |
| |
| if (lhs.isBoolean() && rhs.isBoolean()) { |
| IonSpew(IonSpew_BaselineIC, " Generating %s(Boolean, Boolean) stub", js_CodeName[op]); |
| ICCompare_Boolean::Compiler compiler(cx, op); |
| ICStub *booleanStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!booleanStub) |
| return false; |
| |
| stub->addNewStub(booleanStub); |
| return true; |
| } |
| |
| if ((lhs.isBoolean() && rhs.isInt32()) || (lhs.isInt32() && rhs.isBoolean())) { |
| IonSpew(IonSpew_BaselineIC, " Generating %s(%s, %s) stub", js_CodeName[op], |
| rhs.isInt32() ? "Boolean" : "Int32", |
| rhs.isInt32() ? "Int32" : "Boolean"); |
| ICCompare_Int32WithBoolean::Compiler compiler(cx, op, lhs.isInt32()); |
| ICStub *optStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!optStub) |
| return false; |
| |
| stub->addNewStub(optStub); |
| return true; |
| } |
| |
| if (IsEqualityOp(op)) { |
| if (lhs.isString() && rhs.isString() && !stub->hasStub(ICStub::Compare_String)) { |
| IonSpew(IonSpew_BaselineIC, " Generating %s(String, String) stub", js_CodeName[op]); |
| ICCompare_String::Compiler compiler(cx, op); |
| ICStub *stringStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!stringStub) |
| return false; |
| |
| stub->addNewStub(stringStub); |
| return true; |
| } |
| |
| if (lhs.isObject() && rhs.isObject()) { |
| JS_ASSERT(!stub->hasStub(ICStub::Compare_Object)); |
| IonSpew(IonSpew_BaselineIC, " Generating %s(Object, Object) stub", js_CodeName[op]); |
| ICCompare_Object::Compiler compiler(cx, op); |
| ICStub *objectStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!objectStub) |
| return false; |
| |
| stub->addNewStub(objectStub); |
| return true; |
| } |
| |
| if ((lhs.isObject() || lhs.isNull() || lhs.isUndefined()) && |
| (rhs.isObject() || rhs.isNull() || rhs.isUndefined()) && |
| !stub->hasStub(ICStub::Compare_ObjectWithUndefined)) |
| { |
| IonSpew(IonSpew_BaselineIC, " Generating %s(Obj/Null/Undef, Obj/Null/Undef) stub", |
| js_CodeName[op]); |
| bool lhsIsUndefined = lhs.isNull() || lhs.isUndefined(); |
| bool compareWithNull = lhs.isNull() || rhs.isNull(); |
| ICCompare_ObjectWithUndefined::Compiler compiler(cx, op, |
| lhsIsUndefined, compareWithNull); |
| ICStub *objectStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!objectStub) |
| return false; |
| |
| stub->addNewStub(objectStub); |
| return true; |
| } |
| } |
| |
| return true; |
| } |
| |
| typedef bool (*DoCompareFallbackFn)(JSContext *, BaselineFrame *, ICCompare_Fallback *, |
| HandleValue, HandleValue, MutableHandleValue); |
| static const VMFunction DoCompareFallbackInfo = |
| FunctionInfo<DoCompareFallbackFn>(DoCompareFallback, PopValues(2)); |
| |
| bool |
| ICCompare_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| JS_ASSERT(R0 == JSReturnOperand); |
| |
| // Restore the tail call register. |
| EmitRestoreTailCallReg(masm); |
| |
| // Ensure stack is fully synced for the expression decompiler. |
| masm.pushValue(R0); |
| masm.pushValue(R1); |
| |
| // Push arguments. |
| masm.pushValue(R1); |
| masm.pushValue(R0); |
| masm.push(BaselineStubReg); |
| masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| return tailCallVM(DoCompareFallbackInfo, masm); |
| } |
| |
| // |
| // Compare_String |
| // |
| |
| bool |
| ICCompare_String::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| masm.branchTestString(Assembler::NotEqual, R0, &failure); |
| masm.branchTestString(Assembler::NotEqual, R1, &failure); |
| |
| JS_ASSERT(IsEqualityOp(op)); |
| |
| Register left = masm.extractString(R0, ExtractTemp0); |
| Register right = masm.extractString(R1, ExtractTemp1); |
| |
| GeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratchReg = regs.takeAny(); |
| // x86 doesn't have the luxury of a second scratch. |
| Register scratchReg2; |
| if (regs.empty()) { |
| scratchReg2 = BaselineStubReg; |
| masm.push(BaselineStubReg); |
| } else { |
| scratchReg2 = regs.takeAny(); |
| } |
| JS_ASSERT(scratchReg2 != scratchReg); |
| |
| Label inlineCompareFailed; |
| masm.compareStrings(op, left, right, scratchReg2, scratchReg, &inlineCompareFailed); |
| masm.tagValue(JSVAL_TYPE_BOOLEAN, scratchReg2, R0); |
| if (scratchReg2 == BaselineStubReg) |
| masm.pop(BaselineStubReg); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&inlineCompareFailed); |
| if (scratchReg2 == BaselineStubReg) |
| masm.pop(BaselineStubReg); |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // Compare_Boolean |
| // |
| |
| bool |
| ICCompare_Boolean::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| masm.branchTestBoolean(Assembler::NotEqual, R0, &failure); |
| masm.branchTestBoolean(Assembler::NotEqual, R1, &failure); |
| |
| Register left = masm.extractInt32(R0, ExtractTemp0); |
| Register right = masm.extractInt32(R1, ExtractTemp1); |
| |
| // Compare payload regs of R0 and R1. |
| Assembler::Condition cond = JSOpToCondition(op, /* signed = */true); |
| #if defined(JS_CPU_MIPS) |
| masm.cmp32Set(cond, left, right, left); |
| #else |
| masm.cmp32(left, right); |
| masm.emitSet(cond, left); |
| #endif |
| |
| // Box the result and return |
| masm.tagValue(JSVAL_TYPE_BOOLEAN, left, R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // Compare_NumberWithUndefined |
| // |
| |
| bool |
| ICCompare_NumberWithUndefined::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| ValueOperand numberOperand, undefinedOperand; |
| if (lhsIsUndefined) { |
| numberOperand = R1; |
| undefinedOperand = R0; |
| } else { |
| numberOperand = R0; |
| undefinedOperand = R1; |
| } |
| |
| Label failure; |
| masm.branchTestNumber(Assembler::NotEqual, numberOperand, &failure); |
| masm.branchTestUndefined(Assembler::NotEqual, undefinedOperand, &failure); |
| |
| // Comparing a number with undefined will always be true for NE/STRICTNE, |
| // and always be false for other compare ops. |
| masm.moveValue(BooleanValue(op == JSOP_NE || op == JSOP_STRICTNE), R0); |
| |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // Compare_Object |
| // |
| |
| bool |
| ICCompare_Object::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| masm.branchTestObject(Assembler::NotEqual, R1, &failure); |
| |
| JS_ASSERT(IsEqualityOp(op)); |
| |
| Register left = masm.extractObject(R0, ExtractTemp0); |
| Register right = masm.extractObject(R1, ExtractTemp1); |
| |
| Label ifTrue; |
| masm.branchPtr(JSOpToCondition(op, /* signed = */true), left, right, &ifTrue); |
| |
| masm.moveValue(BooleanValue(false), R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&ifTrue); |
| masm.moveValue(BooleanValue(true), R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // Compare_ObjectWithUndefined |
| // |
| |
| bool |
| ICCompare_ObjectWithUndefined::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| JS_ASSERT(IsEqualityOp(op)); |
| |
| ValueOperand objectOperand, undefinedOperand; |
| if (lhsIsUndefined) { |
| objectOperand = R1; |
| undefinedOperand = R0; |
| } else { |
| objectOperand = R0; |
| undefinedOperand = R1; |
| } |
| |
| Label failure; |
| if (compareWithNull) |
| masm.branchTestNull(Assembler::NotEqual, undefinedOperand, &failure); |
| else |
| masm.branchTestUndefined(Assembler::NotEqual, undefinedOperand, &failure); |
| |
| Label notObject; |
| masm.branchTestObject(Assembler::NotEqual, objectOperand, ¬Object); |
| |
| if (op == JSOP_STRICTEQ || op == JSOP_STRICTNE) { |
| // obj !== undefined for all objects. |
| masm.moveValue(BooleanValue(op == JSOP_STRICTNE), R0); |
| EmitReturnFromIC(masm); |
| } else { |
| // obj != undefined only where !obj->getClass()->emulatesUndefined() |
| Label emulatesUndefined; |
| Register obj = masm.extractObject(objectOperand, ExtractTemp0); |
| masm.loadPtr(Address(obj, JSObject::offsetOfType()), obj); |
| masm.loadPtr(Address(obj, offsetof(types::TypeObject, clasp)), obj); |
| masm.branchTest32(Assembler::NonZero, |
| Address(obj, Class::offsetOfFlags()), |
| Imm32(JSCLASS_EMULATES_UNDEFINED), |
| &emulatesUndefined); |
| masm.moveValue(BooleanValue(op == JSOP_NE), R0); |
| EmitReturnFromIC(masm); |
| masm.bind(&emulatesUndefined); |
| masm.moveValue(BooleanValue(op == JSOP_EQ), R0); |
| EmitReturnFromIC(masm); |
| } |
| |
| masm.bind(¬Object); |
| |
| // Also support null == null or undefined == undefined comparisons. |
| if (compareWithNull) |
| masm.branchTestNull(Assembler::NotEqual, objectOperand, &failure); |
| else |
| masm.branchTestUndefined(Assembler::NotEqual, objectOperand, &failure); |
| |
| masm.moveValue(BooleanValue(op == JSOP_STRICTEQ || op == JSOP_EQ), R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // Compare_Int32WithBoolean |
| // |
| |
| bool |
| ICCompare_Int32WithBoolean::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| ValueOperand int32Val; |
| ValueOperand boolVal; |
| if (lhsIsInt32_) { |
| int32Val = R0; |
| boolVal = R1; |
| } else { |
| boolVal = R0; |
| int32Val = R1; |
| } |
| masm.branchTestBoolean(Assembler::NotEqual, boolVal, &failure); |
| masm.branchTestInt32(Assembler::NotEqual, int32Val, &failure); |
| |
| if (op_ == JSOP_STRICTEQ || op_ == JSOP_STRICTNE) { |
| // Ints and booleans are never strictly equal, always strictly not equal. |
| masm.moveValue(BooleanValue(op_ == JSOP_STRICTNE), R0); |
| EmitReturnFromIC(masm); |
| } else { |
| Register boolReg = masm.extractBoolean(boolVal, ExtractTemp0); |
| Register int32Reg = masm.extractInt32(int32Val, ExtractTemp1); |
| |
| // Compare payload regs of R0 and R1. |
| Assembler::Condition cond = JSOpToCondition(op_, /* signed = */true); |
| #if defined(JS_CPU_MIPS) |
| masm.cmp32Set( |
| cond, |
| lhsIsInt32_ ? int32Reg : boolReg, |
| lhsIsInt32_ ? boolReg : int32Reg, |
| R0.scratchReg() |
| ); |
| #else |
| masm.cmp32(lhsIsInt32_ ? int32Reg : boolReg, |
| lhsIsInt32_ ? boolReg : int32Reg); |
| masm.emitSet(cond, R0.scratchReg()); |
| #endif |
| |
| // Box the result and return |
| masm.tagValue(JSVAL_TYPE_BOOLEAN, R0.scratchReg(), R0); |
| EmitReturnFromIC(masm); |
| } |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // ToBool_Fallback |
| // |
| |
| static bool |
| DoToBoolFallback(JSContext *cx, BaselineFrame *frame, ICToBool_Fallback *stub, HandleValue arg, |
| MutableHandleValue ret) |
| { |
| FallbackICSpew(cx, stub, "ToBool"); |
| |
| bool cond = ToBoolean(arg); |
| ret.setBoolean(cond); |
| |
| // Check to see if a new stub should be generated. |
| if (stub->numOptimizedStubs() >= ICToBool_Fallback::MAX_OPTIMIZED_STUBS) { |
| // TODO: Discard all stubs in this IC and replace with inert megamorphic stub. |
| // But for now we just bail. |
| return true; |
| } |
| |
| JS_ASSERT(!arg.isBoolean()); |
| |
| JSScript *script = frame->script(); |
| |
| // Try to generate new stubs. |
| if (arg.isInt32()) { |
| IonSpew(IonSpew_BaselineIC, " Generating ToBool(Int32) stub."); |
| ICToBool_Int32::Compiler compiler(cx); |
| ICStub *int32Stub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!int32Stub) |
| return false; |
| |
| stub->addNewStub(int32Stub); |
| return true; |
| } |
| |
| if (arg.isDouble() && cx->runtime()->jitSupportsFloatingPoint) { |
| IonSpew(IonSpew_BaselineIC, " Generating ToBool(Double) stub."); |
| ICToBool_Double::Compiler compiler(cx); |
| ICStub *doubleStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!doubleStub) |
| return false; |
| |
| stub->addNewStub(doubleStub); |
| return true; |
| } |
| |
| if (arg.isString()) { |
| IonSpew(IonSpew_BaselineIC, " Generating ToBool(String) stub"); |
| ICToBool_String::Compiler compiler(cx); |
| ICStub *stringStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!stringStub) |
| return false; |
| |
| stub->addNewStub(stringStub); |
| return true; |
| } |
| |
| if (arg.isNull() || arg.isUndefined()) { |
| ICToBool_NullUndefined::Compiler compiler(cx); |
| ICStub *nilStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!nilStub) |
| return false; |
| |
| stub->addNewStub(nilStub); |
| return true; |
| } |
| |
| if (arg.isObject()) { |
| IonSpew(IonSpew_BaselineIC, " Generating ToBool(Object) stub."); |
| ICToBool_Object::Compiler compiler(cx); |
| ICStub *objStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!objStub) |
| return false; |
| |
| stub->addNewStub(objStub); |
| return true; |
| } |
| |
| return true; |
| } |
| |
| typedef bool (*pf)(JSContext *, BaselineFrame *, ICToBool_Fallback *, HandleValue, |
| MutableHandleValue); |
| static const VMFunction fun = FunctionInfo<pf>(DoToBoolFallback); |
| |
| bool |
| ICToBool_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| JS_ASSERT(R0 == JSReturnOperand); |
| |
| // Restore the tail call register. |
| EmitRestoreTailCallReg(masm); |
| |
| // Push arguments. |
| masm.pushValue(R0); |
| masm.push(BaselineStubReg); |
| masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| return tailCallVM(fun, masm); |
| } |
| |
| // |
| // ToBool_Int32 |
| // |
| |
| bool |
| ICToBool_Int32::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| masm.branchTestInt32(Assembler::NotEqual, R0, &failure); |
| |
| Label ifFalse; |
| #if defined(JS_CPU_MIPS) |
| masm.branchTestInt32Truthy(false, R0, &ifFalse); |
| #else |
| Assembler::Condition cond = masm.testInt32Truthy(false, R0); |
| masm.j(cond, &ifFalse); |
| #endif |
| |
| masm.moveValue(BooleanValue(true), R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&ifFalse); |
| masm.moveValue(BooleanValue(false), R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // ToBool_String |
| // |
| |
| bool |
| ICToBool_String::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| masm.branchTestString(Assembler::NotEqual, R0, &failure); |
| |
| Label ifFalse; |
| #if defined(JS_CPU_MIPS) |
| masm.branchTestStringTruthy(false, R0, &ifFalse); |
| #else |
| Assembler::Condition cond = masm.testStringTruthy(false, R0); |
| masm.j(cond, &ifFalse); |
| #endif |
| |
| masm.moveValue(BooleanValue(true), R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&ifFalse); |
| masm.moveValue(BooleanValue(false), R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // ToBool_NullUndefined |
| // |
| |
| bool |
| ICToBool_NullUndefined::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure, ifFalse; |
| masm.branchTestNull(Assembler::Equal, R0, &ifFalse); |
| masm.branchTestUndefined(Assembler::NotEqual, R0, &failure); |
| |
| masm.bind(&ifFalse); |
| masm.moveValue(BooleanValue(false), R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // ToBool_Double |
| // |
| |
| bool |
| ICToBool_Double::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure, ifTrue; |
| masm.branchTestDouble(Assembler::NotEqual, R0, &failure); |
| masm.unboxDouble(R0, FloatReg0); |
| #if defined(JS_CPU_MIPS) |
| masm.branchTestDoubleTruthy(true, FloatReg0, &ifTrue); |
| #else |
| Assembler::Condition cond = masm.testDoubleTruthy(true, FloatReg0); |
| masm.j(cond, &ifTrue); |
| #endif |
| |
| masm.moveValue(BooleanValue(false), R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&ifTrue); |
| masm.moveValue(BooleanValue(true), R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // ToBool_Object |
| // |
| |
| bool |
| ICToBool_Object::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure, ifFalse, slowPath; |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| Register objReg = masm.extractObject(R0, ExtractTemp0); |
| Register scratch = R1.scratchReg(); |
| #if defined(JS_CPU_MIPS) |
| masm.branchTestObjectTruthy(false, objReg, scratch, &slowPath, &ifFalse); |
| #else |
| Assembler::Condition cond = masm.branchTestObjectTruthy(false, objReg, scratch, &slowPath); |
| masm.j(cond, &ifFalse); |
| #endif |
| |
| // If object doesn't emulate undefined, it evaulates to true. |
| masm.moveValue(BooleanValue(true), R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&ifFalse); |
| masm.moveValue(BooleanValue(false), R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&slowPath); |
| masm.setupUnalignedABICall(1, scratch); |
| masm.passABIArg(objReg); |
| masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ObjectEmulatesUndefined)); |
| #if defined(JS_CPU_MIPS) |
| masm.convertBoolToInt32(ReturnReg, ReturnReg); |
| #endif |
| masm.xor32(Imm32(1), ReturnReg); |
| masm.tagValue(JSVAL_TYPE_BOOLEAN, ReturnReg, R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // ToNumber_Fallback |
| // |
| |
| static bool |
| DoToNumberFallback(JSContext *cx, ICToNumber_Fallback *stub, HandleValue arg, MutableHandleValue ret) |
| { |
| FallbackICSpew(cx, stub, "ToNumber"); |
| ret.set(arg); |
| return ToNumber(cx, ret.address()); |
| } |
| |
| typedef bool (*DoToNumberFallbackFn)(JSContext *, ICToNumber_Fallback *, HandleValue, MutableHandleValue); |
| static const VMFunction DoToNumberFallbackInfo = |
| FunctionInfo<DoToNumberFallbackFn>(DoToNumberFallback, PopValues(1)); |
| |
| bool |
| ICToNumber_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| JS_ASSERT(R0 == JSReturnOperand); |
| |
| // Restore the tail call register. |
| EmitRestoreTailCallReg(masm); |
| |
| // Ensure stack is fully synced for the expression decompiler. |
| masm.pushValue(R0); |
| |
| // Push arguments. |
| masm.pushValue(R0); |
| masm.push(BaselineStubReg); |
| |
| return tailCallVM(DoToNumberFallbackInfo, masm); |
| } |
| |
| // |
| // BinaryArith_Fallback |
| // |
| |
| // Disable PGO (see bug 851490). |
| #if defined(_MSC_VER) |
| # pragma optimize("g", off) |
| #endif |
| static bool |
| DoBinaryArithFallback(JSContext *cx, BaselineFrame *frame, ICBinaryArith_Fallback *stub, |
| HandleValue lhs, HandleValue rhs, MutableHandleValue ret) |
| { |
| RootedScript script(cx, frame->script()); |
| jsbytecode *pc = stub->icEntry()->pc(script); |
| JSOp op = JSOp(*pc); |
| FallbackICSpew(cx, stub, "BinaryArith(%s,%d,%d)", js_CodeName[op], |
| int(lhs.isDouble() ? JSVAL_TYPE_DOUBLE : lhs.extractNonDoubleType()), |
| int(rhs.isDouble() ? JSVAL_TYPE_DOUBLE : rhs.extractNonDoubleType())); |
| |
| // Don't pass lhs/rhs directly, we need the original values when |
| // generating stubs. |
| RootedValue lhsCopy(cx, lhs); |
| RootedValue rhsCopy(cx, rhs); |
| |
| // Perform the compare operation. |
| switch(op) { |
| case JSOP_ADD: |
| // Do an add. |
| if (!AddValues(cx, script, pc, &lhsCopy, &rhsCopy, ret.address())) |
| return false; |
| break; |
| case JSOP_SUB: |
| if (!SubValues(cx, script, pc, &lhsCopy, &rhsCopy, ret.address())) |
| return false; |
| break; |
| case JSOP_MUL: |
| if (!MulValues(cx, script, pc, &lhsCopy, &rhsCopy, ret.address())) |
| return false; |
| break; |
| case JSOP_DIV: |
| if (!DivValues(cx, script, pc, &lhsCopy, &rhsCopy, ret.address())) |
| return false; |
| break; |
| case JSOP_MOD: |
| if (!ModValues(cx, script, pc, &lhsCopy, &rhsCopy, ret.address())) |
| return false; |
| break; |
| case JSOP_BITOR: { |
| int32_t result; |
| if (!BitOr(cx, lhs, rhs, &result)) |
| return false; |
| ret.setInt32(result); |
| break; |
| } |
| case JSOP_BITXOR: { |
| int32_t result; |
| if (!BitXor(cx, lhs, rhs, &result)) |
| return false; |
| ret.setInt32(result); |
| break; |
| } |
| case JSOP_BITAND: { |
| int32_t result; |
| if (!BitAnd(cx, lhs, rhs, &result)) |
| return false; |
| ret.setInt32(result); |
| break; |
| } |
| case JSOP_LSH: { |
| int32_t result; |
| if (!BitLsh(cx, lhs, rhs, &result)) |
| return false; |
| ret.setInt32(result); |
| break; |
| } |
| case JSOP_RSH: { |
| int32_t result; |
| if (!BitRsh(cx, lhs, rhs, &result)) |
| return false; |
| ret.setInt32(result); |
| break; |
| } |
| case JSOP_URSH: { |
| if (!UrshOperation(cx, script, pc, lhs, rhs, ret.address())) |
| return false; |
| break; |
| } |
| default: |
| JS_NOT_REACHED("Unhandled baseline arith op"); |
| return false; |
| } |
| |
| if (ret.isDouble()) |
| stub->setSawDoubleResult(); |
| |
| // Check to see if a new stub should be generated. |
| if (stub->numOptimizedStubs() >= ICBinaryArith_Fallback::MAX_OPTIMIZED_STUBS) { |
| // TODO: Discard all stubs in this IC and replace with inert megamorphic stub. |
| // But for now we just bail. |
| return true; |
| } |
| |
| // Handle string concat. |
| if (op == JSOP_ADD) { |
| if (lhs.isString() && rhs.isString()) { |
| IonSpew(IonSpew_BaselineIC, " Generating %s(String, String) stub", js_CodeName[op]); |
| JS_ASSERT(ret.isString()); |
| ICBinaryArith_StringConcat::Compiler compiler(cx); |
| ICStub *strcatStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!strcatStub) |
| return false; |
| stub->addNewStub(strcatStub); |
| return true; |
| } |
| |
| if ((lhs.isString() && rhs.isObject()) || (lhs.isObject() && rhs.isString())) { |
| IonSpew(IonSpew_BaselineIC, " Generating %s(%s, %s) stub", js_CodeName[op], |
| lhs.isString() ? "String" : "Object", |
| lhs.isString() ? "Object" : "String"); |
| JS_ASSERT(ret.isString()); |
| ICBinaryArith_StringObjectConcat::Compiler compiler(cx, lhs.isString()); |
| ICStub *strcatStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!strcatStub) |
| return false; |
| stub->addNewStub(strcatStub); |
| return true; |
| } |
| } |
| |
| if (((lhs.isBoolean() && (rhs.isBoolean() || rhs.isInt32())) || |
| (rhs.isBoolean() && (lhs.isBoolean() || lhs.isInt32()))) && |
| (op == JSOP_ADD || op == JSOP_SUB || op == JSOP_BITOR || op == JSOP_BITAND || |
| op == JSOP_BITXOR)) |
| { |
| IonSpew(IonSpew_BaselineIC, " Generating %s(%s, %s) stub", js_CodeName[op], |
| lhs.isBoolean() ? "Boolean" : "Int32", rhs.isBoolean() ? "Boolean" : "Int32"); |
| ICBinaryArith_BooleanWithInt32::Compiler compiler(cx, op, lhs.isBoolean(), rhs.isBoolean()); |
| ICStub *arithStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!arithStub) |
| return false; |
| stub->addNewStub(arithStub); |
| return true; |
| } |
| |
| // Handle only int32 or double. |
| if (!lhs.isNumber() || !rhs.isNumber()) |
| return true; |
| |
| JS_ASSERT(ret.isNumber()); |
| |
| if (lhs.isDouble() || rhs.isDouble() || ret.isDouble()) { |
| if (!cx->runtime()->jitSupportsFloatingPoint) |
| return true; |
| |
| switch (op) { |
| case JSOP_ADD: |
| case JSOP_SUB: |
| case JSOP_MUL: |
| case JSOP_DIV: |
| case JSOP_MOD: { |
| // Unlink int32 stubs, it's faster to always use the double stub. |
| stub->unlinkStubsWithKind(cx, ICStub::BinaryArith_Int32); |
| IonSpew(IonSpew_BaselineIC, " Generating %s(Double, Double) stub", js_CodeName[op]); |
| |
| ICBinaryArith_Double::Compiler compiler(cx, op); |
| ICStub *doubleStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!doubleStub) |
| return false; |
| stub->addNewStub(doubleStub); |
| return true; |
| } |
| default: |
| break; |
| } |
| } |
| |
| if (lhs.isInt32() && rhs.isInt32()) { |
| bool allowDouble = ret.isDouble(); |
| if (allowDouble) |
| stub->unlinkStubsWithKind(cx, ICStub::BinaryArith_Int32); |
| IonSpew(IonSpew_BaselineIC, " Generating %s(Int32, Int32%s) stub", js_CodeName[op], |
| allowDouble ? " => Double" : ""); |
| ICBinaryArith_Int32::Compiler compilerInt32(cx, op, allowDouble); |
| ICStub *int32Stub = compilerInt32.getStub(compilerInt32.getStubSpace(script)); |
| if (!int32Stub) |
| return false; |
| stub->addNewStub(int32Stub); |
| return true; |
| } |
| |
| // Handle Double <BITOP> Int32 or Int32 <BITOP> Double case. |
| if (((lhs.isDouble() && rhs.isInt32()) || (lhs.isInt32() && rhs.isDouble())) && |
| ret.isInt32()) |
| { |
| switch(op) { |
| case JSOP_BITOR: |
| case JSOP_BITXOR: |
| case JSOP_BITAND: { |
| IonSpew(IonSpew_BaselineIC, " Generating %s(%s, %s) stub", js_CodeName[op], |
| lhs.isDouble() ? "Double" : "Int32", |
| lhs.isDouble() ? "Int32" : "Double"); |
| ICBinaryArith_DoubleWithInt32::Compiler compiler(cx, op, lhs.isDouble()); |
| ICStub *optStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!optStub) |
| return false; |
| stub->addNewStub(optStub); |
| return true; |
| } |
| default: |
| break; |
| } |
| } |
| |
| return true; |
| } |
| #if defined(_MSC_VER) |
| # pragma optimize("", on) |
| #endif |
| |
| typedef bool (*DoBinaryArithFallbackFn)(JSContext *, BaselineFrame *, ICBinaryArith_Fallback *, |
| HandleValue, HandleValue, MutableHandleValue); |
| static const VMFunction DoBinaryArithFallbackInfo = |
| FunctionInfo<DoBinaryArithFallbackFn>(DoBinaryArithFallback, PopValues(2)); |
| |
| bool |
| ICBinaryArith_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| JS_ASSERT(R0 == JSReturnOperand); |
| |
| // Restore the tail call register. |
| EmitRestoreTailCallReg(masm); |
| |
| // Ensure stack is fully synced for the expression decompiler. |
| masm.pushValue(R0); |
| masm.pushValue(R1); |
| |
| // Push arguments. |
| masm.pushValue(R1); |
| masm.pushValue(R0); |
| masm.push(BaselineStubReg); |
| masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| return tailCallVM(DoBinaryArithFallbackInfo, masm); |
| } |
| |
| static bool |
| DoConcatStrings(JSContext *cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) |
| { |
| JS_ASSERT(lhs.isString()); |
| JS_ASSERT(rhs.isString()); |
| JSString *lstr = lhs.toString(); |
| JSString *rstr = rhs.toString(); |
| JSString *result = ConcatStrings<NoGC>(cx, lstr, rstr); |
| if (result) { |
| res.set(StringValue(result)); |
| return true; |
| } |
| |
| RootedString rootedl(cx, lstr), rootedr(cx, rstr); |
| result = ConcatStrings<CanGC>(cx, rootedl, rootedr); |
| if (!result) |
| return false; |
| |
| res.set(StringValue(result)); |
| return true; |
| } |
| |
| typedef bool (*DoConcatStringsFn)(JSContext *, HandleValue, HandleValue, MutableHandleValue); |
| static const VMFunction DoConcatStringsInfo = FunctionInfo<DoConcatStringsFn>(DoConcatStrings); |
| |
| bool |
| ICBinaryArith_StringConcat::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| masm.branchTestString(Assembler::NotEqual, R0, &failure); |
| masm.branchTestString(Assembler::NotEqual, R1, &failure); |
| |
| // Restore the tail call register. |
| EmitRestoreTailCallReg(masm); |
| |
| masm.pushValue(R1); |
| masm.pushValue(R0); |
| if (!tailCallVM(DoConcatStringsInfo, masm)) |
| return false; |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| static JSString * |
| ConvertObjectToStringForConcat(JSContext *cx, HandleValue obj) |
| { |
| JS_ASSERT(obj.isObject()); |
| RootedValue rootedObj(cx, obj); |
| if (!ToPrimitive(cx, &rootedObj)) |
| return NULL; |
| return ToString<CanGC>(cx, rootedObj); |
| } |
| |
| static bool |
| DoConcatStringObject(JSContext *cx, bool lhsIsString, HandleValue lhs, HandleValue rhs, |
| MutableHandleValue res) |
| { |
| JSString *lstr = NULL; |
| JSString *rstr = NULL; |
| if (lhsIsString) { |
| // Convert rhs first. |
| JS_ASSERT(lhs.isString() && rhs.isObject()); |
| rstr = ConvertObjectToStringForConcat(cx, rhs); |
| if (!rstr) |
| return false; |
| |
| // lhs is already string. |
| lstr = lhs.toString(); |
| } else { |
| JS_ASSERT(rhs.isString() && lhs.isObject()); |
| // Convert lhs first. |
| lstr = ConvertObjectToStringForConcat(cx, lhs); |
| if (!lstr) |
| return false; |
| |
| // rhs is already string. |
| rstr = rhs.toString(); |
| } |
| |
| JSString *str = ConcatStrings<NoGC>(cx, lstr, rstr); |
| if (!str) { |
| RootedString nlstr(cx, lstr), nrstr(cx, rstr); |
| str = ConcatStrings<CanGC>(cx, nlstr, nrstr); |
| if (!str) |
| return false; |
| } |
| |
| // Technically, we need to call TypeScript::MonitorString for this PC, however |
| // it was called when this stub was attached so it's OK. |
| |
| res.setString(str); |
| return true; |
| } |
| |
| typedef bool (*DoConcatStringObjectFn)(JSContext *, bool lhsIsString, HandleValue, HandleValue, |
| MutableHandleValue); |
| static const VMFunction DoConcatStringObjectInfo = |
| FunctionInfo<DoConcatStringObjectFn>(DoConcatStringObject, PopValues(2)); |
| |
| bool |
| ICBinaryArith_StringObjectConcat::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| if (lhsIsString_) { |
| masm.branchTestString(Assembler::NotEqual, R0, &failure); |
| masm.branchTestObject(Assembler::NotEqual, R1, &failure); |
| } else { |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| masm.branchTestString(Assembler::NotEqual, R1, &failure); |
| } |
| |
| // Restore the tail call register. |
| EmitRestoreTailCallReg(masm); |
| |
| // Sync for the decompiler. |
| masm.pushValue(R0); |
| masm.pushValue(R1); |
| |
| // Push arguments. |
| masm.pushValue(R1); |
| masm.pushValue(R0); |
| masm.push(Imm32(lhsIsString_)); |
| if (!tailCallVM(DoConcatStringObjectInfo, masm)) |
| return false; |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICBinaryArith_Double::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| masm.ensureDouble(R0, FloatReg0, &failure); |
| masm.ensureDouble(R1, FloatReg1, &failure); |
| |
| switch (op) { |
| case JSOP_ADD: |
| masm.addDouble(FloatReg1, FloatReg0); |
| break; |
| case JSOP_SUB: |
| masm.subDouble(FloatReg1, FloatReg0); |
| break; |
| case JSOP_MUL: |
| masm.mulDouble(FloatReg1, FloatReg0); |
| break; |
| case JSOP_DIV: |
| masm.divDouble(FloatReg1, FloatReg0); |
| break; |
| case JSOP_MOD: |
| masm.setupUnalignedABICall(2, R0.scratchReg()); |
| masm.passABIArg(FloatReg0); |
| masm.passABIArg(FloatReg1); |
| masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, NumberMod), MacroAssembler::DOUBLE); |
| JS_ASSERT(ReturnFloatReg == FloatReg0); |
| break; |
| default: |
| JS_NOT_REACHED("Unexpected op"); |
| return false; |
| } |
| |
| masm.boxDouble(FloatReg0, R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICBinaryArith_BooleanWithInt32::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| if (lhsIsBool_) |
| masm.branchTestBoolean(Assembler::NotEqual, R0, &failure); |
| else |
| masm.branchTestInt32(Assembler::NotEqual, R0, &failure); |
| |
| if (rhsIsBool_) |
| masm.branchTestBoolean(Assembler::NotEqual, R1, &failure); |
| else |
| masm.branchTestInt32(Assembler::NotEqual, R1, &failure); |
| |
| Register lhsReg = lhsIsBool_ ? masm.extractBoolean(R0, ExtractTemp0) |
| : masm.extractInt32(R0, ExtractTemp0); |
| Register rhsReg = rhsIsBool_ ? masm.extractBoolean(R1, ExtractTemp1) |
| : masm.extractInt32(R1, ExtractTemp1); |
| |
| JS_ASSERT(op_ == JSOP_ADD || op_ == JSOP_SUB || |
| op_ == JSOP_BITOR || op_ == JSOP_BITXOR || op_ == JSOP_BITAND); |
| |
| switch(op_) { |
| case JSOP_ADD: { |
| Label fixOverflow; |
| |
| #if defined(JS_CPU_MIPS) |
| masm.branchAdd32(Assembler::Overflow, rhsReg, lhsReg, &fixOverflow); |
| masm.tagValue(JSVAL_TYPE_INT32, lhsReg, R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&fixOverflow); |
| masm.sub32(rhsReg, lhsReg); |
| // Proceed to failure below. |
| #else |
| masm.add32(rhsReg, lhsReg); |
| masm.j(Assembler::Overflow, &fixOverflow); |
| masm.tagValue(JSVAL_TYPE_INT32, lhsReg, R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&fixOverflow); |
| masm.sub32(rhsReg, lhsReg); |
| masm.jump(&failure); |
| #endif |
| break; |
| } |
| case JSOP_SUB: { |
| Label fixOverflow; |
| |
| #if defined(JS_CPU_MIPS) |
| masm.branchSub32(Assembler::Overflow, rhsReg, lhsReg, &fixOverflow); |
| masm.tagValue(JSVAL_TYPE_INT32, lhsReg, R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&fixOverflow); |
| masm.add32(rhsReg, lhsReg); |
| // Proceed to failure below. |
| #else |
| masm.sub32(rhsReg, lhsReg); |
| masm.j(Assembler::Overflow, &fixOverflow); |
| masm.tagValue(JSVAL_TYPE_INT32, lhsReg, R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&fixOverflow); |
| masm.add32(rhsReg, lhsReg); |
| masm.jump(&failure); |
| #endif |
| break; |
| } |
| case JSOP_BITOR: { |
| masm.orPtr(rhsReg, lhsReg); |
| masm.tagValue(JSVAL_TYPE_INT32, lhsReg, R0); |
| EmitReturnFromIC(masm); |
| break; |
| } |
| case JSOP_BITXOR: { |
| masm.xorPtr(rhsReg, lhsReg); |
| masm.tagValue(JSVAL_TYPE_INT32, lhsReg, R0); |
| EmitReturnFromIC(masm); |
| break; |
| } |
| case JSOP_BITAND: { |
| masm.andPtr(rhsReg, lhsReg); |
| masm.tagValue(JSVAL_TYPE_INT32, lhsReg, R0); |
| EmitReturnFromIC(masm); |
| break; |
| } |
| default: |
| JS_NOT_REACHED("Unhandled op for BinaryArith_BooleanWithInt32."); |
| return false; |
| } |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICBinaryArith_DoubleWithInt32::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| JS_ASSERT(op == JSOP_BITOR || op == JSOP_BITAND || op == JSOP_BITXOR); |
| |
| Label failure; |
| Register intReg; |
| Register scratchReg; |
| if (lhsIsDouble_) { |
| masm.branchTestDouble(Assembler::NotEqual, R0, &failure); |
| masm.branchTestInt32(Assembler::NotEqual, R1, &failure); |
| intReg = masm.extractInt32(R1, ExtractTemp0); |
| masm.unboxDouble(R0, FloatReg0); |
| scratchReg = R0.scratchReg(); |
| } else { |
| masm.branchTestInt32(Assembler::NotEqual, R0, &failure); |
| masm.branchTestDouble(Assembler::NotEqual, R1, &failure); |
| intReg = masm.extractInt32(R0, ExtractTemp0); |
| masm.unboxDouble(R1, FloatReg0); |
| scratchReg = R1.scratchReg(); |
| } |
| |
| // Truncate the double to an int32. |
| { |
| Label doneTruncate; |
| Label truncateABICall; |
| masm.branchTruncateDouble(FloatReg0, scratchReg, &truncateABICall); |
| masm.jump(&doneTruncate); |
| |
| masm.bind(&truncateABICall); |
| masm.push(intReg); |
| masm.setupUnalignedABICall(1, scratchReg); |
| masm.passABIArg(FloatReg0); |
| masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, js::ToInt32)); |
| masm.storeCallResult(scratchReg); |
| masm.pop(intReg); |
| |
| masm.bind(&doneTruncate); |
| } |
| |
| Register intReg2 = scratchReg; |
| // All handled ops commute, so no need to worry about ordering. |
| switch(op) { |
| case JSOP_BITOR: |
| masm.orPtr(intReg, intReg2); |
| break; |
| case JSOP_BITXOR: |
| masm.xorPtr(intReg, intReg2); |
| break; |
| case JSOP_BITAND: |
| masm.andPtr(intReg, intReg2); |
| break; |
| default: |
| JS_NOT_REACHED("Unhandled op for BinaryArith_DoubleWithInt32."); |
| return false; |
| } |
| masm.tagValue(JSVAL_TYPE_INT32, intReg2, R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // UnaryArith_Fallback |
| // |
| |
| // Disable PGO (see bug 851490). |
| #if defined(_MSC_VER) |
| # pragma optimize("g", off) |
| #endif |
| static bool |
| DoUnaryArithFallback(JSContext *cx, BaselineFrame *frame, ICUnaryArith_Fallback *stub, |
| HandleValue val, MutableHandleValue res) |
| { |
| RootedScript script(cx, frame->script()); |
| jsbytecode *pc = stub->icEntry()->pc(script); |
| JSOp op = JSOp(*pc); |
| FallbackICSpew(cx, stub, "UnaryArith(%s)", js_CodeName[op]); |
| |
| switch (op) { |
| case JSOP_BITNOT: { |
| int32_t result; |
| if (!BitNot(cx, val, &result)) |
| return false; |
| res.setInt32(result); |
| break; |
| } |
| case JSOP_NEG: |
| if (!NegOperation(cx, script, pc, val, res)) |
| return false; |
| break; |
| default: |
| JS_NOT_REACHED("Unexpected op"); |
| return false; |
| } |
| |
| if (res.isDouble()) |
| stub->setSawDoubleResult(); |
| |
| if (stub->numOptimizedStubs() >= ICUnaryArith_Fallback::MAX_OPTIMIZED_STUBS) { |
| // TODO: Discard/replace stubs. |
| return true; |
| } |
| |
| if (val.isInt32() && res.isInt32()) { |
| IonSpew(IonSpew_BaselineIC, " Generating %s(Int32 => Int32) stub", js_CodeName[op]); |
| ICUnaryArith_Int32::Compiler compiler(cx, op); |
| ICStub *int32Stub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!int32Stub) |
| return false; |
| stub->addNewStub(int32Stub); |
| return true; |
| } |
| |
| if (val.isNumber() && res.isNumber() && |
| op == JSOP_NEG && |
| cx->runtime()->jitSupportsFloatingPoint) |
| { |
| IonSpew(IonSpew_BaselineIC, " Generating %s(Number => Number) stub", js_CodeName[op]); |
| // Unlink int32 stubs, the double stub handles both cases and TI specializes for both. |
| stub->unlinkStubsWithKind(cx, ICStub::UnaryArith_Int32); |
| |
| ICUnaryArith_Double::Compiler compiler(cx, op); |
| ICStub *doubleStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!doubleStub) |
| return false; |
| stub->addNewStub(doubleStub); |
| return true; |
| } |
| |
| return true; |
| } |
| #if defined(_MSC_VER) |
| # pragma optimize("", on) |
| #endif |
| |
| typedef bool (*DoUnaryArithFallbackFn)(JSContext *, BaselineFrame *, ICUnaryArith_Fallback *, |
| HandleValue, MutableHandleValue); |
| static const VMFunction DoUnaryArithFallbackInfo = |
| FunctionInfo<DoUnaryArithFallbackFn>(DoUnaryArithFallback, PopValues(1)); |
| |
| bool |
| ICUnaryArith_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| JS_ASSERT(R0 == JSReturnOperand); |
| |
| // Restore the tail call register. |
| EmitRestoreTailCallReg(masm); |
| |
| // Ensure stack is fully synced for the expression decompiler. |
| masm.pushValue(R0); |
| |
| // Push arguments. |
| masm.pushValue(R0); |
| masm.push(BaselineStubReg); |
| masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| return tailCallVM(DoUnaryArithFallbackInfo, masm); |
| } |
| |
| bool |
| ICUnaryArith_Double::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| masm.ensureDouble(R0, FloatReg0, &failure); |
| |
| JS_ASSERT(op == JSOP_NEG); |
| masm.negateDouble(FloatReg0); |
| masm.boxDouble(FloatReg0, R0); |
| |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // GetElem_Fallback |
| // |
| |
| static void GetFixedOrDynamicSlotOffset(HandleObject obj, uint32_t slot, |
| bool *isFixed, uint32_t *offset) |
| { |
| JS_ASSERT(isFixed); |
| JS_ASSERT(offset); |
| *isFixed = obj->isFixedSlot(slot); |
| *offset = *isFixed ? JSObject::getFixedSlotOffset(slot) |
| : obj->dynamicSlotIndex(slot) * sizeof(Value); |
| } |
| |
| static bool |
| IsCacheableDOMProxy(JSObject *obj) |
| { |
| if (!obj->isProxy()) |
| return false; |
| |
| BaseProxyHandler *handler = GetProxyHandler(obj); |
| |
| if (handler->family() != GetDOMProxyHandlerFamily()) |
| return false; |
| |
| if (obj->numFixedSlots() <= GetDOMProxyExpandoSlot()) |
| return false; |
| |
| return true; |
| } |
| |
| static JSObject * |
| GetDOMProxyProto(JSObject *obj) |
| { |
| JS_ASSERT(IsCacheableDOMProxy(obj)); |
| return obj->getTaggedProto().toObjectOrNull(); |
| } |
| |
| static void |
| GenerateDOMProxyChecks(JSContext *cx, MacroAssembler &masm, Register object, |
| Address checkProxyHandlerAddr, |
| Address *checkExpandoShapeAddr, |
| Address *expandoAndGenerationAddr, |
| Address *generationAddr, |
| Register scratch, |
| GeneralRegisterSet &domProxyRegSet, |
| Label *checkFailed) |
| { |
| // Guard the following: |
| // 1. The object is a DOMProxy. |
| // 2. The object does not have expando properties, or has an expando |
| // which is known to not have the desired property. |
| Address handlerAddr(object, JSObject::getFixedSlotOffset(JSSLOT_PROXY_HANDLER)); |
| Address expandoAddr(object, JSObject::getFixedSlotOffset(GetDOMProxyExpandoSlot())); |
| |
| // Check that object is a DOMProxy. |
| masm.loadPtr(checkProxyHandlerAddr, scratch); |
| masm.branchPrivatePtr(Assembler::NotEqual, handlerAddr, scratch, checkFailed); |
| |
| // At this point, if not checking for an expando object, just return. |
| if (!checkExpandoShapeAddr) |
| return; |
| |
| // For the remaining code, we need to reserve some registers to load a value. |
| // This is ugly, but unavoidable. |
| ValueOperand tempVal = domProxyRegSet.takeAnyValue(); |
| masm.pushValue(tempVal); |
| |
| Label failDOMProxyCheck; |
| Label domProxyOk; |
| |
| if (expandoAndGenerationAddr) { |
| JS_ASSERT(generationAddr); |
| |
| masm.loadPtr(*expandoAndGenerationAddr, tempVal.scratchReg()); |
| masm.branchPrivatePtr(Assembler::NotEqual, expandoAddr, tempVal.scratchReg(), |
| &failDOMProxyCheck); |
| |
| masm.load32(*generationAddr, scratch); |
| masm.branch32(Assembler::NotEqual, |
| Address(tempVal.scratchReg(), offsetof(ExpandoAndGeneration, expando)), |
| scratch, &failDOMProxyCheck); |
| |
| masm.loadValue(Address(tempVal.scratchReg(), 0), tempVal); |
| } else { |
| masm.loadValue(expandoAddr, tempVal); |
| } |
| |
| // If the incoming object does not have an expando object then we're sure we're not |
| // shadowing. |
| masm.branchTestUndefined(Assembler::Equal, tempVal, &domProxyOk); |
| |
| // The reference object used to generate this check may not have had an |
| // expando object at all, in which case the presence of a non-undefined |
| // expando value in the incoming object is automatically a failure. |
| masm.loadPtr(*checkExpandoShapeAddr, scratch); |
| masm.branchPtr(Assembler::Equal, scratch, ImmWord((void*)NULL), &failDOMProxyCheck); |
| |
| // Otherwise, ensure that the incoming object has an object for its expando value and that |
| // the shape matches. |
| masm.branchTestObject(Assembler::NotEqual, tempVal, &failDOMProxyCheck); |
| Register objReg = masm.extractObject(tempVal, tempVal.scratchReg()); |
| masm.branchTestObjShape(Assembler::Equal, objReg, scratch, &domProxyOk); |
| |
| // Failure case: restore the tempVal registers and jump to failures. |
| masm.bind(&failDOMProxyCheck); |
| masm.popValue(tempVal); |
| masm.jump(checkFailed); |
| |
| // Success case: restore the tempval and proceed. |
| masm.bind(&domProxyOk); |
| masm.popValue(tempVal); |
| } |
| |
| // Look up a property's shape on an object, being careful never to do any effectful |
| // operations. This procedure not yielding a shape should not be taken as a lack of |
| // existence of the property on the object. |
| static bool |
| EffectlesslyLookupProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, |
| MutableHandleObject holder, MutableHandleShape shape, |
| bool *checkDOMProxy=NULL, |
| DOMProxyShadowsResult *shadowsResult=NULL, |
| bool *domProxyHasGeneration=NULL) |
| { |
| shape.set(NULL); |
| holder.set(NULL); |
| |
| bool isDOMProxy = false; |
| if (checkDOMProxy) |
| *checkDOMProxy = false; |
| |
| // Check for list base if asked to. |
| RootedObject checkObj(cx, obj); |
| if (checkDOMProxy && IsCacheableDOMProxy(obj)) { |
| JS_ASSERT(domProxyHasGeneration); |
| JS_ASSERT(shadowsResult); |
| |
| *checkDOMProxy = isDOMProxy = true; |
| if (obj->hasUncacheableProto()) |
| return true; |
| |
| RootedId id(cx, NameToId(name)); |
| *shadowsResult = GetDOMProxyShadowsCheck()(cx, obj, id); |
| if (*shadowsResult == ShadowCheckFailed) |
| return false; |
| |
| if (*shadowsResult == Shadows) { |
| holder.set(obj); |
| return true; |
| } |
| |
| *domProxyHasGeneration = (*shadowsResult == DoesntShadowUnique); |
| |
| checkObj = GetDOMProxyProto(obj); |
| } |
| |
| if (!isDOMProxy && !obj->isNative()) |
| return true; |
| |
| if (checkObj->hasIdempotentProtoChain()) { |
| if (!JSObject::lookupProperty(cx, checkObj, name, holder, shape)) |
| return false; |
| } else if (checkObj->isNative()) { |
| shape.set(checkObj->nativeLookup(cx, NameToId(name))); |
| if (shape) |
| holder.set(checkObj); |
| } |
| return true; |
| } |
| |
| static bool |
| IsCacheableProtoChain(JSObject *obj, JSObject *holder, bool isDOMProxy=false) |
| { |
| JS_ASSERT_IF(isDOMProxy, IsCacheableDOMProxy(obj)); |
| JS_ASSERT_IF(!isDOMProxy, obj->isNative()); |
| |
| // Don't handle objects which require a prototype guard. This should |
| // be uncommon so handling it is likely not worth the complexity. |
| if (obj->hasUncacheableProto()) |
| return false; |
| |
| JSObject *cur = obj; |
| while (cur != holder) { |
| // We cannot assume that we find the holder object on the prototype |
| // chain and must check for null proto. The prototype chain can be |
| // altered during the lookupProperty call. |
| JSObject *proto; |
| if (isDOMProxy && cur == obj) |
| proto = cur->getTaggedProto().toObjectOrNull(); |
| else |
| proto = cur->getProto(); |
| |
| if (!proto || !proto->isNative()) |
| return false; |
| |
| if (proto->hasUncacheableProto()) |
| return false; |
| |
| cur = proto; |
| } |
| return true; |
| } |
| |
| static bool |
| IsCacheableGetPropReadSlot(JSObject *obj, JSObject *holder, Shape *shape, bool isDOMProxy=false) |
| { |
| if (!shape || !IsCacheableProtoChain(obj, holder, isDOMProxy)) |
| return false; |
| |
| if (!shape->hasSlot() || !shape->hasDefaultGetter()) |
| return false; |
| |
| return true; |
| } |
| |
| static bool |
| IsCacheableGetPropCall(JSObject *obj, JSObject *holder, Shape *shape, bool *isScripted, |
| bool isDOMProxy=false) |
| { |
| JS_ASSERT(isScripted); |
| |
| // Currently we only optimize getter calls for getters bound on prototypes. |
| if (obj == holder) |
| return false; |
| |
| if (!shape || !IsCacheableProtoChain(obj, holder, isDOMProxy)) |
| return false; |
| |
| if (shape->hasSlot() || shape->hasDefaultGetter()) |
| return false; |
| |
| if (!shape->hasGetterValue()) |
| return false; |
| |
| if (!shape->getterValue().isObject() || !shape->getterObject()->is<JSFunction>()) |
| return false; |
| |
| JSFunction *func = &shape->getterObject()->as<JSFunction>(); |
| if (func->isNative()) { |
| *isScripted = false; |
| return true; |
| } |
| |
| if (!func->hasScript()) |
| return false; |
| |
| JSScript *script = func->nonLazyScript(); |
| if (!script->hasIonScript() && !script->hasBaselineScript()) |
| return false; |
| |
| *isScripted = true; |
| return true; |
| } |
| |
| static bool |
| IsCacheableSetPropWriteSlot(JSObject *obj, Shape *oldShape, JSObject *holder, Shape *shape) |
| { |
| if (!shape) |
| return false; |
| |
| // Object shape must not have changed during the property set. |
| if (obj->lastProperty() != oldShape) |
| return false; |
| |
| // Currently we only optimize direct writes. |
| if (obj != holder) |
| return false; |
| |
| if (!shape->hasSlot() || !shape->hasDefaultSetter() || !shape->writable()) |
| return false; |
| |
| return true; |
| } |
| |
| static bool |
| IsCacheableSetPropAddSlot(JSContext *cx, HandleObject obj, HandleShape oldShape, uint32_t oldSlots, |
| HandleId id, HandleObject holder, HandleShape shape, |
| size_t *protoChainDepth) |
| { |
| if (!shape) |
| return false; |
| |
| // Property must be set directly on object, and be last added property of object. |
| if (obj != holder || shape != obj->lastProperty()) |
| return false; |
| |
| // Object must be extensible, oldShape must be immediate parent of curShape. |
| if (!obj->isExtensible() || obj->lastProperty()->previous() != oldShape) |
| return false; |
| |
| // Basic shape checks. |
| if (shape->inDictionary() || !shape->hasSlot() || !shape->hasDefaultSetter() || |
| !shape->writable()) |
| { |
| return false; |
| } |
| |
| // If object has a non-default resolve hook, don't inline |
| if (obj->getClass()->resolve != JS_ResolveStub) |
| return false; |
| |
| size_t chainDepth = 0; |
| // walk up the object prototype chain and ensure that all prototypes |
| // are native, and that all prototypes have setter defined on the property |
| for (JSObject *proto = obj->getProto(); proto; proto = proto->getProto()) { |
| chainDepth++; |
| // if prototype is non-native, don't optimize |
| if (!proto->isNative()) |
| return false; |
| |
| // if prototype defines this property in a non-plain way, don't optimize |
| Shape *protoShape = proto->nativeLookup(cx, id); |
| if (protoShape && !protoShape->hasDefaultSetter()) |
| return false; |
| |
| // Otherise, if there's no such property, watch out for a resolve hook that would need |
| // to be invoked and thus prevent inlining of property addition. |
| if (proto->getClass()->resolve != JS_ResolveStub) |
| return false; |
| } |
| |
| // Only add a IC entry if the dynamic slots didn't change when the shapes |
| // changed. Need to ensure that a shape change for a subsequent object |
| // won't involve reallocating the slot array. |
| if (obj->numDynamicSlots() != oldSlots) |
| return false; |
| |
| *protoChainDepth = chainDepth; |
| return true; |
| } |
| |
| static bool |
| IsCacheableSetPropCall(JSObject *obj, JSObject *holder, Shape *shape, bool *isScripted) |
| { |
| JS_ASSERT(isScripted); |
| |
| // Currently we only optimize setter calls for setters bound on prototypes. |
| if (obj == holder) |
| return false; |
| |
| if (!shape || !IsCacheableProtoChain(obj, holder)) |
| return false; |
| |
| if (shape->hasSlot() || shape->hasDefaultSetter()) |
| return false; |
| |
| if (!shape->hasSetterValue()) |
| return false; |
| |
| if (!shape->setterValue().isObject() || !shape->setterObject()->is<JSFunction>()) |
| return false; |
| |
| JSFunction *func = &shape->setterObject()->as<JSFunction>(); |
| if (func->isNative()) { |
| *isScripted = false; |
| return true; |
| } |
| |
| if (!func->hasScript()) |
| return false; |
| |
| JSScript *script = func->nonLazyScript(); |
| if (!script->hasIonScript() && !script->hasBaselineScript()) |
| return false; |
| |
| *isScripted = true; |
| return true; |
| } |
| |
| static bool |
| TypedArrayGetElemStubExists(ICGetElem_Fallback *stub, HandleObject obj) |
| { |
| for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) { |
| if (!iter->isGetElem_TypedArray()) |
| continue; |
| if (obj->lastProperty() == iter->toGetElem_TypedArray()->shape()) |
| return true; |
| } |
| return false; |
| } |
| |
| static bool |
| ArgumentsGetElemStubExists(ICGetElem_Fallback *stub, ICGetElem_Arguments::Which which) |
| { |
| for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) { |
| if (!iter->isGetElem_Arguments()) |
| continue; |
| if (iter->toGetElem_Arguments()->which() == which) |
| return true; |
| } |
| return false; |
| } |
| |
| |
| static bool TryAttachNativeGetElemStub(JSContext *cx, HandleScript script, |
| ICGetElem_Fallback *stub, HandleObject obj, |
| HandleValue key) |
| { |
| RootedId id(cx); |
| if (!ValueToId<CanGC>(cx, key, &id)) |
| return false; |
| |
| uint32_t dummy; |
| if (!JSID_IS_ATOM(id) || JSID_TO_ATOM(id)->isIndex(&dummy)) |
| return true; |
| |
| RootedPropertyName propName(cx, JSID_TO_ATOM(id)->asPropertyName()); |
| |
| RootedShape shape(cx); |
| RootedObject holder(cx); |
| if (!EffectlesslyLookupProperty(cx, obj, propName, &holder, &shape)) |
| return false; |
| |
| if (!IsCacheableGetPropReadSlot(obj, holder, shape)) |
| return true; |
| |
| bool isFixedSlot; |
| uint32_t offset; |
| GetFixedOrDynamicSlotOffset(holder, shape->slot(), &isFixedSlot, &offset); |
| |
| ICStub *monitorStub = stub->fallbackMonitorStub()->firstMonitorStub(); |
| ICStub::Kind kind = (obj == holder) ? ICStub::GetElem_Native : ICStub::GetElem_NativePrototype; |
| |
| IonSpew(IonSpew_BaselineIC, " Generating GetElem(Native %s) stub (obj=%p, shape=%p, holder=%p, holderShape=%p)", |
| (obj == holder) ? "direct" : "prototype", |
| obj.get(), obj->lastProperty(), holder.get(), holder->lastProperty()); |
| |
| ICGetElemNativeCompiler compiler(cx, kind, monitorStub, obj, holder, key, |
| isFixedSlot, offset); |
| ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| return true; |
| } |
| |
| static bool |
| TypedArrayRequiresFloatingPoint(JSObject *obj) |
| { |
| uint32_t type = TypedArray::type(obj); |
| return (type == TypedArray::TYPE_UINT32 || |
| type == TypedArray::TYPE_FLOAT32 || |
| type == TypedArray::TYPE_FLOAT64); |
| } |
| |
| static bool |
| TryAttachGetElemStub(JSContext *cx, HandleScript script, ICGetElem_Fallback *stub, |
| HandleValue lhs, HandleValue rhs, HandleValue res) |
| { |
| // Check for String[i] => Char accesses. |
| if (lhs.isString() && rhs.isInt32() && res.isString() && |
| !stub->hasStub(ICStub::GetElem_String)) |
| { |
| IonSpew(IonSpew_BaselineIC, " Generating GetElem(String[Int32]) stub"); |
| ICGetElem_String::Compiler compiler(cx); |
| ICStub *stringStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!stringStub) |
| return false; |
| |
| stub->addNewStub(stringStub); |
| return true; |
| } |
| |
| if (lhs.isMagic(JS_OPTIMIZED_ARGUMENTS) && rhs.isInt32() && |
| !ArgumentsGetElemStubExists(stub, ICGetElem_Arguments::Magic)) |
| { |
| IonSpew(IonSpew_BaselineIC, " Generating GetElem(MagicArgs[Int32]) stub"); |
| ICGetElem_Arguments::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(), |
| ICGetElem_Arguments::Magic); |
| ICStub *argsStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!argsStub) |
| return false; |
| |
| stub->addNewStub(argsStub); |
| return true; |
| } |
| |
| // Otherwise, GetElem is only optimized on objects. |
| if (!lhs.isObject()) |
| return true; |
| RootedObject obj(cx, &lhs.toObject()); |
| |
| // Check for ArgumentsObj[int] accesses |
| if (obj->is<ArgumentsObject>() && rhs.isInt32()) { |
| ICGetElem_Arguments::Which which = ICGetElem_Arguments::Normal; |
| if (obj->is<StrictArgumentsObject>()) |
| which = ICGetElem_Arguments::Strict; |
| if (!ArgumentsGetElemStubExists(stub, which)) { |
| IonSpew(IonSpew_BaselineIC, " Generating GetElem(ArgsObj[Int32]) stub"); |
| ICGetElem_Arguments::Compiler compiler( |
| cx, stub->fallbackMonitorStub()->firstMonitorStub(), which); |
| ICStub *argsStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!argsStub) |
| return false; |
| |
| stub->addNewStub(argsStub); |
| return true; |
| } |
| } |
| |
| if (obj->isNative()) { |
| // Check for NativeObject[int] dense accesses. |
| if (rhs.isInt32()) { |
| IonSpew(IonSpew_BaselineIC, " Generating GetElem(Native[Int32] dense) stub"); |
| ICGetElem_Dense::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(), |
| obj->lastProperty()); |
| ICStub *denseStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!denseStub) |
| return false; |
| |
| stub->addNewStub(denseStub); |
| return true; |
| } |
| |
| // Check for NativeObject[id] shape-optimizable accesses. |
| if (rhs.isString()) { |
| if (!TryAttachNativeGetElemStub(cx, script, stub, obj, rhs)) |
| return false; |
| } |
| } |
| |
| // Check for TypedArray[int] => Number accesses. |
| if (obj->isTypedArray() && rhs.isInt32() && res.isNumber() && |
| !TypedArrayGetElemStubExists(stub, obj)) |
| { |
| if (!cx->runtime()->jitSupportsFloatingPoint && TypedArrayRequiresFloatingPoint(obj)) |
| return true; |
| |
| IonSpew(IonSpew_BaselineIC, " Generating GetElem(TypedArray[Int32]) stub"); |
| ICGetElem_TypedArray::Compiler compiler(cx, obj->lastProperty(), TypedArray::type(obj)); |
| ICStub *typedArrayStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!typedArrayStub) |
| return false; |
| |
| stub->addNewStub(typedArrayStub); |
| return true; |
| } |
| |
| // GetElem operations on non-native objects other than typed arrays cannot |
| // be cached by either Baseline or Ion. Indicate this in the cache so that |
| // Ion does not generate a cache for this op. |
| if (!obj->isNative() && !obj->isTypedArray()) |
| stub->noteNonNativeAccess(); |
| |
| return true; |
| } |
| |
| static bool |
| DoGetElemFallback(JSContext *cx, BaselineFrame *frame, ICGetElem_Fallback *stub, HandleValue lhs, |
| HandleValue rhs, MutableHandleValue res) |
| { |
| RootedScript script(cx, frame->script()); |
| jsbytecode *pc = stub->icEntry()->pc(script); |
| JSOp op = JSOp(*pc); |
| FallbackICSpew(cx, stub, "GetElem(%s)", js_CodeName[op]); |
| |
| JS_ASSERT(op == JSOP_GETELEM || op == JSOP_CALLELEM); |
| |
| // Don't pass lhs directly, we need it when generating stubs. |
| RootedValue lhsCopy(cx, lhs); |
| |
| bool isOptimizedArgs = false; |
| if (lhs.isMagic(JS_OPTIMIZED_ARGUMENTS)) { |
| // Handle optimized arguments[i] access. |
| if (!GetElemOptimizedArguments(cx, frame, &lhsCopy, rhs, res, &isOptimizedArgs)) |
| return false; |
| if (isOptimizedArgs) |
| types::TypeScript::Monitor(cx, script, pc, res); |
| } |
| |
| if (!isOptimizedArgs) { |
| if (!GetElementOperation(cx, op, &lhsCopy, rhs, res)) |
| return false; |
| types::TypeScript::Monitor(cx, script, pc, res); |
| } |
| |
| // Add a type monitor stub for the resulting value. |
| if (!stub->addMonitorStubForValue(cx, script, res)) |
| return false; |
| |
| if (stub->numOptimizedStubs() >= ICGetElem_Fallback::MAX_OPTIMIZED_STUBS) { |
| // TODO: Discard all stubs in this IC and replace with inert megamorphic stub. |
| // But for now we just bail. |
| return true; |
| } |
| |
| // Try to attach an optimized stub. |
| if (!TryAttachGetElemStub(cx, script, stub, lhs, rhs, res)) |
| return false; |
| |
| return true; |
| } |
| |
| typedef bool (*DoGetElemFallbackFn)(JSContext *, BaselineFrame *, ICGetElem_Fallback *, |
| HandleValue, HandleValue, MutableHandleValue); |
| static const VMFunction DoGetElemFallbackInfo = |
| FunctionInfo<DoGetElemFallbackFn>(DoGetElemFallback, PopValues(2)); |
| |
| bool |
| ICGetElem_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| JS_ASSERT(R0 == JSReturnOperand); |
| |
| // Restore the tail call register. |
| EmitRestoreTailCallReg(masm); |
| |
| // Ensure stack is fully synced for the expression decompiler. |
| masm.pushValue(R0); |
| masm.pushValue(R1); |
| |
| // Push arguments. |
| masm.pushValue(R1); |
| masm.pushValue(R0); |
| masm.push(BaselineStubReg); |
| masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| return tailCallVM(DoGetElemFallbackInfo, masm); |
| } |
| |
| // |
| // GetElem_Native |
| // |
| |
| bool |
| ICGetElemNativeCompiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| Label failurePopR1; |
| bool popR1 = false; |
| |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| Address idValAddr(BaselineStubReg, ICGetElemNativeStub::offsetOfIdval()); |
| masm.branchTestValue(Assembler::NotEqual, idValAddr, R1, &failure); |
| |
| GeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratchReg = regs.takeAny(); |
| |
| // Unbox object. |
| Register objReg = masm.extractObject(R0, ExtractTemp0); |
| |
| // Check object shape. |
| masm.loadPtr(Address(objReg, JSObject::offsetOfShape()), scratchReg); |
| Address shapeAddr(BaselineStubReg, ICGetElemNativeStub::offsetOfShape()); |
| masm.branchPtr(Assembler::NotEqual, shapeAddr, scratchReg, &failure); |
| |
| Register holderReg; |
| if (obj_ == holder_) { |
| holderReg = objReg; |
| } else { |
| // Shape guard holder. |
| if (regs.empty()) { |
| masm.push(R1.scratchReg()); |
| popR1 = true; |
| holderReg = R1.scratchReg(); |
| } else { |
| holderReg = regs.takeAny(); |
| } |
| masm.loadPtr(Address(BaselineStubReg, ICGetElem_NativePrototype::offsetOfHolder()), |
| holderReg); |
| masm.loadPtr(Address(BaselineStubReg, ICGetElem_NativePrototype::offsetOfHolderShape()), |
| scratchReg); |
| masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratchReg, |
| popR1 ? &failurePopR1 : &failure); |
| } |
| |
| // Load from object. |
| if (!isFixedSlot_) |
| masm.loadPtr(Address(holderReg, JSObject::offsetOfSlots()), holderReg); |
| |
| masm.load32(Address(BaselineStubReg, ICGetElem_Native::offsetOfOffset()), scratchReg); |
| masm.loadValue(BaseIndex(holderReg, scratchReg, TimesOne), R0); |
| |
| if (popR1) |
| masm.pop(R1.scratchReg()); |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| // Failure case - jump to next stub |
| if (popR1) { |
| masm.bind(&failurePopR1); |
| masm.pop(R1.scratchReg()); |
| } |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // GetElem_String |
| // |
| |
| bool |
| ICGetElem_String::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| masm.branchTestString(Assembler::NotEqual, R0, &failure); |
| masm.branchTestInt32(Assembler::NotEqual, R1, &failure); |
| |
| GeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratchReg = regs.takeAny(); |
| |
| // Unbox string in R0. |
| Register str = masm.extractString(R0, ExtractTemp0); |
| |
| // Load string lengthAndFlags |
| Address lengthAndFlagsAddr(str, JSString::offsetOfLengthAndFlags()); |
| masm.loadPtr(lengthAndFlagsAddr, scratchReg); |
| |
| // Check for non-linear strings. |
| masm.branchTest32(Assembler::Zero, scratchReg, Imm32(JSString::FLAGS_MASK), &failure); |
| |
| // Unbox key. |
| Register key = masm.extractInt32(R1, ExtractTemp1); |
| |
| // Extract length and bounds check. |
| masm.rshiftPtr(Imm32(JSString::LENGTH_SHIFT), scratchReg); |
| masm.branch32(Assembler::BelowOrEqual, scratchReg, key, &failure); |
| |
| // Get char code. |
| Address charsAddr(str, JSString::offsetOfChars()); |
| masm.loadPtr(charsAddr, scratchReg); |
| masm.load16ZeroExtend(BaseIndex(scratchReg, key, TimesTwo, 0), scratchReg); |
| |
| // Check if char code >= UNIT_STATIC_LIMIT. |
| masm.branch32(Assembler::AboveOrEqual, scratchReg, Imm32(StaticStrings::UNIT_STATIC_LIMIT), |
| &failure); |
| |
| // Load static string. |
| masm.movePtr(ImmWord(&cx->compartment()->rt->staticStrings.unitStaticTable), str); |
| masm.loadPtr(BaseIndex(str, scratchReg, ScalePointer), str); |
| |
| // Return. |
| masm.tagValue(JSVAL_TYPE_STRING, str, R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // GetElem_Dense |
| // |
| |
| bool |
| ICGetElem_Dense::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| masm.branchTestInt32(Assembler::NotEqual, R1, &failure); |
| |
| GeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratchReg = regs.takeAny(); |
| |
| // Unbox R0 and shape guard. |
| Register obj = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(BaselineStubReg, ICGetElem_Dense::offsetOfShape()), scratchReg); |
| masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure); |
| |
| // Load obj->elements. |
| masm.loadPtr(Address(obj, JSObject::offsetOfElements()), scratchReg); |
| |
| // Unbox key. |
| Register key = masm.extractInt32(R1, ExtractTemp1); |
| |
| // Bounds check. |
| Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength()); |
| masm.branch32(Assembler::BelowOrEqual, initLength, key, &failure); |
| |
| // Hole check and load value. |
| BaseIndex element(scratchReg, key, TimesEight); |
| masm.branchTestMagic(Assembler::Equal, element, &failure); |
| masm.loadValue(element, R0); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // GetElem_TypedArray |
| // |
| |
| bool |
| ICGetElem_TypedArray::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| masm.branchTestInt32(Assembler::NotEqual, R1, &failure); |
| |
| GeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratchReg = regs.takeAny(); |
| |
| // Unbox R0 and shape guard. |
| Register obj = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(BaselineStubReg, ICGetElem_TypedArray::offsetOfShape()), scratchReg); |
| masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure); |
| |
| // Unbox key. |
| Register key = masm.extractInt32(R1, ExtractTemp1); |
| |
| // Bounds check. |
| masm.unboxInt32(Address(obj, TypedArray::lengthOffset()), scratchReg); |
| masm.branch32(Assembler::BelowOrEqual, scratchReg, key, &failure); |
| |
| // Load the elements vector. |
| masm.loadPtr(Address(obj, TypedArray::dataOffset()), scratchReg); |
| |
| // Load the value. |
| BaseIndex source(scratchReg, key, ScaleFromElemWidth(TypedArray::slotWidth(type_))); |
| masm.loadFromTypedArray(type_, source, R0, false, scratchReg, &failure); |
| |
| // Todo: Allow loading doubles from uint32 arrays, but this requires monitoring. |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // GetEelem_Arguments |
| // |
| bool |
| ICGetElem_Arguments::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| if (which_ == ICGetElem_Arguments::Magic) { |
| // Ensure that this is a magic arguments value. |
| masm.branchTestMagicValue(Assembler::NotEqual, R0, JS_OPTIMIZED_ARGUMENTS, &failure); |
| |
| // Ensure that frame has not loaded different arguments object since. |
| masm.branchTest32(Assembler::NonZero, |
| Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFlags()), |
| Imm32(BaselineFrame::HAS_ARGS_OBJ), |
| &failure); |
| |
| // Ensure that index is an integer. |
| masm.branchTestInt32(Assembler::NotEqual, R1, &failure); |
| Register idx = masm.extractInt32(R1, ExtractTemp1); |
| |
| GeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratch = regs.takeAny(); |
| |
| // Load num actual arguments |
| Address actualArgs(BaselineFrameReg, BaselineFrame::offsetOfNumActualArgs()); |
| masm.loadPtr(actualArgs, scratch); |
| |
| // Ensure idx < argc |
| masm.branch32(Assembler::AboveOrEqual, idx, scratch, &failure); |
| |
| // Load argval |
| JS_ASSERT(sizeof(Value) == 8); |
| masm.movePtr(BaselineFrameReg, scratch); |
| masm.addPtr(Imm32(BaselineFrame::offsetOfArg(0)), scratch); |
| BaseIndex element(scratch, idx, TimesEight); |
| masm.loadValue(element, R0); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| JS_ASSERT(which_ == ICGetElem_Arguments::Strict || |
| which_ == ICGetElem_Arguments::Normal); |
| |
| bool isStrict = which_ == ICGetElem_Arguments::Strict; |
| Class *clasp = isStrict ? &StrictArgumentsObject::class_ : &NormalArgumentsObject::class_; |
| |
| GeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratchReg = regs.takeAny(); |
| |
| // Guard on input being an arguments object. |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| Register objReg = masm.extractObject(R0, ExtractTemp0); |
| masm.branchTestObjClass(Assembler::NotEqual, objReg, scratchReg, clasp, &failure); |
| |
| // Guard on index being int32 |
| masm.branchTestInt32(Assembler::NotEqual, R1, &failure); |
| Register idxReg = masm.extractInt32(R1, ExtractTemp1); |
| |
| // Get initial ArgsObj length value. |
| masm.unboxInt32(Address(objReg, ArgumentsObject::getInitialLengthSlotOffset()), scratchReg); |
| |
| // Test if length has been overridden. |
| masm.branchTest32(Assembler::NonZero, |
| scratchReg, |
| Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT), |
| &failure); |
| |
| // Length has not been overridden, ensure that R1 is an integer and is <= length. |
| masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), scratchReg); |
| masm.branch32(Assembler::AboveOrEqual, idxReg, scratchReg, &failure); |
| |
| // Length check succeeded, now check the correct bit. We clobber potential type regs |
| // now. Inputs will have to be reconstructed if we fail after this point, but that's |
| // unlikely. |
| Label failureReconstructInputs; |
| regs = availableGeneralRegs(0); |
| if (regs.has(objReg)) |
| regs.take(objReg); |
| if (regs.has(idxReg)) |
| regs.take(idxReg); |
| if (regs.has(scratchReg)) |
| regs.take(scratchReg); |
| Register argData = regs.takeAny(); |
| Register tempReg = regs.takeAny(); |
| |
| // Load ArgumentsData |
| masm.loadPrivate(Address(objReg, ArgumentsObject::getDataSlotOffset()), argData); |
| |
| // Load deletedBits bitArray pointer into scratchReg |
| masm.loadPtr(Address(argData, offsetof(ArgumentsData, deletedBits)), scratchReg); |
| |
| // In tempReg, calculate index of word containing bit: (idx >> logBitsPerWord) |
| masm.movePtr(idxReg, tempReg); |
| masm.rshiftPtr(Imm32(JS_BITS_PER_WORD_LOG2), tempReg); |
| masm.loadPtr(BaseIndex(scratchReg, tempReg, ScaleFromElemWidth(sizeof(size_t))), scratchReg); |
| |
| // Don't bother testing specific bit, if any bit is set in the word, fail. |
| masm.branchPtr(Assembler::NotEqual, scratchReg, ImmWord((size_t)0), &failureReconstructInputs); |
| |
| // Load the value. use scratchReg and tempReg to form a ValueOperand to load into. |
| masm.addPtr(Imm32(ArgumentsData::offsetOfArgs()), argData); |
| regs.add(scratchReg); |
| regs.add(tempReg); |
| ValueOperand tempVal = regs.takeAnyValue(); |
| masm.loadValue(BaseIndex(argData, idxReg, ScaleFromElemWidth(sizeof(Value))), tempVal); |
| |
| // Makesure that this is not a FORWARD_TO_CALL_SLOT magic value. |
| masm.branchTestMagic(Assembler::Equal, tempVal, &failureReconstructInputs); |
| |
| // Everything checked out, return value. |
| masm.moveValue(tempVal, R0); |
| |
| // Type-check result |
| EmitEnterTypeMonitorIC(masm); |
| |
| // Failed, but inputs are deconstructed into object and int, and need to be |
| // reconstructed into values. |
| masm.bind(&failureReconstructInputs); |
| masm.tagValue(JSVAL_TYPE_OBJECT, objReg, R0); |
| masm.tagValue(JSVAL_TYPE_INT32, idxReg, R1); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // SetElem_Fallback |
| // |
| |
| static bool |
| DenseSetElemStubExists(JSContext *cx, ICStub::Kind kind, ICSetElem_Fallback *stub, HandleObject obj) |
| { |
| JS_ASSERT(kind == ICStub::SetElem_Dense || kind == ICStub::SetElem_DenseAdd); |
| |
| for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) { |
| if (kind == ICStub::SetElem_Dense && iter->isSetElem_Dense()) { |
| ICSetElem_Dense *dense = iter->toSetElem_Dense(); |
| if (obj->lastProperty() == dense->shape() && obj->getType(cx) == dense->type()) |
| return true; |
| } |
| |
| if (kind == ICStub::SetElem_DenseAdd && iter->isSetElem_DenseAdd()) { |
| ICSetElem_DenseAdd *dense = iter->toSetElem_DenseAdd(); |
| if (obj->lastProperty() == dense->toImplUnchecked<0>()->shape(0) && |
| obj->getType(cx) == dense->type()) |
| { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| static bool |
| TypedArraySetElemStubExists(ICSetElem_Fallback *stub, HandleObject obj, bool expectOOB) |
| { |
| for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) { |
| if (!iter->isSetElem_TypedArray()) |
| continue; |
| ICSetElem_TypedArray *taStub = iter->toSetElem_TypedArray(); |
| if (obj->lastProperty() == taStub->shape() && taStub->expectOutOfBounds() == expectOOB) |
| return true; |
| } |
| return false; |
| } |
| |
| static bool |
| RemoveExistingTypedArraySetElemStub(JSContext *cx, ICSetElem_Fallback *stub, HandleObject obj) |
| { |
| for (ICStubIterator iter = stub->beginChain(); !iter.atEnd(); iter++) { |
| if (!iter->isSetElem_TypedArray()) |
| continue; |
| |
| if (obj->lastProperty() != iter->toSetElem_TypedArray()->shape()) |
| continue; |
| |
| // TypedArraySetElem stubs are only removed using this procedure if |
| // being replaced with one that expects out of bounds index. |
| JS_ASSERT(!iter->toSetElem_TypedArray()->expectOutOfBounds()); |
| iter.unlink(cx->zone()); |
| return true; |
| } |
| return false; |
| } |
| |
| static bool |
| CanOptimizeDenseSetElem(JSContext *cx, HandleObject obj, uint32_t index, |
| HandleShape oldShape, uint32_t oldCapacity, uint32_t oldInitLength, |
| bool *isAddingCaseOut, size_t *protoDepthOut) |
| { |
| uint32_t initLength = obj->getDenseInitializedLength(); |
| uint32_t capacity = obj->getDenseCapacity(); |
| |
| *isAddingCaseOut = false; |
| *protoDepthOut = 0; |
| |
| // Some initial sanity checks. |
| if (initLength < oldInitLength || capacity < oldCapacity) |
| return false; |
| |
| RootedShape shape(cx, obj->lastProperty()); |
| |
| // Cannot optimize if the shape changed. |
| if (oldShape != shape) |
| return false; |
| |
| // Cannot optimize if the capacity changed. |
| if (oldCapacity != capacity) |
| return false; |
| |
| // Cannot optimize if the index doesn't fit within the new initialized length. |
| if (index >= initLength) |
| return false; |
| |
| // Cannot optimize if the value at position after the set is a hole. |
| if (!obj->containsDenseElement(index)) |
| return false; |
| |
| // At this point, if we know that the initLength did not change, then |
| // an optimized set is possible. |
| if (oldInitLength == initLength) |
| return true; |
| |
| // If it did change, ensure that it changed specifically by incrementing by 1 |
| // to accomodate this particular indexed set. |
| if (oldInitLength + 1 != initLength) |
| return false; |
| if (index != oldInitLength) |
| return false; |
| |
| // The checks are not complete. The object may have a setter definition, |
| // either directly, or via a prototype, or via the target object for a prototype |
| // which is a proxy, that handles a particular integer write. |
| // Scan the prototype and shape chain to make sure that this is not the case. |
| RootedObject curObj(cx, obj); |
| while (curObj) { |
| // Ensure object is native. |
| if (!curObj->isNative()) |
| return false; |
| |
| // Ensure all indexed properties are stored in dense elements. |
| if (curObj->isIndexed()) |
| return false; |
| |
| curObj = curObj->getProto(); |
| if (curObj) |
| ++*protoDepthOut; |
| } |
| |
| if (*protoDepthOut > ICSetElem_DenseAdd::MAX_PROTO_CHAIN_DEPTH) |
| return false; |
| |
| *isAddingCaseOut = true; |
| |
| return true; |
| } |
| |
| static bool |
| DoSetElemFallback(JSContext *cx, BaselineFrame *frame, ICSetElem_Fallback *stub, Value *stack, |
| HandleValue objv, HandleValue index, HandleValue rhs) |
| { |
| RootedScript script(cx, frame->script()); |
| jsbytecode *pc = stub->icEntry()->pc(script); |
| JSOp op = JSOp(*pc); |
| FallbackICSpew(cx, stub, "SetElem(%s)", js_CodeName[JSOp(*pc)]); |
| |
| JS_ASSERT(op == JSOP_SETELEM || |
| op == JSOP_ENUMELEM || |
| op == JSOP_INITELEM || |
| op == JSOP_INITELEM_ARRAY); |
| |
| RootedObject obj(cx, ToObjectFromStack(cx, objv)); |
| if (!obj) |
| return false; |
| |
| RootedShape oldShape(cx, obj->lastProperty()); |
| |
| // Check the old capacity |
| uint32_t oldCapacity = 0; |
| uint32_t oldInitLength = 0; |
| if (obj->isNative() && index.isInt32() && index.toInt32() >= 0) { |
| oldCapacity = obj->getDenseCapacity(); |
| oldInitLength = obj->getDenseInitializedLength(); |
| } |
| |
| if (op == JSOP_INITELEM) { |
| if (!InitElemOperation(cx, obj, index, rhs)) |
| return false; |
| } else if (op == JSOP_INITELEM_ARRAY) { |
| JS_ASSERT(uint32_t(index.toInt32()) == GET_UINT24(pc)); |
| if (!InitArrayElemOperation(cx, pc, obj, index.toInt32(), rhs)) |
| return false; |
| } else { |
| if (!SetObjectElement(cx, obj, index, rhs, script->strict, script, pc)) |
| return false; |
| } |
| |
| // Overwrite the object on the stack (pushed for the decompiler) with the rhs. |
| JS_ASSERT(stack[2] == objv); |
| stack[2] = rhs; |
| |
| if (stub->numOptimizedStubs() >= ICSetElem_Fallback::MAX_OPTIMIZED_STUBS) { |
| // TODO: Discard all stubs in this IC and replace with inert megamorphic stub. |
| // But for now we just bail. |
| return true; |
| } |
| |
| // Try to generate new stubs. |
| if (obj->isNative() && |
| index.isInt32() && index.toInt32() >= 0 && |
| !rhs.isMagic(JS_ELEMENTS_HOLE)) |
| { |
| JS_ASSERT(!obj->isTypedArray()); |
| |
| bool addingCase; |
| size_t protoDepth; |
| |
| if (CanOptimizeDenseSetElem(cx, obj, index.toInt32(), oldShape, oldCapacity, oldInitLength, |
| &addingCase, &protoDepth)) |
| { |
| RootedTypeObject type(cx, obj->getType(cx)); |
| RootedShape shape(cx, obj->lastProperty()); |
| |
| if (addingCase && !DenseSetElemStubExists(cx, ICStub::SetElem_DenseAdd, stub, obj)) { |
| IonSpew(IonSpew_BaselineIC, |
| " Generating SetElem_DenseAdd stub " |
| "(shape=%p, type=%p, protoDepth=%u)", |
| obj->lastProperty(), type.get(), protoDepth); |
| ICSetElemDenseAddCompiler compiler(cx, obj, protoDepth); |
| ICUpdatedStub *denseStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!denseStub) |
| return false; |
| if (!denseStub->addUpdateStubForValue(cx, script, obj, JS::JSID_VOIDHANDLE, rhs)) |
| return false; |
| |
| stub->addNewStub(denseStub); |
| } else if (!addingCase && |
| !DenseSetElemStubExists(cx, ICStub::SetElem_Dense, stub, obj)) |
| { |
| IonSpew(IonSpew_BaselineIC, |
| " Generating SetElem_Dense stub (shape=%p, type=%p)", |
| obj->lastProperty(), type.get()); |
| ICSetElem_Dense::Compiler compiler(cx, shape, type); |
| ICUpdatedStub *denseStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!denseStub) |
| return false; |
| if (!denseStub->addUpdateStubForValue(cx, script, obj, JS::JSID_VOIDHANDLE, rhs)) |
| return false; |
| |
| stub->addNewStub(denseStub); |
| } |
| } |
| |
| return true; |
| } |
| |
| if (obj->isTypedArray() && index.isInt32() && rhs.isNumber()) { |
| if (!cx->runtime()->jitSupportsFloatingPoint && TypedArrayRequiresFloatingPoint(obj)) |
| return true; |
| |
| uint32_t len = TypedArray::length(obj); |
| int32_t idx = index.toInt32(); |
| bool expectOutOfBounds = (idx < 0) || (static_cast<uint32_t>(idx) >= len); |
| |
| if (!TypedArraySetElemStubExists(stub, obj, expectOutOfBounds)) { |
| // Remove any existing TypedArraySetElemStub that doesn't handle out-of-bounds |
| if (expectOutOfBounds) |
| RemoveExistingTypedArraySetElemStub(cx, stub, obj); |
| |
| IonSpew(IonSpew_BaselineIC, |
| " Generating SetElem_TypedArray stub (shape=%p, type=%u, oob=%s)", |
| obj->lastProperty(), TypedArray::type(obj), |
| expectOutOfBounds ? "yes" : "no"); |
| ICSetElem_TypedArray::Compiler compiler(cx, obj->lastProperty(), TypedArray::type(obj), |
| expectOutOfBounds); |
| ICStub *typedArrayStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!typedArrayStub) |
| return false; |
| |
| stub->addNewStub(typedArrayStub); |
| return true; |
| } |
| } |
| |
| return true; |
| } |
| |
| typedef bool (*DoSetElemFallbackFn)(JSContext *, BaselineFrame *, ICSetElem_Fallback *, Value *, |
| HandleValue, HandleValue, HandleValue); |
| static const VMFunction DoSetElemFallbackInfo = |
| FunctionInfo<DoSetElemFallbackFn>(DoSetElemFallback, PopValues(2)); |
| |
| bool |
| ICSetElem_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| JS_ASSERT(R0 == JSReturnOperand); |
| |
| EmitRestoreTailCallReg(masm); |
| |
| // State: R0: object, R1: index, stack: rhs. |
| // For the decompiler, the stack has to be: object, index, rhs, |
| // so we push the index, then overwrite the rhs Value with R0 |
| // and push the rhs value. |
| masm.pushValue(R1); |
| masm.loadValue(Address(BaselineStackReg, sizeof(Value)), R1); |
| masm.storeValue(R0, Address(BaselineStackReg, sizeof(Value))); |
| masm.pushValue(R1); |
| |
| // Push arguments. |
| masm.pushValue(R1); // RHS |
| |
| // Push index. On x86 and ARM two push instructions are emitted so use a |
| // separate register to store the old stack pointer. |
| masm.mov(BaselineStackReg, R1.scratchReg()); |
| masm.pushValue(Address(R1.scratchReg(), 2 * sizeof(Value))); |
| masm.pushValue(R0); // Object. |
| |
| // Push pointer to stack values, so that the stub can overwrite the object |
| // (pushed for the decompiler) with the rhs. |
| masm.computeEffectiveAddress(Address(BaselineStackReg, 3 * sizeof(Value)), R0.scratchReg()); |
| masm.push(R0.scratchReg()); |
| |
| masm.push(BaselineStubReg); |
| masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| return tailCallVM(DoSetElemFallbackInfo, masm); |
| } |
| |
| // |
| // SetElem_Dense |
| // |
| |
| bool |
| ICSetElem_Dense::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| // R0 = object |
| // R1 = key |
| // Stack = { ... rhs-value, <return-addr>? } |
| Label failure; |
| Label failureUnstow; |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| masm.branchTestInt32(Assembler::NotEqual, R1, &failure); |
| |
| GeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratchReg = regs.takeAny(); |
| |
| // Unbox R0 and guard on its shape. |
| Register obj = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(BaselineStubReg, ICSetElem_Dense::offsetOfShape()), scratchReg); |
| masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure); |
| |
| // Stow both R0 and R1 (object and key) |
| // But R0 and R1 still hold their values. |
| EmitStowICValues(masm, 2); |
| |
| // We may need to free up some registers. |
| regs = availableGeneralRegs(0); |
| regs.take(R0); |
| |
| // Guard that the type object matches. |
| Register typeReg = regs.takeAny(); |
| masm.loadPtr(Address(BaselineStubReg, ICSetElem_Dense::offsetOfType()), typeReg); |
| masm.branchPtr(Assembler::NotEqual, Address(obj, JSObject::offsetOfType()), typeReg, |
| &failureUnstow); |
| regs.add(typeReg); |
| |
| // Stack is now: { ..., rhs-value, object-value, key-value, maybe?-RET-ADDR } |
| // Load rhs-value in to R0 |
| masm.loadValue(Address(BaselineStackReg, 2 * sizeof(Value) + ICStackValueOffset), R0); |
| |
| // Call the type-update stub. |
| if (!callTypeUpdateIC(masm, sizeof(Value))) |
| return false; |
| |
| // Unstow R0 and R1 (object and key) |
| EmitUnstowICValues(masm, 2); |
| |
| // Reset register set. |
| regs = availableGeneralRegs(2); |
| scratchReg = regs.takeAny(); |
| |
| // Unbox object and key. |
| obj = masm.extractObject(R0, ExtractTemp0); |
| Register key = masm.extractInt32(R1, ExtractTemp1); |
| |
| // Load obj->elements in scratchReg. |
| masm.loadPtr(Address(obj, JSObject::offsetOfElements()), scratchReg); |
| |
| // Bounds check. |
| Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength()); |
| masm.branch32(Assembler::BelowOrEqual, initLength, key, &failure); |
| |
| // Hole check. |
| BaseIndex element(scratchReg, key, TimesEight); |
| masm.branchTestMagic(Assembler::Equal, element, &failure); |
| |
| // Failure is not possible now. Free up registers. |
| regs.add(R0); |
| regs.add(R1); |
| regs.takeUnchecked(obj); |
| regs.takeUnchecked(key); |
| Address valueAddr(BaselineStackReg, ICStackValueOffset); |
| |
| // Convert int32 values to double if convertDoubleElements is set. In this |
| // case the heap typeset is guaranteed to contain both int32 and double, so |
| // it's okay to store a double. |
| Label dontConvertDoubles; |
| Address elementsFlags(scratchReg, ObjectElements::offsetOfFlags()); |
| masm.branchTest32(Assembler::Zero, elementsFlags, |
| Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS), |
| &dontConvertDoubles); |
| // Note that double arrays are only created by IonMonkey, so if we have no |
| // floating-point support Ion is disabled and there should be no double arrays. |
| if (cx->runtime()->jitSupportsFloatingPoint) |
| masm.convertInt32ValueToDouble(valueAddr, regs.getAny(), &dontConvertDoubles); |
| else |
| masm.breakpoint(); |
| masm.bind(&dontConvertDoubles); |
| |
| // Don't overwrite R0 becuase |obj| might overlap with it, and it's needed |
| // for post-write barrier later. |
| ValueOperand tmpVal = regs.takeAnyValue(); |
| masm.loadValue(valueAddr, tmpVal); |
| EmitPreBarrier(masm, element, MIRType_Value); |
| masm.storeValue(tmpVal, element); |
| regs.add(key); |
| regs.add(tmpVal); |
| #ifdef JSGC_GENERATIONAL |
| Label skipBarrier; |
| masm.branchTestObject(Assembler::NotEqual, tmpVal, &skipBarrier); |
| { |
| Register r = regs.takeAny(); |
| GeneralRegisterSet saveRegs; |
| emitPostWriteBarrierSlot(masm, obj, r, saveRegs); |
| regs.add(r); |
| } |
| masm.bind(&skipBarrier); |
| #endif |
| EmitReturnFromIC(masm); |
| |
| |
| // Failure case - fail but first unstow R0 and R1 |
| masm.bind(&failureUnstow); |
| EmitUnstowICValues(masm, 2); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| static bool |
| GetProtoShapes(JSObject *obj, size_t protoChainDepth, AutoShapeVector *shapes) |
| { |
| JS_ASSERT(shapes->length() == 1); |
| JSObject *curProto = obj->getProto(); |
| for (size_t i = 0; i < protoChainDepth; i++) { |
| if (!shapes->append(curProto->lastProperty())) |
| return false; |
| curProto = curProto->getProto(); |
| } |
| JS_ASSERT(!curProto); |
| return true; |
| } |
| |
| // |
| // SetElem_DenseAdd |
| // |
| |
| ICUpdatedStub * |
| ICSetElemDenseAddCompiler::getStub(ICStubSpace *space) |
| { |
| AutoShapeVector shapes(cx); |
| if (!shapes.append(obj_->lastProperty())) |
| return NULL; |
| |
| if (!GetProtoShapes(obj_, protoChainDepth_, &shapes)) |
| return NULL; |
| |
| JS_STATIC_ASSERT(ICSetElem_DenseAdd::MAX_PROTO_CHAIN_DEPTH == 4); |
| |
| ICUpdatedStub *stub = NULL; |
| switch (protoChainDepth_) { |
| case 0: stub = getStubSpecific<0>(space, &shapes); break; |
| case 1: stub = getStubSpecific<1>(space, &shapes); break; |
| case 2: stub = getStubSpecific<2>(space, &shapes); break; |
| case 3: stub = getStubSpecific<3>(space, &shapes); break; |
| case 4: stub = getStubSpecific<4>(space, &shapes); break; |
| default: JS_NOT_REACHED("ProtoChainDepth too high."); |
| } |
| if (!stub || !stub->initUpdatingChain(cx, space)) |
| return NULL; |
| return stub; |
| } |
| |
| bool |
| ICSetElemDenseAddCompiler::generateStubCode(MacroAssembler &masm) |
| { |
| // R0 = object |
| // R1 = key |
| // Stack = { ... rhs-value, <return-addr>? } |
| Label failure; |
| Label failureUnstow; |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| masm.branchTestInt32(Assembler::NotEqual, R1, &failure); |
| |
| GeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratchReg = regs.takeAny(); |
| |
| // Unbox R0 and guard on its shape. |
| Register obj = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(BaselineStubReg, ICSetElem_DenseAddImpl<0>::offsetOfShape(0)), |
| scratchReg); |
| masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure); |
| |
| // Stow both R0 and R1 (object and key) |
| // But R0 and R1 still hold their values. |
| EmitStowICValues(masm, 2); |
| |
| // We may need to free up some registers. |
| regs = availableGeneralRegs(0); |
| regs.take(R0); |
| |
| // Guard that the type object matches. |
| Register typeReg = regs.takeAny(); |
| masm.loadPtr(Address(BaselineStubReg, ICSetElem_DenseAdd::offsetOfType()), typeReg); |
| masm.branchPtr(Assembler::NotEqual, Address(obj, JSObject::offsetOfType()), typeReg, |
| &failureUnstow); |
| regs.add(typeReg); |
| |
| // Shape guard objects on the proto chain. |
| scratchReg = regs.takeAny(); |
| Register protoReg = regs.takeAny(); |
| for (size_t i = 0; i < protoChainDepth_; i++) { |
| masm.loadObjProto(i == 0 ? obj : protoReg, protoReg); |
| masm.branchTestPtr(Assembler::Zero, protoReg, protoReg, &failureUnstow); |
| masm.loadPtr(Address(BaselineStubReg, ICSetElem_DenseAddImpl<0>::offsetOfShape(i + 1)), |
| scratchReg); |
| masm.branchTestObjShape(Assembler::NotEqual, protoReg, scratchReg, &failureUnstow); |
| } |
| regs.add(protoReg); |
| regs.add(scratchReg); |
| |
| // Stack is now: { ..., rhs-value, object-value, key-value, maybe?-RET-ADDR } |
| // Load rhs-value in to R0 |
| masm.loadValue(Address(BaselineStackReg, 2 * sizeof(Value) + ICStackValueOffset), R0); |
| |
| // Call the type-update stub. |
| if (!callTypeUpdateIC(masm, sizeof(Value))) |
| return false; |
| |
| // Unstow R0 and R1 (object and key) |
| EmitUnstowICValues(masm, 2); |
| |
| // Reset register set. |
| regs = availableGeneralRegs(2); |
| scratchReg = regs.takeAny(); |
| |
| // Unbox obj and key. |
| obj = masm.extractObject(R0, ExtractTemp0); |
| Register key = masm.extractInt32(R1, ExtractTemp1); |
| |
| // Load obj->elements in scratchReg. |
| masm.loadPtr(Address(obj, JSObject::offsetOfElements()), scratchReg); |
| |
| // Bounds check (key == initLength) |
| Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength()); |
| masm.branch32(Assembler::NotEqual, initLength, key, &failure); |
| |
| // Capacity check. |
| Address capacity(scratchReg, ObjectElements::offsetOfCapacity()); |
| masm.branch32(Assembler::BelowOrEqual, capacity, key, &failure); |
| |
| // Failure is not possible now. Free up registers. |
| regs.add(R0); |
| regs.add(R1); |
| regs.takeUnchecked(obj); |
| regs.takeUnchecked(key); |
| |
| // Increment initLength before write. |
| masm.add32(Imm32(1), initLength); |
| |
| // If length is now <= key, increment length before write. |
| Label skipIncrementLength; |
| Address length(scratchReg, ObjectElements::offsetOfLength()); |
| masm.branch32(Assembler::Above, length, key, &skipIncrementLength); |
| masm.add32(Imm32(1), length); |
| masm.bind(&skipIncrementLength); |
| |
| Address valueAddr(BaselineStackReg, ICStackValueOffset); |
| |
| // Convert int32 values to double if convertDoubleElements is set. In this |
| // case the heap typeset is guaranteed to contain both int32 and double, so |
| // it's okay to store a double. |
| Label dontConvertDoubles; |
| Address elementsFlags(scratchReg, ObjectElements::offsetOfFlags()); |
| masm.branchTest32(Assembler::Zero, elementsFlags, |
| Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS), |
| &dontConvertDoubles); |
| // Note that double arrays are only created by IonMonkey, so if we have no |
| // floating-point support Ion is disabled and there should be no double arrays. |
| if (cx->runtime()->jitSupportsFloatingPoint) |
| masm.convertInt32ValueToDouble(valueAddr, regs.getAny(), &dontConvertDoubles); |
| else |
| masm.breakpoint(); |
| masm.bind(&dontConvertDoubles); |
| |
| // Write the value. No need for pre-barrier since we're not overwriting an old value. |
| ValueOperand tmpVal = regs.takeAnyValue(); |
| BaseIndex element(scratchReg, key, TimesEight); |
| masm.loadValue(valueAddr, tmpVal); |
| masm.storeValue(tmpVal, element); |
| regs.add(key); |
| regs.add(tmpVal); |
| #ifdef JSGC_GENERATIONAL |
| Label skipBarrier; |
| masm.branchTestObject(Assembler::NotEqual, tmpVal, &skipBarrier); |
| { |
| Register r = regs.takeAny(); |
| GeneralRegisterSet saveRegs; |
| emitPostWriteBarrierSlot(masm, obj, r, saveRegs); |
| regs.add(r); |
| } |
| masm.bind(&skipBarrier); |
| #endif |
| EmitReturnFromIC(masm); |
| |
| // Failure case - fail but first unstow R0 and R1 |
| masm.bind(&failureUnstow); |
| EmitUnstowICValues(masm, 2); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // SetElem_TypedArray |
| // |
| |
| bool |
| ICSetElem_TypedArray::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| masm.branchTestInt32(Assembler::NotEqual, R1, &failure); |
| |
| GeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratchReg = regs.takeAny(); |
| |
| // Unbox R0 and shape guard. |
| Register obj = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(BaselineStubReg, ICSetElem_TypedArray::offsetOfShape()), scratchReg); |
| masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure); |
| |
| // Unbox key. |
| Register key = masm.extractInt32(R1, ExtractTemp1); |
| |
| // Bounds check. |
| Label oobWrite; |
| masm.unboxInt32(Address(obj, TypedArray::lengthOffset()), scratchReg); |
| masm.branch32(Assembler::BelowOrEqual, scratchReg, key, |
| expectOutOfBounds_ ? &oobWrite : &failure); |
| |
| // Load the elements vector. |
| masm.loadPtr(Address(obj, TypedArray::dataOffset()), scratchReg); |
| |
| BaseIndex dest(scratchReg, key, ScaleFromElemWidth(TypedArray::slotWidth(type_))); |
| Address value(BaselineStackReg, ICStackValueOffset); |
| |
| // We need a second scratch register. It's okay to clobber the type tag of |
| // R0 or R1, as long as it's restored before jumping to the next stub. |
| regs = availableGeneralRegs(0); |
| regs.takeUnchecked(obj); |
| regs.takeUnchecked(key); |
| regs.take(scratchReg); |
| Register secondScratch = regs.takeAny(); |
| |
| if (type_ == TypedArray::TYPE_FLOAT32 || type_ == TypedArray::TYPE_FLOAT64) { |
| masm.ensureDouble(value, FloatReg0, &failure); |
| masm.storeToTypedFloatArray(type_, FloatReg0, dest); |
| EmitReturnFromIC(masm); |
| } else if (type_ == TypedArray::TYPE_UINT8_CLAMPED) { |
| Label notInt32; |
| masm.branchTestInt32(Assembler::NotEqual, value, ¬Int32); |
| masm.unboxInt32(value, secondScratch); |
| masm.clampIntToUint8(secondScratch, secondScratch); |
| |
| Label clamped; |
| masm.bind(&clamped); |
| masm.storeToTypedIntArray(type_, secondScratch, dest); |
| EmitReturnFromIC(masm); |
| |
| // If the value is a double, clamp to uint8 and jump back. |
| // Else, jump to failure. |
| masm.bind(¬Int32); |
| if (cx->runtime()->jitSupportsFloatingPoint) { |
| masm.branchTestDouble(Assembler::NotEqual, value, &failure); |
| masm.unboxDouble(value, FloatReg0); |
| masm.clampDoubleToUint8(FloatReg0, secondScratch); |
| masm.jump(&clamped); |
| } else { |
| masm.jump(&failure); |
| } |
| } else { |
| Label notInt32; |
| masm.branchTestInt32(Assembler::NotEqual, value, ¬Int32); |
| masm.unboxInt32(value, secondScratch); |
| |
| Label isInt32; |
| masm.bind(&isInt32); |
| masm.storeToTypedIntArray(type_, secondScratch, dest); |
| EmitReturnFromIC(masm); |
| |
| // If the value is a double, truncate and jump back. |
| // Else, jump to failure. |
| Label failureRestoreRegs; |
| masm.bind(¬Int32); |
| if (cx->runtime()->jitSupportsFloatingPoint) { |
| masm.branchTestDouble(Assembler::NotEqual, value, &failure); |
| masm.unboxDouble(value, FloatReg0); |
| masm.branchTruncateDouble(FloatReg0, secondScratch, &failureRestoreRegs); |
| masm.jump(&isInt32); |
| } else { |
| masm.jump(&failure); |
| } |
| |
| // Writing to secondScratch may have clobbered R0 or R1, restore them |
| // first. |
| masm.bind(&failureRestoreRegs); |
| masm.tagValue(JSVAL_TYPE_OBJECT, obj, R0); |
| masm.tagValue(JSVAL_TYPE_INT32, key, R1); |
| } |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| |
| if (expectOutOfBounds_) { |
| masm.bind(&oobWrite); |
| EmitReturnFromIC(masm); |
| } |
| return true; |
| } |
| |
| // |
| // In_Fallback |
| // |
| |
| static bool |
| DoInFallback(JSContext *cx, ICIn_Fallback *stub, HandleValue key, HandleValue objValue, |
| MutableHandleValue res) |
| { |
| FallbackICSpew(cx, stub, "In"); |
| |
| if (!objValue.isObject()) { |
| js_ReportValueError(cx, JSMSG_IN_NOT_OBJECT, -1, objValue, NullPtr()); |
| return false; |
| } |
| |
| RootedObject obj(cx, &objValue.toObject()); |
| |
| JSBool cond = false; |
| if (!OperatorIn(cx, key, obj, &cond)) |
| return false; |
| |
| res.setBoolean(cond); |
| return true; |
| } |
| |
| typedef bool (*DoInFallbackFn)(JSContext *, ICIn_Fallback *, HandleValue, HandleValue, |
| MutableHandleValue); |
| static const VMFunction DoInFallbackInfo = |
| FunctionInfo<DoInFallbackFn>(DoInFallback, PopValues(2)); |
| |
| bool |
| ICIn_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| EmitRestoreTailCallReg(masm); |
| |
| // Sync for the decompiler. |
| masm.pushValue(R0); |
| masm.pushValue(R1); |
| |
| // Push arguments. |
| masm.pushValue(R1); |
| masm.pushValue(R0); |
| masm.push(BaselineStubReg); |
| |
| return tailCallVM(DoInFallbackInfo, masm); |
| } |
| |
| // Attach an optimized stub for a GETGNAME/CALLGNAME op. |
| static bool |
| TryAttachGlobalNameStub(JSContext *cx, HandleScript script, ICGetName_Fallback *stub, |
| HandleObject global, HandlePropertyName name) |
| { |
| JS_ASSERT(global->is<GlobalObject>()); |
| |
| RootedId id(cx, NameToId(name)); |
| |
| // The property must be found, and it must be found as a normal data property. |
| RootedShape shape(cx, global->nativeLookup(cx, id)); |
| if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot()) |
| return true; |
| |
| JS_ASSERT(shape->slot() >= global->numFixedSlots()); |
| uint32_t slot = shape->slot() - global->numFixedSlots(); |
| |
| // TODO: if there's a previous stub discard it, or just update its Shape + slot? |
| |
| ICStub *monitorStub = stub->fallbackMonitorStub()->firstMonitorStub(); |
| IonSpew(IonSpew_BaselineIC, " Generating GetName(GlobalName) stub"); |
| ICGetName_Global::Compiler compiler(cx, monitorStub, global->lastProperty(), slot); |
| ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| return true; |
| } |
| |
| static bool |
| TryAttachScopeNameStub(JSContext *cx, HandleScript script, ICGetName_Fallback *stub, |
| HandleObject initialScopeChain, HandlePropertyName name) |
| { |
| AutoShapeVector shapes(cx); |
| RootedId id(cx, NameToId(name)); |
| RootedObject scopeChain(cx, initialScopeChain); |
| |
| Shape *shape = NULL; |
| while (scopeChain) { |
| if (!shapes.append(scopeChain->lastProperty())) |
| return false; |
| |
| if (scopeChain->is<GlobalObject>()) { |
| shape = scopeChain->nativeLookup(cx, id); |
| if (shape) |
| break; |
| return true; |
| } |
| |
| if (!scopeChain->is<ScopeObject>() || scopeChain->is<WithObject>()) |
| return true; |
| |
| // Check for an 'own' property on the scope. There is no need to |
| // check the prototype as non-with scopes do not inherit properties |
| // from any prototype. |
| shape = scopeChain->nativeLookup(cx, id); |
| if (shape) |
| break; |
| |
| scopeChain = scopeChain->enclosingScope(); |
| } |
| |
| if (!IsCacheableGetPropReadSlot(scopeChain, scopeChain, shape)) |
| return true; |
| |
| bool isFixedSlot; |
| uint32_t offset; |
| GetFixedOrDynamicSlotOffset(scopeChain, shape->slot(), &isFixedSlot, &offset); |
| |
| ICStub *monitorStub = stub->fallbackMonitorStub()->firstMonitorStub(); |
| ICStub *newStub; |
| |
| switch (shapes.length()) { |
| case 1: { |
| ICGetName_Scope<0>::Compiler compiler(cx, monitorStub, &shapes, isFixedSlot, offset); |
| newStub = compiler.getStub(compiler.getStubSpace(script)); |
| break; |
| } |
| case 2: { |
| ICGetName_Scope<1>::Compiler compiler(cx, monitorStub, &shapes, isFixedSlot, offset); |
| newStub = compiler.getStub(compiler.getStubSpace(script)); |
| break; |
| } |
| case 3: { |
| ICGetName_Scope<2>::Compiler compiler(cx, monitorStub, &shapes, isFixedSlot, offset); |
| newStub = compiler.getStub(compiler.getStubSpace(script)); |
| break; |
| } |
| case 4: { |
| ICGetName_Scope<3>::Compiler compiler(cx, monitorStub, &shapes, isFixedSlot, offset); |
| newStub = compiler.getStub(compiler.getStubSpace(script)); |
| break; |
| } |
| case 5: { |
| ICGetName_Scope<4>::Compiler compiler(cx, monitorStub, &shapes, isFixedSlot, offset); |
| newStub = compiler.getStub(compiler.getStubSpace(script)); |
| break; |
| } |
| case 6: { |
| ICGetName_Scope<5>::Compiler compiler(cx, monitorStub, &shapes, isFixedSlot, offset); |
| newStub = compiler.getStub(compiler.getStubSpace(script)); |
| break; |
| } |
| case 7: { |
| ICGetName_Scope<6>::Compiler compiler(cx, monitorStub, &shapes, isFixedSlot, offset); |
| newStub = compiler.getStub(compiler.getStubSpace(script)); |
| break; |
| } |
| default: |
| return true; |
| } |
| |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| return true; |
| } |
| |
| static bool |
| DoGetNameFallback(JSContext *cx, BaselineFrame *frame, ICGetName_Fallback *stub, |
| HandleObject scopeChain, MutableHandleValue res) |
| { |
| RootedScript script(cx, frame->script()); |
| jsbytecode *pc = stub->icEntry()->pc(script); |
| mozilla::DebugOnly<JSOp> op = JSOp(*pc); |
| FallbackICSpew(cx, stub, "GetName(%s)", js_CodeName[JSOp(*pc)]); |
| |
| JS_ASSERT(op == JSOP_NAME || op == JSOP_CALLNAME || op == JSOP_GETGNAME || op == JSOP_CALLGNAME); |
| |
| RootedPropertyName name(cx, script->getName(pc)); |
| |
| if (JSOp(pc[JSOP_GETGNAME_LENGTH]) == JSOP_TYPEOF) { |
| if (!GetScopeNameForTypeOf(cx, scopeChain, name, res)) |
| return false; |
| } else { |
| if (!GetScopeName(cx, scopeChain, name, res)) |
| return false; |
| } |
| |
| types::TypeScript::Monitor(cx, script, pc, res); |
| |
| // Add a type monitor stub for the resulting value. |
| if (!stub->addMonitorStubForValue(cx, script, res)) |
| return false; |
| |
| // Attach new stub. |
| if (stub->numOptimizedStubs() >= ICGetName_Fallback::MAX_OPTIMIZED_STUBS) { |
| // TODO: Discard all stubs in this IC and replace with generic stub. |
| return true; |
| } |
| |
| if (js_CodeSpec[*pc].format & JOF_GNAME) { |
| if (!TryAttachGlobalNameStub(cx, script, stub, scopeChain, name)) |
| return false; |
| } else { |
| if (!TryAttachScopeNameStub(cx, script, stub, scopeChain, name)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| typedef bool (*DoGetNameFallbackFn)(JSContext *, BaselineFrame *, ICGetName_Fallback *, |
| HandleObject, MutableHandleValue); |
| static const VMFunction DoGetNameFallbackInfo = FunctionInfo<DoGetNameFallbackFn>(DoGetNameFallback); |
| |
| bool |
| ICGetName_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| JS_ASSERT(R0 == JSReturnOperand); |
| |
| EmitRestoreTailCallReg(masm); |
| |
| masm.push(R0.scratchReg()); |
| masm.push(BaselineStubReg); |
| masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| return tailCallVM(DoGetNameFallbackInfo, masm); |
| } |
| |
| bool |
| ICGetName_Global::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| Register obj = R0.scratchReg(); |
| Register scratch = R1.scratchReg(); |
| |
| // Shape guard. |
| masm.loadPtr(Address(BaselineStubReg, ICGetName_Global::offsetOfShape()), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, obj, scratch, &failure); |
| |
| // Load dynamic slot. |
| masm.loadPtr(Address(obj, JSObject::offsetOfSlots()), obj); |
| masm.load32(Address(BaselineStubReg, ICGetName_Global::offsetOfSlot()), scratch); |
| masm.loadValue(BaseIndex(obj, scratch, TimesEight), R0); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| template <size_t NumHops> |
| bool |
| ICGetName_Scope<NumHops>::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| GeneralRegisterSet regs(availableGeneralRegs(1)); |
| Register obj = R0.scratchReg(); |
| Register walker = regs.takeAny(); |
| Register scratch = regs.takeAny(); |
| |
| // Use a local to silence Clang tautological-compare warning if NumHops is 0. |
| size_t numHops = NumHops; |
| |
| for (size_t index = 0; index < NumHops + 1; index++) { |
| Register scope = index ? walker : obj; |
| |
| // Shape guard. |
| masm.loadPtr(Address(BaselineStubReg, ICGetName_Scope::offsetOfShape(index)), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, scope, scratch, &failure); |
| |
| if (index < numHops) |
| masm.extractObject(Address(scope, ScopeObject::offsetOfEnclosingScope()), walker); |
| } |
| |
| Register scope = NumHops ? walker : obj; |
| |
| if (!isFixedSlot_) { |
| masm.loadPtr(Address(scope, JSObject::offsetOfSlots()), walker); |
| scope = walker; |
| } |
| |
| masm.load32(Address(BaselineStubReg, ICGetName_Scope::offsetOfOffset()), scratch); |
| masm.loadValue(BaseIndex(scope, scratch, TimesOne), R0); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // BindName_Fallback |
| // |
| |
| static bool |
| DoBindNameFallback(JSContext *cx, BaselineFrame *frame, ICBindName_Fallback *stub, |
| HandleObject scopeChain, MutableHandleValue res) |
| { |
| jsbytecode *pc = stub->icEntry()->pc(frame->script()); |
| mozilla::DebugOnly<JSOp> op = JSOp(*pc); |
| FallbackICSpew(cx, stub, "BindName(%s)", js_CodeName[JSOp(*pc)]); |
| |
| JS_ASSERT(op == JSOP_BINDNAME); |
| |
| RootedPropertyName name(cx, frame->script()->getName(pc)); |
| |
| RootedObject scope(cx); |
| if (!LookupNameWithGlobalDefault(cx, name, scopeChain, &scope)) |
| return false; |
| |
| res.setObject(*scope); |
| return true; |
| } |
| |
| typedef bool (*DoBindNameFallbackFn)(JSContext *, BaselineFrame *, ICBindName_Fallback *, |
| HandleObject, MutableHandleValue); |
| static const VMFunction DoBindNameFallbackInfo = |
| FunctionInfo<DoBindNameFallbackFn>(DoBindNameFallback); |
| |
| bool |
| ICBindName_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| JS_ASSERT(R0 == JSReturnOperand); |
| |
| EmitRestoreTailCallReg(masm); |
| |
| masm.push(R0.scratchReg()); |
| masm.push(BaselineStubReg); |
| masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| return tailCallVM(DoBindNameFallbackInfo, masm); |
| } |
| |
| // |
| // GetIntrinsic_Fallback |
| // |
| |
| static bool |
| DoGetIntrinsicFallback(JSContext *cx, BaselineFrame *frame, ICGetIntrinsic_Fallback *stub, |
| MutableHandleValue res) |
| { |
| RootedScript script(cx, frame->script()); |
| jsbytecode *pc = stub->icEntry()->pc(script); |
| mozilla::DebugOnly<JSOp> op = JSOp(*pc); |
| FallbackICSpew(cx, stub, "GetIntrinsic(%s)", js_CodeName[JSOp(*pc)]); |
| |
| JS_ASSERT(op == JSOP_GETINTRINSIC || op == JSOP_CALLINTRINSIC); |
| |
| if (!GetIntrinsicOperation(cx, pc, res)) |
| return false; |
| |
| // An intrinsic operation will always produce the same result, so only |
| // needs to be monitored once. Attach a stub to load the resulting constant |
| // directly. |
| |
| types::TypeScript::Monitor(cx, script, pc, res); |
| |
| IonSpew(IonSpew_BaselineIC, " Generating GetIntrinsic optimized stub"); |
| ICGetIntrinsic_Constant::Compiler compiler(cx, res); |
| ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| return true; |
| } |
| |
| typedef bool (*DoGetIntrinsicFallbackFn)(JSContext *, BaselineFrame *, ICGetIntrinsic_Fallback *, |
| MutableHandleValue); |
| static const VMFunction DoGetIntrinsicFallbackInfo = |
| FunctionInfo<DoGetIntrinsicFallbackFn>(DoGetIntrinsicFallback); |
| |
| bool |
| ICGetIntrinsic_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| EmitRestoreTailCallReg(masm); |
| |
| masm.push(BaselineStubReg); |
| masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| return tailCallVM(DoGetIntrinsicFallbackInfo, masm); |
| } |
| |
| bool |
| ICGetIntrinsic_Constant::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| masm.loadValue(Address(BaselineStubReg, ICGetIntrinsic_Constant::offsetOfValue()), R0); |
| |
| EmitReturnFromIC(masm); |
| return true; |
| } |
| |
| // |
| // GetProp_Fallback |
| // |
| |
| static bool |
| TryAttachLengthStub(JSContext *cx, HandleScript script, ICGetProp_Fallback *stub, HandleValue val, |
| HandleValue res, bool *attached) |
| { |
| JS_ASSERT(!*attached); |
| |
| if (val.isString()) { |
| JS_ASSERT(res.isInt32()); |
| IonSpew(IonSpew_BaselineIC, " Generating GetProp(String.length) stub"); |
| ICGetProp_StringLength::Compiler compiler(cx); |
| ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| *attached = true; |
| stub->addNewStub(newStub); |
| return true; |
| } |
| |
| if (val.isMagic(JS_OPTIMIZED_ARGUMENTS) && res.isInt32()) { |
| IonSpew(IonSpew_BaselineIC, " Generating GetProp(MagicArgs.length) stub"); |
| ICGetProp_ArgumentsLength::Compiler compiler(cx, ICGetProp_ArgumentsLength::Magic); |
| ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| *attached = true; |
| stub->addNewStub(newStub); |
| return true; |
| } |
| |
| if (!val.isObject()) |
| return true; |
| |
| RootedObject obj(cx, &val.toObject()); |
| |
| if (obj->isArray() && res.isInt32()) { |
| IonSpew(IonSpew_BaselineIC, " Generating GetProp(Array.length) stub"); |
| ICGetProp_ArrayLength::Compiler compiler(cx); |
| ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| *attached = true; |
| stub->addNewStub(newStub); |
| return true; |
| } |
| if (obj->isTypedArray()) { |
| JS_ASSERT(res.isInt32()); |
| IonSpew(IonSpew_BaselineIC, " Generating GetProp(TypedArray.length) stub"); |
| ICGetProp_TypedArrayLength::Compiler compiler(cx); |
| ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| *attached = true; |
| stub->addNewStub(newStub); |
| return true; |
| } |
| |
| if (obj->is<ArgumentsObject>() && res.isInt32()) { |
| IonSpew(IonSpew_BaselineIC, " Generating GetProp(ArgsObj.length %s) stub", |
| obj->is<StrictArgumentsObject>() ? "Strict" : "Normal"); |
| ICGetProp_ArgumentsLength::Which which = ICGetProp_ArgumentsLength::Normal; |
| if (obj->is<StrictArgumentsObject>()) |
| which = ICGetProp_ArgumentsLength::Strict; |
| ICGetProp_ArgumentsLength::Compiler compiler(cx, which); |
| ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| *attached = true; |
| stub->addNewStub(newStub); |
| return true; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| UpdateExistingGenerationalDOMProxyStub(ICGetProp_Fallback *stub, |
| HandleObject obj) |
| { |
| Value expandoSlot = obj->getFixedSlot(GetDOMProxyExpandoSlot()); |
| JS_ASSERT(!expandoSlot.isObject() && !expandoSlot.isUndefined()); |
| ExpandoAndGeneration *expandoAndGeneration = (ExpandoAndGeneration*)expandoSlot.toPrivate(); |
| for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) { |
| if (iter->isGetProp_CallDOMProxyWithGenerationNative()) { |
| ICGetProp_CallDOMProxyWithGenerationNative* updateStub = |
| iter->toGetProp_CallDOMProxyWithGenerationNative(); |
| if (updateStub->expandoAndGeneration() == expandoAndGeneration) { |
| // Update generation |
| uint32_t generation = expandoAndGeneration->generation; |
| IonSpew(IonSpew_BaselineIC, |
| " Updating existing stub with generation, old value: %i, " |
| "new value: %i", updateStub->generation(), |
| generation); |
| updateStub->setGeneration(generation); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| static bool |
| TryAttachNativeGetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, |
| ICGetProp_Fallback *stub, HandlePropertyName name, |
| HandleValue val, HandleValue res, bool *attached) |
| { |
| JS_ASSERT(!*attached); |
| |
| if (!val.isObject()) |
| return true; |
| |
| RootedObject obj(cx, &val.toObject()); |
| |
| bool isDOMProxy; |
| bool domProxyHasGeneration; |
| DOMProxyShadowsResult domProxyShadowsResult; |
| RootedShape shape(cx); |
| RootedObject holder(cx); |
| if (!EffectlesslyLookupProperty(cx, obj, name, &holder, &shape, &isDOMProxy, |
| &domProxyShadowsResult, &domProxyHasGeneration)) |
| { |
| return false; |
| } |
| |
| if (!isDOMProxy && !obj->isNative()) |
| return true; |
| |
| ICStub *monitorStub = stub->fallbackMonitorStub()->firstMonitorStub(); |
| if (!isDOMProxy && IsCacheableGetPropReadSlot(obj, holder, shape)) { |
| bool isFixedSlot; |
| uint32_t offset; |
| GetFixedOrDynamicSlotOffset(holder, shape->slot(), &isFixedSlot, &offset); |
| |
| ICStub::Kind kind = (obj == holder) ? ICStub::GetProp_Native |
| : ICStub::GetProp_NativePrototype; |
| |
| IonSpew(IonSpew_BaselineIC, " Generating GetProp(%s %s) stub", |
| isDOMProxy ? "DOMProxy" : "Native", |
| (obj == holder) ? "direct" : "prototype"); |
| ICGetPropNativeCompiler compiler(cx, kind, monitorStub, obj, holder, isFixedSlot, offset); |
| ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| bool isScripted = false; |
| bool cacheableCall = IsCacheableGetPropCall(obj, holder, shape, &isScripted, isDOMProxy); |
| |
| // Try handling scripted getters. |
| if (cacheableCall && isScripted && !isDOMProxy) { |
| RootedFunction callee(cx, &shape->getterObject()->as<JSFunction>()); |
| JS_ASSERT(obj != holder); |
| JS_ASSERT(callee->hasScript()); |
| |
| IonSpew(IonSpew_BaselineIC, " Generating GetProp(NativeObj/ScriptedGetter %s:%d) stub", |
| callee->nonLazyScript()->filename(), callee->nonLazyScript()->lineno); |
| |
| ICGetProp_CallScripted::Compiler compiler(cx, monitorStub, obj, holder, callee, |
| pc - script->code); |
| ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| // Try handling JSNative getters. |
| if (cacheableCall && !isScripted) { |
| RootedFunction callee(cx, &shape->getterObject()->as<JSFunction>()); |
| JS_ASSERT(obj != holder); |
| JS_ASSERT(callee->isNative()); |
| |
| IonSpew(IonSpew_BaselineIC, " Generating GetProp(%s%s/NativeGetter %p) stub", |
| isDOMProxy ? "DOMProxyObj" : "NativeObj", |
| isDOMProxy && domProxyHasGeneration ? "WithGeneration" : "", |
| callee->native()); |
| |
| ICStub *newStub = NULL; |
| if (isDOMProxy) { |
| ICStub::Kind kind; |
| if (domProxyHasGeneration) { |
| if (UpdateExistingGenerationalDOMProxyStub(stub, obj)) { |
| *attached = true; |
| return true; |
| } |
| kind = ICStub::GetProp_CallDOMProxyWithGenerationNative; |
| } else { |
| kind = ICStub::GetProp_CallDOMProxyNative; |
| } |
| ICGetPropCallDOMProxyNativeCompiler compiler(cx, kind, monitorStub, obj, holder, callee, |
| pc - script->code); |
| newStub = compiler.getStub(compiler.getStubSpace(script)); |
| } else { |
| ICGetProp_CallNative::Compiler compiler(cx, monitorStub, obj, holder, callee, |
| pc - script->code); |
| newStub = compiler.getStub(compiler.getStubSpace(script)); |
| } |
| if (!newStub) |
| return false; |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| // If it's a shadowed listbase proxy property, attach stub to call Proxy::get instead. |
| if (isDOMProxy && domProxyShadowsResult == Shadows) { |
| JS_ASSERT(obj == holder); |
| |
| IonSpew(IonSpew_BaselineIC, " Generating GetProp(DOMProxyProxy) stub"); |
| ICGetProp_DOMProxyShadowed::Compiler compiler(cx, monitorStub, obj, name, |
| pc - script->code); |
| ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| TryAttachStringGetPropStub(JSContext *cx, HandleScript script, ICGetProp_Fallback *stub, |
| HandlePropertyName name, HandleValue val, HandleValue res, |
| bool *attached) |
| { |
| JS_ASSERT(!*attached); |
| JS_ASSERT(val.isString()); |
| |
| RootedObject stringProto(cx, script->global().getOrCreateStringPrototype(cx)); |
| if (!stringProto) |
| return false; |
| |
| // For now, only look for properties directly set on String.prototype |
| RootedId propId(cx, NameToId(name)); |
| RootedShape shape(cx, stringProto->nativeLookup(cx, propId)); |
| if (!shape || !shape->hasSlot() || !shape->hasDefaultGetter()) |
| return true; |
| |
| bool isFixedSlot; |
| uint32_t offset; |
| GetFixedOrDynamicSlotOffset(stringProto, shape->slot(), &isFixedSlot, &offset); |
| |
| ICStub *monitorStub = stub->fallbackMonitorStub()->firstMonitorStub(); |
| |
| IonSpew(IonSpew_BaselineIC, " Generating GetProp(String.ID from prototype) stub"); |
| ICGetProp_String::Compiler compiler(cx, monitorStub, stringProto, isFixedSlot, offset); |
| ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| static bool |
| DoGetPropFallback(JSContext *cx, BaselineFrame *frame, ICGetProp_Fallback *stub, |
| MutableHandleValue val, MutableHandleValue res) |
| { |
| RootedScript script(cx, frame->script()); |
| jsbytecode *pc = stub->icEntry()->pc(script); |
| JSOp op = JSOp(*pc); |
| FallbackICSpew(cx, stub, "GetProp(%s)", js_CodeName[op]); |
| |
| JS_ASSERT(op == JSOP_GETPROP || op == JSOP_CALLPROP || op == JSOP_LENGTH || op == JSOP_GETXPROP); |
| |
| RootedPropertyName name(cx, script->getName(pc)); |
| RootedId id(cx, NameToId(name)); |
| |
| if (op == JSOP_LENGTH && val.isMagic(JS_OPTIMIZED_ARGUMENTS)) { |
| // Handle arguments.length access. |
| if (IsOptimizedArguments(frame, val.address())) { |
| res.setInt32(frame->numActualArgs()); |
| |
| // Monitor result |
| types::TypeScript::Monitor(cx, script, pc, res); |
| if (!stub->addMonitorStubForValue(cx, script, res)) |
| return false; |
| |
| bool attached = false; |
| if (!TryAttachLengthStub(cx, script, stub, val, res, &attached)) |
| return false; |
| JS_ASSERT(attached); |
| |
| return true; |
| } |
| } |
| |
| RootedObject obj(cx, ToObjectFromStack(cx, val)); |
| if (!obj) |
| return false; |
| |
| if (obj->getOps()->getProperty) { |
| if (!JSObject::getGeneric(cx, obj, obj, id, res)) |
| return false; |
| } else { |
| if (!GetPropertyHelper(cx, obj, id, 0, res)) |
| return false; |
| } |
| |
| #if JS_HAS_NO_SUCH_METHOD |
| // Handle objects with __noSuchMethod__. |
| if (op == JSOP_CALLPROP && JS_UNLIKELY(res.isPrimitive()) && val.isObject()) { |
| if (!OnUnknownMethod(cx, obj, IdToValue(id), res)) |
| return false; |
| } |
| #endif |
| |
| types::TypeScript::Monitor(cx, script, pc, res); |
| |
| // Add a type monitor stub for the resulting value. |
| if (!stub->addMonitorStubForValue(cx, script, res)) |
| return false; |
| |
| if (stub->numOptimizedStubs() >= ICGetProp_Fallback::MAX_OPTIMIZED_STUBS) { |
| // TODO: Discard all stubs in this IC and replace with generic getprop stub. |
| return true; |
| } |
| |
| bool attached = false; |
| |
| if (op == JSOP_LENGTH) { |
| if (!TryAttachLengthStub(cx, script, stub, val, res, &attached)) |
| return false; |
| if (attached) |
| return true; |
| } |
| |
| if (!TryAttachNativeGetPropStub(cx, script, pc, stub, name, val, res, &attached)) |
| return false; |
| if (attached) |
| return true; |
| |
| if (val.isString()) { |
| if (!TryAttachStringGetPropStub(cx, script, stub, name, val, res, &attached)) |
| return false; |
| if (attached) |
| return true; |
| } |
| |
| JS_ASSERT(!attached); |
| stub->noteUnoptimizableAccess(); |
| |
| return true; |
| } |
| |
| typedef bool (*DoGetPropFallbackFn)(JSContext *, BaselineFrame *, ICGetProp_Fallback *, |
| MutableHandleValue, MutableHandleValue); |
| static const VMFunction DoGetPropFallbackInfo = |
| FunctionInfo<DoGetPropFallbackFn>(DoGetPropFallback, PopValues(1)); |
| |
| bool |
| ICGetProp_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| JS_ASSERT(R0 == JSReturnOperand); |
| |
| EmitRestoreTailCallReg(masm); |
| |
| // Ensure stack is fully synced for the expression decompiler. |
| masm.pushValue(R0); |
| |
| // Push arguments. |
| masm.pushValue(R0); |
| masm.push(BaselineStubReg); |
| masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| return tailCallVM(DoGetPropFallbackInfo, masm); |
| } |
| |
| bool |
| ICGetProp_ArrayLength::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| Register scratch = R1.scratchReg(); |
| |
| // Unbox R0 and guard it's an array. |
| Register obj = masm.extractObject(R0, ExtractTemp0); |
| masm.branchTestObjClass(Assembler::NotEqual, obj, scratch, &ArrayClass, &failure); |
| |
| // Load obj->elements->length. |
| masm.loadPtr(Address(obj, JSObject::offsetOfElements()), scratch); |
| masm.load32(Address(scratch, ObjectElements::offsetOfLength()), scratch); |
| |
| // Guard length fits in an int32. |
| masm.branchTest32(Assembler::Signed, scratch, scratch, &failure); |
| |
| masm.tagValue(JSVAL_TYPE_INT32, scratch, R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICGetProp_TypedArrayLength::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| Register scratch = R1.scratchReg(); |
| |
| // Unbox R0. |
| Register obj = masm.extractObject(R0, ExtractTemp0); |
| |
| // Implement the negated version of JSObject::isTypedArray predicate. |
| masm.loadObjClass(obj, scratch); |
| masm.branchPtr(Assembler::Below, scratch, ImmWord(&TypedArray::classes[0]), &failure); |
| masm.branchPtr(Assembler::AboveOrEqual, scratch, |
| ImmWord(&TypedArray::classes[TypedArray::TYPE_MAX]), &failure); |
| |
| // Load length from fixed slot. |
| masm.loadValue(Address(obj, TypedArray::lengthOffset()), R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICGetProp_StringLength::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| masm.branchTestString(Assembler::NotEqual, R0, &failure); |
| |
| // Unbox string and load its length. |
| Register string = masm.extractString(R0, ExtractTemp0); |
| masm.loadStringLength(string, string); |
| |
| masm.tagValue(JSVAL_TYPE_INT32, string, R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICGetProp_String::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| masm.branchTestString(Assembler::NotEqual, R0, &failure); |
| |
| GeneralRegisterSet regs(availableGeneralRegs(1)); |
| Register holderReg = regs.takeAny(); |
| Register scratchReg = regs.takeAny(); |
| |
| // Verify the shape of |String.prototype| |
| masm.movePtr(ImmGCPtr(stringPrototype_.get()), holderReg); |
| |
| Address shapeAddr(BaselineStubReg, ICGetProp_String::offsetOfStringProtoShape()); |
| masm.loadPtr(Address(holderReg, JSObject::offsetOfShape()), scratchReg); |
| masm.branchPtr(Assembler::NotEqual, shapeAddr, scratchReg, &failure); |
| |
| if (!isFixedSlot_) |
| masm.loadPtr(Address(holderReg, JSObject::offsetOfSlots()), holderReg); |
| |
| masm.load32(Address(BaselineStubReg, ICGetPropNativeStub::offsetOfOffset()), scratchReg); |
| masm.loadValue(BaseIndex(holderReg, scratchReg, TimesOne), R0); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICGetPropNativeCompiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| GeneralRegisterSet regs(availableGeneralRegs(1)); |
| |
| // Guard input is an object. |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| Register scratch = regs.takeAny(); |
| |
| // Unbox and shape guard. |
| Register objReg = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(BaselineStubReg, ICGetPropNativeStub::offsetOfShape()), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, objReg, scratch, &failure); |
| |
| Register holderReg; |
| if (obj_ == holder_) { |
| holderReg = objReg; |
| } else { |
| // Shape guard holder. |
| holderReg = regs.takeAny(); |
| masm.loadPtr(Address(BaselineStubReg, ICGetProp_NativePrototype::offsetOfHolder()), |
| holderReg); |
| masm.loadPtr(Address(BaselineStubReg, ICGetProp_NativePrototype::offsetOfHolderShape()), |
| scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, &failure); |
| } |
| |
| if (!isFixedSlot_) |
| masm.loadPtr(Address(holderReg, JSObject::offsetOfSlots()), holderReg); |
| |
| masm.load32(Address(BaselineStubReg, ICGetPropNativeStub::offsetOfOffset()), scratch); |
| masm.loadValue(BaseIndex(holderReg, scratch, TimesOne), R0); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICGetProp_CallScripted::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| Label failureLeaveStubFrame; |
| GeneralRegisterSet regs(availableGeneralRegs(1)); |
| Register scratch = regs.takeAnyExcluding(BaselineTailCallReg); |
| |
| // Guard input is an object. |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| // Unbox and shape guard. |
| Register objReg = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(BaselineStubReg, ICGetProp_CallScripted::offsetOfShape()), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, objReg, scratch, &failure); |
| |
| Register holderReg = regs.takeAny(); |
| masm.loadPtr(Address(BaselineStubReg, ICGetProp_CallScripted::offsetOfHolder()), holderReg); |
| masm.loadPtr(Address(BaselineStubReg, ICGetProp_CallScripted::offsetOfHolderShape()), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, &failure); |
| regs.add(holderReg); |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| enterStubFrame(masm, scratch); |
| |
| // Load callee function and code. To ensure that |code| doesn't end up being |
| // ArgumentsRectifierReg, if it's available we assign it to |callee| instead. |
| Register callee; |
| if (regs.has(ArgumentsRectifierReg)) { |
| callee = ArgumentsRectifierReg; |
| regs.take(callee); |
| } else { |
| callee = regs.takeAny(); |
| } |
| Register code = regs.takeAny(); |
| masm.loadPtr(Address(BaselineStubReg, ICGetProp_CallScripted::offsetOfGetter()), callee); |
| masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), code); |
| masm.loadBaselineOrIonRaw(code, code, SequentialExecution, &failureLeaveStubFrame); |
| |
| // Getter is called with 0 arguments, just |obj| as thisv. |
| // Note that we use Push, not push, so that callIon will align the stack |
| // properly on ARM. |
| masm.Push(R0); |
| EmitCreateStubFrameDescriptor(masm, scratch); |
| masm.Push(Imm32(0)); // ActualArgc is 0 |
| masm.Push(callee); |
| masm.Push(scratch); |
| |
| // Handle arguments underflow. |
| Label noUnderflow; |
| masm.load16ZeroExtend(Address(callee, offsetof(JSFunction, nargs)), scratch); |
| masm.branch32(Assembler::Equal, scratch, Imm32(0), &noUnderflow); |
| { |
| // Call the arguments rectifier. |
| JS_ASSERT(ArgumentsRectifierReg != code); |
| |
| IonCode *argumentsRectifier = |
| cx->compartment()->ionCompartment()->getArgumentsRectifier(SequentialExecution); |
| |
| masm.movePtr(ImmGCPtr(argumentsRectifier), code); |
| masm.loadPtr(Address(code, IonCode::offsetOfCode()), code); |
| masm.mov(Imm32(0), ArgumentsRectifierReg); |
| } |
| |
| masm.bind(&noUnderflow); |
| |
| // If needed, update SPS Profiler frame entry. At this point, callee and scratch can |
| // be clobbered. |
| { |
| Label skipProfilerUpdate; |
| |
| // Need to avoid using ArgumentsRectifierReg and code register. |
| GeneralRegisterSet availRegs = availableGeneralRegs(0); |
| availRegs.take(ArgumentsRectifierReg); |
| availRegs.take(code); |
| Register scratch = availRegs.takeAny(); |
| Register pcIdx = availRegs.takeAny(); |
| |
| // Check if profiling is enabled. |
| guardProfilingEnabled(masm, scratch, &skipProfilerUpdate); |
| |
| // Update profiling entry before leaving function. |
| masm.load32(Address(BaselineStubReg, ICGetProp_CallScripted::offsetOfPCOffset()), pcIdx); |
| masm.spsUpdatePCIdx(&cx->runtime()->spsProfiler, pcIdx, scratch); |
| |
| masm.bind(&skipProfilerUpdate); |
| } |
| masm.callIon(code); |
| |
| leaveStubFrame(masm, true); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| // Leave stub frame and go to next stub. |
| masm.bind(&failureLeaveStubFrame); |
| leaveStubFrame(masm, false); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| static bool |
| DoCallNativeGetter(JSContext *cx, HandleFunction callee, HandleObject obj, |
| MutableHandleValue result) |
| { |
| JS_ASSERT(callee->isNative()); |
| JSNative natfun = callee->native(); |
| |
| Value vp[2] = { ObjectValue(*callee.get()), ObjectValue(*obj.get()) }; |
| AutoValueArray rootVp(cx, vp, 2); |
| |
| if (!natfun(cx, 0, vp)) |
| return false; |
| |
| result.set(vp[0]); |
| return true; |
| } |
| |
| typedef bool (*DoCallNativeGetterFn)(JSContext *, HandleFunction, HandleObject, MutableHandleValue); |
| static const VMFunction DoCallNativeGetterInfo = |
| FunctionInfo<DoCallNativeGetterFn>(DoCallNativeGetter); |
| |
| bool |
| ICGetProp_CallNative::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| GeneralRegisterSet regs(availableGeneralRegs(1)); |
| Register scratch = regs.takeAnyExcluding(BaselineTailCallReg); |
| |
| // Guard input is an object. |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| // Unbox and shape guard. |
| Register objReg = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(BaselineStubReg, ICGetProp_CallNative::offsetOfShape()), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, objReg, scratch, &failure); |
| |
| Register holderReg = regs.takeAny(); |
| masm.loadPtr(Address(BaselineStubReg, ICGetProp_CallNative::offsetOfHolder()), holderReg); |
| masm.loadPtr(Address(BaselineStubReg, ICGetProp_CallNative::offsetOfHolderShape()), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, &failure); |
| regs.add(holderReg); |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| enterStubFrame(masm, scratch); |
| |
| // Load callee function. |
| Register callee = regs.takeAny(); |
| masm.loadPtr(Address(BaselineStubReg, ICGetProp_CallNative::offsetOfGetter()), callee); |
| |
| // Push args for vm call. |
| masm.push(objReg); |
| masm.push(callee); |
| |
| // Don't to preserve R0 anymore. |
| regs.add(R0); |
| |
| // If needed, update SPS Profiler frame entry. |
| { |
| Label skipProfilerUpdate; |
| Register scratch = regs.takeAny(); |
| Register pcIdx = regs.takeAny(); |
| |
| // Check if profiling is enabled. |
| guardProfilingEnabled(masm, scratch, &skipProfilerUpdate); |
| |
| // Update profiling entry before leaving function. |
| masm.load32(Address(BaselineStubReg, ICGetProp_CallNative::offsetOfPCOffset()), pcIdx); |
| masm.spsUpdatePCIdx(&cx->runtime()->spsProfiler, pcIdx, scratch); |
| |
| masm.bind(&skipProfilerUpdate); |
| regs.add(scratch); |
| regs.add(pcIdx); |
| } |
| if (!callVM(DoCallNativeGetterInfo, masm)) |
| return false; |
| leaveStubFrame(masm); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICGetPropCallDOMProxyNativeCompiler::generateStubCode(MacroAssembler &masm, |
| Address* expandoAndGenerationAddr, |
| Address* generationAddr) |
| { |
| Label failure; |
| GeneralRegisterSet regs(availableGeneralRegs(1)); |
| Register scratch = regs.takeAnyExcluding(BaselineTailCallReg); |
| |
| // Guard input is an object. |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| // Unbox. |
| Register objReg = masm.extractObject(R0, ExtractTemp0); |
| |
| // Shape guard. |
| masm.loadPtr(Address(BaselineStubReg, ICGetProp_CallDOMProxyNative::offsetOfShape()), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, objReg, scratch, &failure); |
| |
| // Guard for ListObject. |
| { |
| GeneralRegisterSet domProxyRegSet(GeneralRegisterSet::All()); |
| domProxyRegSet.take(BaselineStubReg); |
| domProxyRegSet.take(objReg); |
| domProxyRegSet.take(scratch); |
| Address expandoShapeAddr(BaselineStubReg, ICGetProp_CallDOMProxyNative::offsetOfExpandoShape()); |
| GenerateDOMProxyChecks( |
| cx, masm, objReg, |
| Address(BaselineStubReg, ICGetProp_CallDOMProxyNative::offsetOfProxyHandler()), |
| &expandoShapeAddr, expandoAndGenerationAddr, generationAddr, |
| scratch, |
| domProxyRegSet, |
| &failure); |
| } |
| |
| Register holderReg = regs.takeAny(); |
| masm.loadPtr(Address(BaselineStubReg, ICGetProp_CallDOMProxyNative::offsetOfHolder()), |
| holderReg); |
| masm.loadPtr(Address(BaselineStubReg, ICGetProp_CallDOMProxyNative::offsetOfHolderShape()), |
| scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, &failure); |
| regs.add(holderReg); |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| enterStubFrame(masm, scratch); |
| |
| // Load callee function. |
| Register callee = regs.takeAny(); |
| masm.loadPtr(Address(BaselineStubReg, ICGetProp_CallDOMProxyNative::offsetOfGetter()), callee); |
| |
| // Push args for vm call. |
| masm.push(objReg); |
| masm.push(callee); |
| |
| // Don't have to preserve R0 anymore. |
| regs.add(R0); |
| |
| // If needed, update SPS Profiler frame entry. |
| { |
| Label skipProfilerUpdate; |
| Register scratch = regs.takeAny(); |
| Register pcIdx = regs.takeAny(); |
| |
| // Check if profiling is enabled. |
| guardProfilingEnabled(masm, scratch, &skipProfilerUpdate); |
| |
| // Update profiling entry before leaving function. |
| masm.load32(Address(BaselineStubReg, ICGetProp_CallDOMProxyNative::offsetOfPCOffset()), |
| pcIdx); |
| masm.spsUpdatePCIdx(&cx->runtime()->spsProfiler, pcIdx, scratch); |
| |
| masm.bind(&skipProfilerUpdate); |
| regs.add(scratch); |
| regs.add(pcIdx); |
| } |
| if (!callVM(DoCallNativeGetterInfo, masm)) |
| return false; |
| leaveStubFrame(masm); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICGetPropCallDOMProxyNativeCompiler::generateStubCode(MacroAssembler &masm) |
| { |
| if (kind == ICStub::GetProp_CallDOMProxyNative) |
| return generateStubCode(masm, NULL, NULL); |
| |
| Address internalStructAddress(BaselineStubReg, |
| ICGetProp_CallDOMProxyWithGenerationNative::offsetOfInternalStruct()); |
| Address generationAddress(BaselineStubReg, |
| ICGetProp_CallDOMProxyWithGenerationNative::offsetOfGeneration()); |
| return generateStubCode(masm, &internalStructAddress, &generationAddress); |
| } |
| |
| ICStub * |
| ICGetPropCallDOMProxyNativeCompiler::getStub(ICStubSpace *space) |
| { |
| RootedShape shape(cx, obj_->lastProperty()); |
| RootedShape holderShape(cx, holder_->lastProperty()); |
| |
| Value expandoSlot = obj_->getFixedSlot(GetDOMProxyExpandoSlot()); |
| RootedShape expandoShape(cx, NULL); |
| ExpandoAndGeneration *expandoAndGeneration; |
| int32_t generation; |
| Value expandoVal; |
| if (kind == ICStub::GetProp_CallDOMProxyNative) { |
| expandoVal = expandoSlot; |
| } else { |
| JS_ASSERT(kind == ICStub::GetProp_CallDOMProxyWithGenerationNative); |
| JS_ASSERT(!expandoSlot.isObject() && !expandoSlot.isUndefined()); |
| expandoAndGeneration = (ExpandoAndGeneration*)expandoSlot.toPrivate(); |
| expandoVal = expandoAndGeneration->expando; |
| generation = expandoAndGeneration->generation; |
| } |
| |
| if (expandoVal.isObject()) |
| expandoShape = expandoVal.toObject().lastProperty(); |
| |
| if (kind == ICStub::GetProp_CallDOMProxyNative) { |
| return ICGetProp_CallDOMProxyNative::New( |
| space, getStubCode(), firstMonitorStub_, shape, GetProxyHandler(obj_), |
| expandoShape, holder_, holderShape, getter_, pcOffset_); |
| } |
| |
| return ICGetProp_CallDOMProxyWithGenerationNative::New( |
| space, getStubCode(), firstMonitorStub_, shape, GetProxyHandler(obj_), |
| expandoAndGeneration, generation, expandoShape, holder_, holderShape, getter_, |
| pcOffset_); |
| } |
| |
| ICStub * |
| ICGetProp_DOMProxyShadowed::Compiler::getStub(ICStubSpace *space) |
| { |
| RootedShape shape(cx, obj_->lastProperty()); |
| return ICGetProp_DOMProxyShadowed::New(space, getStubCode(), firstMonitorStub_, |
| shape, GetProxyHandler(obj_), name_, pcOffset_); |
| } |
| |
| static bool |
| ProxyGet(JSContext *cx, HandleObject proxy, HandlePropertyName name, MutableHandleValue vp) |
| { |
| RootedId id(cx, NameToId(name)); |
| return Proxy::get(cx, proxy, proxy, id, vp); |
| } |
| |
| typedef bool (*ProxyGetFn)(JSContext *cx, HandleObject proxy, HandlePropertyName name, |
| MutableHandleValue vp); |
| static const VMFunction ProxyGetInfo = FunctionInfo<ProxyGetFn>(ProxyGet); |
| |
| bool |
| ICGetProp_DOMProxyShadowed::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| |
| GeneralRegisterSet regs(availableGeneralRegs(1)); |
| // Need to reserve a scratch register, but the scratch register should not be |
| // BaselineTailCallReg, because it's used for |enterStubFrame| which needs a |
| // non-BaselineTailCallReg scratch reg. |
| Register scratch = regs.takeAnyExcluding(BaselineTailCallReg); |
| |
| // Guard input is an object. |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| // Unbox. |
| Register objReg = masm.extractObject(R0, ExtractTemp0); |
| |
| // Shape guard. |
| masm.loadPtr(Address(BaselineStubReg, ICGetProp_DOMProxyShadowed::offsetOfShape()), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, objReg, scratch, &failure); |
| |
| // Guard for ListObject. |
| { |
| GeneralRegisterSet domProxyRegSet(GeneralRegisterSet::All()); |
| domProxyRegSet.take(BaselineStubReg); |
| domProxyRegSet.take(objReg); |
| domProxyRegSet.take(scratch); |
| GenerateDOMProxyChecks( |
| cx, masm, objReg, |
| Address(BaselineStubReg, ICGetProp_DOMProxyShadowed::offsetOfProxyHandler()), |
| /*expandoShapeAddr=*/NULL, /*expandoAndGenerationAddr=*/NULL, /*generationAddr=*/NULL, |
| scratch, |
| domProxyRegSet, |
| &failure); |
| } |
| |
| // Call ProxyGet(JSContext *cx, HandleObject proxy, HandlePropertyName name, MutableHandleValue vp); |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| enterStubFrame(masm, scratch); |
| |
| // Push property name and proxy object. |
| masm.loadPtr(Address(BaselineStubReg, ICGetProp_DOMProxyShadowed::offsetOfName()), scratch); |
| masm.push(scratch); |
| masm.push(objReg); |
| |
| // Don't have to preserve R0 anymore. |
| regs.add(R0); |
| |
| // If needed, update SPS Profiler frame entry. |
| { |
| Label skipProfilerUpdate; |
| Register scratch = regs.takeAny(); |
| Register pcIdx = regs.takeAny(); |
| |
| // Check if profiling is enabled. |
| guardProfilingEnabled(masm, scratch, &skipProfilerUpdate); |
| |
| // Update profiling entry before leaving function. |
| masm.load32(Address(BaselineStubReg, ICGetProp_DOMProxyShadowed::offsetOfPCOffset()), pcIdx); |
| masm.spsUpdatePCIdx(&cx->runtime()->spsProfiler, pcIdx, scratch); |
| |
| masm.bind(&skipProfilerUpdate); |
| regs.add(scratch); |
| regs.add(pcIdx); |
| } |
| if (!callVM(ProxyGetInfo, masm)) |
| return false; |
| leaveStubFrame(masm); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICGetProp_ArgumentsLength::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| if (which_ == ICGetProp_ArgumentsLength::Magic) { |
| // Ensure that this is lazy arguments. |
| masm.branchTestMagicValue(Assembler::NotEqual, R0, JS_OPTIMIZED_ARGUMENTS, &failure); |
| |
| // Ensure that frame has not loaded different arguments object since. |
| masm.branchTest32(Assembler::NonZero, |
| Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFlags()), |
| Imm32(BaselineFrame::HAS_ARGS_OBJ), |
| &failure); |
| |
| Address actualArgs(BaselineFrameReg, BaselineFrame::offsetOfNumActualArgs()); |
| masm.loadPtr(actualArgs, R0.scratchReg()); |
| masm.tagValue(JSVAL_TYPE_INT32, R0.scratchReg(), R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| JS_ASSERT(which_ == ICGetProp_ArgumentsLength::Strict || |
| which_ == ICGetProp_ArgumentsLength::Normal); |
| |
| bool isStrict = which_ == ICGetProp_ArgumentsLength::Strict; |
| Class *clasp = isStrict ? &StrictArgumentsObject::class_ : &NormalArgumentsObject::class_; |
| |
| Register scratchReg = R1.scratchReg(); |
| |
| // Guard on input being an arguments object. |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| Register objReg = masm.extractObject(R0, ExtractTemp0); |
| masm.branchTestObjClass(Assembler::NotEqual, objReg, scratchReg, clasp, &failure); |
| |
| // Get initial length value. |
| masm.unboxInt32(Address(objReg, ArgumentsObject::getInitialLengthSlotOffset()), scratchReg); |
| |
| // Test if length has been overridden. |
| masm.branchTest32(Assembler::NonZero, |
| scratchReg, |
| Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT), |
| &failure); |
| |
| // Nope, shift out arguments length and return it. |
| // No need to type monitor because this stub always returns Int32. |
| masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), scratchReg); |
| masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| void |
| BaselineScript::noteAccessedGetter(uint32_t pcOffset) |
| { |
| ICEntry &entry = icEntryFromPCOffset(pcOffset); |
| ICFallbackStub *stub = entry.fallbackStub(); |
| |
| if (stub->isGetProp_Fallback()) |
| stub->toGetProp_Fallback()->noteAccessedGetter(); |
| } |
| |
| // |
| // SetProp_Fallback |
| // |
| |
| // Attach an optimized stub for a SETPROP/SETGNAME/SETNAME op. |
| static bool |
| TryAttachSetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICSetProp_Fallback *stub, |
| HandleObject obj, HandleShape oldShape, uint32_t oldSlots, |
| HandlePropertyName name, HandleId id, HandleValue rhs, bool *attached) |
| { |
| JS_ASSERT(!*attached); |
| |
| if (!obj->isNative() || obj->watched()) |
| return true; |
| |
| RootedShape shape(cx); |
| RootedObject holder(cx); |
| if (!EffectlesslyLookupProperty(cx, obj, name, &holder, &shape)) |
| return false; |
| |
| size_t chainDepth; |
| if (IsCacheableSetPropAddSlot(cx, obj, oldShape, oldSlots, id, holder, shape, &chainDepth)) { |
| // Don't attach if proto chain depth is too high. |
| if (chainDepth > ICSetProp_NativeAdd::MAX_PROTO_CHAIN_DEPTH) |
| return true; |
| |
| bool isFixedSlot; |
| uint32_t offset; |
| GetFixedOrDynamicSlotOffset(obj, shape->slot(), &isFixedSlot, &offset); |
| |
| IonSpew(IonSpew_BaselineIC, " Generating SetProp(NativeObject.ADD) stub"); |
| ICSetPropNativeAddCompiler compiler(cx, obj, oldShape, chainDepth, isFixedSlot, offset); |
| ICUpdatedStub *newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| if (!newStub->addUpdateStubForValue(cx, script, obj, id, rhs)) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| if (IsCacheableSetPropWriteSlot(obj, oldShape, holder, shape)) { |
| bool isFixedSlot; |
| uint32_t offset; |
| GetFixedOrDynamicSlotOffset(obj, shape->slot(), &isFixedSlot, &offset); |
| |
| IonSpew(IonSpew_BaselineIC, " Generating SetProp(NativeObject.PROP) stub"); |
| ICSetProp_Native::Compiler compiler(cx, obj, isFixedSlot, offset); |
| ICUpdatedStub *newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| if (!newStub->addUpdateStubForValue(cx, script, obj, id, rhs)) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| bool isScripted = false; |
| bool cacheableCall = IsCacheableSetPropCall(obj, holder, shape, &isScripted); |
| |
| // Try handling scripted setters. |
| if (cacheableCall && isScripted) { |
| RootedFunction callee(cx, &shape->setterObject()->as<JSFunction>()); |
| JS_ASSERT(obj != holder); |
| JS_ASSERT(callee->hasScript()); |
| |
| IonSpew(IonSpew_BaselineIC, " Generating SetProp(NativeObj/ScriptedSetter %s:%d) stub", |
| callee->nonLazyScript()->filename(), callee->nonLazyScript()->lineno); |
| |
| ICSetProp_CallScripted::Compiler compiler(cx, obj, holder, callee, pc - script->code); |
| ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| // Try handling JSNative setters. |
| if (cacheableCall && !isScripted) { |
| RootedFunction callee(cx, &shape->setterObject()->as<JSFunction>()); |
| JS_ASSERT(obj != holder); |
| JS_ASSERT(callee->isNative()); |
| |
| IonSpew(IonSpew_BaselineIC, " Generating SetProp(NativeObj/NativeSetter %p) stub", |
| callee->native()); |
| |
| ICSetProp_CallNative::Compiler compiler(cx, obj, holder, callee, pc - script->code); |
| ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| DoSetPropFallback(JSContext *cx, BaselineFrame *frame, ICSetProp_Fallback *stub, HandleValue lhs, |
| HandleValue rhs, MutableHandleValue res) |
| { |
| RootedScript script(cx, frame->script()); |
| jsbytecode *pc = stub->icEntry()->pc(script); |
| JSOp op = JSOp(*pc); |
| FallbackICSpew(cx, stub, "SetProp(%s)", js_CodeName[op]); |
| |
| JS_ASSERT(op == JSOP_SETPROP || |
| op == JSOP_SETNAME || |
| op == JSOP_SETGNAME || |
| op == JSOP_INITPROP || |
| op == JSOP_SETALIASEDVAR); |
| |
| RootedPropertyName name(cx); |
| if (op == JSOP_SETALIASEDVAR) |
| name = ScopeCoordinateName(cx, script, pc); |
| else |
| name = script->getName(pc); |
| RootedId id(cx, NameToId(name)); |
| |
| RootedObject obj(cx, ToObjectFromStack(cx, lhs)); |
| if (!obj) |
| return false; |
| RootedShape oldShape(cx, obj->lastProperty()); |
| uint32_t oldSlots = obj->numDynamicSlots(); |
| |
| if (op == JSOP_INITPROP && name != cx->names().proto) { |
| JS_ASSERT(obj->isObject()); |
| if (!DefineNativeProperty(cx, obj, id, rhs, NULL, NULL, JSPROP_ENUMERATE, 0, 0, 0)) |
| return false; |
| } else if (op == JSOP_SETNAME || op == JSOP_SETGNAME) { |
| if (!SetNameOperation(cx, script, pc, obj, rhs)) |
| return false; |
| } else if (op == JSOP_SETALIASEDVAR) { |
| obj->as<ScopeObject>().setAliasedVar(cx, pc, name, rhs); |
| } else if (script->strict) { |
| if (!js::SetProperty<true>(cx, obj, id, rhs)) |
| return false; |
| } else { |
| if (!js::SetProperty<false>(cx, obj, id, rhs)) |
| return false; |
| } |
| |
| // Leave the RHS on the stack. |
| res.set(rhs); |
| |
| if (stub->numOptimizedStubs() >= ICSetProp_Fallback::MAX_OPTIMIZED_STUBS) { |
| // TODO: Discard all stubs in this IC and replace with generic setprop stub. |
| return true; |
| } |
| |
| bool attached = false; |
| if (!TryAttachSetPropStub(cx, script, pc, stub, obj, oldShape, oldSlots, name, id, rhs, |
| &attached)) |
| { |
| return false; |
| } |
| if (attached) |
| return true; |
| |
| JS_ASSERT(!attached); |
| stub->noteUnoptimizableAccess(); |
| |
| return true; |
| } |
| |
| typedef bool (*DoSetPropFallbackFn)(JSContext *, BaselineFrame *, ICSetProp_Fallback *, |
| HandleValue, HandleValue, MutableHandleValue); |
| static const VMFunction DoSetPropFallbackInfo = |
| FunctionInfo<DoSetPropFallbackFn>(DoSetPropFallback, PopValues(2)); |
| |
| bool |
| ICSetProp_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| JS_ASSERT(R0 == JSReturnOperand); |
| |
| EmitRestoreTailCallReg(masm); |
| |
| // Ensure stack is fully synced for the expression decompiler. |
| masm.pushValue(R0); |
| masm.pushValue(R1); |
| |
| // Push arguments. |
| masm.pushValue(R1); |
| masm.pushValue(R0); |
| masm.push(BaselineStubReg); |
| masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| return tailCallVM(DoSetPropFallbackInfo, masm); |
| } |
| |
| bool |
| ICSetProp_Native::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| |
| // Guard input is an object. |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| GeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratch = regs.takeAny(); |
| |
| // Unbox and shape guard. |
| Register objReg = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(BaselineStubReg, ICSetProp_Native::offsetOfShape()), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, objReg, scratch, &failure); |
| |
| // Guard that the type object matches. |
| masm.loadPtr(Address(BaselineStubReg, ICSetProp_Native::offsetOfType()), scratch); |
| masm.branchPtr(Assembler::NotEqual, Address(objReg, JSObject::offsetOfType()), scratch, |
| &failure); |
| |
| // Stow both R0 and R1 (object and value). |
| EmitStowICValues(masm, 2); |
| |
| // Type update stub expects the value to check in R0. |
| masm.moveValue(R1, R0); |
| |
| // Call the type-update stub. |
| if (!callTypeUpdateIC(masm, sizeof(Value))) |
| return false; |
| |
| // Unstow R0 and R1 (object and key) |
| EmitUnstowICValues(masm, 2); |
| |
| regs.add(R0); |
| regs.takeUnchecked(objReg); |
| |
| Register holderReg; |
| if (isFixedSlot_) { |
| holderReg = objReg; |
| } else { |
| holderReg = regs.takeAny(); |
| masm.loadPtr(Address(objReg, JSObject::offsetOfSlots()), holderReg); |
| } |
| |
| // Perform the store. |
| masm.load32(Address(BaselineStubReg, ICSetProp_Native::offsetOfOffset()), scratch); |
| EmitPreBarrier(masm, BaseIndex(holderReg, scratch, TimesOne), MIRType_Value); |
| masm.storeValue(R1, BaseIndex(holderReg, scratch, TimesOne)); |
| if (holderReg != objReg) |
| regs.add(holderReg); |
| #ifdef JSGC_GENERATIONAL |
| Label skipBarrier; |
| masm.branchTestObject(Assembler::NotEqual, R1, &skipBarrier); |
| { |
| Register scr = regs.takeAny(); |
| GeneralRegisterSet saveRegs; |
| saveRegs.add(R1); |
| emitPostWriteBarrierSlot(masm, objReg, scr, saveRegs); |
| regs.add(scr); |
| } |
| masm.bind(&skipBarrier); |
| #endif |
| |
| // The RHS has to be in R0. |
| masm.moveValue(R1, R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| ICUpdatedStub * |
| ICSetPropNativeAddCompiler::getStub(ICStubSpace *space) |
| { |
| AutoShapeVector shapes(cx); |
| if (!shapes.append(oldShape_)) |
| return NULL; |
| |
| if (!GetProtoShapes(obj_, protoChainDepth_, &shapes)) |
| return NULL; |
| |
| JS_STATIC_ASSERT(ICSetProp_NativeAdd::MAX_PROTO_CHAIN_DEPTH == 4); |
| |
| ICUpdatedStub *stub = NULL; |
| switch(protoChainDepth_) { |
| case 0: stub = getStubSpecific<0>(space, &shapes); break; |
| case 1: stub = getStubSpecific<1>(space, &shapes); break; |
| case 2: stub = getStubSpecific<2>(space, &shapes); break; |
| case 3: stub = getStubSpecific<3>(space, &shapes); break; |
| case 4: stub = getStubSpecific<4>(space, &shapes); break; |
| default: JS_NOT_REACHED("ProtoChainDepth too high."); |
| } |
| if (!stub || !stub->initUpdatingChain(cx, space)) |
| return NULL; |
| return stub; |
| } |
| |
| bool |
| ICSetPropNativeAddCompiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| Label failureUnstow; |
| |
| // Guard input is an object. |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| GeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratch = regs.takeAny(); |
| |
| // Unbox and guard against old shape. |
| Register objReg = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(BaselineStubReg, ICSetProp_NativeAddImpl<0>::offsetOfShape(0)), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, objReg, scratch, &failure); |
| |
| // Guard that the type object matches. |
| masm.loadPtr(Address(BaselineStubReg, ICSetProp_NativeAdd::offsetOfType()), scratch); |
| masm.branchPtr(Assembler::NotEqual, Address(objReg, JSObject::offsetOfType()), scratch, |
| &failure); |
| |
| // Stow both R0 and R1 (object and value). |
| EmitStowICValues(masm, 2); |
| |
| regs = availableGeneralRegs(1); |
| scratch = regs.takeAny(); |
| Register protoReg = regs.takeAny(); |
| // Check the proto chain. |
| for (size_t i = 0; i < protoChainDepth_; i++) { |
| masm.loadObjProto(i == 0 ? objReg : protoReg, protoReg); |
| masm.branchTestPtr(Assembler::Zero, protoReg, protoReg, &failureUnstow); |
| masm.loadPtr(Address(BaselineStubReg, ICSetProp_NativeAddImpl<0>::offsetOfShape(i + 1)), |
| scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, protoReg, scratch, &failureUnstow); |
| } |
| |
| // Shape and type checks succeeded, ok to proceed. |
| |
| // Load RHS into R0 for TypeUpdate check. |
| // Stack is currently: [..., ObjValue, RHSValue, MaybeReturnAddr? ] |
| masm.loadValue(Address(BaselineStackReg, ICStackValueOffset), R0); |
| |
| // Call the type-update stub. |
| if (!callTypeUpdateIC(masm, sizeof(Value))) |
| return false; |
| |
| // Unstow R0 and R1 (object and key) |
| EmitUnstowICValues(masm, 2); |
| regs = availableGeneralRegs(2); |
| scratch = regs.takeAny(); |
| |
| // Changing object shape. Write the object's new shape. |
| Address shapeAddr(objReg, JSObject::offsetOfShape()); |
| EmitPreBarrier(masm, shapeAddr, MIRType_Shape); |
| masm.loadPtr(Address(BaselineStubReg, ICSetProp_NativeAdd::offsetOfNewShape()), scratch); |
| masm.storePtr(scratch, shapeAddr); |
| |
| Register holderReg; |
| regs.add(R0); |
| regs.takeUnchecked(objReg); |
| if (isFixedSlot_) { |
| holderReg = objReg; |
| } else { |
| holderReg = regs.takeAny(); |
| masm.loadPtr(Address(objReg, JSObject::offsetOfSlots()), holderReg); |
| } |
| |
| // Perform the store. No write barrier required since this is a new |
| // initialization. |
| masm.load32(Address(BaselineStubReg, ICSetProp_NativeAdd::offsetOfOffset()), scratch); |
| masm.storeValue(R1, BaseIndex(holderReg, scratch, TimesOne)); |
| |
| if (holderReg != objReg) |
| regs.add(holderReg); |
| |
| #ifdef JSGC_GENERATIONAL |
| Label skipBarrier; |
| masm.branchTestObject(Assembler::NotEqual, R1, &skipBarrier); |
| { |
| Register scr = regs.takeAny(); |
| GeneralRegisterSet saveRegs; |
| saveRegs.add(R1); |
| emitPostWriteBarrierSlot(masm, objReg, scr, saveRegs); |
| } |
| masm.bind(&skipBarrier); |
| #endif |
| |
| // The RHS has to be in R0. |
| masm.moveValue(R1, R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failureUnstow); |
| EmitUnstowICValues(masm, 2); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICSetProp_CallScripted::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| Label failureUnstow; |
| Label failureLeaveStubFrame; |
| |
| // Guard input is an object. |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| // Stow R0 and R1 to free up registers. |
| EmitStowICValues(masm, 2); |
| |
| GeneralRegisterSet regs(availableGeneralRegs(1)); |
| Register scratch = regs.takeAnyExcluding(BaselineTailCallReg); |
| |
| // Unbox and shape guard. |
| Register objReg = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(BaselineStubReg, ICSetProp_CallScripted::offsetOfShape()), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, objReg, scratch, &failureUnstow); |
| |
| Register holderReg = regs.takeAny(); |
| masm.loadPtr(Address(BaselineStubReg, ICSetProp_CallScripted::offsetOfHolder()), holderReg); |
| masm.loadPtr(Address(BaselineStubReg, ICSetProp_CallScripted::offsetOfHolderShape()), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, &failureUnstow); |
| regs.add(holderReg); |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| enterStubFrame(masm, scratch); |
| |
| // Load callee function and code. To ensure that |code| doesn't end up being |
| // ArgumentsRectifierReg, if it's available we assign it to |callee| instead. |
| Register callee; |
| if (regs.has(ArgumentsRectifierReg)) { |
| callee = ArgumentsRectifierReg; |
| regs.take(callee); |
| } else { |
| callee = regs.takeAny(); |
| } |
| Register code = regs.takeAny(); |
| masm.loadPtr(Address(BaselineStubReg, ICSetProp_CallScripted::offsetOfSetter()), callee); |
| masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), code); |
| masm.loadBaselineOrIonRaw(code, code, SequentialExecution, &failureLeaveStubFrame); |
| |
| // Setter is called with the new value as the only argument, and |obj| as thisv. |
| // Note that we use Push, not push, so that callIon will align the stack |
| // properly on ARM. |
| |
| // To Push R1, read it off of the stowed values on stack. |
| // Stack: [ ..., R0, R1, ..STUBFRAME-HEADER.. ] |
| masm.movePtr(BaselineStackReg, scratch); |
| masm.PushValue(Address(scratch, STUB_FRAME_SIZE)); |
| masm.Push(R0); |
| EmitCreateStubFrameDescriptor(masm, scratch); |
| masm.Push(Imm32(1)); // ActualArgc is 1 |
| masm.Push(callee); |
| masm.Push(scratch); |
| |
| // Handle arguments underflow. |
| Label noUnderflow; |
| masm.load16ZeroExtend(Address(callee, offsetof(JSFunction, nargs)), scratch); |
| masm.branch32(Assembler::BelowOrEqual, scratch, Imm32(1), &noUnderflow); |
| { |
| // Call the arguments rectifier. |
| JS_ASSERT(ArgumentsRectifierReg != code); |
| |
| IonCode *argumentsRectifier = |
| cx->compartment()->ionCompartment()->getArgumentsRectifier(SequentialExecution); |
| |
| masm.movePtr(ImmGCPtr(argumentsRectifier), code); |
| masm.loadPtr(Address(code, IonCode::offsetOfCode()), code); |
| masm.mov(Imm32(1), ArgumentsRectifierReg); |
| } |
| |
| masm.bind(&noUnderflow); |
| |
| // If needed, update SPS Profiler frame entry. At this point, callee and scratch can |
| // be clobbered. |
| { |
| Label skipProfilerUpdate; |
| |
| // Need to avoid using ArgumentsRectifierReg and code register. |
| GeneralRegisterSet availRegs = availableGeneralRegs(0); |
| availRegs.take(ArgumentsRectifierReg); |
| availRegs.take(code); |
| Register scratch = availRegs.takeAny(); |
| Register pcIdx = availRegs.takeAny(); |
| |
| // Check if profiling is enabled. |
| guardProfilingEnabled(masm, scratch, &skipProfilerUpdate); |
| |
| // Update profiling entry before leaving function. |
| masm.load32(Address(BaselineStubReg, ICSetProp_CallScripted::offsetOfPCOffset()), pcIdx); |
| masm.spsUpdatePCIdx(&cx->runtime()->spsProfiler, pcIdx, scratch); |
| |
| masm.bind(&skipProfilerUpdate); |
| } |
| masm.callIon(code); |
| |
| leaveStubFrame(masm, true); |
| // Do not care about return value from function. The original RHS should be returned |
| // as the result of this operation. |
| EmitUnstowICValues(masm, 2); |
| masm.moveValue(R1, R0); |
| EmitReturnFromIC(masm); |
| |
| // Leave stub frame and go to next stub. |
| masm.bind(&failureLeaveStubFrame); |
| leaveStubFrame(masm, false); |
| |
| // Unstow R0 and R1 |
| masm.bind(&failureUnstow); |
| EmitUnstowICValues(masm, 2); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| static bool |
| DoCallNativeSetter(JSContext *cx, HandleFunction callee, HandleObject obj, HandleValue val) |
| { |
| JS_ASSERT(callee->isNative()); |
| JSNative natfun = callee->native(); |
| |
| Value vp[3] = { ObjectValue(*callee.get()), ObjectValue(*obj.get()), val }; |
| AutoValueArray rootVp(cx, vp, 3); |
| |
| return natfun(cx, 1, vp); |
| } |
| |
| typedef bool (*DoCallNativeSetterFn)(JSContext *, HandleFunction, HandleObject, HandleValue); |
| static const VMFunction DoCallNativeSetterInfo = |
| FunctionInfo<DoCallNativeSetterFn>(DoCallNativeSetter); |
| |
| bool |
| ICSetProp_CallNative::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| Label failureUnstow; |
| |
| // Guard input is an object. |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| // Stow R0 and R1 to free up registers. |
| EmitStowICValues(masm, 2); |
| |
| GeneralRegisterSet regs(availableGeneralRegs(1)); |
| Register scratch = regs.takeAnyExcluding(BaselineTailCallReg); |
| |
| // Unbox and shape guard. |
| Register objReg = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(BaselineStubReg, ICSetProp_CallNative::offsetOfShape()), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, objReg, scratch, &failureUnstow); |
| |
| Register holderReg = regs.takeAny(); |
| masm.loadPtr(Address(BaselineStubReg, ICSetProp_CallNative::offsetOfHolder()), holderReg); |
| masm.loadPtr(Address(BaselineStubReg, ICSetProp_CallNative::offsetOfHolderShape()), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, &failureUnstow); |
| regs.add(holderReg); |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| enterStubFrame(masm, scratch); |
| |
| // Load callee function and code. To ensure that |code| doesn't end up being |
| // ArgumentsRectifierReg, if it's available we assign it to |callee| instead. |
| Register callee = regs.takeAny(); |
| masm.loadPtr(Address(BaselineStubReg, ICSetProp_CallNative::offsetOfSetter()), callee); |
| |
| // To Push R1, read it off of the stowed values on stack. |
| // Stack: [ ..., R0, R1, ..STUBFRAME-HEADER.. ] |
| masm.movePtr(BaselineStackReg, scratch); |
| masm.pushValue(Address(scratch, STUB_FRAME_SIZE)); |
| masm.push(objReg); |
| masm.push(callee); |
| |
| // Don't need to preserve R0 anymore. |
| regs.add(R0); |
| |
| // If needed, update SPS Profiler frame entry. |
| { |
| Label skipProfilerUpdate; |
| Register scratch = regs.takeAny(); |
| Register pcIdx = regs.takeAny(); |
| |
| // Check if profiling is enabled. |
| guardProfilingEnabled(masm, scratch, &skipProfilerUpdate); |
| |
| // Update profiling entry before leaving function. |
| masm.load32(Address(BaselineStubReg, ICSetProp_CallNative::offsetOfPCOffset()), pcIdx); |
| masm.spsUpdatePCIdx(&cx->runtime()->spsProfiler, pcIdx, scratch); |
| |
| masm.bind(&skipProfilerUpdate); |
| regs.add(scratch); |
| regs.add(pcIdx); |
| } |
| if (!callVM(DoCallNativeSetterInfo, masm)) |
| return false; |
| leaveStubFrame(masm); |
| |
| // Do not care about return value from function. The original RHS should be returned |
| // as the result of this operation. |
| EmitUnstowICValues(masm, 2); |
| masm.moveValue(R1, R0); |
| EmitReturnFromIC(masm); |
| |
| // Unstow R0 and R1 |
| masm.bind(&failureUnstow); |
| EmitUnstowICValues(masm, 2); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // Call_Fallback |
| // |
| |
| static bool |
| TryAttachFunApplyStub(JSContext *cx, ICCall_Fallback *stub, HandleScript script, jsbytecode *pc, |
| HandleValue thisv, uint32_t argc, Value *argv) |
| { |
| if (argc != 2) |
| return true; |
| |
| if (!thisv.isObject() || !thisv.toObject().is<JSFunction>()) |
| return true; |
| RootedFunction target(cx, &thisv.toObject().as<JSFunction>()); |
| |
| // right now, only handle situation where second argument is |arguments| |
| if (argv[1].isMagic(JS_OPTIMIZED_ARGUMENTS) && !script->needsArgsObj()) { |
| if (target->hasScript() && |
| (target->nonLazyScript()->hasBaselineScript() || |
| target->nonLazyScript()->hasIonScript()) && |
| !stub->hasStub(ICStub::Call_ScriptedApplyArguments)) |
| { |
| IonSpew(IonSpew_BaselineIC, " Generating Call_ScriptedApplyArguments stub"); |
| |
| ICCall_ScriptedApplyArguments::Compiler compiler( |
| cx, stub->fallbackMonitorStub()->firstMonitorStub(), pc - script->code); |
| ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| return true; |
| } |
| |
| // TODO: handle FUNAPPLY for native targets. |
| } |
| return true; |
| } |
| |
| static bool |
| TryAttachCallStub(JSContext *cx, ICCall_Fallback *stub, HandleScript script, jsbytecode *pc, |
| JSOp op, uint32_t argc, Value *vp, bool constructing, bool useNewType) |
| { |
| if (useNewType || op == JSOP_EVAL) |
| return true; |
| |
| if (stub->numOptimizedStubs() >= ICCall_Fallback::MAX_OPTIMIZED_STUBS) { |
| // TODO: Discard all stubs in this IC and replace with inert megamorphic stub. |
| // But for now we just bail. |
| return true; |
| } |
| |
| RootedValue callee(cx, vp[0]); |
| RootedValue thisv(cx, vp[1]); |
| |
| if (!callee.isObject()) |
| return true; |
| |
| RootedObject obj(cx, &callee.toObject()); |
| if (!obj->is<JSFunction>()) |
| return true; |
| |
| RootedFunction fun(cx, &obj->as<JSFunction>()); |
| |
| if (fun->hasScript()) { |
| // Never attach optimized scripted call stubs for JSOP_FUNAPPLY. |
| // MagicArguments may escape the frame through them. |
| if (op == JSOP_FUNAPPLY) |
| return true; |
| |
| RootedScript calleeScript(cx, fun->nonLazyScript()); |
| if (!calleeScript->hasBaselineScript() && !calleeScript->hasIonScript()) |
| return true; |
| |
| if (calleeScript->shouldCloneAtCallsite) |
| return true; |
| |
| // Check if this stub chain has already generalized scripted calls. |
| if (stub->scriptedStubsAreGeneralized()) { |
| IonSpew(IonSpew_BaselineIC, " Chain already has generalized scripted call stub!"); |
| return true; |
| } |
| |
| if (stub->scriptedStubCount() >= ICCall_Fallback::MAX_SCRIPTED_STUBS) { |
| // Create a Call_AnyScripted stub. |
| IonSpew(IonSpew_BaselineIC, " Generating Call_AnyScripted stub (cons=%s)", |
| constructing ? "yes" : "no"); |
| |
| ICCallScriptedCompiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(), |
| constructing, pc - script->code); |
| ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| // Before adding new stub, unlink all previous Call_Scripted. |
| stub->unlinkStubsWithKind(cx, ICStub::Call_Scripted); |
| |
| // Add new generalized stub. |
| stub->addNewStub(newStub); |
| return true; |
| } |
| |
| IonSpew(IonSpew_BaselineIC, |
| " Generating Call_Scripted stub (fun=%p, %s:%d, cons=%s)", |
| fun.get(), fun->nonLazyScript()->filename(), fun->nonLazyScript()->lineno, |
| constructing ? "yes" : "no"); |
| ICCallScriptedCompiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(), |
| calleeScript, constructing, pc - script->code); |
| ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| return true; |
| } |
| |
| if (fun->isNative() && (!constructing || (constructing && fun->isNativeConstructor()))) { |
| // Generalied native call stubs are not here yet! |
| JS_ASSERT(!stub->nativeStubsAreGeneralized()); |
| |
| // Check for JSOP_FUNAPPLY |
| if (op == JSOP_FUNAPPLY) { |
| if (fun->maybeNative() == js_fun_apply) |
| return TryAttachFunApplyStub(cx, stub, script, pc, thisv, argc, vp + 2); |
| |
| // Don't try to attach a "regular" optimized call stubs for FUNAPPLY ops, |
| // since MagicArguments may escape through them. |
| return true; |
| } |
| |
| if (stub->nativeStubCount() >= ICCall_Fallback::MAX_NATIVE_STUBS) { |
| IonSpew(IonSpew_BaselineIC, |
| " Too many Call_Native stubs. TODO: add Call_AnyNative!"); |
| return true; |
| } |
| |
| IonSpew(IonSpew_BaselineIC, " Generating Call_Native stub (fun=%p, cons=%s)", |
| fun.get(), constructing ? "yes" : "no"); |
| ICCall_Native::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(), |
| fun, constructing, pc - script->code); |
| ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| return true; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| MaybeCloneFunctionAtCallsite(JSContext *cx, MutableHandleValue callee, HandleScript script, |
| jsbytecode *pc) |
| { |
| RootedFunction fun(cx); |
| if (!IsFunctionObject(callee, fun.address())) |
| return true; |
| |
| if (!fun->hasScript() || !fun->nonLazyScript()->shouldCloneAtCallsite) |
| return true; |
| |
| if (!cx->typeInferenceEnabled()) |
| return true; |
| |
| fun = CloneFunctionAtCallsite(cx, fun, script, pc); |
| if (!fun) |
| return false; |
| |
| callee.setObject(*fun); |
| return true; |
| } |
| |
| static bool |
| DoCallFallback(JSContext *cx, BaselineFrame *frame, ICCall_Fallback *stub, uint32_t argc, |
| Value *vp, MutableHandleValue res) |
| { |
| // Ensure vp array is rooted - we may GC in here. |
| AutoArrayRooter vpRoot(cx, argc + 2, vp); |
| |
| RootedScript script(cx, frame->script()); |
| jsbytecode *pc = stub->icEntry()->pc(script); |
| JSOp op = JSOp(*pc); |
| FallbackICSpew(cx, stub, "Call(%s)", js_CodeName[op]); |
| |
| JS_ASSERT(argc == GET_ARGC(pc)); |
| |
| RootedValue callee(cx, vp[0]); |
| RootedValue thisv(cx, vp[1]); |
| |
| Value *args = vp + 2; |
| |
| // Handle funapply with JSOP_ARGUMENTS |
| if (op == JSOP_FUNAPPLY && argc == 2 && args[1].isMagic(JS_OPTIMIZED_ARGUMENTS)) { |
| if (!GuardFunApplyArgumentsOptimization(cx, frame, callee, args, argc)) |
| return false; |
| } |
| |
| // Compute construcing and useNewType flags. |
| bool constructing = (op == JSOP_NEW); |
| bool newType = false; |
| if (cx->typeInferenceEnabled()) |
| newType = types::UseNewType(cx, script, pc); |
| |
| // Try attaching a call stub. |
| if (!TryAttachCallStub(cx, stub, script, pc, op, argc, vp, constructing, newType)) |
| return false; |
| |
| // Maybe update PC in profiler entry before leaving this script by call. |
| if (cx->runtime()->spsProfiler.enabled() && frame->hasPushedSPSFrame()) |
| cx->runtime()->spsProfiler.updatePC(script, pc); |
| |
| if (!MaybeCloneFunctionAtCallsite(cx, &callee, script, pc)) |
| return false; |
| |
| if (op == JSOP_NEW) { |
| if (!InvokeConstructor(cx, callee, argc, args, res.address())) |
| return false; |
| } else if (op == JSOP_EVAL && IsBuiltinEvalForScope(frame->scopeChain(), callee)) { |
| if (!DirectEval(cx, CallArgsFromVp(argc, vp))) |
| return false; |
| res.set(vp[0]); |
| } else { |
| JS_ASSERT(op == JSOP_CALL || op == JSOP_FUNCALL || op == JSOP_FUNAPPLY || op == JSOP_EVAL); |
| if (!Invoke(cx, thisv, callee, argc, args, res.address())) |
| return false; |
| } |
| |
| types::TypeScript::Monitor(cx, script, pc, res); |
| |
| // Attach a new TypeMonitor stub for this value. |
| ICTypeMonitor_Fallback *typeMonFbStub = stub->fallbackMonitorStub(); |
| if (!typeMonFbStub->addMonitorStubForValue(cx, script, res)) |
| return false; |
| // Add a type monitor stub for the resulting value. |
| if (!stub->addMonitorStubForValue(cx, script, res)) |
| return false; |
| |
| return true; |
| } |
| |
| void |
| ICCallStubCompiler::pushCallArguments(MacroAssembler &masm, GeneralRegisterSet regs, Register argcReg) |
| { |
| JS_ASSERT(!regs.has(argcReg)); |
| |
| // Push the callee and |this| too. |
| Register count = regs.takeAny(); |
| masm.mov(argcReg, count); |
| masm.add32(Imm32(2), count); |
| |
| // argPtr initially points to the last argument. |
| Register argPtr = regs.takeAny(); |
| masm.mov(BaselineStackReg, argPtr); |
| |
| // Skip 4 pointers pushed on top of the arguments: the frame descriptor, |
| // return address, old frame pointer and stub reg. |
| masm.addPtr(Imm32(STUB_FRAME_SIZE), argPtr); |
| |
| // Push all values, starting at the last one. |
| Label loop, done; |
| masm.bind(&loop); |
| masm.branchTest32(Assembler::Zero, count, count, &done); |
| { |
| masm.pushValue(Address(argPtr, 0)); |
| masm.addPtr(Imm32(sizeof(Value)), argPtr); |
| |
| masm.sub32(Imm32(1), count); |
| masm.jump(&loop); |
| } |
| masm.bind(&done); |
| } |
| |
| Register |
| ICCallStubCompiler::guardFunApply(MacroAssembler &masm, GeneralRegisterSet regs, Register argcReg, |
| bool checkNative, Label *failure) |
| { |
| // Ensure argc == 2 |
| masm.branch32(Assembler::NotEqual, argcReg, Imm32(2), failure); |
| |
| // Stack looks like: |
| // [..., CalleeV, ThisV, Arg0V, Arg1V <MaybeReturnReg>] |
| |
| // Ensure that the second arg is magic arguments. |
| Address secondArgSlot(BaselineStackReg, ICStackValueOffset); |
| masm.branchTestMagic(Assembler::NotEqual, secondArgSlot, failure); |
| |
| // Ensure that this frame doesn't have an arguments object. |
| masm.branchTest32(Assembler::NonZero, |
| Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFlags()), |
| Imm32(BaselineFrame::HAS_ARGS_OBJ), |
| failure); |
| |
| // Stack now confirmed to be like: |
| // [..., CalleeV, ThisV, Arg0V, MagicValue(Arguments), <MaybeReturnAddr>] |
| |
| // Load the callee, ensure that it's js_fun_apply |
| ValueOperand val = regs.takeAnyValue(); |
| Address calleeSlot(BaselineStackReg, ICStackValueOffset + (3 * sizeof(Value))); |
| masm.loadValue(calleeSlot, val); |
| |
| masm.branchTestObject(Assembler::NotEqual, val, failure); |
| Register callee = masm.extractObject(val, ExtractTemp1); |
| |
| masm.branchTestObjClass(Assembler::NotEqual, callee, regs.getAny(), &JSFunction::class_, |
| failure); |
| masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), callee); |
| |
| masm.branchPtr(Assembler::NotEqual, callee, ImmWord((void*) js_fun_apply), failure); |
| |
| // Load the |thisv|, ensure that it's a scripted function with a valid baseline or ion |
| // script, or a native function. |
| Address thisSlot(BaselineStackReg, ICStackValueOffset + (2 * sizeof(Value))); |
| masm.loadValue(thisSlot, val); |
| |
| masm.branchTestObject(Assembler::NotEqual, val, failure); |
| Register target = masm.extractObject(val, ExtractTemp1); |
| regs.add(val); |
| regs.takeUnchecked(target); |
| |
| masm.branchTestObjClass(Assembler::NotEqual, target, regs.getAny(), &JSFunction::class_, |
| failure); |
| |
| if (checkNative) { |
| masm.branchIfInterpreted(target, failure); |
| } else { |
| masm.branchIfFunctionHasNoScript(target, failure); |
| Register temp = regs.takeAny(); |
| masm.loadPtr(Address(target, JSFunction::offsetOfNativeOrScript()), temp); |
| masm.loadBaselineOrIonRaw(temp, temp, SequentialExecution, failure); |
| regs.add(temp); |
| } |
| return target; |
| } |
| |
| void |
| ICCallStubCompiler::pushCallerArguments(MacroAssembler &masm, GeneralRegisterSet regs) |
| { |
| // Initialize copyReg to point to start caller arguments vector. |
| // Initialize argcReg to poitn to the end of it. |
| Register startReg = regs.takeAny(); |
| Register endReg = regs.takeAny(); |
| masm.loadPtr(Address(BaselineFrameReg, 0), startReg); |
| masm.loadPtr(Address(startReg, BaselineFrame::offsetOfNumActualArgs()), endReg); |
| masm.addPtr(Imm32(BaselineFrame::offsetOfArg(0)), startReg); |
| JS_ASSERT(sizeof(Value) == 8); |
| masm.lshiftPtr(Imm32(3), endReg); |
| masm.addPtr(startReg, endReg); |
| |
| // Copying pre-decrements endReg by 8 until startReg is reached |
| Label copyDone; |
| Label copyStart; |
| masm.bind(©Start); |
| masm.branchPtr(Assembler::Equal, endReg, startReg, ©Done); |
| masm.subPtr(Imm32(sizeof(Value)), endReg); |
| masm.pushValue(Address(endReg, 0)); |
| masm.jump(©Start); |
| masm.bind(©Done); |
| } |
| |
| typedef bool (*DoCallFallbackFn)(JSContext *, BaselineFrame *, ICCall_Fallback *, |
| uint32_t, Value *, MutableHandleValue); |
| static const VMFunction DoCallFallbackInfo = FunctionInfo<DoCallFallbackFn>(DoCallFallback); |
| |
| bool |
| ICCall_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| JS_ASSERT(R0 == JSReturnOperand); |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| enterStubFrame(masm, R1.scratchReg()); |
| |
| // Values are on the stack left-to-right. Calling convention wants them |
| // right-to-left so duplicate them on the stack in reverse order. |
| // |this| and callee are pushed last. |
| |
| GeneralRegisterSet regs(availableGeneralRegs(0)); |
| regs.take(R0.scratchReg()); // argc. |
| |
| pushCallArguments(masm, regs, R0.scratchReg()); |
| |
| masm.push(BaselineStackReg); |
| masm.push(R0.scratchReg()); |
| masm.push(BaselineStubReg); |
| |
| // Load previous frame pointer, push BaselineFrame *. |
| masm.loadPtr(Address(BaselineFrameReg, 0), R0.scratchReg()); |
| masm.pushBaselineFramePtr(R0.scratchReg(), R0.scratchReg()); |
| |
| if (!callVM(DoCallFallbackInfo, masm)) |
| return false; |
| |
| leaveStubFrame(masm); |
| EmitReturnFromIC(masm); |
| |
| // The following asmcode is only used when an Ion inlined frame bails out into |
| // baseline jitcode. The return address pushed onto the reconstructed baseline stack |
| // points here. |
| returnOffset_ = masm.currentOffset(); |
| |
| // Load passed-in ThisV into R1 just in case it's needed. Need to do this before |
| // we leave the stub frame since that info will be lost. |
| // Current stack: [...., ThisV, ActualArgc, CalleeToken, Descriptor ] |
| masm.loadValue(Address(BaselineStackReg, 3 * sizeof(size_t)), R1); |
| |
| leaveStubFrame(masm, true); |
| |
| // R1 and R0 are taken. |
| regs = availableGeneralRegs(2); |
| Register scratch = regs.takeAny(); |
| |
| // If this is a |constructing| call, if the callee returns a non-object, we replace it with |
| // the |this| object passed in. |
| JS_ASSERT(JSReturnOperand == R0); |
| Label skipThisReplace; |
| masm.load16ZeroExtend(Address(BaselineStubReg, ICStub::offsetOfExtra()), scratch); |
| masm.branchTest32(Assembler::Zero, scratch, Imm32(ICCall_Fallback::CONSTRUCTING_FLAG), |
| &skipThisReplace); |
| masm.branchTestObject(Assembler::Equal, JSReturnOperand, &skipThisReplace); |
| masm.moveValue(R1, R0); |
| #ifdef DEBUG |
| masm.branchTestObject(Assembler::Equal, JSReturnOperand, &skipThisReplace); |
| masm.breakpoint(); |
| #endif |
| masm.bind(&skipThisReplace); |
| |
| // At this point, BaselineStubReg points to the ICCall_Fallback stub, which is NOT |
| // a MonitoredStub, but rather a MonitoredFallbackStub. To use EmitEnterTypeMonitorIC, |
| // first load the ICTypeMonitor_Fallback stub into BaselineStubReg. Then, use |
| // EmitEnterTypeMonitorIC with a custom struct offset. |
| masm.loadPtr(Address(BaselineStubReg, ICMonitoredFallbackStub::offsetOfFallbackMonitorStub()), |
| BaselineStubReg); |
| EmitEnterTypeMonitorIC(masm, ICTypeMonitor_Fallback::offsetOfFirstMonitorStub()); |
| |
| return true; |
| } |
| |
| bool |
| ICCall_Fallback::Compiler::postGenerateStubCode(MacroAssembler &masm, Handle<IonCode *> code) |
| { |
| CodeOffsetLabel offset(returnOffset_); |
| offset.fixup(&masm); |
| cx->compartment()->ionCompartment()->initBaselineCallReturnAddr(code->raw() + offset.offset()); |
| return true; |
| } |
| |
| typedef bool (*CreateThisFn)(JSContext *cx, HandleObject callee, MutableHandleValue rval); |
| static const VMFunction CreateThisInfo = FunctionInfo<CreateThisFn>(CreateThis); |
| |
| bool |
| ICCallScriptedCompiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| GeneralRegisterSet regs(availableGeneralRegs(0)); |
| bool canUseTailCallReg = regs.has(BaselineTailCallReg); |
| |
| Register argcReg = R0.scratchReg(); |
| JS_ASSERT(argcReg != ArgumentsRectifierReg); |
| |
| regs.take(argcReg); |
| regs.take(ArgumentsRectifierReg); |
| if (regs.has(BaselineTailCallReg)) |
| regs.take(BaselineTailCallReg); |
| |
| // Load the callee in R1. |
| // Stack Layout: [ ..., CalleeVal, ThisVal, Arg0Val, ..., ArgNVal, +ICStackValueOffset+ ] |
| BaseIndex calleeSlot(BaselineStackReg, argcReg, TimesEight, ICStackValueOffset + sizeof(Value)); |
| masm.loadValue(calleeSlot, R1); |
| regs.take(R1); |
| |
| // Ensure callee is an object. |
| masm.branchTestObject(Assembler::NotEqual, R1, &failure); |
| |
| // Ensure callee is a function. |
| Register callee = masm.extractObject(R1, ExtractTemp0); |
| masm.branchTestObjClass(Assembler::NotEqual, callee, regs.getAny(), &JSFunction::class_, |
| &failure); |
| |
| // If calling a specific script, check if the script matches. Otherwise, ensure that |
| // callee function is scripted. Leave calleeScript in |callee| reg. |
| if (calleeScript_) { |
| JS_ASSERT(kind == ICStub::Call_Scripted); |
| |
| // Callee is a function. Check if script matches. |
| masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), callee); |
| Address expectedScript(BaselineStubReg, ICCall_Scripted::offsetOfCalleeScript()); |
| masm.branchPtr(Assembler::NotEqual, expectedScript, callee, &failure); |
| } else { |
| #if defined(JS_CPU_MIPS) |
| if (isConstructing_) { |
| masm.branchIfNotInterpretedConstructor(callee, regs.getAny(), &failure); |
| } else { |
| masm.branchIfFunctionHasNoScript(callee, &failure); |
| } |
| masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), callee); |
| #else |
| masm.branchIfFunctionHasNoScript(callee, &failure); |
| masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), callee); |
| #endif |
| } |
| |
| // Load the start of the target IonCode. |
| Register code; |
| if (!isConstructing_) { |
| code = regs.takeAny(); |
| masm.loadBaselineOrIonRaw(callee, code, SequentialExecution, &failure); |
| } else { |
| Address scriptCode(callee, JSScript::offsetOfBaselineOrIonRaw()); |
| masm.branchPtr(Assembler::Equal, scriptCode, ImmWord((void *)NULL), &failure); |
| } |
| |
| // We no longer need R1. |
| regs.add(R1); |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| enterStubFrame(masm, regs.getAny()); |
| if (canUseTailCallReg) |
| regs.add(BaselineTailCallReg); |
| |
| Label failureLeaveStubFrame; |
| |
| if (isConstructing_) { |
| // Save argc before call. |
| masm.push(argcReg); |
| |
| // Stack now looks like: |
| // [..., Callee, ThisV, Arg0V, ..., ArgNV, StubFrameHeader, ArgC ] |
| BaseIndex calleeSlot2(BaselineStackReg, argcReg, TimesEight, |
| sizeof(Value) + STUB_FRAME_SIZE + sizeof(size_t)); |
| masm.loadValue(calleeSlot2, R1); |
| masm.push(masm.extractObject(R1, ExtractTemp0)); |
| if (!callVM(CreateThisInfo, masm)) |
| return false; |
| |
| // Return of CreateThis must be an object. |
| #ifdef DEBUG |
| Label createdThisIsObject; |
| masm.branchTestObject(Assembler::Equal, JSReturnOperand, &createdThisIsObject); |
| masm.breakpoint(); |
| masm.bind(&createdThisIsObject); |
| #endif |
| |
| // Reset the register set from here on in. |
| JS_ASSERT(JSReturnOperand == R0); |
| regs = availableGeneralRegs(0); |
| regs.take(R0); |
| regs.take(ArgumentsRectifierReg); |
| argcReg = regs.takeAny(); |
| |
| // Restore saved argc so we can use it to calculate the address to save |
| // the resulting this object to. |
| masm.pop(argcReg); |
| |
| // Save "this" value back into pushed arguments on stack. R0 can be clobbered after that. |
| // Stack now looks like: |
| // [..., Callee, ThisV, Arg0V, ..., ArgNV, StubFrameHeader ] |
| BaseIndex thisSlot(BaselineStackReg, argcReg, TimesEight, STUB_FRAME_SIZE); |
| masm.storeValue(R0, thisSlot); |
| |
| // Restore the stub register from the baseline stub frame. |
| masm.loadPtr(Address(BaselineStackReg, STUB_FRAME_SAVED_STUB_OFFSET), BaselineStubReg); |
| |
| // Reload callee script. Note that a GC triggered by CreateThis may |
| // have destroyed the callee BaselineScript and IonScript. CreateThis is |
| // safely repeatable though, so in this case we just leave the stub frame |
| // and jump to the next stub. |
| |
| // Just need to load the script now. |
| BaseIndex calleeSlot3(BaselineStackReg, argcReg, TimesEight, |
| sizeof(Value) + STUB_FRAME_SIZE); |
| masm.loadValue(calleeSlot3, R0); |
| callee = masm.extractObject(R0, ExtractTemp0); |
| regs.add(R0); |
| regs.takeUnchecked(callee); |
| masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), callee); |
| |
| code = regs.takeAny(); |
| masm.loadBaselineOrIonRaw(callee, code, SequentialExecution, &failureLeaveStubFrame); |
| |
| // Release callee register, but don't add ExtractTemp0 back into the pool |
| // ExtractTemp0 is used later, and if it's allocated to some other register at that |
| // point, it will get clobbered when used. |
| if (callee != ExtractTemp0) |
| regs.add(callee); |
| |
| if (canUseTailCallReg) |
| regs.addUnchecked(BaselineTailCallReg); |
| } |
| Register scratch = regs.takeAny(); |
| |
| // Values are on the stack left-to-right. Calling convention wants them |
| // right-to-left so duplicate them on the stack in reverse order. |
| // |this| and callee are pushed last. |
| pushCallArguments(masm, regs, argcReg); |
| |
| // The callee is on top of the stack. Pop and unbox it. |
| ValueOperand val = regs.takeAnyValue(); |
| masm.popValue(val); |
| callee = masm.extractObject(val, ExtractTemp0); |
| |
| EmitCreateStubFrameDescriptor(masm, scratch); |
| |
| // Note that we use Push, not push, so that callIon will align the stack |
| // properly on ARM. |
| masm.Push(argcReg); |
| masm.Push(callee); |
| masm.Push(scratch); |
| |
| // Handle arguments underflow. |
| Label noUnderflow; |
| masm.load16ZeroExtend(Address(callee, offsetof(JSFunction, nargs)), callee); |
| masm.branch32(Assembler::AboveOrEqual, argcReg, callee, &noUnderflow); |
| { |
| // Call the arguments rectifier. |
| JS_ASSERT(ArgumentsRectifierReg != code); |
| JS_ASSERT(ArgumentsRectifierReg != argcReg); |
| |
| IonCode *argumentsRectifier = |
| cx->compartment()->ionCompartment()->getArgumentsRectifier(SequentialExecution); |
| |
| masm.movePtr(ImmGCPtr(argumentsRectifier), code); |
| masm.loadPtr(Address(code, IonCode::offsetOfCode()), code); |
| masm.mov(argcReg, ArgumentsRectifierReg); |
| } |
| |
| masm.bind(&noUnderflow); |
| |
| // If needed, update SPS Profiler frame entry before and after call. |
| { |
| Label skipProfilerUpdate; |
| |
| // Need to avoid using ArgumentsRectifierReg and code register. |
| GeneralRegisterSet availRegs = availableGeneralRegs(0); |
| availRegs.take(ArgumentsRectifierReg); |
| availRegs.take(code); |
| Register scratch = availRegs.takeAny(); |
| Register pcIdx = availRegs.takeAny(); |
| |
| // Check if profiling is enabled. |
| guardProfilingEnabled(masm, scratch, &skipProfilerUpdate); |
| |
| // Update profiling entry before leaving function. |
| JS_ASSERT(kind == ICStub::Call_Scripted || kind == ICStub::Call_AnyScripted); |
| if (kind == ICStub::Call_Scripted) |
| masm.load32(Address(BaselineStubReg, ICCall_Scripted::offsetOfPCOffset()), pcIdx); |
| else |
| masm.load32(Address(BaselineStubReg, ICCall_AnyScripted::offsetOfPCOffset()), pcIdx); |
| masm.spsUpdatePCIdx(&cx->runtime()->spsProfiler, pcIdx, scratch); |
| |
| masm.bind(&skipProfilerUpdate); |
| } |
| |
| masm.callIon(code); |
| |
| // If this is a constructing call, and the callee returns a non-object, replace it with |
| // the |this| object passed in. |
| if (isConstructing_) { |
| Label skipThisReplace; |
| masm.branchTestObject(Assembler::Equal, JSReturnOperand, &skipThisReplace); |
| |
| Register scratchReg = JSReturnOperand.scratchReg(); |
| |
| // Current stack: [ ARGVALS..., ThisVal, ActualArgc, Callee, Descriptor ] |
| // However, we can't use this ThisVal, because it hasn't been traced. We need to use |
| // The ThisVal higher up the stack: |
| // Current stack: [ ThisVal, ARGVALS..., ...STUB FRAME..., |
| // ARGVALS..., ThisVal, ActualArgc, Callee, Descriptor ] |
| masm.loadPtr(Address(BaselineStackReg, 2*sizeof(size_t)), scratchReg); |
| |
| // scratchReg now contains actualArgCount. Double it to account for skipping past two |
| // pushed copies of argument values. Additionally, we need to add: |
| // STUB_FRAME_SIZE + sizeof(ThisVal) + sizeof(size_t) + sizeof(void *) + sizoef(size_t) |
| // for: stub frame, this value, actual argc, callee, and descriptor |
| masm.lshiftPtr(Imm32(1), scratchReg); |
| BaseIndex reloadThisSlot(BaselineStackReg, scratchReg, TimesEight, |
| STUB_FRAME_SIZE + sizeof(Value) + 3*sizeof(size_t)); |
| masm.loadValue(reloadThisSlot, JSReturnOperand); |
| #ifdef DEBUG |
| masm.branchTestObject(Assembler::Equal, JSReturnOperand, &skipThisReplace); |
| masm.breakpoint(); |
| #endif |
| masm.bind(&skipThisReplace); |
| } |
| |
| leaveStubFrame(masm, true); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| // Leave stub frame and restore argc for the next stub. |
| masm.bind(&failureLeaveStubFrame); |
| leaveStubFrame(masm, false); |
| if (argcReg != R0.scratchReg()) |
| masm.mov(argcReg, R0.scratchReg()); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICCall_Native::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| GeneralRegisterSet regs(availableGeneralRegs(0)); |
| |
| Register argcReg = R0.scratchReg(); |
| regs.take(argcReg); |
| regs.takeUnchecked(BaselineTailCallReg); |
| |
| // Load the callee in R1. |
| BaseIndex calleeSlot(BaselineStackReg, argcReg, TimesEight, ICStackValueOffset + sizeof(Value)); |
| masm.loadValue(calleeSlot, R1); |
| regs.take(R1); |
| |
| masm.branchTestObject(Assembler::NotEqual, R1, &failure); |
| |
| // Ensure callee matches this stub's callee. |
| Register callee = masm.extractObject(R1, ExtractTemp0); |
| Address expectedCallee(BaselineStubReg, ICCall_Native::offsetOfCallee()); |
| masm.branchPtr(Assembler::NotEqual, expectedCallee, callee, &failure); |
| |
| regs.add(R1); |
| regs.takeUnchecked(callee); |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| // Note that this leaves the return address in TailCallReg. |
| enterStubFrame(masm, regs.getAny()); |
| |
| // Values are on the stack left-to-right. Calling convention wants them |
| // right-to-left so duplicate them on the stack in reverse order. |
| // |this| and callee are pushed last. |
| pushCallArguments(masm, regs, argcReg); |
| |
| if (isConstructing_) { |
| // Stack looks like: [ ..., Arg0Val, ThisVal, CalleeVal ] |
| // Replace ThisVal with MagicValue(JS_IS_CONSTRUCTING) |
| masm.storeValue(MagicValue(JS_IS_CONSTRUCTING), Address(BaselineStackReg, sizeof(Value))); |
| } |
| |
| masm.checkStackAlignment(); |
| |
| // Native functions have the signature: |
| // |
| // bool (*)(JSContext *, unsigned, Value *vp) |
| // |
| // Where vp[0] is space for callee/return value, vp[1] is |this|, and vp[2] onward |
| // are the function arguments. |
| |
| // Initialize vp. |
| Register vpReg = regs.takeAny(); |
| masm.movePtr(StackPointer, vpReg); |
| |
| // Construct a native exit frame. |
| masm.push(argcReg); |
| |
| Register scratch = regs.takeAny(); |
| EmitCreateStubFrameDescriptor(masm, scratch); |
| masm.push(scratch); |
| masm.push(BaselineTailCallReg); |
| masm.enterFakeExitFrame(); |
| |
| // If needed, update SPS Profiler frame entry. At this point, BaselineTailCallReg |
| // and scratch can be clobbered. |
| { |
| Label skipProfilerUpdate; |
| Register pcIdx = BaselineTailCallReg; |
| guardProfilingEnabled(masm, scratch, &skipProfilerUpdate); |
| |
| masm.load32(Address(BaselineStubReg, ICCall_Native::offsetOfPCOffset()), pcIdx); |
| masm.spsUpdatePCIdx(&cx->runtime()->spsProfiler, pcIdx, scratch); |
| |
| masm.bind(&skipProfilerUpdate); |
| } |
| // Execute call. |
| masm.setupUnalignedABICall(3, scratch); |
| masm.loadJSContext(scratch); |
| masm.passABIArg(scratch); |
| masm.passABIArg(argcReg); |
| masm.passABIArg(vpReg); |
| masm.callWithABI(Address(callee, JSFunction::offsetOfNativeOrScript())); |
| |
| // Test for failure. |
| Label success, exception; |
| masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, &exception); |
| |
| // Load the return value into R0. |
| masm.loadValue(Address(StackPointer, IonNativeExitFrameLayout::offsetOfResult()), R0); |
| |
| leaveStubFrame(masm); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| // Handle exception case. |
| masm.bind(&exception); |
| masm.handleException(); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICCall_ScriptedApplyArguments::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| GeneralRegisterSet regs(availableGeneralRegs(0)); |
| |
| Register argcReg = R0.scratchReg(); |
| regs.take(argcReg); |
| regs.takeUnchecked(BaselineTailCallReg); |
| regs.takeUnchecked(ArgumentsRectifierReg); |
| |
| // |
| // Validate inputs |
| // |
| |
| Register target = guardFunApply(masm, regs, argcReg, /*checkNative=*/false, &failure); |
| if (regs.has(target)) { |
| regs.take(target); |
| } else { |
| // If target is already a reserved reg, take another register for it, because it's |
| // probably currently an ExtractTemp, which might get clobbered later. |
| Register targetTemp = regs.takeAny(); |
| masm.movePtr(target, targetTemp); |
| target = targetTemp; |
| } |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| enterStubFrame(masm, regs.getAny()); |
| |
| // |
| // Push arguments |
| // |
| |
| // Stack now looks like: |
| // [..., js_fun_apply, TargetV, TargetThisV, MagicArgsV, StubFrameHeader] |
| |
| // Push all arguments supplied to caller function onto the stack. |
| pushCallerArguments(masm, regs); |
| |
| // Stack now looks like: |
| // BaselineFrameReg -------------------. |
| // v |
| // [..., js_fun_apply, TargetV, TargetThisV, MagicArgsV, StubFrameHeader, |
| // PushedArgN, ..., PushedArg0] |
| // Can't fail after this, so it's ok to release argcReg back. |
| |
| // Push actual argument 0 as |thisv| for call. |
| masm.pushValue(Address(BaselineFrameReg, STUB_FRAME_SIZE + sizeof(Value))); |
| |
| // All pushes after this use Push instead of push to make sure ARM can align |
| // stack properly for call. |
| Register scratch = regs.takeAny(); |
| EmitCreateStubFrameDescriptor(masm, scratch); |
| |
| masm.loadPtr(Address(BaselineFrameReg, 0), argcReg); |
| masm.loadPtr(Address(argcReg, BaselineFrame::offsetOfNumActualArgs()), argcReg); |
| masm.Push(argcReg); |
| masm.Push(target); |
| masm.Push(scratch); |
| |
| // Load nargs into scratch for underflow check, and then load jitcode pointer into target. |
| masm.load16ZeroExtend(Address(target, offsetof(JSFunction, nargs)), scratch); |
| masm.loadPtr(Address(target, JSFunction::offsetOfNativeOrScript()), target); |
| masm.loadBaselineOrIonRaw(target, target, SequentialExecution, NULL); |
| |
| // Handle arguments underflow. |
| Label noUnderflow; |
| masm.branch32(Assembler::AboveOrEqual, argcReg, scratch, &noUnderflow); |
| { |
| // Call the arguments rectifier. |
| JS_ASSERT(ArgumentsRectifierReg != target); |
| JS_ASSERT(ArgumentsRectifierReg != argcReg); |
| |
| IonCode *argumentsRectifier = |
| cx->compartment()->ionCompartment()->getArgumentsRectifier(SequentialExecution); |
| |
| masm.movePtr(ImmGCPtr(argumentsRectifier), target); |
| masm.loadPtr(Address(target, IonCode::offsetOfCode()), target); |
| masm.mov(argcReg, ArgumentsRectifierReg); |
| } |
| masm.bind(&noUnderflow); |
| regs.add(argcReg); |
| |
| // If needed, update SPS Profiler frame entry. At this point, BaselineTailCallReg |
| // and scratch can be clobbered. |
| { |
| Label skipProfilerUpdate; |
| Register pcIdx = regs.getAny(); |
| JS_ASSERT(pcIdx != ArgumentsRectifierReg); |
| JS_ASSERT(pcIdx != target); |
| guardProfilingEnabled(masm, scratch, &skipProfilerUpdate); |
| |
| masm.load32(Address(BaselineStubReg, ICCall_ScriptedApplyArguments::offsetOfPCOffset()), |
| pcIdx); |
| masm.spsUpdatePCIdx(&cx->runtime()->spsProfiler, pcIdx, scratch); |
| |
| masm.bind(&skipProfilerUpdate); |
| } |
| // Do call |
| masm.callIon(target); |
| leaveStubFrame(masm, true); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| static JSBool |
| DoubleValueToInt32ForSwitch(Value *v) |
| { |
| double d = v->toDouble(); |
| int32_t truncated = int32_t(d); |
| if (d != double(truncated)) |
| return false; |
| |
| v->setInt32(truncated); |
| return true; |
| } |
| |
| bool |
| ICTableSwitch::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label isInt32, notInt32, outOfRange; |
| Register scratch = R1.scratchReg(); |
| |
| masm.branchTestInt32(Assembler::NotEqual, R0, ¬Int32); |
| |
| Register key = masm.extractInt32(R0, ExtractTemp0); |
| |
| masm.bind(&isInt32); |
| |
| masm.load32(Address(BaselineStubReg, offsetof(ICTableSwitch, min_)), scratch); |
| masm.sub32(scratch, key); |
| masm.branch32(Assembler::BelowOrEqual, |
| Address(BaselineStubReg, offsetof(ICTableSwitch, length_)), key, &outOfRange); |
| |
| masm.loadPtr(Address(BaselineStubReg, offsetof(ICTableSwitch, table_)), scratch); |
| masm.loadPtr(BaseIndex(scratch, key, ScalePointer), scratch); |
| |
| EmitChangeICReturnAddress(masm, scratch); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(¬Int32); |
| |
| masm.branchTestDouble(Assembler::NotEqual, R0, &outOfRange); |
| if (cx->runtime()->jitSupportsFloatingPoint) { |
| masm.unboxDouble(R0, FloatReg0); |
| |
| // N.B. -0 === 0, so convert -0 to a 0 int32. |
| masm.convertDoubleToInt32(FloatReg0, key, &outOfRange, /* negativeZeroCheck = */ false); |
| } else { |
| // Pass pointer to double value. |
| masm.pushValue(R0); |
| masm.movePtr(StackPointer, R0.scratchReg()); |
| |
| masm.setupUnalignedABICall(1, scratch); |
| masm.passABIArg(R0.scratchReg()); |
| masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, DoubleValueToInt32ForSwitch)); |
| |
| // If the function returns |true|, the value has been converted to |
| // int32. |
| masm.mov(ReturnReg, scratch); |
| masm.popValue(R0); |
| masm.branchTest32(Assembler::Zero, scratch, scratch, &outOfRange); |
| masm.unboxInt32(R0, key); |
| } |
| masm.jump(&isInt32); |
| |
| masm.bind(&outOfRange); |
| |
| masm.loadPtr(Address(BaselineStubReg, offsetof(ICTableSwitch, defaultTarget_)), scratch); |
| |
| EmitChangeICReturnAddress(masm, scratch); |
| EmitReturnFromIC(masm); |
| return true; |
| } |
| |
| ICStub * |
| ICTableSwitch::Compiler::getStub(ICStubSpace *space) |
| { |
| IonCode *code = getStubCode(); |
| if (!code) |
| return NULL; |
| |
| jsbytecode *pc = pc_; |
| pc += JUMP_OFFSET_LEN; |
| int32_t low = GET_JUMP_OFFSET(pc); |
| pc += JUMP_OFFSET_LEN; |
| int32_t high = GET_JUMP_OFFSET(pc); |
| int32_t length = high - low + 1; |
| pc += JUMP_OFFSET_LEN; |
| |
| void **table = (void**) space->alloc(sizeof(void*) * length); |
| if (!table) |
| return NULL; |
| |
| jsbytecode *defaultpc = pc_ + GET_JUMP_OFFSET(pc_); |
| |
| for (int32_t i = 0; i < length; i++) { |
| int32_t off = GET_JUMP_OFFSET(pc); |
| if (off) |
| table[i] = pc_ + off; |
| else |
| table[i] = defaultpc; |
| pc += JUMP_OFFSET_LEN; |
| } |
| |
| return ICTableSwitch::New(space, code, table, low, length, defaultpc); |
| } |
| |
| void |
| ICTableSwitch::fixupJumpTable(HandleScript script, BaselineScript *baseline) |
| { |
| defaultTarget_ = baseline->nativeCodeForPC(script, (jsbytecode *) defaultTarget_); |
| |
| for (int32_t i = 0; i < length_; i++) |
| table_[i] = baseline->nativeCodeForPC(script, (jsbytecode *) table_[i]); |
| } |
| |
| // |
| // IteratorNew_Fallback |
| // |
| |
| static bool |
| DoIteratorNewFallback(JSContext *cx, BaselineFrame *frame, ICIteratorNew_Fallback *stub, |
| HandleValue value, MutableHandleValue res) |
| { |
| jsbytecode *pc = stub->icEntry()->pc(frame->script()); |
| FallbackICSpew(cx, stub, "IteratorNew"); |
| |
| uint8_t flags = GET_UINT8(pc); |
| res.set(value); |
| return ValueToIterator(cx, flags, res); |
| } |
| |
| typedef bool (*DoIteratorNewFallbackFn)(JSContext *, BaselineFrame *, ICIteratorNew_Fallback *, |
| HandleValue, MutableHandleValue); |
| static const VMFunction DoIteratorNewFallbackInfo = |
| FunctionInfo<DoIteratorNewFallbackFn>(DoIteratorNewFallback, PopValues(1)); |
| |
| bool |
| ICIteratorNew_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| EmitRestoreTailCallReg(masm); |
| |
| // Sync stack for the decompiler. |
| masm.pushValue(R0); |
| |
| masm.pushValue(R0); |
| masm.push(BaselineStubReg); |
| masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| return tailCallVM(DoIteratorNewFallbackInfo, masm); |
| } |
| |
| // |
| // IteratorMore_Fallback |
| // |
| |
| static bool |
| DoIteratorMoreFallback(JSContext *cx, BaselineFrame *frame, ICIteratorMore_Fallback *stub, |
| HandleValue iterValue, MutableHandleValue res) |
| { |
| FallbackICSpew(cx, stub, "IteratorMore"); |
| |
| bool cond; |
| if (!IteratorMore(cx, &iterValue.toObject(), &cond, res)) |
| return false; |
| res.setBoolean(cond); |
| |
| if (iterValue.toObject().is<PropertyIteratorObject>() && |
| !stub->hasStub(ICStub::IteratorMore_Native)) |
| { |
| ICIteratorMore_Native::Compiler compiler(cx); |
| ICStub *newStub = compiler.getStub(compiler.getStubSpace(frame->script())); |
| if (!newStub) |
| return false; |
| stub->addNewStub(newStub); |
| } |
| |
| return true; |
| } |
| |
| typedef bool (*DoIteratorMoreFallbackFn)(JSContext *, BaselineFrame *, ICIteratorMore_Fallback *, |
| HandleValue, MutableHandleValue); |
| static const VMFunction DoIteratorMoreFallbackInfo = |
| FunctionInfo<DoIteratorMoreFallbackFn>(DoIteratorMoreFallback); |
| |
| bool |
| ICIteratorMore_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| EmitRestoreTailCallReg(masm); |
| |
| masm.pushValue(R0); |
| masm.push(BaselineStubReg); |
| masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| return tailCallVM(DoIteratorMoreFallbackInfo, masm); |
| } |
| |
| // |
| // IteratorMore_Native |
| // |
| |
| bool |
| ICIteratorMore_Native::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| |
| Register obj = masm.extractObject(R0, ExtractTemp0); |
| |
| GeneralRegisterSet regs(availableGeneralRegs(1)); |
| Register nativeIterator = regs.takeAny(); |
| Register scratch = regs.takeAny(); |
| |
| masm.branchTestObjClass(Assembler::NotEqual, obj, scratch, |
| &PropertyIteratorObject::class_, &failure); |
| masm.loadObjPrivate(obj, JSObject::ITER_CLASS_NFIXED_SLOTS, nativeIterator); |
| |
| masm.branchTest32(Assembler::NonZero, Address(nativeIterator, offsetof(NativeIterator, flags)), |
| Imm32(JSITER_FOREACH), &failure); |
| |
| // Set output to true if props_cursor < props_end. |
| masm.loadPtr(Address(nativeIterator, offsetof(NativeIterator, props_end)), scratch); |
| #if defined(JS_CPU_MIPS) |
| Address cursorAddr = Address(nativeIterator, offsetof(NativeIterator, props_cursor)); |
| masm.cmpPtrSet(Assembler::LessThan, cursorAddr, scratch, scratch); |
| #else |
| masm.cmpPtr(Address(nativeIterator, offsetof(NativeIterator, props_cursor)), scratch); |
| masm.emitSet(Assembler::LessThan, scratch); |
| #endif |
| |
| masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch, R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // IteratorNext_Fallback |
| // |
| |
| static bool |
| DoIteratorNextFallback(JSContext *cx, BaselineFrame *frame, ICIteratorNext_Fallback *stub, |
| HandleValue iterValue, MutableHandleValue res) |
| { |
| FallbackICSpew(cx, stub, "IteratorNext"); |
| |
| RootedObject iteratorObject(cx, &iterValue.toObject()); |
| if (!IteratorNext(cx, iteratorObject, res)) |
| return false; |
| |
| if (iteratorObject->is<PropertyIteratorObject>() && |
| !stub->hasStub(ICStub::IteratorNext_Native)) |
| { |
| ICIteratorNext_Native::Compiler compiler(cx); |
| ICStub *newStub = compiler.getStub(compiler.getStubSpace(frame->script())); |
| if (!newStub) |
| return false; |
| stub->addNewStub(newStub); |
| } |
| |
| return true; |
| } |
| |
| typedef bool (*DoIteratorNextFallbackFn)(JSContext *, BaselineFrame *, ICIteratorNext_Fallback *, |
| HandleValue, MutableHandleValue); |
| static const VMFunction DoIteratorNextFallbackInfo = |
| FunctionInfo<DoIteratorNextFallbackFn>(DoIteratorNextFallback); |
| |
| bool |
| ICIteratorNext_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| EmitRestoreTailCallReg(masm); |
| |
| masm.pushValue(R0); |
| masm.push(BaselineStubReg); |
| masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| return tailCallVM(DoIteratorNextFallbackInfo, masm); |
| } |
| |
| // |
| // IteratorNext_Native |
| // |
| |
| bool |
| ICIteratorNext_Native::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| Label failure; |
| |
| Register obj = masm.extractObject(R0, ExtractTemp0); |
| |
| GeneralRegisterSet regs(availableGeneralRegs(1)); |
| Register nativeIterator = regs.takeAny(); |
| Register scratch = regs.takeAny(); |
| |
| masm.branchTestObjClass(Assembler::NotEqual, obj, scratch, |
| &PropertyIteratorObject::class_, &failure); |
| masm.loadObjPrivate(obj, JSObject::ITER_CLASS_NFIXED_SLOTS, nativeIterator); |
| |
| masm.branchTest32(Assembler::NonZero, Address(nativeIterator, offsetof(NativeIterator, flags)), |
| Imm32(JSITER_FOREACH), &failure); |
| |
| // Get cursor, next string. |
| masm.loadPtr(Address(nativeIterator, offsetof(NativeIterator, props_cursor)), scratch); |
| masm.loadPtr(Address(scratch, 0), scratch); |
| |
| // Increase the cursor. |
| masm.addPtr(Imm32(sizeof(JSString *)), |
| Address(nativeIterator, offsetof(NativeIterator, props_cursor))); |
| |
| masm.tagValue(JSVAL_TYPE_STRING, scratch, R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // IteratorClose_Fallback |
| // |
| |
| static bool |
| DoIteratorCloseFallback(JSContext *cx, ICIteratorClose_Fallback *stub, HandleValue iterValue) |
| { |
| FallbackICSpew(cx, stub, "IteratorClose"); |
| |
| RootedObject iteratorObject(cx, &iterValue.toObject()); |
| return CloseIterator(cx, iteratorObject); |
| } |
| |
| typedef bool (*DoIteratorCloseFallbackFn)(JSContext *, ICIteratorClose_Fallback *, HandleValue); |
| static const VMFunction DoIteratorCloseFallbackInfo = |
| FunctionInfo<DoIteratorCloseFallbackFn>(DoIteratorCloseFallback); |
| |
| bool |
| ICIteratorClose_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| EmitRestoreTailCallReg(masm); |
| |
| masm.pushValue(R0); |
| masm.push(BaselineStubReg); |
| |
| return tailCallVM(DoIteratorCloseFallbackInfo, masm); |
| } |
| |
| // |
| // InstanceOf_Fallback |
| // |
| |
| static bool |
| DoInstanceOfFallback(JSContext *cx, ICInstanceOf_Fallback *stub, |
| HandleValue lhs, HandleValue rhs, |
| MutableHandleValue res) |
| { |
| FallbackICSpew(cx, stub, "InstanceOf"); |
| |
| if (!rhs.isObject()) { |
| js_ReportValueError(cx, JSMSG_BAD_INSTANCEOF_RHS, -1, rhs, NullPtr()); |
| return false; |
| } |
| |
| RootedObject obj(cx, &rhs.toObject()); |
| |
| JSBool cond = false; |
| if (!HasInstance(cx, obj, lhs, &cond)) |
| return false; |
| |
| res.setBoolean(cond); |
| return true; |
| } |
| |
| typedef bool (*DoInstanceOfFallbackFn)(JSContext *, ICInstanceOf_Fallback *, HandleValue, HandleValue, |
| MutableHandleValue); |
| static const VMFunction DoInstanceOfFallbackInfo = |
| FunctionInfo<DoInstanceOfFallbackFn>(DoInstanceOfFallback, PopValues(2)); |
| |
| bool |
| ICInstanceOf_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| EmitRestoreTailCallReg(masm); |
| |
| // Sync stack for the decompiler. |
| masm.pushValue(R0); |
| masm.pushValue(R1); |
| |
| masm.pushValue(R1); |
| masm.pushValue(R0); |
| masm.push(BaselineStubReg); |
| |
| return tailCallVM(DoInstanceOfFallbackInfo, masm); |
| } |
| |
| // |
| // TypeOf_Fallback |
| // |
| |
| static bool |
| DoTypeOfFallback(JSContext *cx, BaselineFrame *frame, ICTypeOf_Fallback *stub, HandleValue val, |
| MutableHandleValue res) |
| { |
| FallbackICSpew(cx, stub, "TypeOf"); |
| JSType type = JS_TypeOfValue(cx, val); |
| RootedString string(cx, TypeName(type, cx)); |
| |
| res.setString(string); |
| |
| JS_ASSERT(type != JSTYPE_NULL); |
| if (type != JSTYPE_OBJECT && type != JSTYPE_FUNCTION) { |
| // Create a new TypeOf stub. |
| IonSpew(IonSpew_BaselineIC, " Generating TypeOf stub for JSType (%d)", (int) type); |
| ICTypeOf_Typed::Compiler compiler(cx, type, string); |
| ICStub *typeOfStub = compiler.getStub(compiler.getStubSpace(frame->script())); |
| if (!typeOfStub) |
| return false; |
| stub->addNewStub(typeOfStub); |
| } |
| |
| return true; |
| } |
| |
| typedef bool (*DoTypeOfFallbackFn)(JSContext *, BaselineFrame *frame, ICTypeOf_Fallback *, |
| HandleValue, MutableHandleValue); |
| static const VMFunction DoTypeOfFallbackInfo = |
| FunctionInfo<DoTypeOfFallbackFn>(DoTypeOfFallback); |
| |
| bool |
| ICTypeOf_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| EmitRestoreTailCallReg(masm); |
| |
| masm.pushValue(R0); |
| masm.push(BaselineStubReg); |
| masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| return tailCallVM(DoTypeOfFallbackInfo, masm); |
| } |
| |
| bool |
| ICTypeOf_Typed::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| JS_ASSERT(type_ != JSTYPE_NULL); |
| JS_ASSERT(type_ != JSTYPE_FUNCTION); |
| JS_ASSERT(type_ != JSTYPE_OBJECT); |
| |
| Label failure; |
| switch(type_) { |
| case JSTYPE_VOID: |
| masm.branchTestUndefined(Assembler::NotEqual, R0, &failure); |
| break; |
| |
| case JSTYPE_STRING: |
| masm.branchTestString(Assembler::NotEqual, R0, &failure); |
| break; |
| |
| case JSTYPE_NUMBER: |
| masm.branchTestNumber(Assembler::NotEqual, R0, &failure); |
| break; |
| |
| case JSTYPE_BOOLEAN: |
| masm.branchTestBoolean(Assembler::NotEqual, R0, &failure); |
| break; |
| |
| default: |
| JS_NOT_REACHED("Unexpected type"); |
| } |
| |
| masm.movePtr(ImmGCPtr(typeString_), R0.scratchReg()); |
| masm.tagValue(JSVAL_TYPE_STRING, R0.scratchReg(), R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // Rest_Fallback |
| // |
| |
| static bool |
| DoCreateRestParameter(JSContext *cx, BaselineFrame *frame, ICRest_Fallback *stub, |
| HandleTypeObject type, MutableHandleValue res) |
| { |
| FallbackICSpew(cx, stub, "Rest"); |
| |
| unsigned numFormals = frame->numFormalArgs() - 1; |
| unsigned numActuals = frame->numActualArgs(); |
| unsigned numRest = numActuals > numFormals ? numActuals - numFormals : 0; |
| Value *rest = frame->argv() + numFormals; |
| |
| JSObject *obj = NewDenseCopiedArray(cx, numRest, rest, NULL); |
| if (!obj) |
| return false; |
| obj->setType(type); |
| |
| // Ensure that values in the rest array are represented in the type of the |
| // array. |
| for (unsigned i = 0; i < numRest; i++) |
| types::AddTypePropertyId(cx, obj, JSID_VOID, rest[i]); |
| |
| res.setObject(*obj); |
| return true; |
| } |
| |
| typedef bool(*DoCreateRestParameterFn)(JSContext *cx, BaselineFrame *, ICRest_Fallback *, |
| HandleTypeObject, MutableHandleValue); |
| static const VMFunction DoCreateRestParameterInfo = |
| FunctionInfo<DoCreateRestParameterFn>(DoCreateRestParameter); |
| |
| bool |
| ICRest_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| EmitRestoreTailCallReg(masm); |
| |
| masm.push(R0.scratchReg()); // type |
| masm.push(BaselineStubReg); // stub |
| masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); // frame pointer |
| |
| return tailCallVM(DoCreateRestParameterInfo, masm); |
| } |
| |
| static bool |
| DoRetSubFallback(JSContext *cx, BaselineFrame *frame, ICRetSub_Fallback *stub, |
| HandleValue val, uint8_t **resumeAddr) |
| { |
| FallbackICSpew(cx, stub, "RetSub"); |
| |
| // |val| is the bytecode offset where we should resume. |
| |
| JS_ASSERT(val.isInt32()); |
| JS_ASSERT(val.toInt32() >= 0); |
| |
| JSScript *script = frame->script(); |
| uint32_t offset = uint32_t(val.toInt32()); |
| JS_ASSERT(offset < script->length); |
| |
| *resumeAddr = script->baselineScript()->nativeCodeForPC(script, script->code + offset); |
| |
| if (stub->numOptimizedStubs() >= ICRetSub_Fallback::MAX_OPTIMIZED_STUBS) |
| return true; |
| |
| // Attach an optimized stub for this pc offset. |
| IonSpew(IonSpew_BaselineIC, " Generating RetSub stub for pc offset %u", offset); |
| ICRetSub_Resume::Compiler compiler(cx, offset, *resumeAddr); |
| ICStub *optStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!optStub) |
| return false; |
| |
| stub->addNewStub(optStub); |
| return true; |
| } |
| |
| typedef bool(*DoRetSubFallbackFn)(JSContext *cx, BaselineFrame *, ICRetSub_Fallback *, |
| HandleValue, uint8_t **); |
| static const VMFunction DoRetSubFallbackInfo = FunctionInfo<DoRetSubFallbackFn>(DoRetSubFallback); |
| |
| typedef bool (*ThrowFn)(JSContext *, HandleValue); |
| static const VMFunction ThrowInfo = FunctionInfo<ThrowFn>(js::Throw); |
| |
| bool |
| ICRetSub_Fallback::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| // If R0 is BooleanValue(true), rethrow R1. |
| Label rethrow; |
| masm.branchTestBooleanTruthy(true, R0, &rethrow); |
| { |
| // Call a stub to get the native code address for the pc offset in R1. |
| GeneralRegisterSet regs(availableGeneralRegs(0)); |
| regs.take(R1); |
| regs.takeUnchecked(BaselineTailCallReg); |
| |
| Register frame = regs.takeAny(); |
| masm.movePtr(BaselineFrameReg, frame); |
| |
| enterStubFrame(masm, regs.getAny()); |
| |
| masm.pushValue(R1); |
| masm.push(BaselineStubReg); |
| masm.pushBaselineFramePtr(frame, frame); |
| |
| if (!callVM(DoRetSubFallbackInfo, masm)) |
| return false; |
| |
| leaveStubFrame(masm); |
| |
| EmitChangeICReturnAddress(masm, ReturnReg); |
| EmitReturnFromIC(masm); |
| } |
| |
| masm.bind(&rethrow); |
| EmitRestoreTailCallReg(masm); |
| masm.pushValue(R1); |
| return tailCallVM(ThrowInfo, masm); |
| } |
| |
| bool |
| ICRetSub_Resume::Compiler::generateStubCode(MacroAssembler &masm) |
| { |
| // If R0 is BooleanValue(true), rethrow R1. |
| Label fail, rethrow; |
| masm.branchTestBooleanTruthy(true, R0, &rethrow); |
| |
| // R1 is the pc offset. Ensure it matches this stub's offset. |
| Register offset = masm.extractInt32(R1, ExtractTemp0); |
| masm.branch32(Assembler::NotEqual, |
| Address(BaselineStubReg, ICRetSub_Resume::offsetOfPCOffset()), |
| offset, |
| &fail); |
| |
| // pc offset matches, resume at the target pc. |
| masm.loadPtr(Address(BaselineStubReg, ICRetSub_Resume::offsetOfAddr()), R0.scratchReg()); |
| EmitChangeICReturnAddress(masm, R0.scratchReg()); |
| EmitReturnFromIC(masm); |
| |
| // Rethrow the Value stored in R1. |
| masm.bind(&rethrow); |
| EmitRestoreTailCallReg(masm); |
| masm.pushValue(R1); |
| if (!tailCallVM(ThrowInfo, masm)) |
| return false; |
| |
| masm.bind(&fail); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| ICProfiler_PushFunction::ICProfiler_PushFunction(IonCode *stubCode, const char *str, |
| HandleScript script) |
| : ICStub(ICStub::Profiler_PushFunction, stubCode), |
| str_(str), |
| script_(script) |
| { } |
| |
| ICTypeMonitor_SingleObject::ICTypeMonitor_SingleObject(IonCode *stubCode, HandleObject obj) |
| : ICStub(TypeMonitor_SingleObject, stubCode), |
| obj_(obj) |
| { } |
| |
| ICTypeMonitor_TypeObject::ICTypeMonitor_TypeObject(IonCode *stubCode, HandleTypeObject type) |
| : ICStub(TypeMonitor_TypeObject, stubCode), |
| type_(type) |
| { } |
| |
| ICTypeUpdate_SingleObject::ICTypeUpdate_SingleObject(IonCode *stubCode, HandleObject obj) |
| : ICStub(TypeUpdate_SingleObject, stubCode), |
| obj_(obj) |
| { } |
| |
| ICTypeUpdate_TypeObject::ICTypeUpdate_TypeObject(IonCode *stubCode, HandleTypeObject type) |
| : ICStub(TypeUpdate_TypeObject, stubCode), |
| type_(type) |
| { } |
| |
| ICGetElemNativeStub::ICGetElemNativeStub(ICStub::Kind kind, IonCode *stubCode, |
| ICStub *firstMonitorStub, |
| HandleShape shape, HandleValue idval, |
| bool isFixedSlot, uint32_t offset) |
| : ICMonitoredStub(kind, stubCode, firstMonitorStub), |
| shape_(shape), |
| idval_(idval), |
| offset_(offset) |
| { |
| extra_ = isFixedSlot; |
| } |
| |
| ICGetElemNativeStub::~ICGetElemNativeStub() |
| { } |
| |
| ICGetElem_NativePrototype::ICGetElem_NativePrototype(IonCode *stubCode, ICStub *firstMonitorStub, |
| HandleShape shape, HandleValue idval, |
| bool isFixedSlot, uint32_t offset, |
| HandleObject holder, HandleShape holderShape) |
| : ICGetElemNativeStub(ICStub::GetElem_NativePrototype, stubCode, firstMonitorStub, shape, |
| idval, isFixedSlot, offset), |
| holder_(holder), |
| holderShape_(holderShape) |
| { } |
| |
| ICGetElem_Dense::ICGetElem_Dense(IonCode *stubCode, ICStub *firstMonitorStub, HandleShape shape) |
| : ICMonitoredStub(GetElem_Dense, stubCode, firstMonitorStub), |
| shape_(shape) |
| { } |
| |
| ICGetElem_TypedArray::ICGetElem_TypedArray(IonCode *stubCode, HandleShape shape, uint32_t type) |
| : ICStub(GetElem_TypedArray, stubCode), |
| shape_(shape) |
| { |
| extra_ = uint16_t(type); |
| JS_ASSERT(extra_ == type); |
| } |
| |
| ICSetElem_Dense::ICSetElem_Dense(IonCode *stubCode, HandleShape shape, HandleTypeObject type) |
| : ICUpdatedStub(SetElem_Dense, stubCode), |
| shape_(shape), |
| type_(type) |
| { } |
| |
| ICSetElem_DenseAdd::ICSetElem_DenseAdd(IonCode *stubCode, types::TypeObject *type, |
| size_t protoChainDepth) |
| : ICUpdatedStub(SetElem_DenseAdd, stubCode), |
| type_(type) |
| { |
| JS_ASSERT(protoChainDepth <= MAX_PROTO_CHAIN_DEPTH); |
| extra_ = protoChainDepth; |
| } |
| |
| template <size_t ProtoChainDepth> |
| ICUpdatedStub * |
| ICSetElemDenseAddCompiler::getStubSpecific(ICStubSpace *space, const AutoShapeVector *shapes) |
| { |
| RootedTypeObject objType(cx, obj_->getType(cx)); |
| Rooted<IonCode *> stubCode(cx, getStubCode()); |
| return ICSetElem_DenseAddImpl<ProtoChainDepth>::New(space, stubCode, objType, shapes); |
| } |
| |
| ICSetElem_TypedArray::ICSetElem_TypedArray(IonCode *stubCode, HandleShape shape, uint32_t type, |
| bool expectOutOfBounds) |
| : ICStub(SetElem_TypedArray, stubCode), |
| shape_(shape) |
| { |
| extra_ = uint8_t(type); |
| JS_ASSERT(extra_ == type); |
| extra_ |= (static_cast<uint16_t>(expectOutOfBounds) << 8); |
| } |
| |
| ICGetName_Global::ICGetName_Global(IonCode *stubCode, ICStub *firstMonitorStub, HandleShape shape, |
| uint32_t slot) |
| : ICMonitoredStub(GetName_Global, stubCode, firstMonitorStub), |
| shape_(shape), |
| slot_(slot) |
| { } |
| |
| template <size_t NumHops> |
| ICGetName_Scope<NumHops>::ICGetName_Scope(IonCode *stubCode, ICStub *firstMonitorStub, |
| AutoShapeVector *shapes, uint32_t offset) |
| : ICMonitoredStub(GetStubKind(), stubCode, firstMonitorStub), |
| offset_(offset) |
| { |
| JS_STATIC_ASSERT(NumHops <= MAX_HOPS); |
| JS_ASSERT(shapes->length() == NumHops + 1); |
| for (size_t i = 0; i < NumHops + 1; i++) |
| shapes_[i].init((*shapes)[i]); |
| } |
| |
| ICGetIntrinsic_Constant::ICGetIntrinsic_Constant(IonCode *stubCode, HandleValue value) |
| : ICStub(GetIntrinsic_Constant, stubCode), |
| value_(value) |
| { } |
| |
| ICGetIntrinsic_Constant::~ICGetIntrinsic_Constant() |
| { } |
| |
| ICGetProp_String::ICGetProp_String(IonCode *stubCode, ICStub *firstMonitorStub, |
| HandleShape stringProtoShape, uint32_t offset) |
| : ICMonitoredStub(GetProp_String, stubCode, firstMonitorStub), |
| stringProtoShape_(stringProtoShape), |
| offset_(offset) |
| { } |
| |
| ICGetPropNativeStub::ICGetPropNativeStub(ICStub::Kind kind, IonCode *stubCode, |
| ICStub *firstMonitorStub, |
| HandleShape shape, uint32_t offset) |
| : ICMonitoredStub(kind, stubCode, firstMonitorStub), |
| shape_(shape), |
| offset_(offset) |
| { } |
| |
| ICGetProp_NativePrototype::ICGetProp_NativePrototype(IonCode *stubCode, ICStub *firstMonitorStub, |
| HandleShape shape, uint32_t offset, |
| HandleObject holder, HandleShape holderShape) |
| : ICGetPropNativeStub(GetProp_NativePrototype, stubCode, firstMonitorStub, shape, offset), |
| holder_(holder), |
| holderShape_(holderShape) |
| { } |
| |
| ICGetPropCallGetter::ICGetPropCallGetter(Kind kind, IonCode *stubCode, ICStub *firstMonitorStub, |
| HandleShape shape, HandleObject holder, |
| HandleShape holderShape, |
| HandleFunction getter, uint32_t pcOffset) |
| : ICMonitoredStub(kind, stubCode, firstMonitorStub), |
| shape_(shape), |
| holder_(holder), |
| holderShape_(holderShape), |
| getter_(getter), |
| pcOffset_(pcOffset) |
| { |
| JS_ASSERT(kind == ICStub::GetProp_CallScripted || kind == ICStub::GetProp_CallNative); |
| } |
| |
| ICSetProp_Native::ICSetProp_Native(IonCode *stubCode, HandleTypeObject type, HandleShape shape, |
| uint32_t offset) |
| : ICUpdatedStub(SetProp_Native, stubCode), |
| type_(type), |
| shape_(shape), |
| offset_(offset) |
| { } |
| |
| ICUpdatedStub * |
| ICSetProp_Native::Compiler::getStub(ICStubSpace *space) |
| { |
| RootedTypeObject type(cx, obj_->getType(cx)); |
| RootedShape shape(cx, obj_->lastProperty()); |
| ICUpdatedStub *stub = ICSetProp_Native::New(space, getStubCode(), type, shape, offset_); |
| if (!stub || !stub->initUpdatingChain(cx, space)) |
| return NULL; |
| return stub; |
| } |
| |
| ICSetProp_NativeAdd::ICSetProp_NativeAdd(IonCode *stubCode, HandleTypeObject type, |
| size_t protoChainDepth, |
| HandleShape newShape, |
| uint32_t offset) |
| : ICUpdatedStub(SetProp_NativeAdd, stubCode), |
| type_(type), |
| newShape_(newShape), |
| offset_(offset) |
| { |
| JS_ASSERT(protoChainDepth <= MAX_PROTO_CHAIN_DEPTH); |
| extra_ = protoChainDepth; |
| } |
| |
| template <size_t ProtoChainDepth> |
| ICSetProp_NativeAddImpl<ProtoChainDepth>::ICSetProp_NativeAddImpl(IonCode *stubCode, |
| HandleTypeObject type, |
| const AutoShapeVector *shapes, |
| HandleShape newShape, |
| uint32_t offset) |
| : ICSetProp_NativeAdd(stubCode, type, ProtoChainDepth, newShape, offset) |
| { |
| JS_ASSERT(shapes->length() == NumShapes); |
| for (size_t i = 0; i < NumShapes; i++) |
| shapes_[i].init((*shapes)[i]); |
| } |
| |
| ICSetPropNativeAddCompiler::ICSetPropNativeAddCompiler(JSContext *cx, HandleObject obj, |
| HandleShape oldShape, |
| size_t protoChainDepth, |
| bool isFixedSlot, |
| uint32_t offset) |
| : ICStubCompiler(cx, ICStub::SetProp_NativeAdd), |
| obj_(cx, obj), |
| oldShape_(cx, oldShape), |
| protoChainDepth_(protoChainDepth), |
| isFixedSlot_(isFixedSlot), |
| offset_(offset) |
| { |
| JS_ASSERT(protoChainDepth_ <= ICSetProp_NativeAdd::MAX_PROTO_CHAIN_DEPTH); |
| } |
| |
| ICSetPropCallSetter::ICSetPropCallSetter(Kind kind, IonCode *stubCode, HandleShape shape, |
| HandleObject holder, HandleShape holderShape, |
| HandleFunction setter, uint32_t pcOffset) |
| : ICStub(kind, stubCode), |
| shape_(shape), |
| holder_(holder), |
| holderShape_(holderShape), |
| setter_(setter), |
| pcOffset_(pcOffset) |
| { |
| JS_ASSERT(kind == ICStub::SetProp_CallScripted || kind == ICStub::SetProp_CallNative); |
| } |
| |
| ICCall_Scripted::ICCall_Scripted(IonCode *stubCode, ICStub *firstMonitorStub, |
| HandleScript calleeScript, uint32_t pcOffset) |
| : ICMonitoredStub(ICStub::Call_Scripted, stubCode, firstMonitorStub), |
| calleeScript_(calleeScript), |
| pcOffset_(pcOffset) |
| { } |
| |
| ICCall_Native::ICCall_Native(IonCode *stubCode, ICStub *firstMonitorStub, HandleFunction callee, |
| uint32_t pcOffset) |
| : ICMonitoredStub(ICStub::Call_Native, stubCode, firstMonitorStub), |
| callee_(callee), |
| pcOffset_(pcOffset) |
| { } |
| |
| ICGetPropCallDOMProxyNativeStub::ICGetPropCallDOMProxyNativeStub(Kind kind, IonCode *stubCode, |
| ICStub *firstMonitorStub, |
| HandleShape shape, |
| BaseProxyHandler *proxyHandler, |
| HandleShape expandoShape, |
| HandleObject holder, |
| HandleShape holderShape, |
| HandleFunction getter, |
| uint32_t pcOffset) |
| : ICMonitoredStub(kind, stubCode, firstMonitorStub), |
| shape_(shape), |
| proxyHandler_(proxyHandler), |
| expandoShape_(expandoShape), |
| holder_(holder), |
| holderShape_(holderShape), |
| getter_(getter), |
| pcOffset_(pcOffset) |
| { } |
| |
| ICGetPropCallDOMProxyNativeCompiler::ICGetPropCallDOMProxyNativeCompiler(JSContext *cx, |
| ICStub::Kind kind, |
| ICStub *firstMonitorStub, |
| HandleObject obj, |
| HandleObject holder, |
| HandleFunction getter, |
| uint32_t pcOffset) |
| : ICStubCompiler(cx, kind), |
| firstMonitorStub_(firstMonitorStub), |
| obj_(cx, obj), |
| holder_(cx, holder), |
| getter_(cx, getter), |
| pcOffset_(pcOffset) |
| { |
| JS_ASSERT(kind == ICStub::GetProp_CallDOMProxyNative || |
| kind == ICStub::GetProp_CallDOMProxyWithGenerationNative); |
| JS_ASSERT(obj_->isProxy()); |
| JS_ASSERT(GetProxyHandler(obj_)->family() == GetDOMProxyHandlerFamily()); |
| } |
| |
| ICGetProp_DOMProxyShadowed::ICGetProp_DOMProxyShadowed(IonCode *stubCode, |
| ICStub *firstMonitorStub, |
| HandleShape shape, |
| BaseProxyHandler *proxyHandler, |
| HandlePropertyName name, |
| uint32_t pcOffset) |
| : ICMonitoredStub(ICStub::GetProp_DOMProxyShadowed, stubCode, firstMonitorStub), |
| shape_(shape), |
| proxyHandler_(proxyHandler), |
| name_(name), |
| pcOffset_(pcOffset) |
| { } |
| |
| } // namespace jit |
| } // namespace js |