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