diff options
Diffstat (limited to 'js/src/jit/SharedIC.cpp')
-rw-r--r-- | js/src/jit/SharedIC.cpp | 4306 |
1 files changed, 4306 insertions, 0 deletions
diff --git a/js/src/jit/SharedIC.cpp b/js/src/jit/SharedIC.cpp new file mode 100644 index 000000000..767cff661 --- /dev/null +++ b/js/src/jit/SharedIC.cpp @@ -0,0 +1,4306 @@ +/* -*- 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/SharedIC.h" + +#include "mozilla/Casting.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/SizePrintfMacros.h" +#include "mozilla/Sprintf.h" + +#include "jslibmath.h" +#include "jstypes.h" + +#include "gc/Policy.h" +#include "jit/BaselineCacheIR.h" +#include "jit/BaselineDebugModeOSR.h" +#include "jit/BaselineIC.h" +#include "jit/JitSpewer.h" +#include "jit/Linker.h" +#include "jit/SharedICHelpers.h" +#ifdef JS_ION_PERF +# include "jit/PerfSpewer.h" +#endif +#include "jit/VMFunctions.h" +#include "vm/Interpreter.h" + +#include "jit/MacroAssembler-inl.h" +#include "vm/Interpreter-inl.h" + +using mozilla::BitwiseCast; +using mozilla::DebugOnly; + +namespace js { +namespace jit { + +#ifdef JS_JITSPEW +void +FallbackICSpew(JSContext* cx, ICFallbackStub* stub, const char* fmt, ...) +{ + if (JitSpewEnabled(JitSpew_BaselineICFallback)) { + RootedScript script(cx, GetTopJitJSScript(cx)); + jsbytecode* pc = stub->icEntry()->pc(script); + + char fmtbuf[100]; + va_list args; + va_start(args, fmt); + (void) VsprintfLiteral(fmtbuf, fmt, args); + va_end(args); + + JitSpew(JitSpew_BaselineICFallback, + "Fallback hit for (%s:%" PRIuSIZE ") (pc=%" PRIuSIZE ",line=%d,uses=%d,stubs=%" PRIuSIZE "): %s", + script->filename(), + script->lineno(), + script->pcToOffset(pc), + PCToLineNumber(script, pc), + script->getWarmUpCount(), + stub->numOptimizedStubs(), + fmtbuf); + } +} + +void +TypeFallbackICSpew(JSContext* cx, ICTypeMonitor_Fallback* stub, const char* fmt, ...) +{ + if (JitSpewEnabled(JitSpew_BaselineICFallback)) { + RootedScript script(cx, GetTopJitJSScript(cx)); + jsbytecode* pc = stub->icEntry()->pc(script); + + char fmtbuf[100]; + va_list args; + va_start(args, fmt); + (void) VsprintfLiteral(fmtbuf, fmt, args); + va_end(args); + + JitSpew(JitSpew_BaselineICFallback, + "Type monitor fallback hit for (%s:%" PRIuSIZE ") (pc=%" PRIuSIZE ",line=%d,uses=%d,stubs=%d): %s", + script->filename(), + script->lineno(), + script->pcToOffset(pc), + PCToLineNumber(script, pc), + script->getWarmUpCount(), + (int) stub->numOptimizedMonitorStubs(), + fmtbuf); + } +} +#endif // JS_JITSPEW + +ICFallbackStub* +ICEntry::fallbackStub() const +{ + return firstStub()->getChainFallback(); +} + +void +IonICEntry::trace(JSTracer* trc) +{ + TraceManuallyBarrieredEdge(trc, &script_, "IonICEntry::script_"); + traceEntry(trc); +} + +void +BaselineICEntry::trace(JSTracer* trc) +{ + traceEntry(trc); +} + +void +ICEntry::traceEntry(JSTracer* trc) +{ + if (!hasStub()) + return; + for (ICStub* stub = firstStub(); stub; stub = stub->next()) + stub->trace(trc); +} + +ICStubConstIterator& +ICStubConstIterator::operator++() +{ + MOZ_ASSERT(currentStub_ != nullptr); + currentStub_ = currentStub_->next(); + return *this; +} + + +ICStubIterator::ICStubIterator(ICFallbackStub* fallbackStub, bool end) + : icEntry_(fallbackStub->icEntry()), + fallbackStub_(fallbackStub), + previousStub_(nullptr), + currentStub_(end ? fallbackStub : icEntry_->firstStub()), + unlinked_(false) +{ } + +ICStubIterator& +ICStubIterator::operator++() +{ + MOZ_ASSERT(currentStub_->next() != nullptr); + if (!unlinked_) + previousStub_ = currentStub_; + currentStub_ = currentStub_->next(); + unlinked_ = false; + return *this; +} + +void +ICStubIterator::unlink(JSContext* cx) +{ + MOZ_ASSERT(currentStub_->next() != nullptr); + MOZ_ASSERT(currentStub_ != fallbackStub_); + MOZ_ASSERT(!unlinked_); + + fallbackStub_->unlinkStub(cx->zone(), previousStub_, currentStub_); + + // Mark the current iterator position as unlinked, so operator++ works properly. + unlinked_ = true; +} + + +void +ICStub::markCode(JSTracer* trc, const char* name) +{ + JitCode* stubJitCode = jitCode(); + TraceManuallyBarrieredEdge(trc, &stubJitCode, name); +} + +void +ICStub::updateCode(JitCode* code) +{ + // Write barrier on the old code. + JitCode::writeBarrierPre(jitCode()); + stubCode_ = code->raw(); +} + +/* static */ void +ICStub::trace(JSTracer* trc) +{ + markCode(trc, "shared-stub-jitcode"); + + // 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++) { + MOZ_ASSERT_IF(iter->next() == nullptr, *iter == lastMonStub); + iter->trace(trc); + } + } + + if (isUpdated()) { + for (ICStubConstIterator iter(toUpdatedStub()->firstUpdateStub()); !iter.atEnd(); iter++) { + MOZ_ASSERT_IF(iter->next() == nullptr, iter->isTypeUpdate_Fallback()); + iter->trace(trc); + } + } + + switch (kind()) { + case ICStub::Call_Scripted: { + ICCall_Scripted* callStub = toCall_Scripted(); + TraceEdge(trc, &callStub->callee(), "baseline-callscripted-callee"); + TraceNullableEdge(trc, &callStub->templateObject(), "baseline-callscripted-template"); + break; + } + case ICStub::Call_Native: { + ICCall_Native* callStub = toCall_Native(); + TraceEdge(trc, &callStub->callee(), "baseline-callnative-callee"); + TraceNullableEdge(trc, &callStub->templateObject(), "baseline-callnative-template"); + break; + } + case ICStub::Call_ClassHook: { + ICCall_ClassHook* callStub = toCall_ClassHook(); + TraceNullableEdge(trc, &callStub->templateObject(), "baseline-callclasshook-template"); + break; + } + case ICStub::Call_StringSplit: { + ICCall_StringSplit* callStub = toCall_StringSplit(); + TraceEdge(trc, &callStub->templateObject(), "baseline-callstringsplit-template"); + TraceEdge(trc, &callStub->expectedSep(), "baseline-callstringsplit-sep"); + TraceEdge(trc, &callStub->expectedStr(), "baseline-callstringsplit-str"); + break; + } + case ICStub::GetElem_NativeSlotName: + case ICStub::GetElem_NativeSlotSymbol: + case ICStub::GetElem_UnboxedPropertyName: { + ICGetElemNativeStub* getElemStub = static_cast<ICGetElemNativeStub*>(this); + getElemStub->receiverGuard().trace(trc); + if (getElemStub->isSymbol()) { + ICGetElem_NativeSlot<JS::Symbol*>* typedGetElemStub = toGetElem_NativeSlotSymbol(); + TraceEdge(trc, &typedGetElemStub->key(), "baseline-getelem-native-key"); + } else { + ICGetElemNativeSlotStub<PropertyName*>* typedGetElemStub = + reinterpret_cast<ICGetElemNativeSlotStub<PropertyName*>*>(this); + TraceEdge(trc, &typedGetElemStub->key(), "baseline-getelem-native-key"); + } + break; + } + case ICStub::GetElem_NativePrototypeSlotName: + case ICStub::GetElem_NativePrototypeSlotSymbol: { + ICGetElemNativeStub* getElemStub = static_cast<ICGetElemNativeStub*>(this); + getElemStub->receiverGuard().trace(trc); + if (getElemStub->isSymbol()) { + ICGetElem_NativePrototypeSlot<JS::Symbol*>* typedGetElemStub + = toGetElem_NativePrototypeSlotSymbol(); + TraceEdge(trc, &typedGetElemStub->key(), "baseline-getelem-nativeproto-key"); + TraceEdge(trc, &typedGetElemStub->holder(), "baseline-getelem-nativeproto-holder"); + TraceEdge(trc, &typedGetElemStub->holderShape(), "baseline-getelem-nativeproto-holdershape"); + } else { + ICGetElem_NativePrototypeSlot<PropertyName*>* typedGetElemStub + = toGetElem_NativePrototypeSlotName(); + TraceEdge(trc, &typedGetElemStub->key(), "baseline-getelem-nativeproto-key"); + TraceEdge(trc, &typedGetElemStub->holder(), "baseline-getelem-nativeproto-holder"); + TraceEdge(trc, &typedGetElemStub->holderShape(), "baseline-getelem-nativeproto-holdershape"); + } + break; + } + case ICStub::GetElem_NativePrototypeCallNativeName: + case ICStub::GetElem_NativePrototypeCallNativeSymbol: + case ICStub::GetElem_NativePrototypeCallScriptedName: + case ICStub::GetElem_NativePrototypeCallScriptedSymbol: { + ICGetElemNativeStub* getElemStub = static_cast<ICGetElemNativeStub*>(this); + getElemStub->receiverGuard().trace(trc); + if (getElemStub->isSymbol()) { + ICGetElemNativePrototypeCallStub<JS::Symbol*>* callStub = + reinterpret_cast<ICGetElemNativePrototypeCallStub<JS::Symbol*>*>(this); + TraceEdge(trc, &callStub->key(), "baseline-getelem-nativeprotocall-key"); + TraceEdge(trc, &callStub->getter(), "baseline-getelem-nativeprotocall-getter"); + TraceEdge(trc, &callStub->holder(), "baseline-getelem-nativeprotocall-holder"); + TraceEdge(trc, &callStub->holderShape(), "baseline-getelem-nativeprotocall-holdershape"); + } else { + ICGetElemNativePrototypeCallStub<PropertyName*>* callStub = + reinterpret_cast<ICGetElemNativePrototypeCallStub<PropertyName*>*>(this); + TraceEdge(trc, &callStub->key(), "baseline-getelem-nativeprotocall-key"); + TraceEdge(trc, &callStub->getter(), "baseline-getelem-nativeprotocall-getter"); + TraceEdge(trc, &callStub->holder(), "baseline-getelem-nativeprotocall-holder"); + TraceEdge(trc, &callStub->holderShape(), "baseline-getelem-nativeprotocall-holdershape"); + } + break; + } + case ICStub::GetElem_Dense: { + ICGetElem_Dense* getElemStub = toGetElem_Dense(); + TraceEdge(trc, &getElemStub->shape(), "baseline-getelem-dense-shape"); + break; + } + case ICStub::GetElem_UnboxedArray: { + ICGetElem_UnboxedArray* getElemStub = toGetElem_UnboxedArray(); + TraceEdge(trc, &getElemStub->group(), "baseline-getelem-unboxed-array-group"); + break; + } + case ICStub::GetElem_TypedArray: { + ICGetElem_TypedArray* getElemStub = toGetElem_TypedArray(); + TraceEdge(trc, &getElemStub->shape(), "baseline-getelem-typedarray-shape"); + break; + } + case ICStub::SetElem_DenseOrUnboxedArray: { + ICSetElem_DenseOrUnboxedArray* setElemStub = toSetElem_DenseOrUnboxedArray(); + TraceNullableEdge(trc, &setElemStub->shape(), "baseline-getelem-dense-shape"); + TraceEdge(trc, &setElemStub->group(), "baseline-setelem-dense-group"); + break; + } + case ICStub::SetElem_DenseOrUnboxedArrayAdd: { + ICSetElem_DenseOrUnboxedArrayAdd* setElemStub = toSetElem_DenseOrUnboxedArrayAdd(); + TraceEdge(trc, &setElemStub->group(), "baseline-setelem-denseadd-group"); + + JS_STATIC_ASSERT(ICSetElem_DenseOrUnboxedArrayAdd::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: MOZ_CRASH("Invalid proto stub."); + } + break; + } + case ICStub::SetElem_TypedArray: { + ICSetElem_TypedArray* setElemStub = toSetElem_TypedArray(); + TraceEdge(trc, &setElemStub->shape(), "baseline-setelem-typedarray-shape"); + break; + } + case ICStub::TypeMonitor_SingleObject: { + ICTypeMonitor_SingleObject* monitorStub = toTypeMonitor_SingleObject(); + TraceEdge(trc, &monitorStub->object(), "baseline-monitor-singleton"); + break; + } + case ICStub::TypeMonitor_ObjectGroup: { + ICTypeMonitor_ObjectGroup* monitorStub = toTypeMonitor_ObjectGroup(); + TraceEdge(trc, &monitorStub->group(), "baseline-monitor-group"); + break; + } + case ICStub::TypeUpdate_SingleObject: { + ICTypeUpdate_SingleObject* updateStub = toTypeUpdate_SingleObject(); + TraceEdge(trc, &updateStub->object(), "baseline-update-singleton"); + break; + } + case ICStub::TypeUpdate_ObjectGroup: { + ICTypeUpdate_ObjectGroup* updateStub = toTypeUpdate_ObjectGroup(); + TraceEdge(trc, &updateStub->group(), "baseline-update-group"); + break; + } + case ICStub::In_Native: { + ICIn_Native* inStub = toIn_Native(); + TraceEdge(trc, &inStub->shape(), "baseline-innative-stub-shape"); + TraceEdge(trc, &inStub->name(), "baseline-innative-stub-name"); + break; + } + case ICStub::In_NativePrototype: { + ICIn_NativePrototype* inStub = toIn_NativePrototype(); + TraceEdge(trc, &inStub->shape(), "baseline-innativeproto-stub-shape"); + TraceEdge(trc, &inStub->name(), "baseline-innativeproto-stub-name"); + TraceEdge(trc, &inStub->holder(), "baseline-innativeproto-stub-holder"); + TraceEdge(trc, &inStub->holderShape(), "baseline-innativeproto-stub-holdershape"); + break; + } + case ICStub::In_NativeDoesNotExist: { + ICIn_NativeDoesNotExist* inStub = toIn_NativeDoesNotExist(); + TraceEdge(trc, &inStub->name(), "baseline-innativedoesnotexist-stub-name"); + JS_STATIC_ASSERT(ICIn_NativeDoesNotExist::MAX_PROTO_CHAIN_DEPTH == 8); + switch (inStub->protoChainDepth()) { + case 0: inStub->toImpl<0>()->traceShapes(trc); break; + case 1: inStub->toImpl<1>()->traceShapes(trc); break; + case 2: inStub->toImpl<2>()->traceShapes(trc); break; + case 3: inStub->toImpl<3>()->traceShapes(trc); break; + case 4: inStub->toImpl<4>()->traceShapes(trc); break; + case 5: inStub->toImpl<5>()->traceShapes(trc); break; + case 6: inStub->toImpl<6>()->traceShapes(trc); break; + case 7: inStub->toImpl<7>()->traceShapes(trc); break; + case 8: inStub->toImpl<8>()->traceShapes(trc); break; + default: MOZ_CRASH("Invalid proto stub."); + } + break; + } + case ICStub::In_Dense: { + ICIn_Dense* inStub = toIn_Dense(); + TraceEdge(trc, &inStub->shape(), "baseline-in-dense-shape"); + break; + } + case ICStub::GetName_Global: { + ICGetName_Global* globalStub = toGetName_Global(); + globalStub->receiverGuard().trace(trc); + TraceEdge(trc, &globalStub->holder(), "baseline-global-stub-holder"); + TraceEdge(trc, &globalStub->holderShape(), "baseline-global-stub-holdershape"); + TraceEdge(trc, &globalStub->globalShape(), "baseline-global-stub-globalshape"); + break; + } + case ICStub::GetName_Env0: + static_cast<ICGetName_Env<0>*>(this)->traceEnvironments(trc); + break; + case ICStub::GetName_Env1: + static_cast<ICGetName_Env<1>*>(this)->traceEnvironments(trc); + break; + case ICStub::GetName_Env2: + static_cast<ICGetName_Env<2>*>(this)->traceEnvironments(trc); + break; + case ICStub::GetName_Env3: + static_cast<ICGetName_Env<3>*>(this)->traceEnvironments(trc); + break; + case ICStub::GetName_Env4: + static_cast<ICGetName_Env<4>*>(this)->traceEnvironments(trc); + break; + case ICStub::GetName_Env5: + static_cast<ICGetName_Env<5>*>(this)->traceEnvironments(trc); + break; + case ICStub::GetName_Env6: + static_cast<ICGetName_Env<6>*>(this)->traceEnvironments(trc); + break; + case ICStub::GetIntrinsic_Constant: { + ICGetIntrinsic_Constant* constantStub = toGetIntrinsic_Constant(); + TraceEdge(trc, &constantStub->value(), "baseline-getintrinsic-constant-value"); + break; + } + case ICStub::GetProp_CallDOMProxyNative: + case ICStub::GetProp_CallDOMProxyWithGenerationNative: { + ICGetPropCallDOMProxyNativeStub* propStub; + if (kind() == ICStub::GetProp_CallDOMProxyNative) + propStub = toGetProp_CallDOMProxyNative(); + else + propStub = toGetProp_CallDOMProxyWithGenerationNative(); + propStub->receiverGuard().trace(trc); + TraceNullableEdge(trc, &propStub->expandoShape(), + "baseline-getproplistbasenative-stub-expandoshape"); + TraceEdge(trc, &propStub->holder(), "baseline-getproplistbasenative-stub-holder"); + TraceEdge(trc, &propStub->holderShape(), "baseline-getproplistbasenative-stub-holdershape"); + TraceEdge(trc, &propStub->getter(), "baseline-getproplistbasenative-stub-getter"); + break; + } + case ICStub::GetProp_DOMProxyShadowed: { + ICGetProp_DOMProxyShadowed* propStub = toGetProp_DOMProxyShadowed(); + TraceEdge(trc, &propStub->shape(), "baseline-getproplistbaseshadowed-stub-shape"); + TraceEdge(trc, &propStub->name(), "baseline-getproplistbaseshadowed-stub-name"); + break; + } + case ICStub::GetProp_CallScripted: { + ICGetProp_CallScripted* callStub = toGetProp_CallScripted(); + callStub->receiverGuard().trace(trc); + TraceEdge(trc, &callStub->holder(), "baseline-getpropcallscripted-stub-holder"); + TraceEdge(trc, &callStub->holderShape(), "baseline-getpropcallscripted-stub-holdershape"); + TraceEdge(trc, &callStub->getter(), "baseline-getpropcallscripted-stub-getter"); + break; + } + case ICStub::GetProp_CallNative: { + ICGetProp_CallNative* callStub = toGetProp_CallNative(); + callStub->receiverGuard().trace(trc); + TraceEdge(trc, &callStub->holder(), "baseline-getpropcallnative-stub-holder"); + TraceEdge(trc, &callStub->holderShape(), "baseline-getpropcallnative-stub-holdershape"); + TraceEdge(trc, &callStub->getter(), "baseline-getpropcallnative-stub-getter"); + break; + } + case ICStub::GetProp_CallNativeGlobal: { + ICGetProp_CallNativeGlobal* callStub = toGetProp_CallNativeGlobal(); + callStub->receiverGuard().trace(trc); + TraceEdge(trc, &callStub->holder(), "baseline-getpropcallnativeglobal-stub-holder"); + TraceEdge(trc, &callStub->holderShape(), "baseline-getpropcallnativeglobal-stub-holdershape"); + TraceEdge(trc, &callStub->globalShape(), "baseline-getpropcallnativeglobal-stub-globalshape"); + TraceEdge(trc, &callStub->getter(), "baseline-getpropcallnativeglobal-stub-getter"); + break; + } + case ICStub::SetProp_Native: { + ICSetProp_Native* propStub = toSetProp_Native(); + TraceEdge(trc, &propStub->shape(), "baseline-setpropnative-stub-shape"); + TraceEdge(trc, &propStub->group(), "baseline-setpropnative-stub-group"); + break; + } + case ICStub::SetProp_NativeAdd: { + ICSetProp_NativeAdd* propStub = toSetProp_NativeAdd(); + TraceEdge(trc, &propStub->group(), "baseline-setpropnativeadd-stub-group"); + TraceEdge(trc, &propStub->newShape(), "baseline-setpropnativeadd-stub-newshape"); + TraceNullableEdge(trc, &propStub->newGroup(), "baseline-setpropnativeadd-stub-new-group"); + 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: MOZ_CRASH("Invalid proto stub."); + } + break; + } + case ICStub::SetProp_Unboxed: { + ICSetProp_Unboxed* propStub = toSetProp_Unboxed(); + TraceEdge(trc, &propStub->group(), "baseline-setprop-unboxed-stub-group"); + break; + } + case ICStub::SetProp_TypedObject: { + ICSetProp_TypedObject* propStub = toSetProp_TypedObject(); + TraceEdge(trc, &propStub->shape(), "baseline-setprop-typedobject-stub-shape"); + TraceEdge(trc, &propStub->group(), "baseline-setprop-typedobject-stub-group"); + break; + } + case ICStub::SetProp_CallScripted: { + ICSetProp_CallScripted* callStub = toSetProp_CallScripted(); + callStub->receiverGuard().trace(trc); + TraceEdge(trc, &callStub->holder(), "baseline-setpropcallscripted-stub-holder"); + TraceEdge(trc, &callStub->holderShape(), "baseline-setpropcallscripted-stub-holdershape"); + TraceEdge(trc, &callStub->setter(), "baseline-setpropcallscripted-stub-setter"); + break; + } + case ICStub::SetProp_CallNative: { + ICSetProp_CallNative* callStub = toSetProp_CallNative(); + callStub->receiverGuard().trace(trc); + TraceEdge(trc, &callStub->holder(), "baseline-setpropcallnative-stub-holder"); + TraceEdge(trc, &callStub->holderShape(), "baseline-setpropcallnative-stub-holdershape"); + TraceEdge(trc, &callStub->setter(), "baseline-setpropcallnative-stub-setter"); + break; + } + case ICStub::InstanceOf_Function: { + ICInstanceOf_Function* instanceofStub = toInstanceOf_Function(); + TraceEdge(trc, &instanceofStub->shape(), "baseline-instanceof-fun-shape"); + TraceEdge(trc, &instanceofStub->prototypeObject(), "baseline-instanceof-fun-prototype"); + break; + } + case ICStub::NewArray_Fallback: { + ICNewArray_Fallback* stub = toNewArray_Fallback(); + TraceNullableEdge(trc, &stub->templateObject(), "baseline-newarray-template"); + TraceEdge(trc, &stub->templateGroup(), "baseline-newarray-template-group"); + break; + } + case ICStub::NewObject_Fallback: { + ICNewObject_Fallback* stub = toNewObject_Fallback(); + TraceNullableEdge(trc, &stub->templateObject(), "baseline-newobject-template"); + break; + } + case ICStub::Rest_Fallback: { + ICRest_Fallback* stub = toRest_Fallback(); + TraceEdge(trc, &stub->templateObject(), "baseline-rest-template"); + break; + } + case ICStub::CacheIR_Monitored: + TraceBaselineCacheIRStub(trc, this, toCacheIR_Monitored()->stubInfo()); + break; + default: + break; + } +} + +void +ICFallbackStub::unlinkStub(Zone* zone, ICStub* prev, ICStub* stub) +{ + MOZ_ASSERT(stub->next()); + + // If stub is the last optimized stub, update lastStubPtrAddr. + if (stub->next() == this) { + MOZ_ASSERT(lastStubPtrAddr_ == stub->addressOfNext()); + if (prev) + lastStubPtrAddr_ = prev->addressOfNext(); + else + lastStubPtrAddr_ = icEntry()->addressOfFirstStub(); + *lastStubPtrAddr_ = this; + } else { + if (prev) { + MOZ_ASSERT(prev->next() == stub); + prev->setNext(stub->next()); + } else { + MOZ_ASSERT(icEntry()->firstStub() == stub); + icEntry()->setFirstStub(stub->next()); + } + } + + MOZ_ASSERT(numOptimizedStubs_ > 0); + numOptimizedStubs_--; + + if (zone->needsIncrementalBarrier()) { + // 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); + } +} + +void +ICTypeMonitor_Fallback::resetMonitorStubChain(Zone* zone) +{ + if (zone->needsIncrementalBarrier()) { + // We are removing edges from monitored stubs to gcthings (JitCode). + // Perform one final trace of all monitor stubs for incremental GC, + // as it must know about those edges. + for (ICStub* s = firstMonitorStub_; !s->isTypeMonitor_Fallback(); s = s->next()) + s->trace(zone->barrierTracer()); + } + + firstMonitorStub_ = this; + numOptimizedMonitorStubs_ = 0; + + if (hasFallbackStub_) { + lastMonitorStubPtrAddr_ = nullptr; + + // 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, JitCode* stubCode, ICStub* firstMonitorStub) + : ICStub(kind, ICStub::Monitored, stubCode), + firstMonitorStub_(firstMonitorStub) +{ + // In order to silence Coverity - null pointer dereference checker + MOZ_ASSERT(firstMonitorStub_); + // If the first monitored stub is a ICTypeMonitor_Fallback stub, then + // double check that _its_ firstMonitorStub is the same as this one. + MOZ_ASSERT_IF(firstMonitorStub_->isTypeMonitor_Fallback(), + firstMonitorStub_->toTypeMonitor_Fallback()->firstMonitorStub() == + firstMonitorStub_); +} + +bool +ICMonitoredFallbackStub::initMonitoringChain(JSContext* cx, ICStubSpace* space, + ICStubCompiler::Engine engine) +{ + MOZ_ASSERT(fallbackMonitorStub_ == nullptr); + + ICTypeMonitor_Fallback::Compiler compiler(cx, engine, this); + ICTypeMonitor_Fallback* stub = compiler.getStub(space); + if (!stub) + return false; + fallbackMonitorStub_ = stub; + return true; +} + +bool +ICMonitoredFallbackStub::addMonitorStubForValue(JSContext* cx, SharedStubInfo* stub, + HandleValue val) +{ + return fallbackMonitorStub_->addMonitorStubForValue(cx, stub, val); +} + +bool +ICUpdatedStub::initUpdatingChain(JSContext* cx, ICStubSpace* space) +{ + MOZ_ASSERT(firstUpdateStub_ == nullptr); + + ICTypeUpdate_Fallback::Compiler compiler(cx); + ICTypeUpdate_Fallback* stub = compiler.getStub(space); + if (!stub) + return false; + + firstUpdateStub_ = stub; + return true; +} + +JitCode* +ICStubCompiler::getStubCode() +{ + JitCompartment* comp = cx->compartment()->jitCompartment(); + + // Check for existing cached stubcode. + uint32_t stubKey = getKey(); + JitCode* stubCode = comp->getStubCode(stubKey); + if (stubCode) + return stubCode; + + // Compile new stubcode. + JitContext jctx(cx, nullptr); + MacroAssembler masm; +#ifndef JS_USE_LINK_REGISTER + // The first value contains the return addres, + // which we pull into ICTailCallReg for tail calls. + masm.adjustFrame(sizeof(intptr_t)); +#endif +#ifdef JS_CODEGEN_ARM + masm.setSecondScratchReg(BaselineSecondScratchReg); +#endif + + if (!generateStubCode(masm)) + return nullptr; + Linker linker(masm); + AutoFlushICache afc("getStubCode"); + Rooted<JitCode*> newStubCode(cx, linker.newCode<CanGC>(cx, BASELINE_CODE)); + if (!newStubCode) + return nullptr; + + // All barriers are emitted off-by-default, enable them if needed. + if (cx->zone()->needsIncrementalBarrier()) + newStubCode->togglePreBarriers(true, DontReprotect); + + // Cache newly compiled stubcode. + if (!comp->putStubCode(cx, stubKey, newStubCode)) + return nullptr; + + // After generating code, run postGenerateStubCode(). We must not fail + // after this point. + postGenerateStubCode(masm, newStubCode); + + MOZ_ASSERT(entersStubFrame_ == ICStub::CanMakeCalls(kind)); + MOZ_ASSERT(!inStubFrame_); + +#ifdef JS_ION_PERF + writePerfSpewerJitCodeProfile(newStubCode, "BaselineIC"); +#endif + + return newStubCode; +} + +bool +ICStubCompiler::tailCallVM(const VMFunction& fun, MacroAssembler& masm) +{ + JitCode* code = cx->runtime()->jitRuntime()->getVMWrapper(fun); + if (!code) + return false; + + MOZ_ASSERT(fun.expectTailCall == TailCall); + uint32_t argSize = fun.explicitStackSlots() * sizeof(void*); + if (engine_ == Engine::Baseline) { + EmitBaselineTailCallVM(code, masm, argSize); + } else { + uint32_t stackSize = argSize + fun.extraValuesToPop * sizeof(Value); + EmitIonTailCallVM(code, masm, stackSize); + } + return true; +} + +bool +ICStubCompiler::callVM(const VMFunction& fun, MacroAssembler& masm) +{ + MOZ_ASSERT(inStubFrame_); + + JitCode* code = cx->runtime()->jitRuntime()->getVMWrapper(fun); + if (!code) + return false; + + MOZ_ASSERT(fun.expectTailCall == NonTailCall); + if (engine_ == Engine::Baseline) + EmitBaselineCallVM(code, masm); + else + EmitIonCallVM(code, fun.explicitStackSlots(), masm); + return true; +} + +bool +ICStubCompiler::callTypeUpdateIC(MacroAssembler& masm, uint32_t objectOffset) +{ + JitCode* code = cx->runtime()->jitRuntime()->getVMWrapper(DoTypeUpdateFallbackInfo); + if (!code) + return false; + + EmitCallTypeUpdateIC(masm, code, objectOffset); + return true; +} + +void +ICStubCompiler::enterStubFrame(MacroAssembler& masm, Register scratch) +{ + if (engine_ == Engine::Baseline) { + EmitBaselineEnterStubFrame(masm, scratch); +#ifdef DEBUG + framePushedAtEnterStubFrame_ = masm.framePushed(); +#endif + } else { + EmitIonEnterStubFrame(masm, scratch); + } + + MOZ_ASSERT(!inStubFrame_); + inStubFrame_ = true; + +#ifdef DEBUG + entersStubFrame_ = true; +#endif +} + +void +ICStubCompiler::leaveStubFrame(MacroAssembler& masm, bool calledIntoIon) +{ + MOZ_ASSERT(entersStubFrame_ && inStubFrame_); + inStubFrame_ = false; + + if (engine_ == Engine::Baseline) { +#ifdef DEBUG + masm.setFramePushed(framePushedAtEnterStubFrame_); + if (calledIntoIon) + masm.adjustFrame(sizeof(intptr_t)); // Calls into ion have this extra. +#endif + + EmitBaselineLeaveStubFrame(masm, calledIntoIon); + } else { + EmitIonLeaveStubFrame(masm); + } +} + +void +ICStubCompiler::pushStubPayload(MacroAssembler& masm, Register scratch) +{ + if (engine_ == Engine::IonMonkey) { + masm.push(Imm32(0)); + return; + } + + if (inStubFrame_) { + masm.loadPtr(Address(BaselineFrameReg, 0), scratch); + masm.pushBaselineFramePtr(scratch, scratch); + } else { + masm.pushBaselineFramePtr(BaselineFrameReg, scratch); + } +} + +void +ICStubCompiler::PushStubPayload(MacroAssembler& masm, Register scratch) +{ + pushStubPayload(masm, scratch); + masm.adjustFrame(sizeof(intptr_t)); +} + +void +ICStubCompiler::emitPostWriteBarrierSlot(MacroAssembler& masm, Register obj, ValueOperand val, + Register scratch, LiveGeneralRegisterSet saveRegs) +{ + Label skipBarrier; + masm.branchPtrInNurseryChunk(Assembler::Equal, obj, scratch, &skipBarrier); + masm.branchValueIsNurseryObject(Assembler::NotEqual, val, scratch, &skipBarrier); + + // void PostWriteBarrier(JSRuntime* rt, JSObject* obj); +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) + saveRegs.add(ICTailCallReg); +#endif + saveRegs.set() = GeneralRegisterSet::Intersect(saveRegs.set(), GeneralRegisterSet::Volatile()); + masm.PushRegsInMask(saveRegs); + masm.setupUnalignedABICall(scratch); + masm.movePtr(ImmPtr(cx->runtime()), scratch); + masm.passABIArg(scratch); + masm.passABIArg(obj); + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, PostWriteBarrier)); + masm.PopRegsInMask(saveRegs); + + masm.bind(&skipBarrier); +} + +SharedStubInfo::SharedStubInfo(JSContext* cx, void* payload, ICEntry* icEntry) + : maybeFrame_(nullptr), + outerScript_(cx), + innerScript_(cx), + icEntry_(icEntry) +{ + if (payload) { + maybeFrame_ = (BaselineFrame*) payload; + outerScript_ = maybeFrame_->script(); + innerScript_ = maybeFrame_->script(); + } else { + IonICEntry* entry = (IonICEntry*) icEntry; + innerScript_ = entry->script(); + // outerScript_ is initialized lazily. + } +} + +HandleScript +SharedStubInfo::outerScript(JSContext* cx) +{ + if (!outerScript_) { + js::jit::JitActivationIterator iter(cx->runtime()); + JitFrameIterator it(iter); + MOZ_ASSERT(it.isExitFrame()); + ++it; + MOZ_ASSERT(it.isIonJS()); + outerScript_ = it.script(); + MOZ_ASSERT(!it.ionScript()->invalidated()); + } + return outerScript_; +} + +// +// BinaryArith_Fallback +// + +static bool +DoBinaryArithFallback(JSContext* cx, void* payload, ICBinaryArith_Fallback* stub_, + HandleValue lhs, HandleValue rhs, MutableHandleValue ret) +{ + SharedStubInfo info(cx, payload, stub_->icEntry()); + ICStubCompiler::Engine engine = info.engine(); + + // This fallback stub may trigger debug mode toggling. + DebugModeOSRVolatileStub<ICBinaryArith_Fallback*> stub(engine, info.maybeFrame(), stub_); + + jsbytecode* pc = info.pc(); + JSOp op = JSOp(*pc); + FallbackICSpew(cx, stub, "BinaryArith(%s,%d,%d)", 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, &lhsCopy, &rhsCopy, ret)) + return false; + break; + case JSOP_SUB: + if (!SubValues(cx, &lhsCopy, &rhsCopy, ret)) + return false; + break; + case JSOP_MUL: + if (!MulValues(cx, &lhsCopy, &rhsCopy, ret)) + return false; + break; + case JSOP_DIV: + if (!DivValues(cx, &lhsCopy, &rhsCopy, ret)) + return false; + break; + case JSOP_MOD: + if (!ModValues(cx, &lhsCopy, &rhsCopy, ret)) + return false; + break; + case JSOP_POW: + if (!math_pow_handle(cx, lhsCopy, rhsCopy, ret)) + 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, lhs, rhs, ret)) + return false; + break; + } + default: + MOZ_CRASH("Unhandled baseline arith op"); + } + + // Check if debug mode toggling made the stub invalid. + if (stub.invalid()) + return true; + + if (ret.isDouble()) + stub->setSawDoubleResult(); + + // Check to see if a new stub should be generated. + if (stub->numOptimizedStubs() >= ICBinaryArith_Fallback::MAX_OPTIMIZED_STUBS) { + stub->noteUnoptimizableOperands(); + return true; + } + + // Handle string concat. + if (op == JSOP_ADD) { + if (lhs.isString() && rhs.isString()) { + JitSpew(JitSpew_BaselineIC, " Generating %s(String, String) stub", CodeName[op]); + MOZ_ASSERT(ret.isString()); + ICBinaryArith_StringConcat::Compiler compiler(cx, engine); + ICStub* strcatStub = compiler.getStub(compiler.getStubSpace(info.outerScript(cx))); + if (!strcatStub) + return false; + stub->addNewStub(strcatStub); + return true; + } + + if ((lhs.isString() && rhs.isObject()) || (lhs.isObject() && rhs.isString())) { + JitSpew(JitSpew_BaselineIC, " Generating %s(%s, %s) stub", CodeName[op], + lhs.isString() ? "String" : "Object", + lhs.isString() ? "Object" : "String"); + MOZ_ASSERT(ret.isString()); + ICBinaryArith_StringObjectConcat::Compiler compiler(cx, engine, lhs.isString()); + ICStub* strcatStub = compiler.getStub(compiler.getStubSpace(info.outerScript(cx))); + 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)) + { + JitSpew(JitSpew_BaselineIC, " Generating %s(%s, %s) stub", CodeName[op], + lhs.isBoolean() ? "Boolean" : "Int32", rhs.isBoolean() ? "Boolean" : "Int32"); + ICBinaryArith_BooleanWithInt32::Compiler compiler(cx, op, engine, + lhs.isBoolean(), rhs.isBoolean()); + ICStub* arithStub = compiler.getStub(compiler.getStubSpace(info.outerScript(cx))); + if (!arithStub) + return false; + stub->addNewStub(arithStub); + return true; + } + + // Handle only int32 or double. + if (!lhs.isNumber() || !rhs.isNumber()) { + stub->noteUnoptimizableOperands(); + return true; + } + + MOZ_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); + JitSpew(JitSpew_BaselineIC, " Generating %s(Double, Double) stub", CodeName[op]); + + ICBinaryArith_Double::Compiler compiler(cx, op, engine); + ICStub* doubleStub = compiler.getStub(compiler.getStubSpace(info.outerScript(cx))); + if (!doubleStub) + return false; + stub->addNewStub(doubleStub); + return true; + } + default: + break; + } + } + + if (lhs.isInt32() && rhs.isInt32() && op != JSOP_POW) { + bool allowDouble = ret.isDouble(); + if (allowDouble) + stub->unlinkStubsWithKind(cx, ICStub::BinaryArith_Int32); + JitSpew(JitSpew_BaselineIC, " Generating %s(Int32, Int32%s) stub", CodeName[op], + allowDouble ? " => Double" : ""); + ICBinaryArith_Int32::Compiler compilerInt32(cx, op, engine, allowDouble); + ICStub* int32Stub = compilerInt32.getStub(compilerInt32.getStubSpace(info.outerScript(cx))); + 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: { + JitSpew(JitSpew_BaselineIC, " Generating %s(%s, %s) stub", CodeName[op], + lhs.isDouble() ? "Double" : "Int32", + lhs.isDouble() ? "Int32" : "Double"); + ICBinaryArith_DoubleWithInt32::Compiler compiler(cx, op, engine, lhs.isDouble()); + ICStub* optStub = compiler.getStub(compiler.getStubSpace(info.outerScript(cx))); + if (!optStub) + return false; + stub->addNewStub(optStub); + return true; + } + default: + break; + } + } + + stub->noteUnoptimizableOperands(); + return true; +} + +typedef bool (*DoBinaryArithFallbackFn)(JSContext*, void*, ICBinaryArith_Fallback*, + HandleValue, HandleValue, MutableHandleValue); +static const VMFunction DoBinaryArithFallbackInfo = + FunctionInfo<DoBinaryArithFallbackFn>(DoBinaryArithFallback, "DoBinaryArithFallback", + TailCall, PopValues(2)); + +bool +ICBinaryArith_Fallback::Compiler::generateStubCode(MacroAssembler& masm) +{ + MOZ_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(ICStubReg); + pushStubPayload(masm, R0.scratchReg()); + + return tailCallVM(DoBinaryArithFallbackInfo, masm); +} + +static bool +DoConcatStrings(JSContext* cx, HandleString lhs, HandleString rhs, MutableHandleValue res) +{ + JSString* result = ConcatStrings<CanGC>(cx, lhs, rhs); + if (!result) + return false; + + res.setString(result); + return true; +} + +typedef bool (*DoConcatStringsFn)(JSContext*, HandleString, HandleString, MutableHandleValue); +static const VMFunction DoConcatStringsInfo = + FunctionInfo<DoConcatStringsFn>(DoConcatStrings, "DoConcatStrings", TailCall); + +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.unboxString(R0, R0.scratchReg()); + masm.unboxString(R1, R1.scratchReg()); + + masm.push(R1.scratchReg()); + masm.push(R0.scratchReg()); + 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) +{ + MOZ_ASSERT(obj.isObject()); + RootedValue rootedObj(cx, obj); + if (!ToPrimitive(cx, &rootedObj)) + return nullptr; + return ToString<CanGC>(cx, rootedObj); +} + +static bool +DoConcatStringObject(JSContext* cx, bool lhsIsString, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) +{ + JSString* lstr = nullptr; + JSString* rstr = nullptr; + if (lhsIsString) { + // Convert rhs first. + MOZ_ASSERT(lhs.isString() && rhs.isObject()); + rstr = ConvertObjectToStringForConcat(cx, rhs); + if (!rstr) + return false; + + // lhs is already string. + lstr = lhs.toString(); + } else { + MOZ_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, "DoConcatStringObject", TailCall, + 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(R0.scratchReg()); + masm.passABIArg(FloatReg0, MoveOp::DOUBLE); + masm.passABIArg(FloatReg1, MoveOp::DOUBLE); + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, NumberMod), MoveOp::DOUBLE); + MOZ_ASSERT(ReturnDoubleReg == FloatReg0); + break; + default: + MOZ_CRASH("Unexpected op"); + } + + 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); + + MOZ_ASSERT(op_ == JSOP_ADD || op_ == JSOP_SUB || + op_ == JSOP_BITOR || op_ == JSOP_BITXOR || op_ == JSOP_BITAND); + + switch(op_) { + case JSOP_ADD: { + Label fixOverflow; + + 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. + break; + } + case JSOP_SUB: { + Label fixOverflow; + + 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. + break; + } + case JSOP_BITOR: { + masm.or32(rhsReg, lhsReg); + masm.tagValue(JSVAL_TYPE_INT32, lhsReg, R0); + EmitReturnFromIC(masm); + break; + } + case JSOP_BITXOR: { + masm.xor32(rhsReg, lhsReg); + masm.tagValue(JSVAL_TYPE_INT32, lhsReg, R0); + EmitReturnFromIC(masm); + break; + } + case JSOP_BITAND: { + masm.and32(rhsReg, lhsReg); + masm.tagValue(JSVAL_TYPE_INT32, lhsReg, R0); + EmitReturnFromIC(masm); + break; + } + default: + MOZ_CRASH("Unhandled op for BinaryArith_BooleanWithInt32."); + } + + // Failure case - jump to next stub + masm.bind(&failure); + EmitStubGuardFailure(masm); + return true; +} + +bool +ICBinaryArith_DoubleWithInt32::Compiler::generateStubCode(MacroAssembler& masm) +{ + MOZ_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.branchTruncateDoubleMaybeModUint32(FloatReg0, scratchReg, &truncateABICall); + masm.jump(&doneTruncate); + + masm.bind(&truncateABICall); + masm.push(intReg); + masm.setupUnalignedABICall(scratchReg); + masm.passABIArg(FloatReg0, MoveOp::DOUBLE); + masm.callWithABI(mozilla::BitwiseCast<void*, int32_t(*)(double)>(JS::ToInt32)); + masm.storeCallInt32Result(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.or32(intReg, intReg2); + break; + case JSOP_BITXOR: + masm.xor32(intReg, intReg2); + break; + case JSOP_BITAND: + masm.and32(intReg, intReg2); + break; + default: + MOZ_CRASH("Unhandled op for BinaryArith_DoubleWithInt32."); + } + masm.tagValue(JSVAL_TYPE_INT32, intReg2, R0); + EmitReturnFromIC(masm); + + // Failure case - jump to next stub + masm.bind(&failure); + EmitStubGuardFailure(masm); + return true; +} + +// +// UnaryArith_Fallback +// + +static bool +DoUnaryArithFallback(JSContext* cx, void* payload, ICUnaryArith_Fallback* stub_, + HandleValue val, MutableHandleValue res) +{ + SharedStubInfo info(cx, payload, stub_->icEntry()); + ICStubCompiler::Engine engine = info.engine(); + HandleScript script = info.innerScript(); + + // This fallback stub may trigger debug mode toggling. + DebugModeOSRVolatileStub<ICUnaryArith_Fallback*> stub(engine, info.maybeFrame(), stub_); + + jsbytecode* pc = info.pc(); + JSOp op = JSOp(*pc); + FallbackICSpew(cx, stub, "UnaryArith(%s)", 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: + MOZ_CRASH("Unexpected op"); + } + + // Check if debug mode toggling made the stub invalid. + if (stub.invalid()) + return true; + + if (res.isDouble()) + stub->setSawDoubleResult(); + + if (stub->numOptimizedStubs() >= ICUnaryArith_Fallback::MAX_OPTIMIZED_STUBS) { + // TODO: Discard/replace stubs. + return true; + } + + if (val.isInt32() && res.isInt32()) { + JitSpew(JitSpew_BaselineIC, " Generating %s(Int32 => Int32) stub", CodeName[op]); + ICUnaryArith_Int32::Compiler compiler(cx, op, engine); + ICStub* int32Stub = compiler.getStub(compiler.getStubSpace(info.outerScript(cx))); + if (!int32Stub) + return false; + stub->addNewStub(int32Stub); + return true; + } + + if (val.isNumber() && res.isNumber() && cx->runtime()->jitSupportsFloatingPoint) { + JitSpew(JitSpew_BaselineIC, " Generating %s(Number => Number) stub", 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, engine); + ICStub* doubleStub = compiler.getStub(compiler.getStubSpace(info.outerScript(cx))); + if (!doubleStub) + return false; + stub->addNewStub(doubleStub); + return true; + } + + return true; +} + +typedef bool (*DoUnaryArithFallbackFn)(JSContext*, void*, ICUnaryArith_Fallback*, + HandleValue, MutableHandleValue); +static const VMFunction DoUnaryArithFallbackInfo = + FunctionInfo<DoUnaryArithFallbackFn>(DoUnaryArithFallback, "DoUnaryArithFallback", TailCall, + PopValues(1)); + +bool +ICUnaryArith_Fallback::Compiler::generateStubCode(MacroAssembler& masm) +{ + MOZ_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(ICStubReg); + pushStubPayload(masm, R0.scratchReg()); + + return tailCallVM(DoUnaryArithFallbackInfo, masm); +} + +bool +ICUnaryArith_Double::Compiler::generateStubCode(MacroAssembler& masm) +{ + Label failure; + masm.ensureDouble(R0, FloatReg0, &failure); + + MOZ_ASSERT(op == JSOP_NEG || op == JSOP_BITNOT); + + if (op == JSOP_NEG) { + masm.negateDouble(FloatReg0); + masm.boxDouble(FloatReg0, R0); + } else { + // Truncate the double to an int32. + Register scratchReg = R1.scratchReg(); + + Label doneTruncate; + Label truncateABICall; + masm.branchTruncateDoubleMaybeModUint32(FloatReg0, scratchReg, &truncateABICall); + masm.jump(&doneTruncate); + + masm.bind(&truncateABICall); + masm.setupUnalignedABICall(scratchReg); + masm.passABIArg(FloatReg0, MoveOp::DOUBLE); + masm.callWithABI(BitwiseCast<void*, int32_t(*)(double)>(JS::ToInt32)); + masm.storeCallInt32Result(scratchReg); + + masm.bind(&doneTruncate); + masm.not32(scratchReg); + masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R0); + } + + EmitReturnFromIC(masm); + + // Failure case - jump to next stub + masm.bind(&failure); + EmitStubGuardFailure(masm); + return true; +} + +// +// Compare_Fallback +// + +static bool +DoCompareFallback(JSContext* cx, void* payload, ICCompare_Fallback* stub_, HandleValue lhs, + HandleValue rhs, MutableHandleValue ret) +{ + SharedStubInfo info(cx, payload, stub_->icEntry()); + ICStubCompiler::Engine engine = info.engine(); + + // This fallback stub may trigger debug mode toggling. + DebugModeOSRVolatileStub<ICCompare_Fallback*> stub(engine, info.maybeFrame(), stub_); + + jsbytecode* pc = info.pc(); + JSOp op = JSOp(*pc); + + FallbackICSpew(cx, stub, "Compare(%s)", 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. + bool 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: + MOZ_ASSERT_UNREACHABLE("Unhandled baseline compare op"); + return false; + } + + ret.setBoolean(out); + + // Check if debug mode toggling made the stub invalid. + if (stub.invalid()) + return true; + + // 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; + } + + // Try to generate new stubs. + if (lhs.isInt32() && rhs.isInt32()) { + JitSpew(JitSpew_BaselineIC, " Generating %s(Int32, Int32) stub", CodeName[op]); + ICCompare_Int32::Compiler compiler(cx, op, engine); + ICStub* int32Stub = compiler.getStub(compiler.getStubSpace(info.outerScript(cx))); + if (!int32Stub) + return false; + + stub->addNewStub(int32Stub); + return true; + } + + if (!cx->runtime()->jitSupportsFloatingPoint && (lhs.isNumber() || rhs.isNumber())) + return true; + + if (lhs.isNumber() && rhs.isNumber()) { + JitSpew(JitSpew_BaselineIC, " Generating %s(Number, Number) stub", 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, engine); + ICStub* doubleStub = compiler.getStub(compiler.getStubSpace(info.outerScript(cx))); + if (!doubleStub) + return false; + + stub->addNewStub(doubleStub); + return true; + } + + if ((lhs.isNumber() && rhs.isUndefined()) || + (lhs.isUndefined() && rhs.isNumber())) + { + JitSpew(JitSpew_BaselineIC, " Generating %s(%s, %s) stub", CodeName[op], + rhs.isUndefined() ? "Number" : "Undefined", + rhs.isUndefined() ? "Undefined" : "Number"); + ICCompare_NumberWithUndefined::Compiler compiler(cx, op, engine, lhs.isUndefined()); + ICStub* doubleStub = compiler.getStub(compiler.getStubSpace(info.outerScript(cx))); + if (!doubleStub) + return false; + + stub->addNewStub(doubleStub); + return true; + } + + if (lhs.isBoolean() && rhs.isBoolean()) { + JitSpew(JitSpew_BaselineIC, " Generating %s(Boolean, Boolean) stub", CodeName[op]); + ICCompare_Boolean::Compiler compiler(cx, op, engine); + ICStub* booleanStub = compiler.getStub(compiler.getStubSpace(info.outerScript(cx))); + if (!booleanStub) + return false; + + stub->addNewStub(booleanStub); + return true; + } + + if ((lhs.isBoolean() && rhs.isInt32()) || (lhs.isInt32() && rhs.isBoolean())) { + JitSpew(JitSpew_BaselineIC, " Generating %s(%s, %s) stub", CodeName[op], + rhs.isInt32() ? "Boolean" : "Int32", + rhs.isInt32() ? "Int32" : "Boolean"); + ICCompare_Int32WithBoolean::Compiler compiler(cx, op, engine, lhs.isInt32()); + ICStub* optStub = compiler.getStub(compiler.getStubSpace(info.outerScript(cx))); + if (!optStub) + return false; + + stub->addNewStub(optStub); + return true; + } + + if (IsEqualityOp(op)) { + if (lhs.isString() && rhs.isString() && !stub->hasStub(ICStub::Compare_String)) { + JitSpew(JitSpew_BaselineIC, " Generating %s(String, String) stub", CodeName[op]); + ICCompare_String::Compiler compiler(cx, op, engine); + ICStub* stringStub = compiler.getStub(compiler.getStubSpace(info.outerScript(cx))); + if (!stringStub) + return false; + + stub->addNewStub(stringStub); + return true; + } + + if (lhs.isObject() && rhs.isObject()) { + MOZ_ASSERT(!stub->hasStub(ICStub::Compare_Object)); + JitSpew(JitSpew_BaselineIC, " Generating %s(Object, Object) stub", CodeName[op]); + ICCompare_Object::Compiler compiler(cx, op, engine); + ICStub* objectStub = compiler.getStub(compiler.getStubSpace(info.outerScript(cx))); + 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)) + { + JitSpew(JitSpew_BaselineIC, " Generating %s(Obj/Null/Undef, Obj/Null/Undef) stub", + CodeName[op]); + bool lhsIsUndefined = lhs.isNull() || lhs.isUndefined(); + bool compareWithNull = lhs.isNull() || rhs.isNull(); + ICCompare_ObjectWithUndefined::Compiler compiler(cx, op, engine, + lhsIsUndefined, compareWithNull); + ICStub* objectStub = compiler.getStub(compiler.getStubSpace(info.outerScript(cx))); + if (!objectStub) + return false; + + stub->addNewStub(objectStub); + return true; + } + } + + stub->noteUnoptimizableAccess(); + + return true; +} + +typedef bool (*DoCompareFallbackFn)(JSContext*, void*, ICCompare_Fallback*, + HandleValue, HandleValue, MutableHandleValue); +static const VMFunction DoCompareFallbackInfo = + FunctionInfo<DoCompareFallbackFn>(DoCompareFallback, "DoCompareFallback", TailCall, + PopValues(2)); + +bool +ICCompare_Fallback::Compiler::generateStubCode(MacroAssembler& masm) +{ + MOZ_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(ICStubReg); + pushStubPayload(masm, 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); + + MOZ_ASSERT(IsEqualityOp(op)); + + Register left = masm.extractString(R0, ExtractTemp0); + Register right = masm.extractString(R1, ExtractTemp1); + + AllocatableGeneralRegisterSet regs(availableGeneralRegs(2)); + Register scratchReg = regs.takeAny(); + + masm.compareStrings(op, left, right, scratchReg, &failure); + masm.tagValue(JSVAL_TYPE_BOOLEAN, scratchReg, R0); + EmitReturnFromIC(masm); + + 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); + masm.cmp32Set(cond, left, right, left); + + // 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); + + MOZ_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) +{ + MOZ_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::offsetOfGroup()), obj); + masm.loadPtr(Address(obj, ObjectGroup::offsetOfClasp()), 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); + masm.cmp32Set(cond, (lhsIsInt32_ ? int32Reg : boolReg), + (lhsIsInt32_ ? boolReg : int32Reg), R0.scratchReg()); + + // 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; +} + +// +// GetProp_Fallback +// + +static bool +TryAttachMagicArgumentsGetPropStub(JSContext* cx, SharedStubInfo* info, + ICGetProp_Fallback* stub, HandlePropertyName name, + HandleValue val, HandleValue res, bool* attached) +{ + MOZ_ASSERT(!*attached); + + if (!val.isMagic(JS_OPTIMIZED_ARGUMENTS)) + return true; + + // Try handling arguments.callee on optimized arguments. + if (name == cx->names().callee) { + MOZ_ASSERT(info->script()->hasMappedArgsObj()); + + JitSpew(JitSpew_BaselineIC, " Generating GetProp(MagicArgs.callee) stub"); + + // Unlike ICGetProp_ArgumentsLength, only magic argument stubs are + // supported at the moment. + ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub(); + ICGetProp_ArgumentsCallee::Compiler compiler(cx, info->engine(), monitorStub); + ICStub* newStub = compiler.getStub(compiler.getStubSpace(info->outerScript(cx))); + if (!newStub) + return false; + stub->addNewStub(newStub); + + *attached = true; + return true; + } + + return true; +} + +static bool +TryAttachLengthStub(JSContext* cx, SharedStubInfo* info, + ICGetProp_Fallback* stub, HandleValue val, + HandleValue res, bool* attached) +{ + MOZ_ASSERT(!*attached); + + if (val.isString()) { + MOZ_ASSERT(res.isInt32()); + JitSpew(JitSpew_BaselineIC, " Generating GetProp(String.length) stub"); + ICGetProp_StringLength::Compiler compiler(cx, info->engine()); + ICStub* newStub = compiler.getStub(compiler.getStubSpace(info->outerScript(cx))); + if (!newStub) + return false; + + *attached = true; + stub->addNewStub(newStub); + return true; + } + + if (val.isMagic(JS_OPTIMIZED_ARGUMENTS) && res.isInt32()) { + JitSpew(JitSpew_BaselineIC, " Generating GetProp(MagicArgs.length) stub"); + ICGetProp_ArgumentsLength::Compiler compiler(cx, info->engine(), ICGetProp_ArgumentsLength::Magic); + ICStub* newStub = compiler.getStub(compiler.getStubSpace(info->outerScript(cx))); + if (!newStub) + return false; + + *attached = true; + stub->addNewStub(newStub); + return true; + } + + return true; +} + +static bool +UpdateExistingGenerationalDOMProxyStub(ICGetProp_Fallback* stub, + HandleObject obj) +{ + Value expandoSlot = GetProxyExtra(obj, GetDOMProxyExpandoSlot()); + MOZ_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 + uint64_t generation = expandoAndGeneration->generation; + JitSpew(JitSpew_BaselineIC, + " Updating existing stub with generation, old value: %" PRIu64 ", " + "new value: %" PRIu64 "", updateStub->generation(), + generation); + updateStub->setGeneration(generation); + return true; + } + } + } + return false; +} + +// Return whether obj is in some PreliminaryObjectArray and has a structure +// that might change in the future. +bool +IsPreliminaryObject(JSObject* obj) +{ + if (obj->isSingleton()) + return false; + + TypeNewScript* newScript = obj->group()->newScript(); + if (newScript && !newScript->analyzed()) + return true; + + if (obj->group()->maybePreliminaryObjects()) + return true; + + return false; +} + +void +StripPreliminaryObjectStubs(JSContext* cx, ICFallbackStub* stub) +{ + // Before the new script properties analysis has been performed on a type, + // all instances of that type have the maximum number of fixed slots. + // Afterwards, the objects (even the preliminary ones) might be changed + // to reduce the number of fixed slots they have. If we generate stubs for + // both the old and new number of fixed slots, the stub will look + // polymorphic to IonBuilder when it is actually monomorphic. To avoid + // this, strip out any stubs for preliminary objects before attaching a new + // stub which isn't on a preliminary object. + + for (ICStubIterator iter = stub->beginChain(); !iter.atEnd(); iter++) { + if (iter->isCacheIR_Monitored() && iter->toCacheIR_Monitored()->hasPreliminaryObject()) + iter.unlink(cx); + else if (iter->isSetProp_Native() && iter->toSetProp_Native()->hasPreliminaryObject()) + iter.unlink(cx); + } +} + +JSObject* +GetDOMProxyProto(JSObject* obj) +{ + MOZ_ASSERT(IsCacheableDOMProxy(obj)); + return obj->staticPrototype(); +} + +// 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. +bool +EffectlesslyLookupProperty(JSContext* cx, HandleObject obj, HandleId id, + MutableHandleObject holder, MutableHandleShape shape, + bool* checkDOMProxy, + DOMProxyShadowsResult* shadowsResult, + bool* domProxyHasGeneration) +{ + shape.set(nullptr); + holder.set(nullptr); + + if (checkDOMProxy) { + *checkDOMProxy = false; + *shadowsResult = ShadowCheckFailed; + } + + // Check for list base if asked to. + RootedObject checkObj(cx, obj); + if (checkDOMProxy && IsCacheableDOMProxy(obj)) { + MOZ_ASSERT(domProxyHasGeneration); + MOZ_ASSERT(shadowsResult); + + *checkDOMProxy = true; + if (obj->hasUncacheableProto()) + return true; + + *shadowsResult = GetDOMProxyShadowsCheck()(cx, obj, id); + if (*shadowsResult == ShadowCheckFailed) + return false; + + if (DOMProxyIsShadowing(*shadowsResult)) { + holder.set(obj); + return true; + } + + *domProxyHasGeneration = (*shadowsResult == DoesntShadowUnique); + + checkObj = GetDOMProxyProto(obj); + if (!checkObj) + return true; + } + + if (LookupPropertyPure(cx, checkObj, id, holder.address(), shape.address())) + return true; + + holder.set(nullptr); + shape.set(nullptr); + return true; +} + +bool +IsCacheableProtoChain(JSObject* obj, JSObject* holder, bool isDOMProxy) +{ + MOZ_ASSERT_IF(isDOMProxy, IsCacheableDOMProxy(obj)); + + if (!isDOMProxy && !obj->isNative()) { + if (obj == holder) + return false; + if (!obj->is<UnboxedPlainObject>() && + !obj->is<UnboxedArrayObject>() && + !obj->is<TypedObject>()) + { + 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. + MOZ_ASSERT(!cur->hasDynamicPrototype()); + + // Don't handle objects which require a prototype guard. This should + // be uncommon so handling it is likely not worth the complexity. + if (cur->hasUncacheableProto()) + return false; + + JSObject* proto = cur->staticPrototype(); + if (!proto || !proto->isNative()) + return false; + + cur = proto; + } + + return true; +} + +bool +IsCacheableGetPropReadSlot(JSObject* obj, JSObject* holder, Shape* shape, bool isDOMProxy) +{ + if (!shape || !IsCacheableProtoChain(obj, holder, isDOMProxy)) + return false; + + if (!shape->hasSlot() || !shape->hasDefaultGetter()) + return false; + + return true; +} + +void +GetFixedOrDynamicSlotOffset(Shape* shape, bool* isFixed, uint32_t* offset) +{ + MOZ_ASSERT(isFixed); + MOZ_ASSERT(offset); + *isFixed = shape->slot() < shape->numFixedSlots(); + *offset = *isFixed ? NativeObject::getFixedSlotOffset(shape->slot()) + : (shape->slot() - shape->numFixedSlots()) * sizeof(Value); +} + +bool +IsCacheableGetPropCall(JSContext* cx, JSObject* obj, JSObject* holder, Shape* shape, + bool* isScripted, bool* isTemporarilyUnoptimizable, bool isDOMProxy) +{ + MOZ_ASSERT(isScripted); + + 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 (IsWindow(obj)) { + if (!func->isNative()) + return false; + + if (!func->jitInfo() || func->jitInfo()->needsOuterizedThisObject()) + return false; + } + + if (func->isNative()) { + *isScripted = false; + return true; + } + + if (!func->hasJITCode()) { + *isTemporarilyUnoptimizable = true; + return false; + } + + *isScripted = true; + return true; +} + +// Try to update all existing GetProp/GetName getter call stubs that match the +// given holder in place with a new shape and getter. fallbackStub can be +// either an ICGetProp_Fallback or an ICGetName_Fallback. +// +// If 'getter' is an own property, holder == receiver must be true. +bool +UpdateExistingGetPropCallStubs(ICFallbackStub* fallbackStub, + ICStub::Kind kind, + HandleNativeObject holder, + HandleObject receiver, + HandleFunction getter) +{ + MOZ_ASSERT(kind == ICStub::GetProp_CallScripted || + kind == ICStub::GetProp_CallNative || + kind == ICStub::GetProp_CallNativeGlobal); + MOZ_ASSERT(fallbackStub->isGetName_Fallback() || + fallbackStub->isGetProp_Fallback()); + MOZ_ASSERT(holder); + MOZ_ASSERT(receiver); + + bool isOwnGetter = (holder == receiver); + bool foundMatchingStub = false; + ReceiverGuard receiverGuard(receiver); + for (ICStubConstIterator iter = fallbackStub->beginChainConst(); !iter.atEnd(); iter++) { + if (iter->kind() == kind) { + ICGetPropCallGetter* getPropStub = static_cast<ICGetPropCallGetter*>(*iter); + if (getPropStub->holder() == holder && getPropStub->isOwnGetter() == isOwnGetter) { + // If this is an own getter, update the receiver guard as well, + // since that's the shape we'll be guarding on. Furthermore, + // isOwnGetter() relies on holderShape_ and receiverGuard_ being + // the same shape. + if (isOwnGetter) + getPropStub->receiverGuard().update(receiverGuard); + + MOZ_ASSERT(getPropStub->holderShape() != holder->lastProperty() || + !getPropStub->receiverGuard().matches(receiverGuard) || + getPropStub->toGetProp_CallNativeGlobal()->globalShape() != + receiver->as<LexicalEnvironmentObject>().global().lastProperty(), + "Why didn't we end up using this stub?"); + + // We want to update the holder shape to match the new one no + // matter what, even if the receiver shape is different. + getPropStub->holderShape() = holder->lastProperty(); + + // Make sure to update the getter, since a shape change might + // have changed which getter we want to use. + getPropStub->getter() = getter; + + if (getPropStub->isGetProp_CallNativeGlobal()) { + ICGetProp_CallNativeGlobal* globalStub = + getPropStub->toGetProp_CallNativeGlobal(); + globalStub->globalShape() = + receiver->as<LexicalEnvironmentObject>().global().lastProperty(); + } + + if (getPropStub->receiverGuard().matches(receiverGuard)) + foundMatchingStub = true; + } + } + } + + return foundMatchingStub; +} + +static bool +TryAttachNativeGetAccessorPropStub(JSContext* cx, SharedStubInfo* info, + ICGetProp_Fallback* stub, HandlePropertyName name, + HandleValue val, HandleValue res, bool* attached, + bool* isTemporarilyUnoptimizable) +{ + MOZ_ASSERT(!*attached); + MOZ_ASSERT(!*isTemporarilyUnoptimizable); + + if (!val.isObject()) + return true; + + RootedObject obj(cx, &val.toObject()); + + bool isDOMProxy; + bool domProxyHasGeneration; + DOMProxyShadowsResult domProxyShadowsResult; + RootedShape shape(cx); + RootedObject holder(cx); + RootedId id(cx, NameToId(name)); + if (!EffectlesslyLookupProperty(cx, obj, id, &holder, &shape, &isDOMProxy, + &domProxyShadowsResult, &domProxyHasGeneration)) + { + return false; + } + + ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub(); + + bool isScripted = false; + bool cacheableCall = IsCacheableGetPropCall(cx, obj, holder, shape, &isScripted, + isTemporarilyUnoptimizable, + isDOMProxy); + + // Try handling scripted getters. + if (cacheableCall && isScripted && !isDOMProxy && + info->engine() == ICStubCompiler::Engine::Baseline) + { + RootedFunction callee(cx, &shape->getterObject()->as<JSFunction>()); + MOZ_ASSERT(callee->hasScript()); + + if (UpdateExistingGetPropCallStubs(stub, ICStub::GetProp_CallScripted, + holder.as<NativeObject>(), obj, callee)) { + *attached = true; + return true; + } + + JitSpew(JitSpew_BaselineIC, " Generating GetProp(NativeObj/ScriptedGetter %s:%" PRIuSIZE ") stub", + callee->nonLazyScript()->filename(), callee->nonLazyScript()->lineno()); + + ICGetProp_CallScripted::Compiler compiler(cx, monitorStub, obj, holder, callee, + info->pcOffset()); + ICStub* newStub = compiler.getStub(compiler.getStubSpace(info->outerScript(cx))); + 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 && DOMProxyIsShadowing(domProxyShadowsResult)) { + MOZ_ASSERT(obj == holder); + + JitSpew(JitSpew_BaselineIC, " Generating GetProp(DOMProxyProxy) stub"); + Rooted<ProxyObject*> proxy(cx, &obj->as<ProxyObject>()); + ICGetProp_DOMProxyShadowed::Compiler compiler(cx, info->engine(), monitorStub, proxy, name, + info->pcOffset()); + ICStub* newStub = compiler.getStub(compiler.getStubSpace(info->outerScript(cx))); + if (!newStub) + return false; + stub->addNewStub(newStub); + *attached = true; + return true; + } + + const Class* outerClass = nullptr; + if (!isDOMProxy && !obj->isNative()) { + outerClass = obj->getClass(); + if (!IsWindowProxy(obj)) + return true; + + // This must be a WindowProxy for the current Window/global. Else it'd + // be a cross-compartment wrapper and IsWindowProxy returns false for + // those. + MOZ_ASSERT(ToWindowIfWindowProxy(obj) == cx->global()); + obj = cx->global(); + + if (!EffectlesslyLookupProperty(cx, obj, id, &holder, &shape, &isDOMProxy, + &domProxyShadowsResult, &domProxyHasGeneration)) + { + return false; + } + cacheableCall = IsCacheableGetPropCall(cx, obj, holder, shape, &isScripted, + isTemporarilyUnoptimizable, isDOMProxy); + } + + // Try handling JSNative getters. + if (!cacheableCall || isScripted) + return true; + + if (!shape || !shape->hasGetterValue() || !shape->getterValue().isObject() || + !shape->getterObject()->is<JSFunction>()) + { + return true; + } + + RootedFunction callee(cx, &shape->getterObject()->as<JSFunction>()); + MOZ_ASSERT(callee->isNative()); + + if (outerClass && (!callee->jitInfo() || callee->jitInfo()->needsOuterizedThisObject())) + return true; + + JitSpew(JitSpew_BaselineIC, " Generating GetProp(%s%s/NativeGetter %p) stub", + isDOMProxy ? "DOMProxyObj" : "NativeObj", + isDOMProxy && domProxyHasGeneration ? "WithGeneration" : "", + callee->native()); + + ICStub* newStub = nullptr; + if (isDOMProxy) { + MOZ_ASSERT(obj != holder); + ICStub::Kind kind; + if (domProxyHasGeneration) { + if (UpdateExistingGenerationalDOMProxyStub(stub, obj)) { + *attached = true; + return true; + } + kind = ICStub::GetProp_CallDOMProxyWithGenerationNative; + } else { + kind = ICStub::GetProp_CallDOMProxyNative; + } + Rooted<ProxyObject*> proxy(cx, &obj->as<ProxyObject>()); + ICGetPropCallDOMProxyNativeCompiler compiler(cx, kind, info->engine(), monitorStub, proxy, holder, + callee, info->pcOffset()); + newStub = compiler.getStub(compiler.getStubSpace(info->outerScript(cx))); + } else { + if (UpdateExistingGetPropCallStubs(stub, ICStub::GetProp_CallNative, + holder.as<NativeObject>(), obj, callee)) + { + *attached = true; + return true; + } + + ICGetPropCallNativeCompiler compiler(cx, ICStub::GetProp_CallNative, info->engine(), + monitorStub, obj, holder, callee, + info->pcOffset(), outerClass); + newStub = compiler.getStub(compiler.getStubSpace(info->outerScript(cx))); + } + if (!newStub) + return false; + stub->addNewStub(newStub); + *attached = true; + return true; +} + +bool +CheckHasNoSuchProperty(JSContext* cx, JSObject* obj, PropertyName* name, + JSObject** lastProto, size_t* protoChainDepthOut) +{ + size_t depth = 0; + JSObject* curObj = obj; + while (curObj) { + if (curObj->isNative()) { + // Don't handle proto chains with resolve hooks. + if (ClassMayResolveId(cx->names(), curObj->getClass(), NameToId(name), curObj)) + return false; + if (curObj->as<NativeObject>().contains(cx, NameToId(name))) + return false; + if (curObj->getClass()->getGetProperty()) + return false; + } else if (curObj != obj) { + // Non-native objects are only handled as the original receiver. + return false; + } else if (curObj->is<UnboxedPlainObject>()) { + if (curObj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, NameToId(name))) + return false; + } else if (curObj->is<UnboxedArrayObject>()) { + if (name == cx->names().length) + return false; + } else if (curObj->is<TypedObject>()) { + if (curObj->as<TypedObject>().typeDescr().hasProperty(cx->names(), NameToId(name))) + return false; + } else { + return false; + } + + JSObject* proto = curObj->staticPrototype(); + if (!proto) + break; + + curObj = proto; + depth++; + } + + if (lastProto) + *lastProto = curObj; + if (protoChainDepthOut) + *protoChainDepthOut = depth; + return true; +} + +static bool +ComputeGetPropResult(JSContext* cx, BaselineFrame* frame, JSOp op, HandlePropertyName name, + MutableHandleValue val, MutableHandleValue res) +{ + // Handle arguments.length and arguments.callee on optimized arguments, as + // it is not an object. + if (frame && val.isMagic(JS_OPTIMIZED_ARGUMENTS) && IsOptimizedArguments(frame, val)) { + if (op == JSOP_LENGTH) { + res.setInt32(frame->numActualArgs()); + } else { + MOZ_ASSERT(name == cx->names().callee); + MOZ_ASSERT(frame->script()->hasMappedArgsObj()); + res.setObject(*frame->callee()); + } + } else { + if (op == JSOP_GETXPROP) { + RootedObject obj(cx, &val.toObject()); + RootedId id(cx, NameToId(name)); + if (!GetPropertyForNameLookup(cx, obj, id, res)) + return false; + } else { + MOZ_ASSERT(op == JSOP_GETPROP || op == JSOP_CALLPROP || op == JSOP_LENGTH); + if (!GetProperty(cx, val, name, res)) + return false; + } + } + + return true; +} + +static bool +DoGetPropFallback(JSContext* cx, void* payload, ICGetProp_Fallback* stub_, + MutableHandleValue val, MutableHandleValue res) +{ + SharedStubInfo info(cx, payload, stub_->icEntry()); + ICStubCompiler::Engine engine = info.engine(); + HandleScript script = info.innerScript(); + + // This fallback stub may trigger debug mode toggling. + DebugModeOSRVolatileStub<ICGetProp_Fallback*> stub(engine, info.maybeFrame(), stub_); + + jsbytecode* pc = info.pc(); + JSOp op = JSOp(*pc); + FallbackICSpew(cx, stub, "GetProp(%s)", CodeName[op]); + + MOZ_ASSERT(op == JSOP_GETPROP || op == JSOP_CALLPROP || op == JSOP_LENGTH || op == JSOP_GETXPROP); + + // Grab our old shape before it goes away. + RootedShape oldShape(cx); + if (val.isObject()) + oldShape = val.toObject().maybeShape(); + + bool attached = false; + // There are some reasons we can fail to attach a stub that are temporary. + // We want to avoid calling noteUnoptimizableAccess() if the reason we + // failed to attach a stub is one of those temporary reasons, since we might + // end up attaching a stub for the exact same access later. + bool isTemporarilyUnoptimizable = false; + + RootedPropertyName name(cx, script->getName(pc)); + + // After the Genericstub was added, we should never reach the Fallbackstub again. + MOZ_ASSERT(!stub->hasStub(ICStub::GetProp_Generic)); + + if (stub->numOptimizedStubs() >= ICGetProp_Fallback::MAX_OPTIMIZED_STUBS && !stub.invalid()) { + // Discard all stubs in this IC and replace with generic getprop stub. + for (ICStubIterator iter = stub->beginChain(); !iter.atEnd(); iter++) + iter.unlink(cx); + ICGetProp_Generic::Compiler compiler(cx, engine, + stub->fallbackMonitorStub()->firstMonitorStub()); + ICStub* newStub = compiler.getStub(compiler.getStubSpace(info.outerScript(cx))); + if (!newStub) + return false; + stub->addNewStub(newStub); + attached = true; + } + + if (!attached && !JitOptions.disableCacheIR) { + mozilla::Maybe<CacheIRWriter> writer; + GetPropIRGenerator gen(cx, pc, val, name, res); + if (!gen.tryAttachStub(writer)) + return false; + if (gen.emitted()) { + ICStub* newStub = AttachBaselineCacheIRStub(cx, writer.ref(), CacheKind::GetProp, stub); + if (newStub) { + JitSpew(JitSpew_BaselineIC, " Attached CacheIR stub"); + attached = true; + if (gen.shouldNotePreliminaryObjectStub()) + newStub->toCacheIR_Monitored()->notePreliminaryObject(); + else if (gen.shouldUnlinkPreliminaryObjectStubs()) + StripPreliminaryObjectStubs(cx, stub); + } + } + } + + if (!attached && !stub.invalid() && + !TryAttachNativeGetAccessorPropStub(cx, &info, stub, name, val, res, &attached, + &isTemporarilyUnoptimizable)) + { + return false; + } + + if (!ComputeGetPropResult(cx, info.maybeFrame(), op, name, val, res)) + return false; + + TypeScript::Monitor(cx, script, pc, res); + + // Check if debug mode toggling made the stub invalid. + if (stub.invalid()) + return true; + + // Add a type monitor stub for the resulting value. + if (!stub->addMonitorStubForValue(cx, &info, res)) + return false; + + if (attached) + return true; + + if (op == JSOP_LENGTH) { + if (!TryAttachLengthStub(cx, &info, stub, val, res, &attached)) + return false; + if (attached) + return true; + } + + if (!TryAttachMagicArgumentsGetPropStub(cx, &info, stub, name, val, + res, &attached)) + return false; + if (attached) + return true; + + + MOZ_ASSERT(!attached); + if (!isTemporarilyUnoptimizable) + stub->noteUnoptimizableAccess(); + + return true; +} + +typedef bool (*DoGetPropFallbackFn)(JSContext*, void*, ICGetProp_Fallback*, + MutableHandleValue, MutableHandleValue); +static const VMFunction DoGetPropFallbackInfo = + FunctionInfo<DoGetPropFallbackFn>(DoGetPropFallback, "DoGetPropFallback", TailCall, + PopValues(1)); + +bool +ICGetProp_Fallback::Compiler::generateStubCode(MacroAssembler& masm) +{ + MOZ_ASSERT(R0 == JSReturnOperand); + + EmitRestoreTailCallReg(masm); + + // Ensure stack is fully synced for the expression decompiler. + masm.pushValue(R0); + + // Push arguments. + masm.pushValue(R0); + masm.push(ICStubReg); + pushStubPayload(masm, R0.scratchReg()); + + if (!tailCallVM(DoGetPropFallbackInfo, masm)) + return false; + + // Even though the fallback frame doesn't enter a stub frame, the CallScripted + // frame that we are emulating does. Again, we lie. +#ifdef DEBUG + EmitRepushTailCallReg(masm); + enterStubFrame(masm, R0.scratchReg()); +#else + inStubFrame_ = true; +#endif + + // What follows is bailout for inlined scripted getters. + // The return address pointed to by the baseline stack points here. + returnOffset_ = masm.currentOffset(); + + leaveStubFrame(masm, true); + + // When we get here, ICStubReg contains the ICGetProp_Fallback stub, + // which we can't use to enter the TypeMonitor IC, because it's a MonitoredFallbackStub + // instead of a MonitoredStub. So, we cheat. + masm.loadPtr(Address(ICStubReg, ICMonitoredFallbackStub::offsetOfFallbackMonitorStub()), + ICStubReg); + EmitEnterTypeMonitorIC(masm, ICTypeMonitor_Fallback::offsetOfFirstMonitorStub()); + + return true; +} + +void +ICGetProp_Fallback::Compiler::postGenerateStubCode(MacroAssembler& masm, Handle<JitCode*> code) +{ + if (engine_ == Engine::Baseline) { + void* address = code->raw() + returnOffset_; + cx->compartment()->jitCompartment()->initBaselineGetPropReturnAddr(address); + } +} + +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; +} + +ICGetPropNativeStub* +ICGetPropNativeCompiler::getStub(ICStubSpace* space) +{ + ReceiverGuard guard(obj_); + + switch (kind) { + case ICStub::GetName_Global: { + MOZ_ASSERT(obj_ != holder_); + Shape* holderShape = holder_->as<NativeObject>().lastProperty(); + Shape* globalShape = obj_->as<LexicalEnvironmentObject>().global().lastProperty(); + return newStub<ICGetName_Global>(space, getStubCode(), firstMonitorStub_, guard, + offset_, holder_, holderShape, globalShape); + } + + default: + MOZ_CRASH("Bad stub kind"); + } +} + +void +GuardReceiverObject(MacroAssembler& masm, ReceiverGuard guard, + Register object, Register scratch, + size_t receiverGuardOffset, Label* failure) +{ + Address groupAddress(ICStubReg, receiverGuardOffset + HeapReceiverGuard::offsetOfGroup()); + Address shapeAddress(ICStubReg, receiverGuardOffset + HeapReceiverGuard::offsetOfShape()); + Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando()); + + if (guard.group) { + masm.loadPtr(groupAddress, scratch); + masm.branchTestObjGroup(Assembler::NotEqual, object, scratch, failure); + + if (guard.group->clasp() == &UnboxedPlainObject::class_ && !guard.shape) { + // Guard the unboxed object has no expando object. + masm.branchPtr(Assembler::NotEqual, expandoAddress, ImmWord(0), failure); + } + } + + if (guard.shape) { + masm.loadPtr(shapeAddress, scratch); + if (guard.group && guard.group->clasp() == &UnboxedPlainObject::class_) { + // Guard the unboxed object has a matching expando object. + masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failure); + Label done; + masm.push(object); + masm.loadPtr(expandoAddress, object); + masm.branchTestObjShape(Assembler::Equal, object, scratch, &done); + masm.pop(object); + masm.jump(failure); + masm.bind(&done); + masm.pop(object); + } else { + masm.branchTestObjShape(Assembler::NotEqual, object, scratch, failure); + } + } +} + +static void +GuardGlobalObject(MacroAssembler& masm, HandleObject holder, Register globalLexicalReg, + Register holderReg, Register scratch, size_t globalShapeOffset, Label* failure) +{ + if (holder->is<GlobalObject>()) + return; + masm.extractObject(Address(globalLexicalReg, EnvironmentObject::offsetOfEnclosingEnvironment()), + holderReg); + masm.loadPtr(Address(ICStubReg, globalShapeOffset), scratch); + masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, failure); +} + +bool +ICGetPropNativeCompiler::generateStubCode(MacroAssembler& masm) +{ + Label failure; + AllocatableGeneralRegisterSet regs(availableGeneralRegs(0)); + Register objReg = InvalidReg; + + if (inputDefinitelyObject_) { + objReg = R0.scratchReg(); + } else { + regs.take(R0); + // Guard input is an object and unbox. + masm.branchTestObject(Assembler::NotEqual, R0, &failure); + objReg = masm.extractObject(R0, ExtractTemp0); + } + regs.takeUnchecked(objReg); + + Register scratch = regs.takeAnyExcluding(ICTailCallReg); + + // Shape/group guard. + GuardReceiverObject(masm, ReceiverGuard(obj_), objReg, scratch, + ICGetPropNativeStub::offsetOfReceiverGuard(), &failure); + + MOZ_ASSERT(obj_ != holder_); + MOZ_ASSERT(kind == ICStub::GetName_Global); + + Register holderReg = regs.takeAny(); + + // If we are generating a non-lexical GETGNAME stub, we must also + // guard on the shape of the GlobalObject. + MOZ_ASSERT(obj_->is<LexicalEnvironmentObject>() && + obj_->as<LexicalEnvironmentObject>().isGlobal()); + GuardGlobalObject(masm, holder_, objReg, holderReg, scratch, + ICGetName_Global::offsetOfGlobalShape(), &failure); + + // Shape guard holder. + masm.loadPtr(Address(ICStubReg, ICGetName_Global::offsetOfHolder()), + holderReg); + masm.loadPtr(Address(ICStubReg, ICGetName_Global::offsetOfHolderShape()), + scratch); + masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, &failure); + + if (!isFixedSlot_) { + // Don't overwrite actual holderReg if we need to load a dynamic slots object. + // May need to preserve object for noSuchMethod check later. + Register nextHolder = regs.takeAny(); + masm.loadPtr(Address(holderReg, NativeObject::offsetOfSlots()), nextHolder); + holderReg = nextHolder; + } + + masm.load32(Address(ICStubReg, ICGetPropNativeStub::offsetOfOffset()), scratch); + BaseIndex result(holderReg, scratch, TimesOne); + + masm.loadValue(result, 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 +GetProtoShapes(JSObject* obj, size_t protoChainDepth, MutableHandle<ShapeVector> shapes) +{ + JSObject* curProto = obj->staticPrototype(); + for (size_t i = 0; i < protoChainDepth; i++) { + if (!shapes.append(curProto->as<NativeObject>().lastProperty())) + return false; + curProto = curProto->staticPrototype(); + } + + MOZ_ASSERT(!curProto, + "longer prototype chain encountered than this stub permits!"); + return true; +} + +bool +ICGetProp_CallScripted::Compiler::generateStubCode(MacroAssembler& masm) +{ + MOZ_ASSERT(engine_ == Engine::Baseline); + + Label failure; + Label failureLeaveStubFrame; + AllocatableGeneralRegisterSet regs(availableGeneralRegs(1)); + Register scratch = regs.takeAnyExcluding(ICTailCallReg); + + // Guard input is an object. + masm.branchTestObject(Assembler::NotEqual, R0, &failure); + + // Unbox and shape guard. + Register objReg = masm.extractObject(R0, ExtractTemp0); + GuardReceiverObject(masm, ReceiverGuard(receiver_), objReg, scratch, + ICGetProp_CallScripted::offsetOfReceiverGuard(), &failure); + + if (receiver_ != holder_) { + Register holderReg = regs.takeAny(); + masm.loadPtr(Address(ICStubReg, ICGetProp_CallScripted::offsetOfHolder()), holderReg); + masm.loadPtr(Address(ICStubReg, 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(ICStubReg, ICGetProp_CallScripted::offsetOfGetter()), callee); + masm.branchIfFunctionHasNoScript(callee, &failureLeaveStubFrame); + masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), code); + masm.loadBaselineOrIonRaw(code, code, &failureLeaveStubFrame); + + // Align the stack such that the JitFrameLayout is aligned on + // JitStackAlignment. + masm.alignJitStackBasedOnNArgs(0); + + // Getter is called with 0 arguments, just |obj| as thisv. + // Note that we use Push, not push, so that callJit will align the stack + // properly on ARM. + masm.Push(R0); + EmitBaselineCreateStubFrameDescriptor(masm, scratch, JitFrameLayout::Size()); + masm.Push(Imm32(0)); // ActualArgc is 0 + masm.Push(callee); + masm.Push(scratch); + + // Handle arguments underflow. + Label noUnderflow; + masm.load16ZeroExtend(Address(callee, JSFunction::offsetOfNargs()), scratch); + masm.branch32(Assembler::Equal, scratch, Imm32(0), &noUnderflow); + { + // Call the arguments rectifier. + MOZ_ASSERT(ArgumentsRectifierReg != code); + + JitCode* argumentsRectifier = + cx->runtime()->jitRuntime()->getArgumentsRectifier(); + + masm.movePtr(ImmGCPtr(argumentsRectifier), code); + masm.loadPtr(Address(code, JitCode::offsetOfCode()), code); + masm.movePtr(ImmWord(0), ArgumentsRectifierReg); + } + + masm.bind(&noUnderflow); + masm.callJit(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); + inStubFrame_ = true; + leaveStubFrame(masm, false); + + // Failure case - jump to next stub + masm.bind(&failure); + EmitStubGuardFailure(masm); + return true; +} + +// +// VM function to help call native getters. +// + +bool +DoCallNativeGetter(JSContext* cx, HandleFunction callee, HandleObject obj, + MutableHandleValue result) +{ + MOZ_ASSERT(callee->isNative()); + JSNative natfun = callee->native(); + + JS::AutoValueArray<2> vp(cx); + vp[0].setObject(*callee.get()); + vp[1].setObject(*obj.get()); + + if (!natfun(cx, 0, vp.begin())) + return false; + + result.set(vp[0]); + return true; +} + +typedef bool (*DoCallNativeGetterFn)(JSContext*, HandleFunction, HandleObject, MutableHandleValue); +static const VMFunction DoCallNativeGetterInfo = + FunctionInfo<DoCallNativeGetterFn>(DoCallNativeGetter, "DoCallNativeGetter"); + +bool +ICGetPropCallNativeCompiler::generateStubCode(MacroAssembler& masm) +{ + Label failure; + + AllocatableGeneralRegisterSet regs(availableGeneralRegs(1)); + Register objReg = InvalidReg; + + MOZ_ASSERT(!(inputDefinitelyObject_ && outerClass_)); + if (inputDefinitelyObject_) { + objReg = R0.scratchReg(); + } else { + // Guard input is an object and unbox. + masm.branchTestObject(Assembler::NotEqual, R0, &failure); + objReg = masm.extractObject(R0, ExtractTemp0); + if (outerClass_) { + Register tmp = regs.takeAny(); + masm.branchTestObjClass(Assembler::NotEqual, objReg, tmp, outerClass_, &failure); + masm.movePtr(ImmGCPtr(cx->global()), objReg); + regs.add(tmp); + } + } + + Register scratch = regs.takeAnyExcluding(ICTailCallReg); + + // Shape guard. + GuardReceiverObject(masm, ReceiverGuard(receiver_), objReg, scratch, + ICGetPropCallGetter::offsetOfReceiverGuard(), &failure); + + if (receiver_ != holder_) { + Register holderReg = regs.takeAny(); + + // If we are generating a non-lexical GETGNAME stub, we must also + // guard on the shape of the GlobalObject. + if (kind == ICStub::GetProp_CallNativeGlobal) { + MOZ_ASSERT(receiver_->is<LexicalEnvironmentObject>() && + receiver_->as<LexicalEnvironmentObject>().isGlobal()); + GuardGlobalObject(masm, holder_, objReg, holderReg, scratch, + ICGetProp_CallNativeGlobal::offsetOfGlobalShape(), &failure); + } + + masm.loadPtr(Address(ICStubReg, ICGetPropCallGetter::offsetOfHolder()), holderReg); + masm.loadPtr(Address(ICStubReg, ICGetPropCallGetter::offsetOfHolderShape()), scratch); + masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, &failure); + regs.add(holderReg); + } + + // Box and push obj onto baseline frame stack for decompiler + if (engine_ == Engine::Baseline) { + if (inputDefinitelyObject_) + masm.tagValue(JSVAL_TYPE_OBJECT, objReg, R0); + EmitStowICValues(masm, 1); + if (inputDefinitelyObject_) + objReg = masm.extractObject(R0, ExtractTemp0); + } + + // 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(ICStubReg, ICGetPropCallGetter::offsetOfGetter()), callee); + + // If we're calling a getter on the global, inline the logic for the + // 'this' hook on the global lexical scope and manually push the global. + if (kind == ICStub::GetProp_CallNativeGlobal) + masm.extractObject(Address(objReg, EnvironmentObject::offsetOfEnclosingEnvironment()), + objReg); + + // Push args for vm call. + masm.Push(objReg); + masm.Push(callee); + + regs.add(R0); + + if (!callVM(DoCallNativeGetterInfo, masm)) + return false; + leaveStubFrame(masm); + + if (engine_ == Engine::Baseline) + EmitUnstowICValues(masm, 1, /* discard = */true); + + // Enter type monitor IC to type-check result. + EmitEnterTypeMonitorIC(masm); + + // Failure case - jump to next stub + masm.bind(&failure); + EmitStubGuardFailure(masm); + return true; +} + +ICStub* +ICGetPropCallNativeCompiler::getStub(ICStubSpace* space) +{ + ReceiverGuard guard(receiver_); + Shape* holderShape = holder_->as<NativeObject>().lastProperty(); + + switch (kind) { + case ICStub::GetProp_CallNative: + return newStub<ICGetProp_CallNative>(space, getStubCode(), firstMonitorStub_, + guard, holder_, holderShape, + getter_, pcOffset_); + + case ICStub::GetProp_CallNativeGlobal: { + Shape* globalShape = receiver_->as<LexicalEnvironmentObject>().global().lastProperty(); + return newStub<ICGetProp_CallNativeGlobal>(space, getStubCode(), firstMonitorStub_, + guard, holder_, holderShape, globalShape, + getter_, pcOffset_); + } + + default: + MOZ_CRASH("Bad stub kind"); + } +} + +// Callers are expected to have already guarded on the shape of the +// object, which guarantees the object is a DOM proxy. +void +CheckDOMProxyExpandoDoesNotShadow(JSContext* cx, MacroAssembler& masm, Register object, + const Address& checkExpandoShapeAddr, + Address* expandoAndGenerationAddr, + Address* generationAddr, + Register scratch, + AllocatableGeneralRegisterSet& domProxyRegSet, + Label* checkFailed) +{ + // Guard that the object does not have expando properties, or has an expando + // which is known to not have the desired property. + + // 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; + + masm.loadPtr(Address(object, ProxyObject::offsetOfValues()), scratch); + Address expandoAddr(scratch, ProxyObject::offsetOfExtraSlotInValues(GetDOMProxyExpandoSlot())); + + if (expandoAndGenerationAddr) { + MOZ_ASSERT(generationAddr); + + masm.loadPtr(*expandoAndGenerationAddr, tempVal.scratchReg()); + masm.branchPrivatePtr(Assembler::NotEqual, expandoAddr, tempVal.scratchReg(), + &failDOMProxyCheck); + + masm.branch64(Assembler::NotEqual, + Address(tempVal.scratchReg(), ExpandoAndGeneration::offsetOfGeneration()), + *generationAddr, + 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, ImmPtr(nullptr), &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); +} + +bool +ICGetPropCallDOMProxyNativeCompiler::generateStubCode(MacroAssembler& masm, + Address* expandoAndGenerationAddr, + Address* generationAddr) +{ + Label failure; + AllocatableGeneralRegisterSet regs(availableGeneralRegs(1)); + Register scratch = regs.takeAnyExcluding(ICTailCallReg); + + // Guard input is an object. + masm.branchTestObject(Assembler::NotEqual, R0, &failure); + + // Unbox. + Register objReg = masm.extractObject(R0, ExtractTemp0); + + // Shape guard. + static const size_t receiverShapeOffset = + ICGetProp_CallDOMProxyNative::offsetOfReceiverGuard() + + HeapReceiverGuard::offsetOfShape(); + masm.loadPtr(Address(ICStubReg, receiverShapeOffset), scratch); + masm.branchTestObjShape(Assembler::NotEqual, objReg, scratch, &failure); + + // Guard that our expando object hasn't started shadowing this property. + { + AllocatableGeneralRegisterSet domProxyRegSet(GeneralRegisterSet::All()); + domProxyRegSet.take(ICStubReg); + domProxyRegSet.take(objReg); + domProxyRegSet.take(scratch); + Address expandoShapeAddr(ICStubReg, ICGetProp_CallDOMProxyNative::offsetOfExpandoShape()); + CheckDOMProxyExpandoDoesNotShadow( + cx, masm, objReg, + expandoShapeAddr, expandoAndGenerationAddr, generationAddr, + scratch, + domProxyRegSet, + &failure); + } + + Register holderReg = regs.takeAny(); + masm.loadPtr(Address(ICStubReg, ICGetProp_CallDOMProxyNative::offsetOfHolder()), + holderReg); + masm.loadPtr(Address(ICStubReg, 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(ICStubReg, 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 (!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, nullptr, nullptr); + + Address internalStructAddress(ICStubReg, + ICGetProp_CallDOMProxyWithGenerationNative::offsetOfInternalStruct()); + Address generationAddress(ICStubReg, + ICGetProp_CallDOMProxyWithGenerationNative::offsetOfGeneration()); + return generateStubCode(masm, &internalStructAddress, &generationAddress); +} + +ICStub* +ICGetPropCallDOMProxyNativeCompiler::getStub(ICStubSpace* space) +{ + RootedShape shape(cx, proxy_->maybeShape()); + RootedShape holderShape(cx, holder_->as<NativeObject>().lastProperty()); + + Value expandoSlot = GetProxyExtra(proxy_, GetDOMProxyExpandoSlot()); + RootedShape expandoShape(cx, nullptr); + ExpandoAndGeneration* expandoAndGeneration; + uint64_t generation; + Value expandoVal; + if (kind == ICStub::GetProp_CallDOMProxyNative) { + expandoVal = expandoSlot; + expandoAndGeneration = nullptr; // initialize to silence GCC warning + generation = 0; // initialize to silence GCC warning + } else { + MOZ_ASSERT(kind == ICStub::GetProp_CallDOMProxyWithGenerationNative); + MOZ_ASSERT(!expandoSlot.isObject() && !expandoSlot.isUndefined()); + expandoAndGeneration = (ExpandoAndGeneration*)expandoSlot.toPrivate(); + expandoVal = expandoAndGeneration->expando; + generation = expandoAndGeneration->generation; + } + + if (expandoVal.isObject()) + expandoShape = expandoVal.toObject().as<NativeObject>().lastProperty(); + + if (kind == ICStub::GetProp_CallDOMProxyNative) { + return newStub<ICGetProp_CallDOMProxyNative>( + space, getStubCode(), firstMonitorStub_, shape, + expandoShape, holder_, holderShape, getter_, pcOffset_); + } + + return newStub<ICGetProp_CallDOMProxyWithGenerationNative>( + space, getStubCode(), firstMonitorStub_, shape, + expandoAndGeneration, generation, expandoShape, holder_, holderShape, getter_, + pcOffset_); +} + +ICStub* +ICGetProp_DOMProxyShadowed::Compiler::getStub(ICStubSpace* space) +{ + RootedShape shape(cx, proxy_->maybeShape()); + return New<ICGetProp_DOMProxyShadowed>(cx, space, getStubCode(), firstMonitorStub_, shape, + proxy_->handler(), name_, pcOffset_); +} + +static bool +ProxyGet(JSContext* cx, HandleObject proxy, HandlePropertyName name, MutableHandleValue vp) +{ + RootedValue receiver(cx, ObjectValue(*proxy)); + RootedId id(cx, NameToId(name)); + return Proxy::get(cx, proxy, receiver, id, vp); +} + +typedef bool (*ProxyGetFn)(JSContext* cx, HandleObject proxy, HandlePropertyName name, + MutableHandleValue vp); +static const VMFunction ProxyGetInfo = FunctionInfo<ProxyGetFn>(ProxyGet, "ProxyGet"); + +bool +ICGetProp_DOMProxyShadowed::Compiler::generateStubCode(MacroAssembler& masm) +{ + Label failure; + + AllocatableGeneralRegisterSet regs(availableGeneralRegs(1)); + // Need to reserve a scratch register, but the scratch register should not be + // ICTailCallReg, because it's used for |enterStubFrame| which needs a + // non-ICTailCallReg scratch reg. + Register scratch = regs.takeAnyExcluding(ICTailCallReg); + + // Guard input is an object. + masm.branchTestObject(Assembler::NotEqual, R0, &failure); + + // Unbox. + Register objReg = masm.extractObject(R0, ExtractTemp0); + + // Shape guard. + masm.loadPtr(Address(ICStubReg, ICGetProp_DOMProxyShadowed::offsetOfShape()), scratch); + masm.branchTestObjShape(Assembler::NotEqual, objReg, scratch, &failure); + + // No need to do any more guards; it's safe to call ProxyGet even + // if we've since stopped shadowing. + + // 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(ICStubReg, ICGetProp_DOMProxyShadowed::offsetOfName()), scratch); + masm.Push(scratch); + masm.Push(objReg); + + // Don't have to preserve R0 anymore. + regs.add(R0); + + 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) +{ + MOZ_ASSERT(which_ == ICGetProp_ArgumentsLength::Magic); + + Label failure; + + // 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; +} + +ICGetProp_ArgumentsCallee::ICGetProp_ArgumentsCallee(JitCode* stubCode, ICStub* firstMonitorStub) + : ICMonitoredStub(GetProp_ArgumentsCallee, stubCode, firstMonitorStub) +{ } + +bool +ICGetProp_ArgumentsCallee::Compiler::generateStubCode(MacroAssembler& masm) +{ + Label failure; + + // 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 callee(BaselineFrameReg, BaselineFrame::offsetOfCalleeToken()); + masm.loadFunctionFromCalleeToken(callee, R0.scratchReg()); + masm.tagValue(JSVAL_TYPE_OBJECT, R0.scratchReg(), R0); + + EmitEnterTypeMonitorIC(masm); + + masm.bind(&failure); + EmitStubGuardFailure(masm); + return true; +} + +/* static */ ICGetProp_Generic* +ICGetProp_Generic::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, + ICGetProp_Generic& other) +{ + return New<ICGetProp_Generic>(cx, space, other.jitCode(), firstMonitorStub); +} + +static bool +DoGetPropGeneric(JSContext* cx, void* payload, ICGetProp_Generic* stub, + MutableHandleValue val, MutableHandleValue res) +{ + ICFallbackStub* fallback = stub->getChainFallback(); + SharedStubInfo info(cx, payload, fallback->icEntry()); + HandleScript script = info.innerScript(); + jsbytecode* pc = info.pc(); + JSOp op = JSOp(*pc); + RootedPropertyName name(cx, script->getName(pc)); + return ComputeGetPropResult(cx, info.maybeFrame(), op, name, val, res); +} + +typedef bool (*DoGetPropGenericFn)(JSContext*, void*, ICGetProp_Generic*, MutableHandleValue, MutableHandleValue); +static const VMFunction DoGetPropGenericInfo = + FunctionInfo<DoGetPropGenericFn>(DoGetPropGeneric, "DoGetPropGeneric"); + +bool +ICGetProp_Generic::Compiler::generateStubCode(MacroAssembler& masm) +{ + AllocatableGeneralRegisterSet regs(availableGeneralRegs(1)); + + Register scratch = regs.takeAnyExcluding(ICTailCallReg); + + // Sync for the decompiler. + if (engine_ == Engine::Baseline) + EmitStowICValues(masm, 1); + + enterStubFrame(masm, scratch); + + // Push arguments. + masm.Push(R0); + masm.Push(ICStubReg); + PushStubPayload(masm, R0.scratchReg()); + + if (!callVM(DoGetPropGenericInfo, masm)) + return false; + + leaveStubFrame(masm); + + if (engine_ == Engine::Baseline) + EmitUnstowICValues(masm, 1, /* discard = */ true); + + EmitEnterTypeMonitorIC(masm); + return true; +} + +void +CheckForTypedObjectWithDetachedStorage(JSContext* cx, MacroAssembler& masm, Label* failure) +{ + // All stubs manipulating typed objects must check the compartment-wide + // flag indicating whether their underlying storage might be detached, to + // bail out if needed. + int32_t* address = &cx->compartment()->detachedTypedObjects; + masm.branch32(Assembler::NotEqual, AbsoluteAddress(address), Imm32(0), failure); +} + +void +LoadTypedThingData(MacroAssembler& masm, TypedThingLayout layout, Register obj, Register result) +{ + switch (layout) { + case Layout_TypedArray: + masm.loadPtr(Address(obj, TypedArrayObject::dataOffset()), result); + break; + case Layout_OutlineTypedObject: + masm.loadPtr(Address(obj, OutlineTypedObject::offsetOfData()), result); + break; + case Layout_InlineTypedObject: + masm.computeEffectiveAddress(Address(obj, InlineTypedObject::offsetOfDataStart()), result); + break; + default: + MOZ_CRASH(); + } +} + +void +BaselineScript::noteAccessedGetter(uint32_t pcOffset) +{ + ICEntry& entry = icEntryFromPCOffset(pcOffset); + ICFallbackStub* stub = entry.fallbackStub(); + + if (stub->isGetProp_Fallback()) + stub->toGetProp_Fallback()->noteAccessedGetter(); +} + +ICGetPropNativeStub::ICGetPropNativeStub(ICStub::Kind kind, JitCode* stubCode, + ICStub* firstMonitorStub, + ReceiverGuard guard, uint32_t offset) + : ICMonitoredStub(kind, stubCode, firstMonitorStub), + receiverGuard_(guard), + offset_(offset) +{ } + +ICGetPropNativePrototypeStub::ICGetPropNativePrototypeStub(ICStub::Kind kind, JitCode* stubCode, + ICStub* firstMonitorStub, + ReceiverGuard guard, uint32_t offset, + JSObject* holder, Shape* holderShape) + : ICGetPropNativeStub(kind, stubCode, firstMonitorStub, guard, offset), + holder_(holder), + holderShape_(holderShape) +{ } + +ICGetPropCallGetter::ICGetPropCallGetter(Kind kind, JitCode* stubCode, ICStub* firstMonitorStub, + ReceiverGuard receiverGuard, JSObject* holder, + Shape* holderShape, JSFunction* getter, + uint32_t pcOffset) + : ICMonitoredStub(kind, stubCode, firstMonitorStub), + receiverGuard_(receiverGuard), + holder_(holder), + holderShape_(holderShape), + getter_(getter), + pcOffset_(pcOffset) +{ + MOZ_ASSERT(kind == ICStub::GetProp_CallScripted || + kind == ICStub::GetProp_CallNative || + kind == ICStub::GetProp_CallNativeGlobal || + kind == ICStub::GetProp_CallDOMProxyNative || + kind == ICStub::GetProp_CallDOMProxyWithGenerationNative); +} + +/* static */ ICGetProp_CallScripted* +ICGetProp_CallScripted::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, + ICGetProp_CallScripted& other) +{ + return New<ICGetProp_CallScripted>(cx, space, other.jitCode(), firstMonitorStub, + other.receiverGuard(), + other.holder_, other.holderShape_, + other.getter_, other.pcOffset_); +} + +/* static */ ICGetProp_CallNative* +ICGetProp_CallNative::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, + ICGetProp_CallNative& other) +{ + return New<ICGetProp_CallNative>(cx, space, other.jitCode(), firstMonitorStub, + other.receiverGuard(), other.holder_, + other.holderShape_, other.getter_, other.pcOffset_); +} + +/* static */ ICGetProp_CallNativeGlobal* +ICGetProp_CallNativeGlobal::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, + ICGetProp_CallNativeGlobal& other) +{ + return New<ICGetProp_CallNativeGlobal>(cx, space, other.jitCode(), firstMonitorStub, + other.receiverGuard(), other.holder_, + other.holderShape_, other.globalShape_, + other.getter_, other.pcOffset_); +} + +ICGetPropCallDOMProxyNativeStub::ICGetPropCallDOMProxyNativeStub(Kind kind, JitCode* stubCode, + ICStub* firstMonitorStub, + Shape* shape, + Shape* expandoShape, + JSObject* holder, + Shape* holderShape, + JSFunction* getter, + uint32_t pcOffset) + : ICGetPropCallGetter(kind, stubCode, firstMonitorStub, ReceiverGuard(nullptr, shape), + holder, holderShape, getter, pcOffset), + expandoShape_(expandoShape) +{ } + +ICGetPropCallDOMProxyNativeCompiler::ICGetPropCallDOMProxyNativeCompiler(JSContext* cx, + ICStub::Kind kind, + ICStubCompiler::Engine engine, + ICStub* firstMonitorStub, + Handle<ProxyObject*> proxy, + HandleObject holder, + HandleFunction getter, + uint32_t pcOffset) + : ICStubCompiler(cx, kind, engine), + firstMonitorStub_(firstMonitorStub), + proxy_(cx, proxy), + holder_(cx, holder), + getter_(cx, getter), + pcOffset_(pcOffset) +{ + MOZ_ASSERT(kind == ICStub::GetProp_CallDOMProxyNative || + kind == ICStub::GetProp_CallDOMProxyWithGenerationNative); + MOZ_ASSERT(proxy_->handler()->family() == GetDOMProxyHandlerFamily()); +} + +/* static */ ICGetProp_CallDOMProxyNative* +ICGetProp_CallDOMProxyNative::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, + ICGetProp_CallDOMProxyNative& other) +{ + return New<ICGetProp_CallDOMProxyNative>(cx, space, other.jitCode(), firstMonitorStub, + other.receiverGuard_.shape(), other.expandoShape_, + other.holder_, other.holderShape_, other.getter_, + other.pcOffset_); +} + +/* static */ ICGetProp_CallDOMProxyWithGenerationNative* +ICGetProp_CallDOMProxyWithGenerationNative::Clone(JSContext* cx, + ICStubSpace* space, + ICStub* firstMonitorStub, + ICGetProp_CallDOMProxyWithGenerationNative& other) +{ + return New<ICGetProp_CallDOMProxyWithGenerationNative>(cx, space, other.jitCode(), + firstMonitorStub, + other.receiverGuard_.shape(), + other.expandoAndGeneration_, + other.generation_, + other.expandoShape_, other.holder_, + other.holderShape_, other.getter_, + other.pcOffset_); +} + +ICGetProp_DOMProxyShadowed::ICGetProp_DOMProxyShadowed(JitCode* stubCode, + ICStub* firstMonitorStub, + Shape* shape, + const BaseProxyHandler* proxyHandler, + PropertyName* name, + uint32_t pcOffset) + : ICMonitoredStub(ICStub::GetProp_DOMProxyShadowed, stubCode, firstMonitorStub), + shape_(shape), + proxyHandler_(proxyHandler), + name_(name), + pcOffset_(pcOffset) +{ } + +/* static */ ICGetProp_DOMProxyShadowed* +ICGetProp_DOMProxyShadowed::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, + ICGetProp_DOMProxyShadowed& other) +{ + return New<ICGetProp_DOMProxyShadowed>(cx, space, other.jitCode(), firstMonitorStub, + other.shape_, other.proxyHandler_, other.name_, + other.pcOffset_); +} + +// +// TypeMonitor_Fallback +// + +bool +ICTypeMonitor_Fallback::addMonitorStubForValue(JSContext* cx, SharedStubInfo* info, HandleValue val) +{ + bool wasDetachedMonitorChain = lastMonitorStubPtrAddr_ == nullptr; + MOZ_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()) { + if (val.isMagic(JS_UNINITIALIZED_LEXICAL)) + return true; + MOZ_ASSERT(!val.isMagic()); + JSValueType type = val.isDouble() ? JSVAL_TYPE_DOUBLE : val.extractNonDoubleType(); + + // Check for existing TypeMonitor stub. + ICTypeMonitor_PrimitiveSet* existingStub = nullptr; + 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, info->engine(), existingStub, type); + ICStub* stub = existingStub ? compiler.updateStub() + : compiler.getStub(compiler.getStubSpace(info->outerScript(cx))); + if (!stub) { + ReportOutOfMemory(cx); + return false; + } + + JitSpew(JitSpew_BaselineIC, " %s TypeMonitor stub %p for primitive type %d", + existingStub ? "Modified existing" : "Created new", stub, type); + + if (!existingStub) { + MOZ_ASSERT(!hasStub(TypeMonitor_PrimitiveSet)); + addOptimizedMonitorStub(stub); + } + + } else if (val.toObject().isSingleton()) { + 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(info->outerScript(cx))); + if (!stub) { + ReportOutOfMemory(cx); + return false; + } + + JitSpew(JitSpew_BaselineIC, " Added TypeMonitor stub %p for singleton %p", + stub, obj.get()); + + addOptimizedMonitorStub(stub); + + } else { + RootedObjectGroup group(cx, val.toObject().group()); + + // Check for existing TypeMonitor stub. + for (ICStubConstIterator iter(firstMonitorStub()); !iter.atEnd(); iter++) { + if (iter->isTypeMonitor_ObjectGroup() && + iter->toTypeMonitor_ObjectGroup()->group() == group) + { + return true; + } + } + + ICTypeMonitor_ObjectGroup::Compiler compiler(cx, group); + ICStub* stub = compiler.getStub(compiler.getStubSpace(info->outerScript(cx))); + if (!stub) { + ReportOutOfMemory(cx); + return false; + } + + JitSpew(JitSpew_BaselineIC, " Added TypeMonitor stub %p for ObjectGroup %p", + stub, group.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). + MOZ_ASSERT(iter->toMonitoredStub()->firstMonitorStub() == this); + iter->toMonitoredStub()->updateFirstMonitorStub(firstMonitorStub_); + } + } + + return true; +} + +static bool +DoTypeMonitorFallback(JSContext* cx, void* payload, ICTypeMonitor_Fallback* stub, + HandleValue value, MutableHandleValue res) +{ + SharedStubInfo info(cx, payload, stub->icEntry()); + HandleScript script = info.innerScript(); + jsbytecode* pc = stub->icEntry()->pc(script); + TypeFallbackICSpew(cx, stub, "TypeMonitor"); + + if (value.isMagic()) { + // It's possible that we arrived here from bailing out of Ion, and that + // Ion proved that the value is dead and optimized out. In such cases, + // do nothing. However, it's also possible that we have an uninitialized + // this, in which case we should not look for other magic values. + + if (value.whyMagic() == JS_OPTIMIZED_OUT) { + MOZ_ASSERT(!stub->monitorsThis()); + res.set(value); + return true; + } + + // In derived class constructors (including nested arrows/eval), the + // |this| argument or GETALIASEDVAR can return the magic TDZ value. + MOZ_ASSERT(value.isMagic(JS_UNINITIALIZED_LEXICAL)); + MOZ_ASSERT(info.frame()->isFunctionFrame() || info.frame()->isEvalFrame()); + MOZ_ASSERT(stub->monitorsThis() || + *GetNextPc(pc) == JSOP_CHECKTHIS || + *GetNextPc(pc) == JSOP_CHECKRETURN); + } + + uint32_t argument; + if (stub->monitorsThis()) { + MOZ_ASSERT(pc == script->code()); + if (value.isMagic(JS_UNINITIALIZED_LEXICAL)) + TypeScript::SetThis(cx, script, TypeSet::UnknownType()); + else + TypeScript::SetThis(cx, script, value); + } else if (stub->monitorsArgument(&argument)) { + MOZ_ASSERT(pc == script->code()); + MOZ_ASSERT(!value.isMagic(JS_UNINITIALIZED_LEXICAL)); + TypeScript::SetArgument(cx, script, argument, value); + } else { + if (value.isMagic(JS_UNINITIALIZED_LEXICAL)) + TypeScript::Monitor(cx, script, pc, TypeSet::UnknownType()); + else + TypeScript::Monitor(cx, script, pc, value); + } + + if (!stub->invalid() && !stub->addMonitorStubForValue(cx, &info, value)) + return false; + + // Copy input value to res. + res.set(value); + return true; +} + +typedef bool (*DoTypeMonitorFallbackFn)(JSContext*, void*, ICTypeMonitor_Fallback*, + HandleValue, MutableHandleValue); +static const VMFunction DoTypeMonitorFallbackInfo = + FunctionInfo<DoTypeMonitorFallbackFn>(DoTypeMonitorFallback, "DoTypeMonitorFallback", + TailCall); + +bool +ICTypeMonitor_Fallback::Compiler::generateStubCode(MacroAssembler& masm) +{ + MOZ_ASSERT(R0 == JSReturnOperand); + + // Restore the tail call register. + EmitRestoreTailCallReg(masm); + + masm.pushValue(R0); + masm.push(ICStubReg); + pushStubPayload(masm, 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); + + if (flags_ & TypeToFlag(JSVAL_TYPE_SYMBOL)) + masm.branchTestSymbol(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); + */ + MOZ_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; +} + +static void +MaybeWorkAroundAmdBug(MacroAssembler& masm) +{ + // Attempt to work around an AMD bug (see bug 1034706 and bug 1281759), by + // inserting 32-bytes of NOPs. +#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) + if (CPUInfo::NeedAmdBugWorkaround()) { + masm.nop(9); + masm.nop(9); + masm.nop(9); + masm.nop(5); + } +#endif +} + +bool +ICTypeMonitor_SingleObject::Compiler::generateStubCode(MacroAssembler& masm) +{ + Label failure; + masm.branchTestObject(Assembler::NotEqual, R0, &failure); + MaybeWorkAroundAmdBug(masm); + + // Guard on the object's identity. + Register obj = masm.extractObject(R0, ExtractTemp0); + Address expectedObject(ICStubReg, ICTypeMonitor_SingleObject::offsetOfObject()); + masm.branchPtr(Assembler::NotEqual, expectedObject, obj, &failure); + MaybeWorkAroundAmdBug(masm); + + EmitReturnFromIC(masm); + MaybeWorkAroundAmdBug(masm); + + masm.bind(&failure); + EmitStubGuardFailure(masm); + return true; +} + +bool +ICTypeMonitor_ObjectGroup::Compiler::generateStubCode(MacroAssembler& masm) +{ + Label failure; + masm.branchTestObject(Assembler::NotEqual, R0, &failure); + MaybeWorkAroundAmdBug(masm); + + // Guard on the object's ObjectGroup. + Register obj = masm.extractObject(R0, ExtractTemp0); + masm.loadPtr(Address(obj, JSObject::offsetOfGroup()), R1.scratchReg()); + + Address expectedGroup(ICStubReg, ICTypeMonitor_ObjectGroup::offsetOfGroup()); + masm.branchPtr(Assembler::NotEqual, expectedGroup, R1.scratchReg(), &failure); + MaybeWorkAroundAmdBug(masm); + + EmitReturnFromIC(masm); + MaybeWorkAroundAmdBug(masm); + + masm.bind(&failure); + EmitStubGuardFailure(masm); + return true; +} + +bool +ICUpdatedStub::addUpdateStubForValue(JSContext* cx, HandleScript outerScript, 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; + } + + EnsureTrackPropertyTypes(cx, obj, id); + + // Make sure that undefined values are explicitly included in the property + // types for an object if generating a stub to write an undefined value. + if (val.isUndefined() && CanHaveEmptyPropertyTypesForOwnProperty(obj)) + AddTypePropertyId(cx, obj, id, val); + + if (val.isPrimitive()) { + JSValueType type = val.isDouble() ? JSVAL_TYPE_DOUBLE : val.extractNonDoubleType(); + + // Check for existing TypeUpdate stub. + ICTypeUpdate_PrimitiveSet* existingStub = nullptr; + 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(outerScript)); + if (!stub) + return false; + if (!existingStub) { + MOZ_ASSERT(!hasTypeUpdateStub(TypeUpdate_PrimitiveSet)); + addOptimizedUpdateStub(stub); + } + + JitSpew(JitSpew_BaselineIC, " %s TypeUpdate stub %p for primitive type %d", + existingStub ? "Modified existing" : "Created new", stub, type); + + } else if (val.toObject().isSingleton()) { + 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(outerScript)); + if (!stub) + return false; + + JitSpew(JitSpew_BaselineIC, " Added TypeUpdate stub %p for singleton %p", stub, obj.get()); + + addOptimizedUpdateStub(stub); + + } else { + RootedObjectGroup group(cx, val.toObject().group()); + + // Check for existing TypeUpdate stub. + for (ICStubConstIterator iter(firstUpdateStub_); !iter.atEnd(); iter++) { + if (iter->isTypeUpdate_ObjectGroup() && + iter->toTypeUpdate_ObjectGroup()->group() == group) + { + return true; + } + } + + ICTypeUpdate_ObjectGroup::Compiler compiler(cx, group); + ICStub* stub = compiler.getStub(compiler.getStubSpace(outerScript)); + if (!stub) + return false; + + JitSpew(JitSpew_BaselineIC, " Added TypeUpdate stub %p for ObjectGroup %p", + stub, group.get()); + + addOptimizedUpdateStub(stub); + } + + return true; +} + +// +// NewArray_Fallback +// + +static bool +DoNewArray(JSContext* cx, void* payload, ICNewArray_Fallback* stub, uint32_t length, + MutableHandleValue res) +{ + SharedStubInfo info(cx, payload, stub->icEntry()); + + FallbackICSpew(cx, stub, "NewArray"); + + RootedObject obj(cx); + if (stub->templateObject()) { + RootedObject templateObject(cx, stub->templateObject()); + obj = NewArrayOperationWithTemplate(cx, templateObject); + if (!obj) + return false; + } else { + HandleScript script = info.script(); + jsbytecode* pc = info.pc(); + obj = NewArrayOperation(cx, script, pc, length); + if (!obj) + return false; + + if (obj && !obj->isSingleton() && !obj->group()->maybePreliminaryObjects()) { + JSObject* templateObject = NewArrayOperation(cx, script, pc, length, TenuredObject); + if (!templateObject) + return false; + stub->setTemplateObject(templateObject); + } + } + + res.setObject(*obj); + return true; +} + +typedef bool(*DoNewArrayFn)(JSContext*, void*, ICNewArray_Fallback*, uint32_t, + MutableHandleValue); +static const VMFunction DoNewArrayInfo = + FunctionInfo<DoNewArrayFn>(DoNewArray, "DoNewArray", TailCall); + +bool +ICNewArray_Fallback::Compiler::generateStubCode(MacroAssembler& masm) +{ + EmitRestoreTailCallReg(masm); + + masm.push(R0.scratchReg()); // length + masm.push(ICStubReg); // stub. + pushStubPayload(masm, R0.scratchReg()); + + return tailCallVM(DoNewArrayInfo, masm); +} + +// +// NewObject_Fallback +// + +// Unlike typical baseline IC stubs, the code for NewObject_WithTemplate is +// specialized for the template object being allocated. +static JitCode* +GenerateNewObjectWithTemplateCode(JSContext* cx, JSObject* templateObject) +{ + JitContext jctx(cx, nullptr); + MacroAssembler masm; +#ifdef JS_CODEGEN_ARM + masm.setSecondScratchReg(BaselineSecondScratchReg); +#endif + + Label failure; + Register objReg = R0.scratchReg(); + Register tempReg = R1.scratchReg(); + masm.movePtr(ImmGCPtr(templateObject->group()), tempReg); + masm.branchTest32(Assembler::NonZero, Address(tempReg, ObjectGroup::offsetOfFlags()), + Imm32(OBJECT_FLAG_PRE_TENURE), &failure); + masm.branchPtr(Assembler::NotEqual, AbsoluteAddress(cx->compartment()->addressOfMetadataBuilder()), + ImmWord(0), &failure); + masm.createGCObject(objReg, tempReg, templateObject, gc::DefaultHeap, &failure); + masm.tagValue(JSVAL_TYPE_OBJECT, objReg, R0); + + EmitReturnFromIC(masm); + masm.bind(&failure); + EmitStubGuardFailure(masm); + + Linker linker(masm); + AutoFlushICache afc("GenerateNewObjectWithTemplateCode"); + return linker.newCode<CanGC>(cx, BASELINE_CODE); +} + +static bool +DoNewObject(JSContext* cx, void* payload, ICNewObject_Fallback* stub, MutableHandleValue res) +{ + SharedStubInfo info(cx, payload, stub->icEntry()); + + FallbackICSpew(cx, stub, "NewObject"); + + RootedObject obj(cx); + + RootedObject templateObject(cx, stub->templateObject()); + if (templateObject) { + MOZ_ASSERT(!templateObject->group()->maybePreliminaryObjects()); + obj = NewObjectOperationWithTemplate(cx, templateObject); + } else { + HandleScript script = info.script(); + jsbytecode* pc = info.pc(); + obj = NewObjectOperation(cx, script, pc); + + if (obj && !obj->isSingleton() && !obj->group()->maybePreliminaryObjects()) { + JSObject* templateObject = NewObjectOperation(cx, script, pc, TenuredObject); + if (!templateObject) + return false; + + if (!stub->invalid() && + (templateObject->is<UnboxedPlainObject>() || + !templateObject->as<PlainObject>().hasDynamicSlots())) + { + JitCode* code = GenerateNewObjectWithTemplateCode(cx, templateObject); + if (!code) + return false; + + ICStubSpace* space = + ICStubCompiler::StubSpaceForKind(ICStub::NewObject_WithTemplate, script, + ICStubCompiler::Engine::Baseline); + ICStub* templateStub = ICStub::New<ICNewObject_WithTemplate>(cx, space, code); + if (!templateStub) + return false; + + stub->addNewStub(templateStub); + } + + stub->setTemplateObject(templateObject); + } + } + + if (!obj) + return false; + + res.setObject(*obj); + return true; +} + +typedef bool(*DoNewObjectFn)(JSContext*, void*, ICNewObject_Fallback*, MutableHandleValue); +static const VMFunction DoNewObjectInfo = + FunctionInfo<DoNewObjectFn>(DoNewObject, "DoNewObject", TailCall); + +bool +ICNewObject_Fallback::Compiler::generateStubCode(MacroAssembler& masm) +{ + EmitRestoreTailCallReg(masm); + + masm.push(ICStubReg); // stub. + pushStubPayload(masm, R0.scratchReg()); + + return tailCallVM(DoNewObjectInfo, masm); +} + +} // namespace jit +} // namespace js |