| /* -*- 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 "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]; |
| } |
| |
| IonCache::LinkStatus |
| IonCache::linkCode(JSContext* cx, MacroAssembler& masm, 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; |
| |
| return LINK_GOOD; |
| } |
| |
| 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 ImmPtr 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(STUB_ADDR); |
| hasStubCodePatchOffset_ = true; |
| } |
| |
| void patchRejoinJump(MacroAssembler& masm, JitCode* code) { |
| rejoinOffset_.fixup(&masm); |
| CodeLocationJump rejoinJump(code, rejoinOffset_); |
| AutoWritableJitCode awjc(code); |
| PatchJump(rejoinJump, rejoinLabel_); |
| } |
| |
| void patchStubCodePointer(JitCode* code) { |
| if (hasStubCodePatchOffset_) { |
| AutoWritableJitCode awjc(code); |
| Assembler::PatchDataWithValueCheck(CodeLocationLabel(code, stubCodePatchOffset_), |
| ImmPtr(code), STUB_ADDR); |
| } |
| } |
| |
| void patchNextStubJump(MacroAssembler& masm, JitCode* code) { |
| // Patch the previous nextStubJump of the last stub, or the jump from the |
| // codeGen, to jump into the newly allocated code. |
| PatchJump(cache_.lastJump_, CodeLocationLabel(code), Reprotect); |
| |
| // If this path is not taken, we are producing an entry which can no |
| // longer go back into the update function. |
| if (hasNextStubOffset_) { |
| AutoWritableJitCode awjc(code); |
| 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 ImmPtr IonCache::StubAttacher::STUB_ADDR = ImmPtr((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, Handle<JitCode*> code) |
| { |
| MOZ_ASSERT(canAttachStub()); |
| incrementStubCount(); |
| |
| // 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); |
| } |
| |
| bool |
| IonCache::linkAndAttachStub(JSContext* cx, MacroAssembler& masm, StubAttacher& attacher, |
| IonScript* ion, const char* attachKind, |
| JS::TrackedOutcome trackedOutcome) |
| { |
| 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, 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, 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); |
| } |
| |
| 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->getProto()), failures); |
| } |
| |
| JSObject* pobj = IsCacheableDOMProxy(obj) |
| ? obj->getTaggedProto().toObjectOrNull() |
| : obj->getProto(); |
| 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->getProto()), failures); |
| } else { |
| masm.branchPtr(Assembler::NotEqual, groupAddr, ImmGCPtr(pobj->group()), failures); |
| } |
| } |
| pobj = pobj->getProto(); |
| } |
| } |
| |
| // Note: This differs from IsCacheableProtoChain in BaselineIC.cpp in that |
| // Ion caches can deal with objects on the proto chain that have uncacheable |
| // prototypes. |
| static bool |
| IsCacheableProtoChainForIon(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->getProto(); |
| if (!proto || !proto->isNative()) |
| return false; |
| obj = proto; |
| } |
| return true; |
| } |
| |
| static bool |
| IsCacheableGetPropReadSlotForIon(JSObject* obj, JSObject* holder, Shape* shape) |
| { |
| if (!shape || !IsCacheableProtoChainForIon(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()->getProperty) |
| 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->getProto(); |
| } |
| |
| // 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, Value idval) |
| { |
| if (!IsOptimizableArgumentsObjectForLength(obj)) |
| return false; |
| |
| ArgumentsObject& argsObj = obj->as<ArgumentsObject>(); |
| |
| if (argsObj.isAnyElementDeleted()) |
| 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 || !IsCacheableProtoChainForIon(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 || !IsCacheableProtoChainForIon(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 || !IsCacheableProtoChainForIon(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, JSObject::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(), JSObject::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, 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>() |
| || (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, JSObject::offsetOfShape()), |
| ImmGCPtr(holder->as<NativeObject>().lastProperty()), |
| &prototypeFailures); |
| } else { |
| // The property does not exist. Guard on everything in the |
| // prototype chain. |
| JSObject* proto = obj->getTaggedProto().toObjectOrNull(); |
| Register lastReg = object; |
| MOZ_ASSERT(scratchReg != object); |
| while (proto) { |
| masm.loadObjProto(lastReg, scratchReg); |
| |
| // Guard the shape of the current prototype. |
| masm.branchPtr(Assembler::NotEqual, |
| Address(scratchReg, JSObject::offsetOfShape()), |
| ImmGCPtr(proto->as<NativeObject>().lastProperty()), |
| &prototypeFailures); |
| |
| proto = proto->getProto(); |
| 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); |
| 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); |
| 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); |
| 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 scope and manually push the global. |
| if (IsGlobalLexicalScope(obj)) |
| masm.extractObject(Address(object, ScopeObject::offsetOfEnclosingScope()), 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, failures); |
| |
| // Guard on the holder's shape. |
| Register holderReg = scratchReg; |
| masm.movePtr(ImmGCPtr(holder), holderReg); |
| masm.branchPtr(Assembler::NotEqual, |
| Address(holderReg, JSObject::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 (IsCacheableGetPropReadSlotForIon(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, 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, 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 (!IsAnyTypedArray(obj)) |
| 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(), JSObject::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->getTaggedProto().toObjectOrNull()); |
| 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(), JSObject::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, JSObject::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()) |
| return true; |
| |
| return Invalidate(cx, outerScript); |
| } |
| |
| 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, 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, |
| 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 || !IsCacheableProtoChainForIon(obj, holder)) |
| return false; |
| |
| return shape->hasSetterValue() && shape->setterObject() && |
| shape->setterObject()->is<JSFunction>() && |
| shape->setterObject()->as<JSFunction>().isNative(); |
| } |
| |
| static bool |
| IsCacheableSetPropCallScripted(HandleObject obj, HandleObject holder, HandleShape shape) |
| { |
| if (!shape || !IsCacheableProtoChainForIon(obj, holder)) |
| 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 (!IsCacheableProtoChainForIon(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, |
| 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(), JSObject::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, |
| 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, JSObject::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); |
| 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); |
| 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->getTaggedProto().toObjectOrNull()); |
| 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(), JSObject::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, 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->getProto(); |
| 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->getProto(); |
| } |
| |
| // 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, JSObject::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, 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, |
| 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->getProto(); proto; proto = proto->getProto()) { |
| // 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, 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()->addProperty) |
| 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, 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, 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, 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, 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, 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; |
| |
| 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; |
| |
| // 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::AutoAssertNoAlloc 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()->lexicalScope(), 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; |
| |
| *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; |
| |
| while (obj) { |
| if (obj->isIndexed()) |
| return false; |
| |
| if (ClassCanHaveExtraProperties(obj->getClass())) |
| return false; |
| |
| JSObject* proto = obj->getProto(); |
| 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; |
| } |
| |
| 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, JSObject::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->getProto()), &failures); |
| } |
| |
| JSObject* pobj = obj->getProto(); |
| while (pobj) { |
| MOZ_ASSERT(pobj->as<NativeObject>().lastProperty()); |
| |
| masm.movePtr(ImmGCPtr(pobj), scratchReg); |
| if (pobj->hasUncacheableProto()) { |
| MOZ_ASSERT(!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, JSObject::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->getProto(); |
| } |
| |
| // 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 (!IsAnyTypedArray(obj) && !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 (IsAnyTypedArray(obj)) { |
| if (index >= AnyTypedArrayLength(obj)) |
| 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 = AnyTypedArrayType(obj); |
| 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, |
| 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 (IsAnyTypedArray(array)) { |
| // 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 = AnyTypedArrayType(array); |
| 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 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); |
| |
| // 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); |
| } |
| // Save indexReg because it needs to be clobbered to check deleted bit. |
| Label failurePopIndex; |
| masm.push(indexReg); |
| |
| // Check if property was deleted on arguments object. |
| masm.loadPrivate(Address(object(), ArgumentsObject::getDataSlotOffset()), tmpReg); |
| masm.loadPtr(Address(tmpReg, offsetof(ArgumentsData, deletedBits)), tmpReg); |
| |
| // In tempReg, calculate index of word containing bit: (idx >> logBitsPerWord) |
| const uint32_t shift = FloorLog2<(sizeof(size_t) * JS_BITS_PER_BYTE)>::value; |
| MOZ_ASSERT(shift == 5 || shift == 6); |
| masm.rshiftPtr(Imm32(shift), indexReg); |
| masm.loadPtr(BaseIndex(tmpReg, indexReg, ScaleFromElemWidth(sizeof(size_t))), tmpReg); |
| |
| // Don't bother testing specific bit, if any bit is set in the word, fail. |
| masm.branchPtr(Assembler::NotEqual, tmpReg, ImmPtr(nullptr), &failurePopIndex); |
| |
| // Get the address to load from into tmpReg |
| masm.loadPrivate(Address(object(), ArgumentsObject::getDataSlotOffset()), tmpReg); |
| masm.addPtr(Imm32(ArgumentsData::offsetOfArgs()), tmpReg); |
| |
| // Restore original index register value, to use for indexing element. |
| masm.pop(indexReg); |
| 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.bind(&failurePopIndex); |
| 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, 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. |
| if (!curObj->isNative()) |
| return false; |
| |
| // Ensure all indexed properties are stored in dense elements. |
| if (curObj->isIndexed()) |
| return false; |
| |
| curObj = curObj->getProto(); |
| } |
| |
| *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 IsAnyTypedArray(obj) && idval.isInt32() && |
| !value.isString() && !value.isObject() && !value.isSymbol(); |
| } |
| |
| static void |
| StoreDenseElement(MacroAssembler& masm, 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, 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. |
| Int32Key newLength(indexReg); |
| masm.bumpKey(&newLength, 1); |
| masm.storeKey(newLength, initLength); |
| |
| // Increase length if needed. |
| Label bumpedLength; |
| Address length(elements, ObjectElements::offsetOfLength()); |
| masm.branch32(Assembler::AboveOrEqual, length, indexReg, &bumpedLength); |
| masm.storeKey(newLength, length); |
| masm.bind(&bumpedLength); |
| |
| // Restore the index. |
| masm.bumpKey(&newLength, -1); |
| 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, |
| ConstantOrRegister value, Register tempUnbox, Register temp, |
| FloatRegister tempDouble, FloatRegister tempFloat32) |
| { |
| Label failures, done, popObjectAndFail; |
| |
| // Guard on the shape. |
| Shape* shape = AnyTypedArrayShape(tarr); |
| 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 = AnyTypedArrayType(tarr); |
| 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 scopeChain) |
| { |
| MOZ_ASSERT(scopeChain->is<GlobalObject>()); |
| |
| MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_); |
| StubAttacher attacher(*this); |
| |
| // Guard on the scope chain. |
| attacher.branchNextStub(masm, Assembler::NotEqual, scopeChainReg(), |
| ImmGCPtr(scopeChain)); |
| masm.movePtr(ImmGCPtr(scopeChain), outputReg()); |
| |
| attacher.jumpRejoin(masm); |
| |
| return linkAndAttachStub(cx, masm, attacher, ion, "global"); |
| } |
| |
| static inline void |
| GenerateScopeChainGuard(MacroAssembler& masm, JSObject* scopeObj, |
| Register scopeObjReg, Shape* shape, Label* failures) |
| { |
| if (scopeObj->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 = &scopeObj->as<CallObject>(); |
| if (!callObj->isForEval()) { |
| 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 (scopeObj->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(scopeObjReg, JSObject::offsetOfShape()); |
| masm.branchPtr(Assembler::NotEqual, shapeAddr, |
| ImmGCPtr(scopeObj->as<NativeObject>().lastProperty()), failures); |
| } |
| |
| static void |
| GenerateScopeChainGuards(MacroAssembler& masm, JSObject* scopeChain, JSObject* holder, |
| Register outputReg, Label* failures, bool skipLastGuard = false) |
| { |
| JSObject* tobj = scopeChain; |
| |
| // Walk up the scope chain. Note that IsCacheableScopeChain guarantees the |
| // |tobj == holder| condition terminates the loop. |
| while (true) { |
| MOZ_ASSERT(IsCacheableNonGlobalScope(tobj) || tobj->is<GlobalObject>()); |
| |
| if (skipLastGuard && tobj == holder) |
| break; |
| |
| GenerateScopeChainGuard(masm, tobj, outputReg, nullptr, failures); |
| |
| if (tobj == holder) |
| break; |
| |
| // Load the next link. |
| tobj = &tobj->as<ScopeObject>().enclosingScope(); |
| masm.extractObject(Address(outputReg, ScopeObject::offsetOfEnclosingScope()), outputReg); |
| } |
| } |
| |
| bool |
| BindNameIC::attachNonGlobal(JSContext* cx, HandleScript outerScript, IonScript* ion, |
| HandleObject scopeChain, HandleObject holder) |
| { |
| MOZ_ASSERT(IsCacheableNonGlobalScope(scopeChain)); |
| |
| MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_); |
| StubAttacher attacher(*this); |
| |
| // Guard on the shape of the scope chain. |
| Label failures; |
| attacher.branchNextStubOrLabel(masm, Assembler::NotEqual, |
| Address(scopeChainReg(), JSObject::offsetOfShape()), |
| ImmGCPtr(scopeChain->as<NativeObject>().lastProperty()), |
| holder != scopeChain ? &failures : nullptr); |
| |
| if (holder != scopeChain) { |
| JSObject* parent = &scopeChain->as<ScopeObject>().enclosingScope(); |
| masm.extractObject(Address(scopeChainReg(), ScopeObject::offsetOfEnclosingScope()), outputReg()); |
| |
| GenerateScopeChainGuards(masm, parent, holder, outputReg(), &failures); |
| } else { |
| masm.movePtr(scopeChainReg(), 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 != scopeChain) { |
| masm.bind(&failures); |
| attacher.jumpNextStub(masm); |
| } |
| |
| return linkAndAttachStub(cx, masm, attacher, ion, "non-global"); |
| } |
| |
| static bool |
| IsCacheableNonGlobalScopeChain(JSObject* scopeChain, JSObject* holder) |
| { |
| while (true) { |
| if (!IsCacheableNonGlobalScope(scopeChain)) { |
| JitSpew(JitSpew_IonIC, "Non-cacheable object on scope chain"); |
| return false; |
| } |
| |
| if (scopeChain == holder) |
| return true; |
| |
| scopeChain = &scopeChain->as<ScopeObject>().enclosingScope(); |
| if (!scopeChain) { |
| JitSpew(JitSpew_IonIC, "Scope chain indirect hit"); |
| return false; |
| } |
| } |
| |
| MOZ_CRASH("Invalid scope chain"); |
| } |
| |
| JSObject* |
| BindNameIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex, |
| HandleObject scopeChain) |
| { |
| IonScript* ion = outerScript->ionScript(); |
| BindNameIC& cache = ion->getCache(cacheIndex).toBindName(); |
| HandlePropertyName name = cache.name(); |
| |
| RootedObject holder(cx); |
| if (!LookupNameUnqualified(cx, name, scopeChain, &holder)) |
| return nullptr; |
| |
| // Stop generating new stubs once we hit the stub count limit, see |
| // GetPropertyCache. |
| if (cache.canAttachStub()) { |
| if (scopeChain->is<GlobalObject>()) { |
| if (!cache.attachGlobal(cx, outerScript, ion, scopeChain)) |
| return nullptr; |
| } else if (IsCacheableNonGlobalScopeChain(scopeChain, holder)) { |
| if (!cache.attachNonGlobal(cx, outerScript, ion, scopeChain, holder)) |
| return nullptr; |
| } else { |
| JitSpew(JitSpew_IonIC, "BINDNAME uncacheable scope chain"); |
| } |
| } |
| |
| return holder; |
| } |
| |
| bool |
| NameIC::attachReadSlot(JSContext* cx, HandleScript outerScript, IonScript* ion, |
| HandleObject scopeChain, 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(scopeChainReg(), scratchReg); |
| GenerateScopeChainGuards(masm, scopeChain, holderBase, scratchReg, &failures, |
| /* skipLastGuard = */true); |
| |
| // GenerateScopeChain leaves the last scope chain in scratchReg, even though it |
| // doesn't generate the extra guard. |
| GenerateReadSlot(cx, ion, masm, attacher, holderBase, holder, shape, scratchReg, |
| outputReg(), failures.used() ? &failures : nullptr); |
| |
| return linkAndAttachStub(cx, masm, attacher, ion, "generic", |
| JS::TrackedOutcome::ICNameStub_ReadSlot); |
| } |
| |
| static bool |
| IsCacheableScopeChain(JSObject* scopeChain, JSObject* obj) |
| { |
| JSObject* obj2 = scopeChain; |
| while (obj2) { |
| if (!IsCacheableNonGlobalScope(obj2) && !obj2->is<GlobalObject>()) |
| return false; |
| |
| // Stop once we hit the global or target obj. |
| if (obj2->is<GlobalObject>() || obj2 == obj) |
| break; |
| |
| obj2 = obj2->enclosingScope(); |
| } |
| |
| return obj == obj2; |
| } |
| |
| static bool |
| IsCacheableNameReadSlot(HandleObject scopeChain, 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 (!IsCacheableGetPropReadSlotForIon(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 IsCacheableScopeChain(scopeChain, obj); |
| } |
| |
| bool |
| NameIC::attachCallGetter(JSContext* cx, HandleScript outerScript, IonScript* ion, |
| HandleObject scopeChain, 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(scopeChainReg(), scratchReg); |
| GenerateScopeChainGuards(masm, scopeChain, obj, scratchReg, &failures, |
| /* skipLastGuard = */true); |
| |
| // GenerateScopeChain leaves the last scope 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 scopeChain, HandleObject obj, HandleObject holder, |
| HandleShape shape) |
| { |
| if (!shape) |
| return false; |
| if (!obj->is<GlobalObject>()) |
| return false; |
| |
| if (!IsCacheableScopeChain(scopeChain, obj)) |
| return false; |
| |
| return IsCacheableGetPropCallNative(obj, holder, shape) || |
| IsCacheableGetPropCallPropertyOp(obj, holder, shape) || |
| IsCacheableGetPropCallScripted(obj, holder, shape); |
| } |
| |
| bool |
| NameIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex, HandleObject scopeChain, |
| 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, scopeChain, &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(scopeChain, obj, holder, shape, pc, cache.outputReg())) { |
| if (!cache.attachReadSlot(cx, outerScript, ion, scopeChain, obj, |
| holder.as<NativeObject>(), shape)) |
| { |
| return false; |
| } |
| } else if (IsCacheableNameCallGetter(scopeChain, obj, holder, shape)) { |
| void* returnAddr = GetReturnAddressToIonCode(cx); |
| if (!cache.attachCallGetter(cx, outerScript, ion, scopeChain, obj, holder, shape, |
| returnAddr)) |
| { |
| return false; |
| } |
| } |
| } |
| |
| // Monitor changes to cache entry. |
| TypeScript::Monitor(cx, script, pc, vp); |
| |
| return true; |
| } |
| |