summaryrefslogtreecommitdiffstats
path: root/js/src/jit/IonCaches.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit/IonCaches.cpp')
-rw-r--r--js/src/jit/IonCaches.cpp5072
1 files changed, 5072 insertions, 0 deletions
diff --git a/js/src/jit/IonCaches.cpp b/js/src/jit/IonCaches.cpp
new file mode 100644
index 000000000..96e488ea8
--- /dev/null
+++ b/js/src/jit/IonCaches.cpp
@@ -0,0 +1,5072 @@
+/* -*- 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/IonCaches.h"
+
+#include "mozilla/SizePrintfMacros.h"
+#include "mozilla/TemplateLib.h"
+
+#include "jstypes.h"
+
+#include "builtin/TypedObject.h"
+#include "jit/BaselineIC.h"
+#include "jit/Ion.h"
+#include "jit/JitcodeMap.h"
+#include "jit/JitSpewer.h"
+#include "jit/Linker.h"
+#include "jit/Lowering.h"
+#ifdef JS_ION_PERF
+# include "jit/PerfSpewer.h"
+#endif
+#include "jit/VMFunctions.h"
+#include "js/Proxy.h"
+#include "vm/Shape.h"
+#include "vm/Stack.h"
+
+#include "jit/JitFrames-inl.h"
+#include "jit/MacroAssembler-inl.h"
+#include "jit/shared/Lowering-shared-inl.h"
+#include "vm/Interpreter-inl.h"
+#include "vm/Shape-inl.h"
+
+using namespace js;
+using namespace js::jit;
+
+using mozilla::tl::FloorLog2;
+
+typedef Rooted<TypedArrayObject*> RootedTypedArrayObject;
+
+void
+CodeLocationJump::repoint(JitCode* code, MacroAssembler* masm)
+{
+ MOZ_ASSERT(state_ == Relative);
+ size_t new_off = (size_t)raw_;
+#ifdef JS_SMALL_BRANCH
+ size_t jumpTableEntryOffset = reinterpret_cast<size_t>(jumpTableEntry_);
+#endif
+ if (masm != nullptr) {
+#ifdef JS_CODEGEN_X64
+ MOZ_ASSERT((uint64_t)raw_ <= UINT32_MAX);
+#endif
+ new_off = (uintptr_t)raw_;
+#ifdef JS_SMALL_BRANCH
+ jumpTableEntryOffset = masm->actualIndex(jumpTableEntryOffset);
+#endif
+ }
+ raw_ = code->raw() + new_off;
+#ifdef JS_SMALL_BRANCH
+ jumpTableEntry_ = Assembler::PatchableJumpAddress(code, (size_t) jumpTableEntryOffset);
+#endif
+ setAbsolute();
+}
+
+void
+CodeLocationLabel::repoint(JitCode* code, MacroAssembler* masm)
+{
+ MOZ_ASSERT(state_ == Relative);
+ size_t new_off = (size_t)raw_;
+ if (masm != nullptr) {
+#ifdef JS_CODEGEN_X64
+ MOZ_ASSERT((uint64_t)raw_ <= UINT32_MAX);
+#endif
+ new_off = (uintptr_t)raw_;
+ }
+ MOZ_ASSERT(new_off < code->instructionsSize());
+
+ raw_ = code->raw() + new_off;
+ setAbsolute();
+}
+
+void
+CodeOffsetJump::fixup(MacroAssembler* masm)
+{
+#ifdef JS_SMALL_BRANCH
+ jumpTableIndex_ = masm->actualIndex(jumpTableIndex_);
+#endif
+}
+
+const char*
+IonCache::CacheName(IonCache::Kind kind)
+{
+ static const char * const names[] =
+ {
+#define NAME(x) #x,
+ IONCACHE_KIND_LIST(NAME)
+#undef NAME
+ };
+ return names[kind];
+}
+
+const size_t IonCache::MAX_STUBS = 16;
+
+// Helper class which encapsulates logic to attach a stub to an IC by hooking
+// up rejoins and next stub jumps.
+//
+// The simplest stubs have a single jump to the next stub and look like the
+// following:
+//
+// branch guard NEXTSTUB
+// ... IC-specific code ...
+// jump REJOIN
+//
+// This corresponds to:
+//
+// attacher.branchNextStub(masm, ...);
+// ... emit IC-specific code ...
+// attacher.jumpRejoin(masm);
+//
+// Whether the stub needs multiple next stub jumps look like:
+//
+// branch guard FAILURES
+// ... IC-specific code ...
+// branch another-guard FAILURES
+// ... IC-specific code ...
+// jump REJOIN
+// FAILURES:
+// jump NEXTSTUB
+//
+// This corresponds to:
+//
+// Label failures;
+// masm.branchX(..., &failures);
+// ... emit IC-specific code ...
+// masm.branchY(..., failures);
+// ... emit more IC-specific code ...
+// attacher.jumpRejoin(masm);
+// masm.bind(&failures);
+// attacher.jumpNextStub(masm);
+//
+// A convenience function |branchNextStubOrLabel| is provided in the case that
+// the stub sometimes has multiple next stub jumps and sometimes a single
+// one. If a non-nullptr label is passed in, a |branchPtr| will be made to
+// that label instead of a |branchPtrWithPatch| to the next stub.
+class IonCache::StubAttacher
+{
+ protected:
+ bool hasNextStubOffset_ : 1;
+ bool hasStubCodePatchOffset_ : 1;
+
+ IonCache& cache_;
+
+ CodeLocationLabel rejoinLabel_;
+ CodeOffsetJump nextStubOffset_;
+ CodeOffsetJump rejoinOffset_;
+ CodeOffset stubCodePatchOffset_;
+
+ public:
+ explicit StubAttacher(IonCache& cache)
+ : hasNextStubOffset_(false),
+ hasStubCodePatchOffset_(false),
+ cache_(cache),
+ rejoinLabel_(cache.rejoinLabel_),
+ nextStubOffset_(),
+ rejoinOffset_(),
+ stubCodePatchOffset_()
+ { }
+
+ // Value used instead of the JitCode self-reference of generated
+ // stubs. This value is needed for marking calls made inside stubs. This
+ // value would be replaced by the attachStub function after the allocation
+ // of the JitCode. The self-reference is used to keep the stub path alive
+ // even if the IonScript is invalidated or if the IC is flushed.
+ static const void* const STUB_ADDR;
+
+ template <class T1, class T2>
+ void branchNextStub(MacroAssembler& masm, Assembler::Condition cond, T1 op1, T2 op2) {
+ MOZ_ASSERT(!hasNextStubOffset_);
+ RepatchLabel nextStub;
+ nextStubOffset_ = masm.branchPtrWithPatch(cond, op1, op2, &nextStub);
+ hasNextStubOffset_ = true;
+ masm.bind(&nextStub);
+ }
+
+ template <class T1, class T2>
+ void branchNextStubOrLabel(MacroAssembler& masm, Assembler::Condition cond, T1 op1, T2 op2,
+ Label* label)
+ {
+ if (label != nullptr)
+ masm.branchPtr(cond, op1, op2, label);
+ else
+ branchNextStub(masm, cond, op1, op2);
+ }
+
+ void jumpRejoin(MacroAssembler& masm) {
+ RepatchLabel rejoin;
+ rejoinOffset_ = masm.jumpWithPatch(&rejoin);
+ masm.bind(&rejoin);
+ }
+
+ void jumpNextStub(MacroAssembler& masm) {
+ MOZ_ASSERT(!hasNextStubOffset_);
+ RepatchLabel nextStub;
+ nextStubOffset_ = masm.jumpWithPatch(&nextStub);
+ hasNextStubOffset_ = true;
+ masm.bind(&nextStub);
+ }
+
+ void pushStubCodePointer(MacroAssembler& masm) {
+ // Push the JitCode pointer for the stub we're generating.
+ // WARNING:
+ // WARNING: If JitCode ever becomes relocatable, the following code is incorrect.
+ // WARNING: Note that we're not marking the pointer being pushed as an ImmGCPtr.
+ // WARNING: This location will be patched with the pointer of the generated stub,
+ // WARNING: such as it can be marked when a call is made with this stub. Be aware
+ // WARNING: that ICs are not marked and so this stub will only be kept alive iff
+ // WARNING: it is on the stack at the time of the GC. No ImmGCPtr is needed as the
+ // WARNING: stubs are flushed on GC.
+ // WARNING:
+ MOZ_ASSERT(!hasStubCodePatchOffset_);
+ stubCodePatchOffset_ = masm.PushWithPatch(ImmPtr(STUB_ADDR));
+ hasStubCodePatchOffset_ = true;
+ }
+
+ void patchRejoinJump(MacroAssembler& masm, JitCode* code) {
+ rejoinOffset_.fixup(&masm);
+ CodeLocationJump rejoinJump(code, rejoinOffset_);
+ PatchJump(rejoinJump, rejoinLabel_);
+ }
+
+ void patchStubCodePointer(JitCode* code) {
+ if (hasStubCodePatchOffset_) {
+ Assembler::PatchDataWithValueCheck(CodeLocationLabel(code, stubCodePatchOffset_),
+ ImmPtr(code), ImmPtr(STUB_ADDR));
+ }
+ }
+
+ void patchNextStubJump(MacroAssembler& masm, JitCode* code) {
+ // If this path is not taken, we are producing an entry which can no
+ // longer go back into the update function.
+ if (hasNextStubOffset_) {
+ nextStubOffset_.fixup(&masm);
+ CodeLocationJump nextStubJump(code, nextStubOffset_);
+ PatchJump(nextStubJump, cache_.fallbackLabel_);
+
+ // When the last stub fails, it fallback to the ool call which can
+ // produce a stub. Next time we generate a stub, we will patch the
+ // nextStub jump to try the new stub.
+ cache_.lastJump_ = nextStubJump;
+ }
+ }
+};
+
+const void* const IonCache::StubAttacher::STUB_ADDR = (void*)0xdeadc0de;
+
+void
+IonCache::emitInitialJump(MacroAssembler& masm, RepatchLabel& entry)
+{
+ initialJump_ = masm.jumpWithPatch(&entry);
+ lastJump_ = initialJump_;
+ Label label;
+ masm.bind(&label);
+ rejoinLabel_ = CodeOffset(label.offset());
+}
+
+void
+IonCache::attachStub(MacroAssembler& masm, StubAttacher& attacher, CodeLocationJump lastJump,
+ Handle<JitCode*> code)
+{
+ MOZ_ASSERT(canAttachStub());
+ incrementStubCount();
+
+ // Patch the previous nextStubJump of the last stub, or the jump from the
+ // codeGen, to jump into the newly allocated code.
+ PatchJump(lastJump, CodeLocationLabel(code), Reprotect);
+}
+
+IonCache::LinkStatus
+IonCache::linkCode(JSContext* cx, MacroAssembler& masm, StubAttacher& attacher, IonScript* ion,
+ JitCode** code)
+{
+ Linker linker(masm);
+ *code = linker.newCode<CanGC>(cx, ION_CODE);
+ if (!*code)
+ return LINK_ERROR;
+
+ if (ion->invalidated())
+ return CACHE_FLUSHED;
+
+ // Update the success path to continue after the IC initial jump.
+ attacher.patchRejoinJump(masm, *code);
+
+ // Replace the STUB_ADDR constant by the address of the generated stub, such
+ // as it can be kept alive even if the cache is flushed (see
+ // MarkJitExitFrame).
+ attacher.patchStubCodePointer(*code);
+
+ // Update the failure path.
+ attacher.patchNextStubJump(masm, *code);
+
+ return LINK_GOOD;
+}
+
+bool
+IonCache::linkAndAttachStub(JSContext* cx, MacroAssembler& masm, StubAttacher& attacher,
+ IonScript* ion, const char* attachKind,
+ JS::TrackedOutcome trackedOutcome)
+{
+ CodeLocationJump lastJumpBefore = lastJump_;
+ Rooted<JitCode*> code(cx);
+ {
+ // Need to exit the AutoFlushICache context to flush the cache
+ // before attaching the stub below.
+ AutoFlushICache afc("IonCache");
+ LinkStatus status = linkCode(cx, masm, attacher, ion, code.address());
+ if (status != LINK_GOOD)
+ return status != LINK_ERROR;
+ }
+
+ if (pc_) {
+ JitSpew(JitSpew_IonIC, "Cache %p(%s:%" PRIuSIZE "/%" PRIuSIZE ") generated %s %s stub at %p",
+ this, script_->filename(), script_->lineno(), script_->pcToOffset(pc_),
+ attachKind, CacheName(kind()), code->raw());
+ } else {
+ JitSpew(JitSpew_IonIC, "Cache %p generated %s %s stub at %p",
+ this, attachKind, CacheName(kind()), code->raw());
+ }
+
+#ifdef JS_ION_PERF
+ writePerfSpewerJitCodeProfile(code, "IonCache");
+#endif
+
+ attachStub(masm, attacher, lastJumpBefore, code);
+
+ // Add entry to native => bytecode mapping for this stub if needed.
+ if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) {
+ JitcodeGlobalEntry::IonCacheEntry entry;
+ entry.init(code, code->raw(), code->rawEnd(), rejoinAddress(), trackedOutcome);
+
+ // Add entry to the global table.
+ JitcodeGlobalTable* globalTable = cx->runtime()->jitRuntime()->getJitcodeGlobalTable();
+ if (!globalTable->addEntry(entry, cx->runtime())) {
+ entry.destroy();
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ // Mark the jitcode as having a bytecode map.
+ code->setHasBytecodeMap();
+ } else {
+ JitcodeGlobalEntry::DummyEntry entry;
+ entry.init(code, code->raw(), code->rawEnd());
+
+ // Add entry to the global table.
+ JitcodeGlobalTable* globalTable = cx->runtime()->jitRuntime()->getJitcodeGlobalTable();
+ if (!globalTable->addEntry(entry, cx->runtime())) {
+ entry.destroy();
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ // Mark the jitcode as having a bytecode map.
+ code->setHasBytecodeMap();
+ }
+
+ // Report masm OOM errors here, so all our callers can:
+ // return linkAndAttachStub(...);
+ if (masm.oom()) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ return true;
+}
+
+void
+IonCache::updateBaseAddress(JitCode* code, MacroAssembler& masm)
+{
+ fallbackLabel_.repoint(code, &masm);
+ initialJump_.repoint(code, &masm);
+ lastJump_.repoint(code, &masm);
+ rejoinLabel_.repoint(code, &masm);
+}
+
+void IonCache::trace(JSTracer* trc)
+{
+ if (script_)
+ TraceManuallyBarrieredEdge(trc, &script_, "IonCache::script_");
+}
+
+static void*
+GetReturnAddressToIonCode(JSContext* cx)
+{
+ JitFrameIterator iter(cx);
+ MOZ_ASSERT(iter.type() == JitFrame_Exit,
+ "An exit frame is expected as update functions are called with a VMFunction.");
+
+ void* returnAddr = iter.returnAddress();
+#ifdef DEBUG
+ ++iter;
+ MOZ_ASSERT(iter.isIonJS());
+#endif
+ return returnAddr;
+}
+
+static void
+GeneratePrototypeGuards(JSContext* cx, IonScript* ion, MacroAssembler& masm, JSObject* obj,
+ JSObject* holder, Register objectReg, Register scratchReg,
+ Label* failures)
+{
+ /*
+ * The guards here protect against the effects of JSObject::swap(). If the prototype chain
+ * is directly altered, then TI will toss the jitcode, so we don't have to worry about
+ * it, and any other change to the holder, or adding a shadowing property will result
+ * in reshaping the holder, and thus the failure of the shape guard.
+ */
+ MOZ_ASSERT(obj != holder);
+
+ if (obj->hasUncacheableProto()) {
+ // Note: objectReg and scratchReg may be the same register, so we cannot
+ // use objectReg in the rest of this function.
+ masm.loadPtr(Address(objectReg, JSObject::offsetOfGroup()), scratchReg);
+ Address proto(scratchReg, ObjectGroup::offsetOfProto());
+ masm.branchPtr(Assembler::NotEqual, proto, ImmGCPtr(obj->staticPrototype()), failures);
+ }
+
+ JSObject* pobj = obj->staticPrototype();
+ if (!pobj)
+ return;
+ while (pobj != holder) {
+ if (pobj->hasUncacheableProto()) {
+ masm.movePtr(ImmGCPtr(pobj), scratchReg);
+ Address groupAddr(scratchReg, JSObject::offsetOfGroup());
+ if (pobj->isSingleton()) {
+ // Singletons can have their group's |proto| mutated directly.
+ masm.loadPtr(groupAddr, scratchReg);
+ Address protoAddr(scratchReg, ObjectGroup::offsetOfProto());
+ masm.branchPtr(Assembler::NotEqual, protoAddr, ImmGCPtr(pobj->staticPrototype()),
+ failures);
+ } else {
+ masm.branchPtr(Assembler::NotEqual, groupAddr, ImmGCPtr(pobj->group()), failures);
+ }
+ }
+
+ pobj = pobj->staticPrototype();
+ }
+}
+
+// Note: This differs from IsCacheableProtoChain in BaselineIC.cpp in that
+// Ion caches can deal with objects on the proto chain that have uncacheable
+// prototypes.
+bool
+jit::IsCacheableProtoChainForIonOrCacheIR(JSObject* obj, JSObject* holder)
+{
+ while (obj != holder) {
+ /*
+ * We cannot assume that we find the holder object on the prototype
+ * chain and must check for null proto. The prototype chain can be
+ * altered during the lookupProperty call.
+ */
+ JSObject* proto = obj->staticPrototype();
+ if (!proto || !proto->isNative())
+ return false;
+ obj = proto;
+ }
+ return true;
+}
+
+bool
+jit::IsCacheableGetPropReadSlotForIonOrCacheIR(JSObject* obj, JSObject* holder, Shape* shape)
+{
+ if (!shape || !IsCacheableProtoChainForIonOrCacheIR(obj, holder))
+ return false;
+
+ if (!shape->hasSlot() || !shape->hasDefaultGetter())
+ return false;
+
+ return true;
+}
+
+static bool
+IsCacheableNoProperty(JSObject* obj, JSObject* holder, Shape* shape, jsbytecode* pc,
+ const TypedOrValueRegister& output)
+{
+ if (shape)
+ return false;
+
+ MOZ_ASSERT(!holder);
+
+ // Just because we didn't find the property on the object doesn't mean it
+ // won't magically appear through various engine hacks.
+ if (obj->getClass()->getGetProperty())
+ return false;
+
+ // Don't generate missing property ICs if we skipped a non-native object, as
+ // lookups may extend beyond the prototype chain (e.g. for DOMProxy
+ // proxies).
+ JSObject* obj2 = obj;
+ while (obj2) {
+ if (!obj2->isNative())
+ return false;
+ obj2 = obj2->staticPrototype();
+ }
+
+ // The pc is nullptr if the cache is idempotent. We cannot share missing
+ // properties between caches because TI can only try to prove that a type is
+ // contained, but does not attempts to check if something does not exists.
+ // So the infered type of getprop would be missing and would not contain
+ // undefined, as expected for missing properties.
+ if (!pc)
+ return false;
+
+ // TI has not yet monitored an Undefined value. The fallback path will
+ // monitor and invalidate the script.
+ if (!output.hasValue())
+ return false;
+
+ return true;
+}
+
+static bool
+IsOptimizableArgumentsObjectForLength(JSObject* obj)
+{
+ if (!obj->is<ArgumentsObject>())
+ return false;
+
+ if (obj->as<ArgumentsObject>().hasOverriddenLength())
+ return false;
+
+ return true;
+}
+
+static bool
+IsOptimizableArgumentsObjectForGetElem(JSObject* obj, const Value& idval)
+{
+ if (!IsOptimizableArgumentsObjectForLength(obj))
+ return false;
+
+ ArgumentsObject& argsObj = obj->as<ArgumentsObject>();
+
+ if (argsObj.isAnyElementDeleted())
+ return false;
+
+ if (argsObj.hasOverriddenElement())
+ return false;
+
+ if (!idval.isInt32())
+ return false;
+
+ int32_t idint = idval.toInt32();
+ if (idint < 0 || static_cast<uint32_t>(idint) >= argsObj.initialLength())
+ return false;
+
+ return true;
+}
+
+static bool
+IsCacheableGetPropCallNative(JSObject* obj, JSObject* holder, Shape* shape)
+{
+ if (!shape || !IsCacheableProtoChainForIonOrCacheIR(obj, holder))
+ return false;
+
+ if (!shape->hasGetterValue() || !shape->getterValue().isObject())
+ return false;
+
+ if (!shape->getterValue().toObject().is<JSFunction>())
+ return false;
+
+ JSFunction& getter = shape->getterValue().toObject().as<JSFunction>();
+ if (!getter.isNative())
+ return false;
+
+ // Check for a getter that has jitinfo and whose jitinfo says it's
+ // OK with both inner and outer objects.
+ if (getter.jitInfo() && !getter.jitInfo()->needsOuterizedThisObject())
+ return true;
+
+ // For getters that need the WindowProxy (instead of the Window) as this
+ // object, don't cache if obj is the Window, since our cache will pass that
+ // instead of the WindowProxy.
+ return !IsWindow(obj);
+}
+
+static bool
+IsCacheableGetPropCallScripted(JSObject* obj, JSObject* holder, Shape* shape)
+{
+ if (!shape || !IsCacheableProtoChainForIonOrCacheIR(obj, holder))
+ return false;
+
+ if (!shape->hasGetterValue() || !shape->getterValue().isObject())
+ return false;
+
+ if (!shape->getterValue().toObject().is<JSFunction>())
+ return false;
+
+ JSFunction& getter = shape->getterValue().toObject().as<JSFunction>();
+ if (!getter.hasJITCode())
+ return false;
+
+ // See IsCacheableGetPropCallNative.
+ return !IsWindow(obj);
+}
+
+static bool
+IsCacheableGetPropCallPropertyOp(JSObject* obj, JSObject* holder, Shape* shape)
+{
+ if (!shape || !IsCacheableProtoChainForIonOrCacheIR(obj, holder))
+ return false;
+
+ if (shape->hasSlot() || shape->hasGetterValue() || shape->hasDefaultGetter())
+ return false;
+
+ return true;
+}
+
+static void
+TestMatchingReceiver(MacroAssembler& masm, IonCache::StubAttacher& attacher,
+ Register object, JSObject* obj, Label* failure,
+ bool alwaysCheckGroup = false)
+{
+ if (obj->is<UnboxedPlainObject>()) {
+ MOZ_ASSERT(failure);
+
+ masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), failure);
+ Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando());
+ if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
+ masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failure);
+ Label success;
+ masm.push(object);
+ masm.loadPtr(expandoAddress, object);
+ masm.branchTestObjShape(Assembler::Equal, object, expando->lastProperty(),
+ &success);
+ masm.pop(object);
+ masm.jump(failure);
+ masm.bind(&success);
+ masm.pop(object);
+ } else {
+ masm.branchPtr(Assembler::NotEqual, expandoAddress, ImmWord(0), failure);
+ }
+ } else if (obj->is<UnboxedArrayObject>()) {
+ MOZ_ASSERT(failure);
+ masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), failure);
+ } else if (obj->is<TypedObject>()) {
+ attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
+ Address(object, JSObject::offsetOfGroup()),
+ ImmGCPtr(obj->group()), failure);
+ } else {
+ Shape* shape = obj->maybeShape();
+ MOZ_ASSERT(shape);
+
+ attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
+ Address(object, ShapedObject::offsetOfShape()),
+ ImmGCPtr(shape), failure);
+
+ if (alwaysCheckGroup)
+ masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), failure);
+ }
+}
+
+static inline void
+EmitLoadSlot(MacroAssembler& masm, NativeObject* holder, Shape* shape, Register holderReg,
+ TypedOrValueRegister output, Register scratchReg)
+{
+ MOZ_ASSERT(holder);
+ NativeObject::slotsSizeMustNotOverflow();
+ if (holder->isFixedSlot(shape->slot())) {
+ Address addr(holderReg, NativeObject::getFixedSlotOffset(shape->slot()));
+ masm.loadTypedOrValue(addr, output);
+ } else {
+ masm.loadPtr(Address(holderReg, NativeObject::offsetOfSlots()), scratchReg);
+
+ Address addr(scratchReg, holder->dynamicSlotIndex(shape->slot()) * sizeof(Value));
+ masm.loadTypedOrValue(addr, output);
+ }
+}
+
+// Callers are expected to have already guarded on the shape of the
+// object, which guarantees the object is a DOM proxy.
+static void
+CheckDOMProxyExpandoDoesNotShadow(JSContext* cx, MacroAssembler& masm, JSObject* obj,
+ jsid id, Register object, Label* stubFailure)
+{
+ MOZ_ASSERT(IsCacheableDOMProxy(obj));
+
+ // 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 unvaoidable.
+ AllocatableRegisterSet domProxyRegSet(RegisterSet::All());
+ domProxyRegSet.take(AnyRegister(object));
+ ValueOperand tempVal = domProxyRegSet.takeAnyValue();
+ masm.pushValue(tempVal);
+
+ Label failDOMProxyCheck;
+ Label domProxyOk;
+
+ Value expandoVal = GetProxyExtra(obj, GetDOMProxyExpandoSlot());
+
+ masm.loadPtr(Address(object, ProxyObject::offsetOfValues()), tempVal.scratchReg());
+ masm.loadValue(Address(tempVal.scratchReg(),
+ ProxyObject::offsetOfExtraSlotInValues(GetDOMProxyExpandoSlot())),
+ tempVal);
+
+ if (!expandoVal.isObject() && !expandoVal.isUndefined()) {
+ masm.branchTestValue(Assembler::NotEqual, tempVal, expandoVal, &failDOMProxyCheck);
+
+ ExpandoAndGeneration* expandoAndGeneration = (ExpandoAndGeneration*)expandoVal.toPrivate();
+ masm.movePtr(ImmPtr(expandoAndGeneration), tempVal.scratchReg());
+
+ masm.branch64(Assembler::NotEqual,
+ Address(tempVal.scratchReg(),
+ ExpandoAndGeneration::offsetOfGeneration()),
+ Imm64(expandoAndGeneration->generation),
+ &failDOMProxyCheck);
+
+ expandoVal = expandoAndGeneration->expando;
+ masm.loadValue(Address(tempVal.scratchReg(),
+ ExpandoAndGeneration::offsetOfExpando()),
+ 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);
+
+ if (expandoVal.isObject()) {
+ MOZ_ASSERT(!expandoVal.toObject().as<NativeObject>().contains(cx, id));
+
+ // Reference object has an expando object that doesn't define the name. Check that
+ // the incoming object has an expando object with the same shape.
+ masm.branchTestObject(Assembler::NotEqual, tempVal, &failDOMProxyCheck);
+ masm.extractObject(tempVal, tempVal.scratchReg());
+ masm.branchPtr(Assembler::Equal,
+ Address(tempVal.scratchReg(), ShapedObject::offsetOfShape()),
+ ImmGCPtr(expandoVal.toObject().as<NativeObject>().lastProperty()),
+ &domProxyOk);
+ }
+
+ // Failure case: restore the tempVal registers and jump to failures.
+ masm.bind(&failDOMProxyCheck);
+ masm.popValue(tempVal);
+ masm.jump(stubFailure);
+
+ // Success case: restore the tempval and proceed.
+ masm.bind(&domProxyOk);
+ masm.popValue(tempVal);
+}
+
+static void
+GenerateReadSlot(JSContext* cx, IonScript* ion, MacroAssembler& masm,
+ IonCache::StubAttacher& attacher, MaybeCheckTDZ checkTDZ,
+ JSObject* obj, JSObject* holder, Shape* shape, Register object,
+ TypedOrValueRegister output, Label* failures = nullptr)
+{
+ // If there's a single jump to |failures|, we can patch the shape guard
+ // jump directly. Otherwise, jump to the end of the stub, so there's a
+ // common point to patch.
+ bool multipleFailureJumps = (obj != holder)
+ || obj->is<UnboxedPlainObject>()
+ || (checkTDZ && output.hasValue())
+ || (failures != nullptr && failures->used());
+
+ // If we have multiple failure jumps but didn't get a label from the
+ // outside, make one ourselves.
+ Label failures_;
+ if (multipleFailureJumps && !failures)
+ failures = &failures_;
+
+ TestMatchingReceiver(masm, attacher, object, obj, failures);
+
+ // If we need a scratch register, use either an output register or the
+ // object register. After this point, we cannot jump directly to
+ // |failures| since we may still have to pop the object register.
+ bool restoreScratch = false;
+ Register scratchReg = Register::FromCode(0); // Quell compiler warning.
+
+ if (obj != holder ||
+ obj->is<UnboxedPlainObject>() ||
+ !holder->as<NativeObject>().isFixedSlot(shape->slot()))
+ {
+ if (output.hasValue()) {
+ scratchReg = output.valueReg().scratchReg();
+ } else if (output.type() == MIRType::Double) {
+ scratchReg = object;
+ masm.push(scratchReg);
+ restoreScratch = true;
+ } else {
+ scratchReg = output.typedReg().gpr();
+ }
+ }
+
+ // Fast path: single failure jump, no prototype guards.
+ if (!multipleFailureJumps) {
+ EmitLoadSlot(masm, &holder->as<NativeObject>(), shape, object, output, scratchReg);
+ if (restoreScratch)
+ masm.pop(scratchReg);
+ attacher.jumpRejoin(masm);
+ return;
+ }
+
+ // Slow path: multiple jumps; generate prototype guards.
+ Label prototypeFailures;
+ Register holderReg;
+ if (obj != holder) {
+ // Note: this may clobber the object register if it's used as scratch.
+ GeneratePrototypeGuards(cx, ion, masm, obj, holder, object, scratchReg,
+ &prototypeFailures);
+
+ if (holder) {
+ // Guard on the holder's shape.
+ holderReg = scratchReg;
+ masm.movePtr(ImmGCPtr(holder), holderReg);
+ masm.branchPtr(Assembler::NotEqual,
+ Address(holderReg, ShapedObject::offsetOfShape()),
+ ImmGCPtr(holder->as<NativeObject>().lastProperty()),
+ &prototypeFailures);
+ } else {
+ // The property does not exist. Guard on everything in the
+ // prototype chain.
+ JSObject* proto = obj->staticPrototype();
+ Register lastReg = object;
+ MOZ_ASSERT(scratchReg != object);
+ while (proto) {
+ masm.loadObjProto(lastReg, scratchReg);
+
+ // Guard the shape of the current prototype.
+ MOZ_ASSERT(proto->hasStaticPrototype());
+ masm.branchPtr(Assembler::NotEqual,
+ Address(scratchReg, ShapedObject::offsetOfShape()),
+ ImmGCPtr(proto->as<NativeObject>().lastProperty()),
+ &prototypeFailures);
+
+ proto = proto->staticPrototype();
+ lastReg = scratchReg;
+ }
+
+ holderReg = InvalidReg;
+ }
+ } else if (obj->is<UnboxedPlainObject>()) {
+ holder = obj->as<UnboxedPlainObject>().maybeExpando();
+ holderReg = scratchReg;
+ masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), holderReg);
+ } else {
+ holderReg = object;
+ }
+
+ // Slot access.
+ if (holder) {
+ EmitLoadSlot(masm, &holder->as<NativeObject>(), shape, holderReg, output, scratchReg);
+ if (checkTDZ && output.hasValue())
+ masm.branchTestMagic(Assembler::Equal, output.valueReg(), failures);
+ } else {
+ masm.moveValue(UndefinedValue(), output.valueReg());
+ }
+
+ // Restore scratch on success.
+ if (restoreScratch)
+ masm.pop(scratchReg);
+
+ attacher.jumpRejoin(masm);
+
+ masm.bind(&prototypeFailures);
+ if (restoreScratch)
+ masm.pop(scratchReg);
+ masm.bind(failures);
+
+ attacher.jumpNextStub(masm);
+}
+
+static void
+GenerateReadUnboxed(JSContext* cx, IonScript* ion, MacroAssembler& masm,
+ IonCache::StubAttacher& attacher, JSObject* obj,
+ const UnboxedLayout::Property* property,
+ Register object, TypedOrValueRegister output,
+ Label* failures = nullptr)
+{
+ // Guard on the group of the object.
+ attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
+ Address(object, JSObject::offsetOfGroup()),
+ ImmGCPtr(obj->group()), failures);
+
+ Address address(object, UnboxedPlainObject::offsetOfData() + property->offset);
+
+ masm.loadUnboxedProperty(address, property->type, output);
+
+ attacher.jumpRejoin(masm);
+
+ if (failures) {
+ masm.bind(failures);
+ attacher.jumpNextStub(masm);
+ }
+}
+
+static bool
+EmitGetterCall(JSContext* cx, MacroAssembler& masm,
+ IonCache::StubAttacher& attacher, JSObject* obj,
+ JSObject* holder, HandleShape shape, bool holderIsReceiver,
+ LiveRegisterSet liveRegs, Register object,
+ TypedOrValueRegister output,
+ void* returnAddr)
+{
+ MOZ_ASSERT(output.hasValue());
+ MacroAssembler::AfterICSaveLive aic = masm.icSaveLive(liveRegs);
+
+ MOZ_ASSERT_IF(obj != holder, !holderIsReceiver);
+
+ // Remaining registers should basically be free, but we need to use |object| still
+ // so leave it alone.
+ AllocatableRegisterSet regSet(RegisterSet::All());
+ regSet.take(AnyRegister(object));
+
+ // This is a slower stub path, and we're going to be doing a call anyway. Don't need
+ // to try so hard to not use the stack. Scratch regs are just taken from the register
+ // set not including the input, current value saved on the stack, and restored when
+ // we're done with it.
+ Register scratchReg = regSet.takeAnyGeneral();
+
+ // Shape has a JSNative, PropertyOp or scripted getter function.
+ if (IsCacheableGetPropCallNative(obj, holder, shape)) {
+ Register argJSContextReg = regSet.takeAnyGeneral();
+ Register argUintNReg = regSet.takeAnyGeneral();
+ Register argVpReg = regSet.takeAnyGeneral();
+
+ JSFunction* target = &shape->getterValue().toObject().as<JSFunction>();
+ MOZ_ASSERT(target);
+ MOZ_ASSERT(target->isNative());
+
+ // Native functions have the signature:
+ // bool (*)(JSContext*, unsigned, Value* vp)
+ // Where vp[0] is space for an outparam, vp[1] is |this|, and vp[2] onward
+ // are the function arguments.
+
+ // Construct vp array:
+ // Push object value for |this|
+ masm.Push(TypedOrValueRegister(MIRType::Object, AnyRegister(object)));
+ // Push callee/outparam.
+ masm.Push(ObjectValue(*target));
+
+ // Preload arguments into registers.
+ masm.loadJSContext(argJSContextReg);
+ masm.move32(Imm32(0), argUintNReg);
+ masm.moveStackPtrTo(argVpReg);
+
+ // Push marking data for later use.
+ masm.Push(argUintNReg);
+ attacher.pushStubCodePointer(masm);
+
+ if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic))
+ return false;
+ masm.enterFakeExitFrame(IonOOLNativeExitFrameLayoutToken);
+
+ // Construct and execute call.
+ masm.setupUnalignedABICall(scratchReg);
+ masm.passABIArg(argJSContextReg);
+ masm.passABIArg(argUintNReg);
+ masm.passABIArg(argVpReg);
+ masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, target->native()));
+
+ // Test for failure.
+ masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
+
+ // Load the outparam vp[0] into output register(s).
+ Address outparam(masm.getStackPointer(), IonOOLNativeExitFrameLayout::offsetOfResult());
+ masm.loadTypedOrValue(outparam, output);
+
+ // masm.leaveExitFrame & pop locals
+ masm.adjustStack(IonOOLNativeExitFrameLayout::Size(0));
+ } else if (IsCacheableGetPropCallPropertyOp(obj, holder, shape)) {
+ Register argJSContextReg = regSet.takeAnyGeneral();
+ Register argObjReg = regSet.takeAnyGeneral();
+ Register argIdReg = regSet.takeAnyGeneral();
+ Register argVpReg = regSet.takeAnyGeneral();
+
+ GetterOp target = shape->getterOp();
+ MOZ_ASSERT(target);
+
+ // Push stubCode for marking.
+ attacher.pushStubCodePointer(masm);
+
+ // JSGetterOp: bool fn(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp)
+
+ // Push args on stack first so we can take pointers to make handles.
+ masm.Push(UndefinedValue());
+ masm.moveStackPtrTo(argVpReg);
+
+ // Push canonical jsid from shape instead of propertyname.
+ masm.Push(shape->propid(), scratchReg);
+ masm.moveStackPtrTo(argIdReg);
+
+ // Push the holder.
+ if (holderIsReceiver) {
+ // When the holder is also the current receiver, we just have a shape guard,
+ // so we might end up with a random object which is also guaranteed to have
+ // this JSGetterOp.
+ masm.Push(object);
+ } else {
+ // If the holder is on the prototype chain, the prototype-guarding
+ // only allows objects with the same holder.
+ masm.movePtr(ImmGCPtr(holder), scratchReg);
+ masm.Push(scratchReg);
+ }
+ masm.moveStackPtrTo(argObjReg);
+
+ masm.loadJSContext(argJSContextReg);
+
+ if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic))
+ return false;
+ masm.enterFakeExitFrame(IonOOLPropertyOpExitFrameLayoutToken);
+
+ // Make the call.
+ masm.setupUnalignedABICall(scratchReg);
+ masm.passABIArg(argJSContextReg);
+ masm.passABIArg(argObjReg);
+ masm.passABIArg(argIdReg);
+ masm.passABIArg(argVpReg);
+ masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, target));
+
+ // Test for failure.
+ masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
+
+ // Load the outparam vp[0] into output register(s).
+ Address outparam(masm.getStackPointer(), IonOOLPropertyOpExitFrameLayout::offsetOfResult());
+ masm.loadTypedOrValue(outparam, output);
+
+ // masm.leaveExitFrame & pop locals.
+ masm.adjustStack(IonOOLPropertyOpExitFrameLayout::Size());
+ } else {
+ MOZ_ASSERT(IsCacheableGetPropCallScripted(obj, holder, shape));
+
+ JSFunction* target = &shape->getterValue().toObject().as<JSFunction>();
+ uint32_t framePushedBefore = masm.framePushed();
+
+ // Construct IonAccessorICFrameLayout.
+ uint32_t descriptor = MakeFrameDescriptor(masm.framePushed(), JitFrame_IonJS,
+ IonAccessorICFrameLayout::Size());
+ attacher.pushStubCodePointer(masm);
+ masm.Push(Imm32(descriptor));
+ masm.Push(ImmPtr(returnAddr));
+
+ // The JitFrameLayout pushed below will be aligned to JitStackAlignment,
+ // so we just have to make sure the stack is aligned after we push the
+ // |this| + argument Values.
+ uint32_t argSize = (target->nargs() + 1) * sizeof(Value);
+ uint32_t padding = ComputeByteAlignment(masm.framePushed() + argSize, JitStackAlignment);
+ MOZ_ASSERT(padding % sizeof(uintptr_t) == 0);
+ MOZ_ASSERT(padding < JitStackAlignment);
+ masm.reserveStack(padding);
+
+ for (size_t i = 0; i < target->nargs(); i++)
+ masm.Push(UndefinedValue());
+ masm.Push(TypedOrValueRegister(MIRType::Object, AnyRegister(object)));
+
+ masm.movePtr(ImmGCPtr(target), scratchReg);
+
+ descriptor = MakeFrameDescriptor(argSize + padding, JitFrame_IonAccessorIC,
+ JitFrameLayout::Size());
+ masm.Push(Imm32(0)); // argc
+ masm.Push(scratchReg);
+ masm.Push(Imm32(descriptor));
+
+ // Check stack alignment. Add sizeof(uintptr_t) for the return address.
+ MOZ_ASSERT(((masm.framePushed() + sizeof(uintptr_t)) % JitStackAlignment) == 0);
+
+ // The getter has JIT code now and we will only discard the getter's JIT
+ // code when discarding all JIT code in the Zone, so we can assume it'll
+ // still have JIT code.
+ MOZ_ASSERT(target->hasJITCode());
+ masm.loadPtr(Address(scratchReg, JSFunction::offsetOfNativeOrScript()), scratchReg);
+ masm.loadBaselineOrIonRaw(scratchReg, scratchReg, nullptr);
+ masm.callJit(scratchReg);
+ masm.storeCallResultValue(output);
+
+ masm.freeStack(masm.framePushed() - framePushedBefore);
+ }
+
+ masm.icRestoreLive(liveRegs, aic);
+ return true;
+}
+
+static bool
+GenerateCallGetter(JSContext* cx, IonScript* ion, MacroAssembler& masm,
+ IonCache::StubAttacher& attacher, JSObject* obj,
+ JSObject* holder, HandleShape shape, LiveRegisterSet& liveRegs, Register object,
+ TypedOrValueRegister output, void* returnAddr, Label* failures = nullptr)
+{
+ MOZ_ASSERT(output.hasValue());
+
+ // Use the passed in label if there was one. Otherwise, we'll have to make our own.
+ Label stubFailure;
+ failures = failures ? failures : &stubFailure;
+
+ TestMatchingReceiver(masm, attacher, object, obj, failures);
+
+ Register scratchReg = output.valueReg().scratchReg();
+ bool spillObjReg = scratchReg == object;
+ Label pop1AndFail;
+ Label* maybePopAndFail = failures;
+
+ // If we're calling a getter on the global, inline the logic for the
+ // 'this' hook on the global lexical env and manually push the global.
+ if (IsGlobalLexicalEnvironment(obj)) {
+ masm.extractObject(Address(object, EnvironmentObject::offsetOfEnclosingEnvironment()),
+ object);
+ }
+
+ // Save off the object register if it aliases the scratchReg
+ if (spillObjReg) {
+ masm.push(object);
+ maybePopAndFail = &pop1AndFail;
+ }
+
+ // Note: this may clobber the object register if it's used as scratch.
+ if (obj != holder)
+ GeneratePrototypeGuards(cx, ion, masm, obj, holder, object, scratchReg, maybePopAndFail);
+
+ // Guard on the holder's shape.
+ Register holderReg = scratchReg;
+ masm.movePtr(ImmGCPtr(holder), holderReg);
+ masm.branchPtr(Assembler::NotEqual,
+ Address(holderReg, ShapedObject::offsetOfShape()),
+ ImmGCPtr(holder->as<NativeObject>().lastProperty()),
+ maybePopAndFail);
+
+ if (spillObjReg)
+ masm.pop(object);
+
+ // Now we're good to go to invoke the native call.
+ bool holderIsReceiver = (obj == holder);
+ if (!EmitGetterCall(cx, masm, attacher, obj, holder, shape, holderIsReceiver, liveRegs, object,
+ output, returnAddr))
+ return false;
+
+ // Rejoin jump.
+ attacher.jumpRejoin(masm);
+
+ // Jump to next stub.
+ if (spillObjReg) {
+ masm.bind(&pop1AndFail);
+ masm.pop(object);
+ }
+ masm.bind(failures);
+ attacher.jumpNextStub(masm);
+
+ return true;
+}
+
+static bool
+GenerateArrayLength(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
+ JSObject* obj, Register object, TypedOrValueRegister output, Label* failures)
+{
+ MOZ_ASSERT(obj->is<ArrayObject>());
+
+ // Guard object is a dense array.
+ RootedShape shape(cx, obj->as<ArrayObject>().lastProperty());
+ if (!shape)
+ return false;
+ masm.branchTestObjShape(Assembler::NotEqual, object, shape, failures);
+
+ // Load length.
+ Register outReg;
+ if (output.hasValue()) {
+ outReg = output.valueReg().scratchReg();
+ } else {
+ MOZ_ASSERT(output.type() == MIRType::Int32);
+ outReg = output.typedReg().gpr();
+ }
+
+ masm.loadPtr(Address(object, NativeObject::offsetOfElements()), outReg);
+ masm.load32(Address(outReg, ObjectElements::offsetOfLength()), outReg);
+
+ // The length is an unsigned int, but the value encodes a signed int.
+ MOZ_ASSERT(object != outReg);
+ masm.branchTest32(Assembler::Signed, outReg, outReg, failures);
+
+ if (output.hasValue())
+ masm.tagValue(JSVAL_TYPE_INT32, outReg, output.valueReg());
+
+ /* Success. */
+ attacher.jumpRejoin(masm);
+
+ /* Failure. */
+ masm.bind(failures);
+ attacher.jumpNextStub(masm);
+
+ return true;
+}
+
+static void
+GenerateUnboxedArrayLength(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
+ JSObject* array, Register object, TypedOrValueRegister output,
+ Label* failures)
+{
+ Register outReg;
+ if (output.hasValue()) {
+ outReg = output.valueReg().scratchReg();
+ } else {
+ MOZ_ASSERT(output.type() == MIRType::Int32);
+ outReg = output.typedReg().gpr();
+ }
+ MOZ_ASSERT(object != outReg);
+
+ TestMatchingReceiver(masm, attacher, object, array, failures);
+
+ // Load length.
+ masm.load32(Address(object, UnboxedArrayObject::offsetOfLength()), outReg);
+
+ // Check for a length that fits in an int32.
+ masm.branchTest32(Assembler::Signed, outReg, outReg, failures);
+
+ if (output.hasValue())
+ masm.tagValue(JSVAL_TYPE_INT32, outReg, output.valueReg());
+
+ // Success.
+ attacher.jumpRejoin(masm);
+
+ // Failure.
+ masm.bind(failures);
+ attacher.jumpNextStub(masm);
+}
+
+// In this case, the code for TypedArray and SharedTypedArray is not the same,
+// because the code embeds pointers to the respective class arrays. Code that
+// caches the stub code must distinguish between the two cases.
+static void
+GenerateTypedArrayLength(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
+ Register object, TypedOrValueRegister output, Label* failures)
+{
+ Register tmpReg;
+ if (output.hasValue()) {
+ tmpReg = output.valueReg().scratchReg();
+ } else {
+ MOZ_ASSERT(output.type() == MIRType::Int32);
+ tmpReg = output.typedReg().gpr();
+ }
+ MOZ_ASSERT(object != tmpReg);
+
+ // Implement the negated version of JSObject::isTypedArray predicate.
+ masm.loadObjClass(object, tmpReg);
+ masm.branchPtr(Assembler::Below, tmpReg, ImmPtr(&TypedArrayObject::classes[0]),
+ failures);
+ masm.branchPtr(Assembler::AboveOrEqual, tmpReg,
+ ImmPtr(&TypedArrayObject::classes[Scalar::MaxTypedArrayViewType]),
+ failures);
+
+ // Load length.
+ masm.loadTypedOrValue(Address(object, TypedArrayObject::lengthOffset()), output);
+
+ /* Success. */
+ attacher.jumpRejoin(masm);
+
+ /* Failure. */
+ masm.bind(failures);
+ attacher.jumpNextStub(masm);
+}
+
+static bool
+IsCacheableArrayLength(JSContext* cx, HandleObject obj, TypedOrValueRegister output)
+{
+ if (!obj->is<ArrayObject>())
+ return false;
+
+ if (output.type() != MIRType::Value && output.type() != MIRType::Int32) {
+ // The stub assumes that we always output Int32, so make sure our output
+ // is equipped to handle that.
+ return false;
+ }
+
+ // The emitted stub can only handle int32 lengths. If the length of the
+ // actual object does not fit in an int32 then don't attach a stub, as if
+ // the cache is idempotent we won't end up invalidating the compiled script
+ // otherwise.
+ if (obj->as<ArrayObject>().length() > INT32_MAX)
+ return false;
+
+ return true;
+}
+
+template <class GetPropCache>
+static GetPropertyIC::NativeGetPropCacheability
+CanAttachNativeGetProp(JSContext* cx, const GetPropCache& cache,
+ HandleObject obj, HandleId id,
+ MutableHandleNativeObject holder, MutableHandleShape shape,
+ bool skipArrayLen = false)
+{
+ MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_SYMBOL(id));
+
+ if (!obj)
+ return GetPropertyIC::CanAttachNone;
+
+ // The lookup needs to be universally pure, otherwise we risk calling hooks out
+ // of turn. We don't mind doing this even when purity isn't required, because we
+ // only miss out on shape hashification, which is only a temporary perf cost.
+ // The limits were arbitrarily set, anyways.
+ JSObject* baseHolder = nullptr;
+ if (!LookupPropertyPure(cx, obj, id, &baseHolder, shape.address()))
+ return GetPropertyIC::CanAttachNone;
+
+ MOZ_ASSERT(!holder);
+ if (baseHolder) {
+ if (!baseHolder->isNative())
+ return GetPropertyIC::CanAttachNone;
+ holder.set(&baseHolder->as<NativeObject>());
+ }
+
+ RootedScript script(cx);
+ jsbytecode* pc;
+ cache.getScriptedLocation(&script, &pc);
+ if (IsCacheableGetPropReadSlotForIonOrCacheIR(obj, holder, shape) ||
+ IsCacheableNoProperty(obj, holder, shape, pc, cache.output()))
+ {
+ return GetPropertyIC::CanAttachReadSlot;
+ }
+
+ // |length| is a non-configurable getter property on ArrayObjects. Any time this
+ // check would have passed, we can install a getter stub instead. Allow people to
+ // make that decision themselves with skipArrayLen
+ if (!skipArrayLen && JSID_IS_ATOM(id, cx->names().length) && cache.allowArrayLength(cx) &&
+ IsCacheableArrayLength(cx, obj, cache.output()))
+ {
+ // The array length property is non-configurable, which means both that
+ // checking the class of the object and the name of the property is enough
+ // and that we don't need to worry about monitoring, since we know the
+ // return type statically.
+ return GetPropertyIC::CanAttachArrayLength;
+ }
+
+ // IonBuilder guarantees that it's impossible to generate a GetPropertyIC with
+ // allowGetters() true and cache.output().hasValue() false. If this isn't true,
+ // we will quickly assert during stub generation.
+ //
+ // Be careful when adding support for other getters here: for outer window
+ // proxies, IonBuilder can innerize and pass us the inner window (the global),
+ // see IonBuilder::getPropTryInnerize. This is fine for native/scripted getters
+ // because IsCacheableGetPropCallNative and IsCacheableGetPropCallScripted
+ // handle this.
+ if (cache.allowGetters() &&
+ (IsCacheableGetPropCallNative(obj, holder, shape) ||
+ IsCacheableGetPropCallPropertyOp(obj, holder, shape) ||
+ IsCacheableGetPropCallScripted(obj, holder, shape)))
+ {
+ // Don't enable getter call if cache is idempotent, since they can be
+ // effectful. This is handled by allowGetters()
+ return GetPropertyIC::CanAttachCallGetter;
+ }
+
+ return GetPropertyIC::CanAttachNone;
+}
+
+static bool
+EqualStringsHelper(JSString* str1, JSString* str2)
+{
+ MOZ_ASSERT(str1->isAtom());
+ MOZ_ASSERT(!str2->isAtom());
+ MOZ_ASSERT(str1->length() == str2->length());
+
+ JSLinearString* str2Linear = str2->ensureLinear(nullptr);
+ if (!str2Linear)
+ return false;
+
+ return EqualChars(&str1->asLinear(), str2Linear);
+}
+
+static void
+EmitIdGuard(MacroAssembler& masm, jsid id, TypedOrValueRegister idReg, Register objReg,
+ Register scratchReg, Label* failures)
+{
+ MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_SYMBOL(id));
+
+ MOZ_ASSERT(idReg.type() == MIRType::String ||
+ idReg.type() == MIRType::Symbol ||
+ idReg.type() == MIRType::Value);
+
+ Register payloadReg;
+ if (idReg.type() == MIRType::Value) {
+ ValueOperand val = idReg.valueReg();
+ if (JSID_IS_SYMBOL(id)) {
+ masm.branchTestSymbol(Assembler::NotEqual, val, failures);
+ } else {
+ MOZ_ASSERT(JSID_IS_STRING(id));
+ masm.branchTestString(Assembler::NotEqual, val, failures);
+ }
+ masm.unboxNonDouble(val, scratchReg);
+ payloadReg = scratchReg;
+ } else {
+ payloadReg = idReg.typedReg().gpr();
+ }
+
+ if (JSID_IS_SYMBOL(id)) {
+ // For symbols, we can just do a pointer comparison.
+ masm.branchPtr(Assembler::NotEqual, payloadReg, ImmGCPtr(JSID_TO_SYMBOL(id)), failures);
+ } else {
+ PropertyName* name = JSID_TO_ATOM(id)->asPropertyName();
+
+ Label equal;
+ masm.branchPtr(Assembler::Equal, payloadReg, ImmGCPtr(name), &equal);
+
+ // The pointers are not equal, so if the input string is also an atom it
+ // must be a different string.
+ masm.branchTest32(Assembler::NonZero, Address(payloadReg, JSString::offsetOfFlags()),
+ Imm32(JSString::ATOM_BIT), failures);
+
+ // Check the length.
+ masm.branch32(Assembler::NotEqual, Address(payloadReg, JSString::offsetOfLength()),
+ Imm32(name->length()), failures);
+
+ // We have a non-atomized string with the same length. For now call a helper
+ // function to do the comparison.
+ LiveRegisterSet volatileRegs(RegisterSet::Volatile());
+ masm.PushRegsInMask(volatileRegs);
+
+ if (!volatileRegs.has(objReg))
+ masm.push(objReg);
+
+ masm.setupUnalignedABICall(objReg);
+ masm.movePtr(ImmGCPtr(name), objReg);
+ masm.passABIArg(objReg);
+ masm.passABIArg(payloadReg);
+ masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, EqualStringsHelper));
+ masm.mov(ReturnReg, scratchReg);
+
+ if (!volatileRegs.has(objReg))
+ masm.pop(objReg);
+
+ LiveRegisterSet ignore;
+ ignore.add(scratchReg);
+ masm.PopRegsInMaskIgnore(volatileRegs, ignore);
+
+ masm.branchIfFalseBool(scratchReg, failures);
+ masm.bind(&equal);
+ }
+}
+
+void
+GetPropertyIC::emitIdGuard(MacroAssembler& masm, jsid id, Label* fail)
+{
+ if (this->id().constant())
+ return;
+
+ Register scratch = output().valueReg().scratchReg();
+ EmitIdGuard(masm, id, this->id().reg(), object(), scratch, fail);
+}
+
+void
+SetPropertyIC::emitIdGuard(MacroAssembler& masm, jsid id, Label* fail)
+{
+ if (this->id().constant())
+ return;
+
+ EmitIdGuard(masm, id, this->id().reg(), object(), temp(), fail);
+}
+
+bool
+GetPropertyIC::allowArrayLength(JSContext* cx) const
+{
+ if (!idempotent())
+ return true;
+
+ uint32_t locationIndex, numLocations;
+ getLocationInfo(&locationIndex, &numLocations);
+
+ IonScript* ion = GetTopJitJSScript(cx)->ionScript();
+ CacheLocation* locs = ion->getCacheLocs(locationIndex);
+ for (size_t i = 0; i < numLocations; i++) {
+ CacheLocation& curLoc = locs[i];
+ StackTypeSet* bcTypes = TypeScript::BytecodeTypes(curLoc.script, curLoc.pc);
+
+ if (!bcTypes->hasType(TypeSet::Int32Type()))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+GetPropertyIC::tryAttachNative(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, void* returnAddr, bool* emitted)
+{
+ MOZ_ASSERT(canAttachStub());
+ MOZ_ASSERT(!*emitted);
+ MOZ_ASSERT(outerScript->ionScript() == ion);
+
+ RootedShape shape(cx);
+ RootedNativeObject holder(cx);
+
+ NativeGetPropCacheability type =
+ CanAttachNativeGetProp(cx, *this, obj, id, &holder, &shape);
+ if (type == CanAttachNone)
+ return true;
+
+ *emitted = true;
+
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+
+ StubAttacher attacher(*this);
+ const char* attachKind;
+
+ JS::TrackedOutcome outcome = JS::TrackedOutcome::ICOptStub_GenericSuccess;
+
+ Label failures;
+ emitIdGuard(masm, id, &failures);
+ Label* maybeFailures = failures.used() ? &failures : nullptr;
+
+ switch (type) {
+ case CanAttachReadSlot:
+ GenerateReadSlot(cx, ion, masm, attacher, DontCheckTDZ, obj, holder,
+ shape, object(), output(), maybeFailures);
+ attachKind = idempotent() ? "idempotent reading"
+ : "non idempotent reading";
+ outcome = JS::TrackedOutcome::ICGetPropStub_ReadSlot;
+ break;
+ case CanAttachCallGetter:
+ if (!GenerateCallGetter(cx, ion, masm, attacher, obj, holder, shape,
+ liveRegs_, object(), output(), returnAddr, maybeFailures))
+ {
+ return false;
+ }
+ attachKind = "getter call";
+ outcome = JS::TrackedOutcome::ICGetPropStub_CallGetter;
+ break;
+ case CanAttachArrayLength:
+ if (!GenerateArrayLength(cx, masm, attacher, obj, object(), output(), &failures))
+ return false;
+
+ attachKind = "array length";
+ outcome = JS::TrackedOutcome::ICGetPropStub_ArrayLength;
+ break;
+ default:
+ MOZ_CRASH("Bad NativeGetPropCacheability");
+ }
+ return linkAndAttachStub(cx, masm, attacher, ion, attachKind, outcome);
+}
+
+bool
+GetPropertyIC::tryAttachUnboxed(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, void* returnAddr, bool* emitted)
+{
+ MOZ_ASSERT(canAttachStub());
+ MOZ_ASSERT(!*emitted);
+ MOZ_ASSERT(outerScript->ionScript() == ion);
+
+ if (!obj->is<UnboxedPlainObject>())
+ return true;
+ const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id);
+ if (!property)
+ return true;
+
+ *emitted = true;
+
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+
+ Label failures;
+ emitIdGuard(masm, id, &failures);
+ Label* maybeFailures = failures.used() ? &failures : nullptr;
+
+ StubAttacher attacher(*this);
+ GenerateReadUnboxed(cx, ion, masm, attacher, obj, property, object(), output(), maybeFailures);
+ return linkAndAttachStub(cx, masm, attacher, ion, "read unboxed",
+ JS::TrackedOutcome::ICGetPropStub_UnboxedRead);
+}
+
+bool
+GetPropertyIC::tryAttachUnboxedExpando(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, void* returnAddr, bool* emitted)
+{
+ MOZ_ASSERT(canAttachStub());
+ MOZ_ASSERT(!*emitted);
+ MOZ_ASSERT(outerScript->ionScript() == ion);
+
+ if (!obj->is<UnboxedPlainObject>())
+ return true;
+ Rooted<UnboxedExpandoObject*> expando(cx, obj->as<UnboxedPlainObject>().maybeExpando());
+ if (!expando)
+ return true;
+
+ Shape* shape = expando->lookup(cx, id);
+ if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot())
+ return true;
+
+ *emitted = true;
+
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+
+ Label failures;
+ emitIdGuard(masm, id, &failures);
+ Label* maybeFailures = failures.used() ? &failures : nullptr;
+
+ StubAttacher attacher(*this);
+ GenerateReadSlot(cx, ion, masm, attacher, DontCheckTDZ, obj, obj,
+ shape, object(), output(), maybeFailures);
+ return linkAndAttachStub(cx, masm, attacher, ion, "read unboxed expando",
+ JS::TrackedOutcome::ICGetPropStub_UnboxedReadExpando);
+}
+
+bool
+GetPropertyIC::tryAttachUnboxedArrayLength(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, void* returnAddr,
+ bool* emitted)
+{
+ MOZ_ASSERT(canAttachStub());
+ MOZ_ASSERT(!*emitted);
+ MOZ_ASSERT(outerScript->ionScript() == ion);
+
+ if (!obj->is<UnboxedArrayObject>())
+ return true;
+
+ if (!JSID_IS_ATOM(id, cx->names().length))
+ return true;
+
+ if (obj->as<UnboxedArrayObject>().length() > INT32_MAX)
+ return true;
+
+ if (!allowArrayLength(cx))
+ return true;
+
+ *emitted = true;
+
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+
+ Label failures;
+ emitIdGuard(masm, id, &failures);
+
+ StubAttacher attacher(*this);
+ GenerateUnboxedArrayLength(cx, masm, attacher, obj, object(), output(), &failures);
+ return linkAndAttachStub(cx, masm, attacher, ion, "unboxed array length",
+ JS::TrackedOutcome::ICGetPropStub_UnboxedArrayLength);
+}
+
+bool
+GetPropertyIC::tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, bool* emitted)
+{
+ MOZ_ASSERT(canAttachStub());
+ MOZ_ASSERT(!*emitted);
+
+ if (!obj->is<TypedArrayObject>())
+ return true;
+
+ if (!JSID_IS_ATOM(id, cx->names().length))
+ return true;
+
+ if (hasTypedArrayLengthStub(obj))
+ return true;
+
+ if (output().type() != MIRType::Value && output().type() != MIRType::Int32) {
+ // The next execution should cause an invalidation because the type
+ // does not fit.
+ return true;
+ }
+
+ if (idempotent())
+ return true;
+
+ *emitted = true;
+
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+
+ Label failures;
+ emitIdGuard(masm, id, &failures);
+
+ GenerateTypedArrayLength(cx, masm, attacher, object(), output(), &failures);
+
+ setHasTypedArrayLengthStub(obj);
+ return linkAndAttachStub(cx, masm, attacher, ion, "typed array length",
+ JS::TrackedOutcome::ICGetPropStub_TypedArrayLength);
+}
+
+static void
+PushObjectOpResult(MacroAssembler& masm)
+{
+ static_assert(sizeof(ObjectOpResult) == sizeof(uintptr_t),
+ "ObjectOpResult size must match size reserved by masm.Push() here");
+ masm.Push(ImmWord(ObjectOpResult::Uninitialized));
+}
+
+static bool
+ProxyGetProperty(JSContext* cx, HandleObject proxy, HandleId id, MutableHandleValue vp)
+{
+ RootedValue receiver(cx, ObjectValue(*proxy));
+ return Proxy::get(cx, proxy, receiver, id, vp);
+}
+
+static bool
+EmitCallProxyGet(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
+ jsid id, LiveRegisterSet liveRegs, Register object, TypedOrValueRegister output,
+ jsbytecode* pc, void* returnAddr)
+{
+ MOZ_ASSERT(output.hasValue());
+ MacroAssembler::AfterICSaveLive aic = masm.icSaveLive(liveRegs);
+
+ // Remaining registers should be free, but we need to use |object| still
+ // so leave it alone.
+ AllocatableRegisterSet regSet(RegisterSet::All());
+ regSet.take(AnyRegister(object));
+
+ // ProxyGetProperty(JSContext* cx, HandleObject proxy, HandleId id,
+ // MutableHandleValue vp)
+ Register argJSContextReg = regSet.takeAnyGeneral();
+ Register argProxyReg = regSet.takeAnyGeneral();
+ Register argIdReg = regSet.takeAnyGeneral();
+ Register argVpReg = regSet.takeAnyGeneral();
+
+ Register scratch = regSet.takeAnyGeneral();
+
+ // Push stubCode for marking.
+ attacher.pushStubCodePointer(masm);
+
+ // Push args on stack first so we can take pointers to make handles.
+ masm.Push(UndefinedValue());
+ masm.moveStackPtrTo(argVpReg);
+
+ masm.Push(id, scratch);
+ masm.moveStackPtrTo(argIdReg);
+
+ // Push the proxy. Also used as receiver.
+ masm.Push(object);
+ masm.moveStackPtrTo(argProxyReg);
+
+ masm.loadJSContext(argJSContextReg);
+
+ if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic))
+ return false;
+ masm.enterFakeExitFrame(IonOOLProxyExitFrameLayoutToken);
+
+ // Make the call.
+ masm.setupUnalignedABICall(scratch);
+ masm.passABIArg(argJSContextReg);
+ masm.passABIArg(argProxyReg);
+ masm.passABIArg(argIdReg);
+ masm.passABIArg(argVpReg);
+ masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ProxyGetProperty));
+
+ // Test for failure.
+ masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
+
+ // Load the outparam vp[0] into output register(s).
+ Address outparam(masm.getStackPointer(), IonOOLProxyExitFrameLayout::offsetOfResult());
+ masm.loadTypedOrValue(outparam, output);
+
+ // masm.leaveExitFrame & pop locals
+ masm.adjustStack(IonOOLProxyExitFrameLayout::Size());
+
+ masm.icRestoreLive(liveRegs, aic);
+ return true;
+}
+
+bool
+GetPropertyIC::tryAttachDOMProxyShadowed(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, void* returnAddr,
+ bool* emitted)
+{
+ MOZ_ASSERT(canAttachStub());
+ MOZ_ASSERT(!*emitted);
+ MOZ_ASSERT(IsCacheableDOMProxy(obj));
+ MOZ_ASSERT(monitoredResult());
+ MOZ_ASSERT(output().hasValue());
+
+ if (idempotent())
+ return true;
+
+ *emitted = true;
+
+ Label failures;
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+
+ emitIdGuard(masm, id, &failures);
+
+ // Guard on the shape of the object.
+ attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
+ Address(object(), ShapedObject::offsetOfShape()),
+ ImmGCPtr(obj->maybeShape()),
+ &failures);
+
+ // No need for more guards: we know this is a DOM proxy, since the shape
+ // guard enforces a given JSClass, so just go ahead and emit the call to
+ // ProxyGet.
+
+ if (!EmitCallProxyGet(cx, masm, attacher, id, liveRegs_, object(), output(),
+ pc(), returnAddr))
+ {
+ return false;
+ }
+
+ // Success.
+ attacher.jumpRejoin(masm);
+
+ // Failure.
+ masm.bind(&failures);
+ attacher.jumpNextStub(masm);
+
+ return linkAndAttachStub(cx, masm, attacher, ion, "list base shadowed get",
+ JS::TrackedOutcome::ICGetPropStub_DOMProxyShadowed);
+}
+
+bool
+GetPropertyIC::tryAttachDOMProxyUnshadowed(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, bool resetNeeded,
+ void* returnAddr, bool* emitted)
+{
+ MOZ_ASSERT(canAttachStub());
+ MOZ_ASSERT(!*emitted);
+ MOZ_ASSERT(IsCacheableDOMProxy(obj));
+ MOZ_ASSERT(monitoredResult());
+ MOZ_ASSERT(output().hasValue());
+
+ RootedObject checkObj(cx, obj->staticPrototype());
+ RootedNativeObject holder(cx);
+ RootedShape shape(cx);
+
+ NativeGetPropCacheability canCache =
+ CanAttachNativeGetProp(cx, *this, checkObj, id, &holder, &shape,
+ /* skipArrayLen = */true);
+ MOZ_ASSERT(canCache != CanAttachArrayLength);
+
+ if (canCache == CanAttachNone)
+ return true;
+
+ // Make sure we observe our invariants if we're gonna deoptimize.
+ if (!holder && idempotent())
+ return true;
+
+ *emitted = true;
+
+ if (resetNeeded) {
+ // If we know that we have a DoesntShadowUnique object, then
+ // we reset the cache to clear out an existing IC for the object
+ // (if there is one). The generation is a constant in the generated
+ // code and we will not have the same generation again for this
+ // object, so the generation check in the existing IC would always
+ // fail anyway.
+ reset(Reprotect);
+ }
+
+ Label failures;
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+
+ emitIdGuard(masm, id, &failures);
+
+ // Guard on the shape of the object.
+ attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
+ Address(object(), ShapedObject::offsetOfShape()),
+ ImmGCPtr(obj->maybeShape()),
+ &failures);
+
+ // Guard that our expando object hasn't started shadowing this property.
+ CheckDOMProxyExpandoDoesNotShadow(cx, masm, obj, id, object(), &failures);
+
+ if (holder) {
+ // Found the property on the prototype chain. Treat it like a native
+ // getprop.
+ Register scratchReg = output().valueReg().scratchReg();
+ GeneratePrototypeGuards(cx, ion, masm, obj, holder, object(), scratchReg, &failures);
+
+ // Rename scratch for clarity.
+ Register holderReg = scratchReg;
+
+ // Guard on the holder of the property
+ masm.movePtr(ImmGCPtr(holder), holderReg);
+ masm.branchPtr(Assembler::NotEqual,
+ Address(holderReg, ShapedObject::offsetOfShape()),
+ ImmGCPtr(holder->lastProperty()),
+ &failures);
+
+ if (canCache == CanAttachReadSlot) {
+ EmitLoadSlot(masm, holder, shape, holderReg, output(), scratchReg);
+ } else {
+ // EmitGetterCall() expects |obj| to be the object the property is
+ // on to do some checks. Since we actually looked at checkObj, and
+ // no extra guards will be generated, we can just pass that instead.
+ // The holderIsReceiver check needs to use |obj| though.
+ MOZ_ASSERT(canCache == CanAttachCallGetter);
+ MOZ_ASSERT(!idempotent());
+ bool holderIsReceiver = (obj == holder);
+ if (!EmitGetterCall(cx, masm, attacher, checkObj, holder, shape, holderIsReceiver,
+ liveRegs_, object(), output(), returnAddr))
+ {
+ return false;
+ }
+ }
+ } else {
+ // Property was not found on the prototype chain. Deoptimize down to
+ // proxy get call
+ MOZ_ASSERT(!idempotent());
+ if (!EmitCallProxyGet(cx, masm, attacher, id, liveRegs_, object(), output(),
+ pc(), returnAddr))
+ {
+ return false;
+ }
+ }
+
+ attacher.jumpRejoin(masm);
+ masm.bind(&failures);
+ attacher.jumpNextStub(masm);
+
+ return linkAndAttachStub(cx, masm, attacher, ion, "unshadowed proxy get",
+ JS::TrackedOutcome::ICGetPropStub_DOMProxyUnshadowed);
+}
+
+bool
+GetPropertyIC::tryAttachProxy(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, void* returnAddr, bool* emitted)
+{
+ MOZ_ASSERT(canAttachStub());
+ MOZ_ASSERT(!*emitted);
+
+ if (!obj->is<ProxyObject>())
+ return true;
+
+ // TI can't be sure about our properties, so make sure anything
+ // we return can be monitored directly.
+ if (!monitoredResult())
+ return true;
+
+ // Skim off DOM proxies.
+ if (IsCacheableDOMProxy(obj)) {
+ DOMProxyShadowsResult shadows = GetDOMProxyShadowsCheck()(cx, obj, id);
+ if (shadows == ShadowCheckFailed)
+ return false;
+ if (DOMProxyIsShadowing(shadows))
+ return tryAttachDOMProxyShadowed(cx, outerScript, ion, obj, id, returnAddr, emitted);
+
+ MOZ_ASSERT(shadows == DoesntShadow || shadows == DoesntShadowUnique);
+ return tryAttachDOMProxyUnshadowed(cx, outerScript, ion, obj, id,
+ shadows == DoesntShadowUnique, returnAddr, emitted);
+ }
+
+ return tryAttachGenericProxy(cx, outerScript, ion, obj, id, returnAddr, emitted);
+}
+
+bool
+GetPropertyIC::tryAttachGenericProxy(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, void* returnAddr,
+ bool* emitted)
+{
+ MOZ_ASSERT(canAttachStub());
+ MOZ_ASSERT(!*emitted);
+ MOZ_ASSERT(obj->is<ProxyObject>());
+ MOZ_ASSERT(monitoredResult());
+ MOZ_ASSERT(output().hasValue());
+
+ if (hasGenericProxyStub())
+ return true;
+
+ if (idempotent())
+ return true;
+
+ *emitted = true;
+
+ Label failures;
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+
+ emitIdGuard(masm, id, &failures);
+
+ Register scratchReg = output().valueReg().scratchReg();
+
+ masm.branchTestObjectIsProxy(false, object(), scratchReg, &failures);
+
+ // Ensure that the incoming object is not a DOM proxy, so that we can get to
+ // the specialized stubs
+ masm.branchTestProxyHandlerFamily(Assembler::Equal, object(), scratchReg,
+ GetDOMProxyHandlerFamily(), &failures);
+
+ if (!EmitCallProxyGet(cx, masm, attacher, id, liveRegs_, object(), output(),
+ pc(), returnAddr))
+ {
+ return false;
+ }
+
+ attacher.jumpRejoin(masm);
+
+ masm.bind(&failures);
+ attacher.jumpNextStub(masm);
+
+ MOZ_ASSERT(!hasGenericProxyStub_);
+ hasGenericProxyStub_ = true;
+
+ return linkAndAttachStub(cx, masm, attacher, ion, "Generic Proxy get",
+ JS::TrackedOutcome::ICGetPropStub_GenericProxy);
+}
+
+bool
+GetPropertyIC::tryAttachArgumentsLength(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, bool* emitted)
+{
+ MOZ_ASSERT(canAttachStub());
+ MOZ_ASSERT(!*emitted);
+
+ if (!JSID_IS_ATOM(id, cx->names().length))
+ return true;
+ if (!IsOptimizableArgumentsObjectForLength(obj))
+ return true;
+
+ MIRType outputType = output().type();
+ if (!(outputType == MIRType::Value || outputType == MIRType::Int32))
+ return true;
+
+ if (hasArgumentsLengthStub(obj->is<MappedArgumentsObject>()))
+ return true;
+
+ *emitted = true;
+
+ MOZ_ASSERT(!idempotent());
+
+ Label failures;
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+
+ emitIdGuard(masm, id, &failures);
+
+ Register tmpReg;
+ if (output().hasValue()) {
+ tmpReg = output().valueReg().scratchReg();
+ } else {
+ MOZ_ASSERT(output().type() == MIRType::Int32);
+ tmpReg = output().typedReg().gpr();
+ }
+ MOZ_ASSERT(object() != tmpReg);
+
+ masm.branchTestObjClass(Assembler::NotEqual, object(), tmpReg, obj->getClass(), &failures);
+
+ // Get initial ArgsObj length value, test if length has been overridden.
+ masm.unboxInt32(Address(object(), ArgumentsObject::getInitialLengthSlotOffset()), tmpReg);
+ masm.branchTest32(Assembler::NonZero, tmpReg, Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT),
+ &failures);
+
+ masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), tmpReg);
+
+ // If output is Int32, result is already in right place, otherwise box it into output.
+ if (output().hasValue())
+ masm.tagValue(JSVAL_TYPE_INT32, tmpReg, output().valueReg());
+
+ // Success.
+ attacher.jumpRejoin(masm);
+
+ // Failure.
+ masm.bind(&failures);
+ attacher.jumpNextStub(masm);
+
+ if (obj->is<UnmappedArgumentsObject>()) {
+ MOZ_ASSERT(!hasUnmappedArgumentsLengthStub_);
+ hasUnmappedArgumentsLengthStub_ = true;
+ return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj length (unmapped)",
+ JS::TrackedOutcome::ICGetPropStub_ArgumentsLength);
+ }
+
+ MOZ_ASSERT(!hasMappedArgumentsLengthStub_);
+ hasMappedArgumentsLengthStub_ = true;
+ return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj length (mapped)",
+ JS::TrackedOutcome::ICGetPropStub_ArgumentsLength);
+}
+
+static void
+GenerateReadModuleNamespace(JSContext* cx, IonScript* ion, MacroAssembler& masm,
+ IonCache::StubAttacher& attacher, ModuleNamespaceObject* ns,
+ ModuleEnvironmentObject* env, Shape* shape, Register object,
+ TypedOrValueRegister output, Label* failures)
+{
+ MOZ_ASSERT(ns);
+ MOZ_ASSERT(env);
+
+ // If we have multiple failure jumps but didn't get a label from the
+ // outside, make one ourselves.
+ Label failures_;
+ if (!failures)
+ failures = &failures_;
+
+ // Check for the specific namespace object.
+ attacher.branchNextStubOrLabel(masm, Assembler::NotEqual, object, ImmGCPtr(ns), failures);
+
+ // If we need a scratch register, use either an output register or the
+ // object register.
+ bool restoreScratch = false;
+ Register scratchReg = InvalidReg; // Quell compiler warning.
+
+ if (output.hasValue()) {
+ scratchReg = output.valueReg().scratchReg();
+ } else if (output.type() == MIRType::Double) {
+ masm.push(object);
+ scratchReg = object;
+ restoreScratch = true;
+ } else {
+ scratchReg = output.typedReg().gpr();
+ }
+
+ // Slot access.
+ Register envReg = scratchReg;
+ masm.movePtr(ImmGCPtr(env), envReg);
+ EmitLoadSlot(masm, &env->as<NativeObject>(), shape, envReg, output, scratchReg);
+
+ // Restore scratch on success.
+ if (restoreScratch)
+ masm.pop(object);
+
+ attacher.jumpRejoin(masm);
+
+ masm.bind(failures);
+ attacher.jumpNextStub(masm);
+}
+
+bool
+GetPropertyIC::tryAttachModuleNamespace(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, void* returnAddr,
+ bool* emitted)
+{
+ MOZ_ASSERT(canAttachStub());
+ MOZ_ASSERT(!*emitted);
+ MOZ_ASSERT(outerScript->ionScript() == ion);
+
+ if (!obj->is<ModuleNamespaceObject>())
+ return true;
+
+ Rooted<ModuleNamespaceObject*> ns(cx, &obj->as<ModuleNamespaceObject>());
+
+ RootedModuleEnvironmentObject env(cx);
+ RootedShape shape(cx);
+ if (!ns->bindings().lookup(id, env.address(), shape.address()))
+ return true;
+
+ // Don't emit a stub until the target binding has been initialized.
+ if (env->getSlot(shape->slot()).isMagic(JS_UNINITIALIZED_LEXICAL))
+ return true;
+
+ *emitted = true;
+
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+
+ StubAttacher attacher(*this);
+
+ Label failures;
+ emitIdGuard(masm, id, &failures);
+ Label* maybeFailures = failures.used() ? &failures : nullptr;
+
+ GenerateReadModuleNamespace(cx, ion, masm, attacher, ns, env,
+ shape, object(), output(), maybeFailures);
+ return linkAndAttachStub(cx, masm, attacher, ion, "module namespace",
+ JS::TrackedOutcome::ICGetPropStub_ReadSlot);
+}
+
+static bool
+ValueToNameOrSymbolId(JSContext* cx, HandleValue idval, MutableHandleId id, bool* nameOrSymbol)
+{
+ *nameOrSymbol = false;
+
+ if (!idval.isString() && !idval.isSymbol())
+ return true;
+
+ if (!ValueToId<CanGC>(cx, idval, id))
+ return false;
+
+ if (!JSID_IS_STRING(id) && !JSID_IS_SYMBOL(id)) {
+ id.set(JSID_VOID);
+ return true;
+ }
+
+ uint32_t dummy;
+ if (JSID_IS_STRING(id) && JSID_TO_ATOM(id)->isIndex(&dummy)) {
+ id.set(JSID_VOID);
+ return true;
+ }
+
+ *nameOrSymbol = true;
+ return true;
+}
+
+bool
+GetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleValue idval, bool* emitted)
+{
+ MOZ_ASSERT(!*emitted);
+
+ if (!canAttachStub())
+ return true;
+
+ RootedId id(cx);
+ bool nameOrSymbol;
+ if (!ValueToNameOrSymbolId(cx, idval, &id, &nameOrSymbol))
+ return false;
+
+ if (nameOrSymbol) {
+ if (!*emitted && !tryAttachArgumentsLength(cx, outerScript, ion, obj, id, emitted))
+ return false;
+
+ void* returnAddr = GetReturnAddressToIonCode(cx);
+
+ if (!*emitted && !tryAttachModuleNamespace(cx, outerScript, ion, obj, id, returnAddr, emitted))
+ return false;
+
+ if (!*emitted && !tryAttachProxy(cx, outerScript, ion, obj, id, returnAddr, emitted))
+ return false;
+
+ if (!*emitted && !tryAttachNative(cx, outerScript, ion, obj, id, returnAddr, emitted))
+ return false;
+
+ if (!*emitted && !tryAttachUnboxed(cx, outerScript, ion, obj, id, returnAddr, emitted))
+ return false;
+
+ if (!*emitted && !tryAttachUnboxedExpando(cx, outerScript, ion, obj, id, returnAddr, emitted))
+ return false;
+
+ if (!*emitted && !tryAttachUnboxedArrayLength(cx, outerScript, ion, obj, id, returnAddr, emitted))
+ return false;
+
+ if (!*emitted && !tryAttachTypedArrayLength(cx, outerScript, ion, obj, id, emitted))
+ return false;
+ }
+
+ if (idval.isInt32()) {
+ if (!*emitted && !tryAttachArgumentsElement(cx, outerScript, ion, obj, idval, emitted))
+ return false;
+ if (!*emitted && !tryAttachDenseElement(cx, outerScript, ion, obj, idval, emitted))
+ return false;
+ if (!*emitted && !tryAttachDenseElementHole(cx, outerScript, ion, obj, idval, emitted))
+ return false;
+ }
+
+ if (idval.isInt32() || idval.isString()) {
+ if (!*emitted && !tryAttachTypedOrUnboxedArrayElement(cx, outerScript, ion, obj, idval, emitted))
+ return false;
+ }
+
+ if (!*emitted)
+ JitSpew(JitSpew_IonIC, "Failed to attach GETPROP cache");
+
+ return true;
+}
+
+/* static */ bool
+GetPropertyIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex,
+ HandleObject obj, HandleValue idval, MutableHandleValue vp)
+{
+ IonScript* ion = outerScript->ionScript();
+
+ GetPropertyIC& cache = ion->getCache(cacheIndex).toGetProperty();
+
+ // Override the return value if we are invalidated (bug 728188).
+ AutoDetectInvalidation adi(cx, vp, ion);
+
+ // If the cache is idempotent, we will redo the op in the interpreter.
+ if (cache.idempotent())
+ adi.disable();
+
+ // For now, just stop generating new stubs once we hit the stub count
+ // limit. Once we can make calls from within generated stubs, a new call
+ // stub will be generated instead and the previous stubs unlinked.
+ bool emitted = false;
+ if (!cache.isDisabled()) {
+ if (!cache.tryAttachStub(cx, outerScript, ion, obj, idval, &emitted))
+ return false;
+ cache.maybeDisable(emitted);
+ }
+
+ if (cache.idempotent() && !emitted) {
+ // Invalidate the cache if the property was not found, or was found on
+ // a non-native object. This ensures:
+ // 1) The property read has no observable side-effects.
+ // 2) There's no need to dynamically monitor the return type. This would
+ // be complicated since (due to GVN) there can be multiple pc's
+ // associated with a single idempotent cache.
+ JitSpew(JitSpew_IonIC, "Invalidating from idempotent cache %s:%" PRIuSIZE,
+ outerScript->filename(), outerScript->lineno());
+
+ outerScript->setInvalidatedIdempotentCache();
+
+ // Do not re-invalidate if the lookup already caused invalidation.
+ if (outerScript->hasIonScript())
+ Invalidate(cx, outerScript);
+
+ return true;
+ }
+
+ jsbytecode* pc = cache.idempotent() ? nullptr : cache.pc();
+
+ if (!pc || *pc == JSOP_GETPROP || *pc == JSOP_CALLPROP || *pc == JSOP_LENGTH) {
+ if (!GetProperty(cx, obj, obj, idval.toString()->asAtom().asPropertyName(), vp))
+ return false;
+ } else {
+ MOZ_ASSERT(*pc == JSOP_GETELEM || *pc == JSOP_CALLELEM);
+ if (!GetObjectElementOperation(cx, JSOp(*pc), obj, obj, idval, vp))
+ return false;
+ }
+
+ if (!cache.idempotent()) {
+ RootedScript script(cx);
+ jsbytecode* pc;
+ cache.getScriptedLocation(&script, &pc);
+
+ // Monitor changes to cache entry.
+ if (!cache.monitoredResult())
+ TypeScript::Monitor(cx, script, pc, vp);
+ }
+
+ return true;
+}
+
+void
+GetPropertyIC::reset(ReprotectCode reprotect)
+{
+ IonCache::reset(reprotect);
+ hasTypedArrayLengthStub_ = false;
+ hasMappedArgumentsLengthStub_ = false;
+ hasUnmappedArgumentsLengthStub_ = false;
+ hasMappedArgumentsElementStub_ = false;
+ hasUnmappedArgumentsElementStub_ = false;
+ hasGenericProxyStub_ = false;
+ hasDenseStub_ = false;
+}
+
+void
+IonCache::disable()
+{
+ reset(Reprotect);
+ this->disabled_ = 1;
+}
+
+void
+GetPropertyIC::maybeDisable(bool emitted)
+{
+ if (emitted) {
+ failedUpdates_ = 0;
+ return;
+ }
+
+ if (!canAttachStub() && id().constant()) {
+ // Don't disable the cache (and discard stubs) if we have a GETPROP and
+ // attached the maximum number of stubs. This can happen when JS code
+ // uses an AST-like data structure and accesses a field of a "base
+ // class", like node.nodeType. This should be temporary until we handle
+ // this case better, see bug 1107515.
+ return;
+ }
+
+ if (++failedUpdates_ > MAX_FAILED_UPDATES) {
+ JitSpew(JitSpew_IonIC, "Disable inline cache");
+ disable();
+ }
+}
+
+void
+IonCache::reset(ReprotectCode reprotect)
+{
+ this->stubCount_ = 0;
+ PatchJump(initialJump_, fallbackLabel_, reprotect);
+ lastJump_ = initialJump_;
+}
+
+// Jump to failure if a value being written is not a property for obj/id.
+static void
+CheckTypeSetForWrite(MacroAssembler& masm, JSObject* obj, jsid id,
+ Register scratch, const ConstantOrRegister& value, Label* failure)
+{
+ TypedOrValueRegister valReg = value.reg();
+ ObjectGroup* group = obj->group();
+ MOZ_ASSERT(!group->unknownProperties());
+
+ HeapTypeSet* propTypes = group->maybeGetProperty(id);
+ MOZ_ASSERT(propTypes);
+
+ // guardTypeSet can read from type sets without triggering read barriers.
+ TypeSet::readBarrier(propTypes);
+
+ masm.guardTypeSet(valReg, propTypes, BarrierKind::TypeSet, scratch, failure);
+}
+
+static void
+GenerateSetSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
+ JSObject* obj, Shape* shape, Register object, Register tempReg,
+ const ConstantOrRegister& value, bool needsTypeBarrier, bool checkTypeset,
+ Label* failures)
+{
+ TestMatchingReceiver(masm, attacher, object, obj, failures, needsTypeBarrier);
+
+ // Guard that the incoming value is in the type set for the property
+ // if a type barrier is required.
+ if (checkTypeset) {
+ MOZ_ASSERT(needsTypeBarrier);
+ CheckTypeSetForWrite(masm, obj, shape->propid(), tempReg, value, failures);
+ }
+
+ NativeObject::slotsSizeMustNotOverflow();
+
+ if (obj->is<UnboxedPlainObject>()) {
+ obj = obj->as<UnboxedPlainObject>().maybeExpando();
+ masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), tempReg);
+ object = tempReg;
+ }
+
+ if (obj->as<NativeObject>().isFixedSlot(shape->slot())) {
+ Address addr(object, NativeObject::getFixedSlotOffset(shape->slot()));
+
+ if (cx->zone()->needsIncrementalBarrier())
+ masm.callPreBarrier(addr, MIRType::Value);
+
+ masm.storeConstantOrRegister(value, addr);
+ } else {
+ masm.loadPtr(Address(object, NativeObject::offsetOfSlots()), tempReg);
+
+ Address addr(tempReg, obj->as<NativeObject>().dynamicSlotIndex(shape->slot()) * sizeof(Value));
+
+ if (cx->zone()->needsIncrementalBarrier())
+ masm.callPreBarrier(addr, MIRType::Value);
+
+ masm.storeConstantOrRegister(value, addr);
+ }
+
+ attacher.jumpRejoin(masm);
+
+ masm.bind(failures);
+ attacher.jumpNextStub(masm);
+}
+
+bool
+SetPropertyIC::attachSetSlot(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleShape shape, bool checkTypeset)
+{
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+
+ Label failures;
+ emitIdGuard(masm, shape->propid(), &failures);
+
+ GenerateSetSlot(cx, masm, attacher, obj, shape, object(), temp(), value(), needsTypeBarrier(),
+ checkTypeset, &failures);
+
+ return linkAndAttachStub(cx, masm, attacher, ion, "setting",
+ JS::TrackedOutcome::ICSetPropStub_Slot);
+}
+
+static bool
+IsCacheableSetPropCallNative(HandleObject obj, HandleObject holder, HandleShape shape)
+{
+ if (!shape || !IsCacheableProtoChainForIonOrCacheIR(obj, holder))
+ return false;
+
+ if (!shape->hasSetterValue())
+ return false;
+
+ if (!shape->setterObject() || !shape->setterObject()->is<JSFunction>())
+ return false;
+
+ JSFunction& setter = shape->setterObject()->as<JSFunction>();
+ if (!setter.isNative())
+ return false;
+
+ if (setter.jitInfo() && !setter.jitInfo()->needsOuterizedThisObject())
+ return true;
+
+ return !IsWindow(obj);
+}
+
+static bool
+IsCacheableSetPropCallScripted(HandleObject obj, HandleObject holder, HandleShape shape)
+{
+ if (!shape || !IsCacheableProtoChainForIonOrCacheIR(obj, holder))
+ return false;
+
+ if (IsWindow(obj))
+ return false;
+
+ return shape->hasSetterValue() && shape->setterObject() &&
+ shape->setterObject()->is<JSFunction>() &&
+ shape->setterObject()->as<JSFunction>().hasJITCode();
+}
+
+static bool
+IsCacheableSetPropCallPropertyOp(HandleObject obj, HandleObject holder, HandleShape shape)
+{
+ if (!shape)
+ return false;
+
+ if (!IsCacheableProtoChainForIonOrCacheIR(obj, holder))
+ return false;
+
+ if (shape->hasSlot())
+ return false;
+
+ if (shape->hasDefaultSetter())
+ return false;
+
+ if (shape->hasSetterValue())
+ return false;
+
+ // Despite the vehement claims of Shape.h that writable() is only relevant
+ // for data descriptors, some SetterOps care desperately about its
+ // value. The flag should be always true, apart from these rare instances.
+ if (!shape->writable())
+ return false;
+
+ return true;
+}
+
+static bool
+ReportStrictErrorOrWarning(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool strict,
+ JS::ObjectOpResult& result)
+{
+ return result.reportStrictErrorOrWarning(cx, obj, id, strict);
+}
+
+template <class FrameLayout>
+void
+EmitObjectOpResultCheck(MacroAssembler& masm, Label* failure, bool strict,
+ Register scratchReg,
+ Register argJSContextReg,
+ Register argObjReg,
+ Register argIdReg,
+ Register argStrictReg,
+ Register argResultReg)
+{
+ // if (!result) {
+ Label noStrictError;
+ masm.branch32(Assembler::Equal,
+ Address(masm.getStackPointer(),
+ FrameLayout::offsetOfObjectOpResult()),
+ Imm32(ObjectOpResult::OkCode),
+ &noStrictError);
+
+ // if (!ReportStrictErrorOrWarning(cx, obj, id, strict, &result))
+ // goto failure;
+ masm.loadJSContext(argJSContextReg);
+ masm.computeEffectiveAddress(
+ Address(masm.getStackPointer(), FrameLayout::offsetOfObject()),
+ argObjReg);
+ masm.computeEffectiveAddress(
+ Address(masm.getStackPointer(), FrameLayout::offsetOfId()),
+ argIdReg);
+ masm.move32(Imm32(strict), argStrictReg);
+ masm.computeEffectiveAddress(
+ Address(masm.getStackPointer(), FrameLayout::offsetOfObjectOpResult()),
+ argResultReg);
+ masm.setupUnalignedABICall(scratchReg);
+ masm.passABIArg(argJSContextReg);
+ masm.passABIArg(argObjReg);
+ masm.passABIArg(argIdReg);
+ masm.passABIArg(argStrictReg);
+ masm.passABIArg(argResultReg);
+ masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ReportStrictErrorOrWarning));
+ masm.branchIfFalseBool(ReturnReg, failure);
+
+ // }
+ masm.bind(&noStrictError);
+}
+
+static bool
+ProxySetProperty(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, bool strict)
+{
+ RootedValue receiver(cx, ObjectValue(*proxy));
+ ObjectOpResult result;
+ return Proxy::set(cx, proxy, id, v, receiver, result)
+ && result.checkStrictErrorOrWarning(cx, proxy, id, strict);
+}
+
+static bool
+EmitCallProxySet(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
+ HandleId propId, LiveRegisterSet liveRegs, Register object,
+ const ConstantOrRegister& value, void* returnAddr, bool strict)
+{
+ MacroAssembler::AfterICSaveLive aic = masm.icSaveLive(liveRegs);
+
+ // Remaining registers should be free, but we still need to use |object| so
+ // leave it alone.
+ //
+ // WARNING: We do not take() the register used by |value|, if any, so
+ // regSet is going to re-allocate it. Hence the emitted code must not touch
+ // any of the registers allocated from regSet until after the last use of
+ // |value|. (We can't afford to take it, either, because x86.)
+ AllocatableRegisterSet regSet(RegisterSet::All());
+ regSet.take(AnyRegister(object));
+
+ // ProxySetProperty(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v,
+ // bool strict);
+ Register argJSContextReg = regSet.takeAnyGeneral();
+ Register argProxyReg = regSet.takeAnyGeneral();
+ Register argIdReg = regSet.takeAnyGeneral();
+ Register argValueReg = regSet.takeAnyGeneral();
+ Register argStrictReg = regSet.takeAnyGeneral();
+
+ Register scratch = regSet.takeAnyGeneral();
+
+ // Push stubCode for marking.
+ attacher.pushStubCodePointer(masm);
+
+ // Push args on stack so we can take pointers to make handles.
+ // Push value before touching any other registers (see WARNING above).
+ masm.Push(value);
+ masm.moveStackPtrTo(argValueReg);
+
+ masm.move32(Imm32(strict), argStrictReg);
+
+ masm.Push(propId, scratch);
+ masm.moveStackPtrTo(argIdReg);
+
+ // Push object.
+ masm.Push(object);
+ masm.moveStackPtrTo(argProxyReg);
+
+ masm.loadJSContext(argJSContextReg);
+
+ if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic))
+ return false;
+ masm.enterFakeExitFrame(IonOOLProxyExitFrameLayoutToken);
+
+ // Make the call.
+ masm.setupUnalignedABICall(scratch);
+ masm.passABIArg(argJSContextReg);
+ masm.passABIArg(argProxyReg);
+ masm.passABIArg(argIdReg);
+ masm.passABIArg(argValueReg);
+ masm.passABIArg(argStrictReg);
+ masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ProxySetProperty));
+
+ // Test for error.
+ masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
+
+ // masm.leaveExitFrame & pop locals
+ masm.adjustStack(IonOOLProxyExitFrameLayout::Size());
+
+ masm.icRestoreLive(liveRegs, aic);
+ return true;
+}
+
+bool
+SetPropertyIC::attachGenericProxy(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleId id, void* returnAddr)
+{
+ MOZ_ASSERT(!hasGenericProxyStub());
+
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+
+ Label failures;
+ emitIdGuard(masm, id, &failures);
+ {
+ masm.branchTestObjectIsProxy(false, object(), temp(), &failures);
+
+ // Remove the DOM proxies. They'll take care of themselves so this stub doesn't
+ // catch too much. The failure case is actually Equal. Fall through to the failure code.
+ masm.branchTestProxyHandlerFamily(Assembler::Equal, object(), temp(),
+ GetDOMProxyHandlerFamily(), &failures);
+ }
+
+ if (!EmitCallProxySet(cx, masm, attacher, id, liveRegs_, object(), value(),
+ returnAddr, strict()))
+ {
+ return false;
+ }
+
+ attacher.jumpRejoin(masm);
+
+ masm.bind(&failures);
+ attacher.jumpNextStub(masm);
+
+ MOZ_ASSERT(!hasGenericProxyStub_);
+ hasGenericProxyStub_ = true;
+
+ return linkAndAttachStub(cx, masm, attacher, ion, "generic proxy set",
+ JS::TrackedOutcome::ICSetPropStub_GenericProxy);
+}
+
+bool
+SetPropertyIC::attachDOMProxyShadowed(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, void* returnAddr)
+{
+ MOZ_ASSERT(IsCacheableDOMProxy(obj));
+
+ Label failures;
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+
+ emitIdGuard(masm, id, &failures);
+
+ // Guard on the shape of the object.
+ masm.branchPtr(Assembler::NotEqual,
+ Address(object(), ShapedObject::offsetOfShape()),
+ ImmGCPtr(obj->maybeShape()), &failures);
+
+ // No need for more guards: we know this is a DOM proxy, since the shape
+ // guard enforces a given JSClass, so just go ahead and emit the call to
+ // ProxySet.
+
+ if (!EmitCallProxySet(cx, masm, attacher, id, liveRegs_, object(),
+ value(), returnAddr, strict()))
+ {
+ return false;
+ }
+
+ // Success.
+ attacher.jumpRejoin(masm);
+
+ // Failure.
+ masm.bind(&failures);
+ attacher.jumpNextStub(masm);
+
+ return linkAndAttachStub(cx, masm, attacher, ion, "DOM proxy shadowed set",
+ JS::TrackedOutcome::ICSetPropStub_DOMProxyShadowed);
+}
+
+static bool
+GenerateCallSetter(JSContext* cx, IonScript* ion, MacroAssembler& masm,
+ IonCache::StubAttacher& attacher, HandleObject obj, HandleObject holder,
+ HandleShape shape, bool strict, Register object, Register tempReg,
+ const ConstantOrRegister& value, Label* failure, LiveRegisterSet liveRegs,
+ void* returnAddr)
+{
+ // Generate prototype guards if needed.
+ {
+ // Generate prototype/shape guards.
+ if (obj != holder)
+ GeneratePrototypeGuards(cx, ion, masm, obj, holder, object, tempReg, failure);
+
+ masm.movePtr(ImmGCPtr(holder), tempReg);
+ masm.branchPtr(Assembler::NotEqual,
+ Address(tempReg, ShapedObject::offsetOfShape()),
+ ImmGCPtr(holder->as<NativeObject>().lastProperty()),
+ failure);
+ }
+
+ // Good to go for invoking setter.
+
+ MacroAssembler::AfterICSaveLive aic = masm.icSaveLive(liveRegs);
+
+ // Remaining registers should basically be free, but we need to use |object| still
+ // so leave it alone. And of course we need our value, if it's not a constant.
+ AllocatableRegisterSet regSet(RegisterSet::All());
+ if (!value.constant())
+ regSet.take(value.reg());
+ bool valueAliasesObject = !regSet.has(object);
+ if (!valueAliasesObject)
+ regSet.take(object);
+
+ regSet.take(tempReg);
+
+ // This is a slower stub path, and we're going to be doing a call anyway. Don't need
+ // to try so hard to not use the stack. Scratch regs are just taken from the register
+ // set not including the input, current value saved on the stack, and restored when
+ // we're done with it.
+ //
+ // Be very careful not to use any of these before value is pushed, since they
+ // might shadow.
+
+ if (IsCacheableSetPropCallNative(obj, holder, shape)) {
+ Register argJSContextReg = regSet.takeAnyGeneral();
+ Register argVpReg = regSet.takeAnyGeneral();
+
+ MOZ_ASSERT(shape->hasSetterValue() && shape->setterObject() &&
+ shape->setterObject()->is<JSFunction>());
+ JSFunction* target = &shape->setterObject()->as<JSFunction>();
+
+ MOZ_ASSERT(target->isNative());
+
+ Register argUintNReg = regSet.takeAnyGeneral();
+
+ // Set up the call:
+ // bool (*)(JSContext*, unsigned, Value* vp)
+ // vp[0] is callee/outparam
+ // vp[1] is |this|
+ // vp[2] is the value
+
+ // Build vp and move the base into argVpReg.
+ masm.Push(value);
+ masm.Push(TypedOrValueRegister(MIRType::Object, AnyRegister(object)));
+ masm.Push(ObjectValue(*target));
+ masm.moveStackPtrTo(argVpReg);
+
+ // Preload other regs
+ masm.loadJSContext(argJSContextReg);
+ masm.move32(Imm32(1), argUintNReg);
+
+ // Push data for GC marking
+ masm.Push(argUintNReg);
+ attacher.pushStubCodePointer(masm);
+
+ if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic))
+ return false;
+ masm.enterFakeExitFrame(IonOOLNativeExitFrameLayoutToken);
+
+ // Make the call
+ masm.setupUnalignedABICall(tempReg);
+ masm.passABIArg(argJSContextReg);
+ masm.passABIArg(argUintNReg);
+ masm.passABIArg(argVpReg);
+ masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, target->native()));
+
+ // Test for failure.
+ masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
+
+ // masm.leaveExitFrame & pop locals.
+ masm.adjustStack(IonOOLNativeExitFrameLayout::Size(1));
+ } else if (IsCacheableSetPropCallPropertyOp(obj, holder, shape)) {
+ // We can't take all our registers up front, because on x86 we need 2
+ // for the value, one for scratch, 5 for the arguments, which makes 8,
+ // but we only have 7 to work with. So only grab the ones we need
+ // before we push value and release its reg back into the set.
+ Register argResultReg = regSet.takeAnyGeneral();
+
+ SetterOp target = shape->setterOp();
+ MOZ_ASSERT(target);
+
+ // JSSetterOp: bool fn(JSContext* cx, HandleObject obj,
+ // HandleId id, HandleValue value, ObjectOpResult& result);
+
+ // First, allocate an ObjectOpResult on the stack. We push this before
+ // the stubCode pointer in order to match the layout of
+ // IonOOLSetterOpExitFrameLayout.
+ PushObjectOpResult(masm);
+ masm.moveStackPtrTo(argResultReg);
+
+ attacher.pushStubCodePointer(masm);
+
+ // Push args on stack so we can take pointers to make handles.
+ if (value.constant()) {
+ masm.Push(value.value());
+ } else {
+ masm.Push(value.reg());
+ if (!valueAliasesObject)
+ regSet.add(value.reg());
+ }
+
+ // OK, now we can grab our remaining registers and grab the pointer to
+ // what we just pushed into one of them.
+ Register argJSContextReg = regSet.takeAnyGeneral();
+ Register argValueReg = regSet.takeAnyGeneral();
+ // We can just reuse the "object" register for argObjReg
+ Register argObjReg = object;
+ Register argIdReg = regSet.takeAnyGeneral();
+ masm.moveStackPtrTo(argValueReg);
+
+ // push canonical jsid from shape instead of propertyname.
+ masm.Push(shape->propid(), argIdReg);
+ masm.moveStackPtrTo(argIdReg);
+
+ masm.Push(object);
+ masm.moveStackPtrTo(argObjReg);
+
+ masm.loadJSContext(argJSContextReg);
+
+ if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic))
+ return false;
+ masm.enterFakeExitFrame(IonOOLSetterOpExitFrameLayoutToken);
+
+ // Make the call.
+ masm.setupUnalignedABICall(tempReg);
+ masm.passABIArg(argJSContextReg);
+ masm.passABIArg(argObjReg);
+ masm.passABIArg(argIdReg);
+ masm.passABIArg(argValueReg);
+ masm.passABIArg(argResultReg);
+ masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, target));
+
+ // Test for error.
+ masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
+
+ // Test for strict failure. We emit the check even in non-strict mode
+ // in order to pick up the warning if extraWarnings is enabled.
+ EmitObjectOpResultCheck<IonOOLSetterOpExitFrameLayout>(masm, masm.exceptionLabel(),
+ strict, tempReg,
+ argJSContextReg, argObjReg,
+ argIdReg, argValueReg,
+ argResultReg);
+
+ // masm.leaveExitFrame & pop locals.
+ masm.adjustStack(IonOOLSetterOpExitFrameLayout::Size());
+ } else {
+ MOZ_ASSERT(IsCacheableSetPropCallScripted(obj, holder, shape));
+
+ JSFunction* target = &shape->setterValue().toObject().as<JSFunction>();
+ uint32_t framePushedBefore = masm.framePushed();
+
+ // Construct IonAccessorICFrameLayout.
+ uint32_t descriptor = MakeFrameDescriptor(masm.framePushed(), JitFrame_IonJS,
+ IonAccessorICFrameLayout::Size());
+ attacher.pushStubCodePointer(masm);
+ masm.Push(Imm32(descriptor));
+ masm.Push(ImmPtr(returnAddr));
+
+ // The JitFrameLayout pushed below will be aligned to JitStackAlignment,
+ // so we just have to make sure the stack is aligned after we push the
+ // |this| + argument Values.
+ uint32_t numArgs = Max(size_t(1), target->nargs());
+ uint32_t argSize = (numArgs + 1) * sizeof(Value);
+ uint32_t padding = ComputeByteAlignment(masm.framePushed() + argSize, JitStackAlignment);
+ MOZ_ASSERT(padding % sizeof(uintptr_t) == 0);
+ MOZ_ASSERT(padding < JitStackAlignment);
+ masm.reserveStack(padding);
+
+ for (size_t i = 1; i < target->nargs(); i++)
+ masm.Push(UndefinedValue());
+ masm.Push(value);
+ masm.Push(TypedOrValueRegister(MIRType::Object, AnyRegister(object)));
+
+ masm.movePtr(ImmGCPtr(target), tempReg);
+
+ descriptor = MakeFrameDescriptor(argSize + padding, JitFrame_IonAccessorIC,
+ JitFrameLayout::Size());
+ masm.Push(Imm32(1)); // argc
+ masm.Push(tempReg);
+ masm.Push(Imm32(descriptor));
+
+ // Check stack alignment. Add sizeof(uintptr_t) for the return address.
+ MOZ_ASSERT(((masm.framePushed() + sizeof(uintptr_t)) % JitStackAlignment) == 0);
+
+ // The setter has JIT code now and we will only discard the setter's JIT
+ // code when discarding all JIT code in the Zone, so we can assume it'll
+ // still have JIT code.
+ MOZ_ASSERT(target->hasJITCode());
+ masm.loadPtr(Address(tempReg, JSFunction::offsetOfNativeOrScript()), tempReg);
+ masm.loadBaselineOrIonRaw(tempReg, tempReg, nullptr);
+ masm.callJit(tempReg);
+
+ masm.freeStack(masm.framePushed() - framePushedBefore);
+ }
+
+ masm.icRestoreLive(liveRegs, aic);
+ return true;
+}
+
+static bool
+IsCacheableDOMProxyUnshadowedSetterCall(JSContext* cx, HandleObject obj, HandleId id,
+ MutableHandleObject holder, MutableHandleShape shape)
+{
+ MOZ_ASSERT(IsCacheableDOMProxy(obj));
+
+ RootedObject checkObj(cx, obj->staticPrototype());
+ if (!checkObj)
+ return false;
+
+ if (!LookupPropertyPure(cx, obj, id, holder.address(), shape.address()))
+ return false;
+
+ if (!holder)
+ return false;
+
+ return IsCacheableSetPropCallNative(checkObj, holder, shape) ||
+ IsCacheableSetPropCallPropertyOp(checkObj, holder, shape) ||
+ IsCacheableSetPropCallScripted(checkObj, holder, shape);
+}
+
+bool
+SetPropertyIC::attachDOMProxyUnshadowed(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, void* returnAddr)
+{
+ MOZ_ASSERT(IsCacheableDOMProxy(obj));
+
+ Label failures;
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+
+ emitIdGuard(masm, id, &failures);
+
+ // Guard on the shape of the object.
+ masm.branchPtr(Assembler::NotEqual,
+ Address(object(), ShapedObject::offsetOfShape()),
+ ImmGCPtr(obj->maybeShape()), &failures);
+
+ // Guard that our expando object hasn't started shadowing this property.
+ CheckDOMProxyExpandoDoesNotShadow(cx, masm, obj, id, object(), &failures);
+
+ RootedObject holder(cx);
+ RootedShape shape(cx);
+ if (IsCacheableDOMProxyUnshadowedSetterCall(cx, obj, id, &holder, &shape)) {
+ if (!GenerateCallSetter(cx, ion, masm, attacher, obj, holder, shape, strict(),
+ object(), temp(), value(), &failures, liveRegs_, returnAddr))
+ {
+ return false;
+ }
+ } else {
+ // Either there was no proto, or the property wasn't appropriately found on it.
+ // Drop back to just a call to Proxy::set().
+ if (!EmitCallProxySet(cx, masm, attacher, id, liveRegs_, object(),
+ value(), returnAddr, strict()))
+ {
+ return false;
+ }
+ }
+
+ // Success.
+ attacher.jumpRejoin(masm);
+
+ // Failure.
+ masm.bind(&failures);
+ attacher.jumpNextStub(masm);
+
+ return linkAndAttachStub(cx, masm, attacher, ion, "DOM proxy unshadowed set",
+ JS::TrackedOutcome::ICSetPropStub_DOMProxyUnshadowed);
+}
+
+bool
+SetPropertyIC::attachCallSetter(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleObject holder, HandleShape shape,
+ void* returnAddr)
+{
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+
+ Label failure;
+ emitIdGuard(masm, shape->propid(), &failure);
+ TestMatchingReceiver(masm, attacher, object(), obj, &failure);
+
+ if (!GenerateCallSetter(cx, ion, masm, attacher, obj, holder, shape, strict(),
+ object(), temp(), value(), &failure, liveRegs_, returnAddr))
+ {
+ return false;
+ }
+
+ // Rejoin jump.
+ attacher.jumpRejoin(masm);
+
+ // Jump to next stub.
+ masm.bind(&failure);
+ attacher.jumpNextStub(masm);
+
+ return linkAndAttachStub(cx, masm, attacher, ion, "setter call",
+ JS::TrackedOutcome::ICSetPropStub_CallSetter);
+}
+
+static void
+GenerateAddSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
+ JSObject* obj, Shape* oldShape, ObjectGroup* oldGroup,
+ Register object, Register tempReg, const ConstantOrRegister& value,
+ bool checkTypeset, Label* failures)
+{
+ // Use a modified version of TestMatchingReceiver that uses the old shape and group.
+ masm.branchTestObjGroup(Assembler::NotEqual, object, oldGroup, failures);
+ if (obj->maybeShape()) {
+ masm.branchTestObjShape(Assembler::NotEqual, object, oldShape, failures);
+ } else {
+ MOZ_ASSERT(obj->is<UnboxedPlainObject>());
+
+ Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando());
+ masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failures);
+
+ masm.loadPtr(expandoAddress, tempReg);
+ masm.branchTestObjShape(Assembler::NotEqual, tempReg, oldShape, failures);
+ }
+
+ Shape* newShape = obj->maybeShape();
+ if (!newShape)
+ newShape = obj->as<UnboxedPlainObject>().maybeExpando()->lastProperty();
+
+ // Guard that the incoming value is in the type set for the property
+ // if a type barrier is required.
+ if (checkTypeset)
+ CheckTypeSetForWrite(masm, obj, newShape->propid(), tempReg, value, failures);
+
+ // Guard shapes along prototype chain.
+ JSObject* proto = obj->staticPrototype();
+ Register protoReg = tempReg;
+ bool first = true;
+ while (proto) {
+ Shape* protoShape = proto->as<NativeObject>().lastProperty();
+
+ // Load next prototype.
+ masm.loadObjProto(first ? object : protoReg, protoReg);
+ first = false;
+
+ // Ensure that its shape matches.
+ masm.branchTestObjShape(Assembler::NotEqual, protoReg, protoShape, failures);
+
+ proto = proto->staticPrototype();
+ }
+
+ // Call a stub to (re)allocate dynamic slots, if necessary.
+ uint32_t newNumDynamicSlots = obj->is<UnboxedPlainObject>()
+ ? obj->as<UnboxedPlainObject>().maybeExpando()->numDynamicSlots()
+ : obj->as<NativeObject>().numDynamicSlots();
+ if (NativeObject::dynamicSlotsCount(oldShape) != newNumDynamicSlots) {
+ AllocatableRegisterSet regs(RegisterSet::Volatile());
+ LiveRegisterSet save(regs.asLiveSet());
+ masm.PushRegsInMask(save);
+
+ // Get 2 temp registers, without clobbering the object register.
+ regs.takeUnchecked(object);
+ Register temp1 = regs.takeAnyGeneral();
+ Register temp2 = regs.takeAnyGeneral();
+
+ if (obj->is<UnboxedPlainObject>()) {
+ // Pass the expando object to the stub.
+ masm.Push(object);
+ masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), object);
+ }
+
+ masm.setupUnalignedABICall(temp1);
+ masm.loadJSContext(temp1);
+ masm.passABIArg(temp1);
+ masm.passABIArg(object);
+ masm.move32(Imm32(newNumDynamicSlots), temp2);
+ masm.passABIArg(temp2);
+ masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, NativeObject::growSlotsDontReportOOM));
+
+ // Branch on ReturnReg before restoring volatile registers, so
+ // ReturnReg isn't clobbered.
+ uint32_t framePushedAfterCall = masm.framePushed();
+ Label allocFailed, allocDone;
+ masm.branchIfFalseBool(ReturnReg, &allocFailed);
+ masm.jump(&allocDone);
+
+ masm.bind(&allocFailed);
+ if (obj->is<UnboxedPlainObject>())
+ masm.Pop(object);
+ masm.PopRegsInMask(save);
+ masm.jump(failures);
+
+ masm.bind(&allocDone);
+ masm.setFramePushed(framePushedAfterCall);
+ if (obj->is<UnboxedPlainObject>())
+ masm.Pop(object);
+ masm.PopRegsInMask(save);
+ }
+
+ bool popObject = false;
+
+ if (obj->is<UnboxedPlainObject>()) {
+ masm.push(object);
+ popObject = true;
+ obj = obj->as<UnboxedPlainObject>().maybeExpando();
+ masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), object);
+ }
+
+ // Write the object or expando object's new shape.
+ Address shapeAddr(object, ShapedObject::offsetOfShape());
+ if (cx->zone()->needsIncrementalBarrier())
+ masm.callPreBarrier(shapeAddr, MIRType::Shape);
+ masm.storePtr(ImmGCPtr(newShape), shapeAddr);
+
+ if (oldGroup != obj->group()) {
+ MOZ_ASSERT(!obj->is<UnboxedPlainObject>());
+
+ // Changing object's group from a partially to fully initialized group,
+ // per the acquired properties analysis. Only change the group if the
+ // old group still has a newScript.
+ Label noTypeChange, skipPop;
+
+ masm.loadPtr(Address(object, JSObject::offsetOfGroup()), tempReg);
+ masm.branchPtr(Assembler::Equal,
+ Address(tempReg, ObjectGroup::offsetOfAddendum()),
+ ImmWord(0),
+ &noTypeChange);
+
+ Address groupAddr(object, JSObject::offsetOfGroup());
+ if (cx->zone()->needsIncrementalBarrier())
+ masm.callPreBarrier(groupAddr, MIRType::ObjectGroup);
+ masm.storePtr(ImmGCPtr(obj->group()), groupAddr);
+
+ masm.bind(&noTypeChange);
+ }
+
+ // Set the value on the object. Since this is an add, obj->lastProperty()
+ // must be the shape of the property we are adding.
+ NativeObject::slotsSizeMustNotOverflow();
+ if (obj->as<NativeObject>().isFixedSlot(newShape->slot())) {
+ Address addr(object, NativeObject::getFixedSlotOffset(newShape->slot()));
+ masm.storeConstantOrRegister(value, addr);
+ } else {
+ masm.loadPtr(Address(object, NativeObject::offsetOfSlots()), tempReg);
+
+ Address addr(tempReg, obj->as<NativeObject>().dynamicSlotIndex(newShape->slot()) * sizeof(Value));
+ masm.storeConstantOrRegister(value, addr);
+ }
+
+ if (popObject)
+ masm.pop(object);
+
+ // Success.
+ attacher.jumpRejoin(masm);
+
+ // Failure.
+ masm.bind(failures);
+
+ attacher.jumpNextStub(masm);
+}
+
+bool
+SetPropertyIC::attachAddSlot(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, HandleShape oldShape,
+ HandleObjectGroup oldGroup, bool checkTypeset)
+{
+ MOZ_ASSERT_IF(!needsTypeBarrier(), !checkTypeset);
+
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+
+ Label failures;
+ emitIdGuard(masm, id, &failures);
+
+ GenerateAddSlot(cx, masm, attacher, obj, oldShape, oldGroup, object(), temp(), value(),
+ checkTypeset, &failures);
+ return linkAndAttachStub(cx, masm, attacher, ion, "adding",
+ JS::TrackedOutcome::ICSetPropStub_AddSlot);
+}
+
+static bool
+CanInlineSetPropTypeCheck(JSObject* obj, jsid id, const ConstantOrRegister& val,
+ bool* checkTypeset)
+{
+ bool shouldCheck = false;
+ ObjectGroup* group = obj->group();
+ if (!group->unknownProperties()) {
+ HeapTypeSet* propTypes = group->maybeGetProperty(id);
+ if (!propTypes)
+ return false;
+ if (!propTypes->unknown()) {
+ if (obj->isSingleton() && !propTypes->nonConstantProperty())
+ return false;
+ shouldCheck = true;
+ if (val.constant()) {
+ // If the input is a constant, then don't bother if the barrier will always fail.
+ if (!propTypes->hasType(TypeSet::GetValueType(val.value())))
+ return false;
+ shouldCheck = false;
+ } else {
+ TypedOrValueRegister reg = val.reg();
+ // We can do the same trick as above for primitive types of specialized registers.
+ // TIs handling of objects is complicated enough to warrant a runtime
+ // check, as we can't statically handle the case where the typeset
+ // contains the specific object, but doesn't have ANYOBJECT set.
+ if (reg.hasTyped() && reg.type() != MIRType::Object) {
+ JSValueType valType = ValueTypeFromMIRType(reg.type());
+ if (!propTypes->hasType(TypeSet::PrimitiveType(valType)))
+ return false;
+ shouldCheck = false;
+ }
+ }
+ }
+ }
+
+ *checkTypeset = shouldCheck;
+ return true;
+}
+
+static bool
+IsPropertySetInlineable(NativeObject* obj, HandleId id, MutableHandleShape pshape,
+ const ConstantOrRegister& val, bool needsTypeBarrier, bool* checkTypeset)
+{
+ // CanInlineSetPropTypeCheck assumes obj has a non-lazy group.
+ MOZ_ASSERT(!obj->hasLazyGroup());
+
+ // Do a pure non-proto chain climbing lookup. See note in
+ // CanAttachNativeGetProp.
+ pshape.set(obj->lookupPure(id));
+
+ if (!pshape)
+ return false;
+
+ if (!pshape->hasSlot())
+ return false;
+
+ if (!pshape->hasDefaultSetter())
+ return false;
+
+ if (!pshape->writable())
+ return false;
+
+ *checkTypeset = false;
+ if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
+ return false;
+
+ return true;
+}
+
+static bool
+PrototypeChainShadowsPropertyAdd(JSContext* cx, JSObject* obj, jsid id)
+{
+ // Walk up the object prototype chain and ensure that all prototypes
+ // are native, and that all prototypes have no getter or setter
+ // defined on the property
+ for (JSObject* proto = obj->staticPrototype(); proto; proto = proto->staticPrototype()) {
+ // If prototype is non-native, don't optimize
+ if (!proto->isNative())
+ return true;
+
+ // If prototype defines this property in a non-plain way, don't optimize
+ Shape* protoShape = proto->as<NativeObject>().lookupPure(id);
+ if (protoShape && !protoShape->hasDefaultSetter())
+ return true;
+
+ // Otherwise, if there's no such property, watch out for a resolve
+ // hook that would need to be invoked and thus prevent inlining of
+ // property addition.
+ if (ClassMayResolveId(cx->names(), proto->getClass(), id, proto))
+ return true;
+ }
+
+ return false;
+}
+
+static bool
+IsPropertyAddInlineable(JSContext* cx, NativeObject* obj, HandleId id,
+ const ConstantOrRegister& val,
+ HandleShape oldShape, bool needsTypeBarrier, bool* checkTypeset)
+{
+ // If the shape of the object did not change, then this was not an add.
+ if (obj->lastProperty() == oldShape)
+ return false;
+
+ Shape* shape = obj->lookupPure(id);
+ if (!shape || shape->inDictionary() || !shape->hasSlot() || !shape->hasDefaultSetter())
+ return false;
+
+ // If we have a shape at this point and the object's shape changed, then
+ // the shape must be the one we just added.
+ MOZ_ASSERT(shape == obj->lastProperty());
+
+ // Watch out for resolve hooks.
+ if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj))
+ return false;
+
+ // Likewise for an addProperty hook, since we'll need to invoke it.
+ if (obj->getClass()->getAddProperty())
+ return false;
+
+ if (!obj->nonProxyIsExtensible() || !shape->writable())
+ return false;
+
+ if (PrototypeChainShadowsPropertyAdd(cx, obj, id))
+ return false;
+
+ // Don't attach if we are adding a property to an object which the new
+ // script properties analysis hasn't been performed for yet, as there
+ // may be a group change required here afterwards.
+ if (obj->group()->newScript() && !obj->group()->newScript()->analyzed())
+ return false;
+
+ *checkTypeset = false;
+ if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
+ return false;
+
+ return true;
+}
+
+static SetPropertyIC::NativeSetPropCacheability
+CanAttachNativeSetProp(JSContext* cx, HandleObject obj, HandleId id, const ConstantOrRegister& val,
+ bool needsTypeBarrier, MutableHandleObject holder,
+ MutableHandleShape shape, bool* checkTypeset)
+{
+ // See if the property exists on the object.
+ if (obj->isNative() && IsPropertySetInlineable(&obj->as<NativeObject>(), id, shape, val,
+ needsTypeBarrier, checkTypeset))
+ {
+ return SetPropertyIC::CanAttachSetSlot;
+ }
+
+ // If we couldn't find the property on the object itself, do a full, but
+ // still pure lookup for setters.
+ if (!LookupPropertyPure(cx, obj, id, holder.address(), shape.address()))
+ return SetPropertyIC::CanAttachNone;
+
+ // If the object doesn't have the property, we don't know if we can attach
+ // a stub to add the property until we do the VM call to add. If the
+ // property exists as a data property on the prototype, we should add
+ // a new, shadowing property.
+ if (obj->isNative() && (!shape || (obj != holder && holder->isNative() &&
+ shape->hasDefaultSetter() && shape->hasSlot())))
+ {
+ return SetPropertyIC::MaybeCanAttachAddSlot;
+ }
+
+ if (IsImplicitNonNativeProperty(shape))
+ return SetPropertyIC::CanAttachNone;
+
+ if (IsCacheableSetPropCallPropertyOp(obj, holder, shape) ||
+ IsCacheableSetPropCallNative(obj, holder, shape) ||
+ IsCacheableSetPropCallScripted(obj, holder, shape))
+ {
+ return SetPropertyIC::CanAttachCallSetter;
+ }
+
+ return SetPropertyIC::CanAttachNone;
+}
+
+static void
+GenerateSetUnboxed(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
+ JSObject* obj, jsid id, uint32_t unboxedOffset, JSValueType unboxedType,
+ Register object, Register tempReg, const ConstantOrRegister& value,
+ bool checkTypeset, Label* failures)
+{
+ // Guard on the type of the object.
+ masm.branchPtr(Assembler::NotEqual,
+ Address(object, JSObject::offsetOfGroup()),
+ ImmGCPtr(obj->group()), failures);
+
+ if (checkTypeset)
+ CheckTypeSetForWrite(masm, obj, id, tempReg, value, failures);
+
+ Address address(object, UnboxedPlainObject::offsetOfData() + unboxedOffset);
+
+ if (cx->zone()->needsIncrementalBarrier()) {
+ if (unboxedType == JSVAL_TYPE_OBJECT)
+ masm.callPreBarrier(address, MIRType::Object);
+ else if (unboxedType == JSVAL_TYPE_STRING)
+ masm.callPreBarrier(address, MIRType::String);
+ else
+ MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(unboxedType));
+ }
+
+ masm.storeUnboxedProperty(address, unboxedType, value, failures);
+
+ attacher.jumpRejoin(masm);
+
+ masm.bind(failures);
+ attacher.jumpNextStub(masm);
+}
+
+static bool
+CanAttachSetUnboxed(JSContext* cx, HandleObject obj, HandleId id, const ConstantOrRegister& val,
+ bool needsTypeBarrier, bool* checkTypeset,
+ uint32_t* unboxedOffset, JSValueType* unboxedType)
+{
+ if (!obj->is<UnboxedPlainObject>())
+ return false;
+
+ const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id);
+ if (property) {
+ *checkTypeset = false;
+ if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
+ return false;
+ *unboxedOffset = property->offset;
+ *unboxedType = property->type;
+ return true;
+ }
+
+ return false;
+}
+
+static bool
+CanAttachSetUnboxedExpando(JSContext* cx, HandleObject obj, HandleId id,
+ const ConstantOrRegister& val,
+ bool needsTypeBarrier, bool* checkTypeset, Shape** pshape)
+{
+ if (!obj->is<UnboxedPlainObject>())
+ return false;
+
+ Rooted<UnboxedExpandoObject*> expando(cx, obj->as<UnboxedPlainObject>().maybeExpando());
+ if (!expando)
+ return false;
+
+ Shape* shape = expando->lookupPure(id);
+ if (!shape || !shape->hasDefaultSetter() || !shape->hasSlot() || !shape->writable())
+ return false;
+
+ *checkTypeset = false;
+ if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
+ return false;
+
+ *pshape = shape;
+ return true;
+}
+
+static bool
+CanAttachAddUnboxedExpando(JSContext* cx, HandleObject obj, HandleShape oldShape,
+ HandleId id, const ConstantOrRegister& val,
+ bool needsTypeBarrier, bool* checkTypeset)
+{
+ if (!obj->is<UnboxedPlainObject>())
+ return false;
+
+ Rooted<UnboxedExpandoObject*> expando(cx, obj->as<UnboxedPlainObject>().maybeExpando());
+ if (!expando || expando->inDictionaryMode())
+ return false;
+
+ Shape* newShape = expando->lastProperty();
+ if (newShape->isEmptyShape() || newShape->propid() != id || newShape->previous() != oldShape)
+ return false;
+
+ MOZ_ASSERT(newShape->hasDefaultSetter() && newShape->hasSlot() && newShape->writable());
+
+ if (PrototypeChainShadowsPropertyAdd(cx, obj, id))
+ return false;
+
+ *checkTypeset = false;
+ if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
+ return false;
+
+ return true;
+}
+
+bool
+SetPropertyIC::tryAttachUnboxed(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, bool* emitted)
+{
+ MOZ_ASSERT(!*emitted);
+
+ bool checkTypeset = false;
+ uint32_t unboxedOffset;
+ JSValueType unboxedType;
+ if (!CanAttachSetUnboxed(cx, obj, id, value(), needsTypeBarrier(), &checkTypeset,
+ &unboxedOffset, &unboxedType))
+ {
+ return true;
+ }
+
+ *emitted = true;
+
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+
+ Label failures;
+ emitIdGuard(masm, id, &failures);
+
+ GenerateSetUnboxed(cx, masm, attacher, obj, id, unboxedOffset, unboxedType,
+ object(), temp(), value(), checkTypeset, &failures);
+ return linkAndAttachStub(cx, masm, attacher, ion, "set_unboxed",
+ JS::TrackedOutcome::ICSetPropStub_SetUnboxed);
+}
+
+bool
+SetPropertyIC::tryAttachProxy(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, bool* emitted)
+{
+ MOZ_ASSERT(!*emitted);
+
+ if (!obj->is<ProxyObject>())
+ return true;
+
+ void* returnAddr = GetReturnAddressToIonCode(cx);
+ if (IsCacheableDOMProxy(obj)) {
+ DOMProxyShadowsResult shadows = GetDOMProxyShadowsCheck()(cx, obj, id);
+ if (shadows == ShadowCheckFailed)
+ return false;
+
+ if (DOMProxyIsShadowing(shadows)) {
+ if (!attachDOMProxyShadowed(cx, outerScript, ion, obj, id, returnAddr))
+ return false;
+ *emitted = true;
+ return true;
+ }
+
+ MOZ_ASSERT(shadows == DoesntShadow || shadows == DoesntShadowUnique);
+ if (shadows == DoesntShadowUnique)
+ reset(Reprotect);
+ if (!attachDOMProxyUnshadowed(cx, outerScript, ion, obj, id, returnAddr))
+ return false;
+ *emitted = true;
+ return true;
+ }
+
+ if (hasGenericProxyStub())
+ return true;
+
+ if (!attachGenericProxy(cx, outerScript, ion, id, returnAddr))
+ return false;
+ *emitted = true;
+ return true;
+}
+
+bool
+SetPropertyIC::tryAttachNative(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, bool* emitted, bool* tryNativeAddSlot)
+{
+ MOZ_ASSERT(!*emitted);
+ MOZ_ASSERT(!*tryNativeAddSlot);
+
+ RootedShape shape(cx);
+ RootedObject holder(cx);
+ bool checkTypeset = false;
+ NativeSetPropCacheability canCache = CanAttachNativeSetProp(cx, obj, id, value(), needsTypeBarrier(),
+ &holder, &shape, &checkTypeset);
+ switch (canCache) {
+ case CanAttachNone:
+ return true;
+
+ case CanAttachSetSlot: {
+ RootedNativeObject nobj(cx, &obj->as<NativeObject>());
+ if (!attachSetSlot(cx, outerScript, ion, nobj, shape, checkTypeset))
+ return false;
+ *emitted = true;
+ return true;
+ }
+
+ case CanAttachCallSetter: {
+ void* returnAddr = GetReturnAddressToIonCode(cx);
+ if (!attachCallSetter(cx, outerScript, ion, obj, holder, shape, returnAddr))
+ return false;
+ *emitted = true;
+ return true;
+ }
+
+ case MaybeCanAttachAddSlot:
+ *tryNativeAddSlot = true;
+ return true;
+ }
+
+ MOZ_CRASH("Unreachable");
+}
+
+bool
+SetPropertyIC::tryAttachUnboxedExpando(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, bool* emitted)
+{
+ MOZ_ASSERT(!*emitted);
+
+ RootedShape shape(cx);
+ bool checkTypeset = false;
+ if (!CanAttachSetUnboxedExpando(cx, obj, id, value(), needsTypeBarrier(),
+ &checkTypeset, shape.address()))
+ {
+ return true;
+ }
+
+ if (!attachSetSlot(cx, outerScript, ion, obj, shape, checkTypeset))
+ return false;
+ *emitted = true;
+ return true;
+}
+
+bool
+SetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleValue idval, HandleValue value,
+ MutableHandleId id, bool* emitted, bool* tryNativeAddSlot)
+{
+ MOZ_ASSERT(!*emitted);
+ MOZ_ASSERT(!*tryNativeAddSlot);
+
+ if (!canAttachStub() || obj->watched())
+ return true;
+
+ // Fail cache emission if the object is frozen
+ if (obj->is<NativeObject>() && obj->as<NativeObject>().getElementsHeader()->isFrozen())
+ return true;
+
+ bool nameOrSymbol;
+ if (!ValueToNameOrSymbolId(cx, idval, id, &nameOrSymbol))
+ return false;
+
+ if (nameOrSymbol) {
+ if (!*emitted && !tryAttachProxy(cx, outerScript, ion, obj, id, emitted))
+ return false;
+
+ if (!*emitted && !tryAttachNative(cx, outerScript, ion, obj, id, emitted, tryNativeAddSlot))
+ return false;
+
+ if (!*emitted && !tryAttachUnboxed(cx, outerScript, ion, obj, id, emitted))
+ return false;
+
+ if (!*emitted && !tryAttachUnboxedExpando(cx, outerScript, ion, obj, id, emitted))
+ return false;
+ }
+
+ if (idval.isInt32()) {
+ if (!*emitted && !tryAttachDenseElement(cx, outerScript, ion, obj, idval, emitted))
+ return false;
+ if (!*emitted &&
+ !tryAttachTypedArrayElement(cx, outerScript, ion, obj, idval, value, emitted))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+SetPropertyIC::tryAttachAddSlot(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, HandleObjectGroup oldGroup,
+ HandleShape oldShape, bool tryNativeAddSlot, bool* emitted)
+{
+ MOZ_ASSERT(!*emitted);
+
+ if (!canAttachStub())
+ return true;
+
+ if (!JSID_IS_STRING(id) && !JSID_IS_SYMBOL(id))
+ return true;
+
+ // Fail cache emission if the object is frozen
+ if (obj->is<NativeObject>() && obj->as<NativeObject>().getElementsHeader()->isFrozen())
+ return true;
+
+ // A GC may have caused cache.value() to become stale as it is not traced.
+ // In this case the IonScript will have been invalidated, so check for that.
+ // Assert no further GC is possible past this point.
+ JS::AutoAssertNoGC nogc;
+ if (ion->invalidated())
+ return true;
+
+ // The property did not exist before, now we can try to inline the property add.
+ bool checkTypeset = false;
+ if (tryNativeAddSlot &&
+ IsPropertyAddInlineable(cx, &obj->as<NativeObject>(), id, value(), oldShape,
+ needsTypeBarrier(), &checkTypeset))
+ {
+ if (!attachAddSlot(cx, outerScript, ion, obj, id, oldShape, oldGroup, checkTypeset))
+ return false;
+ *emitted = true;
+ return true;
+ }
+
+ checkTypeset = false;
+ if (CanAttachAddUnboxedExpando(cx, obj, oldShape, id, value(), needsTypeBarrier(),
+ &checkTypeset))
+ {
+ if (!attachAddSlot(cx, outerScript, ion, obj, id, oldShape, oldGroup, checkTypeset))
+ return false;
+ *emitted = true;
+ return true;
+ }
+
+ return true;
+}
+
+bool
+SetPropertyIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex, HandleObject obj,
+ HandleValue idval, HandleValue value)
+{
+ IonScript* ion = outerScript->ionScript();
+ SetPropertyIC& cache = ion->getCache(cacheIndex).toSetProperty();
+
+ // Remember the old group and shape if we may attach an add-property stub.
+ // Also, some code under tryAttachStub depends on obj having a non-lazy
+ // group, see for instance CanInlineSetPropTypeCheck.
+ RootedObjectGroup oldGroup(cx);
+ RootedShape oldShape(cx);
+ if (cache.canAttachStub()) {
+ oldGroup = obj->getGroup(cx);
+ if (!oldGroup)
+ return false;
+
+ oldShape = obj->maybeShape();
+ if (obj->is<UnboxedPlainObject>()) {
+ MOZ_ASSERT(!oldShape);
+ if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
+ oldShape = expando->lastProperty();
+ }
+ }
+
+ RootedId id(cx);
+ bool emitted = false;
+ bool tryNativeAddSlot = false;
+ if (!cache.tryAttachStub(cx, outerScript, ion, obj, idval, value, &id, &emitted,
+ &tryNativeAddSlot))
+ {
+ return false;
+ }
+
+ // Set/Add the property on the object, the inlined cache are setup for the next execution.
+ if (JSOp(*cache.pc()) == JSOP_INITGLEXICAL) {
+ RootedScript script(cx);
+ jsbytecode* pc;
+ cache.getScriptedLocation(&script, &pc);
+ MOZ_ASSERT(!script->hasNonSyntacticScope());
+ InitGlobalLexicalOperation(cx, &cx->global()->lexicalEnvironment(), script, pc, value);
+ } else if (*cache.pc() == JSOP_SETELEM || *cache.pc() == JSOP_STRICTSETELEM) {
+ if (!SetObjectElement(cx, obj, idval, value, cache.strict()))
+ return false;
+ } else {
+ RootedPropertyName name(cx, idval.toString()->asAtom().asPropertyName());
+ if (!SetProperty(cx, obj, name, value, cache.strict(), cache.pc()))
+ return false;
+ }
+
+ if (!emitted &&
+ !cache.tryAttachAddSlot(cx, outerScript, ion, obj, id, oldGroup, oldShape,
+ tryNativeAddSlot, &emitted))
+ {
+ return false;
+ }
+
+ if (!emitted)
+ JitSpew(JitSpew_IonIC, "Failed to attach SETPROP cache");
+
+ return true;
+}
+
+void
+SetPropertyIC::reset(ReprotectCode reprotect)
+{
+ IonCache::reset(reprotect);
+ hasGenericProxyStub_ = false;
+ hasDenseStub_ = false;
+}
+
+static bool
+GenerateDenseElement(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
+ JSObject* obj, const Value& idval, Register object,
+ TypedOrValueRegister index, TypedOrValueRegister output)
+{
+ Label failures;
+
+ // Guard object's shape.
+ RootedShape shape(cx, obj->as<NativeObject>().lastProperty());
+ if (!shape)
+ return false;
+ masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures);
+
+ // Ensure the index is an int32 value.
+ Register indexReg = InvalidReg;
+
+ if (index.hasValue()) {
+ indexReg = output.scratchReg().gpr();
+ MOZ_ASSERT(indexReg != InvalidReg);
+ ValueOperand val = index.valueReg();
+
+ masm.branchTestInt32(Assembler::NotEqual, val, &failures);
+
+ // Unbox the index.
+ masm.unboxInt32(val, indexReg);
+ } else {
+ MOZ_ASSERT(!index.typedReg().isFloat());
+ indexReg = index.typedReg().gpr();
+ }
+
+ // Load elements vector.
+ masm.push(object);
+ masm.loadPtr(Address(object, NativeObject::offsetOfElements()), object);
+
+ Label hole;
+
+ // Guard on the initialized length.
+ Address initLength(object, ObjectElements::offsetOfInitializedLength());
+ masm.branch32(Assembler::BelowOrEqual, initLength, indexReg, &hole);
+
+ // Check for holes & load the value.
+ masm.loadElementTypedOrValue(BaseObjectElementIndex(object, indexReg),
+ output, true, &hole);
+
+ masm.pop(object);
+ attacher.jumpRejoin(masm);
+
+ // All failures flow to here.
+ masm.bind(&hole);
+ masm.pop(object);
+ masm.bind(&failures);
+
+ attacher.jumpNextStub(masm);
+
+ return true;
+}
+
+bool
+GetPropertyIC::tryAttachDenseElement(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleValue idval, bool* emitted)
+{
+ MOZ_ASSERT(canAttachStub());
+ MOZ_ASSERT(!*emitted);
+
+ if (hasDenseStub())
+ return true;
+
+ if (!obj->isNative() || !idval.isInt32())
+ return true;
+
+ if (uint32_t(idval.toInt32()) >= obj->as<NativeObject>().getDenseInitializedLength())
+ return true;
+
+ *emitted = true;
+
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+ if (!GenerateDenseElement(cx, masm, attacher, obj, idval, object(), id().reg(), output()))
+ return false;
+
+ setHasDenseStub();
+ return linkAndAttachStub(cx, masm, attacher, ion, "dense array",
+ JS::TrackedOutcome::ICGetElemStub_Dense);
+}
+
+
+/* static */ bool
+GetPropertyIC::canAttachDenseElementHole(JSObject* obj, HandleValue idval, TypedOrValueRegister output)
+{
+
+ if (!idval.isInt32() || idval.toInt32() < 0)
+ return false;
+
+ if (!output.hasValue())
+ return false;
+
+ if (!obj->isNative())
+ return false;
+
+ if (obj->as<NativeObject>().getDenseInitializedLength() == 0)
+ return false;
+
+ do {
+ if (obj->isIndexed())
+ return false;
+
+ if (ClassCanHaveExtraProperties(obj->getClass()))
+ return false;
+
+ JSObject* proto = obj->staticPrototype();
+ if (!proto)
+ break;
+
+ if (!proto->isNative())
+ return false;
+
+ // Make sure objects on the prototype don't have dense elements.
+ if (proto->as<NativeObject>().getDenseInitializedLength() != 0)
+ return false;
+
+ obj = proto;
+ } while (obj);
+
+ return true;
+}
+
+static bool
+GenerateDenseElementHole(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
+ IonScript* ion, JSObject* obj, HandleValue idval,
+ Register object, TypedOrValueRegister index, TypedOrValueRegister output)
+{
+ MOZ_ASSERT(GetPropertyIC::canAttachDenseElementHole(obj, idval, output));
+
+ Register scratchReg = output.valueReg().scratchReg();
+
+ // Guard on the shape and group, to prevent non-dense elements from appearing.
+ Label failures;
+ attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
+ Address(object, ShapedObject::offsetOfShape()),
+ ImmGCPtr(obj->as<NativeObject>().lastProperty()), &failures);
+
+
+ if (obj->hasUncacheableProto()) {
+ masm.loadPtr(Address(object, JSObject::offsetOfGroup()), scratchReg);
+ Address proto(scratchReg, ObjectGroup::offsetOfProto());
+ masm.branchPtr(Assembler::NotEqual, proto, ImmGCPtr(obj->staticPrototype()), &failures);
+ }
+
+ JSObject* pobj = obj->staticPrototype();
+ while (pobj) {
+ MOZ_ASSERT(pobj->as<NativeObject>().lastProperty());
+
+ masm.movePtr(ImmGCPtr(pobj), scratchReg);
+
+ // Non-singletons with uncacheable protos can change their proto
+ // without a shape change, so also guard on the group (which determines
+ // the proto) in this case.
+ if (pobj->hasUncacheableProto() && !pobj->isSingleton()) {
+ Address groupAddr(scratchReg, JSObject::offsetOfGroup());
+ masm.branchPtr(Assembler::NotEqual, groupAddr, ImmGCPtr(pobj->group()), &failures);
+ }
+
+ // Make sure the shape matches, to avoid non-dense elements.
+ masm.branchPtr(Assembler::NotEqual, Address(scratchReg, ShapedObject::offsetOfShape()),
+ ImmGCPtr(pobj->as<NativeObject>().lastProperty()), &failures);
+
+ // Load elements vector.
+ masm.loadPtr(Address(scratchReg, NativeObject::offsetOfElements()), scratchReg);
+
+ // Also make sure there are no dense elements.
+ Label hole;
+ Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength());
+ masm.branch32(Assembler::NotEqual, initLength, Imm32(0), &failures);
+
+ pobj = pobj->staticPrototype();
+ }
+
+ // Ensure the index is an int32 value.
+ Register indexReg;
+ if (index.hasValue()) {
+ // Unbox the index.
+ ValueOperand val = index.valueReg();
+ masm.branchTestInt32(Assembler::NotEqual, val, &failures);
+ indexReg = scratchReg;
+ masm.unboxInt32(val, indexReg);
+ } else {
+ MOZ_ASSERT(index.type() == MIRType::Int32);
+ indexReg = index.typedReg().gpr();
+ }
+
+ // Make sure index is nonnegative.
+ masm.branch32(Assembler::LessThan, indexReg, Imm32(0), &failures);
+
+ // Save the object register.
+ Register elementsReg = object;
+ masm.push(object);
+
+ // Load elements vector.
+ masm.loadPtr(Address(object, NativeObject::offsetOfElements()), elementsReg);
+
+ // Guard on the initialized length.
+ Label hole;
+ Address initLength(elementsReg, ObjectElements::offsetOfInitializedLength());
+ masm.branch32(Assembler::BelowOrEqual, initLength, indexReg, &hole);
+
+ // Load the value.
+ Label done;
+ masm.loadValue(BaseObjectElementIndex(elementsReg, indexReg), output.valueReg());
+ masm.branchTestMagic(Assembler::NotEqual, output.valueReg(), &done);
+
+ // Load undefined for the hole.
+ masm.bind(&hole);
+ masm.moveValue(UndefinedValue(), output.valueReg());
+
+ masm.bind(&done);
+ // Restore the object register.
+ if (elementsReg == object)
+ masm.pop(object);
+ attacher.jumpRejoin(masm);
+
+ // All failure flows through here.
+ masm.bind(&failures);
+ attacher.jumpNextStub(masm);
+
+ return true;
+}
+
+bool
+GetPropertyIC::tryAttachDenseElementHole(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleValue idval, bool* emitted)
+{
+ MOZ_ASSERT(canAttachStub());
+ MOZ_ASSERT(!*emitted);
+
+ if (!monitoredResult())
+ return true;
+
+ if (!canAttachDenseElementHole(obj, idval, output()))
+ return true;
+
+ *emitted = true;
+
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+ GenerateDenseElementHole(cx, masm, attacher, ion, obj, idval, object(), id().reg(), output());
+
+ return linkAndAttachStub(cx, masm, attacher, ion, "dense hole",
+ JS::TrackedOutcome::ICGetElemStub_DenseHole);
+}
+
+/* static */ bool
+GetPropertyIC::canAttachTypedOrUnboxedArrayElement(JSObject* obj, const Value& idval,
+ TypedOrValueRegister output)
+{
+ if (!obj->is<TypedArrayObject>() && !obj->is<UnboxedArrayObject>())
+ return false;
+
+ MOZ_ASSERT(idval.isInt32() || idval.isString());
+
+ // Don't emit a stub if the access is out of bounds. We make to make
+ // certain that we monitor the type coming out of the typed array when
+ // we generate the stub. Out of bounds accesses will hit the fallback
+ // path.
+ uint32_t index;
+ if (idval.isInt32()) {
+ index = idval.toInt32();
+ } else {
+ index = GetIndexFromString(idval.toString());
+ if (index == UINT32_MAX)
+ return false;
+ }
+
+ if (obj->is<TypedArrayObject>()) {
+ if (index >= obj->as<TypedArrayObject>().length())
+ return false;
+
+ // The output register is not yet specialized as a float register, the only
+ // way to accept float typed arrays for now is to return a Value type.
+ uint32_t arrayType = obj->as<TypedArrayObject>().type();
+ if (arrayType == Scalar::Float32 || arrayType == Scalar::Float64)
+ return output.hasValue();
+
+ return output.hasValue() || !output.typedReg().isFloat();
+ }
+
+ if (index >= obj->as<UnboxedArrayObject>().initializedLength())
+ return false;
+
+ JSValueType elementType = obj->as<UnboxedArrayObject>().elementType();
+ if (elementType == JSVAL_TYPE_DOUBLE)
+ return output.hasValue();
+
+ return output.hasValue() || !output.typedReg().isFloat();
+}
+
+static void
+GenerateGetTypedOrUnboxedArrayElement(JSContext* cx, MacroAssembler& masm,
+ IonCache::StubAttacher& attacher,
+ HandleObject array, const Value& idval, Register object,
+ const ConstantOrRegister& index, TypedOrValueRegister output,
+ bool allowDoubleResult)
+{
+ MOZ_ASSERT(GetPropertyIC::canAttachTypedOrUnboxedArrayElement(array, idval, output));
+
+ Label failures;
+
+ TestMatchingReceiver(masm, attacher, object, array, &failures);
+
+ // Decide to what type index the stub should be optimized
+ Register tmpReg = output.scratchReg().gpr();
+ MOZ_ASSERT(tmpReg != InvalidReg);
+ Register indexReg = tmpReg;
+ if (idval.isString()) {
+ MOZ_ASSERT(GetIndexFromString(idval.toString()) != UINT32_MAX);
+
+ if (index.constant()) {
+ MOZ_ASSERT(idval == index.value());
+ masm.move32(Imm32(GetIndexFromString(idval.toString())), indexReg);
+ } else {
+ // Part 1: Get the string into a register
+ Register str;
+ if (index.reg().hasValue()) {
+ ValueOperand val = index.reg().valueReg();
+ masm.branchTestString(Assembler::NotEqual, val, &failures);
+
+ str = masm.extractString(val, indexReg);
+ } else {
+ MOZ_ASSERT(!index.reg().typedReg().isFloat());
+ str = index.reg().typedReg().gpr();
+ }
+
+ // Part 2: Call to translate the str into index
+ AllocatableRegisterSet regs(RegisterSet::Volatile());
+ LiveRegisterSet save(regs.asLiveSet());
+ masm.PushRegsInMask(save);
+ regs.takeUnchecked(str);
+
+ Register temp = regs.takeAnyGeneral();
+
+ masm.setupUnalignedABICall(temp);
+ masm.passABIArg(str);
+ masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, GetIndexFromString));
+ masm.mov(ReturnReg, indexReg);
+
+ LiveRegisterSet ignore;
+ ignore.add(indexReg);
+ masm.PopRegsInMaskIgnore(save, ignore);
+
+ masm.branch32(Assembler::Equal, indexReg, Imm32(UINT32_MAX), &failures);
+ }
+ } else {
+ MOZ_ASSERT(idval.isInt32());
+ MOZ_ASSERT(!index.constant());
+
+ if (index.reg().hasValue()) {
+ ValueOperand val = index.reg().valueReg();
+ masm.branchTestInt32(Assembler::NotEqual, val, &failures);
+
+ // Unbox the index.
+ masm.unboxInt32(val, indexReg);
+ } else {
+ MOZ_ASSERT(!index.reg().typedReg().isFloat());
+ indexReg = index.reg().typedReg().gpr();
+ }
+ }
+
+ Label popObjectAndFail;
+
+ if (array->is<TypedArrayObject>()) {
+ // Guard on the initialized length.
+ Address length(object, TypedArrayObject::lengthOffset());
+ masm.branch32(Assembler::BelowOrEqual, length, indexReg, &failures);
+
+ // Save the object register on the stack in case of failure.
+ Register elementReg = object;
+ masm.push(object);
+
+ // Load elements vector.
+ masm.loadPtr(Address(object, TypedArrayObject::dataOffset()), elementReg);
+
+ // Load the value. We use an invalid register because the destination
+ // register is necessary a non double register.
+ Scalar::Type arrayType = array->as<TypedArrayObject>().type();
+ int width = Scalar::byteSize(arrayType);
+ BaseIndex source(elementReg, indexReg, ScaleFromElemWidth(width));
+ if (output.hasValue()) {
+ masm.loadFromTypedArray(arrayType, source, output.valueReg(), allowDoubleResult,
+ elementReg, &popObjectAndFail);
+ } else {
+ masm.loadFromTypedArray(arrayType, source, output.typedReg(), elementReg, &popObjectAndFail);
+ }
+ } else {
+ // Save the object register on the stack in case of failure.
+ masm.push(object);
+
+ // Guard on the initialized length.
+ masm.load32(Address(object, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()), object);
+ masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), object);
+ masm.branch32(Assembler::BelowOrEqual, object, indexReg, &popObjectAndFail);
+
+ // Load elements vector.
+ Register elementReg = object;
+ masm.loadPtr(Address(masm.getStackPointer(), 0), object);
+ masm.loadPtr(Address(object, UnboxedArrayObject::offsetOfElements()), elementReg);
+
+ JSValueType elementType = array->as<UnboxedArrayObject>().elementType();
+ BaseIndex source(elementReg, indexReg, ScaleFromElemWidth(UnboxedTypeSize(elementType)));
+ masm.loadUnboxedProperty(source, elementType, output);
+ }
+
+ masm.pop(object);
+ attacher.jumpRejoin(masm);
+
+ // Restore the object before continuing to the next stub.
+ masm.bind(&popObjectAndFail);
+ masm.pop(object);
+ masm.bind(&failures);
+
+ attacher.jumpNextStub(masm);
+}
+
+bool
+GetPropertyIC::tryAttachTypedOrUnboxedArrayElement(JSContext* cx, HandleScript outerScript,
+ IonScript* ion, HandleObject obj,
+ HandleValue idval, bool* emitted)
+{
+ MOZ_ASSERT(canAttachStub());
+ MOZ_ASSERT(!*emitted);
+
+ if (!canAttachTypedOrUnboxedArrayElement(obj, idval, output()))
+ return true;
+
+ *emitted = true;
+
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+ GenerateGetTypedOrUnboxedArrayElement(cx, masm, attacher, obj, idval, object(), id(),
+ output(), allowDoubleResult_);
+ return linkAndAttachStub(cx, masm, attacher, ion, "typed array",
+ JS::TrackedOutcome::ICGetElemStub_TypedArray);
+}
+
+bool
+GetPropertyIC::tryAttachArgumentsElement(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleValue idval, bool* emitted)
+{
+ MOZ_ASSERT(canAttachStub());
+ MOZ_ASSERT(!*emitted);
+
+ if (!IsOptimizableArgumentsObjectForGetElem(obj, idval))
+ return true;
+
+ MOZ_ASSERT(obj->is<ArgumentsObject>());
+
+ if (hasArgumentsElementStub(obj->is<MappedArgumentsObject>()))
+ return true;
+
+ TypedOrValueRegister index = id().reg();
+ if (index.type() != MIRType::Value && index.type() != MIRType::Int32)
+ return true;
+
+ MOZ_ASSERT(output().hasValue());
+
+ *emitted = true;
+
+ Label failures;
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+
+ Register tmpReg = output().scratchReg().gpr();
+ MOZ_ASSERT(tmpReg != InvalidReg);
+
+ masm.branchTestObjClass(Assembler::NotEqual, object(), tmpReg, obj->getClass(), &failures);
+
+ // Get initial ArgsObj length value, test if length or any element have
+ // been overridden.
+ masm.unboxInt32(Address(object(), ArgumentsObject::getInitialLengthSlotOffset()), tmpReg);
+ masm.branchTest32(Assembler::NonZero, tmpReg,
+ Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT |
+ ArgumentsObject::ELEMENT_OVERRIDDEN_BIT),
+ &failures);
+ masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), tmpReg);
+
+ // Decide to what type index the stub should be optimized
+ Register indexReg;
+
+ // Check index against length.
+ Label failureRestoreIndex;
+ if (index.hasValue()) {
+ ValueOperand val = index.valueReg();
+ masm.branchTestInt32(Assembler::NotEqual, val, &failures);
+ indexReg = val.scratchReg();
+
+ masm.unboxInt32(val, indexReg);
+ masm.branch32(Assembler::AboveOrEqual, indexReg, tmpReg, &failureRestoreIndex);
+ } else {
+ MOZ_ASSERT(index.type() == MIRType::Int32);
+ indexReg = index.typedReg().gpr();
+ masm.branch32(Assembler::AboveOrEqual, indexReg, tmpReg, &failures);
+ }
+
+ // Fail if we have a RareArgumentsData (elements were deleted).
+ masm.loadPrivate(Address(object(), ArgumentsObject::getDataSlotOffset()), tmpReg);
+ masm.branchPtr(Assembler::NotEqual,
+ Address(tmpReg, offsetof(ArgumentsData, rareData)),
+ ImmWord(0),
+ &failureRestoreIndex);
+
+ // Get the address to load from into tmpReg
+ masm.loadPrivate(Address(object(), ArgumentsObject::getDataSlotOffset()), tmpReg);
+ masm.addPtr(Imm32(ArgumentsData::offsetOfArgs()), tmpReg);
+
+ BaseValueIndex elemIdx(tmpReg, indexReg);
+
+ // Ensure result is not magic value, and type-check result.
+ masm.branchTestMagic(Assembler::Equal, elemIdx, &failureRestoreIndex);
+
+ masm.loadTypedOrValue(elemIdx, output());
+
+ // indexReg may need to be reconstructed if it was originally a value.
+ if (index.hasValue())
+ masm.tagValue(JSVAL_TYPE_INT32, indexReg, index.valueReg());
+
+ // Success.
+ attacher.jumpRejoin(masm);
+
+ // Restore the object before continuing to the next stub.
+ masm.pop(indexReg);
+ masm.bind(&failureRestoreIndex);
+ if (index.hasValue())
+ masm.tagValue(JSVAL_TYPE_INT32, indexReg, index.valueReg());
+ masm.bind(&failures);
+ attacher.jumpNextStub(masm);
+
+ if (obj->is<UnmappedArgumentsObject>()) {
+ MOZ_ASSERT(!hasUnmappedArgumentsElementStub_);
+ hasUnmappedArgumentsElementStub_ = true;
+ return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj element (unmapped)",
+ JS::TrackedOutcome::ICGetElemStub_ArgsElementUnmapped);
+ }
+
+ MOZ_ASSERT(!hasMappedArgumentsElementStub_);
+ hasMappedArgumentsElementStub_ = true;
+ return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj element (mapped)",
+ JS::TrackedOutcome::ICGetElemStub_ArgsElementMapped);
+}
+
+static bool
+IsDenseElementSetInlineable(JSObject* obj, const Value& idval, const ConstantOrRegister& val,
+ bool needsTypeBarrier, bool* checkTypeset)
+{
+ if (!obj->is<ArrayObject>())
+ return false;
+
+ if (obj->watched())
+ return false;
+
+ if (!idval.isInt32())
+ return false;
+
+ // The object may have a setter definition,
+ // either directly, or via a prototype, or via the target object for a prototype
+ // which is a proxy, that handles a particular integer write.
+ // Scan the prototype and shape chain to make sure that this is not the case.
+ JSObject* curObj = obj;
+ while (curObj) {
+ // Ensure object is native. (This guarantees static prototype below.)
+ if (!curObj->isNative())
+ return false;
+
+ // Ensure all indexed properties are stored in dense elements.
+ if (curObj->isIndexed())
+ return false;
+
+ curObj = curObj->staticPrototype();
+ }
+
+ *checkTypeset = false;
+ if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, JSID_VOID, val, checkTypeset))
+ return false;
+
+ return true;
+}
+
+static bool
+IsTypedArrayElementSetInlineable(JSObject* obj, const Value& idval, const Value& value)
+{
+ // Don't bother attaching stubs for assigning strings, objects or symbols.
+ return obj->is<TypedArrayObject>() && idval.isInt32() &&
+ !value.isString() && !value.isObject() && !value.isSymbol();
+}
+
+static void
+StoreDenseElement(MacroAssembler& masm, const ConstantOrRegister& value, Register elements,
+ BaseObjectElementIndex target)
+{
+ // If the ObjectElements::CONVERT_DOUBLE_ELEMENTS flag is set, int32 values
+ // have to be converted to double first. If the value is not int32, it can
+ // always be stored directly.
+
+ Address elementsFlags(elements, ObjectElements::offsetOfFlags());
+ if (value.constant()) {
+ Value v = value.value();
+ Label done;
+ if (v.isInt32()) {
+ Label dontConvert;
+ masm.branchTest32(Assembler::Zero, elementsFlags,
+ Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS),
+ &dontConvert);
+ masm.storeValue(DoubleValue(v.toInt32()), target);
+ masm.jump(&done);
+ masm.bind(&dontConvert);
+ }
+ masm.storeValue(v, target);
+ masm.bind(&done);
+ return;
+ }
+
+ TypedOrValueRegister reg = value.reg();
+ if (reg.hasTyped() && reg.type() != MIRType::Int32) {
+ masm.storeTypedOrValue(reg, target);
+ return;
+ }
+
+ Label convert, storeValue, done;
+ masm.branchTest32(Assembler::NonZero, elementsFlags,
+ Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS),
+ &convert);
+ masm.bind(&storeValue);
+ masm.storeTypedOrValue(reg, target);
+ masm.jump(&done);
+
+ masm.bind(&convert);
+ if (reg.hasValue()) {
+ masm.branchTestInt32(Assembler::NotEqual, reg.valueReg(), &storeValue);
+ masm.int32ValueToDouble(reg.valueReg(), ScratchDoubleReg);
+ masm.storeDouble(ScratchDoubleReg, target);
+ } else {
+ MOZ_ASSERT(reg.type() == MIRType::Int32);
+ masm.convertInt32ToDouble(reg.typedReg().gpr(), ScratchDoubleReg);
+ masm.storeDouble(ScratchDoubleReg, target);
+ }
+
+ masm.bind(&done);
+}
+
+static bool
+GenerateSetDenseElement(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
+ JSObject* obj, const Value& idval, bool guardHoles, Register object,
+ TypedOrValueRegister index, const ConstantOrRegister& value,
+ Register tempToUnboxIndex, Register temp,
+ bool needsTypeBarrier, bool checkTypeset)
+{
+ MOZ_ASSERT(obj->isNative());
+ MOZ_ASSERT(idval.isInt32());
+
+ Label failures;
+
+ // Guard object is a dense array.
+ Shape* shape = obj->as<NativeObject>().lastProperty();
+ if (!shape)
+ return false;
+ masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures);
+
+ // Guard that the incoming value is in the type set for the property
+ // if a type barrier is required.
+ if (needsTypeBarrier) {
+ masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), &failures);
+ if (checkTypeset)
+ CheckTypeSetForWrite(masm, obj, JSID_VOID, temp, value, &failures);
+ }
+
+ // Ensure the index is an int32 value.
+ Register indexReg;
+ if (index.hasValue()) {
+ ValueOperand val = index.valueReg();
+ masm.branchTestInt32(Assembler::NotEqual, val, &failures);
+
+ indexReg = masm.extractInt32(val, tempToUnboxIndex);
+ } else {
+ MOZ_ASSERT(!index.typedReg().isFloat());
+ indexReg = index.typedReg().gpr();
+ }
+
+ {
+ // Load obj->elements.
+ Register elements = temp;
+ masm.loadPtr(Address(object, NativeObject::offsetOfElements()), elements);
+
+ // Compute the location of the element.
+ BaseObjectElementIndex target(elements, indexReg);
+
+ Label storeElement;
+
+ // If TI cannot help us deal with HOLES by preventing indexed properties
+ // on the prototype chain, we have to be very careful to check for ourselves
+ // to avoid stomping on what should be a setter call. Start by only allowing things
+ // within the initialized length.
+ if (guardHoles) {
+ Address initLength(elements, ObjectElements::offsetOfInitializedLength());
+ masm.branch32(Assembler::BelowOrEqual, initLength, indexReg, &failures);
+ } else {
+ // Guard that we can increase the initialized length.
+ Address capacity(elements, ObjectElements::offsetOfCapacity());
+ masm.branch32(Assembler::BelowOrEqual, capacity, indexReg, &failures);
+
+ // Guard on the initialized length.
+ Address initLength(elements, ObjectElements::offsetOfInitializedLength());
+ masm.branch32(Assembler::Below, initLength, indexReg, &failures);
+
+ // if (initLength == index)
+ Label inBounds;
+ masm.branch32(Assembler::NotEqual, initLength, indexReg, &inBounds);
+ {
+ // Increase initialize length.
+ Register newLength = indexReg;
+ masm.add32(Imm32(1), newLength);
+ masm.store32(newLength, initLength);
+
+ // Increase length if needed.
+ Label bumpedLength;
+ Address length(elements, ObjectElements::offsetOfLength());
+ masm.branch32(Assembler::AboveOrEqual, length, indexReg, &bumpedLength);
+ masm.store32(newLength, length);
+ masm.bind(&bumpedLength);
+
+ // Restore the index.
+ masm.add32(Imm32(-1), newLength);
+ masm.jump(&storeElement);
+ }
+ // else
+ masm.bind(&inBounds);
+ }
+
+ if (cx->zone()->needsIncrementalBarrier())
+ masm.callPreBarrier(target, MIRType::Value);
+
+ // Store the value.
+ if (guardHoles)
+ masm.branchTestMagic(Assembler::Equal, target, &failures);
+ else
+ masm.bind(&storeElement);
+ StoreDenseElement(masm, value, elements, target);
+ }
+ attacher.jumpRejoin(masm);
+
+ masm.bind(&failures);
+ attacher.jumpNextStub(masm);
+
+ return true;
+}
+
+bool
+SetPropertyIC::tryAttachDenseElement(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, const Value& idval, bool* emitted)
+{
+ MOZ_ASSERT(!*emitted);
+ MOZ_ASSERT(canAttachStub());
+
+ if (hasDenseStub())
+ return true;
+
+ bool checkTypeset = false;
+ if (!IsDenseElementSetInlineable(obj, idval, value(), needsTypeBarrier(), &checkTypeset))
+ return true;
+
+ *emitted = true;
+
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+ if (!GenerateSetDenseElement(cx, masm, attacher, obj, idval,
+ guardHoles(), object(), id().reg(),
+ value(), tempToUnboxIndex(), temp(),
+ needsTypeBarrier(), checkTypeset))
+ {
+ return false;
+ }
+
+ setHasDenseStub();
+ const char* message = guardHoles() ? "dense array (holes)" : "dense array";
+ return linkAndAttachStub(cx, masm, attacher, ion, message,
+ JS::TrackedOutcome::ICSetElemStub_Dense);
+}
+
+static bool
+GenerateSetTypedArrayElement(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
+ HandleObject tarr, Register object, TypedOrValueRegister index,
+ const ConstantOrRegister& value, Register tempUnbox, Register temp,
+ FloatRegister tempDouble, FloatRegister tempFloat32)
+{
+ Label failures, done, popObjectAndFail;
+
+ // Guard on the shape.
+ Shape* shape = tarr->as<TypedArrayObject>().lastProperty();
+ if (!shape)
+ return false;
+ masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures);
+
+ // Ensure the index is an int32.
+ Register indexReg;
+ if (index.hasValue()) {
+ ValueOperand val = index.valueReg();
+ masm.branchTestInt32(Assembler::NotEqual, val, &failures);
+
+ indexReg = masm.extractInt32(val, tempUnbox);
+ } else {
+ MOZ_ASSERT(!index.typedReg().isFloat());
+ indexReg = index.typedReg().gpr();
+ }
+
+ // Guard on the length.
+ Address length(object, TypedArrayObject::lengthOffset());
+ masm.unboxInt32(length, temp);
+ masm.branch32(Assembler::BelowOrEqual, temp, indexReg, &done);
+
+ // Load the elements vector.
+ Register elements = temp;
+ masm.loadPtr(Address(object, TypedArrayObject::dataOffset()), elements);
+
+ // Set the value.
+ Scalar::Type arrayType = tarr->as<TypedArrayObject>().type();
+ int width = Scalar::byteSize(arrayType);
+ BaseIndex target(elements, indexReg, ScaleFromElemWidth(width));
+
+ if (arrayType == Scalar::Float32) {
+ MOZ_ASSERT_IF(hasUnaliasedDouble(), tempFloat32 != InvalidFloatReg);
+ FloatRegister tempFloat = hasUnaliasedDouble() ? tempFloat32 : tempDouble;
+ if (!masm.convertConstantOrRegisterToFloat(cx, value, tempFloat, &failures))
+ return false;
+ masm.storeToTypedFloatArray(arrayType, tempFloat, target);
+ } else if (arrayType == Scalar::Float64) {
+ if (!masm.convertConstantOrRegisterToDouble(cx, value, tempDouble, &failures))
+ return false;
+ masm.storeToTypedFloatArray(arrayType, tempDouble, target);
+ } else {
+ // On x86 we only have 6 registers available to use, so reuse the object
+ // register to compute the intermediate value to store and restore it
+ // afterwards.
+ masm.push(object);
+
+ if (arrayType == Scalar::Uint8Clamped) {
+ if (!masm.clampConstantOrRegisterToUint8(cx, value, tempDouble, object,
+ &popObjectAndFail))
+ {
+ return false;
+ }
+ } else {
+ if (!masm.truncateConstantOrRegisterToInt32(cx, value, tempDouble, object,
+ &popObjectAndFail))
+ {
+ return false;
+ }
+ }
+ masm.storeToTypedIntArray(arrayType, object, target);
+
+ masm.pop(object);
+ }
+
+ // Out-of-bound writes jump here as they are no-ops.
+ masm.bind(&done);
+ attacher.jumpRejoin(masm);
+
+ if (popObjectAndFail.used()) {
+ masm.bind(&popObjectAndFail);
+ masm.pop(object);
+ }
+
+ masm.bind(&failures);
+ attacher.jumpNextStub(masm);
+ return true;
+}
+
+bool
+SetPropertyIC::tryAttachTypedArrayElement(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleValue idval, HandleValue val,
+ bool* emitted)
+{
+ MOZ_ASSERT(!*emitted);
+ MOZ_ASSERT(canAttachStub());
+
+ if (!IsTypedArrayElementSetInlineable(obj, idval, val))
+ return true;
+
+ *emitted = true;
+
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+ if (!GenerateSetTypedArrayElement(cx, masm, attacher, obj,
+ object(), id().reg(), value(),
+ tempToUnboxIndex(), temp(), tempDouble(), tempFloat32()))
+ {
+ return false;
+ }
+
+ return linkAndAttachStub(cx, masm, attacher, ion, "typed array",
+ JS::TrackedOutcome::ICSetElemStub_TypedArray);
+}
+
+bool
+BindNameIC::attachGlobal(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject envChain)
+{
+ MOZ_ASSERT(envChain->is<GlobalObject>());
+
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+
+ // Guard on the env chain.
+ attacher.branchNextStub(masm, Assembler::NotEqual, environmentChainReg(),
+ ImmGCPtr(envChain));
+ masm.movePtr(ImmGCPtr(envChain), outputReg());
+
+ attacher.jumpRejoin(masm);
+
+ return linkAndAttachStub(cx, masm, attacher, ion, "global");
+}
+
+static inline void
+GenerateEnvironmentChainGuard(MacroAssembler& masm, JSObject* envObj,
+ Register envObjReg, Shape* shape, Label* failures)
+{
+ if (envObj->is<CallObject>()) {
+ // We can skip a guard on the call object if the script's bindings are
+ // guaranteed to be immutable (and thus cannot introduce shadowing
+ // variables).
+ CallObject* callObj = &envObj->as<CallObject>();
+ JSFunction* fun = &callObj->callee();
+ // The function might have been relazified under rare conditions.
+ // In that case, we pessimistically create the guard, as we'd
+ // need to root various pointers to delazify,
+ if (fun->hasScript()) {
+ JSScript* script = fun->nonLazyScript();
+ if (!script->funHasExtensibleScope())
+ return;
+ }
+ } else if (envObj->is<GlobalObject>()) {
+ // If this is the last object on the scope walk, and the property we've
+ // found is not configurable, then we don't need a shape guard because
+ // the shape cannot be removed.
+ if (shape && !shape->configurable())
+ return;
+ }
+
+ Address shapeAddr(envObjReg, ShapedObject::offsetOfShape());
+ masm.branchPtr(Assembler::NotEqual, shapeAddr,
+ ImmGCPtr(envObj->as<NativeObject>().lastProperty()), failures);
+}
+
+static void
+GenerateEnvironmentChainGuards(MacroAssembler& masm, JSObject* envChain, JSObject* holder,
+ Register outputReg, Label* failures, bool skipLastGuard = false)
+{
+ JSObject* tobj = envChain;
+
+ // Walk up the env chain. Note that IsCacheableEnvironmentChain guarantees the
+ // |tobj == holder| condition terminates the loop.
+ while (true) {
+ MOZ_ASSERT(IsCacheableEnvironment(tobj) || tobj->is<GlobalObject>());
+
+ if (skipLastGuard && tobj == holder)
+ break;
+
+ GenerateEnvironmentChainGuard(masm, tobj, outputReg, nullptr, failures);
+
+ if (tobj == holder)
+ break;
+
+ // Load the next link.
+ tobj = &tobj->as<EnvironmentObject>().enclosingEnvironment();
+ masm.extractObject(Address(outputReg, EnvironmentObject::offsetOfEnclosingEnvironment()),
+ outputReg);
+ }
+}
+
+bool
+BindNameIC::attachNonGlobal(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject envChain, HandleObject holder)
+{
+ MOZ_ASSERT(IsCacheableEnvironment(envChain));
+
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+
+ // Guard on the shape of the env chain.
+ Label failures;
+ attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
+ Address(environmentChainReg(), ShapedObject::offsetOfShape()),
+ ImmGCPtr(envChain->as<NativeObject>().lastProperty()),
+ holder != envChain ? &failures : nullptr);
+
+ if (holder != envChain) {
+ JSObject* parent = &envChain->as<EnvironmentObject>().enclosingEnvironment();
+ masm.extractObject(Address(environmentChainReg(),
+ EnvironmentObject::offsetOfEnclosingEnvironment()),
+ outputReg());
+
+ GenerateEnvironmentChainGuards(masm, parent, holder, outputReg(), &failures);
+ } else {
+ masm.movePtr(environmentChainReg(), outputReg());
+ }
+
+ // At this point outputReg holds the object on which the property
+ // was found, so we're done.
+ attacher.jumpRejoin(masm);
+
+ // All failures flow to here, so there is a common point to patch.
+ if (holder != envChain) {
+ masm.bind(&failures);
+ attacher.jumpNextStub(masm);
+ }
+
+ return linkAndAttachStub(cx, masm, attacher, ion, "non-global");
+}
+
+static bool
+IsCacheableNonGlobalEnvironmentChain(JSObject* envChain, JSObject* holder)
+{
+ while (true) {
+ if (!IsCacheableEnvironment(envChain)) {
+ JitSpew(JitSpew_IonIC, "Non-cacheable object on env chain");
+ return false;
+ }
+
+ if (envChain == holder)
+ return true;
+
+ envChain = &envChain->as<EnvironmentObject>().enclosingEnvironment();
+ if (!envChain) {
+ JitSpew(JitSpew_IonIC, "env chain indirect hit");
+ return false;
+ }
+ }
+
+ MOZ_CRASH("Invalid env chain");
+}
+
+JSObject*
+BindNameIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex,
+ HandleObject envChain)
+{
+ IonScript* ion = outerScript->ionScript();
+ BindNameIC& cache = ion->getCache(cacheIndex).toBindName();
+ HandlePropertyName name = cache.name();
+
+ RootedObject holder(cx);
+ if (!LookupNameUnqualified(cx, name, envChain, &holder))
+ return nullptr;
+
+ // Stop generating new stubs once we hit the stub count limit, see
+ // GetPropertyCache.
+ if (cache.canAttachStub()) {
+ if (envChain->is<GlobalObject>()) {
+ if (!cache.attachGlobal(cx, outerScript, ion, envChain))
+ return nullptr;
+ } else if (IsCacheableNonGlobalEnvironmentChain(envChain, holder)) {
+ if (!cache.attachNonGlobal(cx, outerScript, ion, envChain, holder))
+ return nullptr;
+ } else {
+ JitSpew(JitSpew_IonIC, "BINDNAME uncacheable env chain");
+ }
+ }
+
+ return holder;
+}
+
+bool
+NameIC::attachReadSlot(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject envChain, HandleObject holderBase,
+ HandleNativeObject holder, HandleShape shape)
+{
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ Label failures;
+ StubAttacher attacher(*this);
+
+ Register scratchReg = outputReg().valueReg().scratchReg();
+
+ // Don't guard the base of the proto chain the name was found on. It will be guarded
+ // by GenerateReadSlot().
+ masm.mov(environmentChainReg(), scratchReg);
+ GenerateEnvironmentChainGuards(masm, envChain, holderBase, scratchReg, &failures,
+ /* skipLastGuard = */true);
+
+ // GenerateEnvironmentChain leaves the last env chain in scratchReg, even though it
+ // doesn't generate the extra guard.
+ //
+ // NAME ops must do their own TDZ checks.
+ GenerateReadSlot(cx, ion, masm, attacher, CheckTDZ, holderBase, holder, shape, scratchReg,
+ outputReg(), failures.used() ? &failures : nullptr);
+
+ return linkAndAttachStub(cx, masm, attacher, ion, "generic",
+ JS::TrackedOutcome::ICNameStub_ReadSlot);
+}
+
+static bool
+IsCacheableEnvironmentChain(JSObject* envChain, JSObject* obj)
+{
+ JSObject* obj2 = envChain;
+ while (obj2) {
+ if (!IsCacheableEnvironment(obj2) && !obj2->is<GlobalObject>())
+ return false;
+
+ // Stop once we hit the global or target obj.
+ if (obj2->is<GlobalObject>() || obj2 == obj)
+ break;
+
+ obj2 = obj2->enclosingEnvironment();
+ }
+
+ return obj == obj2;
+}
+
+static bool
+IsCacheableNameReadSlot(HandleObject envChain, HandleObject obj,
+ HandleObject holder, HandleShape shape, jsbytecode* pc,
+ const TypedOrValueRegister& output)
+{
+ if (!shape)
+ return false;
+ if (!obj->isNative())
+ return false;
+
+ if (obj->is<GlobalObject>()) {
+ // Support only simple property lookups.
+ if (!IsCacheableGetPropReadSlotForIonOrCacheIR(obj, holder, shape) &&
+ !IsCacheableNoProperty(obj, holder, shape, pc, output))
+ return false;
+ } else if (obj->is<ModuleEnvironmentObject>()) {
+ // We don't yet support lookups in a module environment.
+ return false;
+ } else if (obj->is<CallObject>()) {
+ MOZ_ASSERT(obj == holder);
+ if (!shape->hasDefaultGetter())
+ return false;
+ } else {
+ // We don't yet support lookups on Block or DeclEnv objects.
+ return false;
+ }
+
+ return IsCacheableEnvironmentChain(envChain, obj);
+}
+
+bool
+NameIC::attachCallGetter(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject envChain, HandleObject obj, HandleObject holder,
+ HandleShape shape, void* returnAddr)
+{
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+
+ Label failures;
+ Register scratchReg = outputReg().valueReg().scratchReg();
+
+ // Don't guard the base of the proto chain the name was found on. It will be guarded
+ // by GenerateCallGetter().
+ masm.mov(environmentChainReg(), scratchReg);
+ GenerateEnvironmentChainGuards(masm, envChain, obj, scratchReg, &failures,
+ /* skipLastGuard = */true);
+
+ // GenerateEnvironmentChain leaves the last env chain in scratchReg, even though it
+ // doesn't generate the extra guard.
+ if (!GenerateCallGetter(cx, ion, masm, attacher, obj, holder, shape, liveRegs_,
+ scratchReg, outputReg(), returnAddr,
+ failures.used() ? &failures : nullptr))
+ {
+ return false;
+ }
+
+ const char* attachKind = "name getter";
+ return linkAndAttachStub(cx, masm, attacher, ion, attachKind,
+ JS::TrackedOutcome::ICNameStub_CallGetter);
+}
+
+static bool
+IsCacheableNameCallGetter(HandleObject envChain, HandleObject obj, HandleObject holder,
+ HandleShape shape)
+{
+ if (!shape)
+ return false;
+ if (!obj->is<GlobalObject>())
+ return false;
+
+ if (!IsCacheableEnvironmentChain(envChain, obj))
+ return false;
+
+ return IsCacheableGetPropCallNative(obj, holder, shape) ||
+ IsCacheableGetPropCallPropertyOp(obj, holder, shape) ||
+ IsCacheableGetPropCallScripted(obj, holder, shape);
+}
+
+bool
+NameIC::attachTypeOfNoProperty(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject envChain)
+{
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ Label failures;
+ StubAttacher attacher(*this);
+
+ Register scratchReg = outputReg().valueReg().scratchReg();
+
+ masm.movePtr(environmentChainReg(), scratchReg);
+
+ // Generate env chain guards.
+ // Since the property was not defined on any object, iterate until reaching the global.
+ JSObject* tobj = envChain;
+ while (true) {
+ GenerateEnvironmentChainGuard(masm, tobj, scratchReg, nullptr, &failures);
+
+ if (tobj->is<GlobalObject>())
+ break;
+
+ // Load the next link.
+ tobj = &tobj->as<EnvironmentObject>().enclosingEnvironment();
+ masm.extractObject(Address(scratchReg, EnvironmentObject::offsetOfEnclosingEnvironment()),
+ scratchReg);
+ }
+
+ masm.moveValue(UndefinedValue(), outputReg().valueReg());
+ attacher.jumpRejoin(masm);
+
+ masm.bind(&failures);
+ attacher.jumpNextStub(masm);
+
+ return linkAndAttachStub(cx, masm, attacher, ion, "generic",
+ JS::TrackedOutcome::ICNameStub_TypeOfNoProperty);
+}
+
+static bool
+IsCacheableNameNoProperty(HandleObject envChain, HandleObject obj,
+ HandleObject holder, HandleShape shape, jsbytecode* pc,
+ NameIC& cache)
+{
+ if (cache.isTypeOf() && !shape) {
+ MOZ_ASSERT(!obj);
+ MOZ_ASSERT(!holder);
+ MOZ_ASSERT(envChain);
+
+ // Assert those extra things checked by IsCacheableNoProperty().
+ MOZ_ASSERT(cache.outputReg().hasValue());
+ MOZ_ASSERT(pc != nullptr);
+
+ return true;
+ }
+
+ return false;
+}
+
+bool
+NameIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex, HandleObject envChain,
+ MutableHandleValue vp)
+{
+ IonScript* ion = outerScript->ionScript();
+
+ NameIC& cache = ion->getCache(cacheIndex).toName();
+ RootedPropertyName name(cx, cache.name());
+
+ RootedScript script(cx);
+ jsbytecode* pc;
+ cache.getScriptedLocation(&script, &pc);
+
+ RootedObject obj(cx);
+ RootedObject holder(cx);
+ RootedShape shape(cx);
+ if (!LookupName(cx, name, envChain, &obj, &holder, &shape))
+ return false;
+
+ // Look first. Don't generate cache entries if the lookup fails.
+ if (cache.isTypeOf()) {
+ if (!FetchName<true>(cx, obj, holder, name, shape, vp))
+ return false;
+ } else {
+ if (!FetchName<false>(cx, obj, holder, name, shape, vp))
+ return false;
+ }
+
+ if (cache.canAttachStub()) {
+ if (IsCacheableNameReadSlot(envChain, obj, holder, shape, pc, cache.outputReg())) {
+ if (!cache.attachReadSlot(cx, outerScript, ion, envChain, obj,
+ holder.as<NativeObject>(), shape))
+ {
+ return false;
+ }
+ } else if (IsCacheableNameCallGetter(envChain, obj, holder, shape)) {
+ void* returnAddr = GetReturnAddressToIonCode(cx);
+ if (!cache.attachCallGetter(cx, outerScript, ion, envChain, obj, holder, shape,
+ returnAddr))
+ {
+ return false;
+ }
+ } else if (IsCacheableNameNoProperty(envChain, obj, holder, shape, pc, cache)) {
+ if (!cache.attachTypeOfNoProperty(cx, outerScript, ion, envChain))
+ return false;
+ }
+ }
+
+ // Monitor changes to cache entry.
+ TypeScript::Monitor(cx, script, pc, vp);
+
+ return true;
+}