Bug 1919824 - Make `NativeKey::GetFollowingCharMessage` return `false` when removed...
[gecko.git] / js / src / jit / CacheIR.cpp
blobd2e0f39c68cb0ae0cfa4474bfa57a47af0d03233
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "jit/CacheIR.h"
9 #include "mozilla/CheckedInt.h"
10 #include "mozilla/DebugOnly.h"
11 #include "mozilla/FloatingPoint.h"
13 #include "jsapi.h"
14 #include "jsmath.h"
15 #include "jsnum.h"
17 #include "builtin/DataViewObject.h"
18 #include "builtin/MapObject.h"
19 #include "builtin/ModuleObject.h"
20 #include "builtin/Object.h"
21 #include "jit/BaselineIC.h"
22 #include "jit/CacheIRCloner.h"
23 #include "jit/CacheIRCompiler.h"
24 #include "jit/CacheIRGenerator.h"
25 #include "jit/CacheIRSpewer.h"
26 #include "jit/CacheIRWriter.h"
27 #include "jit/InlinableNatives.h"
28 #include "jit/JitContext.h"
29 #include "jit/JitZone.h"
30 #include "js/experimental/JitInfo.h" // JSJitInfo
31 #include "js/friend/DOMProxy.h" // JS::ExpandoAndGeneration
32 #include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy, js::ToWindowIfWindowProxy
33 #include "js/friend/XrayJitInfo.h" // js::jit::GetXrayJitInfo, JS::XrayJitInfo
34 #include "js/GCAPI.h" // JS::AutoSuppressGCAnalysis
35 #include "js/Prefs.h" // JS::Prefs
36 #include "js/RegExpFlags.h" // JS::RegExpFlags
37 #include "js/ScalarType.h" // js::Scalar::Type
38 #include "js/Utility.h" // JS::AutoEnterOOMUnsafeRegion
39 #include "js/Wrapper.h"
40 #include "proxy/DOMProxy.h" // js::GetDOMProxyHandlerFamily
41 #include "proxy/ScriptedProxyHandler.h"
42 #include "util/DifferentialTesting.h"
43 #include "util/Unicode.h"
44 #include "vm/ArrayBufferObject.h"
45 #include "vm/BoundFunctionObject.h"
46 #include "vm/BytecodeUtil.h"
47 #include "vm/Compartment.h"
48 #include "vm/Iteration.h"
49 #include "vm/PlainObject.h" // js::PlainObject
50 #include "vm/ProxyObject.h"
51 #include "vm/RegExpObject.h"
52 #include "vm/SelfHosting.h"
53 #include "vm/ThrowMsgKind.h" // ThrowCondition
54 #include "vm/TypeofEqOperand.h" // TypeofEqOperand
55 #include "vm/Watchtower.h"
56 #include "wasm/WasmInstance.h"
58 #include "jit/BaselineFrame-inl.h"
59 #include "jit/MacroAssembler-inl.h"
60 #include "vm/ArrayBufferObject-inl.h"
61 #include "vm/BytecodeUtil-inl.h"
62 #include "vm/EnvironmentObject-inl.h"
63 #include "vm/JSContext-inl.h"
64 #include "vm/JSFunction-inl.h"
65 #include "vm/JSObject-inl.h"
66 #include "vm/JSScript-inl.h"
67 #include "vm/List-inl.h"
68 #include "vm/NativeObject-inl.h"
69 #include "vm/PlainObject-inl.h"
70 #include "vm/StringObject-inl.h"
71 #include "wasm/WasmInstance-inl.h"
73 using namespace js;
74 using namespace js::jit;
76 using mozilla::DebugOnly;
77 using mozilla::Maybe;
79 using JS::DOMProxyShadowsResult;
80 using JS::ExpandoAndGeneration;
82 const char* const js::jit::CacheKindNames[] = {
83 #define DEFINE_KIND(kind) #kind,
84 CACHE_IR_KINDS(DEFINE_KIND)
85 #undef DEFINE_KIND
88 const char* const js::jit::CacheIROpNames[] = {
89 #define OPNAME(op, ...) #op,
90 CACHE_IR_OPS(OPNAME)
91 #undef OPNAME
94 const CacheIROpInfo js::jit::CacheIROpInfos[] = {
95 #define OPINFO(op, len, transpile, ...) {len, transpile},
96 CACHE_IR_OPS(OPINFO)
97 #undef OPINFO
100 const uint32_t js::jit::CacheIROpHealth[] = {
101 #define OPHEALTH(op, len, transpile, health) health,
102 CACHE_IR_OPS(OPHEALTH)
103 #undef OPHEALTH
106 size_t js::jit::NumInputsForCacheKind(CacheKind kind) {
107 switch (kind) {
108 case CacheKind::NewArray:
109 case CacheKind::NewObject:
110 case CacheKind::GetIntrinsic:
111 return 0;
112 case CacheKind::GetProp:
113 case CacheKind::TypeOf:
114 case CacheKind::TypeOfEq:
115 case CacheKind::ToPropertyKey:
116 case CacheKind::GetIterator:
117 case CacheKind::ToBool:
118 case CacheKind::UnaryArith:
119 case CacheKind::GetName:
120 case CacheKind::BindName:
121 case CacheKind::Call:
122 case CacheKind::OptimizeSpreadCall:
123 case CacheKind::CloseIter:
124 case CacheKind::OptimizeGetIterator:
125 return 1;
126 case CacheKind::Compare:
127 case CacheKind::GetElem:
128 case CacheKind::GetPropSuper:
129 case CacheKind::SetProp:
130 case CacheKind::In:
131 case CacheKind::HasOwn:
132 case CacheKind::CheckPrivateField:
133 case CacheKind::InstanceOf:
134 case CacheKind::BinaryArith:
135 return 2;
136 case CacheKind::GetElemSuper:
137 case CacheKind::SetElem:
138 return 3;
140 MOZ_CRASH("Invalid kind");
143 #ifdef DEBUG
144 void CacheIRWriter::assertSameCompartment(JSObject* obj) {
145 MOZ_ASSERT(cx_->compartment() == obj->compartment());
147 void CacheIRWriter::assertSameZone(Shape* shape) {
148 MOZ_ASSERT(cx_->zone() == shape->zone());
150 #endif
152 StubField CacheIRWriter::readStubField(uint32_t offset,
153 StubField::Type type) const {
154 size_t index = 0;
155 size_t currentOffset = 0;
157 // If we've seen an offset earlier than this before, we know we can start the
158 // search there at least, otherwise, we start the search from the beginning.
159 if (lastOffset_ < offset) {
160 currentOffset = lastOffset_;
161 index = lastIndex_;
164 while (currentOffset != offset) {
165 currentOffset += StubField::sizeInBytes(stubFields_[index].type());
166 index++;
167 MOZ_ASSERT(index < stubFields_.length());
170 MOZ_ASSERT(stubFields_[index].type() == type);
172 lastOffset_ = currentOffset;
173 lastIndex_ = index;
175 return stubFields_[index];
178 CacheIRCloner::CacheIRCloner(ICCacheIRStub* stub)
179 : stubInfo_(stub->stubInfo()), stubData_(stub->stubDataStart()) {}
181 void CacheIRCloner::cloneOp(CacheOp op, CacheIRReader& reader,
182 CacheIRWriter& writer) {
183 switch (op) {
184 #define DEFINE_OP(op, ...) \
185 case CacheOp::op: \
186 clone##op(reader, writer); \
187 break;
188 CACHE_IR_OPS(DEFINE_OP)
189 #undef DEFINE_OP
190 default:
191 MOZ_CRASH("Invalid op");
195 uintptr_t CacheIRCloner::readStubWord(uint32_t offset) {
196 return stubInfo_->getStubRawWord(stubData_, offset);
198 int64_t CacheIRCloner::readStubInt64(uint32_t offset) {
199 return stubInfo_->getStubRawInt64(stubData_, offset);
202 Shape* CacheIRCloner::getShapeField(uint32_t stubOffset) {
203 return reinterpret_cast<Shape*>(readStubWord(stubOffset));
205 Shape* CacheIRCloner::getWeakShapeField(uint32_t stubOffset) {
206 // No barrier is required to clone a weak pointer.
207 return reinterpret_cast<Shape*>(readStubWord(stubOffset));
209 GetterSetter* CacheIRCloner::getWeakGetterSetterField(uint32_t stubOffset) {
210 // No barrier is required to clone a weak pointer.
211 return reinterpret_cast<GetterSetter*>(readStubWord(stubOffset));
213 JSObject* CacheIRCloner::getObjectField(uint32_t stubOffset) {
214 return reinterpret_cast<JSObject*>(readStubWord(stubOffset));
216 JSObject* CacheIRCloner::getWeakObjectField(uint32_t stubOffset) {
217 // No barrier is required to clone a weak pointer.
218 return reinterpret_cast<JSObject*>(readStubWord(stubOffset));
220 JSString* CacheIRCloner::getStringField(uint32_t stubOffset) {
221 return reinterpret_cast<JSString*>(readStubWord(stubOffset));
223 JSAtom* CacheIRCloner::getAtomField(uint32_t stubOffset) {
224 return reinterpret_cast<JSAtom*>(readStubWord(stubOffset));
226 JS::Symbol* CacheIRCloner::getSymbolField(uint32_t stubOffset) {
227 return reinterpret_cast<JS::Symbol*>(readStubWord(stubOffset));
229 BaseScript* CacheIRCloner::getWeakBaseScriptField(uint32_t stubOffset) {
230 // No barrier is required to clone a weak pointer.
231 return reinterpret_cast<BaseScript*>(readStubWord(stubOffset));
233 JitCode* CacheIRCloner::getJitCodeField(uint32_t stubOffset) {
234 return reinterpret_cast<JitCode*>(readStubWord(stubOffset));
236 uint32_t CacheIRCloner::getRawInt32Field(uint32_t stubOffset) {
237 return uint32_t(reinterpret_cast<uintptr_t>(readStubWord(stubOffset)));
239 const void* CacheIRCloner::getRawPointerField(uint32_t stubOffset) {
240 return reinterpret_cast<const void*>(readStubWord(stubOffset));
242 uint64_t CacheIRCloner::getRawInt64Field(uint32_t stubOffset) {
243 return static_cast<uint64_t>(readStubInt64(stubOffset));
245 gc::AllocSite* CacheIRCloner::getAllocSiteField(uint32_t stubOffset) {
246 return reinterpret_cast<gc::AllocSite*>(readStubWord(stubOffset));
249 jsid CacheIRCloner::getIdField(uint32_t stubOffset) {
250 return jsid::fromRawBits(readStubWord(stubOffset));
252 const Value CacheIRCloner::getValueField(uint32_t stubOffset) {
253 return Value::fromRawBits(uint64_t(readStubInt64(stubOffset)));
255 double CacheIRCloner::getDoubleField(uint32_t stubOffset) {
256 uint64_t bits = uint64_t(readStubInt64(stubOffset));
257 return mozilla::BitwiseCast<double>(bits);
260 IRGenerator::IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
261 CacheKind cacheKind, ICState state,
262 BaselineFrame* maybeFrame)
263 : writer(cx),
264 cx_(cx),
265 script_(script),
266 pc_(pc),
267 maybeFrame_(maybeFrame),
268 cacheKind_(cacheKind),
269 mode_(state.mode()),
270 isFirstStub_(state.newStubIsFirstStub()),
271 numOptimizedStubs_(state.numOptimizedStubs()) {}
273 // Allocation sites are usually created during baseline compilation, but we also
274 // need to create them when an IC stub is added to a baseline compiled script
275 // and when trial inlining.
276 gc::AllocSite* IRGenerator::maybeCreateAllocSite() {
277 MOZ_ASSERT(BytecodeOpCanHaveAllocSite(JSOp(*pc_)));
279 BaselineFrame* frame = maybeFrame_;
280 MOZ_ASSERT(frame);
282 JSScript* outerScript = frame->outerScript();
283 bool hasBaselineScript = outerScript->hasBaselineScript();
284 bool isInlined = frame->icScript()->isInlined();
285 if (!hasBaselineScript && !isInlined) {
286 MOZ_ASSERT(frame->runningInInterpreter());
287 return outerScript->zone()->unknownAllocSite(JS::TraceKind::Object);
290 uint32_t pcOffset = frame->script()->pcToOffset(pc_);
291 return frame->icScript()->getOrCreateAllocSite(outerScript, pcOffset);
294 GetPropIRGenerator::GetPropIRGenerator(JSContext* cx, HandleScript script,
295 jsbytecode* pc, ICState state,
296 CacheKind cacheKind, HandleValue val,
297 HandleValue idVal)
298 : IRGenerator(cx, script, pc, cacheKind, state), val_(val), idVal_(idVal) {}
300 static void EmitLoadSlotResult(CacheIRWriter& writer, ObjOperandId holderId,
301 NativeObject* holder, PropertyInfo prop) {
302 if (holder->isFixedSlot(prop.slot())) {
303 writer.loadFixedSlotResult(holderId,
304 NativeObject::getFixedSlotOffset(prop.slot()));
305 } else {
306 size_t dynamicSlotOffset =
307 holder->dynamicSlotIndex(prop.slot()) * sizeof(Value);
308 writer.loadDynamicSlotResult(holderId, dynamicSlotOffset);
312 // DOM proxies
313 // -----------
315 // DOM proxies are proxies that are used to implement various DOM objects like
316 // HTMLDocument and NodeList. DOM proxies may have an expando object - a native
317 // object that stores extra properties added to the object. The following
318 // CacheIR instructions are only used with DOM proxies:
320 // * LoadDOMExpandoValue: returns the Value in the proxy's expando slot. This
321 // returns either an UndefinedValue (no expando), ObjectValue (the expando
322 // object), or PrivateValue(ExpandoAndGeneration*).
324 // * LoadDOMExpandoValueGuardGeneration: guards the Value in the proxy's expando
325 // slot is the same PrivateValue(ExpandoAndGeneration*), then guards on its
326 // generation, then returns expandoAndGeneration->expando. This Value is
327 // either an UndefinedValue or ObjectValue.
329 // * LoadDOMExpandoValueIgnoreGeneration: assumes the Value in the proxy's
330 // expando slot is a PrivateValue(ExpandoAndGeneration*), unboxes it, and
331 // returns the expandoAndGeneration->expando Value.
333 // * GuardDOMExpandoMissingOrGuardShape: takes an expando Value as input, then
334 // guards it's either UndefinedValue or an object with the expected shape.
336 enum class ProxyStubType {
337 None,
338 DOMExpando,
339 DOMShadowed,
340 DOMUnshadowed,
341 Generic
344 static bool IsCacheableDOMProxy(ProxyObject* obj) {
345 const BaseProxyHandler* handler = obj->handler();
346 if (handler->family() != GetDOMProxyHandlerFamily()) {
347 return false;
350 // Some DOM proxies have dynamic prototypes. We can't really cache those very
351 // well.
352 return obj->hasStaticPrototype();
355 static ProxyStubType GetProxyStubType(JSContext* cx, HandleObject obj,
356 HandleId id) {
357 if (!obj->is<ProxyObject>()) {
358 return ProxyStubType::None;
360 auto proxy = obj.as<ProxyObject>();
362 if (!IsCacheableDOMProxy(proxy)) {
363 return ProxyStubType::Generic;
366 // Private fields are defined on a separate expando object.
367 if (id.isPrivateName()) {
368 return ProxyStubType::Generic;
371 DOMProxyShadowsResult shadows = GetDOMProxyShadowsCheck()(cx, proxy, id);
372 if (shadows == DOMProxyShadowsResult::ShadowCheckFailed) {
373 cx->clearPendingException();
374 return ProxyStubType::None;
377 if (DOMProxyIsShadowing(shadows)) {
378 if (shadows == DOMProxyShadowsResult::ShadowsViaDirectExpando ||
379 shadows == DOMProxyShadowsResult::ShadowsViaIndirectExpando) {
380 return ProxyStubType::DOMExpando;
382 return ProxyStubType::DOMShadowed;
385 MOZ_ASSERT(shadows == DOMProxyShadowsResult::DoesntShadow ||
386 shadows == DOMProxyShadowsResult::DoesntShadowUnique);
387 return ProxyStubType::DOMUnshadowed;
390 static bool ValueToNameOrSymbolId(JSContext* cx, HandleValue idVal,
391 MutableHandleId id, bool* nameOrSymbol) {
392 *nameOrSymbol = false;
394 if (!idVal.isString() && !idVal.isSymbol() && !idVal.isUndefined() &&
395 !idVal.isNull()) {
396 return true;
399 if (!PrimitiveValueToId<CanGC>(cx, idVal, id)) {
400 return false;
403 if (!id.isAtom() && !id.isSymbol()) {
404 id.set(JS::PropertyKey::Void());
405 return true;
408 if (id.isAtom() && id.toAtom()->isIndex()) {
409 id.set(JS::PropertyKey::Void());
410 return true;
413 *nameOrSymbol = true;
414 return true;
417 AttachDecision GetPropIRGenerator::tryAttachStub() {
418 AutoAssertNoPendingException aanpe(cx_);
420 ValOperandId valId(writer.setInputOperandId(0));
421 if (cacheKind_ != CacheKind::GetProp) {
422 MOZ_ASSERT_IF(cacheKind_ == CacheKind::GetPropSuper,
423 getSuperReceiverValueId().id() == 1);
424 MOZ_ASSERT_IF(cacheKind_ != CacheKind::GetPropSuper,
425 getElemKeyValueId().id() == 1);
426 writer.setInputOperandId(1);
428 if (cacheKind_ == CacheKind::GetElemSuper) {
429 MOZ_ASSERT(getSuperReceiverValueId().id() == 2);
430 writer.setInputOperandId(2);
433 RootedId id(cx_);
434 bool nameOrSymbol;
435 if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
436 cx_->clearPendingException();
437 return AttachDecision::NoAction;
440 // |super.prop| getter calls use a |this| value that differs from lookup
441 // object.
442 ValOperandId receiverId = isSuper() ? getSuperReceiverValueId() : valId;
444 if (val_.isObject()) {
445 RootedObject obj(cx_, &val_.toObject());
446 ObjOperandId objId = writer.guardToObject(valId);
447 if (nameOrSymbol) {
448 TRY_ATTACH(tryAttachObjectLength(obj, objId, id));
449 TRY_ATTACH(tryAttachTypedArray(obj, objId, id));
450 TRY_ATTACH(tryAttachDataView(obj, objId, id));
451 TRY_ATTACH(tryAttachArrayBufferMaybeShared(obj, objId, id));
452 TRY_ATTACH(tryAttachRegExp(obj, objId, id));
453 TRY_ATTACH(tryAttachMap(obj, objId, id));
454 TRY_ATTACH(tryAttachSet(obj, objId, id));
455 TRY_ATTACH(tryAttachNative(obj, objId, id, receiverId));
456 TRY_ATTACH(tryAttachModuleNamespace(obj, objId, id));
457 TRY_ATTACH(tryAttachWindowProxy(obj, objId, id));
458 TRY_ATTACH(tryAttachCrossCompartmentWrapper(obj, objId, id));
459 TRY_ATTACH(
460 tryAttachXrayCrossCompartmentWrapper(obj, objId, id, receiverId));
461 TRY_ATTACH(tryAttachFunction(obj, objId, id));
462 TRY_ATTACH(tryAttachArgumentsObjectIterator(obj, objId, id));
463 TRY_ATTACH(tryAttachArgumentsObjectCallee(obj, objId, id));
464 TRY_ATTACH(tryAttachProxy(obj, objId, id, receiverId));
466 if (!isSuper() && mode_ == ICState::Mode::Megamorphic &&
467 JSOp(*pc_) != JSOp::GetBoundName) {
468 attachMegamorphicNativeSlotPermissive(objId, id);
469 return AttachDecision::Attach;
472 trackAttached(IRGenerator::NotAttached);
473 return AttachDecision::NoAction;
476 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
477 cacheKind_ == CacheKind::GetElemSuper);
479 TRY_ATTACH(tryAttachProxyElement(obj, objId));
480 TRY_ATTACH(tryAttachTypedArrayElement(obj, objId));
482 uint32_t index;
483 Int32OperandId indexId;
484 if (maybeGuardInt32Index(idVal_, getElemKeyValueId(), &index, &indexId)) {
485 TRY_ATTACH(tryAttachDenseElement(obj, objId, index, indexId));
486 TRY_ATTACH(tryAttachDenseElementHole(obj, objId, index, indexId));
487 TRY_ATTACH(tryAttachSparseElement(obj, objId, index, indexId));
488 TRY_ATTACH(tryAttachArgumentsObjectArg(obj, objId, index, indexId));
489 TRY_ATTACH(tryAttachArgumentsObjectArgHole(obj, objId, index, indexId));
490 TRY_ATTACH(
491 tryAttachGenericElement(obj, objId, index, indexId, receiverId));
493 trackAttached(IRGenerator::NotAttached);
494 return AttachDecision::NoAction;
497 trackAttached(IRGenerator::NotAttached);
498 return AttachDecision::NoAction;
501 if (nameOrSymbol) {
502 TRY_ATTACH(tryAttachPrimitive(valId, id));
503 TRY_ATTACH(tryAttachStringLength(valId, id));
505 trackAttached(IRGenerator::NotAttached);
506 return AttachDecision::NoAction;
509 if (idVal_.isInt32()) {
510 ValOperandId indexId = getElemKeyValueId();
511 TRY_ATTACH(tryAttachStringChar(valId, indexId));
513 trackAttached(IRGenerator::NotAttached);
514 return AttachDecision::NoAction;
517 trackAttached(IRGenerator::NotAttached);
518 return AttachDecision::NoAction;
521 #ifdef DEBUG
522 // Any property lookups performed when trying to attach ICs must be pure, i.e.
523 // must use LookupPropertyPure() or similar functions. Pure lookups are
524 // guaranteed to never modify the prototype chain. This ensures that the holder
525 // object can always be found on the prototype chain.
526 static bool IsCacheableProtoChain(NativeObject* obj, NativeObject* holder) {
527 while (obj != holder) {
528 JSObject* proto = obj->staticPrototype();
529 if (!proto || !proto->is<NativeObject>()) {
530 return false;
532 obj = &proto->as<NativeObject>();
534 return true;
536 #endif
538 static bool IsCacheableGetPropSlot(NativeObject* obj, NativeObject* holder,
539 PropertyInfo prop) {
540 MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
542 return prop.isDataProperty();
545 enum class NativeGetPropKind {
546 None,
547 Missing,
548 Slot,
549 NativeGetter,
550 ScriptedGetter,
553 static NativeGetPropKind IsCacheableGetPropCall(NativeObject* obj,
554 NativeObject* holder,
555 PropertyInfo prop,
556 jsbytecode* pc = nullptr) {
557 MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
559 if (pc && JSOp(*pc) == JSOp::GetBoundName) {
560 return NativeGetPropKind::None;
563 if (!prop.isAccessorProperty()) {
564 return NativeGetPropKind::None;
567 JSObject* getterObject = holder->getGetter(prop);
568 if (!getterObject || !getterObject->is<JSFunction>()) {
569 return NativeGetPropKind::None;
572 JSFunction& getter = getterObject->as<JSFunction>();
574 if (getter.isClassConstructor()) {
575 return NativeGetPropKind::None;
578 // Scripted functions and natives with JIT entry can use the scripted path.
579 if (getter.hasJitEntry()) {
580 return NativeGetPropKind::ScriptedGetter;
583 MOZ_ASSERT(getter.isNativeWithoutJitEntry());
584 return NativeGetPropKind::NativeGetter;
587 static bool CheckHasNoSuchOwnProperty(JSContext* cx, JSObject* obj, jsid id) {
588 if (!obj->is<NativeObject>()) {
589 return false;
591 // Don't handle objects with resolve hooks.
592 if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj)) {
593 return false;
595 if (obj->as<NativeObject>().contains(cx, id)) {
596 return false;
598 return true;
601 static bool CheckHasNoSuchProperty(JSContext* cx, JSObject* obj, jsid id) {
602 JSObject* curObj = obj;
603 do {
604 if (!CheckHasNoSuchOwnProperty(cx, curObj, id)) {
605 return false;
608 curObj = curObj->staticPrototype();
609 } while (curObj);
611 return true;
614 static bool IsCacheableNoProperty(JSContext* cx, NativeObject* obj,
615 NativeObject* holder, jsid id,
616 jsbytecode* pc) {
617 MOZ_ASSERT(!holder);
619 // If we're doing a name lookup, we have to throw a ReferenceError.
620 if (JSOp(*pc) == JSOp::GetBoundName) {
621 return false;
624 return CheckHasNoSuchProperty(cx, obj, id);
627 static NativeGetPropKind CanAttachNativeGetProp(JSContext* cx, JSObject* obj,
628 PropertyKey id,
629 NativeObject** holder,
630 Maybe<PropertyInfo>* propInfo,
631 jsbytecode* pc) {
632 MOZ_ASSERT(id.isString() || id.isSymbol());
633 MOZ_ASSERT(!*holder);
635 // The lookup needs to be universally pure, otherwise we risk calling hooks
636 // out of turn. We don't mind doing this even when purity isn't required,
637 // because we only miss out on shape hashification, which is only a temporary
638 // perf cost. The limits were arbitrarily set, anyways.
639 NativeObject* baseHolder = nullptr;
640 PropertyResult prop;
641 if (!LookupPropertyPure(cx, obj, id, &baseHolder, &prop)) {
642 return NativeGetPropKind::None;
644 auto* nobj = &obj->as<NativeObject>();
646 if (prop.isNativeProperty()) {
647 MOZ_ASSERT(baseHolder);
648 *holder = baseHolder;
649 *propInfo = mozilla::Some(prop.propertyInfo());
651 if (IsCacheableGetPropSlot(nobj, *holder, propInfo->ref())) {
652 return NativeGetPropKind::Slot;
655 return IsCacheableGetPropCall(nobj, *holder, propInfo->ref(), pc);
658 if (!prop.isFound()) {
659 if (IsCacheableNoProperty(cx, nobj, *holder, id, pc)) {
660 return NativeGetPropKind::Missing;
664 return NativeGetPropKind::None;
667 static void GuardReceiverProto(CacheIRWriter& writer, NativeObject* obj,
668 ObjOperandId objId) {
669 // Note: we guard on the actual prototype and not on the shape because this is
670 // used for sparse elements where we expect shape changes.
672 if (JSObject* proto = obj->staticPrototype()) {
673 writer.guardProto(objId, proto);
674 } else {
675 writer.guardNullProto(objId);
679 // Guard that a given object has same class and same OwnProperties (excluding
680 // dense elements and dynamic properties).
681 static void TestMatchingNativeReceiver(CacheIRWriter& writer, NativeObject* obj,
682 ObjOperandId objId) {
683 writer.guardShapeForOwnProperties(objId, obj->shape());
686 // Similar to |TestMatchingNativeReceiver|, but specialized for ProxyObject.
687 static void TestMatchingProxyReceiver(CacheIRWriter& writer, ProxyObject* obj,
688 ObjOperandId objId) {
689 writer.guardShapeForClass(objId, obj->shape());
692 static void GeneratePrototypeGuards(CacheIRWriter& writer, JSObject* obj,
693 NativeObject* holder, ObjOperandId objId) {
694 // Assuming target property is on |holder|, generate appropriate guards to
695 // ensure |holder| is still on the prototype chain of |obj| and we haven't
696 // introduced any shadowing definitions.
698 // For each item in the proto chain before holder, we must ensure that
699 // [[GetPrototypeOf]] still has the expected result, and that
700 // [[GetOwnProperty]] has no definition of the target property.
703 // [SMDOC] Shape Teleporting Optimization
704 // --------------------------------------
706 // Starting with the assumption (and guideline to developers) that mutating
707 // prototypes is an uncommon and fair-to-penalize operation we move cost
708 // from the access side to the mutation side.
710 // Consider the following proto chain, with B defining a property 'x':
712 // D -> C -> B{x: 3} -> A -> null
714 // When accessing |D.x| we refer to D as the "receiver", and B as the
715 // "holder". To optimize this access we need to ensure that neither D nor C
716 // has since defined a shadowing property 'x'. Since C is a prototype that
717 // we assume is rarely mutated we would like to avoid checking each time if
718 // new properties are added. To do this we require that whenever C starts
719 // shadowing a property on its proto chain, we invalidate (and opt out of) the
720 // teleporting optimization by setting the InvalidatedTeleporting flag on the
721 // object we're shadowing, triggering a shape change of that object. As a
722 // result, checking the shape of D and B is sufficient. Note that we do not
723 // care if the shape or properties of A change since the lookup of 'x' will
724 // stop at B.
726 // The second condition we must verify is that the prototype chain was not
727 // mutated. The same mechanism as above is used. When the prototype link is
728 // changed, we generate a new shape for the object. If the object whose
729 // link we are mutating is itself a prototype, we regenerate shapes down
730 // the chain by setting the InvalidatedTeleporting flag on them. This means
731 // the same two shape checks as above are sufficient.
733 // Once the InvalidatedTeleporting flag is set, it means the shape will no
734 // longer be changed by ReshapeForProtoMutation and ReshapeForShadowedProp.
735 // In this case we can no longer apply the optimization.
737 // See:
738 // - ReshapeForProtoMutation
739 // - ReshapeForShadowedProp
741 MOZ_ASSERT(holder);
742 MOZ_ASSERT(obj != holder);
744 // Receiver guards (see TestMatchingReceiver) ensure the receiver's proto is
745 // unchanged so peel off the receiver.
746 JSObject* pobj = obj->staticPrototype();
747 MOZ_ASSERT(pobj->isUsedAsPrototype());
749 // If teleporting is supported for this holder, we are done.
750 if (!holder->hasInvalidatedTeleporting()) {
751 return;
754 // If already at the holder, no further proto checks are needed.
755 if (pobj == holder) {
756 return;
759 // Synchronize pobj and protoId.
760 MOZ_ASSERT(pobj == obj->staticPrototype());
761 ObjOperandId protoId = writer.loadProto(objId);
763 // Shape guard each prototype object between receiver and holder. This guards
764 // against both proto changes and shadowing properties.
765 while (pobj != holder) {
766 writer.guardShape(protoId, pobj->shape());
768 pobj = pobj->staticPrototype();
769 protoId = writer.loadProto(protoId);
773 static void GeneratePrototypeHoleGuards(CacheIRWriter& writer,
774 NativeObject* obj, ObjOperandId objId,
775 bool alwaysGuardFirstProto) {
776 if (alwaysGuardFirstProto) {
777 GuardReceiverProto(writer, obj, objId);
780 JSObject* pobj = obj->staticPrototype();
781 while (pobj) {
782 ObjOperandId protoId = writer.loadObject(pobj);
784 // Make sure the shape matches, to ensure the proto is unchanged and to
785 // avoid non-dense elements or anything else that is being checked by
786 // CanAttachDenseElementHole.
787 MOZ_ASSERT(pobj->is<NativeObject>());
788 writer.guardShape(protoId, pobj->shape());
790 // Also make sure there are no dense elements.
791 writer.guardNoDenseElements(protoId);
793 pobj = pobj->staticPrototype();
797 // Similar to |TestMatchingReceiver|, but for the holder object (when it
798 // differs from the receiver). The holder may also be the expando of the
799 // receiver if it exists.
800 static void TestMatchingHolder(CacheIRWriter& writer, NativeObject* obj,
801 ObjOperandId objId) {
802 // The GeneratePrototypeGuards + TestMatchingHolder checks only support
803 // prototype chains composed of NativeObject (excluding the receiver
804 // itself).
805 writer.guardShapeForOwnProperties(objId, obj->shape());
808 enum class IsCrossCompartment { No, Yes };
810 // Emit a shape guard for all objects on the proto chain. This does NOT include
811 // the receiver; callers must ensure the receiver's proto is the first proto by
812 // either emitting a shape guard or a prototype guard for |objId|.
814 // Note: this relies on shape implying proto.
815 template <IsCrossCompartment MaybeCrossCompartment = IsCrossCompartment::No>
816 static void ShapeGuardProtoChain(CacheIRWriter& writer, NativeObject* obj,
817 ObjOperandId objId) {
818 uint32_t depth = 0;
819 static const uint32_t MAX_CACHED_LOADS = 4;
820 ObjOperandId receiverObjId = objId;
822 while (true) {
823 JSObject* proto = obj->staticPrototype();
824 if (!proto) {
825 return;
828 obj = &proto->as<NativeObject>();
830 // After guarding the shape of an object, we can safely bake that
831 // object's proto into the stub data. Compared to LoadProto, this
832 // takes one load instead of three (object -> shape -> baseshape
833 // -> proto). We cap the depth to avoid bloating the size of the
834 // stub data. To avoid compartment mismatch, we skip this optimization
835 // in the cross-compartment case.
836 if (depth < MAX_CACHED_LOADS &&
837 MaybeCrossCompartment == IsCrossCompartment::No) {
838 objId = writer.loadProtoObject(obj, receiverObjId);
839 } else {
840 objId = writer.loadProto(objId);
842 depth++;
844 writer.guardShape(objId, obj->shape());
848 // For cross compartment guards we shape-guard the prototype chain to avoid
849 // referencing the holder object.
851 // This peels off the first layer because it's guarded against obj == holder.
853 // Returns the holder's OperandId.
854 static ObjOperandId ShapeGuardProtoChainForCrossCompartmentHolder(
855 CacheIRWriter& writer, NativeObject* obj, ObjOperandId objId,
856 NativeObject* holder) {
857 MOZ_ASSERT(obj != holder);
858 MOZ_ASSERT(holder);
859 while (true) {
860 MOZ_ASSERT(obj->staticPrototype());
861 obj = &obj->staticPrototype()->as<NativeObject>();
863 objId = writer.loadProto(objId);
864 if (obj == holder) {
865 TestMatchingHolder(writer, obj, objId);
866 return objId;
868 writer.guardShapeForOwnProperties(objId, obj->shape());
872 // Emit guards for reading a data property on |holder|. Returns the holder's
873 // OperandId.
874 template <IsCrossCompartment MaybeCrossCompartment = IsCrossCompartment::No>
875 static ObjOperandId EmitReadSlotGuard(CacheIRWriter& writer, NativeObject* obj,
876 NativeObject* holder,
877 ObjOperandId objId) {
878 MOZ_ASSERT(holder);
879 TestMatchingNativeReceiver(writer, obj, objId);
881 if (obj == holder) {
882 return objId;
885 if (MaybeCrossCompartment == IsCrossCompartment::Yes) {
886 // Guard proto chain integrity.
887 // We use a variant of guards that avoid baking in any cross-compartment
888 // object pointers.
889 return ShapeGuardProtoChainForCrossCompartmentHolder(writer, obj, objId,
890 holder);
893 // Guard proto chain integrity.
894 GeneratePrototypeGuards(writer, obj, holder, objId);
896 // Guard on the holder's shape.
897 ObjOperandId holderId = writer.loadObject(holder);
898 TestMatchingHolder(writer, holder, holderId);
899 return holderId;
902 template <IsCrossCompartment MaybeCrossCompartment = IsCrossCompartment::No>
903 static void EmitMissingPropGuard(CacheIRWriter& writer, NativeObject* obj,
904 ObjOperandId objId) {
905 TestMatchingNativeReceiver(writer, obj, objId);
907 // The property does not exist. Guard on everything in the prototype
908 // chain. This is guaranteed to see only Native objects because of
909 // CanAttachNativeGetProp().
910 ShapeGuardProtoChain<MaybeCrossCompartment>(writer, obj, objId);
913 template <IsCrossCompartment MaybeCrossCompartment = IsCrossCompartment::No>
914 static void EmitReadSlotResult(CacheIRWriter& writer, NativeObject* obj,
915 NativeObject* holder, PropertyInfo prop,
916 ObjOperandId objId) {
917 MOZ_ASSERT(holder);
919 ObjOperandId holderId =
920 EmitReadSlotGuard<MaybeCrossCompartment>(writer, obj, holder, objId);
922 MOZ_ASSERT(holderId.valid());
923 EmitLoadSlotResult(writer, holderId, holder, prop);
926 template <IsCrossCompartment MaybeCrossCompartment = IsCrossCompartment::No>
927 static void EmitMissingPropResult(CacheIRWriter& writer, NativeObject* obj,
928 ObjOperandId objId) {
929 EmitMissingPropGuard<MaybeCrossCompartment>(writer, obj, objId);
930 writer.loadUndefinedResult();
933 static void EmitCallGetterResultNoGuards(JSContext* cx, CacheIRWriter& writer,
934 NativeGetPropKind kind,
935 NativeObject* obj,
936 NativeObject* holder,
937 PropertyInfo prop,
938 ValOperandId receiverId) {
939 MOZ_ASSERT(IsCacheableGetPropCall(obj, holder, prop) == kind);
941 JSFunction* target = &holder->getGetter(prop)->as<JSFunction>();
942 bool sameRealm = cx->realm() == target->realm();
944 switch (kind) {
945 case NativeGetPropKind::NativeGetter: {
946 writer.callNativeGetterResult(receiverId, target, sameRealm);
947 writer.returnFromIC();
948 break;
950 case NativeGetPropKind::ScriptedGetter: {
951 writer.callScriptedGetterResult(receiverId, target, sameRealm);
952 writer.returnFromIC();
953 break;
955 default:
956 // CanAttachNativeGetProp guarantees that the getter is either a native or
957 // a scripted function.
958 MOZ_ASSERT_UNREACHABLE("Can't attach getter");
959 break;
963 // See the SMDOC comment in vm/GetterSetter.h for more info on Getter/Setter
964 // properties
965 static void EmitGuardGetterSetterSlot(CacheIRWriter& writer,
966 NativeObject* holder, PropertyInfo prop,
967 ObjOperandId holderId,
968 bool holderIsConstant = false) {
969 // If the holder is guaranteed to be the same object, and it never had a
970 // slot holding a GetterSetter mutated or deleted, its Shape will change when
971 // that does happen so we don't need to guard on the GetterSetter.
972 if (holderIsConstant && !holder->hadGetterSetterChange()) {
973 return;
976 size_t slot = prop.slot();
977 Value slotVal = holder->getSlot(slot);
978 MOZ_ASSERT(slotVal.isPrivateGCThing());
980 if (holder->isFixedSlot(slot)) {
981 size_t offset = NativeObject::getFixedSlotOffset(slot);
982 writer.guardFixedSlotValue(holderId, offset, slotVal);
983 } else {
984 size_t offset = holder->dynamicSlotIndex(slot) * sizeof(Value);
985 writer.guardDynamicSlotValue(holderId, offset, slotVal);
989 static void EmitCallGetterResultGuards(CacheIRWriter& writer, NativeObject* obj,
990 NativeObject* holder, HandleId id,
991 PropertyInfo prop, ObjOperandId objId,
992 ICState::Mode mode) {
993 // Use the megamorphic guard if we're in megamorphic mode, except if |obj|
994 // is a Window as GuardHasGetterSetter doesn't support this yet (Window may
995 // require outerizing).
997 MOZ_ASSERT(holder->containsPure(id, prop));
999 if (mode == ICState::Mode::Specialized || IsWindow(obj)) {
1000 TestMatchingNativeReceiver(writer, obj, objId);
1002 if (obj != holder) {
1003 GeneratePrototypeGuards(writer, obj, holder, objId);
1005 // Guard on the holder's shape.
1006 ObjOperandId holderId = writer.loadObject(holder);
1007 TestMatchingHolder(writer, holder, holderId);
1009 EmitGuardGetterSetterSlot(writer, holder, prop, holderId,
1010 /* holderIsConstant = */ true);
1011 } else {
1012 EmitGuardGetterSetterSlot(writer, holder, prop, objId);
1014 } else {
1015 GetterSetter* gs = holder->getGetterSetter(prop);
1016 writer.guardHasGetterSetter(objId, id, gs);
1020 static void EmitCallGetterResult(JSContext* cx, CacheIRWriter& writer,
1021 NativeGetPropKind kind, NativeObject* obj,
1022 NativeObject* holder, HandleId id,
1023 PropertyInfo prop, ObjOperandId objId,
1024 ValOperandId receiverId, ICState::Mode mode) {
1025 EmitCallGetterResultGuards(writer, obj, holder, id, prop, objId, mode);
1026 EmitCallGetterResultNoGuards(cx, writer, kind, obj, holder, prop, receiverId);
1029 static bool CanAttachDOMCall(JSContext* cx, JSJitInfo::OpType type,
1030 JSObject* obj, JSFunction* fun,
1031 ICState::Mode mode) {
1032 MOZ_ASSERT(type == JSJitInfo::Getter || type == JSJitInfo::Setter ||
1033 type == JSJitInfo::Method);
1035 if (mode != ICState::Mode::Specialized) {
1036 return false;
1039 if (!fun->hasJitInfo()) {
1040 return false;
1043 if (cx->realm() != fun->realm()) {
1044 return false;
1047 const JSJitInfo* jitInfo = fun->jitInfo();
1048 if (jitInfo->type() != type) {
1049 return false;
1052 MOZ_ASSERT_IF(IsWindow(obj), !jitInfo->needsOuterizedThisObject());
1054 const JSClass* clasp = obj->getClass();
1055 if (!clasp->isDOMClass()) {
1056 return false;
1059 if (type != JSJitInfo::Method && clasp->isProxyObject()) {
1060 return false;
1063 // Ion codegen expects DOM_OBJECT_SLOT to be a fixed slot in LoadDOMPrivate.
1064 // It can be a dynamic slot if we transplanted this reflector object with a
1065 // proxy.
1066 if (obj->is<NativeObject>() && obj->as<NativeObject>().numFixedSlots() == 0) {
1067 return false;
1070 // Tell the analysis the |DOMInstanceClassHasProtoAtDepth| hook can't GC.
1071 JS::AutoSuppressGCAnalysis nogc;
1073 DOMInstanceClassHasProtoAtDepth instanceChecker =
1074 cx->runtime()->DOMcallbacks->instanceClassMatchesProto;
1075 return instanceChecker(clasp, jitInfo->protoID, jitInfo->depth);
1078 static bool CanAttachDOMGetterSetter(JSContext* cx, JSJitInfo::OpType type,
1079 NativeObject* obj, NativeObject* holder,
1080 PropertyInfo prop, ICState::Mode mode) {
1081 MOZ_ASSERT(type == JSJitInfo::Getter || type == JSJitInfo::Setter);
1083 JSObject* accessor = type == JSJitInfo::Getter ? holder->getGetter(prop)
1084 : holder->getSetter(prop);
1085 JSFunction* fun = &accessor->as<JSFunction>();
1087 return CanAttachDOMCall(cx, type, obj, fun, mode);
1090 static void EmitCallDOMGetterResultNoGuards(CacheIRWriter& writer,
1091 NativeObject* holder,
1092 PropertyInfo prop,
1093 ObjOperandId objId) {
1094 JSFunction* getter = &holder->getGetter(prop)->as<JSFunction>();
1095 writer.callDOMGetterResult(objId, getter->jitInfo());
1096 writer.returnFromIC();
1099 static void EmitCallDOMGetterResult(JSContext* cx, CacheIRWriter& writer,
1100 NativeObject* obj, NativeObject* holder,
1101 HandleId id, PropertyInfo prop,
1102 ObjOperandId objId) {
1103 // Note: this relies on EmitCallGetterResultGuards emitting a shape guard
1104 // for specialized stubs.
1105 // The shape guard ensures the receiver's Class is valid for this DOM getter.
1106 EmitCallGetterResultGuards(writer, obj, holder, id, prop, objId,
1107 ICState::Mode::Specialized);
1108 EmitCallDOMGetterResultNoGuards(writer, holder, prop, objId);
1111 static ValOperandId EmitLoadSlot(CacheIRWriter& writer, NativeObject* holder,
1112 ObjOperandId holderId, uint32_t slot) {
1113 if (holder->isFixedSlot(slot)) {
1114 return writer.loadFixedSlot(holderId,
1115 NativeObject::getFixedSlotOffset(slot));
1117 size_t dynamicSlotIndex = holder->dynamicSlotIndex(slot);
1118 return writer.loadDynamicSlot(holderId, dynamicSlotIndex);
1121 void GetPropIRGenerator::attachMegamorphicNativeSlot(ObjOperandId objId,
1122 jsid id) {
1123 MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
1125 // We don't support GetBoundName because environment objects have
1126 // lookupProperty hooks and GetBoundName is usually not megamorphic.
1127 MOZ_ASSERT(JSOp(*pc_) != JSOp::GetBoundName);
1129 if (cacheKind_ == CacheKind::GetProp ||
1130 cacheKind_ == CacheKind::GetPropSuper) {
1131 writer.megamorphicLoadSlotResult(objId, id);
1132 } else {
1133 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
1134 cacheKind_ == CacheKind::GetElemSuper);
1135 writer.megamorphicLoadSlotByValueResult(objId, getElemKeyValueId());
1137 writer.returnFromIC();
1139 trackAttached("GetProp.MegamorphicNativeSlot");
1142 void GetPropIRGenerator::attachMegamorphicNativeSlotPermissive(
1143 ObjOperandId objId, jsid id) {
1144 MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
1146 // We don't support GetBoundName because environment objects have
1147 // lookupProperty hooks and GetBoundName is usually not megamorphic.
1148 MOZ_ASSERT(JSOp(*pc_) != JSOp::GetBoundName);
1149 // It is not worth the complexity to support super here because we'd have
1150 // to plumb the receiver through everywhere, so we just skip it.
1151 MOZ_ASSERT(!isSuper());
1153 if (cacheKind_ == CacheKind::GetProp) {
1154 writer.megamorphicLoadSlotPermissiveResult(objId, id);
1155 } else {
1156 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem);
1157 writer.megamorphicLoadSlotByValuePermissiveResult(objId,
1158 getElemKeyValueId());
1160 writer.returnFromIC();
1162 trackAttached("GetProp.MegamorphicNativeSlotPermissive");
1165 AttachDecision GetPropIRGenerator::tryAttachNative(HandleObject obj,
1166 ObjOperandId objId,
1167 HandleId id,
1168 ValOperandId receiverId) {
1169 Maybe<PropertyInfo> prop;
1170 NativeObject* holder = nullptr;
1172 NativeGetPropKind kind =
1173 CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
1174 switch (kind) {
1175 case NativeGetPropKind::None:
1176 return AttachDecision::NoAction;
1177 case NativeGetPropKind::Missing:
1178 case NativeGetPropKind::Slot: {
1179 auto* nobj = &obj->as<NativeObject>();
1181 if (mode_ == ICState::Mode::Megamorphic &&
1182 JSOp(*pc_) != JSOp::GetBoundName) {
1183 attachMegamorphicNativeSlot(objId, id);
1184 return AttachDecision::Attach;
1187 maybeEmitIdGuard(id);
1188 if (kind == NativeGetPropKind::Slot) {
1189 EmitReadSlotResult(writer, nobj, holder, *prop, objId);
1190 writer.returnFromIC();
1191 trackAttached("GetProp.NativeSlot");
1192 } else {
1193 EmitMissingPropResult(writer, nobj, objId);
1194 writer.returnFromIC();
1195 trackAttached("GetProp.Missing");
1197 return AttachDecision::Attach;
1199 case NativeGetPropKind::ScriptedGetter:
1200 case NativeGetPropKind::NativeGetter: {
1201 auto* nobj = &obj->as<NativeObject>();
1202 MOZ_ASSERT(!IsWindow(nobj));
1204 // If we're in megamorphic mode, we assume that a specialized
1205 // getter call is just going to end up failing later, so we let this
1206 // get handled further down the chain by
1207 // attachMegamorphicNativeSlotPermissive
1208 if (!isSuper() && mode_ == ICState::Mode::Megamorphic) {
1209 return AttachDecision::NoAction;
1212 maybeEmitIdGuard(id);
1214 if (!isSuper() && CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, nobj,
1215 holder, *prop, mode_)) {
1216 EmitCallDOMGetterResult(cx_, writer, nobj, holder, id, *prop, objId);
1218 trackAttached("GetProp.DOMGetter");
1219 return AttachDecision::Attach;
1222 EmitCallGetterResult(cx_, writer, kind, nobj, holder, id, *prop, objId,
1223 receiverId, mode_);
1225 trackAttached("GetProp.NativeGetter");
1226 return AttachDecision::Attach;
1230 MOZ_CRASH("Bad NativeGetPropKind");
1233 // Returns whether obj is a WindowProxy wrapping the script's global.
1234 static bool IsWindowProxyForScriptGlobal(JSScript* script, JSObject* obj) {
1235 if (!IsWindowProxy(obj)) {
1236 return false;
1239 MOZ_ASSERT(obj->getClass() ==
1240 script->runtimeFromMainThread()->maybeWindowProxyClass());
1242 JSObject* window = ToWindowIfWindowProxy(obj);
1244 // Ion relies on the WindowProxy's group changing (and the group getting
1245 // marked as having unknown properties) on navigation. If we ever stop
1246 // transplanting same-compartment WindowProxies, this assert will fail and we
1247 // need to fix that code.
1248 MOZ_ASSERT(window == &obj->nonCCWGlobal());
1250 // This must be a WindowProxy for a global in this compartment. Else it would
1251 // be a cross-compartment wrapper and IsWindowProxy returns false for
1252 // those.
1253 MOZ_ASSERT(script->compartment() == obj->compartment());
1255 // Only optimize lookups on the WindowProxy for the current global. Other
1256 // WindowProxies in the compartment may require security checks (based on
1257 // mutable document.domain). See bug 1516775.
1258 return window == &script->global();
1261 // Guards objId is a WindowProxy for windowObj. Returns the window's operand id.
1262 static ObjOperandId GuardAndLoadWindowProxyWindow(CacheIRWriter& writer,
1263 ObjOperandId objId,
1264 GlobalObject* windowObj) {
1265 writer.guardClass(objId, GuardClassKind::WindowProxy);
1266 ObjOperandId windowObjId = writer.loadWrapperTarget(objId,
1267 /*fallible = */ false);
1268 writer.guardSpecificObject(windowObjId, windowObj);
1269 return windowObjId;
1272 // Whether a getter/setter on the global should have the WindowProxy as |this|
1273 // value instead of the Window (the global object). This always returns true for
1274 // scripted functions.
1275 static bool GetterNeedsWindowProxyThis(NativeObject* holder,
1276 PropertyInfo prop) {
1277 JSFunction* callee = &holder->getGetter(prop)->as<JSFunction>();
1278 return !callee->hasJitInfo() || callee->jitInfo()->needsOuterizedThisObject();
1280 static bool SetterNeedsWindowProxyThis(NativeObject* holder,
1281 PropertyInfo prop) {
1282 JSFunction* callee = &holder->getSetter(prop)->as<JSFunction>();
1283 return !callee->hasJitInfo() || callee->jitInfo()->needsOuterizedThisObject();
1286 AttachDecision GetPropIRGenerator::tryAttachWindowProxy(HandleObject obj,
1287 ObjOperandId objId,
1288 HandleId id) {
1289 // Attach a stub when the receiver is a WindowProxy and we can do the lookup
1290 // on the Window (the global object).
1292 if (!IsWindowProxyForScriptGlobal(script_, obj)) {
1293 return AttachDecision::NoAction;
1296 // If we're megamorphic prefer a generic proxy stub that handles a lot more
1297 // cases.
1298 if (mode_ == ICState::Mode::Megamorphic) {
1299 return AttachDecision::NoAction;
1302 // Now try to do the lookup on the Window (the current global).
1303 GlobalObject* windowObj = cx_->global();
1304 NativeObject* holder = nullptr;
1305 Maybe<PropertyInfo> prop;
1306 NativeGetPropKind kind =
1307 CanAttachNativeGetProp(cx_, windowObj, id, &holder, &prop, pc_);
1308 switch (kind) {
1309 case NativeGetPropKind::None:
1310 return AttachDecision::NoAction;
1312 case NativeGetPropKind::Slot: {
1313 maybeEmitIdGuard(id);
1314 ObjOperandId windowObjId =
1315 GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
1316 EmitReadSlotResult(writer, windowObj, holder, *prop, windowObjId);
1317 writer.returnFromIC();
1319 trackAttached("GetProp.WindowProxySlot");
1320 return AttachDecision::Attach;
1323 case NativeGetPropKind::Missing: {
1324 maybeEmitIdGuard(id);
1325 ObjOperandId windowObjId =
1326 GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
1327 EmitMissingPropResult(writer, windowObj, windowObjId);
1328 writer.returnFromIC();
1330 trackAttached("GetProp.WindowProxyMissing");
1331 return AttachDecision::Attach;
1334 case NativeGetPropKind::NativeGetter:
1335 case NativeGetPropKind::ScriptedGetter: {
1336 // If a |super| access, it is not worth the complexity to attach an IC.
1337 if (isSuper()) {
1338 return AttachDecision::NoAction;
1341 bool needsWindowProxy = GetterNeedsWindowProxyThis(holder, *prop);
1343 // Guard the incoming object is a WindowProxy and inline a getter call
1344 // based on the Window object.
1345 maybeEmitIdGuard(id);
1346 ObjOperandId windowObjId =
1347 GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
1349 if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, windowObj, holder,
1350 *prop, mode_)) {
1351 MOZ_ASSERT(!needsWindowProxy);
1352 EmitCallDOMGetterResult(cx_, writer, windowObj, holder, id, *prop,
1353 windowObjId);
1354 trackAttached("GetProp.WindowProxyDOMGetter");
1355 } else {
1356 ValOperandId receiverId =
1357 writer.boxObject(needsWindowProxy ? objId : windowObjId);
1358 EmitCallGetterResult(cx_, writer, kind, windowObj, holder, id, *prop,
1359 windowObjId, receiverId, mode_);
1360 trackAttached("GetProp.WindowProxyGetter");
1363 return AttachDecision::Attach;
1367 MOZ_CRASH("Unreachable");
1370 AttachDecision GetPropIRGenerator::tryAttachCrossCompartmentWrapper(
1371 HandleObject obj, ObjOperandId objId, HandleId id) {
1372 // We can only optimize this very wrapper-handler, because others might
1373 // have a security policy.
1374 if (!IsWrapper(obj) ||
1375 Wrapper::wrapperHandler(obj) != &CrossCompartmentWrapper::singleton) {
1376 return AttachDecision::NoAction;
1379 // If we're megamorphic prefer a generic proxy stub that handles a lot more
1380 // cases.
1381 if (mode_ == ICState::Mode::Megamorphic) {
1382 return AttachDecision::NoAction;
1385 RootedObject unwrapped(cx_, Wrapper::wrappedObject(obj));
1386 MOZ_ASSERT(unwrapped == UnwrapOneCheckedStatic(obj));
1387 MOZ_ASSERT(!IsCrossCompartmentWrapper(unwrapped),
1388 "CCWs must not wrap other CCWs");
1390 // If we allowed different zones we would have to wrap strings.
1391 if (unwrapped->compartment()->zone() != cx_->compartment()->zone()) {
1392 return AttachDecision::NoAction;
1395 // Take the unwrapped object's global, and wrap in a
1396 // this-compartment wrapper. This is what will be stored in the IC
1397 // keep the compartment alive.
1398 RootedObject wrappedTargetGlobal(cx_, &unwrapped->nonCCWGlobal());
1399 if (!cx_->compartment()->wrap(cx_, &wrappedTargetGlobal)) {
1400 cx_->clearPendingException();
1401 return AttachDecision::NoAction;
1404 NativeObject* holder = nullptr;
1405 Maybe<PropertyInfo> prop;
1407 // Enter realm of target to prevent failing compartment assertions when doing
1408 // the lookup.
1410 AutoRealm ar(cx_, unwrapped);
1412 NativeGetPropKind kind =
1413 CanAttachNativeGetProp(cx_, unwrapped, id, &holder, &prop, pc_);
1414 if (kind != NativeGetPropKind::Slot && kind != NativeGetPropKind::Missing) {
1415 return AttachDecision::NoAction;
1418 auto* unwrappedNative = &unwrapped->as<NativeObject>();
1420 maybeEmitIdGuard(id);
1421 writer.guardIsProxy(objId);
1422 writer.guardHasProxyHandler(objId, Wrapper::wrapperHandler(obj));
1424 // Load the object wrapped by the CCW
1425 ObjOperandId wrapperTargetId =
1426 writer.loadWrapperTarget(objId, /*fallible = */ false);
1428 // If the compartment of the wrapped object is different we should fail.
1429 writer.guardCompartment(wrapperTargetId, wrappedTargetGlobal,
1430 unwrappedNative->compartment());
1432 ObjOperandId unwrappedId = wrapperTargetId;
1433 if (holder) {
1434 EmitReadSlotResult<IsCrossCompartment::Yes>(writer, unwrappedNative, holder,
1435 *prop, unwrappedId);
1436 writer.wrapResult();
1437 writer.returnFromIC();
1438 trackAttached("GetProp.CCWSlot");
1439 } else {
1440 EmitMissingPropResult<IsCrossCompartment::Yes>(writer, unwrappedNative,
1441 unwrappedId);
1442 writer.returnFromIC();
1443 trackAttached("GetProp.CCWMissing");
1445 return AttachDecision::Attach;
1448 static JSObject* NewWrapperWithObjectShape(JSContext* cx,
1449 Handle<NativeObject*> obj);
1451 static bool GetXrayExpandoShapeWrapper(JSContext* cx, HandleObject xray,
1452 MutableHandleObject wrapper) {
1453 Value v = GetProxyReservedSlot(xray, GetXrayJitInfo()->xrayHolderSlot);
1454 if (v.isObject()) {
1455 NativeObject* holder = &v.toObject().as<NativeObject>();
1456 v = holder->getFixedSlot(GetXrayJitInfo()->holderExpandoSlot);
1457 if (v.isObject()) {
1458 Rooted<NativeObject*> expando(
1459 cx, &UncheckedUnwrap(&v.toObject())->as<NativeObject>());
1460 wrapper.set(NewWrapperWithObjectShape(cx, expando));
1461 return wrapper != nullptr;
1464 wrapper.set(nullptr);
1465 return true;
1468 AttachDecision GetPropIRGenerator::tryAttachXrayCrossCompartmentWrapper(
1469 HandleObject obj, ObjOperandId objId, HandleId id,
1470 ValOperandId receiverId) {
1471 if (!obj->is<ProxyObject>()) {
1472 return AttachDecision::NoAction;
1475 JS::XrayJitInfo* info = GetXrayJitInfo();
1476 if (!info || !info->isCrossCompartmentXray(GetProxyHandler(obj))) {
1477 return AttachDecision::NoAction;
1480 if (!info->compartmentHasExclusiveExpandos(obj)) {
1481 return AttachDecision::NoAction;
1484 RootedObject target(cx_, UncheckedUnwrap(obj));
1486 RootedObject expandoShapeWrapper(cx_);
1487 if (!GetXrayExpandoShapeWrapper(cx_, obj, &expandoShapeWrapper)) {
1488 cx_->recoverFromOutOfMemory();
1489 return AttachDecision::NoAction;
1492 // Look for a getter we can call on the xray or its prototype chain.
1493 Rooted<Maybe<PropertyDescriptor>> desc(cx_);
1494 RootedObject holder(cx_, obj);
1495 RootedObjectVector prototypes(cx_);
1496 RootedObjectVector prototypeExpandoShapeWrappers(cx_);
1497 while (true) {
1498 if (!GetOwnPropertyDescriptor(cx_, holder, id, &desc)) {
1499 cx_->clearPendingException();
1500 return AttachDecision::NoAction;
1502 if (desc.isSome()) {
1503 break;
1505 if (!GetPrototype(cx_, holder, &holder)) {
1506 cx_->clearPendingException();
1507 return AttachDecision::NoAction;
1509 if (!holder || !holder->is<ProxyObject>() ||
1510 !info->isCrossCompartmentXray(GetProxyHandler(holder))) {
1511 return AttachDecision::NoAction;
1513 RootedObject prototypeExpandoShapeWrapper(cx_);
1514 if (!GetXrayExpandoShapeWrapper(cx_, holder,
1515 &prototypeExpandoShapeWrapper) ||
1516 !prototypes.append(holder) ||
1517 !prototypeExpandoShapeWrappers.append(prototypeExpandoShapeWrapper)) {
1518 cx_->recoverFromOutOfMemory();
1519 return AttachDecision::NoAction;
1522 if (!desc->isAccessorDescriptor()) {
1523 return AttachDecision::NoAction;
1526 RootedObject getter(cx_, desc->getter());
1527 if (!getter || !getter->is<JSFunction>() ||
1528 !getter->as<JSFunction>().isNativeWithoutJitEntry()) {
1529 return AttachDecision::NoAction;
1532 maybeEmitIdGuard(id);
1533 writer.guardIsProxy(objId);
1534 writer.guardHasProxyHandler(objId, GetProxyHandler(obj));
1536 // Load the object wrapped by the CCW
1537 ObjOperandId wrapperTargetId =
1538 writer.loadWrapperTarget(objId, /*fallible = */ false);
1540 // Test the wrapped object's class. The properties held by xrays or their
1541 // prototypes will be invariant for objects of a given class, except for
1542 // changes due to xray expandos or xray prototype mutations.
1543 writer.guardAnyClass(wrapperTargetId, target->getClass());
1545 // Make sure the expandos on the xray and its prototype chain match up with
1546 // what we expect. The expando shape needs to be consistent, to ensure it
1547 // has not had any shadowing properties added, and the expando cannot have
1548 // any custom prototype (xray prototypes are stable otherwise).
1550 // We can only do this for xrays with exclusive access to their expandos
1551 // (as we checked earlier), which store a pointer to their expando
1552 // directly. Xrays in other compartments may share their expandos with each
1553 // other and a VM call is needed just to find the expando.
1554 if (expandoShapeWrapper) {
1555 writer.guardXrayExpandoShapeAndDefaultProto(objId, expandoShapeWrapper);
1556 } else {
1557 writer.guardXrayNoExpando(objId);
1559 for (size_t i = 0; i < prototypes.length(); i++) {
1560 JSObject* proto = prototypes[i];
1561 ObjOperandId protoId = writer.loadObject(proto);
1562 if (JSObject* protoShapeWrapper = prototypeExpandoShapeWrappers[i]) {
1563 writer.guardXrayExpandoShapeAndDefaultProto(protoId, protoShapeWrapper);
1564 } else {
1565 writer.guardXrayNoExpando(protoId);
1569 bool sameRealm = cx_->realm() == getter->as<JSFunction>().realm();
1570 writer.callNativeGetterResult(receiverId, &getter->as<JSFunction>(),
1571 sameRealm);
1572 writer.returnFromIC();
1574 trackAttached("GetProp.XrayCCW");
1575 return AttachDecision::Attach;
1578 #ifdef JS_PUNBOX64
1579 AttachDecision GetPropIRGenerator::tryAttachScriptedProxy(
1580 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id) {
1581 if (cacheKind_ != CacheKind::GetProp && cacheKind_ != CacheKind::GetElem) {
1582 return AttachDecision::NoAction;
1584 if (cacheKind_ == CacheKind::GetElem) {
1585 if (!idVal_.isString() && !idVal_.isInt32() && !idVal_.isSymbol()) {
1586 return AttachDecision::NoAction;
1590 JSObject* handlerObj = ScriptedProxyHandler::handlerObject(obj);
1591 if (!handlerObj) {
1592 return AttachDecision::NoAction;
1595 NativeObject* trapHolder = nullptr;
1596 Maybe<PropertyInfo> trapProp;
1597 // We call with pc_ even though that's not the actual corresponding pc. It
1598 // should, however, be fine, because it's just used to check if this is a
1599 // GetBoundName, which it's not.
1600 NativeGetPropKind trapKind = CanAttachNativeGetProp(
1601 cx_, handlerObj, NameToId(cx_->names().get), &trapHolder, &trapProp, pc_);
1603 if (trapKind != NativeGetPropKind::Missing &&
1604 trapKind != NativeGetPropKind::Slot) {
1605 return AttachDecision::NoAction;
1608 if (trapKind != NativeGetPropKind::Missing) {
1609 uint32_t trapSlot = trapProp->slot();
1610 const Value& trapVal = trapHolder->getSlot(trapSlot);
1611 if (!trapVal.isObject()) {
1612 return AttachDecision::NoAction;
1615 JSObject* trapObj = &trapVal.toObject();
1616 if (!trapObj->is<JSFunction>()) {
1617 return AttachDecision::NoAction;
1620 JSFunction* trapFn = &trapObj->as<JSFunction>();
1621 if (trapFn->isClassConstructor()) {
1622 return AttachDecision::NoAction;
1625 if (!trapFn->hasJitEntry()) {
1626 return AttachDecision::NoAction;
1629 if (cx_->realm() != trapFn->realm()) {
1630 return AttachDecision::NoAction;
1634 NativeObject* nHandlerObj = &handlerObj->as<NativeObject>();
1635 JSObject* targetObj = obj->target();
1636 MOZ_ASSERT(targetObj, "Guaranteed by the scripted Proxy constructor");
1638 // We just require that the target is a NativeObject to make our lives
1639 // easier. There's too much nonsense we might have to handle otherwise and
1640 // we're not set up to recursively call GetPropIRGenerator::tryAttachStub
1641 // for the target object.
1642 if (!targetObj->is<NativeObject>()) {
1643 return AttachDecision::NoAction;
1646 writer.guardIsProxy(objId);
1647 writer.guardHasProxyHandler(objId, &ScriptedProxyHandler::singleton);
1648 ObjOperandId handlerObjId = writer.loadScriptedProxyHandler(objId);
1649 ObjOperandId targetObjId =
1650 writer.loadWrapperTarget(objId, /*fallible =*/true);
1652 writer.guardIsNativeObject(targetObjId);
1654 if (trapKind == NativeGetPropKind::Missing) {
1655 EmitMissingPropGuard(writer, nHandlerObj, handlerObjId);
1656 if (cacheKind_ == CacheKind::GetProp) {
1657 writer.megamorphicLoadSlotResult(targetObjId, id);
1658 } else {
1659 writer.megamorphicLoadSlotByValueResult(objId, getElemKeyValueId());
1661 } else {
1662 uint32_t trapSlot = trapProp->slot();
1663 const Value& trapVal = trapHolder->getSlot(trapSlot);
1664 JSObject* trapObj = &trapVal.toObject();
1665 JSFunction* trapFn = &trapObj->as<JSFunction>();
1666 ObjOperandId trapHolderId =
1667 EmitReadSlotGuard(writer, nHandlerObj, trapHolder, handlerObjId);
1669 ValOperandId fnValId =
1670 EmitLoadSlot(writer, trapHolder, trapHolderId, trapSlot);
1671 ObjOperandId fnObjId = writer.guardToObject(fnValId);
1672 emitCalleeGuard(fnObjId, trapFn);
1673 ValOperandId targetValId = writer.boxObject(targetObjId);
1674 if (cacheKind_ == CacheKind::GetProp) {
1675 writer.callScriptedProxyGetResult(targetValId, objId, handlerObjId,
1676 fnObjId, trapFn, id);
1677 } else {
1678 ValOperandId idId = getElemKeyValueId();
1679 ValOperandId stringIdId = writer.idToStringOrSymbol(idId);
1680 writer.callScriptedProxyGetByValueResult(targetValId, objId, handlerObjId,
1681 stringIdId, fnObjId, trapFn);
1684 writer.returnFromIC();
1686 trackAttached("GetScriptedProxy");
1687 return AttachDecision::Attach;
1689 #endif
1691 AttachDecision GetPropIRGenerator::tryAttachGenericProxy(
1692 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
1693 bool handleDOMProxies) {
1694 writer.guardIsProxy(objId);
1696 if (!handleDOMProxies) {
1697 // Ensure that the incoming object is not a DOM proxy, so that we can get to
1698 // the specialized stubs
1699 writer.guardIsNotDOMProxy(objId);
1702 if (cacheKind_ == CacheKind::GetProp || mode_ == ICState::Mode::Specialized) {
1703 MOZ_ASSERT(!isSuper());
1704 maybeEmitIdGuard(id);
1705 writer.proxyGetResult(objId, id);
1706 } else {
1707 // Attach a stub that handles every id.
1708 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem);
1709 MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
1710 MOZ_ASSERT(!isSuper());
1711 writer.proxyGetByValueResult(objId, getElemKeyValueId());
1714 writer.returnFromIC();
1716 trackAttached("GetProp.GenericProxy");
1717 return AttachDecision::Attach;
1720 static bool ValueIsInt64Index(const Value& val, int64_t* index) {
1721 // Try to convert the Value to a TypedArray index or DataView offset.
1723 if (val.isInt32()) {
1724 *index = val.toInt32();
1725 return true;
1728 if (val.isDouble()) {
1729 // Use NumberEqualsInt64 because ToPropertyKey(-0) is 0.
1730 return mozilla::NumberEqualsInt64(val.toDouble(), index);
1733 return false;
1736 IntPtrOperandId IRGenerator::guardToIntPtrIndex(const Value& index,
1737 ValOperandId indexId,
1738 bool supportOOB) {
1739 #ifdef DEBUG
1740 int64_t indexInt64;
1741 MOZ_ASSERT_IF(!supportOOB, ValueIsInt64Index(index, &indexInt64));
1742 #endif
1744 if (index.isInt32()) {
1745 Int32OperandId int32IndexId = writer.guardToInt32(indexId);
1746 return writer.int32ToIntPtr(int32IndexId);
1749 MOZ_ASSERT(index.isNumber());
1750 NumberOperandId numberIndexId = writer.guardIsNumber(indexId);
1751 return writer.guardNumberToIntPtrIndex(numberIndexId, supportOOB);
1754 ObjOperandId IRGenerator::guardDOMProxyExpandoObjectAndShape(
1755 ProxyObject* obj, ObjOperandId objId, const Value& expandoVal,
1756 NativeObject* expandoObj) {
1757 MOZ_ASSERT(IsCacheableDOMProxy(obj));
1759 TestMatchingProxyReceiver(writer, obj, objId);
1761 // Shape determines Class, so now it must be a DOM proxy.
1762 ValOperandId expandoValId;
1763 if (expandoVal.isObject()) {
1764 expandoValId = writer.loadDOMExpandoValue(objId);
1765 } else {
1766 expandoValId = writer.loadDOMExpandoValueIgnoreGeneration(objId);
1769 // Guard the expando is an object and shape guard.
1770 ObjOperandId expandoObjId = writer.guardToObject(expandoValId);
1771 TestMatchingHolder(writer, expandoObj, expandoObjId);
1772 return expandoObjId;
1775 AttachDecision GetPropIRGenerator::tryAttachDOMProxyExpando(
1776 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
1777 ValOperandId receiverId) {
1778 MOZ_ASSERT(IsCacheableDOMProxy(obj));
1780 Value expandoVal = GetProxyPrivate(obj);
1781 JSObject* expandoObj;
1782 if (expandoVal.isObject()) {
1783 expandoObj = &expandoVal.toObject();
1784 } else {
1785 MOZ_ASSERT(!expandoVal.isUndefined(),
1786 "How did a missing expando manage to shadow things?");
1787 auto expandoAndGeneration =
1788 static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
1789 MOZ_ASSERT(expandoAndGeneration);
1790 expandoObj = &expandoAndGeneration->expando.toObject();
1793 // Try to do the lookup on the expando object.
1794 NativeObject* holder = nullptr;
1795 Maybe<PropertyInfo> prop;
1796 NativeGetPropKind kind =
1797 CanAttachNativeGetProp(cx_, expandoObj, id, &holder, &prop, pc_);
1798 if (kind == NativeGetPropKind::None) {
1799 return AttachDecision::NoAction;
1801 if (!holder) {
1802 return AttachDecision::NoAction;
1804 auto* nativeExpandoObj = &expandoObj->as<NativeObject>();
1806 MOZ_ASSERT(holder == nativeExpandoObj);
1808 maybeEmitIdGuard(id);
1809 ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape(
1810 obj, objId, expandoVal, nativeExpandoObj);
1812 if (kind == NativeGetPropKind::Slot) {
1813 // Load from the expando's slots.
1814 EmitLoadSlotResult(writer, expandoObjId, nativeExpandoObj, *prop);
1815 writer.returnFromIC();
1816 } else {
1817 // Call the getter. Note that we pass objId, the DOM proxy, as |this|
1818 // and not the expando object.
1819 MOZ_ASSERT(kind == NativeGetPropKind::NativeGetter ||
1820 kind == NativeGetPropKind::ScriptedGetter);
1821 EmitGuardGetterSetterSlot(writer, nativeExpandoObj, *prop, expandoObjId);
1822 EmitCallGetterResultNoGuards(cx_, writer, kind, nativeExpandoObj,
1823 nativeExpandoObj, *prop, receiverId);
1826 trackAttached("GetProp.DOMProxyExpando");
1827 return AttachDecision::Attach;
1830 AttachDecision GetPropIRGenerator::tryAttachDOMProxyShadowed(
1831 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id) {
1832 MOZ_ASSERT(!isSuper());
1833 MOZ_ASSERT(IsCacheableDOMProxy(obj));
1835 maybeEmitIdGuard(id);
1836 TestMatchingProxyReceiver(writer, obj, objId);
1837 writer.proxyGetResult(objId, id);
1838 writer.returnFromIC();
1840 trackAttached("GetProp.DOMProxyShadowed");
1841 return AttachDecision::Attach;
1844 // Emit CacheIR to guard the DOM proxy doesn't shadow |id|. There are two types
1845 // of DOM proxies:
1847 // (a) DOM proxies marked LegacyOverrideBuiltIns in WebIDL, for example
1848 // HTMLDocument or HTMLFormElement. These proxies look up properties in this
1849 // order:
1851 // (1) The expando object.
1852 // (2) The proxy's named-property handler.
1853 // (3) The prototype chain.
1855 // To optimize properties on the prototype chain, we have to guard that (1)
1856 // and (2) don't shadow (3). We handle (1) by either emitting a shape guard
1857 // for the expando object or by guarding the proxy has no expando object. To
1858 // efficiently handle (2), the proxy must have an ExpandoAndGeneration*
1859 // stored as PrivateValue. We guard on its generation field to ensure the
1860 // set of names hasn't changed.
1862 // Missing properties can be optimized in a similar way by emitting shape
1863 // guards for the prototype chain.
1865 // (b) Other DOM proxies. These proxies look up properties in this
1866 // order:
1868 // (1) The expando object.
1869 // (2) The prototype chain.
1870 // (3) The proxy's named-property handler.
1872 // To optimize properties on the prototype chain, we only have to guard the
1873 // expando object doesn't shadow it.
1875 // Missing properties can't be optimized in this case because we don't have
1876 // an efficient way to guard against the proxy handler shadowing the
1877 // property (there's no ExpandoAndGeneration*).
1879 // See also:
1880 // * DOMProxyShadows in DOMJSProxyHandler.cpp
1881 // * https://webidl.spec.whatwg.org/#dfn-named-property-visibility (the Note at
1882 // the end)
1884 // Callers are expected to have already guarded on the shape of the
1885 // object, which guarantees the object is a DOM proxy.
1886 static void CheckDOMProxyDoesNotShadow(CacheIRWriter& writer, ProxyObject* obj,
1887 jsid id, ObjOperandId objId,
1888 bool* canOptimizeMissing) {
1889 MOZ_ASSERT(IsCacheableDOMProxy(obj));
1891 Value expandoVal = GetProxyPrivate(obj);
1893 ValOperandId expandoId;
1894 if (!expandoVal.isObject() && !expandoVal.isUndefined()) {
1895 // Case (a).
1896 auto expandoAndGeneration =
1897 static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
1898 uint64_t generation = expandoAndGeneration->generation;
1899 expandoId = writer.loadDOMExpandoValueGuardGeneration(
1900 objId, expandoAndGeneration, generation);
1901 expandoVal = expandoAndGeneration->expando;
1902 *canOptimizeMissing = true;
1903 } else {
1904 // Case (b).
1905 expandoId = writer.loadDOMExpandoValue(objId);
1906 *canOptimizeMissing = false;
1909 if (expandoVal.isUndefined()) {
1910 // Guard there's no expando object.
1911 writer.guardNonDoubleType(expandoId, ValueType::Undefined);
1912 } else if (expandoVal.isObject()) {
1913 // Guard the proxy either has no expando object or, if it has one, that
1914 // the shape matches the current expando object.
1915 NativeObject& expandoObj = expandoVal.toObject().as<NativeObject>();
1916 MOZ_ASSERT(!expandoObj.containsPure(id));
1917 writer.guardDOMExpandoMissingOrGuardShape(expandoId, expandoObj.shape());
1918 } else {
1919 MOZ_CRASH("Invalid expando value");
1923 AttachDecision GetPropIRGenerator::tryAttachDOMProxyUnshadowed(
1924 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
1925 ValOperandId receiverId) {
1926 MOZ_ASSERT(IsCacheableDOMProxy(obj));
1928 JSObject* protoObj = obj->staticPrototype();
1929 if (!protoObj) {
1930 return AttachDecision::NoAction;
1933 NativeObject* holder = nullptr;
1934 Maybe<PropertyInfo> prop;
1935 NativeGetPropKind kind =
1936 CanAttachNativeGetProp(cx_, protoObj, id, &holder, &prop, pc_);
1937 if (kind == NativeGetPropKind::None) {
1938 return AttachDecision::NoAction;
1940 auto* nativeProtoObj = &protoObj->as<NativeObject>();
1942 maybeEmitIdGuard(id);
1944 // Guard that our proxy (expando) object hasn't started shadowing this
1945 // property.
1946 TestMatchingProxyReceiver(writer, obj, objId);
1947 bool canOptimizeMissing = false;
1948 CheckDOMProxyDoesNotShadow(writer, obj, id, objId, &canOptimizeMissing);
1950 if (holder) {
1951 // Found the property on the prototype chain. Treat it like a native
1952 // getprop.
1953 GeneratePrototypeGuards(writer, obj, holder, objId);
1955 // Guard on the holder of the property.
1956 ObjOperandId holderId = writer.loadObject(holder);
1957 TestMatchingHolder(writer, holder, holderId);
1959 if (kind == NativeGetPropKind::Slot) {
1960 EmitLoadSlotResult(writer, holderId, holder, *prop);
1961 writer.returnFromIC();
1962 } else {
1963 // EmitCallGetterResultNoGuards expects |obj| to be the object the
1964 // property is on to do some checks. Since we actually looked at
1965 // checkObj, and no extra guards will be generated, we can just
1966 // pass that instead.
1967 MOZ_ASSERT(kind == NativeGetPropKind::NativeGetter ||
1968 kind == NativeGetPropKind::ScriptedGetter);
1969 MOZ_ASSERT(!isSuper());
1970 EmitGuardGetterSetterSlot(writer, holder, *prop, holderId,
1971 /* holderIsConstant = */ true);
1972 EmitCallGetterResultNoGuards(cx_, writer, kind, nativeProtoObj, holder,
1973 *prop, receiverId);
1975 } else {
1976 // Property was not found on the prototype chain.
1977 MOZ_ASSERT(kind == NativeGetPropKind::Missing);
1978 if (canOptimizeMissing) {
1979 // We already guarded on the proxy's shape, so now shape guard the proto
1980 // chain.
1981 ObjOperandId protoId = writer.loadObject(nativeProtoObj);
1982 EmitMissingPropResult(writer, nativeProtoObj, protoId);
1983 } else {
1984 MOZ_ASSERT(!isSuper());
1985 writer.proxyGetResult(objId, id);
1987 writer.returnFromIC();
1990 trackAttached("GetProp.DOMProxyUnshadowed");
1991 return AttachDecision::Attach;
1994 AttachDecision GetPropIRGenerator::tryAttachProxy(HandleObject obj,
1995 ObjOperandId objId,
1996 HandleId id,
1997 ValOperandId receiverId) {
1998 // The proxy stubs don't currently support |super| access.
1999 if (isSuper()) {
2000 return AttachDecision::NoAction;
2003 // Always try to attach scripted proxy get even if we're megamorphic.
2004 // In Speedometer 3 we'll often run into cases where we're megamorphic
2005 // overall, but monomorphic for the proxy case. This is because there
2006 // are functions which lazily turn various differently-shaped objects
2007 // into proxies. So the un-proxified objects are megamorphic, but the
2008 // proxy handlers are actually monomorphic. There is room for a bit
2009 // more sophistication here, but this should do for now.
2010 if (!obj->is<ProxyObject>()) {
2011 return AttachDecision::NoAction;
2013 auto proxy = obj.as<ProxyObject>();
2014 #ifdef JS_PUNBOX64
2015 if (proxy->handler()->isScripted()) {
2016 TRY_ATTACH(tryAttachScriptedProxy(proxy, objId, id));
2018 #endif
2020 ProxyStubType type = GetProxyStubType(cx_, obj, id);
2021 if (type == ProxyStubType::None) {
2022 return AttachDecision::NoAction;
2025 if (mode_ == ICState::Mode::Megamorphic) {
2026 return tryAttachGenericProxy(proxy, objId, id,
2027 /* handleDOMProxies = */ true);
2030 switch (type) {
2031 case ProxyStubType::None:
2032 break;
2033 case ProxyStubType::DOMExpando:
2034 TRY_ATTACH(tryAttachDOMProxyExpando(proxy, objId, id, receiverId));
2035 [[fallthrough]]; // Fall through to the generic shadowed case.
2036 case ProxyStubType::DOMShadowed:
2037 return tryAttachDOMProxyShadowed(proxy, objId, id);
2038 case ProxyStubType::DOMUnshadowed:
2039 TRY_ATTACH(tryAttachDOMProxyUnshadowed(proxy, objId, id, receiverId));
2040 return tryAttachGenericProxy(proxy, objId, id,
2041 /* handleDOMProxies = */ true);
2042 case ProxyStubType::Generic:
2043 return tryAttachGenericProxy(proxy, objId, id,
2044 /* handleDOMProxies = */ false);
2047 MOZ_CRASH("Unexpected ProxyStubType");
2050 const JSClass* js::jit::ClassFor(GuardClassKind kind) {
2051 switch (kind) {
2052 case GuardClassKind::Array:
2053 return &ArrayObject::class_;
2054 case GuardClassKind::PlainObject:
2055 return &PlainObject::class_;
2056 case GuardClassKind::FixedLengthArrayBuffer:
2057 return &FixedLengthArrayBufferObject::class_;
2058 case GuardClassKind::ResizableArrayBuffer:
2059 return &ResizableArrayBufferObject::class_;
2060 case GuardClassKind::FixedLengthSharedArrayBuffer:
2061 return &FixedLengthSharedArrayBufferObject::class_;
2062 case GuardClassKind::GrowableSharedArrayBuffer:
2063 return &GrowableSharedArrayBufferObject::class_;
2064 case GuardClassKind::FixedLengthDataView:
2065 return &FixedLengthDataViewObject::class_;
2066 case GuardClassKind::ResizableDataView:
2067 return &ResizableDataViewObject::class_;
2068 case GuardClassKind::MappedArguments:
2069 return &MappedArgumentsObject::class_;
2070 case GuardClassKind::UnmappedArguments:
2071 return &UnmappedArgumentsObject::class_;
2072 case GuardClassKind::WindowProxy:
2073 // Caller needs to handle this case, see
2074 // JSRuntime::maybeWindowProxyClass().
2075 break;
2076 case GuardClassKind::JSFunction:
2077 // Caller needs to handle this case. Can be either |js::FunctionClass| or
2078 // |js::ExtendedFunctionClass|.
2079 break;
2080 case GuardClassKind::BoundFunction:
2081 return &BoundFunctionObject::class_;
2082 case GuardClassKind::Set:
2083 return &SetObject::class_;
2084 case GuardClassKind::Map:
2085 return &MapObject::class_;
2087 MOZ_CRASH("unexpected kind");
2090 // Guards the class of an object. Because shape implies class, and a shape guard
2091 // is faster than a class guard, if this is our first time attaching a stub, we
2092 // instead generate a shape guard.
2093 void IRGenerator::emitOptimisticClassGuard(ObjOperandId objId, JSObject* obj,
2094 GuardClassKind kind) {
2095 #ifdef DEBUG
2096 switch (kind) {
2097 case GuardClassKind::Array:
2098 case GuardClassKind::PlainObject:
2099 case GuardClassKind::FixedLengthArrayBuffer:
2100 case GuardClassKind::ResizableArrayBuffer:
2101 case GuardClassKind::FixedLengthSharedArrayBuffer:
2102 case GuardClassKind::GrowableSharedArrayBuffer:
2103 case GuardClassKind::FixedLengthDataView:
2104 case GuardClassKind::ResizableDataView:
2105 case GuardClassKind::Set:
2106 case GuardClassKind::Map:
2107 MOZ_ASSERT(obj->hasClass(ClassFor(kind)));
2108 break;
2110 case GuardClassKind::MappedArguments:
2111 case GuardClassKind::UnmappedArguments:
2112 case GuardClassKind::JSFunction:
2113 case GuardClassKind::BoundFunction:
2114 case GuardClassKind::WindowProxy:
2115 // Arguments, functions, and the global object have
2116 // less consistent shapes.
2117 MOZ_CRASH("GuardClassKind not supported");
2119 #endif
2121 if (isFirstStub_) {
2122 writer.guardShapeForClass(objId, obj->shape());
2123 } else {
2124 writer.guardClass(objId, kind);
2128 static void AssertArgumentsCustomDataProp(ArgumentsObject* obj,
2129 PropertyKey key) {
2130 #ifdef DEBUG
2131 // The property must still be a custom data property if it has been resolved.
2132 // If this assertion fails, we're probably missing a call to mark this
2133 // property overridden.
2134 Maybe<PropertyInfo> prop = obj->lookupPure(key);
2135 MOZ_ASSERT_IF(prop, prop->isCustomDataProperty());
2136 #endif
2139 AttachDecision GetPropIRGenerator::tryAttachObjectLength(HandleObject obj,
2140 ObjOperandId objId,
2141 HandleId id) {
2142 if (!id.isAtom(cx_->names().length)) {
2143 return AttachDecision::NoAction;
2146 if (obj->is<ArrayObject>()) {
2147 if (obj->as<ArrayObject>().length() > INT32_MAX) {
2148 return AttachDecision::NoAction;
2151 maybeEmitIdGuard(id);
2152 emitOptimisticClassGuard(objId, obj, GuardClassKind::Array);
2153 writer.loadInt32ArrayLengthResult(objId);
2154 writer.returnFromIC();
2156 trackAttached("GetProp.ArrayLength");
2157 return AttachDecision::Attach;
2160 if (obj->is<ArgumentsObject>() &&
2161 !obj->as<ArgumentsObject>().hasOverriddenLength()) {
2162 AssertArgumentsCustomDataProp(&obj->as<ArgumentsObject>(), id);
2163 maybeEmitIdGuard(id);
2164 if (obj->is<MappedArgumentsObject>()) {
2165 writer.guardClass(objId, GuardClassKind::MappedArguments);
2166 } else {
2167 MOZ_ASSERT(obj->is<UnmappedArgumentsObject>());
2168 writer.guardClass(objId, GuardClassKind::UnmappedArguments);
2170 writer.loadArgumentsObjectLengthResult(objId);
2171 writer.returnFromIC();
2173 trackAttached("GetProp.ArgumentsObjectLength");
2174 return AttachDecision::Attach;
2177 return AttachDecision::NoAction;
2180 AttachDecision GetPropIRGenerator::tryAttachTypedArray(HandleObject obj,
2181 ObjOperandId objId,
2182 HandleId id) {
2183 if (!obj->is<TypedArrayObject>()) {
2184 return AttachDecision::NoAction;
2187 if (mode_ != ICState::Mode::Specialized) {
2188 return AttachDecision::NoAction;
2191 // Receiver should be the object.
2192 if (isSuper()) {
2193 return AttachDecision::NoAction;
2196 bool isLength = id.isAtom(cx_->names().length);
2197 bool isByteOffset = id.isAtom(cx_->names().byteOffset);
2198 if (!isLength && !isByteOffset && !id.isAtom(cx_->names().byteLength)) {
2199 return AttachDecision::NoAction;
2202 NativeObject* holder = nullptr;
2203 Maybe<PropertyInfo> prop;
2204 NativeGetPropKind kind =
2205 CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
2206 if (kind != NativeGetPropKind::NativeGetter) {
2207 return AttachDecision::NoAction;
2210 JSFunction& fun = holder->getGetter(*prop)->as<JSFunction>();
2211 if (isLength) {
2212 if (!TypedArrayObject::isOriginalLengthGetter(fun.native())) {
2213 return AttachDecision::NoAction;
2215 } else if (isByteOffset) {
2216 if (!TypedArrayObject::isOriginalByteOffsetGetter(fun.native())) {
2217 return AttachDecision::NoAction;
2219 } else {
2220 if (!TypedArrayObject::isOriginalByteLengthGetter(fun.native())) {
2221 return AttachDecision::NoAction;
2225 auto* tarr = &obj->as<TypedArrayObject>();
2227 maybeEmitIdGuard(id);
2228 // Emit all the normal guards for calling this native, but specialize
2229 // callNativeGetterResult.
2230 EmitCallGetterResultGuards(writer, tarr, holder, id, *prop, objId, mode_);
2231 if (isLength) {
2232 size_t length = tarr->length().valueOr(0);
2233 if (tarr->is<FixedLengthTypedArrayObject>()) {
2234 if (length <= INT32_MAX) {
2235 writer.loadArrayBufferViewLengthInt32Result(objId);
2236 } else {
2237 writer.loadArrayBufferViewLengthDoubleResult(objId);
2239 } else {
2240 if (length <= INT32_MAX) {
2241 writer.resizableTypedArrayLengthInt32Result(objId);
2242 } else {
2243 writer.resizableTypedArrayLengthDoubleResult(objId);
2246 trackAttached("GetProp.TypedArrayLength");
2247 } else if (isByteOffset) {
2248 // byteOffset doesn't need to use different code paths for fixed-length and
2249 // resizable TypedArrays.
2250 size_t byteOffset = tarr->byteOffset().valueOr(0);
2251 if (byteOffset <= INT32_MAX) {
2252 writer.arrayBufferViewByteOffsetInt32Result(objId);
2253 } else {
2254 writer.arrayBufferViewByteOffsetDoubleResult(objId);
2256 trackAttached("GetProp.TypedArrayByteOffset");
2257 } else {
2258 size_t byteLength = tarr->byteLength().valueOr(0);
2259 if (tarr->is<FixedLengthTypedArrayObject>()) {
2260 if (byteLength <= INT32_MAX) {
2261 writer.typedArrayByteLengthInt32Result(objId);
2262 } else {
2263 writer.typedArrayByteLengthDoubleResult(objId);
2265 } else {
2266 if (byteLength <= INT32_MAX) {
2267 writer.resizableTypedArrayByteLengthInt32Result(objId);
2268 } else {
2269 writer.resizableTypedArrayByteLengthDoubleResult(objId);
2272 trackAttached("GetProp.TypedArrayByteLength");
2274 writer.returnFromIC();
2276 return AttachDecision::Attach;
2279 AttachDecision GetPropIRGenerator::tryAttachDataView(HandleObject obj,
2280 ObjOperandId objId,
2281 HandleId id) {
2282 if (!obj->is<DataViewObject>()) {
2283 return AttachDecision::NoAction;
2285 auto* dv = &obj->as<DataViewObject>();
2287 if (mode_ != ICState::Mode::Specialized) {
2288 return AttachDecision::NoAction;
2291 // Receiver should be the object.
2292 if (isSuper()) {
2293 return AttachDecision::NoAction;
2296 bool isByteOffset = id.isAtom(cx_->names().byteOffset);
2297 if (!isByteOffset && !id.isAtom(cx_->names().byteLength)) {
2298 return AttachDecision::NoAction;
2301 // byteOffset and byteLength both throw when the ArrayBuffer is detached.
2302 if (dv->hasDetachedBuffer()) {
2303 return AttachDecision::NoAction;
2306 // byteOffset and byteLength both throw when the ArrayBuffer is out-of-bounds.
2307 if (dv->is<ResizableDataViewObject>() &&
2308 dv->as<ResizableDataViewObject>().isOutOfBounds()) {
2309 return AttachDecision::NoAction;
2312 NativeObject* holder = nullptr;
2313 Maybe<PropertyInfo> prop;
2314 NativeGetPropKind kind =
2315 CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
2316 if (kind != NativeGetPropKind::NativeGetter) {
2317 return AttachDecision::NoAction;
2320 auto& fun = holder->getGetter(*prop)->as<JSFunction>();
2321 if (isByteOffset) {
2322 if (!DataViewObject::isOriginalByteOffsetGetter(fun.native())) {
2323 return AttachDecision::NoAction;
2325 } else {
2326 if (!DataViewObject::isOriginalByteLengthGetter(fun.native())) {
2327 return AttachDecision::NoAction;
2331 maybeEmitIdGuard(id);
2332 // Emit all the normal guards for calling this native, but specialize
2333 // callNativeGetterResult.
2334 EmitCallGetterResultGuards(writer, dv, holder, id, *prop, objId, mode_);
2335 writer.guardHasAttachedArrayBuffer(objId);
2336 if (dv->is<ResizableDataViewObject>()) {
2337 writer.guardResizableArrayBufferViewInBounds(objId);
2339 if (isByteOffset) {
2340 // byteOffset doesn't need to use different code paths for fixed-length and
2341 // resizable DataViews.
2342 size_t byteOffset = dv->byteOffset().valueOr(0);
2343 if (byteOffset <= INT32_MAX) {
2344 writer.arrayBufferViewByteOffsetInt32Result(objId);
2345 } else {
2346 writer.arrayBufferViewByteOffsetDoubleResult(objId);
2348 trackAttached("GetProp.DataViewByteOffset");
2349 } else {
2350 size_t byteLength = dv->byteLength().valueOr(0);
2351 if (dv->is<FixedLengthDataViewObject>()) {
2352 if (byteLength <= INT32_MAX) {
2353 writer.loadArrayBufferViewLengthInt32Result(objId);
2354 } else {
2355 writer.loadArrayBufferViewLengthDoubleResult(objId);
2357 } else {
2358 if (byteLength <= INT32_MAX) {
2359 writer.resizableDataViewByteLengthInt32Result(objId);
2360 } else {
2361 writer.resizableDataViewByteLengthDoubleResult(objId);
2364 trackAttached("GetProp.DataViewByteLength");
2366 writer.returnFromIC();
2368 return AttachDecision::Attach;
2371 AttachDecision GetPropIRGenerator::tryAttachArrayBufferMaybeShared(
2372 HandleObject obj, ObjOperandId objId, HandleId id) {
2373 if (!obj->is<ArrayBufferObjectMaybeShared>()) {
2374 return AttachDecision::NoAction;
2376 auto* buf = &obj->as<ArrayBufferObjectMaybeShared>();
2378 if (mode_ != ICState::Mode::Specialized) {
2379 return AttachDecision::NoAction;
2382 // Receiver should be the object.
2383 if (isSuper()) {
2384 return AttachDecision::NoAction;
2387 if (!id.isAtom(cx_->names().byteLength)) {
2388 return AttachDecision::NoAction;
2391 NativeObject* holder = nullptr;
2392 Maybe<PropertyInfo> prop;
2393 NativeGetPropKind kind =
2394 CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
2395 if (kind != NativeGetPropKind::NativeGetter) {
2396 return AttachDecision::NoAction;
2399 auto& fun = holder->getGetter(*prop)->as<JSFunction>();
2400 if (buf->is<ArrayBufferObject>()) {
2401 if (!ArrayBufferObject::isOriginalByteLengthGetter(fun.native())) {
2402 return AttachDecision::NoAction;
2404 } else {
2405 if (!SharedArrayBufferObject::isOriginalByteLengthGetter(fun.native())) {
2406 return AttachDecision::NoAction;
2410 maybeEmitIdGuard(id);
2411 // Emit all the normal guards for calling this native, but specialize
2412 // callNativeGetterResult.
2413 EmitCallGetterResultGuards(writer, buf, holder, id, *prop, objId, mode_);
2414 if (!buf->is<GrowableSharedArrayBufferObject>()) {
2415 if (buf->byteLength() <= INT32_MAX) {
2416 writer.loadArrayBufferByteLengthInt32Result(objId);
2417 } else {
2418 writer.loadArrayBufferByteLengthDoubleResult(objId);
2420 } else {
2421 if (buf->byteLength() <= INT32_MAX) {
2422 writer.growableSharedArrayBufferByteLengthInt32Result(objId);
2423 } else {
2424 writer.growableSharedArrayBufferByteLengthDoubleResult(objId);
2427 writer.returnFromIC();
2429 trackAttached("GetProp.ArrayBufferMaybeSharedByteLength");
2430 return AttachDecision::Attach;
2433 AttachDecision GetPropIRGenerator::tryAttachRegExp(HandleObject obj,
2434 ObjOperandId objId,
2435 HandleId id) {
2436 if (!obj->is<RegExpObject>()) {
2437 return AttachDecision::NoAction;
2439 auto* regExp = &obj->as<RegExpObject>();
2441 if (mode_ != ICState::Mode::Specialized) {
2442 return AttachDecision::NoAction;
2445 // Receiver should be the object.
2446 if (isSuper()) {
2447 return AttachDecision::NoAction;
2450 NativeObject* holder = nullptr;
2451 Maybe<PropertyInfo> prop;
2452 NativeGetPropKind kind =
2453 CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
2454 if (kind != NativeGetPropKind::NativeGetter) {
2455 return AttachDecision::NoAction;
2458 auto& fun = holder->getGetter(*prop)->as<JSFunction>();
2459 JS::RegExpFlags flags = JS::RegExpFlag::NoFlags;
2460 if (!RegExpObject::isOriginalFlagGetter(fun.native(), &flags)) {
2461 return AttachDecision::NoAction;
2464 maybeEmitIdGuard(id);
2465 // Emit all the normal guards for calling this native, but specialize
2466 // callNativeGetterResult.
2467 EmitCallGetterResultGuards(writer, regExp, holder, id, *prop, objId, mode_);
2469 writer.regExpFlagResult(objId, flags.value());
2470 writer.returnFromIC();
2472 trackAttached("GetProp.RegExpFlag");
2473 return AttachDecision::Attach;
2476 AttachDecision GetPropIRGenerator::tryAttachMap(HandleObject obj,
2477 ObjOperandId objId,
2478 HandleId id) {
2479 if (!obj->is<MapObject>()) {
2480 return AttachDecision::NoAction;
2482 auto* mapObj = &obj->as<MapObject>();
2484 if (mode_ != ICState::Mode::Specialized) {
2485 return AttachDecision::NoAction;
2488 // Receiver should be the object.
2489 if (isSuper()) {
2490 return AttachDecision::NoAction;
2493 if (!id.isAtom(cx_->names().size)) {
2494 return AttachDecision::NoAction;
2497 NativeObject* holder = nullptr;
2498 Maybe<PropertyInfo> prop;
2499 NativeGetPropKind kind =
2500 CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
2501 if (kind != NativeGetPropKind::NativeGetter) {
2502 return AttachDecision::NoAction;
2505 auto& fun = holder->getGetter(*prop)->as<JSFunction>();
2506 if (!MapObject::isOriginalSizeGetter(fun.native())) {
2507 return AttachDecision::NoAction;
2510 maybeEmitIdGuard(id);
2512 // Emit all the normal guards for calling this native, but specialize
2513 // callNativeGetterResult.
2514 EmitCallGetterResultGuards(writer, mapObj, holder, id, *prop, objId, mode_);
2516 writer.mapSizeResult(objId);
2517 writer.returnFromIC();
2519 trackAttached("GetProp.MapSize");
2520 return AttachDecision::Attach;
2523 AttachDecision GetPropIRGenerator::tryAttachSet(HandleObject obj,
2524 ObjOperandId objId,
2525 HandleId id) {
2526 if (!obj->is<SetObject>()) {
2527 return AttachDecision::NoAction;
2529 auto* setObj = &obj->as<SetObject>();
2531 if (mode_ != ICState::Mode::Specialized) {
2532 return AttachDecision::NoAction;
2535 // Receiver should be the object.
2536 if (isSuper()) {
2537 return AttachDecision::NoAction;
2540 if (!id.isAtom(cx_->names().size)) {
2541 return AttachDecision::NoAction;
2544 NativeObject* holder = nullptr;
2545 Maybe<PropertyInfo> prop;
2546 NativeGetPropKind kind =
2547 CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
2548 if (kind != NativeGetPropKind::NativeGetter) {
2549 return AttachDecision::NoAction;
2552 auto& fun = holder->getGetter(*prop)->as<JSFunction>();
2553 if (!SetObject::isOriginalSizeGetter(fun.native())) {
2554 return AttachDecision::NoAction;
2557 maybeEmitIdGuard(id);
2559 // Emit all the normal guards for calling this native, but specialize
2560 // callNativeGetterResult.
2561 EmitCallGetterResultGuards(writer, setObj, holder, id, *prop, objId, mode_);
2563 writer.setSizeResult(objId);
2564 writer.returnFromIC();
2566 trackAttached("GetProp.SetSize");
2567 return AttachDecision::Attach;
2570 AttachDecision GetPropIRGenerator::tryAttachFunction(HandleObject obj,
2571 ObjOperandId objId,
2572 HandleId id) {
2573 // Function properties are lazily resolved so they might not be defined yet.
2574 // And we might end up in a situation where we always have a fresh function
2575 // object during the IC generation.
2576 if (!obj->is<JSFunction>()) {
2577 return AttachDecision::NoAction;
2580 bool isLength = id.isAtom(cx_->names().length);
2581 if (!isLength && !id.isAtom(cx_->names().name)) {
2582 return AttachDecision::NoAction;
2585 NativeObject* holder = nullptr;
2586 PropertyResult prop;
2587 // If this property exists already, don't attach the stub.
2588 if (LookupPropertyPure(cx_, obj, id, &holder, &prop)) {
2589 return AttachDecision::NoAction;
2592 JSFunction* fun = &obj->as<JSFunction>();
2594 if (isLength) {
2595 // length was probably deleted from the function.
2596 if (fun->hasResolvedLength()) {
2597 return AttachDecision::NoAction;
2600 // Lazy functions don't store the length.
2601 if (!fun->hasBytecode()) {
2602 return AttachDecision::NoAction;
2604 } else {
2605 // name was probably deleted from the function.
2606 if (fun->hasResolvedName()) {
2607 return AttachDecision::NoAction;
2611 maybeEmitIdGuard(id);
2612 writer.guardClass(objId, GuardClassKind::JSFunction);
2613 if (isLength) {
2614 writer.loadFunctionLengthResult(objId);
2615 writer.returnFromIC();
2616 trackAttached("GetProp.FunctionLength");
2617 } else {
2618 writer.loadFunctionNameResult(objId);
2619 writer.returnFromIC();
2620 trackAttached("GetProp.FunctionName");
2622 return AttachDecision::Attach;
2625 AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectIterator(
2626 HandleObject obj, ObjOperandId objId, HandleId id) {
2627 if (!obj->is<ArgumentsObject>()) {
2628 return AttachDecision::NoAction;
2631 if (!id.isWellKnownSymbol(JS::SymbolCode::iterator)) {
2632 return AttachDecision::NoAction;
2635 Handle<ArgumentsObject*> args = obj.as<ArgumentsObject>();
2636 if (args->hasOverriddenIterator()) {
2637 return AttachDecision::NoAction;
2640 AssertArgumentsCustomDataProp(args, id);
2642 RootedValue iterator(cx_);
2643 if (!ArgumentsObject::getArgumentsIterator(cx_, &iterator)) {
2644 cx_->recoverFromOutOfMemory();
2645 return AttachDecision::NoAction;
2647 MOZ_ASSERT(iterator.isObject());
2649 maybeEmitIdGuard(id);
2650 if (args->is<MappedArgumentsObject>()) {
2651 writer.guardClass(objId, GuardClassKind::MappedArguments);
2652 } else {
2653 MOZ_ASSERT(args->is<UnmappedArgumentsObject>());
2654 writer.guardClass(objId, GuardClassKind::UnmappedArguments);
2656 uint32_t flags = ArgumentsObject::ITERATOR_OVERRIDDEN_BIT;
2657 writer.guardArgumentsObjectFlags(objId, flags);
2659 ObjOperandId iterId = writer.loadObject(&iterator.toObject());
2660 writer.loadObjectResult(iterId);
2661 writer.returnFromIC();
2663 trackAttached("GetProp.ArgumentsObjectIterator");
2664 return AttachDecision::Attach;
2667 AttachDecision GetPropIRGenerator::tryAttachModuleNamespace(HandleObject obj,
2668 ObjOperandId objId,
2669 HandleId id) {
2670 if (!obj->is<ModuleNamespaceObject>()) {
2671 return AttachDecision::NoAction;
2674 auto* ns = &obj->as<ModuleNamespaceObject>();
2675 ModuleEnvironmentObject* env = nullptr;
2676 Maybe<PropertyInfo> prop;
2677 if (!ns->bindings().lookup(id, &env, &prop)) {
2678 return AttachDecision::NoAction;
2681 // Don't emit a stub until the target binding has been initialized.
2682 if (env->getSlot(prop->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) {
2683 return AttachDecision::NoAction;
2686 // Check for the specific namespace object.
2687 maybeEmitIdGuard(id);
2688 writer.guardSpecificObject(objId, ns);
2690 ObjOperandId envId = writer.loadObject(env);
2691 EmitLoadSlotResult(writer, envId, env, *prop);
2692 writer.returnFromIC();
2694 trackAttached("GetProp.ModuleNamespace");
2695 return AttachDecision::Attach;
2698 AttachDecision GetPropIRGenerator::tryAttachPrimitive(ValOperandId valId,
2699 HandleId id) {
2700 MOZ_ASSERT(!isSuper(), "SuperBase is guaranteed to be an object");
2702 JSProtoKey protoKey;
2703 switch (val_.type()) {
2704 case ValueType::String:
2705 if (id.isAtom(cx_->names().length)) {
2706 // String length is special-cased, see js::GetProperty.
2707 return AttachDecision::NoAction;
2709 protoKey = JSProto_String;
2710 break;
2711 case ValueType::Int32:
2712 case ValueType::Double:
2713 protoKey = JSProto_Number;
2714 break;
2715 case ValueType::Boolean:
2716 protoKey = JSProto_Boolean;
2717 break;
2718 case ValueType::Symbol:
2719 protoKey = JSProto_Symbol;
2720 break;
2721 case ValueType::BigInt:
2722 protoKey = JSProto_BigInt;
2723 break;
2724 case ValueType::Null:
2725 case ValueType::Undefined:
2726 case ValueType::Magic:
2727 return AttachDecision::NoAction;
2728 #ifdef ENABLE_RECORD_TUPLE
2729 case ValueType::ExtendedPrimitive:
2730 #endif
2731 case ValueType::Object:
2732 case ValueType::PrivateGCThing:
2733 MOZ_CRASH("unexpected type");
2736 JSObject* proto = GlobalObject::getOrCreatePrototype(cx_, protoKey);
2737 if (!proto) {
2738 cx_->recoverFromOutOfMemory();
2739 return AttachDecision::NoAction;
2742 NativeObject* holder = nullptr;
2743 Maybe<PropertyInfo> prop;
2744 NativeGetPropKind kind =
2745 CanAttachNativeGetProp(cx_, proto, id, &holder, &prop, pc_);
2746 switch (kind) {
2747 case NativeGetPropKind::None:
2748 return AttachDecision::NoAction;
2749 case NativeGetPropKind::Missing:
2750 case NativeGetPropKind::Slot: {
2751 auto* nproto = &proto->as<NativeObject>();
2753 if (val_.isNumber()) {
2754 writer.guardIsNumber(valId);
2755 } else {
2756 writer.guardNonDoubleType(valId, val_.type());
2758 maybeEmitIdGuard(id);
2760 ObjOperandId protoId = writer.loadObject(nproto);
2761 if (kind == NativeGetPropKind::Slot) {
2762 EmitReadSlotResult(writer, nproto, holder, *prop, protoId);
2763 writer.returnFromIC();
2764 trackAttached("GetProp.PrimitiveSlot");
2765 } else {
2766 EmitMissingPropResult(writer, nproto, protoId);
2767 writer.returnFromIC();
2768 trackAttached("GetProp.PrimitiveMissing");
2770 return AttachDecision::Attach;
2772 case NativeGetPropKind::ScriptedGetter:
2773 case NativeGetPropKind::NativeGetter: {
2774 auto* nproto = &proto->as<NativeObject>();
2776 if (val_.isNumber()) {
2777 writer.guardIsNumber(valId);
2778 } else {
2779 writer.guardNonDoubleType(valId, val_.type());
2781 maybeEmitIdGuard(id);
2783 ObjOperandId protoId = writer.loadObject(nproto);
2784 EmitCallGetterResult(cx_, writer, kind, nproto, holder, id, *prop,
2785 protoId, valId, mode_);
2787 trackAttached("GetProp.PrimitiveGetter");
2788 return AttachDecision::Attach;
2792 MOZ_CRASH("Bad NativeGetPropKind");
2795 AttachDecision GetPropIRGenerator::tryAttachStringLength(ValOperandId valId,
2796 HandleId id) {
2797 if (!val_.isString() || !id.isAtom(cx_->names().length)) {
2798 return AttachDecision::NoAction;
2801 StringOperandId strId = writer.guardToString(valId);
2802 maybeEmitIdGuard(id);
2803 writer.loadStringLengthResult(strId);
2804 writer.returnFromIC();
2806 trackAttached("GetProp.StringLength");
2807 return AttachDecision::Attach;
2810 enum class AttachStringChar { No, Yes, Linearize, OutOfBounds };
2812 static AttachStringChar CanAttachStringChar(const Value& val,
2813 const Value& idVal,
2814 StringChar kind) {
2815 if (!val.isString() || !idVal.isInt32()) {
2816 return AttachStringChar::No;
2819 JSString* str = val.toString();
2820 int32_t index = idVal.toInt32();
2822 if (index < 0 && kind == StringChar::At) {
2823 static_assert(JSString::MAX_LENGTH <= INT32_MAX,
2824 "string length fits in int32");
2825 index += int32_t(str->length());
2828 if (index < 0 || size_t(index) >= str->length()) {
2829 return AttachStringChar::OutOfBounds;
2832 // This follows JSString::getChar and MacroAssembler::loadStringChar.
2833 if (str->isRope()) {
2834 JSRope* rope = &str->asRope();
2835 if (size_t(index) < rope->leftChild()->length()) {
2836 str = rope->leftChild();
2838 // MacroAssembler::loadStringChar doesn't support surrogate pairs which
2839 // are split between the left and right child of a rope.
2840 if (kind == StringChar::CodePointAt &&
2841 size_t(index) + 1 == str->length() && str->isLinear()) {
2842 // Linearize the string when the last character of the left child is a
2843 // a lead surrogate.
2844 char16_t ch = str->asLinear().latin1OrTwoByteChar(index);
2845 if (unicode::IsLeadSurrogate(ch)) {
2846 return AttachStringChar::Linearize;
2849 } else {
2850 str = rope->rightChild();
2854 if (!str->isLinear()) {
2855 return AttachStringChar::Linearize;
2858 return AttachStringChar::Yes;
2861 AttachDecision GetPropIRGenerator::tryAttachStringChar(ValOperandId valId,
2862 ValOperandId indexId) {
2863 MOZ_ASSERT(idVal_.isInt32());
2865 auto attach = CanAttachStringChar(val_, idVal_, StringChar::CharAt);
2866 if (attach == AttachStringChar::No) {
2867 return AttachDecision::NoAction;
2870 // Can't attach for out-of-bounds access without guarding that indexed
2871 // properties aren't present along the prototype chain of |String.prototype|.
2872 if (attach == AttachStringChar::OutOfBounds) {
2873 return AttachDecision::NoAction;
2876 StringOperandId strId = writer.guardToString(valId);
2877 Int32OperandId int32IndexId = writer.guardToInt32Index(indexId);
2878 if (attach == AttachStringChar::Linearize) {
2879 strId = writer.linearizeForCharAccess(strId, int32IndexId);
2881 writer.loadStringCharResult(strId, int32IndexId, /* handleOOB = */ false);
2882 writer.returnFromIC();
2884 trackAttached("GetProp.StringChar");
2885 return AttachDecision::Attach;
2888 static bool ClassCanHaveExtraProperties(const JSClass* clasp) {
2889 return clasp->getResolve() || clasp->getOpsLookupProperty() ||
2890 clasp->getOpsGetProperty() || IsTypedArrayClass(clasp);
2893 enum class OwnProperty : bool { No, Yes };
2894 enum class AllowIndexedReceiver : bool { No, Yes };
2895 enum class AllowExtraReceiverProperties : bool { No, Yes };
2897 static bool CanAttachDenseElementHole(
2898 NativeObject* obj, OwnProperty ownProp,
2899 AllowIndexedReceiver allowIndexedReceiver = AllowIndexedReceiver::No,
2900 AllowExtraReceiverProperties allowExtraReceiverProperties =
2901 AllowExtraReceiverProperties::No) {
2902 // Make sure the objects on the prototype don't have any indexed properties
2903 // or that such properties can't appear without a shape change.
2904 // Otherwise returning undefined for holes would obviously be incorrect,
2905 // because we would have to lookup a property on the prototype instead.
2906 do {
2907 // The first two checks are also relevant to the receiver object.
2908 if (allowIndexedReceiver == AllowIndexedReceiver::No && obj->isIndexed()) {
2909 return false;
2911 allowIndexedReceiver = AllowIndexedReceiver::No;
2913 if (allowExtraReceiverProperties == AllowExtraReceiverProperties::No &&
2914 ClassCanHaveExtraProperties(obj->getClass())) {
2915 return false;
2917 allowExtraReceiverProperties = AllowExtraReceiverProperties::No;
2919 // Don't need to check prototype for OwnProperty checks
2920 if (ownProp == OwnProperty::Yes) {
2921 return true;
2924 JSObject* proto = obj->staticPrototype();
2925 if (!proto) {
2926 break;
2929 if (!proto->is<NativeObject>()) {
2930 return false;
2933 // Make sure objects on the prototype don't have dense elements.
2934 if (proto->as<NativeObject>().getDenseInitializedLength() != 0) {
2935 return false;
2938 obj = &proto->as<NativeObject>();
2939 } while (true);
2941 return true;
2944 AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectArg(
2945 HandleObject obj, ObjOperandId objId, uint32_t index,
2946 Int32OperandId indexId) {
2947 if (!obj->is<ArgumentsObject>()) {
2948 return AttachDecision::NoAction;
2950 auto* args = &obj->as<ArgumentsObject>();
2952 // No elements must have been overridden or deleted.
2953 if (args->hasOverriddenElement()) {
2954 return AttachDecision::NoAction;
2957 // Check bounds.
2958 if (index >= args->initialLength()) {
2959 return AttachDecision::NoAction;
2962 AssertArgumentsCustomDataProp(args, PropertyKey::Int(index));
2964 // And finally also check that the argument isn't forwarded.
2965 if (args->argIsForwarded(index)) {
2966 return AttachDecision::NoAction;
2969 if (args->is<MappedArgumentsObject>()) {
2970 writer.guardClass(objId, GuardClassKind::MappedArguments);
2971 } else {
2972 MOZ_ASSERT(args->is<UnmappedArgumentsObject>());
2973 writer.guardClass(objId, GuardClassKind::UnmappedArguments);
2976 writer.loadArgumentsObjectArgResult(objId, indexId);
2977 writer.returnFromIC();
2979 trackAttached("GetProp.ArgumentsObjectArg");
2980 return AttachDecision::Attach;
2983 AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectArgHole(
2984 HandleObject obj, ObjOperandId objId, uint32_t index,
2985 Int32OperandId indexId) {
2986 if (!obj->is<ArgumentsObject>()) {
2987 return AttachDecision::NoAction;
2989 auto* args = &obj->as<ArgumentsObject>();
2991 // No elements must have been overridden or deleted.
2992 if (args->hasOverriddenElement()) {
2993 return AttachDecision::NoAction;
2996 // And also check that the argument isn't forwarded.
2997 if (index < args->initialLength() && args->argIsForwarded(index)) {
2998 return AttachDecision::NoAction;
3001 if (!CanAttachDenseElementHole(args, OwnProperty::No,
3002 AllowIndexedReceiver::Yes,
3003 AllowExtraReceiverProperties::Yes)) {
3004 return AttachDecision::NoAction;
3007 // We don't need to guard on the shape, because we check if any element is
3008 // overridden. Elements are marked as overridden iff any element is defined,
3009 // irrespective of whether the element is in-bounds or out-of-bounds. So when
3010 // that flag isn't set, we can guarantee that the arguments object doesn't
3011 // have any additional own elements.
3013 if (args->is<MappedArgumentsObject>()) {
3014 writer.guardClass(objId, GuardClassKind::MappedArguments);
3015 } else {
3016 MOZ_ASSERT(args->is<UnmappedArgumentsObject>());
3017 writer.guardClass(objId, GuardClassKind::UnmappedArguments);
3020 GeneratePrototypeHoleGuards(writer, args, objId,
3021 /* alwaysGuardFirstProto = */ true);
3023 writer.loadArgumentsObjectArgHoleResult(objId, indexId);
3024 writer.returnFromIC();
3026 trackAttached("GetProp.ArgumentsObjectArgHole");
3027 return AttachDecision::Attach;
3030 AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectCallee(
3031 HandleObject obj, ObjOperandId objId, HandleId id) {
3032 // Only mapped arguments objects have a `callee` property.
3033 if (!obj->is<MappedArgumentsObject>()) {
3034 return AttachDecision::NoAction;
3037 if (!id.isAtom(cx_->names().callee)) {
3038 return AttachDecision::NoAction;
3041 // The callee must not have been overridden or deleted.
3042 MappedArgumentsObject* args = &obj->as<MappedArgumentsObject>();
3043 if (args->hasOverriddenCallee()) {
3044 return AttachDecision::NoAction;
3047 AssertArgumentsCustomDataProp(args, id);
3049 maybeEmitIdGuard(id);
3050 writer.guardClass(objId, GuardClassKind::MappedArguments);
3052 uint32_t flags = ArgumentsObject::CALLEE_OVERRIDDEN_BIT;
3053 writer.guardArgumentsObjectFlags(objId, flags);
3055 writer.loadFixedSlotResult(objId,
3056 MappedArgumentsObject::getCalleeSlotOffset());
3057 writer.returnFromIC();
3059 trackAttached("GetProp.ArgumentsObjectCallee");
3060 return AttachDecision::Attach;
3063 AttachDecision GetPropIRGenerator::tryAttachDenseElement(
3064 HandleObject obj, ObjOperandId objId, uint32_t index,
3065 Int32OperandId indexId) {
3066 if (!obj->is<NativeObject>()) {
3067 return AttachDecision::NoAction;
3070 NativeObject* nobj = &obj->as<NativeObject>();
3071 if (!nobj->containsDenseElement(index)) {
3072 return AttachDecision::NoAction;
3075 if (mode_ == ICState::Mode::Megamorphic) {
3076 writer.guardIsNativeObject(objId);
3077 } else {
3078 TestMatchingNativeReceiver(writer, nobj, objId);
3080 writer.loadDenseElementResult(objId, indexId);
3081 writer.returnFromIC();
3083 trackAttached("GetProp.DenseElement");
3084 return AttachDecision::Attach;
3087 AttachDecision GetPropIRGenerator::tryAttachDenseElementHole(
3088 HandleObject obj, ObjOperandId objId, uint32_t index,
3089 Int32OperandId indexId) {
3090 if (!obj->is<NativeObject>()) {
3091 return AttachDecision::NoAction;
3094 NativeObject* nobj = &obj->as<NativeObject>();
3095 if (nobj->containsDenseElement(index)) {
3096 return AttachDecision::NoAction;
3098 if (!CanAttachDenseElementHole(nobj, OwnProperty::No)) {
3099 return AttachDecision::NoAction;
3102 // Guard on the shape, to prevent non-dense elements from appearing.
3103 TestMatchingNativeReceiver(writer, nobj, objId);
3104 GeneratePrototypeHoleGuards(writer, nobj, objId,
3105 /* alwaysGuardFirstProto = */ false);
3106 writer.loadDenseElementHoleResult(objId, indexId);
3107 writer.returnFromIC();
3109 trackAttached("GetProp.DenseElementHole");
3110 return AttachDecision::Attach;
3113 AttachDecision GetPropIRGenerator::tryAttachSparseElement(
3114 HandleObject obj, ObjOperandId objId, uint32_t index,
3115 Int32OperandId indexId) {
3116 if (!obj->is<NativeObject>()) {
3117 return AttachDecision::NoAction;
3119 NativeObject* nobj = &obj->as<NativeObject>();
3121 // Stub doesn't handle negative indices.
3122 if (index > INT32_MAX) {
3123 return AttachDecision::NoAction;
3126 // The object must have sparse elements.
3127 if (!nobj->isIndexed()) {
3128 return AttachDecision::NoAction;
3131 // The index must not be for a dense element.
3132 if (nobj->containsDenseElement(index)) {
3133 return AttachDecision::NoAction;
3136 // Only handle ArrayObject and PlainObject in this stub.
3137 if (!nobj->is<ArrayObject>() && !nobj->is<PlainObject>()) {
3138 return AttachDecision::NoAction;
3141 // GetSparseElementHelper assumes that the target and the receiver
3142 // are the same.
3143 if (isSuper()) {
3144 return AttachDecision::NoAction;
3147 // Here, we ensure that the prototype chain does not define any sparse
3148 // indexed properties on the shape lineage. This allows us to guard on
3149 // the shapes up the prototype chain to ensure that no indexed properties
3150 // exist outside of the dense elements.
3152 // The `GeneratePrototypeHoleGuards` call below will guard on the shapes,
3153 // as well as ensure that no prototypes contain dense elements, allowing
3154 // us to perform a pure shape-search for out-of-bounds integer-indexed
3155 // properties on the receiver object.
3156 if (PrototypeMayHaveIndexedProperties(nobj)) {
3157 return AttachDecision::NoAction;
3160 // Ensure that obj is an ArrayObject or PlainObject.
3161 if (nobj->is<ArrayObject>()) {
3162 writer.guardClass(objId, GuardClassKind::Array);
3163 } else {
3164 MOZ_ASSERT(nobj->is<PlainObject>());
3165 writer.guardClass(objId, GuardClassKind::PlainObject);
3168 // The helper we are going to call only applies to non-dense elements.
3169 writer.guardIndexIsNotDenseElement(objId, indexId);
3171 // Ensures we are able to efficiently able to map to an integral jsid.
3172 writer.guardInt32IsNonNegative(indexId);
3174 // Shape guard the prototype chain to avoid shadowing indexes from appearing.
3175 // The helper function also ensures that the index does not appear within the
3176 // dense element set of the prototypes.
3177 GeneratePrototypeHoleGuards(writer, nobj, objId,
3178 /* alwaysGuardFirstProto = */ true);
3180 // At this point, we are guaranteed that the indexed property will not
3181 // be found on one of the prototypes. We are assured that we only have
3182 // to check that the receiving object has the property.
3184 writer.callGetSparseElementResult(objId, indexId);
3185 writer.returnFromIC();
3187 trackAttached("GetProp.SparseElement");
3188 return AttachDecision::Attach;
3191 // For Uint32Array we let the stub return an Int32 if we have not seen a
3192 // double, to allow better codegen in Warp while avoiding bailout loops.
3193 static bool ForceDoubleForUint32Array(TypedArrayObject* tarr, uint64_t index) {
3194 MOZ_ASSERT(index < tarr->length().valueOr(0));
3196 if (tarr->type() != Scalar::Type::Uint32) {
3197 // Return value is only relevant for Uint32Array.
3198 return false;
3201 Value res;
3202 MOZ_ALWAYS_TRUE(tarr->getElementPure(index, &res));
3203 MOZ_ASSERT(res.isNumber());
3204 return res.isDouble();
3207 static ArrayBufferViewKind ToArrayBufferViewKind(const TypedArrayObject* obj) {
3208 if (obj->is<FixedLengthTypedArrayObject>()) {
3209 return ArrayBufferViewKind::FixedLength;
3212 MOZ_ASSERT(obj->is<ResizableTypedArrayObject>());
3213 return ArrayBufferViewKind::Resizable;
3216 static ArrayBufferViewKind ToArrayBufferViewKind(const DataViewObject* obj) {
3217 if (obj->is<FixedLengthDataViewObject>()) {
3218 return ArrayBufferViewKind::FixedLength;
3221 MOZ_ASSERT(obj->is<ResizableDataViewObject>());
3222 return ArrayBufferViewKind::Resizable;
3225 AttachDecision GetPropIRGenerator::tryAttachTypedArrayElement(
3226 HandleObject obj, ObjOperandId objId) {
3227 if (!obj->is<TypedArrayObject>()) {
3228 return AttachDecision::NoAction;
3231 if (!idVal_.isNumber()) {
3232 return AttachDecision::NoAction;
3235 auto* tarr = &obj->as<TypedArrayObject>();
3237 bool handleOOB = false;
3238 int64_t indexInt64;
3239 if (!ValueIsInt64Index(idVal_, &indexInt64) || indexInt64 < 0 ||
3240 uint64_t(indexInt64) >= tarr->length().valueOr(0)) {
3241 handleOOB = true;
3244 // If the number is not representable as an integer the result will be
3245 // |undefined| so we leave |forceDoubleForUint32| as false.
3246 bool forceDoubleForUint32 = false;
3247 if (!handleOOB) {
3248 uint64_t index = uint64_t(indexInt64);
3249 forceDoubleForUint32 = ForceDoubleForUint32Array(tarr, index);
3252 writer.guardShapeForClass(objId, tarr->shape());
3254 ValOperandId keyId = getElemKeyValueId();
3255 IntPtrOperandId intPtrIndexId = guardToIntPtrIndex(idVal_, keyId, handleOOB);
3257 auto viewKind = ToArrayBufferViewKind(tarr);
3258 writer.loadTypedArrayElementResult(objId, intPtrIndexId, tarr->type(),
3259 handleOOB, forceDoubleForUint32, viewKind);
3260 writer.returnFromIC();
3262 trackAttached("GetProp.TypedElement");
3263 return AttachDecision::Attach;
3266 AttachDecision GetPropIRGenerator::tryAttachGenericElement(
3267 HandleObject obj, ObjOperandId objId, uint32_t index,
3268 Int32OperandId indexId, ValOperandId receiverId) {
3269 if (!obj->is<NativeObject>()) {
3270 return AttachDecision::NoAction;
3273 #ifdef JS_CODEGEN_X86
3274 if (isSuper()) {
3275 // There aren't enough registers available on x86.
3276 return AttachDecision::NoAction;
3278 #endif
3280 // To allow other types to attach in the non-megamorphic case we test the
3281 // specific matching native receiver; however, once megamorphic we can attach
3282 // for any native
3283 if (mode_ == ICState::Mode::Megamorphic) {
3284 writer.guardIsNativeObject(objId);
3285 } else {
3286 NativeObject* nobj = &obj->as<NativeObject>();
3287 TestMatchingNativeReceiver(writer, nobj, objId);
3289 writer.guardIndexIsNotDenseElement(objId, indexId);
3290 if (isSuper()) {
3291 writer.callNativeGetElementSuperResult(objId, indexId, receiverId);
3292 } else {
3293 writer.callNativeGetElementResult(objId, indexId);
3295 writer.returnFromIC();
3297 trackAttached(mode_ == ICState::Mode::Megamorphic
3298 ? "GenericElementMegamorphic"
3299 : "GenericElement");
3300 return AttachDecision::Attach;
3303 AttachDecision GetPropIRGenerator::tryAttachProxyElement(HandleObject obj,
3304 ObjOperandId objId) {
3305 if (!obj->is<ProxyObject>()) {
3306 return AttachDecision::NoAction;
3309 // The proxy stubs don't currently support |super| access.
3310 if (isSuper()) {
3311 return AttachDecision::NoAction;
3314 #ifdef JS_PUNBOX64
3315 auto proxy = obj.as<ProxyObject>();
3316 if (proxy->handler()->isScripted()) {
3317 TRY_ATTACH(tryAttachScriptedProxy(proxy, objId, JS::VoidHandlePropertyKey));
3319 #endif
3321 writer.guardIsProxy(objId);
3323 // We are not guarding against DOM proxies here, because there is no other
3324 // specialized DOM IC we could attach.
3325 // We could call maybeEmitIdGuard here and then emit ProxyGetResult,
3326 // but for GetElem we prefer to attach a stub that can handle any Value
3327 // so we don't attach a new stub for every id.
3328 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem);
3329 MOZ_ASSERT(!isSuper());
3330 writer.proxyGetByValueResult(objId, getElemKeyValueId());
3331 writer.returnFromIC();
3333 trackAttached("GetProp.ProxyElement");
3334 return AttachDecision::Attach;
3337 void GetPropIRGenerator::trackAttached(const char* name) {
3338 stubName_ = name ? name : "NotAttached";
3339 #ifdef JS_CACHEIR_SPEW
3340 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
3341 sp.valueProperty("base", val_);
3342 sp.valueProperty("property", idVal_);
3344 #endif
3347 void IRGenerator::emitIdGuard(ValOperandId valId, const Value& idVal, jsid id) {
3348 if (id.isSymbol()) {
3349 MOZ_ASSERT(idVal.toSymbol() == id.toSymbol());
3350 SymbolOperandId symId = writer.guardToSymbol(valId);
3351 writer.guardSpecificSymbol(symId, id.toSymbol());
3352 } else {
3353 MOZ_ASSERT(id.isAtom());
3354 if (idVal.isUndefined()) {
3355 MOZ_ASSERT(id.isAtom(cx_->names().undefined));
3356 writer.guardIsUndefined(valId);
3357 } else if (idVal.isNull()) {
3358 MOZ_ASSERT(id.isAtom(cx_->names().null));
3359 writer.guardIsNull(valId);
3360 } else {
3361 MOZ_ASSERT(idVal.isString());
3362 StringOperandId strId = writer.guardToString(valId);
3363 writer.guardSpecificAtom(strId, id.toAtom());
3368 void GetPropIRGenerator::maybeEmitIdGuard(jsid id) {
3369 if (cacheKind_ == CacheKind::GetProp ||
3370 cacheKind_ == CacheKind::GetPropSuper) {
3371 // Constant PropertyName, no guards necessary.
3372 MOZ_ASSERT(&idVal_.toString()->asAtom() == id.toAtom());
3373 return;
3376 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
3377 cacheKind_ == CacheKind::GetElemSuper);
3378 emitIdGuard(getElemKeyValueId(), idVal_, id);
3381 void SetPropIRGenerator::maybeEmitIdGuard(jsid id) {
3382 if (cacheKind_ == CacheKind::SetProp) {
3383 // Constant PropertyName, no guards necessary.
3384 MOZ_ASSERT(&idVal_.toString()->asAtom() == id.toAtom());
3385 return;
3388 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
3389 emitIdGuard(setElemKeyValueId(), idVal_, id);
3392 GetNameIRGenerator::GetNameIRGenerator(JSContext* cx, HandleScript script,
3393 jsbytecode* pc, ICState state,
3394 HandleObject env,
3395 Handle<PropertyName*> name)
3396 : IRGenerator(cx, script, pc, CacheKind::GetName, state),
3397 env_(env),
3398 name_(name) {}
3400 AttachDecision GetNameIRGenerator::tryAttachStub() {
3401 MOZ_ASSERT(cacheKind_ == CacheKind::GetName);
3403 AutoAssertNoPendingException aanpe(cx_);
3405 ObjOperandId envId(writer.setInputOperandId(0));
3406 RootedId id(cx_, NameToId(name_));
3408 TRY_ATTACH(tryAttachGlobalNameValue(envId, id));
3409 TRY_ATTACH(tryAttachGlobalNameGetter(envId, id));
3410 TRY_ATTACH(tryAttachEnvironmentName(envId, id));
3412 trackAttached(IRGenerator::NotAttached);
3413 return AttachDecision::NoAction;
3416 static bool CanAttachGlobalName(JSContext* cx,
3417 GlobalLexicalEnvironmentObject* globalLexical,
3418 PropertyKey id, NativeObject** holder,
3419 Maybe<PropertyInfo>* prop) {
3420 // The property must be found, and it must be found as a normal data property.
3421 NativeObject* current = globalLexical;
3422 while (true) {
3423 *prop = current->lookup(cx, id);
3424 if (prop->isSome()) {
3425 break;
3428 if (current == globalLexical) {
3429 current = &globalLexical->global();
3430 } else {
3431 // In the browser the global prototype chain should be immutable.
3432 if (!current->staticPrototypeIsImmutable()) {
3433 return false;
3436 JSObject* proto = current->staticPrototype();
3437 if (!proto || !proto->is<NativeObject>()) {
3438 return false;
3441 current = &proto->as<NativeObject>();
3445 *holder = current;
3446 return true;
3449 AttachDecision GetNameIRGenerator::tryAttachGlobalNameValue(ObjOperandId objId,
3450 HandleId id) {
3451 if (!IsGlobalOp(JSOp(*pc_))) {
3452 return AttachDecision::NoAction;
3454 MOZ_ASSERT(!script_->hasNonSyntacticScope());
3456 auto* globalLexical = &env_->as<GlobalLexicalEnvironmentObject>();
3458 NativeObject* holder = nullptr;
3459 Maybe<PropertyInfo> prop;
3460 if (!CanAttachGlobalName(cx_, globalLexical, id, &holder, &prop)) {
3461 return AttachDecision::NoAction;
3464 // The property must be found, and it must be found as a normal data property.
3465 if (!prop->isDataProperty()) {
3466 return AttachDecision::NoAction;
3469 // This might still be an uninitialized lexical.
3470 if (holder->getSlot(prop->slot()).isMagic()) {
3471 return AttachDecision::NoAction;
3474 if (holder == globalLexical) {
3475 // There is no need to guard on the shape. Lexical bindings are
3476 // non-configurable, and this stub cannot be shared across globals.
3477 size_t dynamicSlotOffset =
3478 holder->dynamicSlotIndex(prop->slot()) * sizeof(Value);
3479 writer.loadDynamicSlotResult(objId, dynamicSlotOffset);
3480 } else if (holder == &globalLexical->global()) {
3481 MOZ_ASSERT(globalLexical->global().isGenerationCountedGlobal());
3482 writer.guardGlobalGeneration(
3483 globalLexical->global().generationCount(),
3484 globalLexical->global().addressOfGenerationCount());
3485 ObjOperandId holderId = writer.loadObject(holder);
3486 #ifdef DEBUG
3487 writer.assertPropertyLookup(holderId, id, prop->slot());
3488 #endif
3489 EmitLoadSlotResult(writer, holderId, holder, *prop);
3490 } else {
3491 // Check the prototype chain from the global to the holder
3492 // prototype. Ignore the global lexical scope as it doesn't figure
3493 // into the prototype chain. We guard on the global lexical
3494 // scope's shape independently.
3495 if (!IsCacheableGetPropSlot(&globalLexical->global(), holder, *prop)) {
3496 return AttachDecision::NoAction;
3499 // Shape guard for global lexical.
3500 writer.guardShape(objId, globalLexical->shape());
3502 // Guard on the shape of the GlobalObject.
3503 ObjOperandId globalId = writer.loadObject(&globalLexical->global());
3504 writer.guardShape(globalId, globalLexical->global().shape());
3506 // Shape guard holder.
3507 ObjOperandId holderId = writer.loadObject(holder);
3508 writer.guardShape(holderId, holder->shape());
3510 EmitLoadSlotResult(writer, holderId, holder, *prop);
3513 writer.returnFromIC();
3515 trackAttached("GetName.GlobalNameValue");
3516 return AttachDecision::Attach;
3519 AttachDecision GetNameIRGenerator::tryAttachGlobalNameGetter(ObjOperandId objId,
3520 HandleId id) {
3521 if (!IsGlobalOp(JSOp(*pc_))) {
3522 return AttachDecision::NoAction;
3524 MOZ_ASSERT(!script_->hasNonSyntacticScope());
3526 Handle<GlobalLexicalEnvironmentObject*> globalLexical =
3527 env_.as<GlobalLexicalEnvironmentObject>();
3528 MOZ_ASSERT(globalLexical->isGlobal());
3530 NativeObject* holder = nullptr;
3531 Maybe<PropertyInfo> prop;
3532 if (!CanAttachGlobalName(cx_, globalLexical, id, &holder, &prop)) {
3533 return AttachDecision::NoAction;
3536 if (holder == globalLexical) {
3537 return AttachDecision::NoAction;
3540 GlobalObject* global = &globalLexical->global();
3542 NativeGetPropKind kind = IsCacheableGetPropCall(global, holder, *prop, pc_);
3543 if (kind != NativeGetPropKind::NativeGetter &&
3544 kind != NativeGetPropKind::ScriptedGetter) {
3545 return AttachDecision::NoAction;
3548 bool needsWindowProxy =
3549 IsWindow(global) && GetterNeedsWindowProxyThis(holder, *prop);
3551 // Shape guard for global lexical.
3552 writer.guardShape(objId, globalLexical->shape());
3554 // Guard on the shape of the GlobalObject.
3555 ObjOperandId globalId = writer.loadEnclosingEnvironment(objId);
3556 writer.guardShape(globalId, global->shape());
3558 if (holder != global) {
3559 // Shape guard holder.
3560 ObjOperandId holderId = writer.loadObject(holder);
3561 writer.guardShape(holderId, holder->shape());
3562 EmitGuardGetterSetterSlot(writer, holder, *prop, holderId,
3563 /* holderIsConstant = */ true);
3564 } else {
3565 // Note: pass true for |holderIsConstant| because the holder must be the
3566 // current global object.
3567 EmitGuardGetterSetterSlot(writer, holder, *prop, globalId,
3568 /* holderIsConstant = */ true);
3571 if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, global, holder, *prop,
3572 mode_)) {
3573 // The global shape guard above ensures the instance JSClass is correct.
3574 MOZ_ASSERT(!needsWindowProxy);
3575 EmitCallDOMGetterResultNoGuards(writer, holder, *prop, globalId);
3576 trackAttached("GetName.GlobalNameDOMGetter");
3577 } else {
3578 ObjOperandId receiverObjId;
3579 if (needsWindowProxy) {
3580 MOZ_ASSERT(cx_->global()->maybeWindowProxy());
3581 receiverObjId = writer.loadObject(cx_->global()->maybeWindowProxy());
3582 } else {
3583 receiverObjId = globalId;
3585 ValOperandId receiverId = writer.boxObject(receiverObjId);
3586 EmitCallGetterResultNoGuards(cx_, writer, kind, global, holder, *prop,
3587 receiverId);
3588 trackAttached("GetName.GlobalNameGetter");
3591 return AttachDecision::Attach;
3594 static bool NeedEnvironmentShapeGuard(JSContext* cx, JSObject* envObj) {
3595 // We can skip a guard on the call object if the script's bindings are
3596 // guaranteed to be immutable (and thus cannot introduce shadowing variables).
3597 // If the function is a relazified self-hosted function it has no BaseScript
3598 // and we pessimistically create the guard.
3599 if (envObj->is<CallObject>()) {
3600 auto* callObj = &envObj->as<CallObject>();
3601 JSFunction* fun = &callObj->callee();
3602 return !fun->hasBaseScript() ||
3603 fun->baseScript()->funHasExtensibleScope() ||
3604 DebugEnvironments::hasDebugEnvironment(cx, *callObj);
3607 // Similar to the call object case, we can also skip a guard if the lexical
3608 // environment's bindings are immutable.
3609 if (envObj->is<LexicalEnvironmentObject>()) {
3610 return envObj->as<LexicalEnvironmentObject>().isExtensible();
3613 // Use a shape guard for all other environment objects.
3614 return true;
3617 AttachDecision GetNameIRGenerator::tryAttachEnvironmentName(ObjOperandId objId,
3618 HandleId id) {
3619 if (IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) {
3620 return AttachDecision::NoAction;
3623 JSObject* env = env_;
3624 Maybe<PropertyInfo> prop;
3625 NativeObject* holder = nullptr;
3627 while (env) {
3628 if (env->is<GlobalObject>()) {
3629 prop = env->as<GlobalObject>().lookup(cx_, id);
3630 if (prop.isSome()) {
3631 break;
3633 return AttachDecision::NoAction;
3636 if (!env->is<EnvironmentObject>() || env->is<WithEnvironmentObject>()) {
3637 return AttachDecision::NoAction;
3640 // Check for an 'own' property on the env. There is no need to
3641 // check the prototype as non-with scopes do not inherit properties
3642 // from any prototype.
3643 prop = env->as<NativeObject>().lookup(cx_, id);
3644 if (prop.isSome()) {
3645 break;
3648 env = env->enclosingEnvironment();
3651 holder = &env->as<NativeObject>();
3652 if (!IsCacheableGetPropSlot(holder, holder, *prop)) {
3653 return AttachDecision::NoAction;
3655 if (holder->getSlot(prop->slot()).isMagic()) {
3656 MOZ_ASSERT(holder->is<EnvironmentObject>());
3657 return AttachDecision::NoAction;
3660 ObjOperandId lastObjId = objId;
3661 env = env_;
3662 while (env) {
3663 if (NeedEnvironmentShapeGuard(cx_, env)) {
3664 writer.guardShape(lastObjId, env->shape());
3667 if (env == holder) {
3668 break;
3671 lastObjId = writer.loadEnclosingEnvironment(lastObjId);
3672 env = env->enclosingEnvironment();
3675 ValOperandId resId = EmitLoadSlot(writer, holder, lastObjId, prop->slot());
3676 if (holder->is<EnvironmentObject>()) {
3677 writer.guardIsNotUninitializedLexical(resId);
3679 writer.loadOperandResult(resId);
3680 writer.returnFromIC();
3682 trackAttached("GetName.EnvironmentName");
3683 return AttachDecision::Attach;
3686 void GetNameIRGenerator::trackAttached(const char* name) {
3687 stubName_ = name ? name : "NotAttached";
3688 #ifdef JS_CACHEIR_SPEW
3689 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
3690 sp.valueProperty("base", ObjectValue(*env_));
3691 sp.valueProperty("property", StringValue(name_));
3693 #endif
3696 BindNameIRGenerator::BindNameIRGenerator(JSContext* cx, HandleScript script,
3697 jsbytecode* pc, ICState state,
3698 HandleObject env,
3699 Handle<PropertyName*> name)
3700 : IRGenerator(cx, script, pc, CacheKind::BindName, state),
3701 env_(env),
3702 name_(name) {}
3704 AttachDecision BindNameIRGenerator::tryAttachStub() {
3705 MOZ_ASSERT(cacheKind_ == CacheKind::BindName);
3707 AutoAssertNoPendingException aanpe(cx_);
3709 ObjOperandId envId(writer.setInputOperandId(0));
3710 RootedId id(cx_, NameToId(name_));
3712 TRY_ATTACH(tryAttachGlobalName(envId, id));
3713 TRY_ATTACH(tryAttachEnvironmentName(envId, id));
3715 trackAttached(IRGenerator::NotAttached);
3716 return AttachDecision::NoAction;
3719 AttachDecision BindNameIRGenerator::tryAttachGlobalName(ObjOperandId objId,
3720 HandleId id) {
3721 if (!IsGlobalOp(JSOp(*pc_))) {
3722 return AttachDecision::NoAction;
3724 MOZ_ASSERT(!script_->hasNonSyntacticScope());
3726 Handle<GlobalLexicalEnvironmentObject*> globalLexical =
3727 env_.as<GlobalLexicalEnvironmentObject>();
3728 MOZ_ASSERT(globalLexical->isGlobal());
3730 JSObject* result = nullptr;
3731 if (Maybe<PropertyInfo> prop = globalLexical->lookup(cx_, id)) {
3732 // If this is an uninitialized lexical or a const, we need to return a
3733 // RuntimeLexicalErrorObject.
3734 if (globalLexical->getSlot(prop->slot()).isMagic() || !prop->writable()) {
3735 return AttachDecision::NoAction;
3737 result = globalLexical;
3738 } else {
3739 result = &globalLexical->global();
3742 if (result == globalLexical) {
3743 // Lexical bindings are non-configurable so we can just return the
3744 // global lexical.
3745 writer.loadObjectResult(objId);
3746 } else {
3747 // If the property exists on the global and is non-configurable, it cannot
3748 // be shadowed by the lexical scope so we can just return the global without
3749 // a shape guard.
3750 Maybe<PropertyInfo> prop = result->as<GlobalObject>().lookup(cx_, id);
3751 if (prop.isNothing() || prop->configurable()) {
3752 writer.guardShape(objId, globalLexical->shape());
3754 ObjOperandId globalId = writer.loadEnclosingEnvironment(objId);
3755 writer.loadObjectResult(globalId);
3757 writer.returnFromIC();
3759 trackAttached("BindName.GlobalName");
3760 return AttachDecision::Attach;
3763 AttachDecision BindNameIRGenerator::tryAttachEnvironmentName(ObjOperandId objId,
3764 HandleId id) {
3765 if (IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) {
3766 return AttachDecision::NoAction;
3769 // JSOp::BindUnqualifiedName when writing to a dynamic environment binding.
3770 // JSOp::BindName when reading from a dynamic environment binding.
3771 bool unqualifiedLookup = JSOp(*pc_) == JSOp::BindUnqualifiedName;
3773 JSObject* env = env_;
3774 Maybe<PropertyInfo> prop;
3775 while (true) {
3776 // Stop when we've reached the global object.
3777 if (env->is<GlobalObject>()) {
3778 break;
3781 if (!env->is<EnvironmentObject>() || env->is<WithEnvironmentObject>()) {
3782 return AttachDecision::NoAction;
3785 // When we reach an unqualified variables object (like the global) we
3786 // have to stop looking and return that object.
3787 if (unqualifiedLookup && env->isUnqualifiedVarObj()) {
3788 break;
3791 // Check for an 'own' property on the env. There is no need to
3792 // check the prototype as non-with scopes do not inherit properties
3793 // from any prototype.
3794 prop = env->as<NativeObject>().lookup(cx_, id);
3795 if (prop.isSome()) {
3796 break;
3799 env = env->enclosingEnvironment();
3802 // If this is an uninitialized lexical or a const, we need to return a
3803 // RuntimeLexicalErrorObject.
3804 auto* holder = &env->as<NativeObject>();
3805 if (prop.isSome() && holder->is<EnvironmentObject>()) {
3806 // Uninitialized lexical binding.
3807 if (holder->getSlot(prop->slot()).isMagic()) {
3808 return AttachDecision::NoAction;
3811 // Attempt to write to a const binding.
3812 if (unqualifiedLookup && !prop->writable()) {
3813 return AttachDecision::NoAction;
3817 ObjOperandId lastObjId = objId;
3818 env = env_;
3819 while (env) {
3820 if (NeedEnvironmentShapeGuard(cx_, env) && !env->is<GlobalObject>()) {
3821 writer.guardShape(lastObjId, env->shape());
3824 if (env == holder) {
3825 break;
3828 lastObjId = writer.loadEnclosingEnvironment(lastObjId);
3829 env = env->enclosingEnvironment();
3832 if (prop.isSome() && holder->is<EnvironmentObject>()) {
3833 ValOperandId valId = EmitLoadSlot(writer, holder, lastObjId, prop->slot());
3834 writer.guardIsNotUninitializedLexical(valId);
3837 writer.loadObjectResult(lastObjId);
3838 writer.returnFromIC();
3840 trackAttached("BindName.EnvironmentName");
3841 return AttachDecision::Attach;
3844 void BindNameIRGenerator::trackAttached(const char* name) {
3845 stubName_ = name ? name : "NotAttached";
3846 #ifdef JS_CACHEIR_SPEW
3847 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
3848 sp.valueProperty("base", ObjectValue(*env_));
3849 sp.valueProperty("property", StringValue(name_));
3851 #endif
3854 HasPropIRGenerator::HasPropIRGenerator(JSContext* cx, HandleScript script,
3855 jsbytecode* pc, ICState state,
3856 CacheKind cacheKind, HandleValue idVal,
3857 HandleValue val)
3858 : IRGenerator(cx, script, pc, cacheKind, state), val_(val), idVal_(idVal) {}
3860 AttachDecision HasPropIRGenerator::tryAttachDense(HandleObject obj,
3861 ObjOperandId objId,
3862 uint32_t index,
3863 Int32OperandId indexId) {
3864 if (!obj->is<NativeObject>()) {
3865 return AttachDecision::NoAction;
3868 NativeObject* nobj = &obj->as<NativeObject>();
3869 if (!nobj->containsDenseElement(index)) {
3870 return AttachDecision::NoAction;
3873 if (mode_ == ICState::Mode::Megamorphic) {
3874 writer.guardIsNativeObject(objId);
3875 } else {
3876 // Guard shape to ensure object class is NativeObject.
3877 TestMatchingNativeReceiver(writer, nobj, objId);
3879 writer.loadDenseElementExistsResult(objId, indexId);
3880 writer.returnFromIC();
3882 trackAttached("HasProp.Dense");
3883 return AttachDecision::Attach;
3886 AttachDecision HasPropIRGenerator::tryAttachDenseHole(HandleObject obj,
3887 ObjOperandId objId,
3888 uint32_t index,
3889 Int32OperandId indexId) {
3890 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
3891 OwnProperty ownProp = hasOwn ? OwnProperty::Yes : OwnProperty::No;
3893 if (!obj->is<NativeObject>()) {
3894 return AttachDecision::NoAction;
3897 NativeObject* nobj = &obj->as<NativeObject>();
3898 if (nobj->containsDenseElement(index)) {
3899 return AttachDecision::NoAction;
3901 if (!CanAttachDenseElementHole(nobj, ownProp)) {
3902 return AttachDecision::NoAction;
3905 // Guard shape to ensure class is NativeObject and to prevent non-dense
3906 // elements being added. Also ensures prototype doesn't change if dynamic
3907 // checks aren't emitted.
3908 TestMatchingNativeReceiver(writer, nobj, objId);
3910 // Generate prototype guards if needed. This includes monitoring that
3911 // properties were not added in the chain.
3912 if (!hasOwn) {
3913 GeneratePrototypeHoleGuards(writer, nobj, objId,
3914 /* alwaysGuardFirstProto = */ false);
3917 writer.loadDenseElementHoleExistsResult(objId, indexId);
3918 writer.returnFromIC();
3920 trackAttached("HasProp.DenseHole");
3921 return AttachDecision::Attach;
3924 AttachDecision HasPropIRGenerator::tryAttachSparse(HandleObject obj,
3925 ObjOperandId objId,
3926 Int32OperandId indexId) {
3927 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
3928 OwnProperty ownProp = hasOwn ? OwnProperty::Yes : OwnProperty::No;
3930 if (!obj->is<NativeObject>()) {
3931 return AttachDecision::NoAction;
3933 auto* nobj = &obj->as<NativeObject>();
3935 if (!nobj->isIndexed()) {
3936 return AttachDecision::NoAction;
3938 if (!CanAttachDenseElementHole(nobj, ownProp, AllowIndexedReceiver::Yes)) {
3939 return AttachDecision::NoAction;
3942 // Guard that this is a native object.
3943 writer.guardIsNativeObject(objId);
3945 // Generate prototype guards if needed. This includes monitoring that
3946 // properties were not added in the chain.
3947 if (!hasOwn) {
3948 GeneratePrototypeHoleGuards(writer, nobj, objId,
3949 /* alwaysGuardFirstProto = */ true);
3952 // Because of the prototype guard we know that the prototype chain
3953 // does not include any dense or sparse (i.e indexed) properties.
3954 writer.callObjectHasSparseElementResult(objId, indexId);
3955 writer.returnFromIC();
3957 trackAttached("HasProp.Sparse");
3958 return AttachDecision::Attach;
3961 AttachDecision HasPropIRGenerator::tryAttachArgumentsObjectArg(
3962 HandleObject obj, ObjOperandId objId, Int32OperandId indexId) {
3963 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
3964 OwnProperty ownProp = hasOwn ? OwnProperty::Yes : OwnProperty::No;
3966 if (!obj->is<ArgumentsObject>()) {
3967 return AttachDecision::NoAction;
3969 auto* args = &obj->as<ArgumentsObject>();
3971 // No elements must have been overridden or deleted.
3972 if (args->hasOverriddenElement()) {
3973 return AttachDecision::NoAction;
3976 if (!CanAttachDenseElementHole(args, ownProp, AllowIndexedReceiver::Yes,
3977 AllowExtraReceiverProperties::Yes)) {
3978 return AttachDecision::NoAction;
3981 if (args->is<MappedArgumentsObject>()) {
3982 writer.guardClass(objId, GuardClassKind::MappedArguments);
3983 } else {
3984 MOZ_ASSERT(args->is<UnmappedArgumentsObject>());
3985 writer.guardClass(objId, GuardClassKind::UnmappedArguments);
3988 if (!hasOwn) {
3989 GeneratePrototypeHoleGuards(writer, args, objId,
3990 /* alwaysGuardFirstProto = */ true);
3993 writer.loadArgumentsObjectArgExistsResult(objId, indexId);
3994 writer.returnFromIC();
3996 trackAttached("HasProp.ArgumentsObjectArg");
3997 return AttachDecision::Attach;
4000 AttachDecision HasPropIRGenerator::tryAttachNamedProp(HandleObject obj,
4001 ObjOperandId objId,
4002 HandleId key,
4003 ValOperandId keyId) {
4004 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
4006 Rooted<NativeObject*> holder(cx_);
4007 PropertyResult prop;
4009 if (hasOwn) {
4010 if (!LookupOwnPropertyPure(cx_, obj, key, &prop)) {
4011 return AttachDecision::NoAction;
4014 holder.set(&obj->as<NativeObject>());
4015 } else {
4016 NativeObject* nHolder = nullptr;
4017 if (!LookupPropertyPure(cx_, obj, key, &nHolder, &prop)) {
4018 return AttachDecision::NoAction;
4020 holder.set(nHolder);
4022 if (prop.isNotFound()) {
4023 return AttachDecision::NoAction;
4026 TRY_ATTACH(tryAttachSmallObjectVariableKey(obj, objId, key, keyId));
4027 TRY_ATTACH(tryAttachMegamorphic(objId, keyId));
4028 TRY_ATTACH(tryAttachNative(&obj->as<NativeObject>(), objId, key, keyId, prop,
4029 holder.get()));
4031 return AttachDecision::NoAction;
4034 AttachDecision HasPropIRGenerator::tryAttachSmallObjectVariableKey(
4035 HandleObject obj, ObjOperandId objId, jsid key, ValOperandId keyId) {
4036 MOZ_ASSERT(obj->is<NativeObject>());
4038 if (cacheKind_ != CacheKind::HasOwn) {
4039 return AttachDecision::NoAction;
4042 if (mode_ != ICState::Mode::Megamorphic) {
4043 return AttachDecision::NoAction;
4046 if (numOptimizedStubs_ != 0) {
4047 return AttachDecision::NoAction;
4050 if (!key.isString()) {
4051 return AttachDecision::NoAction;
4054 if (!obj->as<NativeObject>().hasEmptyElements()) {
4055 return AttachDecision::NoAction;
4058 if (obj->getClass()->getResolve()) {
4059 return AttachDecision::NoAction;
4062 if (!obj->shape()->isShared()) {
4063 return AttachDecision::NoAction;
4066 static constexpr size_t SMALL_OBJECT_SIZE = 5;
4068 if (obj->shape()->asShared().slotSpan() > SMALL_OBJECT_SIZE) {
4069 return AttachDecision::NoAction;
4072 Rooted<ListObject*> keyListObj(cx_, ListObject::create(cx_));
4073 if (!keyListObj) {
4074 cx_->recoverFromOutOfMemory();
4075 return AttachDecision::NoAction;
4078 for (SharedShapePropertyIter<CanGC> iter(cx_, &obj->shape()->asShared());
4079 !iter.done(); iter++) {
4080 if (!iter->key().isAtom()) {
4081 return AttachDecision::NoAction;
4084 if (keyListObj->length() == SMALL_OBJECT_SIZE) {
4085 return AttachDecision::NoAction;
4088 RootedValue key(cx_, StringValue(iter->key().toAtom()));
4089 if (!keyListObj->append(cx_, key)) {
4090 cx_->recoverFromOutOfMemory();
4091 return AttachDecision::NoAction;
4095 writer.guardShape(objId, obj->shape());
4096 writer.guardNoDenseElements(objId);
4097 StringOperandId keyStrId = writer.guardToString(keyId);
4098 StringOperandId keyAtomId = writer.stringToAtom(keyStrId);
4099 writer.smallObjectVariableKeyHasOwnResult(keyAtomId, keyListObj,
4100 obj->shape());
4101 writer.returnFromIC();
4102 trackAttached("HasProp.SmallObjectVariableKey");
4103 return AttachDecision::Attach;
4106 AttachDecision HasPropIRGenerator::tryAttachMegamorphic(ObjOperandId objId,
4107 ValOperandId keyId) {
4108 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
4110 if (mode_ != ICState::Mode::Megamorphic) {
4111 return AttachDecision::NoAction;
4114 writer.megamorphicHasPropResult(objId, keyId, hasOwn);
4115 writer.returnFromIC();
4116 trackAttached("HasProp.Megamorphic");
4117 return AttachDecision::Attach;
4120 AttachDecision HasPropIRGenerator::tryAttachNative(NativeObject* obj,
4121 ObjOperandId objId, jsid key,
4122 ValOperandId keyId,
4123 PropertyResult prop,
4124 NativeObject* holder) {
4125 MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
4127 if (!prop.isNativeProperty()) {
4128 return AttachDecision::NoAction;
4131 emitIdGuard(keyId, idVal_, key);
4132 EmitReadSlotGuard(writer, obj, holder, objId);
4133 writer.loadBooleanResult(true);
4134 writer.returnFromIC();
4136 trackAttached("HasProp.Native");
4137 return AttachDecision::Attach;
4140 static void EmitGuardTypedArray(CacheIRWriter& writer, TypedArrayObject* obj,
4141 ObjOperandId objId) {
4142 if (obj->is<FixedLengthTypedArrayObject>()) {
4143 writer.guardIsFixedLengthTypedArray(objId);
4144 } else {
4145 writer.guardIsResizableTypedArray(objId);
4149 AttachDecision HasPropIRGenerator::tryAttachTypedArray(HandleObject obj,
4150 ObjOperandId objId,
4151 ValOperandId keyId) {
4152 if (!obj->is<TypedArrayObject>()) {
4153 return AttachDecision::NoAction;
4156 int64_t index;
4157 if (!ValueIsInt64Index(idVal_, &index)) {
4158 return AttachDecision::NoAction;
4161 auto* tarr = &obj->as<TypedArrayObject>();
4162 EmitGuardTypedArray(writer, tarr, objId);
4164 IntPtrOperandId intPtrIndexId =
4165 guardToIntPtrIndex(idVal_, keyId, /* supportOOB = */ true);
4167 auto viewKind = ToArrayBufferViewKind(tarr);
4168 writer.loadTypedArrayElementExistsResult(objId, intPtrIndexId, viewKind);
4169 writer.returnFromIC();
4171 trackAttached("HasProp.TypedArrayObject");
4172 return AttachDecision::Attach;
4175 AttachDecision HasPropIRGenerator::tryAttachSlotDoesNotExist(
4176 NativeObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId) {
4177 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
4179 emitIdGuard(keyId, idVal_, key);
4180 if (hasOwn) {
4181 TestMatchingNativeReceiver(writer, obj, objId);
4182 } else {
4183 EmitMissingPropGuard(writer, obj, objId);
4185 writer.loadBooleanResult(false);
4186 writer.returnFromIC();
4188 trackAttached("HasProp.DoesNotExist");
4189 return AttachDecision::Attach;
4192 AttachDecision HasPropIRGenerator::tryAttachDoesNotExist(HandleObject obj,
4193 ObjOperandId objId,
4194 HandleId key,
4195 ValOperandId keyId) {
4196 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
4198 // Check that property doesn't exist on |obj| or it's prototype chain. These
4199 // checks allow NativeObjects with a NativeObject prototype chain. They return
4200 // NoAction if unknown such as resolve hooks or proxies.
4201 if (hasOwn) {
4202 if (!CheckHasNoSuchOwnProperty(cx_, obj, key)) {
4203 return AttachDecision::NoAction;
4205 } else {
4206 if (!CheckHasNoSuchProperty(cx_, obj, key)) {
4207 return AttachDecision::NoAction;
4211 TRY_ATTACH(tryAttachSmallObjectVariableKey(obj, objId, key, keyId));
4212 TRY_ATTACH(tryAttachMegamorphic(objId, keyId));
4213 TRY_ATTACH(
4214 tryAttachSlotDoesNotExist(&obj->as<NativeObject>(), objId, key, keyId));
4216 return AttachDecision::NoAction;
4219 AttachDecision HasPropIRGenerator::tryAttachProxyElement(HandleObject obj,
4220 ObjOperandId objId,
4221 ValOperandId keyId) {
4222 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
4224 if (!obj->is<ProxyObject>()) {
4225 return AttachDecision::NoAction;
4228 writer.guardIsProxy(objId);
4229 writer.proxyHasPropResult(objId, keyId, hasOwn);
4230 writer.returnFromIC();
4232 trackAttached("HasProp.ProxyElement");
4233 return AttachDecision::Attach;
4236 AttachDecision HasPropIRGenerator::tryAttachStub() {
4237 MOZ_ASSERT(cacheKind_ == CacheKind::In || cacheKind_ == CacheKind::HasOwn);
4239 AutoAssertNoPendingException aanpe(cx_);
4241 // NOTE: Argument order is PROPERTY, OBJECT
4242 ValOperandId keyId(writer.setInputOperandId(0));
4243 ValOperandId valId(writer.setInputOperandId(1));
4245 if (!val_.isObject()) {
4246 trackAttached(IRGenerator::NotAttached);
4247 return AttachDecision::NoAction;
4249 RootedObject obj(cx_, &val_.toObject());
4250 ObjOperandId objId = writer.guardToObject(valId);
4252 // Optimize Proxies
4253 TRY_ATTACH(tryAttachProxyElement(obj, objId, keyId));
4255 RootedId id(cx_);
4256 bool nameOrSymbol;
4257 if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
4258 cx_->clearPendingException();
4259 return AttachDecision::NoAction;
4262 if (nameOrSymbol) {
4263 TRY_ATTACH(tryAttachNamedProp(obj, objId, id, keyId));
4264 TRY_ATTACH(tryAttachDoesNotExist(obj, objId, id, keyId));
4266 trackAttached(IRGenerator::NotAttached);
4267 return AttachDecision::NoAction;
4270 TRY_ATTACH(tryAttachTypedArray(obj, objId, keyId));
4272 uint32_t index;
4273 Int32OperandId indexId;
4274 if (maybeGuardInt32Index(idVal_, keyId, &index, &indexId)) {
4275 TRY_ATTACH(tryAttachDense(obj, objId, index, indexId));
4276 TRY_ATTACH(tryAttachDenseHole(obj, objId, index, indexId));
4277 TRY_ATTACH(tryAttachSparse(obj, objId, indexId));
4278 TRY_ATTACH(tryAttachArgumentsObjectArg(obj, objId, indexId));
4280 trackAttached(IRGenerator::NotAttached);
4281 return AttachDecision::NoAction;
4284 trackAttached(IRGenerator::NotAttached);
4285 return AttachDecision::NoAction;
4288 void HasPropIRGenerator::trackAttached(const char* name) {
4289 stubName_ = name ? name : "NotAttached";
4290 #ifdef JS_CACHEIR_SPEW
4291 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
4292 sp.valueProperty("base", val_);
4293 sp.valueProperty("property", idVal_);
4295 #endif
4298 CheckPrivateFieldIRGenerator::CheckPrivateFieldIRGenerator(
4299 JSContext* cx, HandleScript script, jsbytecode* pc, ICState state,
4300 CacheKind cacheKind, HandleValue idVal, HandleValue val)
4301 : IRGenerator(cx, script, pc, cacheKind, state), val_(val), idVal_(idVal) {
4302 MOZ_ASSERT(idVal.isSymbol() && idVal.toSymbol()->isPrivateName());
4305 AttachDecision CheckPrivateFieldIRGenerator::tryAttachStub() {
4306 AutoAssertNoPendingException aanpe(cx_);
4308 ValOperandId valId(writer.setInputOperandId(0));
4309 ValOperandId keyId(writer.setInputOperandId(1));
4311 if (!val_.isObject()) {
4312 trackAttached(IRGenerator::NotAttached);
4313 return AttachDecision::NoAction;
4315 JSObject* obj = &val_.toObject();
4316 ObjOperandId objId = writer.guardToObject(valId);
4317 PropertyKey key = PropertyKey::Symbol(idVal_.toSymbol());
4319 ThrowCondition condition;
4320 ThrowMsgKind msgKind;
4321 GetCheckPrivateFieldOperands(pc_, &condition, &msgKind);
4323 PropertyResult prop;
4324 if (!LookupOwnPropertyPure(cx_, obj, key, &prop)) {
4325 return AttachDecision::NoAction;
4328 if (CheckPrivateFieldWillThrow(condition, prop.isFound())) {
4329 // Don't attach a stub if the operation will throw.
4330 return AttachDecision::NoAction;
4333 auto* nobj = &obj->as<NativeObject>();
4335 TRY_ATTACH(tryAttachNative(nobj, objId, key, keyId, prop));
4337 return AttachDecision::NoAction;
4340 AttachDecision CheckPrivateFieldIRGenerator::tryAttachNative(
4341 NativeObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId,
4342 PropertyResult prop) {
4343 MOZ_ASSERT(prop.isNativeProperty() || prop.isNotFound());
4345 emitIdGuard(keyId, idVal_, key);
4346 TestMatchingNativeReceiver(writer, obj, objId);
4347 writer.loadBooleanResult(prop.isFound());
4348 writer.returnFromIC();
4350 trackAttached("CheckPrivateField.Native");
4351 return AttachDecision::Attach;
4354 void CheckPrivateFieldIRGenerator::trackAttached(const char* name) {
4355 stubName_ = name ? name : "NotAttached";
4356 #ifdef JS_CACHEIR_SPEW
4357 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
4358 sp.valueProperty("base", val_);
4359 sp.valueProperty("property", idVal_);
4361 #endif
4364 bool IRGenerator::maybeGuardInt32Index(const Value& index, ValOperandId indexId,
4365 uint32_t* int32Index,
4366 Int32OperandId* int32IndexId) {
4367 if (index.isNumber()) {
4368 int32_t indexSigned;
4369 if (index.isInt32()) {
4370 indexSigned = index.toInt32();
4371 } else {
4372 // We allow negative zero here.
4373 if (!mozilla::NumberEqualsInt32(index.toDouble(), &indexSigned)) {
4374 return false;
4378 if (indexSigned < 0) {
4379 return false;
4382 *int32Index = uint32_t(indexSigned);
4383 *int32IndexId = writer.guardToInt32Index(indexId);
4384 return true;
4387 if (index.isString()) {
4388 int32_t indexSigned = GetIndexFromString(index.toString());
4389 if (indexSigned < 0) {
4390 return false;
4393 StringOperandId strId = writer.guardToString(indexId);
4394 *int32Index = uint32_t(indexSigned);
4395 *int32IndexId = writer.guardStringToIndex(strId);
4396 return true;
4399 return false;
4402 SetPropIRGenerator::SetPropIRGenerator(JSContext* cx, HandleScript script,
4403 jsbytecode* pc, CacheKind cacheKind,
4404 ICState state, HandleValue lhsVal,
4405 HandleValue idVal, HandleValue rhsVal)
4406 : IRGenerator(cx, script, pc, cacheKind, state),
4407 lhsVal_(lhsVal),
4408 idVal_(idVal),
4409 rhsVal_(rhsVal) {}
4411 AttachDecision SetPropIRGenerator::tryAttachStub() {
4412 AutoAssertNoPendingException aanpe(cx_);
4414 ValOperandId objValId(writer.setInputOperandId(0));
4415 ValOperandId rhsValId;
4416 if (cacheKind_ == CacheKind::SetProp) {
4417 rhsValId = ValOperandId(writer.setInputOperandId(1));
4418 } else {
4419 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
4420 MOZ_ASSERT(setElemKeyValueId().id() == 1);
4421 writer.setInputOperandId(1);
4422 rhsValId = ValOperandId(writer.setInputOperandId(2));
4425 RootedId id(cx_);
4426 bool nameOrSymbol;
4427 if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
4428 cx_->clearPendingException();
4429 return AttachDecision::NoAction;
4432 if (lhsVal_.isObject()) {
4433 RootedObject obj(cx_, &lhsVal_.toObject());
4435 ObjOperandId objId = writer.guardToObject(objValId);
4436 if (IsPropertySetOp(JSOp(*pc_))) {
4437 TRY_ATTACH(tryAttachMegamorphicSetElement(obj, objId, rhsValId));
4439 if (nameOrSymbol) {
4440 TRY_ATTACH(tryAttachNativeSetSlot(obj, objId, id, rhsValId));
4441 if (IsPropertySetOp(JSOp(*pc_))) {
4442 TRY_ATTACH(tryAttachSetArrayLength(obj, objId, id, rhsValId));
4443 TRY_ATTACH(tryAttachSetter(obj, objId, id, rhsValId));
4444 TRY_ATTACH(tryAttachWindowProxy(obj, objId, id, rhsValId));
4445 TRY_ATTACH(tryAttachProxy(obj, objId, id, rhsValId));
4446 TRY_ATTACH(tryAttachMegamorphicSetSlot(obj, objId, id, rhsValId));
4448 if (canAttachAddSlotStub(obj, id)) {
4449 deferType_ = DeferType::AddSlot;
4450 return AttachDecision::Deferred;
4452 return AttachDecision::NoAction;
4455 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
4457 if (IsPropertySetOp(JSOp(*pc_))) {
4458 TRY_ATTACH(tryAttachProxyElement(obj, objId, rhsValId));
4461 TRY_ATTACH(tryAttachSetTypedArrayElement(obj, objId, rhsValId));
4463 uint32_t index;
4464 Int32OperandId indexId;
4465 if (maybeGuardInt32Index(idVal_, setElemKeyValueId(), &index, &indexId)) {
4466 TRY_ATTACH(
4467 tryAttachSetDenseElement(obj, objId, index, indexId, rhsValId));
4468 TRY_ATTACH(
4469 tryAttachSetDenseElementHole(obj, objId, index, indexId, rhsValId));
4470 TRY_ATTACH(tryAttachAddOrUpdateSparseElement(obj, objId, index, indexId,
4471 rhsValId));
4472 return AttachDecision::NoAction;
4475 return AttachDecision::NoAction;
4478 static void EmitStoreSlotAndReturn(CacheIRWriter& writer, ObjOperandId objId,
4479 NativeObject* nobj, PropertyInfo prop,
4480 ValOperandId rhsId) {
4481 if (nobj->isFixedSlot(prop.slot())) {
4482 size_t offset = NativeObject::getFixedSlotOffset(prop.slot());
4483 writer.storeFixedSlot(objId, offset, rhsId);
4484 } else {
4485 size_t offset = nobj->dynamicSlotIndex(prop.slot()) * sizeof(Value);
4486 writer.storeDynamicSlot(objId, offset, rhsId);
4488 writer.returnFromIC();
4491 static Maybe<PropertyInfo> LookupShapeForSetSlot(JSOp op, NativeObject* obj,
4492 jsid id) {
4493 Maybe<PropertyInfo> prop = obj->lookupPure(id);
4494 if (prop.isNothing() || !prop->isDataProperty() || !prop->writable()) {
4495 return mozilla::Nothing();
4498 // If this is a property init operation, the property's attributes may have to
4499 // be changed too, so make sure the current flags match.
4500 if (IsPropertyInitOp(op)) {
4501 // Don't support locked init operations.
4502 if (IsLockedInitOp(op)) {
4503 return mozilla::Nothing();
4506 // Can't redefine a non-configurable property.
4507 if (!prop->configurable()) {
4508 return mozilla::Nothing();
4511 // Make sure the enumerable flag matches the init operation.
4512 if (IsHiddenInitOp(op) == prop->enumerable()) {
4513 return mozilla::Nothing();
4517 return prop;
4520 static bool CanAttachNativeSetSlot(JSOp op, JSObject* obj, PropertyKey id,
4521 Maybe<PropertyInfo>* prop) {
4522 if (!obj->is<NativeObject>()) {
4523 return false;
4526 if (Watchtower::watchesPropertyModification(&obj->as<NativeObject>())) {
4527 return false;
4530 *prop = LookupShapeForSetSlot(op, &obj->as<NativeObject>(), id);
4531 return prop->isSome();
4534 // There is no need to guard on the shape. Global lexical bindings are
4535 // non-configurable and can not be shadowed.
4536 static bool IsGlobalLexicalSetGName(JSOp op, NativeObject* obj,
4537 PropertyInfo prop) {
4538 // Ensure that the env can't change.
4539 if (op != JSOp::SetGName && op != JSOp::StrictSetGName) {
4540 return false;
4543 if (!obj->is<GlobalLexicalEnvironmentObject>()) {
4544 return false;
4547 // Uninitialized let bindings use a RuntimeLexicalErrorObject.
4548 MOZ_ASSERT(!obj->getSlot(prop.slot()).isMagic());
4549 MOZ_ASSERT(prop.writable());
4550 MOZ_ASSERT(!prop.configurable());
4551 return true;
4554 AttachDecision SetPropIRGenerator::tryAttachNativeSetSlot(HandleObject obj,
4555 ObjOperandId objId,
4556 HandleId id,
4557 ValOperandId rhsId) {
4558 Maybe<PropertyInfo> prop;
4559 if (!CanAttachNativeSetSlot(JSOp(*pc_), obj, id, &prop)) {
4560 return AttachDecision::NoAction;
4563 if (mode_ == ICState::Mode::Megamorphic && cacheKind_ == CacheKind::SetProp &&
4564 IsPropertySetOp(JSOp(*pc_))) {
4565 return AttachDecision::NoAction;
4568 maybeEmitIdGuard(id);
4570 NativeObject* nobj = &obj->as<NativeObject>();
4571 if (!IsGlobalLexicalSetGName(JSOp(*pc_), nobj, *prop)) {
4572 TestMatchingNativeReceiver(writer, nobj, objId);
4574 EmitStoreSlotAndReturn(writer, objId, nobj, *prop, rhsId);
4576 trackAttached("SetProp.NativeSlot");
4577 return AttachDecision::Attach;
4580 static bool ValueCanConvertToNumeric(Scalar::Type type, const Value& val) {
4581 if (Scalar::isBigIntType(type)) {
4582 return val.isBigInt();
4584 return val.isNumber() || val.isNullOrUndefined() || val.isBoolean() ||
4585 val.isString();
4588 OperandId IRGenerator::emitNumericGuard(ValOperandId valId, const Value& v,
4589 Scalar::Type type) {
4590 MOZ_ASSERT(ValueCanConvertToNumeric(type, v));
4591 switch (type) {
4592 case Scalar::Int8:
4593 case Scalar::Uint8:
4594 case Scalar::Int16:
4595 case Scalar::Uint16:
4596 case Scalar::Int32:
4597 case Scalar::Uint32: {
4598 if (v.isNumber()) {
4599 return writer.guardToInt32ModUint32(valId);
4601 if (v.isNullOrUndefined()) {
4602 writer.guardIsNullOrUndefined(valId);
4603 return writer.loadInt32Constant(0);
4605 if (v.isBoolean()) {
4606 return writer.guardBooleanToInt32(valId);
4608 MOZ_ASSERT(v.isString());
4609 StringOperandId strId = writer.guardToString(valId);
4610 NumberOperandId numId = writer.guardStringToNumber(strId);
4611 return writer.truncateDoubleToUInt32(numId);
4614 case Scalar::Float16:
4615 case Scalar::Float32:
4616 case Scalar::Float64: {
4617 if (v.isNumber()) {
4618 return writer.guardIsNumber(valId);
4620 if (v.isNull()) {
4621 writer.guardIsNull(valId);
4622 return writer.loadDoubleConstant(0.0);
4624 if (v.isUndefined()) {
4625 writer.guardIsUndefined(valId);
4626 return writer.loadDoubleConstant(JS::GenericNaN());
4628 if (v.isBoolean()) {
4629 BooleanOperandId boolId = writer.guardToBoolean(valId);
4630 return writer.booleanToNumber(boolId);
4632 MOZ_ASSERT(v.isString());
4633 StringOperandId strId = writer.guardToString(valId);
4634 return writer.guardStringToNumber(strId);
4637 case Scalar::Uint8Clamped: {
4638 if (v.isNumber()) {
4639 return writer.guardToUint8Clamped(valId);
4641 if (v.isNullOrUndefined()) {
4642 writer.guardIsNullOrUndefined(valId);
4643 return writer.loadInt32Constant(0);
4645 if (v.isBoolean()) {
4646 return writer.guardBooleanToInt32(valId);
4648 MOZ_ASSERT(v.isString());
4649 StringOperandId strId = writer.guardToString(valId);
4650 NumberOperandId numId = writer.guardStringToNumber(strId);
4651 return writer.doubleToUint8Clamped(numId);
4654 case Scalar::BigInt64:
4655 case Scalar::BigUint64:
4656 MOZ_ASSERT(v.isBigInt());
4657 return writer.guardToBigInt(valId);
4659 case Scalar::MaxTypedArrayViewType:
4660 case Scalar::Int64:
4661 case Scalar::Simd128:
4662 break;
4664 MOZ_CRASH("Unsupported TypedArray type");
4667 void SetPropIRGenerator::trackAttached(const char* name) {
4668 stubName_ = name ? name : "NotAttached";
4669 #ifdef JS_CACHEIR_SPEW
4670 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
4671 sp.opcodeProperty("op", JSOp(*pc_));
4672 sp.valueProperty("base", lhsVal_);
4673 sp.valueProperty("property", idVal_);
4674 sp.valueProperty("value", rhsVal_);
4676 #endif
4679 static bool IsCacheableSetPropCallNative(NativeObject* obj,
4680 NativeObject* holder,
4681 PropertyInfo prop) {
4682 MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
4684 if (!prop.isAccessorProperty()) {
4685 return false;
4688 JSObject* setterObject = holder->getSetter(prop);
4689 if (!setterObject || !setterObject->is<JSFunction>()) {
4690 return false;
4693 JSFunction& setter = setterObject->as<JSFunction>();
4694 if (!setter.isNativeWithoutJitEntry()) {
4695 return false;
4698 if (setter.isClassConstructor()) {
4699 return false;
4702 return true;
4705 static bool IsCacheableSetPropCallScripted(NativeObject* obj,
4706 NativeObject* holder,
4707 PropertyInfo prop) {
4708 MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
4710 if (!prop.isAccessorProperty()) {
4711 return false;
4714 JSObject* setterObject = holder->getSetter(prop);
4715 if (!setterObject || !setterObject->is<JSFunction>()) {
4716 return false;
4719 JSFunction& setter = setterObject->as<JSFunction>();
4720 if (setter.isClassConstructor()) {
4721 return false;
4724 // Scripted functions and natives with JIT entry can use the scripted path.
4725 return setter.hasJitEntry();
4728 static bool CanAttachSetter(JSContext* cx, jsbytecode* pc, JSObject* obj,
4729 PropertyKey id, NativeObject** holder,
4730 Maybe<PropertyInfo>* propInfo) {
4731 // Don't attach a setter stub for ops like JSOp::InitElem.
4732 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc)));
4734 PropertyResult prop;
4735 if (!LookupPropertyPure(cx, obj, id, holder, &prop)) {
4736 return false;
4738 auto* nobj = &obj->as<NativeObject>();
4740 if (!prop.isNativeProperty()) {
4741 return false;
4744 if (!IsCacheableSetPropCallScripted(nobj, *holder, prop.propertyInfo()) &&
4745 !IsCacheableSetPropCallNative(nobj, *holder, prop.propertyInfo())) {
4746 return false;
4749 *propInfo = mozilla::Some(prop.propertyInfo());
4750 return true;
4753 static void EmitCallSetterNoGuards(JSContext* cx, CacheIRWriter& writer,
4754 NativeObject* obj, NativeObject* holder,
4755 PropertyInfo prop, ObjOperandId receiverId,
4756 ValOperandId rhsId) {
4757 JSFunction* target = &holder->getSetter(prop)->as<JSFunction>();
4758 bool sameRealm = cx->realm() == target->realm();
4760 if (target->isNativeWithoutJitEntry()) {
4761 MOZ_ASSERT(IsCacheableSetPropCallNative(obj, holder, prop));
4762 writer.callNativeSetter(receiverId, target, rhsId, sameRealm);
4763 writer.returnFromIC();
4764 return;
4767 MOZ_ASSERT(IsCacheableSetPropCallScripted(obj, holder, prop));
4768 writer.callScriptedSetter(receiverId, target, rhsId, sameRealm);
4769 writer.returnFromIC();
4772 static void EmitCallDOMSetterNoGuards(JSContext* cx, CacheIRWriter& writer,
4773 NativeObject* holder, PropertyInfo prop,
4774 ObjOperandId objId, ValOperandId rhsId) {
4775 JSFunction* setter = &holder->getSetter(prop)->as<JSFunction>();
4776 MOZ_ASSERT(cx->realm() == setter->realm());
4778 writer.callDOMSetter(objId, setter->jitInfo(), rhsId);
4779 writer.returnFromIC();
4782 AttachDecision SetPropIRGenerator::tryAttachSetter(HandleObject obj,
4783 ObjOperandId objId,
4784 HandleId id,
4785 ValOperandId rhsId) {
4786 // Don't attach a setter stub for ops like JSOp::InitElem.
4787 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
4789 NativeObject* holder = nullptr;
4790 Maybe<PropertyInfo> prop;
4791 if (!CanAttachSetter(cx_, pc_, obj, id, &holder, &prop)) {
4792 return AttachDecision::NoAction;
4794 auto* nobj = &obj->as<NativeObject>();
4796 bool needsWindowProxy =
4797 IsWindow(nobj) && SetterNeedsWindowProxyThis(holder, *prop);
4799 maybeEmitIdGuard(id);
4801 // Use the megamorphic guard if we're in megamorphic mode, except if |obj|
4802 // is a Window as GuardHasGetterSetter doesn't support this yet (Window may
4803 // require outerizing).
4804 if (mode_ == ICState::Mode::Specialized || IsWindow(nobj)) {
4805 TestMatchingNativeReceiver(writer, nobj, objId);
4807 if (nobj != holder) {
4808 GeneratePrototypeGuards(writer, nobj, holder, objId);
4810 // Guard on the holder's shape.
4811 ObjOperandId holderId = writer.loadObject(holder);
4812 TestMatchingHolder(writer, holder, holderId);
4814 EmitGuardGetterSetterSlot(writer, holder, *prop, holderId,
4815 /* holderIsConstant = */ true);
4816 } else {
4817 EmitGuardGetterSetterSlot(writer, holder, *prop, objId);
4819 } else {
4820 GetterSetter* gs = holder->getGetterSetter(*prop);
4821 writer.guardHasGetterSetter(objId, id, gs);
4824 if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Setter, nobj, holder, *prop,
4825 mode_)) {
4826 MOZ_ASSERT(!needsWindowProxy);
4827 EmitCallDOMSetterNoGuards(cx_, writer, holder, *prop, objId, rhsId);
4829 trackAttached("SetProp.DOMSetter");
4830 return AttachDecision::Attach;
4833 ObjOperandId receiverId;
4834 if (needsWindowProxy) {
4835 MOZ_ASSERT(cx_->global()->maybeWindowProxy());
4836 receiverId = writer.loadObject(cx_->global()->maybeWindowProxy());
4837 } else {
4838 receiverId = objId;
4840 EmitCallSetterNoGuards(cx_, writer, nobj, holder, *prop, receiverId, rhsId);
4842 trackAttached("SetProp.Setter");
4843 return AttachDecision::Attach;
4846 AttachDecision SetPropIRGenerator::tryAttachSetArrayLength(HandleObject obj,
4847 ObjOperandId objId,
4848 HandleId id,
4849 ValOperandId rhsId) {
4850 // Don't attach an array length stub for ops like JSOp::InitElem.
4851 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
4853 if (!obj->is<ArrayObject>() || !id.isAtom(cx_->names().length) ||
4854 !obj->as<ArrayObject>().lengthIsWritable()) {
4855 return AttachDecision::NoAction;
4858 maybeEmitIdGuard(id);
4859 emitOptimisticClassGuard(objId, obj, GuardClassKind::Array);
4860 writer.callSetArrayLength(objId, IsStrictSetPC(pc_), rhsId);
4861 writer.returnFromIC();
4863 trackAttached("SetProp.ArrayLength");
4864 return AttachDecision::Attach;
4867 AttachDecision SetPropIRGenerator::tryAttachSetDenseElement(
4868 HandleObject obj, ObjOperandId objId, uint32_t index,
4869 Int32OperandId indexId, ValOperandId rhsId) {
4870 if (!obj->is<NativeObject>()) {
4871 return AttachDecision::NoAction;
4874 NativeObject* nobj = &obj->as<NativeObject>();
4875 if (!nobj->containsDenseElement(index) || nobj->denseElementsAreFrozen()) {
4876 return AttachDecision::NoAction;
4879 // Setting holes requires extra code for marking the elements non-packed.
4880 MOZ_ASSERT(!rhsVal_.isMagic(JS_ELEMENTS_HOLE));
4882 JSOp op = JSOp(*pc_);
4884 // We don't currently emit locked init for any indexed properties.
4885 MOZ_ASSERT(!IsLockedInitOp(op));
4887 // We don't currently emit hidden init for any existing indexed properties.
4888 MOZ_ASSERT(!IsHiddenInitOp(op));
4890 // Don't optimize InitElem (DefineProperty) on non-extensible objects: when
4891 // the elements are sealed, we have to throw an exception. Note that we have
4892 // to check !isExtensible instead of denseElementsAreSealed because sealing
4893 // a (non-extensible) object does not necessarily trigger a Shape change.
4894 if (IsPropertyInitOp(op) && !nobj->isExtensible()) {
4895 return AttachDecision::NoAction;
4898 TestMatchingNativeReceiver(writer, nobj, objId);
4900 writer.storeDenseElement(objId, indexId, rhsId);
4901 writer.returnFromIC();
4903 trackAttached("SetProp.DenseElement");
4904 return AttachDecision::Attach;
4907 static bool CanAttachAddElement(NativeObject* obj, bool isInit,
4908 AllowIndexedReceiver allowIndexedReceiver) {
4909 // Make sure the receiver doesn't have any indexed properties and that such
4910 // properties can't appear without a shape change.
4911 if (allowIndexedReceiver == AllowIndexedReceiver::No && obj->isIndexed()) {
4912 return false;
4915 do {
4916 // This check is also relevant for the receiver object.
4917 const JSClass* clasp = obj->getClass();
4918 if (clasp != &ArrayObject::class_ &&
4919 (clasp->getAddProperty() || clasp->getResolve() ||
4920 clasp->getOpsLookupProperty() || clasp->getOpsSetProperty())) {
4921 return false;
4924 // If we're initializing a property instead of setting one, the objects
4925 // on the prototype are not relevant.
4926 if (isInit) {
4927 break;
4930 JSObject* proto = obj->staticPrototype();
4931 if (!proto) {
4932 break;
4935 if (!proto->is<NativeObject>()) {
4936 return false;
4939 NativeObject* nproto = &proto->as<NativeObject>();
4940 if (nproto->isIndexed()) {
4941 return false;
4944 // We have to make sure the proto has no non-writable (frozen) elements
4945 // because we're not allowed to shadow them.
4946 if (nproto->denseElementsAreFrozen() &&
4947 nproto->getDenseInitializedLength() > 0) {
4948 return false;
4951 obj = nproto;
4952 } while (true);
4954 return true;
4957 AttachDecision SetPropIRGenerator::tryAttachSetDenseElementHole(
4958 HandleObject obj, ObjOperandId objId, uint32_t index,
4959 Int32OperandId indexId, ValOperandId rhsId) {
4960 if (!obj->is<NativeObject>()) {
4961 return AttachDecision::NoAction;
4964 // Setting holes requires extra code for marking the elements non-packed.
4965 if (rhsVal_.isMagic(JS_ELEMENTS_HOLE)) {
4966 return AttachDecision::NoAction;
4969 JSOp op = JSOp(*pc_);
4970 MOZ_ASSERT(IsPropertySetOp(op) || IsPropertyInitOp(op));
4972 // We don't currently emit locked init for any indexed properties.
4973 MOZ_ASSERT(!IsLockedInitOp(op));
4975 // Hidden init can be emitted for absent indexed properties.
4976 if (IsHiddenInitOp(op)) {
4977 MOZ_ASSERT(op == JSOp::InitHiddenElem);
4978 return AttachDecision::NoAction;
4981 NativeObject* nobj = &obj->as<NativeObject>();
4982 if (!nobj->isExtensible()) {
4983 return AttachDecision::NoAction;
4986 MOZ_ASSERT(!nobj->denseElementsAreFrozen(),
4987 "Extensible objects should not have frozen elements");
4989 uint32_t initLength = nobj->getDenseInitializedLength();
4991 // Optimize if we're adding an element at initLength or writing to a hole.
4993 // In the case where index > initLength, we need noteHasDenseAdd to be called
4994 // to ensure Ion is aware that writes have occurred to-out-of-bound indexes
4995 // before.
4997 // TODO(post-Warp): noteHasDenseAdd (nee: noteArrayWriteHole) no longer exists
4998 bool isAdd = index == initLength;
4999 bool isHoleInBounds =
5000 index < initLength && !nobj->containsDenseElement(index);
5001 if (!isAdd && !isHoleInBounds) {
5002 return AttachDecision::NoAction;
5005 // Can't add new elements to arrays with non-writable length.
5006 if (isAdd && nobj->is<ArrayObject>() &&
5007 !nobj->as<ArrayObject>().lengthIsWritable()) {
5008 return AttachDecision::NoAction;
5011 // Typed arrays don't have dense elements.
5012 if (nobj->is<TypedArrayObject>()) {
5013 return AttachDecision::NoAction;
5016 // Check for other indexed properties or class hooks.
5017 if (!CanAttachAddElement(nobj, IsPropertyInitOp(op),
5018 AllowIndexedReceiver::No)) {
5019 return AttachDecision::NoAction;
5022 TestMatchingNativeReceiver(writer, nobj, objId);
5024 // Also shape guard the proto chain, unless this is an InitElem.
5025 if (IsPropertySetOp(op)) {
5026 ShapeGuardProtoChain(writer, nobj, objId);
5029 writer.storeDenseElementHole(objId, indexId, rhsId, isAdd);
5030 writer.returnFromIC();
5032 trackAttached(isAdd ? "AddDenseElement" : "StoreDenseElementHole");
5033 return AttachDecision::Attach;
5036 // Add an IC for adding or updating a sparse element.
5037 AttachDecision SetPropIRGenerator::tryAttachAddOrUpdateSparseElement(
5038 HandleObject obj, ObjOperandId objId, uint32_t index,
5039 Int32OperandId indexId, ValOperandId rhsId) {
5040 JSOp op = JSOp(*pc_);
5041 MOZ_ASSERT(IsPropertySetOp(op) || IsPropertyInitOp(op));
5043 if (op != JSOp::SetElem && op != JSOp::StrictSetElem) {
5044 return AttachDecision::NoAction;
5047 if (!obj->is<NativeObject>()) {
5048 return AttachDecision::NoAction;
5050 NativeObject* nobj = &obj->as<NativeObject>();
5052 // We cannot attach a stub to a non-extensible object
5053 if (!nobj->isExtensible()) {
5054 return AttachDecision::NoAction;
5057 // Stub doesn't handle negative indices.
5058 if (index > INT32_MAX) {
5059 return AttachDecision::NoAction;
5062 // The index must not be for a dense element.
5063 if (nobj->containsDenseElement(index)) {
5064 return AttachDecision::NoAction;
5067 // Only handle ArrayObject and PlainObject in this stub.
5068 if (!nobj->is<ArrayObject>() && !nobj->is<PlainObject>()) {
5069 return AttachDecision::NoAction;
5072 // Don't attach if we're adding to an array with non-writable length.
5073 if (nobj->is<ArrayObject>()) {
5074 ArrayObject* aobj = &nobj->as<ArrayObject>();
5075 bool isAdd = (index >= aobj->length());
5076 if (isAdd && !aobj->lengthIsWritable()) {
5077 return AttachDecision::NoAction;
5081 // Check for class hooks or indexed properties on the prototype chain that
5082 // we're not allowed to shadow.
5083 if (!CanAttachAddElement(nobj, /* isInit = */ false,
5084 AllowIndexedReceiver::Yes)) {
5085 return AttachDecision::NoAction;
5088 // Ensure that obj is an ArrayObject or PlainObject.
5089 if (nobj->is<ArrayObject>()) {
5090 writer.guardClass(objId, GuardClassKind::Array);
5091 } else {
5092 MOZ_ASSERT(nobj->is<PlainObject>());
5093 writer.guardClass(objId, GuardClassKind::PlainObject);
5096 // The helper we are going to call only applies to non-dense elements.
5097 writer.guardIndexIsNotDenseElement(objId, indexId);
5099 // Guard extensible: We may be trying to add a new element, and so we'd best
5100 // be able to do so safely.
5101 writer.guardIsExtensible(objId);
5103 // Ensures we are able to efficiently able to map to an integral jsid.
5104 writer.guardInt32IsNonNegative(indexId);
5106 // Shape guard the prototype chain to avoid shadowing indexes from appearing.
5107 // Guard the prototype of the receiver explicitly, because the receiver's
5108 // shape is not being guarded as a proxy for that.
5109 GuardReceiverProto(writer, nobj, objId);
5111 // Dense elements may appear on the prototype chain (and prototypes may
5112 // have a different notion of which elements are dense), but they can
5113 // only be data properties, so our specialized Set handler is ok to bind
5114 // to them.
5115 if (IsPropertySetOp(op)) {
5116 ShapeGuardProtoChain(writer, nobj, objId);
5119 // Ensure that if we're adding an element to the object, the object's
5120 // length is writable.
5121 if (nobj->is<ArrayObject>()) {
5122 writer.guardIndexIsValidUpdateOrAdd(objId, indexId);
5125 writer.callAddOrUpdateSparseElementHelper(
5126 objId, indexId, rhsId,
5127 /* strict = */ op == JSOp::StrictSetElem);
5128 writer.returnFromIC();
5130 trackAttached("SetProp.AddOrUpdateSparseElement");
5131 return AttachDecision::Attach;
5134 AttachDecision SetPropIRGenerator::tryAttachSetTypedArrayElement(
5135 HandleObject obj, ObjOperandId objId, ValOperandId rhsId) {
5136 if (!obj->is<TypedArrayObject>()) {
5137 return AttachDecision::NoAction;
5139 if (!idVal_.isNumber()) {
5140 return AttachDecision::NoAction;
5143 auto* tarr = &obj->as<TypedArrayObject>();
5144 Scalar::Type elementType = tarr->type();
5146 // Don't attach if the input type doesn't match the guard added below.
5147 if (!ValueCanConvertToNumeric(elementType, rhsVal_)) {
5148 return AttachDecision::NoAction;
5151 bool handleOOB = false;
5152 int64_t indexInt64;
5153 if (!ValueIsInt64Index(idVal_, &indexInt64) || indexInt64 < 0 ||
5154 uint64_t(indexInt64) >= tarr->length().valueOr(0)) {
5155 handleOOB = true;
5158 JSOp op = JSOp(*pc_);
5160 // The only expected property init operation is InitElem.
5161 MOZ_ASSERT_IF(IsPropertyInitOp(op), op == JSOp::InitElem);
5163 // InitElem (DefineProperty) has to throw an exception on out-of-bounds.
5164 if (handleOOB && IsPropertyInitOp(op)) {
5165 return AttachDecision::NoAction;
5168 writer.guardShapeForClass(objId, tarr->shape());
5170 OperandId rhsValId = emitNumericGuard(rhsId, rhsVal_, elementType);
5172 ValOperandId keyId = setElemKeyValueId();
5173 IntPtrOperandId indexId = guardToIntPtrIndex(idVal_, keyId, handleOOB);
5175 auto viewKind = ToArrayBufferViewKind(tarr);
5176 writer.storeTypedArrayElement(objId, elementType, indexId, rhsValId,
5177 handleOOB, viewKind);
5178 writer.returnFromIC();
5180 trackAttached(handleOOB ? "SetTypedElementOOB" : "SetTypedElement");
5181 return AttachDecision::Attach;
5184 AttachDecision SetPropIRGenerator::tryAttachGenericProxy(
5185 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
5186 ValOperandId rhsId, bool handleDOMProxies) {
5187 // Don't attach a proxy stub for ops like JSOp::InitElem.
5188 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
5190 writer.guardIsProxy(objId);
5192 if (!handleDOMProxies) {
5193 // Ensure that the incoming object is not a DOM proxy, so that we can
5194 // get to the specialized stubs. If handleDOMProxies is true, we were
5195 // unable to attach a specialized DOM stub, so we just handle all
5196 // proxies here.
5197 writer.guardIsNotDOMProxy(objId);
5200 if (cacheKind_ == CacheKind::SetProp || mode_ == ICState::Mode::Specialized) {
5201 maybeEmitIdGuard(id);
5202 writer.proxySet(objId, id, rhsId, IsStrictSetPC(pc_));
5203 } else {
5204 // Attach a stub that handles every id.
5205 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
5206 MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
5207 writer.proxySetByValue(objId, setElemKeyValueId(), rhsId,
5208 IsStrictSetPC(pc_));
5211 writer.returnFromIC();
5213 trackAttached("SetProp.GenericProxy");
5214 return AttachDecision::Attach;
5217 AttachDecision SetPropIRGenerator::tryAttachDOMProxyShadowed(
5218 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
5219 ValOperandId rhsId) {
5220 // Don't attach a proxy stub for ops like JSOp::InitElem.
5221 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
5223 MOZ_ASSERT(IsCacheableDOMProxy(obj));
5225 maybeEmitIdGuard(id);
5226 TestMatchingProxyReceiver(writer, obj, objId);
5227 writer.proxySet(objId, id, rhsId, IsStrictSetPC(pc_));
5228 writer.returnFromIC();
5230 trackAttached("SetProp.DOMProxyShadowed");
5231 return AttachDecision::Attach;
5234 AttachDecision SetPropIRGenerator::tryAttachDOMProxyUnshadowed(
5235 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
5236 ValOperandId rhsId) {
5237 // Don't attach a proxy stub for ops like JSOp::InitElem.
5238 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
5240 MOZ_ASSERT(IsCacheableDOMProxy(obj));
5242 JSObject* proto = obj->staticPrototype();
5243 if (!proto) {
5244 return AttachDecision::NoAction;
5247 NativeObject* holder = nullptr;
5248 Maybe<PropertyInfo> prop;
5249 if (!CanAttachSetter(cx_, pc_, proto, id, &holder, &prop)) {
5250 return AttachDecision::NoAction;
5252 auto* nproto = &proto->as<NativeObject>();
5254 maybeEmitIdGuard(id);
5256 // Guard that our proxy (expando) object hasn't started shadowing this
5257 // property.
5258 TestMatchingProxyReceiver(writer, obj, objId);
5259 bool canOptimizeMissing = false;
5260 CheckDOMProxyDoesNotShadow(writer, obj, id, objId, &canOptimizeMissing);
5262 GeneratePrototypeGuards(writer, obj, holder, objId);
5264 // Guard on the holder of the property.
5265 ObjOperandId holderId = writer.loadObject(holder);
5266 TestMatchingHolder(writer, holder, holderId);
5268 EmitGuardGetterSetterSlot(writer, holder, *prop, holderId,
5269 /* holderIsConstant = */ true);
5271 // EmitCallSetterNoGuards expects |obj| to be the object the property is
5272 // on to do some checks. Since we actually looked at proto, and no extra
5273 // guards will be generated, we can just pass that instead.
5274 EmitCallSetterNoGuards(cx_, writer, nproto, holder, *prop, objId, rhsId);
5276 trackAttached("SetProp.DOMProxyUnshadowed");
5277 return AttachDecision::Attach;
5280 AttachDecision SetPropIRGenerator::tryAttachDOMProxyExpando(
5281 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
5282 ValOperandId rhsId) {
5283 // Don't attach a proxy stub for ops like JSOp::InitElem.
5284 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
5286 MOZ_ASSERT(IsCacheableDOMProxy(obj));
5288 Value expandoVal = GetProxyPrivate(obj);
5289 JSObject* expandoObj;
5290 if (expandoVal.isObject()) {
5291 expandoObj = &expandoVal.toObject();
5292 } else {
5293 MOZ_ASSERT(!expandoVal.isUndefined(),
5294 "How did a missing expando manage to shadow things?");
5295 auto expandoAndGeneration =
5296 static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
5297 MOZ_ASSERT(expandoAndGeneration);
5298 expandoObj = &expandoAndGeneration->expando.toObject();
5301 Maybe<PropertyInfo> prop;
5302 if (CanAttachNativeSetSlot(JSOp(*pc_), expandoObj, id, &prop)) {
5303 auto* nativeExpandoObj = &expandoObj->as<NativeObject>();
5305 maybeEmitIdGuard(id);
5306 ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape(
5307 obj, objId, expandoVal, nativeExpandoObj);
5309 EmitStoreSlotAndReturn(writer, expandoObjId, nativeExpandoObj, *prop,
5310 rhsId);
5311 trackAttached("SetProp.DOMProxyExpandoSlot");
5312 return AttachDecision::Attach;
5315 NativeObject* holder = nullptr;
5316 if (CanAttachSetter(cx_, pc_, expandoObj, id, &holder, &prop)) {
5317 auto* nativeExpandoObj = &expandoObj->as<NativeObject>();
5319 // Call the setter. Note that we pass objId, the DOM proxy, as |this|
5320 // and not the expando object.
5321 maybeEmitIdGuard(id);
5322 ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape(
5323 obj, objId, expandoVal, nativeExpandoObj);
5325 MOZ_ASSERT(holder == nativeExpandoObj);
5326 EmitGuardGetterSetterSlot(writer, nativeExpandoObj, *prop, expandoObjId);
5327 EmitCallSetterNoGuards(cx_, writer, nativeExpandoObj, nativeExpandoObj,
5328 *prop, objId, rhsId);
5329 trackAttached("SetProp.DOMProxyExpandoSetter");
5330 return AttachDecision::Attach;
5333 return AttachDecision::NoAction;
5336 AttachDecision SetPropIRGenerator::tryAttachProxy(HandleObject obj,
5337 ObjOperandId objId,
5338 HandleId id,
5339 ValOperandId rhsId) {
5340 // Don't attach a proxy stub for ops like JSOp::InitElem.
5341 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
5343 ProxyStubType type = GetProxyStubType(cx_, obj, id);
5344 if (type == ProxyStubType::None) {
5345 return AttachDecision::NoAction;
5347 auto proxy = obj.as<ProxyObject>();
5349 if (mode_ == ICState::Mode::Megamorphic) {
5350 return tryAttachGenericProxy(proxy, objId, id, rhsId,
5351 /* handleDOMProxies = */ true);
5354 switch (type) {
5355 case ProxyStubType::None:
5356 break;
5357 case ProxyStubType::DOMExpando:
5358 TRY_ATTACH(tryAttachDOMProxyExpando(proxy, objId, id, rhsId));
5359 [[fallthrough]]; // Fall through to the generic shadowed case.
5360 case ProxyStubType::DOMShadowed:
5361 return tryAttachDOMProxyShadowed(proxy, objId, id, rhsId);
5362 case ProxyStubType::DOMUnshadowed:
5363 TRY_ATTACH(tryAttachDOMProxyUnshadowed(proxy, objId, id, rhsId));
5364 return tryAttachGenericProxy(proxy, objId, id, rhsId,
5365 /* handleDOMProxies = */ true);
5366 case ProxyStubType::Generic:
5367 return tryAttachGenericProxy(proxy, objId, id, rhsId,
5368 /* handleDOMProxies = */ false);
5371 MOZ_CRASH("Unexpected ProxyStubType");
5374 AttachDecision SetPropIRGenerator::tryAttachProxyElement(HandleObject obj,
5375 ObjOperandId objId,
5376 ValOperandId rhsId) {
5377 // Don't attach a proxy stub for ops like JSOp::InitElem.
5378 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
5380 if (!obj->is<ProxyObject>()) {
5381 return AttachDecision::NoAction;
5384 writer.guardIsProxy(objId);
5386 // Like GetPropIRGenerator::tryAttachProxyElement, don't check for DOM
5387 // proxies here as we don't have specialized DOM stubs for this.
5388 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
5389 writer.proxySetByValue(objId, setElemKeyValueId(), rhsId, IsStrictSetPC(pc_));
5390 writer.returnFromIC();
5392 trackAttached("SetProp.ProxyElement");
5393 return AttachDecision::Attach;
5396 AttachDecision SetPropIRGenerator::tryAttachMegamorphicSetElement(
5397 HandleObject obj, ObjOperandId objId, ValOperandId rhsId) {
5398 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
5400 if (mode_ != ICState::Mode::Megamorphic || cacheKind_ != CacheKind::SetElem) {
5401 return AttachDecision::NoAction;
5404 // The generic proxy stubs are faster.
5405 if (obj->is<ProxyObject>()) {
5406 return AttachDecision::NoAction;
5409 writer.megamorphicSetElement(objId, setElemKeyValueId(), rhsId,
5410 IsStrictSetPC(pc_));
5411 writer.returnFromIC();
5413 trackAttached("SetProp.MegamorphicSetElement");
5414 return AttachDecision::Attach;
5417 AttachDecision SetPropIRGenerator::tryAttachMegamorphicSetSlot(
5418 HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) {
5419 if (mode_ != ICState::Mode::Megamorphic || cacheKind_ != CacheKind::SetProp) {
5420 return AttachDecision::NoAction;
5423 writer.megamorphicStoreSlot(objId, id, rhsId, IsStrictSetPC(pc_));
5424 writer.returnFromIC();
5425 trackAttached("SetProp.MegamorphicNativeSlot");
5426 return AttachDecision::Attach;
5429 AttachDecision SetPropIRGenerator::tryAttachWindowProxy(HandleObject obj,
5430 ObjOperandId objId,
5431 HandleId id,
5432 ValOperandId rhsId) {
5433 // Don't attach a window proxy stub for ops like JSOp::InitElem.
5434 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
5436 // Attach a stub when the receiver is a WindowProxy and we can do the set
5437 // on the Window (the global object).
5439 if (!IsWindowProxyForScriptGlobal(script_, obj)) {
5440 return AttachDecision::NoAction;
5443 // If we're megamorphic prefer a generic proxy stub that handles a lot more
5444 // cases.
5445 if (mode_ == ICState::Mode::Megamorphic) {
5446 return AttachDecision::NoAction;
5449 // Now try to do the set on the Window (the current global).
5450 GlobalObject* windowObj = cx_->global();
5452 Maybe<PropertyInfo> prop;
5453 if (!CanAttachNativeSetSlot(JSOp(*pc_), windowObj, id, &prop)) {
5454 return AttachDecision::NoAction;
5457 maybeEmitIdGuard(id);
5459 ObjOperandId windowObjId =
5460 GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
5461 writer.guardShape(windowObjId, windowObj->shape());
5463 EmitStoreSlotAndReturn(writer, windowObjId, windowObj, *prop, rhsId);
5465 trackAttached("SetProp.WindowProxySlot");
5466 return AttachDecision::Attach;
5469 // Detect if |id| refers to the 'prototype' property of a function object. This
5470 // property is special-cased in canAttachAddSlotStub().
5471 static bool IsFunctionPrototype(const JSAtomState& names, JSObject* obj,
5472 PropertyKey id) {
5473 return obj->is<JSFunction>() && id.isAtom(names.prototype);
5476 bool SetPropIRGenerator::canAttachAddSlotStub(HandleObject obj, HandleId id) {
5477 if (!obj->is<NativeObject>()) {
5478 return false;
5480 auto* nobj = &obj->as<NativeObject>();
5482 // Special-case JSFunction resolve hook to allow redefining the 'prototype'
5483 // property without triggering lazy expansion of property and object
5484 // allocation.
5485 if (IsFunctionPrototype(cx_->names(), nobj, id)) {
5486 MOZ_ASSERT(ClassMayResolveId(cx_->names(), nobj->getClass(), id, nobj));
5488 // We're only interested in functions that have a builtin .prototype
5489 // property (needsPrototypeProperty). The stub will guard on this because
5490 // the builtin .prototype property is non-configurable/non-enumerable and it
5491 // would be wrong to add a property with those attributes to a function that
5492 // doesn't have a builtin .prototype.
5494 // Inlining needsPrototypeProperty in JIT code is complicated so we use
5495 // isNonBuiltinConstructor as a stronger condition that's easier to check
5496 // from JIT code.
5497 JSFunction* fun = &nobj->as<JSFunction>();
5498 if (!fun->isNonBuiltinConstructor()) {
5499 return false;
5501 MOZ_ASSERT(fun->needsPrototypeProperty());
5503 // If property exists this isn't an "add".
5504 if (fun->lookupPure(id)) {
5505 return false;
5507 } else {
5508 // Normal Case: If property exists this isn't an "add"
5509 PropertyResult prop;
5510 if (!LookupOwnPropertyPure(cx_, nobj, id, &prop)) {
5511 return false;
5513 if (prop.isFound()) {
5514 return false;
5518 // For now we don't optimize Watchtower-monitored objects.
5519 if (Watchtower::watchesPropertyAdd(nobj)) {
5520 return false;
5523 // Object must be extensible, or we must be initializing a private
5524 // elem.
5525 bool canAddNewProperty = nobj->isExtensible() || id.isPrivateName();
5526 if (!canAddNewProperty) {
5527 return false;
5530 JSOp op = JSOp(*pc_);
5531 if (IsPropertyInitOp(op)) {
5532 return true;
5535 MOZ_ASSERT(IsPropertySetOp(op));
5537 // Walk up the object prototype chain and ensure that all prototypes are
5538 // native, and that all prototypes have no setter defined on the property.
5539 for (JSObject* proto = nobj->staticPrototype(); proto;
5540 proto = proto->staticPrototype()) {
5541 if (!proto->is<NativeObject>()) {
5542 return false;
5545 // If prototype defines this property in a non-plain way, don't optimize.
5546 Maybe<PropertyInfo> protoProp = proto->as<NativeObject>().lookup(cx_, id);
5547 if (protoProp.isSome() && !protoProp->isDataProperty()) {
5548 return false;
5551 // Otherwise, if there's no such property, watch out for a resolve hook
5552 // that would need to be invoked and thus prevent inlining of property
5553 // addition. Allow the JSFunction resolve hook as it only defines plain
5554 // data properties and we don't need to invoke it for objects on the
5555 // proto chain.
5556 if (ClassMayResolveId(cx_->names(), proto->getClass(), id, proto) &&
5557 !proto->is<JSFunction>()) {
5558 return false;
5562 return true;
5565 static PropertyFlags SetPropertyFlags(JSOp op, bool isFunctionPrototype) {
5566 // Locked properties are non-writable, non-enumerable, and non-configurable.
5567 if (IsLockedInitOp(op)) {
5568 return {};
5571 // Hidden properties are writable, non-enumerable, and configurable.
5572 if (IsHiddenInitOp(op)) {
5573 return {
5574 PropertyFlag::Writable,
5575 PropertyFlag::Configurable,
5579 // This is a special case to overwrite an unresolved function.prototype
5580 // property. The initial property flags of this property are writable,
5581 // non-enumerable, and non-configurable. See canAttachAddSlotStub.
5582 if (isFunctionPrototype) {
5583 return {
5584 PropertyFlag::Writable,
5588 // Other properties are writable, enumerable, and configurable.
5589 return PropertyFlags::defaultDataPropFlags;
5592 AttachDecision SetPropIRGenerator::tryAttachAddSlotStub(
5593 Handle<Shape*> oldShape) {
5594 ValOperandId objValId(writer.setInputOperandId(0));
5595 ValOperandId rhsValId;
5596 if (cacheKind_ == CacheKind::SetProp) {
5597 rhsValId = ValOperandId(writer.setInputOperandId(1));
5598 } else {
5599 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
5600 MOZ_ASSERT(setElemKeyValueId().id() == 1);
5601 writer.setInputOperandId(1);
5602 rhsValId = ValOperandId(writer.setInputOperandId(2));
5605 RootedId id(cx_);
5606 bool nameOrSymbol;
5607 if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
5608 cx_->clearPendingException();
5609 return AttachDecision::NoAction;
5612 if (!lhsVal_.isObject() || !nameOrSymbol) {
5613 return AttachDecision::NoAction;
5616 JSObject* obj = &lhsVal_.toObject();
5618 PropertyResult prop;
5619 if (!LookupOwnPropertyPure(cx_, obj, id, &prop)) {
5620 return AttachDecision::NoAction;
5622 if (prop.isNotFound()) {
5623 return AttachDecision::NoAction;
5626 if (!obj->is<NativeObject>()) {
5627 return AttachDecision::NoAction;
5629 auto* nobj = &obj->as<NativeObject>();
5631 PropertyInfo propInfo = prop.propertyInfo();
5632 NativeObject* holder = nobj;
5634 if (holder->inDictionaryMode()) {
5635 return AttachDecision::NoAction;
5638 SharedShape* oldSharedShape = &oldShape->asShared();
5640 // The property must be the last added property of the object.
5641 SharedShape* newShape = holder->sharedShape();
5642 MOZ_RELEASE_ASSERT(newShape->lastProperty() == propInfo);
5644 #ifdef DEBUG
5645 // Verify exactly one property was added by comparing the property map
5646 // lengths.
5647 if (oldSharedShape->propMapLength() == PropMap::Capacity) {
5648 MOZ_ASSERT(newShape->propMapLength() == 1);
5649 } else {
5650 MOZ_ASSERT(newShape->propMapLength() ==
5651 oldSharedShape->propMapLength() + 1);
5653 #endif
5655 bool isFunctionPrototype = IsFunctionPrototype(cx_->names(), nobj, id);
5657 JSOp op = JSOp(*pc_);
5658 PropertyFlags flags = SetPropertyFlags(op, isFunctionPrototype);
5660 // Basic property checks.
5661 if (!propInfo.isDataProperty() || propInfo.flags() != flags) {
5662 return AttachDecision::NoAction;
5665 ObjOperandId objId = writer.guardToObject(objValId);
5666 maybeEmitIdGuard(id);
5668 // Shape guard the object.
5669 writer.guardShape(objId, oldShape);
5671 // If this is the special function.prototype case, we need to guard the
5672 // function is a non-builtin constructor. See canAttachAddSlotStub.
5673 if (isFunctionPrototype) {
5674 MOZ_ASSERT(nobj->as<JSFunction>().isNonBuiltinConstructor());
5675 writer.guardFunctionIsNonBuiltinCtor(objId);
5678 // Also shape guard the proto chain, unless this is an InitElem.
5679 if (IsPropertySetOp(op)) {
5680 ShapeGuardProtoChain(writer, nobj, objId);
5683 // If the JSClass has an addProperty hook, we need to call a VM function to
5684 // invoke this hook. Ignore the Array addProperty hook, because it doesn't do
5685 // anything for non-index properties.
5686 DebugOnly<uint32_t> index;
5687 MOZ_ASSERT_IF(obj->is<ArrayObject>(), !IdIsIndex(id, &index));
5688 bool mustCallAddPropertyHook =
5689 obj->getClass()->getAddProperty() && !obj->is<ArrayObject>();
5691 if (mustCallAddPropertyHook) {
5692 writer.addSlotAndCallAddPropHook(objId, rhsValId, newShape);
5693 trackAttached("SetProp.AddSlotWithAddPropertyHook");
5694 } else if (holder->isFixedSlot(propInfo.slot())) {
5695 size_t offset = NativeObject::getFixedSlotOffset(propInfo.slot());
5696 writer.addAndStoreFixedSlot(objId, offset, rhsValId, newShape);
5697 trackAttached("SetProp.AddSlotFixed");
5698 } else {
5699 size_t offset = holder->dynamicSlotIndex(propInfo.slot()) * sizeof(Value);
5700 uint32_t numOldSlots = NativeObject::calculateDynamicSlots(oldSharedShape);
5701 uint32_t numNewSlots = holder->numDynamicSlots();
5702 if (numOldSlots == numNewSlots) {
5703 writer.addAndStoreDynamicSlot(objId, offset, rhsValId, newShape);
5704 trackAttached("SetProp.AddSlotDynamic");
5705 } else {
5706 MOZ_ASSERT(numNewSlots > numOldSlots);
5707 writer.allocateAndStoreDynamicSlot(objId, offset, rhsValId, newShape,
5708 numNewSlots);
5709 trackAttached("SetProp.AllocateSlot");
5712 writer.returnFromIC();
5714 return AttachDecision::Attach;
5717 InstanceOfIRGenerator::InstanceOfIRGenerator(JSContext* cx, HandleScript script,
5718 jsbytecode* pc, ICState state,
5719 HandleValue lhs, HandleObject rhs)
5720 : IRGenerator(cx, script, pc, CacheKind::InstanceOf, state),
5721 lhsVal_(lhs),
5722 rhsObj_(rhs) {}
5724 AttachDecision InstanceOfIRGenerator::tryAttachStub() {
5725 MOZ_ASSERT(cacheKind_ == CacheKind::InstanceOf);
5726 AutoAssertNoPendingException aanpe(cx_);
5728 // Ensure RHS is a function -- could be a Proxy, which the IC isn't prepared
5729 // to handle.
5730 if (!rhsObj_->is<JSFunction>()) {
5731 trackAttached(IRGenerator::NotAttached);
5732 return AttachDecision::NoAction;
5735 HandleFunction fun = rhsObj_.as<JSFunction>();
5737 // Look up the @@hasInstance property, and check that Function.__proto__ is
5738 // the property holder, and that no object further down the prototype chain
5739 // (including this function) has shadowed it; together with the fact that
5740 // Function.__proto__[@@hasInstance] is immutable, this ensures that the
5741 // hasInstance hook will not change without the need to guard on the actual
5742 // property value.
5743 PropertyResult hasInstanceProp;
5744 NativeObject* hasInstanceHolder = nullptr;
5745 jsid hasInstanceID = PropertyKey::Symbol(cx_->wellKnownSymbols().hasInstance);
5746 if (!LookupPropertyPure(cx_, fun, hasInstanceID, &hasInstanceHolder,
5747 &hasInstanceProp) ||
5748 !hasInstanceProp.isNativeProperty()) {
5749 trackAttached(IRGenerator::NotAttached);
5750 return AttachDecision::NoAction;
5753 JSObject& funProto = cx_->global()->getPrototype(JSProto_Function);
5754 if (hasInstanceHolder != &funProto) {
5755 trackAttached(IRGenerator::NotAttached);
5756 return AttachDecision::NoAction;
5759 // If the above succeeded, then these should be true about @@hasInstance,
5760 // because the property on Function.__proto__ is an immutable data property:
5761 MOZ_ASSERT(hasInstanceProp.propertyInfo().isDataProperty());
5762 MOZ_ASSERT(!hasInstanceProp.propertyInfo().configurable());
5763 MOZ_ASSERT(!hasInstanceProp.propertyInfo().writable());
5765 MOZ_ASSERT(IsCacheableProtoChain(fun, hasInstanceHolder));
5767 // Ensure that the function's prototype slot is the same.
5768 Maybe<PropertyInfo> prop = fun->lookupPure(cx_->names().prototype);
5769 if (prop.isNothing() || !prop->isDataProperty()) {
5770 trackAttached(IRGenerator::NotAttached);
5771 return AttachDecision::NoAction;
5774 uint32_t slot = prop->slot();
5775 MOZ_ASSERT(slot >= fun->numFixedSlots(), "Stub code relies on this");
5776 if (!fun->getSlot(slot).isObject()) {
5777 trackAttached(IRGenerator::NotAttached);
5778 return AttachDecision::NoAction;
5781 // Abstract Objects
5782 ValOperandId lhs(writer.setInputOperandId(0));
5783 ValOperandId rhs(writer.setInputOperandId(1));
5785 ObjOperandId rhsId = writer.guardToObject(rhs);
5786 writer.guardShape(rhsId, fun->shape());
5788 // Ensure that the shapes up the prototype chain for the RHS remain the same
5789 // so that @@hasInstance is not shadowed by some intermediate prototype
5790 // object.
5791 if (hasInstanceHolder != fun) {
5792 GeneratePrototypeGuards(writer, fun, hasInstanceHolder, rhsId);
5793 ObjOperandId holderId = writer.loadObject(hasInstanceHolder);
5794 TestMatchingHolder(writer, hasInstanceHolder, holderId);
5797 // Load the .prototype value and ensure it's an object.
5798 ValOperandId protoValId =
5799 writer.loadDynamicSlot(rhsId, slot - fun->numFixedSlots());
5800 ObjOperandId protoId = writer.guardToObject(protoValId);
5802 // Needn't guard LHS is object, because the actual stub can handle that
5803 // and correctly return false.
5804 writer.loadInstanceOfObjectResult(lhs, protoId);
5805 writer.returnFromIC();
5806 trackAttached("InstanceOf");
5807 return AttachDecision::Attach;
5810 void InstanceOfIRGenerator::trackAttached(const char* name) {
5811 stubName_ = name ? name : "NotAttached";
5812 #ifdef JS_CACHEIR_SPEW
5813 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
5814 sp.valueProperty("lhs", lhsVal_);
5815 sp.valueProperty("rhs", ObjectValue(*rhsObj_));
5817 #else
5818 // Silence Clang -Wunused-private-field warning.
5819 (void)lhsVal_;
5820 #endif
5823 TypeOfIRGenerator::TypeOfIRGenerator(JSContext* cx, HandleScript script,
5824 jsbytecode* pc, ICState state,
5825 HandleValue value)
5826 : IRGenerator(cx, script, pc, CacheKind::TypeOf, state), val_(value) {}
5828 void TypeOfIRGenerator::trackAttached(const char* name) {
5829 stubName_ = name ? name : "NotAttached";
5830 #ifdef JS_CACHEIR_SPEW
5831 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
5832 sp.valueProperty("val", val_);
5834 #endif
5837 AttachDecision TypeOfIRGenerator::tryAttachStub() {
5838 MOZ_ASSERT(cacheKind_ == CacheKind::TypeOf);
5840 AutoAssertNoPendingException aanpe(cx_);
5842 ValOperandId valId(writer.setInputOperandId(0));
5844 TRY_ATTACH(tryAttachPrimitive(valId));
5845 TRY_ATTACH(tryAttachObject(valId));
5847 MOZ_ASSERT_UNREACHABLE("Failed to attach TypeOf");
5848 return AttachDecision::NoAction;
5851 AttachDecision TypeOfIRGenerator::tryAttachPrimitive(ValOperandId valId) {
5852 if (!val_.isPrimitive()) {
5853 return AttachDecision::NoAction;
5856 // Note: we don't use GuardIsNumber for int32 values because it's less
5857 // efficient in Warp (unboxing to double instead of int32).
5858 if (val_.isDouble()) {
5859 writer.guardIsNumber(valId);
5860 } else {
5861 writer.guardNonDoubleType(valId, val_.type());
5864 writer.loadConstantStringResult(
5865 TypeName(js::TypeOfValue(val_), cx_->names()));
5866 writer.returnFromIC();
5867 writer.setTypeData(TypeData(JSValueType(val_.type())));
5868 trackAttached("TypeOf.Primitive");
5869 return AttachDecision::Attach;
5872 AttachDecision TypeOfIRGenerator::tryAttachObject(ValOperandId valId) {
5873 if (!val_.isObject()) {
5874 return AttachDecision::NoAction;
5877 ObjOperandId objId = writer.guardToObject(valId);
5878 writer.loadTypeOfObjectResult(objId);
5879 writer.returnFromIC();
5880 writer.setTypeData(TypeData(JSValueType(val_.type())));
5881 trackAttached("TypeOf.Object");
5882 return AttachDecision::Attach;
5885 TypeOfEqIRGenerator::TypeOfEqIRGenerator(JSContext* cx, HandleScript script,
5886 jsbytecode* pc, ICState state,
5887 HandleValue value, JSType type,
5888 JSOp compareOp)
5889 : IRGenerator(cx, script, pc, CacheKind::TypeOfEq, state),
5890 val_(value),
5891 type_(type),
5892 compareOp_(compareOp) {}
5894 void TypeOfEqIRGenerator::trackAttached(const char* name) {
5895 stubName_ = name ? name : "NotAttached";
5896 #ifdef JS_CACHEIR_SPEW
5897 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
5898 sp.valueProperty("val", val_);
5899 sp.jstypeProperty("type", type_);
5900 sp.opcodeProperty("compareOp", compareOp_);
5902 #endif
5905 AttachDecision TypeOfEqIRGenerator::tryAttachStub() {
5906 MOZ_ASSERT(cacheKind_ == CacheKind::TypeOfEq);
5908 AutoAssertNoPendingException aanpe(cx_);
5910 ValOperandId valId(writer.setInputOperandId(0));
5912 TRY_ATTACH(tryAttachPrimitive(valId));
5913 TRY_ATTACH(tryAttachObject(valId));
5915 MOZ_ASSERT_UNREACHABLE("Failed to attach TypeOfEq");
5916 return AttachDecision::NoAction;
5919 AttachDecision TypeOfEqIRGenerator::tryAttachPrimitive(ValOperandId valId) {
5920 if (!val_.isPrimitive()) {
5921 return AttachDecision::NoAction;
5924 // Note: we don't use GuardIsNumber for int32 values because it's less
5925 // efficient in Warp (unboxing to double instead of int32).
5926 if (val_.isDouble()) {
5927 writer.guardIsNumber(valId);
5928 } else {
5929 writer.guardNonDoubleType(valId, val_.type());
5932 bool result = js::TypeOfValue(val_) == type_;
5933 if (compareOp_ == JSOp::Ne) {
5934 result = !result;
5936 writer.loadBooleanResult(result);
5937 writer.returnFromIC();
5938 writer.setTypeData(TypeData(JSValueType(val_.type())));
5939 trackAttached("TypeOfEq.Primitive");
5940 return AttachDecision::Attach;
5943 AttachDecision TypeOfEqIRGenerator::tryAttachObject(ValOperandId valId) {
5944 if (!val_.isObject()) {
5945 return AttachDecision::NoAction;
5948 ObjOperandId objId = writer.guardToObject(valId);
5949 writer.loadTypeOfEqObjectResult(objId, TypeofEqOperand(type_, compareOp_));
5950 writer.returnFromIC();
5951 writer.setTypeData(TypeData(JSValueType(val_.type())));
5952 trackAttached("TypeOfEq.Object");
5953 return AttachDecision::Attach;
5956 GetIteratorIRGenerator::GetIteratorIRGenerator(JSContext* cx,
5957 HandleScript script,
5958 jsbytecode* pc, ICState state,
5959 HandleValue value)
5960 : IRGenerator(cx, script, pc, CacheKind::GetIterator, state), val_(value) {}
5962 AttachDecision GetIteratorIRGenerator::tryAttachStub() {
5963 MOZ_ASSERT(cacheKind_ == CacheKind::GetIterator);
5965 AutoAssertNoPendingException aanpe(cx_);
5967 ValOperandId valId(writer.setInputOperandId(0));
5969 TRY_ATTACH(tryAttachObject(valId));
5970 TRY_ATTACH(tryAttachNullOrUndefined(valId));
5971 TRY_ATTACH(tryAttachGeneric(valId));
5973 trackAttached(IRGenerator::NotAttached);
5974 return AttachDecision::NoAction;
5977 AttachDecision GetIteratorIRGenerator::tryAttachObject(ValOperandId valId) {
5978 if (!val_.isObject()) {
5979 return AttachDecision::NoAction;
5982 MOZ_ASSERT(val_.toObject().compartment() == cx_->compartment());
5984 ObjOperandId objId = writer.guardToObject(valId);
5985 writer.objectToIteratorResult(objId, cx_->compartment()->enumeratorsAddr());
5986 writer.returnFromIC();
5988 trackAttached("GetIterator.Object");
5989 return AttachDecision::Attach;
5992 AttachDecision GetIteratorIRGenerator::tryAttachNullOrUndefined(
5993 ValOperandId valId) {
5994 MOZ_ASSERT(JSOp(*pc_) == JSOp::Iter);
5996 // For null/undefined we can simply return the empty iterator singleton. This
5997 // works because this iterator is unlinked and immutable.
5999 if (!val_.isNullOrUndefined()) {
6000 return AttachDecision::NoAction;
6003 PropertyIteratorObject* emptyIter =
6004 GlobalObject::getOrCreateEmptyIterator(cx_);
6005 if (!emptyIter) {
6006 cx_->recoverFromOutOfMemory();
6007 return AttachDecision::NoAction;
6010 writer.guardIsNullOrUndefined(valId);
6012 ObjOperandId iterId = writer.loadObject(emptyIter);
6013 writer.loadObjectResult(iterId);
6014 writer.returnFromIC();
6016 trackAttached("GetIterator.NullOrUndefined");
6017 return AttachDecision::Attach;
6020 AttachDecision GetIteratorIRGenerator::tryAttachGeneric(ValOperandId valId) {
6021 writer.valueToIteratorResult(valId);
6022 writer.returnFromIC();
6024 trackAttached("GetIterator.Generic");
6025 return AttachDecision::Attach;
6028 void GetIteratorIRGenerator::trackAttached(const char* name) {
6029 stubName_ = name ? name : "NotAttached";
6030 #ifdef JS_CACHEIR_SPEW
6031 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
6032 sp.valueProperty("val", val_);
6034 #endif
6037 OptimizeSpreadCallIRGenerator::OptimizeSpreadCallIRGenerator(
6038 JSContext* cx, HandleScript script, jsbytecode* pc, ICState state,
6039 HandleValue value)
6040 : IRGenerator(cx, script, pc, CacheKind::OptimizeSpreadCall, state),
6041 val_(value) {}
6043 AttachDecision OptimizeSpreadCallIRGenerator::tryAttachStub() {
6044 MOZ_ASSERT(cacheKind_ == CacheKind::OptimizeSpreadCall);
6046 AutoAssertNoPendingException aanpe(cx_);
6048 TRY_ATTACH(tryAttachArray());
6049 TRY_ATTACH(tryAttachArguments());
6050 TRY_ATTACH(tryAttachNotOptimizable());
6052 trackAttached(IRGenerator::NotAttached);
6053 return AttachDecision::NoAction;
6056 static bool IsArrayInstanceOptimizable(JSContext* cx, Handle<ArrayObject*> arr,
6057 MutableHandle<NativeObject*> arrProto) {
6058 // Prototype must be Array.prototype.
6059 auto* proto = cx->global()->maybeGetArrayPrototype();
6060 if (!proto || arr->staticPrototype() != proto) {
6061 return false;
6063 arrProto.set(proto);
6065 // The object must not have an own @@iterator property.
6066 PropertyKey iteratorKey =
6067 PropertyKey::Symbol(cx->wellKnownSymbols().iterator);
6068 return !arr->lookupPure(iteratorKey);
6071 static bool IsArrayPrototypeOptimizable(JSContext* cx, Handle<ArrayObject*> arr,
6072 Handle<NativeObject*> arrProto,
6073 uint32_t* slot,
6074 MutableHandle<JSFunction*> iterFun) {
6075 PropertyKey iteratorKey =
6076 PropertyKey::Symbol(cx->wellKnownSymbols().iterator);
6077 // Ensure that Array.prototype's @@iterator slot is unchanged.
6078 Maybe<PropertyInfo> prop = arrProto->lookupPure(iteratorKey);
6079 if (prop.isNothing() || !prop->isDataProperty()) {
6080 return false;
6083 *slot = prop->slot();
6084 MOZ_ASSERT(arrProto->numFixedSlots() == 0, "Stub code relies on this");
6086 const Value& iterVal = arrProto->getSlot(*slot);
6087 if (!iterVal.isObject() || !iterVal.toObject().is<JSFunction>()) {
6088 return false;
6091 iterFun.set(&iterVal.toObject().as<JSFunction>());
6092 return IsSelfHostedFunctionWithName(iterFun, cx->names().dollar_ArrayValues_);
6095 enum class AllowIteratorReturn : bool {
6097 Yes,
6099 static bool IsArrayIteratorPrototypeOptimizable(
6100 JSContext* cx, AllowIteratorReturn allowReturn,
6101 MutableHandle<NativeObject*> arrIterProto, uint32_t* slot,
6102 MutableHandle<JSFunction*> nextFun) {
6103 NativeObject* proto = nullptr;
6105 AutoEnterOOMUnsafeRegion oom;
6106 proto = GlobalObject::getOrCreateArrayIteratorPrototype(cx, cx->global());
6107 if (!proto) {
6108 oom.crash("failed to allocate Array iterator prototype");
6111 arrIterProto.set(proto);
6113 // Ensure that %ArrayIteratorPrototype%'s "next" slot is unchanged.
6114 Maybe<PropertyInfo> prop = proto->lookupPure(cx->names().next);
6115 if (prop.isNothing() || !prop->isDataProperty()) {
6116 return false;
6119 *slot = prop->slot();
6120 MOZ_ASSERT(proto->numFixedSlots() == 0, "Stub code relies on this");
6122 const Value& nextVal = proto->getSlot(*slot);
6123 if (!nextVal.isObject() || !nextVal.toObject().is<JSFunction>()) {
6124 return false;
6127 nextFun.set(&nextVal.toObject().as<JSFunction>());
6128 if (!IsSelfHostedFunctionWithName(nextFun, cx->names().ArrayIteratorNext)) {
6129 return false;
6132 if (allowReturn == AllowIteratorReturn::No) {
6133 // Ensure that %ArrayIteratorPrototype% doesn't define "return".
6134 if (!CheckHasNoSuchProperty(cx, proto, NameToId(cx->names().return_))) {
6135 return false;
6139 return true;
6142 AttachDecision OptimizeSpreadCallIRGenerator::tryAttachArray() {
6143 if (!isFirstStub_) {
6144 return AttachDecision::NoAction;
6147 // The value must be a packed array.
6148 if (!val_.isObject()) {
6149 return AttachDecision::NoAction;
6151 Rooted<JSObject*> obj(cx_, &val_.toObject());
6152 if (!IsPackedArray(obj)) {
6153 return AttachDecision::NoAction;
6156 // Prototype must be Array.prototype and Array.prototype[@@iterator] must not
6157 // be modified.
6158 Rooted<NativeObject*> arrProto(cx_);
6159 uint32_t arrProtoIterSlot;
6160 Rooted<JSFunction*> iterFun(cx_);
6161 if (!IsArrayInstanceOptimizable(cx_, obj.as<ArrayObject>(), &arrProto)) {
6162 return AttachDecision::NoAction;
6165 if (!IsArrayPrototypeOptimizable(cx_, obj.as<ArrayObject>(), arrProto,
6166 &arrProtoIterSlot, &iterFun)) {
6167 return AttachDecision::NoAction;
6170 // %ArrayIteratorPrototype%.next must not be modified.
6171 Rooted<NativeObject*> arrayIteratorProto(cx_);
6172 uint32_t iterNextSlot;
6173 Rooted<JSFunction*> nextFun(cx_);
6174 if (!IsArrayIteratorPrototypeOptimizable(cx_, AllowIteratorReturn::Yes,
6175 &arrayIteratorProto, &iterNextSlot,
6176 &nextFun)) {
6177 return AttachDecision::NoAction;
6180 ValOperandId valId(writer.setInputOperandId(0));
6181 ObjOperandId objId = writer.guardToObject(valId);
6183 // Guard the object is a packed array with Array.prototype as proto.
6184 MOZ_ASSERT(obj->is<ArrayObject>());
6185 writer.guardShape(objId, obj->shape());
6186 writer.guardArrayIsPacked(objId);
6188 // Guard on Array.prototype[@@iterator].
6189 ObjOperandId arrProtoId = writer.loadObject(arrProto);
6190 ObjOperandId iterId = writer.loadObject(iterFun);
6191 writer.guardShape(arrProtoId, arrProto->shape());
6192 writer.guardDynamicSlotIsSpecificObject(arrProtoId, iterId, arrProtoIterSlot);
6194 // Guard on %ArrayIteratorPrototype%.next.
6195 ObjOperandId iterProtoId = writer.loadObject(arrayIteratorProto);
6196 ObjOperandId nextId = writer.loadObject(nextFun);
6197 writer.guardShape(iterProtoId, arrayIteratorProto->shape());
6198 writer.guardDynamicSlotIsSpecificObject(iterProtoId, nextId, iterNextSlot);
6200 writer.loadObjectResult(objId);
6201 writer.returnFromIC();
6203 trackAttached("OptimizeSpreadCall.Array");
6204 return AttachDecision::Attach;
6207 AttachDecision OptimizeSpreadCallIRGenerator::tryAttachArguments() {
6208 // The value must be an arguments object.
6209 if (!val_.isObject()) {
6210 return AttachDecision::NoAction;
6212 RootedObject obj(cx_, &val_.toObject());
6213 if (!obj->is<ArgumentsObject>()) {
6214 return AttachDecision::NoAction;
6216 auto args = obj.as<ArgumentsObject>();
6218 // Ensure neither elements, nor the length, nor the iterator has been
6219 // overridden. Also ensure no args are forwarded to allow reading them
6220 // directly from the frame.
6221 if (args->hasOverriddenElement() || args->hasOverriddenLength() ||
6222 args->hasOverriddenIterator() || args->anyArgIsForwarded()) {
6223 return AttachDecision::NoAction;
6226 Rooted<Shape*> shape(cx_, GlobalObject::getArrayShapeWithDefaultProto(cx_));
6227 if (!shape) {
6228 cx_->clearPendingException();
6229 return AttachDecision::NoAction;
6232 Rooted<NativeObject*> arrayIteratorProto(cx_);
6233 uint32_t slot;
6234 Rooted<JSFunction*> nextFun(cx_);
6235 if (!IsArrayIteratorPrototypeOptimizable(cx_, AllowIteratorReturn::Yes,
6236 &arrayIteratorProto, &slot,
6237 &nextFun)) {
6238 return AttachDecision::NoAction;
6241 ValOperandId valId(writer.setInputOperandId(0));
6242 ObjOperandId objId = writer.guardToObject(valId);
6244 if (args->is<MappedArgumentsObject>()) {
6245 writer.guardClass(objId, GuardClassKind::MappedArguments);
6246 } else {
6247 MOZ_ASSERT(args->is<UnmappedArgumentsObject>());
6248 writer.guardClass(objId, GuardClassKind::UnmappedArguments);
6250 uint8_t flags = ArgumentsObject::ELEMENT_OVERRIDDEN_BIT |
6251 ArgumentsObject::LENGTH_OVERRIDDEN_BIT |
6252 ArgumentsObject::ITERATOR_OVERRIDDEN_BIT |
6253 ArgumentsObject::FORWARDED_ARGUMENTS_BIT;
6254 writer.guardArgumentsObjectFlags(objId, flags);
6256 ObjOperandId protoId = writer.loadObject(arrayIteratorProto);
6257 ObjOperandId nextId = writer.loadObject(nextFun);
6259 writer.guardShape(protoId, arrayIteratorProto->shape());
6261 // Ensure that proto[slot] == nextFun.
6262 writer.guardDynamicSlotIsSpecificObject(protoId, nextId, slot);
6264 writer.arrayFromArgumentsObjectResult(objId, shape);
6265 writer.returnFromIC();
6267 trackAttached("OptimizeSpreadCall.Arguments");
6268 return AttachDecision::Attach;
6271 AttachDecision OptimizeSpreadCallIRGenerator::tryAttachNotOptimizable() {
6272 ValOperandId valId(writer.setInputOperandId(0));
6274 writer.loadUndefinedResult();
6275 writer.returnFromIC();
6277 trackAttached("OptimizeSpreadCall.NotOptimizable");
6278 return AttachDecision::Attach;
6281 void OptimizeSpreadCallIRGenerator::trackAttached(const char* name) {
6282 stubName_ = name ? name : "NotAttached";
6283 #ifdef JS_CACHEIR_SPEW
6284 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
6285 sp.valueProperty("val", val_);
6287 #endif
6290 CallIRGenerator::CallIRGenerator(JSContext* cx, HandleScript script,
6291 jsbytecode* pc, JSOp op, ICState state,
6292 BaselineFrame* frame, uint32_t argc,
6293 HandleValue callee, HandleValue thisval,
6294 HandleValue newTarget, HandleValueArray args)
6295 : IRGenerator(cx, script, pc, CacheKind::Call, state, frame),
6296 op_(op),
6297 argc_(argc),
6298 callee_(callee),
6299 thisval_(thisval),
6300 newTarget_(newTarget),
6301 args_(args) {}
6303 void InlinableNativeIRGenerator::emitNativeCalleeGuard() {
6304 // Note: we rely on GuardSpecificFunction to also guard against the same
6305 // native from a different realm.
6306 MOZ_ASSERT(callee_->isNativeWithoutJitEntry());
6308 ObjOperandId calleeObjId;
6309 if (flags_.getArgFormat() == CallFlags::Standard) {
6310 ValOperandId calleeValId =
6311 writer.loadArgumentFixedSlot(ArgumentKind::Callee, argc_, flags_);
6312 calleeObjId = writer.guardToObject(calleeValId);
6313 } else if (flags_.getArgFormat() == CallFlags::Spread) {
6314 ValOperandId calleeValId =
6315 writer.loadArgumentFixedSlot(ArgumentKind::Callee, argc_, flags_);
6316 calleeObjId = writer.guardToObject(calleeValId);
6317 } else if (flags_.getArgFormat() == CallFlags::FunCall) {
6318 MOZ_ASSERT(generator_.writer.numOperandIds() > 0, "argcId is initialized");
6320 Int32OperandId argcId(0);
6321 calleeObjId = generator_.emitFunCallOrApplyGuard(argcId);
6322 } else {
6323 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::FunApplyArray);
6324 MOZ_ASSERT(generator_.writer.numOperandIds() > 0, "argcId is initialized");
6326 Int32OperandId argcId(0);
6327 calleeObjId = generator_.emitFunApplyGuard(argcId);
6330 writer.guardSpecificFunction(calleeObjId, callee_);
6332 // If we're constructing we also need to guard newTarget == callee.
6333 if (flags_.isConstructing()) {
6334 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Standard);
6335 MOZ_ASSERT(&newTarget_.toObject() == callee_);
6337 ValOperandId newTargetValId =
6338 writer.loadArgumentFixedSlot(ArgumentKind::NewTarget, argc_, flags_);
6339 ObjOperandId newTargetObjId = writer.guardToObject(newTargetValId);
6340 writer.guardSpecificFunction(newTargetObjId, callee_);
6344 ObjOperandId InlinableNativeIRGenerator::emitLoadArgsArray() {
6345 if (flags_.getArgFormat() == CallFlags::Spread) {
6346 return writer.loadSpreadArgs();
6349 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::FunApplyArray);
6350 return generator_.emitFunApplyArgsGuard(flags_.getArgFormat()).ref();
6353 void IRGenerator::emitCalleeGuard(ObjOperandId calleeId, JSFunction* callee) {
6354 // Guarding on the callee JSFunction* is most efficient, but doesn't work well
6355 // for lambda clones (multiple functions with the same BaseScript). We guard
6356 // on the function's BaseScript if the callee is scripted and this isn't the
6357 // first IC stub.
6359 // Self-hosted functions are more complicated: top-level functions can be
6360 // relazified using SelfHostedLazyScript and this means they don't have a
6361 // stable BaseScript pointer. These functions are never lambda clones, though,
6362 // so we can just always guard on the JSFunction*. Self-hosted lambdas are
6363 // never relazified so there we use the normal heuristics.
6364 if (isFirstStub_ || !callee->hasBaseScript() ||
6365 (callee->isSelfHostedBuiltin() && !callee->isLambda())) {
6366 writer.guardSpecificFunction(calleeId, callee);
6367 } else {
6368 MOZ_ASSERT_IF(callee->isSelfHostedBuiltin(),
6369 !callee->baseScript()->allowRelazify());
6370 writer.guardClass(calleeId, GuardClassKind::JSFunction);
6371 writer.guardFunctionScript(calleeId, callee->baseScript());
6375 ObjOperandId CallIRGenerator::emitFunCallOrApplyGuard(Int32OperandId argcId) {
6376 JSFunction* callee = &callee_.toObject().as<JSFunction>();
6377 MOZ_ASSERT(callee->native() == fun_call || callee->native() == fun_apply);
6379 // Guard that callee is the |fun_call| or |fun_apply| native function.
6380 ValOperandId calleeValId =
6381 writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId);
6382 ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
6383 writer.guardSpecificFunction(calleeObjId, callee);
6385 // Guard that |this| is an object.
6386 ValOperandId thisValId =
6387 writer.loadArgumentDynamicSlot(ArgumentKind::This, argcId);
6388 return writer.guardToObject(thisValId);
6391 ObjOperandId CallIRGenerator::emitFunCallGuard(Int32OperandId argcId) {
6392 MOZ_ASSERT(callee_.toObject().as<JSFunction>().native() == fun_call);
6394 return emitFunCallOrApplyGuard(argcId);
6397 ObjOperandId CallIRGenerator::emitFunApplyGuard(Int32OperandId argcId) {
6398 MOZ_ASSERT(callee_.toObject().as<JSFunction>().native() == fun_apply);
6400 return emitFunCallOrApplyGuard(argcId);
6403 Maybe<ObjOperandId> CallIRGenerator::emitFunApplyArgsGuard(
6404 CallFlags::ArgFormat format) {
6405 MOZ_ASSERT(argc_ == 2);
6407 ValOperandId argValId =
6408 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
6410 if (format == CallFlags::FunApplyArgsObj) {
6411 ObjOperandId argObjId = writer.guardToObject(argValId);
6412 if (args_[1].toObject().is<MappedArgumentsObject>()) {
6413 writer.guardClass(argObjId, GuardClassKind::MappedArguments);
6414 } else {
6415 MOZ_ASSERT(args_[1].toObject().is<UnmappedArgumentsObject>());
6416 writer.guardClass(argObjId, GuardClassKind::UnmappedArguments);
6418 uint8_t flags = ArgumentsObject::ELEMENT_OVERRIDDEN_BIT |
6419 ArgumentsObject::FORWARDED_ARGUMENTS_BIT;
6420 writer.guardArgumentsObjectFlags(argObjId, flags);
6421 return mozilla::Some(argObjId);
6424 if (format == CallFlags::FunApplyArray) {
6425 ObjOperandId argObjId = writer.guardToObject(argValId);
6426 emitOptimisticClassGuard(argObjId, &args_[1].toObject(),
6427 GuardClassKind::Array);
6428 writer.guardArrayIsPacked(argObjId);
6429 return mozilla::Some(argObjId);
6432 MOZ_ASSERT(format == CallFlags::FunApplyNullUndefined);
6433 writer.guardIsNullOrUndefined(argValId);
6434 return mozilla::Nothing();
6437 AttachDecision InlinableNativeIRGenerator::tryAttachArrayPush() {
6438 // Only optimize on obj.push(val);
6439 if (argc_ != 1 || !thisval_.isObject()) {
6440 return AttachDecision::NoAction;
6443 // Where |obj| is a native array.
6444 JSObject* thisobj = &thisval_.toObject();
6445 if (!thisobj->is<ArrayObject>()) {
6446 return AttachDecision::NoAction;
6449 auto* thisarray = &thisobj->as<ArrayObject>();
6451 // Check for other indexed properties or class hooks.
6452 if (!CanAttachAddElement(thisarray, /* isInit = */ false,
6453 AllowIndexedReceiver::No)) {
6454 return AttachDecision::NoAction;
6457 // Can't add new elements to arrays with non-writable length.
6458 if (!thisarray->lengthIsWritable()) {
6459 return AttachDecision::NoAction;
6462 // Check that array is extensible.
6463 if (!thisarray->isExtensible()) {
6464 return AttachDecision::NoAction;
6467 // Check that the array is completely initialized (no holes).
6468 if (thisarray->getDenseInitializedLength() != thisarray->length()) {
6469 return AttachDecision::NoAction;
6472 MOZ_ASSERT(!thisarray->denseElementsAreFrozen(),
6473 "Extensible arrays should not have frozen elements");
6475 // After this point, we can generate code fine.
6477 // Initialize the input operand.
6478 initializeInputOperand();
6480 // Guard callee is the 'push' native function.
6481 emitNativeCalleeGuard();
6483 // Guard this is an array object.
6484 ValOperandId thisValId =
6485 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
6486 ObjOperandId thisObjId = writer.guardToObject(thisValId);
6488 // Guard that the shape matches.
6489 TestMatchingNativeReceiver(writer, thisarray, thisObjId);
6491 // Guard proto chain shapes.
6492 ShapeGuardProtoChain(writer, thisarray, thisObjId);
6494 // arr.push(x) is equivalent to arr[arr.length] = x for regular arrays.
6495 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6496 writer.arrayPush(thisObjId, argId);
6498 writer.returnFromIC();
6500 trackAttached("ArrayPush");
6501 return AttachDecision::Attach;
6504 AttachDecision InlinableNativeIRGenerator::tryAttachArrayPopShift(
6505 InlinableNative native) {
6506 // Expecting no arguments.
6507 if (argc_ != 0) {
6508 return AttachDecision::NoAction;
6511 // Only optimize if |this| is a packed array.
6512 if (!thisval_.isObject() || !IsPackedArray(&thisval_.toObject())) {
6513 return AttachDecision::NoAction;
6516 // Other conditions:
6518 // * The array length needs to be writable because we're changing it.
6519 // * The array must be extensible. Non-extensible arrays require preserving
6520 // the |initializedLength == capacity| invariant on ObjectElements.
6521 // See NativeObject::shrinkCapacityToInitializedLength.
6522 // This also ensures the elements aren't sealed/frozen.
6523 // * There must not be a for-in iterator for the elements because the IC stub
6524 // does not suppress deleted properties.
6525 ArrayObject* arr = &thisval_.toObject().as<ArrayObject>();
6526 if (!arr->lengthIsWritable() || !arr->isExtensible() ||
6527 arr->denseElementsHaveMaybeInIterationFlag()) {
6528 return AttachDecision::NoAction;
6531 // Initialize the input operand.
6532 initializeInputOperand();
6534 // Guard callee is the 'pop' or 'shift' native function.
6535 emitNativeCalleeGuard();
6537 ValOperandId thisValId =
6538 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
6539 ObjOperandId objId = writer.guardToObject(thisValId);
6540 emitOptimisticClassGuard(objId, arr, GuardClassKind::Array);
6542 if (native == InlinableNative::ArrayPop) {
6543 writer.packedArrayPopResult(objId);
6544 } else {
6545 MOZ_ASSERT(native == InlinableNative::ArrayShift);
6546 writer.packedArrayShiftResult(objId);
6549 writer.returnFromIC();
6551 trackAttached("ArrayPopShift");
6552 return AttachDecision::Attach;
6555 AttachDecision InlinableNativeIRGenerator::tryAttachArrayJoin() {
6556 // Only handle argc <= 1.
6557 if (argc_ > 1) {
6558 return AttachDecision::NoAction;
6561 // Only optimize if |this| is an array.
6562 if (!thisval_.isObject() || !thisval_.toObject().is<ArrayObject>()) {
6563 return AttachDecision::NoAction;
6566 // The separator argument must be a string, if present.
6567 if (argc_ > 0 && !args_[0].isString()) {
6568 return AttachDecision::NoAction;
6571 // IC stub code can handle non-packed array.
6573 // Initialize the input operand.
6574 initializeInputOperand();
6576 // Guard callee is the 'join' native function.
6577 emitNativeCalleeGuard();
6579 // Guard this is an array object.
6580 ValOperandId thisValId =
6581 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
6582 ObjOperandId thisObjId = writer.guardToObject(thisValId);
6583 emitOptimisticClassGuard(thisObjId, &thisval_.toObject(),
6584 GuardClassKind::Array);
6586 StringOperandId sepId;
6587 if (argc_ == 1) {
6588 // If argcount is 1, guard that the argument is a string.
6589 ValOperandId argValId =
6590 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6591 sepId = writer.guardToString(argValId);
6592 } else {
6593 sepId = writer.loadConstantString(cx_->names().comma_);
6596 // Do the join.
6597 writer.arrayJoinResult(thisObjId, sepId);
6599 writer.returnFromIC();
6601 trackAttached("ArrayJoin");
6602 return AttachDecision::Attach;
6605 AttachDecision InlinableNativeIRGenerator::tryAttachArraySlice() {
6606 // Only handle argc <= 2.
6607 if (argc_ > 2) {
6608 return AttachDecision::NoAction;
6611 // Only optimize if |this| is a packed array or an arguments object.
6612 if (!thisval_.isObject()) {
6613 return AttachDecision::NoAction;
6616 bool isPackedArray = IsPackedArray(&thisval_.toObject());
6617 if (!isPackedArray) {
6618 if (!thisval_.toObject().is<ArgumentsObject>()) {
6619 return AttachDecision::NoAction;
6621 auto* args = &thisval_.toObject().as<ArgumentsObject>();
6623 // No elements must have been overridden or deleted.
6624 if (args->hasOverriddenElement()) {
6625 return AttachDecision::NoAction;
6628 // The length property mustn't be overridden.
6629 if (args->hasOverriddenLength()) {
6630 return AttachDecision::NoAction;
6633 // And finally also check that no argument is forwarded.
6634 if (args->anyArgIsForwarded()) {
6635 return AttachDecision::NoAction;
6639 // Arguments for the sliced region must be integers.
6640 if (argc_ > 0 && !args_[0].isInt32()) {
6641 return AttachDecision::NoAction;
6643 if (argc_ > 1 && !args_[1].isInt32()) {
6644 return AttachDecision::NoAction;
6647 JSObject* templateObj = NewDenseFullyAllocatedArray(cx_, 0, TenuredObject);
6648 if (!templateObj) {
6649 cx_->recoverFromOutOfMemory();
6650 return AttachDecision::NoAction;
6653 // Initialize the input operand.
6654 initializeInputOperand();
6656 // Guard callee is the 'slice' native function.
6657 emitNativeCalleeGuard();
6659 ValOperandId thisValId =
6660 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
6661 ObjOperandId objId = writer.guardToObject(thisValId);
6663 if (isPackedArray) {
6664 emitOptimisticClassGuard(objId, &thisval_.toObject(),
6665 GuardClassKind::Array);
6666 } else {
6667 auto* args = &thisval_.toObject().as<ArgumentsObject>();
6669 if (args->is<MappedArgumentsObject>()) {
6670 writer.guardClass(objId, GuardClassKind::MappedArguments);
6671 } else {
6672 MOZ_ASSERT(args->is<UnmappedArgumentsObject>());
6673 writer.guardClass(objId, GuardClassKind::UnmappedArguments);
6676 uint8_t flags = ArgumentsObject::ELEMENT_OVERRIDDEN_BIT |
6677 ArgumentsObject::LENGTH_OVERRIDDEN_BIT |
6678 ArgumentsObject::FORWARDED_ARGUMENTS_BIT;
6679 writer.guardArgumentsObjectFlags(objId, flags);
6682 Int32OperandId int32BeginId;
6683 if (argc_ > 0) {
6684 ValOperandId beginId =
6685 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6686 int32BeginId = writer.guardToInt32(beginId);
6687 } else {
6688 int32BeginId = writer.loadInt32Constant(0);
6691 Int32OperandId int32EndId;
6692 if (argc_ > 1) {
6693 ValOperandId endId =
6694 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
6695 int32EndId = writer.guardToInt32(endId);
6696 } else if (isPackedArray) {
6697 int32EndId = writer.loadInt32ArrayLength(objId);
6698 } else {
6699 int32EndId = writer.loadArgumentsObjectLength(objId);
6702 if (isPackedArray) {
6703 writer.packedArraySliceResult(templateObj, objId, int32BeginId, int32EndId);
6704 } else {
6705 writer.argumentsSliceResult(templateObj, objId, int32BeginId, int32EndId);
6707 writer.returnFromIC();
6709 trackAttached(isPackedArray ? "ArraySlice" : "ArgumentsSlice");
6710 return AttachDecision::Attach;
6713 AttachDecision InlinableNativeIRGenerator::tryAttachArrayIsArray() {
6714 // Need a single argument.
6715 if (argc_ != 1) {
6716 return AttachDecision::NoAction;
6719 // Initialize the input operand.
6720 initializeInputOperand();
6722 // Guard callee is the 'isArray' native function.
6723 emitNativeCalleeGuard();
6725 // Check if the argument is an Array and return result.
6726 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6727 writer.isArrayResult(argId);
6728 writer.returnFromIC();
6730 trackAttached("ArrayIsArray");
6731 return AttachDecision::Attach;
6734 AttachDecision InlinableNativeIRGenerator::tryAttachDataViewGet(
6735 Scalar::Type type) {
6736 // Ensure |this| is a DataViewObject.
6737 if (!thisval_.isObject() || !thisval_.toObject().is<DataViewObject>()) {
6738 return AttachDecision::NoAction;
6741 // Expected arguments: offset (number), optional littleEndian (boolean).
6742 if (argc_ < 1 || argc_ > 2) {
6743 return AttachDecision::NoAction;
6745 int64_t offsetInt64;
6746 if (!ValueIsInt64Index(args_[0], &offsetInt64)) {
6747 return AttachDecision::NoAction;
6749 if (argc_ > 1 && !args_[1].isBoolean()) {
6750 return AttachDecision::NoAction;
6753 auto* dv = &thisval_.toObject().as<DataViewObject>();
6755 // Bounds check the offset.
6756 size_t byteLength = dv->byteLength().valueOr(0);
6757 if (offsetInt64 < 0 || !DataViewObject::offsetIsInBounds(
6758 Scalar::byteSize(type), offsetInt64, byteLength)) {
6759 return AttachDecision::NoAction;
6762 // For getUint32 we let the stub return an Int32 if we have not seen a
6763 // double, to allow better codegen in Warp while avoiding bailout loops.
6764 bool forceDoubleForUint32 = false;
6765 if (type == Scalar::Uint32) {
6766 bool isLittleEndian = argc_ > 1 && args_[1].toBoolean();
6767 uint32_t res = dv->read<uint32_t>(offsetInt64, byteLength, isLittleEndian);
6768 forceDoubleForUint32 = res >= INT32_MAX;
6771 // Initialize the input operand.
6772 initializeInputOperand();
6774 // Guard callee is this DataView native function.
6775 emitNativeCalleeGuard();
6777 // Guard |this| is a DataViewObject.
6778 ValOperandId thisValId =
6779 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
6780 ObjOperandId objId = writer.guardToObject(thisValId);
6782 if (dv->is<FixedLengthDataViewObject>()) {
6783 emitOptimisticClassGuard(objId, &thisval_.toObject(),
6784 GuardClassKind::FixedLengthDataView);
6785 } else {
6786 emitOptimisticClassGuard(objId, &thisval_.toObject(),
6787 GuardClassKind::ResizableDataView);
6790 // Convert offset to intPtr.
6791 ValOperandId offsetId =
6792 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6793 IntPtrOperandId intPtrOffsetId =
6794 guardToIntPtrIndex(args_[0], offsetId, /* supportOOB = */ false);
6796 BooleanOperandId boolLittleEndianId;
6797 if (argc_ > 1) {
6798 ValOperandId littleEndianId =
6799 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
6800 boolLittleEndianId = writer.guardToBoolean(littleEndianId);
6801 } else {
6802 boolLittleEndianId = writer.loadBooleanConstant(false);
6805 auto viewKind = ToArrayBufferViewKind(dv);
6806 writer.loadDataViewValueResult(objId, intPtrOffsetId, boolLittleEndianId,
6807 type, forceDoubleForUint32, viewKind);
6809 writer.returnFromIC();
6811 trackAttached("DataViewGet");
6812 return AttachDecision::Attach;
6815 AttachDecision InlinableNativeIRGenerator::tryAttachDataViewSet(
6816 Scalar::Type type) {
6817 // Ensure |this| is a DataViewObject.
6818 if (!thisval_.isObject() || !thisval_.toObject().is<DataViewObject>()) {
6819 return AttachDecision::NoAction;
6822 // Expected arguments: offset (number), value, optional littleEndian (boolean)
6823 if (argc_ < 2 || argc_ > 3) {
6824 return AttachDecision::NoAction;
6826 int64_t offsetInt64;
6827 if (!ValueIsInt64Index(args_[0], &offsetInt64)) {
6828 return AttachDecision::NoAction;
6830 if (!ValueCanConvertToNumeric(type, args_[1])) {
6831 return AttachDecision::NoAction;
6833 if (argc_ > 2 && !args_[2].isBoolean()) {
6834 return AttachDecision::NoAction;
6837 auto* dv = &thisval_.toObject().as<DataViewObject>();
6839 // Bounds check the offset.
6840 size_t byteLength = dv->byteLength().valueOr(0);
6841 if (offsetInt64 < 0 || !DataViewObject::offsetIsInBounds(
6842 Scalar::byteSize(type), offsetInt64, byteLength)) {
6843 return AttachDecision::NoAction;
6846 // Initialize the input operand.
6847 initializeInputOperand();
6849 // Guard callee is this DataView native function.
6850 emitNativeCalleeGuard();
6852 // Guard |this| is a DataViewObject.
6853 ValOperandId thisValId =
6854 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
6855 ObjOperandId objId = writer.guardToObject(thisValId);
6857 if (dv->is<FixedLengthDataViewObject>()) {
6858 emitOptimisticClassGuard(objId, &thisval_.toObject(),
6859 GuardClassKind::FixedLengthDataView);
6860 } else {
6861 emitOptimisticClassGuard(objId, &thisval_.toObject(),
6862 GuardClassKind::ResizableDataView);
6865 // Convert offset to intPtr.
6866 ValOperandId offsetId =
6867 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6868 IntPtrOperandId intPtrOffsetId =
6869 guardToIntPtrIndex(args_[0], offsetId, /* supportOOB = */ false);
6871 // Convert value to number or BigInt.
6872 ValOperandId valueId =
6873 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
6874 OperandId numericValueId = emitNumericGuard(valueId, args_[1], type);
6876 BooleanOperandId boolLittleEndianId;
6877 if (argc_ > 2) {
6878 ValOperandId littleEndianId =
6879 writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
6880 boolLittleEndianId = writer.guardToBoolean(littleEndianId);
6881 } else {
6882 boolLittleEndianId = writer.loadBooleanConstant(false);
6885 auto viewKind = ToArrayBufferViewKind(dv);
6886 writer.storeDataViewValueResult(objId, intPtrOffsetId, numericValueId,
6887 boolLittleEndianId, type, viewKind);
6889 writer.returnFromIC();
6891 trackAttached("DataViewSet");
6892 return AttachDecision::Attach;
6895 AttachDecision InlinableNativeIRGenerator::tryAttachUnsafeGetReservedSlot(
6896 InlinableNative native) {
6897 // Self-hosted code calls this with (object, int32) arguments.
6898 MOZ_ASSERT(argc_ == 2);
6899 MOZ_ASSERT(args_[0].isObject());
6900 MOZ_ASSERT(args_[1].isInt32());
6901 MOZ_ASSERT(args_[1].toInt32() >= 0);
6903 uint32_t slot = uint32_t(args_[1].toInt32());
6904 if (slot >= NativeObject::MAX_FIXED_SLOTS) {
6905 return AttachDecision::NoAction;
6907 size_t offset = NativeObject::getFixedSlotOffset(slot);
6909 // Initialize the input operand.
6910 initializeInputOperand();
6912 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
6914 // Guard that the first argument is an object.
6915 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6916 ObjOperandId objId = writer.guardToObject(arg0Id);
6918 // BytecodeEmitter::assertSelfHostedUnsafeGetReservedSlot ensures that the
6919 // slot argument is constant. (At least for direct calls)
6921 switch (native) {
6922 case InlinableNative::IntrinsicUnsafeGetReservedSlot:
6923 writer.loadFixedSlotResult(objId, offset);
6924 break;
6925 case InlinableNative::IntrinsicUnsafeGetObjectFromReservedSlot:
6926 writer.loadFixedSlotTypedResult(objId, offset, ValueType::Object);
6927 break;
6928 case InlinableNative::IntrinsicUnsafeGetInt32FromReservedSlot:
6929 writer.loadFixedSlotTypedResult(objId, offset, ValueType::Int32);
6930 break;
6931 case InlinableNative::IntrinsicUnsafeGetStringFromReservedSlot:
6932 writer.loadFixedSlotTypedResult(objId, offset, ValueType::String);
6933 break;
6934 default:
6935 MOZ_CRASH("unexpected native");
6938 writer.returnFromIC();
6940 trackAttached("UnsafeGetReservedSlot");
6941 return AttachDecision::Attach;
6944 AttachDecision InlinableNativeIRGenerator::tryAttachUnsafeSetReservedSlot() {
6945 // Self-hosted code calls this with (object, int32, value) arguments.
6946 MOZ_ASSERT(argc_ == 3);
6947 MOZ_ASSERT(args_[0].isObject());
6948 MOZ_ASSERT(args_[1].isInt32());
6949 MOZ_ASSERT(args_[1].toInt32() >= 0);
6951 uint32_t slot = uint32_t(args_[1].toInt32());
6952 if (slot >= NativeObject::MAX_FIXED_SLOTS) {
6953 return AttachDecision::NoAction;
6955 size_t offset = NativeObject::getFixedSlotOffset(slot);
6957 // Initialize the input operand.
6958 initializeInputOperand();
6960 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
6962 // Guard that the first argument is an object.
6963 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6964 ObjOperandId objId = writer.guardToObject(arg0Id);
6966 // BytecodeEmitter::assertSelfHostedUnsafeSetReservedSlot ensures that the
6967 // slot argument is constant. (At least for direct calls)
6969 // Get the value to set.
6970 ValOperandId valId = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
6972 // Set the fixed slot and return undefined.
6973 writer.storeFixedSlotUndefinedResult(objId, offset, valId);
6975 // This stub always returns undefined.
6976 writer.returnFromIC();
6978 trackAttached("UnsafeSetReservedSlot");
6979 return AttachDecision::Attach;
6982 AttachDecision InlinableNativeIRGenerator::tryAttachIsSuspendedGenerator() {
6983 // The IsSuspendedGenerator intrinsic is only called in
6984 // self-hosted code, so it's safe to assume we have a single
6985 // argument and the callee is our intrinsic.
6987 MOZ_ASSERT(argc_ == 1);
6989 initializeInputOperand();
6991 // Stack layout here is (bottom to top):
6992 // 2: Callee
6993 // 1: ThisValue
6994 // 0: Arg <-- Top of stack.
6995 // We only care about the argument.
6996 ValOperandId valId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6998 // Check whether the argument is a suspended generator.
6999 // We don't need guards, because IsSuspendedGenerator returns
7000 // false for values that are not generator objects.
7001 writer.callIsSuspendedGeneratorResult(valId);
7002 writer.returnFromIC();
7004 trackAttached("IsSuspendedGenerator");
7005 return AttachDecision::Attach;
7008 AttachDecision InlinableNativeIRGenerator::tryAttachToObject() {
7009 // Self-hosted code calls this with a single argument.
7010 MOZ_ASSERT(argc_ == 1);
7012 // Need a single object argument.
7013 // TODO(Warp): Support all or more conversions to object.
7014 if (!args_[0].isObject()) {
7015 return AttachDecision::NoAction;
7018 // Initialize the input operand.
7019 initializeInputOperand();
7021 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7023 // Guard that the argument is an object.
7024 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7025 ObjOperandId objId = writer.guardToObject(argId);
7027 // Return the object.
7028 writer.loadObjectResult(objId);
7029 writer.returnFromIC();
7031 trackAttached("ToObject");
7032 return AttachDecision::Attach;
7035 AttachDecision InlinableNativeIRGenerator::tryAttachToInteger() {
7036 // Self-hosted code calls this with a single argument.
7037 MOZ_ASSERT(argc_ == 1);
7039 // Need a single int32 argument.
7040 // TODO(Warp): Support all or more conversions to integer.
7041 // Make sure to update this code correctly if we ever start
7042 // returning non-int32 integers.
7043 if (!args_[0].isInt32()) {
7044 return AttachDecision::NoAction;
7047 // Initialize the input operand.
7048 initializeInputOperand();
7050 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7052 // Guard that the argument is an int32.
7053 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7054 Int32OperandId int32Id = writer.guardToInt32(argId);
7056 // Return the int32.
7057 writer.loadInt32Result(int32Id);
7058 writer.returnFromIC();
7060 trackAttached("ToInteger");
7061 return AttachDecision::Attach;
7064 AttachDecision InlinableNativeIRGenerator::tryAttachToLength() {
7065 // Self-hosted code calls this with a single argument.
7066 MOZ_ASSERT(argc_ == 1);
7068 // Need a single int32 argument.
7069 if (!args_[0].isInt32()) {
7070 return AttachDecision::NoAction;
7073 // Initialize the input operand.
7074 initializeInputOperand();
7076 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7078 // ToLength(int32) is equivalent to max(int32, 0).
7079 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7080 Int32OperandId int32ArgId = writer.guardToInt32(argId);
7081 Int32OperandId zeroId = writer.loadInt32Constant(0);
7082 bool isMax = true;
7083 Int32OperandId maxId = writer.int32MinMax(isMax, int32ArgId, zeroId);
7084 writer.loadInt32Result(maxId);
7085 writer.returnFromIC();
7087 trackAttached("ToLength");
7088 return AttachDecision::Attach;
7091 AttachDecision InlinableNativeIRGenerator::tryAttachIsObject() {
7092 // Self-hosted code calls this with a single argument.
7093 MOZ_ASSERT(argc_ == 1);
7095 // Initialize the input operand.
7096 initializeInputOperand();
7098 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7100 // Type check the argument and return result.
7101 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7102 writer.isObjectResult(argId);
7103 writer.returnFromIC();
7105 trackAttached("IsObject");
7106 return AttachDecision::Attach;
7109 AttachDecision InlinableNativeIRGenerator::tryAttachIsPackedArray() {
7110 // Self-hosted code calls this with a single object argument.
7111 MOZ_ASSERT(argc_ == 1);
7112 MOZ_ASSERT(args_[0].isObject());
7114 // Initialize the input operand.
7115 initializeInputOperand();
7117 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7119 // Check if the argument is packed and return result.
7120 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7121 ObjOperandId objArgId = writer.guardToObject(argId);
7122 writer.isPackedArrayResult(objArgId);
7123 writer.returnFromIC();
7125 trackAttached("IsPackedArray");
7126 return AttachDecision::Attach;
7129 AttachDecision InlinableNativeIRGenerator::tryAttachIsCallable() {
7130 // Self-hosted code calls this with a single argument.
7131 MOZ_ASSERT(argc_ == 1);
7133 // Initialize the input operand.
7134 initializeInputOperand();
7136 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7138 // Check if the argument is callable and return result.
7139 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7140 writer.isCallableResult(argId);
7141 writer.returnFromIC();
7143 trackAttached("IsCallable");
7144 return AttachDecision::Attach;
7147 AttachDecision InlinableNativeIRGenerator::tryAttachIsConstructor() {
7148 // Self-hosted code calls this with a single argument.
7149 MOZ_ASSERT(argc_ == 1);
7151 // Need a single object argument.
7152 if (!args_[0].isObject()) {
7153 return AttachDecision::NoAction;
7156 // Initialize the input operand.
7157 initializeInputOperand();
7159 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7161 // Guard that the argument is an object.
7162 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7163 ObjOperandId objId = writer.guardToObject(argId);
7165 // Check if the argument is a constructor and return result.
7166 writer.isConstructorResult(objId);
7167 writer.returnFromIC();
7169 trackAttached("IsConstructor");
7170 return AttachDecision::Attach;
7173 AttachDecision
7174 InlinableNativeIRGenerator::tryAttachIsCrossRealmArrayConstructor() {
7175 // Self-hosted code calls this with an object argument.
7176 MOZ_ASSERT(argc_ == 1);
7177 MOZ_ASSERT(args_[0].isObject());
7179 if (args_[0].toObject().is<ProxyObject>()) {
7180 return AttachDecision::NoAction;
7183 // Initialize the input operand.
7184 initializeInputOperand();
7186 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7188 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7189 ObjOperandId objId = writer.guardToObject(argId);
7190 writer.guardIsNotProxy(objId);
7191 writer.isCrossRealmArrayConstructorResult(objId);
7192 writer.returnFromIC();
7194 trackAttached("IsCrossRealmArrayConstructor");
7195 return AttachDecision::Attach;
7198 AttachDecision InlinableNativeIRGenerator::tryAttachGuardToClass(
7199 InlinableNative native) {
7200 // Self-hosted code calls this with an object argument.
7201 MOZ_ASSERT(argc_ == 1);
7202 MOZ_ASSERT(args_[0].isObject());
7204 // Class must match.
7205 const JSClass* clasp = InlinableNativeGuardToClass(native);
7206 if (args_[0].toObject().getClass() != clasp) {
7207 return AttachDecision::NoAction;
7210 // Initialize the input operand.
7211 initializeInputOperand();
7213 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7215 // Guard that the argument is an object.
7216 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7217 ObjOperandId objId = writer.guardToObject(argId);
7219 // Guard that the object has the correct class.
7220 writer.guardAnyClass(objId, clasp);
7222 // Return the object.
7223 writer.loadObjectResult(objId);
7224 writer.returnFromIC();
7226 trackAttached("GuardToClass");
7227 return AttachDecision::Attach;
7230 AttachDecision InlinableNativeIRGenerator::tryAttachGuardToClass(
7231 GuardClassKind kind) {
7232 // Self-hosted code calls this with an object argument.
7233 MOZ_ASSERT(argc_ == 1);
7234 MOZ_ASSERT(args_[0].isObject());
7236 // Class must match.
7237 const JSClass* clasp = ClassFor(kind);
7238 if (args_[0].toObject().getClass() != clasp) {
7239 return AttachDecision::NoAction;
7242 // Initialize the input operand.
7243 initializeInputOperand();
7245 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7247 // Guard that the argument is an object.
7248 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7249 ObjOperandId objId = writer.guardToObject(argId);
7251 // Guard that the object has the correct class.
7252 writer.guardClass(objId, kind);
7254 // Return the object.
7255 writer.loadObjectResult(objId);
7256 writer.returnFromIC();
7258 trackAttached("GuardToClass");
7259 return AttachDecision::Attach;
7262 AttachDecision InlinableNativeIRGenerator::tryAttachGuardToEitherClass(
7263 GuardClassKind kind1, GuardClassKind kind2) {
7264 MOZ_ASSERT(kind1 != kind2,
7265 "prefer tryAttachGuardToClass for the same class case");
7267 // Self-hosted code calls this with an object argument.
7268 MOZ_ASSERT(argc_ == 1);
7269 MOZ_ASSERT(args_[0].isObject());
7271 // Class must match.
7272 const JSClass* clasp1 = ClassFor(kind1);
7273 const JSClass* clasp2 = ClassFor(kind2);
7274 const JSClass* objClass = args_[0].toObject().getClass();
7275 if (objClass != clasp1 && objClass != clasp2) {
7276 return AttachDecision::NoAction;
7279 // Initialize the input operand.
7280 initializeInputOperand();
7282 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7284 // Guard that the argument is an object.
7285 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7286 ObjOperandId objId = writer.guardToObject(argId);
7288 // Guard that the object has the correct class.
7289 writer.guardEitherClass(objId, kind1, kind2);
7291 // Return the object.
7292 writer.loadObjectResult(objId);
7293 writer.returnFromIC();
7295 trackAttached("GuardToEitherClass");
7296 return AttachDecision::Attach;
7299 AttachDecision InlinableNativeIRGenerator::tryAttachGuardToArrayBuffer() {
7300 return tryAttachGuardToEitherClass(GuardClassKind::FixedLengthArrayBuffer,
7301 GuardClassKind::ResizableArrayBuffer);
7304 AttachDecision InlinableNativeIRGenerator::tryAttachGuardToSharedArrayBuffer() {
7305 return tryAttachGuardToEitherClass(
7306 GuardClassKind::FixedLengthSharedArrayBuffer,
7307 GuardClassKind::GrowableSharedArrayBuffer);
7310 AttachDecision InlinableNativeIRGenerator::tryAttachHasClass(
7311 const JSClass* clasp, bool isPossiblyWrapped) {
7312 // Self-hosted code calls this with an object argument.
7313 MOZ_ASSERT(argc_ == 1);
7314 MOZ_ASSERT(args_[0].isObject());
7316 // Only optimize when the object isn't a proxy.
7317 if (isPossiblyWrapped && args_[0].toObject().is<ProxyObject>()) {
7318 return AttachDecision::NoAction;
7321 // Initialize the input operand.
7322 initializeInputOperand();
7324 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7326 // Perform the Class check.
7327 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7328 ObjOperandId objId = writer.guardToObject(argId);
7330 if (isPossiblyWrapped) {
7331 writer.guardIsNotProxy(objId);
7334 writer.hasClassResult(objId, clasp);
7335 writer.returnFromIC();
7337 trackAttached("HasClass");
7338 return AttachDecision::Attach;
7341 // Returns whether the .lastIndex property is a non-negative int32 value and is
7342 // still writable.
7343 static bool HasOptimizableLastIndexSlot(RegExpObject* regexp, JSContext* cx) {
7344 auto lastIndexProp = regexp->lookupPure(cx->names().lastIndex);
7345 MOZ_ASSERT(lastIndexProp->isDataProperty());
7346 if (!lastIndexProp->writable()) {
7347 return false;
7349 Value lastIndex = regexp->getLastIndex();
7350 if (!lastIndex.isInt32() || lastIndex.toInt32() < 0) {
7351 return false;
7353 return true;
7356 // Returns the RegExp stub used by the optimized code path for this intrinsic.
7357 // We store a pointer to this in the IC stub to ensure GC doesn't discard it.
7358 static JitCode* GetOrCreateRegExpStub(JSContext* cx, InlinableNative native) {
7359 #ifdef ENABLE_PORTABLE_BASELINE_INTERP
7360 return nullptr;
7361 #else
7362 // The stubs assume the global has non-null RegExpStatics and match result
7363 // shape.
7364 if (!GlobalObject::getRegExpStatics(cx, cx->global()) ||
7365 !cx->global()->regExpRealm().getOrCreateMatchResultShape(cx)) {
7366 MOZ_ASSERT(cx->isThrowingOutOfMemory() || cx->isThrowingOverRecursed());
7367 cx->clearPendingException();
7368 return nullptr;
7370 JitCode* code;
7371 switch (native) {
7372 case InlinableNative::IntrinsicRegExpBuiltinExecForTest:
7373 case InlinableNative::IntrinsicRegExpExecForTest:
7374 code = cx->zone()->jitZone()->ensureRegExpExecTestStubExists(cx);
7375 break;
7376 case InlinableNative::IntrinsicRegExpBuiltinExec:
7377 case InlinableNative::IntrinsicRegExpExec:
7378 code = cx->zone()->jitZone()->ensureRegExpExecMatchStubExists(cx);
7379 break;
7380 case InlinableNative::RegExpMatcher:
7381 code = cx->zone()->jitZone()->ensureRegExpMatcherStubExists(cx);
7382 break;
7383 case InlinableNative::RegExpSearcher:
7384 code = cx->zone()->jitZone()->ensureRegExpSearcherStubExists(cx);
7385 break;
7386 default:
7387 MOZ_CRASH("Unexpected native");
7389 if (!code) {
7390 MOZ_ASSERT(cx->isThrowingOutOfMemory() || cx->isThrowingOverRecursed());
7391 cx->clearPendingException();
7392 return nullptr;
7394 return code;
7395 #endif
7398 static void EmitGuardLastIndexIsNonNegativeInt32(CacheIRWriter& writer,
7399 ObjOperandId regExpId) {
7400 size_t offset =
7401 NativeObject::getFixedSlotOffset(RegExpObject::lastIndexSlot());
7402 ValOperandId lastIndexValId = writer.loadFixedSlot(regExpId, offset);
7403 Int32OperandId lastIndexId = writer.guardToInt32(lastIndexValId);
7404 writer.guardInt32IsNonNegative(lastIndexId);
7407 AttachDecision InlinableNativeIRGenerator::tryAttachIntrinsicRegExpBuiltinExec(
7408 InlinableNative native) {
7409 // Self-hosted code calls this with (regexp, string) arguments.
7410 MOZ_ASSERT(argc_ == 2);
7411 MOZ_ASSERT(args_[0].isObject());
7412 MOZ_ASSERT(args_[1].isString());
7414 JitCode* stub = GetOrCreateRegExpStub(cx_, native);
7415 if (!stub) {
7416 return AttachDecision::NoAction;
7419 RegExpObject* re = &args_[0].toObject().as<RegExpObject>();
7420 if (!HasOptimizableLastIndexSlot(re, cx_)) {
7421 return AttachDecision::NoAction;
7424 // Initialize the input operand.
7425 initializeInputOperand();
7427 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7429 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7430 ObjOperandId regExpId = writer.guardToObject(arg0Id);
7431 writer.guardShape(regExpId, re->shape());
7432 EmitGuardLastIndexIsNonNegativeInt32(writer, regExpId);
7434 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7435 StringOperandId inputId = writer.guardToString(arg1Id);
7437 if (native == InlinableNative::IntrinsicRegExpBuiltinExecForTest) {
7438 writer.regExpBuiltinExecTestResult(regExpId, inputId, stub);
7439 } else {
7440 writer.regExpBuiltinExecMatchResult(regExpId, inputId, stub);
7442 writer.returnFromIC();
7444 trackAttached("IntrinsicRegExpBuiltinExec");
7445 return AttachDecision::Attach;
7448 AttachDecision InlinableNativeIRGenerator::tryAttachIntrinsicRegExpExec(
7449 InlinableNative native) {
7450 // Self-hosted code calls this with (object, string) arguments.
7451 MOZ_ASSERT(argc_ == 2);
7452 MOZ_ASSERT(args_[0].isObject());
7453 MOZ_ASSERT(args_[1].isString());
7455 if (!args_[0].toObject().is<RegExpObject>()) {
7456 return AttachDecision::NoAction;
7459 JitCode* stub = GetOrCreateRegExpStub(cx_, native);
7460 if (!stub) {
7461 return AttachDecision::NoAction;
7464 RegExpObject* re = &args_[0].toObject().as<RegExpObject>();
7465 if (!HasOptimizableLastIndexSlot(re, cx_)) {
7466 return AttachDecision::NoAction;
7469 // Ensure regexp.exec is the original RegExp.prototype.exec function on the
7470 // prototype.
7471 if (re->containsPure(cx_->names().exec)) {
7472 return AttachDecision::NoAction;
7474 MOZ_ASSERT(cx_->global()->maybeGetRegExpPrototype());
7475 auto* regExpProto =
7476 &cx_->global()->maybeGetRegExpPrototype()->as<NativeObject>();
7477 if (re->staticPrototype() != regExpProto) {
7478 return AttachDecision::NoAction;
7480 auto execProp = regExpProto->as<NativeObject>().lookupPure(cx_->names().exec);
7481 if (!execProp || !execProp->isDataProperty()) {
7482 return AttachDecision::NoAction;
7484 // It should be stored in a dynamic slot. We assert this in
7485 // FinishRegExpClassInit.
7486 if (regExpProto->isFixedSlot(execProp->slot())) {
7487 return AttachDecision::NoAction;
7489 Value execVal = regExpProto->getSlot(execProp->slot());
7490 PropertyName* execName = cx_->names().RegExp_prototype_Exec;
7491 if (!IsSelfHostedFunctionWithName(execVal, execName)) {
7492 return AttachDecision::NoAction;
7494 JSFunction* execFunction = &execVal.toObject().as<JSFunction>();
7496 // Initialize the input operand.
7497 initializeInputOperand();
7499 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7501 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7502 ObjOperandId regExpId = writer.guardToObject(arg0Id);
7503 writer.guardShape(regExpId, re->shape());
7504 EmitGuardLastIndexIsNonNegativeInt32(writer, regExpId);
7506 // Emit guards for the RegExp.prototype.exec property.
7507 ObjOperandId regExpProtoId = writer.loadObject(regExpProto);
7508 writer.guardShape(regExpProtoId, regExpProto->shape());
7509 size_t offset =
7510 regExpProto->dynamicSlotIndex(execProp->slot()) * sizeof(Value);
7511 writer.guardDynamicSlotValue(regExpProtoId, offset,
7512 ObjectValue(*execFunction));
7514 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7515 StringOperandId inputId = writer.guardToString(arg1Id);
7517 if (native == InlinableNative::IntrinsicRegExpExecForTest) {
7518 writer.regExpBuiltinExecTestResult(regExpId, inputId, stub);
7519 } else {
7520 writer.regExpBuiltinExecMatchResult(regExpId, inputId, stub);
7522 writer.returnFromIC();
7524 trackAttached("IntrinsicRegExpExec");
7525 return AttachDecision::Attach;
7528 AttachDecision InlinableNativeIRGenerator::tryAttachRegExpMatcherSearcher(
7529 InlinableNative native) {
7530 // Self-hosted code calls this with (object, string, number) arguments.
7531 MOZ_ASSERT(argc_ == 3);
7532 MOZ_ASSERT(args_[0].isObject());
7533 MOZ_ASSERT(args_[1].isString());
7534 MOZ_ASSERT(args_[2].isNumber());
7536 // It's not guaranteed that the JITs have typed |lastIndex| as an Int32.
7537 if (!args_[2].isInt32()) {
7538 return AttachDecision::NoAction;
7541 JitCode* stub = GetOrCreateRegExpStub(cx_, native);
7542 if (!stub) {
7543 return AttachDecision::NoAction;
7546 // Initialize the input operand.
7547 initializeInputOperand();
7549 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7551 // Guard argument types.
7552 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7553 ObjOperandId reId = writer.guardToObject(arg0Id);
7555 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7556 StringOperandId inputId = writer.guardToString(arg1Id);
7558 ValOperandId arg2Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
7559 Int32OperandId lastIndexId = writer.guardToInt32(arg2Id);
7561 switch (native) {
7562 case InlinableNative::RegExpMatcher:
7563 writer.callRegExpMatcherResult(reId, inputId, lastIndexId, stub);
7564 writer.returnFromIC();
7565 trackAttached("RegExpMatcher");
7566 break;
7568 case InlinableNative::RegExpSearcher:
7569 writer.callRegExpSearcherResult(reId, inputId, lastIndexId, stub);
7570 writer.returnFromIC();
7571 trackAttached("RegExpSearcher");
7572 break;
7574 default:
7575 MOZ_CRASH("Unexpected native");
7578 return AttachDecision::Attach;
7581 AttachDecision InlinableNativeIRGenerator::tryAttachRegExpSearcherLastLimit() {
7582 // Self-hosted code calls this with a string argument that's only used for an
7583 // assertion.
7584 MOZ_ASSERT(argc_ == 1);
7585 MOZ_ASSERT(args_[0].isString());
7587 // Initialize the input operand.
7588 initializeInputOperand();
7590 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7592 writer.regExpSearcherLastLimitResult();
7593 writer.returnFromIC();
7595 trackAttached("RegExpSearcherLastLimit");
7596 return AttachDecision::Attach;
7599 AttachDecision InlinableNativeIRGenerator::tryAttachRegExpHasCaptureGroups() {
7600 // Self-hosted code calls this with object and string arguments.
7601 MOZ_ASSERT(argc_ == 2);
7602 MOZ_ASSERT(args_[0].toObject().is<RegExpObject>());
7603 MOZ_ASSERT(args_[1].isString());
7605 // Initialize the input operand.
7606 initializeInputOperand();
7608 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7610 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7611 ObjOperandId objId = writer.guardToObject(arg0Id);
7613 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7614 StringOperandId inputId = writer.guardToString(arg1Id);
7616 writer.regExpHasCaptureGroupsResult(objId, inputId);
7617 writer.returnFromIC();
7619 trackAttached("RegExpHasCaptureGroups");
7620 return AttachDecision::Attach;
7623 AttachDecision
7624 InlinableNativeIRGenerator::tryAttachRegExpPrototypeOptimizable() {
7625 // Self-hosted code calls this with a single object argument.
7626 MOZ_ASSERT(argc_ == 1);
7627 MOZ_ASSERT(args_[0].isObject());
7629 // Initialize the input operand.
7630 initializeInputOperand();
7632 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7634 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7635 ObjOperandId protoId = writer.guardToObject(arg0Id);
7637 writer.regExpPrototypeOptimizableResult(protoId);
7638 writer.returnFromIC();
7640 trackAttached("RegExpPrototypeOptimizable");
7641 return AttachDecision::Attach;
7644 AttachDecision
7645 InlinableNativeIRGenerator::tryAttachRegExpInstanceOptimizable() {
7646 // Self-hosted code calls this with two object arguments.
7647 MOZ_ASSERT(argc_ == 2);
7648 MOZ_ASSERT(args_[0].isObject());
7649 MOZ_ASSERT(args_[1].isObject());
7651 // Initialize the input operand.
7652 initializeInputOperand();
7654 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7656 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7657 ObjOperandId regexpId = writer.guardToObject(arg0Id);
7659 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7660 ObjOperandId protoId = writer.guardToObject(arg1Id);
7662 writer.regExpInstanceOptimizableResult(regexpId, protoId);
7663 writer.returnFromIC();
7665 trackAttached("RegExpInstanceOptimizable");
7666 return AttachDecision::Attach;
7669 AttachDecision InlinableNativeIRGenerator::tryAttachGetFirstDollarIndex() {
7670 // Self-hosted code calls this with a single string argument.
7671 MOZ_ASSERT(argc_ == 1);
7672 MOZ_ASSERT(args_[0].isString());
7674 // Initialize the input operand.
7675 initializeInputOperand();
7677 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7679 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7680 StringOperandId strId = writer.guardToString(arg0Id);
7682 writer.getFirstDollarIndexResult(strId);
7683 writer.returnFromIC();
7685 trackAttached("GetFirstDollarIndex");
7686 return AttachDecision::Attach;
7689 AttachDecision InlinableNativeIRGenerator::tryAttachSubstringKernel() {
7690 // Self-hosted code calls this with (string, int32, int32) arguments.
7691 MOZ_ASSERT(argc_ == 3);
7692 MOZ_ASSERT(args_[0].isString());
7693 MOZ_ASSERT(args_[1].isInt32());
7694 MOZ_ASSERT(args_[2].isInt32());
7696 // Initialize the input operand.
7697 initializeInputOperand();
7699 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7701 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7702 StringOperandId strId = writer.guardToString(arg0Id);
7704 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7705 Int32OperandId beginId = writer.guardToInt32(arg1Id);
7707 ValOperandId arg2Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
7708 Int32OperandId lengthId = writer.guardToInt32(arg2Id);
7710 writer.callSubstringKernelResult(strId, beginId, lengthId);
7711 writer.returnFromIC();
7713 trackAttached("SubstringKernel");
7714 return AttachDecision::Attach;
7717 AttachDecision InlinableNativeIRGenerator::tryAttachObjectHasPrototype() {
7718 // Self-hosted code calls this with (object, object) arguments.
7719 MOZ_ASSERT(argc_ == 2);
7720 MOZ_ASSERT(args_[0].isObject());
7721 MOZ_ASSERT(args_[1].isObject());
7723 auto* obj = &args_[0].toObject().as<NativeObject>();
7724 auto* proto = &args_[1].toObject().as<NativeObject>();
7726 // Only attach when obj.__proto__ is proto.
7727 if (obj->staticPrototype() != proto) {
7728 return AttachDecision::NoAction;
7731 // Initialize the input operand.
7732 initializeInputOperand();
7734 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7736 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7737 ObjOperandId objId = writer.guardToObject(arg0Id);
7739 writer.guardProto(objId, proto);
7740 writer.loadBooleanResult(true);
7741 writer.returnFromIC();
7743 trackAttached("ObjectHasPrototype");
7744 return AttachDecision::Attach;
7747 static bool CanConvertToString(const Value& v) {
7748 return v.isString() || v.isNumber() || v.isBoolean() || v.isNullOrUndefined();
7751 AttachDecision InlinableNativeIRGenerator::tryAttachString() {
7752 // Need a single argument that is or can be converted to a string.
7753 if (argc_ != 1 || !CanConvertToString(args_[0])) {
7754 return AttachDecision::NoAction;
7757 // Initialize the input operand.
7758 initializeInputOperand();
7760 // Guard callee is the 'String' function.
7761 emitNativeCalleeGuard();
7763 // Guard that the argument is a string or can be converted to one.
7764 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7765 StringOperandId strId = emitToStringGuard(argId, args_[0]);
7767 // Return the string.
7768 writer.loadStringResult(strId);
7769 writer.returnFromIC();
7771 trackAttached("String");
7772 return AttachDecision::Attach;
7775 AttachDecision InlinableNativeIRGenerator::tryAttachStringConstructor() {
7776 // Need a single argument that is or can be converted to a string.
7777 if (argc_ != 1 || !CanConvertToString(args_[0])) {
7778 return AttachDecision::NoAction;
7781 RootedString emptyString(cx_, cx_->runtime()->emptyString);
7782 JSObject* templateObj = StringObject::create(
7783 cx_, emptyString, /* proto = */ nullptr, TenuredObject);
7784 if (!templateObj) {
7785 cx_->recoverFromOutOfMemory();
7786 return AttachDecision::NoAction;
7789 // Initialize the input operand.
7790 initializeInputOperand();
7792 // Guard callee is the 'String' function.
7793 emitNativeCalleeGuard();
7795 // Guard on number and convert to string.
7796 ValOperandId argId =
7797 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_, flags_);
7798 StringOperandId strId = emitToStringGuard(argId, args_[0]);
7800 writer.newStringObjectResult(templateObj, strId);
7801 writer.returnFromIC();
7803 trackAttached("StringConstructor");
7804 return AttachDecision::Attach;
7807 AttachDecision InlinableNativeIRGenerator::tryAttachStringToStringValueOf() {
7808 // Expecting no arguments.
7809 if (argc_ != 0) {
7810 return AttachDecision::NoAction;
7813 // Ensure |this| is a primitive string value.
7814 if (!thisval_.isString()) {
7815 return AttachDecision::NoAction;
7818 // Initialize the input operand.
7819 initializeInputOperand();
7821 // Guard callee is the 'toString' OR 'valueOf' native function.
7822 emitNativeCalleeGuard();
7824 // Guard |this| is a string.
7825 ValOperandId thisValId =
7826 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
7827 StringOperandId strId = writer.guardToString(thisValId);
7829 // Return the string
7830 writer.loadStringResult(strId);
7831 writer.returnFromIC();
7833 trackAttached("StringToStringValueOf");
7834 return AttachDecision::Attach;
7837 AttachDecision InlinableNativeIRGenerator::tryAttachStringReplaceString() {
7838 // Self-hosted code calls this with (string, string, string) arguments.
7839 MOZ_ASSERT(argc_ == 3);
7840 MOZ_ASSERT(args_[0].isString());
7841 MOZ_ASSERT(args_[1].isString());
7842 MOZ_ASSERT(args_[2].isString());
7844 // Initialize the input operand.
7845 initializeInputOperand();
7847 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7849 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7850 StringOperandId strId = writer.guardToString(arg0Id);
7852 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7853 StringOperandId patternId = writer.guardToString(arg1Id);
7855 ValOperandId arg2Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
7856 StringOperandId replacementId = writer.guardToString(arg2Id);
7858 writer.stringReplaceStringResult(strId, patternId, replacementId);
7859 writer.returnFromIC();
7861 trackAttached("StringReplaceString");
7862 return AttachDecision::Attach;
7865 AttachDecision InlinableNativeIRGenerator::tryAttachStringSplitString() {
7866 // Self-hosted code calls this with (string, string) arguments.
7867 MOZ_ASSERT(argc_ == 2);
7868 MOZ_ASSERT(args_[0].isString());
7869 MOZ_ASSERT(args_[1].isString());
7871 // Initialize the input operand.
7872 initializeInputOperand();
7874 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7876 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7877 StringOperandId strId = writer.guardToString(arg0Id);
7879 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7880 StringOperandId separatorId = writer.guardToString(arg1Id);
7882 writer.stringSplitStringResult(strId, separatorId);
7883 writer.returnFromIC();
7885 trackAttached("StringSplitString");
7886 return AttachDecision::Attach;
7889 AttachDecision InlinableNativeIRGenerator::tryAttachStringChar(
7890 StringChar kind) {
7891 // Need one argument.
7892 if (argc_ != 1) {
7893 return AttachDecision::NoAction;
7896 auto attach = CanAttachStringChar(thisval_, args_[0], kind);
7897 if (attach == AttachStringChar::No) {
7898 return AttachDecision::NoAction;
7901 bool handleOOB = attach == AttachStringChar::OutOfBounds;
7903 // Initialize the input operand.
7904 initializeInputOperand();
7906 // Guard callee is the 'charCodeAt', 'codePointAt', 'charAt', or 'at' native
7907 // function.
7908 emitNativeCalleeGuard();
7910 // Guard this is a string.
7911 ValOperandId thisValId =
7912 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
7913 StringOperandId strId = writer.guardToString(thisValId);
7915 // Guard int32 index.
7916 ValOperandId indexId =
7917 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7918 Int32OperandId int32IndexId = writer.guardToInt32Index(indexId);
7920 // Handle relative string indices, if necessary.
7921 if (kind == StringChar::At) {
7922 int32IndexId = writer.toRelativeStringIndex(int32IndexId, strId);
7925 // Linearize the string.
7927 // AttachStringChar doesn't have a separate state when OOB access happens on
7928 // a string which needs to be linearized, so just linearize unconditionally
7929 // for out-of-bounds accesses.
7930 if (attach == AttachStringChar::Linearize ||
7931 attach == AttachStringChar::OutOfBounds) {
7932 switch (kind) {
7933 case StringChar::CharCodeAt:
7934 case StringChar::CharAt:
7935 case StringChar::At:
7936 strId = writer.linearizeForCharAccess(strId, int32IndexId);
7937 break;
7938 case StringChar::CodePointAt:
7939 strId = writer.linearizeForCodePointAccess(strId, int32IndexId);
7940 break;
7944 // Load string char or code.
7945 switch (kind) {
7946 case StringChar::CharCodeAt:
7947 writer.loadStringCharCodeResult(strId, int32IndexId, handleOOB);
7948 break;
7949 case StringChar::CodePointAt:
7950 writer.loadStringCodePointResult(strId, int32IndexId, handleOOB);
7951 break;
7952 case StringChar::CharAt:
7953 writer.loadStringCharResult(strId, int32IndexId, handleOOB);
7954 break;
7955 case StringChar::At:
7956 writer.loadStringAtResult(strId, int32IndexId, handleOOB);
7957 break;
7960 writer.returnFromIC();
7962 switch (kind) {
7963 case StringChar::CharCodeAt:
7964 trackAttached("StringCharCodeAt");
7965 break;
7966 case StringChar::CodePointAt:
7967 trackAttached("StringCodePointAt");
7968 break;
7969 case StringChar::CharAt:
7970 trackAttached("StringCharAt");
7971 break;
7972 case StringChar::At:
7973 trackAttached("StringAt");
7974 break;
7977 return AttachDecision::Attach;
7980 AttachDecision InlinableNativeIRGenerator::tryAttachStringCharCodeAt() {
7981 return tryAttachStringChar(StringChar::CharCodeAt);
7984 AttachDecision InlinableNativeIRGenerator::tryAttachStringCodePointAt() {
7985 return tryAttachStringChar(StringChar::CodePointAt);
7988 AttachDecision InlinableNativeIRGenerator::tryAttachStringCharAt() {
7989 return tryAttachStringChar(StringChar::CharAt);
7992 AttachDecision InlinableNativeIRGenerator::tryAttachStringAt() {
7993 return tryAttachStringChar(StringChar::At);
7996 AttachDecision InlinableNativeIRGenerator::tryAttachStringFromCharCode() {
7997 // Need one number argument.
7998 if (argc_ != 1 || !args_[0].isNumber()) {
7999 return AttachDecision::NoAction;
8002 // Initialize the input operand.
8003 initializeInputOperand();
8005 // Guard callee is the 'fromCharCode' native function.
8006 emitNativeCalleeGuard();
8008 // Guard int32 argument.
8009 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8010 Int32OperandId codeId;
8011 if (args_[0].isInt32()) {
8012 codeId = writer.guardToInt32(argId);
8013 } else {
8014 // 'fromCharCode' performs ToUint16 on its input. We can use Uint32
8015 // semantics, because ToUint16(ToUint32(v)) == ToUint16(v).
8016 codeId = writer.guardToInt32ModUint32(argId);
8019 // Return string created from code.
8020 writer.stringFromCharCodeResult(codeId);
8021 writer.returnFromIC();
8023 trackAttached("StringFromCharCode");
8024 return AttachDecision::Attach;
8027 AttachDecision InlinableNativeIRGenerator::tryAttachStringFromCodePoint() {
8028 // Need one int32 argument.
8029 if (argc_ != 1 || !args_[0].isInt32()) {
8030 return AttachDecision::NoAction;
8033 // String.fromCodePoint throws for invalid code points.
8034 int32_t codePoint = args_[0].toInt32();
8035 if (codePoint < 0 || codePoint > int32_t(unicode::NonBMPMax)) {
8036 return AttachDecision::NoAction;
8039 // Initialize the input operand.
8040 initializeInputOperand();
8042 // Guard callee is the 'fromCodePoint' native function.
8043 emitNativeCalleeGuard();
8045 // Guard int32 argument.
8046 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8047 Int32OperandId codeId = writer.guardToInt32(argId);
8049 // Return string created from code point.
8050 writer.stringFromCodePointResult(codeId);
8051 writer.returnFromIC();
8053 trackAttached("StringFromCodePoint");
8054 return AttachDecision::Attach;
8057 AttachDecision InlinableNativeIRGenerator::tryAttachStringIncludes() {
8058 // Need one string argument.
8059 if (argc_ != 1 || !args_[0].isString()) {
8060 return AttachDecision::NoAction;
8063 // Ensure |this| is a primitive string value.
8064 if (!thisval_.isString()) {
8065 return AttachDecision::NoAction;
8068 // Initialize the input operand.
8069 initializeInputOperand();
8071 // Guard callee is the 'includes' native function.
8072 emitNativeCalleeGuard();
8074 // Guard this is a string.
8075 ValOperandId thisValId =
8076 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
8077 StringOperandId strId = writer.guardToString(thisValId);
8079 // Guard string argument.
8080 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8081 StringOperandId searchStrId = writer.guardToString(argId);
8083 writer.stringIncludesResult(strId, searchStrId);
8084 writer.returnFromIC();
8086 trackAttached("StringIncludes");
8087 return AttachDecision::Attach;
8090 AttachDecision InlinableNativeIRGenerator::tryAttachStringIndexOf() {
8091 // Need one string argument.
8092 if (argc_ != 1 || !args_[0].isString()) {
8093 return AttachDecision::NoAction;
8096 // Ensure |this| is a primitive string value.
8097 if (!thisval_.isString()) {
8098 return AttachDecision::NoAction;
8101 // Initialize the input operand.
8102 initializeInputOperand();
8104 // Guard callee is the 'indexOf' native function.
8105 emitNativeCalleeGuard();
8107 // Guard this is a string.
8108 ValOperandId thisValId =
8109 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
8110 StringOperandId strId = writer.guardToString(thisValId);
8112 // Guard string argument.
8113 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8114 StringOperandId searchStrId = writer.guardToString(argId);
8116 writer.stringIndexOfResult(strId, searchStrId);
8117 writer.returnFromIC();
8119 trackAttached("StringIndexOf");
8120 return AttachDecision::Attach;
8123 AttachDecision InlinableNativeIRGenerator::tryAttachStringLastIndexOf() {
8124 // Need one string argument.
8125 if (argc_ != 1 || !args_[0].isString()) {
8126 return AttachDecision::NoAction;
8129 // Ensure |this| is a primitive string value.
8130 if (!thisval_.isString()) {
8131 return AttachDecision::NoAction;
8134 // Initialize the input operand.
8135 initializeInputOperand();
8137 // Guard callee is the 'lastIndexOf' native function.
8138 emitNativeCalleeGuard();
8140 // Guard this is a string.
8141 ValOperandId thisValId =
8142 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
8143 StringOperandId strId = writer.guardToString(thisValId);
8145 // Guard string argument.
8146 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8147 StringOperandId searchStrId = writer.guardToString(argId);
8149 writer.stringLastIndexOfResult(strId, searchStrId);
8150 writer.returnFromIC();
8152 trackAttached("StringLastIndexOf");
8153 return AttachDecision::Attach;
8156 AttachDecision InlinableNativeIRGenerator::tryAttachStringStartsWith() {
8157 // Need one string argument.
8158 if (argc_ != 1 || !args_[0].isString()) {
8159 return AttachDecision::NoAction;
8162 // Ensure |this| is a primitive string value.
8163 if (!thisval_.isString()) {
8164 return AttachDecision::NoAction;
8167 // Initialize the input operand.
8168 initializeInputOperand();
8170 // Guard callee is the 'startsWith' native function.
8171 emitNativeCalleeGuard();
8173 // Guard this is a string.
8174 ValOperandId thisValId =
8175 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
8176 StringOperandId strId = writer.guardToString(thisValId);
8178 // Guard string argument.
8179 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8180 StringOperandId searchStrId = writer.guardToString(argId);
8182 writer.stringStartsWithResult(strId, searchStrId);
8183 writer.returnFromIC();
8185 trackAttached("StringStartsWith");
8186 return AttachDecision::Attach;
8189 AttachDecision InlinableNativeIRGenerator::tryAttachStringEndsWith() {
8190 // Need one string argument.
8191 if (argc_ != 1 || !args_[0].isString()) {
8192 return AttachDecision::NoAction;
8195 // Ensure |this| is a primitive string value.
8196 if (!thisval_.isString()) {
8197 return AttachDecision::NoAction;
8200 // Initialize the input operand.
8201 initializeInputOperand();
8203 // Guard callee is the 'endsWith' native function.
8204 emitNativeCalleeGuard();
8206 // Guard this is a string.
8207 ValOperandId thisValId =
8208 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
8209 StringOperandId strId = writer.guardToString(thisValId);
8211 // Guard string argument.
8212 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8213 StringOperandId searchStrId = writer.guardToString(argId);
8215 writer.stringEndsWithResult(strId, searchStrId);
8216 writer.returnFromIC();
8218 trackAttached("StringEndsWith");
8219 return AttachDecision::Attach;
8222 AttachDecision InlinableNativeIRGenerator::tryAttachStringToLowerCase() {
8223 // Expecting no arguments.
8224 if (argc_ != 0) {
8225 return AttachDecision::NoAction;
8228 // Ensure |this| is a primitive string value.
8229 if (!thisval_.isString()) {
8230 return AttachDecision::NoAction;
8233 // Initialize the input operand.
8234 initializeInputOperand();
8236 // Guard callee is the 'toLowerCase' native function.
8237 emitNativeCalleeGuard();
8239 // Guard this is a string.
8240 ValOperandId thisValId =
8241 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
8242 StringOperandId strId = writer.guardToString(thisValId);
8244 // Return string converted to lower-case.
8245 writer.stringToLowerCaseResult(strId);
8246 writer.returnFromIC();
8248 trackAttached("StringToLowerCase");
8249 return AttachDecision::Attach;
8252 AttachDecision InlinableNativeIRGenerator::tryAttachStringToUpperCase() {
8253 // Expecting no arguments.
8254 if (argc_ != 0) {
8255 return AttachDecision::NoAction;
8258 // Ensure |this| is a primitive string value.
8259 if (!thisval_.isString()) {
8260 return AttachDecision::NoAction;
8263 // Initialize the input operand.
8264 initializeInputOperand();
8266 // Guard callee is the 'toUpperCase' native function.
8267 emitNativeCalleeGuard();
8269 // Guard this is a string.
8270 ValOperandId thisValId =
8271 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
8272 StringOperandId strId = writer.guardToString(thisValId);
8274 // Return string converted to upper-case.
8275 writer.stringToUpperCaseResult(strId);
8276 writer.returnFromIC();
8278 trackAttached("StringToUpperCase");
8279 return AttachDecision::Attach;
8282 AttachDecision InlinableNativeIRGenerator::tryAttachStringTrim() {
8283 // Expecting no arguments.
8284 if (argc_ != 0) {
8285 return AttachDecision::NoAction;
8288 // Ensure |this| is a primitive string value.
8289 if (!thisval_.isString()) {
8290 return AttachDecision::NoAction;
8293 // Initialize the input operand.
8294 initializeInputOperand();
8296 // Guard callee is the 'trim' native function.
8297 emitNativeCalleeGuard();
8299 // Guard this is a string.
8300 ValOperandId thisValId =
8301 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
8302 StringOperandId strId = writer.guardToString(thisValId);
8304 writer.stringTrimResult(strId);
8305 writer.returnFromIC();
8307 trackAttached("StringTrim");
8308 return AttachDecision::Attach;
8311 AttachDecision InlinableNativeIRGenerator::tryAttachStringTrimStart() {
8312 // Expecting no arguments.
8313 if (argc_ != 0) {
8314 return AttachDecision::NoAction;
8317 // Ensure |this| is a primitive string value.
8318 if (!thisval_.isString()) {
8319 return AttachDecision::NoAction;
8322 // Initialize the input operand.
8323 initializeInputOperand();
8325 // Guard callee is the 'trimStart' native function.
8326 emitNativeCalleeGuard();
8328 // Guard this is a string.
8329 ValOperandId thisValId =
8330 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
8331 StringOperandId strId = writer.guardToString(thisValId);
8333 writer.stringTrimStartResult(strId);
8334 writer.returnFromIC();
8336 trackAttached("StringTrimStart");
8337 return AttachDecision::Attach;
8340 AttachDecision InlinableNativeIRGenerator::tryAttachStringTrimEnd() {
8341 // Expecting no arguments.
8342 if (argc_ != 0) {
8343 return AttachDecision::NoAction;
8346 // Ensure |this| is a primitive string value.
8347 if (!thisval_.isString()) {
8348 return AttachDecision::NoAction;
8351 // Initialize the input operand.
8352 initializeInputOperand();
8354 // Guard callee is the 'trimEnd' native function.
8355 emitNativeCalleeGuard();
8357 // Guard this is a string.
8358 ValOperandId thisValId =
8359 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
8360 StringOperandId strId = writer.guardToString(thisValId);
8362 writer.stringTrimEndResult(strId);
8363 writer.returnFromIC();
8365 trackAttached("StringTrimEnd");
8366 return AttachDecision::Attach;
8369 AttachDecision InlinableNativeIRGenerator::tryAttachMathRandom() {
8370 // Expecting no arguments.
8371 if (argc_ != 0) {
8372 return AttachDecision::NoAction;
8375 MOZ_ASSERT(cx_->realm() == callee_->realm(),
8376 "Shouldn't inline cross-realm Math.random because per-realm RNG");
8378 // Initialize the input operand.
8379 initializeInputOperand();
8381 // Guard callee is the 'random' native function.
8382 emitNativeCalleeGuard();
8384 mozilla::non_crypto::XorShift128PlusRNG* rng =
8385 &cx_->realm()->getOrCreateRandomNumberGenerator();
8386 writer.mathRandomResult(rng);
8388 writer.returnFromIC();
8390 trackAttached("MathRandom");
8391 return AttachDecision::Attach;
8394 AttachDecision InlinableNativeIRGenerator::tryAttachMathAbs() {
8395 // Need one argument.
8396 if (argc_ != 1) {
8397 return AttachDecision::NoAction;
8400 if (!args_[0].isNumber()) {
8401 return AttachDecision::NoAction;
8404 // Initialize the input operand.
8405 initializeInputOperand();
8407 // Guard callee is the 'abs' native function.
8408 emitNativeCalleeGuard();
8410 ValOperandId argumentId =
8411 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8413 // abs(INT_MIN) is a double.
8414 if (args_[0].isInt32() && args_[0].toInt32() != INT_MIN) {
8415 Int32OperandId int32Id = writer.guardToInt32(argumentId);
8416 writer.mathAbsInt32Result(int32Id);
8417 } else {
8418 NumberOperandId numberId = writer.guardIsNumber(argumentId);
8419 writer.mathAbsNumberResult(numberId);
8422 writer.returnFromIC();
8424 trackAttached("MathAbs");
8425 return AttachDecision::Attach;
8428 AttachDecision InlinableNativeIRGenerator::tryAttachMathClz32() {
8429 // Need one (number) argument.
8430 if (argc_ != 1 || !args_[0].isNumber()) {
8431 return AttachDecision::NoAction;
8434 // Initialize the input operand.
8435 initializeInputOperand();
8437 // Guard callee is the 'clz32' native function.
8438 emitNativeCalleeGuard();
8440 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8442 Int32OperandId int32Id;
8443 if (args_[0].isInt32()) {
8444 int32Id = writer.guardToInt32(argId);
8445 } else {
8446 MOZ_ASSERT(args_[0].isDouble());
8447 NumberOperandId numId = writer.guardIsNumber(argId);
8448 int32Id = writer.truncateDoubleToUInt32(numId);
8450 writer.mathClz32Result(int32Id);
8451 writer.returnFromIC();
8453 trackAttached("MathClz32");
8454 return AttachDecision::Attach;
8457 AttachDecision InlinableNativeIRGenerator::tryAttachMathSign() {
8458 // Need one (number) argument.
8459 if (argc_ != 1 || !args_[0].isNumber()) {
8460 return AttachDecision::NoAction;
8463 // Initialize the input operand.
8464 initializeInputOperand();
8466 // Guard callee is the 'sign' native function.
8467 emitNativeCalleeGuard();
8469 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8471 if (args_[0].isInt32()) {
8472 Int32OperandId int32Id = writer.guardToInt32(argId);
8473 writer.mathSignInt32Result(int32Id);
8474 } else {
8475 // Math.sign returns a double only if the input is -0 or NaN so try to
8476 // optimize the common Number => Int32 case.
8477 double d = math_sign_impl(args_[0].toDouble());
8478 int32_t unused;
8479 bool resultIsInt32 = mozilla::NumberIsInt32(d, &unused);
8481 NumberOperandId numId = writer.guardIsNumber(argId);
8482 if (resultIsInt32) {
8483 writer.mathSignNumberToInt32Result(numId);
8484 } else {
8485 writer.mathSignNumberResult(numId);
8489 writer.returnFromIC();
8491 trackAttached("MathSign");
8492 return AttachDecision::Attach;
8495 AttachDecision InlinableNativeIRGenerator::tryAttachMathImul() {
8496 // Need two (number) arguments.
8497 if (argc_ != 2 || !args_[0].isNumber() || !args_[1].isNumber()) {
8498 return AttachDecision::NoAction;
8501 // Initialize the input operand.
8502 initializeInputOperand();
8504 // Guard callee is the 'imul' native function.
8505 emitNativeCalleeGuard();
8507 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8508 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
8510 Int32OperandId int32Arg0Id, int32Arg1Id;
8511 if (args_[0].isInt32() && args_[1].isInt32()) {
8512 int32Arg0Id = writer.guardToInt32(arg0Id);
8513 int32Arg1Id = writer.guardToInt32(arg1Id);
8514 } else {
8515 // Treat both arguments as numbers if at least one of them is non-int32.
8516 NumberOperandId numArg0Id = writer.guardIsNumber(arg0Id);
8517 NumberOperandId numArg1Id = writer.guardIsNumber(arg1Id);
8518 int32Arg0Id = writer.truncateDoubleToUInt32(numArg0Id);
8519 int32Arg1Id = writer.truncateDoubleToUInt32(numArg1Id);
8521 writer.mathImulResult(int32Arg0Id, int32Arg1Id);
8522 writer.returnFromIC();
8524 trackAttached("MathImul");
8525 return AttachDecision::Attach;
8528 AttachDecision InlinableNativeIRGenerator::tryAttachMathFloor() {
8529 // Need one (number) argument.
8530 if (argc_ != 1 || !args_[0].isNumber()) {
8531 return AttachDecision::NoAction;
8534 // Check if the result fits in int32.
8535 double res = math_floor_impl(args_[0].toNumber());
8536 int32_t unused;
8537 bool resultIsInt32 = mozilla::NumberIsInt32(res, &unused);
8539 // Initialize the input operand.
8540 initializeInputOperand();
8542 // Guard callee is the 'floor' native function.
8543 emitNativeCalleeGuard();
8545 ValOperandId argumentId =
8546 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8548 if (args_[0].isInt32()) {
8549 MOZ_ASSERT(resultIsInt32);
8551 // Use an indirect truncation to inform the optimizer it needs to preserve
8552 // a bailout when the input can't be represented as an int32, even if the
8553 // final result is fully truncated.
8554 Int32OperandId intId = writer.guardToInt32(argumentId);
8555 writer.indirectTruncateInt32Result(intId);
8556 } else {
8557 NumberOperandId numberId = writer.guardIsNumber(argumentId);
8559 if (resultIsInt32) {
8560 writer.mathFloorToInt32Result(numberId);
8561 } else {
8562 writer.mathFloorNumberResult(numberId);
8566 writer.returnFromIC();
8568 trackAttached("MathFloor");
8569 return AttachDecision::Attach;
8572 AttachDecision InlinableNativeIRGenerator::tryAttachMathCeil() {
8573 // Need one (number) argument.
8574 if (argc_ != 1 || !args_[0].isNumber()) {
8575 return AttachDecision::NoAction;
8578 // Check if the result fits in int32.
8579 double res = math_ceil_impl(args_[0].toNumber());
8580 int32_t unused;
8581 bool resultIsInt32 = mozilla::NumberIsInt32(res, &unused);
8583 // Initialize the input operand.
8584 initializeInputOperand();
8586 // Guard callee is the 'ceil' native function.
8587 emitNativeCalleeGuard();
8589 ValOperandId argumentId =
8590 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8592 if (args_[0].isInt32()) {
8593 MOZ_ASSERT(resultIsInt32);
8595 // Use an indirect truncation to inform the optimizer it needs to preserve
8596 // a bailout when the input can't be represented as an int32, even if the
8597 // final result is fully truncated.
8598 Int32OperandId intId = writer.guardToInt32(argumentId);
8599 writer.indirectTruncateInt32Result(intId);
8600 } else {
8601 NumberOperandId numberId = writer.guardIsNumber(argumentId);
8603 if (resultIsInt32) {
8604 writer.mathCeilToInt32Result(numberId);
8605 } else {
8606 writer.mathCeilNumberResult(numberId);
8610 writer.returnFromIC();
8612 trackAttached("MathCeil");
8613 return AttachDecision::Attach;
8616 AttachDecision InlinableNativeIRGenerator::tryAttachMathTrunc() {
8617 // Need one (number) argument.
8618 if (argc_ != 1 || !args_[0].isNumber()) {
8619 return AttachDecision::NoAction;
8622 // Check if the result fits in int32.
8623 double res = math_trunc_impl(args_[0].toNumber());
8624 int32_t unused;
8625 bool resultIsInt32 = mozilla::NumberIsInt32(res, &unused);
8627 // Initialize the input operand.
8628 initializeInputOperand();
8630 // Guard callee is the 'trunc' native function.
8631 emitNativeCalleeGuard();
8633 ValOperandId argumentId =
8634 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8636 if (args_[0].isInt32()) {
8637 MOZ_ASSERT(resultIsInt32);
8639 // We don't need an indirect truncation barrier here, because Math.trunc
8640 // always truncates, but never rounds its input away from zero.
8641 Int32OperandId intId = writer.guardToInt32(argumentId);
8642 writer.loadInt32Result(intId);
8643 } else {
8644 NumberOperandId numberId = writer.guardIsNumber(argumentId);
8646 if (resultIsInt32) {
8647 writer.mathTruncToInt32Result(numberId);
8648 } else {
8649 writer.mathTruncNumberResult(numberId);
8653 writer.returnFromIC();
8655 trackAttached("MathTrunc");
8656 return AttachDecision::Attach;
8659 AttachDecision InlinableNativeIRGenerator::tryAttachMathRound() {
8660 // Need one (number) argument.
8661 if (argc_ != 1 || !args_[0].isNumber()) {
8662 return AttachDecision::NoAction;
8665 // Check if the result fits in int32.
8666 double res = math_round_impl(args_[0].toNumber());
8667 int32_t unused;
8668 bool resultIsInt32 = mozilla::NumberIsInt32(res, &unused);
8670 // Initialize the input operand.
8671 initializeInputOperand();
8673 // Guard callee is the 'round' native function.
8674 emitNativeCalleeGuard();
8676 ValOperandId argumentId =
8677 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8679 if (args_[0].isInt32()) {
8680 MOZ_ASSERT(resultIsInt32);
8682 // Use an indirect truncation to inform the optimizer it needs to preserve
8683 // a bailout when the input can't be represented as an int32, even if the
8684 // final result is fully truncated.
8685 Int32OperandId intId = writer.guardToInt32(argumentId);
8686 writer.indirectTruncateInt32Result(intId);
8687 } else {
8688 NumberOperandId numberId = writer.guardIsNumber(argumentId);
8690 if (resultIsInt32) {
8691 writer.mathRoundToInt32Result(numberId);
8692 } else {
8693 writer.mathFunctionNumberResult(numberId, UnaryMathFunction::Round);
8697 writer.returnFromIC();
8699 trackAttached("MathRound");
8700 return AttachDecision::Attach;
8703 AttachDecision InlinableNativeIRGenerator::tryAttachMathSqrt() {
8704 // Need one (number) argument.
8705 if (argc_ != 1 || !args_[0].isNumber()) {
8706 return AttachDecision::NoAction;
8709 // Initialize the input operand.
8710 initializeInputOperand();
8712 // Guard callee is the 'sqrt' native function.
8713 emitNativeCalleeGuard();
8715 ValOperandId argumentId =
8716 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8717 NumberOperandId numberId = writer.guardIsNumber(argumentId);
8718 writer.mathSqrtNumberResult(numberId);
8719 writer.returnFromIC();
8721 trackAttached("MathSqrt");
8722 return AttachDecision::Attach;
8725 AttachDecision InlinableNativeIRGenerator::tryAttachMathFRound() {
8726 // Need one (number) argument.
8727 if (argc_ != 1 || !args_[0].isNumber()) {
8728 return AttachDecision::NoAction;
8731 // Initialize the input operand.
8732 initializeInputOperand();
8734 // Guard callee is the 'fround' native function.
8735 emitNativeCalleeGuard();
8737 ValOperandId argumentId =
8738 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8739 NumberOperandId numberId = writer.guardIsNumber(argumentId);
8740 writer.mathFRoundNumberResult(numberId);
8741 writer.returnFromIC();
8743 trackAttached("MathFRound");
8744 return AttachDecision::Attach;
8747 AttachDecision InlinableNativeIRGenerator::tryAttachMathF16Round() {
8748 // Need one (number) argument.
8749 if (argc_ != 1 || !args_[0].isNumber()) {
8750 return AttachDecision::NoAction;
8753 // Initialize the input operand.
8754 initializeInputOperand();
8756 // Guard callee is the 'f16round' native function.
8757 emitNativeCalleeGuard();
8759 ValOperandId argumentId =
8760 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8761 NumberOperandId numberId = writer.guardIsNumber(argumentId);
8762 writer.mathF16RoundNumberResult(numberId);
8763 writer.returnFromIC();
8765 trackAttached("MathF16Round");
8766 return AttachDecision::Attach;
8769 static bool CanAttachInt32Pow(const Value& baseVal, const Value& powerVal) {
8770 auto valToInt32 = [](const Value& v) {
8771 if (v.isInt32()) {
8772 return v.toInt32();
8774 if (v.isBoolean()) {
8775 return int32_t(v.toBoolean());
8777 MOZ_ASSERT(v.isNull());
8778 return 0;
8780 int32_t base = valToInt32(baseVal);
8781 int32_t power = valToInt32(powerVal);
8783 // x^y where y < 0 is most of the time not an int32, except when x is 1 or y
8784 // gets large enough. It's hard to determine when exactly y is "large enough",
8785 // so we don't use Int32PowResult when x != 1 and y < 0.
8786 // Note: it's important for this condition to match the code generated by
8787 // MacroAssembler::pow32 to prevent failure loops.
8788 if (power < 0) {
8789 return base == 1;
8792 double res = powi(base, power);
8793 int32_t unused;
8794 return mozilla::NumberIsInt32(res, &unused);
8797 AttachDecision InlinableNativeIRGenerator::tryAttachMathPow() {
8798 // Need two number arguments.
8799 if (argc_ != 2 || !args_[0].isNumber() || !args_[1].isNumber()) {
8800 return AttachDecision::NoAction;
8803 // Initialize the input operand.
8804 initializeInputOperand();
8806 // Guard callee is the 'pow' function.
8807 emitNativeCalleeGuard();
8809 ValOperandId baseId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8810 ValOperandId exponentId =
8811 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
8813 if (args_[0].isInt32() && args_[1].isInt32() &&
8814 CanAttachInt32Pow(args_[0], args_[1])) {
8815 Int32OperandId baseInt32Id = writer.guardToInt32(baseId);
8816 Int32OperandId exponentInt32Id = writer.guardToInt32(exponentId);
8817 writer.int32PowResult(baseInt32Id, exponentInt32Id);
8818 } else {
8819 NumberOperandId baseNumberId = writer.guardIsNumber(baseId);
8820 NumberOperandId exponentNumberId = writer.guardIsNumber(exponentId);
8821 writer.doublePowResult(baseNumberId, exponentNumberId);
8824 writer.returnFromIC();
8826 trackAttached("MathPow");
8827 return AttachDecision::Attach;
8830 AttachDecision InlinableNativeIRGenerator::tryAttachMathHypot() {
8831 // Only optimize if there are 2-4 arguments.
8832 if (argc_ < 2 || argc_ > 4) {
8833 return AttachDecision::NoAction;
8836 for (size_t i = 0; i < argc_; i++) {
8837 if (!args_[i].isNumber()) {
8838 return AttachDecision::NoAction;
8842 // Initialize the input operand.
8843 initializeInputOperand();
8845 // Guard callee is the 'hypot' native function.
8846 emitNativeCalleeGuard();
8848 ValOperandId firstId =
8849 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8850 ValOperandId secondId =
8851 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
8853 NumberOperandId firstNumId = writer.guardIsNumber(firstId);
8854 NumberOperandId secondNumId = writer.guardIsNumber(secondId);
8856 ValOperandId thirdId;
8857 ValOperandId fourthId;
8858 NumberOperandId thirdNumId;
8859 NumberOperandId fourthNumId;
8861 switch (argc_) {
8862 case 2:
8863 writer.mathHypot2NumberResult(firstNumId, secondNumId);
8864 break;
8865 case 3:
8866 thirdId = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
8867 thirdNumId = writer.guardIsNumber(thirdId);
8868 writer.mathHypot3NumberResult(firstNumId, secondNumId, thirdNumId);
8869 break;
8870 case 4:
8871 thirdId = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
8872 fourthId = writer.loadArgumentFixedSlot(ArgumentKind::Arg3, argc_);
8873 thirdNumId = writer.guardIsNumber(thirdId);
8874 fourthNumId = writer.guardIsNumber(fourthId);
8875 writer.mathHypot4NumberResult(firstNumId, secondNumId, thirdNumId,
8876 fourthNumId);
8877 break;
8878 default:
8879 MOZ_CRASH("Unexpected number of arguments to hypot function.");
8882 writer.returnFromIC();
8884 trackAttached("MathHypot");
8885 return AttachDecision::Attach;
8888 AttachDecision InlinableNativeIRGenerator::tryAttachMathATan2() {
8889 // Requires two numbers as arguments.
8890 if (argc_ != 2 || !args_[0].isNumber() || !args_[1].isNumber()) {
8891 return AttachDecision::NoAction;
8894 // Initialize the input operand.
8895 initializeInputOperand();
8897 // Guard callee is the 'atan2' native function.
8898 emitNativeCalleeGuard();
8900 ValOperandId yId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8901 ValOperandId xId = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
8903 NumberOperandId yNumberId = writer.guardIsNumber(yId);
8904 NumberOperandId xNumberId = writer.guardIsNumber(xId);
8906 writer.mathAtan2NumberResult(yNumberId, xNumberId);
8907 writer.returnFromIC();
8909 trackAttached("MathAtan2");
8910 return AttachDecision::Attach;
8913 AttachDecision InlinableNativeIRGenerator::tryAttachMathMinMax(bool isMax) {
8914 // For now only optimize if there are 1-4 arguments.
8915 if (argc_ < 1 || argc_ > 4) {
8916 return AttachDecision::NoAction;
8919 // Ensure all arguments are numbers.
8920 bool allInt32 = true;
8921 for (size_t i = 0; i < argc_; i++) {
8922 if (!args_[i].isNumber()) {
8923 return AttachDecision::NoAction;
8925 if (!args_[i].isInt32()) {
8926 allInt32 = false;
8930 // Initialize the input operand.
8931 initializeInputOperand();
8933 // Guard callee is this Math function.
8934 emitNativeCalleeGuard();
8936 if (allInt32) {
8937 ValOperandId valId =
8938 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8939 Int32OperandId resId = writer.guardToInt32(valId);
8940 for (size_t i = 1; i < argc_; i++) {
8941 ValOperandId argId =
8942 writer.loadArgumentFixedSlot(ArgumentKindForArgIndex(i), argc_);
8943 Int32OperandId argInt32Id = writer.guardToInt32(argId);
8944 resId = writer.int32MinMax(isMax, resId, argInt32Id);
8946 writer.loadInt32Result(resId);
8947 } else {
8948 ValOperandId valId =
8949 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8950 NumberOperandId resId = writer.guardIsNumber(valId);
8951 for (size_t i = 1; i < argc_; i++) {
8952 ValOperandId argId =
8953 writer.loadArgumentFixedSlot(ArgumentKindForArgIndex(i), argc_);
8954 NumberOperandId argNumId = writer.guardIsNumber(argId);
8955 resId = writer.numberMinMax(isMax, resId, argNumId);
8957 writer.loadDoubleResult(resId);
8960 writer.returnFromIC();
8962 trackAttached(isMax ? "MathMax" : "MathMin");
8963 return AttachDecision::Attach;
8966 AttachDecision InlinableNativeIRGenerator::tryAttachSpreadMathMinMax(
8967 bool isMax) {
8968 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Spread ||
8969 flags_.getArgFormat() == CallFlags::FunApplyArray);
8971 // The result will be an int32 if there is at least one argument,
8972 // and all the arguments are int32.
8973 bool int32Result = args_.length() > 0;
8974 for (size_t i = 0; i < args_.length(); i++) {
8975 if (!args_[i].isNumber()) {
8976 return AttachDecision::NoAction;
8978 if (!args_[i].isInt32()) {
8979 int32Result = false;
8983 // Initialize the input operand.
8984 initializeInputOperand();
8986 // Guard callee is this Math function.
8987 emitNativeCalleeGuard();
8989 // Load the argument array.
8990 ObjOperandId argsId = emitLoadArgsArray();
8992 if (int32Result) {
8993 writer.int32MinMaxArrayResult(argsId, isMax);
8994 } else {
8995 writer.numberMinMaxArrayResult(argsId, isMax);
8998 writer.returnFromIC();
9000 trackAttached(isMax ? "MathMaxArray" : "MathMinArray");
9001 return AttachDecision::Attach;
9004 AttachDecision InlinableNativeIRGenerator::tryAttachMathFunction(
9005 UnaryMathFunction fun) {
9006 // Need one argument.
9007 if (argc_ != 1) {
9008 return AttachDecision::NoAction;
9011 if (!args_[0].isNumber()) {
9012 return AttachDecision::NoAction;
9015 if (math_use_fdlibm_for_sin_cos_tan() ||
9016 callee_->realm()->creationOptions().alwaysUseFdlibm()) {
9017 switch (fun) {
9018 case UnaryMathFunction::SinNative:
9019 fun = UnaryMathFunction::SinFdlibm;
9020 break;
9021 case UnaryMathFunction::CosNative:
9022 fun = UnaryMathFunction::CosFdlibm;
9023 break;
9024 case UnaryMathFunction::TanNative:
9025 fun = UnaryMathFunction::TanFdlibm;
9026 break;
9027 default:
9028 break;
9032 // Initialize the input operand.
9033 initializeInputOperand();
9035 // Guard callee is this Math function.
9036 emitNativeCalleeGuard();
9038 ValOperandId argumentId =
9039 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9040 NumberOperandId numberId = writer.guardIsNumber(argumentId);
9041 writer.mathFunctionNumberResult(numberId, fun);
9042 writer.returnFromIC();
9044 trackAttached("MathFunction");
9045 return AttachDecision::Attach;
9048 AttachDecision InlinableNativeIRGenerator::tryAttachNumber() {
9049 // Expect a single string argument.
9050 if (argc_ != 1 || !args_[0].isString()) {
9051 return AttachDecision::NoAction;
9054 double num;
9055 if (!StringToNumber(cx_, args_[0].toString(), &num)) {
9056 cx_->recoverFromOutOfMemory();
9057 return AttachDecision::NoAction;
9060 // Initialize the input operand.
9061 initializeInputOperand();
9063 // Guard callee is the `Number` function.
9064 emitNativeCalleeGuard();
9066 // Guard that the argument is a string.
9067 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9068 StringOperandId strId = writer.guardToString(argId);
9070 // Return either an Int32 or Double result.
9071 int32_t unused;
9072 if (mozilla::NumberIsInt32(num, &unused)) {
9073 Int32OperandId resultId = writer.guardStringToInt32(strId);
9074 writer.loadInt32Result(resultId);
9075 } else {
9076 NumberOperandId resultId = writer.guardStringToNumber(strId);
9077 writer.loadDoubleResult(resultId);
9079 writer.returnFromIC();
9081 trackAttached("Number");
9082 return AttachDecision::Attach;
9085 AttachDecision InlinableNativeIRGenerator::tryAttachNumberParseInt() {
9086 // Expected arguments: input (string or number), optional radix (int32).
9087 if (argc_ < 1 || argc_ > 2) {
9088 return AttachDecision::NoAction;
9090 if (!args_[0].isString() && !args_[0].isNumber()) {
9091 return AttachDecision::NoAction;
9093 if (args_[0].isDouble()) {
9094 double d = args_[0].toDouble();
9096 // See num_parseInt for why we have to reject numbers smaller than 1.0e-6.
9097 // Negative numbers in the exclusive range (-1, -0) return -0.
9098 bool canTruncateToInt32 =
9099 (DOUBLE_DECIMAL_IN_SHORTEST_LOW <= d && d <= double(INT32_MAX)) ||
9100 (double(INT32_MIN) <= d && d <= -1.0) || (d == 0.0);
9101 if (!canTruncateToInt32) {
9102 return AttachDecision::NoAction;
9105 if (argc_ > 1 && !args_[1].isInt32(10)) {
9106 return AttachDecision::NoAction;
9109 // Initialize the input operand.
9110 initializeInputOperand();
9112 // Guard callee is the 'parseInt' native function.
9113 emitNativeCalleeGuard();
9115 auto guardRadix = [&]() {
9116 ValOperandId radixId =
9117 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
9118 Int32OperandId intRadixId = writer.guardToInt32(radixId);
9119 writer.guardSpecificInt32(intRadixId, 10);
9120 return intRadixId;
9123 ValOperandId inputId =
9124 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9126 if (args_[0].isString()) {
9127 StringOperandId strId = writer.guardToString(inputId);
9129 Int32OperandId intRadixId;
9130 if (argc_ > 1) {
9131 intRadixId = guardRadix();
9132 } else {
9133 intRadixId = writer.loadInt32Constant(0);
9136 writer.numberParseIntResult(strId, intRadixId);
9137 } else if (args_[0].isInt32()) {
9138 Int32OperandId intId = writer.guardToInt32(inputId);
9139 if (argc_ > 1) {
9140 guardRadix();
9142 writer.loadInt32Result(intId);
9143 } else {
9144 MOZ_ASSERT(args_[0].isDouble());
9146 NumberOperandId numId = writer.guardIsNumber(inputId);
9147 if (argc_ > 1) {
9148 guardRadix();
9150 writer.doubleParseIntResult(numId);
9153 writer.returnFromIC();
9155 trackAttached("NumberParseInt");
9156 return AttachDecision::Attach;
9159 StringOperandId IRGenerator::emitToStringGuard(ValOperandId id,
9160 const Value& v) {
9161 MOZ_ASSERT(CanConvertToString(v));
9162 if (v.isString()) {
9163 return writer.guardToString(id);
9165 if (v.isBoolean()) {
9166 BooleanOperandId boolId = writer.guardToBoolean(id);
9167 return writer.booleanToString(boolId);
9169 if (v.isNull()) {
9170 writer.guardIsNull(id);
9171 return writer.loadConstantString(cx_->names().null);
9173 if (v.isUndefined()) {
9174 writer.guardIsUndefined(id);
9175 return writer.loadConstantString(cx_->names().undefined);
9177 if (v.isInt32()) {
9178 Int32OperandId intId = writer.guardToInt32(id);
9179 return writer.callInt32ToString(intId);
9181 // At this point we are creating an IC that will handle
9182 // both Int32 and Double cases.
9183 MOZ_ASSERT(v.isNumber());
9184 NumberOperandId numId = writer.guardIsNumber(id);
9185 return writer.callNumberToString(numId);
9188 AttachDecision InlinableNativeIRGenerator::tryAttachNumberToString() {
9189 // Expecting no arguments or a single int32 argument.
9190 if (argc_ > 1) {
9191 return AttachDecision::NoAction;
9193 if (argc_ == 1 && !args_[0].isInt32()) {
9194 return AttachDecision::NoAction;
9197 // Ensure |this| is a primitive number value.
9198 if (!thisval_.isNumber()) {
9199 return AttachDecision::NoAction;
9202 // No arguments means base 10.
9203 int32_t base = 10;
9204 if (argc_ > 0) {
9205 base = args_[0].toInt32();
9206 if (base < 2 || base > 36) {
9207 return AttachDecision::NoAction;
9210 // Non-decimal bases currently only support int32 inputs.
9211 if (base != 10 && !thisval_.isInt32()) {
9212 return AttachDecision::NoAction;
9215 MOZ_ASSERT(2 <= base && base <= 36);
9217 // Initialize the input operand.
9218 initializeInputOperand();
9220 // Guard callee is the 'toString' native function.
9221 emitNativeCalleeGuard();
9223 // Initialize the |this| operand.
9224 ValOperandId thisValId =
9225 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
9227 // Guard on number and convert to string.
9228 if (base == 10) {
9229 // If an explicit base was passed, guard its value.
9230 if (argc_ > 0) {
9231 // Guard the `base` argument is an int32.
9232 ValOperandId baseId =
9233 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9234 Int32OperandId intBaseId = writer.guardToInt32(baseId);
9236 // Guard `base` is 10 for decimal toString representation.
9237 writer.guardSpecificInt32(intBaseId, 10);
9240 StringOperandId strId = emitToStringGuard(thisValId, thisval_);
9242 // Return the string.
9243 writer.loadStringResult(strId);
9244 } else {
9245 MOZ_ASSERT(argc_ > 0);
9247 // Guard the |this| value is an int32.
9248 Int32OperandId thisIntId = writer.guardToInt32(thisValId);
9250 // Guard the `base` argument is an int32.
9251 ValOperandId baseId =
9252 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9253 Int32OperandId intBaseId = writer.guardToInt32(baseId);
9255 // Return the string.
9256 writer.int32ToStringWithBaseResult(thisIntId, intBaseId);
9259 writer.returnFromIC();
9261 trackAttached("NumberToString");
9262 return AttachDecision::Attach;
9265 AttachDecision InlinableNativeIRGenerator::tryAttachReflectGetPrototypeOf() {
9266 // Need one argument.
9267 if (argc_ != 1) {
9268 return AttachDecision::NoAction;
9271 if (!args_[0].isObject()) {
9272 return AttachDecision::NoAction;
9275 // Initialize the input operand.
9276 initializeInputOperand();
9278 // Guard callee is the 'getPrototypeOf' native function.
9279 emitNativeCalleeGuard();
9281 ValOperandId argumentId =
9282 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9283 ObjOperandId objId = writer.guardToObject(argumentId);
9285 writer.reflectGetPrototypeOfResult(objId);
9286 writer.returnFromIC();
9288 trackAttached("ReflectGetPrototypeOf");
9289 return AttachDecision::Attach;
9292 static bool AtomicsMeetsPreconditions(TypedArrayObject* typedArray,
9293 const Value& index) {
9294 switch (typedArray->type()) {
9295 case Scalar::Int8:
9296 case Scalar::Uint8:
9297 case Scalar::Int16:
9298 case Scalar::Uint16:
9299 case Scalar::Int32:
9300 case Scalar::Uint32:
9301 case Scalar::BigInt64:
9302 case Scalar::BigUint64:
9303 break;
9305 case Scalar::Float16:
9306 case Scalar::Float32:
9307 case Scalar::Float64:
9308 case Scalar::Uint8Clamped:
9309 // Exclude floating types and Uint8Clamped.
9310 return false;
9312 case Scalar::MaxTypedArrayViewType:
9313 case Scalar::Int64:
9314 case Scalar::Simd128:
9315 MOZ_CRASH("Unsupported TypedArray type");
9318 // Bounds check the index argument.
9319 int64_t indexInt64;
9320 if (!ValueIsInt64Index(index, &indexInt64)) {
9321 return false;
9323 if (indexInt64 < 0 ||
9324 uint64_t(indexInt64) >= typedArray->length().valueOr(0)) {
9325 return false;
9328 return true;
9331 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsCompareExchange() {
9332 if (!JitSupportsAtomics()) {
9333 return AttachDecision::NoAction;
9336 // Need four arguments.
9337 if (argc_ != 4) {
9338 return AttachDecision::NoAction;
9341 // Arguments: typedArray, index (number), expected, replacement.
9342 if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) {
9343 return AttachDecision::NoAction;
9345 if (!args_[1].isNumber()) {
9346 return AttachDecision::NoAction;
9349 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9350 if (!AtomicsMeetsPreconditions(typedArray, args_[1])) {
9351 return AttachDecision::NoAction;
9354 Scalar::Type elementType = typedArray->type();
9355 if (!ValueCanConvertToNumeric(elementType, args_[2])) {
9356 return AttachDecision::NoAction;
9358 if (!ValueCanConvertToNumeric(elementType, args_[3])) {
9359 return AttachDecision::NoAction;
9362 // Initialize the input operand.
9363 initializeInputOperand();
9365 // Guard callee is the `compareExchange` native function.
9366 emitNativeCalleeGuard();
9368 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9369 ObjOperandId objId = writer.guardToObject(arg0Id);
9370 writer.guardShapeForClass(objId, typedArray->shape());
9372 // Convert index to intPtr.
9373 ValOperandId indexId =
9374 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
9375 IntPtrOperandId intPtrIndexId =
9376 guardToIntPtrIndex(args_[1], indexId, /* supportOOB = */ false);
9378 // Convert expected value to int32/BigInt.
9379 ValOperandId expectedId =
9380 writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
9381 OperandId numericExpectedId =
9382 emitNumericGuard(expectedId, args_[2], elementType);
9384 // Convert replacement value to int32/BigInt.
9385 ValOperandId replacementId =
9386 writer.loadArgumentFixedSlot(ArgumentKind::Arg3, argc_);
9387 OperandId numericReplacementId =
9388 emitNumericGuard(replacementId, args_[3], elementType);
9390 auto viewKind = ToArrayBufferViewKind(typedArray);
9391 writer.atomicsCompareExchangeResult(objId, intPtrIndexId, numericExpectedId,
9392 numericReplacementId, typedArray->type(),
9393 viewKind);
9394 writer.returnFromIC();
9396 trackAttached("AtomicsCompareExchange");
9397 return AttachDecision::Attach;
9400 bool InlinableNativeIRGenerator::canAttachAtomicsReadWriteModify() {
9401 if (!JitSupportsAtomics()) {
9402 return false;
9405 // Need three arguments.
9406 if (argc_ != 3) {
9407 return false;
9410 // Arguments: typedArray, index (number), value.
9411 if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) {
9412 return false;
9414 if (!args_[1].isNumber()) {
9415 return false;
9418 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9419 if (!AtomicsMeetsPreconditions(typedArray, args_[1])) {
9420 return false;
9422 if (!ValueCanConvertToNumeric(typedArray->type(), args_[2])) {
9423 return false;
9425 return true;
9428 InlinableNativeIRGenerator::AtomicsReadWriteModifyOperands
9429 InlinableNativeIRGenerator::emitAtomicsReadWriteModifyOperands() {
9430 MOZ_ASSERT(canAttachAtomicsReadWriteModify());
9432 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9434 // Initialize the input operand.
9435 initializeInputOperand();
9437 // Guard callee is this Atomics function.
9438 emitNativeCalleeGuard();
9440 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9441 ObjOperandId objId = writer.guardToObject(arg0Id);
9442 writer.guardShapeForClass(objId, typedArray->shape());
9444 // Convert index to intPtr.
9445 ValOperandId indexId =
9446 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
9447 IntPtrOperandId intPtrIndexId =
9448 guardToIntPtrIndex(args_[1], indexId, /* supportOOB = */ false);
9450 // Convert value to int32/BigInt.
9451 ValOperandId valueId =
9452 writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
9453 OperandId numericValueId =
9454 emitNumericGuard(valueId, args_[2], typedArray->type());
9456 return {objId, intPtrIndexId, numericValueId};
9459 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsExchange() {
9460 if (!canAttachAtomicsReadWriteModify()) {
9461 return AttachDecision::NoAction;
9464 auto [objId, intPtrIndexId, numericValueId] =
9465 emitAtomicsReadWriteModifyOperands();
9467 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9468 auto viewKind = ToArrayBufferViewKind(typedArray);
9470 writer.atomicsExchangeResult(objId, intPtrIndexId, numericValueId,
9471 typedArray->type(), viewKind);
9472 writer.returnFromIC();
9474 trackAttached("AtomicsExchange");
9475 return AttachDecision::Attach;
9478 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsAdd() {
9479 if (!canAttachAtomicsReadWriteModify()) {
9480 return AttachDecision::NoAction;
9483 auto [objId, intPtrIndexId, numericValueId] =
9484 emitAtomicsReadWriteModifyOperands();
9486 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9487 bool forEffect = ignoresResult();
9488 auto viewKind = ToArrayBufferViewKind(typedArray);
9490 writer.atomicsAddResult(objId, intPtrIndexId, numericValueId,
9491 typedArray->type(), forEffect, viewKind);
9492 writer.returnFromIC();
9494 trackAttached("AtomicsAdd");
9495 return AttachDecision::Attach;
9498 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsSub() {
9499 if (!canAttachAtomicsReadWriteModify()) {
9500 return AttachDecision::NoAction;
9503 auto [objId, intPtrIndexId, numericValueId] =
9504 emitAtomicsReadWriteModifyOperands();
9506 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9507 bool forEffect = ignoresResult();
9508 auto viewKind = ToArrayBufferViewKind(typedArray);
9510 writer.atomicsSubResult(objId, intPtrIndexId, numericValueId,
9511 typedArray->type(), forEffect, viewKind);
9512 writer.returnFromIC();
9514 trackAttached("AtomicsSub");
9515 return AttachDecision::Attach;
9518 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsAnd() {
9519 if (!canAttachAtomicsReadWriteModify()) {
9520 return AttachDecision::NoAction;
9523 auto [objId, intPtrIndexId, numericValueId] =
9524 emitAtomicsReadWriteModifyOperands();
9526 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9527 bool forEffect = ignoresResult();
9528 auto viewKind = ToArrayBufferViewKind(typedArray);
9530 writer.atomicsAndResult(objId, intPtrIndexId, numericValueId,
9531 typedArray->type(), forEffect, viewKind);
9532 writer.returnFromIC();
9534 trackAttached("AtomicsAnd");
9535 return AttachDecision::Attach;
9538 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsOr() {
9539 if (!canAttachAtomicsReadWriteModify()) {
9540 return AttachDecision::NoAction;
9543 auto [objId, intPtrIndexId, numericValueId] =
9544 emitAtomicsReadWriteModifyOperands();
9546 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9547 bool forEffect = ignoresResult();
9548 auto viewKind = ToArrayBufferViewKind(typedArray);
9550 writer.atomicsOrResult(objId, intPtrIndexId, numericValueId,
9551 typedArray->type(), forEffect, viewKind);
9552 writer.returnFromIC();
9554 trackAttached("AtomicsOr");
9555 return AttachDecision::Attach;
9558 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsXor() {
9559 if (!canAttachAtomicsReadWriteModify()) {
9560 return AttachDecision::NoAction;
9563 auto [objId, intPtrIndexId, numericValueId] =
9564 emitAtomicsReadWriteModifyOperands();
9566 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9567 bool forEffect = ignoresResult();
9568 auto viewKind = ToArrayBufferViewKind(typedArray);
9570 writer.atomicsXorResult(objId, intPtrIndexId, numericValueId,
9571 typedArray->type(), forEffect, viewKind);
9572 writer.returnFromIC();
9574 trackAttached("AtomicsXor");
9575 return AttachDecision::Attach;
9578 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsLoad() {
9579 if (!JitSupportsAtomics()) {
9580 return AttachDecision::NoAction;
9583 // Need two arguments.
9584 if (argc_ != 2) {
9585 return AttachDecision::NoAction;
9588 // Arguments: typedArray, index (number).
9589 if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) {
9590 return AttachDecision::NoAction;
9592 if (!args_[1].isNumber()) {
9593 return AttachDecision::NoAction;
9596 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9597 if (!AtomicsMeetsPreconditions(typedArray, args_[1])) {
9598 return AttachDecision::NoAction;
9601 // Initialize the input operand.
9602 initializeInputOperand();
9604 // Guard callee is the `load` native function.
9605 emitNativeCalleeGuard();
9607 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9608 ObjOperandId objId = writer.guardToObject(arg0Id);
9609 writer.guardShapeForClass(objId, typedArray->shape());
9611 // Convert index to intPtr.
9612 ValOperandId indexId =
9613 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
9614 IntPtrOperandId intPtrIndexId =
9615 guardToIntPtrIndex(args_[1], indexId, /* supportOOB = */ false);
9617 auto viewKind = ToArrayBufferViewKind(typedArray);
9618 writer.atomicsLoadResult(objId, intPtrIndexId, typedArray->type(), viewKind);
9619 writer.returnFromIC();
9621 trackAttached("AtomicsLoad");
9622 return AttachDecision::Attach;
9625 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsStore() {
9626 if (!JitSupportsAtomics()) {
9627 return AttachDecision::NoAction;
9630 // Need three arguments.
9631 if (argc_ != 3) {
9632 return AttachDecision::NoAction;
9635 // Atomics.store() is annoying because it returns the result of converting the
9636 // value by ToInteger(), not the input value, nor the result of converting the
9637 // value by ToInt32(). It is especially annoying because almost nobody uses
9638 // the result value.
9640 // As an expedient compromise, therefore, we inline only if the result is
9641 // obviously unused or if the argument is already Int32 and thus requires no
9642 // conversion.
9644 // Arguments: typedArray, index (number), value.
9645 if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) {
9646 return AttachDecision::NoAction;
9648 if (!args_[1].isNumber()) {
9649 return AttachDecision::NoAction;
9652 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9653 if (!AtomicsMeetsPreconditions(typedArray, args_[1])) {
9654 return AttachDecision::NoAction;
9657 Scalar::Type elementType = typedArray->type();
9658 if (!ValueCanConvertToNumeric(elementType, args_[2])) {
9659 return AttachDecision::NoAction;
9662 bool guardIsInt32 = !Scalar::isBigIntType(elementType) && !ignoresResult();
9664 if (guardIsInt32 && !args_[2].isInt32()) {
9665 return AttachDecision::NoAction;
9668 // Initialize the input operand.
9669 initializeInputOperand();
9671 // Guard callee is the `store` native function.
9672 emitNativeCalleeGuard();
9674 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9675 ObjOperandId objId = writer.guardToObject(arg0Id);
9676 writer.guardShapeForClass(objId, typedArray->shape());
9678 // Convert index to intPtr.
9679 ValOperandId indexId =
9680 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
9681 IntPtrOperandId intPtrIndexId =
9682 guardToIntPtrIndex(args_[1], indexId, /* supportOOB = */ false);
9684 // Ensure value is int32 or BigInt.
9685 ValOperandId valueId =
9686 writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
9687 OperandId numericValueId;
9688 if (guardIsInt32) {
9689 numericValueId = writer.guardToInt32(valueId);
9690 } else {
9691 numericValueId = emitNumericGuard(valueId, args_[2], elementType);
9694 auto viewKind = ToArrayBufferViewKind(typedArray);
9695 writer.atomicsStoreResult(objId, intPtrIndexId, numericValueId,
9696 typedArray->type(), viewKind);
9697 writer.returnFromIC();
9699 trackAttached("AtomicsStore");
9700 return AttachDecision::Attach;
9703 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsIsLockFree() {
9704 // Need one argument.
9705 if (argc_ != 1) {
9706 return AttachDecision::NoAction;
9709 if (!args_[0].isInt32()) {
9710 return AttachDecision::NoAction;
9713 // Initialize the input operand.
9714 initializeInputOperand();
9716 // Guard callee is the `isLockFree` native function.
9717 emitNativeCalleeGuard();
9719 // Ensure value is int32.
9720 ValOperandId valueId =
9721 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9722 Int32OperandId int32ValueId = writer.guardToInt32(valueId);
9724 writer.atomicsIsLockFreeResult(int32ValueId);
9725 writer.returnFromIC();
9727 trackAttached("AtomicsIsLockFree");
9728 return AttachDecision::Attach;
9731 AttachDecision InlinableNativeIRGenerator::tryAttachBoolean() {
9732 // Need zero or one argument.
9733 if (argc_ > 1) {
9734 return AttachDecision::NoAction;
9737 // Initialize the input operand.
9738 initializeInputOperand();
9740 // Guard callee is the 'Boolean' native function.
9741 emitNativeCalleeGuard();
9743 if (argc_ == 0) {
9744 writer.loadBooleanResult(false);
9745 } else {
9746 ValOperandId valId =
9747 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9749 writer.loadValueTruthyResult(valId);
9752 writer.returnFromIC();
9754 trackAttached("Boolean");
9755 return AttachDecision::Attach;
9758 AttachDecision InlinableNativeIRGenerator::tryAttachBailout() {
9759 // Expecting no arguments.
9760 if (argc_ != 0) {
9761 return AttachDecision::NoAction;
9764 // Initialize the input operand.
9765 initializeInputOperand();
9767 // Guard callee is the 'bailout' native function.
9768 emitNativeCalleeGuard();
9770 writer.bailout();
9771 writer.loadUndefinedResult();
9772 writer.returnFromIC();
9774 trackAttached("Bailout");
9775 return AttachDecision::Attach;
9778 AttachDecision InlinableNativeIRGenerator::tryAttachAssertFloat32() {
9779 // Expecting two arguments.
9780 if (argc_ != 2) {
9781 return AttachDecision::NoAction;
9784 // (Fuzzing unsafe) testing function which must be called with a constant
9785 // boolean as its second argument.
9786 bool mustBeFloat32 = args_[1].toBoolean();
9788 // Initialize the input operand.
9789 initializeInputOperand();
9791 // Guard callee is the 'assertFloat32' native function.
9792 emitNativeCalleeGuard();
9794 ValOperandId valId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9796 writer.assertFloat32Result(valId, mustBeFloat32);
9797 writer.returnFromIC();
9799 trackAttached("AssertFloat32");
9800 return AttachDecision::Attach;
9803 AttachDecision InlinableNativeIRGenerator::tryAttachAssertRecoveredOnBailout() {
9804 // Expecting two arguments.
9805 if (argc_ != 2) {
9806 return AttachDecision::NoAction;
9809 // (Fuzzing unsafe) testing function which must be called with a constant
9810 // boolean as its second argument.
9811 bool mustBeRecovered = args_[1].toBoolean();
9813 // Initialize the input operand.
9814 initializeInputOperand();
9816 // Guard callee is the 'assertRecoveredOnBailout' native function.
9817 emitNativeCalleeGuard();
9819 ValOperandId valId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9821 writer.assertRecoveredOnBailoutResult(valId, mustBeRecovered);
9822 writer.returnFromIC();
9824 trackAttached("AssertRecoveredOnBailout");
9825 return AttachDecision::Attach;
9828 AttachDecision InlinableNativeIRGenerator::tryAttachObjectIs() {
9829 // Need two arguments.
9830 if (argc_ != 2) {
9831 return AttachDecision::NoAction;
9834 // Initialize the input operand.
9835 initializeInputOperand();
9837 // Guard callee is the `is` native function.
9838 emitNativeCalleeGuard();
9840 ValOperandId lhsId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9841 ValOperandId rhsId = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
9843 HandleValue lhs = args_[0];
9844 HandleValue rhs = args_[1];
9846 if (!isFirstStub()) {
9847 writer.sameValueResult(lhsId, rhsId);
9848 } else if (lhs.isNumber() && rhs.isNumber() &&
9849 !(lhs.isInt32() && rhs.isInt32())) {
9850 NumberOperandId lhsNumId = writer.guardIsNumber(lhsId);
9851 NumberOperandId rhsNumId = writer.guardIsNumber(rhsId);
9852 writer.compareDoubleSameValueResult(lhsNumId, rhsNumId);
9853 } else if (!SameType(lhs, rhs)) {
9854 // Compare tags for strictly different types.
9855 ValueTagOperandId lhsTypeId = writer.loadValueTag(lhsId);
9856 ValueTagOperandId rhsTypeId = writer.loadValueTag(rhsId);
9857 writer.guardTagNotEqual(lhsTypeId, rhsTypeId);
9858 writer.loadBooleanResult(false);
9859 } else {
9860 MOZ_ASSERT(lhs.type() == rhs.type());
9861 MOZ_ASSERT(lhs.type() != JS::ValueType::Double);
9863 switch (lhs.type()) {
9864 case JS::ValueType::Int32: {
9865 Int32OperandId lhsIntId = writer.guardToInt32(lhsId);
9866 Int32OperandId rhsIntId = writer.guardToInt32(rhsId);
9867 writer.compareInt32Result(JSOp::StrictEq, lhsIntId, rhsIntId);
9868 break;
9870 case JS::ValueType::Boolean: {
9871 Int32OperandId lhsIntId = writer.guardBooleanToInt32(lhsId);
9872 Int32OperandId rhsIntId = writer.guardBooleanToInt32(rhsId);
9873 writer.compareInt32Result(JSOp::StrictEq, lhsIntId, rhsIntId);
9874 break;
9876 case JS::ValueType::Undefined: {
9877 writer.guardIsUndefined(lhsId);
9878 writer.guardIsUndefined(rhsId);
9879 writer.loadBooleanResult(true);
9880 break;
9882 case JS::ValueType::Null: {
9883 writer.guardIsNull(lhsId);
9884 writer.guardIsNull(rhsId);
9885 writer.loadBooleanResult(true);
9886 break;
9888 case JS::ValueType::String: {
9889 StringOperandId lhsStrId = writer.guardToString(lhsId);
9890 StringOperandId rhsStrId = writer.guardToString(rhsId);
9891 writer.compareStringResult(JSOp::StrictEq, lhsStrId, rhsStrId);
9892 break;
9894 case JS::ValueType::Symbol: {
9895 SymbolOperandId lhsSymId = writer.guardToSymbol(lhsId);
9896 SymbolOperandId rhsSymId = writer.guardToSymbol(rhsId);
9897 writer.compareSymbolResult(JSOp::StrictEq, lhsSymId, rhsSymId);
9898 break;
9900 case JS::ValueType::BigInt: {
9901 BigIntOperandId lhsBigIntId = writer.guardToBigInt(lhsId);
9902 BigIntOperandId rhsBigIntId = writer.guardToBigInt(rhsId);
9903 writer.compareBigIntResult(JSOp::StrictEq, lhsBigIntId, rhsBigIntId);
9904 break;
9906 case JS::ValueType::Object: {
9907 ObjOperandId lhsObjId = writer.guardToObject(lhsId);
9908 ObjOperandId rhsObjId = writer.guardToObject(rhsId);
9909 writer.compareObjectResult(JSOp::StrictEq, lhsObjId, rhsObjId);
9910 break;
9913 #ifdef ENABLE_RECORD_TUPLE
9914 case ValueType::ExtendedPrimitive:
9915 #endif
9916 case JS::ValueType::Double:
9917 case JS::ValueType::Magic:
9918 case JS::ValueType::PrivateGCThing:
9919 MOZ_CRASH("Unexpected type");
9923 writer.returnFromIC();
9925 trackAttached("ObjectIs");
9926 return AttachDecision::Attach;
9929 AttachDecision InlinableNativeIRGenerator::tryAttachObjectIsPrototypeOf() {
9930 // Ensure |this| is an object.
9931 if (!thisval_.isObject()) {
9932 return AttachDecision::NoAction;
9935 // Need a single argument.
9936 if (argc_ != 1) {
9937 return AttachDecision::NoAction;
9940 // Initialize the input operand.
9941 initializeInputOperand();
9943 // Guard callee is the `isPrototypeOf` native function.
9944 emitNativeCalleeGuard();
9946 // Guard that |this| is an object.
9947 ValOperandId thisValId =
9948 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
9949 ObjOperandId thisObjId = writer.guardToObject(thisValId);
9951 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9953 writer.loadInstanceOfObjectResult(argId, thisObjId);
9954 writer.returnFromIC();
9956 trackAttached("ObjectIsPrototypeOf");
9957 return AttachDecision::Attach;
9960 AttachDecision InlinableNativeIRGenerator::tryAttachObjectKeys() {
9961 // Only handle argc <= 1.
9962 if (argc_ != 1) {
9963 return AttachDecision::NoAction;
9966 // Do not attach any IC if the argument is not an object.
9967 if (!args_[0].isObject()) {
9968 return AttachDecision::NoAction;
9970 // Do not attach any IC if the argument is a Proxy. While implementation could
9971 // work with proxies the goal of this implementation is to provide an
9972 // optimization for calls of `Object.keys(obj)` where there is no side-effect,
9973 // and where the computation of the array of property name can be moved.
9974 const JSClass* clasp = args_[0].toObject().getClass();
9975 if (clasp->isProxyObject()) {
9976 return AttachDecision::NoAction;
9979 // Generate cache IR code to attach a new inline cache which will delegate the
9980 // call to Object.keys to the native function.
9981 initializeInputOperand();
9983 // Guard callee is the 'keys' native function.
9984 emitNativeCalleeGuard();
9986 // Implicit: Note `Object.keys` is a property of the `Object` global. The fact
9987 // that we are in this function implies that we already identify the function
9988 // as being the proper one. Thus there should not be any need to validate that
9989 // this is the proper function. (test: ion/object-keys-05)
9991 // Guard `arg0` is an object.
9992 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9993 ObjOperandId argObjId = writer.guardToObject(argId);
9995 // Guard against proxies.
9996 writer.guardIsNotProxy(argObjId);
9998 // Compute the keys array.
9999 writer.objectKeysResult(argObjId);
10001 writer.returnFromIC();
10003 trackAttached("ObjectKeys");
10004 return AttachDecision::Attach;
10007 AttachDecision InlinableNativeIRGenerator::tryAttachObjectToString() {
10008 // Expecting no arguments.
10009 if (argc_ != 0) {
10010 return AttachDecision::NoAction;
10013 // Ensure |this| is an object.
10014 if (!thisval_.isObject()) {
10015 return AttachDecision::NoAction;
10018 // Don't attach if the object has @@toStringTag or is a proxy.
10019 if (!ObjectClassToString(cx_, &thisval_.toObject())) {
10020 return AttachDecision::NoAction;
10023 // Initialize the input operand.
10024 initializeInputOperand();
10026 // Guard callee is the 'toString' native function.
10027 emitNativeCalleeGuard();
10029 // Guard that |this| is an object.
10030 ValOperandId thisValId =
10031 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
10032 ObjOperandId thisObjId = writer.guardToObject(thisValId);
10034 writer.objectToStringResult(thisObjId);
10035 writer.returnFromIC();
10037 trackAttached("ObjectToString");
10038 return AttachDecision::Attach;
10041 AttachDecision InlinableNativeIRGenerator::tryAttachBigInt() {
10042 // Need a single argument (Int32).
10043 if (argc_ != 1 || !args_[0].isInt32()) {
10044 return AttachDecision::NoAction;
10047 // Initialize the input operand.
10048 initializeInputOperand();
10050 // Guard callee is the 'BigInt' native function.
10051 emitNativeCalleeGuard();
10053 // Guard that the argument is an Int32.
10054 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10055 Int32OperandId int32Id = writer.guardToInt32(argId);
10057 // Convert Int32 to BigInt.
10058 writer.int32ToBigIntResult(int32Id);
10059 writer.returnFromIC();
10061 trackAttached("BigInt");
10062 return AttachDecision::Attach;
10065 AttachDecision InlinableNativeIRGenerator::tryAttachBigIntAsIntN() {
10066 // Need two arguments (Int32, BigInt).
10067 if (argc_ != 2 || !args_[0].isInt32() || !args_[1].isBigInt()) {
10068 return AttachDecision::NoAction;
10071 // Negative bits throws an error.
10072 if (args_[0].toInt32() < 0) {
10073 return AttachDecision::NoAction;
10076 // Initialize the input operand.
10077 initializeInputOperand();
10079 // Guard callee is the 'BigInt.asIntN' native function.
10080 emitNativeCalleeGuard();
10082 // Convert bits to int32.
10083 ValOperandId bitsId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10084 Int32OperandId int32BitsId = writer.guardToInt32Index(bitsId);
10086 // Number of bits mustn't be negative.
10087 writer.guardInt32IsNonNegative(int32BitsId);
10089 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
10090 BigIntOperandId bigIntId = writer.guardToBigInt(arg1Id);
10092 writer.bigIntAsIntNResult(int32BitsId, bigIntId);
10093 writer.returnFromIC();
10095 trackAttached("BigIntAsIntN");
10096 return AttachDecision::Attach;
10099 AttachDecision InlinableNativeIRGenerator::tryAttachBigIntAsUintN() {
10100 // Need two arguments (Int32, BigInt).
10101 if (argc_ != 2 || !args_[0].isInt32() || !args_[1].isBigInt()) {
10102 return AttachDecision::NoAction;
10105 // Negative bits throws an error.
10106 if (args_[0].toInt32() < 0) {
10107 return AttachDecision::NoAction;
10110 // Initialize the input operand.
10111 initializeInputOperand();
10113 // Guard callee is the 'BigInt.asUintN' native function.
10114 emitNativeCalleeGuard();
10116 // Convert bits to int32.
10117 ValOperandId bitsId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10118 Int32OperandId int32BitsId = writer.guardToInt32Index(bitsId);
10120 // Number of bits mustn't be negative.
10121 writer.guardInt32IsNonNegative(int32BitsId);
10123 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
10124 BigIntOperandId bigIntId = writer.guardToBigInt(arg1Id);
10126 writer.bigIntAsUintNResult(int32BitsId, bigIntId);
10127 writer.returnFromIC();
10129 trackAttached("BigIntAsUintN");
10130 return AttachDecision::Attach;
10133 AttachDecision InlinableNativeIRGenerator::tryAttachSetHas() {
10134 // Ensure |this| is a SetObject.
10135 if (!thisval_.isObject() || !thisval_.toObject().is<SetObject>()) {
10136 return AttachDecision::NoAction;
10139 // Need a single argument.
10140 if (argc_ != 1) {
10141 return AttachDecision::NoAction;
10144 // Initialize the input operand.
10145 initializeInputOperand();
10147 // Guard callee is the 'has' native function.
10148 emitNativeCalleeGuard();
10150 // Guard |this| is a SetObject.
10151 ValOperandId thisValId =
10152 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
10153 ObjOperandId objId = writer.guardToObject(thisValId);
10154 emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Set);
10156 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10158 #ifndef JS_CODEGEN_X86
10159 // Assume the hash key will likely always have the same type when attaching
10160 // the first stub. If the call is polymorphic on the hash key, attach a stub
10161 // which handles any value.
10162 if (isFirstStub()) {
10163 switch (args_[0].type()) {
10164 case ValueType::Double:
10165 case ValueType::Int32:
10166 case ValueType::Boolean:
10167 case ValueType::Undefined:
10168 case ValueType::Null: {
10169 writer.guardToNonGCThing(argId);
10170 writer.setHasNonGCThingResult(objId, argId);
10171 break;
10173 case ValueType::String: {
10174 StringOperandId strId = writer.guardToString(argId);
10175 writer.setHasStringResult(objId, strId);
10176 break;
10178 case ValueType::Symbol: {
10179 SymbolOperandId symId = writer.guardToSymbol(argId);
10180 writer.setHasSymbolResult(objId, symId);
10181 break;
10183 case ValueType::BigInt: {
10184 BigIntOperandId bigIntId = writer.guardToBigInt(argId);
10185 writer.setHasBigIntResult(objId, bigIntId);
10186 break;
10188 case ValueType::Object: {
10189 // Currently only supported on 64-bit platforms.
10190 # ifdef JS_PUNBOX64
10191 ObjOperandId valId = writer.guardToObject(argId);
10192 writer.setHasObjectResult(objId, valId);
10193 # else
10194 writer.setHasResult(objId, argId);
10195 # endif
10196 break;
10199 # ifdef ENABLE_RECORD_TUPLE
10200 case ValueType::ExtendedPrimitive:
10201 # endif
10202 case ValueType::Magic:
10203 case ValueType::PrivateGCThing:
10204 MOZ_CRASH("Unexpected type");
10206 } else {
10207 writer.setHasResult(objId, argId);
10209 #else
10210 // The optimized versions require too many registers on x86.
10211 writer.setHasResult(objId, argId);
10212 #endif
10214 writer.returnFromIC();
10216 trackAttached("SetHas");
10217 return AttachDecision::Attach;
10220 AttachDecision InlinableNativeIRGenerator::tryAttachSetSize() {
10221 // Ensure |this| is a SetObject.
10222 if (!thisval_.isObject() || !thisval_.toObject().is<SetObject>()) {
10223 return AttachDecision::NoAction;
10226 // Expecting no arguments.
10227 if (argc_ != 0) {
10228 return AttachDecision::NoAction;
10231 // Initialize the input operand.
10232 initializeInputOperand();
10234 // Guard callee is the 'size' native function.
10235 emitNativeCalleeGuard();
10237 // Guard |this| is a SetObject.
10238 ValOperandId thisValId =
10239 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
10240 ObjOperandId objId = writer.guardToObject(thisValId);
10241 writer.guardClass(objId, GuardClassKind::Set);
10243 writer.setSizeResult(objId);
10244 writer.returnFromIC();
10246 trackAttached("SetSize");
10247 return AttachDecision::Attach;
10250 AttachDecision InlinableNativeIRGenerator::tryAttachMapHas() {
10251 // Ensure |this| is a MapObject.
10252 if (!thisval_.isObject() || !thisval_.toObject().is<MapObject>()) {
10253 return AttachDecision::NoAction;
10256 // Need a single argument.
10257 if (argc_ != 1) {
10258 return AttachDecision::NoAction;
10261 // Initialize the input operand.
10262 initializeInputOperand();
10264 // Guard callee is the 'has' native function.
10265 emitNativeCalleeGuard();
10267 // Guard |this| is a MapObject.
10268 ValOperandId thisValId =
10269 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
10270 ObjOperandId objId = writer.guardToObject(thisValId);
10271 emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Map);
10273 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10275 #ifndef JS_CODEGEN_X86
10276 // Assume the hash key will likely always have the same type when attaching
10277 // the first stub. If the call is polymorphic on the hash key, attach a stub
10278 // which handles any value.
10279 if (isFirstStub()) {
10280 switch (args_[0].type()) {
10281 case ValueType::Double:
10282 case ValueType::Int32:
10283 case ValueType::Boolean:
10284 case ValueType::Undefined:
10285 case ValueType::Null: {
10286 writer.guardToNonGCThing(argId);
10287 writer.mapHasNonGCThingResult(objId, argId);
10288 break;
10290 case ValueType::String: {
10291 StringOperandId strId = writer.guardToString(argId);
10292 writer.mapHasStringResult(objId, strId);
10293 break;
10295 case ValueType::Symbol: {
10296 SymbolOperandId symId = writer.guardToSymbol(argId);
10297 writer.mapHasSymbolResult(objId, symId);
10298 break;
10300 case ValueType::BigInt: {
10301 BigIntOperandId bigIntId = writer.guardToBigInt(argId);
10302 writer.mapHasBigIntResult(objId, bigIntId);
10303 break;
10305 case ValueType::Object: {
10306 // Currently only supported on 64-bit platforms.
10307 # ifdef JS_PUNBOX64
10308 ObjOperandId valId = writer.guardToObject(argId);
10309 writer.mapHasObjectResult(objId, valId);
10310 # else
10311 writer.mapHasResult(objId, argId);
10312 # endif
10313 break;
10316 # ifdef ENABLE_RECORD_TUPLE
10317 case ValueType::ExtendedPrimitive:
10318 # endif
10319 case ValueType::Magic:
10320 case ValueType::PrivateGCThing:
10321 MOZ_CRASH("Unexpected type");
10323 } else {
10324 writer.mapHasResult(objId, argId);
10326 #else
10327 // The optimized versions require too many registers on x86.
10328 writer.mapHasResult(objId, argId);
10329 #endif
10331 writer.returnFromIC();
10333 trackAttached("MapHas");
10334 return AttachDecision::Attach;
10337 AttachDecision InlinableNativeIRGenerator::tryAttachMapGet() {
10338 // Ensure |this| is a MapObject.
10339 if (!thisval_.isObject() || !thisval_.toObject().is<MapObject>()) {
10340 return AttachDecision::NoAction;
10343 // Need a single argument.
10344 if (argc_ != 1) {
10345 return AttachDecision::NoAction;
10348 // Initialize the input operand.
10349 initializeInputOperand();
10351 // Guard callee is the 'get' native function.
10352 emitNativeCalleeGuard();
10354 // Guard |this| is a MapObject.
10355 ValOperandId thisValId =
10356 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
10357 ObjOperandId objId = writer.guardToObject(thisValId);
10358 emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Map);
10360 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10362 #ifndef JS_CODEGEN_X86
10363 // Assume the hash key will likely always have the same type when attaching
10364 // the first stub. If the call is polymorphic on the hash key, attach a stub
10365 // which handles any value.
10366 if (isFirstStub()) {
10367 switch (args_[0].type()) {
10368 case ValueType::Double:
10369 case ValueType::Int32:
10370 case ValueType::Boolean:
10371 case ValueType::Undefined:
10372 case ValueType::Null: {
10373 writer.guardToNonGCThing(argId);
10374 writer.mapGetNonGCThingResult(objId, argId);
10375 break;
10377 case ValueType::String: {
10378 StringOperandId strId = writer.guardToString(argId);
10379 writer.mapGetStringResult(objId, strId);
10380 break;
10382 case ValueType::Symbol: {
10383 SymbolOperandId symId = writer.guardToSymbol(argId);
10384 writer.mapGetSymbolResult(objId, symId);
10385 break;
10387 case ValueType::BigInt: {
10388 BigIntOperandId bigIntId = writer.guardToBigInt(argId);
10389 writer.mapGetBigIntResult(objId, bigIntId);
10390 break;
10392 case ValueType::Object: {
10393 // Currently only supported on 64-bit platforms.
10394 # ifdef JS_PUNBOX64
10395 ObjOperandId valId = writer.guardToObject(argId);
10396 writer.mapGetObjectResult(objId, valId);
10397 # else
10398 writer.mapGetResult(objId, argId);
10399 # endif
10400 break;
10403 # ifdef ENABLE_RECORD_TUPLE
10404 case ValueType::ExtendedPrimitive:
10405 # endif
10406 case ValueType::Magic:
10407 case ValueType::PrivateGCThing:
10408 MOZ_CRASH("Unexpected type");
10410 } else {
10411 writer.mapGetResult(objId, argId);
10413 #else
10414 // The optimized versions require too many registers on x86.
10415 writer.mapGetResult(objId, argId);
10416 #endif
10418 writer.returnFromIC();
10420 trackAttached("MapGet");
10421 return AttachDecision::Attach;
10424 AttachDecision CallIRGenerator::tryAttachFunCall(HandleFunction callee) {
10425 MOZ_ASSERT(callee->isNativeWithoutJitEntry());
10427 if (callee->native() != fun_call) {
10428 return AttachDecision::NoAction;
10431 if (!thisval_.isObject() || !thisval_.toObject().is<JSFunction>()) {
10432 return AttachDecision::NoAction;
10434 RootedFunction target(cx_, &thisval_.toObject().as<JSFunction>());
10436 bool isScripted = target->hasJitEntry();
10437 MOZ_ASSERT_IF(!isScripted, target->isNativeWithoutJitEntry());
10439 if (target->isClassConstructor()) {
10440 return AttachDecision::NoAction;
10442 Int32OperandId argcId(writer.setInputOperandId(0));
10444 CallFlags targetFlags(CallFlags::FunCall);
10445 if (mode_ == ICState::Mode::Specialized) {
10446 if (cx_->realm() == target->realm()) {
10447 targetFlags.setIsSameRealm();
10451 if (mode_ == ICState::Mode::Specialized && !isScripted && argc_ > 0) {
10452 // The stack layout is already in the correct form for calls with at least
10453 // one argument.
10455 // clang-format off
10457 // *** STACK LAYOUT (bottom to top) *** *** INDEX ***
10458 // Callee <-- argc+1
10459 // ThisValue <-- argc
10460 // Args: | Arg0 | <-- argc-1
10461 // | Arg1 | <-- argc-2
10462 // | ... | <-- ...
10463 // | ArgN | <-- 0
10465 // When passing |argc-1| as the number of arguments, we get:
10467 // *** STACK LAYOUT (bottom to top) *** *** INDEX ***
10468 // Callee <-- (argc-1)+1 = argc = ThisValue
10469 // ThisValue <-- (argc-1) = argc-1 = Arg0
10470 // Args: | Arg0 | <-- (argc-1)-1 = argc-2 = Arg1
10471 // | Arg1 | <-- (argc-1)-2 = argc-3 = Arg2
10472 // | ... | <-- ...
10474 // clang-format on
10476 // This allows to call |loadArgumentFixedSlot(ArgumentKind::Arg0)| and we
10477 // still load the correct argument index from |ArgumentKind::Arg1|.
10479 // When no arguments are passed, i.e. |argc==0|, we have to replace
10480 // |ArgumentKind::Arg0| with the undefined value. But we don't yet support
10481 // this case.
10482 HandleValue newTarget = NullHandleValue;
10483 HandleValue thisValue = args_[0];
10484 HandleValueArray args =
10485 HandleValueArray::subarray(args_, 1, args_.length() - 1);
10487 // Check for specific native-function optimizations.
10488 InlinableNativeIRGenerator nativeGen(*this, target, newTarget, thisValue,
10489 args, targetFlags);
10490 TRY_ATTACH(nativeGen.tryAttachStub());
10493 ObjOperandId thisObjId = emitFunCallGuard(argcId);
10495 if (mode_ == ICState::Mode::Specialized) {
10496 // Ensure that |this| is the expected target function.
10497 emitCalleeGuard(thisObjId, target);
10499 if (isScripted) {
10500 writer.callScriptedFunction(thisObjId, argcId, targetFlags,
10501 ClampFixedArgc(argc_));
10502 } else {
10503 writer.callNativeFunction(thisObjId, argcId, op_, target, targetFlags,
10504 ClampFixedArgc(argc_));
10506 } else {
10507 // Guard that |this| is a function.
10508 writer.guardClass(thisObjId, GuardClassKind::JSFunction);
10510 // Guard that function is not a class constructor.
10511 writer.guardNotClassConstructor(thisObjId);
10513 if (isScripted) {
10514 writer.guardFunctionHasJitEntry(thisObjId);
10515 writer.callScriptedFunction(thisObjId, argcId, targetFlags,
10516 ClampFixedArgc(argc_));
10517 } else {
10518 writer.guardFunctionHasNoJitEntry(thisObjId);
10519 writer.callAnyNativeFunction(thisObjId, argcId, targetFlags,
10520 ClampFixedArgc(argc_));
10524 writer.returnFromIC();
10526 if (isScripted) {
10527 trackAttached("Scripted fun_call");
10528 } else {
10529 trackAttached("Native fun_call");
10532 return AttachDecision::Attach;
10535 AttachDecision InlinableNativeIRGenerator::tryAttachIsTypedArray(
10536 bool isPossiblyWrapped) {
10537 // Self-hosted code calls this with a single object argument.
10538 MOZ_ASSERT(argc_ == 1);
10539 MOZ_ASSERT(args_[0].isObject());
10541 // Initialize the input operand.
10542 initializeInputOperand();
10544 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10546 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10547 ObjOperandId objArgId = writer.guardToObject(argId);
10548 writer.isTypedArrayResult(objArgId, isPossiblyWrapped);
10549 writer.returnFromIC();
10551 trackAttached(isPossiblyWrapped ? "IsPossiblyWrappedTypedArray"
10552 : "IsTypedArray");
10553 return AttachDecision::Attach;
10556 AttachDecision InlinableNativeIRGenerator::tryAttachIsTypedArrayConstructor() {
10557 // Self-hosted code calls this with a single object argument.
10558 MOZ_ASSERT(argc_ == 1);
10559 MOZ_ASSERT(args_[0].isObject());
10561 // Initialize the input operand.
10562 initializeInputOperand();
10564 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10566 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10567 ObjOperandId objArgId = writer.guardToObject(argId);
10568 writer.isTypedArrayConstructorResult(objArgId);
10569 writer.returnFromIC();
10571 trackAttached("IsTypedArrayConstructor");
10572 return AttachDecision::Attach;
10575 AttachDecision InlinableNativeIRGenerator::tryAttachTypedArrayByteOffset() {
10576 // Self-hosted code calls this with a single TypedArrayObject argument.
10577 MOZ_ASSERT(argc_ == 1);
10578 MOZ_ASSERT(args_[0].isObject());
10579 MOZ_ASSERT(args_[0].toObject().is<TypedArrayObject>());
10581 auto* tarr = &args_[0].toObject().as<TypedArrayObject>();
10583 // Initialize the input operand.
10584 initializeInputOperand();
10586 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10588 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10589 ObjOperandId objArgId = writer.guardToObject(argId);
10591 EmitGuardTypedArray(writer, tarr, objArgId);
10593 size_t byteOffset = tarr->byteOffsetMaybeOutOfBounds();
10594 if (tarr->is<FixedLengthTypedArrayObject>()) {
10595 if (byteOffset <= INT32_MAX) {
10596 writer.arrayBufferViewByteOffsetInt32Result(objArgId);
10597 } else {
10598 writer.arrayBufferViewByteOffsetDoubleResult(objArgId);
10600 } else {
10601 if (byteOffset <= INT32_MAX) {
10602 writer.resizableTypedArrayByteOffsetMaybeOutOfBoundsInt32Result(objArgId);
10603 } else {
10604 writer.resizableTypedArrayByteOffsetMaybeOutOfBoundsDoubleResult(
10605 objArgId);
10609 writer.returnFromIC();
10611 trackAttached("IntrinsicTypedArrayByteOffset");
10612 return AttachDecision::Attach;
10615 AttachDecision InlinableNativeIRGenerator::tryAttachTypedArrayElementSize() {
10616 // Self-hosted code calls this with a single TypedArrayObject argument.
10617 MOZ_ASSERT(argc_ == 1);
10618 MOZ_ASSERT(args_[0].isObject());
10619 MOZ_ASSERT(args_[0].toObject().is<TypedArrayObject>());
10621 // Initialize the input operand.
10622 initializeInputOperand();
10624 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10626 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10627 ObjOperandId objArgId = writer.guardToObject(argId);
10628 writer.typedArrayElementSizeResult(objArgId);
10629 writer.returnFromIC();
10631 trackAttached("TypedArrayElementSize");
10632 return AttachDecision::Attach;
10635 AttachDecision InlinableNativeIRGenerator::tryAttachTypedArrayLength(
10636 bool isPossiblyWrapped, bool allowOutOfBounds) {
10637 // Self-hosted code calls this with a single, possibly wrapped,
10638 // TypedArrayObject argument.
10639 MOZ_ASSERT(argc_ == 1);
10640 MOZ_ASSERT(args_[0].isObject());
10642 // Only optimize when the object isn't a wrapper.
10643 if (isPossiblyWrapped && IsWrapper(&args_[0].toObject())) {
10644 return AttachDecision::NoAction;
10647 MOZ_ASSERT(args_[0].toObject().is<TypedArrayObject>());
10649 auto* tarr = &args_[0].toObject().as<TypedArrayObject>();
10651 // Don't optimize when a resizable TypedArray is out-of-bounds and
10652 // out-of-bounds isn't allowed.
10653 auto length = tarr->length();
10654 if (length.isNothing() && !tarr->hasDetachedBuffer()) {
10655 MOZ_ASSERT(tarr->is<ResizableTypedArrayObject>());
10656 MOZ_ASSERT(tarr->isOutOfBounds());
10658 if (!allowOutOfBounds) {
10659 return AttachDecision::NoAction;
10663 // Initialize the input operand.
10664 initializeInputOperand();
10666 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10668 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10669 ObjOperandId objArgId = writer.guardToObject(argId);
10671 if (isPossiblyWrapped) {
10672 writer.guardIsNotProxy(objArgId);
10675 EmitGuardTypedArray(writer, tarr, objArgId);
10677 if (tarr->is<FixedLengthTypedArrayObject>()) {
10678 if (length.valueOr(0) <= INT32_MAX) {
10679 writer.loadArrayBufferViewLengthInt32Result(objArgId);
10680 } else {
10681 writer.loadArrayBufferViewLengthDoubleResult(objArgId);
10683 } else {
10684 if (!allowOutOfBounds) {
10685 writer.guardResizableArrayBufferViewInBoundsOrDetached(objArgId);
10688 if (length.valueOr(0) <= INT32_MAX) {
10689 writer.resizableTypedArrayLengthInt32Result(objArgId);
10690 } else {
10691 writer.resizableTypedArrayLengthDoubleResult(objArgId);
10694 writer.returnFromIC();
10696 trackAttached("IntrinsicTypedArrayLength");
10697 return AttachDecision::Attach;
10700 AttachDecision InlinableNativeIRGenerator::tryAttachArrayBufferByteLength(
10701 bool isPossiblyWrapped) {
10702 // Self-hosted code calls this with a single, possibly wrapped,
10703 // ArrayBufferObject argument.
10704 MOZ_ASSERT(argc_ == 1);
10705 MOZ_ASSERT(args_[0].isObject());
10707 // Only optimize when the object isn't a wrapper.
10708 if (isPossiblyWrapped && IsWrapper(&args_[0].toObject())) {
10709 return AttachDecision::NoAction;
10712 MOZ_ASSERT(args_[0].toObject().is<ArrayBufferObject>());
10714 auto* buffer = &args_[0].toObject().as<ArrayBufferObject>();
10716 // Initialize the input operand.
10717 initializeInputOperand();
10719 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10721 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10722 ObjOperandId objArgId = writer.guardToObject(argId);
10724 if (isPossiblyWrapped) {
10725 writer.guardIsNotProxy(objArgId);
10728 if (buffer->byteLength() <= INT32_MAX) {
10729 writer.loadArrayBufferByteLengthInt32Result(objArgId);
10730 } else {
10731 writer.loadArrayBufferByteLengthDoubleResult(objArgId);
10733 writer.returnFromIC();
10735 trackAttached("ArrayBufferByteLength");
10736 return AttachDecision::Attach;
10739 AttachDecision InlinableNativeIRGenerator::tryAttachIsConstructing() {
10740 // Self-hosted code calls this with no arguments in function scripts.
10741 MOZ_ASSERT(argc_ == 0);
10742 MOZ_ASSERT(script()->isFunction());
10744 // Initialize the input operand.
10745 initializeInputOperand();
10747 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10749 writer.frameIsConstructingResult();
10750 writer.returnFromIC();
10752 trackAttached("IsConstructing");
10753 return AttachDecision::Attach;
10756 AttachDecision
10757 InlinableNativeIRGenerator::tryAttachGetNextMapSetEntryForIterator(bool isMap) {
10758 // Self-hosted code calls this with two objects.
10759 MOZ_ASSERT(argc_ == 2);
10760 if (isMap) {
10761 MOZ_ASSERT(args_[0].toObject().is<MapIteratorObject>());
10762 } else {
10763 MOZ_ASSERT(args_[0].toObject().is<SetIteratorObject>());
10765 MOZ_ASSERT(args_[1].toObject().is<ArrayObject>());
10767 // Initialize the input operand.
10768 initializeInputOperand();
10770 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10772 ValOperandId iterId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10773 ObjOperandId objIterId = writer.guardToObject(iterId);
10775 ValOperandId resultArrId =
10776 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
10777 ObjOperandId objResultArrId = writer.guardToObject(resultArrId);
10779 writer.getNextMapSetEntryForIteratorResult(objIterId, objResultArrId, isMap);
10780 writer.returnFromIC();
10782 trackAttached("GetNextMapSetEntryForIterator");
10783 return AttachDecision::Attach;
10786 AttachDecision InlinableNativeIRGenerator::tryAttachNewArrayIterator() {
10787 // Self-hosted code calls this without any arguments
10788 MOZ_ASSERT(argc_ == 0);
10790 JSObject* templateObj = NewArrayIteratorTemplate(cx_);
10791 if (!templateObj) {
10792 cx_->recoverFromOutOfMemory();
10793 return AttachDecision::NoAction;
10796 // Initialize the input operand.
10797 initializeInputOperand();
10799 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10801 writer.newArrayIteratorResult(templateObj);
10802 writer.returnFromIC();
10804 trackAttached("NewArrayIterator");
10805 return AttachDecision::Attach;
10808 AttachDecision InlinableNativeIRGenerator::tryAttachNewStringIterator() {
10809 // Self-hosted code calls this without any arguments
10810 MOZ_ASSERT(argc_ == 0);
10812 JSObject* templateObj = NewStringIteratorTemplate(cx_);
10813 if (!templateObj) {
10814 cx_->recoverFromOutOfMemory();
10815 return AttachDecision::NoAction;
10818 // Initialize the input operand.
10819 initializeInputOperand();
10821 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10823 writer.newStringIteratorResult(templateObj);
10824 writer.returnFromIC();
10826 trackAttached("NewStringIterator");
10827 return AttachDecision::Attach;
10830 AttachDecision InlinableNativeIRGenerator::tryAttachNewRegExpStringIterator() {
10831 // Self-hosted code calls this without any arguments
10832 MOZ_ASSERT(argc_ == 0);
10834 JSObject* templateObj = NewRegExpStringIteratorTemplate(cx_);
10835 if (!templateObj) {
10836 cx_->recoverFromOutOfMemory();
10837 return AttachDecision::NoAction;
10840 // Initialize the input operand.
10841 initializeInputOperand();
10843 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10845 writer.newRegExpStringIteratorResult(templateObj);
10846 writer.returnFromIC();
10848 trackAttached("NewRegExpStringIterator");
10849 return AttachDecision::Attach;
10852 AttachDecision
10853 InlinableNativeIRGenerator::tryAttachArrayIteratorPrototypeOptimizable() {
10854 // Self-hosted code calls this without any arguments
10855 MOZ_ASSERT(argc_ == 0);
10857 if (!isFirstStub()) {
10858 // Attach only once to prevent slowdowns for polymorphic calls.
10859 return AttachDecision::NoAction;
10862 Rooted<NativeObject*> arrayIteratorProto(cx_);
10863 uint32_t slot;
10864 Rooted<JSFunction*> nextFun(cx_);
10865 if (!IsArrayIteratorPrototypeOptimizable(cx_, AllowIteratorReturn::Yes,
10866 &arrayIteratorProto, &slot,
10867 &nextFun)) {
10868 return AttachDecision::NoAction;
10871 // Initialize the input operand.
10872 initializeInputOperand();
10874 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10876 ObjOperandId protoId = writer.loadObject(arrayIteratorProto);
10877 ObjOperandId nextId = writer.loadObject(nextFun);
10879 writer.guardShape(protoId, arrayIteratorProto->shape());
10881 // Ensure that proto[slot] == nextFun.
10882 writer.guardDynamicSlotIsSpecificObject(protoId, nextId, slot);
10883 writer.loadBooleanResult(true);
10884 writer.returnFromIC();
10886 trackAttached("ArrayIteratorPrototypeOptimizable");
10887 return AttachDecision::Attach;
10890 AttachDecision InlinableNativeIRGenerator::tryAttachObjectCreate() {
10891 // Need a single object-or-null argument.
10892 if (argc_ != 1 || !args_[0].isObjectOrNull()) {
10893 return AttachDecision::NoAction;
10896 if (!isFirstStub()) {
10897 // Attach only once to prevent slowdowns for polymorphic calls.
10898 return AttachDecision::NoAction;
10901 RootedObject proto(cx_, args_[0].toObjectOrNull());
10902 JSObject* templateObj = ObjectCreateImpl(cx_, proto, TenuredObject);
10903 if (!templateObj) {
10904 cx_->recoverFromOutOfMemory();
10905 return AttachDecision::NoAction;
10908 // Initialize the input operand.
10909 initializeInputOperand();
10911 // Guard callee is the 'create' native function.
10912 emitNativeCalleeGuard();
10914 // Guard on the proto argument.
10915 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10916 if (proto) {
10917 ObjOperandId protoId = writer.guardToObject(argId);
10918 writer.guardSpecificObject(protoId, proto);
10919 } else {
10920 writer.guardIsNull(argId);
10923 writer.objectCreateResult(templateObj);
10924 writer.returnFromIC();
10926 trackAttached("ObjectCreate");
10927 return AttachDecision::Attach;
10930 AttachDecision InlinableNativeIRGenerator::tryAttachObjectConstructor() {
10931 // Expecting no arguments or a single object argument.
10932 // TODO(Warp): Support all or more conversions to object.
10933 if (argc_ > 1) {
10934 return AttachDecision::NoAction;
10936 if (argc_ == 1 && !args_[0].isObject()) {
10937 return AttachDecision::NoAction;
10940 gc::AllocSite* site = nullptr;
10941 PlainObject* templateObj = nullptr;
10942 if (argc_ == 0) {
10943 // Stub doesn't support metadata builder
10944 if (cx_->realm()->hasAllocationMetadataBuilder()) {
10945 return AttachDecision::NoAction;
10948 site = generator_.maybeCreateAllocSite();
10949 if (!site) {
10950 return AttachDecision::NoAction;
10953 // Create a temporary object to act as the template object.
10954 templateObj = NewPlainObjectWithAllocKind(cx_, NewObjectGCKind());
10955 if (!templateObj) {
10956 cx_->recoverFromOutOfMemory();
10957 return AttachDecision::NoAction;
10961 // Initialize the input operand.
10962 initializeInputOperand();
10964 // Guard callee and newTarget (if constructing) are this Object constructor
10965 // function.
10966 emitNativeCalleeGuard();
10968 if (argc_ == 0) {
10969 uint32_t numFixedSlots = templateObj->numUsedFixedSlots();
10970 uint32_t numDynamicSlots = templateObj->numDynamicSlots();
10971 gc::AllocKind allocKind = templateObj->allocKindForTenure();
10972 Shape* shape = templateObj->shape();
10974 writer.guardNoAllocationMetadataBuilder(
10975 cx_->realm()->addressOfMetadataBuilder());
10976 writer.newPlainObjectResult(numFixedSlots, numDynamicSlots, allocKind,
10977 shape, site);
10978 } else {
10979 // Use standard call flags when this is an inline Function.prototype.call(),
10980 // because GetIndexOfArgument() doesn't yet support |CallFlags::FunCall|.
10981 CallFlags flags = flags_;
10982 if (flags.getArgFormat() == CallFlags::FunCall) {
10983 flags = CallFlags(CallFlags::Standard);
10986 // Guard that the argument is an object.
10987 ValOperandId argId =
10988 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_, flags);
10989 ObjOperandId objId = writer.guardToObject(argId);
10991 // Return the object.
10992 writer.loadObjectResult(objId);
10995 writer.returnFromIC();
10997 trackAttached("ObjectConstructor");
10998 return AttachDecision::Attach;
11001 AttachDecision InlinableNativeIRGenerator::tryAttachArrayConstructor() {
11002 // Only optimize the |Array()| and |Array(n)| cases (with or without |new|)
11003 // for now. Note that self-hosted code calls this without |new| via std_Array.
11004 if (argc_ > 1) {
11005 return AttachDecision::NoAction;
11007 if (argc_ == 1 && !args_[0].isInt32()) {
11008 return AttachDecision::NoAction;
11011 int32_t length = (argc_ == 1) ? args_[0].toInt32() : 0;
11012 if (length < 0 || uint32_t(length) > ArrayObject::EagerAllocationMaxLength) {
11013 return AttachDecision::NoAction;
11016 // We allow inlining this function across realms so make sure the template
11017 // object is allocated in that realm. See CanInlineNativeCrossRealm.
11018 JSObject* templateObj;
11020 AutoRealm ar(cx_, callee_);
11021 templateObj = NewDenseFullyAllocatedArray(cx_, length, TenuredObject);
11022 if (!templateObj) {
11023 cx_->clearPendingException();
11024 return AttachDecision::NoAction;
11028 gc::AllocSite* site = generator_.maybeCreateAllocSite();
11029 if (!site) {
11030 return AttachDecision::NoAction;
11033 // Initialize the input operand.
11034 initializeInputOperand();
11036 // Guard callee and newTarget (if constructing) are this Array constructor
11037 // function.
11038 emitNativeCalleeGuard();
11040 Int32OperandId lengthId;
11041 if (argc_ == 1) {
11042 // Use standard call flags when this is an inline Function.prototype.call(),
11043 // because GetIndexOfArgument() doesn't yet support |CallFlags::FunCall|.
11044 CallFlags flags = flags_;
11045 if (flags.getArgFormat() == CallFlags::FunCall) {
11046 flags = CallFlags(CallFlags::Standard);
11049 ValOperandId arg0Id =
11050 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_, flags);
11051 lengthId = writer.guardToInt32(arg0Id);
11052 } else {
11053 MOZ_ASSERT(argc_ == 0);
11054 lengthId = writer.loadInt32Constant(0);
11057 writer.newArrayFromLengthResult(templateObj, lengthId, site);
11058 writer.returnFromIC();
11060 trackAttached("ArrayConstructor");
11061 return AttachDecision::Attach;
11064 AttachDecision InlinableNativeIRGenerator::tryAttachTypedArrayConstructor() {
11065 MOZ_ASSERT(flags_.isConstructing());
11067 if (argc_ == 0 || argc_ > 3) {
11068 return AttachDecision::NoAction;
11071 if (!isFirstStub()) {
11072 // Attach only once to prevent slowdowns for polymorphic calls.
11073 return AttachDecision::NoAction;
11076 // The first argument must be int32 or a non-proxy object.
11077 if (!args_[0].isInt32() && !args_[0].isObject()) {
11078 return AttachDecision::NoAction;
11080 if (args_[0].isObject() && args_[0].toObject().is<ProxyObject>()) {
11081 return AttachDecision::NoAction;
11084 #ifdef JS_CODEGEN_X86
11085 // Unfortunately NewTypedArrayFromArrayBufferResult needs more registers than
11086 // we can easily support on 32-bit x86 for now.
11087 if (args_[0].isObject() &&
11088 args_[0].toObject().is<ArrayBufferObjectMaybeShared>()) {
11089 return AttachDecision::NoAction;
11091 #endif
11093 RootedObject templateObj(cx_);
11094 if (!TypedArrayObject::GetTemplateObjectForNative(cx_, callee_->native(),
11095 args_, &templateObj)) {
11096 cx_->recoverFromOutOfMemory();
11097 return AttachDecision::NoAction;
11100 if (!templateObj) {
11101 // This can happen for large length values.
11102 MOZ_ASSERT(args_[0].isInt32());
11103 return AttachDecision::NoAction;
11106 // Initialize the input operand.
11107 initializeInputOperand();
11109 // Guard callee and newTarget are this TypedArray constructor function.
11110 emitNativeCalleeGuard();
11112 ValOperandId arg0Id =
11113 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_, flags_);
11115 if (args_[0].isInt32()) {
11116 // From length.
11117 Int32OperandId lengthId = writer.guardToInt32(arg0Id);
11118 writer.newTypedArrayFromLengthResult(templateObj, lengthId);
11119 } else {
11120 JSObject* obj = &args_[0].toObject();
11121 ObjOperandId objId = writer.guardToObject(arg0Id);
11123 if (obj->is<ArrayBufferObjectMaybeShared>()) {
11124 // From ArrayBuffer.
11125 if (obj->is<FixedLengthArrayBufferObject>()) {
11126 writer.guardClass(objId, GuardClassKind::FixedLengthArrayBuffer);
11127 } else if (obj->is<FixedLengthSharedArrayBufferObject>()) {
11128 writer.guardClass(objId, GuardClassKind::FixedLengthSharedArrayBuffer);
11129 } else if (obj->is<ResizableArrayBufferObject>()) {
11130 writer.guardClass(objId, GuardClassKind::ResizableArrayBuffer);
11131 } else {
11132 MOZ_ASSERT(obj->is<GrowableSharedArrayBufferObject>());
11133 writer.guardClass(objId, GuardClassKind::GrowableSharedArrayBuffer);
11135 ValOperandId byteOffsetId;
11136 if (argc_ > 1) {
11137 byteOffsetId =
11138 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_, flags_);
11139 } else {
11140 byteOffsetId = writer.loadUndefined();
11142 ValOperandId lengthId;
11143 if (argc_ > 2) {
11144 lengthId =
11145 writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_, flags_);
11146 } else {
11147 lengthId = writer.loadUndefined();
11149 writer.newTypedArrayFromArrayBufferResult(templateObj, objId,
11150 byteOffsetId, lengthId);
11151 } else {
11152 // From Array-like.
11153 writer.guardIsNotArrayBufferMaybeShared(objId);
11154 writer.guardIsNotProxy(objId);
11155 writer.newTypedArrayFromArrayResult(templateObj, objId);
11159 writer.returnFromIC();
11161 trackAttached("TypedArrayConstructor");
11162 return AttachDecision::Attach;
11165 AttachDecision InlinableNativeIRGenerator::tryAttachSpecializedFunctionBind(
11166 Handle<JSObject*> target, Handle<BoundFunctionObject*> templateObj) {
11167 // Try to attach a faster stub that's more specialized than what we emit in
11168 // tryAttachFunctionBind. This lets us allocate and initialize a bound
11169 // function object in Ion without calling into C++.
11171 // We can do this if:
11173 // * The target's prototype is Function.prototype, because that's the proto we
11174 // use for the template object.
11175 // * All bound arguments can be stored inline.
11176 // * The `.name`, `.length`, and `IsConstructor` values match `target`.
11178 // We initialize the template object with the bound function's name, length,
11179 // and flags. At runtime we then only have to clone the template object and
11180 // initialize the slots for the target, the bound `this` and the bound
11181 // arguments.
11183 if (!isFirstStub()) {
11184 return AttachDecision::NoAction;
11186 if (!target->is<JSFunction>() && !target->is<BoundFunctionObject>()) {
11187 return AttachDecision::NoAction;
11189 if (target->staticPrototype() != &cx_->global()->getFunctionPrototype()) {
11190 return AttachDecision::NoAction;
11192 size_t numBoundArgs = argc_ > 0 ? argc_ - 1 : 0;
11193 if (numBoundArgs > BoundFunctionObject::MaxInlineBoundArgs) {
11194 return AttachDecision::NoAction;
11197 const bool targetIsConstructor = target->isConstructor();
11198 Rooted<JSAtom*> targetName(cx_);
11199 uint32_t targetLength = 0;
11201 if (target->is<JSFunction>()) {
11202 Rooted<JSFunction*> fun(cx_, &target->as<JSFunction>());
11203 if (fun->isNativeFun()) {
11204 return AttachDecision::NoAction;
11206 if (fun->hasResolvedLength() || fun->hasResolvedName()) {
11207 return AttachDecision::NoAction;
11209 uint16_t len;
11210 if (!JSFunction::getUnresolvedLength(cx_, fun, &len)) {
11211 cx_->clearPendingException();
11212 return AttachDecision::NoAction;
11214 targetName = fun->getUnresolvedName(cx_);
11215 if (!targetName) {
11216 cx_->clearPendingException();
11217 return AttachDecision::NoAction;
11220 targetLength = len;
11221 } else {
11222 BoundFunctionObject* bound = &target->as<BoundFunctionObject>();
11223 if (!targetIsConstructor) {
11224 // Only support constructors for now. This lets us use
11225 // GuardBoundFunctionIsConstructor.
11226 return AttachDecision::NoAction;
11228 Shape* initialShape =
11229 cx_->global()->maybeBoundFunctionShapeWithDefaultProto();
11230 if (bound->shape() != initialShape) {
11231 return AttachDecision::NoAction;
11233 Value lenVal = bound->getLengthForInitialShape();
11234 Value nameVal = bound->getNameForInitialShape();
11235 if (!lenVal.isInt32() || lenVal.toInt32() < 0 || !nameVal.isString() ||
11236 !nameVal.toString()->isAtom()) {
11237 return AttachDecision::NoAction;
11239 targetName = &nameVal.toString()->asAtom();
11240 targetLength = uint32_t(lenVal.toInt32());
11243 if (!templateObj->initTemplateSlotsForSpecializedBind(
11244 cx_, numBoundArgs, targetIsConstructor, targetLength, targetName)) {
11245 cx_->recoverFromOutOfMemory();
11246 return AttachDecision::NoAction;
11249 initializeInputOperand();
11250 emitNativeCalleeGuard();
11252 ValOperandId thisValId =
11253 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
11254 ObjOperandId targetId = writer.guardToObject(thisValId);
11256 // Ensure the JSClass and proto match, and that the `length` and `name`
11257 // properties haven't been redefined.
11258 writer.guardShape(targetId, target->shape());
11260 // Emit guards for the `IsConstructor`, `.length`, and `.name` values.
11261 if (target->is<JSFunction>()) {
11262 // Guard on:
11263 // * The BaseScript (because that's what JSFunction uses for the `length`).
11264 // Because MGuardFunctionScript doesn't support self-hosted functions yet,
11265 // we use GuardSpecificFunction instead in this case.
11266 // See assertion in MGuardFunctionScript::getAliasSet.
11267 // * The flags slot (for the CONSTRUCTOR, RESOLVED_NAME, RESOLVED_LENGTH,
11268 // HAS_INFERRED_NAME, and HAS_GUESSED_ATOM flags).
11269 // * The atom slot.
11270 JSFunction* fun = &target->as<JSFunction>();
11271 if (fun->isSelfHostedBuiltin()) {
11272 writer.guardSpecificFunction(targetId, fun);
11273 } else {
11274 writer.guardFunctionScript(targetId, fun->baseScript());
11276 writer.guardFixedSlotValue(
11277 targetId, JSFunction::offsetOfFlagsAndArgCount(),
11278 fun->getReservedSlot(JSFunction::FlagsAndArgCountSlot));
11279 writer.guardFixedSlotValue(targetId, JSFunction::offsetOfAtom(),
11280 fun->getReservedSlot(JSFunction::AtomSlot));
11281 } else {
11282 BoundFunctionObject* bound = &target->as<BoundFunctionObject>();
11283 writer.guardBoundFunctionIsConstructor(targetId);
11284 writer.guardFixedSlotValue(targetId,
11285 BoundFunctionObject::offsetOfLengthSlot(),
11286 bound->getLengthForInitialShape());
11287 writer.guardFixedSlotValue(targetId,
11288 BoundFunctionObject::offsetOfNameSlot(),
11289 bound->getNameForInitialShape());
11292 writer.specializedBindFunctionResult(targetId, argc_, templateObj);
11293 writer.returnFromIC();
11295 trackAttached("SpecializedFunctionBind");
11296 return AttachDecision::Attach;
11299 AttachDecision InlinableNativeIRGenerator::tryAttachFunctionBind() {
11300 // Ensure |this| (the target) is a function object or a bound function object.
11301 // We could support other callables too, but note that we rely on the target
11302 // having a static prototype in BoundFunctionObject::functionBindImpl.
11303 if (!thisval_.isObject()) {
11304 return AttachDecision::NoAction;
11306 Rooted<JSObject*> target(cx_, &thisval_.toObject());
11307 if (!target->is<JSFunction>() && !target->is<BoundFunctionObject>()) {
11308 return AttachDecision::NoAction;
11311 // Only support standard, non-spread calls.
11312 if (flags_.getArgFormat() != CallFlags::Standard) {
11313 return AttachDecision::NoAction;
11316 // Only optimize if the number of arguments is small. This ensures we don't
11317 // compile a lot of different stubs (because we bake in argc) and that we
11318 // don't get anywhere near ARGS_LENGTH_MAX.
11319 static constexpr size_t MaxArguments = 6;
11320 if (argc_ > MaxArguments) {
11321 return AttachDecision::NoAction;
11324 Rooted<BoundFunctionObject*> templateObj(
11325 cx_, BoundFunctionObject::createTemplateObject(cx_));
11326 if (!templateObj) {
11327 cx_->recoverFromOutOfMemory();
11328 return AttachDecision::NoAction;
11331 TRY_ATTACH(tryAttachSpecializedFunctionBind(target, templateObj));
11333 initializeInputOperand();
11335 emitNativeCalleeGuard();
11337 // Guard |this| is a function object or a bound function object.
11338 ValOperandId thisValId =
11339 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
11340 ObjOperandId targetId = writer.guardToObject(thisValId);
11341 if (target->is<JSFunction>()) {
11342 writer.guardClass(targetId, GuardClassKind::JSFunction);
11343 } else {
11344 MOZ_ASSERT(target->is<BoundFunctionObject>());
11345 writer.guardClass(targetId, GuardClassKind::BoundFunction);
11348 writer.bindFunctionResult(targetId, argc_, templateObj);
11349 writer.returnFromIC();
11351 trackAttached("FunctionBind");
11352 return AttachDecision::Attach;
11355 AttachDecision CallIRGenerator::tryAttachFunApply(HandleFunction calleeFunc) {
11356 MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry());
11358 if (calleeFunc->native() != fun_apply) {
11359 return AttachDecision::NoAction;
11362 if (argc_ > 2) {
11363 return AttachDecision::NoAction;
11366 if (!thisval_.isObject() || !thisval_.toObject().is<JSFunction>()) {
11367 return AttachDecision::NoAction;
11369 Rooted<JSFunction*> target(cx_, &thisval_.toObject().as<JSFunction>());
11371 bool isScripted = target->hasJitEntry();
11372 MOZ_ASSERT_IF(!isScripted, target->isNativeWithoutJitEntry());
11374 if (target->isClassConstructor()) {
11375 return AttachDecision::NoAction;
11378 CallFlags::ArgFormat format = CallFlags::Standard;
11379 if (argc_ < 2) {
11380 // |fun.apply()| and |fun.apply(thisValue)| are equivalent to |fun.call()|
11381 // resp. |fun.call(thisValue)|.
11382 format = CallFlags::FunCall;
11383 } else if (args_[1].isNullOrUndefined()) {
11384 // |fun.apply(thisValue, null)| and |fun.apply(thisValue, undefined)| are
11385 // also equivalent to |fun.call(thisValue)|, but we can't use FunCall
11386 // because we have to discard the second argument.
11387 format = CallFlags::FunApplyNullUndefined;
11388 } else if (args_[1].isObject() && args_[1].toObject().is<ArgumentsObject>()) {
11389 auto* argsObj = &args_[1].toObject().as<ArgumentsObject>();
11390 if (argsObj->hasOverriddenElement() || argsObj->anyArgIsForwarded() ||
11391 argsObj->hasOverriddenLength() ||
11392 argsObj->initialLength() > JIT_ARGS_LENGTH_MAX) {
11393 return AttachDecision::NoAction;
11395 format = CallFlags::FunApplyArgsObj;
11396 } else if (args_[1].isObject() && args_[1].toObject().is<ArrayObject>() &&
11397 args_[1].toObject().as<ArrayObject>().length() <=
11398 JIT_ARGS_LENGTH_MAX &&
11399 IsPackedArray(&args_[1].toObject())) {
11400 format = CallFlags::FunApplyArray;
11401 } else {
11402 return AttachDecision::NoAction;
11405 Int32OperandId argcId(writer.setInputOperandId(0));
11407 CallFlags targetFlags(format);
11408 if (mode_ == ICState::Mode::Specialized) {
11409 if (cx_->realm() == target->realm()) {
11410 targetFlags.setIsSameRealm();
11414 if (mode_ == ICState::Mode::Specialized && !isScripted &&
11415 format == CallFlags::FunApplyArray) {
11416 HandleValue newTarget = NullHandleValue;
11417 HandleValue thisValue = args_[0];
11418 Rooted<ArrayObject*> aobj(cx_, &args_[1].toObject().as<ArrayObject>());
11419 HandleValueArray args = HandleValueArray::fromMarkedLocation(
11420 aobj->length(), aobj->getDenseElements());
11422 // Check for specific native-function optimizations.
11423 InlinableNativeIRGenerator nativeGen(*this, target, newTarget, thisValue,
11424 args, targetFlags);
11425 TRY_ATTACH(nativeGen.tryAttachStub());
11428 // Don't inline when no arguments are passed, cf. |tryAttachFunCall()|.
11429 if (mode_ == ICState::Mode::Specialized && !isScripted &&
11430 format == CallFlags::FunCall && argc_ > 0) {
11431 MOZ_ASSERT(argc_ == 1);
11433 HandleValue newTarget = NullHandleValue;
11434 HandleValue thisValue = args_[0];
11435 HandleValueArray args = HandleValueArray::empty();
11437 // Check for specific native-function optimizations.
11438 InlinableNativeIRGenerator nativeGen(*this, target, newTarget, thisValue,
11439 args, targetFlags);
11440 TRY_ATTACH(nativeGen.tryAttachStub());
11443 ObjOperandId thisObjId = emitFunApplyGuard(argcId);
11445 uint32_t fixedArgc;
11446 if (format == CallFlags::FunApplyArray ||
11447 format == CallFlags::FunApplyArgsObj ||
11448 format == CallFlags::FunApplyNullUndefined) {
11449 emitFunApplyArgsGuard(format);
11451 // We always use MaxUnrolledArgCopy here because the fixed argc is
11452 // meaningless in a FunApply case.
11453 fixedArgc = MaxUnrolledArgCopy;
11454 } else {
11455 MOZ_ASSERT(format == CallFlags::FunCall);
11457 // Whereas for the FunCall case we need to use the actual fixed argc value.
11458 fixedArgc = ClampFixedArgc(argc_);
11461 if (mode_ == ICState::Mode::Specialized) {
11462 // Ensure that |this| is the expected target function.
11463 emitCalleeGuard(thisObjId, target);
11465 if (isScripted) {
11466 writer.callScriptedFunction(thisObjId, argcId, targetFlags, fixedArgc);
11467 } else {
11468 writer.callNativeFunction(thisObjId, argcId, op_, target, targetFlags,
11469 fixedArgc);
11471 } else {
11472 // Guard that |this| is a function.
11473 writer.guardClass(thisObjId, GuardClassKind::JSFunction);
11475 // Guard that function is not a class constructor.
11476 writer.guardNotClassConstructor(thisObjId);
11478 if (isScripted) {
11479 // Guard that function is scripted.
11480 writer.guardFunctionHasJitEntry(thisObjId);
11481 writer.callScriptedFunction(thisObjId, argcId, targetFlags, fixedArgc);
11482 } else {
11483 // Guard that function is native.
11484 writer.guardFunctionHasNoJitEntry(thisObjId);
11485 writer.callAnyNativeFunction(thisObjId, argcId, targetFlags, fixedArgc);
11489 writer.returnFromIC();
11491 if (isScripted) {
11492 trackAttached("Call.ScriptedFunApply");
11493 } else {
11494 trackAttached("Call.NativeFunApply");
11497 return AttachDecision::Attach;
11500 AttachDecision CallIRGenerator::tryAttachWasmCall(HandleFunction calleeFunc) {
11501 // Try to optimize calls into Wasm code by emitting the CallWasmFunction
11502 // CacheIR op. Baseline ICs currently treat this as a CallScriptedFunction op
11503 // (calling Wasm's JitEntry stub) but Warp transpiles it to a more direct call
11504 // into Wasm code.
11506 // Note: some code refers to these optimized Wasm calls as "inlined" calls.
11508 MOZ_ASSERT(calleeFunc->isWasmWithJitEntry());
11510 if (!JitOptions.enableWasmIonFastCalls) {
11511 return AttachDecision::NoAction;
11513 if (!isFirstStub_) {
11514 return AttachDecision::NoAction;
11516 JSOp op = JSOp(*pc_);
11517 if (op != JSOp::Call && op != JSOp::CallContent &&
11518 op != JSOp::CallIgnoresRv) {
11519 return AttachDecision::NoAction;
11521 if (cx_->realm() != calleeFunc->realm()) {
11522 return AttachDecision::NoAction;
11525 wasm::Instance& inst = wasm::ExportedFunctionToInstance(calleeFunc);
11526 uint32_t funcIndex = wasm::ExportedFunctionToFuncIndex(calleeFunc);
11527 const wasm::CodeBlock& codeBlock = inst.code().funcCodeBlock(funcIndex);
11528 const wasm::FuncExport& funcExport = codeBlock.lookupFuncExport(funcIndex);
11529 const wasm::FuncType& sig =
11530 wasm::ExportedFunctionToTypeDef(calleeFunc).funcType();
11532 MOZ_ASSERT(!IsInsideNursery(inst.object()));
11533 MOZ_ASSERT(sig.canHaveJitEntry(), "Function should allow a Wasm JitEntry");
11535 // If there are too many arguments, don't optimize (we won't be able to store
11536 // the arguments in the LIR node).
11537 static_assert(wasm::MaxArgsForJitInlineCall <= ArgumentKindArgIndexLimit);
11538 if (sig.args().length() > wasm::MaxArgsForJitInlineCall ||
11539 argc_ > ArgumentKindArgIndexLimit) {
11540 return AttachDecision::NoAction;
11543 // If there are too many results, don't optimize as Warp currently doesn't
11544 // have code to handle this.
11545 if (sig.results().length() > wasm::MaxResultsForJitInlineCall) {
11546 return AttachDecision::NoAction;
11549 // Bug 1631656 - Don't try to optimize with I64 args on 32-bit platforms
11550 // because it is more difficult (because it requires multiple LIR arguments
11551 // per I64).
11553 // Bug 1631650 - On 64-bit platforms, we also give up optimizing for I64 args
11554 // spilled to the stack because it causes problems with register allocation.
11555 #ifdef JS_64BIT
11556 constexpr bool optimizeWithI64 = true;
11557 #else
11558 constexpr bool optimizeWithI64 = false;
11559 #endif
11560 ABIArgGenerator abi;
11561 for (const auto& valType : sig.args()) {
11562 MIRType mirType = valType.toMIRType();
11563 ABIArg abiArg = abi.next(mirType);
11564 if (mirType != MIRType::Int64) {
11565 continue;
11567 if (!optimizeWithI64 || abiArg.kind() == ABIArg::Stack) {
11568 return AttachDecision::NoAction;
11572 // Check that all arguments can be converted to the Wasm type in Warp code
11573 // without bailing out.
11574 for (size_t i = 0; i < sig.args().length(); i++) {
11575 Value argVal = i < argc_ ? args_[i] : UndefinedValue();
11576 switch (sig.args()[i].kind()) {
11577 case wasm::ValType::I32:
11578 case wasm::ValType::F32:
11579 case wasm::ValType::F64:
11580 if (!argVal.isNumber() && !argVal.isBoolean() &&
11581 !argVal.isUndefined()) {
11582 return AttachDecision::NoAction;
11584 break;
11585 case wasm::ValType::I64:
11586 if (!argVal.isBigInt() && !argVal.isBoolean() && !argVal.isString()) {
11587 return AttachDecision::NoAction;
11589 break;
11590 case wasm::ValType::V128:
11591 MOZ_CRASH("Function should not have a Wasm JitEntry");
11592 case wasm::ValType::Ref:
11593 // canHaveJitEntry restricts args to externref, where all JS values are
11594 // valid and can be boxed.
11595 MOZ_ASSERT(sig.args()[i].refType().isExtern(),
11596 "Unexpected type for Wasm JitEntry");
11597 break;
11601 CallFlags flags(/* isConstructing = */ false, /* isSpread = */ false,
11602 /* isSameRealm = */ true);
11604 // Load argc.
11605 Int32OperandId argcId(writer.setInputOperandId(0));
11607 // Load the callee and ensure it is an object
11608 ValOperandId calleeValId =
11609 writer.loadArgumentFixedSlot(ArgumentKind::Callee, argc_, flags);
11610 ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
11612 // Ensure the callee is this Wasm function.
11613 emitCalleeGuard(calleeObjId, calleeFunc);
11615 // Guard the argument types.
11616 uint32_t guardedArgs = std::min<uint32_t>(sig.args().length(), argc_);
11617 for (uint32_t i = 0; i < guardedArgs; i++) {
11618 ArgumentKind argKind = ArgumentKindForArgIndex(i);
11619 ValOperandId argId = writer.loadArgumentFixedSlot(argKind, argc_, flags);
11620 writer.guardWasmArg(argId, sig.args()[i].kind());
11623 writer.callWasmFunction(calleeObjId, argcId, flags, ClampFixedArgc(argc_),
11624 &funcExport, inst.object());
11625 writer.returnFromIC();
11627 trackAttached("Call.WasmCall");
11629 return AttachDecision::Attach;
11632 AttachDecision CallIRGenerator::tryAttachInlinableNative(HandleFunction callee,
11633 CallFlags flags) {
11634 MOZ_ASSERT(mode_ == ICState::Mode::Specialized);
11635 MOZ_ASSERT(callee->isNativeWithoutJitEntry());
11636 MOZ_ASSERT(flags.getArgFormat() == CallFlags::Standard ||
11637 flags.getArgFormat() == CallFlags::Spread);
11639 // Special case functions are only optimized for normal calls.
11640 if (!BytecodeCallOpCanHaveInlinableNative(op_)) {
11641 return AttachDecision::NoAction;
11644 InlinableNativeIRGenerator nativeGen(*this, callee, newTarget_, thisval_,
11645 args_, flags);
11646 return nativeGen.tryAttachStub();
11649 #ifdef FUZZING_JS_FUZZILLI
11650 AttachDecision InlinableNativeIRGenerator::tryAttachFuzzilliHash() {
11651 if (argc_ != 1) {
11652 return AttachDecision::NoAction;
11655 // Initialize the input operand.
11656 initializeInputOperand();
11658 // Guard callee is the 'fuzzilli_hash' native function.
11659 emitNativeCalleeGuard();
11661 ValOperandId argValId =
11662 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
11664 writer.fuzzilliHashResult(argValId);
11665 writer.returnFromIC();
11667 trackAttached("FuzzilliHash");
11668 return AttachDecision::Attach;
11670 #endif
11672 AttachDecision InlinableNativeIRGenerator::tryAttachStub() {
11673 MOZ_ASSERT(BytecodeCallOpCanHaveInlinableNative(generator_.op_));
11675 if (!callee_->hasJitInfo() ||
11676 callee_->jitInfo()->type() != JSJitInfo::InlinableNative) {
11677 return AttachDecision::NoAction;
11680 InlinableNative native = callee_->jitInfo()->inlinableNative;
11682 // Not all natives can be inlined cross-realm.
11683 if (cx_->realm() != callee_->realm() && !CanInlineNativeCrossRealm(native)) {
11684 return AttachDecision::NoAction;
11687 // Check for special-cased native constructors.
11688 if (flags_.isConstructing()) {
11689 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Standard);
11691 // newTarget must match the callee. CacheIR for this is emitted in
11692 // emitNativeCalleeGuard.
11693 if (ObjectValue(*callee_) != newTarget_) {
11694 return AttachDecision::NoAction;
11696 switch (native) {
11697 case InlinableNative::Array:
11698 return tryAttachArrayConstructor();
11699 case InlinableNative::TypedArrayConstructor:
11700 return tryAttachTypedArrayConstructor();
11701 case InlinableNative::String:
11702 return tryAttachStringConstructor();
11703 case InlinableNative::Object:
11704 return tryAttachObjectConstructor();
11705 default:
11706 break;
11708 return AttachDecision::NoAction;
11711 // Check for special-cased native spread calls.
11712 if (flags_.getArgFormat() == CallFlags::Spread ||
11713 flags_.getArgFormat() == CallFlags::FunApplyArray) {
11714 switch (native) {
11715 case InlinableNative::MathMin:
11716 return tryAttachSpreadMathMinMax(/*isMax = */ false);
11717 case InlinableNative::MathMax:
11718 return tryAttachSpreadMathMinMax(/*isMax = */ true);
11719 default:
11720 break;
11722 return AttachDecision::NoAction;
11725 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Standard ||
11726 flags_.getArgFormat() == CallFlags::FunCall);
11728 // Check for special-cased native functions.
11729 switch (native) {
11730 // Array natives.
11731 case InlinableNative::Array:
11732 return tryAttachArrayConstructor();
11733 case InlinableNative::ArrayPush:
11734 return tryAttachArrayPush();
11735 case InlinableNative::ArrayPop:
11736 case InlinableNative::ArrayShift:
11737 return tryAttachArrayPopShift(native);
11738 case InlinableNative::ArrayJoin:
11739 return tryAttachArrayJoin();
11740 case InlinableNative::ArraySlice:
11741 return tryAttachArraySlice();
11742 case InlinableNative::ArrayIsArray:
11743 return tryAttachArrayIsArray();
11745 // DataView natives.
11746 case InlinableNative::DataViewGetInt8:
11747 return tryAttachDataViewGet(Scalar::Int8);
11748 case InlinableNative::DataViewGetUint8:
11749 return tryAttachDataViewGet(Scalar::Uint8);
11750 case InlinableNative::DataViewGetInt16:
11751 return tryAttachDataViewGet(Scalar::Int16);
11752 case InlinableNative::DataViewGetUint16:
11753 return tryAttachDataViewGet(Scalar::Uint16);
11754 case InlinableNative::DataViewGetInt32:
11755 return tryAttachDataViewGet(Scalar::Int32);
11756 case InlinableNative::DataViewGetUint32:
11757 return tryAttachDataViewGet(Scalar::Uint32);
11758 case InlinableNative::DataViewGetFloat16:
11759 return tryAttachDataViewGet(Scalar::Float16);
11760 case InlinableNative::DataViewGetFloat32:
11761 return tryAttachDataViewGet(Scalar::Float32);
11762 case InlinableNative::DataViewGetFloat64:
11763 return tryAttachDataViewGet(Scalar::Float64);
11764 case InlinableNative::DataViewGetBigInt64:
11765 return tryAttachDataViewGet(Scalar::BigInt64);
11766 case InlinableNative::DataViewGetBigUint64:
11767 return tryAttachDataViewGet(Scalar::BigUint64);
11768 case InlinableNative::DataViewSetInt8:
11769 return tryAttachDataViewSet(Scalar::Int8);
11770 case InlinableNative::DataViewSetUint8:
11771 return tryAttachDataViewSet(Scalar::Uint8);
11772 case InlinableNative::DataViewSetInt16:
11773 return tryAttachDataViewSet(Scalar::Int16);
11774 case InlinableNative::DataViewSetUint16:
11775 return tryAttachDataViewSet(Scalar::Uint16);
11776 case InlinableNative::DataViewSetInt32:
11777 return tryAttachDataViewSet(Scalar::Int32);
11778 case InlinableNative::DataViewSetUint32:
11779 return tryAttachDataViewSet(Scalar::Uint32);
11780 case InlinableNative::DataViewSetFloat16:
11781 return tryAttachDataViewSet(Scalar::Float16);
11782 case InlinableNative::DataViewSetFloat32:
11783 return tryAttachDataViewSet(Scalar::Float32);
11784 case InlinableNative::DataViewSetFloat64:
11785 return tryAttachDataViewSet(Scalar::Float64);
11786 case InlinableNative::DataViewSetBigInt64:
11787 return tryAttachDataViewSet(Scalar::BigInt64);
11788 case InlinableNative::DataViewSetBigUint64:
11789 return tryAttachDataViewSet(Scalar::BigUint64);
11791 // Function natives.
11792 case InlinableNative::FunctionBind:
11793 return tryAttachFunctionBind();
11795 // Intl natives.
11796 case InlinableNative::IntlGuardToCollator:
11797 case InlinableNative::IntlGuardToDateTimeFormat:
11798 case InlinableNative::IntlGuardToDisplayNames:
11799 case InlinableNative::IntlGuardToListFormat:
11800 case InlinableNative::IntlGuardToNumberFormat:
11801 case InlinableNative::IntlGuardToPluralRules:
11802 case InlinableNative::IntlGuardToRelativeTimeFormat:
11803 case InlinableNative::IntlGuardToSegmenter:
11804 case InlinableNative::IntlGuardToSegments:
11805 case InlinableNative::IntlGuardToSegmentIterator:
11806 return tryAttachGuardToClass(native);
11808 // Slot intrinsics.
11809 case InlinableNative::IntrinsicUnsafeGetReservedSlot:
11810 case InlinableNative::IntrinsicUnsafeGetObjectFromReservedSlot:
11811 case InlinableNative::IntrinsicUnsafeGetInt32FromReservedSlot:
11812 case InlinableNative::IntrinsicUnsafeGetStringFromReservedSlot:
11813 return tryAttachUnsafeGetReservedSlot(native);
11814 case InlinableNative::IntrinsicUnsafeSetReservedSlot:
11815 return tryAttachUnsafeSetReservedSlot();
11817 // Intrinsics.
11818 case InlinableNative::IntrinsicIsSuspendedGenerator:
11819 return tryAttachIsSuspendedGenerator();
11820 case InlinableNative::IntrinsicToObject:
11821 return tryAttachToObject();
11822 case InlinableNative::IntrinsicToInteger:
11823 return tryAttachToInteger();
11824 case InlinableNative::IntrinsicToLength:
11825 return tryAttachToLength();
11826 case InlinableNative::IntrinsicIsObject:
11827 return tryAttachIsObject();
11828 case InlinableNative::IntrinsicIsPackedArray:
11829 return tryAttachIsPackedArray();
11830 case InlinableNative::IntrinsicIsCallable:
11831 return tryAttachIsCallable();
11832 case InlinableNative::IntrinsicIsConstructor:
11833 return tryAttachIsConstructor();
11834 case InlinableNative::IntrinsicIsCrossRealmArrayConstructor:
11835 return tryAttachIsCrossRealmArrayConstructor();
11836 case InlinableNative::IntrinsicGuardToArrayIterator:
11837 case InlinableNative::IntrinsicGuardToMapIterator:
11838 case InlinableNative::IntrinsicGuardToSetIterator:
11839 case InlinableNative::IntrinsicGuardToStringIterator:
11840 case InlinableNative::IntrinsicGuardToRegExpStringIterator:
11841 case InlinableNative::IntrinsicGuardToWrapForValidIterator:
11842 case InlinableNative::IntrinsicGuardToIteratorHelper:
11843 case InlinableNative::IntrinsicGuardToAsyncIteratorHelper:
11844 #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
11845 case InlinableNative::IntrinsicGuardToAsyncDisposableStack:
11846 #endif
11847 return tryAttachGuardToClass(native);
11848 case InlinableNative::IntrinsicSubstringKernel:
11849 return tryAttachSubstringKernel();
11850 case InlinableNative::IntrinsicIsConstructing:
11851 return tryAttachIsConstructing();
11852 case InlinableNative::IntrinsicNewArrayIterator:
11853 return tryAttachNewArrayIterator();
11854 case InlinableNative::IntrinsicNewStringIterator:
11855 return tryAttachNewStringIterator();
11856 case InlinableNative::IntrinsicNewRegExpStringIterator:
11857 return tryAttachNewRegExpStringIterator();
11858 case InlinableNative::IntrinsicArrayIteratorPrototypeOptimizable:
11859 return tryAttachArrayIteratorPrototypeOptimizable();
11860 case InlinableNative::IntrinsicObjectHasPrototype:
11861 return tryAttachObjectHasPrototype();
11863 // RegExp natives.
11864 case InlinableNative::IsRegExpObject:
11865 return tryAttachHasClass(&RegExpObject::class_,
11866 /* isPossiblyWrapped = */ false);
11867 case InlinableNative::IsPossiblyWrappedRegExpObject:
11868 return tryAttachHasClass(&RegExpObject::class_,
11869 /* isPossiblyWrapped = */ true);
11870 case InlinableNative::RegExpMatcher:
11871 case InlinableNative::RegExpSearcher:
11872 return tryAttachRegExpMatcherSearcher(native);
11873 case InlinableNative::RegExpSearcherLastLimit:
11874 return tryAttachRegExpSearcherLastLimit();
11875 case InlinableNative::RegExpHasCaptureGroups:
11876 return tryAttachRegExpHasCaptureGroups();
11877 case InlinableNative::RegExpPrototypeOptimizable:
11878 return tryAttachRegExpPrototypeOptimizable();
11879 case InlinableNative::RegExpInstanceOptimizable:
11880 return tryAttachRegExpInstanceOptimizable();
11881 case InlinableNative::GetFirstDollarIndex:
11882 return tryAttachGetFirstDollarIndex();
11883 case InlinableNative::IntrinsicRegExpBuiltinExec:
11884 case InlinableNative::IntrinsicRegExpBuiltinExecForTest:
11885 return tryAttachIntrinsicRegExpBuiltinExec(native);
11886 case InlinableNative::IntrinsicRegExpExec:
11887 case InlinableNative::IntrinsicRegExpExecForTest:
11888 return tryAttachIntrinsicRegExpExec(native);
11890 // String natives.
11891 case InlinableNative::String:
11892 return tryAttachString();
11893 case InlinableNative::StringToString:
11894 case InlinableNative::StringValueOf:
11895 return tryAttachStringToStringValueOf();
11896 case InlinableNative::StringCharCodeAt:
11897 return tryAttachStringCharCodeAt();
11898 case InlinableNative::StringCodePointAt:
11899 return tryAttachStringCodePointAt();
11900 case InlinableNative::StringCharAt:
11901 return tryAttachStringCharAt();
11902 case InlinableNative::StringAt:
11903 return tryAttachStringAt();
11904 case InlinableNative::StringFromCharCode:
11905 return tryAttachStringFromCharCode();
11906 case InlinableNative::StringFromCodePoint:
11907 return tryAttachStringFromCodePoint();
11908 case InlinableNative::StringIncludes:
11909 return tryAttachStringIncludes();
11910 case InlinableNative::StringIndexOf:
11911 return tryAttachStringIndexOf();
11912 case InlinableNative::StringLastIndexOf:
11913 return tryAttachStringLastIndexOf();
11914 case InlinableNative::StringStartsWith:
11915 return tryAttachStringStartsWith();
11916 case InlinableNative::StringEndsWith:
11917 return tryAttachStringEndsWith();
11918 case InlinableNative::StringToLowerCase:
11919 return tryAttachStringToLowerCase();
11920 case InlinableNative::StringToUpperCase:
11921 return tryAttachStringToUpperCase();
11922 case InlinableNative::StringTrim:
11923 return tryAttachStringTrim();
11924 case InlinableNative::StringTrimStart:
11925 return tryAttachStringTrimStart();
11926 case InlinableNative::StringTrimEnd:
11927 return tryAttachStringTrimEnd();
11928 case InlinableNative::IntrinsicStringReplaceString:
11929 return tryAttachStringReplaceString();
11930 case InlinableNative::IntrinsicStringSplitString:
11931 return tryAttachStringSplitString();
11933 // Math natives.
11934 case InlinableNative::MathRandom:
11935 return tryAttachMathRandom();
11936 case InlinableNative::MathAbs:
11937 return tryAttachMathAbs();
11938 case InlinableNative::MathClz32:
11939 return tryAttachMathClz32();
11940 case InlinableNative::MathSign:
11941 return tryAttachMathSign();
11942 case InlinableNative::MathImul:
11943 return tryAttachMathImul();
11944 case InlinableNative::MathFloor:
11945 return tryAttachMathFloor();
11946 case InlinableNative::MathCeil:
11947 return tryAttachMathCeil();
11948 case InlinableNative::MathTrunc:
11949 return tryAttachMathTrunc();
11950 case InlinableNative::MathRound:
11951 return tryAttachMathRound();
11952 case InlinableNative::MathSqrt:
11953 return tryAttachMathSqrt();
11954 case InlinableNative::MathFRound:
11955 return tryAttachMathFRound();
11956 case InlinableNative::MathF16Round:
11957 return tryAttachMathF16Round();
11958 case InlinableNative::MathHypot:
11959 return tryAttachMathHypot();
11960 case InlinableNative::MathATan2:
11961 return tryAttachMathATan2();
11962 case InlinableNative::MathSin:
11963 return tryAttachMathFunction(UnaryMathFunction::SinNative);
11964 case InlinableNative::MathTan:
11965 return tryAttachMathFunction(UnaryMathFunction::TanNative);
11966 case InlinableNative::MathCos:
11967 return tryAttachMathFunction(UnaryMathFunction::CosNative);
11968 case InlinableNative::MathExp:
11969 return tryAttachMathFunction(UnaryMathFunction::Exp);
11970 case InlinableNative::MathLog:
11971 return tryAttachMathFunction(UnaryMathFunction::Log);
11972 case InlinableNative::MathASin:
11973 return tryAttachMathFunction(UnaryMathFunction::ASin);
11974 case InlinableNative::MathATan:
11975 return tryAttachMathFunction(UnaryMathFunction::ATan);
11976 case InlinableNative::MathACos:
11977 return tryAttachMathFunction(UnaryMathFunction::ACos);
11978 case InlinableNative::MathLog10:
11979 return tryAttachMathFunction(UnaryMathFunction::Log10);
11980 case InlinableNative::MathLog2:
11981 return tryAttachMathFunction(UnaryMathFunction::Log2);
11982 case InlinableNative::MathLog1P:
11983 return tryAttachMathFunction(UnaryMathFunction::Log1P);
11984 case InlinableNative::MathExpM1:
11985 return tryAttachMathFunction(UnaryMathFunction::ExpM1);
11986 case InlinableNative::MathCosH:
11987 return tryAttachMathFunction(UnaryMathFunction::CosH);
11988 case InlinableNative::MathSinH:
11989 return tryAttachMathFunction(UnaryMathFunction::SinH);
11990 case InlinableNative::MathTanH:
11991 return tryAttachMathFunction(UnaryMathFunction::TanH);
11992 case InlinableNative::MathACosH:
11993 return tryAttachMathFunction(UnaryMathFunction::ACosH);
11994 case InlinableNative::MathASinH:
11995 return tryAttachMathFunction(UnaryMathFunction::ASinH);
11996 case InlinableNative::MathATanH:
11997 return tryAttachMathFunction(UnaryMathFunction::ATanH);
11998 case InlinableNative::MathCbrt:
11999 return tryAttachMathFunction(UnaryMathFunction::Cbrt);
12000 case InlinableNative::MathPow:
12001 return tryAttachMathPow();
12002 case InlinableNative::MathMin:
12003 return tryAttachMathMinMax(/* isMax = */ false);
12004 case InlinableNative::MathMax:
12005 return tryAttachMathMinMax(/* isMax = */ true);
12007 // Map intrinsics.
12008 case InlinableNative::IntrinsicGuardToMapObject:
12009 return tryAttachGuardToClass(GuardClassKind::Map);
12010 case InlinableNative::IntrinsicGetNextMapEntryForIterator:
12011 return tryAttachGetNextMapSetEntryForIterator(/* isMap = */ true);
12013 // Number natives.
12014 case InlinableNative::Number:
12015 return tryAttachNumber();
12016 case InlinableNative::NumberParseInt:
12017 return tryAttachNumberParseInt();
12018 case InlinableNative::NumberToString:
12019 return tryAttachNumberToString();
12021 // Object natives.
12022 case InlinableNative::Object:
12023 return tryAttachObjectConstructor();
12024 case InlinableNative::ObjectCreate:
12025 return tryAttachObjectCreate();
12026 case InlinableNative::ObjectIs:
12027 return tryAttachObjectIs();
12028 case InlinableNative::ObjectIsPrototypeOf:
12029 return tryAttachObjectIsPrototypeOf();
12030 case InlinableNative::ObjectKeys:
12031 return tryAttachObjectKeys();
12032 case InlinableNative::ObjectToString:
12033 return tryAttachObjectToString();
12035 // Set intrinsics.
12036 case InlinableNative::IntrinsicGuardToSetObject:
12037 return tryAttachGuardToClass(GuardClassKind::Set);
12038 case InlinableNative::IntrinsicGetNextSetEntryForIterator:
12039 return tryAttachGetNextMapSetEntryForIterator(/* isMap = */ false);
12041 // ArrayBuffer intrinsics.
12042 case InlinableNative::IntrinsicGuardToArrayBuffer:
12043 return tryAttachGuardToArrayBuffer();
12044 case InlinableNative::IntrinsicArrayBufferByteLength:
12045 return tryAttachArrayBufferByteLength(/* isPossiblyWrapped = */ false);
12046 case InlinableNative::IntrinsicPossiblyWrappedArrayBufferByteLength:
12047 return tryAttachArrayBufferByteLength(/* isPossiblyWrapped = */ true);
12049 // SharedArrayBuffer intrinsics.
12050 case InlinableNative::IntrinsicGuardToSharedArrayBuffer:
12051 return tryAttachGuardToSharedArrayBuffer();
12053 // TypedArray intrinsics.
12054 case InlinableNative::TypedArrayConstructor:
12055 return AttachDecision::NoAction; // Not callable.
12056 case InlinableNative::IntrinsicIsTypedArray:
12057 return tryAttachIsTypedArray(/* isPossiblyWrapped = */ false);
12058 case InlinableNative::IntrinsicIsPossiblyWrappedTypedArray:
12059 return tryAttachIsTypedArray(/* isPossiblyWrapped = */ true);
12060 case InlinableNative::IntrinsicIsTypedArrayConstructor:
12061 return tryAttachIsTypedArrayConstructor();
12062 case InlinableNative::IntrinsicTypedArrayByteOffset:
12063 return tryAttachTypedArrayByteOffset();
12064 case InlinableNative::IntrinsicTypedArrayElementSize:
12065 return tryAttachTypedArrayElementSize();
12066 case InlinableNative::IntrinsicTypedArrayLength:
12067 return tryAttachTypedArrayLength(/* isPossiblyWrapped = */ false,
12068 /* allowOutOfBounds = */ false);
12069 case InlinableNative::IntrinsicTypedArrayLengthZeroOnOutOfBounds:
12070 return tryAttachTypedArrayLength(/* isPossiblyWrapped = */ false,
12071 /* allowOutOfBounds = */ true);
12072 case InlinableNative::IntrinsicPossiblyWrappedTypedArrayLength:
12073 return tryAttachTypedArrayLength(/* isPossiblyWrapped = */ true,
12074 /* allowOutOfBounds = */ false);
12076 // Reflect natives.
12077 case InlinableNative::ReflectGetPrototypeOf:
12078 return tryAttachReflectGetPrototypeOf();
12080 // Atomics intrinsics:
12081 case InlinableNative::AtomicsCompareExchange:
12082 return tryAttachAtomicsCompareExchange();
12083 case InlinableNative::AtomicsExchange:
12084 return tryAttachAtomicsExchange();
12085 case InlinableNative::AtomicsAdd:
12086 return tryAttachAtomicsAdd();
12087 case InlinableNative::AtomicsSub:
12088 return tryAttachAtomicsSub();
12089 case InlinableNative::AtomicsAnd:
12090 return tryAttachAtomicsAnd();
12091 case InlinableNative::AtomicsOr:
12092 return tryAttachAtomicsOr();
12093 case InlinableNative::AtomicsXor:
12094 return tryAttachAtomicsXor();
12095 case InlinableNative::AtomicsLoad:
12096 return tryAttachAtomicsLoad();
12097 case InlinableNative::AtomicsStore:
12098 return tryAttachAtomicsStore();
12099 case InlinableNative::AtomicsIsLockFree:
12100 return tryAttachAtomicsIsLockFree();
12102 // BigInt natives.
12103 case InlinableNative::BigInt:
12104 return tryAttachBigInt();
12105 case InlinableNative::BigIntAsIntN:
12106 return tryAttachBigIntAsIntN();
12107 case InlinableNative::BigIntAsUintN:
12108 return tryAttachBigIntAsUintN();
12110 // Boolean natives.
12111 case InlinableNative::Boolean:
12112 return tryAttachBoolean();
12114 // Set natives.
12115 case InlinableNative::SetHas:
12116 return tryAttachSetHas();
12117 case InlinableNative::SetSize:
12118 return tryAttachSetSize();
12120 // Map natives.
12121 case InlinableNative::MapHas:
12122 return tryAttachMapHas();
12123 case InlinableNative::MapGet:
12124 return tryAttachMapGet();
12126 // Testing functions.
12127 case InlinableNative::TestBailout:
12128 if (js::SupportDifferentialTesting()) {
12129 return AttachDecision::NoAction;
12131 return tryAttachBailout();
12132 case InlinableNative::TestAssertFloat32:
12133 return tryAttachAssertFloat32();
12134 case InlinableNative::TestAssertRecoveredOnBailout:
12135 if (js::SupportDifferentialTesting()) {
12136 return AttachDecision::NoAction;
12138 return tryAttachAssertRecoveredOnBailout();
12140 #ifdef FUZZING_JS_FUZZILLI
12141 // Fuzzilli function
12142 case InlinableNative::FuzzilliHash:
12143 return tryAttachFuzzilliHash();
12144 #endif
12146 case InlinableNative::Limit:
12147 break;
12150 MOZ_CRASH("Shouldn't get here");
12153 // Remember the shape of the this object for any script being called as a
12154 // constructor, for later use during Ion compilation.
12155 ScriptedThisResult CallIRGenerator::getThisShapeForScripted(
12156 HandleFunction calleeFunc, Handle<JSObject*> newTarget,
12157 MutableHandle<Shape*> result) {
12158 // Some constructors allocate their own |this| object.
12159 if (calleeFunc->constructorNeedsUninitializedThis()) {
12160 return ScriptedThisResult::UninitializedThis;
12163 // Only attach a stub if the newTarget is a function with a
12164 // nonconfigurable prototype.
12165 if (!newTarget->is<JSFunction>() ||
12166 !newTarget->as<JSFunction>().hasNonConfigurablePrototypeDataProperty()) {
12167 return ScriptedThisResult::NoAction;
12170 AutoRealm ar(cx_, calleeFunc);
12171 Shape* thisShape = ThisShapeForFunction(cx_, calleeFunc, newTarget);
12172 if (!thisShape) {
12173 cx_->clearPendingException();
12174 return ScriptedThisResult::NoAction;
12177 MOZ_ASSERT(thisShape->realm() == calleeFunc->realm());
12178 result.set(thisShape);
12179 return ScriptedThisResult::PlainObjectShape;
12182 static bool CanOptimizeScriptedCall(JSFunction* callee, bool isConstructing) {
12183 if (!callee->hasJitEntry()) {
12184 return false;
12187 // If callee is not an interpreted constructor, we have to throw.
12188 if (isConstructing && !callee->isConstructor()) {
12189 return false;
12192 // Likewise, if the callee is a class constructor, we have to throw.
12193 if (!isConstructing && callee->isClassConstructor()) {
12194 return false;
12197 return true;
12200 void CallIRGenerator::emitCallScriptedGuards(ObjOperandId calleeObjId,
12201 JSFunction* calleeFunc,
12202 Int32OperandId argcId,
12203 CallFlags flags, Shape* thisShape,
12204 bool isBoundFunction) {
12205 bool isConstructing = flags.isConstructing();
12207 if (mode_ == ICState::Mode::Specialized) {
12208 MOZ_ASSERT_IF(isConstructing, thisShape || flags.needsUninitializedThis());
12210 // Ensure callee matches this stub's callee
12211 emitCalleeGuard(calleeObjId, calleeFunc);
12212 if (thisShape) {
12213 // Emit guards to ensure the newTarget's .prototype property is what we
12214 // expect. Note that getThisForScripted checked newTarget is a function
12215 // with a non-configurable .prototype data property.
12217 JSFunction* newTarget;
12218 ObjOperandId newTargetObjId;
12219 if (isBoundFunction) {
12220 newTarget = calleeFunc;
12221 newTargetObjId = calleeObjId;
12222 } else {
12223 newTarget = &newTarget_.toObject().as<JSFunction>();
12224 ValOperandId newTargetValId = writer.loadArgumentDynamicSlot(
12225 ArgumentKind::NewTarget, argcId, flags);
12226 newTargetObjId = writer.guardToObject(newTargetValId);
12229 Maybe<PropertyInfo> prop = newTarget->lookupPure(cx_->names().prototype);
12230 MOZ_ASSERT(prop.isSome());
12231 uint32_t slot = prop->slot();
12232 MOZ_ASSERT(slot >= newTarget->numFixedSlots(),
12233 "Stub code relies on this");
12235 writer.guardShape(newTargetObjId, newTarget->shape());
12237 const Value& value = newTarget->getSlot(slot);
12238 if (value.isObject()) {
12239 JSObject* prototypeObject = &value.toObject();
12241 ObjOperandId protoId = writer.loadObject(prototypeObject);
12242 writer.guardDynamicSlotIsSpecificObject(
12243 newTargetObjId, protoId, slot - newTarget->numFixedSlots());
12244 } else {
12245 writer.guardDynamicSlotIsNotObject(newTargetObjId,
12246 slot - newTarget->numFixedSlots());
12249 // Call metaScriptedThisShape before emitting the call, so that Warp can
12250 // use the shape to create the |this| object before transpiling the call.
12251 writer.metaScriptedThisShape(thisShape);
12253 } else {
12254 // Guard that object is a scripted function
12255 writer.guardClass(calleeObjId, GuardClassKind::JSFunction);
12256 writer.guardFunctionHasJitEntry(calleeObjId);
12258 if (isConstructing) {
12259 // If callee is not a constructor, we have to throw.
12260 writer.guardFunctionIsConstructor(calleeObjId);
12261 } else {
12262 // If callee is a class constructor, we have to throw.
12263 writer.guardNotClassConstructor(calleeObjId);
12268 AttachDecision CallIRGenerator::tryAttachCallScripted(
12269 HandleFunction calleeFunc) {
12270 MOZ_ASSERT(calleeFunc->hasJitEntry());
12272 if (calleeFunc->isWasmWithJitEntry()) {
12273 TRY_ATTACH(tryAttachWasmCall(calleeFunc));
12276 bool isSpecialized = mode_ == ICState::Mode::Specialized;
12278 bool isConstructing = IsConstructPC(pc_);
12279 bool isSpread = IsSpreadPC(pc_);
12280 bool isSameRealm = isSpecialized && cx_->realm() == calleeFunc->realm();
12281 CallFlags flags(isConstructing, isSpread, isSameRealm);
12283 if (!CanOptimizeScriptedCall(calleeFunc, isConstructing)) {
12284 return AttachDecision::NoAction;
12287 if (isConstructing && !calleeFunc->hasJitScript()) {
12288 // If we're constructing, require the callee to have a JitScript. This isn't
12289 // required for correctness but avoids allocating a template object below
12290 // for constructors that aren't hot. See bug 1419758.
12291 return AttachDecision::TemporarilyUnoptimizable;
12294 // Verify that spread calls have a reasonable number of arguments.
12295 if (isSpread && args_.length() > JIT_ARGS_LENGTH_MAX) {
12296 return AttachDecision::NoAction;
12299 Rooted<Shape*> thisShape(cx_);
12300 if (isConstructing && isSpecialized) {
12301 Rooted<JSObject*> newTarget(cx_, &newTarget_.toObject());
12302 switch (getThisShapeForScripted(calleeFunc, newTarget, &thisShape)) {
12303 case ScriptedThisResult::PlainObjectShape:
12304 break;
12305 case ScriptedThisResult::UninitializedThis:
12306 flags.setNeedsUninitializedThis();
12307 break;
12308 case ScriptedThisResult::NoAction:
12309 return AttachDecision::NoAction;
12313 // Load argc.
12314 Int32OperandId argcId(writer.setInputOperandId(0));
12316 // Load the callee and ensure it is an object
12317 ValOperandId calleeValId =
12318 writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId, flags);
12319 ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
12321 emitCallScriptedGuards(calleeObjId, calleeFunc, argcId, flags, thisShape,
12322 /* isBoundFunction = */ false);
12324 writer.callScriptedFunction(calleeObjId, argcId, flags,
12325 ClampFixedArgc(argc_));
12326 writer.returnFromIC();
12328 if (isSpecialized) {
12329 trackAttached("Call.CallScripted");
12330 } else {
12331 trackAttached("Call.CallAnyScripted");
12334 return AttachDecision::Attach;
12337 AttachDecision CallIRGenerator::tryAttachCallNative(HandleFunction calleeFunc) {
12338 MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry());
12340 bool isSpecialized = mode_ == ICState::Mode::Specialized;
12342 bool isSpread = IsSpreadPC(pc_);
12343 bool isSameRealm = isSpecialized && cx_->realm() == calleeFunc->realm();
12344 bool isConstructing = IsConstructPC(pc_);
12345 CallFlags flags(isConstructing, isSpread, isSameRealm);
12347 if (isConstructing && !calleeFunc->isConstructor()) {
12348 return AttachDecision::NoAction;
12351 // Verify that spread calls have a reasonable number of arguments.
12352 if (isSpread && args_.length() > JIT_ARGS_LENGTH_MAX) {
12353 return AttachDecision::NoAction;
12356 // Check for specific native-function optimizations.
12357 if (isSpecialized) {
12358 TRY_ATTACH(tryAttachInlinableNative(calleeFunc, flags));
12361 // Load argc.
12362 Int32OperandId argcId(writer.setInputOperandId(0));
12364 // Load the callee and ensure it is an object
12365 ValOperandId calleeValId =
12366 writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId, flags);
12367 ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
12369 // DOM calls need an additional guard so only try optimizing the first stub.
12370 // Can only optimize normal (non-spread) calls.
12371 if (isFirstStub_ && !isSpread && thisval_.isObject() &&
12372 CanAttachDOMCall(cx_, JSJitInfo::Method, &thisval_.toObject(), calleeFunc,
12373 mode_)) {
12374 MOZ_ASSERT(!isConstructing, "DOM functions are not constructors");
12376 // Guard that |this| is an object.
12377 ValOperandId thisValId =
12378 writer.loadArgumentDynamicSlot(ArgumentKind::This, argcId, flags);
12379 ObjOperandId thisObjId = writer.guardToObject(thisValId);
12381 // Guard on the |this| shape to make sure it's the right instance. This also
12382 // ensures DOM_OBJECT_SLOT is stored in a fixed slot. See CanAttachDOMCall.
12383 writer.guardShape(thisObjId, thisval_.toObject().shape());
12385 // Ensure callee matches this stub's callee
12386 writer.guardSpecificFunction(calleeObjId, calleeFunc);
12387 writer.callDOMFunction(calleeObjId, argcId, thisObjId, calleeFunc, flags,
12388 ClampFixedArgc(argc_));
12390 trackAttached("Call.CallDOM");
12391 } else if (isSpecialized) {
12392 // Ensure callee matches this stub's callee
12393 writer.guardSpecificFunction(calleeObjId, calleeFunc);
12394 writer.callNativeFunction(calleeObjId, argcId, op_, calleeFunc, flags,
12395 ClampFixedArgc(argc_));
12397 trackAttached("Call.CallNative");
12398 } else {
12399 // Guard that object is a native function
12400 writer.guardClass(calleeObjId, GuardClassKind::JSFunction);
12401 writer.guardFunctionHasNoJitEntry(calleeObjId);
12403 if (isConstructing) {
12404 // If callee is not a constructor, we have to throw.
12405 writer.guardFunctionIsConstructor(calleeObjId);
12406 } else {
12407 // If callee is a class constructor, we have to throw.
12408 writer.guardNotClassConstructor(calleeObjId);
12410 writer.callAnyNativeFunction(calleeObjId, argcId, flags,
12411 ClampFixedArgc(argc_));
12413 trackAttached("Call.CallAnyNative");
12416 writer.returnFromIC();
12418 return AttachDecision::Attach;
12421 AttachDecision CallIRGenerator::tryAttachCallHook(HandleObject calleeObj) {
12422 if (mode_ != ICState::Mode::Specialized) {
12423 // We do not have megamorphic call hook stubs.
12424 // TODO: Should we attach specialized call hook stubs in
12425 // megamorphic mode to avoid going generic?
12426 return AttachDecision::NoAction;
12429 bool isSpread = IsSpreadPC(pc_);
12430 bool isConstructing = IsConstructPC(pc_);
12431 CallFlags flags(isConstructing, isSpread);
12432 JSNative hook =
12433 isConstructing ? calleeObj->constructHook() : calleeObj->callHook();
12434 if (!hook) {
12435 return AttachDecision::NoAction;
12438 // Bound functions have a JSClass construct hook but are not always
12439 // constructors.
12440 if (isConstructing && !calleeObj->isConstructor()) {
12441 return AttachDecision::NoAction;
12444 // We don't support spread calls in the transpiler yet.
12445 if (isSpread) {
12446 return AttachDecision::NoAction;
12449 // Load argc.
12450 Int32OperandId argcId(writer.setInputOperandId(0));
12452 // Load the callee and ensure it is an object
12453 ValOperandId calleeValId =
12454 writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId, flags);
12455 ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
12457 // Ensure the callee's class matches the one in this stub.
12458 writer.guardAnyClass(calleeObjId, calleeObj->getClass());
12460 if (isConstructing && calleeObj->is<BoundFunctionObject>()) {
12461 writer.guardBoundFunctionIsConstructor(calleeObjId);
12464 writer.callClassHook(calleeObjId, argcId, hook, flags, ClampFixedArgc(argc_));
12465 writer.returnFromIC();
12467 trackAttached("Call.CallHook");
12469 return AttachDecision::Attach;
12472 AttachDecision CallIRGenerator::tryAttachBoundFunction(
12473 Handle<BoundFunctionObject*> calleeObj) {
12474 // The target must be a JSFunction with a JitEntry.
12475 if (!calleeObj->getTarget()->is<JSFunction>()) {
12476 return AttachDecision::NoAction;
12479 bool isSpread = IsSpreadPC(pc_);
12480 bool isConstructing = IsConstructPC(pc_);
12482 // Spread calls are not supported yet.
12483 if (isSpread) {
12484 return AttachDecision::NoAction;
12487 Rooted<JSFunction*> target(cx_, &calleeObj->getTarget()->as<JSFunction>());
12488 if (!CanOptimizeScriptedCall(target, isConstructing)) {
12489 return AttachDecision::NoAction;
12492 // Limit the number of bound arguments to prevent us from compiling many
12493 // different stubs (we bake in numBoundArgs and it's usually very small).
12494 static constexpr size_t MaxBoundArgs = 10;
12495 size_t numBoundArgs = calleeObj->numBoundArgs();
12496 if (numBoundArgs > MaxBoundArgs) {
12497 return AttachDecision::NoAction;
12500 // Ensure we don't exceed JIT_ARGS_LENGTH_MAX.
12501 if (numBoundArgs + argc_ > JIT_ARGS_LENGTH_MAX) {
12502 return AttachDecision::NoAction;
12505 CallFlags flags(isConstructing, isSpread);
12507 if (mode_ == ICState::Mode::Specialized) {
12508 if (cx_->realm() == target->realm()) {
12509 flags.setIsSameRealm();
12513 Rooted<Shape*> thisShape(cx_);
12514 if (isConstructing) {
12515 // Only optimize if newTarget == callee. This is the common case and ensures
12516 // we can always pass the bound function's target as newTarget.
12517 if (newTarget_ != ObjectValue(*calleeObj)) {
12518 return AttachDecision::NoAction;
12521 if (mode_ == ICState::Mode::Specialized) {
12522 Handle<JSFunction*> newTarget = target;
12523 switch (getThisShapeForScripted(target, newTarget, &thisShape)) {
12524 case ScriptedThisResult::PlainObjectShape:
12525 break;
12526 case ScriptedThisResult::UninitializedThis:
12527 flags.setNeedsUninitializedThis();
12528 break;
12529 case ScriptedThisResult::NoAction:
12530 return AttachDecision::NoAction;
12535 // Load argc.
12536 Int32OperandId argcId(writer.setInputOperandId(0));
12538 // Load the callee and ensure it's a bound function.
12539 ValOperandId calleeValId =
12540 writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId, flags);
12541 ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
12542 writer.guardClass(calleeObjId, GuardClassKind::BoundFunction);
12544 // Ensure numBoundArgs matches.
12545 Int32OperandId numBoundArgsId = writer.loadBoundFunctionNumArgs(calleeObjId);
12546 writer.guardSpecificInt32(numBoundArgsId, numBoundArgs);
12548 if (isConstructing) {
12549 // Guard newTarget == callee. We depend on this in CallBoundScriptedFunction
12550 // and in emitCallScriptedGuards by using boundTarget as newTarget.
12551 ValOperandId newTargetValId =
12552 writer.loadArgumentDynamicSlot(ArgumentKind::NewTarget, argcId, flags);
12553 ObjOperandId newTargetObjId = writer.guardToObject(newTargetValId);
12554 writer.guardObjectIdentity(newTargetObjId, calleeObjId);
12557 ObjOperandId targetId = writer.loadBoundFunctionTarget(calleeObjId);
12559 emitCallScriptedGuards(targetId, target, argcId, flags, thisShape,
12560 /* isBoundFunction = */ true);
12562 writer.callBoundScriptedFunction(calleeObjId, targetId, argcId, flags,
12563 numBoundArgs);
12564 writer.returnFromIC();
12566 trackAttached("Call.BoundFunction");
12567 return AttachDecision::Attach;
12570 AttachDecision CallIRGenerator::tryAttachStub() {
12571 AutoAssertNoPendingException aanpe(cx_);
12573 // Some opcodes are not yet supported.
12574 switch (op_) {
12575 case JSOp::Call:
12576 case JSOp::CallContent:
12577 case JSOp::CallIgnoresRv:
12578 case JSOp::CallIter:
12579 case JSOp::CallContentIter:
12580 case JSOp::SpreadCall:
12581 case JSOp::New:
12582 case JSOp::NewContent:
12583 case JSOp::SpreadNew:
12584 case JSOp::SuperCall:
12585 case JSOp::SpreadSuperCall:
12586 break;
12587 default:
12588 return AttachDecision::NoAction;
12591 MOZ_ASSERT(mode_ != ICState::Mode::Generic);
12593 // Ensure callee is a function.
12594 if (!callee_.isObject()) {
12595 return AttachDecision::NoAction;
12598 RootedObject calleeObj(cx_, &callee_.toObject());
12599 if (calleeObj->is<BoundFunctionObject>()) {
12600 TRY_ATTACH(tryAttachBoundFunction(calleeObj.as<BoundFunctionObject>()));
12602 if (!calleeObj->is<JSFunction>()) {
12603 return tryAttachCallHook(calleeObj);
12606 HandleFunction calleeFunc = calleeObj.as<JSFunction>();
12608 // Check for scripted optimizations.
12609 if (calleeFunc->hasJitEntry()) {
12610 return tryAttachCallScripted(calleeFunc);
12613 // Check for native-function optimizations.
12614 MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry());
12616 // Try inlining Function.prototype.{call,apply}. We don't use the
12617 // InlinableNative mechanism for this because we want to optimize these more
12618 // aggressively than other natives.
12619 if (op_ == JSOp::Call || op_ == JSOp::CallContent ||
12620 op_ == JSOp::CallIgnoresRv) {
12621 TRY_ATTACH(tryAttachFunCall(calleeFunc));
12622 TRY_ATTACH(tryAttachFunApply(calleeFunc));
12625 return tryAttachCallNative(calleeFunc);
12628 void CallIRGenerator::trackAttached(const char* name) {
12629 stubName_ = name ? name : "NotAttached";
12630 #ifdef JS_CACHEIR_SPEW
12631 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
12632 sp.valueProperty("callee", callee_);
12633 sp.valueProperty("thisval", thisval_);
12634 sp.valueProperty("argc", Int32Value(argc_));
12636 // Try to log the first two arguments.
12637 if (args_.length() >= 1) {
12638 sp.valueProperty("arg0", args_[0]);
12640 if (args_.length() >= 2) {
12641 sp.valueProperty("arg1", args_[1]);
12644 #endif
12647 // Class which holds a shape pointer for use when caches might reference data in
12648 // other zones.
12649 static const JSClass shapeContainerClass = {"ShapeContainer",
12650 JSCLASS_HAS_RESERVED_SLOTS(1)};
12652 static const size_t SHAPE_CONTAINER_SLOT = 0;
12654 static JSObject* NewWrapperWithObjectShape(JSContext* cx,
12655 Handle<NativeObject*> obj) {
12656 MOZ_ASSERT(cx->compartment() != obj->compartment());
12658 RootedObject wrapper(cx);
12660 AutoRealm ar(cx, obj);
12661 wrapper = NewBuiltinClassInstance(cx, &shapeContainerClass);
12662 if (!wrapper) {
12663 return nullptr;
12665 wrapper->as<NativeObject>().setReservedSlot(
12666 SHAPE_CONTAINER_SLOT, PrivateGCThingValue(obj->shape()));
12668 if (!JS_WrapObject(cx, &wrapper)) {
12669 return nullptr;
12671 MOZ_ASSERT(IsWrapper(wrapper));
12672 return wrapper;
12675 void jit::LoadShapeWrapperContents(MacroAssembler& masm, Register obj,
12676 Register dst, Label* failure) {
12677 masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), dst);
12678 Address privateAddr(dst,
12679 js::detail::ProxyReservedSlots::offsetOfPrivateSlot());
12680 masm.fallibleUnboxObject(privateAddr, dst, failure);
12681 masm.unboxNonDouble(
12682 Address(dst, NativeObject::getFixedSlotOffset(SHAPE_CONTAINER_SLOT)), dst,
12683 JSVAL_TYPE_PRIVATE_GCTHING);
12686 static bool CanConvertToInt32ForToNumber(const Value& v) {
12687 return v.isInt32() || v.isBoolean() || v.isNull();
12690 static Int32OperandId EmitGuardToInt32ForToNumber(CacheIRWriter& writer,
12691 ValOperandId id,
12692 const Value& v) {
12693 if (v.isInt32()) {
12694 return writer.guardToInt32(id);
12696 if (v.isNull()) {
12697 writer.guardIsNull(id);
12698 return writer.loadInt32Constant(0);
12700 MOZ_ASSERT(v.isBoolean());
12701 return writer.guardBooleanToInt32(id);
12704 static bool CanConvertToDoubleForToNumber(const Value& v) {
12705 return v.isNumber() || v.isBoolean() || v.isNullOrUndefined();
12708 static NumberOperandId EmitGuardToDoubleForToNumber(CacheIRWriter& writer,
12709 ValOperandId id,
12710 const Value& v) {
12711 if (v.isNumber()) {
12712 return writer.guardIsNumber(id);
12714 if (v.isBoolean()) {
12715 BooleanOperandId boolId = writer.guardToBoolean(id);
12716 return writer.booleanToNumber(boolId);
12718 if (v.isNull()) {
12719 writer.guardIsNull(id);
12720 return writer.loadDoubleConstant(0.0);
12722 MOZ_ASSERT(v.isUndefined());
12723 writer.guardIsUndefined(id);
12724 return writer.loadDoubleConstant(JS::GenericNaN());
12727 CompareIRGenerator::CompareIRGenerator(JSContext* cx, HandleScript script,
12728 jsbytecode* pc, ICState state, JSOp op,
12729 HandleValue lhsVal, HandleValue rhsVal)
12730 : IRGenerator(cx, script, pc, CacheKind::Compare, state),
12731 op_(op),
12732 lhsVal_(lhsVal),
12733 rhsVal_(rhsVal) {}
12735 AttachDecision CompareIRGenerator::tryAttachString(ValOperandId lhsId,
12736 ValOperandId rhsId) {
12737 if (!lhsVal_.isString() || !rhsVal_.isString()) {
12738 return AttachDecision::NoAction;
12741 StringOperandId lhsStrId = writer.guardToString(lhsId);
12742 StringOperandId rhsStrId = writer.guardToString(rhsId);
12743 writer.compareStringResult(op_, lhsStrId, rhsStrId);
12744 writer.returnFromIC();
12746 trackAttached("Compare.String");
12747 return AttachDecision::Attach;
12750 AttachDecision CompareIRGenerator::tryAttachObject(ValOperandId lhsId,
12751 ValOperandId rhsId) {
12752 MOZ_ASSERT(IsEqualityOp(op_));
12754 if (!lhsVal_.isObject() || !rhsVal_.isObject()) {
12755 return AttachDecision::NoAction;
12758 ObjOperandId lhsObjId = writer.guardToObject(lhsId);
12759 ObjOperandId rhsObjId = writer.guardToObject(rhsId);
12760 writer.compareObjectResult(op_, lhsObjId, rhsObjId);
12761 writer.returnFromIC();
12763 trackAttached("Compare.Object");
12764 return AttachDecision::Attach;
12767 AttachDecision CompareIRGenerator::tryAttachSymbol(ValOperandId lhsId,
12768 ValOperandId rhsId) {
12769 MOZ_ASSERT(IsEqualityOp(op_));
12771 if (!lhsVal_.isSymbol() || !rhsVal_.isSymbol()) {
12772 return AttachDecision::NoAction;
12775 SymbolOperandId lhsSymId = writer.guardToSymbol(lhsId);
12776 SymbolOperandId rhsSymId = writer.guardToSymbol(rhsId);
12777 writer.compareSymbolResult(op_, lhsSymId, rhsSymId);
12778 writer.returnFromIC();
12780 trackAttached("Compare.Symbol");
12781 return AttachDecision::Attach;
12784 AttachDecision CompareIRGenerator::tryAttachStrictDifferentTypes(
12785 ValOperandId lhsId, ValOperandId rhsId) {
12786 MOZ_ASSERT(IsEqualityOp(op_));
12788 if (op_ != JSOp::StrictEq && op_ != JSOp::StrictNe) {
12789 return AttachDecision::NoAction;
12792 // Probably can't hit some of these.
12793 if (SameType(lhsVal_, rhsVal_) ||
12794 (lhsVal_.isNumber() && rhsVal_.isNumber())) {
12795 return AttachDecision::NoAction;
12798 // Compare tags
12799 ValueTagOperandId lhsTypeId = writer.loadValueTag(lhsId);
12800 ValueTagOperandId rhsTypeId = writer.loadValueTag(rhsId);
12801 writer.guardTagNotEqual(lhsTypeId, rhsTypeId);
12803 // Now that we've passed the guard, we know differing types, so return the
12804 // bool result.
12805 writer.loadBooleanResult(op_ == JSOp::StrictNe ? true : false);
12806 writer.returnFromIC();
12808 trackAttached("Compare.StrictDifferentTypes");
12809 return AttachDecision::Attach;
12812 AttachDecision CompareIRGenerator::tryAttachInt32(ValOperandId lhsId,
12813 ValOperandId rhsId) {
12814 if (!CanConvertToInt32ForToNumber(lhsVal_) ||
12815 !CanConvertToInt32ForToNumber(rhsVal_)) {
12816 return AttachDecision::NoAction;
12819 // Strictly different types should have been handed by
12820 // tryAttachStrictDifferentTypes.
12821 MOZ_ASSERT_IF(op_ == JSOp::StrictEq || op_ == JSOp::StrictNe,
12822 lhsVal_.type() == rhsVal_.type());
12824 // Should have been handled by tryAttachAnyNullUndefined.
12825 MOZ_ASSERT_IF(lhsVal_.isNull() || rhsVal_.isNull(), !IsEqualityOp(op_));
12827 Int32OperandId lhsIntId = EmitGuardToInt32ForToNumber(writer, lhsId, lhsVal_);
12828 Int32OperandId rhsIntId = EmitGuardToInt32ForToNumber(writer, rhsId, rhsVal_);
12830 writer.compareInt32Result(op_, lhsIntId, rhsIntId);
12831 writer.returnFromIC();
12833 trackAttached("Compare.Int32");
12834 return AttachDecision::Attach;
12837 AttachDecision CompareIRGenerator::tryAttachNumber(ValOperandId lhsId,
12838 ValOperandId rhsId) {
12839 if (!CanConvertToDoubleForToNumber(lhsVal_) ||
12840 !CanConvertToDoubleForToNumber(rhsVal_)) {
12841 return AttachDecision::NoAction;
12844 // Strictly different types should have been handed by
12845 // tryAttachStrictDifferentTypes.
12846 MOZ_ASSERT_IF(op_ == JSOp::StrictEq || op_ == JSOp::StrictNe,
12847 lhsVal_.type() == rhsVal_.type() ||
12848 (lhsVal_.isNumber() && rhsVal_.isNumber()));
12850 // Should have been handled by tryAttachAnyNullUndefined.
12851 MOZ_ASSERT_IF(lhsVal_.isNullOrUndefined() || rhsVal_.isNullOrUndefined(),
12852 !IsEqualityOp(op_));
12854 NumberOperandId lhs = EmitGuardToDoubleForToNumber(writer, lhsId, lhsVal_);
12855 NumberOperandId rhs = EmitGuardToDoubleForToNumber(writer, rhsId, rhsVal_);
12856 writer.compareDoubleResult(op_, lhs, rhs);
12857 writer.returnFromIC();
12859 trackAttached("Compare.Number");
12860 return AttachDecision::Attach;
12863 AttachDecision CompareIRGenerator::tryAttachBigInt(ValOperandId lhsId,
12864 ValOperandId rhsId) {
12865 if (!lhsVal_.isBigInt() || !rhsVal_.isBigInt()) {
12866 return AttachDecision::NoAction;
12869 BigIntOperandId lhs = writer.guardToBigInt(lhsId);
12870 BigIntOperandId rhs = writer.guardToBigInt(rhsId);
12872 writer.compareBigIntResult(op_, lhs, rhs);
12873 writer.returnFromIC();
12875 trackAttached("Compare.BigInt");
12876 return AttachDecision::Attach;
12879 AttachDecision CompareIRGenerator::tryAttachAnyNullUndefined(
12880 ValOperandId lhsId, ValOperandId rhsId) {
12881 MOZ_ASSERT(IsEqualityOp(op_));
12883 // Either RHS or LHS needs to be null/undefined.
12884 if (!lhsVal_.isNullOrUndefined() && !rhsVal_.isNullOrUndefined()) {
12885 return AttachDecision::NoAction;
12888 // We assume that the side with null/undefined is usually constant, in
12889 // code like `if (x === undefined) { x = {}; }`.
12890 // That is why we don't attach when both sides are undefined/null,
12891 // because we would basically need to decide by chance which side is
12892 // the likely constant.
12893 // The actual generated code however handles null/undefined of course.
12894 if (lhsVal_.isNullOrUndefined() && rhsVal_.isNullOrUndefined()) {
12895 return AttachDecision::NoAction;
12898 if (rhsVal_.isNullOrUndefined()) {
12899 if (rhsVal_.isNull()) {
12900 writer.guardIsNull(rhsId);
12901 writer.compareNullUndefinedResult(op_, /* isUndefined */ false, lhsId);
12902 trackAttached("Compare.AnyNull");
12903 } else {
12904 writer.guardIsUndefined(rhsId);
12905 writer.compareNullUndefinedResult(op_, /* isUndefined */ true, lhsId);
12906 trackAttached("Compare.AnyUndefined");
12908 } else {
12909 if (lhsVal_.isNull()) {
12910 writer.guardIsNull(lhsId);
12911 writer.compareNullUndefinedResult(op_, /* isUndefined */ false, rhsId);
12912 trackAttached("Compare.NullAny");
12913 } else {
12914 writer.guardIsUndefined(lhsId);
12915 writer.compareNullUndefinedResult(op_, /* isUndefined */ true, rhsId);
12916 trackAttached("Compare.UndefinedAny");
12920 writer.returnFromIC();
12921 return AttachDecision::Attach;
12924 // Handle {null/undefined} x {null,undefined} equality comparisons
12925 AttachDecision CompareIRGenerator::tryAttachNullUndefined(ValOperandId lhsId,
12926 ValOperandId rhsId) {
12927 if (!lhsVal_.isNullOrUndefined() || !rhsVal_.isNullOrUndefined()) {
12928 return AttachDecision::NoAction;
12931 if (op_ == JSOp::Eq || op_ == JSOp::Ne) {
12932 writer.guardIsNullOrUndefined(lhsId);
12933 writer.guardIsNullOrUndefined(rhsId);
12934 // Sloppy equality means we actually only care about the op:
12935 writer.loadBooleanResult(op_ == JSOp::Eq);
12936 trackAttached("Compare.SloppyNullUndefined");
12937 } else {
12938 // Strict equality only hits this branch, and only in the
12939 // undef {!,=}== undef and null {!,=}== null cases.
12940 // The other cases should have hit tryAttachStrictDifferentTypes.
12941 MOZ_ASSERT(lhsVal_.isNull() == rhsVal_.isNull());
12942 lhsVal_.isNull() ? writer.guardIsNull(lhsId)
12943 : writer.guardIsUndefined(lhsId);
12944 rhsVal_.isNull() ? writer.guardIsNull(rhsId)
12945 : writer.guardIsUndefined(rhsId);
12946 writer.loadBooleanResult(op_ == JSOp::StrictEq);
12947 trackAttached("Compare.StrictNullUndefinedEquality");
12950 writer.returnFromIC();
12951 return AttachDecision::Attach;
12954 AttachDecision CompareIRGenerator::tryAttachStringNumber(ValOperandId lhsId,
12955 ValOperandId rhsId) {
12956 // Ensure String x {Number, Boolean, Null, Undefined}
12957 if (!(lhsVal_.isString() && CanConvertToDoubleForToNumber(rhsVal_)) &&
12958 !(rhsVal_.isString() && CanConvertToDoubleForToNumber(lhsVal_))) {
12959 return AttachDecision::NoAction;
12962 // Case should have been handled by tryAttachStrictDifferentTypes
12963 MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe);
12965 auto createGuards = [&](const Value& v, ValOperandId vId) {
12966 if (v.isString()) {
12967 StringOperandId strId = writer.guardToString(vId);
12968 return writer.guardStringToNumber(strId);
12970 return EmitGuardToDoubleForToNumber(writer, vId, v);
12973 NumberOperandId lhsGuardedId = createGuards(lhsVal_, lhsId);
12974 NumberOperandId rhsGuardedId = createGuards(rhsVal_, rhsId);
12975 writer.compareDoubleResult(op_, lhsGuardedId, rhsGuardedId);
12976 writer.returnFromIC();
12978 trackAttached("Compare.StringNumber");
12979 return AttachDecision::Attach;
12982 AttachDecision CompareIRGenerator::tryAttachPrimitiveSymbol(
12983 ValOperandId lhsId, ValOperandId rhsId) {
12984 MOZ_ASSERT(IsEqualityOp(op_));
12986 // The set of primitive cases we want to handle here (excluding null,
12987 // undefined, and symbol)
12988 auto isPrimitive = [](const Value& x) {
12989 return x.isString() || x.isBoolean() || x.isNumber() || x.isBigInt();
12992 // Ensure Symbol x {String, Bool, Number, BigInt}.
12993 if (!(lhsVal_.isSymbol() && isPrimitive(rhsVal_)) &&
12994 !(rhsVal_.isSymbol() && isPrimitive(lhsVal_))) {
12995 return AttachDecision::NoAction;
12998 auto guardPrimitive = [&](const Value& v, ValOperandId id) {
12999 MOZ_ASSERT(isPrimitive(v));
13000 if (v.isNumber()) {
13001 writer.guardIsNumber(id);
13002 return;
13004 switch (v.extractNonDoubleType()) {
13005 case JSVAL_TYPE_STRING:
13006 writer.guardToString(id);
13007 return;
13008 case JSVAL_TYPE_BOOLEAN:
13009 writer.guardToBoolean(id);
13010 return;
13011 case JSVAL_TYPE_BIGINT:
13012 writer.guardToBigInt(id);
13013 return;
13014 default:
13015 MOZ_CRASH("unexpected type");
13016 return;
13020 if (lhsVal_.isSymbol()) {
13021 writer.guardToSymbol(lhsId);
13022 guardPrimitive(rhsVal_, rhsId);
13023 } else {
13024 guardPrimitive(lhsVal_, lhsId);
13025 writer.guardToSymbol(rhsId);
13028 // Comparing a primitive with symbol will always be true for Ne/StrictNe, and
13029 // always be false for other compare ops.
13030 writer.loadBooleanResult(op_ == JSOp::Ne || op_ == JSOp::StrictNe);
13031 writer.returnFromIC();
13033 trackAttached("Compare.PrimitiveSymbol");
13034 return AttachDecision::Attach;
13037 AttachDecision CompareIRGenerator::tryAttachBigIntInt32(ValOperandId lhsId,
13038 ValOperandId rhsId) {
13039 // Ensure BigInt x {Int32, Boolean, Null}.
13040 if (!(lhsVal_.isBigInt() && CanConvertToInt32ForToNumber(rhsVal_)) &&
13041 !(rhsVal_.isBigInt() && CanConvertToInt32ForToNumber(lhsVal_))) {
13042 return AttachDecision::NoAction;
13045 // Case should have been handled by tryAttachStrictDifferentTypes
13046 MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe);
13048 if (lhsVal_.isBigInt()) {
13049 BigIntOperandId bigIntId = writer.guardToBigInt(lhsId);
13050 Int32OperandId intId = EmitGuardToInt32ForToNumber(writer, rhsId, rhsVal_);
13052 writer.compareBigIntInt32Result(op_, bigIntId, intId);
13053 } else {
13054 Int32OperandId intId = EmitGuardToInt32ForToNumber(writer, lhsId, lhsVal_);
13055 BigIntOperandId bigIntId = writer.guardToBigInt(rhsId);
13057 writer.compareBigIntInt32Result(ReverseCompareOp(op_), bigIntId, intId);
13059 writer.returnFromIC();
13061 trackAttached("Compare.BigIntInt32");
13062 return AttachDecision::Attach;
13065 AttachDecision CompareIRGenerator::tryAttachBigIntNumber(ValOperandId lhsId,
13066 ValOperandId rhsId) {
13067 // Ensure BigInt x {Number, Undefined}.
13068 if (!(lhsVal_.isBigInt() && CanConvertToDoubleForToNumber(rhsVal_)) &&
13069 !(rhsVal_.isBigInt() && CanConvertToDoubleForToNumber(lhsVal_))) {
13070 return AttachDecision::NoAction;
13073 // Case should have been handled by tryAttachStrictDifferentTypes
13074 MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe);
13076 // Case should have been handled by tryAttachBigIntInt32.
13077 MOZ_ASSERT(!CanConvertToInt32ForToNumber(lhsVal_));
13078 MOZ_ASSERT(!CanConvertToInt32ForToNumber(rhsVal_));
13080 if (lhsVal_.isBigInt()) {
13081 BigIntOperandId bigIntId = writer.guardToBigInt(lhsId);
13082 NumberOperandId numId =
13083 EmitGuardToDoubleForToNumber(writer, rhsId, rhsVal_);
13085 writer.compareBigIntNumberResult(op_, bigIntId, numId);
13086 } else {
13087 NumberOperandId numId =
13088 EmitGuardToDoubleForToNumber(writer, lhsId, lhsVal_);
13089 BigIntOperandId bigIntId = writer.guardToBigInt(rhsId);
13091 writer.compareBigIntNumberResult(ReverseCompareOp(op_), bigIntId, numId);
13093 writer.returnFromIC();
13095 trackAttached("Compare.BigIntNumber");
13096 return AttachDecision::Attach;
13099 AttachDecision CompareIRGenerator::tryAttachBigIntString(ValOperandId lhsId,
13100 ValOperandId rhsId) {
13101 // Ensure BigInt x String.
13102 if (!(lhsVal_.isBigInt() && rhsVal_.isString()) &&
13103 !(rhsVal_.isBigInt() && lhsVal_.isString())) {
13104 return AttachDecision::NoAction;
13107 // Case should have been handled by tryAttachStrictDifferentTypes
13108 MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe);
13110 if (lhsVal_.isBigInt()) {
13111 BigIntOperandId bigIntId = writer.guardToBigInt(lhsId);
13112 StringOperandId strId = writer.guardToString(rhsId);
13114 writer.compareBigIntStringResult(op_, bigIntId, strId);
13115 } else {
13116 StringOperandId strId = writer.guardToString(lhsId);
13117 BigIntOperandId bigIntId = writer.guardToBigInt(rhsId);
13119 writer.compareBigIntStringResult(ReverseCompareOp(op_), bigIntId, strId);
13121 writer.returnFromIC();
13123 trackAttached("Compare.BigIntString");
13124 return AttachDecision::Attach;
13127 AttachDecision CompareIRGenerator::tryAttachStub() {
13128 MOZ_ASSERT(cacheKind_ == CacheKind::Compare);
13129 MOZ_ASSERT(IsEqualityOp(op_) || IsRelationalOp(op_));
13131 AutoAssertNoPendingException aanpe(cx_);
13133 constexpr uint8_t lhsIndex = 0;
13134 constexpr uint8_t rhsIndex = 1;
13136 ValOperandId lhsId(writer.setInputOperandId(lhsIndex));
13137 ValOperandId rhsId(writer.setInputOperandId(rhsIndex));
13139 // For sloppy equality ops, there are cases this IC does not handle:
13140 // - {Object} x {String, Symbol, Bool, Number, BigInt}.
13142 // For relational comparison ops, these cases aren't handled:
13143 // - Object x {String, Bool, Number, BigInt, Object, Null, Undefined}.
13144 // Note: |Symbol x any| always throws, so it doesn't need to be handled.
13146 // (The above lists omits the equivalent case {B} x {A} when {A} x {B} is
13147 // already present.)
13149 if (IsEqualityOp(op_)) {
13150 TRY_ATTACH(tryAttachObject(lhsId, rhsId));
13151 TRY_ATTACH(tryAttachSymbol(lhsId, rhsId));
13153 // Handles any (non null or undefined) comparison with null/undefined.
13154 TRY_ATTACH(tryAttachAnyNullUndefined(lhsId, rhsId));
13156 // This covers -strict- equality/inequality using a type tag check, so
13157 // catches all different type pairs outside of Numbers, which cannot be
13158 // checked on tags alone.
13159 TRY_ATTACH(tryAttachStrictDifferentTypes(lhsId, rhsId));
13161 TRY_ATTACH(tryAttachNullUndefined(lhsId, rhsId));
13163 TRY_ATTACH(tryAttachPrimitiveSymbol(lhsId, rhsId));
13166 // We want these to be last, to allow us to bypass the
13167 // strictly-different-types cases in the below attachment code
13168 TRY_ATTACH(tryAttachInt32(lhsId, rhsId));
13169 TRY_ATTACH(tryAttachNumber(lhsId, rhsId));
13170 TRY_ATTACH(tryAttachBigInt(lhsId, rhsId));
13171 TRY_ATTACH(tryAttachString(lhsId, rhsId));
13173 TRY_ATTACH(tryAttachStringNumber(lhsId, rhsId));
13175 TRY_ATTACH(tryAttachBigIntInt32(lhsId, rhsId));
13176 TRY_ATTACH(tryAttachBigIntNumber(lhsId, rhsId));
13177 TRY_ATTACH(tryAttachBigIntString(lhsId, rhsId));
13179 // Strict equality is always supported.
13180 MOZ_ASSERT(!IsStrictEqualityOp(op_));
13182 // Other operations are unsupported iff at least one operand is an object.
13183 MOZ_ASSERT(lhsVal_.isObject() || rhsVal_.isObject());
13185 trackAttached(IRGenerator::NotAttached);
13186 return AttachDecision::NoAction;
13189 void CompareIRGenerator::trackAttached(const char* name) {
13190 stubName_ = name ? name : "NotAttached";
13191 #ifdef JS_CACHEIR_SPEW
13192 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
13193 sp.valueProperty("lhs", lhsVal_);
13194 sp.valueProperty("rhs", rhsVal_);
13195 sp.opcodeProperty("op", op_);
13197 #endif
13200 ToBoolIRGenerator::ToBoolIRGenerator(JSContext* cx, HandleScript script,
13201 jsbytecode* pc, ICState state,
13202 HandleValue val)
13203 : IRGenerator(cx, script, pc, CacheKind::ToBool, state), val_(val) {}
13205 void ToBoolIRGenerator::trackAttached(const char* name) {
13206 stubName_ = name ? name : "NotAttached";
13207 #ifdef JS_CACHEIR_SPEW
13208 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
13209 sp.valueProperty("val", val_);
13211 #endif
13214 AttachDecision ToBoolIRGenerator::tryAttachStub() {
13215 AutoAssertNoPendingException aanpe(cx_);
13216 writer.setTypeData(TypeData(JSValueType(val_.type())));
13218 TRY_ATTACH(tryAttachBool());
13219 TRY_ATTACH(tryAttachInt32());
13220 TRY_ATTACH(tryAttachNumber());
13221 TRY_ATTACH(tryAttachString());
13222 TRY_ATTACH(tryAttachNullOrUndefined());
13223 TRY_ATTACH(tryAttachObject());
13224 TRY_ATTACH(tryAttachSymbol());
13225 TRY_ATTACH(tryAttachBigInt());
13227 trackAttached(IRGenerator::NotAttached);
13228 return AttachDecision::NoAction;
13231 AttachDecision ToBoolIRGenerator::tryAttachBool() {
13232 if (!val_.isBoolean()) {
13233 return AttachDecision::NoAction;
13236 ValOperandId valId(writer.setInputOperandId(0));
13237 writer.guardNonDoubleType(valId, ValueType::Boolean);
13238 writer.loadOperandResult(valId);
13239 writer.returnFromIC();
13240 trackAttached("ToBool.Bool");
13241 return AttachDecision::Attach;
13244 AttachDecision ToBoolIRGenerator::tryAttachInt32() {
13245 if (!val_.isInt32()) {
13246 return AttachDecision::NoAction;
13249 ValOperandId valId(writer.setInputOperandId(0));
13250 writer.guardNonDoubleType(valId, ValueType::Int32);
13251 writer.loadInt32TruthyResult(valId);
13252 writer.returnFromIC();
13253 trackAttached("ToBool.Int32");
13254 return AttachDecision::Attach;
13257 AttachDecision ToBoolIRGenerator::tryAttachNumber() {
13258 if (!val_.isNumber()) {
13259 return AttachDecision::NoAction;
13262 ValOperandId valId(writer.setInputOperandId(0));
13263 NumberOperandId numId = writer.guardIsNumber(valId);
13264 writer.loadDoubleTruthyResult(numId);
13265 writer.returnFromIC();
13266 trackAttached("ToBool.Number");
13267 return AttachDecision::Attach;
13270 AttachDecision ToBoolIRGenerator::tryAttachSymbol() {
13271 if (!val_.isSymbol()) {
13272 return AttachDecision::NoAction;
13275 ValOperandId valId(writer.setInputOperandId(0));
13276 writer.guardNonDoubleType(valId, ValueType::Symbol);
13277 writer.loadBooleanResult(true);
13278 writer.returnFromIC();
13279 trackAttached("ToBool.Symbol");
13280 return AttachDecision::Attach;
13283 AttachDecision ToBoolIRGenerator::tryAttachString() {
13284 if (!val_.isString()) {
13285 return AttachDecision::NoAction;
13288 ValOperandId valId(writer.setInputOperandId(0));
13289 StringOperandId strId = writer.guardToString(valId);
13290 writer.loadStringTruthyResult(strId);
13291 writer.returnFromIC();
13292 trackAttached("ToBool.String");
13293 return AttachDecision::Attach;
13296 AttachDecision ToBoolIRGenerator::tryAttachNullOrUndefined() {
13297 if (!val_.isNullOrUndefined()) {
13298 return AttachDecision::NoAction;
13301 ValOperandId valId(writer.setInputOperandId(0));
13302 writer.guardIsNullOrUndefined(valId);
13303 writer.loadBooleanResult(false);
13304 writer.returnFromIC();
13305 trackAttached("ToBool.NullOrUndefined");
13306 return AttachDecision::Attach;
13309 AttachDecision ToBoolIRGenerator::tryAttachObject() {
13310 if (!val_.isObject()) {
13311 return AttachDecision::NoAction;
13314 ValOperandId valId(writer.setInputOperandId(0));
13315 ObjOperandId objId = writer.guardToObject(valId);
13316 writer.loadObjectTruthyResult(objId);
13317 writer.returnFromIC();
13318 trackAttached("ToBool.Object");
13319 return AttachDecision::Attach;
13322 AttachDecision ToBoolIRGenerator::tryAttachBigInt() {
13323 if (!val_.isBigInt()) {
13324 return AttachDecision::NoAction;
13327 ValOperandId valId(writer.setInputOperandId(0));
13328 BigIntOperandId bigIntId = writer.guardToBigInt(valId);
13329 writer.loadBigIntTruthyResult(bigIntId);
13330 writer.returnFromIC();
13331 trackAttached("ToBool.BigInt");
13332 return AttachDecision::Attach;
13335 GetIntrinsicIRGenerator::GetIntrinsicIRGenerator(JSContext* cx,
13336 HandleScript script,
13337 jsbytecode* pc, ICState state,
13338 HandleValue val)
13339 : IRGenerator(cx, script, pc, CacheKind::GetIntrinsic, state), val_(val) {}
13341 void GetIntrinsicIRGenerator::trackAttached(const char* name) {
13342 stubName_ = name ? name : "NotAttached";
13343 #ifdef JS_CACHEIR_SPEW
13344 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
13345 sp.valueProperty("val", val_);
13347 #endif
13350 AttachDecision GetIntrinsicIRGenerator::tryAttachStub() {
13351 AutoAssertNoPendingException aanpe(cx_);
13352 writer.loadValueResult(val_);
13353 writer.returnFromIC();
13354 trackAttached("GetIntrinsic");
13355 return AttachDecision::Attach;
13358 UnaryArithIRGenerator::UnaryArithIRGenerator(JSContext* cx, HandleScript script,
13359 jsbytecode* pc, ICState state,
13360 JSOp op, HandleValue val,
13361 HandleValue res)
13362 : IRGenerator(cx, script, pc, CacheKind::UnaryArith, state),
13363 op_(op),
13364 val_(val),
13365 res_(res) {}
13367 void UnaryArithIRGenerator::trackAttached(const char* name) {
13368 stubName_ = name ? name : "NotAttached";
13369 #ifdef JS_CACHEIR_SPEW
13370 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
13371 sp.valueProperty("val", val_);
13372 sp.valueProperty("res", res_);
13374 #endif
13377 AttachDecision UnaryArithIRGenerator::tryAttachStub() {
13378 AutoAssertNoPendingException aanpe(cx_);
13379 TRY_ATTACH(tryAttachInt32());
13380 TRY_ATTACH(tryAttachNumber());
13381 TRY_ATTACH(tryAttachBitwise());
13382 TRY_ATTACH(tryAttachBigIntPtr());
13383 TRY_ATTACH(tryAttachBigInt());
13384 TRY_ATTACH(tryAttachStringInt32());
13385 TRY_ATTACH(tryAttachStringNumber());
13387 trackAttached(IRGenerator::NotAttached);
13388 return AttachDecision::NoAction;
13391 AttachDecision UnaryArithIRGenerator::tryAttachInt32() {
13392 if (op_ == JSOp::BitNot) {
13393 return AttachDecision::NoAction;
13395 if (!CanConvertToInt32ForToNumber(val_) || !res_.isInt32()) {
13396 return AttachDecision::NoAction;
13399 ValOperandId valId(writer.setInputOperandId(0));
13401 Int32OperandId intId = EmitGuardToInt32ForToNumber(writer, valId, val_);
13402 switch (op_) {
13403 case JSOp::Pos:
13404 writer.loadInt32Result(intId);
13405 trackAttached("UnaryArith.Int32Pos");
13406 break;
13407 case JSOp::Neg:
13408 writer.int32NegationResult(intId);
13409 trackAttached("UnaryArith.Int32Neg");
13410 break;
13411 case JSOp::Inc:
13412 writer.int32IncResult(intId);
13413 trackAttached("UnaryArith.Int32Inc");
13414 break;
13415 case JSOp::Dec:
13416 writer.int32DecResult(intId);
13417 trackAttached("UnaryArith.Int32Dec");
13418 break;
13419 case JSOp::ToNumeric:
13420 writer.loadInt32Result(intId);
13421 trackAttached("UnaryArith.Int32ToNumeric");
13422 break;
13423 default:
13424 MOZ_CRASH("unexpected OP");
13427 writer.returnFromIC();
13428 return AttachDecision::Attach;
13431 AttachDecision UnaryArithIRGenerator::tryAttachNumber() {
13432 if (op_ == JSOp::BitNot) {
13433 return AttachDecision::NoAction;
13435 if (!CanConvertToDoubleForToNumber(val_)) {
13436 return AttachDecision::NoAction;
13438 MOZ_ASSERT(res_.isNumber());
13440 ValOperandId valId(writer.setInputOperandId(0));
13441 NumberOperandId numId = EmitGuardToDoubleForToNumber(writer, valId, val_);
13443 switch (op_) {
13444 case JSOp::Pos:
13445 writer.loadDoubleResult(numId);
13446 trackAttached("UnaryArith.DoublePos");
13447 break;
13448 case JSOp::Neg:
13449 writer.doubleNegationResult(numId);
13450 trackAttached("UnaryArith.DoubleNeg");
13451 break;
13452 case JSOp::Inc:
13453 writer.doubleIncResult(numId);
13454 trackAttached("UnaryArith.DoubleInc");
13455 break;
13456 case JSOp::Dec:
13457 writer.doubleDecResult(numId);
13458 trackAttached("UnaryArith.DoubleDec");
13459 break;
13460 case JSOp::ToNumeric:
13461 writer.loadDoubleResult(numId);
13462 trackAttached("UnaryArith.DoubleToNumeric");
13463 break;
13464 default:
13465 MOZ_CRASH("Unexpected OP");
13468 writer.returnFromIC();
13469 return AttachDecision::Attach;
13472 static bool CanTruncateToInt32(const Value& val) {
13473 return val.isNumber() || val.isBoolean() || val.isNullOrUndefined() ||
13474 val.isString();
13477 // Convert type into int32 for the bitwise/shift operands.
13478 static Int32OperandId EmitTruncateToInt32Guard(CacheIRWriter& writer,
13479 ValOperandId id,
13480 const Value& val) {
13481 MOZ_ASSERT(CanTruncateToInt32(val));
13482 if (val.isInt32()) {
13483 return writer.guardToInt32(id);
13485 if (val.isBoolean()) {
13486 return writer.guardBooleanToInt32(id);
13488 if (val.isNullOrUndefined()) {
13489 writer.guardIsNullOrUndefined(id);
13490 return writer.loadInt32Constant(0);
13492 NumberOperandId numId;
13493 if (val.isString()) {
13494 StringOperandId strId = writer.guardToString(id);
13495 numId = writer.guardStringToNumber(strId);
13496 } else {
13497 MOZ_ASSERT(val.isDouble());
13498 numId = writer.guardIsNumber(id);
13500 return writer.truncateDoubleToUInt32(numId);
13503 AttachDecision UnaryArithIRGenerator::tryAttachBitwise() {
13504 // Only bitwise operators.
13505 if (op_ != JSOp::BitNot) {
13506 return AttachDecision::NoAction;
13509 // Check guard conditions
13510 if (!CanTruncateToInt32(val_)) {
13511 return AttachDecision::NoAction;
13514 // Bitwise operators always produce Int32 values.
13515 MOZ_ASSERT(res_.isInt32());
13517 ValOperandId valId(writer.setInputOperandId(0));
13518 Int32OperandId intId = EmitTruncateToInt32Guard(writer, valId, val_);
13519 writer.int32NotResult(intId);
13520 trackAttached("UnaryArith.BitwiseBitNot");
13522 writer.returnFromIC();
13523 return AttachDecision::Attach;
13526 AttachDecision UnaryArithIRGenerator::tryAttachBigInt() {
13527 if (!val_.isBigInt()) {
13528 return AttachDecision::NoAction;
13530 MOZ_ASSERT(res_.isBigInt());
13532 MOZ_ASSERT(op_ != JSOp::Pos,
13533 "Applying the unary + operator on BigInt values throws an error");
13535 ValOperandId valId(writer.setInputOperandId(0));
13536 BigIntOperandId bigIntId = writer.guardToBigInt(valId);
13537 switch (op_) {
13538 case JSOp::BitNot:
13539 writer.bigIntNotResult(bigIntId);
13540 trackAttached("UnaryArith.BigIntNot");
13541 break;
13542 case JSOp::Neg:
13543 writer.bigIntNegationResult(bigIntId);
13544 trackAttached("UnaryArith.BigIntNeg");
13545 break;
13546 case JSOp::Inc:
13547 writer.bigIntIncResult(bigIntId);
13548 trackAttached("UnaryArith.BigIntInc");
13549 break;
13550 case JSOp::Dec:
13551 writer.bigIntDecResult(bigIntId);
13552 trackAttached("UnaryArith.BigIntDec");
13553 break;
13554 case JSOp::ToNumeric:
13555 writer.loadBigIntResult(bigIntId);
13556 trackAttached("UnaryArith.BigIntToNumeric");
13557 break;
13558 default:
13559 MOZ_CRASH("Unexpected OP");
13562 writer.returnFromIC();
13563 return AttachDecision::Attach;
13566 AttachDecision UnaryArithIRGenerator::tryAttachBigIntPtr() {
13567 if (!val_.isBigInt()) {
13568 return AttachDecision::NoAction;
13570 MOZ_ASSERT(res_.isBigInt());
13572 MOZ_ASSERT(op_ != JSOp::Pos,
13573 "Applying the unary + operator on BigInt values throws an error");
13575 switch (op_) {
13576 case JSOp::BitNot:
13577 case JSOp::Neg:
13578 case JSOp::Inc:
13579 case JSOp::Dec:
13580 break;
13581 case JSOp::ToNumeric:
13582 return AttachDecision::NoAction;
13583 default:
13584 MOZ_CRASH("Unexpected OP");
13587 intptr_t val;
13588 if (!BigInt::isIntPtr(val_.toBigInt(), &val)) {
13589 return AttachDecision::NoAction;
13592 using CheckedIntPtr = mozilla::CheckedInt<intptr_t>;
13594 switch (op_) {
13595 case JSOp::BitNot: {
13596 // Bitwise operations always return an intptr-sized result.
13597 break;
13599 case JSOp::Neg: {
13600 auto result = -CheckedIntPtr(val);
13601 if (result.isValid()) {
13602 break;
13604 return AttachDecision::NoAction;
13606 case JSOp::Inc: {
13607 auto result = CheckedIntPtr(val) + intptr_t(1);
13608 if (result.isValid()) {
13609 break;
13611 return AttachDecision::NoAction;
13613 case JSOp::Dec: {
13614 auto result = CheckedIntPtr(val) - intptr_t(1);
13615 if (result.isValid()) {
13616 break;
13618 return AttachDecision::NoAction;
13620 default:
13621 MOZ_CRASH("Unexpected OP");
13624 ValOperandId valId(writer.setInputOperandId(0));
13625 BigIntOperandId bigIntId = writer.guardToBigInt(valId);
13626 IntPtrOperandId intPtrId = writer.bigIntToIntPtr(bigIntId);
13627 IntPtrOperandId resultId;
13628 switch (op_) {
13629 case JSOp::BitNot:
13630 resultId = writer.bigIntPtrNot(intPtrId);
13631 trackAttached("UnaryArith.BigIntPtrNot");
13632 break;
13633 case JSOp::Neg:
13634 resultId = writer.bigIntPtrNegation(intPtrId);
13635 trackAttached("UnaryArith.BigIntPtrNeg");
13636 break;
13637 case JSOp::Inc:
13638 resultId = writer.bigIntPtrInc(intPtrId);
13639 trackAttached("UnaryArith.BigIntPtrInc");
13640 break;
13641 case JSOp::Dec:
13642 resultId = writer.bigIntPtrDec(intPtrId);
13643 trackAttached("UnaryArith.BigIntPtrDec");
13644 break;
13645 default:
13646 MOZ_CRASH("Unexpected OP");
13649 writer.intPtrToBigIntResult(resultId);
13650 writer.returnFromIC();
13651 return AttachDecision::Attach;
13654 AttachDecision UnaryArithIRGenerator::tryAttachStringInt32() {
13655 if (!val_.isString()) {
13656 return AttachDecision::NoAction;
13658 MOZ_ASSERT(res_.isNumber());
13660 // Case should have been handled by tryAttachBitwise.
13661 MOZ_ASSERT(op_ != JSOp::BitNot);
13663 if (!res_.isInt32()) {
13664 return AttachDecision::NoAction;
13667 ValOperandId valId(writer.setInputOperandId(0));
13668 StringOperandId stringId = writer.guardToString(valId);
13669 Int32OperandId intId = writer.guardStringToInt32(stringId);
13671 switch (op_) {
13672 case JSOp::Pos:
13673 writer.loadInt32Result(intId);
13674 trackAttached("UnaryArith.StringInt32Pos");
13675 break;
13676 case JSOp::Neg:
13677 writer.int32NegationResult(intId);
13678 trackAttached("UnaryArith.StringInt32Neg");
13679 break;
13680 case JSOp::Inc:
13681 writer.int32IncResult(intId);
13682 trackAttached("UnaryArith.StringInt32Inc");
13683 break;
13684 case JSOp::Dec:
13685 writer.int32DecResult(intId);
13686 trackAttached("UnaryArith.StringInt32Dec");
13687 break;
13688 case JSOp::ToNumeric:
13689 writer.loadInt32Result(intId);
13690 trackAttached("UnaryArith.StringInt32ToNumeric");
13691 break;
13692 default:
13693 MOZ_CRASH("Unexpected OP");
13696 writer.returnFromIC();
13697 return AttachDecision::Attach;
13700 AttachDecision UnaryArithIRGenerator::tryAttachStringNumber() {
13701 if (!val_.isString()) {
13702 return AttachDecision::NoAction;
13704 MOZ_ASSERT(res_.isNumber());
13706 // Case should have been handled by tryAttachBitwise.
13707 MOZ_ASSERT(op_ != JSOp::BitNot);
13709 ValOperandId valId(writer.setInputOperandId(0));
13710 StringOperandId stringId = writer.guardToString(valId);
13711 NumberOperandId numId = writer.guardStringToNumber(stringId);
13713 Int32OperandId truncatedId;
13714 switch (op_) {
13715 case JSOp::Pos:
13716 writer.loadDoubleResult(numId);
13717 trackAttached("UnaryArith.StringNumberPos");
13718 break;
13719 case JSOp::Neg:
13720 writer.doubleNegationResult(numId);
13721 trackAttached("UnaryArith.StringNumberNeg");
13722 break;
13723 case JSOp::Inc:
13724 writer.doubleIncResult(numId);
13725 trackAttached("UnaryArith.StringNumberInc");
13726 break;
13727 case JSOp::Dec:
13728 writer.doubleDecResult(numId);
13729 trackAttached("UnaryArith.StringNumberDec");
13730 break;
13731 case JSOp::ToNumeric:
13732 writer.loadDoubleResult(numId);
13733 trackAttached("UnaryArith.StringNumberToNumeric");
13734 break;
13735 default:
13736 MOZ_CRASH("Unexpected OP");
13739 writer.returnFromIC();
13740 return AttachDecision::Attach;
13743 ToPropertyKeyIRGenerator::ToPropertyKeyIRGenerator(JSContext* cx,
13744 HandleScript script,
13745 jsbytecode* pc,
13746 ICState state,
13747 HandleValue val)
13748 : IRGenerator(cx, script, pc, CacheKind::ToPropertyKey, state), val_(val) {}
13750 void ToPropertyKeyIRGenerator::trackAttached(const char* name) {
13751 stubName_ = name ? name : "NotAttached";
13752 #ifdef JS_CACHEIR_SPEW
13753 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
13754 sp.valueProperty("val", val_);
13756 #endif
13759 AttachDecision ToPropertyKeyIRGenerator::tryAttachStub() {
13760 AutoAssertNoPendingException aanpe(cx_);
13761 TRY_ATTACH(tryAttachInt32());
13762 TRY_ATTACH(tryAttachNumber());
13763 TRY_ATTACH(tryAttachString());
13764 TRY_ATTACH(tryAttachSymbol());
13766 trackAttached(IRGenerator::NotAttached);
13767 return AttachDecision::NoAction;
13770 AttachDecision ToPropertyKeyIRGenerator::tryAttachInt32() {
13771 if (!val_.isInt32()) {
13772 return AttachDecision::NoAction;
13775 ValOperandId valId(writer.setInputOperandId(0));
13777 Int32OperandId intId = writer.guardToInt32(valId);
13778 writer.loadInt32Result(intId);
13779 writer.returnFromIC();
13781 trackAttached("ToPropertyKey.Int32");
13782 return AttachDecision::Attach;
13785 AttachDecision ToPropertyKeyIRGenerator::tryAttachNumber() {
13786 if (!val_.isNumber()) {
13787 return AttachDecision::NoAction;
13790 // We allow negative zero here because ToPropertyKey(-0.0) is 0.
13791 int32_t unused;
13792 if (!mozilla::NumberEqualsInt32(val_.toNumber(), &unused)) {
13793 return AttachDecision::NoAction;
13796 ValOperandId valId(writer.setInputOperandId(0));
13798 Int32OperandId intId = writer.guardToInt32Index(valId);
13799 writer.loadInt32Result(intId);
13800 writer.returnFromIC();
13802 trackAttached("ToPropertyKey.Number");
13803 return AttachDecision::Attach;
13806 AttachDecision ToPropertyKeyIRGenerator::tryAttachString() {
13807 if (!val_.isString()) {
13808 return AttachDecision::NoAction;
13811 ValOperandId valId(writer.setInputOperandId(0));
13813 StringOperandId strId = writer.guardToString(valId);
13814 writer.loadStringResult(strId);
13815 writer.returnFromIC();
13817 trackAttached("ToPropertyKey.String");
13818 return AttachDecision::Attach;
13821 AttachDecision ToPropertyKeyIRGenerator::tryAttachSymbol() {
13822 if (!val_.isSymbol()) {
13823 return AttachDecision::NoAction;
13826 ValOperandId valId(writer.setInputOperandId(0));
13828 SymbolOperandId strId = writer.guardToSymbol(valId);
13829 writer.loadSymbolResult(strId);
13830 writer.returnFromIC();
13832 trackAttached("ToPropertyKey.Symbol");
13833 return AttachDecision::Attach;
13836 BinaryArithIRGenerator::BinaryArithIRGenerator(JSContext* cx,
13837 HandleScript script,
13838 jsbytecode* pc, ICState state,
13839 JSOp op, HandleValue lhs,
13840 HandleValue rhs, HandleValue res)
13841 : IRGenerator(cx, script, pc, CacheKind::BinaryArith, state),
13842 op_(op),
13843 lhs_(lhs),
13844 rhs_(rhs),
13845 res_(res) {}
13847 void BinaryArithIRGenerator::trackAttached(const char* name) {
13848 stubName_ = name ? name : "NotAttached";
13849 #ifdef JS_CACHEIR_SPEW
13850 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
13851 sp.opcodeProperty("op", op_);
13852 sp.valueProperty("rhs", rhs_);
13853 sp.valueProperty("lhs", lhs_);
13855 #endif
13858 AttachDecision BinaryArithIRGenerator::tryAttachStub() {
13859 AutoAssertNoPendingException aanpe(cx_);
13860 // Arithmetic operations with Int32 operands
13861 TRY_ATTACH(tryAttachInt32());
13863 // Bitwise operations with Int32/Double/Boolean/Null/Undefined/String
13864 // operands.
13865 TRY_ATTACH(tryAttachBitwise());
13867 // Arithmetic operations with Double operands. This needs to come after
13868 // tryAttachInt32, as the guards overlap, and we'd prefer to attach the
13869 // more specialized Int32 IC if it is possible.
13870 TRY_ATTACH(tryAttachDouble());
13872 // String x {String,Number,Boolean,Null,Undefined}
13873 TRY_ATTACH(tryAttachStringConcat());
13875 // String x Object
13876 TRY_ATTACH(tryAttachStringObjectConcat());
13878 // Arithmetic operations or bitwise operations with intptr-sized BigInt
13879 // operands.
13880 TRY_ATTACH(tryAttachBigIntPtr());
13882 // Arithmetic operations or bitwise operations with BigInt operands
13883 TRY_ATTACH(tryAttachBigInt());
13885 // Arithmetic operations (without addition) with String x Int32.
13886 TRY_ATTACH(tryAttachStringInt32Arith());
13888 // Arithmetic operations (without addition) with String x Number. This needs
13889 // to come after tryAttachStringInt32Arith, as the guards overlap, and we'd
13890 // prefer to attach the more specialized Int32 IC if it is possible.
13891 TRY_ATTACH(tryAttachStringNumberArith());
13893 trackAttached(IRGenerator::NotAttached);
13894 return AttachDecision::NoAction;
13897 AttachDecision BinaryArithIRGenerator::tryAttachBitwise() {
13898 // Only bit-wise and shifts.
13899 if (op_ != JSOp::BitOr && op_ != JSOp::BitXor && op_ != JSOp::BitAnd &&
13900 op_ != JSOp::Lsh && op_ != JSOp::Rsh && op_ != JSOp::Ursh) {
13901 return AttachDecision::NoAction;
13904 // Check guard conditions
13905 if (!CanTruncateToInt32(lhs_) || !CanTruncateToInt32(rhs_)) {
13906 return AttachDecision::NoAction;
13909 // All ops, with the exception of Ursh, produce Int32 values.
13910 MOZ_ASSERT_IF(op_ != JSOp::Ursh, res_.isInt32());
13912 ValOperandId lhsId(writer.setInputOperandId(0));
13913 ValOperandId rhsId(writer.setInputOperandId(1));
13915 Int32OperandId lhsIntId = EmitTruncateToInt32Guard(writer, lhsId, lhs_);
13916 Int32OperandId rhsIntId = EmitTruncateToInt32Guard(writer, rhsId, rhs_);
13918 switch (op_) {
13919 case JSOp::BitOr:
13920 writer.int32BitOrResult(lhsIntId, rhsIntId);
13921 trackAttached("BinaryArith.BitwiseBitOr");
13922 break;
13923 case JSOp::BitXor:
13924 writer.int32BitXorResult(lhsIntId, rhsIntId);
13925 trackAttached("BinaryArith.BitwiseBitXor");
13926 break;
13927 case JSOp::BitAnd:
13928 writer.int32BitAndResult(lhsIntId, rhsIntId);
13929 trackAttached("BinaryArith.BitwiseBitAnd");
13930 break;
13931 case JSOp::Lsh:
13932 writer.int32LeftShiftResult(lhsIntId, rhsIntId);
13933 trackAttached("BinaryArith.BitwiseLeftShift");
13934 break;
13935 case JSOp::Rsh:
13936 writer.int32RightShiftResult(lhsIntId, rhsIntId);
13937 trackAttached("BinaryArith.BitwiseRightShift");
13938 break;
13939 case JSOp::Ursh:
13940 writer.int32URightShiftResult(lhsIntId, rhsIntId, res_.isDouble());
13941 trackAttached("BinaryArith.BitwiseUnsignedRightShift");
13942 break;
13943 default:
13944 MOZ_CRASH("Unhandled op in tryAttachBitwise");
13947 writer.returnFromIC();
13948 return AttachDecision::Attach;
13951 AttachDecision BinaryArithIRGenerator::tryAttachDouble() {
13952 // Check valid opcodes
13953 if (op_ != JSOp::Add && op_ != JSOp::Sub && op_ != JSOp::Mul &&
13954 op_ != JSOp::Div && op_ != JSOp::Mod && op_ != JSOp::Pow) {
13955 return AttachDecision::NoAction;
13958 // Check guard conditions.
13959 if (!CanConvertToDoubleForToNumber(lhs_) ||
13960 !CanConvertToDoubleForToNumber(rhs_)) {
13961 return AttachDecision::NoAction;
13964 ValOperandId lhsId(writer.setInputOperandId(0));
13965 ValOperandId rhsId(writer.setInputOperandId(1));
13967 NumberOperandId lhs = EmitGuardToDoubleForToNumber(writer, lhsId, lhs_);
13968 NumberOperandId rhs = EmitGuardToDoubleForToNumber(writer, rhsId, rhs_);
13970 switch (op_) {
13971 case JSOp::Add:
13972 writer.doubleAddResult(lhs, rhs);
13973 trackAttached("BinaryArith.DoubleAdd");
13974 break;
13975 case JSOp::Sub:
13976 writer.doubleSubResult(lhs, rhs);
13977 trackAttached("BinaryArith.DoubleSub");
13978 break;
13979 case JSOp::Mul:
13980 writer.doubleMulResult(lhs, rhs);
13981 trackAttached("BinaryArith.DoubleMul");
13982 break;
13983 case JSOp::Div:
13984 writer.doubleDivResult(lhs, rhs);
13985 trackAttached("BinaryArith.DoubleDiv");
13986 break;
13987 case JSOp::Mod:
13988 writer.doubleModResult(lhs, rhs);
13989 trackAttached("BinaryArith.DoubleMod");
13990 break;
13991 case JSOp::Pow:
13992 writer.doublePowResult(lhs, rhs);
13993 trackAttached("BinaryArith.DoublePow");
13994 break;
13995 default:
13996 MOZ_CRASH("Unhandled Op");
13998 writer.returnFromIC();
13999 return AttachDecision::Attach;
14002 AttachDecision BinaryArithIRGenerator::tryAttachInt32() {
14003 // Check guard conditions.
14004 if (!CanConvertToInt32ForToNumber(lhs_) ||
14005 !CanConvertToInt32ForToNumber(rhs_)) {
14006 return AttachDecision::NoAction;
14009 // These ICs will failure() if result can't be encoded in an Int32:
14010 // If sample result is not Int32, we should avoid IC.
14011 if (!res_.isInt32()) {
14012 return AttachDecision::NoAction;
14015 if (op_ != JSOp::Add && op_ != JSOp::Sub && op_ != JSOp::Mul &&
14016 op_ != JSOp::Div && op_ != JSOp::Mod && op_ != JSOp::Pow) {
14017 return AttachDecision::NoAction;
14020 if (op_ == JSOp::Pow && !CanAttachInt32Pow(lhs_, rhs_)) {
14021 return AttachDecision::NoAction;
14024 ValOperandId lhsId(writer.setInputOperandId(0));
14025 ValOperandId rhsId(writer.setInputOperandId(1));
14027 Int32OperandId lhsIntId = EmitGuardToInt32ForToNumber(writer, lhsId, lhs_);
14028 Int32OperandId rhsIntId = EmitGuardToInt32ForToNumber(writer, rhsId, rhs_);
14030 switch (op_) {
14031 case JSOp::Add:
14032 writer.int32AddResult(lhsIntId, rhsIntId);
14033 trackAttached("BinaryArith.Int32Add");
14034 break;
14035 case JSOp::Sub:
14036 writer.int32SubResult(lhsIntId, rhsIntId);
14037 trackAttached("BinaryArith.Int32Sub");
14038 break;
14039 case JSOp::Mul:
14040 writer.int32MulResult(lhsIntId, rhsIntId);
14041 trackAttached("BinaryArith.Int32Mul");
14042 break;
14043 case JSOp::Div:
14044 writer.int32DivResult(lhsIntId, rhsIntId);
14045 trackAttached("BinaryArith.Int32Div");
14046 break;
14047 case JSOp::Mod:
14048 writer.int32ModResult(lhsIntId, rhsIntId);
14049 trackAttached("BinaryArith.Int32Mod");
14050 break;
14051 case JSOp::Pow:
14052 writer.int32PowResult(lhsIntId, rhsIntId);
14053 trackAttached("BinaryArith.Int32Pow");
14054 break;
14055 default:
14056 MOZ_CRASH("Unhandled op in tryAttachInt32");
14059 writer.returnFromIC();
14060 return AttachDecision::Attach;
14063 AttachDecision BinaryArithIRGenerator::tryAttachStringConcat() {
14064 // Only Addition
14065 if (op_ != JSOp::Add) {
14066 return AttachDecision::NoAction;
14069 // One side must be a string, the other side a primitive value we can easily
14070 // convert to a string.
14071 if (!(lhs_.isString() && CanConvertToString(rhs_)) &&
14072 !(CanConvertToString(lhs_) && rhs_.isString())) {
14073 return AttachDecision::NoAction;
14076 ValOperandId lhsId(writer.setInputOperandId(0));
14077 ValOperandId rhsId(writer.setInputOperandId(1));
14079 StringOperandId lhsStrId = emitToStringGuard(lhsId, lhs_);
14080 StringOperandId rhsStrId = emitToStringGuard(rhsId, rhs_);
14082 writer.callStringConcatResult(lhsStrId, rhsStrId);
14084 writer.returnFromIC();
14085 trackAttached("BinaryArith.StringConcat");
14086 return AttachDecision::Attach;
14089 AttachDecision BinaryArithIRGenerator::tryAttachStringObjectConcat() {
14090 // Only Addition
14091 if (op_ != JSOp::Add) {
14092 return AttachDecision::NoAction;
14095 // Check Guards
14096 if (!(lhs_.isObject() && rhs_.isString()) &&
14097 !(lhs_.isString() && rhs_.isObject()))
14098 return AttachDecision::NoAction;
14100 ValOperandId lhsId(writer.setInputOperandId(0));
14101 ValOperandId rhsId(writer.setInputOperandId(1));
14103 // This guard is actually overly tight, as the runtime
14104 // helper can handle lhs or rhs being a string, so long
14105 // as the other is an object.
14106 if (lhs_.isString()) {
14107 writer.guardToString(lhsId);
14108 writer.guardToObject(rhsId);
14109 } else {
14110 writer.guardToObject(lhsId);
14111 writer.guardToString(rhsId);
14114 writer.callStringObjectConcatResult(lhsId, rhsId);
14116 writer.returnFromIC();
14117 trackAttached("BinaryArith.StringObjectConcat");
14118 return AttachDecision::Attach;
14121 AttachDecision BinaryArithIRGenerator::tryAttachBigInt() {
14122 // Check Guards
14123 if (!lhs_.isBigInt() || !rhs_.isBigInt()) {
14124 return AttachDecision::NoAction;
14127 switch (op_) {
14128 case JSOp::Add:
14129 case JSOp::Sub:
14130 case JSOp::Mul:
14131 case JSOp::Div:
14132 case JSOp::Mod:
14133 case JSOp::Pow:
14134 // Arithmetic operations.
14135 break;
14137 case JSOp::BitOr:
14138 case JSOp::BitXor:
14139 case JSOp::BitAnd:
14140 case JSOp::Lsh:
14141 case JSOp::Rsh:
14142 // Bitwise operations.
14143 break;
14145 default:
14146 return AttachDecision::NoAction;
14149 ValOperandId lhsId(writer.setInputOperandId(0));
14150 ValOperandId rhsId(writer.setInputOperandId(1));
14152 BigIntOperandId lhsBigIntId = writer.guardToBigInt(lhsId);
14153 BigIntOperandId rhsBigIntId = writer.guardToBigInt(rhsId);
14155 switch (op_) {
14156 case JSOp::Add:
14157 writer.bigIntAddResult(lhsBigIntId, rhsBigIntId);
14158 trackAttached("BinaryArith.BigIntAdd");
14159 break;
14160 case JSOp::Sub:
14161 writer.bigIntSubResult(lhsBigIntId, rhsBigIntId);
14162 trackAttached("BinaryArith.BigIntSub");
14163 break;
14164 case JSOp::Mul:
14165 writer.bigIntMulResult(lhsBigIntId, rhsBigIntId);
14166 trackAttached("BinaryArith.BigIntMul");
14167 break;
14168 case JSOp::Div:
14169 writer.bigIntDivResult(lhsBigIntId, rhsBigIntId);
14170 trackAttached("BinaryArith.BigIntDiv");
14171 break;
14172 case JSOp::Mod:
14173 writer.bigIntModResult(lhsBigIntId, rhsBigIntId);
14174 trackAttached("BinaryArith.BigIntMod");
14175 break;
14176 case JSOp::Pow:
14177 writer.bigIntPowResult(lhsBigIntId, rhsBigIntId);
14178 trackAttached("BinaryArith.BigIntPow");
14179 break;
14180 case JSOp::BitOr:
14181 writer.bigIntBitOrResult(lhsBigIntId, rhsBigIntId);
14182 trackAttached("BinaryArith.BigIntBitOr");
14183 break;
14184 case JSOp::BitXor:
14185 writer.bigIntBitXorResult(lhsBigIntId, rhsBigIntId);
14186 trackAttached("BinaryArith.BigIntBitXor");
14187 break;
14188 case JSOp::BitAnd:
14189 writer.bigIntBitAndResult(lhsBigIntId, rhsBigIntId);
14190 trackAttached("BinaryArith.BigIntBitAnd");
14191 break;
14192 case JSOp::Lsh:
14193 writer.bigIntLeftShiftResult(lhsBigIntId, rhsBigIntId);
14194 trackAttached("BinaryArith.BigIntLeftShift");
14195 break;
14196 case JSOp::Rsh:
14197 writer.bigIntRightShiftResult(lhsBigIntId, rhsBigIntId);
14198 trackAttached("BinaryArith.BigIntRightShift");
14199 break;
14200 default:
14201 MOZ_CRASH("Unhandled op in tryAttachBigInt");
14204 writer.returnFromIC();
14205 return AttachDecision::Attach;
14208 AttachDecision BinaryArithIRGenerator::tryAttachBigIntPtr() {
14209 // Check Guards
14210 if (!lhs_.isBigInt() || !rhs_.isBigInt()) {
14211 return AttachDecision::NoAction;
14214 switch (op_) {
14215 case JSOp::Add:
14216 case JSOp::Sub:
14217 case JSOp::Mul:
14218 case JSOp::Div:
14219 case JSOp::Mod:
14220 case JSOp::Pow:
14221 // Arithmetic operations.
14222 break;
14224 case JSOp::BitOr:
14225 case JSOp::BitXor:
14226 case JSOp::BitAnd:
14227 case JSOp::Lsh:
14228 case JSOp::Rsh:
14229 // Bitwise operations.
14230 break;
14232 default:
14233 return AttachDecision::NoAction;
14236 intptr_t lhs;
14237 intptr_t rhs;
14238 if (!BigInt::isIntPtr(lhs_.toBigInt(), &lhs) ||
14239 !BigInt::isIntPtr(rhs_.toBigInt(), &rhs)) {
14240 return AttachDecision::NoAction;
14243 using CheckedIntPtr = mozilla::CheckedInt<intptr_t>;
14245 switch (op_) {
14246 case JSOp::Add: {
14247 auto result = CheckedIntPtr(lhs) + rhs;
14248 if (result.isValid()) {
14249 break;
14251 return AttachDecision::NoAction;
14253 case JSOp::Sub: {
14254 auto result = CheckedIntPtr(lhs) - rhs;
14255 if (result.isValid()) {
14256 break;
14258 return AttachDecision::NoAction;
14260 case JSOp::Mul: {
14261 auto result = CheckedIntPtr(lhs) * rhs;
14262 if (result.isValid()) {
14263 break;
14265 return AttachDecision::NoAction;
14267 case JSOp::Div: {
14268 auto result = CheckedIntPtr(lhs) / rhs;
14269 if (result.isValid()) {
14270 break;
14272 return AttachDecision::NoAction;
14274 case JSOp::Mod: {
14275 // We can't use mozilla::CheckedInt here, because it disallows negative
14276 // inputs.
14277 if (rhs != 0) {
14278 break;
14280 return AttachDecision::NoAction;
14282 case JSOp::Pow: {
14283 intptr_t result;
14284 if (BigInt::powIntPtr(lhs, rhs, &result)) {
14285 break;
14287 return AttachDecision::NoAction;
14289 case JSOp::BitOr:
14290 case JSOp::BitXor:
14291 case JSOp::BitAnd: {
14292 // Bitwise operations always return an intptr-sized result.
14293 break;
14295 case JSOp::Lsh: {
14296 if (lhs == 0 || rhs <= 0) {
14297 break;
14299 if (size_t(rhs) < BigInt::DigitBits) {
14300 intptr_t result = lhs << rhs;
14301 if ((result >> rhs) == lhs) {
14302 break;
14305 return AttachDecision::NoAction;
14307 case JSOp::Rsh: {
14308 if (lhs == 0 || rhs >= 0) {
14309 break;
14311 if (rhs > -intptr_t(BigInt::DigitBits)) {
14312 intptr_t result = lhs << -rhs;
14313 if ((result >> -rhs) == lhs) {
14314 break;
14317 return AttachDecision::NoAction;
14319 default:
14320 MOZ_CRASH("Unexpected OP");
14323 ValOperandId lhsId(writer.setInputOperandId(0));
14324 ValOperandId rhsId(writer.setInputOperandId(1));
14326 BigIntOperandId lhsBigIntId = writer.guardToBigInt(lhsId);
14327 BigIntOperandId rhsBigIntId = writer.guardToBigInt(rhsId);
14329 IntPtrOperandId lhsIntPtrId = writer.bigIntToIntPtr(lhsBigIntId);
14330 IntPtrOperandId rhsIntPtrId = writer.bigIntToIntPtr(rhsBigIntId);
14332 IntPtrOperandId resultId;
14333 switch (op_) {
14334 case JSOp::Add: {
14335 resultId = writer.bigIntPtrAdd(lhsIntPtrId, rhsIntPtrId);
14336 trackAttached("BinaryArith.BigIntPtr.Add");
14337 break;
14339 case JSOp::Sub: {
14340 resultId = writer.bigIntPtrSub(lhsIntPtrId, rhsIntPtrId);
14341 trackAttached("BinaryArith.BigIntPtr.Sub");
14342 break;
14344 case JSOp::Mul: {
14345 resultId = writer.bigIntPtrMul(lhsIntPtrId, rhsIntPtrId);
14346 trackAttached("BinaryArith.BigIntPtr.Mul");
14347 break;
14349 case JSOp::Div: {
14350 resultId = writer.bigIntPtrDiv(lhsIntPtrId, rhsIntPtrId);
14351 trackAttached("BinaryArith.BigIntPtr.Div");
14352 break;
14354 case JSOp::Mod: {
14355 resultId = writer.bigIntPtrMod(lhsIntPtrId, rhsIntPtrId);
14356 trackAttached("BinaryArith.BigIntPtr.Mod");
14357 break;
14359 case JSOp::Pow: {
14360 resultId = writer.bigIntPtrPow(lhsIntPtrId, rhsIntPtrId);
14361 trackAttached("BinaryArith.BigIntPtr.Pow");
14362 break;
14364 case JSOp::BitOr: {
14365 resultId = writer.bigIntPtrBitOr(lhsIntPtrId, rhsIntPtrId);
14366 trackAttached("BinaryArith.BigIntPtr.BitOr");
14367 break;
14369 case JSOp::BitXor: {
14370 resultId = writer.bigIntPtrBitXor(lhsIntPtrId, rhsIntPtrId);
14371 trackAttached("BinaryArith.BigIntPtr.BitXor");
14372 break;
14374 case JSOp::BitAnd: {
14375 resultId = writer.bigIntPtrBitAnd(lhsIntPtrId, rhsIntPtrId);
14376 trackAttached("BinaryArith.BigIntPtr.BitAnd");
14377 break;
14379 case JSOp::Lsh: {
14380 resultId = writer.bigIntPtrLeftShift(lhsIntPtrId, rhsIntPtrId);
14381 trackAttached("BinaryArith.BigIntPtr.LeftShift");
14382 break;
14384 case JSOp::Rsh: {
14385 resultId = writer.bigIntPtrRightShift(lhsIntPtrId, rhsIntPtrId);
14386 trackAttached("BinaryArith.BigIntPtr.RightShift");
14387 break;
14389 default:
14390 MOZ_CRASH("Unexpected OP");
14393 writer.intPtrToBigIntResult(resultId);
14394 writer.returnFromIC();
14395 return AttachDecision::Attach;
14398 AttachDecision BinaryArithIRGenerator::tryAttachStringInt32Arith() {
14399 // Check for either int32 x string or string x int32.
14400 if (!(lhs_.isInt32() && rhs_.isString()) &&
14401 !(lhs_.isString() && rhs_.isInt32())) {
14402 return AttachDecision::NoAction;
14405 // The created ICs will fail if the result can't be encoded as as int32.
14406 // Thus skip this IC, if the sample result is not an int32.
14407 if (!res_.isInt32()) {
14408 return AttachDecision::NoAction;
14411 // Must _not_ support Add, because it would be string concatenation instead.
14412 // For Pow we can't easily determine the CanAttachInt32Pow conditions so we
14413 // reject that as well.
14414 if (op_ != JSOp::Sub && op_ != JSOp::Mul && op_ != JSOp::Div &&
14415 op_ != JSOp::Mod) {
14416 return AttachDecision::NoAction;
14419 // The string operand must be convertable to an int32 value.
14420 JSString* str = lhs_.isString() ? lhs_.toString() : rhs_.toString();
14422 double num;
14423 if (!StringToNumber(cx_, str, &num)) {
14424 cx_->recoverFromOutOfMemory();
14425 return AttachDecision::NoAction;
14428 int32_t unused;
14429 if (!mozilla::NumberIsInt32(num, &unused)) {
14430 return AttachDecision::NoAction;
14433 ValOperandId lhsId(writer.setInputOperandId(0));
14434 ValOperandId rhsId(writer.setInputOperandId(1));
14436 auto guardToInt32 = [&](ValOperandId id, const Value& v) {
14437 if (v.isInt32()) {
14438 return writer.guardToInt32(id);
14441 MOZ_ASSERT(v.isString());
14442 StringOperandId strId = writer.guardToString(id);
14443 return writer.guardStringToInt32(strId);
14446 Int32OperandId lhsIntId = guardToInt32(lhsId, lhs_);
14447 Int32OperandId rhsIntId = guardToInt32(rhsId, rhs_);
14449 switch (op_) {
14450 case JSOp::Sub:
14451 writer.int32SubResult(lhsIntId, rhsIntId);
14452 trackAttached("BinaryArith.StringInt32Sub");
14453 break;
14454 case JSOp::Mul:
14455 writer.int32MulResult(lhsIntId, rhsIntId);
14456 trackAttached("BinaryArith.StringInt32Mul");
14457 break;
14458 case JSOp::Div:
14459 writer.int32DivResult(lhsIntId, rhsIntId);
14460 trackAttached("BinaryArith.StringInt32Div");
14461 break;
14462 case JSOp::Mod:
14463 writer.int32ModResult(lhsIntId, rhsIntId);
14464 trackAttached("BinaryArith.StringInt32Mod");
14465 break;
14466 default:
14467 MOZ_CRASH("Unhandled op in tryAttachStringInt32Arith");
14470 writer.returnFromIC();
14471 return AttachDecision::Attach;
14474 AttachDecision BinaryArithIRGenerator::tryAttachStringNumberArith() {
14475 // Check for either number x string or string x number.
14476 if (!(lhs_.isNumber() && rhs_.isString()) &&
14477 !(lhs_.isString() && rhs_.isNumber())) {
14478 return AttachDecision::NoAction;
14481 // Must _not_ support Add, because it would be string concatenation instead.
14482 if (op_ != JSOp::Sub && op_ != JSOp::Mul && op_ != JSOp::Div &&
14483 op_ != JSOp::Mod && op_ != JSOp::Pow) {
14484 return AttachDecision::NoAction;
14487 ValOperandId lhsId(writer.setInputOperandId(0));
14488 ValOperandId rhsId(writer.setInputOperandId(1));
14490 auto guardToNumber = [&](ValOperandId id, const Value& v) {
14491 if (v.isNumber()) {
14492 return writer.guardIsNumber(id);
14495 MOZ_ASSERT(v.isString());
14496 StringOperandId strId = writer.guardToString(id);
14497 return writer.guardStringToNumber(strId);
14500 NumberOperandId lhsIntId = guardToNumber(lhsId, lhs_);
14501 NumberOperandId rhsIntId = guardToNumber(rhsId, rhs_);
14503 switch (op_) {
14504 case JSOp::Sub:
14505 writer.doubleSubResult(lhsIntId, rhsIntId);
14506 trackAttached("BinaryArith.StringNumberSub");
14507 break;
14508 case JSOp::Mul:
14509 writer.doubleMulResult(lhsIntId, rhsIntId);
14510 trackAttached("BinaryArith.StringNumberMul");
14511 break;
14512 case JSOp::Div:
14513 writer.doubleDivResult(lhsIntId, rhsIntId);
14514 trackAttached("BinaryArith.StringNumberDiv");
14515 break;
14516 case JSOp::Mod:
14517 writer.doubleModResult(lhsIntId, rhsIntId);
14518 trackAttached("BinaryArith.StringNumberMod");
14519 break;
14520 case JSOp::Pow:
14521 writer.doublePowResult(lhsIntId, rhsIntId);
14522 trackAttached("BinaryArith.StringNumberPow");
14523 break;
14524 default:
14525 MOZ_CRASH("Unhandled op in tryAttachStringNumberArith");
14528 writer.returnFromIC();
14529 return AttachDecision::Attach;
14532 NewArrayIRGenerator::NewArrayIRGenerator(JSContext* cx, HandleScript script,
14533 jsbytecode* pc, ICState state, JSOp op,
14534 HandleObject templateObj,
14535 BaselineFrame* frame)
14536 : IRGenerator(cx, script, pc, CacheKind::NewArray, state, frame),
14537 #ifdef JS_CACHEIR_SPEW
14538 op_(op),
14539 #endif
14540 templateObject_(templateObj) {
14541 MOZ_ASSERT(templateObject_);
14544 void NewArrayIRGenerator::trackAttached(const char* name) {
14545 stubName_ = name ? name : "NotAttached";
14546 #ifdef JS_CACHEIR_SPEW
14547 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
14548 sp.opcodeProperty("op", op_);
14550 #endif
14553 AttachDecision NewArrayIRGenerator::tryAttachArrayObject() {
14554 ArrayObject* arrayObj = &templateObject_->as<ArrayObject>();
14556 MOZ_ASSERT(arrayObj->numUsedFixedSlots() == 0);
14557 MOZ_ASSERT(arrayObj->numDynamicSlots() == 0);
14558 MOZ_ASSERT(!arrayObj->isSharedMemory());
14560 // The macro assembler only supports creating arrays with fixed elements.
14561 if (arrayObj->hasDynamicElements()) {
14562 return AttachDecision::NoAction;
14565 // Stub doesn't support metadata builder
14566 if (cx_->realm()->hasAllocationMetadataBuilder()) {
14567 return AttachDecision::NoAction;
14570 writer.guardNoAllocationMetadataBuilder(
14571 cx_->realm()->addressOfMetadataBuilder());
14573 gc::AllocSite* site = maybeCreateAllocSite();
14574 if (!site) {
14575 return AttachDecision::NoAction;
14578 Shape* shape = arrayObj->shape();
14579 uint32_t length = arrayObj->length();
14581 writer.newArrayObjectResult(length, shape, site);
14583 writer.returnFromIC();
14585 trackAttached("NewArray.Object");
14586 return AttachDecision::Attach;
14589 AttachDecision NewArrayIRGenerator::tryAttachStub() {
14590 AutoAssertNoPendingException aanpe(cx_);
14592 TRY_ATTACH(tryAttachArrayObject());
14594 trackAttached(IRGenerator::NotAttached);
14595 return AttachDecision::NoAction;
14598 NewObjectIRGenerator::NewObjectIRGenerator(JSContext* cx, HandleScript script,
14599 jsbytecode* pc, ICState state,
14600 JSOp op, HandleObject templateObj,
14601 BaselineFrame* frame)
14602 : IRGenerator(cx, script, pc, CacheKind::NewObject, state, frame),
14603 #ifdef JS_CACHEIR_SPEW
14604 op_(op),
14605 #endif
14606 templateObject_(templateObj) {
14607 MOZ_ASSERT(templateObject_);
14610 void NewObjectIRGenerator::trackAttached(const char* name) {
14611 stubName_ = name ? name : "NotAttached";
14612 #ifdef JS_CACHEIR_SPEW
14613 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
14614 sp.opcodeProperty("op", op_);
14616 #endif
14619 AttachDecision NewObjectIRGenerator::tryAttachPlainObject() {
14620 // Don't optimize allocations with too many dynamic slots. We use an unrolled
14621 // loop when initializing slots and this avoids generating too much code.
14622 static const uint32_t MaxDynamicSlotsToOptimize = 64;
14624 NativeObject* nativeObj = &templateObject_->as<NativeObject>();
14625 MOZ_ASSERT(nativeObj->is<PlainObject>());
14627 // Stub doesn't support metadata builder
14628 if (cx_->realm()->hasAllocationMetadataBuilder()) {
14629 return AttachDecision::NoAction;
14632 if (nativeObj->numDynamicSlots() > MaxDynamicSlotsToOptimize) {
14633 return AttachDecision::NoAction;
14636 MOZ_ASSERT(!nativeObj->hasDynamicElements());
14637 MOZ_ASSERT(!nativeObj->isSharedMemory());
14639 gc::AllocSite* site = maybeCreateAllocSite();
14640 if (!site) {
14641 return AttachDecision::NoAction;
14644 uint32_t numFixedSlots = nativeObj->numUsedFixedSlots();
14645 uint32_t numDynamicSlots = nativeObj->numDynamicSlots();
14646 gc::AllocKind allocKind = nativeObj->allocKindForTenure();
14647 Shape* shape = nativeObj->shape();
14649 writer.guardNoAllocationMetadataBuilder(
14650 cx_->realm()->addressOfMetadataBuilder());
14651 writer.newPlainObjectResult(numFixedSlots, numDynamicSlots, allocKind, shape,
14652 site);
14654 writer.returnFromIC();
14656 trackAttached("NewObject.PlainObject");
14657 return AttachDecision::Attach;
14660 AttachDecision NewObjectIRGenerator::tryAttachStub() {
14661 AutoAssertNoPendingException aanpe(cx_);
14663 TRY_ATTACH(tryAttachPlainObject());
14665 trackAttached(IRGenerator::NotAttached);
14666 return AttachDecision::NoAction;
14669 CloseIterIRGenerator::CloseIterIRGenerator(JSContext* cx, HandleScript script,
14670 jsbytecode* pc, ICState state,
14671 HandleObject iter,
14672 CompletionKind kind)
14673 : IRGenerator(cx, script, pc, CacheKind::CloseIter, state),
14674 iter_(iter),
14675 kind_(kind) {}
14677 void CloseIterIRGenerator::trackAttached(const char* name) {
14678 #ifdef JS_CACHEIR_SPEW
14679 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
14680 sp.valueProperty("iter", ObjectValue(*iter_));
14682 #endif
14685 AttachDecision CloseIterIRGenerator::tryAttachNoReturnMethod() {
14686 Maybe<PropertyInfo> prop;
14687 NativeObject* holder = nullptr;
14689 // If we can guard that the iterator does not have a |return| method,
14690 // then this CloseIter is a no-op.
14691 NativeGetPropKind kind = CanAttachNativeGetProp(
14692 cx_, iter_, NameToId(cx_->names().return_), &holder, &prop, pc_);
14693 if (kind != NativeGetPropKind::Missing) {
14694 return AttachDecision::NoAction;
14696 MOZ_ASSERT(!holder);
14698 ObjOperandId objId(writer.setInputOperandId(0));
14700 EmitMissingPropGuard(writer, &iter_->as<NativeObject>(), objId);
14702 // There is no return method, so we don't have to do anything.
14703 writer.returnFromIC();
14705 trackAttached("CloseIter.NoReturn");
14706 return AttachDecision::Attach;
14709 AttachDecision CloseIterIRGenerator::tryAttachScriptedReturn() {
14710 Maybe<PropertyInfo> prop;
14711 NativeObject* holder = nullptr;
14713 NativeGetPropKind kind = CanAttachNativeGetProp(
14714 cx_, iter_, NameToId(cx_->names().return_), &holder, &prop, pc_);
14715 if (kind != NativeGetPropKind::Slot) {
14716 return AttachDecision::NoAction;
14718 MOZ_ASSERT(holder);
14719 MOZ_ASSERT(prop->isDataProperty());
14721 size_t slot = prop->slot();
14722 Value calleeVal = holder->getSlot(slot);
14723 if (!calleeVal.isObject() || !calleeVal.toObject().is<JSFunction>()) {
14724 return AttachDecision::NoAction;
14727 JSFunction* callee = &calleeVal.toObject().as<JSFunction>();
14728 if (!callee->hasJitEntry()) {
14729 return AttachDecision::NoAction;
14731 if (callee->isClassConstructor()) {
14732 return AttachDecision::NoAction;
14735 // We don't support cross-realm |return|.
14736 if (cx_->realm() != callee->realm()) {
14737 return AttachDecision::NoAction;
14740 ObjOperandId objId(writer.setInputOperandId(0));
14742 ObjOperandId holderId =
14743 EmitReadSlotGuard(writer, &iter_->as<NativeObject>(), holder, objId);
14745 ValOperandId calleeValId = EmitLoadSlot(writer, holder, holderId, slot);
14746 ObjOperandId calleeId = writer.guardToObject(calleeValId);
14747 emitCalleeGuard(calleeId, callee);
14749 writer.closeIterScriptedResult(objId, calleeId, kind_, callee->nargs());
14751 writer.returnFromIC();
14752 trackAttached("CloseIter.ScriptedReturn");
14754 return AttachDecision::Attach;
14757 AttachDecision CloseIterIRGenerator::tryAttachStub() {
14758 AutoAssertNoPendingException aanpe(cx_);
14760 TRY_ATTACH(tryAttachNoReturnMethod());
14761 TRY_ATTACH(tryAttachScriptedReturn());
14763 trackAttached(IRGenerator::NotAttached);
14764 return AttachDecision::NoAction;
14767 OptimizeGetIteratorIRGenerator::OptimizeGetIteratorIRGenerator(
14768 JSContext* cx, HandleScript script, jsbytecode* pc, ICState state,
14769 HandleValue value)
14770 : IRGenerator(cx, script, pc, CacheKind::OptimizeGetIterator, state),
14771 val_(value) {}
14773 AttachDecision OptimizeGetIteratorIRGenerator::tryAttachStub() {
14774 MOZ_ASSERT(cacheKind_ == CacheKind::OptimizeGetIterator);
14776 AutoAssertNoPendingException aanpe(cx_);
14778 TRY_ATTACH(tryAttachArray());
14779 TRY_ATTACH(tryAttachNotOptimizable());
14781 MOZ_CRASH("Failed to attach unoptimizable case.");
14784 AttachDecision OptimizeGetIteratorIRGenerator::tryAttachArray() {
14785 if (!isFirstStub_) {
14786 return AttachDecision::NoAction;
14789 // The value must be a packed array.
14790 if (!val_.isObject()) {
14791 return AttachDecision::NoAction;
14793 Rooted<JSObject*> obj(cx_, &val_.toObject());
14794 if (!IsPackedArray(obj)) {
14795 return AttachDecision::NoAction;
14798 // Prototype must be Array.prototype and Array.prototype[@@iterator] must not
14799 // be modified.
14800 Rooted<NativeObject*> arrProto(cx_);
14801 uint32_t arrProtoIterSlot;
14802 Rooted<JSFunction*> iterFun(cx_);
14803 if (!IsArrayInstanceOptimizable(cx_, obj.as<ArrayObject>(), &arrProto)) {
14804 return AttachDecision::NoAction;
14807 if (!IsArrayPrototypeOptimizable(cx_, obj.as<ArrayObject>(), arrProto,
14808 &arrProtoIterSlot, &iterFun)) {
14809 // Fuse should be popped.
14810 MOZ_ASSERT(
14811 !obj->nonCCWRealm()->realmFuses.optimizeGetIteratorFuse.intact());
14812 return AttachDecision::NoAction;
14815 // %ArrayIteratorPrototype%.next must not be modified and
14816 // %ArrayIteratorPrototype%.return must not be present.
14817 Rooted<NativeObject*> arrayIteratorProto(cx_);
14818 uint32_t slot;
14819 Rooted<JSFunction*> nextFun(cx_);
14820 if (!IsArrayIteratorPrototypeOptimizable(
14821 cx_, AllowIteratorReturn::No, &arrayIteratorProto, &slot, &nextFun)) {
14822 // Fuse should be popped.
14823 MOZ_ASSERT(
14824 !obj->nonCCWRealm()->realmFuses.optimizeGetIteratorFuse.intact());
14825 return AttachDecision::NoAction;
14828 ValOperandId valId(writer.setInputOperandId(0));
14829 ObjOperandId objId = writer.guardToObject(valId);
14831 // Guard the object is a packed array with Array.prototype as proto.
14832 MOZ_ASSERT(obj->is<ArrayObject>());
14833 writer.guardShape(objId, obj->shape());
14834 writer.guardArrayIsPacked(objId);
14835 bool intact = obj->nonCCWRealm()->realmFuses.optimizeGetIteratorFuse.intact();
14837 // If the fuse isn't intact but we've still passed all these dynamic checks
14838 // then we can attach a version of the IC that dynamically checks to ensure
14839 // the required invariants still hold.
14841 // As an example of how this could be the case, consider an assignment
14843 // Array.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]
14845 // This assignment pops the fuse, however we can still use the dynamic check
14846 // version of this IC, as the actual -value- is still correct.
14847 bool useDynamicCheck = !intact || !JS::Prefs::destructuring_fuse();
14848 if (useDynamicCheck) {
14849 // Guard on Array.prototype[@@iterator].
14850 ObjOperandId arrProtoId = writer.loadObject(arrProto);
14851 ObjOperandId iterId = writer.loadObject(iterFun);
14852 writer.guardShape(arrProtoId, arrProto->shape());
14853 writer.guardDynamicSlotIsSpecificObject(arrProtoId, iterId,
14854 arrProtoIterSlot);
14856 // Guard on %ArrayIteratorPrototype%.next.
14857 ObjOperandId iterProtoId = writer.loadObject(arrayIteratorProto);
14858 ObjOperandId nextId = writer.loadObject(nextFun);
14859 writer.guardShape(iterProtoId, arrayIteratorProto->shape());
14860 writer.guardDynamicSlotIsSpecificObject(iterProtoId, nextId, slot);
14862 // Guard on the prototype chain to ensure no "return" method is present.
14863 ShapeGuardProtoChain(writer, arrayIteratorProto, iterProtoId);
14864 } else {
14865 // Guard on Array.prototype[@@iterator] and %ArrayIteratorPrototype%.next.
14866 // This fuse also ensures the prototype chain for Array Iterator is
14867 // maintained and that no return method is added.
14868 writer.guardFuse(RealmFuses::FuseIndex::OptimizeGetIteratorFuse);
14871 writer.loadBooleanResult(true);
14872 writer.returnFromIC();
14874 if (useDynamicCheck) {
14875 trackAttached("OptimizeGetIterator.Array.Dynamic");
14876 } else {
14877 trackAttached("OptimizeGetIterator.Array.Fuse");
14879 return AttachDecision::Attach;
14882 AttachDecision OptimizeGetIteratorIRGenerator::tryAttachNotOptimizable() {
14883 ValOperandId valId(writer.setInputOperandId(0));
14885 writer.loadBooleanResult(false);
14886 writer.returnFromIC();
14888 trackAttached("OptimizeGetIterator.NotOptimizable");
14889 return AttachDecision::Attach;
14892 void OptimizeGetIteratorIRGenerator::trackAttached(const char* name) {
14893 stubName_ = name ? name : "NotAttached";
14895 #ifdef JS_CACHEIR_SPEW
14896 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
14897 sp.valueProperty("val", val_);
14899 #endif
14902 #ifdef JS_SIMULATOR
14903 bool js::jit::CallAnyNative(JSContext* cx, unsigned argc, Value* vp) {
14904 CallArgs args = CallArgsFromVp(argc, vp);
14905 JSObject* calleeObj = &args.callee();
14907 MOZ_ASSERT(calleeObj->is<JSFunction>());
14908 auto* calleeFunc = &calleeObj->as<JSFunction>();
14909 MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry());
14911 JSNative native = calleeFunc->native();
14912 return native(cx, args.length(), args.base());
14915 const void* js::jit::RedirectedCallAnyNative() {
14916 // The simulator requires native calls to be redirected to a
14917 // special swi instruction. If we are calling an arbitrary native
14918 // function, we can't wrap the real target ahead of time, so we
14919 // call a wrapper function (CallAnyNative) that calls the target
14920 // itself, and redirect that wrapper.
14921 JSNative target = CallAnyNative;
14922 void* rawPtr = JS_FUNC_TO_DATA_PTR(void*, target);
14923 void* redirected = Simulator::RedirectNativeFunction(rawPtr, Args_General3);
14924 return redirected;
14926 #endif