michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "jit/IonCaches.h" michael@0: michael@0: #include "mozilla/TemplateLib.h" michael@0: michael@0: #include "jsproxy.h" michael@0: #include "jstypes.h" michael@0: michael@0: #include "builtin/TypedObject.h" michael@0: #include "jit/Ion.h" michael@0: #include "jit/IonLinker.h" michael@0: #include "jit/IonSpewer.h" michael@0: #include "jit/Lowering.h" michael@0: #ifdef JS_ION_PERF michael@0: # include "jit/PerfSpewer.h" michael@0: #endif michael@0: #include "jit/ParallelFunctions.h" michael@0: #include "jit/VMFunctions.h" michael@0: #include "vm/Shape.h" michael@0: michael@0: #include "jit/IonFrames-inl.h" michael@0: #include "vm/Interpreter-inl.h" michael@0: #include "vm/Shape-inl.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::jit; michael@0: michael@0: using mozilla::tl::FloorLog2; michael@0: michael@0: void michael@0: CodeLocationJump::repoint(JitCode *code, MacroAssembler *masm) michael@0: { michael@0: JS_ASSERT(state_ == Relative); michael@0: size_t new_off = (size_t)raw_; michael@0: #ifdef JS_SMALL_BRANCH michael@0: size_t jumpTableEntryOffset = reinterpret_cast(jumpTableEntry_); michael@0: #endif michael@0: if (masm != nullptr) { michael@0: #ifdef JS_CODEGEN_X64 michael@0: JS_ASSERT((uint64_t)raw_ <= UINT32_MAX); michael@0: #endif michael@0: new_off = masm->actualOffset((uintptr_t)raw_); michael@0: #ifdef JS_SMALL_BRANCH michael@0: jumpTableEntryOffset = masm->actualIndex(jumpTableEntryOffset); michael@0: #endif michael@0: } michael@0: raw_ = code->raw() + new_off; michael@0: #ifdef JS_SMALL_BRANCH michael@0: jumpTableEntry_ = Assembler::PatchableJumpAddress(code, (size_t) jumpTableEntryOffset); michael@0: #endif michael@0: setAbsolute(); michael@0: } michael@0: michael@0: void michael@0: CodeLocationLabel::repoint(JitCode *code, MacroAssembler *masm) michael@0: { michael@0: JS_ASSERT(state_ == Relative); michael@0: size_t new_off = (size_t)raw_; michael@0: if (masm != nullptr) { michael@0: #ifdef JS_CODEGEN_X64 michael@0: JS_ASSERT((uint64_t)raw_ <= UINT32_MAX); michael@0: #endif michael@0: new_off = masm->actualOffset((uintptr_t)raw_); michael@0: } michael@0: JS_ASSERT(new_off < code->instructionsSize()); michael@0: michael@0: raw_ = code->raw() + new_off; michael@0: setAbsolute(); michael@0: } michael@0: michael@0: void michael@0: CodeOffsetLabel::fixup(MacroAssembler *masm) michael@0: { michael@0: offset_ = masm->actualOffset(offset_); michael@0: } michael@0: michael@0: void michael@0: CodeOffsetJump::fixup(MacroAssembler *masm) michael@0: { michael@0: offset_ = masm->actualOffset(offset_); michael@0: #ifdef JS_SMALL_BRANCH michael@0: jumpTableIndex_ = masm->actualIndex(jumpTableIndex_); michael@0: #endif michael@0: } michael@0: michael@0: const char * michael@0: IonCache::CacheName(IonCache::Kind kind) michael@0: { michael@0: static const char * const names[] = michael@0: { michael@0: #define NAME(x) #x, michael@0: IONCACHE_KIND_LIST(NAME) michael@0: #undef NAME michael@0: }; michael@0: return names[kind]; michael@0: } michael@0: michael@0: IonCache::LinkStatus michael@0: IonCache::linkCode(JSContext *cx, MacroAssembler &masm, IonScript *ion, JitCode **code) michael@0: { michael@0: Linker linker(masm); michael@0: *code = linker.newCode(cx, JSC::ION_CODE); michael@0: if (!*code) michael@0: return LINK_ERROR; michael@0: michael@0: if (ion->invalidated()) michael@0: return CACHE_FLUSHED; michael@0: michael@0: return LINK_GOOD; michael@0: } michael@0: michael@0: const size_t IonCache::MAX_STUBS = 16; michael@0: michael@0: // Helper class which encapsulates logic to attach a stub to an IC by hooking michael@0: // up rejoins and next stub jumps. michael@0: // michael@0: // The simplest stubs have a single jump to the next stub and look like the michael@0: // following: michael@0: // michael@0: // branch guard NEXTSTUB michael@0: // ... IC-specific code ... michael@0: // jump REJOIN michael@0: // michael@0: // This corresponds to: michael@0: // michael@0: // attacher.branchNextStub(masm, ...); michael@0: // ... emit IC-specific code ... michael@0: // attacher.jumpRejoin(masm); michael@0: // michael@0: // Whether the stub needs multiple next stub jumps look like: michael@0: // michael@0: // branch guard FAILURES michael@0: // ... IC-specific code ... michael@0: // branch another-guard FAILURES michael@0: // ... IC-specific code ... michael@0: // jump REJOIN michael@0: // FAILURES: michael@0: // jump NEXTSTUB michael@0: // michael@0: // This corresponds to: michael@0: // michael@0: // Label failures; michael@0: // masm.branchX(..., &failures); michael@0: // ... emit IC-specific code ... michael@0: // masm.branchY(..., failures); michael@0: // ... emit more IC-specific code ... michael@0: // attacher.jumpRejoin(masm); michael@0: // masm.bind(&failures); michael@0: // attacher.jumpNextStub(masm); michael@0: // michael@0: // A convenience function |branchNextStubOrLabel| is provided in the case that michael@0: // the stub sometimes has multiple next stub jumps and sometimes a single michael@0: // one. If a non-nullptr label is passed in, a |branchPtr| will be made to michael@0: // that label instead of a |branchPtrWithPatch| to the next stub. michael@0: class IonCache::StubAttacher michael@0: { michael@0: protected: michael@0: bool hasNextStubOffset_ : 1; michael@0: bool hasStubCodePatchOffset_ : 1; michael@0: michael@0: CodeLocationLabel rejoinLabel_; michael@0: CodeOffsetJump nextStubOffset_; michael@0: CodeOffsetJump rejoinOffset_; michael@0: CodeOffsetLabel stubCodePatchOffset_; michael@0: michael@0: public: michael@0: StubAttacher(CodeLocationLabel rejoinLabel) michael@0: : hasNextStubOffset_(false), michael@0: hasStubCodePatchOffset_(false), michael@0: rejoinLabel_(rejoinLabel), michael@0: nextStubOffset_(), michael@0: rejoinOffset_(), michael@0: stubCodePatchOffset_() michael@0: { } michael@0: michael@0: // Value used instead of the JitCode self-reference of generated michael@0: // stubs. This value is needed for marking calls made inside stubs. This michael@0: // value would be replaced by the attachStub function after the allocation michael@0: // of the JitCode. The self-reference is used to keep the stub path alive michael@0: // even if the IonScript is invalidated or if the IC is flushed. michael@0: static const ImmPtr STUB_ADDR; michael@0: michael@0: template michael@0: void branchNextStub(MacroAssembler &masm, Assembler::Condition cond, T1 op1, T2 op2) { michael@0: JS_ASSERT(!hasNextStubOffset_); michael@0: RepatchLabel nextStub; michael@0: nextStubOffset_ = masm.branchPtrWithPatch(cond, op1, op2, &nextStub); michael@0: hasNextStubOffset_ = true; michael@0: masm.bind(&nextStub); michael@0: } michael@0: michael@0: template michael@0: void branchNextStubOrLabel(MacroAssembler &masm, Assembler::Condition cond, T1 op1, T2 op2, michael@0: Label *label) michael@0: { michael@0: if (label != nullptr) michael@0: masm.branchPtr(cond, op1, op2, label); michael@0: else michael@0: branchNextStub(masm, cond, op1, op2); michael@0: } michael@0: michael@0: void jumpRejoin(MacroAssembler &masm) { michael@0: RepatchLabel rejoin; michael@0: rejoinOffset_ = masm.jumpWithPatch(&rejoin); michael@0: masm.bind(&rejoin); michael@0: } michael@0: michael@0: void jumpNextStub(MacroAssembler &masm) { michael@0: JS_ASSERT(!hasNextStubOffset_); michael@0: RepatchLabel nextStub; michael@0: nextStubOffset_ = masm.jumpWithPatch(&nextStub); michael@0: hasNextStubOffset_ = true; michael@0: masm.bind(&nextStub); michael@0: } michael@0: michael@0: void pushStubCodePointer(MacroAssembler &masm) { michael@0: // Push the JitCode pointer for the stub we're generating. michael@0: // WARNING: michael@0: // WARNING: If JitCode ever becomes relocatable, the following code is incorrect. michael@0: // WARNING: Note that we're not marking the pointer being pushed as an ImmGCPtr. michael@0: // WARNING: This location will be patched with the pointer of the generated stub, michael@0: // WARNING: such as it can be marked when a call is made with this stub. Be aware michael@0: // WARNING: that ICs are not marked and so this stub will only be kept alive iff michael@0: // WARNING: it is on the stack at the time of the GC. No ImmGCPtr is needed as the michael@0: // WARNING: stubs are flushed on GC. michael@0: // WARNING: michael@0: JS_ASSERT(!hasStubCodePatchOffset_); michael@0: stubCodePatchOffset_ = masm.PushWithPatch(STUB_ADDR); michael@0: hasStubCodePatchOffset_ = true; michael@0: } michael@0: michael@0: void patchRejoinJump(MacroAssembler &masm, JitCode *code) { michael@0: rejoinOffset_.fixup(&masm); michael@0: CodeLocationJump rejoinJump(code, rejoinOffset_); michael@0: PatchJump(rejoinJump, rejoinLabel_); michael@0: } michael@0: michael@0: void patchStubCodePointer(MacroAssembler &masm, JitCode *code) { michael@0: if (hasStubCodePatchOffset_) { michael@0: stubCodePatchOffset_.fixup(&masm); michael@0: Assembler::patchDataWithValueCheck(CodeLocationLabel(code, stubCodePatchOffset_), michael@0: ImmPtr(code), STUB_ADDR); michael@0: } michael@0: } michael@0: michael@0: virtual void patchNextStubJump(MacroAssembler &masm, JitCode *code) = 0; michael@0: }; michael@0: michael@0: const ImmPtr IonCache::StubAttacher::STUB_ADDR = ImmPtr((void*)0xdeadc0de); michael@0: michael@0: class RepatchIonCache::RepatchStubAppender : public IonCache::StubAttacher michael@0: { michael@0: RepatchIonCache &cache_; michael@0: michael@0: public: michael@0: RepatchStubAppender(RepatchIonCache &cache) michael@0: : StubAttacher(cache.rejoinLabel()), michael@0: cache_(cache) michael@0: { michael@0: } michael@0: michael@0: void patchNextStubJump(MacroAssembler &masm, JitCode *code) { michael@0: // Patch the previous nextStubJump of the last stub, or the jump from the michael@0: // codeGen, to jump into the newly allocated code. michael@0: PatchJump(cache_.lastJump_, CodeLocationLabel(code)); michael@0: michael@0: // If this path is not taken, we are producing an entry which can no michael@0: // longer go back into the update function. michael@0: if (hasNextStubOffset_) { michael@0: nextStubOffset_.fixup(&masm); michael@0: CodeLocationJump nextStubJump(code, nextStubOffset_); michael@0: PatchJump(nextStubJump, cache_.fallbackLabel_); michael@0: michael@0: // When the last stub fails, it fallback to the ool call which can michael@0: // produce a stub. Next time we generate a stub, we will patch the michael@0: // nextStub jump to try the new stub. michael@0: cache_.lastJump_ = nextStubJump; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: void michael@0: RepatchIonCache::reset() michael@0: { michael@0: IonCache::reset(); michael@0: PatchJump(initialJump_, fallbackLabel_); michael@0: lastJump_ = initialJump_; michael@0: } michael@0: michael@0: void michael@0: RepatchIonCache::emitInitialJump(MacroAssembler &masm, AddCacheState &addState) michael@0: { michael@0: initialJump_ = masm.jumpWithPatch(&addState.repatchEntry); michael@0: lastJump_ = initialJump_; michael@0: } michael@0: michael@0: void michael@0: RepatchIonCache::bindInitialJump(MacroAssembler &masm, AddCacheState &addState) michael@0: { michael@0: masm.bind(&addState.repatchEntry); michael@0: } michael@0: michael@0: void michael@0: RepatchIonCache::updateBaseAddress(JitCode *code, MacroAssembler &masm) michael@0: { michael@0: IonCache::updateBaseAddress(code, masm); michael@0: initialJump_.repoint(code, &masm); michael@0: lastJump_.repoint(code, &masm); michael@0: } michael@0: michael@0: class DispatchIonCache::DispatchStubPrepender : public IonCache::StubAttacher michael@0: { michael@0: DispatchIonCache &cache_; michael@0: michael@0: public: michael@0: DispatchStubPrepender(DispatchIonCache &cache) michael@0: : StubAttacher(cache.rejoinLabel_), michael@0: cache_(cache) michael@0: { michael@0: } michael@0: michael@0: void patchNextStubJump(MacroAssembler &masm, JitCode *code) { michael@0: JS_ASSERT(hasNextStubOffset_); michael@0: michael@0: // Jump to the previous entry in the stub dispatch table. We michael@0: // have not yet executed the code we're patching the jump in. michael@0: nextStubOffset_.fixup(&masm); michael@0: CodeLocationJump nextStubJump(code, nextStubOffset_); michael@0: PatchJump(nextStubJump, CodeLocationLabel(cache_.firstStub_)); michael@0: michael@0: // Update the dispatch table. Modification to jumps after the dispatch michael@0: // table is updated is disallowed, lest we race on entry into an michael@0: // unfinalized stub. michael@0: cache_.firstStub_ = code->raw(); michael@0: } michael@0: }; michael@0: michael@0: void michael@0: DispatchIonCache::reset() michael@0: { michael@0: IonCache::reset(); michael@0: firstStub_ = fallbackLabel_.raw(); michael@0: } michael@0: void michael@0: DispatchIonCache::emitInitialJump(MacroAssembler &masm, AddCacheState &addState) michael@0: { michael@0: Register scratch = addState.dispatchScratch; michael@0: dispatchLabel_ = masm.movWithPatch(ImmPtr((void*)-1), scratch); michael@0: masm.loadPtr(Address(scratch, 0), scratch); michael@0: masm.jump(scratch); michael@0: rejoinLabel_ = masm.labelForPatch(); michael@0: } michael@0: michael@0: void michael@0: DispatchIonCache::bindInitialJump(MacroAssembler &masm, AddCacheState &addState) michael@0: { michael@0: // Do nothing. michael@0: } michael@0: michael@0: void michael@0: DispatchIonCache::updateBaseAddress(JitCode *code, MacroAssembler &masm) michael@0: { michael@0: // The address of firstStub_ should be pointer aligned. michael@0: JS_ASSERT(uintptr_t(&firstStub_) % sizeof(uintptr_t) == 0); michael@0: michael@0: IonCache::updateBaseAddress(code, masm); michael@0: dispatchLabel_.fixup(&masm); michael@0: Assembler::patchDataWithValueCheck(CodeLocationLabel(code, dispatchLabel_), michael@0: ImmPtr(&firstStub_), michael@0: ImmPtr((void*)-1)); michael@0: firstStub_ = fallbackLabel_.raw(); michael@0: rejoinLabel_.repoint(code, &masm); michael@0: } michael@0: michael@0: void michael@0: IonCache::attachStub(MacroAssembler &masm, StubAttacher &attacher, Handle code) michael@0: { michael@0: JS_ASSERT(canAttachStub()); michael@0: incrementStubCount(); michael@0: michael@0: // Update the success path to continue after the IC initial jump. michael@0: attacher.patchRejoinJump(masm, code); michael@0: michael@0: // Replace the STUB_ADDR constant by the address of the generated stub, such michael@0: // as it can be kept alive even if the cache is flushed (see michael@0: // MarkJitExitFrame). michael@0: attacher.patchStubCodePointer(masm, code); michael@0: michael@0: // Update the failure path. Note it is this patch that makes the stub michael@0: // accessible for parallel ICs so it should not be moved unless you really michael@0: // know what is going on. michael@0: attacher.patchNextStubJump(masm, code); michael@0: } michael@0: michael@0: bool michael@0: IonCache::linkAndAttachStub(JSContext *cx, MacroAssembler &masm, StubAttacher &attacher, michael@0: IonScript *ion, const char *attachKind) michael@0: { michael@0: Rooted code(cx); michael@0: { michael@0: // Need to exit the AutoFlushICache context to flush the cache michael@0: // before attaching the stub below. michael@0: AutoFlushICache afc("IonCache"); michael@0: LinkStatus status = linkCode(cx, masm, ion, code.address()); michael@0: if (status != LINK_GOOD) michael@0: return status != LINK_ERROR; michael@0: } michael@0: michael@0: if (pc_) { michael@0: IonSpew(IonSpew_InlineCaches, "Cache %p(%s:%d/%d) generated %s %s stub at %p", michael@0: this, script_->filename(), script_->lineno(), script_->pcToOffset(pc_), michael@0: attachKind, CacheName(kind()), code->raw()); michael@0: } else { michael@0: IonSpew(IonSpew_InlineCaches, "Cache %p generated %s %s stub at %p", michael@0: this, attachKind, CacheName(kind()), code->raw()); michael@0: } michael@0: michael@0: #ifdef JS_ION_PERF michael@0: writePerfSpewerJitCodeProfile(code, "IonCache"); michael@0: #endif michael@0: michael@0: attachStub(masm, attacher, code); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: IonCache::updateBaseAddress(JitCode *code, MacroAssembler &masm) michael@0: { michael@0: fallbackLabel_.repoint(code, &masm); michael@0: } michael@0: michael@0: void michael@0: IonCache::initializeAddCacheState(LInstruction *ins, AddCacheState *addState) michael@0: { michael@0: } michael@0: michael@0: static bool michael@0: IsCacheableDOMProxy(JSObject *obj) michael@0: { michael@0: if (!obj->is()) michael@0: return false; michael@0: michael@0: BaseProxyHandler *handler = obj->as().handler(); michael@0: michael@0: if (handler->family() != GetDOMProxyHandlerFamily()) michael@0: return false; michael@0: michael@0: if (obj->numFixedSlots() <= GetDOMProxyExpandoSlot()) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: GeneratePrototypeGuards(JSContext *cx, IonScript *ion, MacroAssembler &masm, JSObject *obj, michael@0: JSObject *holder, Register objectReg, Register scratchReg, michael@0: Label *failures) michael@0: { michael@0: /* The guards here protect against the effects of TradeGuts(). If the prototype chain michael@0: * is directly altered, then TI will toss the jitcode, so we don't have to worry about michael@0: * it, and any other change to the holder, or adding a shadowing property will result michael@0: * in reshaping the holder, and thus the failure of the shape guard. michael@0: */ michael@0: JS_ASSERT(obj != holder); michael@0: michael@0: if (obj->hasUncacheableProto()) { michael@0: // Note: objectReg and scratchReg may be the same register, so we cannot michael@0: // use objectReg in the rest of this function. michael@0: masm.loadPtr(Address(objectReg, JSObject::offsetOfType()), scratchReg); michael@0: Address proto(scratchReg, types::TypeObject::offsetOfProto()); michael@0: masm.branchNurseryPtr(Assembler::NotEqual, proto, michael@0: ImmMaybeNurseryPtr(obj->getProto()), failures); michael@0: } michael@0: michael@0: JSObject *pobj = IsCacheableDOMProxy(obj) michael@0: ? obj->getTaggedProto().toObjectOrNull() michael@0: : obj->getProto(); michael@0: if (!pobj) michael@0: return; michael@0: while (pobj != holder) { michael@0: if (pobj->hasUncacheableProto()) { michael@0: JS_ASSERT(!pobj->hasSingletonType()); michael@0: masm.moveNurseryPtr(ImmMaybeNurseryPtr(pobj), scratchReg); michael@0: Address objType(scratchReg, JSObject::offsetOfType()); michael@0: masm.branchPtr(Assembler::NotEqual, objType, ImmGCPtr(pobj->type()), failures); michael@0: } michael@0: pobj = pobj->getProto(); michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: IsCacheableProtoChain(JSObject *obj, JSObject *holder) michael@0: { michael@0: while (obj != holder) { michael@0: /* michael@0: * We cannot assume that we find the holder object on the prototype michael@0: * chain and must check for null proto. The prototype chain can be michael@0: * altered during the lookupProperty call. michael@0: */ michael@0: JSObject *proto = obj->getProto(); michael@0: if (!proto || !proto->isNative()) michael@0: return false; michael@0: obj = proto; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: IsCacheableGetPropReadSlot(JSObject *obj, JSObject *holder, Shape *shape) michael@0: { michael@0: if (!shape || !IsCacheableProtoChain(obj, holder)) michael@0: return false; michael@0: michael@0: if (!shape->hasSlot() || !shape->hasDefaultGetter()) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: IsCacheableNoProperty(JSObject *obj, JSObject *holder, Shape *shape, jsbytecode *pc, michael@0: const TypedOrValueRegister &output) michael@0: { michael@0: if (shape) michael@0: return false; michael@0: michael@0: JS_ASSERT(!holder); michael@0: michael@0: // Just because we didn't find the property on the object doesn't mean it michael@0: // won't magically appear through various engine hacks: michael@0: if (obj->getClass()->getProperty && obj->getClass()->getProperty != JS_PropertyStub) michael@0: return false; michael@0: michael@0: // Don't generate missing property ICs if we skipped a non-native object, as michael@0: // lookups may extend beyond the prototype chain (e.g. for DOMProxy michael@0: // proxies). michael@0: JSObject *obj2 = obj; michael@0: while (obj2) { michael@0: if (!obj2->isNative()) michael@0: return false; michael@0: obj2 = obj2->getProto(); michael@0: } michael@0: michael@0: // The pc is nullptr if the cache is idempotent. We cannot share missing michael@0: // properties between caches because TI can only try to prove that a type is michael@0: // contained, but does not attempts to check if something does not exists. michael@0: // So the infered type of getprop would be missing and would not contain michael@0: // undefined, as expected for missing properties. michael@0: if (!pc) michael@0: return false; michael@0: michael@0: #if JS_HAS_NO_SUCH_METHOD michael@0: // The __noSuchMethod__ hook may substitute in a valid method. Since, michael@0: // if o.m is missing, o.m() will probably be an error, just mark all michael@0: // missing callprops as uncacheable. michael@0: if (JSOp(*pc) == JSOP_CALLPROP || michael@0: JSOp(*pc) == JSOP_CALLELEM) michael@0: { michael@0: return false; michael@0: } michael@0: #endif michael@0: michael@0: // TI has not yet monitored an Undefined value. The fallback path will michael@0: // monitor and invalidate the script. michael@0: if (!output.hasValue()) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: IsOptimizableArgumentsObjectForLength(JSObject *obj) michael@0: { michael@0: if (!obj->is()) michael@0: return false; michael@0: michael@0: if (obj->as().hasOverriddenLength()) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: IsOptimizableArgumentsObjectForGetElem(JSObject *obj, Value idval) michael@0: { michael@0: if (!IsOptimizableArgumentsObjectForLength(obj)) michael@0: return false; michael@0: michael@0: ArgumentsObject &argsObj = obj->as(); michael@0: michael@0: if (argsObj.isAnyElementDeleted()) michael@0: return false; michael@0: michael@0: if (!idval.isInt32()) michael@0: return false; michael@0: michael@0: int32_t idint = idval.toInt32(); michael@0: if (idint < 0 || static_cast(idint) >= argsObj.initialLength()) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: IsCacheableGetPropCallNative(JSObject *obj, JSObject *holder, Shape *shape) michael@0: { michael@0: if (!shape || !IsCacheableProtoChain(obj, holder)) michael@0: return false; michael@0: michael@0: if (!shape->hasGetterValue() || !shape->getterValue().isObject()) michael@0: return false; michael@0: michael@0: if (!shape->getterValue().toObject().is()) michael@0: return false; michael@0: michael@0: JSFunction& getter = shape->getterValue().toObject().as(); michael@0: if (!getter.isNative()) michael@0: return false; michael@0: michael@0: // Check for a getter that has jitinfo and whose jitinfo says it's michael@0: // OK with both inner and outer objects. michael@0: if (getter.jitInfo() && !getter.jitInfo()->needsOuterizedThisObject()) michael@0: return true; michael@0: michael@0: // For getters that need an outerized this object, don't cache if michael@0: // obj has an outerObject hook, since our cache will pass obj michael@0: // itself without outerizing. michael@0: return !obj->getClass()->ext.outerObject; michael@0: } michael@0: michael@0: static bool michael@0: IsCacheableGetPropCallPropertyOp(JSObject *obj, JSObject *holder, Shape *shape) michael@0: { michael@0: if (!shape || !IsCacheableProtoChain(obj, holder)) michael@0: return false; michael@0: michael@0: if (shape->hasSlot() || shape->hasGetterValue() || shape->hasDefaultGetter()) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static inline void michael@0: EmitLoadSlot(MacroAssembler &masm, JSObject *holder, Shape *shape, Register holderReg, michael@0: TypedOrValueRegister output, Register scratchReg) michael@0: { michael@0: JS_ASSERT(holder); michael@0: if (holder->isFixedSlot(shape->slot())) { michael@0: Address addr(holderReg, JSObject::getFixedSlotOffset(shape->slot())); michael@0: masm.loadTypedOrValue(addr, output); michael@0: } else { michael@0: masm.loadPtr(Address(holderReg, JSObject::offsetOfSlots()), scratchReg); michael@0: michael@0: Address addr(scratchReg, holder->dynamicSlotIndex(shape->slot()) * sizeof(Value)); michael@0: masm.loadTypedOrValue(addr, output); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: GenerateDOMProxyChecks(JSContext *cx, MacroAssembler &masm, JSObject *obj, michael@0: PropertyName *name, Register object, Label *stubFailure, michael@0: bool skipExpandoCheck = false) michael@0: { michael@0: JS_ASSERT(IsCacheableDOMProxy(obj)); michael@0: michael@0: // Guard the following: michael@0: // 1. The object is a DOMProxy. michael@0: // 2. The object does not have expando properties, or has an expando michael@0: // which is known to not have the desired property. michael@0: Address handlerAddr(object, ProxyObject::offsetOfHandler()); michael@0: Address expandoSlotAddr(object, JSObject::getFixedSlotOffset(GetDOMProxyExpandoSlot())); michael@0: michael@0: // Check that object is a DOMProxy. michael@0: masm.branchPrivatePtr(Assembler::NotEqual, handlerAddr, michael@0: ImmPtr(obj->as().handler()), stubFailure); michael@0: michael@0: if (skipExpandoCheck) michael@0: return; michael@0: michael@0: // For the remaining code, we need to reserve some registers to load a value. michael@0: // This is ugly, but unvaoidable. michael@0: RegisterSet domProxyRegSet(RegisterSet::All()); michael@0: domProxyRegSet.take(AnyRegister(object)); michael@0: ValueOperand tempVal = domProxyRegSet.takeValueOperand(); michael@0: masm.pushValue(tempVal); michael@0: michael@0: Label failDOMProxyCheck; michael@0: Label domProxyOk; michael@0: michael@0: Value expandoVal = obj->getFixedSlot(GetDOMProxyExpandoSlot()); michael@0: masm.loadValue(expandoSlotAddr, tempVal); michael@0: michael@0: if (!expandoVal.isObject() && !expandoVal.isUndefined()) { michael@0: masm.branchTestValue(Assembler::NotEqual, tempVal, expandoVal, &failDOMProxyCheck); michael@0: michael@0: ExpandoAndGeneration *expandoAndGeneration = (ExpandoAndGeneration*)expandoVal.toPrivate(); michael@0: masm.movePtr(ImmPtr(expandoAndGeneration), tempVal.scratchReg()); michael@0: michael@0: masm.branch32(Assembler::NotEqual, michael@0: Address(tempVal.scratchReg(), michael@0: ExpandoAndGeneration::offsetOfGeneration()), michael@0: Imm32(expandoAndGeneration->generation), michael@0: &failDOMProxyCheck); michael@0: michael@0: expandoVal = expandoAndGeneration->expando; michael@0: masm.loadValue(Address(tempVal.scratchReg(), michael@0: ExpandoAndGeneration::offsetOfExpando()), michael@0: tempVal); michael@0: } michael@0: michael@0: // If the incoming object does not have an expando object then we're sure we're not michael@0: // shadowing. michael@0: masm.branchTestUndefined(Assembler::Equal, tempVal, &domProxyOk); michael@0: michael@0: if (expandoVal.isObject()) { michael@0: JS_ASSERT(!expandoVal.toObject().nativeContains(cx, name)); michael@0: michael@0: // Reference object has an expando object that doesn't define the name. Check that michael@0: // the incoming object has an expando object with the same shape. michael@0: masm.branchTestObject(Assembler::NotEqual, tempVal, &failDOMProxyCheck); michael@0: masm.extractObject(tempVal, tempVal.scratchReg()); michael@0: masm.branchPtr(Assembler::Equal, michael@0: Address(tempVal.scratchReg(), JSObject::offsetOfShape()), michael@0: ImmGCPtr(expandoVal.toObject().lastProperty()), michael@0: &domProxyOk); michael@0: } michael@0: michael@0: // Failure case: restore the tempVal registers and jump to failures. michael@0: masm.bind(&failDOMProxyCheck); michael@0: masm.popValue(tempVal); michael@0: masm.jump(stubFailure); michael@0: michael@0: // Success case: restore the tempval and proceed. michael@0: masm.bind(&domProxyOk); michael@0: masm.popValue(tempVal); michael@0: } michael@0: michael@0: static void michael@0: GenerateReadSlot(JSContext *cx, IonScript *ion, MacroAssembler &masm, michael@0: IonCache::StubAttacher &attacher, JSObject *obj, JSObject *holder, michael@0: Shape *shape, Register object, TypedOrValueRegister output, michael@0: Label *failures = nullptr) michael@0: { michael@0: JS_ASSERT(obj->isNative()); michael@0: // If there's a single jump to |failures|, we can patch the shape guard michael@0: // jump directly. Otherwise, jump to the end of the stub, so there's a michael@0: // common point to patch. michael@0: bool multipleFailureJumps = (obj != holder) || (failures != nullptr && failures->used()); michael@0: michael@0: // If we have multiple failure jumps but didn't get a label from the michael@0: // outside, make one ourselves. michael@0: Label failures_; michael@0: if (multipleFailureJumps && !failures) michael@0: failures = &failures_; michael@0: michael@0: // Guard on the shape of the object. michael@0: attacher.branchNextStubOrLabel(masm, Assembler::NotEqual, michael@0: Address(object, JSObject::offsetOfShape()), michael@0: ImmGCPtr(obj->lastProperty()), michael@0: failures); michael@0: michael@0: // If we need a scratch register, use either an output register or the michael@0: // object register. After this point, we cannot jump directly to michael@0: // |failures| since we may still have to pop the object register. michael@0: bool restoreScratch = false; michael@0: Register scratchReg = Register::FromCode(0); // Quell compiler warning. michael@0: michael@0: if (obj != holder || !holder->isFixedSlot(shape->slot())) { michael@0: if (output.hasValue()) { michael@0: scratchReg = output.valueReg().scratchReg(); michael@0: } else if (output.type() == MIRType_Double) { michael@0: scratchReg = object; michael@0: masm.push(scratchReg); michael@0: restoreScratch = true; michael@0: } else { michael@0: scratchReg = output.typedReg().gpr(); michael@0: } michael@0: } michael@0: michael@0: // Fast path: single failure jump, no prototype guards. michael@0: if (!multipleFailureJumps) { michael@0: EmitLoadSlot(masm, holder, shape, object, output, scratchReg); michael@0: if (restoreScratch) michael@0: masm.pop(scratchReg); michael@0: attacher.jumpRejoin(masm); michael@0: return; michael@0: } michael@0: michael@0: // Slow path: multiple jumps; generate prototype guards. michael@0: Label prototypeFailures; michael@0: Register holderReg; michael@0: if (obj != holder) { michael@0: // Note: this may clobber the object register if it's used as scratch. michael@0: GeneratePrototypeGuards(cx, ion, masm, obj, holder, object, scratchReg, michael@0: &prototypeFailures); michael@0: michael@0: if (holder) { michael@0: // Guard on the holder's shape. michael@0: holderReg = scratchReg; michael@0: masm.moveNurseryPtr(ImmMaybeNurseryPtr(holder), holderReg); michael@0: masm.branchPtr(Assembler::NotEqual, michael@0: Address(holderReg, JSObject::offsetOfShape()), michael@0: ImmGCPtr(holder->lastProperty()), michael@0: &prototypeFailures); michael@0: } else { michael@0: // The property does not exist. Guard on everything in the michael@0: // prototype chain. michael@0: JSObject *proto = obj->getTaggedProto().toObjectOrNull(); michael@0: Register lastReg = object; michael@0: JS_ASSERT(scratchReg != object); michael@0: while (proto) { michael@0: masm.loadObjProto(lastReg, scratchReg); michael@0: michael@0: // Guard the shape of the current prototype. michael@0: masm.branchPtr(Assembler::NotEqual, michael@0: Address(scratchReg, JSObject::offsetOfShape()), michael@0: ImmGCPtr(proto->lastProperty()), michael@0: &prototypeFailures); michael@0: michael@0: proto = proto->getProto(); michael@0: lastReg = scratchReg; michael@0: } michael@0: michael@0: holderReg = InvalidReg; michael@0: } michael@0: } else { michael@0: holderReg = object; michael@0: } michael@0: michael@0: // Slot access. michael@0: if (holder) michael@0: EmitLoadSlot(masm, holder, shape, holderReg, output, scratchReg); michael@0: else michael@0: masm.moveValue(UndefinedValue(), output.valueReg()); michael@0: michael@0: // Restore scratch on success. michael@0: if (restoreScratch) michael@0: masm.pop(scratchReg); michael@0: michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: masm.bind(&prototypeFailures); michael@0: if (restoreScratch) michael@0: masm.pop(scratchReg); michael@0: masm.bind(failures); michael@0: michael@0: attacher.jumpNextStub(masm); michael@0: michael@0: } michael@0: michael@0: static bool michael@0: EmitGetterCall(JSContext *cx, MacroAssembler &masm, michael@0: IonCache::StubAttacher &attacher, JSObject *obj, michael@0: JSObject *holder, HandleShape shape, michael@0: RegisterSet liveRegs, Register object, michael@0: Register scratchReg, TypedOrValueRegister output, michael@0: void *returnAddr) michael@0: { michael@0: JS_ASSERT(output.hasValue()); michael@0: MacroAssembler::AfterICSaveLive aic = masm.icSaveLive(liveRegs); michael@0: michael@0: // Remaining registers should basically be free, but we need to use |object| still michael@0: // so leave it alone. michael@0: RegisterSet regSet(RegisterSet::All()); michael@0: regSet.take(AnyRegister(object)); michael@0: michael@0: // This is a slower stub path, and we're going to be doing a call anyway. Don't need michael@0: // to try so hard to not use the stack. Scratch regs are just taken from the register michael@0: // set not including the input, current value saved on the stack, and restored when michael@0: // we're done with it. michael@0: scratchReg = regSet.takeGeneral(); michael@0: Register argJSContextReg = regSet.takeGeneral(); michael@0: Register argUintNReg = regSet.takeGeneral(); michael@0: Register argVpReg = regSet.takeGeneral(); michael@0: michael@0: // Shape has a getter function. michael@0: bool callNative = IsCacheableGetPropCallNative(obj, holder, shape); michael@0: JS_ASSERT_IF(!callNative, IsCacheableGetPropCallPropertyOp(obj, holder, shape)); michael@0: michael@0: if (callNative) { michael@0: JS_ASSERT(shape->hasGetterValue() && shape->getterValue().isObject() && michael@0: shape->getterValue().toObject().is()); michael@0: JSFunction *target = &shape->getterValue().toObject().as(); michael@0: michael@0: JS_ASSERT(target); michael@0: JS_ASSERT(target->isNative()); michael@0: michael@0: // Native functions have the signature: michael@0: // bool (*)(JSContext *, unsigned, Value *vp) michael@0: // Where vp[0] is space for an outparam, vp[1] is |this|, and vp[2] onward michael@0: // are the function arguments. michael@0: michael@0: // Construct vp array: michael@0: // Push object value for |this| michael@0: masm.Push(TypedOrValueRegister(MIRType_Object, AnyRegister(object))); michael@0: // Push callee/outparam. michael@0: masm.Push(ObjectValue(*target)); michael@0: michael@0: // Preload arguments into registers. michael@0: masm.loadJSContext(argJSContextReg); michael@0: masm.move32(Imm32(0), argUintNReg); michael@0: masm.movePtr(StackPointer, argVpReg); michael@0: michael@0: // Push marking data for later use. michael@0: masm.Push(argUintNReg); michael@0: attacher.pushStubCodePointer(masm); michael@0: michael@0: if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic)) michael@0: return false; michael@0: masm.enterFakeExitFrame(ION_FRAME_OOL_NATIVE); michael@0: michael@0: // Construct and execute call. michael@0: masm.setupUnalignedABICall(3, scratchReg); michael@0: masm.passABIArg(argJSContextReg); michael@0: masm.passABIArg(argUintNReg); michael@0: masm.passABIArg(argVpReg); michael@0: masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, target->native())); michael@0: michael@0: // Test for failure. michael@0: masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel()); michael@0: michael@0: // Load the outparam vp[0] into output register(s). michael@0: Address outparam(StackPointer, IonOOLNativeExitFrameLayout::offsetOfResult()); michael@0: masm.loadTypedOrValue(outparam, output); michael@0: michael@0: // masm.leaveExitFrame & pop locals michael@0: masm.adjustStack(IonOOLNativeExitFrameLayout::Size(0)); michael@0: } else { michael@0: Register argObjReg = argUintNReg; michael@0: Register argIdReg = regSet.takeGeneral(); michael@0: michael@0: PropertyOp target = shape->getterOp(); michael@0: JS_ASSERT(target); michael@0: michael@0: // Push stubCode for marking. michael@0: attacher.pushStubCodePointer(masm); michael@0: michael@0: // JSPropertyOp: bool fn(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp) michael@0: michael@0: // Push args on stack first so we can take pointers to make handles. michael@0: masm.Push(UndefinedValue()); michael@0: masm.movePtr(StackPointer, argVpReg); michael@0: michael@0: // push canonical jsid from shape instead of propertyname. michael@0: masm.Push(shape->propid(), scratchReg); michael@0: masm.movePtr(StackPointer, argIdReg); michael@0: michael@0: masm.Push(object); michael@0: masm.movePtr(StackPointer, argObjReg); michael@0: michael@0: masm.loadJSContext(argJSContextReg); michael@0: michael@0: if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic)) michael@0: return false; michael@0: masm.enterFakeExitFrame(ION_FRAME_OOL_PROPERTY_OP); michael@0: michael@0: // Make the call. michael@0: masm.setupUnalignedABICall(4, scratchReg); michael@0: masm.passABIArg(argJSContextReg); michael@0: masm.passABIArg(argObjReg); michael@0: masm.passABIArg(argIdReg); michael@0: masm.passABIArg(argVpReg); michael@0: masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, target)); michael@0: michael@0: // Test for failure. michael@0: masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel()); michael@0: michael@0: // Load the outparam vp[0] into output register(s). michael@0: Address outparam(StackPointer, IonOOLPropertyOpExitFrameLayout::offsetOfResult()); michael@0: masm.loadTypedOrValue(outparam, output); michael@0: michael@0: // masm.leaveExitFrame & pop locals. michael@0: masm.adjustStack(IonOOLPropertyOpExitFrameLayout::Size()); michael@0: } michael@0: michael@0: masm.icRestoreLive(liveRegs, aic); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: GenerateCallGetter(JSContext *cx, IonScript *ion, MacroAssembler &masm, michael@0: IonCache::StubAttacher &attacher, JSObject *obj, PropertyName *name, michael@0: JSObject *holder, HandleShape shape, RegisterSet &liveRegs, Register object, michael@0: TypedOrValueRegister output, void *returnAddr, Label *failures = nullptr) michael@0: { michael@0: JS_ASSERT(obj->isNative()); michael@0: JS_ASSERT(output.hasValue()); michael@0: michael@0: // Use the passed in label if there was one. Otherwise, we'll have to make our own. michael@0: Label stubFailure; michael@0: failures = failures ? failures : &stubFailure; michael@0: michael@0: // Initial shape check. michael@0: masm.branchPtr(Assembler::NotEqual, Address(object, JSObject::offsetOfShape()), michael@0: ImmGCPtr(obj->lastProperty()), failures); michael@0: michael@0: Register scratchReg = output.valueReg().scratchReg(); michael@0: michael@0: // Note: this may clobber the object register if it's used as scratch. michael@0: if (obj != holder) michael@0: GeneratePrototypeGuards(cx, ion, masm, obj, holder, object, scratchReg, failures); michael@0: michael@0: // Guard on the holder's shape. michael@0: Register holderReg = scratchReg; michael@0: masm.moveNurseryPtr(ImmMaybeNurseryPtr(holder), holderReg); michael@0: masm.branchPtr(Assembler::NotEqual, michael@0: Address(holderReg, JSObject::offsetOfShape()), michael@0: ImmGCPtr(holder->lastProperty()), michael@0: failures); michael@0: michael@0: // Now we're good to go to invoke the native call. michael@0: if (!EmitGetterCall(cx, masm, attacher, obj, holder, shape, liveRegs, object, michael@0: scratchReg, output, returnAddr)) michael@0: return false; michael@0: michael@0: // Rejoin jump. michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: // Jump to next stub. michael@0: masm.bind(failures); michael@0: attacher.jumpNextStub(masm); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: GenerateArrayLength(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher, michael@0: JSObject *obj, Register object, TypedOrValueRegister output) michael@0: { michael@0: JS_ASSERT(obj->is()); michael@0: michael@0: Label failures; michael@0: michael@0: // Guard object is a dense array. michael@0: RootedShape shape(cx, obj->lastProperty()); michael@0: if (!shape) michael@0: return false; michael@0: masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures); michael@0: michael@0: // Load length. michael@0: Register outReg; michael@0: if (output.hasValue()) { michael@0: outReg = output.valueReg().scratchReg(); michael@0: } else { michael@0: JS_ASSERT(output.type() == MIRType_Int32); michael@0: outReg = output.typedReg().gpr(); michael@0: } michael@0: michael@0: masm.loadPtr(Address(object, JSObject::offsetOfElements()), outReg); michael@0: masm.load32(Address(outReg, ObjectElements::offsetOfLength()), outReg); michael@0: michael@0: // The length is an unsigned int, but the value encodes a signed int. michael@0: JS_ASSERT(object != outReg); michael@0: masm.branchTest32(Assembler::Signed, outReg, outReg, &failures); michael@0: michael@0: if (output.hasValue()) michael@0: masm.tagValue(JSVAL_TYPE_INT32, outReg, output.valueReg()); michael@0: michael@0: /* Success. */ michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: /* Failure. */ michael@0: masm.bind(&failures); michael@0: attacher.jumpNextStub(masm); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: GenerateTypedArrayLength(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher, michael@0: JSObject *obj, Register object, TypedOrValueRegister output) michael@0: { michael@0: JS_ASSERT(obj->is()); michael@0: michael@0: Label failures; michael@0: michael@0: Register tmpReg; michael@0: if (output.hasValue()) { michael@0: tmpReg = output.valueReg().scratchReg(); michael@0: } else { michael@0: JS_ASSERT(output.type() == MIRType_Int32); michael@0: tmpReg = output.typedReg().gpr(); michael@0: } michael@0: JS_ASSERT(object != tmpReg); michael@0: michael@0: // Implement the negated version of JSObject::isTypedArray predicate. michael@0: masm.loadObjClass(object, tmpReg); michael@0: masm.branchPtr(Assembler::Below, tmpReg, ImmPtr(&TypedArrayObject::classes[0]), michael@0: &failures); michael@0: masm.branchPtr(Assembler::AboveOrEqual, tmpReg, michael@0: ImmPtr(&TypedArrayObject::classes[ScalarTypeDescr::TYPE_MAX]), michael@0: &failures); michael@0: michael@0: // Load length. michael@0: masm.loadTypedOrValue(Address(object, TypedArrayObject::lengthOffset()), output); michael@0: michael@0: /* Success. */ michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: /* Failure. */ michael@0: masm.bind(&failures); michael@0: attacher.jumpNextStub(masm); michael@0: } michael@0: michael@0: static bool michael@0: IsCacheableArrayLength(JSContext *cx, HandleObject obj, HandlePropertyName name, michael@0: TypedOrValueRegister output) michael@0: { michael@0: if (!obj->is()) michael@0: return false; michael@0: michael@0: if (output.type() != MIRType_Value && output.type() != MIRType_Int32) { michael@0: // The stub assumes that we always output Int32, so make sure our output michael@0: // is equipped to handle that. michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: static GetPropertyIC::NativeGetPropCacheability michael@0: CanAttachNativeGetProp(typename GetPropCache::Context cx, const GetPropCache &cache, michael@0: HandleObject obj, HandlePropertyName name, michael@0: MutableHandleObject holder, MutableHandleShape shape, michael@0: bool skipArrayLen = false) michael@0: { michael@0: if (!obj || !obj->isNative()) michael@0: return GetPropertyIC::CanAttachNone; michael@0: michael@0: // The lookup needs to be universally pure, otherwise we risk calling hooks out michael@0: // of turn. We don't mind doing this even when purity isn't required, because we michael@0: // only miss out on shape hashification, which is only a temporary perf cost. michael@0: // The limits were arbitrarily set, anyways. michael@0: if (!LookupPropertyPure(obj, NameToId(name), holder.address(), shape.address())) michael@0: return GetPropertyIC::CanAttachNone; michael@0: michael@0: RootedScript script(cx); michael@0: jsbytecode *pc; michael@0: cache.getScriptedLocation(&script, &pc); michael@0: if (IsCacheableGetPropReadSlot(obj, holder, shape) || michael@0: IsCacheableNoProperty(obj, holder, shape, pc, cache.output())) michael@0: { michael@0: return GetPropertyIC::CanAttachReadSlot; michael@0: } michael@0: michael@0: // |length| is a non-configurable getter property on ArrayObjects. Any time this michael@0: // check would have passed, we can install a getter stub instead. Allow people to michael@0: // make that decision themselves with skipArrayLen michael@0: if (!skipArrayLen && cx->names().length == name && cache.allowArrayLength(cx, obj) && michael@0: IsCacheableArrayLength(cx, obj, name, cache.output())) michael@0: { michael@0: // The array length property is non-configurable, which means both that michael@0: // checking the class of the object and the name of the property is enough michael@0: // and that we don't need to worry about monitoring, since we know the michael@0: // return type statically. michael@0: return GetPropertyIC::CanAttachArrayLength; michael@0: } michael@0: michael@0: // IonBuilder guarantees that it's impossible to generate a GetPropertyIC with michael@0: // allowGetters() true and cache.output().hasValue() false. If this isn't true, michael@0: // we will quickly assert during stub generation. michael@0: if (cache.allowGetters() && michael@0: (IsCacheableGetPropCallNative(obj, holder, shape) || michael@0: IsCacheableGetPropCallPropertyOp(obj, holder, shape))) michael@0: { michael@0: // Don't enable getter call if cache is parallel or idempotent, since michael@0: // they can be effectful. This is handled by allowGetters() michael@0: return GetPropertyIC::CanAttachCallGetter; michael@0: } michael@0: michael@0: return GetPropertyIC::CanAttachNone; michael@0: } michael@0: michael@0: bool michael@0: GetPropertyIC::allowArrayLength(Context cx, HandleObject obj) const michael@0: { michael@0: if (!idempotent()) michael@0: return true; michael@0: michael@0: uint32_t locationIndex, numLocations; michael@0: getLocationInfo(&locationIndex, &numLocations); michael@0: michael@0: IonScript *ion = GetTopIonJSScript(cx)->ionScript(); michael@0: CacheLocation *locs = ion->getCacheLocs(locationIndex); michael@0: for (size_t i = 0; i < numLocations; i++) { michael@0: CacheLocation &curLoc = locs[i]; michael@0: types::StackTypeSet *bcTypes = michael@0: types::TypeScript::BytecodeTypes(curLoc.script, curLoc.pc); michael@0: michael@0: if (!bcTypes->hasType(types::Type::Int32Type())) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: GetPropertyIC::tryAttachNative(JSContext *cx, IonScript *ion, HandleObject obj, michael@0: HandlePropertyName name, void *returnAddr, bool *emitted) michael@0: { michael@0: JS_ASSERT(canAttachStub()); michael@0: JS_ASSERT(!*emitted); michael@0: michael@0: RootedShape shape(cx); michael@0: RootedObject holder(cx); michael@0: michael@0: NativeGetPropCacheability type = michael@0: CanAttachNativeGetProp(cx, *this, obj, name, &holder, &shape); michael@0: if (type == CanAttachNone) michael@0: return true; michael@0: michael@0: *emitted = true; michael@0: michael@0: MacroAssembler masm(cx, ion, script_, pc_); michael@0: michael@0: RepatchStubAppender attacher(*this); michael@0: const char *attachKind; michael@0: michael@0: switch (type) { michael@0: case CanAttachReadSlot: michael@0: GenerateReadSlot(cx, ion, masm, attacher, obj, holder, michael@0: shape, object(), output()); michael@0: attachKind = idempotent() ? "idempotent reading" michael@0: : "non idempotent reading"; michael@0: break; michael@0: case CanAttachCallGetter: michael@0: if (!GenerateCallGetter(cx, ion, masm, attacher, obj, name, holder, shape, michael@0: liveRegs_, object(), output(), returnAddr)) michael@0: { michael@0: return false; michael@0: } michael@0: attachKind = "getter call"; michael@0: break; michael@0: case CanAttachArrayLength: michael@0: if (!GenerateArrayLength(cx, masm, attacher, obj, object(), output())) michael@0: return false; michael@0: michael@0: attachKind = "array length"; michael@0: break; michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("Bad NativeGetPropCacheability"); michael@0: } michael@0: return linkAndAttachStub(cx, masm, attacher, ion, attachKind); michael@0: } michael@0: michael@0: bool michael@0: GetPropertyIC::tryAttachTypedArrayLength(JSContext *cx, IonScript *ion, HandleObject obj, michael@0: HandlePropertyName name, bool *emitted) michael@0: { michael@0: JS_ASSERT(canAttachStub()); michael@0: JS_ASSERT(!*emitted); michael@0: michael@0: if (!obj->is()) michael@0: return true; michael@0: michael@0: if (cx->names().length != name) michael@0: return true; michael@0: michael@0: if (hasTypedArrayLengthStub()) michael@0: return true; michael@0: michael@0: if (output().type() != MIRType_Value && output().type() != MIRType_Int32) { michael@0: // The next execution should cause an invalidation because the type michael@0: // does not fit. michael@0: return true; michael@0: } michael@0: michael@0: if (idempotent()) michael@0: return true; michael@0: michael@0: *emitted = true; michael@0: michael@0: MacroAssembler masm(cx, ion); michael@0: RepatchStubAppender attacher(*this); michael@0: GenerateTypedArrayLength(cx, masm, attacher, obj, object(), output()); michael@0: michael@0: JS_ASSERT(!hasTypedArrayLengthStub_); michael@0: hasTypedArrayLengthStub_ = true; michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "typed array length"); michael@0: } michael@0: michael@0: michael@0: static bool michael@0: EmitCallProxyGet(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher, michael@0: PropertyName *name, RegisterSet liveRegs, Register object, michael@0: TypedOrValueRegister output, jsbytecode *pc, void *returnAddr) michael@0: { michael@0: JS_ASSERT(output.hasValue()); michael@0: MacroAssembler::AfterICSaveLive aic = masm.icSaveLive(liveRegs); michael@0: michael@0: // Remaining registers should be free, but we need to use |object| still michael@0: // so leave it alone. michael@0: RegisterSet regSet(RegisterSet::All()); michael@0: regSet.take(AnyRegister(object)); michael@0: michael@0: // Proxy::get(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id, michael@0: // MutableHandleValue vp) michael@0: Register argJSContextReg = regSet.takeGeneral(); michael@0: Register argProxyReg = regSet.takeGeneral(); michael@0: Register argIdReg = regSet.takeGeneral(); michael@0: Register argVpReg = regSet.takeGeneral(); michael@0: michael@0: Register scratch = regSet.takeGeneral(); michael@0: michael@0: void *getFunction = JSOp(*pc) == JSOP_CALLPROP ? michael@0: JS_FUNC_TO_DATA_PTR(void *, Proxy::callProp) : michael@0: JS_FUNC_TO_DATA_PTR(void *, Proxy::get); michael@0: michael@0: // Push stubCode for marking. michael@0: attacher.pushStubCodePointer(masm); michael@0: michael@0: // Push args on stack first so we can take pointers to make handles. michael@0: masm.Push(UndefinedValue()); michael@0: masm.movePtr(StackPointer, argVpReg); michael@0: michael@0: RootedId propId(cx, AtomToId(name)); michael@0: masm.Push(propId, scratch); michael@0: masm.movePtr(StackPointer, argIdReg); michael@0: michael@0: // Pushing object and receiver. Both are the same, so Handle to one is equivalent to michael@0: // handle to other. michael@0: masm.Push(object); michael@0: masm.Push(object); michael@0: masm.movePtr(StackPointer, argProxyReg); michael@0: michael@0: masm.loadJSContext(argJSContextReg); michael@0: michael@0: if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic)) michael@0: return false; michael@0: masm.enterFakeExitFrame(ION_FRAME_OOL_PROXY); michael@0: michael@0: // Make the call. michael@0: masm.setupUnalignedABICall(5, scratch); michael@0: masm.passABIArg(argJSContextReg); michael@0: masm.passABIArg(argProxyReg); michael@0: masm.passABIArg(argProxyReg); michael@0: masm.passABIArg(argIdReg); michael@0: masm.passABIArg(argVpReg); michael@0: masm.callWithABI(getFunction); michael@0: michael@0: // Test for failure. michael@0: masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel()); michael@0: michael@0: // Load the outparam vp[0] into output register(s). michael@0: Address outparam(StackPointer, IonOOLProxyExitFrameLayout::offsetOfResult()); michael@0: masm.loadTypedOrValue(outparam, output); michael@0: michael@0: // masm.leaveExitFrame & pop locals michael@0: masm.adjustStack(IonOOLProxyExitFrameLayout::Size()); michael@0: michael@0: masm.icRestoreLive(liveRegs, aic); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: GetPropertyIC::tryAttachDOMProxyShadowed(JSContext *cx, IonScript *ion, michael@0: HandleObject obj, void *returnAddr, michael@0: bool *emitted) michael@0: { michael@0: JS_ASSERT(canAttachStub()); michael@0: JS_ASSERT(!*emitted); michael@0: JS_ASSERT(IsCacheableDOMProxy(obj)); michael@0: JS_ASSERT(monitoredResult()); michael@0: JS_ASSERT(output().hasValue()); michael@0: michael@0: if (idempotent()) michael@0: return true; michael@0: michael@0: *emitted = true; michael@0: michael@0: Label failures; michael@0: MacroAssembler masm(cx, ion, script_, pc_); michael@0: RepatchStubAppender attacher(*this); michael@0: michael@0: // Guard on the shape of the object. michael@0: attacher.branchNextStubOrLabel(masm, Assembler::NotEqual, michael@0: Address(object(), JSObject::offsetOfShape()), michael@0: ImmGCPtr(obj->lastProperty()), michael@0: &failures); michael@0: michael@0: // Make sure object is a DOMProxy michael@0: GenerateDOMProxyChecks(cx, masm, obj, name(), object(), &failures, michael@0: /*skipExpandoCheck=*/true); michael@0: michael@0: if (!EmitCallProxyGet(cx, masm, attacher, name(), liveRegs_, object(), output(), michael@0: pc(), returnAddr)) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: // Success. michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: // Failure. michael@0: masm.bind(&failures); michael@0: attacher.jumpNextStub(masm); michael@0: michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "list base shadowed get"); michael@0: } michael@0: michael@0: bool michael@0: GetPropertyIC::tryAttachDOMProxyUnshadowed(JSContext *cx, IonScript *ion, HandleObject obj, michael@0: HandlePropertyName name, bool resetNeeded, michael@0: void *returnAddr, bool *emitted) michael@0: { michael@0: JS_ASSERT(canAttachStub()); michael@0: JS_ASSERT(!*emitted); michael@0: JS_ASSERT(IsCacheableDOMProxy(obj)); michael@0: JS_ASSERT(monitoredResult()); michael@0: JS_ASSERT(output().hasValue()); michael@0: michael@0: RootedObject checkObj(cx, obj->getTaggedProto().toObjectOrNull()); michael@0: RootedObject holder(cx); michael@0: RootedShape shape(cx); michael@0: michael@0: NativeGetPropCacheability canCache = michael@0: CanAttachNativeGetProp(cx, *this, checkObj, name, &holder, &shape, michael@0: /* skipArrayLen = */true); michael@0: JS_ASSERT(canCache != CanAttachArrayLength); michael@0: michael@0: if (canCache == CanAttachNone) michael@0: return true; michael@0: michael@0: // Make sure we observe our invariants if we're gonna deoptimize. michael@0: if (!holder && idempotent()) michael@0: return true; michael@0: michael@0: *emitted = true; michael@0: michael@0: if (resetNeeded) { michael@0: // If we know that we have a DoesntShadowUnique object, then michael@0: // we reset the cache to clear out an existing IC for the object michael@0: // (if there is one). The generation is a constant in the generated michael@0: // code and we will not have the same generation again for this michael@0: // object, so the generation check in the existing IC would always michael@0: // fail anyway. michael@0: reset(); michael@0: } michael@0: michael@0: Label failures; michael@0: MacroAssembler masm(cx, ion, script_, pc_); michael@0: RepatchStubAppender attacher(*this); michael@0: michael@0: // Guard on the shape of the object. michael@0: attacher.branchNextStubOrLabel(masm, Assembler::NotEqual, michael@0: Address(object(), JSObject::offsetOfShape()), michael@0: ImmGCPtr(obj->lastProperty()), michael@0: &failures); michael@0: michael@0: // Make sure object is a DOMProxy proxy michael@0: GenerateDOMProxyChecks(cx, masm, obj, name, object(), &failures); michael@0: michael@0: if (holder) { michael@0: // Found the property on the prototype chain. Treat it like a native michael@0: // getprop. michael@0: Register scratchReg = output().valueReg().scratchReg(); michael@0: GeneratePrototypeGuards(cx, ion, masm, obj, holder, object(), scratchReg, &failures); michael@0: michael@0: // Rename scratch for clarity. michael@0: Register holderReg = scratchReg; michael@0: michael@0: // Guard on the holder of the property michael@0: masm.moveNurseryPtr(ImmMaybeNurseryPtr(holder), holderReg); michael@0: masm.branchPtr(Assembler::NotEqual, michael@0: Address(holderReg, JSObject::offsetOfShape()), michael@0: ImmGCPtr(holder->lastProperty()), michael@0: &failures); michael@0: michael@0: if (canCache == CanAttachReadSlot) { michael@0: EmitLoadSlot(masm, holder, shape, holderReg, output(), scratchReg); michael@0: } else { michael@0: // EmitGetterCall() expects |obj| to be the object the property is michael@0: // on to do some checks. Since we actually looked at checkObj, and michael@0: // no extra guards will be generated, we can just pass that instead. michael@0: JS_ASSERT(canCache == CanAttachCallGetter); michael@0: JS_ASSERT(!idempotent()); michael@0: if (!EmitGetterCall(cx, masm, attacher, checkObj, holder, shape, liveRegs_, michael@0: object(), scratchReg, output(), returnAddr)) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: } else { michael@0: // Property was not found on the prototype chain. Deoptimize down to michael@0: // proxy get call michael@0: JS_ASSERT(!idempotent()); michael@0: if (!EmitCallProxyGet(cx, masm, attacher, name, liveRegs_, object(), output(), michael@0: pc(), returnAddr)) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: attacher.jumpRejoin(masm); michael@0: masm.bind(&failures); michael@0: attacher.jumpNextStub(masm); michael@0: michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "unshadowed proxy get"); michael@0: } michael@0: michael@0: bool michael@0: GetPropertyIC::tryAttachProxy(JSContext *cx, IonScript *ion, HandleObject obj, michael@0: HandlePropertyName name, void *returnAddr, michael@0: bool *emitted) michael@0: { michael@0: JS_ASSERT(canAttachStub()); michael@0: JS_ASSERT(!*emitted); michael@0: michael@0: if (!obj->is()) michael@0: return true; michael@0: michael@0: // TI can't be sure about our properties, so make sure anything michael@0: // we return can be monitored directly. michael@0: if (!monitoredResult()) michael@0: return true; michael@0: michael@0: // Skim off DOM proxies. michael@0: if (IsCacheableDOMProxy(obj)) { michael@0: RootedId id(cx, NameToId(name)); michael@0: DOMProxyShadowsResult shadows = GetDOMProxyShadowsCheck()(cx, obj, id); michael@0: if (shadows == ShadowCheckFailed) michael@0: return false; michael@0: if (shadows == Shadows) michael@0: return tryAttachDOMProxyShadowed(cx, ion, obj, returnAddr, emitted); michael@0: michael@0: return tryAttachDOMProxyUnshadowed(cx, ion, obj, name, shadows == DoesntShadowUnique, michael@0: returnAddr, emitted); michael@0: } michael@0: michael@0: return tryAttachGenericProxy(cx, ion, obj, name, returnAddr, emitted); michael@0: } michael@0: michael@0: static void michael@0: GenerateProxyClassGuards(MacroAssembler &masm, Register object, Register scratchReg, michael@0: Label *failures) michael@0: { michael@0: masm.loadObjClass(object, scratchReg); michael@0: masm.branchTest32(Assembler::Zero, michael@0: Address(scratchReg, Class::offsetOfFlags()), michael@0: Imm32(JSCLASS_IS_PROXY), failures); michael@0: } michael@0: michael@0: bool michael@0: GetPropertyIC::tryAttachGenericProxy(JSContext *cx, IonScript *ion, HandleObject obj, michael@0: HandlePropertyName name, void *returnAddr, michael@0: bool *emitted) michael@0: { michael@0: JS_ASSERT(canAttachStub()); michael@0: JS_ASSERT(!*emitted); michael@0: JS_ASSERT(obj->is()); michael@0: JS_ASSERT(monitoredResult()); michael@0: JS_ASSERT(output().hasValue()); michael@0: michael@0: if (hasGenericProxyStub()) michael@0: return true; michael@0: michael@0: if (idempotent()) michael@0: return true; michael@0: michael@0: *emitted = true; michael@0: michael@0: Label failures; michael@0: MacroAssembler masm(cx, ion, script_, pc_); michael@0: RepatchStubAppender attacher(*this); michael@0: michael@0: Register scratchReg = output().valueReg().scratchReg(); michael@0: michael@0: GenerateProxyClassGuards(masm, object(), scratchReg, &failures); michael@0: michael@0: // Ensure that the incoming object is not a DOM proxy, so that we can get to michael@0: // the specialized stubs michael@0: masm.branchTestProxyHandlerFamily(Assembler::Equal, object(), scratchReg, michael@0: GetDOMProxyHandlerFamily(), &failures); michael@0: michael@0: if (!EmitCallProxyGet(cx, masm, attacher, name, liveRegs_, object(), output(), michael@0: pc(), returnAddr)) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: masm.bind(&failures); michael@0: attacher.jumpNextStub(masm); michael@0: michael@0: JS_ASSERT(!hasGenericProxyStub_); michael@0: hasGenericProxyStub_ = true; michael@0: michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "Generic Proxy get"); michael@0: } michael@0: michael@0: bool michael@0: GetPropertyIC::tryAttachArgumentsLength(JSContext *cx, IonScript *ion, HandleObject obj, michael@0: HandlePropertyName name, bool *emitted) michael@0: { michael@0: JS_ASSERT(canAttachStub()); michael@0: JS_ASSERT(!*emitted); michael@0: michael@0: if (name != cx->names().length) michael@0: return true; michael@0: if (!IsOptimizableArgumentsObjectForLength(obj)) michael@0: return true; michael@0: michael@0: MIRType outputType = output().type(); michael@0: if (!(outputType == MIRType_Value || outputType == MIRType_Int32)) michael@0: return true; michael@0: michael@0: if (hasArgumentsLengthStub(obj->is())) michael@0: return true; michael@0: michael@0: *emitted = true; michael@0: michael@0: JS_ASSERT(!idempotent()); michael@0: michael@0: Label failures; michael@0: MacroAssembler masm(cx, ion); michael@0: RepatchStubAppender attacher(*this); michael@0: michael@0: Register tmpReg; michael@0: if (output().hasValue()) { michael@0: tmpReg = output().valueReg().scratchReg(); michael@0: } else { michael@0: JS_ASSERT(output().type() == MIRType_Int32); michael@0: tmpReg = output().typedReg().gpr(); michael@0: } michael@0: JS_ASSERT(object() != tmpReg); michael@0: michael@0: const Class *clasp = obj->is() ? &StrictArgumentsObject::class_ michael@0: : &NormalArgumentsObject::class_; michael@0: michael@0: masm.branchTestObjClass(Assembler::NotEqual, object(), tmpReg, clasp, &failures); michael@0: michael@0: // Get initial ArgsObj length value, test if length has been overridden. michael@0: masm.unboxInt32(Address(object(), ArgumentsObject::getInitialLengthSlotOffset()), tmpReg); michael@0: masm.branchTest32(Assembler::NonZero, tmpReg, Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT), michael@0: &failures); michael@0: michael@0: masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), tmpReg); michael@0: michael@0: // If output is Int32, result is already in right place, otherwise box it into output. michael@0: if (output().hasValue()) michael@0: masm.tagValue(JSVAL_TYPE_INT32, tmpReg, output().valueReg()); michael@0: michael@0: // Success. michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: // Failure. michael@0: masm.bind(&failures); michael@0: attacher.jumpNextStub(masm); michael@0: michael@0: if (obj->is()) { michael@0: JS_ASSERT(!hasStrictArgumentsLengthStub_); michael@0: hasStrictArgumentsLengthStub_ = true; michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj length (strict)"); michael@0: } michael@0: michael@0: JS_ASSERT(!hasNormalArgumentsLengthStub_); michael@0: hasNormalArgumentsLengthStub_ = true; michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj length (normal)"); michael@0: } michael@0: michael@0: bool michael@0: GetPropertyIC::tryAttachStub(JSContext *cx, IonScript *ion, HandleObject obj, michael@0: HandlePropertyName name, void *returnAddr, bool *emitted) michael@0: { michael@0: JS_ASSERT(!*emitted); michael@0: michael@0: if (!canAttachStub()) michael@0: return true; michael@0: michael@0: if (!*emitted && !tryAttachArgumentsLength(cx, ion, obj, name, emitted)) michael@0: return false; michael@0: michael@0: if (!*emitted && !tryAttachProxy(cx, ion, obj, name, returnAddr, emitted)) michael@0: return false; michael@0: michael@0: if (!*emitted && !tryAttachNative(cx, ion, obj, name, returnAddr, emitted)) michael@0: return false; michael@0: michael@0: if (!*emitted && !tryAttachTypedArrayLength(cx, ion, obj, name, emitted)) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* static */ bool michael@0: GetPropertyIC::update(JSContext *cx, size_t cacheIndex, michael@0: HandleObject obj, MutableHandleValue vp) michael@0: { michael@0: void *returnAddr; michael@0: RootedScript topScript(cx, GetTopIonJSScript(cx, &returnAddr)); michael@0: IonScript *ion = topScript->ionScript(); michael@0: michael@0: GetPropertyIC &cache = ion->getCache(cacheIndex).toGetProperty(); michael@0: RootedPropertyName name(cx, cache.name()); michael@0: michael@0: // Override the return value if we are invalidated (bug 728188). michael@0: AutoDetectInvalidation adi(cx, vp.address(), ion); michael@0: michael@0: // If the cache is idempotent, we will redo the op in the interpreter. michael@0: if (cache.idempotent()) michael@0: adi.disable(); michael@0: michael@0: // For now, just stop generating new stubs once we hit the stub count michael@0: // limit. Once we can make calls from within generated stubs, a new call michael@0: // stub will be generated instead and the previous stubs unlinked. michael@0: bool emitted = false; michael@0: if (!cache.tryAttachStub(cx, ion, obj, name, returnAddr, &emitted)) michael@0: return false; michael@0: michael@0: if (cache.idempotent() && !emitted) { michael@0: // Invalidate the cache if the property was not found, or was found on michael@0: // a non-native object. This ensures: michael@0: // 1) The property read has no observable side-effects. michael@0: // 2) There's no need to dynamically monitor the return type. This would michael@0: // be complicated since (due to GVN) there can be multiple pc's michael@0: // associated with a single idempotent cache. michael@0: IonSpew(IonSpew_InlineCaches, "Invalidating from idempotent cache %s:%d", michael@0: topScript->filename(), topScript->lineno()); michael@0: michael@0: topScript->setInvalidatedIdempotentCache(); michael@0: michael@0: // Do not re-invalidate if the lookup already caused invalidation. michael@0: if (!topScript->hasIonScript()) michael@0: return true; michael@0: michael@0: return Invalidate(cx, topScript); michael@0: } michael@0: michael@0: RootedId id(cx, NameToId(name)); michael@0: if (!JSObject::getGeneric(cx, obj, obj, id, vp)) michael@0: return false; michael@0: michael@0: if (!cache.idempotent()) { michael@0: RootedScript script(cx); michael@0: jsbytecode *pc; michael@0: cache.getScriptedLocation(&script, &pc); michael@0: michael@0: // If the cache is idempotent, the property exists so we don't have to michael@0: // call __noSuchMethod__. michael@0: michael@0: #if JS_HAS_NO_SUCH_METHOD michael@0: // Handle objects with __noSuchMethod__. michael@0: if (JSOp(*pc) == JSOP_CALLPROP && MOZ_UNLIKELY(vp.isUndefined())) { michael@0: if (!OnUnknownMethod(cx, obj, IdToValue(id), vp)) michael@0: return false; michael@0: } michael@0: #endif michael@0: michael@0: // Monitor changes to cache entry. michael@0: if (!cache.monitoredResult()) michael@0: types::TypeScript::Monitor(cx, script, pc, vp); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: GetPropertyIC::reset() michael@0: { michael@0: RepatchIonCache::reset(); michael@0: hasTypedArrayLengthStub_ = false; michael@0: hasStrictArgumentsLengthStub_ = false; michael@0: hasNormalArgumentsLengthStub_ = false; michael@0: hasGenericProxyStub_ = false; michael@0: } michael@0: michael@0: bool michael@0: ParallelIonCache::initStubbedShapes(JSContext *cx) michael@0: { michael@0: JS_ASSERT(isAllocated()); michael@0: if (!stubbedShapes_) { michael@0: stubbedShapes_ = cx->new_(cx); michael@0: return stubbedShapes_ && stubbedShapes_->init(); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: ParallelIonCache::hasOrAddStubbedShape(LockedJSContext &cx, Shape *shape, bool *alreadyStubbed) michael@0: { michael@0: // Check if we have already stubbed the current object to avoid michael@0: // attaching a duplicate stub. michael@0: if (!initStubbedShapes(cx)) michael@0: return false; michael@0: ShapeSet::AddPtr p = stubbedShapes_->lookupForAdd(shape); michael@0: if ((*alreadyStubbed = !!p)) michael@0: return true; michael@0: return stubbedShapes_->add(p, shape); michael@0: } michael@0: michael@0: void michael@0: ParallelIonCache::reset() michael@0: { michael@0: DispatchIonCache::reset(); michael@0: if (stubbedShapes_) michael@0: stubbedShapes_->clear(); michael@0: } michael@0: michael@0: void michael@0: ParallelIonCache::destroy() michael@0: { michael@0: DispatchIonCache::destroy(); michael@0: js_delete(stubbedShapes_); michael@0: } michael@0: michael@0: void michael@0: GetPropertyParIC::reset() michael@0: { michael@0: ParallelIonCache::reset(); michael@0: hasTypedArrayLengthStub_ = false; michael@0: } michael@0: michael@0: bool michael@0: GetPropertyParIC::attachReadSlot(LockedJSContext &cx, IonScript *ion, JSObject *obj, michael@0: JSObject *holder, Shape *shape) michael@0: { michael@0: // Ready to generate the read slot stub. michael@0: DispatchStubPrepender attacher(*this); michael@0: MacroAssembler masm(cx, ion); michael@0: GenerateReadSlot(cx, ion, masm, attacher, obj, holder, shape, object(), output()); michael@0: michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "parallel reading"); michael@0: } michael@0: michael@0: bool michael@0: GetPropertyParIC::attachArrayLength(LockedJSContext &cx, IonScript *ion, JSObject *obj) michael@0: { michael@0: MacroAssembler masm(cx, ion); michael@0: DispatchStubPrepender attacher(*this); michael@0: if (!GenerateArrayLength(cx, masm, attacher, obj, object(), output())) michael@0: return false; michael@0: michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "parallel array length"); michael@0: } michael@0: michael@0: bool michael@0: GetPropertyParIC::attachTypedArrayLength(LockedJSContext &cx, IonScript *ion, JSObject *obj) michael@0: { michael@0: MacroAssembler masm(cx, ion); michael@0: DispatchStubPrepender attacher(*this); michael@0: GenerateTypedArrayLength(cx, masm, attacher, obj, object(), output()); michael@0: michael@0: JS_ASSERT(!hasTypedArrayLengthStub_); michael@0: hasTypedArrayLengthStub_ = true; michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "parallel typed array length"); michael@0: } michael@0: michael@0: bool michael@0: GetPropertyParIC::update(ForkJoinContext *cx, size_t cacheIndex, michael@0: HandleObject obj, MutableHandleValue vp) michael@0: { michael@0: IonScript *ion = GetTopIonJSScript(cx)->parallelIonScript(); michael@0: GetPropertyParIC &cache = ion->getCache(cacheIndex).toGetPropertyPar(); michael@0: michael@0: // Grab the property early, as the pure path is fast anyways and doesn't michael@0: // need a lock. If we can't do it purely, bail out of parallel execution. michael@0: if (!GetPropertyPure(cx, obj, NameToId(cache.name()), vp.address())) michael@0: return false; michael@0: michael@0: // Avoid unnecessary locking if cannot attach stubs. michael@0: if (!cache.canAttachStub()) michael@0: return true; michael@0: michael@0: { michael@0: // Lock the context before mutating the cache. Ideally we'd like to do michael@0: // finer-grained locking, with one lock per cache. However, generating michael@0: // new jitcode uses a global ExecutableAllocator tied to the runtime. michael@0: LockedJSContext ncx(cx); michael@0: michael@0: if (cache.canAttachStub()) { michael@0: bool alreadyStubbed; michael@0: if (!cache.hasOrAddStubbedShape(ncx, obj->lastProperty(), &alreadyStubbed)) michael@0: return cx->setPendingAbortFatal(ParallelBailoutFailedIC); michael@0: if (alreadyStubbed) michael@0: return true; michael@0: michael@0: // See note about the stub limit in GetPropertyCache. michael@0: bool attachedStub = false; michael@0: michael@0: { michael@0: RootedShape shape(ncx); michael@0: RootedObject holder(ncx); michael@0: RootedPropertyName name(ncx, cache.name()); michael@0: michael@0: GetPropertyIC::NativeGetPropCacheability canCache = michael@0: CanAttachNativeGetProp(ncx, cache, obj, name, &holder, &shape); michael@0: michael@0: if (canCache == GetPropertyIC::CanAttachReadSlot) { michael@0: if (!cache.attachReadSlot(ncx, ion, obj, holder, shape)) michael@0: return cx->setPendingAbortFatal(ParallelBailoutFailedIC); michael@0: attachedStub = true; michael@0: } michael@0: michael@0: if (!attachedStub && canCache == GetPropertyIC::CanAttachArrayLength) { michael@0: if (!cache.attachArrayLength(ncx, ion, obj)) michael@0: return cx->setPendingAbortFatal(ParallelBailoutFailedIC); michael@0: attachedStub = true; michael@0: } michael@0: } michael@0: michael@0: if (!attachedStub && !cache.hasTypedArrayLengthStub() && michael@0: obj->is() && cx->names().length == cache.name() && michael@0: (cache.output().type() == MIRType_Value || cache.output().type() == MIRType_Int32)) michael@0: { michael@0: if (!cache.attachTypedArrayLength(ncx, ion, obj)) michael@0: return cx->setPendingAbortFatal(ParallelBailoutFailedIC); michael@0: attachedStub = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: IonCache::disable() michael@0: { michael@0: reset(); michael@0: this->disabled_ = 1; michael@0: } michael@0: michael@0: void michael@0: IonCache::reset() michael@0: { michael@0: this->stubCount_ = 0; michael@0: } michael@0: michael@0: void michael@0: IonCache::destroy() michael@0: { michael@0: } michael@0: michael@0: static void michael@0: GenerateSetSlot(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher, michael@0: JSObject *obj, Shape *shape, Register object, ConstantOrRegister value, michael@0: bool needsTypeBarrier, bool checkTypeset) michael@0: { michael@0: JS_ASSERT(obj->isNative()); michael@0: michael@0: Label failures, barrierFailure; michael@0: masm.branchPtr(Assembler::NotEqual, michael@0: Address(object, JSObject::offsetOfShape()), michael@0: ImmGCPtr(obj->lastProperty()), &failures); michael@0: michael@0: // Guard that the incoming value is in the type set for the property michael@0: // if a type barrier is required. michael@0: if (needsTypeBarrier) { michael@0: // We can't do anything that would change the HeapTypeSet, so michael@0: // just guard that it's already there. michael@0: michael@0: // Obtain and guard on the TypeObject of the object. michael@0: types::TypeObject *type = obj->type(); michael@0: masm.branchPtr(Assembler::NotEqual, michael@0: Address(object, JSObject::offsetOfType()), michael@0: ImmGCPtr(type), &failures); michael@0: michael@0: if (checkTypeset) { michael@0: TypedOrValueRegister valReg = value.reg(); michael@0: types::HeapTypeSet *propTypes = type->maybeGetProperty(shape->propid()); michael@0: JS_ASSERT(propTypes); michael@0: JS_ASSERT(!propTypes->unknown()); michael@0: michael@0: Register scratchReg = object; michael@0: masm.push(scratchReg); michael@0: michael@0: masm.guardTypeSet(valReg, propTypes, scratchReg, &barrierFailure); michael@0: masm.pop(object); michael@0: } michael@0: } michael@0: michael@0: if (obj->isFixedSlot(shape->slot())) { michael@0: Address addr(object, JSObject::getFixedSlotOffset(shape->slot())); michael@0: michael@0: if (cx->zone()->needsBarrier()) michael@0: masm.callPreBarrier(addr, MIRType_Value); michael@0: michael@0: masm.storeConstantOrRegister(value, addr); michael@0: } else { michael@0: Register slotsReg = object; michael@0: masm.loadPtr(Address(object, JSObject::offsetOfSlots()), slotsReg); michael@0: michael@0: Address addr(slotsReg, obj->dynamicSlotIndex(shape->slot()) * sizeof(Value)); michael@0: michael@0: if (cx->zone()->needsBarrier()) michael@0: masm.callPreBarrier(addr, MIRType_Value); michael@0: michael@0: masm.storeConstantOrRegister(value, addr); michael@0: } michael@0: michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: if (barrierFailure.used()) { michael@0: masm.bind(&barrierFailure); michael@0: masm.pop(object); michael@0: } michael@0: michael@0: masm.bind(&failures); michael@0: attacher.jumpNextStub(masm); michael@0: } michael@0: michael@0: bool michael@0: SetPropertyIC::attachSetSlot(JSContext *cx, IonScript *ion, HandleObject obj, michael@0: HandleShape shape, bool checkTypeset) michael@0: { michael@0: MacroAssembler masm(cx, ion); michael@0: RepatchStubAppender attacher(*this); michael@0: GenerateSetSlot(cx, masm, attacher, obj, shape, object(), value(), needsTypeBarrier(), michael@0: checkTypeset); michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "setting"); michael@0: } michael@0: michael@0: static bool michael@0: IsCacheableSetPropCallNative(HandleObject obj, HandleObject holder, HandleShape shape) michael@0: { michael@0: JS_ASSERT(obj->isNative()); michael@0: michael@0: if (!shape || !IsCacheableProtoChain(obj, holder)) michael@0: return false; michael@0: michael@0: return shape->hasSetterValue() && shape->setterObject() && michael@0: shape->setterObject()->is() && michael@0: shape->setterObject()->as().isNative(); michael@0: } michael@0: michael@0: static bool michael@0: IsCacheableSetPropCallPropertyOp(HandleObject obj, HandleObject holder, HandleShape shape) michael@0: { michael@0: JS_ASSERT(obj->isNative()); michael@0: michael@0: if (!shape) michael@0: return false; michael@0: michael@0: if (!IsCacheableProtoChain(obj, holder)) michael@0: return false; michael@0: michael@0: if (shape->hasSlot()) michael@0: return false; michael@0: michael@0: if (shape->hasDefaultSetter()) michael@0: return false; michael@0: michael@0: if (shape->hasSetterValue()) michael@0: return false; michael@0: michael@0: // Despite the vehement claims of Shape.h that writable() is only michael@0: // relevant for data descriptors, some PropertyOp setters care michael@0: // desperately about its value. The flag should be always true, apart michael@0: // from these rare instances. michael@0: if (!shape->writable()) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: EmitCallProxySet(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher, michael@0: HandleId propId, RegisterSet liveRegs, Register object, michael@0: ConstantOrRegister value, void *returnAddr, bool strict) michael@0: { michael@0: MacroAssembler::AfterICSaveLive aic = masm.icSaveLive(liveRegs); michael@0: michael@0: // Remaining registers should be free, but we need to use |object| still michael@0: // so leave it alone. michael@0: RegisterSet regSet(RegisterSet::All()); michael@0: regSet.take(AnyRegister(object)); michael@0: michael@0: // Proxy::set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id, michael@0: // bool strict, MutableHandleValue vp) michael@0: Register argJSContextReg = regSet.takeGeneral(); michael@0: Register argProxyReg = regSet.takeGeneral(); michael@0: Register argIdReg = regSet.takeGeneral(); michael@0: Register argVpReg = regSet.takeGeneral(); michael@0: Register argStrictReg = regSet.takeGeneral(); michael@0: michael@0: Register scratch = regSet.takeGeneral(); michael@0: michael@0: // Push stubCode for marking. michael@0: attacher.pushStubCodePointer(masm); michael@0: michael@0: // Push args on stack first so we can take pointers to make handles. michael@0: masm.Push(value); michael@0: masm.movePtr(StackPointer, argVpReg); michael@0: michael@0: masm.Push(propId, scratch); michael@0: masm.movePtr(StackPointer, argIdReg); michael@0: michael@0: // Pushing object and receiver. Both are the same, so Handle to one is equivalent to michael@0: // handle to other. michael@0: masm.Push(object); michael@0: masm.Push(object); michael@0: masm.movePtr(StackPointer, argProxyReg); michael@0: michael@0: masm.loadJSContext(argJSContextReg); michael@0: masm.move32(Imm32(strict? 1 : 0), argStrictReg); michael@0: michael@0: if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic)) michael@0: return false; michael@0: masm.enterFakeExitFrame(ION_FRAME_OOL_PROXY); michael@0: michael@0: // Make the call. michael@0: masm.setupUnalignedABICall(6, scratch); michael@0: masm.passABIArg(argJSContextReg); michael@0: masm.passABIArg(argProxyReg); michael@0: masm.passABIArg(argProxyReg); michael@0: masm.passABIArg(argIdReg); michael@0: masm.passABIArg(argStrictReg); michael@0: masm.passABIArg(argVpReg); michael@0: masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, Proxy::set)); michael@0: michael@0: // Test for failure. michael@0: masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel()); michael@0: michael@0: // masm.leaveExitFrame & pop locals michael@0: masm.adjustStack(IonOOLProxyExitFrameLayout::Size()); michael@0: michael@0: masm.icRestoreLive(liveRegs, aic); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: SetPropertyIC::attachGenericProxy(JSContext *cx, IonScript *ion, void *returnAddr) michael@0: { michael@0: JS_ASSERT(!hasGenericProxyStub()); michael@0: michael@0: MacroAssembler masm(cx, ion, script_, pc_); michael@0: RepatchStubAppender attacher(*this); michael@0: michael@0: Label failures; michael@0: { michael@0: Label proxyFailures; michael@0: Label proxySuccess; michael@0: michael@0: RegisterSet regSet(RegisterSet::All()); michael@0: regSet.take(AnyRegister(object())); michael@0: if (!value().constant()) michael@0: regSet.takeUnchecked(value().reg()); michael@0: michael@0: Register scratch = regSet.takeGeneral(); michael@0: masm.push(scratch); michael@0: michael@0: GenerateProxyClassGuards(masm, object(), scratch, &proxyFailures); michael@0: michael@0: // Remove the DOM proxies. They'll take care of themselves so this stub doesn't michael@0: // catch too much. The failure case is actually Equal. Fall through to the failure code. michael@0: masm.branchTestProxyHandlerFamily(Assembler::NotEqual, object(), scratch, michael@0: GetDOMProxyHandlerFamily(), &proxySuccess); michael@0: michael@0: masm.bind(&proxyFailures); michael@0: masm.pop(scratch); michael@0: // Unify the point of failure to allow for later DOM proxy handling. michael@0: masm.jump(&failures); michael@0: michael@0: masm.bind(&proxySuccess); michael@0: masm.pop(scratch); michael@0: } michael@0: michael@0: RootedId propId(cx, AtomToId(name())); michael@0: if (!EmitCallProxySet(cx, masm, attacher, propId, liveRegs_, object(), value(), michael@0: returnAddr, strict())) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: masm.bind(&failures); michael@0: attacher.jumpNextStub(masm); michael@0: michael@0: JS_ASSERT(!hasGenericProxyStub_); michael@0: hasGenericProxyStub_ = true; michael@0: michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "generic proxy set"); michael@0: } michael@0: michael@0: bool michael@0: SetPropertyIC::attachDOMProxyShadowed(JSContext *cx, IonScript *ion, HandleObject obj, michael@0: void *returnAddr) michael@0: { michael@0: JS_ASSERT(IsCacheableDOMProxy(obj)); michael@0: michael@0: Label failures; michael@0: MacroAssembler masm(cx, ion, script_, pc_); michael@0: RepatchStubAppender attacher(*this); michael@0: michael@0: // Guard on the shape of the object. michael@0: masm.branchPtr(Assembler::NotEqual, michael@0: Address(object(), JSObject::offsetOfShape()), michael@0: ImmGCPtr(obj->lastProperty()), &failures); michael@0: michael@0: // Make sure object is a DOMProxy michael@0: GenerateDOMProxyChecks(cx, masm, obj, name(), object(), &failures, michael@0: /*skipExpandoCheck=*/true); michael@0: michael@0: RootedId propId(cx, AtomToId(name())); michael@0: if (!EmitCallProxySet(cx, masm, attacher, propId, liveRegs_, object(), michael@0: value(), returnAddr, strict())) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: // Success. michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: // Failure. michael@0: masm.bind(&failures); michael@0: attacher.jumpNextStub(masm); michael@0: michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "DOM proxy shadowed set"); michael@0: } michael@0: michael@0: static bool michael@0: GenerateCallSetter(JSContext *cx, IonScript *ion, MacroAssembler &masm, michael@0: IonCache::StubAttacher &attacher, HandleObject obj, michael@0: HandleObject holder, HandleShape shape, bool strict, Register object, michael@0: ConstantOrRegister value, Label *failure, RegisterSet liveRegs, michael@0: void *returnAddr) michael@0: { michael@0: // Generate prototype guards if needed. michael@0: // Take a scratch register for use, save on stack. michael@0: { michael@0: RegisterSet regSet(RegisterSet::All()); michael@0: regSet.take(AnyRegister(object)); michael@0: if (!value.constant()) michael@0: regSet.takeUnchecked(value.reg()); michael@0: Register scratchReg = regSet.takeGeneral(); michael@0: masm.push(scratchReg); michael@0: michael@0: Label protoFailure; michael@0: Label protoSuccess; michael@0: michael@0: // Generate prototype/shape guards. michael@0: if (obj != holder) michael@0: GeneratePrototypeGuards(cx, ion, masm, obj, holder, object, scratchReg, &protoFailure); michael@0: michael@0: masm.moveNurseryPtr(ImmMaybeNurseryPtr(holder), scratchReg); michael@0: masm.branchPtr(Assembler::NotEqual, michael@0: Address(scratchReg, JSObject::offsetOfShape()), michael@0: ImmGCPtr(holder->lastProperty()), michael@0: &protoFailure); michael@0: michael@0: masm.jump(&protoSuccess); michael@0: michael@0: masm.bind(&protoFailure); michael@0: masm.pop(scratchReg); michael@0: masm.jump(failure); michael@0: michael@0: masm.bind(&protoSuccess); michael@0: masm.pop(scratchReg); michael@0: } michael@0: michael@0: // Good to go for invoking setter. michael@0: michael@0: MacroAssembler::AfterICSaveLive aic = masm.icSaveLive(liveRegs); michael@0: michael@0: // Remaining registers should basically be free, but we need to use |object| still michael@0: // so leave it alone. michael@0: RegisterSet regSet(RegisterSet::All()); michael@0: regSet.take(AnyRegister(object)); michael@0: michael@0: // This is a slower stub path, and we're going to be doing a call anyway. Don't need michael@0: // to try so hard to not use the stack. Scratch regs are just taken from the register michael@0: // set not including the input, current value saved on the stack, and restored when michael@0: // we're done with it. michael@0: // michael@0: // Be very careful not to use any of these before value is pushed, since they michael@0: // might shadow. michael@0: Register scratchReg = regSet.takeGeneral(); michael@0: Register argJSContextReg = regSet.takeGeneral(); michael@0: Register argVpReg = regSet.takeGeneral(); michael@0: michael@0: bool callNative = IsCacheableSetPropCallNative(obj, holder, shape); michael@0: JS_ASSERT_IF(!callNative, IsCacheableSetPropCallPropertyOp(obj, holder, shape)); michael@0: michael@0: if (callNative) { michael@0: JS_ASSERT(shape->hasSetterValue() && shape->setterObject() && michael@0: shape->setterObject()->is()); michael@0: JSFunction *target = &shape->setterObject()->as(); michael@0: michael@0: JS_ASSERT(target->isNative()); michael@0: michael@0: Register argUintNReg = regSet.takeGeneral(); michael@0: michael@0: // Set up the call: michael@0: // bool (*)(JSContext *, unsigned, Value *vp) michael@0: // vp[0] is callee/outparam michael@0: // vp[1] is |this| michael@0: // vp[2] is the value michael@0: michael@0: // Build vp and move the base into argVpReg. michael@0: masm.Push(value); michael@0: masm.Push(TypedOrValueRegister(MIRType_Object, AnyRegister(object))); michael@0: masm.Push(ObjectValue(*target)); michael@0: masm.movePtr(StackPointer, argVpReg); michael@0: michael@0: // Preload other regs michael@0: masm.loadJSContext(argJSContextReg); michael@0: masm.move32(Imm32(1), argUintNReg); michael@0: michael@0: // Push data for GC marking michael@0: masm.Push(argUintNReg); michael@0: attacher.pushStubCodePointer(masm); michael@0: michael@0: if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic)) michael@0: return false; michael@0: masm.enterFakeExitFrame(ION_FRAME_OOL_NATIVE); michael@0: michael@0: // Make the call michael@0: masm.setupUnalignedABICall(3, scratchReg); michael@0: masm.passABIArg(argJSContextReg); michael@0: masm.passABIArg(argUintNReg); michael@0: masm.passABIArg(argVpReg); michael@0: masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, target->native())); michael@0: michael@0: // Test for failure. michael@0: masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel()); michael@0: michael@0: // masm.leaveExitFrame & pop locals. michael@0: masm.adjustStack(IonOOLNativeExitFrameLayout::Size(1)); michael@0: } else { michael@0: Register argObjReg = regSet.takeGeneral(); michael@0: Register argIdReg = regSet.takeGeneral(); michael@0: Register argStrictReg = regSet.takeGeneral(); michael@0: michael@0: attacher.pushStubCodePointer(masm); michael@0: michael@0: StrictPropertyOp target = shape->setterOp(); michael@0: JS_ASSERT(target); michael@0: // JSStrictPropertyOp: bool fn(JSContext *cx, HandleObject obj, michael@0: // HandleId id, bool strict, MutableHandleValue vp); michael@0: michael@0: // Push args on stack first so we can take pointers to make handles. michael@0: if (value.constant()) michael@0: masm.Push(value.value()); michael@0: else michael@0: masm.Push(value.reg()); michael@0: masm.movePtr(StackPointer, argVpReg); michael@0: michael@0: masm.move32(Imm32(strict ? 1 : 0), argStrictReg); michael@0: michael@0: // push canonical jsid from shape instead of propertyname. michael@0: masm.Push(shape->propid(), argIdReg); michael@0: masm.movePtr(StackPointer, argIdReg); michael@0: michael@0: masm.Push(object); michael@0: masm.movePtr(StackPointer, argObjReg); michael@0: michael@0: masm.loadJSContext(argJSContextReg); michael@0: michael@0: if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic)) michael@0: return false; michael@0: masm.enterFakeExitFrame(ION_FRAME_OOL_PROPERTY_OP); michael@0: michael@0: // Make the call. michael@0: masm.setupUnalignedABICall(5, scratchReg); michael@0: masm.passABIArg(argJSContextReg); michael@0: masm.passABIArg(argObjReg); michael@0: masm.passABIArg(argIdReg); michael@0: masm.passABIArg(argStrictReg); michael@0: masm.passABIArg(argVpReg); michael@0: masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, target)); michael@0: michael@0: // Test for failure. michael@0: masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel()); michael@0: michael@0: // masm.leaveExitFrame & pop locals. michael@0: masm.adjustStack(IonOOLPropertyOpExitFrameLayout::Size()); michael@0: } michael@0: michael@0: masm.icRestoreLive(liveRegs, aic); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: IsCacheableDOMProxyUnshadowedSetterCall(JSContext *cx, HandleObject obj, HandlePropertyName name, michael@0: MutableHandleObject holder, MutableHandleShape shape, michael@0: bool *isSetter) michael@0: { michael@0: JS_ASSERT(IsCacheableDOMProxy(obj)); michael@0: michael@0: *isSetter = false; michael@0: michael@0: RootedObject checkObj(cx, obj->getTaggedProto().toObjectOrNull()); michael@0: if (!checkObj) michael@0: return true; michael@0: michael@0: if (!JSObject::lookupProperty(cx, obj, name, holder, shape)) michael@0: return false; michael@0: michael@0: if (!holder) michael@0: return true; michael@0: michael@0: if (!IsCacheableSetPropCallNative(checkObj, holder, shape) && michael@0: !IsCacheableSetPropCallPropertyOp(checkObj, holder, shape)) michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: *isSetter = true; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: SetPropertyIC::attachDOMProxyUnshadowed(JSContext *cx, IonScript *ion, HandleObject obj, michael@0: void *returnAddr) michael@0: { michael@0: JS_ASSERT(IsCacheableDOMProxy(obj)); michael@0: michael@0: Label failures; michael@0: MacroAssembler masm(cx, ion, script_, pc_); michael@0: RepatchStubAppender attacher(*this); michael@0: michael@0: // Guard on the shape of the object. michael@0: masm.branchPtr(Assembler::NotEqual, michael@0: Address(object(), JSObject::offsetOfShape()), michael@0: ImmGCPtr(obj->lastProperty()), &failures); michael@0: michael@0: // Make sure object is a DOMProxy michael@0: GenerateDOMProxyChecks(cx, masm, obj, name(), object(), &failures); michael@0: michael@0: RootedPropertyName propName(cx, name()); michael@0: RootedObject holder(cx); michael@0: RootedShape shape(cx); michael@0: bool isSetter; michael@0: if (!IsCacheableDOMProxyUnshadowedSetterCall(cx, obj, propName, &holder, michael@0: &shape, &isSetter)) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: if (isSetter) { michael@0: if (!GenerateCallSetter(cx, ion, masm, attacher, obj, holder, shape, strict(), michael@0: object(), value(), &failures, liveRegs_, returnAddr)) michael@0: { michael@0: return false; michael@0: } michael@0: } else { michael@0: // Either there was no proto, or the property wasn't appropriately found on it. michael@0: // Drop back to just a call to Proxy::set(). michael@0: RootedId propId(cx, AtomToId(name())); michael@0: if (!EmitCallProxySet(cx, masm, attacher, propId, liveRegs_, object(), michael@0: value(), returnAddr, strict())) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Success. michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: // Failure. michael@0: masm.bind(&failures); michael@0: attacher.jumpNextStub(masm); michael@0: michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "DOM proxy unshadowed set"); michael@0: } michael@0: michael@0: bool michael@0: SetPropertyIC::attachCallSetter(JSContext *cx, IonScript *ion, michael@0: HandleObject obj, HandleObject holder, HandleShape shape, michael@0: void *returnAddr) michael@0: { michael@0: JS_ASSERT(obj->isNative()); michael@0: michael@0: MacroAssembler masm(cx, ion, script_, pc_); michael@0: RepatchStubAppender attacher(*this); michael@0: michael@0: Label failure; michael@0: masm.branchPtr(Assembler::NotEqual, michael@0: Address(object(), JSObject::offsetOfShape()), michael@0: ImmGCPtr(obj->lastProperty()), michael@0: &failure); michael@0: michael@0: if (!GenerateCallSetter(cx, ion, masm, attacher, obj, holder, shape, strict(), michael@0: object(), value(), &failure, liveRegs_, returnAddr)) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: // Rejoin jump. michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: // Jump to next stub. michael@0: masm.bind(&failure); michael@0: attacher.jumpNextStub(masm); michael@0: michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "setter call"); michael@0: } michael@0: michael@0: static void michael@0: GenerateAddSlot(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher, michael@0: JSObject *obj, Shape *oldShape, Register object, ConstantOrRegister value, michael@0: bool checkTypeset) michael@0: { michael@0: JS_ASSERT(obj->isNative()); michael@0: michael@0: Label failures; michael@0: michael@0: // Guard the type of the object michael@0: masm.branchPtr(Assembler::NotEqual, Address(object, JSObject::offsetOfType()), michael@0: ImmGCPtr(obj->type()), &failures); michael@0: michael@0: // Guard shapes along prototype chain. michael@0: masm.branchTestObjShape(Assembler::NotEqual, object, oldShape, &failures); michael@0: michael@0: Label failuresPopObject; michael@0: masm.push(object); // save object reg because we clobber it michael@0: michael@0: // Guard that the incoming value is in the type set for the property michael@0: // if a type barrier is required. michael@0: if (checkTypeset) { michael@0: TypedOrValueRegister valReg = value.reg(); michael@0: types::TypeObject *type = obj->type(); michael@0: types::HeapTypeSet *propTypes = type->maybeGetProperty(obj->lastProperty()->propid()); michael@0: JS_ASSERT(propTypes); michael@0: JS_ASSERT(!propTypes->unknown()); michael@0: michael@0: Register scratchReg = object; michael@0: masm.guardTypeSet(valReg, propTypes, scratchReg, &failuresPopObject); michael@0: masm.loadPtr(Address(StackPointer, 0), object); michael@0: } michael@0: michael@0: JSObject *proto = obj->getProto(); michael@0: Register protoReg = object; michael@0: while (proto) { michael@0: Shape *protoShape = proto->lastProperty(); michael@0: michael@0: // load next prototype michael@0: masm.loadObjProto(protoReg, protoReg); michael@0: michael@0: // Ensure that its shape matches. michael@0: masm.branchTestObjShape(Assembler::NotEqual, protoReg, protoShape, &failuresPopObject); michael@0: michael@0: proto = proto->getProto(); michael@0: } michael@0: michael@0: masm.pop(object); // restore object reg michael@0: michael@0: // Changing object shape. Write the object's new shape. michael@0: Shape *newShape = obj->lastProperty(); michael@0: Address shapeAddr(object, JSObject::offsetOfShape()); michael@0: if (cx->zone()->needsBarrier()) michael@0: masm.callPreBarrier(shapeAddr, MIRType_Shape); michael@0: masm.storePtr(ImmGCPtr(newShape), shapeAddr); michael@0: michael@0: // Set the value on the object. Since this is an add, obj->lastProperty() michael@0: // must be the shape of the property we are adding. michael@0: if (obj->isFixedSlot(newShape->slot())) { michael@0: Address addr(object, JSObject::getFixedSlotOffset(newShape->slot())); michael@0: masm.storeConstantOrRegister(value, addr); michael@0: } else { michael@0: Register slotsReg = object; michael@0: michael@0: masm.loadPtr(Address(object, JSObject::offsetOfSlots()), slotsReg); michael@0: michael@0: Address addr(slotsReg, obj->dynamicSlotIndex(newShape->slot()) * sizeof(Value)); michael@0: masm.storeConstantOrRegister(value, addr); michael@0: } michael@0: michael@0: // Success. michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: // Failure. michael@0: masm.bind(&failuresPopObject); michael@0: masm.pop(object); michael@0: masm.bind(&failures); michael@0: michael@0: attacher.jumpNextStub(masm); michael@0: } michael@0: michael@0: bool michael@0: SetPropertyIC::attachAddSlot(JSContext *cx, IonScript *ion, JSObject *obj, HandleShape oldShape, michael@0: bool checkTypeset) michael@0: { michael@0: JS_ASSERT_IF(!needsTypeBarrier(), !checkTypeset); michael@0: michael@0: MacroAssembler masm(cx, ion); michael@0: RepatchStubAppender attacher(*this); michael@0: GenerateAddSlot(cx, masm, attacher, obj, oldShape, object(), value(), checkTypeset); michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "adding"); michael@0: } michael@0: michael@0: static bool michael@0: CanInlineSetPropTypeCheck(JSObject *obj, jsid id, ConstantOrRegister val, bool *checkTypeset) michael@0: { michael@0: bool shouldCheck = false; michael@0: types::TypeObject *type = obj->type(); michael@0: if (!type->unknownProperties()) { michael@0: types::HeapTypeSet *propTypes = type->maybeGetProperty(id); michael@0: if (!propTypes) michael@0: return false; michael@0: if (!propTypes->unknown()) { michael@0: shouldCheck = true; michael@0: if (val.constant()) { michael@0: // If the input is a constant, then don't bother if the barrier will always fail. michael@0: if (!propTypes->hasType(types::GetValueType(val.value()))) michael@0: return false; michael@0: shouldCheck = false; michael@0: } else { michael@0: TypedOrValueRegister reg = val.reg(); michael@0: // We can do the same trick as above for primitive types of specialized registers. michael@0: // TIs handling of objects is complicated enough to warrant a runtime michael@0: // check, as we can't statically handle the case where the typeset michael@0: // contains the specific object, but doesn't have ANYOBJECT set. michael@0: if (reg.hasTyped() && reg.type() != MIRType_Object) { michael@0: JSValueType valType = ValueTypeFromMIRType(reg.type()); michael@0: if (!propTypes->hasType(types::Type::PrimitiveType(valType))) michael@0: return false; michael@0: shouldCheck = false; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: *checkTypeset = shouldCheck; michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: IsPropertySetInlineable(HandleObject obj, HandleId id, MutableHandleShape pshape, michael@0: ConstantOrRegister val, bool needsTypeBarrier, bool *checkTypeset) michael@0: { michael@0: JS_ASSERT(obj->isNative()); michael@0: michael@0: // Do a pure non-proto chain climbing lookup. See note in michael@0: // CanAttachNativeGetProp. michael@0: pshape.set(obj->nativeLookupPure(id)); michael@0: michael@0: if (!pshape) michael@0: return false; michael@0: michael@0: if (!pshape->hasSlot()) michael@0: return false; michael@0: michael@0: if (!pshape->hasDefaultSetter()) michael@0: return false; michael@0: michael@0: if (!pshape->writable()) michael@0: return false; michael@0: michael@0: if (needsTypeBarrier) michael@0: return CanInlineSetPropTypeCheck(obj, id, val, checkTypeset); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: IsPropertyAddInlineable(HandleObject obj, HandleId id, ConstantOrRegister val, uint32_t oldSlots, michael@0: HandleShape oldShape, bool needsTypeBarrier, bool *checkTypeset) michael@0: { michael@0: JS_ASSERT(obj->isNative()); michael@0: michael@0: // If the shape of the object did not change, then this was not an add. michael@0: if (obj->lastProperty() == oldShape) michael@0: return false; michael@0: michael@0: Shape *shape = obj->nativeLookupPure(id); michael@0: if (!shape || shape->inDictionary() || !shape->hasSlot() || !shape->hasDefaultSetter()) michael@0: return false; michael@0: michael@0: // If we have a shape at this point and the object's shape changed, then michael@0: // the shape must be the one we just added. michael@0: JS_ASSERT(shape == obj->lastProperty()); michael@0: michael@0: // If object has a non-default resolve hook, don't inline michael@0: if (obj->getClass()->resolve != JS_ResolveStub) michael@0: return false; michael@0: michael@0: // Likewise for a non-default addProperty hook, since we'll need michael@0: // to invoke it. michael@0: if (obj->getClass()->addProperty != JS_PropertyStub) michael@0: return false; michael@0: michael@0: if (!obj->nonProxyIsExtensible() || !shape->writable()) michael@0: return false; michael@0: michael@0: // Walk up the object prototype chain and ensure that all prototypes michael@0: // are native, and that all prototypes have no getter or setter michael@0: // defined on the property michael@0: for (JSObject *proto = obj->getProto(); proto; proto = proto->getProto()) { michael@0: // If prototype is non-native, don't optimize michael@0: if (!proto->isNative()) michael@0: return false; michael@0: michael@0: // If prototype defines this property in a non-plain way, don't optimize michael@0: Shape *protoShape = proto->nativeLookupPure(id); michael@0: if (protoShape && !protoShape->hasDefaultSetter()) michael@0: return false; michael@0: michael@0: // Otherwise, if there's no such property, watch out for a resolve michael@0: // hook that would need to be invoked and thus prevent inlining of michael@0: // property addition. michael@0: if (proto->getClass()->resolve != JS_ResolveStub) michael@0: return false; michael@0: } michael@0: michael@0: // Only add a IC entry if the dynamic slots didn't change when the shapes michael@0: // changed. Need to ensure that a shape change for a subsequent object michael@0: // won't involve reallocating the slot array. michael@0: if (obj->numDynamicSlots() != oldSlots) michael@0: return false; michael@0: michael@0: if (needsTypeBarrier) michael@0: return CanInlineSetPropTypeCheck(obj, id, val, checkTypeset); michael@0: michael@0: *checkTypeset = false; michael@0: return true; michael@0: } michael@0: michael@0: static SetPropertyIC::NativeSetPropCacheability michael@0: CanAttachNativeSetProp(HandleObject obj, HandleId id, ConstantOrRegister val, michael@0: bool needsTypeBarrier, MutableHandleObject holder, michael@0: MutableHandleShape shape, bool *checkTypeset) michael@0: { michael@0: if (!obj->isNative()) michael@0: return SetPropertyIC::CanAttachNone; michael@0: michael@0: // See if the property exists on the object. michael@0: if (IsPropertySetInlineable(obj, id, shape, val, needsTypeBarrier, checkTypeset)) michael@0: return SetPropertyIC::CanAttachSetSlot; michael@0: michael@0: // If we couldn't find the property on the object itself, do a full, but michael@0: // still pure lookup for setters. michael@0: if (!LookupPropertyPure(obj, id, holder.address(), shape.address())) michael@0: return SetPropertyIC::CanAttachNone; michael@0: michael@0: // If the object doesn't have the property, we don't know if we can attach michael@0: // a stub to add the property until we do the VM call to add. If the michael@0: // property exists as a data property on the prototype, we should add michael@0: // a new, shadowing property. michael@0: if (!shape || (obj != holder && shape->hasDefaultSetter() && shape->hasSlot())) michael@0: return SetPropertyIC::MaybeCanAttachAddSlot; michael@0: michael@0: if (IsCacheableSetPropCallPropertyOp(obj, holder, shape) || michael@0: IsCacheableSetPropCallNative(obj, holder, shape)) michael@0: { michael@0: return SetPropertyIC::CanAttachCallSetter; michael@0: } michael@0: michael@0: return SetPropertyIC::CanAttachNone; michael@0: } michael@0: michael@0: bool michael@0: SetPropertyIC::update(JSContext *cx, size_t cacheIndex, HandleObject obj, michael@0: HandleValue value) michael@0: { michael@0: void *returnAddr; michael@0: RootedScript script(cx, GetTopIonJSScript(cx, &returnAddr)); michael@0: IonScript *ion = script->ionScript(); michael@0: SetPropertyIC &cache = ion->getCache(cacheIndex).toSetProperty(); michael@0: RootedPropertyName name(cx, cache.name()); michael@0: RootedId id(cx, AtomToId(name)); michael@0: michael@0: // Stop generating new stubs once we hit the stub count limit, see michael@0: // GetPropertyCache. michael@0: bool inlinable = cache.canAttachStub() && !obj->watched(); michael@0: NativeSetPropCacheability canCache = CanAttachNone; michael@0: bool addedSetterStub = false; michael@0: if (inlinable) { michael@0: if (!addedSetterStub && obj->is()) { michael@0: if (IsCacheableDOMProxy(obj)) { michael@0: DOMProxyShadowsResult shadows = GetDOMProxyShadowsCheck()(cx, obj, id); michael@0: if (shadows == ShadowCheckFailed) michael@0: return false; michael@0: if (shadows == Shadows) { michael@0: if (!cache.attachDOMProxyShadowed(cx, ion, obj, returnAddr)) michael@0: return false; michael@0: addedSetterStub = true; michael@0: } else { michael@0: JS_ASSERT(shadows == DoesntShadow || shadows == DoesntShadowUnique); michael@0: if (shadows == DoesntShadowUnique) michael@0: cache.reset(); michael@0: if (!cache.attachDOMProxyUnshadowed(cx, ion, obj, returnAddr)) michael@0: return false; michael@0: addedSetterStub = true; michael@0: } michael@0: } michael@0: michael@0: if (!addedSetterStub && !cache.hasGenericProxyStub()) { michael@0: if (!cache.attachGenericProxy(cx, ion, returnAddr)) michael@0: return false; michael@0: addedSetterStub = true; michael@0: } michael@0: } michael@0: michael@0: // Make sure the object de-lazifies its type. We do this here so that michael@0: // the parallel IC can share code that assumes that native objects all michael@0: // have a type object. michael@0: if (obj->isNative() && !obj->getType(cx)) michael@0: return false; michael@0: michael@0: RootedShape shape(cx); michael@0: RootedObject holder(cx); michael@0: bool checkTypeset; michael@0: canCache = CanAttachNativeSetProp(obj, id, cache.value(), cache.needsTypeBarrier(), michael@0: &holder, &shape, &checkTypeset); michael@0: michael@0: if (!addedSetterStub && canCache == CanAttachSetSlot) { michael@0: if (!cache.attachSetSlot(cx, ion, obj, shape, checkTypeset)) michael@0: return false; michael@0: addedSetterStub = true; michael@0: } michael@0: michael@0: if (!addedSetterStub && canCache == CanAttachCallSetter) { michael@0: if (!cache.attachCallSetter(cx, ion, obj, holder, shape, returnAddr)) michael@0: return false; michael@0: addedSetterStub = true; michael@0: } michael@0: } michael@0: michael@0: uint32_t oldSlots = obj->numDynamicSlots(); michael@0: RootedShape oldShape(cx, obj->lastProperty()); michael@0: michael@0: // Set/Add the property on the object, the inlined cache are setup for the next execution. michael@0: if (!SetProperty(cx, obj, name, value, cache.strict(), cache.pc())) michael@0: return false; michael@0: michael@0: // The property did not exist before, now we can try to inline the property add. michael@0: bool checkTypeset; michael@0: if (!addedSetterStub && canCache == MaybeCanAttachAddSlot && michael@0: IsPropertyAddInlineable(obj, id, cache.value(), oldSlots, oldShape, cache.needsTypeBarrier(), michael@0: &checkTypeset)) michael@0: { michael@0: if (!cache.attachAddSlot(cx, ion, obj, oldShape, checkTypeset)) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: SetPropertyIC::reset() michael@0: { michael@0: RepatchIonCache::reset(); michael@0: hasGenericProxyStub_ = false; michael@0: } michael@0: michael@0: bool michael@0: SetPropertyParIC::update(ForkJoinContext *cx, size_t cacheIndex, HandleObject obj, michael@0: HandleValue value) michael@0: { michael@0: JS_ASSERT(cx->isThreadLocal(obj)); michael@0: michael@0: IonScript *ion = GetTopIonJSScript(cx)->parallelIonScript(); michael@0: SetPropertyParIC &cache = ion->getCache(cacheIndex).toSetPropertyPar(); michael@0: michael@0: RootedValue v(cx, value); michael@0: RootedId id(cx, AtomToId(cache.name())); michael@0: michael@0: // Avoid unnecessary locking if cannot attach stubs. michael@0: if (!cache.canAttachStub()) { michael@0: return baseops::SetPropertyHelper( michael@0: cx, obj, obj, id, baseops::Qualified, &v, cache.strict()); michael@0: } michael@0: michael@0: SetPropertyIC::NativeSetPropCacheability canCache = SetPropertyIC::CanAttachNone; michael@0: bool attachedStub = false; michael@0: michael@0: { michael@0: // See note about locking context in GetPropertyParIC::update. michael@0: LockedJSContext ncx(cx); michael@0: michael@0: if (cache.canAttachStub()) { michael@0: bool alreadyStubbed; michael@0: if (!cache.hasOrAddStubbedShape(ncx, obj->lastProperty(), &alreadyStubbed)) michael@0: return cx->setPendingAbortFatal(ParallelBailoutFailedIC); michael@0: if (alreadyStubbed) { michael@0: return baseops::SetPropertyHelper( michael@0: cx, obj, obj, id, baseops::Qualified, &v, cache.strict()); michael@0: } michael@0: michael@0: // If the object has a lazy type, we need to de-lazify it, but michael@0: // this is not safe in parallel. michael@0: if (obj->hasLazyType()) michael@0: return false; michael@0: michael@0: { michael@0: RootedShape shape(cx); michael@0: RootedObject holder(cx); michael@0: bool checkTypeset; michael@0: canCache = CanAttachNativeSetProp(obj, id, cache.value(), cache.needsTypeBarrier(), michael@0: &holder, &shape, &checkTypeset); michael@0: michael@0: if (canCache == SetPropertyIC::CanAttachSetSlot) { michael@0: if (!cache.attachSetSlot(ncx, ion, obj, shape, checkTypeset)) michael@0: return cx->setPendingAbortFatal(ParallelBailoutFailedIC); michael@0: attachedStub = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: uint32_t oldSlots = obj->numDynamicSlots(); michael@0: RootedShape oldShape(cx, obj->lastProperty()); michael@0: michael@0: if (!baseops::SetPropertyHelper(cx, obj, obj, id, baseops::Qualified, &v, michael@0: cache.strict())) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: bool checkTypeset; michael@0: if (!attachedStub && canCache == SetPropertyIC::MaybeCanAttachAddSlot && michael@0: IsPropertyAddInlineable(obj, id, cache.value(), oldSlots, oldShape, cache.needsTypeBarrier(), michael@0: &checkTypeset)) michael@0: { michael@0: LockedJSContext ncx(cx); michael@0: if (cache.canAttachStub() && !cache.attachAddSlot(ncx, ion, obj, oldShape, checkTypeset)) michael@0: return cx->setPendingAbortFatal(ParallelBailoutFailedIC); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: SetPropertyParIC::attachSetSlot(LockedJSContext &cx, IonScript *ion, JSObject *obj, Shape *shape, michael@0: bool checkTypeset) michael@0: { michael@0: MacroAssembler masm(cx, ion); michael@0: DispatchStubPrepender attacher(*this); michael@0: GenerateSetSlot(cx, masm, attacher, obj, shape, object(), value(), needsTypeBarrier(), michael@0: checkTypeset); michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "parallel setting"); michael@0: } michael@0: michael@0: bool michael@0: SetPropertyParIC::attachAddSlot(LockedJSContext &cx, IonScript *ion, JSObject *obj, Shape *oldShape, michael@0: bool checkTypeset) michael@0: { michael@0: JS_ASSERT_IF(!needsTypeBarrier(), !checkTypeset); michael@0: michael@0: MacroAssembler masm(cx, ion); michael@0: DispatchStubPrepender attacher(*this); michael@0: GenerateAddSlot(cx, masm, attacher, obj, oldShape, object(), value(), checkTypeset); michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "parallel adding"); michael@0: } michael@0: michael@0: const size_t GetElementIC::MAX_FAILED_UPDATES = 16; michael@0: michael@0: /* static */ bool michael@0: GetElementIC::canAttachGetProp(JSObject *obj, const Value &idval, jsid id) michael@0: { michael@0: uint32_t dummy; michael@0: return (obj->isNative() && michael@0: idval.isString() && michael@0: JSID_IS_ATOM(id) && michael@0: !JSID_TO_ATOM(id)->isIndex(&dummy)); michael@0: } michael@0: michael@0: static bool michael@0: EqualStringsHelper(JSString *str1, JSString *str2) michael@0: { michael@0: JS_ASSERT(str1->isAtom()); michael@0: JS_ASSERT(!str2->isAtom()); michael@0: JS_ASSERT(str1->length() == str2->length()); michael@0: michael@0: const jschar *chars = str2->getChars(nullptr); michael@0: if (!chars) michael@0: return false; michael@0: return mozilla::PodEqual(str1->asAtom().chars(), chars, str1->length()); michael@0: } michael@0: michael@0: bool michael@0: GetElementIC::attachGetProp(JSContext *cx, IonScript *ion, HandleObject obj, michael@0: const Value &idval, HandlePropertyName name, michael@0: void *returnAddr) michael@0: { michael@0: JS_ASSERT(index().reg().hasValue()); michael@0: michael@0: RootedObject holder(cx); michael@0: RootedShape shape(cx); michael@0: michael@0: GetPropertyIC::NativeGetPropCacheability canCache = michael@0: CanAttachNativeGetProp(cx, *this, obj, name, &holder, &shape, michael@0: /* skipArrayLen =*/true); michael@0: michael@0: bool cacheable = canCache == GetPropertyIC::CanAttachReadSlot || michael@0: (canCache == GetPropertyIC::CanAttachCallGetter && michael@0: output().hasValue()); michael@0: michael@0: if (!cacheable) { michael@0: IonSpew(IonSpew_InlineCaches, "GETELEM uncacheable property"); michael@0: return true; michael@0: } michael@0: michael@0: JS_ASSERT(idval.isString()); michael@0: JS_ASSERT(idval.toString()->length() == name->length()); michael@0: michael@0: Label failures; michael@0: MacroAssembler masm(cx, ion); michael@0: michael@0: // Ensure the index is a string. michael@0: ValueOperand val = index().reg().valueReg(); michael@0: masm.branchTestString(Assembler::NotEqual, val, &failures); michael@0: michael@0: Register scratch = output().valueReg().scratchReg(); michael@0: masm.unboxString(val, scratch); michael@0: michael@0: Label equal; michael@0: masm.branchPtr(Assembler::Equal, scratch, ImmGCPtr(name), &equal); michael@0: michael@0: // The pointers are not equal, so if the input string is also an atom it michael@0: // must be a different string. michael@0: masm.loadPtr(Address(scratch, JSString::offsetOfLengthAndFlags()), scratch); michael@0: masm.branchTest32(Assembler::NonZero, scratch, Imm32(JSString::ATOM_BIT), &failures); michael@0: michael@0: // Check the length. michael@0: masm.rshiftPtr(Imm32(JSString::LENGTH_SHIFT), scratch); michael@0: masm.branch32(Assembler::NotEqual, scratch, Imm32(name->length()), &failures); michael@0: michael@0: // We have a non-atomized string with the same length. For now call a helper michael@0: // function to do the comparison. michael@0: RegisterSet volatileRegs = RegisterSet::Volatile(); michael@0: masm.PushRegsInMask(volatileRegs); michael@0: michael@0: Register objReg = object(); michael@0: JS_ASSERT(objReg != scratch); michael@0: michael@0: if (!volatileRegs.has(objReg)) michael@0: masm.push(objReg); michael@0: michael@0: masm.setupUnalignedABICall(2, scratch); michael@0: masm.movePtr(ImmGCPtr(name), objReg); michael@0: masm.passABIArg(objReg); michael@0: masm.unboxString(val, scratch); michael@0: masm.passABIArg(scratch); michael@0: masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, EqualStringsHelper)); michael@0: masm.mov(ReturnReg, scratch); michael@0: michael@0: if (!volatileRegs.has(objReg)) michael@0: masm.pop(objReg); michael@0: michael@0: RegisterSet ignore = RegisterSet(); michael@0: ignore.add(scratch); michael@0: masm.PopRegsInMaskIgnore(volatileRegs, ignore); michael@0: michael@0: masm.branchIfFalseBool(scratch, &failures); michael@0: masm.bind(&equal); michael@0: michael@0: RepatchStubAppender attacher(*this); michael@0: if (canCache == GetPropertyIC::CanAttachReadSlot) { michael@0: GenerateReadSlot(cx, ion, masm, attacher, obj, holder, shape, object(), output(), michael@0: &failures); michael@0: } else { michael@0: JS_ASSERT(canCache == GetPropertyIC::CanAttachCallGetter); michael@0: // Set the frame for bailout safety of the OOL call. michael@0: if (!GenerateCallGetter(cx, ion, masm, attacher, obj, name, holder, shape, liveRegs_, michael@0: object(), output(), returnAddr, &failures)) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "property"); michael@0: } michael@0: michael@0: /* static */ bool michael@0: GetElementIC::canAttachDenseElement(JSObject *obj, const Value &idval) michael@0: { michael@0: return obj->isNative() && idval.isInt32(); michael@0: } michael@0: michael@0: static bool michael@0: GenerateDenseElement(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher, michael@0: JSObject *obj, const Value &idval, Register object, michael@0: ConstantOrRegister index, TypedOrValueRegister output) michael@0: { michael@0: JS_ASSERT(GetElementIC::canAttachDenseElement(obj, idval)); michael@0: michael@0: Label failures; michael@0: michael@0: // Guard object's shape. michael@0: RootedShape shape(cx, obj->lastProperty()); michael@0: if (!shape) michael@0: return false; michael@0: masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures); michael@0: michael@0: // Ensure the index is an int32 value. michael@0: Register indexReg = InvalidReg; michael@0: michael@0: if (index.reg().hasValue()) { michael@0: indexReg = output.scratchReg().gpr(); michael@0: JS_ASSERT(indexReg != InvalidReg); michael@0: ValueOperand val = index.reg().valueReg(); michael@0: michael@0: masm.branchTestInt32(Assembler::NotEqual, val, &failures); michael@0: michael@0: // Unbox the index. michael@0: masm.unboxInt32(val, indexReg); michael@0: } else { michael@0: JS_ASSERT(!index.reg().typedReg().isFloat()); michael@0: indexReg = index.reg().typedReg().gpr(); michael@0: } michael@0: michael@0: // Load elements vector. michael@0: masm.push(object); michael@0: masm.loadPtr(Address(object, JSObject::offsetOfElements()), object); michael@0: michael@0: Label hole; michael@0: michael@0: // Guard on the initialized length. michael@0: Address initLength(object, ObjectElements::offsetOfInitializedLength()); michael@0: masm.branch32(Assembler::BelowOrEqual, initLength, indexReg, &hole); michael@0: michael@0: // Check for holes & load the value. michael@0: masm.loadElementTypedOrValue(BaseIndex(object, indexReg, TimesEight), michael@0: output, true, &hole); michael@0: michael@0: masm.pop(object); michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: // All failures flow to here. michael@0: masm.bind(&hole); michael@0: masm.pop(object); michael@0: masm.bind(&failures); michael@0: michael@0: attacher.jumpNextStub(masm); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: GetElementIC::attachDenseElement(JSContext *cx, IonScript *ion, JSObject *obj, const Value &idval) michael@0: { michael@0: MacroAssembler masm(cx, ion); michael@0: RepatchStubAppender attacher(*this); michael@0: if (!GenerateDenseElement(cx, masm, attacher, obj, idval, object(), index(), output())) michael@0: return false; michael@0: michael@0: setHasDenseStub(); michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "dense array"); michael@0: } michael@0: michael@0: /* static */ bool michael@0: GetElementIC::canAttachTypedArrayElement(JSObject *obj, const Value &idval, michael@0: TypedOrValueRegister output) michael@0: { michael@0: if (!obj->is()) michael@0: return false; michael@0: michael@0: if (!idval.isInt32() && !idval.isString()) michael@0: return false; michael@0: michael@0: michael@0: // Don't emit a stub if the access is out of bounds. We make to make michael@0: // certain that we monitor the type coming out of the typed array when michael@0: // we generate the stub. Out of bounds accesses will hit the fallback michael@0: // path. michael@0: uint32_t index; michael@0: if (idval.isInt32()) { michael@0: index = idval.toInt32(); michael@0: } else { michael@0: index = GetIndexFromString(idval.toString()); michael@0: if (index == UINT32_MAX) michael@0: return false; michael@0: } michael@0: if (index >= obj->as().length()) michael@0: return false; michael@0: michael@0: // The output register is not yet specialized as a float register, the only michael@0: // way to accept float typed arrays for now is to return a Value type. michael@0: uint32_t arrayType = obj->as().type(); michael@0: if (arrayType == ScalarTypeDescr::TYPE_FLOAT32 || michael@0: arrayType == ScalarTypeDescr::TYPE_FLOAT64) michael@0: { michael@0: return output.hasValue(); michael@0: } michael@0: michael@0: return output.hasValue() || !output.typedReg().isFloat(); michael@0: } michael@0: michael@0: static void michael@0: GenerateGetTypedArrayElement(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher, michael@0: TypedArrayObject *tarr, const Value &idval, Register object, michael@0: ConstantOrRegister index, TypedOrValueRegister output, michael@0: bool allowDoubleResult) michael@0: { michael@0: JS_ASSERT(GetElementIC::canAttachTypedArrayElement(tarr, idval, output)); michael@0: michael@0: Label failures; michael@0: michael@0: // The array type is the object within the table of typed array classes. michael@0: int arrayType = tarr->type(); michael@0: michael@0: // Guard on the shape. michael@0: Shape *shape = tarr->lastProperty(); michael@0: masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures); michael@0: michael@0: // Decide to what type index the stub should be optimized michael@0: Register tmpReg = output.scratchReg().gpr(); michael@0: JS_ASSERT(tmpReg != InvalidReg); michael@0: Register indexReg = tmpReg; michael@0: JS_ASSERT(!index.constant()); michael@0: if (idval.isString()) { michael@0: JS_ASSERT(GetIndexFromString(idval.toString()) != UINT32_MAX); michael@0: michael@0: // Part 1: Get the string into a register michael@0: Register str; michael@0: if (index.reg().hasValue()) { michael@0: ValueOperand val = index.reg().valueReg(); michael@0: masm.branchTestString(Assembler::NotEqual, val, &failures); michael@0: michael@0: str = masm.extractString(val, indexReg); michael@0: } else { michael@0: JS_ASSERT(!index.reg().typedReg().isFloat()); michael@0: str = index.reg().typedReg().gpr(); michael@0: } michael@0: michael@0: // Part 2: Call to translate the str into index michael@0: RegisterSet regs = RegisterSet::Volatile(); michael@0: masm.PushRegsInMask(regs); michael@0: regs.takeUnchecked(str); michael@0: michael@0: Register temp = regs.takeGeneral(); michael@0: michael@0: masm.setupUnalignedABICall(1, temp); michael@0: masm.passABIArg(str); michael@0: masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, GetIndexFromString)); michael@0: masm.mov(ReturnReg, indexReg); michael@0: michael@0: RegisterSet ignore = RegisterSet(); michael@0: ignore.add(indexReg); michael@0: masm.PopRegsInMaskIgnore(RegisterSet::Volatile(), ignore); michael@0: michael@0: masm.branch32(Assembler::Equal, indexReg, Imm32(UINT32_MAX), &failures); michael@0: michael@0: } else { michael@0: JS_ASSERT(idval.isInt32()); michael@0: michael@0: if (index.reg().hasValue()) { michael@0: ValueOperand val = index.reg().valueReg(); michael@0: masm.branchTestInt32(Assembler::NotEqual, val, &failures); michael@0: michael@0: // Unbox the index. michael@0: masm.unboxInt32(val, indexReg); michael@0: } else { michael@0: JS_ASSERT(!index.reg().typedReg().isFloat()); michael@0: indexReg = index.reg().typedReg().gpr(); michael@0: } michael@0: } michael@0: michael@0: // Guard on the initialized length. michael@0: Address length(object, TypedArrayObject::lengthOffset()); michael@0: masm.branch32(Assembler::BelowOrEqual, length, indexReg, &failures); michael@0: michael@0: // Save the object register on the stack in case of failure. michael@0: Label popAndFail; michael@0: Register elementReg = object; michael@0: masm.push(object); michael@0: michael@0: // Load elements vector. michael@0: masm.loadPtr(Address(object, TypedArrayObject::dataOffset()), elementReg); michael@0: michael@0: // Load the value. We use an invalid register because the destination michael@0: // register is necessary a non double register. michael@0: int width = TypedArrayObject::slotWidth(arrayType); michael@0: BaseIndex source(elementReg, indexReg, ScaleFromElemWidth(width)); michael@0: if (output.hasValue()) { michael@0: masm.loadFromTypedArray(arrayType, source, output.valueReg(), allowDoubleResult, michael@0: elementReg, &popAndFail); michael@0: } else { michael@0: masm.loadFromTypedArray(arrayType, source, output.typedReg(), elementReg, &popAndFail); michael@0: } michael@0: michael@0: masm.pop(object); michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: // Restore the object before continuing to the next stub. michael@0: masm.bind(&popAndFail); michael@0: masm.pop(object); michael@0: masm.bind(&failures); michael@0: michael@0: attacher.jumpNextStub(masm); michael@0: } michael@0: michael@0: bool michael@0: GetElementIC::attachTypedArrayElement(JSContext *cx, IonScript *ion, TypedArrayObject *tarr, michael@0: const Value &idval) michael@0: { michael@0: MacroAssembler masm(cx, ion); michael@0: RepatchStubAppender attacher(*this); michael@0: GenerateGetTypedArrayElement(cx, masm, attacher, tarr, idval, object(), index(), output(), michael@0: allowDoubleResult()); michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "typed array"); michael@0: } michael@0: michael@0: bool michael@0: GetElementIC::attachArgumentsElement(JSContext *cx, IonScript *ion, JSObject *obj) michael@0: { michael@0: JS_ASSERT(obj->is()); michael@0: michael@0: Label failures; michael@0: MacroAssembler masm(cx, ion); michael@0: RepatchStubAppender attacher(*this); michael@0: michael@0: Register tmpReg = output().scratchReg().gpr(); michael@0: JS_ASSERT(tmpReg != InvalidReg); michael@0: michael@0: const Class *clasp = obj->is() ? &StrictArgumentsObject::class_ michael@0: : &NormalArgumentsObject::class_; michael@0: michael@0: masm.branchTestObjClass(Assembler::NotEqual, object(), tmpReg, clasp, &failures); michael@0: michael@0: // Get initial ArgsObj length value, test if length has been overridden. michael@0: masm.unboxInt32(Address(object(), ArgumentsObject::getInitialLengthSlotOffset()), tmpReg); michael@0: masm.branchTest32(Assembler::NonZero, tmpReg, Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT), michael@0: &failures); michael@0: masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), tmpReg); michael@0: michael@0: // Decide to what type index the stub should be optimized michael@0: Register indexReg; michael@0: JS_ASSERT(!index().constant()); michael@0: michael@0: // Check index against length. michael@0: Label failureRestoreIndex; michael@0: if (index().reg().hasValue()) { michael@0: ValueOperand val = index().reg().valueReg(); michael@0: masm.branchTestInt32(Assembler::NotEqual, val, &failures); michael@0: indexReg = val.scratchReg(); michael@0: michael@0: masm.unboxInt32(val, indexReg); michael@0: masm.branch32(Assembler::AboveOrEqual, indexReg, tmpReg, &failureRestoreIndex); michael@0: } else { michael@0: JS_ASSERT(index().reg().type() == MIRType_Int32); michael@0: indexReg = index().reg().typedReg().gpr(); michael@0: masm.branch32(Assembler::AboveOrEqual, indexReg, tmpReg, &failures); michael@0: } michael@0: // Save indexReg because it needs to be clobbered to check deleted bit. michael@0: Label failurePopIndex; michael@0: masm.push(indexReg); michael@0: michael@0: // Check if property was deleted on arguments object. michael@0: masm.loadPrivate(Address(object(), ArgumentsObject::getDataSlotOffset()), tmpReg); michael@0: masm.loadPtr(Address(tmpReg, offsetof(ArgumentsData, deletedBits)), tmpReg); michael@0: michael@0: // In tempReg, calculate index of word containing bit: (idx >> logBitsPerWord) michael@0: const uint32_t shift = FloorLog2<(sizeof(size_t) * JS_BITS_PER_BYTE)>::value; michael@0: JS_ASSERT(shift == 5 || shift == 6); michael@0: masm.rshiftPtr(Imm32(shift), indexReg); michael@0: masm.loadPtr(BaseIndex(tmpReg, indexReg, ScaleFromElemWidth(sizeof(size_t))), tmpReg); michael@0: michael@0: // Don't bother testing specific bit, if any bit is set in the word, fail. michael@0: masm.branchPtr(Assembler::NotEqual, tmpReg, ImmPtr(nullptr), &failurePopIndex); michael@0: michael@0: // Get the address to load from into tmpReg michael@0: masm.loadPrivate(Address(object(), ArgumentsObject::getDataSlotOffset()), tmpReg); michael@0: masm.addPtr(Imm32(ArgumentsData::offsetOfArgs()), tmpReg); michael@0: michael@0: // Restore original index register value, to use for indexing element. michael@0: masm.pop(indexReg); michael@0: BaseIndex elemIdx(tmpReg, indexReg, ScaleFromElemWidth(sizeof(Value))); michael@0: michael@0: // Ensure result is not magic value, and type-check result. michael@0: masm.branchTestMagic(Assembler::Equal, elemIdx, &failureRestoreIndex); michael@0: michael@0: if (output().hasTyped()) { michael@0: JS_ASSERT(!output().typedReg().isFloat()); michael@0: JS_ASSERT(index().reg().type() == MIRType_Boolean || michael@0: index().reg().type() == MIRType_Int32 || michael@0: index().reg().type() == MIRType_String || michael@0: index().reg().type() == MIRType_Object); michael@0: masm.branchTestMIRType(Assembler::NotEqual, elemIdx, index().reg().type(), michael@0: &failureRestoreIndex); michael@0: } michael@0: michael@0: masm.loadTypedOrValue(elemIdx, output()); michael@0: michael@0: // indexReg may need to be reconstructed if it was originally a value. michael@0: if (index().reg().hasValue()) michael@0: masm.tagValue(JSVAL_TYPE_INT32, indexReg, index().reg().valueReg()); michael@0: michael@0: // Success. michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: // Restore the object before continuing to the next stub. michael@0: masm.bind(&failurePopIndex); michael@0: masm.pop(indexReg); michael@0: masm.bind(&failureRestoreIndex); michael@0: if (index().reg().hasValue()) michael@0: masm.tagValue(JSVAL_TYPE_INT32, indexReg, index().reg().valueReg()); michael@0: masm.bind(&failures); michael@0: attacher.jumpNextStub(masm); michael@0: michael@0: michael@0: if (obj->is()) { michael@0: JS_ASSERT(!hasStrictArgumentsStub_); michael@0: hasStrictArgumentsStub_ = true; michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj element (strict)"); michael@0: } michael@0: michael@0: JS_ASSERT(!hasNormalArgumentsStub_); michael@0: hasNormalArgumentsStub_ = true; michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj element (normal)"); michael@0: } michael@0: michael@0: bool michael@0: GetElementIC::update(JSContext *cx, size_t cacheIndex, HandleObject obj, michael@0: HandleValue idval, MutableHandleValue res) michael@0: { michael@0: void *returnAddr; michael@0: IonScript *ion = GetTopIonJSScript(cx, &returnAddr)->ionScript(); michael@0: GetElementIC &cache = ion->getCache(cacheIndex).toGetElement(); michael@0: RootedScript script(cx); michael@0: jsbytecode *pc; michael@0: cache.getScriptedLocation(&script, &pc); michael@0: michael@0: // Override the return value when the script is invalidated (bug 728188). michael@0: AutoDetectInvalidation adi(cx, res.address(), ion); michael@0: michael@0: if (cache.isDisabled()) { michael@0: if (!GetObjectElementOperation(cx, JSOp(*pc), obj, /* wasObject = */true, idval, res)) michael@0: return false; michael@0: if (!cache.monitoredResult()) michael@0: types::TypeScript::Monitor(cx, script, pc, res); michael@0: return true; michael@0: } michael@0: michael@0: RootedId id(cx); michael@0: if (!ValueToId(cx, idval, &id)) michael@0: return false; michael@0: michael@0: bool attachedStub = false; michael@0: if (cache.canAttachStub()) { michael@0: if (IsOptimizableArgumentsObjectForGetElem(obj, idval) && michael@0: !cache.hasArgumentsStub(obj->is()) && michael@0: !cache.index().constant() && michael@0: (cache.index().reg().hasValue() || michael@0: cache.index().reg().type() == MIRType_Int32) && michael@0: (cache.output().hasValue() || !cache.output().typedReg().isFloat())) michael@0: { michael@0: if (!cache.attachArgumentsElement(cx, ion, obj)) michael@0: return false; michael@0: attachedStub = true; michael@0: } michael@0: if (!attachedStub && cache.monitoredResult() && canAttachGetProp(obj, idval, id)) { michael@0: RootedPropertyName name(cx, JSID_TO_ATOM(id)->asPropertyName()); michael@0: if (!cache.attachGetProp(cx, ion, obj, idval, name, returnAddr)) michael@0: return false; michael@0: attachedStub = true; michael@0: } michael@0: if (!attachedStub && !cache.hasDenseStub() && canAttachDenseElement(obj, idval)) { michael@0: if (!cache.attachDenseElement(cx, ion, obj, idval)) michael@0: return false; michael@0: attachedStub = true; michael@0: } michael@0: if (!attachedStub && canAttachTypedArrayElement(obj, idval, cache.output())) { michael@0: Rooted tarr(cx, &obj->as()); michael@0: if (!cache.attachTypedArrayElement(cx, ion, tarr, idval)) michael@0: return false; michael@0: attachedStub = true; michael@0: } michael@0: } michael@0: michael@0: if (!GetObjectElementOperation(cx, JSOp(*pc), obj, /* wasObject = */true, idval, res)) michael@0: return false; michael@0: michael@0: // Disable cache when we reach max stubs or update failed too much. michael@0: if (!attachedStub) { michael@0: cache.incFailedUpdates(); michael@0: if (cache.shouldDisable()) { michael@0: IonSpew(IonSpew_InlineCaches, "Disable inline cache"); michael@0: cache.disable(); michael@0: } michael@0: } else { michael@0: cache.resetFailedUpdates(); michael@0: } michael@0: michael@0: if (!cache.monitoredResult()) michael@0: types::TypeScript::Monitor(cx, script, pc, res); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: GetElementIC::reset() michael@0: { michael@0: RepatchIonCache::reset(); michael@0: hasDenseStub_ = false; michael@0: hasStrictArgumentsStub_ = false; michael@0: hasNormalArgumentsStub_ = false; michael@0: } michael@0: michael@0: static bool michael@0: IsDenseElementSetInlineable(JSObject *obj, const Value &idval) michael@0: { michael@0: if (!obj->is()) michael@0: return false; michael@0: michael@0: if (obj->watched()) michael@0: return false; michael@0: michael@0: if (!idval.isInt32()) michael@0: return false; michael@0: michael@0: // The object may have a setter definition, michael@0: // either directly, or via a prototype, or via the target object for a prototype michael@0: // which is a proxy, that handles a particular integer write. michael@0: // Scan the prototype and shape chain to make sure that this is not the case. michael@0: JSObject *curObj = obj; michael@0: while (curObj) { michael@0: // Ensure object is native. michael@0: if (!curObj->isNative()) michael@0: return false; michael@0: michael@0: // Ensure all indexed properties are stored in dense elements. michael@0: if (curObj->isIndexed()) michael@0: return false; michael@0: michael@0: curObj = curObj->getProto(); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: IsTypedArrayElementSetInlineable(JSObject *obj, const Value &idval, const Value &value) michael@0: { michael@0: // Don't bother attaching stubs for assigning strings and objects. michael@0: return (obj->is() && idval.isInt32() && michael@0: !value.isString() && !value.isObject()); michael@0: } michael@0: michael@0: static void michael@0: StoreDenseElement(MacroAssembler &masm, ConstantOrRegister value, Register elements, michael@0: BaseIndex target) michael@0: { michael@0: // If the ObjectElements::CONVERT_DOUBLE_ELEMENTS flag is set, int32 values michael@0: // have to be converted to double first. If the value is not int32, it can michael@0: // always be stored directly. michael@0: michael@0: Address elementsFlags(elements, ObjectElements::offsetOfFlags()); michael@0: if (value.constant()) { michael@0: Value v = value.value(); michael@0: Label done; michael@0: if (v.isInt32()) { michael@0: Label dontConvert; michael@0: masm.branchTest32(Assembler::Zero, elementsFlags, michael@0: Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS), michael@0: &dontConvert); michael@0: masm.storeValue(DoubleValue(v.toInt32()), target); michael@0: masm.jump(&done); michael@0: masm.bind(&dontConvert); michael@0: } michael@0: masm.storeValue(v, target); michael@0: masm.bind(&done); michael@0: return; michael@0: } michael@0: michael@0: TypedOrValueRegister reg = value.reg(); michael@0: if (reg.hasTyped() && reg.type() != MIRType_Int32) { michael@0: masm.storeTypedOrValue(reg, target); michael@0: return; michael@0: } michael@0: michael@0: Label convert, storeValue, done; michael@0: masm.branchTest32(Assembler::NonZero, elementsFlags, michael@0: Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS), michael@0: &convert); michael@0: masm.bind(&storeValue); michael@0: masm.storeTypedOrValue(reg, target); michael@0: masm.jump(&done); michael@0: michael@0: masm.bind(&convert); michael@0: if (reg.hasValue()) { michael@0: masm.branchTestInt32(Assembler::NotEqual, reg.valueReg(), &storeValue); michael@0: masm.int32ValueToDouble(reg.valueReg(), ScratchFloatReg); michael@0: masm.storeDouble(ScratchFloatReg, target); michael@0: } else { michael@0: JS_ASSERT(reg.type() == MIRType_Int32); michael@0: masm.convertInt32ToDouble(reg.typedReg().gpr(), ScratchFloatReg); michael@0: masm.storeDouble(ScratchFloatReg, target); michael@0: } michael@0: michael@0: masm.bind(&done); michael@0: } michael@0: michael@0: static bool michael@0: GenerateSetDenseElement(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher, michael@0: JSObject *obj, const Value &idval, bool guardHoles, Register object, michael@0: ValueOperand indexVal, ConstantOrRegister value, Register tempToUnboxIndex, michael@0: Register temp) michael@0: { michael@0: JS_ASSERT(obj->isNative()); michael@0: JS_ASSERT(idval.isInt32()); michael@0: michael@0: Label failures; michael@0: Label outOfBounds; // index represents a known hole, or an illegal append michael@0: michael@0: Label markElem, storeElement; // used if TI protects us from worrying about holes. michael@0: michael@0: // Guard object is a dense array. michael@0: Shape *shape = obj->lastProperty(); michael@0: if (!shape) michael@0: return false; michael@0: masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures); michael@0: michael@0: // Ensure the index is an int32 value. michael@0: masm.branchTestInt32(Assembler::NotEqual, indexVal, &failures); michael@0: michael@0: // Unbox the index. michael@0: Register index = masm.extractInt32(indexVal, tempToUnboxIndex); michael@0: michael@0: { michael@0: // Load obj->elements. michael@0: Register elements = temp; michael@0: masm.loadPtr(Address(object, JSObject::offsetOfElements()), elements); michael@0: michael@0: // Compute the location of the element. michael@0: BaseIndex target(elements, index, TimesEight); michael@0: michael@0: // If TI cannot help us deal with HOLES by preventing indexed properties michael@0: // on the prototype chain, we have to be very careful to check for ourselves michael@0: // to avoid stomping on what should be a setter call. Start by only allowing things michael@0: // within the initialized length. michael@0: if (guardHoles) { michael@0: Address initLength(elements, ObjectElements::offsetOfInitializedLength()); michael@0: masm.branch32(Assembler::BelowOrEqual, initLength, index, &outOfBounds); michael@0: } else { michael@0: // Guard that we can increase the initialized length. michael@0: Address capacity(elements, ObjectElements::offsetOfCapacity()); michael@0: masm.branch32(Assembler::BelowOrEqual, capacity, index, &outOfBounds); michael@0: michael@0: // Guard on the initialized length. michael@0: Address initLength(elements, ObjectElements::offsetOfInitializedLength()); michael@0: masm.branch32(Assembler::Below, initLength, index, &outOfBounds); michael@0: michael@0: // if (initLength == index) michael@0: masm.branch32(Assembler::NotEqual, initLength, index, &markElem); michael@0: { michael@0: // Increase initialize length. michael@0: Int32Key newLength(index); michael@0: masm.bumpKey(&newLength, 1); michael@0: masm.storeKey(newLength, initLength); michael@0: michael@0: // Increase length if needed. michael@0: Label bumpedLength; michael@0: Address length(elements, ObjectElements::offsetOfLength()); michael@0: masm.branch32(Assembler::AboveOrEqual, length, index, &bumpedLength); michael@0: masm.storeKey(newLength, length); michael@0: masm.bind(&bumpedLength); michael@0: michael@0: // Restore the index. michael@0: masm.bumpKey(&newLength, -1); michael@0: masm.jump(&storeElement); michael@0: } michael@0: // else michael@0: masm.bind(&markElem); michael@0: } michael@0: michael@0: if (cx->zone()->needsBarrier()) michael@0: masm.callPreBarrier(target, MIRType_Value); michael@0: michael@0: // Store the value. michael@0: if (guardHoles) michael@0: masm.branchTestMagic(Assembler::Equal, target, &failures); michael@0: else michael@0: masm.bind(&storeElement); michael@0: StoreDenseElement(masm, value, elements, target); michael@0: } michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: // All failures flow to here. michael@0: masm.bind(&outOfBounds); michael@0: masm.bind(&failures); michael@0: attacher.jumpNextStub(masm); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: SetElementIC::attachDenseElement(JSContext *cx, IonScript *ion, JSObject *obj, const Value &idval) michael@0: { michael@0: MacroAssembler masm(cx, ion); michael@0: RepatchStubAppender attacher(*this); michael@0: if (!GenerateSetDenseElement(cx, masm, attacher, obj, idval, michael@0: guardHoles(), object(), index(), michael@0: value(), tempToUnboxIndex(), michael@0: temp())) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: setHasDenseStub(); michael@0: const char *message = guardHoles() ? michael@0: "dense array (holes)" : michael@0: "dense array"; michael@0: return linkAndAttachStub(cx, masm, attacher, ion, message); michael@0: } michael@0: michael@0: static bool michael@0: GenerateSetTypedArrayElement(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher, michael@0: TypedArrayObject *tarr, Register object, michael@0: ValueOperand indexVal, ConstantOrRegister value, michael@0: Register tempUnbox, Register temp, FloatRegister tempFloat) michael@0: { michael@0: Label failures, done, popObjectAndFail; michael@0: michael@0: // Guard on the shape. michael@0: Shape *shape = tarr->lastProperty(); michael@0: if (!shape) michael@0: return false; michael@0: masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures); michael@0: michael@0: // Ensure the index is an int32. michael@0: masm.branchTestInt32(Assembler::NotEqual, indexVal, &failures); michael@0: Register index = masm.extractInt32(indexVal, tempUnbox); michael@0: michael@0: // Guard on the length. michael@0: Address length(object, TypedArrayObject::lengthOffset()); michael@0: masm.unboxInt32(length, temp); michael@0: masm.branch32(Assembler::BelowOrEqual, temp, index, &done); michael@0: michael@0: // Load the elements vector. michael@0: Register elements = temp; michael@0: masm.loadPtr(Address(object, TypedArrayObject::dataOffset()), elements); michael@0: michael@0: // Set the value. michael@0: int arrayType = tarr->type(); michael@0: int width = TypedArrayObject::slotWidth(arrayType); michael@0: BaseIndex target(elements, index, ScaleFromElemWidth(width)); michael@0: michael@0: if (arrayType == ScalarTypeDescr::TYPE_FLOAT32) { michael@0: if (LIRGenerator::allowFloat32Optimizations()) { michael@0: if (!masm.convertConstantOrRegisterToFloat(cx, value, tempFloat, &failures)) michael@0: return false; michael@0: } else { michael@0: if (!masm.convertConstantOrRegisterToDouble(cx, value, tempFloat, &failures)) michael@0: return false; michael@0: } michael@0: masm.storeToTypedFloatArray(arrayType, tempFloat, target); michael@0: } else if (arrayType == ScalarTypeDescr::TYPE_FLOAT64) { michael@0: if (!masm.convertConstantOrRegisterToDouble(cx, value, tempFloat, &failures)) michael@0: return false; michael@0: masm.storeToTypedFloatArray(arrayType, tempFloat, target); michael@0: } else { michael@0: // On x86 we only have 6 registers available to use, so reuse the object michael@0: // register to compute the intermediate value to store and restore it michael@0: // afterwards. michael@0: masm.push(object); michael@0: michael@0: if (arrayType == ScalarTypeDescr::TYPE_UINT8_CLAMPED) { michael@0: if (!masm.clampConstantOrRegisterToUint8(cx, value, tempFloat, object, michael@0: &popObjectAndFail)) michael@0: { michael@0: return false; michael@0: } michael@0: } else { michael@0: if (!masm.truncateConstantOrRegisterToInt32(cx, value, tempFloat, object, michael@0: &popObjectAndFail)) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: masm.storeToTypedIntArray(arrayType, object, target); michael@0: michael@0: masm.pop(object); michael@0: } michael@0: michael@0: // Out-of-bound writes jump here as they are no-ops. michael@0: masm.bind(&done); michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: if (popObjectAndFail.used()) { michael@0: masm.bind(&popObjectAndFail); michael@0: masm.pop(object); michael@0: } michael@0: michael@0: masm.bind(&failures); michael@0: attacher.jumpNextStub(masm); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: SetElementIC::attachTypedArrayElement(JSContext *cx, IonScript *ion, TypedArrayObject *tarr) michael@0: { michael@0: MacroAssembler masm(cx, ion); michael@0: RepatchStubAppender attacher(*this); michael@0: if (!GenerateSetTypedArrayElement(cx, masm, attacher, tarr, michael@0: object(), index(), value(), michael@0: tempToUnboxIndex(), temp(), tempFloat())) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "typed array"); michael@0: } michael@0: michael@0: bool michael@0: SetElementIC::update(JSContext *cx, size_t cacheIndex, HandleObject obj, michael@0: HandleValue idval, HandleValue value) michael@0: { michael@0: IonScript *ion = GetTopIonJSScript(cx)->ionScript(); michael@0: SetElementIC &cache = ion->getCache(cacheIndex).toSetElement(); michael@0: michael@0: bool attachedStub = false; michael@0: if (cache.canAttachStub()) { michael@0: if (!cache.hasDenseStub() && IsDenseElementSetInlineable(obj, idval)) { michael@0: if (!cache.attachDenseElement(cx, ion, obj, idval)) michael@0: return false; michael@0: attachedStub = true; michael@0: } michael@0: if (!attachedStub && IsTypedArrayElementSetInlineable(obj, idval, value)) { michael@0: TypedArrayObject *tarr = &obj->as(); michael@0: if (!cache.attachTypedArrayElement(cx, ion, tarr)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (!SetObjectElement(cx, obj, idval, value, cache.strict())) michael@0: return false; michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: SetElementIC::reset() michael@0: { michael@0: RepatchIonCache::reset(); michael@0: hasDenseStub_ = false; michael@0: } michael@0: michael@0: bool michael@0: SetElementParIC::attachDenseElement(LockedJSContext &cx, IonScript *ion, JSObject *obj, michael@0: const Value &idval) michael@0: { michael@0: MacroAssembler masm(cx, ion); michael@0: DispatchStubPrepender attacher(*this); michael@0: if (!GenerateSetDenseElement(cx, masm, attacher, obj, idval, michael@0: guardHoles(), object(), index(), michael@0: value(), tempToUnboxIndex(), michael@0: temp())) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: const char *message = guardHoles() ? michael@0: "parallel dense array (holes)" : michael@0: "parallel dense array"; michael@0: michael@0: return linkAndAttachStub(cx, masm, attacher, ion, message); michael@0: } michael@0: michael@0: bool michael@0: SetElementParIC::attachTypedArrayElement(LockedJSContext &cx, IonScript *ion, michael@0: TypedArrayObject *tarr) michael@0: { michael@0: MacroAssembler masm(cx, ion); michael@0: DispatchStubPrepender attacher(*this); michael@0: if (!GenerateSetTypedArrayElement(cx, masm, attacher, tarr, michael@0: object(), index(), value(), michael@0: tempToUnboxIndex(), temp(), tempFloat())) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "parallel typed array"); michael@0: } michael@0: michael@0: bool michael@0: SetElementParIC::update(ForkJoinContext *cx, size_t cacheIndex, HandleObject obj, michael@0: HandleValue idval, HandleValue value) michael@0: { michael@0: IonScript *ion = GetTopIonJSScript(cx)->parallelIonScript(); michael@0: SetElementParIC &cache = ion->getCache(cacheIndex).toSetElementPar(); michael@0: michael@0: // Avoid unnecessary locking if cannot attach stubs. michael@0: if (!cache.canAttachStub()) michael@0: return SetElementPar(cx, obj, idval, value, cache.strict()); michael@0: michael@0: { michael@0: LockedJSContext ncx(cx); michael@0: michael@0: if (cache.canAttachStub()) { michael@0: bool alreadyStubbed; michael@0: if (!cache.hasOrAddStubbedShape(ncx, obj->lastProperty(), &alreadyStubbed)) michael@0: return cx->setPendingAbortFatal(ParallelBailoutFailedIC); michael@0: if (alreadyStubbed) michael@0: return SetElementPar(cx, obj, idval, value, cache.strict()); michael@0: michael@0: bool attachedStub = false; michael@0: if (IsDenseElementSetInlineable(obj, idval)) { michael@0: if (!cache.attachDenseElement(ncx, ion, obj, idval)) michael@0: return cx->setPendingAbortFatal(ParallelBailoutFailedIC); michael@0: attachedStub = true; michael@0: } michael@0: if (!attachedStub && IsTypedArrayElementSetInlineable(obj, idval, value)) { michael@0: TypedArrayObject *tarr = &obj->as(); michael@0: if (!cache.attachTypedArrayElement(ncx, ion, tarr)) michael@0: return cx->setPendingAbortFatal(ParallelBailoutFailedIC); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return SetElementPar(cx, obj, idval, value, cache.strict()); michael@0: } michael@0: michael@0: bool michael@0: GetElementParIC::attachReadSlot(LockedJSContext &cx, IonScript *ion, JSObject *obj, michael@0: const Value &idval, PropertyName *name, JSObject *holder, michael@0: Shape *shape) michael@0: { michael@0: MacroAssembler masm(cx, ion); michael@0: DispatchStubPrepender attacher(*this); michael@0: michael@0: // Guard on the index value. michael@0: Label failures; michael@0: ValueOperand val = index().reg().valueReg(); michael@0: masm.branchTestValue(Assembler::NotEqual, val, idval, &failures); michael@0: michael@0: GenerateReadSlot(cx, ion, masm, attacher, obj, holder, shape, object(), output(), michael@0: &failures); michael@0: michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "parallel getelem reading"); michael@0: } michael@0: michael@0: bool michael@0: GetElementParIC::attachDenseElement(LockedJSContext &cx, IonScript *ion, JSObject *obj, michael@0: const Value &idval) michael@0: { michael@0: MacroAssembler masm(cx, ion); michael@0: DispatchStubPrepender attacher(*this); michael@0: if (!GenerateDenseElement(cx, masm, attacher, obj, idval, object(), index(), output())) michael@0: return false; michael@0: michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "parallel dense element"); michael@0: } michael@0: michael@0: bool michael@0: GetElementParIC::attachTypedArrayElement(LockedJSContext &cx, IonScript *ion, michael@0: TypedArrayObject *tarr, const Value &idval) michael@0: { michael@0: MacroAssembler masm(cx, ion); michael@0: DispatchStubPrepender attacher(*this); michael@0: GenerateGetTypedArrayElement(cx, masm, attacher, tarr, idval, object(), index(), output(), michael@0: allowDoubleResult()); michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "parallel typed array"); michael@0: } michael@0: michael@0: bool michael@0: GetElementParIC::update(ForkJoinContext *cx, size_t cacheIndex, HandleObject obj, michael@0: HandleValue idval, MutableHandleValue vp) michael@0: { michael@0: IonScript *ion = GetTopIonJSScript(cx)->parallelIonScript(); michael@0: GetElementParIC &cache = ion->getCache(cacheIndex).toGetElementPar(); michael@0: michael@0: // Try to get the element early, as the pure path doesn't need a lock. If michael@0: // we can't do it purely, bail out of parallel execution. michael@0: if (!GetObjectElementOperationPure(cx, obj, idval, vp.address())) michael@0: return false; michael@0: michael@0: // Avoid unnecessary locking if cannot attach stubs. michael@0: if (!cache.canAttachStub()) michael@0: return true; michael@0: michael@0: { michael@0: // See note about locking context in GetPropertyParIC::update. michael@0: LockedJSContext ncx(cx); michael@0: michael@0: if (cache.canAttachStub()) { michael@0: bool alreadyStubbed; michael@0: if (!cache.hasOrAddStubbedShape(ncx, obj->lastProperty(), &alreadyStubbed)) michael@0: return cx->setPendingAbortFatal(ParallelBailoutFailedIC); michael@0: if (alreadyStubbed) michael@0: return true; michael@0: michael@0: jsid id; michael@0: if (!ValueToIdPure(idval, &id)) michael@0: return false; michael@0: michael@0: bool attachedStub = false; michael@0: if (cache.monitoredResult() && michael@0: GetElementIC::canAttachGetProp(obj, idval, id)) michael@0: { michael@0: RootedShape shape(ncx); michael@0: RootedObject holder(ncx); michael@0: RootedPropertyName name(ncx, JSID_TO_ATOM(id)->asPropertyName()); michael@0: michael@0: GetPropertyIC::NativeGetPropCacheability canCache = michael@0: CanAttachNativeGetProp(ncx, cache, obj, name, &holder, &shape); michael@0: michael@0: if (canCache == GetPropertyIC::CanAttachReadSlot) michael@0: { michael@0: if (!cache.attachReadSlot(ncx, ion, obj, idval, name, holder, shape)) michael@0: return cx->setPendingAbortFatal(ParallelBailoutFailedIC); michael@0: attachedStub = true; michael@0: } michael@0: } michael@0: if (!attachedStub && michael@0: GetElementIC::canAttachDenseElement(obj, idval)) michael@0: { michael@0: if (!cache.attachDenseElement(ncx, ion, obj, idval)) michael@0: return cx->setPendingAbortFatal(ParallelBailoutFailedIC); michael@0: attachedStub = true; michael@0: } michael@0: if (!attachedStub && michael@0: GetElementIC::canAttachTypedArrayElement(obj, idval, cache.output())) michael@0: { michael@0: if (!cache.attachTypedArrayElement(ncx, ion, &obj->as(), idval)) michael@0: return cx->setPendingAbortFatal(ParallelBailoutFailedIC); michael@0: attachedStub = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BindNameIC::attachGlobal(JSContext *cx, IonScript *ion, JSObject *scopeChain) michael@0: { michael@0: JS_ASSERT(scopeChain->is()); michael@0: michael@0: MacroAssembler masm(cx, ion); michael@0: RepatchStubAppender attacher(*this); michael@0: michael@0: // Guard on the scope chain. michael@0: attacher.branchNextStub(masm, Assembler::NotEqual, scopeChainReg(), michael@0: ImmGCPtr(scopeChain)); michael@0: masm.movePtr(ImmGCPtr(scopeChain), outputReg()); michael@0: michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "global"); michael@0: } michael@0: michael@0: static inline void michael@0: GenerateScopeChainGuard(MacroAssembler &masm, JSObject *scopeObj, michael@0: Register scopeObjReg, Shape *shape, Label *failures) michael@0: { michael@0: if (scopeObj->is()) { michael@0: // We can skip a guard on the call object if the script's bindings are michael@0: // guaranteed to be immutable (and thus cannot introduce shadowing michael@0: // variables). michael@0: CallObject *callObj = &scopeObj->as(); michael@0: if (!callObj->isForEval()) { michael@0: JSFunction *fun = &callObj->callee(); michael@0: // The function might have been relazified under rare conditions. michael@0: // In that case, we pessimistically create the guard, as we'd michael@0: // need to root various pointers to delazify, michael@0: if (fun->hasScript()) { michael@0: JSScript *script = fun->nonLazyScript(); michael@0: if (!script->funHasExtensibleScope()) michael@0: return; michael@0: } michael@0: } michael@0: } else if (scopeObj->is()) { michael@0: // If this is the last object on the scope walk, and the property we've michael@0: // found is not configurable, then we don't need a shape guard because michael@0: // the shape cannot be removed. michael@0: if (shape && !shape->configurable()) michael@0: return; michael@0: } michael@0: michael@0: Address shapeAddr(scopeObjReg, JSObject::offsetOfShape()); michael@0: masm.branchPtr(Assembler::NotEqual, shapeAddr, ImmGCPtr(scopeObj->lastProperty()), failures); michael@0: } michael@0: michael@0: static void michael@0: GenerateScopeChainGuards(MacroAssembler &masm, JSObject *scopeChain, JSObject *holder, michael@0: Register outputReg, Label *failures, bool skipLastGuard = false) michael@0: { michael@0: JSObject *tobj = scopeChain; michael@0: michael@0: // Walk up the scope chain. Note that IsCacheableScopeChain guarantees the michael@0: // |tobj == holder| condition terminates the loop. michael@0: while (true) { michael@0: JS_ASSERT(IsCacheableNonGlobalScope(tobj) || tobj->is()); michael@0: michael@0: if (skipLastGuard && tobj == holder) michael@0: break; michael@0: michael@0: GenerateScopeChainGuard(masm, tobj, outputReg, nullptr, failures); michael@0: michael@0: if (tobj == holder) michael@0: break; michael@0: michael@0: // Load the next link. michael@0: tobj = &tobj->as().enclosingScope(); michael@0: masm.extractObject(Address(outputReg, ScopeObject::offsetOfEnclosingScope()), outputReg); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: BindNameIC::attachNonGlobal(JSContext *cx, IonScript *ion, JSObject *scopeChain, JSObject *holder) michael@0: { michael@0: JS_ASSERT(IsCacheableNonGlobalScope(scopeChain)); michael@0: michael@0: MacroAssembler masm(cx, ion); michael@0: RepatchStubAppender attacher(*this); michael@0: michael@0: // Guard on the shape of the scope chain. michael@0: Label failures; michael@0: attacher.branchNextStubOrLabel(masm, Assembler::NotEqual, michael@0: Address(scopeChainReg(), JSObject::offsetOfShape()), michael@0: ImmGCPtr(scopeChain->lastProperty()), michael@0: holder != scopeChain ? &failures : nullptr); michael@0: michael@0: if (holder != scopeChain) { michael@0: JSObject *parent = &scopeChain->as().enclosingScope(); michael@0: masm.extractObject(Address(scopeChainReg(), ScopeObject::offsetOfEnclosingScope()), outputReg()); michael@0: michael@0: GenerateScopeChainGuards(masm, parent, holder, outputReg(), &failures); michael@0: } else { michael@0: masm.movePtr(scopeChainReg(), outputReg()); michael@0: } michael@0: michael@0: // At this point outputReg holds the object on which the property michael@0: // was found, so we're done. michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: // All failures flow to here, so there is a common point to patch. michael@0: if (holder != scopeChain) { michael@0: masm.bind(&failures); michael@0: attacher.jumpNextStub(masm); michael@0: } michael@0: michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "non-global"); michael@0: } michael@0: michael@0: static bool michael@0: IsCacheableScopeChain(JSObject *scopeChain, JSObject *holder) michael@0: { michael@0: while (true) { michael@0: if (!IsCacheableNonGlobalScope(scopeChain)) { michael@0: IonSpew(IonSpew_InlineCaches, "Non-cacheable object on scope chain"); michael@0: return false; michael@0: } michael@0: michael@0: if (scopeChain == holder) michael@0: return true; michael@0: michael@0: scopeChain = &scopeChain->as().enclosingScope(); michael@0: if (!scopeChain) { michael@0: IonSpew(IonSpew_InlineCaches, "Scope chain indirect hit"); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: MOZ_ASSUME_UNREACHABLE("Invalid scope chain"); michael@0: } michael@0: michael@0: JSObject * michael@0: BindNameIC::update(JSContext *cx, size_t cacheIndex, HandleObject scopeChain) michael@0: { michael@0: IonScript *ion = GetTopIonJSScript(cx)->ionScript(); michael@0: BindNameIC &cache = ion->getCache(cacheIndex).toBindName(); michael@0: HandlePropertyName name = cache.name(); michael@0: michael@0: RootedObject holder(cx); michael@0: if (scopeChain->is()) { michael@0: holder = scopeChain; michael@0: } else { michael@0: if (!LookupNameWithGlobalDefault(cx, name, scopeChain, &holder)) michael@0: return nullptr; michael@0: } michael@0: michael@0: // Stop generating new stubs once we hit the stub count limit, see michael@0: // GetPropertyCache. michael@0: if (cache.canAttachStub()) { michael@0: if (scopeChain->is()) { michael@0: if (!cache.attachGlobal(cx, ion, scopeChain)) michael@0: return nullptr; michael@0: } else if (IsCacheableScopeChain(scopeChain, holder)) { michael@0: if (!cache.attachNonGlobal(cx, ion, scopeChain, holder)) michael@0: return nullptr; michael@0: } else { michael@0: IonSpew(IonSpew_InlineCaches, "BINDNAME uncacheable scope chain"); michael@0: } michael@0: } michael@0: michael@0: return holder; michael@0: } michael@0: michael@0: bool michael@0: NameIC::attachReadSlot(JSContext *cx, IonScript *ion, HandleObject scopeChain, michael@0: HandleObject holderBase, HandleObject holder, michael@0: HandleShape shape) michael@0: { michael@0: MacroAssembler masm(cx, ion); michael@0: Label failures; michael@0: RepatchStubAppender attacher(*this); michael@0: michael@0: Register scratchReg = outputReg().valueReg().scratchReg(); michael@0: michael@0: // Don't guard the base of the proto chain the name was found on. It will be guarded michael@0: // by GenerateReadSlot(). michael@0: masm.mov(scopeChainReg(), scratchReg); michael@0: GenerateScopeChainGuards(masm, scopeChain, holderBase, scratchReg, &failures, michael@0: /* skipLastGuard = */true); michael@0: michael@0: // GenerateScopeChain leaves the last scope chain in scrachReg, even though it michael@0: // doesn't generate the extra guard. michael@0: GenerateReadSlot(cx, ion, masm, attacher, holderBase, holder, shape, scratchReg, michael@0: outputReg(), failures.used() ? &failures : nullptr); michael@0: michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "generic"); michael@0: } michael@0: michael@0: static bool michael@0: IsCacheableNameReadSlot(JSContext *cx, HandleObject scopeChain, HandleObject obj, michael@0: HandleObject holder, HandleShape shape, jsbytecode *pc, michael@0: const TypedOrValueRegister &output) michael@0: { michael@0: if (!shape) michael@0: return false; michael@0: if (!obj->isNative()) michael@0: return false; michael@0: michael@0: if (obj->is()) { michael@0: // Support only simple property lookups. michael@0: if (!IsCacheableGetPropReadSlot(obj, holder, shape) && michael@0: !IsCacheableNoProperty(obj, holder, shape, pc, output)) michael@0: return false; michael@0: } else if (obj->is()) { michael@0: JS_ASSERT(obj == holder); michael@0: if (!shape->hasDefaultGetter()) michael@0: return false; michael@0: } else { michael@0: // We don't yet support lookups on Block or DeclEnv objects. michael@0: return false; michael@0: } michael@0: michael@0: RootedObject obj2(cx, scopeChain); michael@0: while (obj2) { michael@0: if (!IsCacheableNonGlobalScope(obj2) && !obj2->is()) michael@0: return false; michael@0: michael@0: // Stop once we hit the global or target obj. michael@0: if (obj2->is() || obj2 == obj) michael@0: break; michael@0: michael@0: obj2 = obj2->enclosingScope(); michael@0: } michael@0: michael@0: return obj == obj2; michael@0: } michael@0: michael@0: bool michael@0: NameIC::attachCallGetter(JSContext *cx, IonScript *ion, JSObject *obj, JSObject *holder, michael@0: HandleShape shape, void *returnAddr) michael@0: { michael@0: MacroAssembler masm(cx, ion, script_, pc_); michael@0: michael@0: RepatchStubAppender attacher(*this); michael@0: if (!GenerateCallGetter(cx, ion, masm, attacher, obj, name(), holder, shape, liveRegs_, michael@0: scopeChainReg(), outputReg(), returnAddr)) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: const char *attachKind = "name getter"; michael@0: return linkAndAttachStub(cx, masm, attacher, ion, attachKind); michael@0: } michael@0: michael@0: static bool michael@0: IsCacheableNameCallGetter(JSObject *scopeChain, JSObject *obj, JSObject *holder, Shape *shape) michael@0: { michael@0: if (obj != scopeChain) michael@0: return false; michael@0: michael@0: if (!obj->is()) michael@0: return false; michael@0: michael@0: return IsCacheableGetPropCallNative(obj, holder, shape) || michael@0: IsCacheableGetPropCallPropertyOp(obj, holder, shape); michael@0: } michael@0: michael@0: bool michael@0: NameIC::update(JSContext *cx, size_t cacheIndex, HandleObject scopeChain, michael@0: MutableHandleValue vp) michael@0: { michael@0: void *returnAddr; michael@0: IonScript *ion = GetTopIonJSScript(cx, &returnAddr)->ionScript(); michael@0: michael@0: NameIC &cache = ion->getCache(cacheIndex).toName(); michael@0: RootedPropertyName name(cx, cache.name()); michael@0: michael@0: RootedScript script(cx); michael@0: jsbytecode *pc; michael@0: cache.getScriptedLocation(&script, &pc); michael@0: michael@0: RootedObject obj(cx); michael@0: RootedObject holder(cx); michael@0: RootedShape shape(cx); michael@0: if (!LookupName(cx, name, scopeChain, &obj, &holder, &shape)) michael@0: return false; michael@0: michael@0: if (cache.canAttachStub()) { michael@0: if (IsCacheableNameReadSlot(cx, scopeChain, obj, holder, shape, pc, cache.outputReg())) { michael@0: if (!cache.attachReadSlot(cx, ion, scopeChain, obj, holder, shape)) michael@0: return false; michael@0: } else if (IsCacheableNameCallGetter(scopeChain, obj, holder, shape)) { michael@0: if (!cache.attachCallGetter(cx, ion, obj, holder, shape, returnAddr)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (cache.isTypeOf()) { michael@0: if (!FetchName(cx, obj, holder, name, shape, vp)) michael@0: return false; michael@0: } else { michael@0: if (!FetchName(cx, obj, holder, name, shape, vp)) michael@0: return false; michael@0: } michael@0: michael@0: // Monitor changes to cache entry. michael@0: types::TypeScript::Monitor(cx, script, pc, vp); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CallsiteCloneIC::attach(JSContext *cx, IonScript *ion, HandleFunction original, michael@0: HandleFunction clone) michael@0: { michael@0: MacroAssembler masm(cx, ion); michael@0: RepatchStubAppender attacher(*this); michael@0: michael@0: // Guard against object identity on the original. michael@0: attacher.branchNextStub(masm, Assembler::NotEqual, calleeReg(), ImmGCPtr(original)); michael@0: michael@0: // Load the clone. michael@0: masm.movePtr(ImmGCPtr(clone), outputReg()); michael@0: michael@0: attacher.jumpRejoin(masm); michael@0: michael@0: return linkAndAttachStub(cx, masm, attacher, ion, "generic"); michael@0: } michael@0: michael@0: JSObject * michael@0: CallsiteCloneIC::update(JSContext *cx, size_t cacheIndex, HandleObject callee) michael@0: { michael@0: // Act as the identity for functions that are not clone-at-callsite, as we michael@0: // generate this cache as long as some callees are clone-at-callsite. michael@0: RootedFunction fun(cx, &callee->as()); michael@0: if (!fun->hasScript() || !fun->nonLazyScript()->shouldCloneAtCallsite()) michael@0: return fun; michael@0: michael@0: IonScript *ion = GetTopIonJSScript(cx)->ionScript(); michael@0: CallsiteCloneIC &cache = ion->getCache(cacheIndex).toCallsiteClone(); michael@0: michael@0: RootedFunction clone(cx, CloneFunctionAtCallsite(cx, fun, cache.callScript(), cache.callPc())); michael@0: if (!clone) michael@0: return nullptr; michael@0: michael@0: if (cache.canAttachStub()) { michael@0: if (!cache.attach(cx, ion, fun, clone)) michael@0: return nullptr; michael@0: } michael@0: michael@0: return clone; michael@0: }