CacheIR.cpp (566641B)
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/. */ 6 7 #include "jit/CacheIR.h" 8 9 #include "mozilla/CheckedInt.h" 10 #include "mozilla/DebugOnly.h" 11 #include "mozilla/FloatingPoint.h" 12 13 #include "jsapi.h" 14 #include "jsdate.h" 15 #include "jsmath.h" 16 #include "jsnum.h" 17 18 #include "builtin/DataViewObject.h" 19 #include "builtin/MapObject.h" 20 #include "builtin/ModuleObject.h" 21 #include "builtin/Object.h" 22 #include "builtin/WeakMapObject.h" 23 #include "builtin/WeakSetObject.h" 24 #include "gc/GC.h" 25 #include "jit/BaselineIC.h" 26 #include "jit/CacheIRCloner.h" 27 #include "jit/CacheIRCompiler.h" 28 #include "jit/CacheIRGenerator.h" 29 #include "jit/CacheIRSpewer.h" 30 #include "jit/CacheIRWriter.h" 31 #include "jit/InlinableNatives.h" 32 #include "jit/JitContext.h" 33 #include "jit/JitZone.h" 34 #include "js/experimental/JitInfo.h" // JSJitInfo 35 #include "js/friend/DOMProxy.h" // JS::ExpandoAndGeneration 36 #include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy, js::ToWindowIfWindowProxy 37 #include "js/friend/XrayJitInfo.h" // js::jit::GetXrayJitInfo, JS::XrayJitInfo 38 #include "js/GCAPI.h" // JS::AutoSuppressGCAnalysis 39 #include "js/Prefs.h" // JS::Prefs 40 #include "js/RegExpFlags.h" // JS::RegExpFlags 41 #include "js/ScalarType.h" // js::Scalar::Type 42 #include "js/Utility.h" // JS::AutoEnterOOMUnsafeRegion 43 #include "js/Wrapper.h" 44 #include "proxy/DOMProxy.h" // js::GetDOMProxyHandlerFamily 45 #include "proxy/ScriptedProxyHandler.h" 46 #include "util/DifferentialTesting.h" 47 #include "util/Unicode.h" 48 #include "vm/ArrayBufferObject.h" 49 #include "vm/BoundFunctionObject.h" 50 #include "vm/BytecodeUtil.h" 51 #include "vm/Compartment.h" 52 #include "vm/DateObject.h" 53 #include "vm/Iteration.h" 54 #include "vm/PlainObject.h" // js::PlainObject 55 #include "vm/ProxyObject.h" 56 #include "vm/RegExpObject.h" 57 #include "vm/SelfHosting.h" 58 #include "vm/ThrowMsgKind.h" // ThrowCondition 59 #include "vm/TypeofEqOperand.h" // TypeofEqOperand 60 #include "vm/Watchtower.h" 61 #include "wasm/WasmInstance.h" 62 63 #include "jit/BaselineFrame-inl.h" 64 #include "jit/MacroAssembler-inl.h" 65 #include "vm/ArrayBufferObject-inl.h" 66 #include "vm/BytecodeUtil-inl.h" 67 #include "vm/EnvironmentObject-inl.h" 68 #include "vm/JSContext-inl.h" 69 #include "vm/JSFunction-inl.h" 70 #include "vm/JSObject-inl.h" 71 #include "vm/JSScript-inl.h" 72 #include "vm/List-inl.h" 73 #include "vm/NativeObject-inl.h" 74 #include "vm/PlainObject-inl.h" 75 #include "vm/StringObject-inl.h" 76 #include "wasm/WasmInstance-inl.h" 77 78 using namespace js; 79 using namespace js::jit; 80 81 using mozilla::DebugOnly; 82 using mozilla::Maybe; 83 84 using JS::DOMProxyShadowsResult; 85 using JS::ExpandoAndGeneration; 86 87 const char* const js::jit::CacheKindNames[] = { 88 #define DEFINE_KIND(kind) #kind, 89 CACHE_IR_KINDS(DEFINE_KIND) 90 #undef DEFINE_KIND 91 }; 92 93 const char* const js::jit::CacheIROpNames[] = { 94 #define OPNAME(op, ...) #op, 95 CACHE_IR_OPS(OPNAME) 96 #undef OPNAME 97 }; 98 99 const CacheIROpInfo js::jit::CacheIROpInfos[] = { 100 #define OPINFO(op, len, transpile, ...) {len, transpile}, 101 CACHE_IR_OPS(OPINFO) 102 #undef OPINFO 103 }; 104 105 const uint32_t js::jit::CacheIROpHealth[] = { 106 #define OPHEALTH(op, len, transpile, health) health, 107 CACHE_IR_OPS(OPHEALTH) 108 #undef OPHEALTH 109 }; 110 111 size_t js::jit::NumInputsForCacheKind(CacheKind kind) { 112 switch (kind) { 113 case CacheKind::NewArray: 114 case CacheKind::NewObject: 115 case CacheKind::Lambda: 116 case CacheKind::LazyConstant: 117 case CacheKind::GetImport: 118 return 0; 119 case CacheKind::GetProp: 120 case CacheKind::TypeOf: 121 case CacheKind::TypeOfEq: 122 case CacheKind::ToPropertyKey: 123 case CacheKind::GetIterator: 124 case CacheKind::ToBool: 125 case CacheKind::UnaryArith: 126 case CacheKind::GetName: 127 case CacheKind::BindName: 128 case CacheKind::Call: 129 case CacheKind::OptimizeSpreadCall: 130 case CacheKind::CloseIter: 131 case CacheKind::OptimizeGetIterator: 132 return 1; 133 case CacheKind::Compare: 134 case CacheKind::GetElem: 135 case CacheKind::GetPropSuper: 136 case CacheKind::SetProp: 137 case CacheKind::In: 138 case CacheKind::HasOwn: 139 case CacheKind::CheckPrivateField: 140 case CacheKind::InstanceOf: 141 case CacheKind::BinaryArith: 142 return 2; 143 case CacheKind::GetElemSuper: 144 case CacheKind::SetElem: 145 return 3; 146 } 147 MOZ_CRASH("Invalid kind"); 148 } 149 150 #ifdef DEBUG 151 void CacheIRWriter::assertSameCompartment(JSObject* obj) { 152 MOZ_ASSERT(cx_->compartment() == obj->compartment()); 153 } 154 void CacheIRWriter::assertSameZone(Shape* shape) { 155 MOZ_ASSERT(cx_->zone() == shape->zone()); 156 } 157 #endif 158 159 StubField CacheIRWriter::readStubField(uint32_t offset, 160 StubField::Type type) const { 161 size_t index = 0; 162 size_t currentOffset = 0; 163 164 // If we've seen an offset earlier than this before, we know we can start the 165 // search there at least, otherwise, we start the search from the beginning. 166 if (lastOffset_ < offset) { 167 currentOffset = lastOffset_; 168 index = lastIndex_; 169 } 170 171 while (currentOffset != offset) { 172 currentOffset += StubField::sizeInBytes(stubFields_[index].type()); 173 index++; 174 MOZ_ASSERT(index < stubFields_.length()); 175 } 176 177 MOZ_ASSERT(stubFields_[index].type() == type); 178 179 lastOffset_ = currentOffset; 180 lastIndex_ = index; 181 182 return stubFields_[index]; 183 } 184 185 CacheIRCloner::CacheIRCloner(ICCacheIRStub* stub) 186 : stubInfo_(stub->stubInfo()), stubData_(stub->stubDataStart()) {} 187 188 void CacheIRCloner::cloneOp(CacheOp op, CacheIRReader& reader, 189 CacheIRWriter& writer) { 190 switch (op) { 191 #define DEFINE_OP(op, ...) \ 192 case CacheOp::op: \ 193 clone##op(reader, writer); \ 194 break; 195 CACHE_IR_OPS(DEFINE_OP) 196 #undef DEFINE_OP 197 default: 198 MOZ_CRASH("Invalid op"); 199 } 200 } 201 202 uintptr_t CacheIRCloner::readStubWord(uint32_t offset) { 203 return stubInfo_->getStubRawWord(stubData_, offset); 204 } 205 int64_t CacheIRCloner::readStubInt64(uint32_t offset) { 206 return stubInfo_->getStubRawInt64(stubData_, offset); 207 } 208 209 Shape* CacheIRCloner::getShapeField(uint32_t stubOffset) { 210 return reinterpret_cast<Shape*>(readStubWord(stubOffset)); 211 } 212 Shape* CacheIRCloner::getWeakShapeField(uint32_t stubOffset) { 213 // No barrier is required to clone a weak pointer. 214 return reinterpret_cast<Shape*>(readStubWord(stubOffset)); 215 } 216 JSObject* CacheIRCloner::getObjectField(uint32_t stubOffset) { 217 return reinterpret_cast<JSObject*>(readStubWord(stubOffset)); 218 } 219 JSObject* CacheIRCloner::getWeakObjectField(uint32_t stubOffset) { 220 // No barrier is required to clone a weak pointer. 221 return reinterpret_cast<JSObject*>(readStubWord(stubOffset)); 222 } 223 JSString* CacheIRCloner::getStringField(uint32_t stubOffset) { 224 return reinterpret_cast<JSString*>(readStubWord(stubOffset)); 225 } 226 JSAtom* CacheIRCloner::getAtomField(uint32_t stubOffset) { 227 return reinterpret_cast<JSAtom*>(readStubWord(stubOffset)); 228 } 229 JS::Symbol* CacheIRCloner::getSymbolField(uint32_t stubOffset) { 230 return reinterpret_cast<JS::Symbol*>(readStubWord(stubOffset)); 231 } 232 BaseScript* CacheIRCloner::getWeakBaseScriptField(uint32_t stubOffset) { 233 // No barrier is required to clone a weak pointer. 234 return reinterpret_cast<BaseScript*>(readStubWord(stubOffset)); 235 } 236 JitCode* CacheIRCloner::getJitCodeField(uint32_t stubOffset) { 237 return reinterpret_cast<JitCode*>(readStubWord(stubOffset)); 238 } 239 uint32_t CacheIRCloner::getRawInt32Field(uint32_t stubOffset) { 240 return uint32_t(reinterpret_cast<uintptr_t>(readStubWord(stubOffset))); 241 } 242 const void* CacheIRCloner::getRawPointerField(uint32_t stubOffset) { 243 return reinterpret_cast<const void*>(readStubWord(stubOffset)); 244 } 245 uint64_t CacheIRCloner::getRawInt64Field(uint32_t stubOffset) { 246 return static_cast<uint64_t>(readStubInt64(stubOffset)); 247 } 248 gc::AllocSite* CacheIRCloner::getAllocSiteField(uint32_t stubOffset) { 249 return reinterpret_cast<gc::AllocSite*>(readStubWord(stubOffset)); 250 } 251 252 jsid CacheIRCloner::getIdField(uint32_t stubOffset) { 253 return jsid::fromRawBits(readStubWord(stubOffset)); 254 } 255 Value CacheIRCloner::getValueField(uint32_t stubOffset) { 256 return Value::fromRawBits(uint64_t(readStubInt64(stubOffset))); 257 } 258 Value CacheIRCloner::getWeakValueField(uint32_t stubOffset) { 259 // No barrier is required to clone a weak pointer. 260 return Value::fromRawBits(uint64_t(readStubInt64(stubOffset))); 261 } 262 double CacheIRCloner::getDoubleField(uint32_t stubOffset) { 263 uint64_t bits = uint64_t(readStubInt64(stubOffset)); 264 return mozilla::BitwiseCast<double>(bits); 265 } 266 267 IRGenerator::IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, 268 CacheKind cacheKind, ICState state, 269 BaselineFrame* maybeFrame) 270 : writer(cx), 271 cx_(cx), 272 script_(script), 273 pc_(pc), 274 maybeFrame_(maybeFrame), 275 cacheKind_(cacheKind), 276 mode_(state.mode()), 277 isFirstStub_(state.newStubIsFirstStub()), 278 numOptimizedStubs_(state.numOptimizedStubs()) {} 279 280 // Allocation sites are usually created during baseline compilation, but we also 281 // need to create them when an IC stub is added to a baseline compiled script 282 // and when trial inlining. 283 gc::AllocSite* IRGenerator::maybeCreateAllocSite() { 284 MOZ_ASSERT(BytecodeOpCanHaveAllocSite(JSOp(*pc_))); 285 286 BaselineFrame* frame = maybeFrame_; 287 MOZ_ASSERT(frame); 288 289 JSScript* outerScript = frame->outerScript(); 290 bool hasBaselineScript = outerScript->hasBaselineScript(); 291 bool isInlined = frame->icScript()->isInlined(); 292 if (!hasBaselineScript && !isInlined) { 293 MOZ_ASSERT(frame->runningInInterpreter()); 294 return outerScript->zone()->unknownAllocSite(JS::TraceKind::Object); 295 } 296 297 uint32_t pcOffset = frame->script()->pcToOffset(pc_); 298 return frame->icScript()->getOrCreateAllocSite(outerScript, pcOffset); 299 } 300 301 GetPropIRGenerator::GetPropIRGenerator(JSContext* cx, HandleScript script, 302 jsbytecode* pc, ICState state, 303 CacheKind cacheKind, HandleValue val, 304 HandleValue idVal, 305 HandleValue receiverVal) 306 : IRGenerator(cx, script, pc, cacheKind, state), 307 val_(val), 308 idVal_(idVal), 309 receiverVal_(receiverVal) {} 310 311 static void EmitLoadSlotResult(CacheIRWriter& writer, ObjOperandId holderId, 312 NativeObject* holder, PropertyInfo prop) { 313 if (holder->isFixedSlot(prop.slot())) { 314 writer.loadFixedSlotResult(holderId, 315 NativeObject::getFixedSlotOffset(prop.slot())); 316 } else { 317 size_t dynamicSlotOffset = 318 holder->dynamicSlotIndex(prop.slot()) * sizeof(Value); 319 writer.loadDynamicSlotResult(holderId, dynamicSlotOffset); 320 } 321 } 322 323 // DOM proxies 324 // ----------- 325 // 326 // DOM proxies are proxies that are used to implement various DOM objects like 327 // HTMLDocument and NodeList. DOM proxies may have an expando object - a native 328 // object that stores extra properties added to the object. The following 329 // CacheIR instructions are only used with DOM proxies: 330 // 331 // * LoadDOMExpandoValue: returns the Value in the proxy's expando slot. This 332 // returns either an UndefinedValue (no expando), ObjectValue (the expando 333 // object), or PrivateValue(ExpandoAndGeneration*). 334 // 335 // * LoadDOMExpandoValueGuardGeneration: guards the Value in the proxy's expando 336 // slot is the same PrivateValue(ExpandoAndGeneration*), then guards on its 337 // generation, then returns expandoAndGeneration->expando. This Value is 338 // either an UndefinedValue or ObjectValue. 339 // 340 // * LoadDOMExpandoValueIgnoreGeneration: assumes the Value in the proxy's 341 // expando slot is a PrivateValue(ExpandoAndGeneration*), unboxes it, and 342 // returns the expandoAndGeneration->expando Value. 343 // 344 // * GuardDOMExpandoMissingOrGuardShape: takes an expando Value as input, then 345 // guards it's either UndefinedValue or an object with the expected shape. 346 347 enum class ProxyStubType { 348 None, 349 DOMExpando, 350 DOMShadowed, 351 DOMUnshadowed, 352 Generic 353 }; 354 355 static bool IsCacheableDOMProxy(ProxyObject* obj) { 356 const BaseProxyHandler* handler = obj->handler(); 357 if (handler->family() != GetDOMProxyHandlerFamily()) { 358 return false; 359 } 360 361 // Some DOM proxies have dynamic prototypes. We can't really cache those very 362 // well. 363 return obj->hasStaticPrototype(); 364 } 365 366 static ProxyStubType GetProxyStubType(JSContext* cx, HandleObject obj, 367 HandleId id) { 368 if (!obj->is<ProxyObject>()) { 369 return ProxyStubType::None; 370 } 371 auto proxy = obj.as<ProxyObject>(); 372 373 if (!IsCacheableDOMProxy(proxy)) { 374 return ProxyStubType::Generic; 375 } 376 377 // Private fields are defined on a separate expando object. 378 if (id.isPrivateName()) { 379 return ProxyStubType::Generic; 380 } 381 382 DOMProxyShadowsResult shadows = GetDOMProxyShadowsCheck()(cx, proxy, id); 383 if (shadows == DOMProxyShadowsResult::ShadowCheckFailed) { 384 cx->clearPendingException(); 385 return ProxyStubType::None; 386 } 387 388 if (DOMProxyIsShadowing(shadows)) { 389 if (shadows == DOMProxyShadowsResult::ShadowsViaDirectExpando || 390 shadows == DOMProxyShadowsResult::ShadowsViaIndirectExpando) { 391 return ProxyStubType::DOMExpando; 392 } 393 return ProxyStubType::DOMShadowed; 394 } 395 396 MOZ_ASSERT(shadows == DOMProxyShadowsResult::DoesntShadow || 397 shadows == DOMProxyShadowsResult::DoesntShadowUnique); 398 return ProxyStubType::DOMUnshadowed; 399 } 400 401 static bool ValueToNameOrSymbolId(JSContext* cx, HandleValue idVal, 402 MutableHandleId id, bool* nameOrSymbol) { 403 *nameOrSymbol = false; 404 405 if (idVal.isObject() || idVal.isBigInt()) { 406 return true; 407 } 408 409 MOZ_ASSERT(idVal.isString() || idVal.isSymbol() || idVal.isBoolean() || 410 idVal.isUndefined() || idVal.isNull() || idVal.isNumber()); 411 412 if (IsNumberIndex(idVal)) { 413 return true; 414 } 415 416 if (!PrimitiveValueToId<CanGC>(cx, idVal, id)) { 417 return false; 418 } 419 420 if (!id.isAtom() && !id.isSymbol()) { 421 id.set(JS::PropertyKey::Void()); 422 return true; 423 } 424 425 if (id.isAtom() && id.toAtom()->isIndex()) { 426 id.set(JS::PropertyKey::Void()); 427 return true; 428 } 429 430 *nameOrSymbol = true; 431 return true; 432 } 433 434 AttachDecision GetPropIRGenerator::tryAttachStub() { 435 AutoAssertNoPendingException aanpe(cx_); 436 437 ValOperandId valId(writer.setInputOperandId(0)); 438 if (cacheKind_ != CacheKind::GetProp) { 439 MOZ_ASSERT_IF(cacheKind_ == CacheKind::GetPropSuper, 440 getSuperReceiverValueId().id() == 1); 441 MOZ_ASSERT_IF(cacheKind_ != CacheKind::GetPropSuper, 442 getElemKeyValueId().id() == 1); 443 writer.setInputOperandId(1); 444 } 445 if (cacheKind_ == CacheKind::GetElemSuper) { 446 MOZ_ASSERT(getSuperReceiverValueId().id() == 2); 447 writer.setInputOperandId(2); 448 } 449 450 RootedId id(cx_); 451 bool nameOrSymbol; 452 if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) { 453 cx_->clearPendingException(); 454 return AttachDecision::NoAction; 455 } 456 457 // |super.prop| getter calls use a |this| value that differs from lookup 458 // object. 459 ValOperandId receiverId = isSuper() ? getSuperReceiverValueId() : valId; 460 461 if (val_.isObject()) { 462 RootedObject obj(cx_, &val_.toObject()); 463 ObjOperandId objId = writer.guardToObject(valId); 464 465 TRY_ATTACH(tryAttachTypedArrayElement(obj, objId)); 466 467 if (nameOrSymbol) { 468 TRY_ATTACH(tryAttachObjectLength(obj, objId, id)); 469 TRY_ATTACH(tryAttachNative(obj, objId, id, receiverId)); 470 TRY_ATTACH(tryAttachModuleNamespace(obj, objId, id)); 471 TRY_ATTACH(tryAttachWindowProxy(obj, objId, id)); 472 TRY_ATTACH(tryAttachCrossCompartmentWrapper(obj, objId, id)); 473 TRY_ATTACH( 474 tryAttachXrayCrossCompartmentWrapper(obj, objId, id, receiverId)); 475 TRY_ATTACH(tryAttachFunction(obj, objId, id)); 476 TRY_ATTACH(tryAttachArgumentsObjectIterator(obj, objId, id)); 477 TRY_ATTACH(tryAttachArgumentsObjectCallee(obj, objId, id)); 478 TRY_ATTACH(tryAttachProxy(obj, objId, id, receiverId)); 479 480 if (!isSuper() && mode_ == ICState::Mode::Megamorphic && 481 JSOp(*pc_) != JSOp::GetBoundName) { 482 attachMegamorphicNativeSlotPermissive(objId, id); 483 return AttachDecision::Attach; 484 } 485 486 trackAttached(IRGenerator::NotAttached); 487 return AttachDecision::NoAction; 488 } 489 490 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem || 491 cacheKind_ == CacheKind::GetElemSuper); 492 493 TRY_ATTACH(tryAttachProxyElement(obj, objId)); 494 495 uint32_t index; 496 Int32OperandId indexId; 497 if (maybeGuardInt32Index(idVal_, getElemKeyValueId(), &index, &indexId)) { 498 TRY_ATTACH(tryAttachDenseElement(obj, objId, index, indexId)); 499 TRY_ATTACH(tryAttachDenseElementHole(obj, objId, index, indexId)); 500 TRY_ATTACH(tryAttachSparseElement(obj, objId, index, indexId)); 501 TRY_ATTACH(tryAttachArgumentsObjectArg(obj, objId, index, indexId)); 502 TRY_ATTACH(tryAttachArgumentsObjectArgHole(obj, objId, index, indexId)); 503 TRY_ATTACH( 504 tryAttachGenericElement(obj, objId, index, indexId, receiverId)); 505 506 trackAttached(IRGenerator::NotAttached); 507 return AttachDecision::NoAction; 508 } 509 510 trackAttached(IRGenerator::NotAttached); 511 return AttachDecision::NoAction; 512 } 513 514 if (nameOrSymbol) { 515 TRY_ATTACH(tryAttachPrimitive(valId, id)); 516 TRY_ATTACH(tryAttachStringLength(valId, id)); 517 518 trackAttached(IRGenerator::NotAttached); 519 return AttachDecision::NoAction; 520 } 521 522 if (idVal_.isInt32()) { 523 ValOperandId indexId = getElemKeyValueId(); 524 TRY_ATTACH(tryAttachStringChar(valId, indexId)); 525 526 trackAttached(IRGenerator::NotAttached); 527 return AttachDecision::NoAction; 528 } 529 530 trackAttached(IRGenerator::NotAttached); 531 return AttachDecision::NoAction; 532 } 533 534 #ifdef DEBUG 535 // Any property lookups performed when trying to attach ICs must be pure, i.e. 536 // must use LookupPropertyPure() or similar functions. Pure lookups are 537 // guaranteed to never modify the prototype chain. This ensures that the holder 538 // object can always be found on the prototype chain. 539 static bool IsCacheableProtoChain(NativeObject* obj, NativeObject* holder) { 540 while (obj != holder) { 541 JSObject* proto = obj->staticPrototype(); 542 if (!proto || !proto->is<NativeObject>()) { 543 return false; 544 } 545 obj = &proto->as<NativeObject>(); 546 } 547 return true; 548 } 549 #endif 550 551 static bool IsCacheableGetPropSlot(NativeObject* obj, NativeObject* holder, 552 PropertyInfo prop) { 553 MOZ_ASSERT(IsCacheableProtoChain(obj, holder)); 554 555 return prop.isDataProperty(); 556 } 557 558 static NativeGetPropKind IsCacheableGetPropCall(NativeObject* obj, 559 NativeObject* holder, 560 PropertyInfo prop, 561 jsbytecode* pc = nullptr) { 562 MOZ_ASSERT(IsCacheableProtoChain(obj, holder)); 563 564 if (pc && JSOp(*pc) == JSOp::GetBoundName) { 565 return NativeGetPropKind::None; 566 } 567 568 if (!prop.isAccessorProperty()) { 569 return NativeGetPropKind::None; 570 } 571 572 JSObject* getterObject = holder->getGetter(prop); 573 if (!getterObject || !getterObject->is<JSFunction>()) { 574 return NativeGetPropKind::None; 575 } 576 577 JSFunction& getter = getterObject->as<JSFunction>(); 578 579 if (getter.isClassConstructor()) { 580 return NativeGetPropKind::None; 581 } 582 583 // Scripted functions and natives with JIT entry can use the scripted path. 584 if (getter.hasJitEntry()) { 585 return NativeGetPropKind::ScriptedGetter; 586 } 587 588 MOZ_ASSERT(getter.isNativeWithoutJitEntry()); 589 return NativeGetPropKind::NativeGetter; 590 } 591 592 static bool CheckHasNoSuchOwnProperty(JSContext* cx, JSObject* obj, jsid id) { 593 if (!obj->is<NativeObject>()) { 594 return false; 595 } 596 // Don't handle objects with resolve hooks. 597 if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj)) { 598 return false; 599 } 600 if (obj->as<NativeObject>().contains(cx, id)) { 601 return false; 602 } 603 if (obj->is<TypedArrayObject>() && ToTypedArrayIndex(id).isSome()) { 604 return false; 605 } 606 return true; 607 } 608 609 static bool CheckHasNoSuchProperty(JSContext* cx, JSObject* obj, jsid id) { 610 JSObject* curObj = obj; 611 do { 612 if (!CheckHasNoSuchOwnProperty(cx, curObj, id)) { 613 return false; 614 } 615 616 curObj = curObj->staticPrototype(); 617 } while (curObj); 618 619 return true; 620 } 621 622 static bool IsCacheableNoProperty(JSContext* cx, NativeObject* obj, 623 NativeObject* holder, jsid id, 624 jsbytecode* pc) { 625 MOZ_ASSERT(!holder); 626 627 // If we're doing a name lookup, we have to throw a ReferenceError. 628 if (JSOp(*pc) == JSOp::GetBoundName) { 629 return false; 630 } 631 632 return CheckHasNoSuchProperty(cx, obj, id); 633 } 634 635 static NativeGetPropKind CanAttachNativeGetProp(JSContext* cx, JSObject* obj, 636 PropertyKey id, 637 NativeObject** holder, 638 Maybe<PropertyInfo>* propInfo, 639 jsbytecode* pc) { 640 MOZ_ASSERT(id.isString() || id.isSymbol()); 641 MOZ_ASSERT(!*holder); 642 643 // The lookup needs to be universally pure, otherwise we risk calling hooks 644 // out of turn. We don't mind doing this even when purity isn't required, 645 // because we only miss out on shape hashification, which is only a temporary 646 // perf cost. The limits were arbitrarily set, anyways. 647 NativeObject* baseHolder = nullptr; 648 PropertyResult prop; 649 if (!LookupPropertyPure(cx, obj, id, &baseHolder, &prop)) { 650 return NativeGetPropKind::None; 651 } 652 auto* nobj = &obj->as<NativeObject>(); 653 654 if (prop.isNativeProperty()) { 655 MOZ_ASSERT(baseHolder); 656 *holder = baseHolder; 657 *propInfo = mozilla::Some(prop.propertyInfo()); 658 659 if (IsCacheableGetPropSlot(nobj, *holder, propInfo->ref())) { 660 return NativeGetPropKind::Slot; 661 } 662 663 return IsCacheableGetPropCall(nobj, *holder, propInfo->ref(), pc); 664 } 665 666 if (!prop.isFound()) { 667 if (IsCacheableNoProperty(cx, nobj, *holder, id, pc)) { 668 return NativeGetPropKind::Missing; 669 } 670 } 671 672 return NativeGetPropKind::None; 673 } 674 675 static void GuardReceiverProto(CacheIRWriter& writer, NativeObject* obj, 676 ObjOperandId objId) { 677 // Note: we guard on the actual prototype and not on the shape because this is 678 // used for sparse elements where we expect shape changes. 679 680 if (JSObject* proto = obj->staticPrototype()) { 681 writer.guardProto(objId, proto); 682 } else { 683 writer.guardNullProto(objId); 684 } 685 } 686 687 // Guard that a given object has same class and same OwnProperties (excluding 688 // dense elements and dynamic properties). 689 static void TestMatchingNativeReceiver(CacheIRWriter& writer, NativeObject* obj, 690 ObjOperandId objId) { 691 writer.guardShapeForOwnProperties(objId, obj->shape()); 692 } 693 694 // Similar to |TestMatchingNativeReceiver|, but specialized for ProxyObject. 695 static void TestMatchingProxyReceiver(CacheIRWriter& writer, ProxyObject* obj, 696 ObjOperandId objId) { 697 writer.guardShapeForClass(objId, obj->shape()); 698 } 699 700 static void GeneratePrototypeGuards(CacheIRWriter& writer, JSObject* obj, 701 NativeObject* holder, ObjOperandId objId) { 702 // Assuming target property is on |holder|, generate appropriate guards to 703 // ensure |holder| is still on the prototype chain of |obj| and we haven't 704 // introduced any shadowing definitions. 705 // 706 // For each item in the proto chain before holder, we must ensure that 707 // [[GetPrototypeOf]] still has the expected result, and that 708 // [[GetOwnProperty]] has no definition of the target property. 709 // 710 // 711 // [SMDOC] Shape Teleporting Optimization 712 // -------------------------------------- 713 // 714 // Starting with the assumption (and guideline to developers) that mutating 715 // prototypes is an uncommon and fair-to-penalize operation we move cost 716 // from the access side to the mutation side. 717 // 718 // Consider the following proto chain, with B defining a property 'x': 719 // 720 // D -> C -> B{x: 3} -> A -> null 721 // 722 // When accessing |D.x| we refer to D as the "receiver", and B as the 723 // "holder". To optimize this access we need to ensure that neither D nor C 724 // has since defined a shadowing property 'x'. Since C is a prototype that 725 // we assume is rarely mutated we would like to avoid checking each time if 726 // new properties are added. To do this we require that whenever C starts 727 // shadowing a property on its proto chain, we invalidate (and opt out of) the 728 // teleporting optimization by setting the InvalidatedTeleporting flag on the 729 // object we're shadowing, triggering a shape change of that object. As a 730 // result, checking the shape of D and B is sufficient. Note that we do not 731 // care if the shape or properties of A change since the lookup of 'x' will 732 // stop at B. 733 // 734 // The second condition we must verify is that the prototype chain was not 735 // mutated. The same mechanism as above is used. When the prototype link is 736 // changed, we generate a new shape for the object. If the object whose 737 // link we are mutating is itself a prototype, we regenerate shapes down 738 // the chain by setting the InvalidatedTeleporting flag on them. This means 739 // the same two shape checks as above are sufficient. 740 // 741 // Once the InvalidatedTeleporting flag is set, it means the shape will no 742 // longer be changed by ReshapeForProtoMutation and ReshapeForShadowedProp. 743 // In this case we can no longer apply the optimization. 744 // 745 // See: 746 // - ReshapeForProtoMutation 747 // - ReshapeForShadowedProp 748 749 MOZ_ASSERT(holder); 750 MOZ_ASSERT(obj != holder); 751 752 // Receiver guards (see TestMatchingReceiver) ensure the receiver's proto is 753 // unchanged so peel off the receiver. 754 JSObject* pobj = obj->staticPrototype(); 755 MOZ_ASSERT(pobj->isUsedAsPrototype()); 756 757 // If teleporting is supported for this holder, we are done. 758 if (!holder->hasInvalidatedTeleporting()) { 759 return; 760 } 761 762 // If already at the holder, no further proto checks are needed. 763 if (pobj == holder) { 764 return; 765 } 766 767 // Synchronize pobj and protoId. 768 MOZ_ASSERT(pobj == obj->staticPrototype()); 769 ObjOperandId protoId = writer.loadProto(objId); 770 771 // Shape guard each prototype object between receiver and holder. This guards 772 // against both proto changes and shadowing properties. 773 while (pobj != holder) { 774 writer.guardShape(protoId, pobj->shape()); 775 776 pobj = pobj->staticPrototype(); 777 protoId = writer.loadProto(protoId); 778 } 779 } 780 781 static void GeneratePrototypeHoleGuards(CacheIRWriter& writer, 782 NativeObject* obj, ObjOperandId objId, 783 bool alwaysGuardFirstProto) { 784 if (alwaysGuardFirstProto) { 785 GuardReceiverProto(writer, obj, objId); 786 } 787 788 JSObject* pobj = obj->staticPrototype(); 789 while (pobj) { 790 ObjOperandId protoId = writer.loadObject(pobj); 791 792 // Make sure the shape matches, to ensure the proto is unchanged and to 793 // avoid non-dense elements or anything else that is being checked by 794 // CanAttachDenseElementHole. 795 MOZ_ASSERT(pobj->is<NativeObject>()); 796 writer.guardShape(protoId, pobj->shape()); 797 798 // Also make sure there are no dense elements. 799 writer.guardNoDenseElements(protoId); 800 801 pobj = pobj->staticPrototype(); 802 } 803 } 804 805 // Similar to |TestMatchingReceiver|, but for the holder object (when it 806 // differs from the receiver). The holder may also be the expando of the 807 // receiver if it exists. 808 static void TestMatchingHolder(CacheIRWriter& writer, NativeObject* obj, 809 ObjOperandId objId) { 810 // The GeneratePrototypeGuards + TestMatchingHolder checks only support 811 // prototype chains composed of NativeObject (excluding the receiver 812 // itself). 813 writer.guardShapeForOwnProperties(objId, obj->shape()); 814 } 815 816 enum class IsCrossCompartment { No, Yes }; 817 818 // Emit a shape guard for all objects on the proto chain. This does NOT include 819 // the receiver; callers must ensure the receiver's proto is the first proto by 820 // either emitting a shape guard or a prototype guard for |objId|. 821 // 822 // Note: this relies on shape implying proto. 823 template <IsCrossCompartment MaybeCrossCompartment = IsCrossCompartment::No> 824 static void ShapeGuardProtoChain(CacheIRWriter& writer, NativeObject* obj, 825 ObjOperandId objId) { 826 uint32_t depth = 0; 827 static const uint32_t MAX_CACHED_LOADS = 4; 828 ObjOperandId receiverObjId = objId; 829 830 while (true) { 831 JSObject* proto = obj->staticPrototype(); 832 if (!proto) { 833 return; 834 } 835 836 obj = &proto->as<NativeObject>(); 837 838 // After guarding the shape of an object, we can safely bake that 839 // object's proto into the stub data. Compared to LoadProto, this 840 // takes one load instead of three (object -> shape -> baseshape 841 // -> proto). We cap the depth to avoid bloating the size of the 842 // stub data. To avoid compartment mismatch, we skip this optimization 843 // in the cross-compartment case. 844 if (depth < MAX_CACHED_LOADS && 845 MaybeCrossCompartment == IsCrossCompartment::No) { 846 objId = writer.loadProtoObject(obj, receiverObjId); 847 } else { 848 objId = writer.loadProto(objId); 849 } 850 depth++; 851 852 writer.guardShape(objId, obj->shape()); 853 } 854 } 855 856 // For cross compartment guards we shape-guard the prototype chain to avoid 857 // referencing the holder object. 858 // 859 // This peels off the first layer because it's guarded against obj == holder. 860 // 861 // Returns the holder's OperandId. 862 static ObjOperandId ShapeGuardProtoChainForCrossCompartmentHolder( 863 CacheIRWriter& writer, NativeObject* obj, ObjOperandId objId, 864 NativeObject* holder) { 865 MOZ_ASSERT(obj != holder); 866 MOZ_ASSERT(holder); 867 while (true) { 868 MOZ_ASSERT(obj->staticPrototype()); 869 obj = &obj->staticPrototype()->as<NativeObject>(); 870 871 objId = writer.loadProto(objId); 872 if (obj == holder) { 873 TestMatchingHolder(writer, obj, objId); 874 return objId; 875 } 876 writer.guardShapeForOwnProperties(objId, obj->shape()); 877 } 878 } 879 880 // Emit guards for reading a data property on |holder|. Returns the holder's 881 // OperandId. 882 template <IsCrossCompartment MaybeCrossCompartment = IsCrossCompartment::No> 883 static ObjOperandId EmitReadSlotGuard(CacheIRWriter& writer, NativeObject* obj, 884 NativeObject* holder, 885 ObjOperandId objId) { 886 MOZ_ASSERT(holder); 887 TestMatchingNativeReceiver(writer, obj, objId); 888 889 if (obj == holder) { 890 return objId; 891 } 892 893 if (MaybeCrossCompartment == IsCrossCompartment::Yes) { 894 // Guard proto chain integrity. 895 // We use a variant of guards that avoid baking in any cross-compartment 896 // object pointers. 897 return ShapeGuardProtoChainForCrossCompartmentHolder(writer, obj, objId, 898 holder); 899 } 900 901 // Guard proto chain integrity. 902 GeneratePrototypeGuards(writer, obj, holder, objId); 903 904 // Guard on the holder's shape. 905 ObjOperandId holderId = writer.loadObject(holder); 906 TestMatchingHolder(writer, holder, holderId); 907 return holderId; 908 } 909 910 template <IsCrossCompartment MaybeCrossCompartment = IsCrossCompartment::No> 911 static void EmitMissingPropGuard(CacheIRWriter& writer, NativeObject* obj, 912 ObjOperandId objId) { 913 TestMatchingNativeReceiver(writer, obj, objId); 914 915 // The property does not exist. Guard on everything in the prototype 916 // chain. This is guaranteed to see only Native objects because of 917 // CanAttachNativeGetProp(). 918 ShapeGuardProtoChain<MaybeCrossCompartment>(writer, obj, objId); 919 } 920 921 template <IsCrossCompartment MaybeCrossCompartment = IsCrossCompartment::No> 922 static void EmitReadSlotResult(CacheIRWriter& writer, NativeObject* obj, 923 NativeObject* holder, PropertyInfo prop, 924 ObjOperandId objId) { 925 MOZ_ASSERT(holder); 926 927 ObjOperandId holderId = 928 EmitReadSlotGuard<MaybeCrossCompartment>(writer, obj, holder, objId); 929 930 MOZ_ASSERT(holderId.valid()); 931 EmitLoadSlotResult(writer, holderId, holder, prop); 932 } 933 934 template <IsCrossCompartment MaybeCrossCompartment = IsCrossCompartment::No> 935 static void EmitMissingPropResult(CacheIRWriter& writer, NativeObject* obj, 936 ObjOperandId objId) { 937 EmitMissingPropGuard<MaybeCrossCompartment>(writer, obj, objId); 938 writer.loadUndefinedResult(); 939 } 940 941 static ValOperandId EmitLoadSlot(CacheIRWriter& writer, NativeObject* holder, 942 ObjOperandId holderId, uint32_t slot) { 943 if (holder->isFixedSlot(slot)) { 944 return writer.loadFixedSlot(holderId, 945 NativeObject::getFixedSlotOffset(slot)); 946 } 947 size_t dynamicSlotIndex = holder->dynamicSlotIndex(slot); 948 return writer.loadDynamicSlot(holderId, dynamicSlotIndex); 949 } 950 951 void IRGenerator::emitCallGetterResultNoGuards(NativeGetPropKind kind, 952 NativeObject* obj, 953 NativeObject* holder, 954 PropertyInfo prop, 955 ValOperandId receiverId) { 956 MOZ_ASSERT(IsCacheableGetPropCall(obj, holder, prop) == kind); 957 958 JSFunction* target = &holder->getGetter(prop)->as<JSFunction>(); 959 bool sameRealm = cx_->realm() == target->realm(); 960 961 switch (kind) { 962 case NativeGetPropKind::NativeGetter: { 963 writer.callNativeGetterResult(receiverId, target, sameRealm); 964 writer.returnFromIC(); 965 break; 966 } 967 case NativeGetPropKind::ScriptedGetter: { 968 writer.callScriptedGetterResult(receiverId, target, sameRealm); 969 writer.returnFromIC(); 970 break; 971 } 972 default: 973 // CanAttachNativeGetProp guarantees that the getter is either a native or 974 // a scripted function. 975 MOZ_ASSERT_UNREACHABLE("Can't attach getter"); 976 break; 977 } 978 } 979 980 static bool FunctionHasStableBaseScript(JSFunction* fun) { 981 // When guarding a callee, guarding on the JSFunction* is most efficient, 982 // but doesn't work well for lambda clones (multiple functions with the 983 // same BaseScript). We can instead guard on the BaseScript itself. 984 if (!fun->hasBaseScript()) { 985 return false; 986 } 987 // Self-hosted functions are more complicated: top-level functions can be 988 // relazified using SelfHostedLazyScript and this means they don't have a 989 // stable BaseScript pointer. These functions are never lambda clones, though, 990 // so we can just always guard on the JSFunction*. Self-hosted lambdas are 991 // never relazified so there we use the normal heuristics. 992 if (fun->isSelfHostedBuiltin() && !fun->isLambda()) { 993 return false; 994 } 995 return true; 996 } 997 998 // See the SMDOC comment in vm/GetterSetter.h for more info on Getter/Setter 999 // properties 1000 void IRGenerator::emitGuardGetterSetterSlot(NativeObject* holder, 1001 PropertyInfo prop, 1002 ObjOperandId holderId, 1003 AccessorKind kind, 1004 bool holderIsConstant) { 1005 // If the holder is guaranteed to be the same object, and it never had a 1006 // slot holding a GetterSetter mutated or deleted, its Shape will change when 1007 // that does happen so we don't need to guard on the GetterSetter. 1008 if (holderIsConstant && !holder->hadGetterSetterChange()) { 1009 return; 1010 } 1011 1012 size_t slot = prop.slot(); 1013 1014 // For the same reasons as emitCalleeGuard, we guard on the BaseScript 1015 // instead of the GetterSetter if the callee is scripted and this isn't 1016 // the first IC stub. 1017 if (!isFirstStub_) { 1018 bool isGetter = kind == AccessorKind::Getter; 1019 JSObject* accessor = 1020 isGetter ? holder->getGetter(prop) : holder->getSetter(prop); 1021 JSFunction* fun = &accessor->as<JSFunction>(); 1022 if (FunctionHasStableBaseScript(fun)) { 1023 bool needsClassGuard = holder->hasNonFunctionAccessor(); 1024 ValOperandId getterSetterId = 1025 EmitLoadSlot(writer, holder, holderId, slot); 1026 ObjOperandId functionId = writer.loadGetterSetterFunction( 1027 getterSetterId, isGetter, needsClassGuard); 1028 writer.saveScriptedGetterSetterCallee(functionId); 1029 writer.guardFunctionScript(functionId, fun->baseScript()); 1030 return; 1031 } 1032 } 1033 1034 Value slotVal = holder->getSlot(slot); 1035 MOZ_ASSERT(slotVal.isPrivateGCThing()); 1036 1037 if (holder->isFixedSlot(slot)) { 1038 size_t offset = NativeObject::getFixedSlotOffset(slot); 1039 writer.guardFixedSlotValue(holderId, offset, slotVal); 1040 } else { 1041 size_t offset = holder->dynamicSlotIndex(slot) * sizeof(Value); 1042 writer.guardDynamicSlotValue(holderId, offset, slotVal); 1043 } 1044 } 1045 1046 static ObjOperandId EmitGuardObjectFuseHolder(CacheIRWriter& writer, 1047 NativeObject* obj, 1048 NativeObject* holder, 1049 ObjOperandId objId) { 1050 if (obj == holder) { 1051 // Guard the object is the current |holder| object because each ObjectFuse 1052 // applies to a single object. 1053 writer.guardSpecificObject(objId, obj); 1054 return objId; 1055 } 1056 1057 // Note: we don't need to call GeneratePrototypeGuards here because the 1058 // ObjectFuse's generation will be updated when the proto chain is 1059 // mutated. 1060 TestMatchingNativeReceiver(writer, obj, objId); 1061 return writer.loadObject(holder); 1062 } 1063 1064 void IRGenerator::emitCallAccessorGuards(NativeObject* obj, 1065 NativeObject* holder, HandleId id, 1066 PropertyInfo prop, ObjOperandId objId, 1067 AccessorKind accessorKind) { 1068 // Use the megamorphic guard if we're in megamorphic mode, except if |obj| 1069 // is a Window as GuardHasGetterSetter doesn't support this yet (Window may 1070 // require outerizing). 1071 1072 MOZ_ASSERT(holder->containsPure(id, prop)); 1073 1074 if (mode_ == ICState::Mode::Specialized || IsWindow(obj)) { 1075 // Fast path for constant properties of objects with an ObjectFuse. 1076 ObjectFuse* objFuse = nullptr; 1077 if (canOptimizeConstantAccessorProperty(holder, prop, &objFuse)) { 1078 ObjOperandId holderId = 1079 EmitGuardObjectFuseHolder(writer, obj, holder, objId); 1080 emitGuardConstantAccessorProperty(holder, holderId, id, prop, objFuse); 1081 return; 1082 } 1083 1084 TestMatchingNativeReceiver(writer, obj, objId); 1085 1086 if (obj != holder) { 1087 GeneratePrototypeGuards(writer, obj, holder, objId); 1088 1089 // Guard on the holder's shape. 1090 ObjOperandId holderId = writer.loadObject(holder); 1091 TestMatchingHolder(writer, holder, holderId); 1092 1093 emitGuardGetterSetterSlot(holder, prop, holderId, accessorKind, 1094 /* holderIsConstant = */ true); 1095 } else { 1096 emitGuardGetterSetterSlot(holder, prop, objId, accessorKind); 1097 } 1098 } else { 1099 Value val = holder->getSlot(prop.slot()); 1100 MOZ_ASSERT(val.isPrivateGCThing()); 1101 MOZ_ASSERT(val.toGCThing()->is<GetterSetter>()); 1102 writer.guardHasGetterSetter(objId, id, val); 1103 } 1104 } 1105 1106 void GetPropIRGenerator::emitCallGetterResultGuards(NativeObject* obj, 1107 NativeObject* holder, 1108 HandleId id, 1109 PropertyInfo prop, 1110 ObjOperandId objId) { 1111 emitCallAccessorGuards(obj, holder, id, prop, objId, AccessorKind::Getter); 1112 } 1113 1114 void GetPropIRGenerator::emitCallGetterResult(NativeGetPropKind kind, 1115 Handle<NativeObject*> obj, 1116 Handle<NativeObject*> holder, 1117 HandleId id, PropertyInfo prop, 1118 ObjOperandId objId, 1119 ValOperandId receiverId) { 1120 emitCallGetterResultGuards(obj, holder, id, prop, objId); 1121 1122 if (kind == NativeGetPropKind::NativeGetter && 1123 mode_ == ICState::Mode::Specialized) { 1124 auto attached = tryAttachInlinableNativeGetter(holder, prop, receiverId); 1125 if (attached != AttachDecision::NoAction) { 1126 MOZ_ASSERT(attached == AttachDecision::Attach); 1127 return; 1128 } 1129 } 1130 1131 emitCallGetterResultNoGuards(kind, obj, holder, prop, receiverId); 1132 } 1133 1134 static bool CanAttachDOMCall(JSContext* cx, JSJitInfo::OpType type, 1135 JSObject* obj, JSFunction* fun, 1136 ICState::Mode mode) { 1137 MOZ_ASSERT(type == JSJitInfo::Getter || type == JSJitInfo::Setter || 1138 type == JSJitInfo::Method); 1139 1140 if (mode != ICState::Mode::Specialized) { 1141 return false; 1142 } 1143 1144 if (!fun->hasJitInfo()) { 1145 return false; 1146 } 1147 1148 if (cx->realm() != fun->realm()) { 1149 return false; 1150 } 1151 1152 const JSJitInfo* jitInfo = fun->jitInfo(); 1153 if (jitInfo->type() != type) { 1154 return false; 1155 } 1156 1157 MOZ_ASSERT_IF(IsWindow(obj), !jitInfo->needsOuterizedThisObject()); 1158 1159 const JSClass* clasp = obj->getClass(); 1160 if (!clasp->isDOMClass()) { 1161 return false; 1162 } 1163 1164 if (type != JSJitInfo::Method && clasp->isProxyObject()) { 1165 return false; 1166 } 1167 1168 // Ion codegen expects DOM_OBJECT_SLOT to be a fixed slot in LoadDOMPrivate. 1169 // It can be a dynamic slot if we transplanted this reflector object with a 1170 // proxy. 1171 if (obj->is<NativeObject>() && obj->as<NativeObject>().numFixedSlots() == 0) { 1172 return false; 1173 } 1174 1175 // Tell the analysis the |DOMInstanceClassHasProtoAtDepth| hook can't GC. 1176 JS::AutoSuppressGCAnalysis nogc; 1177 1178 DOMInstanceClassHasProtoAtDepth instanceChecker = 1179 cx->runtime()->DOMcallbacks->instanceClassMatchesProto; 1180 return instanceChecker(clasp, jitInfo->protoID, jitInfo->depth); 1181 } 1182 1183 static bool CanAttachDOMGetterSetter(JSContext* cx, JSJitInfo::OpType type, 1184 NativeObject* obj, NativeObject* holder, 1185 PropertyInfo prop, ICState::Mode mode) { 1186 MOZ_ASSERT(type == JSJitInfo::Getter || type == JSJitInfo::Setter); 1187 1188 JSObject* accessor = type == JSJitInfo::Getter ? holder->getGetter(prop) 1189 : holder->getSetter(prop); 1190 JSFunction* fun = &accessor->as<JSFunction>(); 1191 1192 return CanAttachDOMCall(cx, type, obj, fun, mode); 1193 } 1194 1195 void IRGenerator::emitCallDOMGetterResultNoGuards(NativeObject* holder, 1196 PropertyInfo prop, 1197 ObjOperandId objId) { 1198 JSFunction* getter = &holder->getGetter(prop)->as<JSFunction>(); 1199 writer.callDOMGetterResult(objId, getter->jitInfo()); 1200 writer.returnFromIC(); 1201 } 1202 1203 void GetPropIRGenerator::emitCallDOMGetterResult(NativeObject* obj, 1204 NativeObject* holder, 1205 HandleId id, PropertyInfo prop, 1206 ObjOperandId objId) { 1207 // Note: this relies on emitCallGetterResultGuards emitting a shape guard 1208 // for specialized stubs. 1209 // The shape guard ensures the receiver's Class is valid for this DOM getter. 1210 emitCallGetterResultGuards(obj, holder, id, prop, objId); 1211 emitCallDOMGetterResultNoGuards(holder, prop, objId); 1212 } 1213 1214 void GetPropIRGenerator::attachMegamorphicNativeSlot(ObjOperandId objId, 1215 jsid id) { 1216 MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic); 1217 1218 // We don't support GetBoundName because environment objects have 1219 // lookupProperty hooks and GetBoundName is usually not megamorphic. 1220 MOZ_ASSERT(JSOp(*pc_) != JSOp::GetBoundName); 1221 1222 if (cacheKind_ == CacheKind::GetProp || 1223 cacheKind_ == CacheKind::GetPropSuper) { 1224 writer.megamorphicLoadSlotResult(objId, id); 1225 } else { 1226 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem || 1227 cacheKind_ == CacheKind::GetElemSuper); 1228 writer.megamorphicLoadSlotByValueResult(objId, getElemKeyValueId()); 1229 } 1230 writer.returnFromIC(); 1231 1232 trackAttached("GetProp.MegamorphicNativeSlot"); 1233 } 1234 1235 void GetPropIRGenerator::attachMegamorphicNativeSlotPermissive( 1236 ObjOperandId objId, jsid id) { 1237 MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic); 1238 1239 // We don't support GetBoundName because environment objects have 1240 // lookupProperty hooks and GetBoundName is usually not megamorphic. 1241 MOZ_ASSERT(JSOp(*pc_) != JSOp::GetBoundName); 1242 // It is not worth the complexity to support super here because we'd have 1243 // to plumb the receiver through everywhere, so we just skip it. 1244 MOZ_ASSERT(!isSuper()); 1245 1246 if (cacheKind_ == CacheKind::GetProp) { 1247 writer.megamorphicLoadSlotPermissiveResult(objId, id); 1248 } else { 1249 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem); 1250 writer.megamorphicLoadSlotByValuePermissiveResult(objId, 1251 getElemKeyValueId()); 1252 } 1253 writer.returnFromIC(); 1254 1255 trackAttached("GetProp.MegamorphicNativeSlotPermissive"); 1256 } 1257 1258 AttachDecision GetPropIRGenerator::tryAttachNative(HandleObject obj, 1259 ObjOperandId objId, 1260 HandleId id, 1261 ValOperandId receiverId) { 1262 Maybe<PropertyInfo> prop; 1263 Rooted<NativeObject*> holder(cx_); 1264 1265 NativeGetPropKind kind = 1266 CanAttachNativeGetProp(cx_, obj, id, holder.address(), &prop, pc_); 1267 switch (kind) { 1268 case NativeGetPropKind::None: 1269 return AttachDecision::NoAction; 1270 case NativeGetPropKind::Missing: 1271 case NativeGetPropKind::Slot: { 1272 auto* nobj = &obj->as<NativeObject>(); 1273 1274 if (mode_ == ICState::Mode::Megamorphic && 1275 JSOp(*pc_) != JSOp::GetBoundName) { 1276 attachMegamorphicNativeSlot(objId, id); 1277 return AttachDecision::Attach; 1278 } 1279 1280 maybeEmitIdGuard(id); 1281 if (kind == NativeGetPropKind::Slot) { 1282 emitLoadDataPropertyResult(nobj, holder, id, *prop, objId); 1283 writer.returnFromIC(); 1284 trackAttached("GetProp.NativeSlot"); 1285 } else { 1286 EmitMissingPropResult(writer, nobj, objId); 1287 writer.returnFromIC(); 1288 trackAttached("GetProp.Missing"); 1289 } 1290 return AttachDecision::Attach; 1291 } 1292 case NativeGetPropKind::ScriptedGetter: 1293 case NativeGetPropKind::NativeGetter: { 1294 auto nobj = obj.as<NativeObject>(); 1295 MOZ_ASSERT(!IsWindow(nobj)); 1296 1297 // If we're in megamorphic mode, we assume that a specialized 1298 // getter call is just going to end up failing later, so we let this 1299 // get handled further down the chain by 1300 // attachMegamorphicNativeSlotPermissive 1301 if (!isSuper() && mode_ == ICState::Mode::Megamorphic) { 1302 return AttachDecision::NoAction; 1303 } 1304 1305 maybeEmitIdGuard(id); 1306 1307 if (!isSuper() && CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, nobj, 1308 holder, *prop, mode_)) { 1309 emitCallDOMGetterResult(nobj, holder, id, *prop, objId); 1310 1311 trackAttached("GetProp.DOMGetter"); 1312 return AttachDecision::Attach; 1313 } 1314 1315 emitCallGetterResult(kind, nobj, holder, id, *prop, objId, receiverId); 1316 1317 trackAttached("GetProp.NativeGetter"); 1318 return AttachDecision::Attach; 1319 } 1320 } 1321 1322 MOZ_CRASH("Bad NativeGetPropKind"); 1323 } 1324 1325 // Returns whether obj is a WindowProxy wrapping the script's global. 1326 static bool IsWindowProxyForScriptGlobal(JSScript* script, JSObject* obj) { 1327 if (!IsWindowProxy(obj)) { 1328 return false; 1329 } 1330 1331 MOZ_ASSERT(obj->getClass() == 1332 script->runtimeFromMainThread()->maybeWindowProxyClass()); 1333 1334 JSObject* window = ToWindowIfWindowProxy(obj); 1335 1336 // Ion relies on the WindowProxy's group changing (and the group getting 1337 // marked as having unknown properties) on navigation. If we ever stop 1338 // transplanting same-compartment WindowProxies, this assert will fail and we 1339 // need to fix that code. 1340 MOZ_ASSERT(window == &obj->nonCCWGlobal()); 1341 1342 // This must be a WindowProxy for a global in this compartment. Else it would 1343 // be a cross-compartment wrapper and IsWindowProxy returns false for 1344 // those. 1345 MOZ_ASSERT(script->compartment() == obj->compartment()); 1346 1347 // Only optimize lookups on the WindowProxy for the current global. Other 1348 // WindowProxies in the compartment may require security checks (based on 1349 // mutable document.domain). See bug 1516775. 1350 return window == &script->global(); 1351 } 1352 1353 // Guards objId is a WindowProxy for windowObj. Returns the window's operand id. 1354 static ObjOperandId GuardAndLoadWindowProxyWindow(CacheIRWriter& writer, 1355 ObjOperandId objId, 1356 GlobalObject* windowObj) { 1357 writer.guardClass(objId, GuardClassKind::WindowProxy); 1358 ObjOperandId windowObjId = writer.loadWrapperTarget(objId, 1359 /*fallible = */ false); 1360 writer.guardSpecificObject(windowObjId, windowObj); 1361 return windowObjId; 1362 } 1363 1364 // Whether a getter/setter on the global should have the WindowProxy as |this| 1365 // value instead of the Window (the global object). This always returns true for 1366 // scripted functions. 1367 static bool GetterNeedsWindowProxyThis(NativeObject* holder, 1368 PropertyInfo prop) { 1369 JSFunction* callee = &holder->getGetter(prop)->as<JSFunction>(); 1370 return !callee->hasJitInfo() || callee->jitInfo()->needsOuterizedThisObject(); 1371 } 1372 static bool SetterNeedsWindowProxyThis(NativeObject* holder, 1373 PropertyInfo prop) { 1374 JSFunction* callee = &holder->getSetter(prop)->as<JSFunction>(); 1375 return !callee->hasJitInfo() || callee->jitInfo()->needsOuterizedThisObject(); 1376 } 1377 1378 AttachDecision GetPropIRGenerator::tryAttachWindowProxy(HandleObject obj, 1379 ObjOperandId objId, 1380 HandleId id) { 1381 // Attach a stub when the receiver is a WindowProxy and we can do the lookup 1382 // on the Window (the global object). 1383 1384 if (!IsWindowProxyForScriptGlobal(script_, obj)) { 1385 return AttachDecision::NoAction; 1386 } 1387 1388 // If we're megamorphic prefer a generic proxy stub that handles a lot more 1389 // cases. 1390 if (mode_ == ICState::Mode::Megamorphic) { 1391 return AttachDecision::NoAction; 1392 } 1393 1394 // Now try to do the lookup on the Window (the current global). 1395 Handle<GlobalObject*> windowObj = cx_->global(); 1396 Rooted<NativeObject*> holder(cx_); 1397 Maybe<PropertyInfo> prop; 1398 NativeGetPropKind kind = 1399 CanAttachNativeGetProp(cx_, windowObj, id, holder.address(), &prop, pc_); 1400 switch (kind) { 1401 case NativeGetPropKind::None: 1402 return AttachDecision::NoAction; 1403 1404 case NativeGetPropKind::Slot: { 1405 maybeEmitIdGuard(id); 1406 ObjOperandId windowObjId = 1407 GuardAndLoadWindowProxyWindow(writer, objId, windowObj); 1408 emitLoadDataPropertyResult(windowObj, holder, id, *prop, windowObjId); 1409 writer.returnFromIC(); 1410 1411 trackAttached("GetProp.WindowProxySlot"); 1412 return AttachDecision::Attach; 1413 } 1414 1415 case NativeGetPropKind::Missing: { 1416 maybeEmitIdGuard(id); 1417 ObjOperandId windowObjId = 1418 GuardAndLoadWindowProxyWindow(writer, objId, windowObj); 1419 EmitMissingPropResult(writer, windowObj, windowObjId); 1420 writer.returnFromIC(); 1421 1422 trackAttached("GetProp.WindowProxyMissing"); 1423 return AttachDecision::Attach; 1424 } 1425 1426 case NativeGetPropKind::NativeGetter: 1427 case NativeGetPropKind::ScriptedGetter: { 1428 // If a |super| access, it is not worth the complexity to attach an IC. 1429 if (isSuper()) { 1430 return AttachDecision::NoAction; 1431 } 1432 1433 bool needsWindowProxy = GetterNeedsWindowProxyThis(holder, *prop); 1434 1435 // Guard the incoming object is a WindowProxy and inline a getter call 1436 // based on the Window object. 1437 maybeEmitIdGuard(id); 1438 ObjOperandId windowObjId = 1439 GuardAndLoadWindowProxyWindow(writer, objId, windowObj); 1440 1441 if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, windowObj, holder, 1442 *prop, mode_)) { 1443 MOZ_ASSERT(!needsWindowProxy); 1444 emitCallDOMGetterResult(windowObj, holder, id, *prop, windowObjId); 1445 trackAttached("GetProp.WindowProxyDOMGetter"); 1446 } else { 1447 ValOperandId receiverId = 1448 writer.boxObject(needsWindowProxy ? objId : windowObjId); 1449 emitCallGetterResult(kind, windowObj, holder, id, *prop, windowObjId, 1450 receiverId); 1451 trackAttached("GetProp.WindowProxyGetter"); 1452 } 1453 1454 return AttachDecision::Attach; 1455 } 1456 } 1457 1458 MOZ_CRASH("Unreachable"); 1459 } 1460 1461 AttachDecision GetPropIRGenerator::tryAttachCrossCompartmentWrapper( 1462 HandleObject obj, ObjOperandId objId, HandleId id) { 1463 // We can only optimize this very wrapper-handler, because others might 1464 // have a security policy. 1465 if (!IsWrapper(obj) || 1466 Wrapper::wrapperHandler(obj) != &CrossCompartmentWrapper::singleton) { 1467 return AttachDecision::NoAction; 1468 } 1469 1470 // If we're megamorphic prefer a generic proxy stub that handles a lot more 1471 // cases. 1472 if (mode_ == ICState::Mode::Megamorphic) { 1473 return AttachDecision::NoAction; 1474 } 1475 1476 RootedObject unwrapped(cx_, Wrapper::wrappedObject(obj)); 1477 MOZ_ASSERT(unwrapped == UnwrapOneCheckedStatic(obj)); 1478 MOZ_ASSERT(!IsCrossCompartmentWrapper(unwrapped), 1479 "CCWs must not wrap other CCWs"); 1480 1481 // If we allowed different zones we would have to wrap strings. 1482 if (unwrapped->compartment()->zone() != cx_->compartment()->zone()) { 1483 return AttachDecision::NoAction; 1484 } 1485 1486 // Take the unwrapped object's global, and wrap in a 1487 // this-compartment wrapper. This is what will be stored in the IC 1488 // keep the compartment alive. 1489 RootedObject wrappedTargetGlobal(cx_, &unwrapped->nonCCWGlobal()); 1490 if (!cx_->compartment()->wrap(cx_, &wrappedTargetGlobal)) { 1491 cx_->clearPendingException(); 1492 return AttachDecision::NoAction; 1493 } 1494 1495 NativeObject* holder = nullptr; 1496 Maybe<PropertyInfo> prop; 1497 1498 // Enter realm of target to prevent failing compartment assertions when doing 1499 // the lookup. 1500 { 1501 AutoRealm ar(cx_, unwrapped); 1502 1503 NativeGetPropKind kind = 1504 CanAttachNativeGetProp(cx_, unwrapped, id, &holder, &prop, pc_); 1505 if (kind != NativeGetPropKind::Slot && kind != NativeGetPropKind::Missing) { 1506 return AttachDecision::NoAction; 1507 } 1508 } 1509 auto* unwrappedNative = &unwrapped->as<NativeObject>(); 1510 1511 maybeEmitIdGuard(id); 1512 writer.guardIsProxy(objId); 1513 writer.guardHasProxyHandler(objId, Wrapper::wrapperHandler(obj)); 1514 1515 // Load the object wrapped by the CCW 1516 ObjOperandId wrapperTargetId = 1517 writer.loadWrapperTarget(objId, /*fallible = */ false); 1518 1519 // If the compartment of the wrapped object is different we should fail. 1520 writer.guardCompartment(wrapperTargetId, wrappedTargetGlobal, 1521 unwrappedNative->compartment()); 1522 1523 ObjOperandId unwrappedId = wrapperTargetId; 1524 if (holder) { 1525 EmitReadSlotResult<IsCrossCompartment::Yes>(writer, unwrappedNative, holder, 1526 *prop, unwrappedId); 1527 writer.wrapResult(); 1528 writer.returnFromIC(); 1529 trackAttached("GetProp.CCWSlot"); 1530 } else { 1531 EmitMissingPropResult<IsCrossCompartment::Yes>(writer, unwrappedNative, 1532 unwrappedId); 1533 writer.returnFromIC(); 1534 trackAttached("GetProp.CCWMissing"); 1535 } 1536 return AttachDecision::Attach; 1537 } 1538 1539 static JSObject* NewWrapperWithObjectShape(JSContext* cx, 1540 Handle<NativeObject*> obj); 1541 1542 static bool GetXrayExpandoShapeWrapper(JSContext* cx, HandleObject xray, 1543 MutableHandleObject wrapper) { 1544 Value v = GetProxyReservedSlot(xray, GetXrayJitInfo()->xrayHolderSlot); 1545 if (v.isObject()) { 1546 NativeObject* holder = &v.toObject().as<NativeObject>(); 1547 v = holder->getFixedSlot(GetXrayJitInfo()->holderExpandoSlot); 1548 if (v.isObject()) { 1549 Rooted<NativeObject*> expando( 1550 cx, &UncheckedUnwrap(&v.toObject())->as<NativeObject>()); 1551 wrapper.set(NewWrapperWithObjectShape(cx, expando)); 1552 return wrapper != nullptr; 1553 } 1554 } 1555 wrapper.set(nullptr); 1556 return true; 1557 } 1558 1559 AttachDecision GetPropIRGenerator::tryAttachXrayCrossCompartmentWrapper( 1560 HandleObject obj, ObjOperandId objId, HandleId id, 1561 ValOperandId receiverId) { 1562 if (!obj->is<ProxyObject>()) { 1563 return AttachDecision::NoAction; 1564 } 1565 1566 JS::XrayJitInfo* info = GetXrayJitInfo(); 1567 if (!info || !info->isCrossCompartmentXray(GetProxyHandler(obj))) { 1568 return AttachDecision::NoAction; 1569 } 1570 1571 if (!info->compartmentHasExclusiveExpandos(obj)) { 1572 return AttachDecision::NoAction; 1573 } 1574 1575 RootedObject target(cx_, UncheckedUnwrap(obj)); 1576 1577 RootedObject expandoShapeWrapper(cx_); 1578 if (!GetXrayExpandoShapeWrapper(cx_, obj, &expandoShapeWrapper)) { 1579 cx_->recoverFromOutOfMemory(); 1580 return AttachDecision::NoAction; 1581 } 1582 1583 // Look for a getter we can call on the xray or its prototype chain. 1584 Rooted<Maybe<PropertyDescriptor>> desc(cx_); 1585 RootedObject holder(cx_, obj); 1586 RootedObjectVector prototypes(cx_); 1587 RootedObjectVector prototypeExpandoShapeWrappers(cx_); 1588 while (true) { 1589 if (!GetOwnPropertyDescriptor(cx_, holder, id, &desc)) { 1590 cx_->clearPendingException(); 1591 return AttachDecision::NoAction; 1592 } 1593 if (desc.isSome()) { 1594 break; 1595 } 1596 if (!GetPrototype(cx_, holder, &holder)) { 1597 cx_->clearPendingException(); 1598 return AttachDecision::NoAction; 1599 } 1600 if (!holder || !holder->is<ProxyObject>() || 1601 !info->isCrossCompartmentXray(GetProxyHandler(holder))) { 1602 return AttachDecision::NoAction; 1603 } 1604 RootedObject prototypeExpandoShapeWrapper(cx_); 1605 if (!GetXrayExpandoShapeWrapper(cx_, holder, 1606 &prototypeExpandoShapeWrapper) || 1607 !prototypes.append(holder) || 1608 !prototypeExpandoShapeWrappers.append(prototypeExpandoShapeWrapper)) { 1609 cx_->recoverFromOutOfMemory(); 1610 return AttachDecision::NoAction; 1611 } 1612 } 1613 if (!desc->isAccessorDescriptor()) { 1614 return AttachDecision::NoAction; 1615 } 1616 1617 RootedObject getter(cx_, desc->getter()); 1618 if (!getter || !getter->is<JSFunction>() || 1619 !getter->as<JSFunction>().isNativeWithoutJitEntry()) { 1620 return AttachDecision::NoAction; 1621 } 1622 1623 maybeEmitIdGuard(id); 1624 writer.guardIsProxy(objId); 1625 writer.guardHasProxyHandler(objId, GetProxyHandler(obj)); 1626 1627 // Load the object wrapped by the CCW 1628 ObjOperandId wrapperTargetId = 1629 writer.loadWrapperTarget(objId, /*fallible = */ false); 1630 1631 // Test the wrapped object's class. The properties held by xrays or their 1632 // prototypes will be invariant for objects of a given class, except for 1633 // changes due to xray expandos or xray prototype mutations. 1634 writer.guardAnyClass(wrapperTargetId, target->getClass()); 1635 1636 // Make sure the expandos on the xray and its prototype chain match up with 1637 // what we expect. The expando shape needs to be consistent, to ensure it 1638 // has not had any shadowing properties added, and the expando cannot have 1639 // any custom prototype (xray prototypes are stable otherwise). 1640 // 1641 // We can only do this for xrays with exclusive access to their expandos 1642 // (as we checked earlier), which store a pointer to their expando 1643 // directly. Xrays in other compartments may share their expandos with each 1644 // other and a VM call is needed just to find the expando. 1645 if (expandoShapeWrapper) { 1646 writer.guardXrayExpandoShapeAndDefaultProto(objId, expandoShapeWrapper); 1647 } else { 1648 writer.guardXrayNoExpando(objId); 1649 } 1650 for (size_t i = 0; i < prototypes.length(); i++) { 1651 JSObject* proto = prototypes[i]; 1652 ObjOperandId protoId = writer.loadObject(proto); 1653 if (JSObject* protoShapeWrapper = prototypeExpandoShapeWrappers[i]) { 1654 writer.guardXrayExpandoShapeAndDefaultProto(protoId, protoShapeWrapper); 1655 } else { 1656 writer.guardXrayNoExpando(protoId); 1657 } 1658 } 1659 1660 bool sameRealm = cx_->realm() == getter->as<JSFunction>().realm(); 1661 writer.callNativeGetterResult(receiverId, &getter->as<JSFunction>(), 1662 sameRealm); 1663 writer.returnFromIC(); 1664 1665 trackAttached("GetProp.XrayCCW"); 1666 return AttachDecision::Attach; 1667 } 1668 1669 #ifdef JS_PUNBOX64 1670 AttachDecision GetPropIRGenerator::tryAttachScriptedProxy( 1671 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id) { 1672 if (cacheKind_ != CacheKind::GetProp && cacheKind_ != CacheKind::GetElem) { 1673 return AttachDecision::NoAction; 1674 } 1675 if (cacheKind_ == CacheKind::GetElem) { 1676 if (!idVal_.isString() && !idVal_.isInt32() && !idVal_.isSymbol()) { 1677 return AttachDecision::NoAction; 1678 } 1679 } 1680 1681 // Private fields of proxies are stored on the expando, and don't fire traps. 1682 // Note that we don't need to guard against this in CacheIR, because there's 1683 // no way to generate bytecode that will *sometimes* load a private field; 1684 // either it's accessing a private field, or it isn't. We assert in the 1685 // CacheIR implementation of callScriptedProxy(GetResult|GetByValueResult) 1686 // that we don't see any private field symbols. 1687 if (idVal_.isSymbol() && idVal_.toSymbol()->isPrivateName()) { 1688 return AttachDecision::NoAction; 1689 } 1690 1691 JSObject* handlerObj = ScriptedProxyHandler::handlerObject(obj); 1692 if (!handlerObj) { 1693 return AttachDecision::NoAction; 1694 } 1695 1696 NativeObject* trapHolder = nullptr; 1697 Maybe<PropertyInfo> trapProp; 1698 // We call with pc_ even though that's not the actual corresponding pc. It 1699 // should, however, be fine, because it's just used to check if this is a 1700 // GetBoundName, which it's not. 1701 NativeGetPropKind trapKind = CanAttachNativeGetProp( 1702 cx_, handlerObj, NameToId(cx_->names().get), &trapHolder, &trapProp, pc_); 1703 1704 if (trapKind != NativeGetPropKind::Missing && 1705 trapKind != NativeGetPropKind::Slot) { 1706 return AttachDecision::NoAction; 1707 } 1708 1709 if (trapKind != NativeGetPropKind::Missing) { 1710 uint32_t trapSlot = trapProp->slot(); 1711 const Value& trapVal = trapHolder->getSlot(trapSlot); 1712 if (!trapVal.isObject()) { 1713 return AttachDecision::NoAction; 1714 } 1715 1716 JSObject* trapObj = &trapVal.toObject(); 1717 if (!trapObj->is<JSFunction>()) { 1718 return AttachDecision::NoAction; 1719 } 1720 1721 JSFunction* trapFn = &trapObj->as<JSFunction>(); 1722 if (trapFn->isClassConstructor()) { 1723 return AttachDecision::NoAction; 1724 } 1725 1726 if (!trapFn->hasJitEntry()) { 1727 return AttachDecision::NoAction; 1728 } 1729 1730 if (cx_->realm() != trapFn->realm()) { 1731 return AttachDecision::NoAction; 1732 } 1733 } 1734 1735 NativeObject* nHandlerObj = &handlerObj->as<NativeObject>(); 1736 JSObject* targetObj = obj->target(); 1737 MOZ_ASSERT(targetObj, "Guaranteed by the scripted Proxy constructor"); 1738 1739 // We just require that the target is a NativeObject to make our lives 1740 // easier. There's too much nonsense we might have to handle otherwise and 1741 // we're not set up to recursively call GetPropIRGenerator::tryAttachStub 1742 // for the target object. 1743 if (!targetObj->is<NativeObject>()) { 1744 return AttachDecision::NoAction; 1745 } 1746 1747 writer.guardIsProxy(objId); 1748 writer.guardHasProxyHandler(objId, &ScriptedProxyHandler::singleton); 1749 ObjOperandId handlerObjId = writer.loadScriptedProxyHandler(objId); 1750 ObjOperandId targetObjId = 1751 writer.loadWrapperTarget(objId, /*fallible =*/true); 1752 1753 writer.guardIsNativeObject(targetObjId); 1754 1755 if (trapKind == NativeGetPropKind::Missing) { 1756 EmitMissingPropGuard(writer, nHandlerObj, handlerObjId); 1757 if (cacheKind_ == CacheKind::GetProp) { 1758 writer.megamorphicLoadSlotResult(targetObjId, id); 1759 } else { 1760 writer.megamorphicLoadSlotByValueResult(objId, getElemKeyValueId()); 1761 } 1762 } else { 1763 uint32_t trapSlot = trapProp->slot(); 1764 const Value& trapVal = trapHolder->getSlot(trapSlot); 1765 JSObject* trapObj = &trapVal.toObject(); 1766 JSFunction* trapFn = &trapObj->as<JSFunction>(); 1767 ObjOperandId trapHolderId = 1768 EmitReadSlotGuard(writer, nHandlerObj, trapHolder, handlerObjId); 1769 1770 ValOperandId fnValId = 1771 EmitLoadSlot(writer, trapHolder, trapHolderId, trapSlot); 1772 ObjOperandId fnObjId = writer.guardToObject(fnValId); 1773 emitCalleeGuard(fnObjId, trapFn); 1774 ValOperandId targetValId = writer.boxObject(targetObjId); 1775 if (cacheKind_ == CacheKind::GetProp) { 1776 writer.callScriptedProxyGetResult(targetValId, objId, handlerObjId, 1777 fnObjId, trapFn, id); 1778 } else { 1779 ValOperandId idId = getElemKeyValueId(); 1780 ValOperandId stringIdId = writer.idToStringOrSymbol(idId); 1781 writer.callScriptedProxyGetByValueResult(targetValId, objId, handlerObjId, 1782 stringIdId, fnObjId, trapFn); 1783 } 1784 } 1785 writer.returnFromIC(); 1786 1787 trackAttached("GetScriptedProxy"); 1788 return AttachDecision::Attach; 1789 } 1790 #endif 1791 1792 AttachDecision GetPropIRGenerator::tryAttachGenericProxy( 1793 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id, 1794 bool handleDOMProxies) { 1795 writer.guardIsProxy(objId); 1796 1797 if (!handleDOMProxies) { 1798 // Ensure that the incoming object is not a DOM proxy, so that we can get to 1799 // the specialized stubs 1800 writer.guardIsNotDOMProxy(objId); 1801 } 1802 1803 if (cacheKind_ == CacheKind::GetProp || mode_ == ICState::Mode::Specialized) { 1804 MOZ_ASSERT(!isSuper()); 1805 maybeEmitIdGuard(id); 1806 writer.proxyGetResult(objId, id); 1807 } else { 1808 // Attach a stub that handles every id. 1809 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem); 1810 MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic); 1811 MOZ_ASSERT(!isSuper()); 1812 writer.proxyGetByValueResult(objId, getElemKeyValueId()); 1813 } 1814 1815 writer.returnFromIC(); 1816 1817 trackAttached("GetProp.GenericProxy"); 1818 return AttachDecision::Attach; 1819 } 1820 1821 static bool ValueIsInt64Index(const Value& val, int64_t* index) { 1822 // Try to convert the Value to a TypedArray index or DataView offset. 1823 1824 if (val.isInt32()) { 1825 *index = val.toInt32(); 1826 return true; 1827 } 1828 1829 if (val.isDouble()) { 1830 // Use NumberEqualsInt64 because ToPropertyKey(-0) is 0. 1831 return mozilla::NumberEqualsInt64(val.toDouble(), index); 1832 } 1833 1834 return false; 1835 } 1836 1837 IntPtrOperandId IRGenerator::guardToIntPtrIndex(const Value& index, 1838 ValOperandId indexId, 1839 bool supportOOB) { 1840 #ifdef DEBUG 1841 int64_t indexInt64; 1842 MOZ_ASSERT_IF(!supportOOB, ValueIsInt64Index(index, &indexInt64)); 1843 #endif 1844 1845 if (index.isInt32()) { 1846 Int32OperandId int32IndexId = writer.guardToInt32(indexId); 1847 return writer.int32ToIntPtr(int32IndexId); 1848 } 1849 1850 MOZ_ASSERT(index.isNumber()); 1851 NumberOperandId numberIndexId = writer.guardIsNumber(indexId); 1852 return writer.guardNumberToIntPtrIndex(numberIndexId, supportOOB); 1853 } 1854 1855 ObjOperandId IRGenerator::guardDOMProxyExpandoObjectAndShape( 1856 ProxyObject* obj, ObjOperandId objId, const Value& expandoVal, 1857 NativeObject* expandoObj) { 1858 MOZ_ASSERT(IsCacheableDOMProxy(obj)); 1859 1860 TestMatchingProxyReceiver(writer, obj, objId); 1861 1862 // Shape determines Class, so now it must be a DOM proxy. 1863 ValOperandId expandoValId; 1864 if (expandoVal.isObject()) { 1865 expandoValId = writer.loadDOMExpandoValue(objId); 1866 } else { 1867 expandoValId = writer.loadDOMExpandoValueIgnoreGeneration(objId); 1868 } 1869 1870 // Guard the expando is an object and shape guard. 1871 ObjOperandId expandoObjId = writer.guardToObject(expandoValId); 1872 TestMatchingHolder(writer, expandoObj, expandoObjId); 1873 return expandoObjId; 1874 } 1875 1876 AttachDecision GetPropIRGenerator::tryAttachDOMProxyExpando( 1877 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id, 1878 ValOperandId receiverId) { 1879 MOZ_ASSERT(IsCacheableDOMProxy(obj)); 1880 1881 Value expandoVal = GetProxyPrivate(obj); 1882 JSObject* expandoObj; 1883 if (expandoVal.isObject()) { 1884 expandoObj = &expandoVal.toObject(); 1885 } else { 1886 MOZ_ASSERT(!expandoVal.isUndefined(), 1887 "How did a missing expando manage to shadow things?"); 1888 auto expandoAndGeneration = 1889 static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate()); 1890 MOZ_ASSERT(expandoAndGeneration); 1891 expandoObj = &expandoAndGeneration->expando.toObject(); 1892 } 1893 1894 // Try to do the lookup on the expando object. 1895 NativeObject* holder = nullptr; 1896 Maybe<PropertyInfo> prop; 1897 NativeGetPropKind kind = 1898 CanAttachNativeGetProp(cx_, expandoObj, id, &holder, &prop, pc_); 1899 if (kind == NativeGetPropKind::None) { 1900 return AttachDecision::NoAction; 1901 } 1902 if (!holder) { 1903 return AttachDecision::NoAction; 1904 } 1905 auto* nativeExpandoObj = &expandoObj->as<NativeObject>(); 1906 1907 MOZ_ASSERT(holder == nativeExpandoObj); 1908 1909 maybeEmitIdGuard(id); 1910 ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape( 1911 obj, objId, expandoVal, nativeExpandoObj); 1912 1913 if (kind == NativeGetPropKind::Slot) { 1914 // Load from the expando's slots. 1915 EmitLoadSlotResult(writer, expandoObjId, nativeExpandoObj, *prop); 1916 writer.returnFromIC(); 1917 } else { 1918 // Call the getter. Note that we pass objId, the DOM proxy, as |this| 1919 // and not the expando object. 1920 MOZ_ASSERT(kind == NativeGetPropKind::NativeGetter || 1921 kind == NativeGetPropKind::ScriptedGetter); 1922 emitGuardGetterSetterSlot(nativeExpandoObj, *prop, expandoObjId, 1923 AccessorKind::Getter); 1924 emitCallGetterResultNoGuards(kind, nativeExpandoObj, nativeExpandoObj, 1925 *prop, receiverId); 1926 } 1927 1928 trackAttached("GetProp.DOMProxyExpando"); 1929 return AttachDecision::Attach; 1930 } 1931 1932 AttachDecision GetPropIRGenerator::tryAttachDOMProxyShadowed( 1933 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id) { 1934 MOZ_ASSERT(!isSuper()); 1935 MOZ_ASSERT(IsCacheableDOMProxy(obj)); 1936 1937 maybeEmitIdGuard(id); 1938 TestMatchingProxyReceiver(writer, obj, objId); 1939 writer.proxyGetResult(objId, id); 1940 writer.returnFromIC(); 1941 1942 trackAttached("GetProp.DOMProxyShadowed"); 1943 return AttachDecision::Attach; 1944 } 1945 1946 // Emit CacheIR to guard the DOM proxy doesn't shadow |id|. There are two types 1947 // of DOM proxies: 1948 // 1949 // (a) DOM proxies marked LegacyOverrideBuiltIns in WebIDL, for example 1950 // HTMLDocument or HTMLFormElement. These proxies look up properties in this 1951 // order: 1952 // 1953 // (1) The expando object. 1954 // (2) The proxy's named-property handler. 1955 // (3) The prototype chain. 1956 // 1957 // To optimize properties on the prototype chain, we have to guard that (1) 1958 // and (2) don't shadow (3). We handle (1) by either emitting a shape guard 1959 // for the expando object or by guarding the proxy has no expando object. To 1960 // efficiently handle (2), the proxy must have an ExpandoAndGeneration* 1961 // stored as PrivateValue. We guard on its generation field to ensure the 1962 // set of names hasn't changed. 1963 // 1964 // Missing properties can be optimized in a similar way by emitting shape 1965 // guards for the prototype chain. 1966 // 1967 // (b) Other DOM proxies. These proxies look up properties in this 1968 // order: 1969 // 1970 // (1) The expando object. 1971 // (2) The prototype chain. 1972 // (3) The proxy's named-property handler. 1973 // 1974 // To optimize properties on the prototype chain, we only have to guard the 1975 // expando object doesn't shadow it. 1976 // 1977 // Missing properties can't be optimized in this case because we don't have 1978 // an efficient way to guard against the proxy handler shadowing the 1979 // property (there's no ExpandoAndGeneration*). 1980 // 1981 // See also: 1982 // * DOMProxyShadows in DOMJSProxyHandler.cpp 1983 // * https://webidl.spec.whatwg.org/#dfn-named-property-visibility (the Note at 1984 // the end) 1985 // 1986 // Callers are expected to have already guarded on the shape of the 1987 // object, which guarantees the object is a DOM proxy. 1988 static void CheckDOMProxyDoesNotShadow(CacheIRWriter& writer, ProxyObject* obj, 1989 jsid id, ObjOperandId objId, 1990 bool* canOptimizeMissing) { 1991 MOZ_ASSERT(IsCacheableDOMProxy(obj)); 1992 1993 Value expandoVal = GetProxyPrivate(obj); 1994 1995 ValOperandId expandoId; 1996 if (!expandoVal.isObject() && !expandoVal.isUndefined()) { 1997 // Case (a). 1998 auto expandoAndGeneration = 1999 static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate()); 2000 uint64_t generation = expandoAndGeneration->generation; 2001 expandoId = writer.loadDOMExpandoValueGuardGeneration( 2002 objId, expandoAndGeneration, generation); 2003 expandoVal = expandoAndGeneration->expando; 2004 *canOptimizeMissing = true; 2005 } else { 2006 // Case (b). 2007 expandoId = writer.loadDOMExpandoValue(objId); 2008 *canOptimizeMissing = false; 2009 } 2010 2011 if (expandoVal.isUndefined()) { 2012 // Guard there's no expando object. 2013 writer.guardNonDoubleType(expandoId, ValueType::Undefined); 2014 } else if (expandoVal.isObject()) { 2015 // Guard the proxy either has no expando object or, if it has one, that 2016 // the shape matches the current expando object. 2017 NativeObject& expandoObj = expandoVal.toObject().as<NativeObject>(); 2018 MOZ_ASSERT(!expandoObj.containsPure(id)); 2019 writer.guardDOMExpandoMissingOrGuardShape(expandoId, expandoObj.shape()); 2020 } else { 2021 MOZ_CRASH("Invalid expando value"); 2022 } 2023 } 2024 2025 AttachDecision GetPropIRGenerator::tryAttachDOMProxyUnshadowed( 2026 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id, 2027 ValOperandId receiverId) { 2028 MOZ_ASSERT(IsCacheableDOMProxy(obj)); 2029 2030 JSObject* protoObj = obj->staticPrototype(); 2031 if (!protoObj) { 2032 return AttachDecision::NoAction; 2033 } 2034 2035 NativeObject* holder = nullptr; 2036 Maybe<PropertyInfo> prop; 2037 NativeGetPropKind kind = 2038 CanAttachNativeGetProp(cx_, protoObj, id, &holder, &prop, pc_); 2039 if (kind == NativeGetPropKind::None) { 2040 return AttachDecision::NoAction; 2041 } 2042 auto* nativeProtoObj = &protoObj->as<NativeObject>(); 2043 2044 maybeEmitIdGuard(id); 2045 2046 // Guard that our proxy (expando) object hasn't started shadowing this 2047 // property. 2048 TestMatchingProxyReceiver(writer, obj, objId); 2049 bool canOptimizeMissing = false; 2050 CheckDOMProxyDoesNotShadow(writer, obj, id, objId, &canOptimizeMissing); 2051 2052 if (holder) { 2053 // Found the property on the prototype chain. Treat it like a native 2054 // getprop. 2055 GeneratePrototypeGuards(writer, obj, holder, objId); 2056 2057 // Guard on the holder of the property. 2058 ObjOperandId holderId = writer.loadObject(holder); 2059 TestMatchingHolder(writer, holder, holderId); 2060 2061 if (kind == NativeGetPropKind::Slot) { 2062 EmitLoadSlotResult(writer, holderId, holder, *prop); 2063 writer.returnFromIC(); 2064 } else { 2065 // EmitCallGetterResultNoGuards expects |obj| to be the object the 2066 // property is on to do some checks. Since we actually looked at 2067 // checkObj, and no extra guards will be generated, we can just 2068 // pass that instead. 2069 MOZ_ASSERT(kind == NativeGetPropKind::NativeGetter || 2070 kind == NativeGetPropKind::ScriptedGetter); 2071 MOZ_ASSERT(!isSuper()); 2072 emitGuardGetterSetterSlot(holder, *prop, holderId, AccessorKind::Getter, 2073 /* holderIsConstant = */ true); 2074 emitCallGetterResultNoGuards(kind, nativeProtoObj, holder, *prop, 2075 receiverId); 2076 } 2077 } else { 2078 // Property was not found on the prototype chain. 2079 MOZ_ASSERT(kind == NativeGetPropKind::Missing); 2080 if (canOptimizeMissing) { 2081 // We already guarded on the proxy's shape, so now shape guard the proto 2082 // chain. 2083 ObjOperandId protoId = writer.loadObject(nativeProtoObj); 2084 EmitMissingPropResult(writer, nativeProtoObj, protoId); 2085 } else { 2086 MOZ_ASSERT(!isSuper()); 2087 writer.proxyGetResult(objId, id); 2088 } 2089 writer.returnFromIC(); 2090 } 2091 2092 trackAttached("GetProp.DOMProxyUnshadowed"); 2093 return AttachDecision::Attach; 2094 } 2095 2096 AttachDecision GetPropIRGenerator::tryAttachProxy(HandleObject obj, 2097 ObjOperandId objId, 2098 HandleId id, 2099 ValOperandId receiverId) { 2100 // The proxy stubs don't currently support |super| access. 2101 if (isSuper()) { 2102 return AttachDecision::NoAction; 2103 } 2104 2105 // Always try to attach scripted proxy get even if we're megamorphic. 2106 // In Speedometer 3 we'll often run into cases where we're megamorphic 2107 // overall, but monomorphic for the proxy case. This is because there 2108 // are functions which lazily turn various differently-shaped objects 2109 // into proxies. So the un-proxified objects are megamorphic, but the 2110 // proxy handlers are actually monomorphic. There is room for a bit 2111 // more sophistication here, but this should do for now. 2112 if (!obj->is<ProxyObject>()) { 2113 return AttachDecision::NoAction; 2114 } 2115 auto proxy = obj.as<ProxyObject>(); 2116 #ifdef JS_PUNBOX64 2117 if (proxy->handler()->isScripted()) { 2118 TRY_ATTACH(tryAttachScriptedProxy(proxy, objId, id)); 2119 } 2120 #endif 2121 2122 ProxyStubType type = GetProxyStubType(cx_, obj, id); 2123 if (type == ProxyStubType::None) { 2124 return AttachDecision::NoAction; 2125 } 2126 2127 if (mode_ == ICState::Mode::Megamorphic) { 2128 return tryAttachGenericProxy(proxy, objId, id, 2129 /* handleDOMProxies = */ true); 2130 } 2131 2132 switch (type) { 2133 case ProxyStubType::None: 2134 break; 2135 case ProxyStubType::DOMExpando: 2136 TRY_ATTACH(tryAttachDOMProxyExpando(proxy, objId, id, receiverId)); 2137 [[fallthrough]]; // Fall through to the generic shadowed case. 2138 case ProxyStubType::DOMShadowed: 2139 return tryAttachDOMProxyShadowed(proxy, objId, id); 2140 case ProxyStubType::DOMUnshadowed: 2141 TRY_ATTACH(tryAttachDOMProxyUnshadowed(proxy, objId, id, receiverId)); 2142 return tryAttachGenericProxy(proxy, objId, id, 2143 /* handleDOMProxies = */ true); 2144 case ProxyStubType::Generic: 2145 return tryAttachGenericProxy(proxy, objId, id, 2146 /* handleDOMProxies = */ false); 2147 } 2148 2149 MOZ_CRASH("Unexpected ProxyStubType"); 2150 } 2151 2152 const JSClass* js::jit::ClassFor(GuardClassKind kind) { 2153 switch (kind) { 2154 case GuardClassKind::Array: 2155 return &ArrayObject::class_; 2156 case GuardClassKind::PlainObject: 2157 return &PlainObject::class_; 2158 case GuardClassKind::FixedLengthArrayBuffer: 2159 return &FixedLengthArrayBufferObject::class_; 2160 case GuardClassKind::ImmutableArrayBuffer: 2161 return &ImmutableArrayBufferObject::class_; 2162 case GuardClassKind::ResizableArrayBuffer: 2163 return &ResizableArrayBufferObject::class_; 2164 case GuardClassKind::FixedLengthSharedArrayBuffer: 2165 return &FixedLengthSharedArrayBufferObject::class_; 2166 case GuardClassKind::GrowableSharedArrayBuffer: 2167 return &GrowableSharedArrayBufferObject::class_; 2168 case GuardClassKind::FixedLengthDataView: 2169 return &FixedLengthDataViewObject::class_; 2170 case GuardClassKind::ImmutableDataView: 2171 return &ImmutableDataViewObject::class_; 2172 case GuardClassKind::ResizableDataView: 2173 return &ResizableDataViewObject::class_; 2174 case GuardClassKind::MappedArguments: 2175 return &MappedArgumentsObject::class_; 2176 case GuardClassKind::UnmappedArguments: 2177 return &UnmappedArgumentsObject::class_; 2178 case GuardClassKind::WindowProxy: 2179 // Caller needs to handle this case, see 2180 // JSRuntime::maybeWindowProxyClass(). 2181 break; 2182 case GuardClassKind::JSFunction: 2183 // Caller needs to handle this case. Can be either |js::FunctionClass| or 2184 // |js::ExtendedFunctionClass|. 2185 break; 2186 case GuardClassKind::BoundFunction: 2187 return &BoundFunctionObject::class_; 2188 case GuardClassKind::Set: 2189 return &SetObject::class_; 2190 case GuardClassKind::Map: 2191 return &MapObject::class_; 2192 case GuardClassKind::Date: 2193 return &DateObject::class_; 2194 case GuardClassKind::WeakMap: 2195 return &WeakMapObject::class_; 2196 case GuardClassKind::WeakSet: 2197 return &WeakSetObject::class_; 2198 } 2199 MOZ_CRASH("unexpected kind"); 2200 } 2201 2202 // Guards the class of an object. Because shape implies class, and a shape guard 2203 // is faster than a class guard, if this is our first time attaching a stub, we 2204 // instead generate a shape guard. 2205 void IRGenerator::emitOptimisticClassGuard(ObjOperandId objId, JSObject* obj, 2206 GuardClassKind kind) { 2207 #ifdef DEBUG 2208 switch (kind) { 2209 case GuardClassKind::Array: 2210 case GuardClassKind::PlainObject: 2211 case GuardClassKind::FixedLengthArrayBuffer: 2212 case GuardClassKind::ImmutableArrayBuffer: 2213 case GuardClassKind::ResizableArrayBuffer: 2214 case GuardClassKind::FixedLengthSharedArrayBuffer: 2215 case GuardClassKind::GrowableSharedArrayBuffer: 2216 case GuardClassKind::FixedLengthDataView: 2217 case GuardClassKind::ImmutableDataView: 2218 case GuardClassKind::ResizableDataView: 2219 case GuardClassKind::Set: 2220 case GuardClassKind::Map: 2221 case GuardClassKind::Date: 2222 case GuardClassKind::WeakMap: 2223 case GuardClassKind::WeakSet: 2224 MOZ_ASSERT(obj->hasClass(ClassFor(kind))); 2225 break; 2226 2227 case GuardClassKind::MappedArguments: 2228 case GuardClassKind::UnmappedArguments: 2229 case GuardClassKind::JSFunction: 2230 case GuardClassKind::BoundFunction: 2231 case GuardClassKind::WindowProxy: 2232 // Arguments, functions, and the global object have 2233 // less consistent shapes. 2234 MOZ_CRASH("GuardClassKind not supported"); 2235 } 2236 #endif 2237 2238 if (isFirstStub_) { 2239 writer.guardShapeForClass(objId, obj->shape()); 2240 } else { 2241 writer.guardClass(objId, kind); 2242 } 2243 } 2244 2245 bool IRGenerator::canOptimizeConstantDataProperty(NativeObject* holder, 2246 PropertyInfo prop, 2247 ObjectFuse** objFuse) { 2248 MOZ_ASSERT(prop.isDataProperty()); 2249 2250 if (mode_ != ICState::Mode::Specialized || !holder->hasObjectFuse()) { 2251 return false; 2252 } 2253 2254 *objFuse = cx_->zone()->objectFuses.getOrCreate(cx_, holder); 2255 if (!*objFuse) { 2256 cx_->recoverFromOutOfMemory(); 2257 return false; 2258 } 2259 2260 if (!(*objFuse)->tryOptimizeConstantProperty(prop)) { 2261 return false; 2262 } 2263 2264 // Warp supports nursery objects and strings. Strings are atomized below, 2265 // and if that were not the case, WarpOracle would handle it. Warp does not 2266 // support nursery BigInts, a very uncommon case, so we currently don't 2267 // optimize such properties. 2268 Value result = holder->getSlot(prop.slot()); 2269 if (result.isGCThing() && !result.toGCThing()->isTenured() && 2270 !result.isObject() && !result.isString()) { 2271 MOZ_ASSERT(result.isBigInt()); 2272 return false; 2273 } 2274 2275 // Atomize strings to ensure the slot's value won't be mutated by the 2276 // LLoadFixedSlotAndAtomize or LLoadDynamicSlotAndAtomize instructions in Ion. 2277 // This matters because, for GC reasons, the Value that will be stored in the 2278 // IC stub in a WeakValueField must match the Value stored in the object slot. 2279 // In emitConstantDataPropertyResult we emit CacheIR instructions to assert 2280 // this. 2281 // 2282 // We don't optimize the constant property if the string is very long because 2283 // atomizing such strings can be slow. 2284 if (result.isString() && !result.toString()->isAtom()) { 2285 static constexpr size_t MaxLengthForAtomize = 1000; 2286 if (result.toString()->length() > MaxLengthForAtomize) { 2287 return false; 2288 } 2289 JSAtom* atom = AtomizeString(cx_, result.toString()); 2290 if (!atom) { 2291 cx_->recoverFromOutOfMemory(); 2292 return false; 2293 } 2294 result.setString(atom); 2295 holder->setSlot(prop.slot(), result); 2296 } 2297 2298 return true; 2299 } 2300 2301 void IRGenerator::emitConstantDataPropertyResult(NativeObject* holder, 2302 ObjOperandId holderId, 2303 PropertyKey key, 2304 PropertyInfo prop, 2305 ObjectFuse* objFuse) { 2306 MOZ_ASSERT(prop.isDataProperty()); 2307 2308 auto data = objFuse->getConstantPropertyGuardData(prop); 2309 bool canUseFastPath = !objFuse->hasInvalidatedConstantProperty(); 2310 writer.guardObjectFuseProperty(holderId, holder, objFuse, data.generation, 2311 data.propIndex, data.propMask, canUseFastPath); 2312 #ifdef DEBUG 2313 writer.assertPropertyLookup(holderId, key, prop.slot()); 2314 #endif 2315 2316 Value result = holder->getSlot(prop.slot()); 2317 MOZ_RELEASE_ASSERT(!result.isMagic()); 2318 MOZ_ASSERT_IF(result.isString(), result.toString()->isAtom()); 2319 2320 if (result.isGCThing()) { 2321 // We store the property Value in the IC stub in a WeakValueField (or 2322 // WeakObjectField) to ensure the stub doesn't keep GC things alive after 2323 // the property is mutated. Weak references normally require a read barrier 2324 // but not using a barrier here should be safe, because this property Value 2325 // is also stored in the holder object's slot and Watchtower should pop the 2326 // fuse when that slot is mutated. 2327 // 2328 // To make sure a Watchtower correctness bug doesn't become a GC security 2329 // bug, we check the slot Value still matches the current value before we 2330 // use the weak reference. This only affects Baseline IC code because we 2331 // don't use weak references in the transpiler or for Ion ICs. 2332 if (holder->isFixedSlot(prop.slot())) { 2333 size_t offset = NativeObject::getFixedSlotOffset(prop.slot()); 2334 writer.checkWeakValueResultForFixedSlot(holderId, offset, result); 2335 } else { 2336 size_t offset = holder->dynamicSlotIndex(prop.slot()) * sizeof(Value); 2337 writer.checkWeakValueResultForDynamicSlot(holderId, offset, result); 2338 } 2339 2340 // Use a different CacheIR instruction for objects, to support nursery 2341 // objects in Warp. 2342 if (result.isObject()) { 2343 writer.uncheckedLoadWeakObjectResult(&result.toObject()); 2344 } else { 2345 writer.uncheckedLoadWeakValueResult(result); 2346 } 2347 } else { 2348 writer.loadValueResult(result); 2349 } 2350 } 2351 2352 void IRGenerator::emitLoadDataPropertyResult(NativeObject* obj, 2353 NativeObject* holder, 2354 PropertyKey key, PropertyInfo prop, 2355 ObjOperandId objId) { 2356 ObjectFuse* objFuse = nullptr; 2357 if (canOptimizeConstantDataProperty(holder, prop, &objFuse)) { 2358 ObjOperandId holderId = 2359 EmitGuardObjectFuseHolder(writer, obj, holder, objId); 2360 emitConstantDataPropertyResult(holder, holderId, key, prop, objFuse); 2361 return; 2362 } 2363 2364 EmitReadSlotResult(writer, obj, holder, prop, objId); 2365 } 2366 2367 bool IRGenerator::canOptimizeConstantAccessorProperty(NativeObject* holder, 2368 PropertyInfo prop, 2369 ObjectFuse** objFuse) { 2370 MOZ_ASSERT(prop.isAccessorProperty()); 2371 MOZ_ASSERT(holder->getSlot(prop.slot()).toGCThing()->is<GetterSetter>()); 2372 2373 if (mode_ != ICState::Mode::Specialized || !holder->hasObjectFuse()) { 2374 return false; 2375 } 2376 2377 *objFuse = cx_->zone()->objectFuses.getOrCreate(cx_, holder); 2378 if (!*objFuse) { 2379 cx_->recoverFromOutOfMemory(); 2380 return false; 2381 } 2382 2383 return (*objFuse)->tryOptimizeConstantProperty(prop); 2384 } 2385 2386 void IRGenerator::emitGuardConstantAccessorProperty(NativeObject* holder, 2387 ObjOperandId holderId, 2388 PropertyKey key, 2389 PropertyInfo prop, 2390 ObjectFuse* objFuse) { 2391 MOZ_ASSERT(prop.isAccessorProperty()); 2392 2393 auto data = objFuse->getConstantPropertyGuardData(prop); 2394 bool canUseFastPath = !objFuse->hasInvalidatedConstantProperty(); 2395 writer.guardObjectFuseProperty(holderId, holder, objFuse, data.generation, 2396 data.propIndex, data.propMask, canUseFastPath); 2397 #ifdef DEBUG 2398 writer.assertPropertyLookup(holderId, key, prop.slot()); 2399 #endif 2400 } 2401 2402 static void AssertArgumentsCustomDataProp(ArgumentsObject* obj, 2403 PropertyKey key) { 2404 #ifdef DEBUG 2405 // The property must still be a custom data property if it has been resolved. 2406 // If this assertion fails, we're probably missing a call to mark this 2407 // property overridden. 2408 Maybe<PropertyInfo> prop = obj->lookupPure(key); 2409 MOZ_ASSERT_IF(prop, prop->isCustomDataProperty()); 2410 #endif 2411 } 2412 2413 AttachDecision GetPropIRGenerator::tryAttachObjectLength(HandleObject obj, 2414 ObjOperandId objId, 2415 HandleId id) { 2416 if (!id.isAtom(cx_->names().length)) { 2417 return AttachDecision::NoAction; 2418 } 2419 2420 if (obj->is<ArrayObject>()) { 2421 if (obj->as<ArrayObject>().length() > INT32_MAX) { 2422 return AttachDecision::NoAction; 2423 } 2424 2425 maybeEmitIdGuard(id); 2426 emitOptimisticClassGuard(objId, obj, GuardClassKind::Array); 2427 writer.loadInt32ArrayLengthResult(objId); 2428 writer.returnFromIC(); 2429 2430 trackAttached("GetProp.ArrayLength"); 2431 return AttachDecision::Attach; 2432 } 2433 2434 if (obj->is<ArgumentsObject>() && 2435 !obj->as<ArgumentsObject>().hasOverriddenLength()) { 2436 AssertArgumentsCustomDataProp(&obj->as<ArgumentsObject>(), id); 2437 maybeEmitIdGuard(id); 2438 if (obj->is<MappedArgumentsObject>()) { 2439 writer.guardClass(objId, GuardClassKind::MappedArguments); 2440 } else { 2441 MOZ_ASSERT(obj->is<UnmappedArgumentsObject>()); 2442 writer.guardClass(objId, GuardClassKind::UnmappedArguments); 2443 } 2444 writer.loadArgumentsObjectLengthResult(objId); 2445 writer.returnFromIC(); 2446 2447 trackAttached("GetProp.ArgumentsObjectLength"); 2448 return AttachDecision::Attach; 2449 } 2450 2451 return AttachDecision::NoAction; 2452 } 2453 2454 AttachDecision GetPropIRGenerator::tryAttachInlinableNativeGetter( 2455 Handle<NativeObject*> holder, PropertyInfo prop, ValOperandId receiverId) { 2456 MOZ_ASSERT(mode_ == ICState::Mode::Specialized); 2457 2458 // Suppress GC because |CacheIRWriter::trace()| doesn't yet support stub 2459 // field tracing and stub fields were already added for shape and prototype 2460 // guards. 2461 gc::AutoSuppressGC suppressGC(cx_); 2462 2463 Rooted<JSFunction*> target(cx_, &holder->getGetter(prop)->as<JSFunction>()); 2464 MOZ_ASSERT(target->isNativeWithoutJitEntry()); 2465 2466 Handle<Value> thisValue = receiverVal_; 2467 2468 bool isSpread = false; 2469 bool isSameRealm = cx_->realm() == target->realm(); 2470 bool isConstructing = false; 2471 CallFlags flags(isConstructing, isSpread, isSameRealm); 2472 2473 // Check for specific native-function optimizations. 2474 InlinableNativeIRGenerator nativeGen(*this, target, thisValue, flags, 2475 receiverId); 2476 return nativeGen.tryAttachStub(); 2477 } 2478 2479 AttachDecision GetPropIRGenerator::tryAttachFunction(HandleObject obj, 2480 ObjOperandId objId, 2481 HandleId id) { 2482 // Function properties are lazily resolved so they might not be defined yet. 2483 // And we might end up in a situation where we always have a fresh function 2484 // object during the IC generation. 2485 if (!obj->is<JSFunction>()) { 2486 return AttachDecision::NoAction; 2487 } 2488 2489 bool isLength = id.isAtom(cx_->names().length); 2490 if (!isLength && !id.isAtom(cx_->names().name)) { 2491 return AttachDecision::NoAction; 2492 } 2493 2494 NativeObject* holder = nullptr; 2495 PropertyResult prop; 2496 // If this property exists already, don't attach the stub. 2497 if (LookupPropertyPure(cx_, obj, id, &holder, &prop)) { 2498 return AttachDecision::NoAction; 2499 } 2500 2501 JSFunction* fun = &obj->as<JSFunction>(); 2502 2503 if (isLength) { 2504 // length was probably deleted from the function. 2505 if (fun->hasResolvedLength()) { 2506 return AttachDecision::NoAction; 2507 } 2508 2509 // Lazy functions don't store the length. 2510 if (!fun->hasBytecode()) { 2511 return AttachDecision::NoAction; 2512 } 2513 } else { 2514 // name was probably deleted from the function. 2515 if (fun->hasResolvedName()) { 2516 return AttachDecision::NoAction; 2517 } 2518 } 2519 2520 maybeEmitIdGuard(id); 2521 writer.guardClass(objId, GuardClassKind::JSFunction); 2522 if (isLength) { 2523 writer.loadFunctionLengthResult(objId); 2524 writer.returnFromIC(); 2525 trackAttached("GetProp.FunctionLength"); 2526 } else { 2527 writer.loadFunctionNameResult(objId); 2528 writer.returnFromIC(); 2529 trackAttached("GetProp.FunctionName"); 2530 } 2531 return AttachDecision::Attach; 2532 } 2533 2534 AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectIterator( 2535 HandleObject obj, ObjOperandId objId, HandleId id) { 2536 if (!obj->is<ArgumentsObject>()) { 2537 return AttachDecision::NoAction; 2538 } 2539 2540 if (!id.isWellKnownSymbol(JS::SymbolCode::iterator)) { 2541 return AttachDecision::NoAction; 2542 } 2543 2544 Handle<ArgumentsObject*> args = obj.as<ArgumentsObject>(); 2545 if (args->hasOverriddenIterator()) { 2546 return AttachDecision::NoAction; 2547 } 2548 if (cx_->realm() != args->realm()) { 2549 return AttachDecision::NoAction; 2550 } 2551 2552 AssertArgumentsCustomDataProp(args, id); 2553 2554 RootedValue iterator(cx_); 2555 if (!ArgumentsObject::getArgumentsIterator(cx_, &iterator)) { 2556 cx_->recoverFromOutOfMemory(); 2557 return AttachDecision::NoAction; 2558 } 2559 MOZ_ASSERT(iterator.isObject()); 2560 2561 maybeEmitIdGuard(id); 2562 if (args->is<MappedArgumentsObject>()) { 2563 writer.guardClass(objId, GuardClassKind::MappedArguments); 2564 } else { 2565 MOZ_ASSERT(args->is<UnmappedArgumentsObject>()); 2566 writer.guardClass(objId, GuardClassKind::UnmappedArguments); 2567 } 2568 uint32_t flags = ArgumentsObject::ITERATOR_OVERRIDDEN_BIT; 2569 writer.guardArgumentsObjectFlags(objId, flags); 2570 writer.guardObjectHasSameRealm(objId); 2571 2572 ObjOperandId iterId = writer.loadObject(&iterator.toObject()); 2573 writer.loadObjectResult(iterId); 2574 writer.returnFromIC(); 2575 2576 trackAttached("GetProp.ArgumentsObjectIterator"); 2577 return AttachDecision::Attach; 2578 } 2579 2580 AttachDecision GetPropIRGenerator::tryAttachModuleNamespace(HandleObject obj, 2581 ObjOperandId objId, 2582 HandleId id) { 2583 if (!obj->is<ModuleNamespaceObject>()) { 2584 return AttachDecision::NoAction; 2585 } 2586 2587 auto* ns = &obj->as<ModuleNamespaceObject>(); 2588 ModuleEnvironmentObject* env = nullptr; 2589 Maybe<PropertyInfo> prop; 2590 if (!ns->bindings().lookup(id, &env, &prop)) { 2591 return AttachDecision::NoAction; 2592 } 2593 2594 // Don't emit a stub until the target binding has been initialized. 2595 if (env->getSlot(prop->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) { 2596 return AttachDecision::NoAction; 2597 } 2598 2599 // Check for the specific namespace object. 2600 maybeEmitIdGuard(id); 2601 writer.guardSpecificObject(objId, ns); 2602 2603 ObjOperandId envId = writer.loadObject(env); 2604 EmitLoadSlotResult(writer, envId, env, *prop); 2605 writer.returnFromIC(); 2606 2607 trackAttached("GetProp.ModuleNamespace"); 2608 return AttachDecision::Attach; 2609 } 2610 2611 AttachDecision GetPropIRGenerator::tryAttachPrimitive(ValOperandId valId, 2612 HandleId id) { 2613 MOZ_ASSERT(!isSuper(), "SuperBase is guaranteed to be an object"); 2614 2615 JSProtoKey protoKey; 2616 switch (val_.type()) { 2617 case ValueType::String: 2618 if (id.isAtom(cx_->names().length)) { 2619 // String length is special-cased, see js::GetProperty. 2620 return AttachDecision::NoAction; 2621 } 2622 protoKey = JSProto_String; 2623 break; 2624 case ValueType::Int32: 2625 case ValueType::Double: 2626 protoKey = JSProto_Number; 2627 break; 2628 case ValueType::Boolean: 2629 protoKey = JSProto_Boolean; 2630 break; 2631 case ValueType::Symbol: 2632 protoKey = JSProto_Symbol; 2633 break; 2634 case ValueType::BigInt: 2635 protoKey = JSProto_BigInt; 2636 break; 2637 case ValueType::Null: 2638 case ValueType::Undefined: 2639 case ValueType::Magic: 2640 return AttachDecision::NoAction; 2641 case ValueType::Object: 2642 case ValueType::PrivateGCThing: 2643 MOZ_CRASH("unexpected type"); 2644 } 2645 2646 JSObject* proto = GlobalObject::getOrCreatePrototype(cx_, protoKey); 2647 if (!proto) { 2648 cx_->recoverFromOutOfMemory(); 2649 return AttachDecision::NoAction; 2650 } 2651 2652 Rooted<NativeObject*> holder(cx_); 2653 Maybe<PropertyInfo> prop; 2654 NativeGetPropKind kind = 2655 CanAttachNativeGetProp(cx_, proto, id, holder.address(), &prop, pc_); 2656 if (kind == NativeGetPropKind::None) { 2657 return AttachDecision::NoAction; 2658 } 2659 2660 if (val_.isNumber()) { 2661 writer.guardIsNumber(valId); 2662 } else { 2663 writer.guardNonDoubleType(valId, val_.type()); 2664 } 2665 maybeEmitIdGuard(id); 2666 2667 Rooted<NativeObject*> nproto(cx_, &proto->as<NativeObject>()); 2668 ObjOperandId protoId = writer.loadObject(nproto); 2669 2670 switch (kind) { 2671 case NativeGetPropKind::Missing: { 2672 EmitMissingPropResult(writer, nproto, protoId); 2673 writer.returnFromIC(); 2674 2675 trackAttached("GetProp.PrimitiveMissing"); 2676 return AttachDecision::Attach; 2677 } 2678 case NativeGetPropKind::Slot: { 2679 emitLoadDataPropertyResult(nproto, holder, id, *prop, protoId); 2680 writer.returnFromIC(); 2681 2682 trackAttached("GetProp.PrimitiveSlot"); 2683 return AttachDecision::Attach; 2684 } 2685 case NativeGetPropKind::ScriptedGetter: 2686 case NativeGetPropKind::NativeGetter: { 2687 emitCallGetterResult(kind, nproto, holder, id, *prop, protoId, valId); 2688 2689 trackAttached("GetProp.PrimitiveGetter"); 2690 return AttachDecision::Attach; 2691 } 2692 case NativeGetPropKind::None: 2693 break; 2694 } 2695 2696 MOZ_CRASH("Bad NativeGetPropKind"); 2697 } 2698 2699 AttachDecision GetPropIRGenerator::tryAttachStringLength(ValOperandId valId, 2700 HandleId id) { 2701 if (!val_.isString() || !id.isAtom(cx_->names().length)) { 2702 return AttachDecision::NoAction; 2703 } 2704 2705 StringOperandId strId = writer.guardToString(valId); 2706 maybeEmitIdGuard(id); 2707 writer.loadStringLengthResult(strId); 2708 writer.returnFromIC(); 2709 2710 trackAttached("GetProp.StringLength"); 2711 return AttachDecision::Attach; 2712 } 2713 2714 enum class AttachStringChar { No, Yes, Linearize, OutOfBounds }; 2715 2716 static AttachStringChar CanAttachStringChar(const Value& val, 2717 const Value& idVal, 2718 StringChar kind) { 2719 if (!val.isString() || !idVal.isInt32()) { 2720 return AttachStringChar::No; 2721 } 2722 2723 JSString* str = val.toString(); 2724 int32_t index = idVal.toInt32(); 2725 2726 if (index < 0 && kind == StringChar::At) { 2727 static_assert(JSString::MAX_LENGTH <= INT32_MAX, 2728 "string length fits in int32"); 2729 index += int32_t(str->length()); 2730 } 2731 2732 if (index < 0 || size_t(index) >= str->length()) { 2733 return AttachStringChar::OutOfBounds; 2734 } 2735 2736 // This follows JSString::getChar and MacroAssembler::loadStringChar. 2737 if (str->isRope()) { 2738 JSRope* rope = &str->asRope(); 2739 if (size_t(index) < rope->leftChild()->length()) { 2740 str = rope->leftChild(); 2741 2742 // MacroAssembler::loadStringChar doesn't support surrogate pairs which 2743 // are split between the left and right child of a rope. 2744 if (kind == StringChar::CodePointAt && 2745 size_t(index) + 1 == str->length() && str->isLinear()) { 2746 // Linearize the string when the last character of the left child is a 2747 // a lead surrogate. 2748 char16_t ch = str->asLinear().latin1OrTwoByteChar(index); 2749 if (unicode::IsLeadSurrogate(ch)) { 2750 return AttachStringChar::Linearize; 2751 } 2752 } 2753 } else { 2754 str = rope->rightChild(); 2755 } 2756 } 2757 2758 if (!str->isLinear()) { 2759 return AttachStringChar::Linearize; 2760 } 2761 2762 return AttachStringChar::Yes; 2763 } 2764 2765 static Int32OperandId EmitGuardToInt32Index(CacheIRWriter& writer, 2766 const Value& index, 2767 ValOperandId indexId) { 2768 if (index.isInt32()) { 2769 return writer.guardToInt32(indexId); 2770 } 2771 MOZ_ASSERT(index.isDouble()); 2772 return writer.guardToInt32Index(indexId); 2773 } 2774 2775 AttachDecision GetPropIRGenerator::tryAttachStringChar(ValOperandId valId, 2776 ValOperandId indexId) { 2777 MOZ_ASSERT(idVal_.isInt32()); 2778 2779 auto attach = CanAttachStringChar(val_, idVal_, StringChar::CharAt); 2780 if (attach == AttachStringChar::No) { 2781 return AttachDecision::NoAction; 2782 } 2783 2784 // Can't attach for out-of-bounds access without guarding that indexed 2785 // properties aren't present along the prototype chain of |String.prototype|. 2786 if (attach == AttachStringChar::OutOfBounds) { 2787 return AttachDecision::NoAction; 2788 } 2789 2790 StringOperandId strId = writer.guardToString(valId); 2791 Int32OperandId int32IndexId = EmitGuardToInt32Index(writer, idVal_, indexId); 2792 if (attach == AttachStringChar::Linearize) { 2793 strId = writer.linearizeForCharAccess(strId, int32IndexId); 2794 } 2795 writer.loadStringCharResult(strId, int32IndexId, /* handleOOB = */ false); 2796 writer.returnFromIC(); 2797 2798 trackAttached("GetProp.StringChar"); 2799 return AttachDecision::Attach; 2800 } 2801 2802 static bool ClassCanHaveExtraProperties(const JSClass* clasp) { 2803 return clasp->getResolve() || clasp->getOpsLookupProperty() || 2804 clasp->getOpsGetProperty() || IsTypedArrayClass(clasp); 2805 } 2806 2807 enum class OwnProperty : bool { No, Yes }; 2808 enum class AllowIndexedReceiver : bool { No, Yes }; 2809 enum class AllowExtraReceiverProperties : bool { No, Yes }; 2810 2811 static bool CanAttachDenseElementHole( 2812 NativeObject* obj, OwnProperty ownProp, 2813 AllowIndexedReceiver allowIndexedReceiver = AllowIndexedReceiver::No, 2814 AllowExtraReceiverProperties allowExtraReceiverProperties = 2815 AllowExtraReceiverProperties::No) { 2816 // Make sure the objects on the prototype don't have any indexed properties 2817 // or that such properties can't appear without a shape change. 2818 // Otherwise returning undefined for holes would obviously be incorrect, 2819 // because we would have to lookup a property on the prototype instead. 2820 do { 2821 // The first two checks are also relevant to the receiver object. 2822 if (allowIndexedReceiver == AllowIndexedReceiver::No && obj->isIndexed()) { 2823 return false; 2824 } 2825 allowIndexedReceiver = AllowIndexedReceiver::No; 2826 2827 if (allowExtraReceiverProperties == AllowExtraReceiverProperties::No && 2828 ClassCanHaveExtraProperties(obj->getClass())) { 2829 return false; 2830 } 2831 allowExtraReceiverProperties = AllowExtraReceiverProperties::No; 2832 2833 // Don't need to check prototype for OwnProperty checks 2834 if (ownProp == OwnProperty::Yes) { 2835 return true; 2836 } 2837 2838 JSObject* proto = obj->staticPrototype(); 2839 if (!proto) { 2840 break; 2841 } 2842 2843 if (!proto->is<NativeObject>()) { 2844 return false; 2845 } 2846 2847 // Make sure objects on the prototype don't have dense elements. 2848 if (proto->as<NativeObject>().getDenseInitializedLength() != 0) { 2849 return false; 2850 } 2851 2852 obj = &proto->as<NativeObject>(); 2853 } while (true); 2854 2855 return true; 2856 } 2857 2858 AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectArg( 2859 HandleObject obj, ObjOperandId objId, uint32_t index, 2860 Int32OperandId indexId) { 2861 if (!obj->is<ArgumentsObject>()) { 2862 return AttachDecision::NoAction; 2863 } 2864 auto* args = &obj->as<ArgumentsObject>(); 2865 2866 // No elements must have been overridden or deleted. 2867 if (args->hasOverriddenElement()) { 2868 return AttachDecision::NoAction; 2869 } 2870 2871 // Check bounds. 2872 if (index >= args->initialLength()) { 2873 return AttachDecision::NoAction; 2874 } 2875 2876 AssertArgumentsCustomDataProp(args, PropertyKey::Int(index)); 2877 2878 // And finally also check that the argument isn't forwarded. 2879 if (args->argIsForwarded(index)) { 2880 return AttachDecision::NoAction; 2881 } 2882 2883 if (args->is<MappedArgumentsObject>()) { 2884 writer.guardClass(objId, GuardClassKind::MappedArguments); 2885 } else { 2886 MOZ_ASSERT(args->is<UnmappedArgumentsObject>()); 2887 writer.guardClass(objId, GuardClassKind::UnmappedArguments); 2888 } 2889 2890 writer.loadArgumentsObjectArgResult(objId, indexId); 2891 writer.returnFromIC(); 2892 2893 trackAttached("GetProp.ArgumentsObjectArg"); 2894 return AttachDecision::Attach; 2895 } 2896 2897 AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectArgHole( 2898 HandleObject obj, ObjOperandId objId, uint32_t index, 2899 Int32OperandId indexId) { 2900 if (!obj->is<ArgumentsObject>()) { 2901 return AttachDecision::NoAction; 2902 } 2903 auto* args = &obj->as<ArgumentsObject>(); 2904 2905 // No elements must have been overridden or deleted. 2906 if (args->hasOverriddenElement()) { 2907 return AttachDecision::NoAction; 2908 } 2909 2910 // And also check that the argument isn't forwarded. 2911 if (index < args->initialLength() && args->argIsForwarded(index)) { 2912 return AttachDecision::NoAction; 2913 } 2914 2915 if (!CanAttachDenseElementHole(args, OwnProperty::No, 2916 AllowIndexedReceiver::Yes, 2917 AllowExtraReceiverProperties::Yes)) { 2918 return AttachDecision::NoAction; 2919 } 2920 2921 // We don't need to guard on the shape, because we check if any element is 2922 // overridden. Elements are marked as overridden iff any element is defined, 2923 // irrespective of whether the element is in-bounds or out-of-bounds. So when 2924 // that flag isn't set, we can guarantee that the arguments object doesn't 2925 // have any additional own elements. 2926 2927 if (args->is<MappedArgumentsObject>()) { 2928 writer.guardClass(objId, GuardClassKind::MappedArguments); 2929 } else { 2930 MOZ_ASSERT(args->is<UnmappedArgumentsObject>()); 2931 writer.guardClass(objId, GuardClassKind::UnmappedArguments); 2932 } 2933 2934 GeneratePrototypeHoleGuards(writer, args, objId, 2935 /* alwaysGuardFirstProto = */ true); 2936 2937 writer.loadArgumentsObjectArgHoleResult(objId, indexId); 2938 writer.returnFromIC(); 2939 2940 trackAttached("GetProp.ArgumentsObjectArgHole"); 2941 return AttachDecision::Attach; 2942 } 2943 2944 AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectCallee( 2945 HandleObject obj, ObjOperandId objId, HandleId id) { 2946 // Only mapped arguments objects have a `callee` property. 2947 if (!obj->is<MappedArgumentsObject>()) { 2948 return AttachDecision::NoAction; 2949 } 2950 2951 if (!id.isAtom(cx_->names().callee)) { 2952 return AttachDecision::NoAction; 2953 } 2954 2955 // The callee must not have been overridden or deleted. 2956 MappedArgumentsObject* args = &obj->as<MappedArgumentsObject>(); 2957 if (args->hasOverriddenCallee()) { 2958 return AttachDecision::NoAction; 2959 } 2960 2961 AssertArgumentsCustomDataProp(args, id); 2962 2963 maybeEmitIdGuard(id); 2964 writer.guardClass(objId, GuardClassKind::MappedArguments); 2965 2966 uint32_t flags = ArgumentsObject::CALLEE_OVERRIDDEN_BIT; 2967 writer.guardArgumentsObjectFlags(objId, flags); 2968 2969 writer.loadFixedSlotResult(objId, 2970 MappedArgumentsObject::getCalleeSlotOffset()); 2971 writer.returnFromIC(); 2972 2973 trackAttached("GetProp.ArgumentsObjectCallee"); 2974 return AttachDecision::Attach; 2975 } 2976 2977 AttachDecision GetPropIRGenerator::tryAttachDenseElement( 2978 HandleObject obj, ObjOperandId objId, uint32_t index, 2979 Int32OperandId indexId) { 2980 if (!obj->is<NativeObject>()) { 2981 return AttachDecision::NoAction; 2982 } 2983 2984 NativeObject* nobj = &obj->as<NativeObject>(); 2985 if (!nobj->containsDenseElement(index)) { 2986 return AttachDecision::NoAction; 2987 } 2988 2989 if (mode_ == ICState::Mode::Megamorphic) { 2990 writer.guardIsNativeObject(objId); 2991 } else { 2992 TestMatchingNativeReceiver(writer, nobj, objId); 2993 } 2994 bool expectPackedElements = nobj->denseElementsArePacked(); 2995 writer.loadDenseElementResult(objId, indexId, expectPackedElements); 2996 writer.returnFromIC(); 2997 2998 trackAttached("GetProp.DenseElement"); 2999 return AttachDecision::Attach; 3000 } 3001 3002 AttachDecision GetPropIRGenerator::tryAttachDenseElementHole( 3003 HandleObject obj, ObjOperandId objId, uint32_t index, 3004 Int32OperandId indexId) { 3005 if (!obj->is<NativeObject>()) { 3006 return AttachDecision::NoAction; 3007 } 3008 3009 NativeObject* nobj = &obj->as<NativeObject>(); 3010 if (nobj->containsDenseElement(index)) { 3011 return AttachDecision::NoAction; 3012 } 3013 if (!CanAttachDenseElementHole(nobj, OwnProperty::No)) { 3014 return AttachDecision::NoAction; 3015 } 3016 3017 // Guard on the shape, to prevent non-dense elements from appearing. 3018 TestMatchingNativeReceiver(writer, nobj, objId); 3019 GeneratePrototypeHoleGuards(writer, nobj, objId, 3020 /* alwaysGuardFirstProto = */ false); 3021 writer.loadDenseElementHoleResult(objId, indexId); 3022 writer.returnFromIC(); 3023 3024 trackAttached("GetProp.DenseElementHole"); 3025 return AttachDecision::Attach; 3026 } 3027 3028 AttachDecision GetPropIRGenerator::tryAttachSparseElement( 3029 HandleObject obj, ObjOperandId objId, uint32_t index, 3030 Int32OperandId indexId) { 3031 if (!obj->is<NativeObject>()) { 3032 return AttachDecision::NoAction; 3033 } 3034 NativeObject* nobj = &obj->as<NativeObject>(); 3035 3036 // Stub doesn't handle negative indices. 3037 if (index > INT32_MAX) { 3038 return AttachDecision::NoAction; 3039 } 3040 3041 // The object must have sparse elements. 3042 if (!nobj->isIndexed()) { 3043 return AttachDecision::NoAction; 3044 } 3045 3046 // The index must not be for a dense element. 3047 if (nobj->containsDenseElement(index)) { 3048 return AttachDecision::NoAction; 3049 } 3050 3051 // Only handle ArrayObject and PlainObject in this stub. 3052 if (!nobj->is<ArrayObject>() && !nobj->is<PlainObject>()) { 3053 return AttachDecision::NoAction; 3054 } 3055 3056 // GetSparseElementHelper assumes that the target and the receiver 3057 // are the same. 3058 if (isSuper()) { 3059 return AttachDecision::NoAction; 3060 } 3061 3062 // Here, we ensure that the prototype chain does not define any sparse 3063 // indexed properties on the shape lineage. This allows us to guard on 3064 // the shapes up the prototype chain to ensure that no indexed properties 3065 // exist outside of the dense elements. 3066 // 3067 // The `GeneratePrototypeHoleGuards` call below will guard on the shapes, 3068 // as well as ensure that no prototypes contain dense elements, allowing 3069 // us to perform a pure shape-search for out-of-bounds integer-indexed 3070 // properties on the receiver object. 3071 if (PrototypeMayHaveIndexedProperties(nobj)) { 3072 return AttachDecision::NoAction; 3073 } 3074 3075 // Ensure that obj is an ArrayObject or PlainObject. 3076 if (nobj->is<ArrayObject>()) { 3077 writer.guardClass(objId, GuardClassKind::Array); 3078 } else { 3079 MOZ_ASSERT(nobj->is<PlainObject>()); 3080 writer.guardClass(objId, GuardClassKind::PlainObject); 3081 } 3082 3083 // The helper we are going to call only applies to non-dense elements. 3084 writer.guardIndexIsNotDenseElement(objId, indexId); 3085 3086 // Ensures we are able to efficiently able to map to an integral jsid. 3087 writer.guardInt32IsNonNegative(indexId); 3088 3089 // Shape guard the prototype chain to avoid shadowing indexes from appearing. 3090 // The helper function also ensures that the index does not appear within the 3091 // dense element set of the prototypes. 3092 GeneratePrototypeHoleGuards(writer, nobj, objId, 3093 /* alwaysGuardFirstProto = */ true); 3094 3095 // At this point, we are guaranteed that the indexed property will not 3096 // be found on one of the prototypes. We are assured that we only have 3097 // to check that the receiving object has the property. 3098 3099 writer.callGetSparseElementResult(objId, indexId); 3100 writer.returnFromIC(); 3101 3102 trackAttached("GetProp.SparseElement"); 3103 return AttachDecision::Attach; 3104 } 3105 3106 // For Uint32Array we let the stub return an Int32 if we have not seen a 3107 // double, to allow better codegen in Warp while avoiding bailout loops. 3108 static bool ForceDoubleForUint32Array(TypedArrayObject* tarr, uint64_t index) { 3109 MOZ_ASSERT(index < tarr->length().valueOr(0)); 3110 3111 if (tarr->type() != Scalar::Type::Uint32) { 3112 // Return value is only relevant for Uint32Array. 3113 return false; 3114 } 3115 3116 Value res; 3117 MOZ_ALWAYS_TRUE(tarr->getElementPure(index, &res)); 3118 MOZ_ASSERT(res.isNumber()); 3119 return res.isDouble(); 3120 } 3121 3122 static ArrayBufferViewKind ToArrayBufferViewKind(const TypedArrayObject* obj) { 3123 if (obj->is<FixedLengthTypedArrayObject>()) { 3124 return ArrayBufferViewKind::FixedLength; 3125 } 3126 3127 if (obj->is<ImmutableTypedArrayObject>()) { 3128 return ArrayBufferViewKind::Immutable; 3129 } 3130 3131 MOZ_ASSERT(obj->is<ResizableTypedArrayObject>()); 3132 return ArrayBufferViewKind::Resizable; 3133 } 3134 3135 static ArrayBufferViewKind ToArrayBufferViewKind(const DataViewObject* obj) { 3136 if (obj->is<FixedLengthDataViewObject>()) { 3137 return ArrayBufferViewKind::FixedLength; 3138 } 3139 3140 if (obj->is<ImmutableDataViewObject>()) { 3141 return ArrayBufferViewKind::Immutable; 3142 } 3143 3144 MOZ_ASSERT(obj->is<ResizableDataViewObject>()); 3145 return ArrayBufferViewKind::Resizable; 3146 } 3147 3148 AttachDecision GetPropIRGenerator::tryAttachTypedArrayElement( 3149 HandleObject obj, ObjOperandId objId) { 3150 if (!obj->is<TypedArrayObject>()) { 3151 return AttachDecision::NoAction; 3152 } 3153 3154 if (!idVal_.isNumber()) { 3155 return AttachDecision::NoAction; 3156 } 3157 3158 auto* tarr = &obj->as<TypedArrayObject>(); 3159 3160 bool handleOOB = false; 3161 int64_t indexInt64; 3162 if (!ValueIsInt64Index(idVal_, &indexInt64) || indexInt64 < 0 || 3163 uint64_t(indexInt64) >= tarr->length().valueOr(0)) { 3164 handleOOB = true; 3165 } 3166 3167 // If the number is not representable as an integer the result will be 3168 // |undefined| so we leave |forceDoubleForUint32| as false. 3169 bool forceDoubleForUint32 = false; 3170 if (!handleOOB) { 3171 uint64_t index = uint64_t(indexInt64); 3172 forceDoubleForUint32 = ForceDoubleForUint32Array(tarr, index); 3173 } 3174 3175 writer.guardShapeForClass(objId, tarr->shape()); 3176 3177 ValOperandId keyId = getElemKeyValueId(); 3178 IntPtrOperandId intPtrIndexId = guardToIntPtrIndex(idVal_, keyId, handleOOB); 3179 3180 auto viewKind = ToArrayBufferViewKind(tarr); 3181 writer.loadTypedArrayElementResult(objId, intPtrIndexId, tarr->type(), 3182 handleOOB, forceDoubleForUint32, viewKind); 3183 writer.returnFromIC(); 3184 3185 trackAttached("GetProp.TypedElement"); 3186 return AttachDecision::Attach; 3187 } 3188 3189 AttachDecision GetPropIRGenerator::tryAttachGenericElement( 3190 HandleObject obj, ObjOperandId objId, uint32_t index, 3191 Int32OperandId indexId, ValOperandId receiverId) { 3192 if (!obj->is<NativeObject>()) { 3193 return AttachDecision::NoAction; 3194 } 3195 3196 #ifdef JS_CODEGEN_X86 3197 if (isSuper()) { 3198 // There aren't enough registers available on x86. 3199 return AttachDecision::NoAction; 3200 } 3201 #endif 3202 3203 // To allow other types to attach in the non-megamorphic case we test the 3204 // specific matching native receiver; however, once megamorphic we can attach 3205 // for any native 3206 if (mode_ == ICState::Mode::Megamorphic) { 3207 writer.guardIsNativeObject(objId); 3208 } else { 3209 NativeObject* nobj = &obj->as<NativeObject>(); 3210 TestMatchingNativeReceiver(writer, nobj, objId); 3211 } 3212 writer.guardIndexIsNotDenseElement(objId, indexId); 3213 if (isSuper()) { 3214 writer.callNativeGetElementSuperResult(objId, indexId, receiverId); 3215 } else { 3216 writer.callNativeGetElementResult(objId, indexId); 3217 } 3218 writer.returnFromIC(); 3219 3220 trackAttached(mode_ == ICState::Mode::Megamorphic 3221 ? "GenericElementMegamorphic" 3222 : "GenericElement"); 3223 return AttachDecision::Attach; 3224 } 3225 3226 AttachDecision GetPropIRGenerator::tryAttachProxyElement(HandleObject obj, 3227 ObjOperandId objId) { 3228 if (!obj->is<ProxyObject>()) { 3229 return AttachDecision::NoAction; 3230 } 3231 3232 // The proxy stubs don't currently support |super| access. 3233 if (isSuper()) { 3234 return AttachDecision::NoAction; 3235 } 3236 3237 #ifdef JS_PUNBOX64 3238 auto proxy = obj.as<ProxyObject>(); 3239 if (proxy->handler()->isScripted()) { 3240 TRY_ATTACH(tryAttachScriptedProxy(proxy, objId, JS::VoidHandlePropertyKey)); 3241 } 3242 #endif 3243 3244 writer.guardIsProxy(objId); 3245 3246 // We are not guarding against DOM proxies here, because there is no other 3247 // specialized DOM IC we could attach. 3248 // We could call maybeEmitIdGuard here and then emit ProxyGetResult, 3249 // but for GetElem we prefer to attach a stub that can handle any Value 3250 // so we don't attach a new stub for every id. 3251 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem); 3252 MOZ_ASSERT(!isSuper()); 3253 writer.proxyGetByValueResult(objId, getElemKeyValueId()); 3254 writer.returnFromIC(); 3255 3256 trackAttached("GetProp.ProxyElement"); 3257 return AttachDecision::Attach; 3258 } 3259 3260 void GetPropIRGenerator::trackAttached(const char* name) { 3261 stubName_ = name ? name : "NotAttached"; 3262 #ifdef JS_CACHEIR_SPEW 3263 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { 3264 sp.valueProperty("base", val_); 3265 sp.valueProperty("property", idVal_); 3266 } 3267 #endif 3268 } 3269 3270 void IRGenerator::emitIdGuard(ValOperandId valId, const Value& idVal, jsid id) { 3271 if (id.isSymbol()) { 3272 MOZ_ASSERT(idVal.toSymbol() == id.toSymbol()); 3273 SymbolOperandId symId = writer.guardToSymbol(valId); 3274 writer.guardSpecificSymbol(symId, id.toSymbol()); 3275 return; 3276 } 3277 3278 MOZ_ASSERT(id.isAtom()); 3279 switch (idVal.type()) { 3280 case ValueType::String: { 3281 StringOperandId strId = writer.guardToString(valId); 3282 writer.guardSpecificAtom(strId, id.toAtom()); 3283 break; 3284 } 3285 case ValueType::Null: 3286 MOZ_ASSERT(id.isAtom(cx_->names().null)); 3287 writer.guardIsNull(valId); 3288 break; 3289 case ValueType::Undefined: 3290 MOZ_ASSERT(id.isAtom(cx_->names().undefined)); 3291 writer.guardIsUndefined(valId); 3292 break; 3293 case ValueType::Boolean: 3294 MOZ_ASSERT(id.isAtom(cx_->names().true_) || 3295 id.isAtom(cx_->names().false_)); 3296 writer.guardSpecificValue(valId, idVal); 3297 break; 3298 case ValueType::Int32: 3299 case ValueType::Double: 3300 MOZ_ASSERT(!IsNumberIndex(idVal)); 3301 writer.guardSpecificValue(valId, idVal); 3302 break; 3303 default: 3304 MOZ_CRASH("Unexpected type in emitIdGuard"); 3305 } 3306 } 3307 3308 void GetPropIRGenerator::maybeEmitIdGuard(jsid id) { 3309 if (cacheKind_ == CacheKind::GetProp || 3310 cacheKind_ == CacheKind::GetPropSuper) { 3311 // Constant PropertyName, no guards necessary. 3312 MOZ_ASSERT(&idVal_.toString()->asAtom() == id.toAtom()); 3313 return; 3314 } 3315 3316 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem || 3317 cacheKind_ == CacheKind::GetElemSuper); 3318 emitIdGuard(getElemKeyValueId(), idVal_, id); 3319 } 3320 3321 void SetPropIRGenerator::maybeEmitIdGuard(jsid id) { 3322 if (cacheKind_ == CacheKind::SetProp) { 3323 // Constant PropertyName, no guards necessary. 3324 MOZ_ASSERT(&idVal_.toString()->asAtom() == id.toAtom()); 3325 return; 3326 } 3327 3328 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem); 3329 emitIdGuard(setElemKeyValueId(), idVal_, id); 3330 } 3331 3332 GetNameIRGenerator::GetNameIRGenerator(JSContext* cx, HandleScript script, 3333 jsbytecode* pc, ICState state, 3334 HandleObject env, 3335 Handle<PropertyName*> name) 3336 : IRGenerator(cx, script, pc, CacheKind::GetName, state), 3337 env_(env), 3338 name_(name) {} 3339 3340 AttachDecision GetNameIRGenerator::tryAttachStub() { 3341 MOZ_ASSERT(cacheKind_ == CacheKind::GetName); 3342 3343 AutoAssertNoPendingException aanpe(cx_); 3344 3345 ObjOperandId envId(writer.setInputOperandId(0)); 3346 RootedId id(cx_, NameToId(name_)); 3347 3348 TRY_ATTACH(tryAttachGlobalNameValue(envId, id)); 3349 TRY_ATTACH(tryAttachGlobalNameGetter(envId, id)); 3350 TRY_ATTACH(tryAttachEnvironmentName(envId, id)); 3351 3352 trackAttached(IRGenerator::NotAttached); 3353 return AttachDecision::NoAction; 3354 } 3355 3356 static bool CanAttachGlobalName(JSContext* cx, 3357 GlobalLexicalEnvironmentObject* globalLexical, 3358 PropertyKey id, NativeObject** holder, 3359 Maybe<PropertyInfo>* prop) { 3360 // The property must be found, and it must be found as a normal data property. 3361 NativeObject* current = globalLexical; 3362 while (true) { 3363 *prop = current->lookup(cx, id); 3364 if (prop->isSome()) { 3365 break; 3366 } 3367 3368 if (current == globalLexical) { 3369 current = &globalLexical->global(); 3370 } else { 3371 // In the browser the global prototype chain should be immutable. 3372 if (!current->staticPrototypeIsImmutable()) { 3373 return false; 3374 } 3375 3376 JSObject* proto = current->staticPrototype(); 3377 if (!proto || !proto->is<NativeObject>()) { 3378 return false; 3379 } 3380 3381 current = &proto->as<NativeObject>(); 3382 } 3383 } 3384 3385 *holder = current; 3386 return true; 3387 } 3388 3389 AttachDecision GetNameIRGenerator::tryAttachGlobalNameValue(ObjOperandId objId, 3390 HandleId id) { 3391 if (!IsGlobalOp(JSOp(*pc_))) { 3392 return AttachDecision::NoAction; 3393 } 3394 MOZ_ASSERT(!script_->hasNonSyntacticScope()); 3395 3396 auto* globalLexical = &env_->as<GlobalLexicalEnvironmentObject>(); 3397 3398 NativeObject* holder = nullptr; 3399 Maybe<PropertyInfo> prop; 3400 if (!CanAttachGlobalName(cx_, globalLexical, id, &holder, &prop)) { 3401 return AttachDecision::NoAction; 3402 } 3403 3404 // The property must be found, and it must be found as a normal data property. 3405 if (!prop->isDataProperty()) { 3406 return AttachDecision::NoAction; 3407 } 3408 3409 // This might still be an uninitialized lexical. 3410 if (holder->getSlot(prop->slot()).isMagic()) { 3411 return AttachDecision::NoAction; 3412 } 3413 3414 if (holder == globalLexical) { 3415 // There is no need to guard on the shape. Lexical bindings are 3416 // non-configurable, and this stub cannot be shared across globals. 3417 ObjectFuse* objFuse = nullptr; 3418 if (canOptimizeConstantDataProperty(holder, *prop, &objFuse)) { 3419 emitConstantDataPropertyResult(holder, objId, id, *prop, objFuse); 3420 } else { 3421 size_t dynamicSlotOffset = 3422 holder->dynamicSlotIndex(prop->slot()) * sizeof(Value); 3423 writer.loadDynamicSlotResult(objId, dynamicSlotOffset); 3424 } 3425 } else if (holder == &globalLexical->global()) { 3426 MOZ_ASSERT(globalLexical->global().isGenerationCountedGlobal()); 3427 ObjectFuse* objFuse = nullptr; 3428 if (canOptimizeConstantDataProperty(holder, *prop, &objFuse)) { 3429 ObjOperandId holderId = writer.loadObject(holder); 3430 emitConstantDataPropertyResult(holder, holderId, id, *prop, objFuse); 3431 } else { 3432 writer.guardGlobalGeneration( 3433 globalLexical->global().generationCount(), 3434 globalLexical->global().addressOfGenerationCount()); 3435 ObjOperandId holderId = writer.loadObject(holder); 3436 #ifdef DEBUG 3437 writer.assertPropertyLookup(holderId, id, prop->slot()); 3438 #endif 3439 EmitLoadSlotResult(writer, holderId, holder, *prop); 3440 } 3441 } else { 3442 // Check the prototype chain from the global to the holder 3443 // prototype. Ignore the global lexical scope as it doesn't figure 3444 // into the prototype chain. We guard on the global lexical 3445 // scope's shape independently. 3446 if (!IsCacheableGetPropSlot(&globalLexical->global(), holder, *prop)) { 3447 return AttachDecision::NoAction; 3448 } 3449 3450 // Shape guard for global lexical. 3451 writer.guardShape(objId, globalLexical->shape()); 3452 3453 // Guard on the shape of the GlobalObject. 3454 ObjOperandId globalId = writer.loadObject(&globalLexical->global()); 3455 writer.guardShape(globalId, globalLexical->global().shape()); 3456 3457 // Shape guard holder. 3458 ObjOperandId holderId = writer.loadObject(holder); 3459 writer.guardShape(holderId, holder->shape()); 3460 3461 EmitLoadSlotResult(writer, holderId, holder, *prop); 3462 } 3463 3464 writer.returnFromIC(); 3465 3466 trackAttached("GetName.GlobalNameValue"); 3467 return AttachDecision::Attach; 3468 } 3469 3470 AttachDecision GetNameIRGenerator::tryAttachGlobalNameGetter(ObjOperandId objId, 3471 HandleId id) { 3472 if (!IsGlobalOp(JSOp(*pc_))) { 3473 return AttachDecision::NoAction; 3474 } 3475 MOZ_ASSERT(!script_->hasNonSyntacticScope()); 3476 3477 Handle<GlobalLexicalEnvironmentObject*> globalLexical = 3478 env_.as<GlobalLexicalEnvironmentObject>(); 3479 MOZ_ASSERT(globalLexical->isGlobal()); 3480 3481 NativeObject* holder = nullptr; 3482 Maybe<PropertyInfo> prop; 3483 if (!CanAttachGlobalName(cx_, globalLexical, id, &holder, &prop)) { 3484 return AttachDecision::NoAction; 3485 } 3486 3487 if (holder == globalLexical) { 3488 return AttachDecision::NoAction; 3489 } 3490 3491 GlobalObject* global = &globalLexical->global(); 3492 3493 NativeGetPropKind kind = IsCacheableGetPropCall(global, holder, *prop, pc_); 3494 if (kind != NativeGetPropKind::NativeGetter && 3495 kind != NativeGetPropKind::ScriptedGetter) { 3496 return AttachDecision::NoAction; 3497 } 3498 3499 bool needsWindowProxy = 3500 IsWindow(global) && GetterNeedsWindowProxyThis(holder, *prop); 3501 3502 ObjOperandId globalId; 3503 ObjectFuse* objFuse = nullptr; 3504 if (holder == global && 3505 canOptimizeConstantAccessorProperty(global, *prop, &objFuse)) { 3506 globalId = writer.loadObject(global); 3507 emitGuardConstantAccessorProperty(global, globalId, id, *prop, objFuse); 3508 } else { 3509 // Shape guard for global lexical. 3510 writer.guardShape(objId, globalLexical->shape()); 3511 3512 // Guard on the shape of the GlobalObject. 3513 globalId = writer.loadEnclosingEnvironment(objId); 3514 writer.guardShape(globalId, global->shape()); 3515 3516 if (holder != global) { 3517 // Shape guard holder. 3518 ObjOperandId holderId = writer.loadObject(holder); 3519 writer.guardShape(holderId, holder->shape()); 3520 emitGuardGetterSetterSlot(holder, *prop, holderId, AccessorKind::Getter, 3521 /* holderIsConstant = */ true); 3522 } else { 3523 // Note: pass true for |holderIsConstant| because the holder must be the 3524 // current global object. 3525 emitGuardGetterSetterSlot(holder, *prop, globalId, AccessorKind::Getter, 3526 /* holderIsConstant = */ true); 3527 } 3528 } 3529 3530 if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, global, holder, *prop, 3531 mode_)) { 3532 // The global shape guard above ensures the instance JSClass is correct. 3533 MOZ_ASSERT(!needsWindowProxy); 3534 emitCallDOMGetterResultNoGuards(holder, *prop, globalId); 3535 trackAttached("GetName.GlobalNameDOMGetter"); 3536 } else { 3537 ObjOperandId receiverObjId; 3538 if (needsWindowProxy) { 3539 MOZ_ASSERT(cx_->global()->maybeWindowProxy()); 3540 receiverObjId = writer.loadObject(cx_->global()->maybeWindowProxy()); 3541 } else { 3542 receiverObjId = globalId; 3543 } 3544 ValOperandId receiverId = writer.boxObject(receiverObjId); 3545 emitCallGetterResultNoGuards(kind, global, holder, *prop, receiverId); 3546 trackAttached("GetName.GlobalNameGetter"); 3547 } 3548 3549 return AttachDecision::Attach; 3550 } 3551 3552 static bool NeedEnvironmentShapeGuard(JSContext* cx, JSObject* envObj) { 3553 // We can skip a guard on the call object if the script's bindings are 3554 // guaranteed to be immutable (and thus cannot introduce shadowing variables). 3555 // If the function is a relazified self-hosted function it has no BaseScript 3556 // and we pessimistically create the guard. 3557 if (envObj->is<CallObject>()) { 3558 auto* callObj = &envObj->as<CallObject>(); 3559 JSFunction* fun = &callObj->callee(); 3560 return !fun->hasBaseScript() || 3561 fun->baseScript()->funHasExtensibleScope() || 3562 DebugEnvironments::hasDebugEnvironment(cx, *callObj); 3563 } 3564 3565 // Similar to the call object case, we can also skip a guard if the lexical 3566 // environment's bindings are immutable. 3567 if (envObj->is<LexicalEnvironmentObject>()) { 3568 return envObj->as<LexicalEnvironmentObject>().isExtensible(); 3569 } 3570 3571 // Use a shape guard for all other environment objects. 3572 return true; 3573 } 3574 3575 AttachDecision GetNameIRGenerator::tryAttachEnvironmentName(ObjOperandId objId, 3576 HandleId id) { 3577 if (IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) { 3578 return AttachDecision::NoAction; 3579 } 3580 3581 JSObject* env = env_; 3582 Maybe<PropertyInfo> prop; 3583 NativeObject* holder = nullptr; 3584 3585 while (env) { 3586 if (env->is<GlobalObject>()) { 3587 prop = env->as<GlobalObject>().lookup(cx_, id); 3588 if (prop.isSome()) { 3589 break; 3590 } 3591 return AttachDecision::NoAction; 3592 } 3593 3594 if (!env->is<EnvironmentObject>() || env->is<WithEnvironmentObject>()) { 3595 return AttachDecision::NoAction; 3596 } 3597 3598 // Check for an 'own' property on the env. There is no need to 3599 // check the prototype as non-with scopes do not inherit properties 3600 // from any prototype. 3601 prop = env->as<NativeObject>().lookup(cx_, id); 3602 if (prop.isSome()) { 3603 break; 3604 } 3605 3606 env = env->enclosingEnvironment(); 3607 } 3608 3609 holder = &env->as<NativeObject>(); 3610 if (!IsCacheableGetPropSlot(holder, holder, *prop)) { 3611 return AttachDecision::NoAction; 3612 } 3613 if (holder->getSlot(prop->slot()).isMagic()) { 3614 MOZ_ASSERT(holder->is<EnvironmentObject>()); 3615 return AttachDecision::NoAction; 3616 } 3617 3618 ObjOperandId lastObjId = objId; 3619 env = env_; 3620 while (env) { 3621 if (NeedEnvironmentShapeGuard(cx_, env)) { 3622 writer.guardShape(lastObjId, env->shape()); 3623 } 3624 3625 if (env == holder) { 3626 break; 3627 } 3628 3629 lastObjId = writer.loadEnclosingEnvironment(lastObjId); 3630 env = env->enclosingEnvironment(); 3631 } 3632 3633 ValOperandId resId = EmitLoadSlot(writer, holder, lastObjId, prop->slot()); 3634 if (holder->is<EnvironmentObject>()) { 3635 writer.guardIsNotUninitializedLexical(resId); 3636 } 3637 writer.loadOperandResult(resId); 3638 writer.returnFromIC(); 3639 3640 trackAttached("GetName.EnvironmentName"); 3641 return AttachDecision::Attach; 3642 } 3643 3644 void GetNameIRGenerator::trackAttached(const char* name) { 3645 stubName_ = name ? name : "NotAttached"; 3646 #ifdef JS_CACHEIR_SPEW 3647 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { 3648 sp.valueProperty("base", ObjectValue(*env_)); 3649 sp.valueProperty("property", StringValue(name_)); 3650 } 3651 #endif 3652 } 3653 3654 BindNameIRGenerator::BindNameIRGenerator(JSContext* cx, HandleScript script, 3655 jsbytecode* pc, ICState state, 3656 HandleObject env, 3657 Handle<PropertyName*> name) 3658 : IRGenerator(cx, script, pc, CacheKind::BindName, state), 3659 env_(env), 3660 name_(name) {} 3661 3662 AttachDecision BindNameIRGenerator::tryAttachStub() { 3663 MOZ_ASSERT(cacheKind_ == CacheKind::BindName); 3664 3665 AutoAssertNoPendingException aanpe(cx_); 3666 3667 ObjOperandId envId(writer.setInputOperandId(0)); 3668 RootedId id(cx_, NameToId(name_)); 3669 3670 TRY_ATTACH(tryAttachGlobalName(envId, id)); 3671 TRY_ATTACH(tryAttachEnvironmentName(envId, id)); 3672 3673 trackAttached(IRGenerator::NotAttached); 3674 return AttachDecision::NoAction; 3675 } 3676 3677 AttachDecision BindNameIRGenerator::tryAttachGlobalName(ObjOperandId objId, 3678 HandleId id) { 3679 if (!IsGlobalOp(JSOp(*pc_))) { 3680 return AttachDecision::NoAction; 3681 } 3682 MOZ_ASSERT(!script_->hasNonSyntacticScope()); 3683 3684 Handle<GlobalLexicalEnvironmentObject*> globalLexical = 3685 env_.as<GlobalLexicalEnvironmentObject>(); 3686 MOZ_ASSERT(globalLexical->isGlobal()); 3687 3688 JSObject* result = nullptr; 3689 if (Maybe<PropertyInfo> prop = globalLexical->lookup(cx_, id)) { 3690 // If this is an uninitialized lexical or a const, we need to return a 3691 // RuntimeLexicalErrorObject. 3692 if (globalLexical->getSlot(prop->slot()).isMagic() || !prop->writable()) { 3693 return AttachDecision::NoAction; 3694 } 3695 result = globalLexical; 3696 } else { 3697 result = &globalLexical->global(); 3698 } 3699 3700 if (result == globalLexical) { 3701 // Lexical bindings are non-configurable so we can just return the 3702 // global lexical. 3703 writer.loadObjectResult(objId); 3704 } else { 3705 // If the property exists on the global and is non-configurable, it cannot 3706 // be shadowed by the lexical scope so we can just return the global without 3707 // a shape guard. 3708 Maybe<PropertyInfo> prop = result->as<GlobalObject>().lookup(cx_, id); 3709 if (prop.isNothing() || prop->configurable()) { 3710 writer.guardShape(objId, globalLexical->shape()); 3711 } 3712 ObjOperandId globalId = writer.loadEnclosingEnvironment(objId); 3713 writer.loadObjectResult(globalId); 3714 } 3715 writer.returnFromIC(); 3716 3717 trackAttached("BindName.GlobalName"); 3718 return AttachDecision::Attach; 3719 } 3720 3721 AttachDecision BindNameIRGenerator::tryAttachEnvironmentName(ObjOperandId objId, 3722 HandleId id) { 3723 if (IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) { 3724 return AttachDecision::NoAction; 3725 } 3726 3727 // JSOp::BindUnqualifiedName when writing to a dynamic environment binding. 3728 // JSOp::BindName when reading from a dynamic environment binding. 3729 bool unqualifiedLookup = JSOp(*pc_) == JSOp::BindUnqualifiedName; 3730 3731 JSObject* env = env_; 3732 Maybe<PropertyInfo> prop; 3733 while (true) { 3734 // Stop when we've reached the global object. 3735 if (env->is<GlobalObject>()) { 3736 break; 3737 } 3738 3739 if (!env->is<EnvironmentObject>() || env->is<WithEnvironmentObject>()) { 3740 return AttachDecision::NoAction; 3741 } 3742 3743 // When we reach an unqualified variables object (like the global) we 3744 // have to stop looking and return that object. 3745 if (unqualifiedLookup && env->isUnqualifiedVarObj()) { 3746 break; 3747 } 3748 3749 // Check for an 'own' property on the env. There is no need to 3750 // check the prototype as non-with scopes do not inherit properties 3751 // from any prototype. 3752 prop = env->as<NativeObject>().lookup(cx_, id); 3753 if (prop.isSome()) { 3754 break; 3755 } 3756 3757 env = env->enclosingEnvironment(); 3758 } 3759 3760 // If this is an uninitialized lexical or a const, we need to return a 3761 // RuntimeLexicalErrorObject. 3762 auto* holder = &env->as<NativeObject>(); 3763 if (prop.isSome() && holder->is<EnvironmentObject>()) { 3764 // Uninitialized lexical binding. 3765 if (holder->getSlot(prop->slot()).isMagic()) { 3766 return AttachDecision::NoAction; 3767 } 3768 3769 // Attempt to write to a const binding. 3770 if (unqualifiedLookup && !prop->writable()) { 3771 return AttachDecision::NoAction; 3772 } 3773 } 3774 3775 ObjOperandId lastObjId = objId; 3776 env = env_; 3777 while (env) { 3778 if (NeedEnvironmentShapeGuard(cx_, env) && !env->is<GlobalObject>()) { 3779 writer.guardShape(lastObjId, env->shape()); 3780 } 3781 3782 if (env == holder) { 3783 break; 3784 } 3785 3786 lastObjId = writer.loadEnclosingEnvironment(lastObjId); 3787 env = env->enclosingEnvironment(); 3788 } 3789 3790 if (prop.isSome() && holder->is<EnvironmentObject>()) { 3791 ValOperandId valId = EmitLoadSlot(writer, holder, lastObjId, prop->slot()); 3792 writer.guardIsNotUninitializedLexical(valId); 3793 } 3794 3795 writer.loadObjectResult(lastObjId); 3796 writer.returnFromIC(); 3797 3798 trackAttached("BindName.EnvironmentName"); 3799 return AttachDecision::Attach; 3800 } 3801 3802 void BindNameIRGenerator::trackAttached(const char* name) { 3803 stubName_ = name ? name : "NotAttached"; 3804 #ifdef JS_CACHEIR_SPEW 3805 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { 3806 sp.valueProperty("base", ObjectValue(*env_)); 3807 sp.valueProperty("property", StringValue(name_)); 3808 } 3809 #endif 3810 } 3811 3812 HasPropIRGenerator::HasPropIRGenerator(JSContext* cx, HandleScript script, 3813 jsbytecode* pc, ICState state, 3814 CacheKind cacheKind, HandleValue idVal, 3815 HandleValue val) 3816 : IRGenerator(cx, script, pc, cacheKind, state), val_(val), idVal_(idVal) {} 3817 3818 AttachDecision HasPropIRGenerator::tryAttachDense(HandleObject obj, 3819 ObjOperandId objId, 3820 uint32_t index, 3821 Int32OperandId indexId) { 3822 if (!obj->is<NativeObject>()) { 3823 return AttachDecision::NoAction; 3824 } 3825 3826 NativeObject* nobj = &obj->as<NativeObject>(); 3827 if (!nobj->containsDenseElement(index)) { 3828 return AttachDecision::NoAction; 3829 } 3830 3831 if (mode_ == ICState::Mode::Megamorphic) { 3832 writer.guardIsNativeObject(objId); 3833 } else { 3834 // Guard shape to ensure object class is NativeObject. 3835 TestMatchingNativeReceiver(writer, nobj, objId); 3836 } 3837 writer.loadDenseElementExistsResult(objId, indexId); 3838 writer.returnFromIC(); 3839 3840 trackAttached("HasProp.Dense"); 3841 return AttachDecision::Attach; 3842 } 3843 3844 AttachDecision HasPropIRGenerator::tryAttachDenseHole(HandleObject obj, 3845 ObjOperandId objId, 3846 uint32_t index, 3847 Int32OperandId indexId) { 3848 bool hasOwn = (cacheKind_ == CacheKind::HasOwn); 3849 OwnProperty ownProp = hasOwn ? OwnProperty::Yes : OwnProperty::No; 3850 3851 if (!obj->is<NativeObject>()) { 3852 return AttachDecision::NoAction; 3853 } 3854 3855 NativeObject* nobj = &obj->as<NativeObject>(); 3856 if (nobj->containsDenseElement(index)) { 3857 return AttachDecision::NoAction; 3858 } 3859 if (!CanAttachDenseElementHole(nobj, ownProp)) { 3860 return AttachDecision::NoAction; 3861 } 3862 3863 // Guard shape to ensure class is NativeObject and to prevent non-dense 3864 // elements being added. Also ensures prototype doesn't change if dynamic 3865 // checks aren't emitted. 3866 TestMatchingNativeReceiver(writer, nobj, objId); 3867 3868 // Generate prototype guards if needed. This includes monitoring that 3869 // properties were not added in the chain. 3870 if (!hasOwn) { 3871 GeneratePrototypeHoleGuards(writer, nobj, objId, 3872 /* alwaysGuardFirstProto = */ false); 3873 } 3874 3875 writer.loadDenseElementHoleExistsResult(objId, indexId); 3876 writer.returnFromIC(); 3877 3878 trackAttached("HasProp.DenseHole"); 3879 return AttachDecision::Attach; 3880 } 3881 3882 AttachDecision HasPropIRGenerator::tryAttachSparse(HandleObject obj, 3883 ObjOperandId objId, 3884 Int32OperandId indexId) { 3885 bool hasOwn = (cacheKind_ == CacheKind::HasOwn); 3886 OwnProperty ownProp = hasOwn ? OwnProperty::Yes : OwnProperty::No; 3887 3888 if (!obj->is<NativeObject>()) { 3889 return AttachDecision::NoAction; 3890 } 3891 auto* nobj = &obj->as<NativeObject>(); 3892 3893 if (!nobj->isIndexed()) { 3894 return AttachDecision::NoAction; 3895 } 3896 if (!CanAttachDenseElementHole(nobj, ownProp, AllowIndexedReceiver::Yes)) { 3897 return AttachDecision::NoAction; 3898 } 3899 3900 // Guard that this is a native object. 3901 writer.guardIsNativeObject(objId); 3902 3903 // Generate prototype guards if needed. This includes monitoring that 3904 // properties were not added in the chain. 3905 if (!hasOwn) { 3906 GeneratePrototypeHoleGuards(writer, nobj, objId, 3907 /* alwaysGuardFirstProto = */ true); 3908 } 3909 3910 // Because of the prototype guard we know that the prototype chain 3911 // does not include any dense or sparse (i.e indexed) properties. 3912 writer.callObjectHasSparseElementResult(objId, indexId); 3913 writer.returnFromIC(); 3914 3915 trackAttached("HasProp.Sparse"); 3916 return AttachDecision::Attach; 3917 } 3918 3919 AttachDecision HasPropIRGenerator::tryAttachArgumentsObjectArg( 3920 HandleObject obj, ObjOperandId objId, Int32OperandId indexId) { 3921 bool hasOwn = (cacheKind_ == CacheKind::HasOwn); 3922 OwnProperty ownProp = hasOwn ? OwnProperty::Yes : OwnProperty::No; 3923 3924 if (!obj->is<ArgumentsObject>()) { 3925 return AttachDecision::NoAction; 3926 } 3927 auto* args = &obj->as<ArgumentsObject>(); 3928 3929 // No elements must have been overridden or deleted. 3930 if (args->hasOverriddenElement()) { 3931 return AttachDecision::NoAction; 3932 } 3933 3934 if (!CanAttachDenseElementHole(args, ownProp, AllowIndexedReceiver::Yes, 3935 AllowExtraReceiverProperties::Yes)) { 3936 return AttachDecision::NoAction; 3937 } 3938 3939 if (args->is<MappedArgumentsObject>()) { 3940 writer.guardClass(objId, GuardClassKind::MappedArguments); 3941 } else { 3942 MOZ_ASSERT(args->is<UnmappedArgumentsObject>()); 3943 writer.guardClass(objId, GuardClassKind::UnmappedArguments); 3944 } 3945 3946 if (!hasOwn) { 3947 GeneratePrototypeHoleGuards(writer, args, objId, 3948 /* alwaysGuardFirstProto = */ true); 3949 } 3950 3951 writer.loadArgumentsObjectArgExistsResult(objId, indexId); 3952 writer.returnFromIC(); 3953 3954 trackAttached("HasProp.ArgumentsObjectArg"); 3955 return AttachDecision::Attach; 3956 } 3957 3958 AttachDecision HasPropIRGenerator::tryAttachNamedProp(HandleObject obj, 3959 ObjOperandId objId, 3960 HandleId key, 3961 ValOperandId keyId) { 3962 bool hasOwn = (cacheKind_ == CacheKind::HasOwn); 3963 3964 Rooted<NativeObject*> holder(cx_); 3965 PropertyResult prop; 3966 3967 if (hasOwn) { 3968 if (!LookupOwnPropertyPure(cx_, obj, key, &prop)) { 3969 return AttachDecision::NoAction; 3970 } 3971 3972 holder.set(&obj->as<NativeObject>()); 3973 } else { 3974 NativeObject* nHolder = nullptr; 3975 if (!LookupPropertyPure(cx_, obj, key, &nHolder, &prop)) { 3976 return AttachDecision::NoAction; 3977 } 3978 holder.set(nHolder); 3979 } 3980 if (prop.isNotFound()) { 3981 return AttachDecision::NoAction; 3982 } 3983 3984 TRY_ATTACH(tryAttachSmallObjectVariableKey(obj, objId, key, keyId)); 3985 TRY_ATTACH(tryAttachMegamorphic(objId, keyId)); 3986 TRY_ATTACH(tryAttachNative(&obj->as<NativeObject>(), objId, key, keyId, prop, 3987 holder.get())); 3988 3989 return AttachDecision::NoAction; 3990 } 3991 3992 AttachDecision HasPropIRGenerator::tryAttachSmallObjectVariableKey( 3993 HandleObject obj, ObjOperandId objId, jsid key, ValOperandId keyId) { 3994 MOZ_ASSERT(obj->is<NativeObject>()); 3995 3996 if (cacheKind_ != CacheKind::HasOwn) { 3997 return AttachDecision::NoAction; 3998 } 3999 4000 if (mode_ != ICState::Mode::Megamorphic) { 4001 return AttachDecision::NoAction; 4002 } 4003 4004 if (numOptimizedStubs_ != 0) { 4005 return AttachDecision::NoAction; 4006 } 4007 4008 if (!idVal_.isString()) { 4009 return AttachDecision::NoAction; 4010 } 4011 4012 if (!obj->as<NativeObject>().hasEmptyElements()) { 4013 return AttachDecision::NoAction; 4014 } 4015 4016 if (ClassCanHaveExtraProperties(obj->getClass())) { 4017 return AttachDecision::NoAction; 4018 } 4019 4020 if (!obj->shape()->isShared()) { 4021 return AttachDecision::NoAction; 4022 } 4023 4024 static constexpr size_t SMALL_OBJECT_SIZE = 5; 4025 4026 if (obj->shape()->asShared().slotSpan() > SMALL_OBJECT_SIZE) { 4027 return AttachDecision::NoAction; 4028 } 4029 4030 Rooted<ListObject*> keyListObj(cx_, ListObject::create(cx_)); 4031 if (!keyListObj) { 4032 cx_->recoverFromOutOfMemory(); 4033 return AttachDecision::NoAction; 4034 } 4035 4036 for (SharedShapePropertyIter<CanGC> iter(cx_, &obj->shape()->asShared()); 4037 !iter.done(); iter++) { 4038 if (!iter->key().isAtom()) { 4039 return AttachDecision::NoAction; 4040 } 4041 4042 if (keyListObj->length() == SMALL_OBJECT_SIZE) { 4043 return AttachDecision::NoAction; 4044 } 4045 4046 if (!keyListObj->append(cx_, StringValue(iter->key().toAtom()))) { 4047 cx_->recoverFromOutOfMemory(); 4048 return AttachDecision::NoAction; 4049 } 4050 } 4051 4052 writer.guardShape(objId, obj->shape()); 4053 writer.guardNoDenseElements(objId); 4054 StringOperandId keyStrId = writer.guardToString(keyId); 4055 StringOperandId keyAtomId = writer.stringToAtom(keyStrId); 4056 writer.smallObjectVariableKeyHasOwnResult(keyAtomId, keyListObj, 4057 obj->shape()); 4058 writer.returnFromIC(); 4059 trackAttached("HasProp.SmallObjectVariableKey"); 4060 return AttachDecision::Attach; 4061 } 4062 4063 AttachDecision HasPropIRGenerator::tryAttachMegamorphic(ObjOperandId objId, 4064 ValOperandId keyId) { 4065 bool hasOwn = (cacheKind_ == CacheKind::HasOwn); 4066 4067 if (mode_ != ICState::Mode::Megamorphic) { 4068 return AttachDecision::NoAction; 4069 } 4070 4071 writer.megamorphicHasPropResult(objId, keyId, hasOwn); 4072 writer.returnFromIC(); 4073 trackAttached("HasProp.Megamorphic"); 4074 return AttachDecision::Attach; 4075 } 4076 4077 AttachDecision HasPropIRGenerator::tryAttachNative(NativeObject* obj, 4078 ObjOperandId objId, jsid key, 4079 ValOperandId keyId, 4080 PropertyResult prop, 4081 NativeObject* holder) { 4082 MOZ_ASSERT(IsCacheableProtoChain(obj, holder)); 4083 4084 if (!prop.isNativeProperty()) { 4085 return AttachDecision::NoAction; 4086 } 4087 4088 emitIdGuard(keyId, idVal_, key); 4089 EmitReadSlotGuard(writer, obj, holder, objId); 4090 writer.loadBooleanResult(true); 4091 writer.returnFromIC(); 4092 4093 trackAttached("HasProp.Native"); 4094 return AttachDecision::Attach; 4095 } 4096 4097 static void EmitGuardTypedArray(CacheIRWriter& writer, TypedArrayObject* obj, 4098 ObjOperandId objId) { 4099 if (!obj->is<ResizableTypedArrayObject>()) { 4100 writer.guardIsNonResizableTypedArray(objId); 4101 } else { 4102 writer.guardIsResizableTypedArray(objId); 4103 } 4104 } 4105 4106 AttachDecision HasPropIRGenerator::tryAttachTypedArray(HandleObject obj, 4107 ObjOperandId objId, 4108 ValOperandId keyId) { 4109 if (!obj->is<TypedArrayObject>()) { 4110 return AttachDecision::NoAction; 4111 } 4112 4113 if (!idVal_.isNumber()) { 4114 return AttachDecision::NoAction; 4115 } 4116 4117 auto* tarr = &obj->as<TypedArrayObject>(); 4118 EmitGuardTypedArray(writer, tarr, objId); 4119 4120 IntPtrOperandId intPtrIndexId = 4121 guardToIntPtrIndex(idVal_, keyId, /* supportOOB = */ true); 4122 4123 auto viewKind = ToArrayBufferViewKind(tarr); 4124 writer.loadTypedArrayElementExistsResult(objId, intPtrIndexId, viewKind); 4125 writer.returnFromIC(); 4126 4127 trackAttached("HasProp.TypedArrayObject"); 4128 return AttachDecision::Attach; 4129 } 4130 4131 AttachDecision HasPropIRGenerator::tryAttachSlotDoesNotExist( 4132 NativeObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId) { 4133 bool hasOwn = (cacheKind_ == CacheKind::HasOwn); 4134 4135 emitIdGuard(keyId, idVal_, key); 4136 if (hasOwn) { 4137 TestMatchingNativeReceiver(writer, obj, objId); 4138 } else { 4139 EmitMissingPropGuard(writer, obj, objId); 4140 } 4141 writer.loadBooleanResult(false); 4142 writer.returnFromIC(); 4143 4144 trackAttached("HasProp.DoesNotExist"); 4145 return AttachDecision::Attach; 4146 } 4147 4148 AttachDecision HasPropIRGenerator::tryAttachDoesNotExist(HandleObject obj, 4149 ObjOperandId objId, 4150 HandleId key, 4151 ValOperandId keyId) { 4152 bool hasOwn = (cacheKind_ == CacheKind::HasOwn); 4153 4154 // Check that property doesn't exist on |obj| or it's prototype chain. These 4155 // checks allow NativeObjects with a NativeObject prototype chain. They return 4156 // NoAction if unknown such as resolve hooks or proxies. 4157 if (hasOwn) { 4158 if (!CheckHasNoSuchOwnProperty(cx_, obj, key)) { 4159 return AttachDecision::NoAction; 4160 } 4161 } else { 4162 if (!CheckHasNoSuchProperty(cx_, obj, key)) { 4163 return AttachDecision::NoAction; 4164 } 4165 } 4166 4167 TRY_ATTACH(tryAttachSmallObjectVariableKey(obj, objId, key, keyId)); 4168 TRY_ATTACH(tryAttachMegamorphic(objId, keyId)); 4169 TRY_ATTACH( 4170 tryAttachSlotDoesNotExist(&obj->as<NativeObject>(), objId, key, keyId)); 4171 4172 return AttachDecision::NoAction; 4173 } 4174 4175 AttachDecision HasPropIRGenerator::tryAttachProxyElement(HandleObject obj, 4176 ObjOperandId objId, 4177 ValOperandId keyId) { 4178 bool hasOwn = (cacheKind_ == CacheKind::HasOwn); 4179 4180 if (!obj->is<ProxyObject>()) { 4181 return AttachDecision::NoAction; 4182 } 4183 4184 writer.guardIsProxy(objId); 4185 writer.proxyHasPropResult(objId, keyId, hasOwn); 4186 writer.returnFromIC(); 4187 4188 trackAttached("HasProp.ProxyElement"); 4189 return AttachDecision::Attach; 4190 } 4191 4192 AttachDecision HasPropIRGenerator::tryAttachStub() { 4193 MOZ_ASSERT(cacheKind_ == CacheKind::In || cacheKind_ == CacheKind::HasOwn); 4194 4195 AutoAssertNoPendingException aanpe(cx_); 4196 4197 // NOTE: Argument order is PROPERTY, OBJECT 4198 ValOperandId keyId(writer.setInputOperandId(0)); 4199 ValOperandId valId(writer.setInputOperandId(1)); 4200 4201 if (!val_.isObject()) { 4202 trackAttached(IRGenerator::NotAttached); 4203 return AttachDecision::NoAction; 4204 } 4205 RootedObject obj(cx_, &val_.toObject()); 4206 ObjOperandId objId = writer.guardToObject(valId); 4207 4208 // Optimize Proxies 4209 TRY_ATTACH(tryAttachProxyElement(obj, objId, keyId)); 4210 4211 RootedId id(cx_); 4212 bool nameOrSymbol; 4213 if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) { 4214 cx_->clearPendingException(); 4215 return AttachDecision::NoAction; 4216 } 4217 4218 TRY_ATTACH(tryAttachTypedArray(obj, objId, keyId)); 4219 4220 if (nameOrSymbol) { 4221 TRY_ATTACH(tryAttachNamedProp(obj, objId, id, keyId)); 4222 TRY_ATTACH(tryAttachDoesNotExist(obj, objId, id, keyId)); 4223 4224 trackAttached(IRGenerator::NotAttached); 4225 return AttachDecision::NoAction; 4226 } 4227 4228 uint32_t index; 4229 Int32OperandId indexId; 4230 if (maybeGuardInt32Index(idVal_, keyId, &index, &indexId)) { 4231 TRY_ATTACH(tryAttachDense(obj, objId, index, indexId)); 4232 TRY_ATTACH(tryAttachDenseHole(obj, objId, index, indexId)); 4233 TRY_ATTACH(tryAttachSparse(obj, objId, indexId)); 4234 TRY_ATTACH(tryAttachArgumentsObjectArg(obj, objId, indexId)); 4235 4236 trackAttached(IRGenerator::NotAttached); 4237 return AttachDecision::NoAction; 4238 } 4239 4240 trackAttached(IRGenerator::NotAttached); 4241 return AttachDecision::NoAction; 4242 } 4243 4244 void HasPropIRGenerator::trackAttached(const char* name) { 4245 stubName_ = name ? name : "NotAttached"; 4246 #ifdef JS_CACHEIR_SPEW 4247 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { 4248 sp.valueProperty("base", val_); 4249 sp.valueProperty("property", idVal_); 4250 } 4251 #endif 4252 } 4253 4254 CheckPrivateFieldIRGenerator::CheckPrivateFieldIRGenerator( 4255 JSContext* cx, HandleScript script, jsbytecode* pc, ICState state, 4256 CacheKind cacheKind, HandleValue idVal, HandleValue val) 4257 : IRGenerator(cx, script, pc, cacheKind, state), val_(val), idVal_(idVal) { 4258 MOZ_ASSERT(idVal.isSymbol() && idVal.toSymbol()->isPrivateName()); 4259 } 4260 4261 AttachDecision CheckPrivateFieldIRGenerator::tryAttachStub() { 4262 AutoAssertNoPendingException aanpe(cx_); 4263 4264 ValOperandId valId(writer.setInputOperandId(0)); 4265 ValOperandId keyId(writer.setInputOperandId(1)); 4266 4267 if (!val_.isObject()) { 4268 trackAttached(IRGenerator::NotAttached); 4269 return AttachDecision::NoAction; 4270 } 4271 JSObject* obj = &val_.toObject(); 4272 ObjOperandId objId = writer.guardToObject(valId); 4273 PropertyKey key = PropertyKey::Symbol(idVal_.toSymbol()); 4274 4275 ThrowCondition condition; 4276 ThrowMsgKind msgKind; 4277 GetCheckPrivateFieldOperands(pc_, &condition, &msgKind); 4278 4279 PropertyResult prop; 4280 if (!LookupOwnPropertyPure(cx_, obj, key, &prop)) { 4281 return AttachDecision::NoAction; 4282 } 4283 4284 if (CheckPrivateFieldWillThrow(condition, prop.isFound())) { 4285 // Don't attach a stub if the operation will throw. 4286 return AttachDecision::NoAction; 4287 } 4288 4289 auto* nobj = &obj->as<NativeObject>(); 4290 4291 TRY_ATTACH(tryAttachNative(nobj, objId, key, keyId, prop)); 4292 4293 return AttachDecision::NoAction; 4294 } 4295 4296 AttachDecision CheckPrivateFieldIRGenerator::tryAttachNative( 4297 NativeObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId, 4298 PropertyResult prop) { 4299 MOZ_ASSERT(prop.isNativeProperty() || prop.isNotFound()); 4300 4301 emitIdGuard(keyId, idVal_, key); 4302 TestMatchingNativeReceiver(writer, obj, objId); 4303 writer.loadBooleanResult(prop.isFound()); 4304 writer.returnFromIC(); 4305 4306 trackAttached("CheckPrivateField.Native"); 4307 return AttachDecision::Attach; 4308 } 4309 4310 void CheckPrivateFieldIRGenerator::trackAttached(const char* name) { 4311 stubName_ = name ? name : "NotAttached"; 4312 #ifdef JS_CACHEIR_SPEW 4313 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { 4314 sp.valueProperty("base", val_); 4315 sp.valueProperty("property", idVal_); 4316 } 4317 #endif 4318 } 4319 4320 bool IRGenerator::maybeGuardInt32Index(const Value& index, ValOperandId indexId, 4321 uint32_t* int32Index, 4322 Int32OperandId* int32IndexId) { 4323 if (index.isNumber()) { 4324 int32_t indexSigned; 4325 if (index.isInt32()) { 4326 indexSigned = index.toInt32(); 4327 } else { 4328 // We allow negative zero here. 4329 if (!mozilla::NumberEqualsInt32(index.toDouble(), &indexSigned)) { 4330 return false; 4331 } 4332 } 4333 4334 if (indexSigned < 0) { 4335 return false; 4336 } 4337 4338 *int32Index = uint32_t(indexSigned); 4339 *int32IndexId = EmitGuardToInt32Index(writer, index, indexId); 4340 return true; 4341 } 4342 4343 if (index.isString()) { 4344 int32_t indexSigned = GetIndexFromString(index.toString()); 4345 if (indexSigned < 0) { 4346 return false; 4347 } 4348 4349 StringOperandId strId = writer.guardToString(indexId); 4350 *int32Index = uint32_t(indexSigned); 4351 *int32IndexId = writer.guardStringToIndex(strId); 4352 return true; 4353 } 4354 4355 return false; 4356 } 4357 4358 SetPropIRGenerator::SetPropIRGenerator(JSContext* cx, HandleScript script, 4359 jsbytecode* pc, CacheKind cacheKind, 4360 ICState state, HandleValue lhsVal, 4361 HandleValue idVal, HandleValue rhsVal) 4362 : IRGenerator(cx, script, pc, cacheKind, state), 4363 lhsVal_(lhsVal), 4364 idVal_(idVal), 4365 rhsVal_(rhsVal) {} 4366 4367 AttachDecision SetPropIRGenerator::tryAttachStub() { 4368 AutoAssertNoPendingException aanpe(cx_); 4369 4370 ValOperandId objValId(writer.setInputOperandId(0)); 4371 ValOperandId rhsValId; 4372 if (cacheKind_ == CacheKind::SetProp) { 4373 rhsValId = ValOperandId(writer.setInputOperandId(1)); 4374 } else { 4375 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem); 4376 MOZ_ASSERT(setElemKeyValueId().id() == 1); 4377 writer.setInputOperandId(1); 4378 rhsValId = ValOperandId(writer.setInputOperandId(2)); 4379 } 4380 4381 RootedId id(cx_); 4382 bool nameOrSymbol; 4383 if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) { 4384 cx_->clearPendingException(); 4385 return AttachDecision::NoAction; 4386 } 4387 4388 if (lhsVal_.isObject()) { 4389 RootedObject obj(cx_, &lhsVal_.toObject()); 4390 4391 ObjOperandId objId = writer.guardToObject(objValId); 4392 TRY_ATTACH(tryAttachSetTypedArrayElement(obj, objId, rhsValId)); 4393 if (IsPropertySetOp(JSOp(*pc_))) { 4394 TRY_ATTACH(tryAttachMegamorphicSetElement(obj, objId, rhsValId)); 4395 } 4396 if (nameOrSymbol) { 4397 TRY_ATTACH(tryAttachNativeSetSlot(obj, objId, id, rhsValId)); 4398 if (IsPropertySetOp(JSOp(*pc_))) { 4399 TRY_ATTACH(tryAttachSetArrayLength(obj, objId, id, rhsValId)); 4400 TRY_ATTACH(tryAttachSetter(obj, objId, id, rhsValId)); 4401 TRY_ATTACH(tryAttachWindowProxy(obj, objId, id, rhsValId)); 4402 TRY_ATTACH(tryAttachProxy(obj, objId, id, rhsValId)); 4403 TRY_ATTACH(tryAttachMegamorphicSetSlot(obj, objId, id, rhsValId)); 4404 } 4405 if (canAttachAddSlotStub(obj, id)) { 4406 deferType_ = DeferType::AddSlot; 4407 return AttachDecision::Deferred; 4408 } 4409 return AttachDecision::NoAction; 4410 } 4411 4412 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem); 4413 4414 if (IsPropertySetOp(JSOp(*pc_))) { 4415 TRY_ATTACH(tryAttachProxyElement(obj, objId, rhsValId)); 4416 } 4417 4418 uint32_t index; 4419 Int32OperandId indexId; 4420 if (maybeGuardInt32Index(idVal_, setElemKeyValueId(), &index, &indexId)) { 4421 TRY_ATTACH( 4422 tryAttachSetDenseElement(obj, objId, index, indexId, rhsValId)); 4423 TRY_ATTACH( 4424 tryAttachSetDenseElementHole(obj, objId, index, indexId, rhsValId)); 4425 TRY_ATTACH(tryAttachAddOrUpdateSparseElement(obj, objId, index, indexId, 4426 rhsValId)); 4427 return AttachDecision::NoAction; 4428 } 4429 } 4430 return AttachDecision::NoAction; 4431 } 4432 4433 static void EmitStoreSlotAndReturn(CacheIRWriter& writer, ObjOperandId objId, 4434 NativeObject* nobj, PropertyInfo prop, 4435 ValOperandId rhsId) { 4436 if (nobj->isFixedSlot(prop.slot())) { 4437 size_t offset = NativeObject::getFixedSlotOffset(prop.slot()); 4438 writer.storeFixedSlot(objId, offset, rhsId); 4439 } else { 4440 size_t offset = nobj->dynamicSlotIndex(prop.slot()) * sizeof(Value); 4441 writer.storeDynamicSlot(objId, offset, rhsId); 4442 } 4443 writer.returnFromIC(); 4444 } 4445 4446 static Maybe<PropertyInfo> LookupShapeForSetSlot(JSOp op, NativeObject* obj, 4447 jsid id) { 4448 Maybe<PropertyInfo> prop = obj->lookupPure(id); 4449 if (prop.isNothing() || !prop->isDataProperty() || !prop->writable()) { 4450 return mozilla::Nothing(); 4451 } 4452 4453 // If this is a property init operation, the property's attributes may have to 4454 // be changed too, so make sure the current flags match. 4455 if (IsPropertyInitOp(op)) { 4456 // Don't support locked init operations. 4457 if (IsLockedInitOp(op)) { 4458 return mozilla::Nothing(); 4459 } 4460 4461 // Can't redefine a non-configurable property. 4462 if (!prop->configurable()) { 4463 return mozilla::Nothing(); 4464 } 4465 4466 // Make sure the enumerable flag matches the init operation. 4467 if (IsHiddenInitOp(op) == prop->enumerable()) { 4468 return mozilla::Nothing(); 4469 } 4470 } 4471 4472 return prop; 4473 } 4474 4475 SetSlotOptimizable SetPropIRGenerator::canAttachNativeSetSlot( 4476 JSObject* obj, PropertyKey id, Maybe<PropertyInfo>* prop) { 4477 if (!obj->is<NativeObject>()) { 4478 return SetSlotOptimizable::No; 4479 } 4480 4481 *prop = LookupShapeForSetSlot(JSOp(*pc_), &obj->as<NativeObject>(), id); 4482 if (!prop->isSome()) { 4483 return SetSlotOptimizable::No; 4484 } 4485 4486 return Watchtower::canOptimizeSetSlot(cx_, &obj->as<NativeObject>(), **prop); 4487 } 4488 4489 // There is no need to guard on the shape. Global lexical bindings are 4490 // non-configurable and can not be shadowed. 4491 static bool IsGlobalLexicalSetGName(JSOp op, NativeObject* obj, 4492 PropertyInfo prop) { 4493 // Ensure that the env can't change. 4494 if (op != JSOp::SetGName && op != JSOp::StrictSetGName) { 4495 return false; 4496 } 4497 4498 if (!obj->is<GlobalLexicalEnvironmentObject>()) { 4499 return false; 4500 } 4501 4502 // Uninitialized let bindings use a RuntimeLexicalErrorObject. 4503 MOZ_ASSERT(!obj->getSlot(prop.slot()).isMagic()); 4504 MOZ_ASSERT(prop.writable()); 4505 MOZ_ASSERT(!prop.configurable()); 4506 return true; 4507 } 4508 4509 AttachDecision SetPropIRGenerator::tryAttachNativeSetSlot(HandleObject obj, 4510 ObjOperandId objId, 4511 HandleId id, 4512 ValOperandId rhsId) { 4513 Maybe<PropertyInfo> prop; 4514 SetSlotOptimizable optimizable = canAttachNativeSetSlot(obj, id, &prop); 4515 switch (optimizable) { 4516 case SetSlotOptimizable::No: 4517 return AttachDecision::NoAction; 4518 case SetSlotOptimizable::NotYet: 4519 return AttachDecision::TemporarilyUnoptimizable; 4520 case SetSlotOptimizable::Yes: 4521 break; 4522 } 4523 4524 if (mode_ == ICState::Mode::Megamorphic && cacheKind_ == CacheKind::SetProp && 4525 IsPropertySetOp(JSOp(*pc_))) { 4526 return AttachDecision::NoAction; 4527 } 4528 4529 maybeEmitIdGuard(id); 4530 4531 NativeObject* nobj = &obj->as<NativeObject>(); 4532 if (!IsGlobalLexicalSetGName(JSOp(*pc_), nobj, *prop)) { 4533 // If the object has an ObjectFuse, we can only optimize for this specific 4534 // object so we have to emit GuardSpecificObject. We don't need to do this 4535 // for the global object because there's only one object with that shape. 4536 if (nobj->hasObjectFuse() && !nobj->is<GlobalObject>()) { 4537 writer.guardSpecificObject(objId, nobj); 4538 } 4539 TestMatchingNativeReceiver(writer, nobj, objId); 4540 } 4541 EmitStoreSlotAndReturn(writer, objId, nobj, *prop, rhsId); 4542 4543 trackAttached("SetProp.NativeSlot"); 4544 return AttachDecision::Attach; 4545 } 4546 4547 static bool ValueCanConvertToNumeric(Scalar::Type type, const Value& val) { 4548 if (Scalar::isBigIntType(type)) { 4549 return val.isBigInt(); 4550 } 4551 return val.isNumber() || val.isNullOrUndefined() || val.isBoolean() || 4552 val.isString(); 4553 } 4554 4555 OperandId IRGenerator::emitNumericGuard(ValOperandId valId, const Value& v, 4556 Scalar::Type type) { 4557 MOZ_ASSERT(ValueCanConvertToNumeric(type, v)); 4558 switch (type) { 4559 case Scalar::Int8: 4560 case Scalar::Uint8: 4561 case Scalar::Int16: 4562 case Scalar::Uint16: 4563 case Scalar::Int32: 4564 case Scalar::Uint32: { 4565 if (v.isNumber()) { 4566 return writer.guardToInt32ModUint32(valId); 4567 } 4568 if (v.isNullOrUndefined()) { 4569 writer.guardIsNullOrUndefined(valId); 4570 return writer.loadInt32Constant(0); 4571 } 4572 if (v.isBoolean()) { 4573 return writer.guardBooleanToInt32(valId); 4574 } 4575 MOZ_ASSERT(v.isString()); 4576 StringOperandId strId = writer.guardToString(valId); 4577 NumberOperandId numId = writer.guardStringToNumber(strId); 4578 return writer.truncateDoubleToUInt32(numId); 4579 } 4580 4581 case Scalar::Float16: 4582 case Scalar::Float32: 4583 case Scalar::Float64: { 4584 if (v.isNumber()) { 4585 return writer.guardIsNumber(valId); 4586 } 4587 if (v.isNull()) { 4588 writer.guardIsNull(valId); 4589 return writer.loadDoubleConstant(0.0); 4590 } 4591 if (v.isUndefined()) { 4592 writer.guardIsUndefined(valId); 4593 return writer.loadDoubleConstant(JS::GenericNaN()); 4594 } 4595 if (v.isBoolean()) { 4596 BooleanOperandId boolId = writer.guardToBoolean(valId); 4597 return writer.booleanToNumber(boolId); 4598 } 4599 MOZ_ASSERT(v.isString()); 4600 StringOperandId strId = writer.guardToString(valId); 4601 return writer.guardStringToNumber(strId); 4602 } 4603 4604 case Scalar::Uint8Clamped: { 4605 if (v.isNumber()) { 4606 return writer.guardToUint8Clamped(valId); 4607 } 4608 if (v.isNullOrUndefined()) { 4609 writer.guardIsNullOrUndefined(valId); 4610 return writer.loadInt32Constant(0); 4611 } 4612 if (v.isBoolean()) { 4613 return writer.guardBooleanToInt32(valId); 4614 } 4615 MOZ_ASSERT(v.isString()); 4616 StringOperandId strId = writer.guardToString(valId); 4617 NumberOperandId numId = writer.guardStringToNumber(strId); 4618 return writer.doubleToUint8Clamped(numId); 4619 } 4620 4621 case Scalar::BigInt64: 4622 case Scalar::BigUint64: 4623 MOZ_ASSERT(v.isBigInt()); 4624 return writer.guardToBigInt(valId); 4625 4626 case Scalar::MaxTypedArrayViewType: 4627 case Scalar::Int64: 4628 case Scalar::Simd128: 4629 break; 4630 } 4631 MOZ_CRASH("Unsupported TypedArray type"); 4632 } 4633 4634 void SetPropIRGenerator::trackAttached(const char* name) { 4635 stubName_ = name ? name : "NotAttached"; 4636 #ifdef JS_CACHEIR_SPEW 4637 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { 4638 sp.opcodeProperty("op", JSOp(*pc_)); 4639 sp.valueProperty("base", lhsVal_); 4640 sp.valueProperty("property", idVal_); 4641 sp.valueProperty("value", rhsVal_); 4642 } 4643 #endif 4644 } 4645 4646 static bool IsCacheableSetPropCallNative(NativeObject* obj, 4647 NativeObject* holder, 4648 PropertyInfo prop) { 4649 MOZ_ASSERT(IsCacheableProtoChain(obj, holder)); 4650 4651 if (!prop.isAccessorProperty()) { 4652 return false; 4653 } 4654 4655 JSObject* setterObject = holder->getSetter(prop); 4656 if (!setterObject || !setterObject->is<JSFunction>()) { 4657 return false; 4658 } 4659 4660 JSFunction& setter = setterObject->as<JSFunction>(); 4661 if (!setter.isNativeWithoutJitEntry()) { 4662 return false; 4663 } 4664 4665 if (setter.isClassConstructor()) { 4666 return false; 4667 } 4668 4669 return true; 4670 } 4671 4672 static bool IsCacheableSetPropCallScripted(NativeObject* obj, 4673 NativeObject* holder, 4674 PropertyInfo prop) { 4675 MOZ_ASSERT(IsCacheableProtoChain(obj, holder)); 4676 4677 if (!prop.isAccessorProperty()) { 4678 return false; 4679 } 4680 4681 JSObject* setterObject = holder->getSetter(prop); 4682 if (!setterObject || !setterObject->is<JSFunction>()) { 4683 return false; 4684 } 4685 4686 JSFunction& setter = setterObject->as<JSFunction>(); 4687 if (setter.isClassConstructor()) { 4688 return false; 4689 } 4690 4691 // Scripted functions and natives with JIT entry can use the scripted path. 4692 return setter.hasJitEntry(); 4693 } 4694 4695 static bool CanAttachSetter(JSContext* cx, jsbytecode* pc, JSObject* obj, 4696 PropertyKey id, NativeObject** holder, 4697 Maybe<PropertyInfo>* propInfo) { 4698 // Don't attach a setter stub for ops like JSOp::InitElem. 4699 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc))); 4700 4701 PropertyResult prop; 4702 if (!LookupPropertyPure(cx, obj, id, holder, &prop)) { 4703 return false; 4704 } 4705 auto* nobj = &obj->as<NativeObject>(); 4706 4707 if (!prop.isNativeProperty()) { 4708 return false; 4709 } 4710 4711 if (!IsCacheableSetPropCallScripted(nobj, *holder, prop.propertyInfo()) && 4712 !IsCacheableSetPropCallNative(nobj, *holder, prop.propertyInfo())) { 4713 return false; 4714 } 4715 4716 *propInfo = mozilla::Some(prop.propertyInfo()); 4717 return true; 4718 } 4719 4720 void SetPropIRGenerator::emitCallSetterNoGuards(NativeObject* obj, 4721 NativeObject* holder, 4722 PropertyInfo prop, 4723 ObjOperandId receiverId, 4724 ValOperandId rhsId) { 4725 JSFunction* target = &holder->getSetter(prop)->as<JSFunction>(); 4726 bool sameRealm = cx_->realm() == target->realm(); 4727 4728 if (target->isNativeWithoutJitEntry()) { 4729 MOZ_ASSERT(IsCacheableSetPropCallNative(obj, holder, prop)); 4730 writer.callNativeSetter(receiverId, target, rhsId, sameRealm); 4731 writer.returnFromIC(); 4732 return; 4733 } 4734 4735 MOZ_ASSERT(IsCacheableSetPropCallScripted(obj, holder, prop)); 4736 writer.callScriptedSetter(receiverId, target, rhsId, sameRealm); 4737 writer.returnFromIC(); 4738 } 4739 4740 void SetPropIRGenerator::emitCallDOMSetterNoGuards(NativeObject* holder, 4741 PropertyInfo prop, 4742 ObjOperandId objId, 4743 ValOperandId rhsId) { 4744 JSFunction* setter = &holder->getSetter(prop)->as<JSFunction>(); 4745 MOZ_ASSERT(cx_->realm() == setter->realm()); 4746 4747 writer.callDOMSetter(objId, setter->jitInfo(), rhsId); 4748 writer.returnFromIC(); 4749 } 4750 4751 AttachDecision SetPropIRGenerator::tryAttachSetter(HandleObject obj, 4752 ObjOperandId objId, 4753 HandleId id, 4754 ValOperandId rhsId) { 4755 // Don't attach a setter stub for ops like JSOp::InitElem. 4756 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_))); 4757 4758 NativeObject* holder = nullptr; 4759 Maybe<PropertyInfo> prop; 4760 if (!CanAttachSetter(cx_, pc_, obj, id, &holder, &prop)) { 4761 return AttachDecision::NoAction; 4762 } 4763 auto* nobj = &obj->as<NativeObject>(); 4764 4765 bool needsWindowProxy = 4766 IsWindow(nobj) && SetterNeedsWindowProxyThis(holder, *prop); 4767 4768 maybeEmitIdGuard(id); 4769 4770 emitCallAccessorGuards(nobj, holder, id, *prop, objId, AccessorKind::Setter); 4771 4772 if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Setter, nobj, holder, *prop, 4773 mode_)) { 4774 MOZ_ASSERT(!needsWindowProxy); 4775 emitCallDOMSetterNoGuards(holder, *prop, objId, rhsId); 4776 4777 trackAttached("SetProp.DOMSetter"); 4778 return AttachDecision::Attach; 4779 } 4780 4781 ObjOperandId receiverId; 4782 if (needsWindowProxy) { 4783 MOZ_ASSERT(cx_->global()->maybeWindowProxy()); 4784 receiverId = writer.loadObject(cx_->global()->maybeWindowProxy()); 4785 } else { 4786 receiverId = objId; 4787 } 4788 emitCallSetterNoGuards(nobj, holder, *prop, receiverId, rhsId); 4789 4790 trackAttached("SetProp.Setter"); 4791 return AttachDecision::Attach; 4792 } 4793 4794 AttachDecision SetPropIRGenerator::tryAttachSetArrayLength(HandleObject obj, 4795 ObjOperandId objId, 4796 HandleId id, 4797 ValOperandId rhsId) { 4798 // Don't attach an array length stub for ops like JSOp::InitElem. 4799 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_))); 4800 4801 if (!obj->is<ArrayObject>() || !id.isAtom(cx_->names().length) || 4802 !obj->as<ArrayObject>().lengthIsWritable()) { 4803 return AttachDecision::NoAction; 4804 } 4805 4806 maybeEmitIdGuard(id); 4807 emitOptimisticClassGuard(objId, obj, GuardClassKind::Array); 4808 writer.callSetArrayLength(objId, IsStrictSetPC(pc_), rhsId); 4809 writer.returnFromIC(); 4810 4811 trackAttached("SetProp.ArrayLength"); 4812 return AttachDecision::Attach; 4813 } 4814 4815 AttachDecision SetPropIRGenerator::tryAttachSetDenseElement( 4816 HandleObject obj, ObjOperandId objId, uint32_t index, 4817 Int32OperandId indexId, ValOperandId rhsId) { 4818 if (!obj->is<NativeObject>()) { 4819 return AttachDecision::NoAction; 4820 } 4821 4822 NativeObject* nobj = &obj->as<NativeObject>(); 4823 if (!nobj->containsDenseElement(index) || nobj->denseElementsAreFrozen()) { 4824 return AttachDecision::NoAction; 4825 } 4826 4827 // Setting holes requires extra code for marking the elements non-packed. 4828 MOZ_ASSERT(!rhsVal_.isMagic(JS_ELEMENTS_HOLE)); 4829 4830 JSOp op = JSOp(*pc_); 4831 4832 // We don't currently emit locked init for any indexed properties. 4833 MOZ_ASSERT(!IsLockedInitOp(op)); 4834 4835 // We don't currently emit hidden init for any existing indexed properties. 4836 MOZ_ASSERT(!IsHiddenInitOp(op)); 4837 4838 // Don't optimize InitElem (DefineProperty) on non-extensible objects: when 4839 // the elements are sealed, we have to throw an exception. Note that we have 4840 // to check !isExtensible instead of denseElementsAreSealed because sealing 4841 // a (non-extensible) object does not necessarily trigger a Shape change. 4842 if (IsPropertyInitOp(op) && !nobj->isExtensible()) { 4843 return AttachDecision::NoAction; 4844 } 4845 4846 TestMatchingNativeReceiver(writer, nobj, objId); 4847 4848 bool expectPackedElements = nobj->denseElementsArePacked(); 4849 writer.storeDenseElement(objId, indexId, rhsId, expectPackedElements); 4850 writer.returnFromIC(); 4851 4852 trackAttached("SetProp.DenseElement"); 4853 return AttachDecision::Attach; 4854 } 4855 4856 static bool CanAttachAddElement(NativeObject* obj, bool isInit, 4857 AllowIndexedReceiver allowIndexedReceiver) { 4858 MOZ_ASSERT(!obj->is<TypedArrayObject>()); 4859 4860 // Make sure the receiver doesn't have any indexed properties and that such 4861 // properties can't appear without a shape change. 4862 if (allowIndexedReceiver == AllowIndexedReceiver::No && obj->isIndexed()) { 4863 return false; 4864 } 4865 4866 do { 4867 // This check is also relevant for the receiver object. 4868 const JSClass* clasp = obj->getClass(); 4869 if (clasp != &ArrayObject::class_ && 4870 (clasp->getAddProperty() || clasp->getResolve() || 4871 clasp->getOpsLookupProperty() || clasp->getOpsSetProperty() || 4872 obj->hasUnpreservedWrapper())) { 4873 return false; 4874 } 4875 4876 // If we're initializing a property instead of setting one, the objects 4877 // on the prototype are not relevant. 4878 if (isInit) { 4879 break; 4880 } 4881 4882 JSObject* proto = obj->staticPrototype(); 4883 if (!proto) { 4884 break; 4885 } 4886 4887 if (!proto->is<NativeObject>()) { 4888 return false; 4889 } 4890 4891 // We shouldn't add an element if the index is OOB for a typed array on the 4892 // prototype chain. 4893 if (proto->is<TypedArrayObject>()) { 4894 return false; 4895 } 4896 4897 NativeObject* nproto = &proto->as<NativeObject>(); 4898 if (nproto->isIndexed()) { 4899 return false; 4900 } 4901 4902 // We have to make sure the proto has no non-writable (frozen) elements 4903 // because we're not allowed to shadow them. 4904 if (nproto->denseElementsAreFrozen() && 4905 nproto->getDenseInitializedLength() > 0) { 4906 return false; 4907 } 4908 4909 obj = nproto; 4910 } while (true); 4911 4912 return true; 4913 } 4914 4915 AttachDecision SetPropIRGenerator::tryAttachSetDenseElementHole( 4916 HandleObject obj, ObjOperandId objId, uint32_t index, 4917 Int32OperandId indexId, ValOperandId rhsId) { 4918 if (!obj->is<NativeObject>()) { 4919 return AttachDecision::NoAction; 4920 } 4921 4922 // Setting holes requires extra code for marking the elements non-packed. 4923 if (rhsVal_.isMagic(JS_ELEMENTS_HOLE)) { 4924 return AttachDecision::NoAction; 4925 } 4926 4927 JSOp op = JSOp(*pc_); 4928 MOZ_ASSERT(IsPropertySetOp(op) || IsPropertyInitOp(op)); 4929 4930 // We don't currently emit locked init for any indexed properties. 4931 MOZ_ASSERT(!IsLockedInitOp(op)); 4932 4933 // Hidden init can be emitted for absent indexed properties. 4934 if (IsHiddenInitOp(op)) { 4935 MOZ_ASSERT(op == JSOp::InitHiddenElem); 4936 return AttachDecision::NoAction; 4937 } 4938 4939 NativeObject* nobj = &obj->as<NativeObject>(); 4940 if (!nobj->isExtensible()) { 4941 return AttachDecision::NoAction; 4942 } 4943 4944 MOZ_ASSERT(!nobj->denseElementsAreFrozen(), 4945 "Extensible objects should not have frozen elements"); 4946 4947 uint32_t initLength = nobj->getDenseInitializedLength(); 4948 uint32_t capacity = nobj->getDenseCapacity(); 4949 4950 // Optimize if: 4951 // a) we're adding an element inside capacity, or one element past. 4952 // b) we're writing to a hole inside initLength. 4953 bool isAdd = index >= initLength && index <= capacity; 4954 bool isHoleInBounds = 4955 index < initLength && !nobj->containsDenseElement(index); 4956 if (!isAdd && !isHoleInBounds) { 4957 return AttachDecision::NoAction; 4958 } 4959 4960 // Can't add new elements to arrays with non-writable length. 4961 if (isAdd && nobj->is<ArrayObject>() && 4962 !nobj->as<ArrayObject>().lengthIsWritable()) { 4963 return AttachDecision::NoAction; 4964 } 4965 4966 // Typed arrays don't have dense elements. 4967 if (nobj->is<TypedArrayObject>()) { 4968 return AttachDecision::NoAction; 4969 } 4970 4971 // Check for other indexed properties or class hooks. 4972 if (!CanAttachAddElement(nobj, IsPropertyInitOp(op), 4973 AllowIndexedReceiver::No)) { 4974 return AttachDecision::NoAction; 4975 } 4976 4977 TestMatchingNativeReceiver(writer, nobj, objId); 4978 4979 // Also shape guard the proto chain, unless this is an InitElem. 4980 if (IsPropertySetOp(op)) { 4981 ShapeGuardProtoChain(writer, nobj, objId); 4982 } 4983 4984 writer.storeDenseElementHole(objId, indexId, rhsId, isAdd); 4985 writer.returnFromIC(); 4986 4987 trackAttached(isAdd ? "AddDenseElement" : "StoreDenseElementHole"); 4988 return AttachDecision::Attach; 4989 } 4990 4991 // Add an IC for adding or updating a sparse element. 4992 AttachDecision SetPropIRGenerator::tryAttachAddOrUpdateSparseElement( 4993 HandleObject obj, ObjOperandId objId, uint32_t index, 4994 Int32OperandId indexId, ValOperandId rhsId) { 4995 JSOp op = JSOp(*pc_); 4996 MOZ_ASSERT(IsPropertySetOp(op) || IsPropertyInitOp(op)); 4997 4998 if (op != JSOp::SetElem && op != JSOp::StrictSetElem) { 4999 return AttachDecision::NoAction; 5000 } 5001 5002 if (!obj->is<NativeObject>()) { 5003 return AttachDecision::NoAction; 5004 } 5005 NativeObject* nobj = &obj->as<NativeObject>(); 5006 5007 // We cannot attach a stub to a non-extensible object 5008 if (!nobj->isExtensible()) { 5009 return AttachDecision::NoAction; 5010 } 5011 5012 // Stub doesn't handle negative indices. 5013 if (index > INT32_MAX) { 5014 return AttachDecision::NoAction; 5015 } 5016 5017 // The index must not be for a dense element. 5018 if (nobj->containsDenseElement(index)) { 5019 return AttachDecision::NoAction; 5020 } 5021 5022 // Only handle ArrayObject and PlainObject in this stub. 5023 if (!nobj->is<ArrayObject>() && !nobj->is<PlainObject>()) { 5024 return AttachDecision::NoAction; 5025 } 5026 5027 // Don't attach if we're adding to an array with non-writable length. 5028 if (nobj->is<ArrayObject>()) { 5029 ArrayObject* aobj = &nobj->as<ArrayObject>(); 5030 bool isAdd = (index >= aobj->length()); 5031 if (isAdd && !aobj->lengthIsWritable()) { 5032 return AttachDecision::NoAction; 5033 } 5034 } 5035 5036 // Check for class hooks or indexed properties on the prototype chain that 5037 // we're not allowed to shadow. 5038 if (!CanAttachAddElement(nobj, /* isInit = */ false, 5039 AllowIndexedReceiver::Yes)) { 5040 return AttachDecision::NoAction; 5041 } 5042 5043 // Ensure that obj is an ArrayObject or PlainObject. 5044 if (nobj->is<ArrayObject>()) { 5045 writer.guardClass(objId, GuardClassKind::Array); 5046 } else { 5047 MOZ_ASSERT(nobj->is<PlainObject>()); 5048 writer.guardClass(objId, GuardClassKind::PlainObject); 5049 } 5050 5051 // The helper we are going to call only applies to non-dense elements. 5052 writer.guardIndexIsNotDenseElement(objId, indexId); 5053 5054 // Guard extensible: We may be trying to add a new element, and so we'd best 5055 // be able to do so safely. 5056 writer.guardIsExtensible(objId); 5057 5058 // Ensures we are able to efficiently able to map to an integral jsid. 5059 writer.guardInt32IsNonNegative(indexId); 5060 5061 // Shape guard the prototype chain to avoid shadowing indexes from appearing. 5062 // Guard the prototype of the receiver explicitly, because the receiver's 5063 // shape is not being guarded as a proxy for that. 5064 GuardReceiverProto(writer, nobj, objId); 5065 5066 // Dense elements may appear on the prototype chain (and prototypes may 5067 // have a different notion of which elements are dense), but they can 5068 // only be data properties, so our specialized Set handler is ok to bind 5069 // to them. 5070 if (IsPropertySetOp(op)) { 5071 ShapeGuardProtoChain(writer, nobj, objId); 5072 } 5073 5074 // Ensure that if we're adding an element to the object, the object's 5075 // length is writable. 5076 if (nobj->is<ArrayObject>()) { 5077 writer.guardIndexIsValidUpdateOrAdd(objId, indexId); 5078 } 5079 5080 writer.callAddOrUpdateSparseElementHelper( 5081 objId, indexId, rhsId, 5082 /* strict = */ op == JSOp::StrictSetElem); 5083 writer.returnFromIC(); 5084 5085 trackAttached("SetProp.AddOrUpdateSparseElement"); 5086 return AttachDecision::Attach; 5087 } 5088 5089 AttachDecision SetPropIRGenerator::tryAttachSetTypedArrayElement( 5090 HandleObject obj, ObjOperandId objId, ValOperandId rhsId) { 5091 if (!obj->is<TypedArrayObject>()) { 5092 return AttachDecision::NoAction; 5093 } 5094 if (!idVal_.isNumber()) { 5095 return AttachDecision::NoAction; 5096 } 5097 5098 auto* tarr = &obj->as<TypedArrayObject>(); 5099 Scalar::Type elementType = tarr->type(); 5100 5101 // Immutable TypedArrays can't be modified. 5102 if (tarr->is<ImmutableTypedArrayObject>()) { 5103 return AttachDecision::NoAction; 5104 } 5105 5106 // Don't attach if the input type doesn't match the guard added below. 5107 if (!ValueCanConvertToNumeric(elementType, rhsVal_)) { 5108 return AttachDecision::NoAction; 5109 } 5110 5111 bool handleOOB = false; 5112 int64_t indexInt64; 5113 if (!ValueIsInt64Index(idVal_, &indexInt64) || indexInt64 < 0 || 5114 uint64_t(indexInt64) >= tarr->length().valueOr(0)) { 5115 handleOOB = true; 5116 } 5117 5118 JSOp op = JSOp(*pc_); 5119 5120 // The only expected property init operation is InitElem. 5121 MOZ_ASSERT_IF(IsPropertyInitOp(op), op == JSOp::InitElem); 5122 5123 // InitElem (DefineProperty) has to throw an exception on out-of-bounds. 5124 if (handleOOB && IsPropertyInitOp(op)) { 5125 return AttachDecision::NoAction; 5126 } 5127 5128 writer.guardShapeForClass(objId, tarr->shape()); 5129 5130 OperandId rhsValId = emitNumericGuard(rhsId, rhsVal_, elementType); 5131 5132 ValOperandId keyId = setElemKeyValueId(); 5133 IntPtrOperandId indexId = guardToIntPtrIndex(idVal_, keyId, handleOOB); 5134 5135 auto viewKind = ToArrayBufferViewKind(tarr); 5136 writer.storeTypedArrayElement(objId, elementType, indexId, rhsValId, 5137 handleOOB, viewKind); 5138 writer.returnFromIC(); 5139 5140 trackAttached(handleOOB ? "SetTypedElementOOB" : "SetTypedElement"); 5141 return AttachDecision::Attach; 5142 } 5143 5144 AttachDecision SetPropIRGenerator::tryAttachGenericProxy( 5145 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id, 5146 ValOperandId rhsId, bool handleDOMProxies) { 5147 // Don't attach a proxy stub for ops like JSOp::InitElem. 5148 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_))); 5149 5150 writer.guardIsProxy(objId); 5151 5152 if (!handleDOMProxies) { 5153 // Ensure that the incoming object is not a DOM proxy, so that we can 5154 // get to the specialized stubs. If handleDOMProxies is true, we were 5155 // unable to attach a specialized DOM stub, so we just handle all 5156 // proxies here. 5157 writer.guardIsNotDOMProxy(objId); 5158 } 5159 5160 if (cacheKind_ == CacheKind::SetProp || mode_ == ICState::Mode::Specialized) { 5161 maybeEmitIdGuard(id); 5162 writer.proxySet(objId, id, rhsId, IsStrictSetPC(pc_)); 5163 } else { 5164 // Attach a stub that handles every id. 5165 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem); 5166 MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic); 5167 writer.proxySetByValue(objId, setElemKeyValueId(), rhsId, 5168 IsStrictSetPC(pc_)); 5169 } 5170 5171 writer.returnFromIC(); 5172 5173 trackAttached("SetProp.GenericProxy"); 5174 return AttachDecision::Attach; 5175 } 5176 5177 AttachDecision SetPropIRGenerator::tryAttachDOMProxyShadowed( 5178 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id, 5179 ValOperandId rhsId) { 5180 // Don't attach a proxy stub for ops like JSOp::InitElem. 5181 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_))); 5182 5183 MOZ_ASSERT(IsCacheableDOMProxy(obj)); 5184 5185 maybeEmitIdGuard(id); 5186 TestMatchingProxyReceiver(writer, obj, objId); 5187 writer.proxySet(objId, id, rhsId, IsStrictSetPC(pc_)); 5188 writer.returnFromIC(); 5189 5190 trackAttached("SetProp.DOMProxyShadowed"); 5191 return AttachDecision::Attach; 5192 } 5193 5194 AttachDecision SetPropIRGenerator::tryAttachDOMProxyUnshadowed( 5195 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id, 5196 ValOperandId rhsId) { 5197 // Don't attach a proxy stub for ops like JSOp::InitElem. 5198 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_))); 5199 5200 MOZ_ASSERT(IsCacheableDOMProxy(obj)); 5201 5202 JSObject* proto = obj->staticPrototype(); 5203 if (!proto) { 5204 return AttachDecision::NoAction; 5205 } 5206 5207 NativeObject* holder = nullptr; 5208 Maybe<PropertyInfo> prop; 5209 if (!CanAttachSetter(cx_, pc_, proto, id, &holder, &prop)) { 5210 return AttachDecision::NoAction; 5211 } 5212 auto* nproto = &proto->as<NativeObject>(); 5213 5214 maybeEmitIdGuard(id); 5215 5216 // Guard that our proxy (expando) object hasn't started shadowing this 5217 // property. 5218 TestMatchingProxyReceiver(writer, obj, objId); 5219 bool canOptimizeMissing = false; 5220 CheckDOMProxyDoesNotShadow(writer, obj, id, objId, &canOptimizeMissing); 5221 5222 GeneratePrototypeGuards(writer, obj, holder, objId); 5223 5224 // Guard on the holder of the property. 5225 ObjOperandId holderId = writer.loadObject(holder); 5226 TestMatchingHolder(writer, holder, holderId); 5227 5228 emitGuardGetterSetterSlot(holder, *prop, holderId, AccessorKind::Setter, 5229 /* holderIsConstant = */ true); 5230 5231 // EmitCallSetterNoGuards expects |obj| to be the object the property is 5232 // on to do some checks. Since we actually looked at proto, and no extra 5233 // guards will be generated, we can just pass that instead. 5234 emitCallSetterNoGuards(nproto, holder, *prop, objId, rhsId); 5235 5236 trackAttached("SetProp.DOMProxyUnshadowed"); 5237 return AttachDecision::Attach; 5238 } 5239 5240 AttachDecision SetPropIRGenerator::tryAttachDOMProxyExpando( 5241 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id, 5242 ValOperandId rhsId) { 5243 // Don't attach a proxy stub for ops like JSOp::InitElem. 5244 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_))); 5245 5246 MOZ_ASSERT(IsCacheableDOMProxy(obj)); 5247 5248 Value expandoVal = GetProxyPrivate(obj); 5249 JSObject* expandoObj; 5250 if (expandoVal.isObject()) { 5251 expandoObj = &expandoVal.toObject(); 5252 } else { 5253 MOZ_ASSERT(!expandoVal.isUndefined(), 5254 "How did a missing expando manage to shadow things?"); 5255 auto expandoAndGeneration = 5256 static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate()); 5257 MOZ_ASSERT(expandoAndGeneration); 5258 expandoObj = &expandoAndGeneration->expando.toObject(); 5259 } 5260 5261 Maybe<PropertyInfo> prop; 5262 SetSlotOptimizable optimizable = 5263 canAttachNativeSetSlot(expandoObj, id, &prop); 5264 if (optimizable == SetSlotOptimizable::Yes) { 5265 auto* nativeExpandoObj = &expandoObj->as<NativeObject>(); 5266 MOZ_ASSERT(!nativeExpandoObj->hasObjectFuse()); 5267 5268 maybeEmitIdGuard(id); 5269 ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape( 5270 obj, objId, expandoVal, nativeExpandoObj); 5271 5272 EmitStoreSlotAndReturn(writer, expandoObjId, nativeExpandoObj, *prop, 5273 rhsId); 5274 trackAttached("SetProp.DOMProxyExpandoSlot"); 5275 return AttachDecision::Attach; 5276 } 5277 MOZ_ASSERT(optimizable == SetSlotOptimizable::No); 5278 5279 NativeObject* holder = nullptr; 5280 if (CanAttachSetter(cx_, pc_, expandoObj, id, &holder, &prop)) { 5281 auto* nativeExpandoObj = &expandoObj->as<NativeObject>(); 5282 5283 // Call the setter. Note that we pass objId, the DOM proxy, as |this| 5284 // and not the expando object. 5285 maybeEmitIdGuard(id); 5286 ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape( 5287 obj, objId, expandoVal, nativeExpandoObj); 5288 5289 MOZ_ASSERT(holder == nativeExpandoObj); 5290 emitGuardGetterSetterSlot(nativeExpandoObj, *prop, expandoObjId, 5291 AccessorKind::Setter); 5292 emitCallSetterNoGuards(nativeExpandoObj, nativeExpandoObj, *prop, objId, 5293 rhsId); 5294 trackAttached("SetProp.DOMProxyExpandoSetter"); 5295 return AttachDecision::Attach; 5296 } 5297 5298 return AttachDecision::NoAction; 5299 } 5300 5301 AttachDecision SetPropIRGenerator::tryAttachProxy(HandleObject obj, 5302 ObjOperandId objId, 5303 HandleId id, 5304 ValOperandId rhsId) { 5305 // Don't attach a proxy stub for ops like JSOp::InitElem. 5306 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_))); 5307 5308 ProxyStubType type = GetProxyStubType(cx_, obj, id); 5309 if (type == ProxyStubType::None) { 5310 return AttachDecision::NoAction; 5311 } 5312 auto proxy = obj.as<ProxyObject>(); 5313 5314 if (mode_ == ICState::Mode::Megamorphic) { 5315 return tryAttachGenericProxy(proxy, objId, id, rhsId, 5316 /* handleDOMProxies = */ true); 5317 } 5318 5319 switch (type) { 5320 case ProxyStubType::None: 5321 break; 5322 case ProxyStubType::DOMExpando: 5323 TRY_ATTACH(tryAttachDOMProxyExpando(proxy, objId, id, rhsId)); 5324 [[fallthrough]]; // Fall through to the generic shadowed case. 5325 case ProxyStubType::DOMShadowed: 5326 return tryAttachDOMProxyShadowed(proxy, objId, id, rhsId); 5327 case ProxyStubType::DOMUnshadowed: 5328 TRY_ATTACH(tryAttachDOMProxyUnshadowed(proxy, objId, id, rhsId)); 5329 return tryAttachGenericProxy(proxy, objId, id, rhsId, 5330 /* handleDOMProxies = */ true); 5331 case ProxyStubType::Generic: 5332 return tryAttachGenericProxy(proxy, objId, id, rhsId, 5333 /* handleDOMProxies = */ false); 5334 } 5335 5336 MOZ_CRASH("Unexpected ProxyStubType"); 5337 } 5338 5339 AttachDecision SetPropIRGenerator::tryAttachProxyElement(HandleObject obj, 5340 ObjOperandId objId, 5341 ValOperandId rhsId) { 5342 // Don't attach a proxy stub for ops like JSOp::InitElem. 5343 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_))); 5344 5345 if (!obj->is<ProxyObject>()) { 5346 return AttachDecision::NoAction; 5347 } 5348 5349 writer.guardIsProxy(objId); 5350 5351 // Like GetPropIRGenerator::tryAttachProxyElement, don't check for DOM 5352 // proxies here as we don't have specialized DOM stubs for this. 5353 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem); 5354 writer.proxySetByValue(objId, setElemKeyValueId(), rhsId, IsStrictSetPC(pc_)); 5355 writer.returnFromIC(); 5356 5357 trackAttached("SetProp.ProxyElement"); 5358 return AttachDecision::Attach; 5359 } 5360 5361 AttachDecision SetPropIRGenerator::tryAttachMegamorphicSetElement( 5362 HandleObject obj, ObjOperandId objId, ValOperandId rhsId) { 5363 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_))); 5364 5365 if (mode_ != ICState::Mode::Megamorphic || cacheKind_ != CacheKind::SetElem) { 5366 return AttachDecision::NoAction; 5367 } 5368 5369 // The generic proxy stubs are faster. 5370 if (obj->is<ProxyObject>()) { 5371 return AttachDecision::NoAction; 5372 } 5373 5374 writer.megamorphicSetElement(objId, setElemKeyValueId(), rhsId, 5375 IsStrictSetPC(pc_)); 5376 writer.returnFromIC(); 5377 5378 trackAttached("SetProp.MegamorphicSetElement"); 5379 return AttachDecision::Attach; 5380 } 5381 5382 AttachDecision SetPropIRGenerator::tryAttachMegamorphicSetSlot( 5383 HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) { 5384 if (mode_ != ICState::Mode::Megamorphic || cacheKind_ != CacheKind::SetProp) { 5385 return AttachDecision::NoAction; 5386 } 5387 5388 writer.megamorphicStoreSlot(objId, id, rhsId, IsStrictSetPC(pc_)); 5389 writer.returnFromIC(); 5390 trackAttached("SetProp.MegamorphicNativeSlot"); 5391 return AttachDecision::Attach; 5392 } 5393 5394 AttachDecision SetPropIRGenerator::tryAttachWindowProxy(HandleObject obj, 5395 ObjOperandId objId, 5396 HandleId id, 5397 ValOperandId rhsId) { 5398 // Don't attach a window proxy stub for ops like JSOp::InitElem. 5399 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_))); 5400 5401 // Attach a stub when the receiver is a WindowProxy and we can do the set 5402 // on the Window (the global object). 5403 5404 if (!IsWindowProxyForScriptGlobal(script_, obj)) { 5405 return AttachDecision::NoAction; 5406 } 5407 5408 // If we're megamorphic prefer a generic proxy stub that handles a lot more 5409 // cases. 5410 if (mode_ == ICState::Mode::Megamorphic) { 5411 return AttachDecision::NoAction; 5412 } 5413 5414 // Now try to do the set on the Window (the current global). 5415 GlobalObject* windowObj = cx_->global(); 5416 5417 Maybe<PropertyInfo> prop; 5418 SetSlotOptimizable optimizable = canAttachNativeSetSlot(windowObj, id, &prop); 5419 switch (optimizable) { 5420 case SetSlotOptimizable::No: 5421 return AttachDecision::NoAction; 5422 case SetSlotOptimizable::NotYet: 5423 return AttachDecision::TemporarilyUnoptimizable; 5424 case SetSlotOptimizable::Yes: 5425 break; 5426 } 5427 5428 maybeEmitIdGuard(id); 5429 5430 // Note: we don't need to GuardSpecificObject here for the ObjectFuse, 5431 // because this GlobalObject is the only object with this shape. 5432 ObjOperandId windowObjId = 5433 GuardAndLoadWindowProxyWindow(writer, objId, windowObj); 5434 writer.guardShape(windowObjId, windowObj->shape()); 5435 5436 EmitStoreSlotAndReturn(writer, windowObjId, windowObj, *prop, rhsId); 5437 5438 trackAttached("SetProp.WindowProxySlot"); 5439 return AttachDecision::Attach; 5440 } 5441 5442 // Detect if |id| refers to the 'prototype' property of a function object. This 5443 // property is special-cased in canAttachAddSlotStub(). 5444 static bool IsFunctionPrototype(const JSAtomState& names, JSObject* obj, 5445 PropertyKey id) { 5446 return obj->is<JSFunction>() && id.isAtom(names.prototype); 5447 } 5448 5449 bool SetPropIRGenerator::canAttachAddSlotStub(HandleObject obj, HandleId id) { 5450 if (!obj->is<NativeObject>()) { 5451 return false; 5452 } 5453 auto* nobj = &obj->as<NativeObject>(); 5454 5455 // Special-case JSFunction resolve hook to allow redefining the 'prototype' 5456 // property without triggering lazy expansion of property and object 5457 // allocation. 5458 if (IsFunctionPrototype(cx_->names(), nobj, id)) { 5459 MOZ_ASSERT(ClassMayResolveId(cx_->names(), nobj->getClass(), id, nobj)); 5460 5461 // We're only interested in functions that have a builtin .prototype 5462 // property (needsPrototypeProperty). The stub will guard on this because 5463 // the builtin .prototype property is non-configurable/non-enumerable and it 5464 // would be wrong to add a property with those attributes to a function that 5465 // doesn't have a builtin .prototype. 5466 // 5467 // Inlining needsPrototypeProperty in JIT code is complicated so we use 5468 // isNonBuiltinConstructor as a stronger condition that's easier to check 5469 // from JIT code. 5470 JSFunction* fun = &nobj->as<JSFunction>(); 5471 if (!fun->isNonBuiltinConstructor()) { 5472 return false; 5473 } 5474 MOZ_ASSERT(fun->needsPrototypeProperty()); 5475 5476 // If property exists this isn't an "add". 5477 if (fun->lookupPure(id)) { 5478 return false; 5479 } 5480 } else { 5481 // Normal Case: If property exists or is an OOB typed array index, this 5482 // isn't an "add". 5483 PropertyResult prop; 5484 if (!LookupOwnPropertyPure(cx_, nobj, id, &prop)) { 5485 return false; 5486 } 5487 if (prop.isFound() || prop.isTypedArrayOutOfRange()) { 5488 return false; 5489 } 5490 } 5491 5492 // For now we don't optimize Watchtower-monitored objects. 5493 if (Watchtower::watchesPropertyAdd(nobj)) { 5494 return false; 5495 } 5496 5497 // Object must be extensible, or we must be initializing a private 5498 // elem. 5499 bool canAddNewProperty = nobj->isExtensible() || id.isPrivateName(); 5500 if (!canAddNewProperty) { 5501 return false; 5502 } 5503 5504 JSOp op = JSOp(*pc_); 5505 if (IsPropertyInitOp(op)) { 5506 return true; 5507 } 5508 5509 MOZ_ASSERT(IsPropertySetOp(op)); 5510 5511 // Walk up the object prototype chain and ensure that all prototypes are 5512 // native, and that all prototypes have no setter defined on the property. 5513 for (JSObject* proto = nobj->staticPrototype(); proto; 5514 proto = proto->staticPrototype()) { 5515 if (!proto->is<NativeObject>()) { 5516 return false; 5517 } 5518 5519 // If prototype defines this property in a non-plain way, don't optimize. 5520 Maybe<PropertyInfo> protoProp = proto->as<NativeObject>().lookup(cx_, id); 5521 if (protoProp.isSome() && !protoProp->isDataProperty()) { 5522 return false; 5523 } 5524 5525 // Otherwise, if there's no such property, watch out for a resolve hook 5526 // that would need to be invoked and thus prevent inlining of property 5527 // addition. Allow the JSFunction resolve hook as it only defines plain 5528 // data properties and we don't need to invoke it for objects on the 5529 // proto chain. 5530 if (ClassMayResolveId(cx_->names(), proto->getClass(), id, proto) && 5531 !proto->is<JSFunction>()) { 5532 return false; 5533 } 5534 } 5535 5536 return true; 5537 } 5538 5539 static PropertyFlags SetPropertyFlags(JSOp op, bool isFunctionPrototype) { 5540 // Locked properties are non-writable, non-enumerable, and non-configurable. 5541 if (IsLockedInitOp(op)) { 5542 return {}; 5543 } 5544 5545 // Hidden properties are writable, non-enumerable, and configurable. 5546 if (IsHiddenInitOp(op)) { 5547 return { 5548 PropertyFlag::Writable, 5549 PropertyFlag::Configurable, 5550 }; 5551 } 5552 5553 // This is a special case to overwrite an unresolved function.prototype 5554 // property. The initial property flags of this property are writable, 5555 // non-enumerable, and non-configurable. See canAttachAddSlotStub. 5556 if (isFunctionPrototype) { 5557 return { 5558 PropertyFlag::Writable, 5559 }; 5560 } 5561 5562 // Other properties are writable, enumerable, and configurable. 5563 return PropertyFlags::defaultDataPropFlags; 5564 } 5565 5566 AttachDecision SetPropIRGenerator::tryAttachAddSlotStub( 5567 Handle<Shape*> oldShape) { 5568 ValOperandId objValId(writer.setInputOperandId(0)); 5569 ValOperandId rhsValId; 5570 if (cacheKind_ == CacheKind::SetProp) { 5571 rhsValId = ValOperandId(writer.setInputOperandId(1)); 5572 } else { 5573 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem); 5574 MOZ_ASSERT(setElemKeyValueId().id() == 1); 5575 writer.setInputOperandId(1); 5576 rhsValId = ValOperandId(writer.setInputOperandId(2)); 5577 } 5578 5579 RootedId id(cx_); 5580 bool nameOrSymbol; 5581 if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) { 5582 cx_->clearPendingException(); 5583 return AttachDecision::NoAction; 5584 } 5585 5586 if (!lhsVal_.isObject() || !nameOrSymbol) { 5587 return AttachDecision::NoAction; 5588 } 5589 5590 JSObject* obj = &lhsVal_.toObject(); 5591 if (!obj->is<NativeObject>()) { 5592 return AttachDecision::NoAction; 5593 } 5594 NativeObject* nobj = &obj->as<NativeObject>(); 5595 5596 PropertyResult prop; 5597 if (!LookupOwnPropertyPure(cx_, obj, id, &prop)) { 5598 return AttachDecision::NoAction; 5599 } 5600 if (prop.isNotFound()) { 5601 return AttachDecision::NoAction; 5602 } 5603 5604 MOZ_RELEASE_ASSERT(prop.isNativeProperty()); 5605 PropertyInfo propInfo = prop.propertyInfo(); 5606 NativeObject* holder = nobj; 5607 5608 if (holder->inDictionaryMode()) { 5609 return AttachDecision::NoAction; 5610 } 5611 5612 SharedShape* oldSharedShape = &oldShape->asShared(); 5613 5614 // The property must be the last added property of the object. 5615 SharedShape* newShape = holder->sharedShape(); 5616 MOZ_RELEASE_ASSERT(oldShape != newShape); 5617 MOZ_RELEASE_ASSERT(newShape->lastProperty() == propInfo); 5618 5619 #ifdef DEBUG 5620 // Verify exactly one property was added by comparing the property map 5621 // lengths. 5622 if (oldSharedShape->propMapLength() == PropMap::Capacity) { 5623 MOZ_ASSERT(newShape->propMapLength() == 1); 5624 } else { 5625 MOZ_ASSERT(newShape->propMapLength() == 5626 oldSharedShape->propMapLength() + 1); 5627 } 5628 #endif 5629 5630 bool isFunctionPrototype = IsFunctionPrototype(cx_->names(), nobj, id); 5631 5632 JSOp op = JSOp(*pc_); 5633 PropertyFlags flags = SetPropertyFlags(op, isFunctionPrototype); 5634 5635 // Basic property checks. 5636 if (!propInfo.isDataProperty() || propInfo.flags() != flags) { 5637 return AttachDecision::NoAction; 5638 } 5639 5640 ObjOperandId objId = writer.guardToObject(objValId); 5641 maybeEmitIdGuard(id); 5642 5643 // Shape guard the object. 5644 writer.guardShape(objId, oldShape); 5645 5646 // If this is the special function.prototype case, we need to guard the 5647 // function is a non-builtin constructor. See canAttachAddSlotStub. 5648 if (isFunctionPrototype) { 5649 MOZ_ASSERT(nobj->as<JSFunction>().isNonBuiltinConstructor()); 5650 writer.guardFunctionIsNonBuiltinCtor(objId); 5651 } 5652 5653 // Also shape guard the proto chain, unless this is an InitElem. 5654 if (IsPropertySetOp(op)) { 5655 ShapeGuardProtoChain(writer, nobj, objId); 5656 } 5657 5658 // If the JSClass has an addProperty hook, we need to call a VM function to 5659 // invoke this hook. Ignore the Array addProperty hook, because it doesn't do 5660 // anything for non-index properties. 5661 DebugOnly<uint32_t> index; 5662 MOZ_ASSERT_IF(obj->is<ArrayObject>(), !IdIsIndex(id, &index)); 5663 bool mustCallAddPropertyHook = 5664 !obj->is<ArrayObject>() && obj->getClass()->getAddProperty(); 5665 bool preserveWrapper = 5666 obj->getClass()->preservesWrapper() && 5667 !oldShape->hasObjectFlag(ObjectFlag::HasPreservedWrapper); 5668 5669 if (mustCallAddPropertyHook) { 5670 writer.addSlotAndCallAddPropHook(objId, rhsValId, newShape); 5671 trackAttached("SetProp.AddSlotWithAddPropertyHook"); 5672 } else if (holder->isFixedSlot(propInfo.slot())) { 5673 size_t offset = NativeObject::getFixedSlotOffset(propInfo.slot()); 5674 writer.addAndStoreFixedSlot(objId, offset, rhsValId, newShape, 5675 preserveWrapper); 5676 trackAttached("SetProp.AddSlotFixed"); 5677 } else { 5678 size_t offset = holder->dynamicSlotIndex(propInfo.slot()) * sizeof(Value); 5679 uint32_t numOldSlots = NativeObject::calculateDynamicSlots(oldSharedShape); 5680 uint32_t numNewSlots = holder->numDynamicSlots(); 5681 if (numOldSlots == numNewSlots) { 5682 writer.addAndStoreDynamicSlot(objId, offset, rhsValId, newShape, 5683 preserveWrapper); 5684 trackAttached("SetProp.AddSlotDynamic"); 5685 } else { 5686 MOZ_ASSERT(numNewSlots > numOldSlots); 5687 writer.allocateAndStoreDynamicSlot(objId, offset, rhsValId, newShape, 5688 numNewSlots, preserveWrapper); 5689 trackAttached("SetProp.AllocateSlot"); 5690 } 5691 } 5692 writer.returnFromIC(); 5693 5694 return AttachDecision::Attach; 5695 } 5696 5697 InstanceOfIRGenerator::InstanceOfIRGenerator(JSContext* cx, HandleScript script, 5698 jsbytecode* pc, ICState state, 5699 HandleValue lhs, HandleObject rhs) 5700 : IRGenerator(cx, script, pc, CacheKind::InstanceOf, state), 5701 lhsVal_(lhs), 5702 rhsObj_(rhs) {} 5703 5704 AttachDecision InstanceOfIRGenerator::tryAttachStub() { 5705 MOZ_ASSERT(cacheKind_ == CacheKind::InstanceOf); 5706 AutoAssertNoPendingException aanpe(cx_); 5707 5708 TRY_ATTACH(tryAttachFunction()); 5709 5710 trackAttached(IRGenerator::NotAttached); 5711 return AttachDecision::NoAction; 5712 } 5713 5714 AttachDecision InstanceOfIRGenerator::tryAttachFunction() { 5715 // Ensure RHS is a function -- could be a Proxy, which the IC isn't prepared 5716 // to handle. 5717 if (!rhsObj_->is<JSFunction>()) { 5718 return AttachDecision::NoAction; 5719 } 5720 5721 HandleFunction fun = rhsObj_.as<JSFunction>(); 5722 5723 // Look up the @@hasInstance property, and check that Function.__proto__ is 5724 // the property holder, and that no object further down the prototype chain 5725 // (including this function) has shadowed it; together with the fact that 5726 // Function.__proto__[@@hasInstance] is immutable, this ensures that the 5727 // hasInstance hook will not change without the need to guard on the actual 5728 // property value. 5729 PropertyResult hasInstanceProp; 5730 Rooted<NativeObject*> hasInstanceHolder(cx_); 5731 jsid hasInstanceID = PropertyKey::Symbol(cx_->wellKnownSymbols().hasInstance); 5732 if (!LookupPropertyPure(cx_, fun, hasInstanceID, hasInstanceHolder.address(), 5733 &hasInstanceProp) || 5734 !hasInstanceProp.isNativeProperty()) { 5735 return AttachDecision::NoAction; 5736 } 5737 5738 if (hasInstanceHolder != &cx_->global()->getPrototype(JSProto_Function)) { 5739 return AttachDecision::NoAction; 5740 } 5741 5742 // If the above succeeded, then these should be true about @@hasInstance, 5743 // because the property on Function.__proto__ is an immutable data property: 5744 MOZ_ASSERT(hasInstanceProp.propertyInfo().isDataProperty()); 5745 MOZ_ASSERT(!hasInstanceProp.propertyInfo().configurable()); 5746 MOZ_ASSERT(!hasInstanceProp.propertyInfo().writable()); 5747 5748 MOZ_ASSERT(IsCacheableProtoChain(fun, hasInstanceHolder)); 5749 5750 // Look up the function's .prototype property. 5751 Maybe<PropertyInfo> prototypeProp = fun->lookupPure(cx_->names().prototype); 5752 if (prototypeProp.isNothing()) { 5753 if (!fun->needsPrototypeProperty()) { 5754 return AttachDecision::NoAction; 5755 } 5756 // The function does not have a (lazily resolved) .prototype property yet. 5757 // If the LHS is a primitive, the fallback code in OrdinaryHasInstance will 5758 // return before resolving this property. Our CacheIR implementation expects 5759 // a .prototype property so we resolve it now. 5760 bool hasProp; 5761 if (!HasProperty(cx_, fun, cx_->names().prototype, &hasProp)) { 5762 cx_->clearPendingException(); 5763 return AttachDecision::NoAction; 5764 } 5765 MOZ_ASSERT(hasProp); 5766 prototypeProp = fun->lookupPure(cx_->names().prototype); 5767 MOZ_ASSERT(prototypeProp); 5768 } 5769 if (!prototypeProp->isDataProperty()) { 5770 return AttachDecision::NoAction; 5771 } 5772 5773 // Ensure the .prototype value is an object. 5774 uint32_t prototypeSlot = prototypeProp->slot(); 5775 MOZ_ASSERT(prototypeSlot >= fun->numFixedSlots(), 5776 "LoadDynamicSlot expects a dynamic slot"); 5777 if (!fun->getSlot(prototypeSlot).isObject()) { 5778 return AttachDecision::NoAction; 5779 } 5780 5781 // Abstract Objects 5782 ValOperandId lhs(writer.setInputOperandId(0)); 5783 ValOperandId rhs(writer.setInputOperandId(1)); 5784 5785 ObjOperandId rhsId = writer.guardToObject(rhs); 5786 writer.guardShape(rhsId, fun->shape()); 5787 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); 5795 } 5796 5797 // Load the .prototype value and ensure it's an object. 5798 ValOperandId protoValId = 5799 writer.loadDynamicSlot(rhsId, prototypeSlot - fun->numFixedSlots()); 5800 ObjOperandId protoId = writer.guardToObject(protoValId); 5801 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; 5808 } 5809 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_)); 5816 } 5817 #else 5818 // Silence Clang -Wunused-private-field warning. 5819 (void)lhsVal_; 5820 #endif 5821 } 5822 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) {} 5827 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_); 5833 } 5834 #endif 5835 } 5836 5837 AttachDecision TypeOfIRGenerator::tryAttachStub() { 5838 MOZ_ASSERT(cacheKind_ == CacheKind::TypeOf); 5839 5840 AutoAssertNoPendingException aanpe(cx_); 5841 5842 ValOperandId valId(writer.setInputOperandId(0)); 5843 5844 TRY_ATTACH(tryAttachPrimitive(valId)); 5845 TRY_ATTACH(tryAttachObject(valId)); 5846 5847 MOZ_ASSERT_UNREACHABLE("Failed to attach TypeOf"); 5848 return AttachDecision::NoAction; 5849 } 5850 5851 AttachDecision TypeOfIRGenerator::tryAttachPrimitive(ValOperandId valId) { 5852 if (!val_.isPrimitive()) { 5853 return AttachDecision::NoAction; 5854 } 5855 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()); 5862 } 5863 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; 5870 } 5871 5872 AttachDecision TypeOfIRGenerator::tryAttachObject(ValOperandId valId) { 5873 if (!val_.isObject()) { 5874 return AttachDecision::NoAction; 5875 } 5876 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; 5883 } 5884 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) {} 5893 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_); 5901 } 5902 #endif 5903 } 5904 5905 AttachDecision TypeOfEqIRGenerator::tryAttachStub() { 5906 MOZ_ASSERT(cacheKind_ == CacheKind::TypeOfEq); 5907 5908 AutoAssertNoPendingException aanpe(cx_); 5909 5910 ValOperandId valId(writer.setInputOperandId(0)); 5911 5912 TRY_ATTACH(tryAttachPrimitive(valId)); 5913 TRY_ATTACH(tryAttachObject(valId)); 5914 5915 MOZ_ASSERT_UNREACHABLE("Failed to attach TypeOfEq"); 5916 return AttachDecision::NoAction; 5917 } 5918 5919 AttachDecision TypeOfEqIRGenerator::tryAttachPrimitive(ValOperandId valId) { 5920 if (!val_.isPrimitive()) { 5921 return AttachDecision::NoAction; 5922 } 5923 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()); 5930 } 5931 5932 bool result = js::TypeOfValue(val_) == type_; 5933 if (compareOp_ == JSOp::Ne) { 5934 result = !result; 5935 } 5936 writer.loadBooleanResult(result); 5937 writer.returnFromIC(); 5938 writer.setTypeData(TypeData(JSValueType(val_.type()))); 5939 trackAttached("TypeOfEq.Primitive"); 5940 return AttachDecision::Attach; 5941 } 5942 5943 AttachDecision TypeOfEqIRGenerator::tryAttachObject(ValOperandId valId) { 5944 if (!val_.isObject()) { 5945 return AttachDecision::NoAction; 5946 } 5947 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; 5954 } 5955 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) {} 5961 5962 AttachDecision GetIteratorIRGenerator::tryAttachStub() { 5963 MOZ_ASSERT(cacheKind_ == CacheKind::GetIterator); 5964 5965 AutoAssertNoPendingException aanpe(cx_); 5966 5967 ValOperandId valId(writer.setInputOperandId(0)); 5968 5969 TRY_ATTACH(tryAttachObject(valId)); 5970 TRY_ATTACH(tryAttachNullOrUndefined(valId)); 5971 TRY_ATTACH(tryAttachGeneric(valId)); 5972 5973 trackAttached(IRGenerator::NotAttached); 5974 return AttachDecision::NoAction; 5975 } 5976 5977 AttachDecision GetIteratorIRGenerator::tryAttachObject(ValOperandId valId) { 5978 if (!val_.isObject()) { 5979 return AttachDecision::NoAction; 5980 } 5981 5982 MOZ_ASSERT(val_.toObject().compartment() == cx_->compartment()); 5983 5984 ObjOperandId objId = writer.guardToObject(valId); 5985 writer.objectToIteratorResult(objId, cx_->compartment()->enumeratorsAddr()); 5986 writer.returnFromIC(); 5987 5988 trackAttached("GetIterator.Object"); 5989 return AttachDecision::Attach; 5990 } 5991 5992 AttachDecision GetIteratorIRGenerator::tryAttachNullOrUndefined( 5993 ValOperandId valId) { 5994 MOZ_ASSERT(JSOp(*pc_) == JSOp::Iter); 5995 5996 // For null/undefined we can simply return the empty iterator singleton. This 5997 // works because this iterator is unlinked and immutable. 5998 5999 if (!val_.isNullOrUndefined()) { 6000 return AttachDecision::NoAction; 6001 } 6002 6003 PropertyIteratorObject* emptyIter = 6004 GlobalObject::getOrCreateEmptyIterator(cx_); 6005 if (!emptyIter) { 6006 cx_->recoverFromOutOfMemory(); 6007 return AttachDecision::NoAction; 6008 } 6009 6010 writer.guardIsNullOrUndefined(valId); 6011 6012 ObjOperandId iterId = writer.loadObject(emptyIter); 6013 writer.loadObjectResult(iterId); 6014 writer.returnFromIC(); 6015 6016 trackAttached("GetIterator.NullOrUndefined"); 6017 return AttachDecision::Attach; 6018 } 6019 6020 AttachDecision GetIteratorIRGenerator::tryAttachGeneric(ValOperandId valId) { 6021 writer.valueToIteratorResult(valId); 6022 writer.returnFromIC(); 6023 6024 trackAttached("GetIterator.Generic"); 6025 return AttachDecision::Attach; 6026 } 6027 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_); 6033 } 6034 #endif 6035 } 6036 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) {} 6042 6043 AttachDecision OptimizeSpreadCallIRGenerator::tryAttachStub() { 6044 MOZ_ASSERT(cacheKind_ == CacheKind::OptimizeSpreadCall); 6045 6046 AutoAssertNoPendingException aanpe(cx_); 6047 6048 TRY_ATTACH(tryAttachArray()); 6049 TRY_ATTACH(tryAttachArguments()); 6050 TRY_ATTACH(tryAttachNotOptimizable()); 6051 6052 trackAttached(IRGenerator::NotAttached); 6053 return AttachDecision::NoAction; 6054 } 6055 6056 AttachDecision OptimizeSpreadCallIRGenerator::tryAttachArray() { 6057 if (!isFirstStub_) { 6058 return AttachDecision::NoAction; 6059 } 6060 6061 // The value must be a packed array. 6062 if (!val_.isObject()) { 6063 return AttachDecision::NoAction; 6064 } 6065 Rooted<JSObject*> obj(cx_, &val_.toObject()); 6066 if (!IsArrayWithDefaultIterator<MustBePacked::Yes>(obj, cx_)) { 6067 return AttachDecision::NoAction; 6068 } 6069 6070 ValOperandId valId(writer.setInputOperandId(0)); 6071 ObjOperandId objId = writer.guardToObject(valId); 6072 6073 // Guard the object is a packed array with Array.prototype as proto. 6074 MOZ_ASSERT(obj->is<ArrayObject>()); 6075 writer.guardShape(objId, obj->shape()); 6076 writer.guardArrayIsPacked(objId); 6077 6078 // Ensure Array.prototype[@@iterator] and %ArrayIteratorPrototype%.next 6079 // haven't been mutated. 6080 writer.guardFuse(RealmFuses::FuseIndex::OptimizeGetIteratorFuse); 6081 6082 writer.loadObjectResult(objId); 6083 writer.returnFromIC(); 6084 6085 trackAttached("OptimizeSpreadCall.Array"); 6086 return AttachDecision::Attach; 6087 } 6088 6089 AttachDecision OptimizeSpreadCallIRGenerator::tryAttachArguments() { 6090 // The value must be an arguments object. 6091 if (!val_.isObject()) { 6092 return AttachDecision::NoAction; 6093 } 6094 RootedObject obj(cx_, &val_.toObject()); 6095 if (!obj->is<ArgumentsObject>()) { 6096 return AttachDecision::NoAction; 6097 } 6098 auto args = obj.as<ArgumentsObject>(); 6099 6100 // Ensure neither elements, nor the length, nor the iterator has been 6101 // overridden. Also ensure no args are forwarded to allow reading them 6102 // directly from the frame. 6103 if (args->hasOverriddenElement() || args->hasOverriddenLength() || 6104 args->hasOverriddenIterator() || args->anyArgIsForwarded()) { 6105 return AttachDecision::NoAction; 6106 } 6107 6108 // Don't optimize arguments objects from a different realm because in this 6109 // case we have to use the other realm's %ArrayIteratorPrototype% object. 6110 if (cx_->realm() != args->realm()) { 6111 return AttachDecision::NoAction; 6112 } 6113 6114 if (!HasOptimizableArrayIteratorPrototype(cx_)) { 6115 return AttachDecision::NoAction; 6116 } 6117 6118 Rooted<Shape*> shape(cx_, GlobalObject::getArrayShapeWithDefaultProto(cx_)); 6119 if (!shape) { 6120 cx_->clearPendingException(); 6121 return AttachDecision::NoAction; 6122 } 6123 6124 ValOperandId valId(writer.setInputOperandId(0)); 6125 ObjOperandId objId = writer.guardToObject(valId); 6126 6127 if (args->is<MappedArgumentsObject>()) { 6128 writer.guardClass(objId, GuardClassKind::MappedArguments); 6129 } else { 6130 MOZ_ASSERT(args->is<UnmappedArgumentsObject>()); 6131 writer.guardClass(objId, GuardClassKind::UnmappedArguments); 6132 } 6133 uint8_t flags = ArgumentsObject::ELEMENT_OVERRIDDEN_BIT | 6134 ArgumentsObject::LENGTH_OVERRIDDEN_BIT | 6135 ArgumentsObject::ITERATOR_OVERRIDDEN_BIT | 6136 ArgumentsObject::FORWARDED_ARGUMENTS_BIT; 6137 writer.guardArgumentsObjectFlags(objId, flags); 6138 writer.guardObjectHasSameRealm(objId); 6139 6140 writer.guardFuse(RealmFuses::FuseIndex::OptimizeArrayIteratorPrototypeFuse); 6141 6142 writer.arrayFromArgumentsObjectResult(objId, shape); 6143 writer.returnFromIC(); 6144 6145 trackAttached("OptimizeSpreadCall.Arguments"); 6146 return AttachDecision::Attach; 6147 } 6148 6149 AttachDecision OptimizeSpreadCallIRGenerator::tryAttachNotOptimizable() { 6150 ValOperandId valId(writer.setInputOperandId(0)); 6151 6152 writer.loadUndefinedResult(); 6153 writer.returnFromIC(); 6154 6155 trackAttached("OptimizeSpreadCall.NotOptimizable"); 6156 return AttachDecision::Attach; 6157 } 6158 6159 void OptimizeSpreadCallIRGenerator::trackAttached(const char* name) { 6160 stubName_ = name ? name : "NotAttached"; 6161 #ifdef JS_CACHEIR_SPEW 6162 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { 6163 sp.valueProperty("val", val_); 6164 } 6165 #endif 6166 } 6167 6168 CallIRGenerator::CallIRGenerator(JSContext* cx, HandleScript script, 6169 jsbytecode* pc, ICState state, 6170 BaselineFrame* frame, uint32_t argc, 6171 HandleValue callee, HandleValue thisval, 6172 HandleValue newTarget, HandleValueArray args) 6173 : IRGenerator(cx, script, pc, CacheKind::Call, state, frame), 6174 argc_(argc), 6175 callee_(callee), 6176 thisval_(thisval), 6177 newTarget_(newTarget), 6178 args_(args) {} 6179 6180 bool InlinableNativeIRGenerator::isCalleeBoundFunction() const { 6181 return callee()->is<BoundFunctionObject>(); 6182 } 6183 6184 BoundFunctionObject* InlinableNativeIRGenerator::boundCallee() const { 6185 MOZ_ASSERT(isCalleeBoundFunction()); 6186 return &callee()->as<BoundFunctionObject>(); 6187 } 6188 6189 ObjOperandId InlinableNativeIRGenerator::emitNativeCalleeGuard( 6190 Int32OperandId argcId) { 6191 // Note: we rely on GuardSpecificFunction to also guard against the same 6192 // native from a different realm. 6193 MOZ_ASSERT(target_->isNativeWithoutJitEntry()); 6194 6195 // Guards already emitted in GetPropIRGenerator::emitCallGetterResultGuards. 6196 if (isAccessorOp()) { 6197 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Standard); 6198 MOZ_ASSERT(!flags_.isConstructing()); 6199 MOZ_ASSERT(!isCalleeBoundFunction()); 6200 MOZ_ASSERT(!isTargetBoundFunction()); 6201 return ObjOperandId(); 6202 } 6203 6204 ValOperandId calleeValId; 6205 switch (flags_.getArgFormat()) { 6206 case CallFlags::Standard: 6207 case CallFlags::Spread: 6208 calleeValId = writer.loadArgumentFixedSlot(ArgumentKind::Callee, 6209 stackArgc(), flags_); 6210 break; 6211 case CallFlags::FunCall: 6212 case CallFlags::FunApplyArray: 6213 case CallFlags::FunApplyNullUndefined: 6214 calleeValId = writer.loadArgumentFixedSlot( 6215 ArgumentKind::Callee, stackArgc(), CallFlags(CallFlags::Standard)); 6216 break; 6217 case CallFlags::Unknown: 6218 case CallFlags::FunApplyArgsObj: 6219 MOZ_CRASH("Unsupported arg format"); 6220 } 6221 6222 // Guard that |callee| is an object. 6223 ObjOperandId calleeObjId = writer.guardToObject(calleeValId); 6224 ObjOperandId targetId = calleeObjId; 6225 6226 // The callee is a bound function whose bound target is |target_|. 6227 // 6228 // Example: 6229 // ``` 6230 // var boundPush = Array.prototype.push.bind(arr); 6231 // boundPush(1); 6232 // ``` 6233 // 6234 // Relevant generator members: 6235 // - |CallIRGenerator::callee_| is `boundPush` 6236 // - |InlinableNativeIRGenerator::target_| is `Array.prototype.push` 6237 // 6238 // Also see tryAttachBound{Native,FunCall,FunApply}. 6239 if (isCalleeBoundFunction()) { 6240 // Ensure the callee is a bound function. 6241 writer.guardClass(calleeObjId, GuardClassKind::BoundFunction); 6242 6243 // Ensure numBoundArgs matches. 6244 size_t numBoundArgs = boundCallee()->numBoundArgs(); 6245 Int32OperandId numBoundArgsId = 6246 writer.loadBoundFunctionNumArgs(calleeObjId); 6247 writer.guardSpecificInt32(numBoundArgsId, numBoundArgs); 6248 6249 // Load the bound function target. 6250 targetId = writer.loadBoundFunctionTarget(calleeObjId); 6251 } 6252 6253 if (flags_.getArgFormat() == CallFlags::FunCall || 6254 flags_.getArgFormat() == CallFlags::FunApplyArray || 6255 flags_.getArgFormat() == CallFlags::FunApplyNullUndefined) { 6256 JSFunction* funCallOrApply; 6257 ValOperandId thisValId; 6258 if (isCalleeBoundFunction()) { 6259 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::FunCall || 6260 flags_.getArgFormat() == CallFlags::FunApplyNullUndefined, 6261 "unexpected bound function"); 6262 6263 funCallOrApply = &boundCallee()->getTarget()->as<JSFunction>(); 6264 thisValId = writer.loadFixedSlot( 6265 calleeObjId, BoundFunctionObject::offsetOfBoundThisSlot()); 6266 } else { 6267 funCallOrApply = &callee()->as<JSFunction>(); 6268 thisValId = writer.loadArgumentFixedSlot(ArgumentKind::This, stackArgc(), 6269 CallFlags(CallFlags::Standard)); 6270 } 6271 MOZ_ASSERT(funCallOrApply->native() == fun_call || 6272 funCallOrApply->native() == fun_apply); 6273 6274 // Guard that |target| is the |fun_call| or |fun_apply| native function. 6275 writer.guardSpecificFunction(targetId, funCallOrApply); 6276 6277 // Guard that |this| is an object. 6278 targetId = writer.guardToObject(thisValId); 6279 } 6280 6281 // The callee calls a bound function whose bound target is |target_|. 6282 // 6283 // For example: 6284 // ``` 6285 // var boundPush = Array.prototype.push.bind(arr); 6286 // boundPush.call(null, 1); 6287 // ``` 6288 // 6289 // Relevant generator members: 6290 // - |CallIRGenerator::callee_| is `Function.prototype.call` 6291 // - |CallIRGenerator::thisval_| is `boundPush` 6292 // - |InlinableNativeIRGenerator::target_| is `Array.prototype.push` 6293 // 6294 // Also see tryAttach{FunCall,FunApply}Bound. 6295 if (isTargetBoundFunction()) { 6296 MOZ_ASSERT(!isCalleeBoundFunction(), "unexpected nested bound functions"); 6297 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::FunCall || 6298 flags_.getArgFormat() == CallFlags::FunApplyNullUndefined, 6299 "unsupported arg-format for bound target"); 6300 6301 // Ensure that |target| is a bound function. 6302 writer.guardClass(targetId, GuardClassKind::BoundFunction); 6303 6304 // Ensure numBoundArgs matches. 6305 size_t numBoundArgs = boundTarget()->numBoundArgs(); 6306 Int32OperandId numBoundArgsId = writer.loadBoundFunctionNumArgs(targetId); 6307 writer.guardSpecificInt32(numBoundArgsId, numBoundArgs); 6308 6309 // Return the bound function as callee to support loading bound arguments. 6310 calleeObjId = targetId; 6311 6312 // Load the bound function target. 6313 targetId = writer.loadBoundFunctionTarget(targetId); 6314 } 6315 6316 writer.guardSpecificFunction(targetId, target_); 6317 6318 // If we're constructing we also need to guard newTarget == callee. 6319 if (flags_.isConstructing()) { 6320 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Standard); 6321 MOZ_ASSERT(&newTarget_.toObject() == callee()); 6322 6323 ValOperandId newTargetValId = writer.loadArgumentFixedSlot( 6324 ArgumentKind::NewTarget, stackArgc(), flags_); 6325 ObjOperandId newTargetObjId = writer.guardToObject(newTargetValId); 6326 6327 if (isCalleeBoundFunction()) { 6328 writer.guardObjectIdentity(newTargetObjId, calleeObjId); 6329 } else { 6330 writer.guardSpecificFunction(newTargetObjId, target_); 6331 } 6332 } 6333 6334 // Guard the second argument is null or undefined. 6335 if (flags_.getArgFormat() == CallFlags::FunApplyNullUndefined) { 6336 constexpr size_t argIndex = 1; 6337 6338 size_t numBoundArgs = 0; 6339 if (isCalleeBoundFunction()) { 6340 numBoundArgs = boundCallee()->numBoundArgs(); 6341 } 6342 MOZ_ASSERT(numBoundArgs <= 2); 6343 6344 ValOperandId argValId; 6345 if (argIndex < numBoundArgs) { 6346 argValId = loadBoundArgument(calleeObjId, argIndex); 6347 } else { 6348 auto argKind = ArgumentKindForArgIndex(argIndex - numBoundArgs); 6349 argValId = writer.loadArgumentFixedSlot(argKind, stackArgc(), 6350 CallFlags(CallFlags::Standard)); 6351 } 6352 6353 writer.guardIsNullOrUndefined(argValId); 6354 } 6355 6356 return calleeObjId; 6357 } 6358 6359 ObjOperandId InlinableNativeIRGenerator::emitLoadArgsArray() { 6360 MOZ_ASSERT(!hasBoundArguments()); 6361 6362 if (flags_.getArgFormat() == CallFlags::Spread) { 6363 return writer.loadSpreadArgs(); 6364 } 6365 6366 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::FunApplyArray); 6367 return gen_.as<CallIRGenerator*>() 6368 ->emitFunApplyArgsGuard(flags_.getArgFormat()) 6369 .ref(); 6370 } 6371 6372 ValOperandId InlinableNativeIRGenerator::loadBoundArgument( 6373 ObjOperandId calleeId, size_t argIndex) { 6374 MOZ_ASSERT(isCalleeBoundFunction() || isTargetBoundFunction()); 6375 6376 auto* bound = isCalleeBoundFunction() ? boundCallee() : boundTarget(); 6377 size_t numBoundArgs = bound->numBoundArgs(); 6378 MOZ_ASSERT(argIndex < numBoundArgs); 6379 6380 if (numBoundArgs <= BoundFunctionObject::MaxInlineBoundArgs) { 6381 constexpr size_t inlineArgsOffset = 6382 BoundFunctionObject::offsetOfFirstInlineBoundArg(); 6383 6384 size_t argSlot = inlineArgsOffset + argIndex * sizeof(Value); 6385 return writer.loadFixedSlot(calleeId, argSlot); 6386 } 6387 return writer.loadBoundFunctionArgument(calleeId, argIndex); 6388 } 6389 6390 ValOperandId InlinableNativeIRGenerator::loadThis(ObjOperandId calleeId) { 6391 // Accessor operations use the receiver as their |this| value. 6392 if (isAccessorOp()) { 6393 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Standard); 6394 MOZ_ASSERT(!isTargetBoundFunction()); 6395 MOZ_ASSERT(!isCalleeBoundFunction()); 6396 MOZ_ASSERT(receiverId_.valid()); 6397 return receiverId_; 6398 } 6399 6400 switch (flags_.getArgFormat()) { 6401 case CallFlags::Standard: 6402 case CallFlags::Spread: 6403 MOZ_ASSERT(!isTargetBoundFunction()); 6404 if (isCalleeBoundFunction()) { 6405 return writer.loadFixedSlot( 6406 calleeId, BoundFunctionObject::offsetOfBoundThisSlot()); 6407 } 6408 return writer.loadArgumentFixedSlot(ArgumentKind::This, stackArgc(), 6409 flags_); 6410 case CallFlags::FunCall: 6411 case CallFlags::FunApplyNullUndefined: 6412 // Load |this| from bound this. 6413 if (isTargetBoundFunction()) { 6414 return writer.loadFixedSlot( 6415 calleeId, BoundFunctionObject::offsetOfBoundThisSlot()); 6416 } 6417 6418 // Load |this| from bound arguments, if present. 6419 if (hasBoundArguments()) { 6420 MOZ_ASSERT(isCalleeBoundFunction()); 6421 return loadBoundArgument(calleeId, 0); 6422 } 6423 6424 // The stack layout is already in the correct form for calls with at least 6425 // one argument. 6426 // 6427 // clang-format off 6428 // 6429 // *** STACK LAYOUT (bottom to top) *** *** INDEX *** 6430 // Callee <-- argc+1 6431 // ThisValue <-- argc 6432 // Args: | Arg0 | <-- argc-1 6433 // | Arg1 | <-- argc-2 6434 // | ... | <-- ... 6435 // | ArgN | <-- 0 6436 // 6437 // When passing |argc-1| as the number of arguments, we get: 6438 // 6439 // *** STACK LAYOUT (bottom to top) *** *** INDEX *** 6440 // Callee <-- (argc-1)+1 = argc = ThisValue 6441 // ThisValue <-- (argc-1) = argc-1 = Arg0 6442 // Args: | Arg0 | <-- (argc-1)-1 = argc-2 = Arg1 6443 // | Arg1 | <-- (argc-1)-2 = argc-3 = Arg2 6444 // | ... | <-- ... 6445 // 6446 // clang-format on 6447 // 6448 // This allows to call |loadArgumentFixedSlot(ArgumentKind::This)| and we 6449 // still load the correct argument index from |ArgumentKind::Arg0|. 6450 // 6451 // When no arguments are passed, i.e. |argc==0|, we have to replace 6452 // |ArgumentKind::Arg0| with the undefined value. 6453 if (stackArgc() == 0) { 6454 return writer.loadUndefined(); 6455 } 6456 return writer.loadArgumentFixedSlot(ArgumentKind::This, stackArgc() - 1, 6457 CallFlags(CallFlags::Standard)); 6458 case CallFlags::FunApplyArray: 6459 case CallFlags::FunApplyArgsObj: 6460 MOZ_ASSERT(stackArgc() > 0); 6461 MOZ_ASSERT(!isCalleeBoundFunction()); 6462 MOZ_ASSERT(!isTargetBoundFunction()); 6463 return writer.loadArgumentFixedSlot(ArgumentKind::This, stackArgc() - 1, 6464 CallFlags(CallFlags::Standard)); 6465 case CallFlags::Unknown: 6466 break; 6467 } 6468 MOZ_CRASH("Unsupported arg format"); 6469 } 6470 6471 ValOperandId InlinableNativeIRGenerator::loadArgument(ObjOperandId calleeId, 6472 ArgumentKind kind) { 6473 MOZ_ASSERT(kind >= ArgumentKind::Arg0); 6474 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Standard || 6475 flags_.getArgFormat() == CallFlags::FunCall || 6476 flags_.getArgFormat() == CallFlags::FunApplyNullUndefined); 6477 MOZ_ASSERT_IF(flags_.getArgFormat() == CallFlags::FunApplyNullUndefined, 6478 isTargetBoundFunction() && hasBoundArguments()); 6479 MOZ_ASSERT(!isAccessorOp(), "get property operations don't have arguments"); 6480 6481 // Check if the |this| value is stored in the bound arguments. 6482 bool thisFromBoundArgs = flags_.getArgFormat() == CallFlags::FunCall && 6483 isCalleeBoundFunction() && hasBoundArguments(); 6484 6485 if (hasBoundArguments()) { 6486 auto* bound = isCalleeBoundFunction() ? boundCallee() : boundTarget(); 6487 size_t numBoundArgs = bound->numBoundArgs(); 6488 size_t argIndex = uint8_t(kind) - uint8_t(ArgumentKind::Arg0); 6489 6490 // Skip over the first bound argument, which stores the |this| value for 6491 // bound FunCall. 6492 if (thisFromBoundArgs) { 6493 argIndex += 1; 6494 } 6495 6496 // Load from bound args. 6497 if (argIndex < numBoundArgs) { 6498 return loadBoundArgument(calleeId, argIndex); 6499 } 6500 6501 // Load from stack arguments. 6502 kind = ArgumentKindForArgIndex(argIndex - numBoundArgs); 6503 } 6504 6505 switch (flags_.getArgFormat()) { 6506 case CallFlags::Standard: 6507 return writer.loadArgumentFixedSlot(kind, stackArgc(), flags_); 6508 case CallFlags::FunCall: 6509 if (thisFromBoundArgs) { 6510 return writer.loadArgumentFixedSlot(kind, stackArgc(), 6511 CallFlags(CallFlags::Standard)); 6512 } 6513 MOZ_ASSERT(stackArgc() > 1); 6514 // See |loadThis| for why we subtract |argc - 1| here. 6515 return writer.loadArgumentFixedSlot(kind, stackArgc() - 1, 6516 CallFlags(CallFlags::Standard)); 6517 case CallFlags::Spread: 6518 case CallFlags::FunApplyArray: 6519 case CallFlags::FunApplyArgsObj: 6520 case CallFlags::FunApplyNullUndefined: 6521 case CallFlags::Unknown: 6522 break; 6523 } 6524 MOZ_CRASH("Unsupported arg format"); 6525 } 6526 6527 bool InlinableNativeIRGenerator::hasBoundArguments() const { 6528 if (isCalleeBoundFunction()) { 6529 return boundCallee()->numBoundArgs() != 0; 6530 } 6531 if (isTargetBoundFunction()) { 6532 return boundTarget()->numBoundArgs() != 0; 6533 } 6534 return false; 6535 } 6536 6537 void IRGenerator::emitCalleeGuard(ObjOperandId calleeId, JSFunction* callee) { 6538 // Guarding on the callee JSFunction* is most efficient, but doesn't work well 6539 // for lambda clones (multiple functions with the same BaseScript). We guard 6540 // on the function's BaseScript if the callee is scripted and this isn't the 6541 // first IC stub. 6542 if (isFirstStub_ || !FunctionHasStableBaseScript(callee)) { 6543 writer.guardSpecificFunction(calleeId, callee); 6544 } else { 6545 MOZ_ASSERT_IF(callee->isSelfHostedBuiltin(), 6546 !callee->baseScript()->allowRelazify()); 6547 writer.guardClass(calleeId, GuardClassKind::JSFunction); 6548 writer.guardFunctionScript(calleeId, callee->baseScript()); 6549 } 6550 } 6551 6552 ObjOperandId CallIRGenerator::emitFunCallOrApplyGuard(Int32OperandId argcId) { 6553 JSFunction* callee = &callee_.toObject().as<JSFunction>(); 6554 MOZ_ASSERT(callee->native() == fun_call || callee->native() == fun_apply); 6555 6556 // |GetIndexOfArgument| doesn't yet support FunCall/FunApply. 6557 CallFlags flags(CallFlags::Standard); 6558 6559 // Guard that callee is the |fun_call| or |fun_apply| native function. 6560 ValOperandId calleeValId = 6561 writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId, flags); 6562 ObjOperandId calleeObjId = writer.guardToObject(calleeValId); 6563 writer.guardSpecificFunction(calleeObjId, callee); 6564 6565 // Guard that |this| is an object. 6566 ValOperandId thisValId = 6567 writer.loadArgumentDynamicSlot(ArgumentKind::This, argcId, flags); 6568 return writer.guardToObject(thisValId); 6569 } 6570 6571 ObjOperandId CallIRGenerator::emitFunCallGuard(Int32OperandId argcId) { 6572 MOZ_ASSERT(callee_.toObject().as<JSFunction>().native() == fun_call); 6573 6574 return emitFunCallOrApplyGuard(argcId); 6575 } 6576 6577 ObjOperandId CallIRGenerator::emitFunApplyGuard(Int32OperandId argcId) { 6578 MOZ_ASSERT(callee_.toObject().as<JSFunction>().native() == fun_apply); 6579 6580 return emitFunCallOrApplyGuard(argcId); 6581 } 6582 6583 Maybe<ObjOperandId> CallIRGenerator::emitFunApplyArgsGuard( 6584 CallFlags::ArgFormat format) { 6585 MOZ_ASSERT(argc_ == 2); 6586 6587 // |GetIndexOfArgument| doesn't yet support FunCall/FunApply. 6588 CallFlags flags(CallFlags::Standard); 6589 6590 ValOperandId argValId = 6591 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_, flags); 6592 6593 if (format == CallFlags::FunApplyArgsObj) { 6594 ObjOperandId argObjId = writer.guardToObject(argValId); 6595 if (args_[1].toObject().is<MappedArgumentsObject>()) { 6596 writer.guardClass(argObjId, GuardClassKind::MappedArguments); 6597 } else { 6598 MOZ_ASSERT(args_[1].toObject().is<UnmappedArgumentsObject>()); 6599 writer.guardClass(argObjId, GuardClassKind::UnmappedArguments); 6600 } 6601 uint8_t flags = ArgumentsObject::ELEMENT_OVERRIDDEN_BIT | 6602 ArgumentsObject::FORWARDED_ARGUMENTS_BIT; 6603 writer.guardArgumentsObjectFlags(argObjId, flags); 6604 return mozilla::Some(argObjId); 6605 } 6606 6607 if (format == CallFlags::FunApplyArray) { 6608 ObjOperandId argObjId = writer.guardToObject(argValId); 6609 emitOptimisticClassGuard(argObjId, &args_[1].toObject(), 6610 GuardClassKind::Array); 6611 writer.guardArrayIsPacked(argObjId); 6612 return mozilla::Some(argObjId); 6613 } 6614 6615 MOZ_ASSERT(format == CallFlags::FunApplyNullUndefined); 6616 writer.guardIsNullOrUndefined(argValId); 6617 return mozilla::Nothing(); 6618 } 6619 6620 AttachDecision InlinableNativeIRGenerator::tryAttachArrayPush() { 6621 // Only optimize on obj.push(val); 6622 if (args_.length() != 1 || !thisval_.isObject()) { 6623 return AttachDecision::NoAction; 6624 } 6625 6626 // Where |obj| is a native array. 6627 JSObject* thisobj = &thisval_.toObject(); 6628 if (!thisobj->is<ArrayObject>()) { 6629 return AttachDecision::NoAction; 6630 } 6631 6632 auto* thisarray = &thisobj->as<ArrayObject>(); 6633 6634 // Check for other indexed properties or class hooks. 6635 if (!CanAttachAddElement(thisarray, /* isInit = */ false, 6636 AllowIndexedReceiver::No)) { 6637 return AttachDecision::NoAction; 6638 } 6639 6640 // Can't add new elements to arrays with non-writable length. 6641 if (!thisarray->lengthIsWritable()) { 6642 return AttachDecision::NoAction; 6643 } 6644 6645 // Check that array is extensible. 6646 if (!thisarray->isExtensible()) { 6647 return AttachDecision::NoAction; 6648 } 6649 6650 // Check that the array is completely initialized (no holes). 6651 if (thisarray->getDenseInitializedLength() != thisarray->length()) { 6652 return AttachDecision::NoAction; 6653 } 6654 6655 MOZ_ASSERT(!thisarray->denseElementsAreFrozen(), 6656 "Extensible arrays should not have frozen elements"); 6657 6658 // After this point, we can generate code fine. 6659 6660 // Initialize the input operand. 6661 Int32OperandId argcId = initializeInputOperand(); 6662 6663 // Guard callee is the 'push' native function. 6664 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 6665 6666 // Guard this is an array object. 6667 ValOperandId thisValId = loadThis(calleeId); 6668 ObjOperandId thisObjId = writer.guardToObject(thisValId); 6669 6670 // Guard that the shape matches. 6671 TestMatchingNativeReceiver(writer, thisarray, thisObjId); 6672 6673 // Guard proto chain shapes. 6674 ShapeGuardProtoChain(writer, thisarray, thisObjId); 6675 6676 // arr.push(x) is equivalent to arr[arr.length] = x for regular arrays. 6677 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 6678 writer.arrayPush(thisObjId, argId); 6679 6680 writer.returnFromIC(); 6681 6682 trackAttached("ArrayPush"); 6683 return AttachDecision::Attach; 6684 } 6685 6686 AttachDecision InlinableNativeIRGenerator::tryAttachArrayPopShift( 6687 InlinableNative native) { 6688 // Expecting no arguments. 6689 if (args_.length() != 0) { 6690 return AttachDecision::NoAction; 6691 } 6692 6693 // Only optimize if |this| is a packed array. 6694 if (!thisval_.isObject() || !IsPackedArray(&thisval_.toObject())) { 6695 return AttachDecision::NoAction; 6696 } 6697 6698 // Other conditions: 6699 // 6700 // * The array length needs to be writable because we're changing it. 6701 // * The array must be extensible. Non-extensible arrays require preserving 6702 // the |initializedLength == capacity| invariant on ObjectElements. 6703 // See NativeObject::shrinkCapacityToInitializedLength. 6704 // This also ensures the elements aren't sealed/frozen. 6705 // * There must not be a for-in iterator for the elements because the IC stub 6706 // does not suppress deleted properties. 6707 ArrayObject* arr = &thisval_.toObject().as<ArrayObject>(); 6708 if (!arr->lengthIsWritable() || !arr->isExtensible() || 6709 arr->denseElementsHaveMaybeInIterationFlag()) { 6710 return AttachDecision::NoAction; 6711 } 6712 6713 // Initialize the input operand. 6714 Int32OperandId argcId = initializeInputOperand(); 6715 6716 // Guard callee is the 'pop' or 'shift' native function. 6717 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 6718 6719 ValOperandId thisValId = loadThis(calleeId); 6720 ObjOperandId objId = writer.guardToObject(thisValId); 6721 emitOptimisticClassGuard(objId, arr, GuardClassKind::Array); 6722 6723 if (native == InlinableNative::ArrayPop) { 6724 writer.packedArrayPopResult(objId); 6725 } else { 6726 MOZ_ASSERT(native == InlinableNative::ArrayShift); 6727 writer.packedArrayShiftResult(objId); 6728 } 6729 6730 writer.returnFromIC(); 6731 6732 trackAttached("ArrayPopShift"); 6733 return AttachDecision::Attach; 6734 } 6735 6736 AttachDecision InlinableNativeIRGenerator::tryAttachArrayJoin() { 6737 // Only handle argc <= 1. 6738 if (args_.length() > 1) { 6739 return AttachDecision::NoAction; 6740 } 6741 6742 // Only optimize if |this| is an array. 6743 if (!thisval_.isObject() || !thisval_.toObject().is<ArrayObject>()) { 6744 return AttachDecision::NoAction; 6745 } 6746 6747 // The separator argument must be a string, if present. 6748 if (args_.length() > 0 && !args_[0].isString()) { 6749 return AttachDecision::NoAction; 6750 } 6751 6752 // IC stub code can handle non-packed array. 6753 6754 // Initialize the input operand. 6755 Int32OperandId argcId = initializeInputOperand(); 6756 6757 // Guard callee is the 'join' native function. 6758 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 6759 6760 // Guard this is an array object. 6761 ValOperandId thisValId = loadThis(calleeId); 6762 ObjOperandId thisObjId = writer.guardToObject(thisValId); 6763 emitOptimisticClassGuard(thisObjId, &thisval_.toObject(), 6764 GuardClassKind::Array); 6765 6766 StringOperandId sepId; 6767 if (args_.length() == 1) { 6768 // If argcount is 1, guard that the argument is a string. 6769 ValOperandId argValId = loadArgument(calleeId, ArgumentKind::Arg0); 6770 sepId = writer.guardToString(argValId); 6771 } else { 6772 sepId = writer.loadConstantString(cx_->names().comma_); 6773 } 6774 6775 // Do the join. 6776 writer.arrayJoinResult(thisObjId, sepId); 6777 6778 writer.returnFromIC(); 6779 6780 trackAttached("ArrayJoin"); 6781 return AttachDecision::Attach; 6782 } 6783 6784 AttachDecision InlinableNativeIRGenerator::tryAttachArraySlice() { 6785 // Only handle argc <= 2. 6786 if (args_.length() > 2) { 6787 return AttachDecision::NoAction; 6788 } 6789 6790 // Only optimize if |this| is a packed array or an arguments object. 6791 if (!thisval_.isObject()) { 6792 return AttachDecision::NoAction; 6793 } 6794 6795 bool isPackedArray = IsPackedArray(&thisval_.toObject()); 6796 if (!isPackedArray) { 6797 if (!thisval_.toObject().is<ArgumentsObject>()) { 6798 return AttachDecision::NoAction; 6799 } 6800 auto* args = &thisval_.toObject().as<ArgumentsObject>(); 6801 6802 // No elements must have been overridden or deleted. 6803 if (args->hasOverriddenElement()) { 6804 return AttachDecision::NoAction; 6805 } 6806 6807 // The length property mustn't be overridden. 6808 if (args->hasOverriddenLength()) { 6809 return AttachDecision::NoAction; 6810 } 6811 6812 // And finally also check that no argument is forwarded. 6813 if (args->anyArgIsForwarded()) { 6814 return AttachDecision::NoAction; 6815 } 6816 } 6817 6818 // Arguments for the sliced region must be integers. 6819 if (args_.length() > 0 && !args_[0].isInt32()) { 6820 return AttachDecision::NoAction; 6821 } 6822 if (args_.length() > 1 && !args_[1].isInt32()) { 6823 return AttachDecision::NoAction; 6824 } 6825 6826 JSObject* templateObj = NewDenseFullyAllocatedArray(cx_, 0, TenuredObject); 6827 if (!templateObj) { 6828 cx_->recoverFromOutOfMemory(); 6829 return AttachDecision::NoAction; 6830 } 6831 6832 // Initialize the input operand. 6833 Int32OperandId argcId = initializeInputOperand(); 6834 6835 // Guard callee is the 'slice' native function. 6836 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 6837 6838 ValOperandId thisValId = loadThis(calleeId); 6839 ObjOperandId objId = writer.guardToObject(thisValId); 6840 6841 if (isPackedArray) { 6842 emitOptimisticClassGuard(objId, &thisval_.toObject(), 6843 GuardClassKind::Array); 6844 writer.guardArrayIsPacked(objId); 6845 } else { 6846 auto* args = &thisval_.toObject().as<ArgumentsObject>(); 6847 6848 if (args->is<MappedArgumentsObject>()) { 6849 writer.guardClass(objId, GuardClassKind::MappedArguments); 6850 } else { 6851 MOZ_ASSERT(args->is<UnmappedArgumentsObject>()); 6852 writer.guardClass(objId, GuardClassKind::UnmappedArguments); 6853 } 6854 6855 uint8_t flags = ArgumentsObject::ELEMENT_OVERRIDDEN_BIT | 6856 ArgumentsObject::LENGTH_OVERRIDDEN_BIT | 6857 ArgumentsObject::FORWARDED_ARGUMENTS_BIT; 6858 writer.guardArgumentsObjectFlags(objId, flags); 6859 } 6860 6861 Int32OperandId int32BeginId; 6862 if (args_.length() > 0) { 6863 ValOperandId beginId = loadArgument(calleeId, ArgumentKind::Arg0); 6864 int32BeginId = writer.guardToInt32(beginId); 6865 } else { 6866 int32BeginId = writer.loadInt32Constant(0); 6867 } 6868 6869 Int32OperandId int32EndId; 6870 if (args_.length() > 1) { 6871 ValOperandId endId = loadArgument(calleeId, ArgumentKind::Arg1); 6872 int32EndId = writer.guardToInt32(endId); 6873 } else if (isPackedArray) { 6874 int32EndId = writer.loadInt32ArrayLength(objId); 6875 } else { 6876 int32EndId = writer.loadArgumentsObjectLength(objId); 6877 } 6878 6879 if (isPackedArray) { 6880 writer.packedArraySliceResult(templateObj, objId, int32BeginId, int32EndId); 6881 } else { 6882 writer.argumentsSliceResult(templateObj, objId, int32BeginId, int32EndId); 6883 } 6884 writer.returnFromIC(); 6885 6886 trackAttached(isPackedArray ? "ArraySlice" : "ArgumentsSlice"); 6887 return AttachDecision::Attach; 6888 } 6889 6890 AttachDecision InlinableNativeIRGenerator::tryAttachArrayIsArray() { 6891 // Need a single argument. 6892 if (args_.length() != 1) { 6893 return AttachDecision::NoAction; 6894 } 6895 6896 // Initialize the input operand. 6897 Int32OperandId argcId = initializeInputOperand(); 6898 6899 // Guard callee is the 'isArray' native function. 6900 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 6901 6902 // Check if the argument is an Array and return result. 6903 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 6904 writer.isArrayResult(argId); 6905 writer.returnFromIC(); 6906 6907 trackAttached("ArrayIsArray"); 6908 return AttachDecision::Attach; 6909 } 6910 6911 AttachDecision InlinableNativeIRGenerator::tryAttachDataViewGet( 6912 Scalar::Type type) { 6913 // Ensure |this| is a DataViewObject. 6914 if (!thisval_.isObject() || !thisval_.toObject().is<DataViewObject>()) { 6915 return AttachDecision::NoAction; 6916 } 6917 6918 // Expected arguments: offset (number), optional littleEndian (boolean). 6919 if (args_.length() < 1 || args_.length() > 2) { 6920 return AttachDecision::NoAction; 6921 } 6922 int64_t offsetInt64; 6923 if (!ValueIsInt64Index(args_[0], &offsetInt64)) { 6924 return AttachDecision::NoAction; 6925 } 6926 if (args_.length() > 1 && !args_[1].isBoolean()) { 6927 return AttachDecision::NoAction; 6928 } 6929 6930 auto* dv = &thisval_.toObject().as<DataViewObject>(); 6931 6932 // Bounds check the offset. 6933 size_t byteLength = dv->byteLength().valueOr(0); 6934 if (offsetInt64 < 0 || !DataViewObject::offsetIsInBounds( 6935 Scalar::byteSize(type), offsetInt64, byteLength)) { 6936 return AttachDecision::NoAction; 6937 } 6938 6939 // For getUint32 we let the stub return an Int32 if we have not seen a 6940 // double, to allow better codegen in Warp while avoiding bailout loops. 6941 bool forceDoubleForUint32 = false; 6942 if (type == Scalar::Uint32) { 6943 bool isLittleEndian = args_.length() > 1 && args_[1].toBoolean(); 6944 uint32_t res = dv->read<uint32_t>(offsetInt64, byteLength, isLittleEndian); 6945 forceDoubleForUint32 = res >= INT32_MAX; 6946 } 6947 6948 // Initialize the input operand. 6949 Int32OperandId argcId = initializeInputOperand(); 6950 6951 // Guard callee is this DataView native function. 6952 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 6953 6954 // Guard |this| is a DataViewObject. 6955 ValOperandId thisValId = loadThis(calleeId); 6956 ObjOperandId objId = writer.guardToObject(thisValId); 6957 6958 if (dv->is<FixedLengthDataViewObject>()) { 6959 emitOptimisticClassGuard(objId, dv, GuardClassKind::FixedLengthDataView); 6960 } else if (dv->is<ImmutableDataViewObject>()) { 6961 emitOptimisticClassGuard(objId, dv, GuardClassKind::ImmutableDataView); 6962 } else { 6963 emitOptimisticClassGuard(objId, dv, GuardClassKind::ResizableDataView); 6964 } 6965 6966 // Convert offset to intPtr. 6967 ValOperandId offsetId = loadArgument(calleeId, ArgumentKind::Arg0); 6968 IntPtrOperandId intPtrOffsetId = 6969 guardToIntPtrIndex(args_[0], offsetId, /* supportOOB = */ false); 6970 6971 BooleanOperandId boolLittleEndianId; 6972 if (args_.length() > 1) { 6973 ValOperandId littleEndianId = loadArgument(calleeId, ArgumentKind::Arg1); 6974 boolLittleEndianId = writer.guardToBoolean(littleEndianId); 6975 } else { 6976 boolLittleEndianId = writer.loadBooleanConstant(false); 6977 } 6978 6979 auto viewKind = ToArrayBufferViewKind(dv); 6980 writer.loadDataViewValueResult(objId, intPtrOffsetId, boolLittleEndianId, 6981 type, forceDoubleForUint32, viewKind); 6982 6983 writer.returnFromIC(); 6984 6985 trackAttached("DataViewGet"); 6986 return AttachDecision::Attach; 6987 } 6988 6989 AttachDecision InlinableNativeIRGenerator::tryAttachDataViewSet( 6990 Scalar::Type type) { 6991 // Ensure |this| is a DataViewObject. 6992 if (!thisval_.isObject() || !thisval_.toObject().is<DataViewObject>()) { 6993 return AttachDecision::NoAction; 6994 } 6995 6996 // Expected arguments: offset (number), value, optional littleEndian (boolean) 6997 if (args_.length() < 2 || args_.length() > 3) { 6998 return AttachDecision::NoAction; 6999 } 7000 int64_t offsetInt64; 7001 if (!ValueIsInt64Index(args_[0], &offsetInt64)) { 7002 return AttachDecision::NoAction; 7003 } 7004 if (!ValueCanConvertToNumeric(type, args_[1])) { 7005 return AttachDecision::NoAction; 7006 } 7007 if (args_.length() > 2 && !args_[2].isBoolean()) { 7008 return AttachDecision::NoAction; 7009 } 7010 7011 auto* dv = &thisval_.toObject().as<DataViewObject>(); 7012 7013 // Immutable DataViews can't be modified. 7014 if (dv->is<ImmutableDataViewObject>()) { 7015 return AttachDecision::NoAction; 7016 } 7017 7018 // Bounds check the offset. 7019 size_t byteLength = dv->byteLength().valueOr(0); 7020 if (offsetInt64 < 0 || !DataViewObject::offsetIsInBounds( 7021 Scalar::byteSize(type), offsetInt64, byteLength)) { 7022 return AttachDecision::NoAction; 7023 } 7024 7025 // Initialize the input operand. 7026 Int32OperandId argcId = initializeInputOperand(); 7027 7028 // Guard callee is this DataView native function. 7029 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 7030 7031 // Guard |this| is a DataViewObject. 7032 ValOperandId thisValId = loadThis(calleeId); 7033 ObjOperandId objId = writer.guardToObject(thisValId); 7034 7035 if (dv->is<FixedLengthDataViewObject>()) { 7036 emitOptimisticClassGuard(objId, dv, GuardClassKind::FixedLengthDataView); 7037 } else { 7038 emitOptimisticClassGuard(objId, dv, GuardClassKind::ResizableDataView); 7039 } 7040 7041 // Convert offset to intPtr. 7042 ValOperandId offsetId = loadArgument(calleeId, ArgumentKind::Arg0); 7043 IntPtrOperandId intPtrOffsetId = 7044 guardToIntPtrIndex(args_[0], offsetId, /* supportOOB = */ false); 7045 7046 // Convert value to number or BigInt. 7047 ValOperandId valueId = loadArgument(calleeId, ArgumentKind::Arg1); 7048 OperandId numericValueId = emitNumericGuard(valueId, args_[1], type); 7049 7050 BooleanOperandId boolLittleEndianId; 7051 if (args_.length() > 2) { 7052 ValOperandId littleEndianId = loadArgument(calleeId, ArgumentKind::Arg2); 7053 boolLittleEndianId = writer.guardToBoolean(littleEndianId); 7054 } else { 7055 boolLittleEndianId = writer.loadBooleanConstant(false); 7056 } 7057 7058 auto viewKind = ToArrayBufferViewKind(dv); 7059 writer.storeDataViewValueResult(objId, intPtrOffsetId, numericValueId, 7060 boolLittleEndianId, type, viewKind); 7061 7062 writer.returnFromIC(); 7063 7064 trackAttached("DataViewSet"); 7065 return AttachDecision::Attach; 7066 } 7067 7068 AttachDecision InlinableNativeIRGenerator::tryAttachDataViewByteLength() { 7069 // Expecting no arguments. 7070 if (args_.length() != 0) { 7071 return AttachDecision::NoAction; 7072 } 7073 7074 // Ensure |this| is a DataViewObject. 7075 if (!thisval_.isObject() || !thisval_.toObject().is<DataViewObject>()) { 7076 return AttachDecision::NoAction; 7077 } 7078 7079 auto* dv = &thisval_.toObject().as<DataViewObject>(); 7080 7081 // byteLength throws when the ArrayBuffer is detached. 7082 if (dv->hasDetachedBuffer()) { 7083 // The has-attached-arraybuffer guard is elided for immutable views. Assert 7084 // we never see an immutable view with a detached buffer. 7085 MOZ_ASSERT(!dv->is<ImmutableDataViewObject>(), 7086 "immutable data views can't have their buffer detached"); 7087 return AttachDecision::NoAction; 7088 } 7089 7090 // byteLength throws when the ArrayBuffer is out-of-bounds. 7091 if (dv->is<ResizableDataViewObject>() && 7092 dv->as<ResizableDataViewObject>().isOutOfBounds()) { 7093 return AttachDecision::NoAction; 7094 } 7095 7096 // Initialize the input operand. 7097 Int32OperandId argcId = initializeInputOperand(); 7098 7099 // Guard callee is the 'byteLength' native function. 7100 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 7101 7102 // Guard |this| is a DataViewObject. 7103 ValOperandId thisValId = loadThis(calleeId); 7104 ObjOperandId objId = writer.guardToObject(thisValId); 7105 7106 if (dv->is<FixedLengthDataViewObject>()) { 7107 emitOptimisticClassGuard(objId, dv, GuardClassKind::FixedLengthDataView); 7108 } else if (dv->is<ImmutableDataViewObject>()) { 7109 emitOptimisticClassGuard(objId, dv, GuardClassKind::ImmutableDataView); 7110 } else { 7111 emitOptimisticClassGuard(objId, dv, GuardClassKind::ResizableDataView); 7112 } 7113 7114 // Immutable array buffers can never get detached. 7115 if (!dv->is<ImmutableDataViewObject>()) { 7116 writer.guardHasAttachedArrayBuffer(objId); 7117 } else { 7118 #ifdef DEBUG 7119 // Add a guard in debug-mode, so if the buffer unexpectedly got detached, 7120 // we bail out and rely on the above assertion to fire. 7121 writer.guardHasAttachedArrayBuffer(objId); 7122 #endif 7123 } 7124 7125 // Resizable array buffers can get out-of-bounds when shrunk. 7126 if (dv->is<ResizableDataViewObject>()) { 7127 writer.guardResizableArrayBufferViewInBounds(objId); 7128 } 7129 7130 size_t byteLength = dv->byteLength().valueOr(0); 7131 if (!dv->is<ResizableDataViewObject>()) { 7132 if (byteLength <= INT32_MAX) { 7133 writer.loadArrayBufferViewLengthInt32Result(objId); 7134 } else { 7135 writer.loadArrayBufferViewLengthDoubleResult(objId); 7136 } 7137 } else { 7138 if (byteLength <= INT32_MAX) { 7139 writer.resizableDataViewByteLengthInt32Result(objId); 7140 } else { 7141 writer.resizableDataViewByteLengthDoubleResult(objId); 7142 } 7143 } 7144 7145 writer.returnFromIC(); 7146 7147 trackAttached("DataViewByteLength"); 7148 return AttachDecision::Attach; 7149 } 7150 7151 AttachDecision InlinableNativeIRGenerator::tryAttachDataViewByteOffset() { 7152 // Expecting no arguments. 7153 if (args_.length() != 0) { 7154 return AttachDecision::NoAction; 7155 } 7156 7157 // Ensure |this| is a DataViewObject. 7158 if (!thisval_.isObject() || !thisval_.toObject().is<DataViewObject>()) { 7159 return AttachDecision::NoAction; 7160 } 7161 7162 auto* dv = &thisval_.toObject().as<DataViewObject>(); 7163 7164 // byteOffset throws when the ArrayBuffer is detached. 7165 if (dv->hasDetachedBuffer()) { 7166 // The has-attached-arraybuffer guard is elided for immutable views. Assert 7167 // we never see an immutable view with a detached buffer. 7168 MOZ_ASSERT(!dv->is<ImmutableDataViewObject>(), 7169 "immutable data views can't have their buffer detached"); 7170 return AttachDecision::NoAction; 7171 } 7172 7173 // byteOffset throws when the ArrayBuffer is out-of-bounds. 7174 if (dv->is<ResizableDataViewObject>() && 7175 dv->as<ResizableDataViewObject>().isOutOfBounds()) { 7176 return AttachDecision::NoAction; 7177 } 7178 7179 // Initialize the input operand. 7180 Int32OperandId argcId = initializeInputOperand(); 7181 7182 // Guard callee is the 'byteLength' native function. 7183 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 7184 7185 // Guard |this| is a DataViewObject. 7186 ValOperandId thisValId = loadThis(calleeId); 7187 ObjOperandId objId = writer.guardToObject(thisValId); 7188 7189 if (dv->is<FixedLengthDataViewObject>()) { 7190 emitOptimisticClassGuard(objId, dv, GuardClassKind::FixedLengthDataView); 7191 } else if (dv->is<ImmutableDataViewObject>()) { 7192 emitOptimisticClassGuard(objId, dv, GuardClassKind::ImmutableDataView); 7193 } else { 7194 emitOptimisticClassGuard(objId, dv, GuardClassKind::ResizableDataView); 7195 } 7196 7197 // Immutable array buffers can never get detached. 7198 if (!dv->is<ImmutableDataViewObject>()) { 7199 writer.guardHasAttachedArrayBuffer(objId); 7200 } else { 7201 #ifdef DEBUG 7202 // Add a guard in debug-mode, so if the buffer unexpectedly got detached, 7203 // we bail out and rely on the above assertion to fire. 7204 writer.guardHasAttachedArrayBuffer(objId); 7205 #endif 7206 } 7207 7208 // Resizable array buffers can get out-of-bounds when shrunk. 7209 if (dv->is<ResizableDataViewObject>()) { 7210 writer.guardResizableArrayBufferViewInBounds(objId); 7211 } 7212 7213 // byteOffset doesn't need to use different code paths for fixed-length, 7214 // resizable, or immutable DataViews. 7215 size_t byteOffset = dv->byteOffset().valueOr(0); 7216 if (byteOffset <= INT32_MAX) { 7217 writer.arrayBufferViewByteOffsetInt32Result(objId); 7218 } else { 7219 writer.arrayBufferViewByteOffsetDoubleResult(objId); 7220 } 7221 7222 writer.returnFromIC(); 7223 7224 trackAttached("DataViewByteOffset"); 7225 return AttachDecision::Attach; 7226 } 7227 7228 AttachDecision InlinableNativeIRGenerator::tryAttachUnsafeGetReservedSlot( 7229 InlinableNative native) { 7230 // Self-hosted code calls this with (object, int32) arguments. 7231 MOZ_ASSERT(args_.length() == 2); 7232 MOZ_ASSERT(args_[0].isObject()); 7233 MOZ_ASSERT(args_[1].isInt32()); 7234 MOZ_ASSERT(args_[1].toInt32() >= 0); 7235 7236 uint32_t slot = uint32_t(args_[1].toInt32()); 7237 if (slot >= NativeObject::MAX_FIXED_SLOTS) { 7238 return AttachDecision::NoAction; 7239 } 7240 size_t offset = NativeObject::getFixedSlotOffset(slot); 7241 7242 // Initialize the input operand. 7243 initializeInputOperand(); 7244 7245 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7246 7247 // Guard that the first argument is an object. 7248 ValOperandId arg0Id = loadArgumentIntrinsic(ArgumentKind::Arg0); 7249 ObjOperandId objId = writer.guardToObject(arg0Id); 7250 7251 // BytecodeEmitter::assertSelfHostedUnsafeGetReservedSlot ensures that the 7252 // slot argument is constant. (At least for direct calls) 7253 7254 switch (native) { 7255 case InlinableNative::IntrinsicUnsafeGetReservedSlot: 7256 writer.loadFixedSlotResult(objId, offset); 7257 break; 7258 case InlinableNative::IntrinsicUnsafeGetObjectFromReservedSlot: 7259 writer.loadFixedSlotTypedResult(objId, offset, ValueType::Object); 7260 break; 7261 case InlinableNative::IntrinsicUnsafeGetInt32FromReservedSlot: 7262 writer.loadFixedSlotTypedResult(objId, offset, ValueType::Int32); 7263 break; 7264 case InlinableNative::IntrinsicUnsafeGetStringFromReservedSlot: 7265 writer.loadFixedSlotTypedResult(objId, offset, ValueType::String); 7266 break; 7267 default: 7268 MOZ_CRASH("unexpected native"); 7269 } 7270 7271 writer.returnFromIC(); 7272 7273 trackAttached("UnsafeGetReservedSlot"); 7274 return AttachDecision::Attach; 7275 } 7276 7277 AttachDecision InlinableNativeIRGenerator::tryAttachUnsafeSetReservedSlot() { 7278 // Self-hosted code calls this with (object, int32, value) arguments. 7279 MOZ_ASSERT(args_.length() == 3); 7280 MOZ_ASSERT(args_[0].isObject()); 7281 MOZ_ASSERT(args_[1].isInt32()); 7282 MOZ_ASSERT(args_[1].toInt32() >= 0); 7283 7284 uint32_t slot = uint32_t(args_[1].toInt32()); 7285 if (slot >= NativeObject::MAX_FIXED_SLOTS) { 7286 return AttachDecision::NoAction; 7287 } 7288 size_t offset = NativeObject::getFixedSlotOffset(slot); 7289 7290 // Initialize the input operand. 7291 initializeInputOperand(); 7292 7293 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7294 7295 // Guard that the first argument is an object. 7296 ValOperandId arg0Id = loadArgumentIntrinsic(ArgumentKind::Arg0); 7297 ObjOperandId objId = writer.guardToObject(arg0Id); 7298 7299 // BytecodeEmitter::assertSelfHostedUnsafeSetReservedSlot ensures that the 7300 // slot argument is constant. (At least for direct calls) 7301 7302 // Get the value to set. 7303 ValOperandId valId = loadArgumentIntrinsic(ArgumentKind::Arg2); 7304 7305 // Set the fixed slot and return undefined. 7306 writer.storeFixedSlotUndefinedResult(objId, offset, valId); 7307 7308 // This stub always returns undefined. 7309 writer.returnFromIC(); 7310 7311 trackAttached("UnsafeSetReservedSlot"); 7312 return AttachDecision::Attach; 7313 } 7314 7315 AttachDecision InlinableNativeIRGenerator::tryAttachIsSuspendedGenerator() { 7316 // The IsSuspendedGenerator intrinsic is only called in 7317 // self-hosted code, so it's safe to assume we have a single 7318 // argument and the callee is our intrinsic. 7319 7320 MOZ_ASSERT(args_.length() == 1); 7321 7322 initializeInputOperand(); 7323 7324 // Stack layout here is (bottom to top): 7325 // 2: Callee 7326 // 1: ThisValue 7327 // 0: Arg <-- Top of stack. 7328 // We only care about the argument. 7329 ValOperandId valId = loadArgumentIntrinsic(ArgumentKind::Arg0); 7330 7331 // Check whether the argument is a suspended generator. 7332 // We don't need guards, because IsSuspendedGenerator returns 7333 // false for values that are not generator objects. 7334 writer.callIsSuspendedGeneratorResult(valId); 7335 writer.returnFromIC(); 7336 7337 trackAttached("IsSuspendedGenerator"); 7338 return AttachDecision::Attach; 7339 } 7340 7341 AttachDecision InlinableNativeIRGenerator::tryAttachToObject() { 7342 // Self-hosted code calls this with a single argument. 7343 MOZ_ASSERT(args_.length() == 1); 7344 7345 // Need a single object argument. 7346 // TODO(Warp): Support all or more conversions to object. 7347 if (!args_[0].isObject()) { 7348 return AttachDecision::NoAction; 7349 } 7350 7351 // Initialize the input operand. 7352 initializeInputOperand(); 7353 7354 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7355 7356 // Guard that the argument is an object. 7357 ValOperandId argId = loadArgumentIntrinsic(ArgumentKind::Arg0); 7358 ObjOperandId objId = writer.guardToObject(argId); 7359 7360 // Return the object. 7361 writer.loadObjectResult(objId); 7362 writer.returnFromIC(); 7363 7364 trackAttached("ToObject"); 7365 return AttachDecision::Attach; 7366 } 7367 7368 AttachDecision InlinableNativeIRGenerator::tryAttachToInteger() { 7369 // Self-hosted code calls this with a single argument. 7370 MOZ_ASSERT(args_.length() == 1); 7371 7372 // Need a single int32 argument. 7373 // TODO(Warp): Support all or more conversions to integer. 7374 // Make sure to update this code correctly if we ever start 7375 // returning non-int32 integers. 7376 if (!args_[0].isInt32()) { 7377 return AttachDecision::NoAction; 7378 } 7379 7380 // Initialize the input operand. 7381 initializeInputOperand(); 7382 7383 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7384 7385 // Guard that the argument is an int32. 7386 ValOperandId argId = loadArgumentIntrinsic(ArgumentKind::Arg0); 7387 Int32OperandId int32Id = writer.guardToInt32(argId); 7388 7389 // Return the int32. 7390 writer.loadInt32Result(int32Id); 7391 writer.returnFromIC(); 7392 7393 trackAttached("ToInteger"); 7394 return AttachDecision::Attach; 7395 } 7396 7397 AttachDecision InlinableNativeIRGenerator::tryAttachToLength() { 7398 // Self-hosted code calls this with a single argument. 7399 MOZ_ASSERT(args_.length() == 1); 7400 7401 // Need a single int32 argument. 7402 if (!args_[0].isInt32()) { 7403 return AttachDecision::NoAction; 7404 } 7405 7406 // Initialize the input operand. 7407 initializeInputOperand(); 7408 7409 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7410 7411 // ToLength(int32) is equivalent to max(int32, 0). 7412 ValOperandId argId = loadArgumentIntrinsic(ArgumentKind::Arg0); 7413 Int32OperandId int32ArgId = writer.guardToInt32(argId); 7414 Int32OperandId zeroId = writer.loadInt32Constant(0); 7415 bool isMax = true; 7416 Int32OperandId maxId = writer.int32MinMax(isMax, int32ArgId, zeroId); 7417 writer.loadInt32Result(maxId); 7418 writer.returnFromIC(); 7419 7420 trackAttached("ToLength"); 7421 return AttachDecision::Attach; 7422 } 7423 7424 AttachDecision InlinableNativeIRGenerator::tryAttachIsObject() { 7425 // Self-hosted code calls this with a single argument. 7426 MOZ_ASSERT(args_.length() == 1); 7427 7428 // Initialize the input operand. 7429 initializeInputOperand(); 7430 7431 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7432 7433 // Type check the argument and return result. 7434 ValOperandId argId = loadArgumentIntrinsic(ArgumentKind::Arg0); 7435 writer.isObjectResult(argId); 7436 writer.returnFromIC(); 7437 7438 trackAttached("IsObject"); 7439 return AttachDecision::Attach; 7440 } 7441 7442 AttachDecision InlinableNativeIRGenerator::tryAttachIsPackedArray() { 7443 // Self-hosted code calls this with a single object argument. 7444 MOZ_ASSERT(args_.length() == 1); 7445 MOZ_ASSERT(args_[0].isObject()); 7446 7447 // Initialize the input operand. 7448 initializeInputOperand(); 7449 7450 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7451 7452 // Check if the argument is packed and return result. 7453 ValOperandId argId = loadArgumentIntrinsic(ArgumentKind::Arg0); 7454 ObjOperandId objArgId = writer.guardToObject(argId); 7455 writer.isPackedArrayResult(objArgId); 7456 writer.returnFromIC(); 7457 7458 trackAttached("IsPackedArray"); 7459 return AttachDecision::Attach; 7460 } 7461 7462 AttachDecision InlinableNativeIRGenerator::tryAttachIsCallable() { 7463 // Self-hosted code calls this with a single argument. 7464 MOZ_ASSERT(args_.length() == 1); 7465 7466 // Initialize the input operand. 7467 initializeInputOperand(); 7468 7469 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7470 7471 // Check if the argument is callable and return result. 7472 ValOperandId argId = loadArgumentIntrinsic(ArgumentKind::Arg0); 7473 writer.isCallableResult(argId); 7474 writer.returnFromIC(); 7475 7476 trackAttached("IsCallable"); 7477 return AttachDecision::Attach; 7478 } 7479 7480 AttachDecision InlinableNativeIRGenerator::tryAttachIsConstructor() { 7481 // Self-hosted code calls this with a single argument. 7482 MOZ_ASSERT(args_.length() == 1); 7483 7484 // Need a single object argument. 7485 if (!args_[0].isObject()) { 7486 return AttachDecision::NoAction; 7487 } 7488 7489 // Initialize the input operand. 7490 initializeInputOperand(); 7491 7492 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7493 7494 // Guard that the argument is an object. 7495 ValOperandId argId = loadArgumentIntrinsic(ArgumentKind::Arg0); 7496 ObjOperandId objId = writer.guardToObject(argId); 7497 7498 // Check if the argument is a constructor and return result. 7499 writer.isConstructorResult(objId); 7500 writer.returnFromIC(); 7501 7502 trackAttached("IsConstructor"); 7503 return AttachDecision::Attach; 7504 } 7505 7506 AttachDecision 7507 InlinableNativeIRGenerator::tryAttachIsCrossRealmArrayConstructor() { 7508 // Self-hosted code calls this with an object argument. 7509 MOZ_ASSERT(args_.length() == 1); 7510 MOZ_ASSERT(args_[0].isObject()); 7511 7512 if (args_[0].toObject().is<ProxyObject>()) { 7513 return AttachDecision::NoAction; 7514 } 7515 7516 // Initialize the input operand. 7517 initializeInputOperand(); 7518 7519 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7520 7521 ValOperandId argId = loadArgumentIntrinsic(ArgumentKind::Arg0); 7522 ObjOperandId objId = writer.guardToObject(argId); 7523 writer.guardIsNotProxy(objId); 7524 writer.isCrossRealmArrayConstructorResult(objId); 7525 writer.returnFromIC(); 7526 7527 trackAttached("IsCrossRealmArrayConstructor"); 7528 return AttachDecision::Attach; 7529 } 7530 7531 AttachDecision InlinableNativeIRGenerator::tryAttachCanOptimizeArraySpecies() { 7532 // Self-hosted code calls this with an object argument. 7533 MOZ_ASSERT(args_.length() == 1); 7534 MOZ_ASSERT(args_[0].isObject()); 7535 7536 SharedShape* shape = GlobalObject::getArrayShapeWithDefaultProto(cx_); 7537 if (!shape) { 7538 cx_->recoverFromOutOfMemory(); 7539 return AttachDecision::NoAction; 7540 } 7541 7542 // Initialize the input operand. 7543 initializeInputOperand(); 7544 7545 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7546 7547 if (cx_->realm()->realmFuses.optimizeArraySpeciesFuse.intact()) { 7548 ValOperandId argId = loadArgumentIntrinsic(ArgumentKind::Arg0); 7549 ObjOperandId objId = writer.guardToObject(argId); 7550 writer.guardFuse(RealmFuses::FuseIndex::OptimizeArraySpeciesFuse); 7551 writer.hasShapeResult(objId, shape); 7552 writer.returnFromIC(); 7553 trackAttached("CanOptimizeArraySpecies.Optimized"); 7554 } else { 7555 writer.loadBooleanResult(false); 7556 writer.returnFromIC(); 7557 trackAttached("CanOptimizeArraySpecies.Deoptimized"); 7558 } 7559 7560 return AttachDecision::Attach; 7561 } 7562 7563 AttachDecision InlinableNativeIRGenerator::tryAttachGuardToClass( 7564 InlinableNative native) { 7565 // Self-hosted code calls this with an object argument. 7566 MOZ_ASSERT(args_.length() == 1); 7567 MOZ_ASSERT(args_[0].isObject()); 7568 7569 // Class must match. 7570 const JSClass* clasp = InlinableNativeGuardToClass(native); 7571 if (args_[0].toObject().getClass() != clasp) { 7572 return AttachDecision::NoAction; 7573 } 7574 7575 // Initialize the input operand. 7576 initializeInputOperand(); 7577 7578 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7579 7580 // Guard that the argument is an object. 7581 ValOperandId argId = loadArgumentIntrinsic(ArgumentKind::Arg0); 7582 ObjOperandId objId = writer.guardToObject(argId); 7583 7584 // Guard that the object has the correct class. 7585 writer.guardAnyClass(objId, clasp); 7586 7587 // Return the object. 7588 writer.loadObjectResult(objId); 7589 writer.returnFromIC(); 7590 7591 trackAttached("GuardToClass"); 7592 return AttachDecision::Attach; 7593 } 7594 7595 AttachDecision InlinableNativeIRGenerator::tryAttachGuardToClass( 7596 GuardClassKind kind) { 7597 // Self-hosted code calls this with an object argument. 7598 MOZ_ASSERT(args_.length() == 1); 7599 MOZ_ASSERT(args_[0].isObject()); 7600 7601 // Class must match. 7602 const JSClass* clasp = ClassFor(kind); 7603 if (args_[0].toObject().getClass() != clasp) { 7604 return AttachDecision::NoAction; 7605 } 7606 7607 // Initialize the input operand. 7608 initializeInputOperand(); 7609 7610 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7611 7612 // Guard that the argument is an object. 7613 ValOperandId argId = loadArgumentIntrinsic(ArgumentKind::Arg0); 7614 ObjOperandId objId = writer.guardToObject(argId); 7615 7616 // Guard that the object has the correct class. 7617 writer.guardClass(objId, kind); 7618 7619 // Return the object. 7620 writer.loadObjectResult(objId); 7621 writer.returnFromIC(); 7622 7623 trackAttached("GuardToClass"); 7624 return AttachDecision::Attach; 7625 } 7626 7627 AttachDecision InlinableNativeIRGenerator::tryAttachGuardToArrayBuffer() { 7628 // Self-hosted code calls this with an object argument. 7629 MOZ_ASSERT(args_.length() == 1); 7630 MOZ_ASSERT(args_[0].isObject()); 7631 7632 // Class must match. 7633 if (!args_[0].toObject().is<ArrayBufferObject>()) { 7634 return AttachDecision::NoAction; 7635 } 7636 7637 // Initialize the input operand. 7638 initializeInputOperand(); 7639 7640 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7641 7642 // Guard that the argument is an object. 7643 ValOperandId argId = loadArgumentIntrinsic(ArgumentKind::Arg0); 7644 ObjOperandId objId = writer.guardToObject(argId); 7645 7646 // Guard that the object has the correct class. 7647 writer.guardToArrayBuffer(objId); 7648 7649 // Return the object. 7650 writer.loadObjectResult(objId); 7651 writer.returnFromIC(); 7652 7653 trackAttached("GuardToArrayBuffer"); 7654 return AttachDecision::Attach; 7655 } 7656 7657 AttachDecision InlinableNativeIRGenerator::tryAttachGuardToSharedArrayBuffer() { 7658 // Self-hosted code calls this with an object argument. 7659 MOZ_ASSERT(args_.length() == 1); 7660 MOZ_ASSERT(args_[0].isObject()); 7661 7662 // Class must match. 7663 if (!args_[0].toObject().is<SharedArrayBufferObject>()) { 7664 return AttachDecision::NoAction; 7665 } 7666 7667 // Initialize the input operand. 7668 initializeInputOperand(); 7669 7670 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7671 7672 // Guard that the argument is an object. 7673 ValOperandId argId = loadArgumentIntrinsic(ArgumentKind::Arg0); 7674 ObjOperandId objId = writer.guardToObject(argId); 7675 7676 // Guard that the object has the correct class. 7677 writer.guardToSharedArrayBuffer(objId); 7678 7679 // Return the object. 7680 writer.loadObjectResult(objId); 7681 writer.returnFromIC(); 7682 7683 trackAttached("GuardToSharedArrayBuffer"); 7684 return AttachDecision::Attach; 7685 } 7686 7687 AttachDecision InlinableNativeIRGenerator::tryAttachHasClass( 7688 const JSClass* clasp, bool isPossiblyWrapped) { 7689 // Self-hosted code calls this with an object argument. 7690 MOZ_ASSERT(args_.length() == 1); 7691 MOZ_ASSERT(args_[0].isObject()); 7692 7693 // Only optimize when the object isn't a proxy. 7694 if (isPossiblyWrapped && args_[0].toObject().is<ProxyObject>()) { 7695 return AttachDecision::NoAction; 7696 } 7697 7698 // Initialize the input operand. 7699 initializeInputOperand(); 7700 7701 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7702 7703 // Perform the Class check. 7704 ValOperandId argId = loadArgumentIntrinsic(ArgumentKind::Arg0); 7705 ObjOperandId objId = writer.guardToObject(argId); 7706 7707 if (isPossiblyWrapped) { 7708 writer.guardIsNotProxy(objId); 7709 } 7710 7711 writer.hasClassResult(objId, clasp); 7712 writer.returnFromIC(); 7713 7714 trackAttached("HasClass"); 7715 return AttachDecision::Attach; 7716 } 7717 7718 AttachDecision InlinableNativeIRGenerator::tryAttachRegExpFlag( 7719 JS::RegExpFlags flags) { 7720 // Expecting no arguments. 7721 if (args_.length() != 0) { 7722 return AttachDecision::NoAction; 7723 } 7724 7725 // Ensure |this| is a RegExpObject. 7726 if (!thisval_.isObject() || !thisval_.toObject().is<RegExpObject>()) { 7727 return AttachDecision::NoAction; 7728 } 7729 7730 auto* regExp = &thisval_.toObject().as<RegExpObject>(); 7731 7732 // Initialize the input operand. 7733 Int32OperandId argcId = initializeInputOperand(); 7734 7735 // Guard callee is the native RegExp getter function. 7736 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 7737 7738 // Guard |this| is a RegExpObject. 7739 ValOperandId thisValId = loadThis(calleeId); 7740 ObjOperandId objId = writer.guardToObject(thisValId); 7741 writer.guardShapeForClass(objId, regExp->shape()); 7742 7743 writer.regExpFlagResult(objId, flags.value()); 7744 writer.returnFromIC(); 7745 7746 trackAttached("RegExpFlag"); 7747 return AttachDecision::Attach; 7748 } 7749 7750 // Returns whether the .lastIndex property is a non-negative int32 value and is 7751 // still writable. 7752 static bool HasOptimizableLastIndexSlot(RegExpObject* regexp, JSContext* cx) { 7753 auto lastIndexProp = regexp->lookupPure(cx->names().lastIndex); 7754 MOZ_ASSERT(lastIndexProp->isDataProperty()); 7755 if (!lastIndexProp->writable()) { 7756 return false; 7757 } 7758 Value lastIndex = regexp->getLastIndex(); 7759 if (!lastIndex.isInt32() || lastIndex.toInt32() < 0) { 7760 return false; 7761 } 7762 return true; 7763 } 7764 7765 // Returns the RegExp stub used by the optimized code path for this intrinsic. 7766 // We store a pointer to this in the IC stub to ensure GC doesn't discard it. 7767 static JitCode* GetOrCreateRegExpStub(JSContext* cx, InlinableNative native) { 7768 #ifdef ENABLE_PORTABLE_BASELINE_INTERP 7769 return nullptr; 7770 #else 7771 // The stubs assume the global has non-null RegExpStatics and match result 7772 // shape. 7773 if (!GlobalObject::getRegExpStatics(cx, cx->global()) || 7774 !cx->global()->regExpRealm().getOrCreateMatchResultShape(cx)) { 7775 MOZ_ASSERT(cx->isThrowingOutOfMemory() || cx->isThrowingOverRecursed()); 7776 cx->clearPendingException(); 7777 return nullptr; 7778 } 7779 JitZone::StubKind kind; 7780 switch (native) { 7781 case InlinableNative::IntrinsicRegExpBuiltinExecForTest: 7782 case InlinableNative::IntrinsicRegExpExecForTest: 7783 kind = JitZone::StubKind::RegExpExecTest; 7784 break; 7785 case InlinableNative::IntrinsicRegExpBuiltinExec: 7786 case InlinableNative::IntrinsicRegExpExec: 7787 kind = JitZone::StubKind::RegExpExecMatch; 7788 break; 7789 case InlinableNative::RegExpMatcher: 7790 kind = JitZone::StubKind::RegExpMatcher; 7791 break; 7792 case InlinableNative::RegExpSearcher: 7793 kind = JitZone::StubKind::RegExpSearcher; 7794 break; 7795 default: 7796 MOZ_CRASH("Unexpected native"); 7797 } 7798 JitCode* code = cx->zone()->jitZone()->ensureStubExists(cx, kind); 7799 if (!code) { 7800 MOZ_ASSERT(cx->isThrowingOutOfMemory() || cx->isThrowingOverRecursed()); 7801 cx->clearPendingException(); 7802 return nullptr; 7803 } 7804 return code; 7805 #endif 7806 } 7807 7808 static void EmitGuardLastIndexIsNonNegativeInt32(CacheIRWriter& writer, 7809 ObjOperandId regExpId) { 7810 size_t offset = 7811 NativeObject::getFixedSlotOffset(RegExpObject::lastIndexSlot()); 7812 ValOperandId lastIndexValId = writer.loadFixedSlot(regExpId, offset); 7813 Int32OperandId lastIndexId = writer.guardToInt32(lastIndexValId); 7814 writer.guardInt32IsNonNegative(lastIndexId); 7815 } 7816 7817 AttachDecision InlinableNativeIRGenerator::tryAttachIntrinsicRegExpBuiltinExec( 7818 InlinableNative native) { 7819 // Self-hosted code calls this with (regexp, string) arguments. 7820 MOZ_ASSERT(args_.length() == 2); 7821 MOZ_ASSERT(args_[0].isObject()); 7822 MOZ_ASSERT(args_[1].isString()); 7823 7824 JitCode* stub = GetOrCreateRegExpStub(cx_, native); 7825 if (!stub) { 7826 return AttachDecision::NoAction; 7827 } 7828 7829 RegExpObject* re = &args_[0].toObject().as<RegExpObject>(); 7830 if (!HasOptimizableLastIndexSlot(re, cx_)) { 7831 return AttachDecision::NoAction; 7832 } 7833 7834 // Initialize the input operand. 7835 initializeInputOperand(); 7836 7837 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7838 7839 ValOperandId arg0Id = loadArgumentIntrinsic(ArgumentKind::Arg0); 7840 ObjOperandId regExpId = writer.guardToObject(arg0Id); 7841 writer.guardShape(regExpId, re->shape()); 7842 EmitGuardLastIndexIsNonNegativeInt32(writer, regExpId); 7843 7844 ValOperandId arg1Id = loadArgumentIntrinsic(ArgumentKind::Arg1); 7845 StringOperandId inputId = writer.guardToString(arg1Id); 7846 7847 if (native == InlinableNative::IntrinsicRegExpBuiltinExecForTest) { 7848 writer.regExpBuiltinExecTestResult(regExpId, inputId, stub); 7849 } else { 7850 writer.regExpBuiltinExecMatchResult(regExpId, inputId, stub); 7851 } 7852 writer.returnFromIC(); 7853 7854 trackAttached("IntrinsicRegExpBuiltinExec"); 7855 return AttachDecision::Attach; 7856 } 7857 7858 AttachDecision InlinableNativeIRGenerator::tryAttachIntrinsicRegExpExec( 7859 InlinableNative native) { 7860 // Self-hosted code calls this with (object, string) arguments. 7861 MOZ_ASSERT(args_.length() == 2); 7862 MOZ_ASSERT(args_[0].isObject()); 7863 MOZ_ASSERT(args_[1].isString()); 7864 7865 // Ensure the object is a RegExpObject with the builtin RegExp.prototype.exec 7866 // function. 7867 if (!IsOptimizableRegExpObject(&args_[0].toObject(), cx_)) { 7868 return AttachDecision::NoAction; 7869 } 7870 7871 JitCode* stub = GetOrCreateRegExpStub(cx_, native); 7872 if (!stub) { 7873 return AttachDecision::NoAction; 7874 } 7875 7876 RegExpObject* re = &args_[0].toObject().as<RegExpObject>(); 7877 if (!HasOptimizableLastIndexSlot(re, cx_)) { 7878 return AttachDecision::NoAction; 7879 } 7880 7881 // Initialize the input operand. 7882 initializeInputOperand(); 7883 7884 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7885 7886 ValOperandId arg0Id = loadArgumentIntrinsic(ArgumentKind::Arg0); 7887 ObjOperandId regExpId = writer.guardToObject(arg0Id); 7888 writer.guardShape(regExpId, re->shape()); 7889 writer.guardFuse(RealmFuses::FuseIndex::OptimizeRegExpPrototypeFuse); 7890 EmitGuardLastIndexIsNonNegativeInt32(writer, regExpId); 7891 7892 ValOperandId arg1Id = loadArgumentIntrinsic(ArgumentKind::Arg1); 7893 StringOperandId inputId = writer.guardToString(arg1Id); 7894 7895 if (native == InlinableNative::IntrinsicRegExpExecForTest) { 7896 writer.regExpBuiltinExecTestResult(regExpId, inputId, stub); 7897 } else { 7898 writer.regExpBuiltinExecMatchResult(regExpId, inputId, stub); 7899 } 7900 writer.returnFromIC(); 7901 7902 trackAttached("IntrinsicRegExpExec"); 7903 return AttachDecision::Attach; 7904 } 7905 7906 AttachDecision InlinableNativeIRGenerator::tryAttachRegExpMatcherSearcher( 7907 InlinableNative native) { 7908 // Self-hosted code calls this with (object, string, number) arguments. 7909 MOZ_ASSERT(args_.length() == 3); 7910 MOZ_ASSERT(args_[0].isObject()); 7911 MOZ_ASSERT(args_[1].isString()); 7912 MOZ_ASSERT(args_[2].isNumber()); 7913 7914 // It's not guaranteed that the JITs have typed |lastIndex| as an Int32. 7915 if (!args_[2].isInt32()) { 7916 return AttachDecision::NoAction; 7917 } 7918 7919 JitCode* stub = GetOrCreateRegExpStub(cx_, native); 7920 if (!stub) { 7921 return AttachDecision::NoAction; 7922 } 7923 7924 // Initialize the input operand. 7925 initializeInputOperand(); 7926 7927 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7928 7929 // Guard argument types. 7930 ValOperandId arg0Id = loadArgumentIntrinsic(ArgumentKind::Arg0); 7931 ObjOperandId reId = writer.guardToObject(arg0Id); 7932 7933 ValOperandId arg1Id = loadArgumentIntrinsic(ArgumentKind::Arg1); 7934 StringOperandId inputId = writer.guardToString(arg1Id); 7935 7936 ValOperandId arg2Id = loadArgumentIntrinsic(ArgumentKind::Arg2); 7937 Int32OperandId lastIndexId = writer.guardToInt32(arg2Id); 7938 7939 switch (native) { 7940 case InlinableNative::RegExpMatcher: 7941 writer.callRegExpMatcherResult(reId, inputId, lastIndexId, stub); 7942 writer.returnFromIC(); 7943 trackAttached("RegExpMatcher"); 7944 break; 7945 7946 case InlinableNative::RegExpSearcher: 7947 writer.callRegExpSearcherResult(reId, inputId, lastIndexId, stub); 7948 writer.returnFromIC(); 7949 trackAttached("RegExpSearcher"); 7950 break; 7951 7952 default: 7953 MOZ_CRASH("Unexpected native"); 7954 } 7955 7956 return AttachDecision::Attach; 7957 } 7958 7959 AttachDecision InlinableNativeIRGenerator::tryAttachRegExpSearcherLastLimit() { 7960 // Self-hosted code calls this with a string argument that's only used for an 7961 // assertion. 7962 MOZ_ASSERT(args_.length() == 1); 7963 MOZ_ASSERT(args_[0].isString()); 7964 7965 // Initialize the input operand. 7966 initializeInputOperand(); 7967 7968 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7969 7970 writer.regExpSearcherLastLimitResult(); 7971 writer.returnFromIC(); 7972 7973 trackAttached("RegExpSearcherLastLimit"); 7974 return AttachDecision::Attach; 7975 } 7976 7977 AttachDecision InlinableNativeIRGenerator::tryAttachRegExpHasCaptureGroups() { 7978 // Self-hosted code calls this with object and string arguments. 7979 MOZ_ASSERT(args_.length() == 2); 7980 MOZ_ASSERT(args_[0].toObject().is<RegExpObject>()); 7981 MOZ_ASSERT(args_[1].isString()); 7982 7983 // Initialize the input operand. 7984 initializeInputOperand(); 7985 7986 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 7987 7988 ValOperandId arg0Id = loadArgumentIntrinsic(ArgumentKind::Arg0); 7989 ObjOperandId objId = writer.guardToObject(arg0Id); 7990 7991 ValOperandId arg1Id = loadArgumentIntrinsic(ArgumentKind::Arg1); 7992 StringOperandId inputId = writer.guardToString(arg1Id); 7993 7994 writer.regExpHasCaptureGroupsResult(objId, inputId); 7995 writer.returnFromIC(); 7996 7997 trackAttached("RegExpHasCaptureGroups"); 7998 return AttachDecision::Attach; 7999 } 8000 8001 AttachDecision 8002 InlinableNativeIRGenerator::tryAttachIsRegExpPrototypeOptimizable() { 8003 // Self-hosted code calls this with no arguments. 8004 MOZ_ASSERT(args_.length() == 0); 8005 8006 // Initialize the input operand. 8007 initializeInputOperand(); 8008 8009 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 8010 8011 if (cx_->realm()->realmFuses.optimizeRegExpPrototypeFuse.intact()) { 8012 writer.guardFuse(RealmFuses::FuseIndex::OptimizeRegExpPrototypeFuse); 8013 writer.loadBooleanResult(true); 8014 writer.returnFromIC(); 8015 trackAttached("IsRegExpPrototypeOptimizable.Optimized"); 8016 } else { 8017 writer.loadBooleanResult(false); 8018 writer.returnFromIC(); 8019 trackAttached("IsRegExpPrototypeOptimizable.Deoptimized"); 8020 } 8021 8022 return AttachDecision::Attach; 8023 } 8024 8025 AttachDecision 8026 InlinableNativeIRGenerator::tryAttachIsOptimizableRegExpObject() { 8027 // Self-hosted code calls this with a single object argument. 8028 MOZ_ASSERT(args_.length() == 1); 8029 MOZ_ASSERT(args_[0].isObject()); 8030 8031 Shape* optimizableShape = cx_->global()->maybeRegExpShapeWithDefaultProto(); 8032 if (!optimizableShape) { 8033 return AttachDecision::NoAction; 8034 } 8035 8036 // Initialize the input operand. 8037 initializeInputOperand(); 8038 8039 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 8040 8041 if (cx_->realm()->realmFuses.optimizeRegExpPrototypeFuse.intact()) { 8042 ValOperandId argId = loadArgumentIntrinsic(ArgumentKind::Arg0); 8043 ObjOperandId objId = writer.guardToObject(argId); 8044 writer.guardFuse(RealmFuses::FuseIndex::OptimizeRegExpPrototypeFuse); 8045 writer.hasShapeResult(objId, optimizableShape); 8046 writer.returnFromIC(); 8047 trackAttached("IsOptimizableRegExpObject.Optimized"); 8048 } else { 8049 writer.loadBooleanResult(false); 8050 writer.returnFromIC(); 8051 trackAttached("IsOptimizableRegExpObject.Deoptimized"); 8052 } 8053 8054 return AttachDecision::Attach; 8055 } 8056 8057 AttachDecision InlinableNativeIRGenerator::tryAttachGetFirstDollarIndex() { 8058 // Self-hosted code calls this with a single string argument. 8059 MOZ_ASSERT(args_.length() == 1); 8060 MOZ_ASSERT(args_[0].isString()); 8061 8062 // Initialize the input operand. 8063 initializeInputOperand(); 8064 8065 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 8066 8067 ValOperandId arg0Id = loadArgumentIntrinsic(ArgumentKind::Arg0); 8068 StringOperandId strId = writer.guardToString(arg0Id); 8069 8070 writer.getFirstDollarIndexResult(strId); 8071 writer.returnFromIC(); 8072 8073 trackAttached("GetFirstDollarIndex"); 8074 return AttachDecision::Attach; 8075 } 8076 8077 AttachDecision InlinableNativeIRGenerator::tryAttachSubstringKernel() { 8078 // Self-hosted code calls this with (string, int32, int32) arguments. 8079 MOZ_ASSERT(args_.length() == 3); 8080 MOZ_ASSERT(args_[0].isString()); 8081 MOZ_ASSERT(args_[1].isInt32()); 8082 MOZ_ASSERT(args_[2].isInt32()); 8083 8084 // Initialize the input operand. 8085 initializeInputOperand(); 8086 8087 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 8088 8089 ValOperandId arg0Id = loadArgumentIntrinsic(ArgumentKind::Arg0); 8090 StringOperandId strId = writer.guardToString(arg0Id); 8091 8092 ValOperandId arg1Id = loadArgumentIntrinsic(ArgumentKind::Arg1); 8093 Int32OperandId beginId = writer.guardToInt32(arg1Id); 8094 8095 ValOperandId arg2Id = loadArgumentIntrinsic(ArgumentKind::Arg2); 8096 Int32OperandId lengthId = writer.guardToInt32(arg2Id); 8097 8098 writer.callSubstringKernelResult(strId, beginId, lengthId); 8099 writer.returnFromIC(); 8100 8101 trackAttached("SubstringKernel"); 8102 return AttachDecision::Attach; 8103 } 8104 8105 static bool CanConvertToString(const Value& v) { 8106 return v.isString() || v.isNumber() || v.isBoolean() || v.isNullOrUndefined(); 8107 } 8108 8109 AttachDecision InlinableNativeIRGenerator::tryAttachString() { 8110 // Need a single argument that is or can be converted to a string. 8111 if (args_.length() != 1 || !CanConvertToString(args_[0])) { 8112 return AttachDecision::NoAction; 8113 } 8114 8115 // Initialize the input operand. 8116 Int32OperandId argcId = initializeInputOperand(); 8117 8118 // Guard callee is the 'String' function. 8119 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8120 8121 // Guard that the argument is a string or can be converted to one. 8122 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 8123 StringOperandId strId = emitToStringGuard(argId, args_[0]); 8124 8125 // Return the string. 8126 writer.loadStringResult(strId); 8127 writer.returnFromIC(); 8128 8129 trackAttached("String"); 8130 return AttachDecision::Attach; 8131 } 8132 8133 AttachDecision InlinableNativeIRGenerator::tryAttachStringConstructor() { 8134 // Need a single argument that is or can be converted to a string. 8135 if (args_.length() != 1 || !CanConvertToString(args_[0])) { 8136 return AttachDecision::NoAction; 8137 } 8138 8139 RootedString emptyString(cx_, cx_->runtime()->emptyString); 8140 JSObject* templateObj = StringObject::create( 8141 cx_, emptyString, /* proto = */ nullptr, TenuredObject); 8142 if (!templateObj) { 8143 cx_->recoverFromOutOfMemory(); 8144 return AttachDecision::NoAction; 8145 } 8146 8147 // Initialize the input operand. 8148 Int32OperandId argcId = initializeInputOperand(); 8149 8150 // Guard callee is the 'String' function. 8151 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8152 8153 // Guard on number and convert to string. 8154 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 8155 StringOperandId strId = emitToStringGuard(argId, args_[0]); 8156 8157 writer.newStringObjectResult(templateObj, strId); 8158 writer.returnFromIC(); 8159 8160 trackAttached("StringConstructor"); 8161 return AttachDecision::Attach; 8162 } 8163 8164 AttachDecision InlinableNativeIRGenerator::tryAttachStringToStringValueOf() { 8165 // Expecting no arguments. 8166 if (args_.length() != 0) { 8167 return AttachDecision::NoAction; 8168 } 8169 8170 // Ensure |this| is a primitive string value. 8171 if (!thisval_.isString()) { 8172 return AttachDecision::NoAction; 8173 } 8174 8175 // Initialize the input operand. 8176 Int32OperandId argcId = initializeInputOperand(); 8177 8178 // Guard callee is the 'toString' OR 'valueOf' native function. 8179 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8180 8181 // Guard |this| is a string. 8182 ValOperandId thisValId = loadThis(calleeId); 8183 StringOperandId strId = writer.guardToString(thisValId); 8184 8185 // Return the string 8186 writer.loadStringResult(strId); 8187 writer.returnFromIC(); 8188 8189 trackAttached("StringToStringValueOf"); 8190 return AttachDecision::Attach; 8191 } 8192 8193 AttachDecision InlinableNativeIRGenerator::tryAttachStringReplaceString() { 8194 // Self-hosted code calls this with (string, string, string) arguments. 8195 MOZ_ASSERT(args_.length() == 3); 8196 MOZ_ASSERT(args_[0].isString()); 8197 MOZ_ASSERT(args_[1].isString()); 8198 MOZ_ASSERT(args_[2].isString()); 8199 8200 // Initialize the input operand. 8201 initializeInputOperand(); 8202 8203 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 8204 8205 ValOperandId arg0Id = loadArgumentIntrinsic(ArgumentKind::Arg0); 8206 StringOperandId strId = writer.guardToString(arg0Id); 8207 8208 ValOperandId arg1Id = loadArgumentIntrinsic(ArgumentKind::Arg1); 8209 StringOperandId patternId = writer.guardToString(arg1Id); 8210 8211 ValOperandId arg2Id = loadArgumentIntrinsic(ArgumentKind::Arg2); 8212 StringOperandId replacementId = writer.guardToString(arg2Id); 8213 8214 writer.stringReplaceStringResult(strId, patternId, replacementId); 8215 writer.returnFromIC(); 8216 8217 trackAttached("StringReplaceString"); 8218 return AttachDecision::Attach; 8219 } 8220 8221 AttachDecision InlinableNativeIRGenerator::tryAttachStringSplitString() { 8222 // Self-hosted code calls this with (string, string) arguments. 8223 MOZ_ASSERT(args_.length() == 2); 8224 MOZ_ASSERT(args_[0].isString()); 8225 MOZ_ASSERT(args_[1].isString()); 8226 8227 // Initialize the input operand. 8228 initializeInputOperand(); 8229 8230 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 8231 8232 ValOperandId arg0Id = loadArgumentIntrinsic(ArgumentKind::Arg0); 8233 StringOperandId strId = writer.guardToString(arg0Id); 8234 8235 ValOperandId arg1Id = loadArgumentIntrinsic(ArgumentKind::Arg1); 8236 StringOperandId separatorId = writer.guardToString(arg1Id); 8237 8238 writer.stringSplitStringResult(strId, separatorId); 8239 writer.returnFromIC(); 8240 8241 trackAttached("StringSplitString"); 8242 return AttachDecision::Attach; 8243 } 8244 8245 AttachDecision InlinableNativeIRGenerator::tryAttachStringChar( 8246 StringChar kind) { 8247 // Need zero or one argument. 8248 if (args_.length() > 1) { 8249 return AttachDecision::NoAction; 8250 } 8251 8252 // Absent index argument defaults to zero: 8253 // ToInteger(ToNumber(undefined)) = ToInteger(NaN) = 0. 8254 auto indexArg = args_.length() > 0 ? args_[0] : Int32Value(0); 8255 8256 auto attach = CanAttachStringChar(thisval_, indexArg, kind); 8257 if (attach == AttachStringChar::No) { 8258 return AttachDecision::NoAction; 8259 } 8260 8261 bool handleOOB = attach == AttachStringChar::OutOfBounds; 8262 8263 // Initialize the input operand. 8264 Int32OperandId argcId = initializeInputOperand(); 8265 8266 // Guard callee is the 'charCodeAt', 'codePointAt', 'charAt', or 'at' native 8267 // function. 8268 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8269 8270 // Guard this is a string. 8271 ValOperandId thisValId = loadThis(calleeId); 8272 StringOperandId strId = writer.guardToString(thisValId); 8273 8274 // Guard int32 index. 8275 Int32OperandId int32IndexId; 8276 if (args_.length() > 0) { 8277 ValOperandId indexId = loadArgument(calleeId, ArgumentKind::Arg0); 8278 int32IndexId = EmitGuardToInt32Index(writer, args_[0], indexId); 8279 } else { 8280 int32IndexId = writer.loadInt32Constant(0); 8281 } 8282 8283 // Handle relative string indices, if necessary. 8284 if (kind == StringChar::At) { 8285 int32IndexId = writer.toRelativeStringIndex(int32IndexId, strId); 8286 } 8287 8288 // Linearize the string. 8289 // 8290 // AttachStringChar doesn't have a separate state when OOB access happens on 8291 // a string which needs to be linearized, so just linearize unconditionally 8292 // for out-of-bounds accesses. 8293 if (attach == AttachStringChar::Linearize || 8294 attach == AttachStringChar::OutOfBounds) { 8295 switch (kind) { 8296 case StringChar::CharCodeAt: 8297 case StringChar::CharAt: 8298 case StringChar::At: 8299 strId = writer.linearizeForCharAccess(strId, int32IndexId); 8300 break; 8301 case StringChar::CodePointAt: 8302 strId = writer.linearizeForCodePointAccess(strId, int32IndexId); 8303 break; 8304 } 8305 } 8306 8307 // Load string char or code. 8308 switch (kind) { 8309 case StringChar::CharCodeAt: 8310 writer.loadStringCharCodeResult(strId, int32IndexId, handleOOB); 8311 break; 8312 case StringChar::CodePointAt: 8313 writer.loadStringCodePointResult(strId, int32IndexId, handleOOB); 8314 break; 8315 case StringChar::CharAt: 8316 writer.loadStringCharResult(strId, int32IndexId, handleOOB); 8317 break; 8318 case StringChar::At: 8319 writer.loadStringAtResult(strId, int32IndexId, handleOOB); 8320 break; 8321 } 8322 8323 writer.returnFromIC(); 8324 8325 switch (kind) { 8326 case StringChar::CharCodeAt: 8327 trackAttached("StringCharCodeAt"); 8328 break; 8329 case StringChar::CodePointAt: 8330 trackAttached("StringCodePointAt"); 8331 break; 8332 case StringChar::CharAt: 8333 trackAttached("StringCharAt"); 8334 break; 8335 case StringChar::At: 8336 trackAttached("StringAt"); 8337 break; 8338 } 8339 8340 return AttachDecision::Attach; 8341 } 8342 8343 AttachDecision InlinableNativeIRGenerator::tryAttachStringCharCodeAt() { 8344 return tryAttachStringChar(StringChar::CharCodeAt); 8345 } 8346 8347 AttachDecision InlinableNativeIRGenerator::tryAttachStringCodePointAt() { 8348 return tryAttachStringChar(StringChar::CodePointAt); 8349 } 8350 8351 AttachDecision InlinableNativeIRGenerator::tryAttachStringCharAt() { 8352 return tryAttachStringChar(StringChar::CharAt); 8353 } 8354 8355 AttachDecision InlinableNativeIRGenerator::tryAttachStringAt() { 8356 return tryAttachStringChar(StringChar::At); 8357 } 8358 8359 AttachDecision InlinableNativeIRGenerator::tryAttachStringFromCharCode() { 8360 // Need one number argument. 8361 if (args_.length() != 1 || !args_[0].isNumber()) { 8362 return AttachDecision::NoAction; 8363 } 8364 8365 // Initialize the input operand. 8366 Int32OperandId argcId = initializeInputOperand(); 8367 8368 // Guard callee is the 'fromCharCode' native function. 8369 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8370 8371 // Guard int32 argument. 8372 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 8373 Int32OperandId codeId; 8374 if (args_[0].isInt32()) { 8375 codeId = writer.guardToInt32(argId); 8376 } else { 8377 // 'fromCharCode' performs ToUint16 on its input. We can use Uint32 8378 // semantics, because ToUint16(ToUint32(v)) == ToUint16(v). 8379 codeId = writer.guardToInt32ModUint32(argId); 8380 } 8381 8382 // Return string created from code. 8383 writer.stringFromCharCodeResult(codeId); 8384 writer.returnFromIC(); 8385 8386 trackAttached("StringFromCharCode"); 8387 return AttachDecision::Attach; 8388 } 8389 8390 AttachDecision InlinableNativeIRGenerator::tryAttachStringFromCodePoint() { 8391 // Need one int32 argument. 8392 if (args_.length() != 1 || !args_[0].isInt32()) { 8393 return AttachDecision::NoAction; 8394 } 8395 8396 // String.fromCodePoint throws for invalid code points. 8397 int32_t codePoint = args_[0].toInt32(); 8398 if (codePoint < 0 || codePoint > int32_t(unicode::NonBMPMax)) { 8399 return AttachDecision::NoAction; 8400 } 8401 8402 // Initialize the input operand. 8403 Int32OperandId argcId = initializeInputOperand(); 8404 8405 // Guard callee is the 'fromCodePoint' native function. 8406 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8407 8408 // Guard int32 argument. 8409 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 8410 Int32OperandId codeId = writer.guardToInt32(argId); 8411 8412 // Return string created from code point. 8413 writer.stringFromCodePointResult(codeId); 8414 writer.returnFromIC(); 8415 8416 trackAttached("StringFromCodePoint"); 8417 return AttachDecision::Attach; 8418 } 8419 8420 AttachDecision InlinableNativeIRGenerator::tryAttachStringIncludes() { 8421 // Need one string argument. 8422 if (args_.length() != 1 || !args_[0].isString()) { 8423 return AttachDecision::NoAction; 8424 } 8425 8426 // Ensure |this| is a primitive string value. 8427 if (!thisval_.isString()) { 8428 return AttachDecision::NoAction; 8429 } 8430 8431 // Initialize the input operand. 8432 Int32OperandId argcId = initializeInputOperand(); 8433 8434 // Guard callee is the 'includes' native function. 8435 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8436 8437 // Guard this is a string. 8438 ValOperandId thisValId = loadThis(calleeId); 8439 StringOperandId strId = writer.guardToString(thisValId); 8440 8441 // Guard string argument. 8442 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 8443 StringOperandId searchStrId = writer.guardToString(argId); 8444 8445 writer.stringIncludesResult(strId, searchStrId); 8446 writer.returnFromIC(); 8447 8448 trackAttached("StringIncludes"); 8449 return AttachDecision::Attach; 8450 } 8451 8452 AttachDecision InlinableNativeIRGenerator::tryAttachStringIndexOf() { 8453 // Need one string argument. 8454 if (args_.length() != 1 || !args_[0].isString()) { 8455 return AttachDecision::NoAction; 8456 } 8457 8458 // Ensure |this| is a primitive string value. 8459 if (!thisval_.isString()) { 8460 return AttachDecision::NoAction; 8461 } 8462 8463 // Initialize the input operand. 8464 Int32OperandId argcId = initializeInputOperand(); 8465 8466 // Guard callee is the 'indexOf' native function. 8467 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8468 8469 // Guard this is a string. 8470 ValOperandId thisValId = loadThis(calleeId); 8471 StringOperandId strId = writer.guardToString(thisValId); 8472 8473 // Guard string argument. 8474 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 8475 StringOperandId searchStrId = writer.guardToString(argId); 8476 8477 writer.stringIndexOfResult(strId, searchStrId); 8478 writer.returnFromIC(); 8479 8480 trackAttached("StringIndexOf"); 8481 return AttachDecision::Attach; 8482 } 8483 8484 AttachDecision InlinableNativeIRGenerator::tryAttachStringLastIndexOf() { 8485 // Need one string argument. 8486 if (args_.length() != 1 || !args_[0].isString()) { 8487 return AttachDecision::NoAction; 8488 } 8489 8490 // Ensure |this| is a primitive string value. 8491 if (!thisval_.isString()) { 8492 return AttachDecision::NoAction; 8493 } 8494 8495 // Initialize the input operand. 8496 Int32OperandId argcId = initializeInputOperand(); 8497 8498 // Guard callee is the 'lastIndexOf' native function. 8499 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8500 8501 // Guard this is a string. 8502 ValOperandId thisValId = loadThis(calleeId); 8503 StringOperandId strId = writer.guardToString(thisValId); 8504 8505 // Guard string argument. 8506 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 8507 StringOperandId searchStrId = writer.guardToString(argId); 8508 8509 writer.stringLastIndexOfResult(strId, searchStrId); 8510 writer.returnFromIC(); 8511 8512 trackAttached("StringLastIndexOf"); 8513 return AttachDecision::Attach; 8514 } 8515 8516 AttachDecision InlinableNativeIRGenerator::tryAttachStringStartsWith() { 8517 // Need one string argument. 8518 if (args_.length() != 1 || !args_[0].isString()) { 8519 return AttachDecision::NoAction; 8520 } 8521 8522 // Ensure |this| is a primitive string value. 8523 if (!thisval_.isString()) { 8524 return AttachDecision::NoAction; 8525 } 8526 8527 // Initialize the input operand. 8528 Int32OperandId argcId = initializeInputOperand(); 8529 8530 // Guard callee is the 'startsWith' native function. 8531 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8532 8533 // Guard this is a string. 8534 ValOperandId thisValId = loadThis(calleeId); 8535 StringOperandId strId = writer.guardToString(thisValId); 8536 8537 // Guard string argument. 8538 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 8539 StringOperandId searchStrId = writer.guardToString(argId); 8540 8541 writer.stringStartsWithResult(strId, searchStrId); 8542 writer.returnFromIC(); 8543 8544 trackAttached("StringStartsWith"); 8545 return AttachDecision::Attach; 8546 } 8547 8548 AttachDecision InlinableNativeIRGenerator::tryAttachStringEndsWith() { 8549 // Need one string argument. 8550 if (args_.length() != 1 || !args_[0].isString()) { 8551 return AttachDecision::NoAction; 8552 } 8553 8554 // Ensure |this| is a primitive string value. 8555 if (!thisval_.isString()) { 8556 return AttachDecision::NoAction; 8557 } 8558 8559 // Initialize the input operand. 8560 Int32OperandId argcId = initializeInputOperand(); 8561 8562 // Guard callee is the 'endsWith' native function. 8563 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8564 8565 // Guard this is a string. 8566 ValOperandId thisValId = loadThis(calleeId); 8567 StringOperandId strId = writer.guardToString(thisValId); 8568 8569 // Guard string argument. 8570 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 8571 StringOperandId searchStrId = writer.guardToString(argId); 8572 8573 writer.stringEndsWithResult(strId, searchStrId); 8574 writer.returnFromIC(); 8575 8576 trackAttached("StringEndsWith"); 8577 return AttachDecision::Attach; 8578 } 8579 8580 AttachDecision InlinableNativeIRGenerator::tryAttachStringToLowerCase() { 8581 // Expecting no arguments. 8582 if (args_.length() != 0) { 8583 return AttachDecision::NoAction; 8584 } 8585 8586 // Ensure |this| is a primitive string value. 8587 if (!thisval_.isString()) { 8588 return AttachDecision::NoAction; 8589 } 8590 8591 // Initialize the input operand. 8592 Int32OperandId argcId = initializeInputOperand(); 8593 8594 // Guard callee is the 'toLowerCase' native function. 8595 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8596 8597 // Guard this is a string. 8598 ValOperandId thisValId = loadThis(calleeId); 8599 StringOperandId strId = writer.guardToString(thisValId); 8600 8601 // Return string converted to lower-case. 8602 writer.stringToLowerCaseResult(strId); 8603 writer.returnFromIC(); 8604 8605 trackAttached("StringToLowerCase"); 8606 return AttachDecision::Attach; 8607 } 8608 8609 AttachDecision InlinableNativeIRGenerator::tryAttachStringToUpperCase() { 8610 // Expecting no arguments. 8611 if (args_.length() != 0) { 8612 return AttachDecision::NoAction; 8613 } 8614 8615 // Ensure |this| is a primitive string value. 8616 if (!thisval_.isString()) { 8617 return AttachDecision::NoAction; 8618 } 8619 8620 // Initialize the input operand. 8621 Int32OperandId argcId = initializeInputOperand(); 8622 8623 // Guard callee is the 'toUpperCase' native function. 8624 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8625 8626 // Guard this is a string. 8627 ValOperandId thisValId = loadThis(calleeId); 8628 StringOperandId strId = writer.guardToString(thisValId); 8629 8630 // Return string converted to upper-case. 8631 writer.stringToUpperCaseResult(strId); 8632 writer.returnFromIC(); 8633 8634 trackAttached("StringToUpperCase"); 8635 return AttachDecision::Attach; 8636 } 8637 8638 AttachDecision InlinableNativeIRGenerator::tryAttachStringToLocaleLowerCase() { 8639 #if JS_HAS_INTL_API 8640 // Expecting no arguments. 8641 if (args_.length() != 0) { 8642 return AttachDecision::NoAction; 8643 } 8644 8645 // Ensure |this| is a primitive string value. 8646 if (!thisval_.isString()) { 8647 return AttachDecision::NoAction; 8648 } 8649 8650 // Don't inline when not using the default locale. 8651 if (cx_->realm()->behaviors().localeOverride()) { 8652 return AttachDecision::NoAction; 8653 } 8654 8655 // Default case mapping fuse must still be intact. 8656 if (!cx_->runtime() 8657 ->runtimeFuses.ref() 8658 .defaultLocaleHasDefaultCaseMappingFuse.intact()) { 8659 return AttachDecision::NoAction; 8660 } 8661 8662 // Initialize the input operand. 8663 Int32OperandId argcId = initializeInputOperand(); 8664 8665 // Guard callee is the 'toLocaleLowerCase' native function. 8666 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8667 8668 // Guard this is a string. 8669 ValOperandId thisValId = loadThis(calleeId); 8670 StringOperandId strId = writer.guardToString(thisValId); 8671 8672 // Guard runtime default locale uses default case mapping. 8673 writer.guardRuntimeFuse( 8674 RuntimeFuses::FuseIndex::DefaultLocaleHasDefaultCaseMappingFuse); 8675 8676 // Return string converted to lower-case. 8677 writer.stringToLowerCaseResult(strId); 8678 writer.returnFromIC(); 8679 8680 trackAttached("StringToLocaleLowerCase"); 8681 return AttachDecision::Attach; 8682 #else 8683 // No inlining when Intl support is disabled. 8684 return AttachDecision::Attach; 8685 #endif 8686 } 8687 8688 AttachDecision InlinableNativeIRGenerator::tryAttachStringToLocaleUpperCase() { 8689 #if JS_HAS_INTL_API 8690 // Expecting no arguments. 8691 if (args_.length() != 0) { 8692 return AttachDecision::NoAction; 8693 } 8694 8695 // Ensure |this| is a primitive string value. 8696 if (!thisval_.isString()) { 8697 return AttachDecision::NoAction; 8698 } 8699 8700 // Don't inline when not using the default locale. 8701 if (cx_->realm()->behaviors().localeOverride()) { 8702 return AttachDecision::NoAction; 8703 } 8704 8705 // Default case mapping fuse must still be intact. 8706 if (!cx_->runtime() 8707 ->runtimeFuses.ref() 8708 .defaultLocaleHasDefaultCaseMappingFuse.intact()) { 8709 return AttachDecision::NoAction; 8710 } 8711 8712 // Initialize the input operand. 8713 Int32OperandId argcId = initializeInputOperand(); 8714 8715 // Guard callee is the 'toLocaleUpperCase' native function. 8716 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8717 8718 // Guard this is a string. 8719 ValOperandId thisValId = loadThis(calleeId); 8720 StringOperandId strId = writer.guardToString(thisValId); 8721 8722 // Guard runtime default locale uses default case mapping. 8723 writer.guardRuntimeFuse( 8724 RuntimeFuses::FuseIndex::DefaultLocaleHasDefaultCaseMappingFuse); 8725 8726 // Return string converted to upper-case. 8727 writer.stringToUpperCaseResult(strId); 8728 writer.returnFromIC(); 8729 8730 trackAttached("StringToLocaleUpperCase"); 8731 return AttachDecision::Attach; 8732 #else 8733 // No inlining when Intl support is disabled. 8734 return AttachDecision::Attach; 8735 #endif 8736 } 8737 8738 AttachDecision InlinableNativeIRGenerator::tryAttachStringTrim() { 8739 // Expecting no arguments. 8740 if (args_.length() != 0) { 8741 return AttachDecision::NoAction; 8742 } 8743 8744 // Ensure |this| is a primitive string value. 8745 if (!thisval_.isString()) { 8746 return AttachDecision::NoAction; 8747 } 8748 8749 // Initialize the input operand. 8750 Int32OperandId argcId = initializeInputOperand(); 8751 8752 // Guard callee is the 'trim' native function. 8753 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8754 8755 // Guard this is a string. 8756 ValOperandId thisValId = loadThis(calleeId); 8757 StringOperandId strId = writer.guardToString(thisValId); 8758 8759 writer.stringTrimResult(strId); 8760 writer.returnFromIC(); 8761 8762 trackAttached("StringTrim"); 8763 return AttachDecision::Attach; 8764 } 8765 8766 AttachDecision InlinableNativeIRGenerator::tryAttachStringTrimStart() { 8767 // Expecting no arguments. 8768 if (args_.length() != 0) { 8769 return AttachDecision::NoAction; 8770 } 8771 8772 // Ensure |this| is a primitive string value. 8773 if (!thisval_.isString()) { 8774 return AttachDecision::NoAction; 8775 } 8776 8777 // Initialize the input operand. 8778 Int32OperandId argcId = initializeInputOperand(); 8779 8780 // Guard callee is the 'trimStart' native function. 8781 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8782 8783 // Guard this is a string. 8784 ValOperandId thisValId = loadThis(calleeId); 8785 StringOperandId strId = writer.guardToString(thisValId); 8786 8787 writer.stringTrimStartResult(strId); 8788 writer.returnFromIC(); 8789 8790 trackAttached("StringTrimStart"); 8791 return AttachDecision::Attach; 8792 } 8793 8794 AttachDecision InlinableNativeIRGenerator::tryAttachStringTrimEnd() { 8795 // Expecting no arguments. 8796 if (args_.length() != 0) { 8797 return AttachDecision::NoAction; 8798 } 8799 8800 // Ensure |this| is a primitive string value. 8801 if (!thisval_.isString()) { 8802 return AttachDecision::NoAction; 8803 } 8804 8805 // Initialize the input operand. 8806 Int32OperandId argcId = initializeInputOperand(); 8807 8808 // Guard callee is the 'trimEnd' native function. 8809 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8810 8811 // Guard this is a string. 8812 ValOperandId thisValId = loadThis(calleeId); 8813 StringOperandId strId = writer.guardToString(thisValId); 8814 8815 writer.stringTrimEndResult(strId); 8816 writer.returnFromIC(); 8817 8818 trackAttached("StringTrimEnd"); 8819 return AttachDecision::Attach; 8820 } 8821 8822 AttachDecision InlinableNativeIRGenerator::tryAttachMathRandom() { 8823 // Expecting no arguments. 8824 if (args_.length() != 0) { 8825 return AttachDecision::NoAction; 8826 } 8827 8828 MOZ_ASSERT(cx_->realm() == target_->realm(), 8829 "Shouldn't inline cross-realm Math.random because per-realm RNG"); 8830 8831 // Initialize the input operand. 8832 Int32OperandId argcId = initializeInputOperand(); 8833 8834 // Guard callee is the 'random' native function. 8835 emitNativeCalleeGuard(argcId); 8836 8837 mozilla::non_crypto::XorShift128PlusRNG* rng = 8838 &cx_->realm()->getOrCreateRandomNumberGenerator(); 8839 writer.mathRandomResult(rng); 8840 8841 writer.returnFromIC(); 8842 8843 trackAttached("MathRandom"); 8844 return AttachDecision::Attach; 8845 } 8846 8847 AttachDecision InlinableNativeIRGenerator::tryAttachMathAbs() { 8848 // Need one argument. 8849 if (args_.length() != 1) { 8850 return AttachDecision::NoAction; 8851 } 8852 8853 if (!args_[0].isNumber()) { 8854 return AttachDecision::NoAction; 8855 } 8856 8857 // Initialize the input operand. 8858 Int32OperandId argcId = initializeInputOperand(); 8859 8860 // Guard callee is the 'abs' native function. 8861 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8862 8863 ValOperandId argumentId = loadArgument(calleeId, ArgumentKind::Arg0); 8864 8865 // abs(INT_MIN) is a double. 8866 if (args_[0].isInt32() && args_[0].toInt32() != INT_MIN) { 8867 Int32OperandId int32Id = writer.guardToInt32(argumentId); 8868 writer.mathAbsInt32Result(int32Id); 8869 } else { 8870 NumberOperandId numberId = writer.guardIsNumber(argumentId); 8871 writer.mathAbsNumberResult(numberId); 8872 } 8873 8874 writer.returnFromIC(); 8875 8876 trackAttached("MathAbs"); 8877 return AttachDecision::Attach; 8878 } 8879 8880 AttachDecision InlinableNativeIRGenerator::tryAttachMathClz32() { 8881 // Need one (number) argument. 8882 if (args_.length() != 1 || !args_[0].isNumber()) { 8883 return AttachDecision::NoAction; 8884 } 8885 8886 // Initialize the input operand. 8887 Int32OperandId argcId = initializeInputOperand(); 8888 8889 // Guard callee is the 'clz32' native function. 8890 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8891 8892 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 8893 8894 Int32OperandId int32Id; 8895 if (args_[0].isInt32()) { 8896 int32Id = writer.guardToInt32(argId); 8897 } else { 8898 MOZ_ASSERT(args_[0].isDouble()); 8899 NumberOperandId numId = writer.guardIsNumber(argId); 8900 int32Id = writer.truncateDoubleToUInt32(numId); 8901 } 8902 writer.mathClz32Result(int32Id); 8903 writer.returnFromIC(); 8904 8905 trackAttached("MathClz32"); 8906 return AttachDecision::Attach; 8907 } 8908 8909 AttachDecision InlinableNativeIRGenerator::tryAttachMathSign() { 8910 // Need one (number) argument. 8911 if (args_.length() != 1 || !args_[0].isNumber()) { 8912 return AttachDecision::NoAction; 8913 } 8914 8915 // Initialize the input operand. 8916 Int32OperandId argcId = initializeInputOperand(); 8917 8918 // Guard callee is the 'sign' native function. 8919 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8920 8921 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 8922 8923 if (args_[0].isInt32()) { 8924 Int32OperandId int32Id = writer.guardToInt32(argId); 8925 writer.mathSignInt32Result(int32Id); 8926 } else { 8927 // Math.sign returns a double only if the input is -0 or NaN so try to 8928 // optimize the common Number => Int32 case. 8929 double d = math_sign_impl(args_[0].toDouble()); 8930 int32_t unused; 8931 bool resultIsInt32 = mozilla::NumberIsInt32(d, &unused); 8932 8933 NumberOperandId numId = writer.guardIsNumber(argId); 8934 if (resultIsInt32) { 8935 writer.mathSignNumberToInt32Result(numId); 8936 } else { 8937 writer.mathSignNumberResult(numId); 8938 } 8939 } 8940 8941 writer.returnFromIC(); 8942 8943 trackAttached("MathSign"); 8944 return AttachDecision::Attach; 8945 } 8946 8947 AttachDecision InlinableNativeIRGenerator::tryAttachMathImul() { 8948 // Need two (number) arguments. 8949 if (args_.length() != 2 || !args_[0].isNumber() || !args_[1].isNumber()) { 8950 return AttachDecision::NoAction; 8951 } 8952 8953 // Initialize the input operand. 8954 Int32OperandId argcId = initializeInputOperand(); 8955 8956 // Guard callee is the 'imul' native function. 8957 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8958 8959 ValOperandId arg0Id = loadArgument(calleeId, ArgumentKind::Arg0); 8960 ValOperandId arg1Id = loadArgument(calleeId, ArgumentKind::Arg1); 8961 8962 Int32OperandId int32Arg0Id, int32Arg1Id; 8963 if (args_[0].isInt32() && args_[1].isInt32()) { 8964 int32Arg0Id = writer.guardToInt32(arg0Id); 8965 int32Arg1Id = writer.guardToInt32(arg1Id); 8966 } else { 8967 // Treat both arguments as numbers if at least one of them is non-int32. 8968 NumberOperandId numArg0Id = writer.guardIsNumber(arg0Id); 8969 NumberOperandId numArg1Id = writer.guardIsNumber(arg1Id); 8970 int32Arg0Id = writer.truncateDoubleToUInt32(numArg0Id); 8971 int32Arg1Id = writer.truncateDoubleToUInt32(numArg1Id); 8972 } 8973 writer.mathImulResult(int32Arg0Id, int32Arg1Id); 8974 writer.returnFromIC(); 8975 8976 trackAttached("MathImul"); 8977 return AttachDecision::Attach; 8978 } 8979 8980 AttachDecision InlinableNativeIRGenerator::tryAttachMathFloor() { 8981 // Need one (number) argument. 8982 if (args_.length() != 1 || !args_[0].isNumber()) { 8983 return AttachDecision::NoAction; 8984 } 8985 8986 // Check if the result fits in int32. 8987 double res = math_floor_impl(args_[0].toNumber()); 8988 int32_t unused; 8989 bool resultIsInt32 = mozilla::NumberIsInt32(res, &unused); 8990 8991 // Initialize the input operand. 8992 Int32OperandId argcId = initializeInputOperand(); 8993 8994 // Guard callee is the 'floor' native function. 8995 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 8996 8997 ValOperandId argumentId = loadArgument(calleeId, ArgumentKind::Arg0); 8998 8999 if (args_[0].isInt32()) { 9000 MOZ_ASSERT(resultIsInt32); 9001 9002 // Use an indirect truncation to inform the optimizer it needs to preserve 9003 // a bailout when the input can't be represented as an int32, even if the 9004 // final result is fully truncated. 9005 Int32OperandId intId = writer.guardToInt32(argumentId); 9006 writer.indirectTruncateInt32Result(intId); 9007 } else { 9008 NumberOperandId numberId = writer.guardIsNumber(argumentId); 9009 9010 if (resultIsInt32) { 9011 writer.mathFloorToInt32Result(numberId); 9012 } else { 9013 writer.mathFloorNumberResult(numberId); 9014 } 9015 } 9016 9017 writer.returnFromIC(); 9018 9019 trackAttached("MathFloor"); 9020 return AttachDecision::Attach; 9021 } 9022 9023 AttachDecision InlinableNativeIRGenerator::tryAttachMathCeil() { 9024 // Need one (number) argument. 9025 if (args_.length() != 1 || !args_[0].isNumber()) { 9026 return AttachDecision::NoAction; 9027 } 9028 9029 // Check if the result fits in int32. 9030 double res = math_ceil_impl(args_[0].toNumber()); 9031 int32_t unused; 9032 bool resultIsInt32 = mozilla::NumberIsInt32(res, &unused); 9033 9034 // Initialize the input operand. 9035 Int32OperandId argcId = initializeInputOperand(); 9036 9037 // Guard callee is the 'ceil' native function. 9038 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 9039 9040 ValOperandId argumentId = loadArgument(calleeId, ArgumentKind::Arg0); 9041 9042 if (args_[0].isInt32()) { 9043 MOZ_ASSERT(resultIsInt32); 9044 9045 // Use an indirect truncation to inform the optimizer it needs to preserve 9046 // a bailout when the input can't be represented as an int32, even if the 9047 // final result is fully truncated. 9048 Int32OperandId intId = writer.guardToInt32(argumentId); 9049 writer.indirectTruncateInt32Result(intId); 9050 } else { 9051 NumberOperandId numberId = writer.guardIsNumber(argumentId); 9052 9053 if (resultIsInt32) { 9054 writer.mathCeilToInt32Result(numberId); 9055 } else { 9056 writer.mathCeilNumberResult(numberId); 9057 } 9058 } 9059 9060 writer.returnFromIC(); 9061 9062 trackAttached("MathCeil"); 9063 return AttachDecision::Attach; 9064 } 9065 9066 AttachDecision InlinableNativeIRGenerator::tryAttachMathTrunc() { 9067 // Need one (number) argument. 9068 if (args_.length() != 1 || !args_[0].isNumber()) { 9069 return AttachDecision::NoAction; 9070 } 9071 9072 // Check if the result fits in int32. 9073 double res = math_trunc_impl(args_[0].toNumber()); 9074 int32_t unused; 9075 bool resultIsInt32 = mozilla::NumberIsInt32(res, &unused); 9076 9077 // Initialize the input operand. 9078 Int32OperandId argcId = initializeInputOperand(); 9079 9080 // Guard callee is the 'trunc' native function. 9081 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 9082 9083 ValOperandId argumentId = loadArgument(calleeId, ArgumentKind::Arg0); 9084 9085 if (args_[0].isInt32()) { 9086 MOZ_ASSERT(resultIsInt32); 9087 9088 // We don't need an indirect truncation barrier here, because Math.trunc 9089 // always truncates, but never rounds its input away from zero. 9090 Int32OperandId intId = writer.guardToInt32(argumentId); 9091 writer.loadInt32Result(intId); 9092 } else { 9093 NumberOperandId numberId = writer.guardIsNumber(argumentId); 9094 9095 if (resultIsInt32) { 9096 writer.mathTruncToInt32Result(numberId); 9097 } else { 9098 writer.mathTruncNumberResult(numberId); 9099 } 9100 } 9101 9102 writer.returnFromIC(); 9103 9104 trackAttached("MathTrunc"); 9105 return AttachDecision::Attach; 9106 } 9107 9108 AttachDecision InlinableNativeIRGenerator::tryAttachMathRound() { 9109 // Need one (number) argument. 9110 if (args_.length() != 1 || !args_[0].isNumber()) { 9111 return AttachDecision::NoAction; 9112 } 9113 9114 // Check if the result fits in int32. 9115 double res = math_round_impl(args_[0].toNumber()); 9116 int32_t unused; 9117 bool resultIsInt32 = mozilla::NumberIsInt32(res, &unused); 9118 9119 // Initialize the input operand. 9120 Int32OperandId argcId = initializeInputOperand(); 9121 9122 // Guard callee is the 'round' native function. 9123 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 9124 9125 ValOperandId argumentId = loadArgument(calleeId, ArgumentKind::Arg0); 9126 9127 if (args_[0].isInt32()) { 9128 MOZ_ASSERT(resultIsInt32); 9129 9130 // Use an indirect truncation to inform the optimizer it needs to preserve 9131 // a bailout when the input can't be represented as an int32, even if the 9132 // final result is fully truncated. 9133 Int32OperandId intId = writer.guardToInt32(argumentId); 9134 writer.indirectTruncateInt32Result(intId); 9135 } else { 9136 NumberOperandId numberId = writer.guardIsNumber(argumentId); 9137 9138 if (resultIsInt32) { 9139 writer.mathRoundToInt32Result(numberId); 9140 } else { 9141 writer.mathRoundNumberResult(numberId); 9142 } 9143 } 9144 9145 writer.returnFromIC(); 9146 9147 trackAttached("MathRound"); 9148 return AttachDecision::Attach; 9149 } 9150 9151 AttachDecision InlinableNativeIRGenerator::tryAttachMathSqrt() { 9152 // Need one (number) argument. 9153 if (args_.length() != 1 || !args_[0].isNumber()) { 9154 return AttachDecision::NoAction; 9155 } 9156 9157 // Initialize the input operand. 9158 Int32OperandId argcId = initializeInputOperand(); 9159 9160 // Guard callee is the 'sqrt' native function. 9161 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 9162 9163 ValOperandId argumentId = loadArgument(calleeId, ArgumentKind::Arg0); 9164 NumberOperandId numberId = writer.guardIsNumber(argumentId); 9165 writer.mathSqrtNumberResult(numberId); 9166 writer.returnFromIC(); 9167 9168 trackAttached("MathSqrt"); 9169 return AttachDecision::Attach; 9170 } 9171 9172 AttachDecision InlinableNativeIRGenerator::tryAttachMathFRound() { 9173 // Need one (number) argument. 9174 if (args_.length() != 1 || !args_[0].isNumber()) { 9175 return AttachDecision::NoAction; 9176 } 9177 9178 // Initialize the input operand. 9179 Int32OperandId argcId = initializeInputOperand(); 9180 9181 // Guard callee is the 'fround' native function. 9182 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 9183 9184 ValOperandId argumentId = loadArgument(calleeId, ArgumentKind::Arg0); 9185 NumberOperandId numberId = writer.guardIsNumber(argumentId); 9186 writer.mathFRoundNumberResult(numberId); 9187 writer.returnFromIC(); 9188 9189 trackAttached("MathFRound"); 9190 return AttachDecision::Attach; 9191 } 9192 9193 AttachDecision InlinableNativeIRGenerator::tryAttachMathF16Round() { 9194 // Need one (number) argument. 9195 if (args_.length() != 1 || !args_[0].isNumber()) { 9196 return AttachDecision::NoAction; 9197 } 9198 9199 // Initialize the input operand. 9200 Int32OperandId argcId = initializeInputOperand(); 9201 9202 // Guard callee is the 'f16round' native function. 9203 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 9204 9205 ValOperandId argumentId = loadArgument(calleeId, ArgumentKind::Arg0); 9206 NumberOperandId numberId = writer.guardIsNumber(argumentId); 9207 writer.mathF16RoundNumberResult(numberId); 9208 writer.returnFromIC(); 9209 9210 trackAttached("MathF16Round"); 9211 return AttachDecision::Attach; 9212 } 9213 9214 static bool CanAttachInt32Pow(const Value& baseVal, const Value& powerVal) { 9215 auto valToInt32 = [](const Value& v) { 9216 if (v.isInt32()) { 9217 return v.toInt32(); 9218 } 9219 if (v.isBoolean()) { 9220 return int32_t(v.toBoolean()); 9221 } 9222 MOZ_ASSERT(v.isNull()); 9223 return 0; 9224 }; 9225 int32_t base = valToInt32(baseVal); 9226 int32_t power = valToInt32(powerVal); 9227 9228 // x^y where y < 0 is most of the time not an int32, except when x is 1 or y 9229 // gets large enough. It's hard to determine when exactly y is "large enough", 9230 // so we don't use Int32PowResult when x != 1 and y < 0. 9231 // Note: it's important for this condition to match the code generated by 9232 // MacroAssembler::pow32 to prevent failure loops. 9233 if (power < 0) { 9234 return base == 1; 9235 } 9236 9237 double res = powi(base, power); 9238 int32_t unused; 9239 return mozilla::NumberIsInt32(res, &unused); 9240 } 9241 9242 AttachDecision InlinableNativeIRGenerator::tryAttachMathPow() { 9243 // Need two number arguments. 9244 if (args_.length() != 2 || !args_[0].isNumber() || !args_[1].isNumber()) { 9245 return AttachDecision::NoAction; 9246 } 9247 9248 // Initialize the input operand. 9249 Int32OperandId argcId = initializeInputOperand(); 9250 9251 // Guard callee is the 'pow' function. 9252 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 9253 9254 ValOperandId baseId = loadArgument(calleeId, ArgumentKind::Arg0); 9255 ValOperandId exponentId = loadArgument(calleeId, ArgumentKind::Arg1); 9256 9257 if (args_[0].isInt32() && args_[1].isInt32() && 9258 CanAttachInt32Pow(args_[0], args_[1])) { 9259 Int32OperandId baseInt32Id = writer.guardToInt32(baseId); 9260 Int32OperandId exponentInt32Id = writer.guardToInt32(exponentId); 9261 writer.int32PowResult(baseInt32Id, exponentInt32Id); 9262 } else { 9263 NumberOperandId baseNumberId = writer.guardIsNumber(baseId); 9264 NumberOperandId exponentNumberId = writer.guardIsNumber(exponentId); 9265 writer.doublePowResult(baseNumberId, exponentNumberId); 9266 } 9267 9268 writer.returnFromIC(); 9269 9270 trackAttached("MathPow"); 9271 return AttachDecision::Attach; 9272 } 9273 9274 AttachDecision InlinableNativeIRGenerator::tryAttachMathHypot() { 9275 // Only optimize if there are 2-4 arguments. 9276 if (args_.length() < 2 || args_.length() > 4) { 9277 return AttachDecision::NoAction; 9278 } 9279 9280 for (size_t i = 0; i < args_.length(); i++) { 9281 if (!args_[i].isNumber()) { 9282 return AttachDecision::NoAction; 9283 } 9284 } 9285 9286 // Initialize the input operand. 9287 Int32OperandId argcId = initializeInputOperand(); 9288 9289 // Guard callee is the 'hypot' native function. 9290 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 9291 9292 ValOperandId firstId = loadArgument(calleeId, ArgumentKind::Arg0); 9293 ValOperandId secondId = loadArgument(calleeId, ArgumentKind::Arg1); 9294 9295 NumberOperandId firstNumId = writer.guardIsNumber(firstId); 9296 NumberOperandId secondNumId = writer.guardIsNumber(secondId); 9297 9298 ValOperandId thirdId; 9299 ValOperandId fourthId; 9300 NumberOperandId thirdNumId; 9301 NumberOperandId fourthNumId; 9302 9303 switch (args_.length()) { 9304 case 2: 9305 writer.mathHypot2NumberResult(firstNumId, secondNumId); 9306 break; 9307 case 3: 9308 thirdId = loadArgument(calleeId, ArgumentKind::Arg2); 9309 thirdNumId = writer.guardIsNumber(thirdId); 9310 writer.mathHypot3NumberResult(firstNumId, secondNumId, thirdNumId); 9311 break; 9312 case 4: 9313 thirdId = loadArgument(calleeId, ArgumentKind::Arg2); 9314 fourthId = loadArgument(calleeId, ArgumentKind::Arg3); 9315 thirdNumId = writer.guardIsNumber(thirdId); 9316 fourthNumId = writer.guardIsNumber(fourthId); 9317 writer.mathHypot4NumberResult(firstNumId, secondNumId, thirdNumId, 9318 fourthNumId); 9319 break; 9320 default: 9321 MOZ_CRASH("Unexpected number of arguments to hypot function."); 9322 } 9323 9324 writer.returnFromIC(); 9325 9326 trackAttached("MathHypot"); 9327 return AttachDecision::Attach; 9328 } 9329 9330 AttachDecision InlinableNativeIRGenerator::tryAttachMathATan2() { 9331 // Requires two numbers as arguments. 9332 if (args_.length() != 2 || !args_[0].isNumber() || !args_[1].isNumber()) { 9333 return AttachDecision::NoAction; 9334 } 9335 9336 // Initialize the input operand. 9337 Int32OperandId argcId = initializeInputOperand(); 9338 9339 // Guard callee is the 'atan2' native function. 9340 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 9341 9342 ValOperandId yId = loadArgument(calleeId, ArgumentKind::Arg0); 9343 ValOperandId xId = loadArgument(calleeId, ArgumentKind::Arg1); 9344 9345 NumberOperandId yNumberId = writer.guardIsNumber(yId); 9346 NumberOperandId xNumberId = writer.guardIsNumber(xId); 9347 9348 writer.mathAtan2NumberResult(yNumberId, xNumberId); 9349 writer.returnFromIC(); 9350 9351 trackAttached("MathAtan2"); 9352 return AttachDecision::Attach; 9353 } 9354 9355 AttachDecision InlinableNativeIRGenerator::tryAttachMathMinMax(bool isMax) { 9356 // For now only optimize if there are 1-4 arguments. 9357 if (args_.length() < 1 || args_.length() > 4) { 9358 return AttachDecision::NoAction; 9359 } 9360 9361 // Ensure all arguments are numbers. 9362 bool allInt32 = true; 9363 for (size_t i = 0; i < args_.length(); i++) { 9364 if (!args_[i].isNumber()) { 9365 return AttachDecision::NoAction; 9366 } 9367 if (!args_[i].isInt32()) { 9368 allInt32 = false; 9369 } 9370 } 9371 9372 // Initialize the input operand. 9373 Int32OperandId argcId = initializeInputOperand(); 9374 9375 // Guard callee is this Math function. 9376 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 9377 9378 if (allInt32) { 9379 ValOperandId valId = loadArgument(calleeId, ArgumentKind::Arg0); 9380 Int32OperandId resId = writer.guardToInt32(valId); 9381 for (size_t i = 1; i < args_.length(); i++) { 9382 ValOperandId argId = loadArgument(calleeId, ArgumentKindForArgIndex(i)); 9383 Int32OperandId argInt32Id = writer.guardToInt32(argId); 9384 resId = writer.int32MinMax(isMax, resId, argInt32Id); 9385 } 9386 writer.loadInt32Result(resId); 9387 } else { 9388 ValOperandId valId = loadArgument(calleeId, ArgumentKind::Arg0); 9389 NumberOperandId resId = writer.guardIsNumber(valId); 9390 for (size_t i = 1; i < args_.length(); i++) { 9391 ValOperandId argId = loadArgument(calleeId, ArgumentKindForArgIndex(i)); 9392 NumberOperandId argNumId = writer.guardIsNumber(argId); 9393 resId = writer.numberMinMax(isMax, resId, argNumId); 9394 } 9395 writer.loadDoubleResult(resId); 9396 } 9397 9398 writer.returnFromIC(); 9399 9400 trackAttached(isMax ? "MathMax" : "MathMin"); 9401 return AttachDecision::Attach; 9402 } 9403 9404 AttachDecision InlinableNativeIRGenerator::tryAttachSpreadMathMinMax( 9405 bool isMax) { 9406 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Spread || 9407 flags_.getArgFormat() == CallFlags::FunApplyArray); 9408 9409 // The result will be an int32 if there is at least one argument, 9410 // and all the arguments are int32. 9411 bool int32Result = args_.length() > 0; 9412 for (size_t i = 0; i < args_.length(); i++) { 9413 if (!args_[i].isNumber()) { 9414 return AttachDecision::NoAction; 9415 } 9416 if (!args_[i].isInt32()) { 9417 int32Result = false; 9418 } 9419 } 9420 9421 // Initialize the input operand. 9422 Int32OperandId argcId = initializeInputOperand(); 9423 9424 // Guard callee is this Math function. 9425 emitNativeCalleeGuard(argcId); 9426 9427 // Load the argument array. 9428 ObjOperandId argsId = emitLoadArgsArray(); 9429 9430 if (int32Result) { 9431 writer.int32MinMaxArrayResult(argsId, isMax); 9432 } else { 9433 writer.numberMinMaxArrayResult(argsId, isMax); 9434 } 9435 9436 writer.returnFromIC(); 9437 9438 trackAttached(isMax ? "MathMaxArray" : "MathMinArray"); 9439 return AttachDecision::Attach; 9440 } 9441 9442 AttachDecision InlinableNativeIRGenerator::tryAttachMathFunction( 9443 UnaryMathFunction fun) { 9444 // Need one argument. 9445 if (args_.length() != 1) { 9446 return AttachDecision::NoAction; 9447 } 9448 9449 if (!args_[0].isNumber()) { 9450 return AttachDecision::NoAction; 9451 } 9452 9453 if (math_use_fdlibm_for_sin_cos_tan() || 9454 target_->realm()->creationOptions().alwaysUseFdlibm()) { 9455 switch (fun) { 9456 case UnaryMathFunction::SinNative: 9457 fun = UnaryMathFunction::SinFdlibm; 9458 break; 9459 case UnaryMathFunction::CosNative: 9460 fun = UnaryMathFunction::CosFdlibm; 9461 break; 9462 case UnaryMathFunction::TanNative: 9463 fun = UnaryMathFunction::TanFdlibm; 9464 break; 9465 default: 9466 break; 9467 } 9468 } 9469 9470 // Initialize the input operand. 9471 Int32OperandId argcId = initializeInputOperand(); 9472 9473 // Guard callee is this Math function. 9474 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 9475 9476 ValOperandId argumentId = loadArgument(calleeId, ArgumentKind::Arg0); 9477 NumberOperandId numberId = writer.guardIsNumber(argumentId); 9478 writer.mathFunctionNumberResult(numberId, fun); 9479 writer.returnFromIC(); 9480 9481 trackAttached("MathFunction"); 9482 return AttachDecision::Attach; 9483 } 9484 9485 AttachDecision InlinableNativeIRGenerator::tryAttachNumber() { 9486 // Expect a single string argument. 9487 if (args_.length() != 1 || !args_[0].isString()) { 9488 return AttachDecision::NoAction; 9489 } 9490 9491 double num; 9492 if (!StringToNumber(cx_, args_[0].toString(), &num)) { 9493 cx_->recoverFromOutOfMemory(); 9494 return AttachDecision::NoAction; 9495 } 9496 9497 // Initialize the input operand. 9498 Int32OperandId argcId = initializeInputOperand(); 9499 9500 // Guard callee is the `Number` function. 9501 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 9502 9503 // Guard that the argument is a string. 9504 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 9505 StringOperandId strId = writer.guardToString(argId); 9506 9507 // Return either an Int32 or Double result. 9508 int32_t unused; 9509 if (mozilla::NumberIsInt32(num, &unused)) { 9510 Int32OperandId resultId = writer.guardStringToInt32(strId); 9511 writer.loadInt32Result(resultId); 9512 } else { 9513 NumberOperandId resultId = writer.guardStringToNumber(strId); 9514 writer.loadDoubleResult(resultId); 9515 } 9516 writer.returnFromIC(); 9517 9518 trackAttached("Number"); 9519 return AttachDecision::Attach; 9520 } 9521 9522 AttachDecision InlinableNativeIRGenerator::tryAttachNumberParseInt() { 9523 // Expected arguments: input (string or number), optional radix (int32). 9524 if (args_.length() < 1 || args_.length() > 2) { 9525 return AttachDecision::NoAction; 9526 } 9527 if (!args_[0].isString() && !args_[0].isNumber()) { 9528 return AttachDecision::NoAction; 9529 } 9530 if (args_[0].isDouble()) { 9531 double d = args_[0].toDouble(); 9532 9533 // See num_parseInt for why we have to reject numbers smaller than 1.0e-6. 9534 // Negative numbers in the exclusive range (-1, -0) return -0. 9535 bool canTruncateToInt32 = 9536 (DOUBLE_DECIMAL_IN_SHORTEST_LOW <= d && d <= double(INT32_MAX)) || 9537 (double(INT32_MIN) <= d && d <= -1.0) || (d == 0.0); 9538 if (!canTruncateToInt32) { 9539 return AttachDecision::NoAction; 9540 } 9541 } 9542 if (args_.length() > 1 && !args_[1].isInt32(10)) { 9543 return AttachDecision::NoAction; 9544 } 9545 9546 // Initialize the input operand. 9547 Int32OperandId argcId = initializeInputOperand(); 9548 9549 // Guard callee is the 'parseInt' native function. 9550 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 9551 9552 auto guardRadix = [&]() { 9553 ValOperandId radixId = loadArgument(calleeId, ArgumentKind::Arg1); 9554 Int32OperandId intRadixId = writer.guardToInt32(radixId); 9555 writer.guardSpecificInt32(intRadixId, 10); 9556 return intRadixId; 9557 }; 9558 9559 ValOperandId inputId = loadArgument(calleeId, ArgumentKind::Arg0); 9560 9561 if (args_[0].isString()) { 9562 StringOperandId strId = writer.guardToString(inputId); 9563 9564 Int32OperandId intRadixId; 9565 if (args_.length() > 1) { 9566 intRadixId = guardRadix(); 9567 } else { 9568 intRadixId = writer.loadInt32Constant(0); 9569 } 9570 9571 writer.numberParseIntResult(strId, intRadixId); 9572 } else if (args_[0].isInt32()) { 9573 Int32OperandId intId = writer.guardToInt32(inputId); 9574 if (args_.length() > 1) { 9575 guardRadix(); 9576 } 9577 writer.loadInt32Result(intId); 9578 } else { 9579 MOZ_ASSERT(args_[0].isDouble()); 9580 9581 NumberOperandId numId = writer.guardIsNumber(inputId); 9582 if (args_.length() > 1) { 9583 guardRadix(); 9584 } 9585 writer.doubleParseIntResult(numId); 9586 } 9587 9588 writer.returnFromIC(); 9589 9590 trackAttached("NumberParseInt"); 9591 return AttachDecision::Attach; 9592 } 9593 9594 StringOperandId IRGenerator::emitToStringGuard(ValOperandId id, 9595 const Value& v) { 9596 MOZ_ASSERT(CanConvertToString(v)); 9597 if (v.isString()) { 9598 return writer.guardToString(id); 9599 } 9600 if (v.isBoolean()) { 9601 BooleanOperandId boolId = writer.guardToBoolean(id); 9602 return writer.booleanToString(boolId); 9603 } 9604 if (v.isNull()) { 9605 writer.guardIsNull(id); 9606 return writer.loadConstantString(cx_->names().null); 9607 } 9608 if (v.isUndefined()) { 9609 writer.guardIsUndefined(id); 9610 return writer.loadConstantString(cx_->names().undefined); 9611 } 9612 if (v.isInt32()) { 9613 Int32OperandId intId = writer.guardToInt32(id); 9614 return writer.callInt32ToString(intId); 9615 } 9616 // At this point we are creating an IC that will handle 9617 // both Int32 and Double cases. 9618 MOZ_ASSERT(v.isNumber()); 9619 NumberOperandId numId = writer.guardIsNumber(id); 9620 return writer.callNumberToString(numId); 9621 } 9622 9623 AttachDecision InlinableNativeIRGenerator::tryAttachNumberToString() { 9624 // Expecting no arguments or a single int32 argument. 9625 if (args_.length() > 1) { 9626 return AttachDecision::NoAction; 9627 } 9628 if (args_.length() == 1 && !args_[0].isInt32()) { 9629 return AttachDecision::NoAction; 9630 } 9631 9632 // Ensure |this| is a primitive number value. 9633 if (!thisval_.isNumber()) { 9634 return AttachDecision::NoAction; 9635 } 9636 9637 // No arguments means base 10. 9638 int32_t base = 10; 9639 if (args_.length() > 0) { 9640 base = args_[0].toInt32(); 9641 if (base < 2 || base > 36) { 9642 return AttachDecision::NoAction; 9643 } 9644 9645 // Non-decimal bases currently only support int32 inputs. 9646 if (base != 10 && !thisval_.isInt32()) { 9647 return AttachDecision::NoAction; 9648 } 9649 } 9650 MOZ_ASSERT(2 <= base && base <= 36); 9651 9652 // Initialize the input operand. 9653 Int32OperandId argcId = initializeInputOperand(); 9654 9655 // Guard callee is the 'toString' native function. 9656 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 9657 9658 // Initialize the |this| operand. 9659 ValOperandId thisValId = loadThis(calleeId); 9660 9661 // Guard on number and convert to string. 9662 if (base == 10) { 9663 // If an explicit base was passed, guard its value. 9664 if (args_.length() > 0) { 9665 // Guard the `base` argument is an int32. 9666 ValOperandId baseId = loadArgument(calleeId, ArgumentKind::Arg0); 9667 Int32OperandId intBaseId = writer.guardToInt32(baseId); 9668 9669 // Guard `base` is 10 for decimal toString representation. 9670 writer.guardSpecificInt32(intBaseId, 10); 9671 } 9672 9673 StringOperandId strId = emitToStringGuard(thisValId, thisval_); 9674 9675 // Return the string. 9676 writer.loadStringResult(strId); 9677 } else { 9678 MOZ_ASSERT(args_.length() > 0); 9679 9680 // Guard the |this| value is an int32. 9681 Int32OperandId thisIntId = writer.guardToInt32(thisValId); 9682 9683 // Guard the `base` argument is an int32. 9684 ValOperandId baseId = loadArgument(calleeId, ArgumentKind::Arg0); 9685 Int32OperandId intBaseId = writer.guardToInt32(baseId); 9686 9687 // Return the string. 9688 writer.int32ToStringWithBaseResult(thisIntId, intBaseId); 9689 } 9690 9691 writer.returnFromIC(); 9692 9693 trackAttached("NumberToString"); 9694 return AttachDecision::Attach; 9695 } 9696 9697 AttachDecision InlinableNativeIRGenerator::tryAttachReflectGetPrototypeOf() { 9698 // Need one argument. 9699 if (args_.length() != 1) { 9700 return AttachDecision::NoAction; 9701 } 9702 9703 if (!args_[0].isObject()) { 9704 return AttachDecision::NoAction; 9705 } 9706 9707 // Initialize the input operand. 9708 Int32OperandId argcId = initializeInputOperand(); 9709 9710 // Guard callee is the 'getPrototypeOf' native function. 9711 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 9712 9713 ValOperandId argumentId = loadArgument(calleeId, ArgumentKind::Arg0); 9714 ObjOperandId objId = writer.guardToObject(argumentId); 9715 9716 writer.reflectGetPrototypeOfResult(objId); 9717 writer.returnFromIC(); 9718 9719 trackAttached("ReflectGetPrototypeOf"); 9720 return AttachDecision::Attach; 9721 } 9722 9723 enum class AtomicAccess { Read, Write }; 9724 9725 static bool AtomicsMeetsPreconditions(TypedArrayObject* typedArray, 9726 const Value& index, AtomicAccess access) { 9727 // Can't write into immutable TypedArrays. 9728 if (access == AtomicAccess::Write && 9729 typedArray->is<ImmutableTypedArrayObject>()) { 9730 return false; 9731 } 9732 9733 switch (typedArray->type()) { 9734 case Scalar::Int8: 9735 case Scalar::Uint8: 9736 case Scalar::Int16: 9737 case Scalar::Uint16: 9738 case Scalar::Int32: 9739 case Scalar::Uint32: 9740 case Scalar::BigInt64: 9741 case Scalar::BigUint64: 9742 break; 9743 9744 case Scalar::Float16: 9745 case Scalar::Float32: 9746 case Scalar::Float64: 9747 case Scalar::Uint8Clamped: 9748 // Exclude floating types and Uint8Clamped. 9749 return false; 9750 9751 case Scalar::MaxTypedArrayViewType: 9752 case Scalar::Int64: 9753 case Scalar::Simd128: 9754 MOZ_CRASH("Unsupported TypedArray type"); 9755 } 9756 9757 // Bounds check the index argument. 9758 int64_t indexInt64; 9759 if (!ValueIsInt64Index(index, &indexInt64)) { 9760 return false; 9761 } 9762 if (indexInt64 < 0 || 9763 uint64_t(indexInt64) >= typedArray->length().valueOr(0)) { 9764 return false; 9765 } 9766 9767 return true; 9768 } 9769 9770 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsCompareExchange() { 9771 if (!JitSupportsAtomics()) { 9772 return AttachDecision::NoAction; 9773 } 9774 9775 // Need four arguments. 9776 if (args_.length() != 4) { 9777 return AttachDecision::NoAction; 9778 } 9779 9780 // Arguments: typedArray, index (number), expected, replacement. 9781 if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) { 9782 return AttachDecision::NoAction; 9783 } 9784 if (!args_[1].isNumber()) { 9785 return AttachDecision::NoAction; 9786 } 9787 9788 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>(); 9789 if (!AtomicsMeetsPreconditions(typedArray, args_[1], AtomicAccess::Write)) { 9790 return AttachDecision::NoAction; 9791 } 9792 9793 Scalar::Type elementType = typedArray->type(); 9794 if (!ValueCanConvertToNumeric(elementType, args_[2])) { 9795 return AttachDecision::NoAction; 9796 } 9797 if (!ValueCanConvertToNumeric(elementType, args_[3])) { 9798 return AttachDecision::NoAction; 9799 } 9800 9801 // Initialize the input operand. 9802 Int32OperandId argcId = initializeInputOperand(); 9803 9804 // Guard callee is the `compareExchange` native function. 9805 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 9806 9807 ValOperandId arg0Id = loadArgument(calleeId, ArgumentKind::Arg0); 9808 ObjOperandId objId = writer.guardToObject(arg0Id); 9809 writer.guardShapeForClass(objId, typedArray->shape()); 9810 9811 // Convert index to intPtr. 9812 ValOperandId indexId = loadArgument(calleeId, ArgumentKind::Arg1); 9813 IntPtrOperandId intPtrIndexId = 9814 guardToIntPtrIndex(args_[1], indexId, /* supportOOB = */ false); 9815 9816 // Convert expected value to int32/BigInt. 9817 ValOperandId expectedId = loadArgument(calleeId, ArgumentKind::Arg2); 9818 OperandId numericExpectedId = 9819 emitNumericGuard(expectedId, args_[2], elementType); 9820 9821 // Convert replacement value to int32/BigInt. 9822 ValOperandId replacementId = loadArgument(calleeId, ArgumentKind::Arg3); 9823 OperandId numericReplacementId = 9824 emitNumericGuard(replacementId, args_[3], elementType); 9825 9826 auto viewKind = ToArrayBufferViewKind(typedArray); 9827 writer.atomicsCompareExchangeResult(objId, intPtrIndexId, numericExpectedId, 9828 numericReplacementId, typedArray->type(), 9829 viewKind); 9830 writer.returnFromIC(); 9831 9832 trackAttached("AtomicsCompareExchange"); 9833 return AttachDecision::Attach; 9834 } 9835 9836 bool InlinableNativeIRGenerator::canAttachAtomicsReadWriteModify() { 9837 if (!JitSupportsAtomics()) { 9838 return false; 9839 } 9840 9841 // Need three arguments. 9842 if (args_.length() != 3) { 9843 return false; 9844 } 9845 9846 // Arguments: typedArray, index (number), value. 9847 if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) { 9848 return false; 9849 } 9850 if (!args_[1].isNumber()) { 9851 return false; 9852 } 9853 9854 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>(); 9855 if (!AtomicsMeetsPreconditions(typedArray, args_[1], AtomicAccess::Write)) { 9856 return false; 9857 } 9858 if (!ValueCanConvertToNumeric(typedArray->type(), args_[2])) { 9859 return false; 9860 } 9861 return true; 9862 } 9863 9864 InlinableNativeIRGenerator::AtomicsReadWriteModifyOperands 9865 InlinableNativeIRGenerator::emitAtomicsReadWriteModifyOperands() { 9866 MOZ_ASSERT(canAttachAtomicsReadWriteModify()); 9867 9868 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>(); 9869 9870 // Initialize the input operand. 9871 Int32OperandId argcId = initializeInputOperand(); 9872 9873 // Guard callee is this Atomics function. 9874 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 9875 9876 ValOperandId arg0Id = loadArgument(calleeId, ArgumentKind::Arg0); 9877 ObjOperandId objId = writer.guardToObject(arg0Id); 9878 writer.guardShapeForClass(objId, typedArray->shape()); 9879 9880 // Convert index to intPtr. 9881 ValOperandId indexId = loadArgument(calleeId, ArgumentKind::Arg1); 9882 IntPtrOperandId intPtrIndexId = 9883 guardToIntPtrIndex(args_[1], indexId, /* supportOOB = */ false); 9884 9885 // Convert value to int32/BigInt. 9886 ValOperandId valueId = loadArgument(calleeId, ArgumentKind::Arg2); 9887 OperandId numericValueId = 9888 emitNumericGuard(valueId, args_[2], typedArray->type()); 9889 9890 return {objId, intPtrIndexId, numericValueId}; 9891 } 9892 9893 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsExchange() { 9894 if (!canAttachAtomicsReadWriteModify()) { 9895 return AttachDecision::NoAction; 9896 } 9897 9898 auto [objId, intPtrIndexId, numericValueId] = 9899 emitAtomicsReadWriteModifyOperands(); 9900 9901 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>(); 9902 auto viewKind = ToArrayBufferViewKind(typedArray); 9903 9904 writer.atomicsExchangeResult(objId, intPtrIndexId, numericValueId, 9905 typedArray->type(), viewKind); 9906 writer.returnFromIC(); 9907 9908 trackAttached("AtomicsExchange"); 9909 return AttachDecision::Attach; 9910 } 9911 9912 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsAdd() { 9913 if (!canAttachAtomicsReadWriteModify()) { 9914 return AttachDecision::NoAction; 9915 } 9916 9917 auto [objId, intPtrIndexId, numericValueId] = 9918 emitAtomicsReadWriteModifyOperands(); 9919 9920 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>(); 9921 bool forEffect = ignoresResult(); 9922 auto viewKind = ToArrayBufferViewKind(typedArray); 9923 9924 writer.atomicsAddResult(objId, intPtrIndexId, numericValueId, 9925 typedArray->type(), forEffect, viewKind); 9926 writer.returnFromIC(); 9927 9928 trackAttached("AtomicsAdd"); 9929 return AttachDecision::Attach; 9930 } 9931 9932 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsSub() { 9933 if (!canAttachAtomicsReadWriteModify()) { 9934 return AttachDecision::NoAction; 9935 } 9936 9937 auto [objId, intPtrIndexId, numericValueId] = 9938 emitAtomicsReadWriteModifyOperands(); 9939 9940 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>(); 9941 bool forEffect = ignoresResult(); 9942 auto viewKind = ToArrayBufferViewKind(typedArray); 9943 9944 writer.atomicsSubResult(objId, intPtrIndexId, numericValueId, 9945 typedArray->type(), forEffect, viewKind); 9946 writer.returnFromIC(); 9947 9948 trackAttached("AtomicsSub"); 9949 return AttachDecision::Attach; 9950 } 9951 9952 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsAnd() { 9953 if (!canAttachAtomicsReadWriteModify()) { 9954 return AttachDecision::NoAction; 9955 } 9956 9957 auto [objId, intPtrIndexId, numericValueId] = 9958 emitAtomicsReadWriteModifyOperands(); 9959 9960 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>(); 9961 bool forEffect = ignoresResult(); 9962 auto viewKind = ToArrayBufferViewKind(typedArray); 9963 9964 writer.atomicsAndResult(objId, intPtrIndexId, numericValueId, 9965 typedArray->type(), forEffect, viewKind); 9966 writer.returnFromIC(); 9967 9968 trackAttached("AtomicsAnd"); 9969 return AttachDecision::Attach; 9970 } 9971 9972 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsOr() { 9973 if (!canAttachAtomicsReadWriteModify()) { 9974 return AttachDecision::NoAction; 9975 } 9976 9977 auto [objId, intPtrIndexId, numericValueId] = 9978 emitAtomicsReadWriteModifyOperands(); 9979 9980 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>(); 9981 bool forEffect = ignoresResult(); 9982 auto viewKind = ToArrayBufferViewKind(typedArray); 9983 9984 writer.atomicsOrResult(objId, intPtrIndexId, numericValueId, 9985 typedArray->type(), forEffect, viewKind); 9986 writer.returnFromIC(); 9987 9988 trackAttached("AtomicsOr"); 9989 return AttachDecision::Attach; 9990 } 9991 9992 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsXor() { 9993 if (!canAttachAtomicsReadWriteModify()) { 9994 return AttachDecision::NoAction; 9995 } 9996 9997 auto [objId, intPtrIndexId, numericValueId] = 9998 emitAtomicsReadWriteModifyOperands(); 9999 10000 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>(); 10001 bool forEffect = ignoresResult(); 10002 auto viewKind = ToArrayBufferViewKind(typedArray); 10003 10004 writer.atomicsXorResult(objId, intPtrIndexId, numericValueId, 10005 typedArray->type(), forEffect, viewKind); 10006 writer.returnFromIC(); 10007 10008 trackAttached("AtomicsXor"); 10009 return AttachDecision::Attach; 10010 } 10011 10012 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsLoad() { 10013 if (!JitSupportsAtomics()) { 10014 return AttachDecision::NoAction; 10015 } 10016 10017 // Need two arguments. 10018 if (args_.length() != 2) { 10019 return AttachDecision::NoAction; 10020 } 10021 10022 // Arguments: typedArray, index (number). 10023 if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) { 10024 return AttachDecision::NoAction; 10025 } 10026 if (!args_[1].isNumber()) { 10027 return AttachDecision::NoAction; 10028 } 10029 10030 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>(); 10031 if (!AtomicsMeetsPreconditions(typedArray, args_[1], AtomicAccess::Read)) { 10032 return AttachDecision::NoAction; 10033 } 10034 10035 // Initialize the input operand. 10036 Int32OperandId argcId = initializeInputOperand(); 10037 10038 // Guard callee is the `load` native function. 10039 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10040 10041 ValOperandId arg0Id = loadArgument(calleeId, ArgumentKind::Arg0); 10042 ObjOperandId objId = writer.guardToObject(arg0Id); 10043 writer.guardShapeForClass(objId, typedArray->shape()); 10044 10045 // Convert index to intPtr. 10046 ValOperandId indexId = loadArgument(calleeId, ArgumentKind::Arg1); 10047 IntPtrOperandId intPtrIndexId = 10048 guardToIntPtrIndex(args_[1], indexId, /* supportOOB = */ false); 10049 10050 auto viewKind = ToArrayBufferViewKind(typedArray); 10051 writer.atomicsLoadResult(objId, intPtrIndexId, typedArray->type(), viewKind); 10052 writer.returnFromIC(); 10053 10054 trackAttached("AtomicsLoad"); 10055 return AttachDecision::Attach; 10056 } 10057 10058 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsStore() { 10059 if (!JitSupportsAtomics()) { 10060 return AttachDecision::NoAction; 10061 } 10062 10063 // Need three arguments. 10064 if (args_.length() != 3) { 10065 return AttachDecision::NoAction; 10066 } 10067 10068 // Atomics.store() is annoying because it returns the result of converting the 10069 // value by ToInteger(), not the input value, nor the result of converting the 10070 // value by ToInt32(). It is especially annoying because almost nobody uses 10071 // the result value. 10072 // 10073 // As an expedient compromise, therefore, we inline only if the result is 10074 // obviously unused or if the argument is already Int32 and thus requires no 10075 // conversion. 10076 10077 // Arguments: typedArray, index (number), value. 10078 if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) { 10079 return AttachDecision::NoAction; 10080 } 10081 if (!args_[1].isNumber()) { 10082 return AttachDecision::NoAction; 10083 } 10084 10085 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>(); 10086 if (!AtomicsMeetsPreconditions(typedArray, args_[1], AtomicAccess::Write)) { 10087 return AttachDecision::NoAction; 10088 } 10089 10090 Scalar::Type elementType = typedArray->type(); 10091 if (!ValueCanConvertToNumeric(elementType, args_[2])) { 10092 return AttachDecision::NoAction; 10093 } 10094 10095 bool guardIsInt32 = !Scalar::isBigIntType(elementType) && !ignoresResult(); 10096 10097 if (guardIsInt32 && !args_[2].isInt32()) { 10098 return AttachDecision::NoAction; 10099 } 10100 10101 // Initialize the input operand. 10102 Int32OperandId argcId = initializeInputOperand(); 10103 10104 // Guard callee is the `store` native function. 10105 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10106 10107 ValOperandId arg0Id = loadArgument(calleeId, ArgumentKind::Arg0); 10108 ObjOperandId objId = writer.guardToObject(arg0Id); 10109 writer.guardShapeForClass(objId, typedArray->shape()); 10110 10111 // Convert index to intPtr. 10112 ValOperandId indexId = loadArgument(calleeId, ArgumentKind::Arg1); 10113 IntPtrOperandId intPtrIndexId = 10114 guardToIntPtrIndex(args_[1], indexId, /* supportOOB = */ false); 10115 10116 // Ensure value is int32 or BigInt. 10117 ValOperandId valueId = loadArgument(calleeId, ArgumentKind::Arg2); 10118 OperandId numericValueId; 10119 if (guardIsInt32) { 10120 numericValueId = writer.guardToInt32(valueId); 10121 } else { 10122 numericValueId = emitNumericGuard(valueId, args_[2], elementType); 10123 } 10124 10125 auto viewKind = ToArrayBufferViewKind(typedArray); 10126 writer.atomicsStoreResult(objId, intPtrIndexId, numericValueId, 10127 typedArray->type(), viewKind); 10128 writer.returnFromIC(); 10129 10130 trackAttached("AtomicsStore"); 10131 return AttachDecision::Attach; 10132 } 10133 10134 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsIsLockFree() { 10135 // Need one argument. 10136 if (args_.length() != 1) { 10137 return AttachDecision::NoAction; 10138 } 10139 10140 if (!args_[0].isInt32()) { 10141 return AttachDecision::NoAction; 10142 } 10143 10144 // Initialize the input operand. 10145 Int32OperandId argcId = initializeInputOperand(); 10146 10147 // Guard callee is the `isLockFree` native function. 10148 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10149 10150 // Ensure value is int32. 10151 ValOperandId valueId = loadArgument(calleeId, ArgumentKind::Arg0); 10152 Int32OperandId int32ValueId = writer.guardToInt32(valueId); 10153 10154 writer.atomicsIsLockFreeResult(int32ValueId); 10155 writer.returnFromIC(); 10156 10157 trackAttached("AtomicsIsLockFree"); 10158 return AttachDecision::Attach; 10159 } 10160 10161 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsPause() { 10162 // We don't yet support inlining when the iteration count argument is present. 10163 if (args_.length() != 0) { 10164 return AttachDecision::NoAction; 10165 } 10166 10167 // Initialize the input operand. 10168 Int32OperandId argcId = initializeInputOperand(); 10169 10170 // Guard callee is the `pause` native function. 10171 emitNativeCalleeGuard(argcId); 10172 10173 writer.atomicsPauseResult(); 10174 writer.returnFromIC(); 10175 10176 trackAttached("AtomicsPause"); 10177 return AttachDecision::Attach; 10178 } 10179 10180 AttachDecision InlinableNativeIRGenerator::tryAttachBoolean() { 10181 // Need zero or one argument. 10182 if (args_.length() > 1) { 10183 return AttachDecision::NoAction; 10184 } 10185 10186 // Initialize the input operand. 10187 Int32OperandId argcId = initializeInputOperand(); 10188 10189 // Guard callee is the 'Boolean' native function. 10190 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10191 10192 if (args_.length() == 0) { 10193 writer.loadBooleanResult(false); 10194 } else { 10195 ValOperandId valId = loadArgument(calleeId, ArgumentKind::Arg0); 10196 10197 writer.loadValueTruthyResult(valId); 10198 } 10199 10200 writer.returnFromIC(); 10201 10202 trackAttached("Boolean"); 10203 return AttachDecision::Attach; 10204 } 10205 10206 AttachDecision InlinableNativeIRGenerator::tryAttachBailout() { 10207 // Expecting no arguments. 10208 if (args_.length() != 0) { 10209 return AttachDecision::NoAction; 10210 } 10211 10212 // Initialize the input operand. 10213 Int32OperandId argcId = initializeInputOperand(); 10214 10215 // Guard callee is the 'bailout' native function. 10216 emitNativeCalleeGuard(argcId); 10217 10218 writer.bailout(); 10219 writer.loadUndefinedResult(); 10220 writer.returnFromIC(); 10221 10222 trackAttached("Bailout"); 10223 return AttachDecision::Attach; 10224 } 10225 10226 AttachDecision InlinableNativeIRGenerator::tryAttachAssertFloat32() { 10227 // Expecting two arguments. 10228 if (args_.length() != 2) { 10229 return AttachDecision::NoAction; 10230 } 10231 10232 // (Fuzzing unsafe) testing function which must be called with a constant 10233 // boolean as its second argument. 10234 bool mustBeFloat32 = args_[1].toBoolean(); 10235 10236 // Initialize the input operand. 10237 Int32OperandId argcId = initializeInputOperand(); 10238 10239 // Guard callee is the 'assertFloat32' native function. 10240 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10241 10242 ValOperandId valId = loadArgument(calleeId, ArgumentKind::Arg0); 10243 10244 writer.assertFloat32Result(valId, mustBeFloat32); 10245 writer.returnFromIC(); 10246 10247 trackAttached("AssertFloat32"); 10248 return AttachDecision::Attach; 10249 } 10250 10251 AttachDecision InlinableNativeIRGenerator::tryAttachAssertRecoveredOnBailout() { 10252 // Expecting two arguments. 10253 if (args_.length() != 2) { 10254 return AttachDecision::NoAction; 10255 } 10256 10257 // (Fuzzing unsafe) testing function which must be called with a constant 10258 // boolean as its second argument. 10259 bool mustBeRecovered = args_[1].toBoolean(); 10260 10261 // Initialize the input operand. 10262 Int32OperandId argcId = initializeInputOperand(); 10263 10264 // Guard callee is the 'assertRecoveredOnBailout' native function. 10265 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10266 10267 ValOperandId valId = loadArgument(calleeId, ArgumentKind::Arg0); 10268 10269 writer.assertRecoveredOnBailoutResult(valId, mustBeRecovered); 10270 writer.returnFromIC(); 10271 10272 trackAttached("AssertRecoveredOnBailout"); 10273 return AttachDecision::Attach; 10274 } 10275 10276 AttachDecision InlinableNativeIRGenerator::tryAttachObjectIs() { 10277 // Need two arguments. 10278 if (args_.length() != 2) { 10279 return AttachDecision::NoAction; 10280 } 10281 10282 // Initialize the input operand. 10283 Int32OperandId argcId = initializeInputOperand(); 10284 10285 // Guard callee is the `is` native function. 10286 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10287 10288 ValOperandId lhsId = loadArgument(calleeId, ArgumentKind::Arg0); 10289 ValOperandId rhsId = loadArgument(calleeId, ArgumentKind::Arg1); 10290 10291 HandleValue lhs = args_[0]; 10292 HandleValue rhs = args_[1]; 10293 10294 if (!isFirstStub()) { 10295 writer.sameValueResult(lhsId, rhsId); 10296 } else if (lhs.isNumber() && rhs.isNumber() && 10297 !(lhs.isInt32() && rhs.isInt32())) { 10298 NumberOperandId lhsNumId = writer.guardIsNumber(lhsId); 10299 NumberOperandId rhsNumId = writer.guardIsNumber(rhsId); 10300 writer.compareDoubleSameValueResult(lhsNumId, rhsNumId); 10301 } else if (!SameType(lhs, rhs)) { 10302 // Compare tags for strictly different types. 10303 ValueTagOperandId lhsTypeId = writer.loadValueTag(lhsId); 10304 ValueTagOperandId rhsTypeId = writer.loadValueTag(rhsId); 10305 writer.guardTagNotEqual(lhsTypeId, rhsTypeId); 10306 writer.loadBooleanResult(false); 10307 } else { 10308 MOZ_ASSERT(lhs.type() == rhs.type()); 10309 MOZ_ASSERT(lhs.type() != JS::ValueType::Double); 10310 10311 switch (lhs.type()) { 10312 case JS::ValueType::Int32: { 10313 Int32OperandId lhsIntId = writer.guardToInt32(lhsId); 10314 Int32OperandId rhsIntId = writer.guardToInt32(rhsId); 10315 writer.compareInt32Result(JSOp::StrictEq, lhsIntId, rhsIntId); 10316 break; 10317 } 10318 case JS::ValueType::Boolean: { 10319 Int32OperandId lhsIntId = writer.guardBooleanToInt32(lhsId); 10320 Int32OperandId rhsIntId = writer.guardBooleanToInt32(rhsId); 10321 writer.compareInt32Result(JSOp::StrictEq, lhsIntId, rhsIntId); 10322 break; 10323 } 10324 case JS::ValueType::Undefined: { 10325 writer.guardIsUndefined(lhsId); 10326 writer.guardIsUndefined(rhsId); 10327 writer.loadBooleanResult(true); 10328 break; 10329 } 10330 case JS::ValueType::Null: { 10331 writer.guardIsNull(lhsId); 10332 writer.guardIsNull(rhsId); 10333 writer.loadBooleanResult(true); 10334 break; 10335 } 10336 case JS::ValueType::String: { 10337 StringOperandId lhsStrId = writer.guardToString(lhsId); 10338 StringOperandId rhsStrId = writer.guardToString(rhsId); 10339 writer.compareStringResult(JSOp::StrictEq, lhsStrId, rhsStrId); 10340 break; 10341 } 10342 case JS::ValueType::Symbol: { 10343 SymbolOperandId lhsSymId = writer.guardToSymbol(lhsId); 10344 SymbolOperandId rhsSymId = writer.guardToSymbol(rhsId); 10345 writer.compareSymbolResult(JSOp::StrictEq, lhsSymId, rhsSymId); 10346 break; 10347 } 10348 case JS::ValueType::BigInt: { 10349 BigIntOperandId lhsBigIntId = writer.guardToBigInt(lhsId); 10350 BigIntOperandId rhsBigIntId = writer.guardToBigInt(rhsId); 10351 writer.compareBigIntResult(JSOp::StrictEq, lhsBigIntId, rhsBigIntId); 10352 break; 10353 } 10354 case JS::ValueType::Object: { 10355 ObjOperandId lhsObjId = writer.guardToObject(lhsId); 10356 ObjOperandId rhsObjId = writer.guardToObject(rhsId); 10357 writer.compareObjectResult(JSOp::StrictEq, lhsObjId, rhsObjId); 10358 break; 10359 } 10360 10361 case JS::ValueType::Double: 10362 case JS::ValueType::Magic: 10363 case JS::ValueType::PrivateGCThing: 10364 MOZ_CRASH("Unexpected type"); 10365 } 10366 } 10367 10368 writer.returnFromIC(); 10369 10370 trackAttached("ObjectIs"); 10371 return AttachDecision::Attach; 10372 } 10373 10374 AttachDecision InlinableNativeIRGenerator::tryAttachObjectIsPrototypeOf() { 10375 // Ensure |this| is an object. 10376 if (!thisval_.isObject()) { 10377 return AttachDecision::NoAction; 10378 } 10379 10380 // Need a single argument. 10381 if (args_.length() != 1) { 10382 return AttachDecision::NoAction; 10383 } 10384 10385 // Initialize the input operand. 10386 Int32OperandId argcId = initializeInputOperand(); 10387 10388 // Guard callee is the `isPrototypeOf` native function. 10389 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10390 10391 // Guard that |this| is an object. 10392 ValOperandId thisValId = loadThis(calleeId); 10393 ObjOperandId thisObjId = writer.guardToObject(thisValId); 10394 10395 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 10396 10397 writer.loadInstanceOfObjectResult(argId, thisObjId); 10398 writer.returnFromIC(); 10399 10400 trackAttached("ObjectIsPrototypeOf"); 10401 return AttachDecision::Attach; 10402 } 10403 10404 AttachDecision InlinableNativeIRGenerator::tryAttachObjectKeys() { 10405 // Only handle argc <= 1. 10406 if (args_.length() != 1) { 10407 return AttachDecision::NoAction; 10408 } 10409 10410 // Do not attach any IC if the argument is not an object. 10411 if (!args_[0].isObject()) { 10412 return AttachDecision::NoAction; 10413 } 10414 // Do not attach any IC if the argument is a Proxy. While implementation could 10415 // work with proxies the goal of this implementation is to provide an 10416 // optimization for calls of `Object.keys(obj)` where there is no side-effect, 10417 // and where the computation of the array of property name can be moved. 10418 const JSClass* clasp = args_[0].toObject().getClass(); 10419 if (clasp->isProxyObject()) { 10420 return AttachDecision::NoAction; 10421 } 10422 10423 Shape* expectedObjKeysShape = 10424 GlobalObject::getArrayShapeWithDefaultProto(cx_); 10425 if (!expectedObjKeysShape) { 10426 cx_->recoverFromOutOfMemory(); 10427 return AttachDecision::NoAction; 10428 } 10429 10430 // Generate cache IR code to attach a new inline cache which will delegate the 10431 // call to Object.keys to the native function. 10432 Int32OperandId argcId = initializeInputOperand(); 10433 10434 // Guard callee is the 'keys' native function. 10435 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10436 10437 // Implicit: Note `Object.keys` is a property of the `Object` global. The fact 10438 // that we are in this function implies that we already identify the function 10439 // as being the proper one. Thus there should not be any need to validate that 10440 // this is the proper function. (test: ion/object-keys-05) 10441 10442 // Guard `arg0` is an object. 10443 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 10444 ObjOperandId argObjId = writer.guardToObject(argId); 10445 10446 // Guard against proxies. 10447 writer.guardIsNotProxy(argObjId); 10448 10449 // Compute the keys array. 10450 writer.objectKeysResult(argObjId, expectedObjKeysShape); 10451 10452 writer.returnFromIC(); 10453 10454 trackAttached("ObjectKeys"); 10455 return AttachDecision::Attach; 10456 } 10457 10458 AttachDecision InlinableNativeIRGenerator::tryAttachObjectToString() { 10459 // Expecting no arguments. 10460 if (args_.length() != 0) { 10461 return AttachDecision::NoAction; 10462 } 10463 10464 // Ensure |this| is an object. 10465 if (!thisval_.isObject()) { 10466 return AttachDecision::NoAction; 10467 } 10468 10469 // Don't attach if the object has @@toStringTag or is a proxy. 10470 if (!ObjectClassToString(cx_, &thisval_.toObject())) { 10471 return AttachDecision::NoAction; 10472 } 10473 10474 // Initialize the input operand. 10475 Int32OperandId argcId = initializeInputOperand(); 10476 10477 // Guard callee is the 'toString' native function. 10478 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10479 10480 // Guard that |this| is an object. 10481 ValOperandId thisValId = loadThis(calleeId); 10482 ObjOperandId thisObjId = writer.guardToObject(thisValId); 10483 10484 writer.objectToStringResult(thisObjId); 10485 writer.returnFromIC(); 10486 10487 trackAttached("ObjectToString"); 10488 return AttachDecision::Attach; 10489 } 10490 10491 AttachDecision InlinableNativeIRGenerator::tryAttachBigInt() { 10492 // Need a single argument (Int32). 10493 if (args_.length() != 1 || !args_[0].isInt32()) { 10494 return AttachDecision::NoAction; 10495 } 10496 10497 // Initialize the input operand. 10498 Int32OperandId argcId = initializeInputOperand(); 10499 10500 // Guard callee is the 'BigInt' native function. 10501 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10502 10503 // Guard that the argument is an Int32. 10504 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 10505 Int32OperandId int32Id = writer.guardToInt32(argId); 10506 10507 // Convert Int32 to BigInt. 10508 IntPtrOperandId intptrId = writer.int32ToIntPtr(int32Id); 10509 writer.intPtrToBigIntResult(intptrId); 10510 writer.returnFromIC(); 10511 10512 trackAttached("BigInt"); 10513 return AttachDecision::Attach; 10514 } 10515 10516 AttachDecision InlinableNativeIRGenerator::tryAttachBigIntAsIntN() { 10517 // Need two arguments (Int32, BigInt). 10518 if (args_.length() != 2 || !args_[0].isInt32() || !args_[1].isBigInt()) { 10519 return AttachDecision::NoAction; 10520 } 10521 10522 // Negative bits throws an error. 10523 if (args_[0].toInt32() < 0) { 10524 return AttachDecision::NoAction; 10525 } 10526 10527 // Initialize the input operand. 10528 Int32OperandId argcId = initializeInputOperand(); 10529 10530 // Guard callee is the 'BigInt.asIntN' native function. 10531 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10532 10533 // Convert bits to int32. 10534 ValOperandId bitsId = loadArgument(calleeId, ArgumentKind::Arg0); 10535 Int32OperandId int32BitsId = EmitGuardToInt32Index(writer, args_[0], bitsId); 10536 10537 // Number of bits mustn't be negative. 10538 writer.guardInt32IsNonNegative(int32BitsId); 10539 10540 ValOperandId arg1Id = loadArgument(calleeId, ArgumentKind::Arg1); 10541 BigIntOperandId bigIntId = writer.guardToBigInt(arg1Id); 10542 10543 writer.bigIntAsIntNResult(int32BitsId, bigIntId); 10544 writer.returnFromIC(); 10545 10546 trackAttached("BigIntAsIntN"); 10547 return AttachDecision::Attach; 10548 } 10549 10550 AttachDecision InlinableNativeIRGenerator::tryAttachBigIntAsUintN() { 10551 // Need two arguments (Int32, BigInt). 10552 if (args_.length() != 2 || !args_[0].isInt32() || !args_[1].isBigInt()) { 10553 return AttachDecision::NoAction; 10554 } 10555 10556 // Negative bits throws an error. 10557 if (args_[0].toInt32() < 0) { 10558 return AttachDecision::NoAction; 10559 } 10560 10561 // Initialize the input operand. 10562 Int32OperandId argcId = initializeInputOperand(); 10563 10564 // Guard callee is the 'BigInt.asUintN' native function. 10565 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10566 10567 // Convert bits to int32. 10568 ValOperandId bitsId = loadArgument(calleeId, ArgumentKind::Arg0); 10569 Int32OperandId int32BitsId = EmitGuardToInt32Index(writer, args_[0], bitsId); 10570 10571 // Number of bits mustn't be negative. 10572 writer.guardInt32IsNonNegative(int32BitsId); 10573 10574 ValOperandId arg1Id = loadArgument(calleeId, ArgumentKind::Arg1); 10575 BigIntOperandId bigIntId = writer.guardToBigInt(arg1Id); 10576 10577 writer.bigIntAsUintNResult(int32BitsId, bigIntId); 10578 writer.returnFromIC(); 10579 10580 trackAttached("BigIntAsUintN"); 10581 return AttachDecision::Attach; 10582 } 10583 10584 AttachDecision InlinableNativeIRGenerator::tryAttachSetHas() { 10585 // Ensure |this| is a SetObject. 10586 if (!thisval_.isObject() || !thisval_.toObject().is<SetObject>()) { 10587 return AttachDecision::NoAction; 10588 } 10589 10590 // Need a single argument. 10591 if (args_.length() != 1) { 10592 return AttachDecision::NoAction; 10593 } 10594 10595 // Initialize the input operand. 10596 Int32OperandId argcId = initializeInputOperand(); 10597 10598 // Guard callee is the 'has' native function. 10599 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10600 10601 // Guard |this| is a SetObject. 10602 ValOperandId thisValId = loadThis(calleeId); 10603 ObjOperandId objId = writer.guardToObject(thisValId); 10604 emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Set); 10605 10606 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 10607 10608 #ifndef JS_CODEGEN_X86 10609 // Assume the hash key will likely always have the same type when attaching 10610 // the first stub. If the call is polymorphic on the hash key, attach a stub 10611 // which handles any value. 10612 if (isFirstStub()) { 10613 switch (args_[0].type()) { 10614 case ValueType::Double: 10615 case ValueType::Int32: 10616 case ValueType::Boolean: 10617 case ValueType::Undefined: 10618 case ValueType::Null: { 10619 writer.guardToNonGCThing(argId); 10620 writer.setHasNonGCThingResult(objId, argId); 10621 break; 10622 } 10623 case ValueType::String: { 10624 StringOperandId strId = writer.guardToString(argId); 10625 writer.setHasStringResult(objId, strId); 10626 break; 10627 } 10628 case ValueType::Symbol: { 10629 SymbolOperandId symId = writer.guardToSymbol(argId); 10630 writer.setHasSymbolResult(objId, symId); 10631 break; 10632 } 10633 case ValueType::BigInt: { 10634 BigIntOperandId bigIntId = writer.guardToBigInt(argId); 10635 writer.setHasBigIntResult(objId, bigIntId); 10636 break; 10637 } 10638 case ValueType::Object: { 10639 // Currently only supported on 64-bit platforms. 10640 # ifdef JS_PUNBOX64 10641 ObjOperandId valId = writer.guardToObject(argId); 10642 writer.setHasObjectResult(objId, valId); 10643 # else 10644 writer.setHasResult(objId, argId); 10645 # endif 10646 break; 10647 } 10648 10649 case ValueType::Magic: 10650 case ValueType::PrivateGCThing: 10651 MOZ_CRASH("Unexpected type"); 10652 } 10653 } else { 10654 writer.setHasResult(objId, argId); 10655 } 10656 #else 10657 // The optimized versions require too many registers on x86. 10658 writer.setHasResult(objId, argId); 10659 #endif 10660 10661 writer.returnFromIC(); 10662 10663 trackAttached("SetHas"); 10664 return AttachDecision::Attach; 10665 } 10666 10667 AttachDecision InlinableNativeIRGenerator::tryAttachSetDelete() { 10668 // Ensure |this| is a SetObject. 10669 if (!thisval_.isObject() || !thisval_.toObject().is<SetObject>()) { 10670 return AttachDecision::NoAction; 10671 } 10672 10673 // Need a single argument. 10674 if (args_.length() != 1) { 10675 return AttachDecision::NoAction; 10676 } 10677 10678 // Initialize the input operand. 10679 Int32OperandId argcId = initializeInputOperand(); 10680 10681 // Guard callee is the 'delete' native function. 10682 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10683 10684 // Guard |this| is a SetObject. 10685 ValOperandId thisValId = loadThis(calleeId); 10686 ObjOperandId objId = writer.guardToObject(thisValId); 10687 emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Set); 10688 10689 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 10690 writer.setDeleteResult(objId, argId); 10691 writer.returnFromIC(); 10692 10693 trackAttached("SetDelete"); 10694 return AttachDecision::Attach; 10695 } 10696 10697 AttachDecision InlinableNativeIRGenerator::tryAttachSetAdd() { 10698 // Ensure |this| is a SetObject. 10699 if (!thisval_.isObject() || !thisval_.toObject().is<SetObject>()) { 10700 return AttachDecision::NoAction; 10701 } 10702 10703 // Need one argument. 10704 if (args_.length() != 1) { 10705 return AttachDecision::NoAction; 10706 } 10707 10708 // Initialize the input operand. 10709 Int32OperandId argcId = initializeInputOperand(); 10710 10711 // Guard callee is the 'add' native function. 10712 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10713 10714 // Guard |this| is a SetObject. 10715 ValOperandId thisValId = loadThis(calleeId); 10716 ObjOperandId objId = writer.guardToObject(thisValId); 10717 emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Set); 10718 10719 ValOperandId keyId = loadArgument(calleeId, ArgumentKind::Arg0); 10720 writer.setAddResult(objId, keyId); 10721 writer.returnFromIC(); 10722 10723 trackAttached("SetAdd"); 10724 return AttachDecision::Attach; 10725 } 10726 10727 AttachDecision InlinableNativeIRGenerator::tryAttachSetSize() { 10728 // Ensure |this| is a SetObject. 10729 if (!thisval_.isObject() || !thisval_.toObject().is<SetObject>()) { 10730 return AttachDecision::NoAction; 10731 } 10732 10733 // Expecting no arguments. 10734 if (args_.length() != 0) { 10735 return AttachDecision::NoAction; 10736 } 10737 10738 // Initialize the input operand. 10739 Int32OperandId argcId = initializeInputOperand(); 10740 10741 // Guard callee is the 'size' native function. 10742 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10743 10744 // Guard |this| is a SetObject. 10745 ValOperandId thisValId = loadThis(calleeId); 10746 ObjOperandId objId = writer.guardToObject(thisValId); 10747 emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Set); 10748 10749 writer.setSizeResult(objId); 10750 writer.returnFromIC(); 10751 10752 trackAttached("SetSize"); 10753 return AttachDecision::Attach; 10754 } 10755 10756 AttachDecision InlinableNativeIRGenerator::tryAttachMapHas() { 10757 // Ensure |this| is a MapObject. 10758 if (!thisval_.isObject() || !thisval_.toObject().is<MapObject>()) { 10759 return AttachDecision::NoAction; 10760 } 10761 10762 // Need a single argument. 10763 if (args_.length() != 1) { 10764 return AttachDecision::NoAction; 10765 } 10766 10767 // Initialize the input operand. 10768 Int32OperandId argcId = initializeInputOperand(); 10769 10770 // Guard callee is the 'has' native function. 10771 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10772 10773 // Guard |this| is a MapObject. 10774 ValOperandId thisValId = loadThis(calleeId); 10775 ObjOperandId objId = writer.guardToObject(thisValId); 10776 emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Map); 10777 10778 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 10779 10780 #ifndef JS_CODEGEN_X86 10781 // Assume the hash key will likely always have the same type when attaching 10782 // the first stub. If the call is polymorphic on the hash key, attach a stub 10783 // which handles any value. 10784 if (isFirstStub()) { 10785 switch (args_[0].type()) { 10786 case ValueType::Double: 10787 case ValueType::Int32: 10788 case ValueType::Boolean: 10789 case ValueType::Undefined: 10790 case ValueType::Null: { 10791 writer.guardToNonGCThing(argId); 10792 writer.mapHasNonGCThingResult(objId, argId); 10793 break; 10794 } 10795 case ValueType::String: { 10796 StringOperandId strId = writer.guardToString(argId); 10797 writer.mapHasStringResult(objId, strId); 10798 break; 10799 } 10800 case ValueType::Symbol: { 10801 SymbolOperandId symId = writer.guardToSymbol(argId); 10802 writer.mapHasSymbolResult(objId, symId); 10803 break; 10804 } 10805 case ValueType::BigInt: { 10806 BigIntOperandId bigIntId = writer.guardToBigInt(argId); 10807 writer.mapHasBigIntResult(objId, bigIntId); 10808 break; 10809 } 10810 case ValueType::Object: { 10811 // Currently only supported on 64-bit platforms. 10812 # ifdef JS_PUNBOX64 10813 ObjOperandId valId = writer.guardToObject(argId); 10814 writer.mapHasObjectResult(objId, valId); 10815 # else 10816 writer.mapHasResult(objId, argId); 10817 # endif 10818 break; 10819 } 10820 10821 case ValueType::Magic: 10822 case ValueType::PrivateGCThing: 10823 MOZ_CRASH("Unexpected type"); 10824 } 10825 } else { 10826 writer.mapHasResult(objId, argId); 10827 } 10828 #else 10829 // The optimized versions require too many registers on x86. 10830 writer.mapHasResult(objId, argId); 10831 #endif 10832 10833 writer.returnFromIC(); 10834 10835 trackAttached("MapHas"); 10836 return AttachDecision::Attach; 10837 } 10838 10839 AttachDecision InlinableNativeIRGenerator::tryAttachMapGet() { 10840 // Ensure |this| is a MapObject. 10841 if (!thisval_.isObject() || !thisval_.toObject().is<MapObject>()) { 10842 return AttachDecision::NoAction; 10843 } 10844 10845 // Need a single argument. 10846 if (args_.length() != 1) { 10847 return AttachDecision::NoAction; 10848 } 10849 10850 // Initialize the input operand. 10851 Int32OperandId argcId = initializeInputOperand(); 10852 10853 // Guard callee is the 'get' native function. 10854 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10855 10856 // Guard |this| is a MapObject. 10857 ValOperandId thisValId = loadThis(calleeId); 10858 ObjOperandId objId = writer.guardToObject(thisValId); 10859 emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Map); 10860 10861 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 10862 10863 #ifndef JS_CODEGEN_X86 10864 // Assume the hash key will likely always have the same type when attaching 10865 // the first stub. If the call is polymorphic on the hash key, attach a stub 10866 // which handles any value. 10867 if (isFirstStub()) { 10868 switch (args_[0].type()) { 10869 case ValueType::Double: 10870 case ValueType::Int32: 10871 case ValueType::Boolean: 10872 case ValueType::Undefined: 10873 case ValueType::Null: { 10874 writer.guardToNonGCThing(argId); 10875 writer.mapGetNonGCThingResult(objId, argId); 10876 break; 10877 } 10878 case ValueType::String: { 10879 StringOperandId strId = writer.guardToString(argId); 10880 writer.mapGetStringResult(objId, strId); 10881 break; 10882 } 10883 case ValueType::Symbol: { 10884 SymbolOperandId symId = writer.guardToSymbol(argId); 10885 writer.mapGetSymbolResult(objId, symId); 10886 break; 10887 } 10888 case ValueType::BigInt: { 10889 BigIntOperandId bigIntId = writer.guardToBigInt(argId); 10890 writer.mapGetBigIntResult(objId, bigIntId); 10891 break; 10892 } 10893 case ValueType::Object: { 10894 // Currently only supported on 64-bit platforms. 10895 # ifdef JS_PUNBOX64 10896 ObjOperandId valId = writer.guardToObject(argId); 10897 writer.mapGetObjectResult(objId, valId); 10898 # else 10899 writer.mapGetResult(objId, argId); 10900 # endif 10901 break; 10902 } 10903 10904 case ValueType::Magic: 10905 case ValueType::PrivateGCThing: 10906 MOZ_CRASH("Unexpected type"); 10907 } 10908 } else { 10909 writer.mapGetResult(objId, argId); 10910 } 10911 #else 10912 // The optimized versions require too many registers on x86. 10913 writer.mapGetResult(objId, argId); 10914 #endif 10915 10916 writer.returnFromIC(); 10917 10918 trackAttached("MapGet"); 10919 return AttachDecision::Attach; 10920 } 10921 10922 AttachDecision InlinableNativeIRGenerator::tryAttachMapDelete() { 10923 // Ensure |this| is a MapObject. 10924 if (!thisval_.isObject() || !thisval_.toObject().is<MapObject>()) { 10925 return AttachDecision::NoAction; 10926 } 10927 10928 // Need a single argument. 10929 if (args_.length() != 1) { 10930 return AttachDecision::NoAction; 10931 } 10932 10933 // Initialize the input operand. 10934 Int32OperandId argcId = initializeInputOperand(); 10935 10936 // Guard callee is the 'delete' native function. 10937 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10938 10939 // Guard |this| is a MapObject. 10940 ValOperandId thisValId = loadThis(calleeId); 10941 ObjOperandId objId = writer.guardToObject(thisValId); 10942 emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Map); 10943 10944 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 10945 writer.mapDeleteResult(objId, argId); 10946 writer.returnFromIC(); 10947 10948 trackAttached("MapDelete"); 10949 return AttachDecision::Attach; 10950 } 10951 10952 AttachDecision InlinableNativeIRGenerator::tryAttachMapSet() { 10953 #ifdef JS_CODEGEN_X86 10954 // 32-bit x86 does not have enough registers for the AutoCallVM output, the 10955 // MapObject*, and two Values. 10956 return AttachDecision::NoAction; 10957 #endif 10958 10959 // Ensure |this| is a MapObject. 10960 if (!thisval_.isObject() || !thisval_.toObject().is<MapObject>()) { 10961 return AttachDecision::NoAction; 10962 } 10963 10964 // Need two arguments. 10965 if (args_.length() != 2) { 10966 return AttachDecision::NoAction; 10967 } 10968 10969 // Initialize the input operand. 10970 Int32OperandId argcId = initializeInputOperand(); 10971 10972 // Guard callee is the 'set' native function. 10973 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 10974 10975 // Guard |this| is a MapObject. 10976 ValOperandId thisValId = loadThis(calleeId); 10977 ObjOperandId objId = writer.guardToObject(thisValId); 10978 emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Map); 10979 10980 ValOperandId keyId = loadArgument(calleeId, ArgumentKind::Arg0); 10981 ValOperandId valId = loadArgument(calleeId, ArgumentKind::Arg1); 10982 writer.mapSetResult(objId, keyId, valId); 10983 writer.returnFromIC(); 10984 10985 trackAttached("MapSet"); 10986 return AttachDecision::Attach; 10987 } 10988 10989 AttachDecision InlinableNativeIRGenerator::tryAttachMapSize() { 10990 // Ensure |this| is a MapObject. 10991 if (!thisval_.isObject() || !thisval_.toObject().is<MapObject>()) { 10992 return AttachDecision::NoAction; 10993 } 10994 10995 // Expecting no arguments. 10996 if (args_.length() != 0) { 10997 return AttachDecision::NoAction; 10998 } 10999 11000 // Initialize the input operand. 11001 Int32OperandId argcId = initializeInputOperand(); 11002 11003 // Guard callee is the 'size' native function. 11004 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 11005 11006 // Guard |this| is a MapObject. 11007 ValOperandId thisValId = loadThis(calleeId); 11008 ObjOperandId objId = writer.guardToObject(thisValId); 11009 emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Map); 11010 11011 writer.mapSizeResult(objId); 11012 writer.returnFromIC(); 11013 11014 trackAttached("MapSize"); 11015 return AttachDecision::Attach; 11016 } 11017 11018 AttachDecision InlinableNativeIRGenerator::tryAttachWeakMapGet() { 11019 // Ensure |this| is a WeakMapObject. 11020 if (!thisval_.isObject() || !thisval_.toObject().is<WeakMapObject>()) { 11021 return AttachDecision::NoAction; 11022 } 11023 11024 // Need a single object argument. 11025 if (args_.length() != 1 || !args_[0].isObject()) { 11026 return AttachDecision::NoAction; 11027 } 11028 11029 // Initialize the input operand. 11030 Int32OperandId argcId = initializeInputOperand(); 11031 11032 // Guard callee is the 'get' native function. 11033 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 11034 11035 // Guard |this| is a WeakMapObject. 11036 ValOperandId thisValId = loadThis(calleeId); 11037 ObjOperandId objId = writer.guardToObject(thisValId); 11038 emitOptimisticClassGuard(objId, &thisval_.toObject(), 11039 GuardClassKind::WeakMap); 11040 11041 // Guard the argument is an object. 11042 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 11043 ObjOperandId objArgId = writer.guardToObject(argId); 11044 11045 writer.weakMapGetObjectResult(objId, objArgId); 11046 writer.returnFromIC(); 11047 11048 trackAttached("WeakMapGet"); 11049 return AttachDecision::Attach; 11050 } 11051 11052 AttachDecision InlinableNativeIRGenerator::tryAttachWeakMapHas() { 11053 // Ensure |this| is a WeakMapObject. 11054 if (!thisval_.isObject() || !thisval_.toObject().is<WeakMapObject>()) { 11055 return AttachDecision::NoAction; 11056 } 11057 11058 // Need a single object argument. 11059 if (args_.length() != 1 || !args_[0].isObject()) { 11060 return AttachDecision::NoAction; 11061 } 11062 11063 // Initialize the input operand. 11064 Int32OperandId argcId = initializeInputOperand(); 11065 11066 // Guard callee is the 'has' native function. 11067 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 11068 11069 // Guard |this| is a WeakMapObject. 11070 ValOperandId thisValId = loadThis(calleeId); 11071 ObjOperandId objId = writer.guardToObject(thisValId); 11072 emitOptimisticClassGuard(objId, &thisval_.toObject(), 11073 GuardClassKind::WeakMap); 11074 11075 // Guard the argument is an object. 11076 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 11077 ObjOperandId objArgId = writer.guardToObject(argId); 11078 11079 writer.weakMapHasObjectResult(objId, objArgId); 11080 writer.returnFromIC(); 11081 11082 trackAttached("WeakMapHas"); 11083 return AttachDecision::Attach; 11084 } 11085 11086 AttachDecision InlinableNativeIRGenerator::tryAttachWeakSetHas() { 11087 // Ensure |this| is a WeakSetObject. 11088 if (!thisval_.isObject() || !thisval_.toObject().is<WeakSetObject>()) { 11089 return AttachDecision::NoAction; 11090 } 11091 11092 // Need a single object argument. 11093 if (args_.length() != 1 || !args_[0].isObject()) { 11094 return AttachDecision::NoAction; 11095 } 11096 11097 // Initialize the input operand. 11098 Int32OperandId argcId = initializeInputOperand(); 11099 11100 // Guard callee is the 'has' native function. 11101 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 11102 11103 // Guard |this| is a WeakSetObject. 11104 ValOperandId thisValId = loadThis(calleeId); 11105 ObjOperandId objId = writer.guardToObject(thisValId); 11106 emitOptimisticClassGuard(objId, &thisval_.toObject(), 11107 GuardClassKind::WeakSet); 11108 11109 // Guard the argument is an object. 11110 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 11111 ObjOperandId objArgId = writer.guardToObject(argId); 11112 11113 writer.weakSetHasObjectResult(objId, objArgId); 11114 writer.returnFromIC(); 11115 11116 trackAttached("WeakSetHas"); 11117 return AttachDecision::Attach; 11118 } 11119 11120 AttachDecision InlinableNativeIRGenerator::tryAttachDateGetTime() { 11121 // Ensure |this| is a DateObject. 11122 if (!thisval_.isObject() || !thisval_.toObject().is<DateObject>()) { 11123 return AttachDecision::NoAction; 11124 } 11125 11126 // Expecting no arguments. 11127 if (args_.length() != 0) { 11128 return AttachDecision::NoAction; 11129 } 11130 11131 // Initialize the input operand. 11132 Int32OperandId argcId = initializeInputOperand(); 11133 11134 // Guard callee is the 'getTime' (or 'valueOf') native function. 11135 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 11136 11137 // Guard |this| is a DateObject. 11138 ValOperandId thisValId = loadThis(calleeId); 11139 ObjOperandId objId = writer.guardToObject(thisValId); 11140 emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Date); 11141 11142 writer.loadFixedSlotTypedResult(objId, DateObject::offsetOfUTCTimeSlot(), 11143 ValueType::Double); 11144 11145 writer.returnFromIC(); 11146 11147 trackAttached("DateGetTime"); 11148 return AttachDecision::Attach; 11149 } 11150 11151 AttachDecision InlinableNativeIRGenerator::tryAttachDateGet( 11152 DateComponent component) { 11153 // Ensure |this| is a DateObject. 11154 if (!thisval_.isObject() || !thisval_.toObject().is<DateObject>()) { 11155 return AttachDecision::NoAction; 11156 } 11157 11158 // Expecting no arguments. 11159 if (args_.length() != 0) { 11160 return AttachDecision::NoAction; 11161 } 11162 11163 // Can't check DateTime cache when not using the default time zone. 11164 if (cx_->realm()->behaviors().timeZoneOverride()) { 11165 return AttachDecision::NoAction; 11166 } 11167 11168 // Initialize the input operand. 11169 Int32OperandId argcId = initializeInputOperand(); 11170 11171 // Guard callee is the Date native function. 11172 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 11173 11174 // Guard |this| is a DateObject. 11175 ValOperandId thisValId = loadThis(calleeId); 11176 ObjOperandId objId = writer.guardToObject(thisValId); 11177 emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Date); 11178 11179 // Fill in the local time slots. 11180 writer.dateFillLocalTimeSlots(objId); 11181 11182 switch (component) { 11183 case DateComponent::FullYear: 11184 writer.loadFixedSlotResult(objId, DateObject::offsetOfLocalYearSlot()); 11185 break; 11186 case DateComponent::Month: 11187 writer.loadFixedSlotResult(objId, DateObject::offsetOfLocalMonthSlot()); 11188 break; 11189 case DateComponent::Date: 11190 writer.loadFixedSlotResult(objId, DateObject::offsetOfLocalDateSlot()); 11191 break; 11192 case DateComponent::Day: 11193 writer.loadFixedSlotResult(objId, DateObject::offsetOfLocalDaySlot()); 11194 break; 11195 case DateComponent::Hours: { 11196 ValOperandId secondsIntoYearValId = writer.loadFixedSlot( 11197 objId, DateObject::offsetOfLocalSecondsIntoYearSlot()); 11198 writer.dateHoursFromSecondsIntoYearResult(secondsIntoYearValId); 11199 break; 11200 } 11201 case DateComponent::Minutes: { 11202 ValOperandId secondsIntoYearValId = writer.loadFixedSlot( 11203 objId, DateObject::offsetOfLocalSecondsIntoYearSlot()); 11204 writer.dateMinutesFromSecondsIntoYearResult(secondsIntoYearValId); 11205 break; 11206 } 11207 case DateComponent::Seconds: { 11208 ValOperandId secondsIntoYearValId = writer.loadFixedSlot( 11209 objId, DateObject::offsetOfLocalSecondsIntoYearSlot()); 11210 writer.dateSecondsFromSecondsIntoYearResult(secondsIntoYearValId); 11211 break; 11212 } 11213 } 11214 11215 writer.returnFromIC(); 11216 11217 switch (component) { 11218 case DateComponent::FullYear: 11219 trackAttached("DateGetFullYear"); 11220 break; 11221 case DateComponent::Month: 11222 trackAttached("DateGetMonth"); 11223 break; 11224 case DateComponent::Date: 11225 trackAttached("DateGetDate"); 11226 break; 11227 case DateComponent::Day: 11228 trackAttached("DateGetDay"); 11229 break; 11230 case DateComponent::Hours: 11231 trackAttached("DateGetHours"); 11232 break; 11233 case DateComponent::Minutes: 11234 trackAttached("DateGetMinutes"); 11235 break; 11236 case DateComponent::Seconds: 11237 trackAttached("DateGetSeconds"); 11238 break; 11239 } 11240 return AttachDecision::Attach; 11241 } 11242 11243 AttachDecision CallIRGenerator::tryAttachFunCall(HandleFunction callee) { 11244 MOZ_ASSERT(callee->isNativeWithoutJitEntry()); 11245 11246 if (callee->native() != fun_call) { 11247 return AttachDecision::NoAction; 11248 } 11249 11250 if (!thisval_.isObject() || !thisval_.toObject().is<JSFunction>()) { 11251 return AttachDecision::NoAction; 11252 } 11253 RootedFunction target(cx_, &thisval_.toObject().as<JSFunction>()); 11254 11255 bool isScripted = target->hasJitEntry(); 11256 MOZ_ASSERT_IF(!isScripted, target->isNativeWithoutJitEntry()); 11257 11258 if (target->isClassConstructor()) { 11259 return AttachDecision::NoAction; 11260 } 11261 11262 CallFlags targetFlags(CallFlags::FunCall); 11263 if (mode_ == ICState::Mode::Specialized) { 11264 if (cx_->realm() == target->realm()) { 11265 targetFlags.setIsSameRealm(); 11266 } 11267 } 11268 11269 if (mode_ == ICState::Mode::Specialized && !isScripted) { 11270 HandleValue newTarget = NullHandleValue; 11271 HandleValue thisValue = argc_ > 0 ? args_[0] : UndefinedHandleValue; 11272 HandleValueArray args = 11273 argc_ > 0 ? HandleValueArray::subarray(args_, 1, args_.length() - 1) 11274 : HandleValueArray::empty(); 11275 11276 // Check for specific native-function optimizations. 11277 InlinableNativeIRGenerator nativeGen(*this, callee, target, newTarget, 11278 thisValue, args, targetFlags); 11279 TRY_ATTACH(nativeGen.tryAttachStub()); 11280 } 11281 11282 Int32OperandId argcId(writer.setInputOperandId(0)); 11283 ObjOperandId thisObjId = emitFunCallGuard(argcId); 11284 11285 if (mode_ == ICState::Mode::Specialized) { 11286 // Ensure that |this| is the expected target function. 11287 emitCalleeGuard(thisObjId, target); 11288 11289 if (isScripted) { 11290 writer.callScriptedFunction(thisObjId, argcId, targetFlags, 11291 ClampFixedArgc(argc_)); 11292 } else { 11293 writer.callNativeFunction(thisObjId, argcId, jsop(), target, targetFlags, 11294 ClampFixedArgc(argc_)); 11295 } 11296 } else { 11297 // Guard that |this| is a function. 11298 writer.guardClass(thisObjId, GuardClassKind::JSFunction); 11299 11300 // Guard that function is not a class constructor. 11301 writer.guardNotClassConstructor(thisObjId); 11302 11303 if (isScripted) { 11304 writer.guardFunctionHasJitEntry(thisObjId); 11305 writer.callScriptedFunction(thisObjId, argcId, targetFlags, 11306 ClampFixedArgc(argc_)); 11307 } else { 11308 writer.guardFunctionHasNoJitEntry(thisObjId); 11309 writer.callAnyNativeFunction(thisObjId, argcId, targetFlags, 11310 ClampFixedArgc(argc_)); 11311 } 11312 } 11313 11314 writer.returnFromIC(); 11315 11316 if (isScripted) { 11317 trackAttached("Scripted fun_call"); 11318 } else { 11319 trackAttached("Native fun_call"); 11320 } 11321 11322 return AttachDecision::Attach; 11323 } 11324 11325 AttachDecision InlinableNativeIRGenerator::tryAttachTypedArrayFill() { 11326 // Expected arguments: value, optional start, optional end. 11327 if (args_.length() < 1 || args_.length() > 3) { 11328 return AttachDecision::NoAction; 11329 } 11330 11331 if (!isFirstStub()) { 11332 // Attach only once to prevent slowdowns for polymorphic calls. 11333 return AttachDecision::NoAction; 11334 } 11335 11336 // Ensure |this| is a TypedArrayObject. 11337 if (!thisval_.isObject() || !thisval_.toObject().is<TypedArrayObject>()) { 11338 return AttachDecision::NoAction; 11339 } 11340 11341 // Both arguments must be valid indices. 11342 int64_t unusedIndex; 11343 if (args_.length() > 1 && !ValueIsInt64Index(args_[1], &unusedIndex)) { 11344 return AttachDecision::NoAction; 11345 } 11346 if (args_.length() > 2 && !ValueIsInt64Index(args_[2], &unusedIndex)) { 11347 return AttachDecision::NoAction; 11348 } 11349 11350 auto* tarr = &thisval_.toObject().as<TypedArrayObject>(); 11351 auto elementType = tarr->type(); 11352 11353 // Detached buffers throw. 11354 if (tarr->hasDetachedBuffer()) { 11355 return AttachDecision::NoAction; 11356 } 11357 11358 // Must not be an immutable typed array. 11359 if (tarr->is<ImmutableTypedArrayObject>()) { 11360 return AttachDecision::NoAction; 11361 } 11362 11363 // Resizable typed arrays not yet supported. 11364 if (tarr->is<ResizableTypedArrayObject>()) { 11365 return AttachDecision::NoAction; 11366 } 11367 11368 // Don't attach if the input type doesn't match the guard added below. 11369 if (!ValueCanConvertToNumeric(elementType, args_[0])) { 11370 return AttachDecision::NoAction; 11371 } 11372 11373 // Initialize the input operand. 11374 Int32OperandId argcId = initializeInputOperand(); 11375 11376 // Guard callee is the `fill` native function. 11377 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 11378 11379 // Guard this is an object. 11380 ValOperandId thisValId = loadThis(calleeId); 11381 ObjOperandId objId = writer.guardToObject(thisValId); 11382 11383 // Shape guard to check class. 11384 writer.guardShapeForClass(objId, tarr->shape()); 11385 11386 // Guard the array buffer is not detached. 11387 writer.guardHasAttachedArrayBuffer(objId); 11388 11389 ValOperandId fillValId = loadArgument(calleeId, ArgumentKind::Arg0); 11390 OperandId fillNumericId = emitNumericGuard(fillValId, args_[0], elementType); 11391 11392 // Convert |start| to IntPtr. 11393 IntPtrOperandId intPtrStartId; 11394 if (args_.length() > 1) { 11395 ValOperandId startId = loadArgument(calleeId, ArgumentKind::Arg1); 11396 intPtrStartId = 11397 guardToIntPtrIndex(args_[1], startId, /* supportOOB = */ false); 11398 } else { 11399 // Absent first argument defaults to zero. 11400 intPtrStartId = writer.loadInt32AsIntPtrConstant(0); 11401 } 11402 11403 // Convert |end| to IntPtr. 11404 IntPtrOperandId intPtrEndId; 11405 if (args_.length() > 2) { 11406 ValOperandId endId = loadArgument(calleeId, ArgumentKind::Arg2); 11407 intPtrEndId = guardToIntPtrIndex(args_[2], endId, /* supportOOB = */ false); 11408 } else { 11409 // Absent second argument defaults to the typed array length. 11410 intPtrEndId = writer.loadArrayBufferViewLength(objId); 11411 } 11412 11413 writer.typedArrayFillResult(objId, fillNumericId, intPtrStartId, intPtrEndId, 11414 elementType); 11415 writer.returnFromIC(); 11416 11417 trackAttached("TypedArrayFill"); 11418 return AttachDecision::Attach; 11419 } 11420 11421 AttachDecision InlinableNativeIRGenerator::tryAttachTypedArraySet() { 11422 // Expected arguments: source (typed array), optional offset (int32). 11423 if (args_.length() < 1 || args_.length() > 2) { 11424 return AttachDecision::NoAction; 11425 } 11426 11427 if (!isFirstStub()) { 11428 // Attach only once to prevent slowdowns for polymorphic calls. 11429 return AttachDecision::NoAction; 11430 } 11431 11432 // Ensure |this| is a TypedArrayObject. 11433 if (!thisval_.isObject() || !thisval_.toObject().is<TypedArrayObject>()) { 11434 return AttachDecision::NoAction; 11435 } 11436 11437 // Ensure first argument is a TypedArrayObject. 11438 if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) { 11439 return AttachDecision::NoAction; 11440 } 11441 11442 // Ensure optional second argument is a non-negative index. 11443 uint64_t targetOffset = 0; 11444 if (args_.length() > 1) { 11445 int64_t offsetIndex; 11446 if (!ValueIsInt64Index(args_[1], &offsetIndex) || offsetIndex < 0) { 11447 return AttachDecision::NoAction; 11448 } 11449 targetOffset = uint64_t(offsetIndex); 11450 } 11451 11452 auto* tarr = &thisval_.toObject().as<TypedArrayObject>(); 11453 auto* source = &args_[0].toObject().as<TypedArrayObject>(); 11454 11455 // Detached buffers throw. 11456 if (tarr->hasDetachedBuffer() || source->hasDetachedBuffer()) { 11457 return AttachDecision::NoAction; 11458 } 11459 11460 // Target must not be an immutable typed array. 11461 if (tarr->is<ImmutableTypedArrayObject>()) { 11462 return AttachDecision::NoAction; 11463 } 11464 11465 // Typed array contents must be compatible. 11466 if (Scalar::isBigIntType(tarr->type()) != 11467 Scalar::isBigIntType(source->type())) { 11468 return AttachDecision::NoAction; 11469 } 11470 11471 // `set()` throws if `sourceLength + targetOffset > targetLength`. 11472 size_t targetLength = tarr->length().valueOr(0); 11473 size_t sourceLength = source->length().valueOr(0); 11474 if (targetOffset > targetLength || 11475 sourceLength > targetLength - targetOffset) { 11476 return AttachDecision::NoAction; 11477 } 11478 11479 // Resizable typed arrays not yet supported. 11480 if (tarr->is<ResizableTypedArrayObject>() || 11481 source->is<ResizableTypedArrayObject>()) { 11482 return AttachDecision::NoAction; 11483 } 11484 11485 // Infallible operation if bit-wise copying is possible. 11486 bool canUseBitwiseCopy = CanUseBitwiseCopy(tarr->type(), source->type()); 11487 11488 // Initialize the input operand. 11489 Int32OperandId argcId = initializeInputOperand(); 11490 11491 // Guard callee is the `set` native function. 11492 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 11493 11494 // Guard this is an object. 11495 ValOperandId thisValId = loadThis(calleeId); 11496 ObjOperandId objId = writer.guardToObject(thisValId); 11497 11498 // Shape guard to check class. 11499 writer.guardShapeForClass(objId, tarr->shape()); 11500 11501 // Guard the array buffer is not detached. 11502 writer.guardHasAttachedArrayBuffer(objId); 11503 11504 // Guard first argument is an object. 11505 ValOperandId sourceId = loadArgument(calleeId, ArgumentKind::Arg0); 11506 ObjOperandId sourceObjId = writer.guardToObject(sourceId); 11507 11508 // Shape guard to check class of first argument. 11509 writer.guardShapeForClass(sourceObjId, source->shape()); 11510 11511 // Guard the source is not detached. (Immutable typed arrays can't get 11512 // detached.) 11513 if (!source->is<ImmutableTypedArrayObject>()) { 11514 writer.guardHasAttachedArrayBuffer(sourceObjId); 11515 } 11516 11517 // Convert offset to IntPtr. 11518 IntPtrOperandId intPtrOffsetId; 11519 if (args_.length() > 1) { 11520 ValOperandId offsetId = loadArgument(calleeId, ArgumentKind::Arg1); 11521 intPtrOffsetId = 11522 guardToIntPtrIndex(args_[1], offsetId, /* supportOOB = */ false); 11523 writer.guardIntPtrIsNonNegative(intPtrOffsetId); 11524 } else { 11525 // Absent first argument defaults to zero. 11526 intPtrOffsetId = writer.loadInt32AsIntPtrConstant(0); 11527 } 11528 11529 writer.typedArraySetResult(objId, sourceObjId, intPtrOffsetId, 11530 canUseBitwiseCopy); 11531 writer.returnFromIC(); 11532 11533 trackAttached("TypedArraySet"); 11534 return AttachDecision::Attach; 11535 } 11536 11537 AttachDecision InlinableNativeIRGenerator::tryAttachTypedArraySubarray() { 11538 // Only handle argc <= 2. 11539 if (args_.length() > 2) { 11540 return AttachDecision::NoAction; 11541 } 11542 11543 if (!isFirstStub()) { 11544 // Attach only once to prevent slowdowns for polymorphic calls. 11545 return AttachDecision::NoAction; 11546 } 11547 11548 // Ensure |this| is a TypedArrayObject. 11549 if (!thisval_.isObject() || !thisval_.toObject().is<TypedArrayObject>()) { 11550 return AttachDecision::NoAction; 11551 } 11552 11553 // Both arguments must be valid indices. 11554 int64_t unusedIndex; 11555 if (args_.length() > 0 && !ValueIsInt64Index(args_[0], &unusedIndex)) { 11556 return AttachDecision::NoAction; 11557 } 11558 if (args_.length() > 1 && !ValueIsInt64Index(args_[1], &unusedIndex)) { 11559 return AttachDecision::NoAction; 11560 } 11561 11562 Rooted<TypedArrayObject*> tarr(cx_, 11563 &thisval_.toObject().as<TypedArrayObject>()); 11564 11565 // Detached buffer throws. 11566 if (tarr->hasDetachedBuffer()) { 11567 return AttachDecision::NoAction; 11568 } 11569 11570 // Resizable typed arrays not yet supported. 11571 if (tarr->is<ResizableTypedArrayObject>()) { 11572 return AttachDecision::NoAction; 11573 } 11574 11575 // TypedArray species fuse must still be intact. 11576 if (!cx_->realm()->realmFuses.optimizeTypedArraySpeciesFuse.intact()) { 11577 return AttachDecision::NoAction; 11578 } 11579 11580 // Ensure |tarr|'s prototype is the actual concrete TypedArray.prototype. 11581 auto protoKey = StandardProtoKeyOrNull(tarr); 11582 auto* proto = cx_->global()->maybeGetPrototype(protoKey); 11583 if (!proto || tarr->staticPrototype() != proto) { 11584 return AttachDecision::NoAction; 11585 } 11586 11587 // Ensure no own "constructor" property. 11588 if (tarr->containsPure(cx_->names().constructor)) { 11589 return AttachDecision::NoAction; 11590 } 11591 11592 Rooted<TypedArrayObject*> templateObj( 11593 cx_, TypedArrayObject::GetTemplateObjectForBufferView(cx_, tarr)); 11594 if (!templateObj) { 11595 cx_->recoverFromOutOfMemory(); 11596 return AttachDecision::NoAction; 11597 } 11598 11599 // Initialize the input operand. 11600 Int32OperandId argcId = initializeInputOperand(); 11601 11602 // Guard callee is the `subarray` native function. 11603 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 11604 11605 // Guard this is an object. 11606 ValOperandId thisValId = loadThis(calleeId); 11607 ObjOperandId objId = writer.guardToObject(thisValId); 11608 11609 // Shape guard to check class and prototype, and to ensure no own 11610 // "constructor" property is present. 11611 writer.guardShape(objId, tarr->shape()); 11612 11613 // Guard the array buffer is not detached. (Immutable typed arrays can't get 11614 // detached.) 11615 if (!tarr->is<ImmutableTypedArrayObject>()) { 11616 writer.guardHasAttachedArrayBuffer(objId); 11617 } 11618 11619 // Guard the fuse is intact. 11620 writer.guardFuse(RealmFuses::FuseIndex::OptimizeTypedArraySpeciesFuse); 11621 11622 // Convert |start| to IntPtr. 11623 IntPtrOperandId intPtrStartId; 11624 if (args_.length() > 0) { 11625 ValOperandId startId = loadArgument(calleeId, ArgumentKind::Arg0); 11626 intPtrStartId = 11627 guardToIntPtrIndex(args_[0], startId, /* supportOOB = */ false); 11628 } else { 11629 // Absent first argument defaults to zero. 11630 intPtrStartId = writer.loadInt32AsIntPtrConstant(0); 11631 } 11632 11633 // Convert |end| to IntPtr. 11634 IntPtrOperandId intPtrEndId; 11635 if (args_.length() > 1) { 11636 ValOperandId endId = loadArgument(calleeId, ArgumentKind::Arg1); 11637 intPtrEndId = guardToIntPtrIndex(args_[1], endId, /* supportOOB = */ false); 11638 } else { 11639 // Absent second argument defaults to the typed array length. 11640 intPtrEndId = writer.loadArrayBufferViewLength(objId); 11641 } 11642 11643 writer.typedArraySubarrayResult(templateObj, objId, intPtrStartId, 11644 intPtrEndId); 11645 writer.returnFromIC(); 11646 11647 trackAttached("TypedArraySubarray"); 11648 return AttachDecision::Attach; 11649 } 11650 11651 AttachDecision InlinableNativeIRGenerator::tryAttachTypedArrayLength() { 11652 // Expecting no arguments. 11653 if (args_.length() != 0) { 11654 return AttachDecision::NoAction; 11655 } 11656 11657 // Ensure |this| is a TypedArrayObject. 11658 if (!thisval_.isObject() || !thisval_.toObject().is<TypedArrayObject>()) { 11659 return AttachDecision::NoAction; 11660 } 11661 11662 auto* tarr = &thisval_.toObject().as<TypedArrayObject>(); 11663 11664 // Initialize the input operand. 11665 Int32OperandId argcId = initializeInputOperand(); 11666 11667 // Guard callee is the 'length' native function. 11668 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 11669 11670 // Guard |this| is a TypedArrayObject. 11671 ValOperandId thisValId = loadThis(calleeId); 11672 ObjOperandId objId = writer.guardToObject(thisValId); 11673 writer.guardShapeForClass(objId, tarr->shape()); 11674 11675 size_t length = tarr->length().valueOr(0); 11676 if (!tarr->is<ResizableTypedArrayObject>()) { 11677 if (length <= INT32_MAX) { 11678 writer.loadArrayBufferViewLengthInt32Result(objId); 11679 } else { 11680 writer.loadArrayBufferViewLengthDoubleResult(objId); 11681 } 11682 } else { 11683 if (length <= INT32_MAX) { 11684 writer.resizableTypedArrayLengthInt32Result(objId); 11685 } else { 11686 writer.resizableTypedArrayLengthDoubleResult(objId); 11687 } 11688 } 11689 11690 writer.returnFromIC(); 11691 11692 trackAttached("TypedArrayLength"); 11693 return AttachDecision::Attach; 11694 } 11695 11696 AttachDecision InlinableNativeIRGenerator::tryAttachTypedArrayByteLength() { 11697 // Expecting no arguments. 11698 if (args_.length() != 0) { 11699 return AttachDecision::NoAction; 11700 } 11701 11702 // Ensure |this| is a TypedArrayObject. 11703 if (!thisval_.isObject() || !thisval_.toObject().is<TypedArrayObject>()) { 11704 return AttachDecision::NoAction; 11705 } 11706 11707 auto* tarr = &thisval_.toObject().as<TypedArrayObject>(); 11708 11709 // Initialize the input operand. 11710 Int32OperandId argcId = initializeInputOperand(); 11711 11712 // Guard callee is the 'byteLength' native function. 11713 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 11714 11715 // Guard |this| is a TypedArrayObject. 11716 ValOperandId thisValId = loadThis(calleeId); 11717 ObjOperandId objId = writer.guardToObject(thisValId); 11718 writer.guardShapeForClass(objId, tarr->shape()); 11719 11720 size_t byteLength = tarr->byteLength().valueOr(0); 11721 if (!tarr->is<ResizableTypedArrayObject>()) { 11722 if (byteLength <= INT32_MAX) { 11723 writer.typedArrayByteLengthInt32Result(objId); 11724 } else { 11725 writer.typedArrayByteLengthDoubleResult(objId); 11726 } 11727 } else { 11728 if (byteLength <= INT32_MAX) { 11729 writer.resizableTypedArrayByteLengthInt32Result(objId); 11730 } else { 11731 writer.resizableTypedArrayByteLengthDoubleResult(objId); 11732 } 11733 } 11734 11735 writer.returnFromIC(); 11736 11737 trackAttached("TypedArrayByteLength"); 11738 return AttachDecision::Attach; 11739 } 11740 11741 AttachDecision InlinableNativeIRGenerator::tryAttachTypedArrayByteOffset() { 11742 // Expecting no arguments. 11743 if (args_.length() != 0) { 11744 return AttachDecision::NoAction; 11745 } 11746 11747 // Ensure |this| is a TypedArrayObject. 11748 if (!thisval_.isObject() || !thisval_.toObject().is<TypedArrayObject>()) { 11749 return AttachDecision::NoAction; 11750 } 11751 11752 auto* tarr = &thisval_.toObject().as<TypedArrayObject>(); 11753 11754 // Initialize the input operand. 11755 Int32OperandId argcId = initializeInputOperand(); 11756 11757 // Guard callee is the 'byteLength' native function. 11758 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 11759 11760 // Guard |this| is a TypedArrayObject. 11761 ValOperandId thisValId = loadThis(calleeId); 11762 ObjOperandId objId = writer.guardToObject(thisValId); 11763 writer.guardShapeForClass(objId, tarr->shape()); 11764 11765 // byteOffset doesn't need to use different code paths for fixed-length and 11766 // resizable TypedArrays. 11767 size_t byteOffset = tarr->byteOffset().valueOr(0); 11768 if (byteOffset <= INT32_MAX) { 11769 writer.arrayBufferViewByteOffsetInt32Result(objId); 11770 } else { 11771 writer.arrayBufferViewByteOffsetDoubleResult(objId); 11772 } 11773 11774 writer.returnFromIC(); 11775 11776 trackAttached("TypedArrayByteOffset"); 11777 return AttachDecision::Attach; 11778 } 11779 11780 AttachDecision InlinableNativeIRGenerator::tryAttachIsTypedArray( 11781 bool isPossiblyWrapped) { 11782 // Self-hosted code calls this with a single object argument. 11783 MOZ_ASSERT(args_.length() == 1); 11784 MOZ_ASSERT(args_[0].isObject()); 11785 11786 // Initialize the input operand. 11787 initializeInputOperand(); 11788 11789 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 11790 11791 ValOperandId argId = loadArgumentIntrinsic(ArgumentKind::Arg0); 11792 ObjOperandId objArgId = writer.guardToObject(argId); 11793 writer.isTypedArrayResult(objArgId, isPossiblyWrapped); 11794 writer.returnFromIC(); 11795 11796 trackAttached(isPossiblyWrapped ? "IsPossiblyWrappedTypedArray" 11797 : "IsTypedArray"); 11798 return AttachDecision::Attach; 11799 } 11800 11801 AttachDecision InlinableNativeIRGenerator::tryAttachIsTypedArrayConstructor() { 11802 // Self-hosted code calls this with a single object argument. 11803 MOZ_ASSERT(args_.length() == 1); 11804 MOZ_ASSERT(args_[0].isObject()); 11805 11806 // Initialize the input operand. 11807 initializeInputOperand(); 11808 11809 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 11810 11811 ValOperandId argId = loadArgumentIntrinsic(ArgumentKind::Arg0); 11812 ObjOperandId objArgId = writer.guardToObject(argId); 11813 writer.isTypedArrayConstructorResult(objArgId); 11814 writer.returnFromIC(); 11815 11816 trackAttached("IsTypedArrayConstructor"); 11817 return AttachDecision::Attach; 11818 } 11819 11820 AttachDecision InlinableNativeIRGenerator::tryAttachTypedArrayLength( 11821 bool isPossiblyWrapped) { 11822 // Self-hosted code calls this with a single, possibly wrapped, 11823 // TypedArrayObject argument. 11824 MOZ_ASSERT(args_.length() == 1); 11825 MOZ_ASSERT(args_[0].isObject()); 11826 11827 // Only optimize when the object isn't a wrapper. 11828 if (isPossiblyWrapped && IsWrapper(&args_[0].toObject())) { 11829 return AttachDecision::NoAction; 11830 } 11831 11832 MOZ_ASSERT(args_[0].toObject().is<TypedArrayObject>()); 11833 11834 auto* tarr = &args_[0].toObject().as<TypedArrayObject>(); 11835 11836 // Don't optimize when a resizable TypedArray is out-of-bounds. 11837 auto length = tarr->length(); 11838 if (length.isNothing() && !tarr->hasDetachedBuffer()) { 11839 MOZ_ASSERT(tarr->is<ResizableTypedArrayObject>()); 11840 MOZ_ASSERT(tarr->isOutOfBounds()); 11841 11842 return AttachDecision::NoAction; 11843 } 11844 11845 // Initialize the input operand. 11846 initializeInputOperand(); 11847 11848 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 11849 11850 ValOperandId argId = loadArgumentIntrinsic(ArgumentKind::Arg0); 11851 ObjOperandId objArgId = writer.guardToObject(argId); 11852 11853 if (isPossiblyWrapped) { 11854 writer.guardIsNotProxy(objArgId); 11855 } 11856 11857 EmitGuardTypedArray(writer, tarr, objArgId); 11858 11859 if (!tarr->is<ResizableTypedArrayObject>()) { 11860 if (length.valueOr(0) <= INT32_MAX) { 11861 writer.loadArrayBufferViewLengthInt32Result(objArgId); 11862 } else { 11863 writer.loadArrayBufferViewLengthDoubleResult(objArgId); 11864 } 11865 } else { 11866 writer.guardResizableArrayBufferViewInBoundsOrDetached(objArgId); 11867 11868 if (length.valueOr(0) <= INT32_MAX) { 11869 writer.resizableTypedArrayLengthInt32Result(objArgId); 11870 } else { 11871 writer.resizableTypedArrayLengthDoubleResult(objArgId); 11872 } 11873 } 11874 writer.returnFromIC(); 11875 11876 trackAttached("IntrinsicTypedArrayLength"); 11877 return AttachDecision::Attach; 11878 } 11879 11880 AttachDecision InlinableNativeIRGenerator::tryAttachArrayBufferByteLength() { 11881 // Expecting no arguments. 11882 if (args_.length() != 0) { 11883 return AttachDecision::NoAction; 11884 } 11885 11886 // Ensure |this| is an ArrayBufferObject. 11887 if (!thisval_.isObject() || !thisval_.toObject().is<ArrayBufferObject>()) { 11888 return AttachDecision::NoAction; 11889 } 11890 11891 auto* buf = &thisval_.toObject().as<ArrayBufferObject>(); 11892 11893 // Initialize the input operand. 11894 Int32OperandId argcId = initializeInputOperand(); 11895 11896 // Guard callee is the 'byteLength' native function. 11897 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 11898 11899 // Guard |this| is an ArrayBufferObject. 11900 ValOperandId thisValId = loadThis(calleeId); 11901 ObjOperandId objId = writer.guardToObject(thisValId); 11902 11903 if (buf->is<FixedLengthArrayBufferObject>()) { 11904 emitOptimisticClassGuard(objId, buf, 11905 GuardClassKind::FixedLengthArrayBuffer); 11906 } else if (buf->is<ImmutableArrayBufferObject>()) { 11907 emitOptimisticClassGuard(objId, buf, GuardClassKind::ImmutableArrayBuffer); 11908 } else { 11909 emitOptimisticClassGuard(objId, buf, GuardClassKind::ResizableArrayBuffer); 11910 } 11911 11912 if (buf->byteLength() <= INT32_MAX) { 11913 writer.loadArrayBufferByteLengthInt32Result(objId); 11914 } else { 11915 writer.loadArrayBufferByteLengthDoubleResult(objId); 11916 } 11917 11918 writer.returnFromIC(); 11919 11920 trackAttached("ArrayBufferByteLength"); 11921 return AttachDecision::Attach; 11922 } 11923 11924 AttachDecision 11925 InlinableNativeIRGenerator::tryAttachSharedArrayBufferByteLength() { 11926 // Expecting no arguments. 11927 if (args_.length() != 0) { 11928 return AttachDecision::NoAction; 11929 } 11930 11931 // Ensure |this| is a SharedArrayBufferObject. 11932 if (!thisval_.isObject() || 11933 !thisval_.toObject().is<SharedArrayBufferObject>()) { 11934 return AttachDecision::NoAction; 11935 } 11936 11937 auto* buf = &thisval_.toObject().as<SharedArrayBufferObject>(); 11938 11939 // Initialize the input operand. 11940 Int32OperandId argcId = initializeInputOperand(); 11941 11942 // Guard callee is the 'byteLength' native function. 11943 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 11944 11945 // Guard |this| is a SharedArrayBufferObject. 11946 ValOperandId thisValId = loadThis(calleeId); 11947 ObjOperandId objId = writer.guardToObject(thisValId); 11948 11949 if (buf->is<FixedLengthSharedArrayBufferObject>()) { 11950 emitOptimisticClassGuard(objId, buf, 11951 GuardClassKind::FixedLengthSharedArrayBuffer); 11952 11953 if (buf->byteLength() <= INT32_MAX) { 11954 writer.loadArrayBufferByteLengthInt32Result(objId); 11955 } else { 11956 writer.loadArrayBufferByteLengthDoubleResult(objId); 11957 } 11958 } else { 11959 emitOptimisticClassGuard(objId, buf, 11960 GuardClassKind::GrowableSharedArrayBuffer); 11961 11962 if (buf->byteLength() <= INT32_MAX) { 11963 writer.growableSharedArrayBufferByteLengthInt32Result(objId); 11964 } else { 11965 writer.growableSharedArrayBufferByteLengthDoubleResult(objId); 11966 } 11967 } 11968 11969 writer.returnFromIC(); 11970 11971 trackAttached("SharedArrayBufferByteLength"); 11972 return AttachDecision::Attach; 11973 } 11974 11975 AttachDecision InlinableNativeIRGenerator::tryAttachIsConstructing() { 11976 // Self-hosted code calls this with no arguments in function scripts. 11977 MOZ_ASSERT(args_.length() == 0); 11978 MOZ_ASSERT(script()->isFunction()); 11979 11980 // Initialize the input operand. 11981 initializeInputOperand(); 11982 11983 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 11984 11985 writer.frameIsConstructingResult(); 11986 writer.returnFromIC(); 11987 11988 trackAttached("IsConstructing"); 11989 return AttachDecision::Attach; 11990 } 11991 11992 AttachDecision 11993 InlinableNativeIRGenerator::tryAttachGetNextMapSetEntryForIterator(bool isMap) { 11994 // Self-hosted code calls this with two objects. 11995 MOZ_ASSERT(args_.length() == 2); 11996 if (isMap) { 11997 MOZ_ASSERT(args_[0].toObject().is<MapIteratorObject>()); 11998 } else { 11999 MOZ_ASSERT(args_[0].toObject().is<SetIteratorObject>()); 12000 } 12001 MOZ_ASSERT(args_[1].toObject().is<ArrayObject>()); 12002 12003 // Initialize the input operand. 12004 initializeInputOperand(); 12005 12006 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 12007 12008 ValOperandId iterId = loadArgumentIntrinsic(ArgumentKind::Arg0); 12009 ObjOperandId objIterId = writer.guardToObject(iterId); 12010 12011 ValOperandId resultArrId = loadArgumentIntrinsic(ArgumentKind::Arg1); 12012 ObjOperandId objResultArrId = writer.guardToObject(resultArrId); 12013 12014 writer.getNextMapSetEntryForIteratorResult(objIterId, objResultArrId, isMap); 12015 writer.returnFromIC(); 12016 12017 trackAttached("GetNextMapSetEntryForIterator"); 12018 return AttachDecision::Attach; 12019 } 12020 12021 AttachDecision InlinableNativeIRGenerator::tryAttachNewArrayIterator() { 12022 // Self-hosted code calls this without any arguments 12023 MOZ_ASSERT(args_.length() == 0); 12024 12025 JSObject* templateObj = NewArrayIteratorTemplate(cx_); 12026 if (!templateObj) { 12027 cx_->recoverFromOutOfMemory(); 12028 return AttachDecision::NoAction; 12029 } 12030 12031 // Initialize the input operand. 12032 initializeInputOperand(); 12033 12034 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 12035 12036 writer.newArrayIteratorResult(templateObj); 12037 writer.returnFromIC(); 12038 12039 trackAttached("NewArrayIterator"); 12040 return AttachDecision::Attach; 12041 } 12042 12043 AttachDecision InlinableNativeIRGenerator::tryAttachNewStringIterator() { 12044 // Self-hosted code calls this without any arguments 12045 MOZ_ASSERT(args_.length() == 0); 12046 12047 JSObject* templateObj = NewStringIteratorTemplate(cx_); 12048 if (!templateObj) { 12049 cx_->recoverFromOutOfMemory(); 12050 return AttachDecision::NoAction; 12051 } 12052 12053 // Initialize the input operand. 12054 initializeInputOperand(); 12055 12056 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 12057 12058 writer.newStringIteratorResult(templateObj); 12059 writer.returnFromIC(); 12060 12061 trackAttached("NewStringIterator"); 12062 return AttachDecision::Attach; 12063 } 12064 12065 AttachDecision InlinableNativeIRGenerator::tryAttachNewRegExpStringIterator() { 12066 // Self-hosted code calls this without any arguments 12067 MOZ_ASSERT(args_.length() == 0); 12068 12069 JSObject* templateObj = NewRegExpStringIteratorTemplate(cx_); 12070 if (!templateObj) { 12071 cx_->recoverFromOutOfMemory(); 12072 return AttachDecision::NoAction; 12073 } 12074 12075 // Initialize the input operand. 12076 initializeInputOperand(); 12077 12078 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 12079 12080 writer.newRegExpStringIteratorResult(templateObj); 12081 writer.returnFromIC(); 12082 12083 trackAttached("NewRegExpStringIterator"); 12084 return AttachDecision::Attach; 12085 } 12086 12087 AttachDecision 12088 InlinableNativeIRGenerator::tryAttachArrayIteratorPrototypeOptimizable() { 12089 // Self-hosted code calls this without any arguments 12090 MOZ_ASSERT(args_.length() == 0); 12091 12092 if (!isFirstStub()) { 12093 // Attach only once to prevent slowdowns for polymorphic calls. 12094 return AttachDecision::NoAction; 12095 } 12096 12097 if (!HasOptimizableArrayIteratorPrototype(cx_)) { 12098 return AttachDecision::NoAction; 12099 } 12100 12101 // Initialize the input operand. 12102 initializeInputOperand(); 12103 12104 // Note: we don't need to call emitNativeCalleeGuard for intrinsics. 12105 12106 writer.guardFuse(RealmFuses::FuseIndex::OptimizeArrayIteratorPrototypeFuse); 12107 writer.loadBooleanResult(true); 12108 writer.returnFromIC(); 12109 12110 trackAttached("ArrayIteratorPrototypeOptimizable"); 12111 return AttachDecision::Attach; 12112 } 12113 12114 AttachDecision InlinableNativeIRGenerator::tryAttachObjectCreate() { 12115 // Need a single object-or-null argument. 12116 if (args_.length() != 1 || !args_[0].isObjectOrNull()) { 12117 return AttachDecision::NoAction; 12118 } 12119 12120 if (!isFirstStub()) { 12121 // Attach only once to prevent slowdowns for polymorphic calls. 12122 return AttachDecision::NoAction; 12123 } 12124 12125 RootedObject proto(cx_, args_[0].toObjectOrNull()); 12126 JSObject* templateObj = ObjectCreateImpl(cx_, proto, TenuredObject); 12127 if (!templateObj) { 12128 cx_->recoverFromOutOfMemory(); 12129 return AttachDecision::NoAction; 12130 } 12131 12132 // Initialize the input operand. 12133 Int32OperandId argcId = initializeInputOperand(); 12134 12135 // Guard callee is the 'create' native function. 12136 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 12137 12138 // Guard on the proto argument. 12139 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 12140 if (proto) { 12141 ObjOperandId protoId = writer.guardToObject(argId); 12142 writer.guardSpecificObject(protoId, proto); 12143 } else { 12144 writer.guardIsNull(argId); 12145 } 12146 12147 writer.objectCreateResult(templateObj); 12148 writer.returnFromIC(); 12149 12150 trackAttached("ObjectCreate"); 12151 return AttachDecision::Attach; 12152 } 12153 12154 AttachDecision InlinableNativeIRGenerator::tryAttachObjectConstructor() { 12155 // Expecting no arguments or a single object argument. 12156 // TODO(Warp): Support all or more conversions to object. 12157 if (args_.length() > 1) { 12158 return AttachDecision::NoAction; 12159 } 12160 if (args_.length() == 1 && !args_[0].isObject()) { 12161 return AttachDecision::NoAction; 12162 } 12163 12164 gc::AllocSite* site = nullptr; 12165 PlainObject* templateObj = nullptr; 12166 if (args_.length() == 0) { 12167 // Don't optimize if we can't create an alloc-site. 12168 if (!BytecodeOpCanHaveAllocSite(op())) { 12169 return AttachDecision::NoAction; 12170 } 12171 12172 // Stub doesn't support metadata builder 12173 if (cx_->realm()->hasAllocationMetadataBuilder()) { 12174 return AttachDecision::NoAction; 12175 } 12176 12177 site = generator_.maybeCreateAllocSite(); 12178 if (!site) { 12179 return AttachDecision::NoAction; 12180 } 12181 12182 // Create a temporary object to act as the template object. 12183 templateObj = NewPlainObjectWithAllocKind(cx_, NewObjectGCKind()); 12184 if (!templateObj) { 12185 cx_->recoverFromOutOfMemory(); 12186 return AttachDecision::NoAction; 12187 } 12188 } 12189 12190 // Initialize the input operand. 12191 Int32OperandId argcId = initializeInputOperand(); 12192 12193 // Guard callee and newTarget (if constructing) are this Object constructor 12194 // function. 12195 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 12196 12197 if (args_.length() == 0) { 12198 uint32_t numFixedSlots = templateObj->numUsedFixedSlots(); 12199 uint32_t numDynamicSlots = templateObj->numDynamicSlots(); 12200 gc::AllocKind allocKind = templateObj->allocKindForTenure(); 12201 Shape* shape = templateObj->shape(); 12202 12203 writer.guardNoAllocationMetadataBuilder( 12204 cx_->realm()->addressOfMetadataBuilder()); 12205 writer.newPlainObjectResult(numFixedSlots, numDynamicSlots, allocKind, 12206 shape, site); 12207 } else { 12208 // Guard that the argument is an object. 12209 ValOperandId argId = loadArgument(calleeId, ArgumentKind::Arg0); 12210 ObjOperandId objId = writer.guardToObject(argId); 12211 12212 // Return the object. 12213 writer.loadObjectResult(objId); 12214 } 12215 12216 writer.returnFromIC(); 12217 12218 trackAttached("ObjectConstructor"); 12219 return AttachDecision::Attach; 12220 } 12221 12222 AttachDecision InlinableNativeIRGenerator::tryAttachArrayConstructor() { 12223 // Don't optimize if we can't create an alloc-site. 12224 if (!BytecodeOpCanHaveAllocSite(op())) { 12225 return AttachDecision::NoAction; 12226 } 12227 12228 // Only optimize the |Array()| and |Array(n)| cases (with or without |new|) 12229 // for now. Note that self-hosted code calls this without |new| via std_Array. 12230 if (args_.length() > 1) { 12231 return AttachDecision::NoAction; 12232 } 12233 if (args_.length() == 1 && !args_[0].isInt32()) { 12234 return AttachDecision::NoAction; 12235 } 12236 12237 int32_t length = (args_.length() == 1) ? args_[0].toInt32() : 0; 12238 if (length < 0 || uint32_t(length) > ArrayObject::EagerAllocationMaxLength) { 12239 return AttachDecision::NoAction; 12240 } 12241 12242 // We allow inlining this function across realms so make sure the template 12243 // object is allocated in that realm. See CanInlineNativeCrossRealm. 12244 JSObject* templateObj; 12245 { 12246 AutoRealm ar(cx_, target_); 12247 templateObj = NewDenseFullyAllocatedArray(cx_, length, TenuredObject); 12248 if (!templateObj) { 12249 cx_->clearPendingException(); 12250 return AttachDecision::NoAction; 12251 } 12252 } 12253 12254 gc::AllocSite* site = generator_.maybeCreateAllocSite(); 12255 if (!site) { 12256 return AttachDecision::NoAction; 12257 } 12258 12259 // Initialize the input operand. 12260 Int32OperandId argcId = initializeInputOperand(); 12261 12262 // Guard callee and newTarget (if constructing) are this Array constructor 12263 // function. 12264 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 12265 12266 Int32OperandId lengthId; 12267 if (args_.length() == 1) { 12268 ValOperandId arg0Id = loadArgument(calleeId, ArgumentKind::Arg0); 12269 lengthId = writer.guardToInt32(arg0Id); 12270 } else { 12271 MOZ_ASSERT(args_.length() == 0); 12272 lengthId = writer.loadInt32Constant(0); 12273 } 12274 12275 writer.newArrayFromLengthResult(templateObj, lengthId, site); 12276 writer.returnFromIC(); 12277 12278 trackAttached("ArrayConstructor"); 12279 return AttachDecision::Attach; 12280 } 12281 12282 AttachDecision 12283 InlinableNativeIRGenerator::tryAttachTypedArrayConstructorFromLength() { 12284 MOZ_ASSERT(flags_.isConstructing()); 12285 MOZ_ASSERT(args_.length() == 0 || args_[0].isInt32()); 12286 12287 // Expected arguments: Optional length (int32) 12288 if (args_.length() > 1) { 12289 return AttachDecision::NoAction; 12290 } 12291 12292 int32_t length = args_.length() > 0 ? args_[0].toInt32() : 0; 12293 12294 Scalar::Type type = TypedArrayConstructorType(target_); 12295 Rooted<TypedArrayObject*> templateObj(cx_); 12296 if (!TypedArrayObject::GetTemplateObjectForLength(cx_, type, length, 12297 &templateObj)) { 12298 cx_->recoverFromOutOfMemory(); 12299 return AttachDecision::NoAction; 12300 } 12301 12302 // This can happen for large length values. 12303 if (!templateObj) { 12304 return AttachDecision::NoAction; 12305 } 12306 12307 // Initialize the input operand. 12308 Int32OperandId argcId = initializeInputOperand(); 12309 12310 // Guard callee and newTarget are this TypedArray constructor function. 12311 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 12312 12313 Int32OperandId lengthId; 12314 if (args_.length() > 0) { 12315 ValOperandId arg0Id = loadArgument(calleeId, ArgumentKind::Arg0); 12316 lengthId = writer.guardToInt32(arg0Id); 12317 } else { 12318 lengthId = writer.loadInt32Constant(0); 12319 } 12320 writer.newTypedArrayFromLengthResult(templateObj, lengthId); 12321 writer.returnFromIC(); 12322 12323 trackAttached("TypedArrayConstructorFromLength"); 12324 return AttachDecision::Attach; 12325 } 12326 12327 AttachDecision 12328 InlinableNativeIRGenerator::tryAttachTypedArrayConstructorFromArrayBuffer() { 12329 MOZ_ASSERT(flags_.isConstructing()); 12330 MOZ_ASSERT(args_.length() > 0); 12331 MOZ_ASSERT(args_[0].isObject()); 12332 12333 // Expected arguments: array buffer, optional byteOffset, optional length 12334 if (args_.length() > 3) { 12335 return AttachDecision::NoAction; 12336 } 12337 12338 #ifdef JS_CODEGEN_X86 12339 // Unfortunately NewTypedArrayFromArrayBufferResult needs more registers than 12340 // we can easily support on 32-bit x86 for now. 12341 return AttachDecision::NoAction; 12342 #else 12343 Scalar::Type type = TypedArrayConstructorType(target_); 12344 12345 Rooted<ArrayBufferObjectMaybeShared*> obj( 12346 cx_, &args_[0].toObject().as<ArrayBufferObjectMaybeShared>()); 12347 12348 Rooted<TypedArrayObject*> templateObj( 12349 cx_, TypedArrayObject::GetTemplateObjectForBuffer(cx_, type, obj)); 12350 if (!templateObj) { 12351 cx_->recoverFromOutOfMemory(); 12352 return AttachDecision::NoAction; 12353 } 12354 12355 // Initialize the input operand. 12356 Int32OperandId argcId = initializeInputOperand(); 12357 12358 // Guard callee and newTarget are this TypedArray constructor function. 12359 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 12360 12361 ValOperandId arg0Id = loadArgument(calleeId, ArgumentKind::Arg0); 12362 ObjOperandId objId = writer.guardToObject(arg0Id); 12363 12364 if (obj->is<FixedLengthArrayBufferObject>()) { 12365 writer.guardClass(objId, GuardClassKind::FixedLengthArrayBuffer); 12366 } else if (obj->is<FixedLengthSharedArrayBufferObject>()) { 12367 writer.guardClass(objId, GuardClassKind::FixedLengthSharedArrayBuffer); 12368 } else if (obj->is<ResizableArrayBufferObject>()) { 12369 writer.guardClass(objId, GuardClassKind::ResizableArrayBuffer); 12370 } else if (obj->is<GrowableSharedArrayBufferObject>()) { 12371 writer.guardClass(objId, GuardClassKind::GrowableSharedArrayBuffer); 12372 } else { 12373 MOZ_ASSERT(obj->is<ImmutableArrayBufferObject>()); 12374 writer.guardClass(objId, GuardClassKind::ImmutableArrayBuffer); 12375 } 12376 12377 ValOperandId byteOffsetId; 12378 if (args_.length() > 1) { 12379 byteOffsetId = loadArgument(calleeId, ArgumentKind::Arg1); 12380 } else { 12381 byteOffsetId = writer.loadUndefined(); 12382 } 12383 12384 ValOperandId lengthId; 12385 if (args_.length() > 2) { 12386 lengthId = loadArgument(calleeId, ArgumentKind::Arg2); 12387 } else { 12388 lengthId = writer.loadUndefined(); 12389 } 12390 12391 writer.newTypedArrayFromArrayBufferResult(templateObj, objId, byteOffsetId, 12392 lengthId); 12393 writer.returnFromIC(); 12394 12395 trackAttached("TypedArrayConstructorFromArrayBuffer"); 12396 return AttachDecision::Attach; 12397 #endif 12398 } 12399 12400 AttachDecision 12401 InlinableNativeIRGenerator::tryAttachTypedArrayConstructorFromArray() { 12402 MOZ_ASSERT(flags_.isConstructing()); 12403 MOZ_ASSERT(args_.length() > 0); 12404 MOZ_ASSERT(args_[0].isObject()); 12405 12406 // Expected arguments: Array-like object. 12407 if (args_.length() != 1) { 12408 return AttachDecision::NoAction; 12409 } 12410 12411 Rooted<JSObject*> obj(cx_, &args_[0].toObject()); 12412 MOZ_ASSERT(!obj->is<ProxyObject>()); 12413 MOZ_ASSERT(!obj->is<ArrayBufferObjectMaybeShared>()); 12414 12415 Scalar::Type type = TypedArrayConstructorType(target_); 12416 12417 Rooted<TypedArrayObject*> templateObj( 12418 cx_, TypedArrayObject::GetTemplateObjectForArrayLike(cx_, type, obj)); 12419 if (!templateObj) { 12420 cx_->recoverFromOutOfMemory(); 12421 return AttachDecision::NoAction; 12422 } 12423 12424 // Initialize the input operand. 12425 Int32OperandId argcId = initializeInputOperand(); 12426 12427 // Guard callee and newTarget are this TypedArray constructor function. 12428 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 12429 12430 ValOperandId arg0Id = loadArgument(calleeId, ArgumentKind::Arg0); 12431 ObjOperandId objId = writer.guardToObject(arg0Id); 12432 12433 writer.guardIsNotArrayBufferMaybeShared(objId); 12434 writer.guardIsNotProxy(objId); 12435 writer.newTypedArrayFromArrayResult(templateObj, objId); 12436 writer.returnFromIC(); 12437 12438 trackAttached("TypedArrayConstructorFromArray"); 12439 return AttachDecision::Attach; 12440 } 12441 12442 AttachDecision InlinableNativeIRGenerator::tryAttachTypedArrayConstructor() { 12443 MOZ_ASSERT(flags_.isConstructing()); 12444 12445 if (!isFirstStub()) { 12446 // Attach only once to prevent slowdowns for polymorphic calls. 12447 return AttachDecision::NoAction; 12448 } 12449 12450 // The first argument, if present, must be int32 or a non-proxy object. 12451 12452 if (args_.length() == 0 || args_[0].isInt32()) { 12453 return tryAttachTypedArrayConstructorFromLength(); 12454 } 12455 12456 if (args_[0].isObject()) { 12457 auto* obj = &args_[0].toObject(); 12458 12459 // Proxy objects not allowed, because handling Wrappers is complicated. 12460 if (obj->is<ProxyObject>()) { 12461 return AttachDecision::NoAction; 12462 } 12463 12464 if (obj->is<ArrayBufferObjectMaybeShared>()) { 12465 return tryAttachTypedArrayConstructorFromArrayBuffer(); 12466 } 12467 return tryAttachTypedArrayConstructorFromArray(); 12468 } 12469 12470 // Other argument types are not supported. 12471 return AttachDecision::NoAction; 12472 } 12473 12474 AttachDecision InlinableNativeIRGenerator::tryAttachMapSetConstructor( 12475 InlinableNative native) { 12476 MOZ_ASSERT(native == InlinableNative::MapConstructor || 12477 native == InlinableNative::SetConstructor); 12478 MOZ_ASSERT(flags_.isConstructing()); 12479 12480 // Must have either no arguments or a single (iterable) argument. 12481 if (args_.length() > 1) { 12482 return AttachDecision::NoAction; 12483 } 12484 12485 if (!isFirstStub()) { 12486 // Attach only once to prevent slowdowns for polymorphic calls. 12487 return AttachDecision::NoAction; 12488 } 12489 12490 JSObject* templateObj; 12491 if (native == InlinableNative::MapConstructor) { 12492 templateObj = GlobalObject::getOrCreateMapTemplateObject(cx_); 12493 } else { 12494 templateObj = GlobalObject::getOrCreateSetTemplateObject(cx_); 12495 } 12496 if (!templateObj) { 12497 cx_->recoverFromOutOfMemory(); 12498 return AttachDecision::NoAction; 12499 } 12500 12501 // Initialize the input operand. 12502 Int32OperandId argcId = initializeInputOperand(); 12503 12504 // Guard callee and newTarget are this Map/Set constructor function. 12505 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 12506 12507 if (args_.length() == 1) { 12508 ValOperandId iterableId = loadArgument(calleeId, ArgumentKind::Arg0); 12509 if (native == InlinableNative::MapConstructor) { 12510 writer.newMapObjectFromIterableResult(templateObj, iterableId); 12511 } else { 12512 writer.newSetObjectFromIterableResult(templateObj, iterableId); 12513 } 12514 } else { 12515 if (native == InlinableNative::MapConstructor) { 12516 writer.newMapObjectResult(templateObj); 12517 } else { 12518 writer.newSetObjectResult(templateObj); 12519 } 12520 } 12521 writer.returnFromIC(); 12522 12523 if (native == InlinableNative::MapConstructor) { 12524 trackAttached("MapConstructor"); 12525 } else { 12526 trackAttached("SetConstructor"); 12527 } 12528 return AttachDecision::Attach; 12529 } 12530 12531 AttachDecision InlinableNativeIRGenerator::tryAttachSpecializedFunctionBind( 12532 Handle<JSObject*> target, Handle<BoundFunctionObject*> templateObj) { 12533 // Try to attach a faster stub that's more specialized than what we emit in 12534 // tryAttachFunctionBind. This lets us allocate and initialize a bound 12535 // function object in Ion without calling into C++. 12536 // 12537 // We can do this if: 12538 // 12539 // * The target's prototype is Function.prototype, because that's the proto we 12540 // use for the template object. 12541 // * All bound arguments can be stored inline. 12542 // * The `.name`, `.length`, and `IsConstructor` values match `target`. 12543 // 12544 // We initialize the template object with the bound function's name, length, 12545 // and flags. At runtime we then only have to clone the template object and 12546 // initialize the slots for the target, the bound `this` and the bound 12547 // arguments. 12548 12549 if (!isFirstStub()) { 12550 return AttachDecision::NoAction; 12551 } 12552 if (!target->is<JSFunction>() && !target->is<BoundFunctionObject>()) { 12553 return AttachDecision::NoAction; 12554 } 12555 if (target->staticPrototype() != &cx_->global()->getFunctionPrototype()) { 12556 return AttachDecision::NoAction; 12557 } 12558 size_t numBoundArgs = args_.length() > 0 ? args_.length() - 1 : 0; 12559 if (numBoundArgs > BoundFunctionObject::MaxInlineBoundArgs) { 12560 return AttachDecision::NoAction; 12561 } 12562 12563 const bool targetIsConstructor = target->isConstructor(); 12564 Rooted<JSAtom*> targetName(cx_); 12565 uint32_t targetLength = 0; 12566 12567 if (target->is<JSFunction>()) { 12568 Rooted<JSFunction*> fun(cx_, &target->as<JSFunction>()); 12569 if (fun->isNativeFun()) { 12570 return AttachDecision::NoAction; 12571 } 12572 if (fun->hasResolvedLength() || fun->hasResolvedName()) { 12573 return AttachDecision::NoAction; 12574 } 12575 uint16_t len; 12576 if (!JSFunction::getUnresolvedLength(cx_, fun, &len)) { 12577 cx_->clearPendingException(); 12578 return AttachDecision::NoAction; 12579 } 12580 targetName = fun->getUnresolvedName(cx_); 12581 if (!targetName) { 12582 cx_->clearPendingException(); 12583 return AttachDecision::NoAction; 12584 } 12585 12586 targetLength = len; 12587 } else { 12588 BoundFunctionObject* bound = &target->as<BoundFunctionObject>(); 12589 if (!targetIsConstructor) { 12590 // Only support constructors for now. This lets us use 12591 // GuardBoundFunctionIsConstructor. 12592 return AttachDecision::NoAction; 12593 } 12594 Shape* initialShape = 12595 cx_->global()->maybeBoundFunctionShapeWithDefaultProto(); 12596 if (bound->shape() != initialShape) { 12597 return AttachDecision::NoAction; 12598 } 12599 Value lenVal = bound->getLengthForInitialShape(); 12600 Value nameVal = bound->getNameForInitialShape(); 12601 if (!lenVal.isInt32() || lenVal.toInt32() < 0 || !nameVal.isString() || 12602 !nameVal.toString()->isAtom()) { 12603 return AttachDecision::NoAction; 12604 } 12605 targetName = &nameVal.toString()->asAtom(); 12606 targetLength = uint32_t(lenVal.toInt32()); 12607 } 12608 12609 if (!templateObj->initTemplateSlotsForSpecializedBind( 12610 cx_, numBoundArgs, targetIsConstructor, targetLength, targetName)) { 12611 cx_->recoverFromOutOfMemory(); 12612 return AttachDecision::NoAction; 12613 } 12614 12615 Int32OperandId argcId = initializeInputOperand(); 12616 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 12617 12618 ValOperandId thisValId = loadThis(calleeId); 12619 ObjOperandId targetId = writer.guardToObject(thisValId); 12620 12621 // Ensure the JSClass and proto match, and that the `length` and `name` 12622 // properties haven't been redefined. 12623 writer.guardShape(targetId, target->shape()); 12624 12625 // Emit guards for the `IsConstructor`, `.length`, and `.name` values. 12626 if (target->is<JSFunction>()) { 12627 // Guard on: 12628 // * The BaseScript (because that's what JSFunction uses for the `length`). 12629 // Because MGuardFunctionScript doesn't support self-hosted functions yet, 12630 // we use GuardSpecificFunction instead in this case. 12631 // See assertion in MGuardFunctionScript::getAliasSet. 12632 // * The flags slot (for the CONSTRUCTOR, RESOLVED_NAME, RESOLVED_LENGTH, 12633 // HAS_INFERRED_NAME, and HAS_GUESSED_ATOM flags). 12634 // * The atom slot. 12635 JSFunction* fun = &target->as<JSFunction>(); 12636 if (fun->isSelfHostedBuiltin()) { 12637 writer.guardSpecificFunction(targetId, fun); 12638 } else { 12639 writer.guardFunctionScript(targetId, fun->baseScript()); 12640 } 12641 writer.guardFixedSlotValue( 12642 targetId, JSFunction::offsetOfFlagsAndArgCount(), 12643 fun->getReservedSlot(JSFunction::FlagsAndArgCountSlot)); 12644 writer.guardFixedSlotValue(targetId, JSFunction::offsetOfAtom(), 12645 fun->getReservedSlot(JSFunction::AtomSlot)); 12646 } else { 12647 BoundFunctionObject* bound = &target->as<BoundFunctionObject>(); 12648 writer.guardBoundFunctionIsConstructor(targetId); 12649 writer.guardFixedSlotValue(targetId, 12650 BoundFunctionObject::offsetOfLengthSlot(), 12651 bound->getLengthForInitialShape()); 12652 writer.guardFixedSlotValue(targetId, 12653 BoundFunctionObject::offsetOfNameSlot(), 12654 bound->getNameForInitialShape()); 12655 } 12656 12657 writer.specializedBindFunctionResult(targetId, args_.length(), templateObj); 12658 writer.returnFromIC(); 12659 12660 trackAttached("SpecializedFunctionBind"); 12661 return AttachDecision::Attach; 12662 } 12663 12664 AttachDecision InlinableNativeIRGenerator::tryAttachFunctionBind() { 12665 // Ensure |this| (the target) is a function object or a bound function object. 12666 // We could support other callables too, but note that we rely on the target 12667 // having a static prototype in BoundFunctionObject::functionBindImpl. 12668 if (!thisval_.isObject()) { 12669 return AttachDecision::NoAction; 12670 } 12671 Rooted<JSObject*> target(cx_, &thisval_.toObject()); 12672 if (!target->is<JSFunction>() && !target->is<BoundFunctionObject>()) { 12673 return AttachDecision::NoAction; 12674 } 12675 12676 // Only support standard, non-spread calls. 12677 if (flags_.getArgFormat() != CallFlags::Standard) { 12678 return AttachDecision::NoAction; 12679 } 12680 12681 // Only support when no additional bound arguments are present. 12682 if (hasBoundArguments()) { 12683 return AttachDecision::NoAction; 12684 } 12685 MOZ_ASSERT(stackArgc() == args_.length(), "argc matches number of arguments"); 12686 12687 // Only optimize if the number of arguments is small. This ensures we don't 12688 // compile a lot of different stubs (because we bake in argc) and that we 12689 // don't get anywhere near ARGS_LENGTH_MAX. 12690 static constexpr size_t MaxArguments = 6; 12691 if (args_.length() > MaxArguments) { 12692 return AttachDecision::NoAction; 12693 } 12694 12695 Rooted<BoundFunctionObject*> templateObj( 12696 cx_, BoundFunctionObject::createTemplateObject(cx_)); 12697 if (!templateObj) { 12698 cx_->recoverFromOutOfMemory(); 12699 return AttachDecision::NoAction; 12700 } 12701 12702 TRY_ATTACH(tryAttachSpecializedFunctionBind(target, templateObj)); 12703 12704 Int32OperandId argcId = initializeInputOperand(); 12705 12706 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 12707 12708 // Guard |this| is a function object or a bound function object. 12709 ValOperandId thisValId = loadThis(calleeId); 12710 ObjOperandId targetId = writer.guardToObject(thisValId); 12711 if (target->is<JSFunction>()) { 12712 writer.guardClass(targetId, GuardClassKind::JSFunction); 12713 } else { 12714 MOZ_ASSERT(target->is<BoundFunctionObject>()); 12715 writer.guardClass(targetId, GuardClassKind::BoundFunction); 12716 } 12717 12718 writer.bindFunctionResult(targetId, args_.length(), templateObj); 12719 writer.returnFromIC(); 12720 12721 trackAttached("FunctionBind"); 12722 return AttachDecision::Attach; 12723 } 12724 12725 AttachDecision CallIRGenerator::tryAttachFunApply(HandleFunction calleeFunc) { 12726 MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry()); 12727 12728 if (calleeFunc->native() != fun_apply) { 12729 return AttachDecision::NoAction; 12730 } 12731 12732 if (argc_ > 2) { 12733 return AttachDecision::NoAction; 12734 } 12735 12736 if (!thisval_.isObject() || !thisval_.toObject().is<JSFunction>()) { 12737 return AttachDecision::NoAction; 12738 } 12739 Rooted<JSFunction*> target(cx_, &thisval_.toObject().as<JSFunction>()); 12740 12741 bool isScripted = target->hasJitEntry(); 12742 MOZ_ASSERT_IF(!isScripted, target->isNativeWithoutJitEntry()); 12743 12744 if (target->isClassConstructor()) { 12745 return AttachDecision::NoAction; 12746 } 12747 12748 CallFlags::ArgFormat format = CallFlags::Standard; 12749 if (argc_ < 2) { 12750 // |fun.apply()| and |fun.apply(thisValue)| are equivalent to |fun.call()| 12751 // resp. |fun.call(thisValue)|. 12752 format = CallFlags::FunCall; 12753 } else if (args_[1].isNullOrUndefined()) { 12754 // |fun.apply(thisValue, null)| and |fun.apply(thisValue, undefined)| are 12755 // also equivalent to |fun.call(thisValue)|, but we can't use FunCall 12756 // because we have to discard the second argument. 12757 format = CallFlags::FunApplyNullUndefined; 12758 } else if (args_[1].isObject() && args_[1].toObject().is<ArgumentsObject>()) { 12759 auto* argsObj = &args_[1].toObject().as<ArgumentsObject>(); 12760 if (argsObj->hasOverriddenElement() || argsObj->anyArgIsForwarded() || 12761 argsObj->hasOverriddenLength() || 12762 argsObj->initialLength() > JIT_ARGS_LENGTH_MAX) { 12763 return AttachDecision::NoAction; 12764 } 12765 format = CallFlags::FunApplyArgsObj; 12766 } else if (args_[1].isObject() && args_[1].toObject().is<ArrayObject>() && 12767 IsPackedArray(&args_[1].toObject())) { 12768 format = CallFlags::FunApplyArray; 12769 } else { 12770 return AttachDecision::NoAction; 12771 } 12772 12773 CallFlags targetFlags(format); 12774 if (mode_ == ICState::Mode::Specialized) { 12775 if (cx_->realm() == target->realm()) { 12776 targetFlags.setIsSameRealm(); 12777 } 12778 } 12779 12780 if (mode_ == ICState::Mode::Specialized && !isScripted && 12781 format == CallFlags::FunApplyArray) { 12782 HandleValue newTarget = NullHandleValue; 12783 HandleValue thisValue = args_[0]; 12784 Rooted<ArrayObject*> aobj(cx_, &args_[1].toObject().as<ArrayObject>()); 12785 HandleValueArray args = HandleValueArray::fromMarkedLocation( 12786 aobj->length(), aobj->getDenseElements()); 12787 12788 // Check for specific native-function optimizations. 12789 InlinableNativeIRGenerator nativeGen(*this, calleeFunc, target, newTarget, 12790 thisValue, args, targetFlags); 12791 TRY_ATTACH(nativeGen.tryAttachStub()); 12792 } 12793 if (format == CallFlags::FunApplyArray && 12794 args_[1].toObject().as<ArrayObject>().length() > JIT_ARGS_LENGTH_MAX) { 12795 // We check this after trying to attach inlinable natives, because some 12796 // inlinable natives can safely ignore the limit. 12797 return AttachDecision::NoAction; 12798 } 12799 12800 if (mode_ == ICState::Mode::Specialized && !isScripted && 12801 (format == CallFlags::FunCall || 12802 format == CallFlags::FunApplyNullUndefined)) { 12803 HandleValue newTarget = NullHandleValue; 12804 HandleValue thisValue = argc_ > 0 ? args_[0] : UndefinedHandleValue; 12805 HandleValueArray args = HandleValueArray::empty(); 12806 12807 // Check for specific native-function optimizations. 12808 InlinableNativeIRGenerator nativeGen(*this, calleeFunc, target, newTarget, 12809 thisValue, args, targetFlags); 12810 TRY_ATTACH(nativeGen.tryAttachStub()); 12811 } 12812 12813 Int32OperandId argcId(writer.setInputOperandId(0)); 12814 ObjOperandId thisObjId = emitFunApplyGuard(argcId); 12815 12816 uint32_t fixedArgc; 12817 if (format == CallFlags::FunApplyArray || 12818 format == CallFlags::FunApplyArgsObj || 12819 format == CallFlags::FunApplyNullUndefined) { 12820 emitFunApplyArgsGuard(format); 12821 12822 // We always use MaxUnrolledArgCopy here because the fixed argc is 12823 // meaningless in a FunApply case. 12824 fixedArgc = MaxUnrolledArgCopy; 12825 } else { 12826 MOZ_ASSERT(format == CallFlags::FunCall); 12827 12828 // Whereas for the FunCall case we need to use the actual fixed argc value. 12829 fixedArgc = ClampFixedArgc(argc_); 12830 } 12831 12832 if (mode_ == ICState::Mode::Specialized) { 12833 // Ensure that |this| is the expected target function. 12834 emitCalleeGuard(thisObjId, target); 12835 12836 if (isScripted) { 12837 writer.callScriptedFunction(thisObjId, argcId, targetFlags, fixedArgc); 12838 } else { 12839 writer.callNativeFunction(thisObjId, argcId, jsop(), target, targetFlags, 12840 fixedArgc); 12841 } 12842 } else { 12843 // Guard that |this| is a function. 12844 writer.guardClass(thisObjId, GuardClassKind::JSFunction); 12845 12846 // Guard that function is not a class constructor. 12847 writer.guardNotClassConstructor(thisObjId); 12848 12849 if (isScripted) { 12850 // Guard that function is scripted. 12851 writer.guardFunctionHasJitEntry(thisObjId); 12852 writer.callScriptedFunction(thisObjId, argcId, targetFlags, fixedArgc); 12853 } else { 12854 // Guard that function is native. 12855 writer.guardFunctionHasNoJitEntry(thisObjId); 12856 writer.callAnyNativeFunction(thisObjId, argcId, targetFlags, fixedArgc); 12857 } 12858 } 12859 12860 writer.returnFromIC(); 12861 12862 if (isScripted) { 12863 trackAttached("Call.ScriptedFunApply"); 12864 } else { 12865 trackAttached("Call.NativeFunApply"); 12866 } 12867 12868 return AttachDecision::Attach; 12869 } 12870 12871 AttachDecision CallIRGenerator::tryAttachWasmCall(HandleFunction calleeFunc) { 12872 // Try to optimize calls into Wasm code by emitting the CallWasmFunction 12873 // CacheIR op. Baseline ICs currently treat this as a CallScriptedFunction op 12874 // (calling Wasm's JitEntry stub) but Warp transpiles it to a more direct call 12875 // into Wasm code. 12876 // 12877 // Note: some code refers to these optimized Wasm calls as "inlined" calls. 12878 12879 MOZ_ASSERT(calleeFunc->isWasmWithJitEntry()); 12880 12881 if (!JitOptions.enableWasmIonFastCalls) { 12882 return AttachDecision::NoAction; 12883 } 12884 if (!isFirstStub_) { 12885 return AttachDecision::NoAction; 12886 } 12887 JSOp op = JSOp(*pc_); 12888 if (op != JSOp::Call && op != JSOp::CallContent && 12889 op != JSOp::CallIgnoresRv) { 12890 return AttachDecision::NoAction; 12891 } 12892 if (cx_->realm() != calleeFunc->realm()) { 12893 return AttachDecision::NoAction; 12894 } 12895 12896 wasm::Instance& inst = calleeFunc->wasmInstance(); 12897 uint32_t funcIndex = calleeFunc->wasmFuncIndex(); 12898 const wasm::CodeBlock& codeBlock = inst.code().funcCodeBlock(funcIndex); 12899 const wasm::FuncExport& funcExport = codeBlock.lookupFuncExport(funcIndex); 12900 const wasm::FuncType& sig = calleeFunc->wasmTypeDef()->funcType(); 12901 12902 MOZ_ASSERT(!IsInsideNursery(inst.object())); 12903 MOZ_ASSERT(sig.canHaveJitEntry(), "Function should allow a Wasm JitEntry"); 12904 12905 // If there are too many arguments, don't optimize (we won't be able to store 12906 // the arguments in the LIR node). 12907 static_assert(wasm::MaxArgsForJitInlineCall <= ArgumentKindArgIndexLimit); 12908 if (sig.args().length() > wasm::MaxArgsForJitInlineCall || 12909 argc_ > ArgumentKindArgIndexLimit) { 12910 return AttachDecision::NoAction; 12911 } 12912 12913 // If there are too many results, don't optimize as Warp currently doesn't 12914 // have code to handle this. 12915 if (sig.results().length() > wasm::MaxResultsForJitInlineCall) { 12916 return AttachDecision::NoAction; 12917 } 12918 12919 // Bug 1631656 - Don't try to optimize with I64 args on 32-bit platforms 12920 // because it is more difficult (because it requires multiple LIR arguments 12921 // per I64). 12922 // 12923 // Bug 1631650 - On 64-bit platforms, we also give up optimizing for I64 args 12924 // spilled to the stack because it causes problems with register allocation. 12925 #ifdef JS_64BIT 12926 constexpr bool optimizeWithI64 = true; 12927 #else 12928 constexpr bool optimizeWithI64 = false; 12929 #endif 12930 ABIArgGenerator abi(ABIKind::Wasm); 12931 for (const auto& valType : sig.args()) { 12932 MIRType mirType = valType.toMIRType(); 12933 ABIArg abiArg = abi.next(mirType); 12934 if (mirType != MIRType::Int64) { 12935 continue; 12936 } 12937 if (!optimizeWithI64 || abiArg.kind() == ABIArg::Stack) { 12938 return AttachDecision::NoAction; 12939 } 12940 } 12941 12942 // Check that all arguments can be converted to the Wasm type in Warp code 12943 // without bailing out. 12944 for (size_t i = 0; i < sig.args().length(); i++) { 12945 Value argVal = i < argc_ ? args_[i] : UndefinedValue(); 12946 switch (sig.args()[i].kind()) { 12947 case wasm::ValType::I32: 12948 case wasm::ValType::F32: 12949 case wasm::ValType::F64: 12950 if (!argVal.isNumber() && !argVal.isBoolean() && 12951 !argVal.isUndefined()) { 12952 return AttachDecision::NoAction; 12953 } 12954 break; 12955 case wasm::ValType::I64: 12956 if (!argVal.isBigInt() && !argVal.isBoolean() && !argVal.isString()) { 12957 return AttachDecision::NoAction; 12958 } 12959 break; 12960 case wasm::ValType::V128: 12961 MOZ_CRASH("Function should not have a Wasm JitEntry"); 12962 case wasm::ValType::Ref: 12963 // canHaveJitEntry restricts args to externref, where all JS values are 12964 // valid and can be boxed. 12965 MOZ_ASSERT(sig.args()[i].refType().isExtern(), 12966 "Unexpected type for Wasm JitEntry"); 12967 break; 12968 } 12969 } 12970 12971 CallFlags flags(/* isConstructing = */ false, /* isSpread = */ false, 12972 /* isSameRealm = */ true); 12973 12974 // Load argc. 12975 Int32OperandId argcId(writer.setInputOperandId(0)); 12976 12977 // Load the callee and ensure it is an object 12978 ValOperandId calleeValId = 12979 writer.loadArgumentFixedSlot(ArgumentKind::Callee, argc_, flags); 12980 ObjOperandId calleeObjId = writer.guardToObject(calleeValId); 12981 12982 // Ensure the callee is this Wasm function. 12983 emitCalleeGuard(calleeObjId, calleeFunc); 12984 12985 // Guard the argument types. 12986 uint32_t guardedArgs = std::min<uint32_t>(sig.args().length(), argc_); 12987 for (uint32_t i = 0; i < guardedArgs; i++) { 12988 ArgumentKind argKind = ArgumentKindForArgIndex(i); 12989 ValOperandId argId = writer.loadArgumentFixedSlot(argKind, argc_, flags); 12990 writer.guardWasmArg(argId, sig.args()[i].kind()); 12991 } 12992 12993 writer.callWasmFunction(calleeObjId, argcId, flags, ClampFixedArgc(argc_), 12994 &funcExport, inst.object()); 12995 writer.returnFromIC(); 12996 12997 trackAttached("Call.WasmCall"); 12998 12999 return AttachDecision::Attach; 13000 } 13001 13002 AttachDecision CallIRGenerator::tryAttachInlinableNative(HandleFunction callee, 13003 CallFlags flags) { 13004 MOZ_ASSERT(mode_ == ICState::Mode::Specialized); 13005 MOZ_ASSERT(callee->isNativeWithoutJitEntry()); 13006 MOZ_ASSERT(flags.getArgFormat() == CallFlags::Standard || 13007 flags.getArgFormat() == CallFlags::Spread); 13008 13009 InlinableNativeIRGenerator nativeGen(*this, callee, callee, newTarget_, 13010 thisval_, args_, flags); 13011 return nativeGen.tryAttachStub(); 13012 } 13013 13014 #ifdef FUZZING_JS_FUZZILLI 13015 AttachDecision InlinableNativeIRGenerator::tryAttachFuzzilliHash() { 13016 if (args_.length() != 1) { 13017 return AttachDecision::NoAction; 13018 } 13019 13020 // Initialize the input operand. 13021 Int32OperandId argcId = initializeInputOperand(); 13022 13023 // Guard callee is the 'fuzzilli_hash' native function. 13024 ObjOperandId calleeId = emitNativeCalleeGuard(argcId); 13025 13026 ValOperandId argValId = loadArgument(calleeId, ArgumentKind::Arg0); 13027 13028 writer.fuzzilliHashResult(argValId); 13029 writer.returnFromIC(); 13030 13031 trackAttached("FuzzilliHash"); 13032 return AttachDecision::Attach; 13033 } 13034 #endif 13035 13036 AttachDecision InlinableNativeIRGenerator::tryAttachStub() { 13037 MOZ_ASSERT(generator_.mode_ == ICState::Mode::Specialized); 13038 MOZ_ASSERT(target_->isNativeWithoutJitEntry()); 13039 13040 // Special case functions are only optimized for normal calls. 13041 if (!BytecodeCallOpCanHaveInlinableNative(op()) && 13042 !BytecodeGetOpCanHaveInlinableNative(op())) { 13043 return AttachDecision::NoAction; 13044 } 13045 13046 if (!target_->hasJitInfo() || 13047 target_->jitInfo()->type() != JSJitInfo::InlinableNative) { 13048 return AttachDecision::NoAction; 13049 } 13050 13051 InlinableNative native = target_->jitInfo()->inlinableNative; 13052 13053 // Not all natives can be inlined cross-realm. 13054 if (cx_->realm() != target_->realm() && !CanInlineNativeCrossRealm(native)) { 13055 return AttachDecision::NoAction; 13056 } 13057 13058 // Check for special-cased native constructors. 13059 if (flags_.isConstructing()) { 13060 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Standard); 13061 13062 // newTarget must match the callee. CacheIR for this is emitted in 13063 // emitNativeCalleeGuard. 13064 if (ObjectValue(*callee()) != newTarget_) { 13065 return AttachDecision::NoAction; 13066 } 13067 switch (native) { 13068 case InlinableNative::Array: 13069 return tryAttachArrayConstructor(); 13070 case InlinableNative::TypedArrayConstructor: 13071 return tryAttachTypedArrayConstructor(); 13072 case InlinableNative::MapConstructor: 13073 case InlinableNative::SetConstructor: 13074 return tryAttachMapSetConstructor(native); 13075 case InlinableNative::String: 13076 return tryAttachStringConstructor(); 13077 case InlinableNative::Object: 13078 return tryAttachObjectConstructor(); 13079 default: 13080 break; 13081 } 13082 return AttachDecision::NoAction; 13083 } 13084 13085 // Check for special-cased native spread calls. 13086 if (flags_.getArgFormat() == CallFlags::Spread || 13087 flags_.getArgFormat() == CallFlags::FunApplyArray) { 13088 // Can't inline spread calls when bound arguments are present. 13089 MOZ_ASSERT(!hasBoundArguments()); 13090 13091 // Note: we haven't compared args.length() to JIT_ARGS_LENGTH_MAX 13092 // yet, to support natives that can handle more arguments without 13093 // having to push them on the stack. Spread-call inlined natives 13094 // are responsible for enforcing the limit on themselves if 13095 // necessary. 13096 switch (native) { 13097 case InlinableNative::MathMin: 13098 return tryAttachSpreadMathMinMax(/*isMax = */ false); 13099 case InlinableNative::MathMax: 13100 return tryAttachSpreadMathMinMax(/*isMax = */ true); 13101 default: 13102 break; 13103 } 13104 return AttachDecision::NoAction; 13105 } 13106 13107 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Standard || 13108 flags_.getArgFormat() == CallFlags::FunCall || 13109 flags_.getArgFormat() == CallFlags::FunApplyNullUndefined); 13110 13111 // Check for special-cased native functions. 13112 switch (native) { 13113 // Array natives. 13114 case InlinableNative::Array: 13115 return tryAttachArrayConstructor(); 13116 case InlinableNative::ArrayPush: 13117 return tryAttachArrayPush(); 13118 case InlinableNative::ArrayPop: 13119 case InlinableNative::ArrayShift: 13120 return tryAttachArrayPopShift(native); 13121 case InlinableNative::ArrayJoin: 13122 return tryAttachArrayJoin(); 13123 case InlinableNative::ArraySlice: 13124 return tryAttachArraySlice(); 13125 case InlinableNative::ArrayIsArray: 13126 return tryAttachArrayIsArray(); 13127 13128 // DataView natives. 13129 case InlinableNative::DataViewGetInt8: 13130 return tryAttachDataViewGet(Scalar::Int8); 13131 case InlinableNative::DataViewGetUint8: 13132 return tryAttachDataViewGet(Scalar::Uint8); 13133 case InlinableNative::DataViewGetInt16: 13134 return tryAttachDataViewGet(Scalar::Int16); 13135 case InlinableNative::DataViewGetUint16: 13136 return tryAttachDataViewGet(Scalar::Uint16); 13137 case InlinableNative::DataViewGetInt32: 13138 return tryAttachDataViewGet(Scalar::Int32); 13139 case InlinableNative::DataViewGetUint32: 13140 return tryAttachDataViewGet(Scalar::Uint32); 13141 case InlinableNative::DataViewGetFloat16: 13142 return tryAttachDataViewGet(Scalar::Float16); 13143 case InlinableNative::DataViewGetFloat32: 13144 return tryAttachDataViewGet(Scalar::Float32); 13145 case InlinableNative::DataViewGetFloat64: 13146 return tryAttachDataViewGet(Scalar::Float64); 13147 case InlinableNative::DataViewGetBigInt64: 13148 return tryAttachDataViewGet(Scalar::BigInt64); 13149 case InlinableNative::DataViewGetBigUint64: 13150 return tryAttachDataViewGet(Scalar::BigUint64); 13151 case InlinableNative::DataViewSetInt8: 13152 return tryAttachDataViewSet(Scalar::Int8); 13153 case InlinableNative::DataViewSetUint8: 13154 return tryAttachDataViewSet(Scalar::Uint8); 13155 case InlinableNative::DataViewSetInt16: 13156 return tryAttachDataViewSet(Scalar::Int16); 13157 case InlinableNative::DataViewSetUint16: 13158 return tryAttachDataViewSet(Scalar::Uint16); 13159 case InlinableNative::DataViewSetInt32: 13160 return tryAttachDataViewSet(Scalar::Int32); 13161 case InlinableNative::DataViewSetUint32: 13162 return tryAttachDataViewSet(Scalar::Uint32); 13163 case InlinableNative::DataViewSetFloat16: 13164 return tryAttachDataViewSet(Scalar::Float16); 13165 case InlinableNative::DataViewSetFloat32: 13166 return tryAttachDataViewSet(Scalar::Float32); 13167 case InlinableNative::DataViewSetFloat64: 13168 return tryAttachDataViewSet(Scalar::Float64); 13169 case InlinableNative::DataViewSetBigInt64: 13170 return tryAttachDataViewSet(Scalar::BigInt64); 13171 case InlinableNative::DataViewSetBigUint64: 13172 return tryAttachDataViewSet(Scalar::BigUint64); 13173 case InlinableNative::DataViewByteLength: 13174 return tryAttachDataViewByteLength(); 13175 case InlinableNative::DataViewByteOffset: 13176 return tryAttachDataViewByteOffset(); 13177 13178 // Function natives. 13179 case InlinableNative::FunctionBind: 13180 return tryAttachFunctionBind(); 13181 13182 // Intl natives. 13183 case InlinableNative::IntlGuardToCollator: 13184 case InlinableNative::IntlGuardToDateTimeFormat: 13185 case InlinableNative::IntlGuardToDisplayNames: 13186 case InlinableNative::IntlGuardToDurationFormat: 13187 case InlinableNative::IntlGuardToListFormat: 13188 case InlinableNative::IntlGuardToNumberFormat: 13189 case InlinableNative::IntlGuardToPluralRules: 13190 case InlinableNative::IntlGuardToRelativeTimeFormat: 13191 case InlinableNative::IntlGuardToSegmenter: 13192 case InlinableNative::IntlGuardToSegments: 13193 case InlinableNative::IntlGuardToSegmentIterator: 13194 return tryAttachGuardToClass(native); 13195 13196 // Slot intrinsics. 13197 case InlinableNative::IntrinsicUnsafeGetReservedSlot: 13198 case InlinableNative::IntrinsicUnsafeGetObjectFromReservedSlot: 13199 case InlinableNative::IntrinsicUnsafeGetInt32FromReservedSlot: 13200 case InlinableNative::IntrinsicUnsafeGetStringFromReservedSlot: 13201 return tryAttachUnsafeGetReservedSlot(native); 13202 case InlinableNative::IntrinsicUnsafeSetReservedSlot: 13203 return tryAttachUnsafeSetReservedSlot(); 13204 13205 // Intrinsics. 13206 case InlinableNative::IntrinsicIsSuspendedGenerator: 13207 return tryAttachIsSuspendedGenerator(); 13208 case InlinableNative::IntrinsicToObject: 13209 return tryAttachToObject(); 13210 case InlinableNative::IntrinsicToInteger: 13211 return tryAttachToInteger(); 13212 case InlinableNative::IntrinsicToLength: 13213 return tryAttachToLength(); 13214 case InlinableNative::IntrinsicIsObject: 13215 return tryAttachIsObject(); 13216 case InlinableNative::IntrinsicIsPackedArray: 13217 return tryAttachIsPackedArray(); 13218 case InlinableNative::IntrinsicIsCallable: 13219 return tryAttachIsCallable(); 13220 case InlinableNative::IntrinsicIsConstructor: 13221 return tryAttachIsConstructor(); 13222 case InlinableNative::IntrinsicIsCrossRealmArrayConstructor: 13223 return tryAttachIsCrossRealmArrayConstructor(); 13224 case InlinableNative::IntrinsicCanOptimizeArraySpecies: 13225 return tryAttachCanOptimizeArraySpecies(); 13226 case InlinableNative::IntrinsicGuardToArrayIterator: 13227 case InlinableNative::IntrinsicGuardToMapIterator: 13228 case InlinableNative::IntrinsicGuardToSetIterator: 13229 case InlinableNative::IntrinsicGuardToStringIterator: 13230 case InlinableNative::IntrinsicGuardToRegExpStringIterator: 13231 case InlinableNative::IntrinsicGuardToWrapForValidIterator: 13232 case InlinableNative::IntrinsicGuardToIteratorHelper: 13233 #ifdef NIGHTLY_BUILD 13234 case InlinableNative::IntrinsicGuardToIteratorRange: 13235 #endif 13236 case InlinableNative::IntrinsicGuardToAsyncIteratorHelper: 13237 #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT 13238 case InlinableNative::IntrinsicGuardToAsyncDisposableStack: 13239 case InlinableNative::IntrinsicGuardToDisposableStack: 13240 #endif 13241 return tryAttachGuardToClass(native); 13242 case InlinableNative::IntrinsicSubstringKernel: 13243 return tryAttachSubstringKernel(); 13244 case InlinableNative::IntrinsicIsConstructing: 13245 return tryAttachIsConstructing(); 13246 case InlinableNative::IntrinsicNewArrayIterator: 13247 return tryAttachNewArrayIterator(); 13248 case InlinableNative::IntrinsicNewStringIterator: 13249 return tryAttachNewStringIterator(); 13250 case InlinableNative::IntrinsicNewRegExpStringIterator: 13251 return tryAttachNewRegExpStringIterator(); 13252 case InlinableNative::IntrinsicArrayIteratorPrototypeOptimizable: 13253 return tryAttachArrayIteratorPrototypeOptimizable(); 13254 13255 // RegExp natives. 13256 case InlinableNative::RegExpDotAll: 13257 return tryAttachRegExpFlag(JS::RegExpFlag::DotAll); 13258 case InlinableNative::RegExpGlobal: 13259 return tryAttachRegExpFlag(JS::RegExpFlag::Global); 13260 case InlinableNative::RegExpHasIndices: 13261 return tryAttachRegExpFlag(JS::RegExpFlag::HasIndices); 13262 case InlinableNative::RegExpIgnoreCase: 13263 return tryAttachRegExpFlag(JS::RegExpFlag::IgnoreCase); 13264 case InlinableNative::RegExpMultiline: 13265 return tryAttachRegExpFlag(JS::RegExpFlag::Multiline); 13266 case InlinableNative::RegExpSticky: 13267 return tryAttachRegExpFlag(JS::RegExpFlag::Sticky); 13268 case InlinableNative::RegExpUnicode: 13269 return tryAttachRegExpFlag(JS::RegExpFlag::Unicode); 13270 case InlinableNative::RegExpUnicodeSets: 13271 return tryAttachRegExpFlag(JS::RegExpFlag::UnicodeSets); 13272 case InlinableNative::IsRegExpObject: 13273 return tryAttachHasClass(&RegExpObject::class_, 13274 /* isPossiblyWrapped = */ false); 13275 case InlinableNative::IsPossiblyWrappedRegExpObject: 13276 return tryAttachHasClass(&RegExpObject::class_, 13277 /* isPossiblyWrapped = */ true); 13278 case InlinableNative::RegExpMatcher: 13279 case InlinableNative::RegExpSearcher: 13280 return tryAttachRegExpMatcherSearcher(native); 13281 case InlinableNative::RegExpSearcherLastLimit: 13282 return tryAttachRegExpSearcherLastLimit(); 13283 case InlinableNative::RegExpHasCaptureGroups: 13284 return tryAttachRegExpHasCaptureGroups(); 13285 case InlinableNative::IsRegExpPrototypeOptimizable: 13286 return tryAttachIsRegExpPrototypeOptimizable(); 13287 case InlinableNative::IsOptimizableRegExpObject: 13288 return tryAttachIsOptimizableRegExpObject(); 13289 case InlinableNative::GetFirstDollarIndex: 13290 return tryAttachGetFirstDollarIndex(); 13291 case InlinableNative::IntrinsicRegExpBuiltinExec: 13292 case InlinableNative::IntrinsicRegExpBuiltinExecForTest: 13293 return tryAttachIntrinsicRegExpBuiltinExec(native); 13294 case InlinableNative::IntrinsicRegExpExec: 13295 case InlinableNative::IntrinsicRegExpExecForTest: 13296 return tryAttachIntrinsicRegExpExec(native); 13297 13298 // String natives. 13299 case InlinableNative::String: 13300 return tryAttachString(); 13301 case InlinableNative::StringToString: 13302 case InlinableNative::StringValueOf: 13303 return tryAttachStringToStringValueOf(); 13304 case InlinableNative::StringCharCodeAt: 13305 return tryAttachStringCharCodeAt(); 13306 case InlinableNative::StringCodePointAt: 13307 return tryAttachStringCodePointAt(); 13308 case InlinableNative::StringCharAt: 13309 return tryAttachStringCharAt(); 13310 case InlinableNative::StringAt: 13311 return tryAttachStringAt(); 13312 case InlinableNative::StringFromCharCode: 13313 return tryAttachStringFromCharCode(); 13314 case InlinableNative::StringFromCodePoint: 13315 return tryAttachStringFromCodePoint(); 13316 case InlinableNative::StringIncludes: 13317 return tryAttachStringIncludes(); 13318 case InlinableNative::StringIndexOf: 13319 return tryAttachStringIndexOf(); 13320 case InlinableNative::StringLastIndexOf: 13321 return tryAttachStringLastIndexOf(); 13322 case InlinableNative::StringStartsWith: 13323 return tryAttachStringStartsWith(); 13324 case InlinableNative::StringEndsWith: 13325 return tryAttachStringEndsWith(); 13326 case InlinableNative::StringToLowerCase: 13327 return tryAttachStringToLowerCase(); 13328 case InlinableNative::StringToUpperCase: 13329 return tryAttachStringToUpperCase(); 13330 case InlinableNative::StringToLocaleLowerCase: 13331 return tryAttachStringToLocaleLowerCase(); 13332 case InlinableNative::StringToLocaleUpperCase: 13333 return tryAttachStringToLocaleUpperCase(); 13334 case InlinableNative::StringTrim: 13335 return tryAttachStringTrim(); 13336 case InlinableNative::StringTrimStart: 13337 return tryAttachStringTrimStart(); 13338 case InlinableNative::StringTrimEnd: 13339 return tryAttachStringTrimEnd(); 13340 case InlinableNative::IntrinsicStringReplaceString: 13341 return tryAttachStringReplaceString(); 13342 case InlinableNative::IntrinsicStringSplitString: 13343 return tryAttachStringSplitString(); 13344 13345 // Math natives. 13346 case InlinableNative::MathRandom: 13347 return tryAttachMathRandom(); 13348 case InlinableNative::MathAbs: 13349 return tryAttachMathAbs(); 13350 case InlinableNative::MathClz32: 13351 return tryAttachMathClz32(); 13352 case InlinableNative::MathSign: 13353 return tryAttachMathSign(); 13354 case InlinableNative::MathImul: 13355 return tryAttachMathImul(); 13356 case InlinableNative::MathFloor: 13357 return tryAttachMathFloor(); 13358 case InlinableNative::MathCeil: 13359 return tryAttachMathCeil(); 13360 case InlinableNative::MathTrunc: 13361 return tryAttachMathTrunc(); 13362 case InlinableNative::MathRound: 13363 return tryAttachMathRound(); 13364 case InlinableNative::MathSqrt: 13365 return tryAttachMathSqrt(); 13366 case InlinableNative::MathFRound: 13367 return tryAttachMathFRound(); 13368 case InlinableNative::MathF16Round: 13369 return tryAttachMathF16Round(); 13370 case InlinableNative::MathHypot: 13371 return tryAttachMathHypot(); 13372 case InlinableNative::MathATan2: 13373 return tryAttachMathATan2(); 13374 case InlinableNative::MathSin: 13375 return tryAttachMathFunction(UnaryMathFunction::SinNative); 13376 case InlinableNative::MathTan: 13377 return tryAttachMathFunction(UnaryMathFunction::TanNative); 13378 case InlinableNative::MathCos: 13379 return tryAttachMathFunction(UnaryMathFunction::CosNative); 13380 case InlinableNative::MathExp: 13381 return tryAttachMathFunction(UnaryMathFunction::Exp); 13382 case InlinableNative::MathLog: 13383 return tryAttachMathFunction(UnaryMathFunction::Log); 13384 case InlinableNative::MathASin: 13385 return tryAttachMathFunction(UnaryMathFunction::ASin); 13386 case InlinableNative::MathATan: 13387 return tryAttachMathFunction(UnaryMathFunction::ATan); 13388 case InlinableNative::MathACos: 13389 return tryAttachMathFunction(UnaryMathFunction::ACos); 13390 case InlinableNative::MathLog10: 13391 return tryAttachMathFunction(UnaryMathFunction::Log10); 13392 case InlinableNative::MathLog2: 13393 return tryAttachMathFunction(UnaryMathFunction::Log2); 13394 case InlinableNative::MathLog1P: 13395 return tryAttachMathFunction(UnaryMathFunction::Log1P); 13396 case InlinableNative::MathExpM1: 13397 return tryAttachMathFunction(UnaryMathFunction::ExpM1); 13398 case InlinableNative::MathCosH: 13399 return tryAttachMathFunction(UnaryMathFunction::CosH); 13400 case InlinableNative::MathSinH: 13401 return tryAttachMathFunction(UnaryMathFunction::SinH); 13402 case InlinableNative::MathTanH: 13403 return tryAttachMathFunction(UnaryMathFunction::TanH); 13404 case InlinableNative::MathACosH: 13405 return tryAttachMathFunction(UnaryMathFunction::ACosH); 13406 case InlinableNative::MathASinH: 13407 return tryAttachMathFunction(UnaryMathFunction::ASinH); 13408 case InlinableNative::MathATanH: 13409 return tryAttachMathFunction(UnaryMathFunction::ATanH); 13410 case InlinableNative::MathCbrt: 13411 return tryAttachMathFunction(UnaryMathFunction::Cbrt); 13412 case InlinableNative::MathPow: 13413 return tryAttachMathPow(); 13414 case InlinableNative::MathMin: 13415 return tryAttachMathMinMax(/* isMax = */ false); 13416 case InlinableNative::MathMax: 13417 return tryAttachMathMinMax(/* isMax = */ true); 13418 13419 // Map intrinsics. 13420 case InlinableNative::IntrinsicGuardToMapObject: 13421 return tryAttachGuardToClass(GuardClassKind::Map); 13422 case InlinableNative::IntrinsicGetNextMapEntryForIterator: 13423 return tryAttachGetNextMapSetEntryForIterator(/* isMap = */ true); 13424 13425 // Number natives. 13426 case InlinableNative::Number: 13427 return tryAttachNumber(); 13428 case InlinableNative::NumberParseInt: 13429 return tryAttachNumberParseInt(); 13430 case InlinableNative::NumberToString: 13431 return tryAttachNumberToString(); 13432 13433 // Object natives. 13434 case InlinableNative::Object: 13435 return tryAttachObjectConstructor(); 13436 case InlinableNative::ObjectCreate: 13437 return tryAttachObjectCreate(); 13438 case InlinableNative::ObjectIs: 13439 return tryAttachObjectIs(); 13440 case InlinableNative::ObjectIsPrototypeOf: 13441 return tryAttachObjectIsPrototypeOf(); 13442 case InlinableNative::ObjectKeys: 13443 return tryAttachObjectKeys(); 13444 case InlinableNative::ObjectToString: 13445 return tryAttachObjectToString(); 13446 13447 // Set intrinsics. 13448 case InlinableNative::IntrinsicGuardToSetObject: 13449 return tryAttachGuardToClass(GuardClassKind::Set); 13450 case InlinableNative::IntrinsicGetNextSetEntryForIterator: 13451 return tryAttachGetNextMapSetEntryForIterator(/* isMap = */ false); 13452 13453 // ArrayBuffer intrinsics. 13454 case InlinableNative::IntrinsicGuardToArrayBuffer: 13455 return tryAttachGuardToArrayBuffer(); 13456 13457 // SharedArrayBuffer intrinsics. 13458 case InlinableNative::IntrinsicGuardToSharedArrayBuffer: 13459 return tryAttachGuardToSharedArrayBuffer(); 13460 13461 // TypedArray natives. 13462 case InlinableNative::TypedArrayConstructor: 13463 return AttachDecision::NoAction; // Not callable. 13464 case InlinableNative::TypedArrayFill: 13465 return tryAttachTypedArrayFill(); 13466 case InlinableNative::TypedArraySet: 13467 return tryAttachTypedArraySet(); 13468 case InlinableNative::TypedArraySubarray: 13469 return tryAttachTypedArraySubarray(); 13470 case InlinableNative::TypedArrayLength: 13471 return tryAttachTypedArrayLength(); 13472 case InlinableNative::TypedArrayByteLength: 13473 return tryAttachTypedArrayByteLength(); 13474 case InlinableNative::TypedArrayByteOffset: 13475 return tryAttachTypedArrayByteOffset(); 13476 13477 // TypedArray intrinsics. 13478 case InlinableNative::IntrinsicIsTypedArray: 13479 return tryAttachIsTypedArray(/* isPossiblyWrapped = */ false); 13480 case InlinableNative::IntrinsicIsPossiblyWrappedTypedArray: 13481 return tryAttachIsTypedArray(/* isPossiblyWrapped = */ true); 13482 case InlinableNative::IntrinsicIsTypedArrayConstructor: 13483 return tryAttachIsTypedArrayConstructor(); 13484 case InlinableNative::IntrinsicTypedArrayLength: 13485 return tryAttachTypedArrayLength(/* isPossiblyWrapped = */ false); 13486 case InlinableNative::IntrinsicPossiblyWrappedTypedArrayLength: 13487 return tryAttachTypedArrayLength(/* isPossiblyWrapped = */ true); 13488 13489 // Reflect natives. 13490 case InlinableNative::ReflectGetPrototypeOf: 13491 return tryAttachReflectGetPrototypeOf(); 13492 13493 // Atomics intrinsics: 13494 case InlinableNative::AtomicsCompareExchange: 13495 return tryAttachAtomicsCompareExchange(); 13496 case InlinableNative::AtomicsExchange: 13497 return tryAttachAtomicsExchange(); 13498 case InlinableNative::AtomicsAdd: 13499 return tryAttachAtomicsAdd(); 13500 case InlinableNative::AtomicsSub: 13501 return tryAttachAtomicsSub(); 13502 case InlinableNative::AtomicsAnd: 13503 return tryAttachAtomicsAnd(); 13504 case InlinableNative::AtomicsOr: 13505 return tryAttachAtomicsOr(); 13506 case InlinableNative::AtomicsXor: 13507 return tryAttachAtomicsXor(); 13508 case InlinableNative::AtomicsLoad: 13509 return tryAttachAtomicsLoad(); 13510 case InlinableNative::AtomicsStore: 13511 return tryAttachAtomicsStore(); 13512 case InlinableNative::AtomicsIsLockFree: 13513 return tryAttachAtomicsIsLockFree(); 13514 case InlinableNative::AtomicsPause: 13515 return tryAttachAtomicsPause(); 13516 13517 // BigInt natives. 13518 case InlinableNative::BigInt: 13519 return tryAttachBigInt(); 13520 case InlinableNative::BigIntAsIntN: 13521 return tryAttachBigIntAsIntN(); 13522 case InlinableNative::BigIntAsUintN: 13523 return tryAttachBigIntAsUintN(); 13524 13525 // Boolean natives. 13526 case InlinableNative::Boolean: 13527 return tryAttachBoolean(); 13528 13529 // Set natives. 13530 case InlinableNative::SetConstructor: 13531 return AttachDecision::NoAction; // Not callable. 13532 case InlinableNative::SetHas: 13533 return tryAttachSetHas(); 13534 case InlinableNative::SetDelete: 13535 return tryAttachSetDelete(); 13536 case InlinableNative::SetAdd: 13537 return tryAttachSetAdd(); 13538 case InlinableNative::SetSize: 13539 return tryAttachSetSize(); 13540 13541 // Map natives. 13542 case InlinableNative::MapConstructor: 13543 return AttachDecision::NoAction; // Not callable. 13544 case InlinableNative::MapHas: 13545 return tryAttachMapHas(); 13546 case InlinableNative::MapGet: 13547 return tryAttachMapGet(); 13548 case InlinableNative::MapDelete: 13549 return tryAttachMapDelete(); 13550 case InlinableNative::MapSet: 13551 return tryAttachMapSet(); 13552 case InlinableNative::MapSize: 13553 return tryAttachMapSize(); 13554 13555 // Date natives and intrinsics. 13556 case InlinableNative::DateGetTime: 13557 return tryAttachDateGetTime(); 13558 case InlinableNative::DateGetFullYear: 13559 return tryAttachDateGet(DateComponent::FullYear); 13560 case InlinableNative::DateGetMonth: 13561 return tryAttachDateGet(DateComponent::Month); 13562 case InlinableNative::DateGetDate: 13563 return tryAttachDateGet(DateComponent::Date); 13564 case InlinableNative::DateGetDay: 13565 return tryAttachDateGet(DateComponent::Day); 13566 case InlinableNative::DateGetHours: 13567 return tryAttachDateGet(DateComponent::Hours); 13568 case InlinableNative::DateGetMinutes: 13569 return tryAttachDateGet(DateComponent::Minutes); 13570 case InlinableNative::DateGetSeconds: 13571 return tryAttachDateGet(DateComponent::Seconds); 13572 13573 // WeakMap/WeakSet natives. 13574 case InlinableNative::WeakMapGet: 13575 return tryAttachWeakMapGet(); 13576 case InlinableNative::WeakMapHas: 13577 return tryAttachWeakMapHas(); 13578 case InlinableNative::WeakSetHas: 13579 return tryAttachWeakSetHas(); 13580 13581 // ArrayBuffer natives. 13582 case InlinableNative::ArrayBufferByteLength: 13583 return tryAttachArrayBufferByteLength(); 13584 13585 // SharedArrayBuffer natives. 13586 case InlinableNative::SharedArrayBufferByteLength: 13587 return tryAttachSharedArrayBufferByteLength(); 13588 13589 // Testing functions. 13590 case InlinableNative::TestBailout: 13591 if (js::SupportDifferentialTesting()) { 13592 return AttachDecision::NoAction; 13593 } 13594 return tryAttachBailout(); 13595 case InlinableNative::TestAssertFloat32: 13596 return tryAttachAssertFloat32(); 13597 case InlinableNative::TestAssertRecoveredOnBailout: 13598 if (js::SupportDifferentialTesting()) { 13599 return AttachDecision::NoAction; 13600 } 13601 return tryAttachAssertRecoveredOnBailout(); 13602 13603 #ifdef FUZZING_JS_FUZZILLI 13604 // Fuzzilli function 13605 case InlinableNative::FuzzilliHash: 13606 return tryAttachFuzzilliHash(); 13607 #endif 13608 13609 case InlinableNative::Limit: 13610 break; 13611 } 13612 13613 MOZ_CRASH("Shouldn't get here"); 13614 } 13615 13616 // Remember the shape of the this object for any script being called as a 13617 // constructor, for later use during Ion compilation. 13618 ScriptedThisResult CallIRGenerator::getThisShapeForScripted( 13619 HandleFunction calleeFunc, Handle<JSObject*> newTarget, 13620 MutableHandle<Shape*> result) { 13621 // Some constructors allocate their own |this| object. 13622 if (calleeFunc->constructorNeedsUninitializedThis()) { 13623 return ScriptedThisResult::UninitializedThis; 13624 } 13625 13626 // Only attach a stub if the newTarget is a function with a 13627 // nonconfigurable prototype. 13628 if (!newTarget->is<JSFunction>() || 13629 !newTarget->as<JSFunction>().hasNonConfigurablePrototypeDataProperty()) { 13630 return ScriptedThisResult::NoAction; 13631 } 13632 13633 AutoRealm ar(cx_, calleeFunc); 13634 Shape* thisShape = ThisShapeForFunction(cx_, calleeFunc, newTarget); 13635 if (!thisShape) { 13636 cx_->clearPendingException(); 13637 return ScriptedThisResult::NoAction; 13638 } 13639 13640 MOZ_ASSERT(thisShape->realm() == calleeFunc->realm()); 13641 result.set(thisShape); 13642 return ScriptedThisResult::PlainObjectShape; 13643 } 13644 13645 static bool CanOptimizeScriptedCall(JSFunction* callee, bool isConstructing) { 13646 if (!callee->hasJitEntry()) { 13647 return false; 13648 } 13649 13650 // If callee is not an interpreted constructor, we have to throw. 13651 if (isConstructing && !callee->isConstructor()) { 13652 return false; 13653 } 13654 13655 // Likewise, if the callee is a class constructor, we have to throw. 13656 if (!isConstructing && callee->isClassConstructor()) { 13657 return false; 13658 } 13659 13660 return true; 13661 } 13662 13663 void CallIRGenerator::emitCallScriptedGuards(ObjOperandId calleeObjId, 13664 JSFunction* calleeFunc, 13665 Int32OperandId argcId, 13666 CallFlags flags, Shape* thisShape, 13667 bool isBoundFunction) { 13668 bool isConstructing = flags.isConstructing(); 13669 13670 if (mode_ == ICState::Mode::Specialized) { 13671 MOZ_ASSERT_IF(isConstructing, thisShape || flags.needsUninitializedThis()); 13672 13673 // Ensure callee matches this stub's callee 13674 emitCalleeGuard(calleeObjId, calleeFunc); 13675 if (thisShape) { 13676 // Emit guards to ensure the newTarget's .prototype property is what we 13677 // expect. Note that getThisForScripted checked newTarget is a function 13678 // with a non-configurable .prototype data property. 13679 13680 JSFunction* newTarget; 13681 ObjOperandId newTargetObjId; 13682 if (isBoundFunction) { 13683 newTarget = calleeFunc; 13684 newTargetObjId = calleeObjId; 13685 } else { 13686 newTarget = &newTarget_.toObject().as<JSFunction>(); 13687 ValOperandId newTargetValId = writer.loadArgumentDynamicSlot( 13688 ArgumentKind::NewTarget, argcId, flags); 13689 newTargetObjId = writer.guardToObject(newTargetValId); 13690 } 13691 13692 Maybe<PropertyInfo> prop = newTarget->lookupPure(cx_->names().prototype); 13693 MOZ_ASSERT(prop.isSome()); 13694 uint32_t slot = prop->slot(); 13695 MOZ_ASSERT(slot >= newTarget->numFixedSlots(), 13696 "Stub code relies on this"); 13697 13698 writer.guardShape(newTargetObjId, newTarget->shape()); 13699 13700 const Value& value = newTarget->getSlot(slot); 13701 if (value.isObject()) { 13702 JSObject* prototypeObject = &value.toObject(); 13703 13704 ObjOperandId protoId = writer.loadObject(prototypeObject); 13705 writer.guardDynamicSlotIsSpecificObject( 13706 newTargetObjId, protoId, slot - newTarget->numFixedSlots()); 13707 } else { 13708 writer.guardDynamicSlotIsNotObject(newTargetObjId, 13709 slot - newTarget->numFixedSlots()); 13710 } 13711 13712 // Call metaScriptedThisShape before emitting the call, so that Warp can 13713 // use the shape to create the |this| object before transpiling the call. 13714 writer.metaScriptedThisShape(thisShape); 13715 } 13716 } else { 13717 // Guard that object is a scripted function 13718 writer.guardClass(calleeObjId, GuardClassKind::JSFunction); 13719 writer.guardFunctionHasJitEntry(calleeObjId); 13720 13721 if (isConstructing) { 13722 // If callee is not a constructor, we have to throw. 13723 writer.guardFunctionIsConstructor(calleeObjId); 13724 } else { 13725 // If callee is a class constructor, we have to throw. 13726 writer.guardNotClassConstructor(calleeObjId); 13727 } 13728 } 13729 } 13730 13731 AttachDecision CallIRGenerator::tryAttachCallScripted( 13732 HandleFunction calleeFunc) { 13733 MOZ_ASSERT(calleeFunc->hasJitEntry()); 13734 13735 if (calleeFunc->isWasmWithJitEntry()) { 13736 TRY_ATTACH(tryAttachWasmCall(calleeFunc)); 13737 } 13738 13739 bool isSpecialized = mode_ == ICState::Mode::Specialized; 13740 13741 bool isConstructing = IsConstructPC(pc_); 13742 bool isSpread = IsSpreadPC(pc_); 13743 bool isSameRealm = isSpecialized && cx_->realm() == calleeFunc->realm(); 13744 CallFlags flags(isConstructing, isSpread, isSameRealm); 13745 13746 if (!CanOptimizeScriptedCall(calleeFunc, isConstructing)) { 13747 return AttachDecision::NoAction; 13748 } 13749 13750 // Verify that spread calls have a reasonable number of arguments. 13751 if (isSpread && args_.length() > JIT_ARGS_LENGTH_MAX) { 13752 return AttachDecision::NoAction; 13753 } 13754 13755 Rooted<Shape*> thisShape(cx_); 13756 if (isConstructing && isSpecialized) { 13757 Rooted<JSObject*> newTarget(cx_, &newTarget_.toObject()); 13758 switch (getThisShapeForScripted(calleeFunc, newTarget, &thisShape)) { 13759 case ScriptedThisResult::PlainObjectShape: 13760 break; 13761 case ScriptedThisResult::UninitializedThis: 13762 flags.setNeedsUninitializedThis(); 13763 break; 13764 case ScriptedThisResult::NoAction: 13765 return AttachDecision::NoAction; 13766 } 13767 } 13768 13769 // Load argc. 13770 Int32OperandId argcId(writer.setInputOperandId(0)); 13771 13772 // Load the callee and ensure it is an object 13773 ValOperandId calleeValId = 13774 writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId, flags); 13775 ObjOperandId calleeObjId = writer.guardToObject(calleeValId); 13776 13777 emitCallScriptedGuards(calleeObjId, calleeFunc, argcId, flags, thisShape, 13778 /* isBoundFunction = */ false); 13779 13780 writer.callScriptedFunction(calleeObjId, argcId, flags, 13781 ClampFixedArgc(argc_)); 13782 writer.returnFromIC(); 13783 13784 if (isSpecialized) { 13785 trackAttached("Call.CallScripted"); 13786 } else { 13787 trackAttached("Call.CallAnyScripted"); 13788 } 13789 13790 return AttachDecision::Attach; 13791 } 13792 13793 AttachDecision CallIRGenerator::tryAttachCallNative(HandleFunction calleeFunc) { 13794 MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry()); 13795 13796 bool isSpecialized = mode_ == ICState::Mode::Specialized; 13797 13798 bool isSpread = IsSpreadPC(pc_); 13799 bool isSameRealm = isSpecialized && cx_->realm() == calleeFunc->realm(); 13800 bool isConstructing = IsConstructPC(pc_); 13801 CallFlags flags(isConstructing, isSpread, isSameRealm); 13802 13803 if (isConstructing && !calleeFunc->isConstructor()) { 13804 return AttachDecision::NoAction; 13805 } 13806 13807 // Check for specific native-function optimizations. 13808 if (isSpecialized) { 13809 TRY_ATTACH(tryAttachInlinableNative(calleeFunc, flags)); 13810 } 13811 13812 // Verify that spread calls have a reasonable number of arguments. 13813 // We check this after trying to attach inlinable natives, because some 13814 // inlinable natives can safely ignore the limit. 13815 if (isSpread && args_.length() > JIT_ARGS_LENGTH_MAX) { 13816 return AttachDecision::NoAction; 13817 } 13818 13819 // Load argc. 13820 Int32OperandId argcId(writer.setInputOperandId(0)); 13821 13822 // Load the callee and ensure it is an object 13823 ValOperandId calleeValId = 13824 writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId, flags); 13825 ObjOperandId calleeObjId = writer.guardToObject(calleeValId); 13826 13827 // DOM calls need an additional guard so only try optimizing the first stub. 13828 // Can only optimize normal (non-spread) calls. 13829 if (isFirstStub_ && !isSpread && thisval_.isObject() && 13830 CanAttachDOMCall(cx_, JSJitInfo::Method, &thisval_.toObject(), calleeFunc, 13831 mode_)) { 13832 MOZ_ASSERT(!isConstructing, "DOM functions are not constructors"); 13833 13834 gc::AllocSite* allocSite = nullptr; 13835 if (calleeFunc->jitInfo()->returnType() == JSVAL_TYPE_OBJECT && 13836 JS::Prefs::dom_alloc_site()) { 13837 allocSite = maybeCreateAllocSite(); 13838 if (!allocSite) { 13839 return AttachDecision::NoAction; 13840 } 13841 } 13842 13843 // Guard that |this| is an object. 13844 ValOperandId thisValId = 13845 writer.loadArgumentDynamicSlot(ArgumentKind::This, argcId, flags); 13846 ObjOperandId thisObjId = writer.guardToObject(thisValId); 13847 13848 // Guard on the |this| shape to make sure it's the right instance. This also 13849 // ensures DOM_OBJECT_SLOT is stored in a fixed slot. See CanAttachDOMCall. 13850 writer.guardShape(thisObjId, thisval_.toObject().shape()); 13851 13852 // Ensure callee matches this stub's callee 13853 writer.guardSpecificFunction(calleeObjId, calleeFunc); 13854 13855 if (allocSite) { 13856 writer.callDOMFunctionWithAllocSite(calleeObjId, argcId, thisObjId, 13857 calleeFunc, flags, 13858 ClampFixedArgc(argc_), allocSite); 13859 } else { 13860 writer.callDOMFunction(calleeObjId, argcId, thisObjId, calleeFunc, flags, 13861 ClampFixedArgc(argc_)); 13862 } 13863 13864 trackAttached("Call.CallDOM"); 13865 } else if (isSpecialized) { 13866 // Ensure callee matches this stub's callee 13867 writer.guardSpecificFunction(calleeObjId, calleeFunc); 13868 writer.callNativeFunction(calleeObjId, argcId, jsop(), calleeFunc, flags, 13869 ClampFixedArgc(argc_)); 13870 13871 trackAttached("Call.CallNative"); 13872 } else { 13873 // Guard that object is a native function 13874 writer.guardClass(calleeObjId, GuardClassKind::JSFunction); 13875 writer.guardFunctionHasNoJitEntry(calleeObjId); 13876 13877 if (isConstructing) { 13878 // If callee is not a constructor, we have to throw. 13879 writer.guardFunctionIsConstructor(calleeObjId); 13880 } else { 13881 // If callee is a class constructor, we have to throw. 13882 writer.guardNotClassConstructor(calleeObjId); 13883 } 13884 writer.callAnyNativeFunction(calleeObjId, argcId, flags, 13885 ClampFixedArgc(argc_)); 13886 13887 trackAttached("Call.CallAnyNative"); 13888 } 13889 13890 writer.returnFromIC(); 13891 13892 return AttachDecision::Attach; 13893 } 13894 13895 AttachDecision CallIRGenerator::tryAttachCallHook(HandleObject calleeObj) { 13896 if (mode_ != ICState::Mode::Specialized) { 13897 // We do not have megamorphic call hook stubs. 13898 // TODO: Should we attach specialized call hook stubs in 13899 // megamorphic mode to avoid going generic? 13900 return AttachDecision::NoAction; 13901 } 13902 13903 bool isSpread = IsSpreadPC(pc_); 13904 bool isConstructing = IsConstructPC(pc_); 13905 CallFlags flags(isConstructing, isSpread); 13906 JSNative hook = 13907 isConstructing ? calleeObj->constructHook() : calleeObj->callHook(); 13908 if (!hook) { 13909 return AttachDecision::NoAction; 13910 } 13911 13912 // Bound functions have a JSClass construct hook but are not always 13913 // constructors. 13914 if (isConstructing && !calleeObj->isConstructor()) { 13915 return AttachDecision::NoAction; 13916 } 13917 13918 // We don't support spread calls in the transpiler yet. 13919 if (isSpread) { 13920 return AttachDecision::NoAction; 13921 } 13922 13923 // Load argc. 13924 Int32OperandId argcId(writer.setInputOperandId(0)); 13925 13926 // Load the callee and ensure it is an object 13927 ValOperandId calleeValId = 13928 writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId, flags); 13929 ObjOperandId calleeObjId = writer.guardToObject(calleeValId); 13930 13931 // Ensure the callee's class matches the one in this stub. 13932 writer.guardAnyClass(calleeObjId, calleeObj->getClass()); 13933 13934 if (isConstructing && calleeObj->is<BoundFunctionObject>()) { 13935 writer.guardBoundFunctionIsConstructor(calleeObjId); 13936 } 13937 13938 writer.callClassHook(calleeObjId, argcId, hook, flags, ClampFixedArgc(argc_)); 13939 writer.returnFromIC(); 13940 13941 trackAttached("Call.CallHook"); 13942 13943 return AttachDecision::Attach; 13944 } 13945 13946 AttachDecision CallIRGenerator::tryAttachBoundFunction( 13947 Handle<BoundFunctionObject*> calleeObj) { 13948 // The target must be a JSFunction with a JitEntry. 13949 if (!calleeObj->getTarget()->is<JSFunction>()) { 13950 return AttachDecision::NoAction; 13951 } 13952 13953 bool isSpread = IsSpreadPC(pc_); 13954 bool isConstructing = IsConstructPC(pc_); 13955 13956 // Spread calls are not supported yet. 13957 if (isSpread) { 13958 return AttachDecision::NoAction; 13959 } 13960 13961 Rooted<JSFunction*> target(cx_, &calleeObj->getTarget()->as<JSFunction>()); 13962 if (!CanOptimizeScriptedCall(target, isConstructing)) { 13963 return AttachDecision::NoAction; 13964 } 13965 13966 // Limit the number of bound arguments to prevent us from compiling many 13967 // different stubs (we bake in numBoundArgs and it's usually very small). 13968 static constexpr size_t MaxBoundArgs = 10; 13969 size_t numBoundArgs = calleeObj->numBoundArgs(); 13970 if (numBoundArgs > MaxBoundArgs) { 13971 return AttachDecision::NoAction; 13972 } 13973 13974 // Ensure we don't exceed JIT_ARGS_LENGTH_MAX. 13975 if (numBoundArgs + argc_ > JIT_ARGS_LENGTH_MAX) { 13976 return AttachDecision::NoAction; 13977 } 13978 13979 CallFlags flags(isConstructing, isSpread); 13980 13981 if (mode_ == ICState::Mode::Specialized) { 13982 if (cx_->realm() == target->realm()) { 13983 flags.setIsSameRealm(); 13984 } 13985 } 13986 13987 Rooted<Shape*> thisShape(cx_); 13988 if (isConstructing) { 13989 // Only optimize if newTarget == callee. This is the common case and ensures 13990 // we can always pass the bound function's target as newTarget. 13991 if (newTarget_ != ObjectValue(*calleeObj)) { 13992 return AttachDecision::NoAction; 13993 } 13994 13995 if (mode_ == ICState::Mode::Specialized) { 13996 Handle<JSFunction*> newTarget = target; 13997 switch (getThisShapeForScripted(target, newTarget, &thisShape)) { 13998 case ScriptedThisResult::PlainObjectShape: 13999 break; 14000 case ScriptedThisResult::UninitializedThis: 14001 flags.setNeedsUninitializedThis(); 14002 break; 14003 case ScriptedThisResult::NoAction: 14004 return AttachDecision::NoAction; 14005 } 14006 } 14007 } 14008 14009 // Load argc. 14010 Int32OperandId argcId(writer.setInputOperandId(0)); 14011 14012 // Load the callee and ensure it's a bound function. 14013 ValOperandId calleeValId = 14014 writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId, flags); 14015 ObjOperandId calleeObjId = writer.guardToObject(calleeValId); 14016 writer.guardClass(calleeObjId, GuardClassKind::BoundFunction); 14017 14018 // Ensure numBoundArgs matches. 14019 Int32OperandId numBoundArgsId = writer.loadBoundFunctionNumArgs(calleeObjId); 14020 writer.guardSpecificInt32(numBoundArgsId, numBoundArgs); 14021 14022 if (isConstructing) { 14023 // Guard newTarget == callee. We depend on this in CallBoundScriptedFunction 14024 // and in emitCallScriptedGuards by using boundTarget as newTarget. 14025 ValOperandId newTargetValId = 14026 writer.loadArgumentDynamicSlot(ArgumentKind::NewTarget, argcId, flags); 14027 ObjOperandId newTargetObjId = writer.guardToObject(newTargetValId); 14028 writer.guardObjectIdentity(newTargetObjId, calleeObjId); 14029 } 14030 14031 ObjOperandId targetId = writer.loadBoundFunctionTarget(calleeObjId); 14032 14033 emitCallScriptedGuards(targetId, target, argcId, flags, thisShape, 14034 /* isBoundFunction = */ true); 14035 14036 writer.callBoundScriptedFunction(calleeObjId, targetId, argcId, flags, 14037 numBoundArgs); 14038 writer.returnFromIC(); 14039 14040 trackAttached("Call.BoundFunction"); 14041 return AttachDecision::Attach; 14042 } 14043 14044 AttachDecision CallIRGenerator::tryAttachBoundNative( 14045 Handle<BoundFunctionObject*> calleeObj) { 14046 // The target must be a native JSFunction without a JitEntry. 14047 Rooted<JSObject*> boundTarget(cx_, calleeObj->getTarget()); 14048 if (!boundTarget->is<JSFunction>()) { 14049 return AttachDecision::NoAction; 14050 } 14051 auto target = boundTarget.as<JSFunction>(); 14052 14053 bool isScripted = target->hasJitEntry(); 14054 MOZ_ASSERT_IF(!isScripted, target->isNativeWithoutJitEntry()); 14055 14056 if (isScripted) { 14057 return AttachDecision::NoAction; 14058 } 14059 14060 // Limit the number of bound arguments to prevent us from compiling many 14061 // different stubs (we bake in numBoundArgs and it's usually very small). 14062 static constexpr size_t MaxBoundArgs = 10; 14063 size_t numBoundArgs = calleeObj->numBoundArgs(); 14064 if (numBoundArgs > MaxBoundArgs) { 14065 return AttachDecision::NoAction; 14066 } 14067 14068 // Ensure we don't exceed JIT_ARGS_LENGTH_MAX. 14069 if (numBoundArgs + argc_ > JIT_ARGS_LENGTH_MAX) { 14070 return AttachDecision::NoAction; 14071 } 14072 14073 // Don't try to optimize when we're already megamorphic. 14074 if (mode_ != ICState::Mode::Specialized) { 14075 return AttachDecision::NoAction; 14076 } 14077 14078 bool isSpread = IsSpreadPC(pc_); 14079 bool isSameRealm = cx_->realm() == target->realm(); 14080 bool isConstructing = IsConstructPC(pc_); 14081 CallFlags flags(isConstructing, isSpread, isSameRealm); 14082 14083 if (isConstructing && !target->isConstructor()) { 14084 return AttachDecision::NoAction; 14085 } 14086 14087 // Verify that spread calls have a reasonable number of arguments. 14088 if (isSpread && args_.length() > JIT_ARGS_LENGTH_MAX) { 14089 return AttachDecision::NoAction; 14090 } 14091 14092 // Spread calls are only supported when we don't have to insert bound args. 14093 if (isSpread && numBoundArgs != 0) { 14094 return AttachDecision::NoAction; 14095 } 14096 14097 // Use the bound |this| value. 14098 Rooted<Value> thisValue(cx_, calleeObj->getBoundThis()); 14099 14100 // Concatenate the bound arguments and the stack arguments. 14101 JS::RootedVector<Value> concatenatedArgs(cx_); 14102 if (numBoundArgs != 0) { 14103 if (!concatenatedArgs.reserve(numBoundArgs + args_.length())) { 14104 cx_->recoverFromOutOfMemory(); 14105 return AttachDecision::NoAction; 14106 } 14107 14108 for (size_t i = 0; i < numBoundArgs; i++) { 14109 concatenatedArgs.infallibleAppend(calleeObj->getBoundArg(i)); 14110 } 14111 concatenatedArgs.infallibleAppend(args_.begin(), args_.length()); 14112 } 14113 auto args = numBoundArgs != 0 ? concatenatedArgs : args_; 14114 14115 // Check for specific native-function optimizations. 14116 InlinableNativeIRGenerator nativeGen(*this, calleeObj, target, newTarget_, 14117 thisValue, args, flags); 14118 return nativeGen.tryAttachStub(); 14119 } 14120 14121 static bool IsInlinableFunCallOrApply(JSOp op) { 14122 return op == JSOp::Call || op == JSOp::CallContent || 14123 op == JSOp::CallIgnoresRv; 14124 } 14125 14126 AttachDecision CallIRGenerator::tryAttachBoundFunCall( 14127 Handle<BoundFunctionObject*> calleeObj) { 14128 // Only optimize fun_call for simple calls. 14129 if (!IsInlinableFunCallOrApply(jsop())) { 14130 return AttachDecision::NoAction; 14131 } 14132 14133 // The target must be a native JSFunction to fun_call. 14134 JSObject* boundTarget = calleeObj->getTarget(); 14135 if (!boundTarget->is<JSFunction>()) { 14136 return AttachDecision::NoAction; 14137 } 14138 auto* boundTargetFn = &boundTarget->as<JSFunction>(); 14139 14140 bool isScripted = boundTargetFn->hasJitEntry(); 14141 MOZ_ASSERT_IF(!isScripted, boundTargetFn->isNativeWithoutJitEntry()); 14142 14143 if (isScripted || boundTargetFn->native() != fun_call) { 14144 return AttachDecision::NoAction; 14145 } 14146 14147 // Limit the number of bound arguments to prevent us from compiling many 14148 // different stubs (we bake in numBoundArgs and it's usually very small). 14149 static constexpr size_t MaxBoundArgs = 10; 14150 size_t numBoundArgs = calleeObj->numBoundArgs(); 14151 if (numBoundArgs > MaxBoundArgs) { 14152 return AttachDecision::NoAction; 14153 } 14154 14155 // Ensure we don't exceed JIT_ARGS_LENGTH_MAX. 14156 if (numBoundArgs + argc_ > JIT_ARGS_LENGTH_MAX) { 14157 return AttachDecision::NoAction; 14158 } 14159 14160 // Don't try to optimize when we're already megamorphic. 14161 if (mode_ != ICState::Mode::Specialized) { 14162 return AttachDecision::NoAction; 14163 } 14164 14165 JSFunction* boundThis; 14166 if (!IsFunctionObject(calleeObj->getBoundThis(), &boundThis)) { 14167 return AttachDecision::NoAction; 14168 } 14169 14170 bool boundThisIsScripted = boundThis->hasJitEntry(); 14171 MOZ_ASSERT_IF(!boundThisIsScripted, boundThis->isNativeWithoutJitEntry()); 14172 14173 if (boundThisIsScripted) { 14174 return AttachDecision::NoAction; 14175 } 14176 14177 CallFlags targetFlags(CallFlags::FunCall); 14178 if (cx_->realm() == boundThis->realm()) { 14179 targetFlags.setIsSameRealm(); 14180 } 14181 14182 Rooted<JSFunction*> target(cx_, boundThis); 14183 HandleValue newTarget = NullHandleValue; 14184 14185 Rooted<Value> thisValue(cx_); 14186 if (numBoundArgs > 0) { 14187 thisValue = calleeObj->getBoundArg(0); 14188 } else if (argc_ > 0) { 14189 thisValue = args_[0]; 14190 } else { 14191 MOZ_ASSERT(thisValue.isUndefined()); 14192 } 14193 14194 // Concatenate the bound arguments and the stack arguments. 14195 JS::RootedVector<Value> concatenatedArgs(cx_); 14196 if (numBoundArgs > 1) { 14197 if (!concatenatedArgs.reserve((numBoundArgs - 1) + args_.length())) { 14198 cx_->recoverFromOutOfMemory(); 14199 return AttachDecision::NoAction; 14200 } 14201 14202 for (size_t i = 1; i < numBoundArgs; i++) { 14203 concatenatedArgs.infallibleAppend(calleeObj->getBoundArg(i)); 14204 } 14205 concatenatedArgs.infallibleAppend(args_.begin(), args_.length()); 14206 } 14207 auto args = ([&]() -> HandleValueArray { 14208 if (numBoundArgs > 1) { 14209 // Return |concatenatedArgs| if there are any bound arguments. 14210 return concatenatedArgs; 14211 } 14212 if (numBoundArgs > 0) { 14213 // Return |args_| if only the |this| value is bound. 14214 return args_; 14215 } 14216 if (argc_ > 0) { 14217 // Nothing bound at all, return stack arguments starting from |args[1]|. 14218 return HandleValueArray::subarray(args_, 1, args_.length() - 1); 14219 } 14220 // No arguments at all. 14221 return HandleValueArray::empty(); 14222 })(); 14223 14224 // Check for specific native-function optimizations. 14225 InlinableNativeIRGenerator nativeGen(*this, calleeObj, target, newTarget, 14226 thisValue, args, targetFlags); 14227 return nativeGen.tryAttachStub(); 14228 } 14229 14230 AttachDecision CallIRGenerator::tryAttachBoundFunApply( 14231 Handle<BoundFunctionObject*> calleeObj) { 14232 // Only optimize fun_apply for simple calls. 14233 if (!IsInlinableFunCallOrApply(jsop())) { 14234 return AttachDecision::NoAction; 14235 } 14236 14237 // The target must be a native JSFunction to fun_apply. 14238 JSObject* boundTarget = calleeObj->getTarget(); 14239 if (!boundTarget->is<JSFunction>()) { 14240 return AttachDecision::NoAction; 14241 } 14242 auto* boundTargetFn = &boundTarget->as<JSFunction>(); 14243 14244 bool isScripted = boundTargetFn->hasJitEntry(); 14245 MOZ_ASSERT_IF(!isScripted, boundTargetFn->isNativeWithoutJitEntry()); 14246 14247 if (isScripted || boundTargetFn->native() != fun_apply) { 14248 return AttachDecision::NoAction; 14249 } 14250 14251 size_t numBoundArgs = calleeObj->numBoundArgs(); 14252 if (numBoundArgs + argc_ > 2) { 14253 return AttachDecision::NoAction; 14254 } 14255 14256 // Don't try to optimize when we're already megamorphic. 14257 if (mode_ != ICState::Mode::Specialized) { 14258 return AttachDecision::NoAction; 14259 } 14260 14261 JSFunction* boundThis; 14262 if (!IsFunctionObject(calleeObj->getBoundThis(), &boundThis)) { 14263 return AttachDecision::NoAction; 14264 } 14265 14266 bool boundThisIsScripted = boundThis->hasJitEntry(); 14267 MOZ_ASSERT_IF(!boundThisIsScripted, boundThis->isNativeWithoutJitEntry()); 14268 14269 if (boundThisIsScripted) { 14270 return AttachDecision::NoAction; 14271 } 14272 14273 // The second argument must be |null| or |undefined|, because we only support 14274 // |CallFlags::FunCall| and |CallFlags::FunApplyNullUndefined|. 14275 CallFlags::ArgFormat format; 14276 if (numBoundArgs + argc_ < 2) { 14277 format = CallFlags::FunCall; 14278 } else { 14279 Value arg; 14280 if (numBoundArgs == 2) { 14281 arg = calleeObj->getBoundArg(1); 14282 } else if (numBoundArgs == 1) { 14283 arg = args_[0]; 14284 } else { 14285 arg = args_[1]; 14286 } 14287 if (!arg.isNullOrUndefined()) { 14288 return AttachDecision::NoAction; 14289 } 14290 format = CallFlags::FunApplyNullUndefined; 14291 } 14292 14293 CallFlags targetFlags(format); 14294 if (cx_->realm() == boundThis->realm()) { 14295 targetFlags.setIsSameRealm(); 14296 } 14297 14298 Rooted<JSFunction*> target(cx_, boundThis); 14299 HandleValue newTarget = NullHandleValue; 14300 14301 Rooted<Value> thisValue(cx_); 14302 if (numBoundArgs > 0) { 14303 thisValue = calleeObj->getBoundArg(0); 14304 } else if (argc_ > 0) { 14305 thisValue = args_[0]; 14306 } else { 14307 MOZ_ASSERT(thisValue.isUndefined()); 14308 } 14309 HandleValueArray args = HandleValueArray::empty(); 14310 14311 // Check for specific native-function optimizations. 14312 InlinableNativeIRGenerator nativeGen(*this, calleeObj, target, newTarget, 14313 thisValue, args, targetFlags); 14314 return nativeGen.tryAttachStub(); 14315 } 14316 14317 AttachDecision CallIRGenerator::tryAttachFunCallBound( 14318 Handle<JSFunction*> callee) { 14319 MOZ_ASSERT(callee->isNativeWithoutJitEntry()); 14320 14321 if (callee->native() != fun_call) { 14322 return AttachDecision::NoAction; 14323 } 14324 14325 if (!thisval_.isObject() || !thisval_.toObject().is<BoundFunctionObject>()) { 14326 return AttachDecision::NoAction; 14327 } 14328 Rooted<BoundFunctionObject*> bound( 14329 cx_, &thisval_.toObject().as<BoundFunctionObject>()); 14330 14331 // The target must be a native JSFunction without a JitEntry. 14332 Rooted<JSObject*> boundTarget(cx_, bound->getTarget()); 14333 if (!boundTarget->is<JSFunction>()) { 14334 return AttachDecision::NoAction; 14335 } 14336 auto target = boundTarget.as<JSFunction>(); 14337 14338 bool isScripted = target->hasJitEntry(); 14339 MOZ_ASSERT_IF(!isScripted, target->isNativeWithoutJitEntry()); 14340 14341 // We don't yet supported scripted bound targets. 14342 if (isScripted) { 14343 return AttachDecision::NoAction; 14344 } 14345 14346 // Limit the number of bound arguments to prevent us from compiling many 14347 // different stubs (we bake in numBoundArgs and it's usually very small). 14348 static constexpr size_t MaxBoundArgs = 10; 14349 size_t numBoundArgs = bound->numBoundArgs(); 14350 if (numBoundArgs > MaxBoundArgs) { 14351 return AttachDecision::NoAction; 14352 } 14353 14354 // Ensure we don't exceed JIT_ARGS_LENGTH_MAX. 14355 if (numBoundArgs + argc_ > JIT_ARGS_LENGTH_MAX) { 14356 return AttachDecision::NoAction; 14357 } 14358 14359 // Don't try to optimize when we're already megamorphic. 14360 if (mode_ != ICState::Mode::Specialized) { 14361 return AttachDecision::NoAction; 14362 } 14363 14364 CallFlags targetFlags(CallFlags::FunCall); 14365 if (cx_->realm() == target->realm()) { 14366 targetFlags.setIsSameRealm(); 14367 } 14368 14369 HandleValue newTarget = NullHandleValue; 14370 14371 // Use the bound |this| value. 14372 Rooted<Value> thisValue(cx_, bound->getBoundThis()); 14373 14374 auto callArgs = argc_ > 0 14375 ? HandleValueArray::subarray(args_, 1, args_.length() - 1) 14376 : HandleValueArray::empty(); 14377 14378 // Concatenate the bound arguments and the stack arguments. 14379 JS::RootedVector<Value> concatenatedArgs(cx_); 14380 if (numBoundArgs != 0) { 14381 if (!concatenatedArgs.reserve(numBoundArgs + callArgs.length())) { 14382 cx_->recoverFromOutOfMemory(); 14383 return AttachDecision::NoAction; 14384 } 14385 14386 for (size_t i = 0; i < numBoundArgs; i++) { 14387 concatenatedArgs.infallibleAppend(bound->getBoundArg(i)); 14388 } 14389 concatenatedArgs.infallibleAppend(callArgs.begin(), callArgs.length()); 14390 } 14391 14392 // Actual args. 14393 auto args = numBoundArgs != 0 ? concatenatedArgs : callArgs; 14394 14395 // Check for specific native-function optimizations. 14396 InlinableNativeIRGenerator nativeGen(*this, callee, target, newTarget, 14397 thisValue, args, targetFlags, bound); 14398 return nativeGen.tryAttachStub(); 14399 } 14400 14401 AttachDecision CallIRGenerator::tryAttachFunApplyBound( 14402 Handle<JSFunction*> callee) { 14403 MOZ_ASSERT(callee->isNativeWithoutJitEntry()); 14404 14405 if (callee->native() != fun_apply) { 14406 return AttachDecision::NoAction; 14407 } 14408 14409 if (argc_ > 2) { 14410 return AttachDecision::NoAction; 14411 } 14412 14413 if (!thisval_.isObject() || !thisval_.toObject().is<BoundFunctionObject>()) { 14414 return AttachDecision::NoAction; 14415 } 14416 Rooted<BoundFunctionObject*> bound( 14417 cx_, &thisval_.toObject().as<BoundFunctionObject>()); 14418 14419 // The target must be a native JSFunction without a JitEntry. 14420 Rooted<JSObject*> boundTarget(cx_, bound->getTarget()); 14421 if (!boundTarget->is<JSFunction>()) { 14422 return AttachDecision::NoAction; 14423 } 14424 auto target = boundTarget.as<JSFunction>(); 14425 14426 bool isScripted = target->hasJitEntry(); 14427 MOZ_ASSERT_IF(!isScripted, target->isNativeWithoutJitEntry()); 14428 14429 // We don't yet supported scripted bound targets. 14430 if (isScripted) { 14431 return AttachDecision::NoAction; 14432 } 14433 14434 // Limit the number of bound arguments to prevent us from compiling many 14435 // different stubs (we bake in numBoundArgs and it's usually very small). 14436 static constexpr size_t MaxBoundArgs = 10; 14437 size_t numBoundArgs = bound->numBoundArgs(); 14438 if (numBoundArgs > MaxBoundArgs) { 14439 return AttachDecision::NoAction; 14440 } 14441 14442 // The second argument must be |null| or |undefined|, because we only support 14443 // |CallFlags::FunCall| and |CallFlags::FunApplyNullUndefined|. 14444 CallFlags::ArgFormat format; 14445 if (argc_ < 2) { 14446 format = CallFlags::FunCall; 14447 } else if (args_[1].isNullOrUndefined()) { 14448 format = CallFlags::FunApplyNullUndefined; 14449 } else { 14450 return AttachDecision::NoAction; 14451 } 14452 14453 // Don't try to optimize when we're already megamorphic. 14454 if (mode_ != ICState::Mode::Specialized) { 14455 return AttachDecision::NoAction; 14456 } 14457 14458 CallFlags targetFlags(format); 14459 if (cx_->realm() == target->realm()) { 14460 targetFlags.setIsSameRealm(); 14461 } 14462 14463 HandleValue newTarget = NullHandleValue; 14464 14465 // Use the bound |this| value. 14466 Rooted<Value> thisValue(cx_, bound->getBoundThis()); 14467 14468 // Collect all bound arguments. 14469 JS::RootedVector<Value> args(cx_); 14470 if (numBoundArgs != 0) { 14471 if (!args.reserve(numBoundArgs)) { 14472 cx_->recoverFromOutOfMemory(); 14473 return AttachDecision::NoAction; 14474 } 14475 14476 for (size_t i = 0; i < numBoundArgs; i++) { 14477 args.infallibleAppend(bound->getBoundArg(i)); 14478 } 14479 } 14480 14481 // Check for specific native-function optimizations. 14482 InlinableNativeIRGenerator nativeGen(*this, callee, target, newTarget, 14483 thisValue, args, targetFlags, bound); 14484 return nativeGen.tryAttachStub(); 14485 } 14486 14487 AttachDecision CallIRGenerator::tryAttachStub() { 14488 AutoAssertNoPendingException aanpe(cx_); 14489 14490 // Some opcodes are not yet supported. 14491 switch (jsop()) { 14492 case JSOp::Call: 14493 case JSOp::CallContent: 14494 case JSOp::CallIgnoresRv: 14495 case JSOp::CallIter: 14496 case JSOp::CallContentIter: 14497 case JSOp::SpreadCall: 14498 case JSOp::New: 14499 case JSOp::NewContent: 14500 case JSOp::SpreadNew: 14501 case JSOp::SuperCall: 14502 case JSOp::SpreadSuperCall: 14503 break; 14504 default: 14505 return AttachDecision::NoAction; 14506 } 14507 14508 MOZ_ASSERT(mode_ != ICState::Mode::Generic); 14509 14510 // Ensure callee is a function. 14511 if (!callee_.isObject()) { 14512 return AttachDecision::NoAction; 14513 } 14514 14515 RootedObject calleeObj(cx_, &callee_.toObject()); 14516 if (calleeObj->is<BoundFunctionObject>()) { 14517 auto boundCalleeObj = calleeObj.as<BoundFunctionObject>(); 14518 14519 TRY_ATTACH(tryAttachBoundFunction(boundCalleeObj)); 14520 TRY_ATTACH(tryAttachBoundNative(boundCalleeObj)); 14521 TRY_ATTACH(tryAttachBoundFunCall(boundCalleeObj)); 14522 TRY_ATTACH(tryAttachBoundFunApply(boundCalleeObj)); 14523 } 14524 if (!calleeObj->is<JSFunction>()) { 14525 return tryAttachCallHook(calleeObj); 14526 } 14527 14528 HandleFunction calleeFunc = calleeObj.as<JSFunction>(); 14529 14530 // Check for scripted optimizations. 14531 if (calleeFunc->hasJitEntry()) { 14532 return tryAttachCallScripted(calleeFunc); 14533 } 14534 14535 // Check for native-function optimizations. 14536 MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry()); 14537 14538 // Try inlining Function.prototype.{call,apply}. We don't use the 14539 // InlinableNative mechanism for this because we want to optimize these more 14540 // aggressively than other natives. 14541 if (IsInlinableFunCallOrApply(jsop())) { 14542 TRY_ATTACH(tryAttachFunCall(calleeFunc)); 14543 TRY_ATTACH(tryAttachFunApply(calleeFunc)); 14544 TRY_ATTACH(tryAttachFunCallBound(calleeFunc)); 14545 TRY_ATTACH(tryAttachFunApplyBound(calleeFunc)); 14546 } 14547 14548 return tryAttachCallNative(calleeFunc); 14549 } 14550 14551 void CallIRGenerator::trackAttached(const char* name) { 14552 stubName_ = name ? name : "NotAttached"; 14553 #ifdef JS_CACHEIR_SPEW 14554 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { 14555 sp.valueProperty("callee", callee_); 14556 sp.valueProperty("thisval", thisval_); 14557 sp.valueProperty("argc", Int32Value(argc_)); 14558 14559 // Try to log the first two arguments. 14560 if (args_.length() >= 1) { 14561 sp.valueProperty("arg0", args_[0]); 14562 } 14563 if (args_.length() >= 2) { 14564 sp.valueProperty("arg1", args_[1]); 14565 } 14566 } 14567 #endif 14568 } 14569 14570 // Class which holds a shape pointer for use when caches might reference data in 14571 // other zones. 14572 static const JSClass shapeContainerClass = {"ShapeContainer", 14573 JSCLASS_HAS_RESERVED_SLOTS(1)}; 14574 14575 static const size_t SHAPE_CONTAINER_SLOT = 0; 14576 14577 static JSObject* NewWrapperWithObjectShape(JSContext* cx, 14578 Handle<NativeObject*> obj) { 14579 MOZ_ASSERT(cx->compartment() != obj->compartment()); 14580 14581 RootedObject wrapper(cx); 14582 { 14583 AutoRealm ar(cx, obj); 14584 wrapper = NewBuiltinClassInstance(cx, &shapeContainerClass); 14585 if (!wrapper) { 14586 return nullptr; 14587 } 14588 wrapper->as<NativeObject>().setReservedSlot( 14589 SHAPE_CONTAINER_SLOT, PrivateGCThingValue(obj->shape())); 14590 } 14591 if (!JS_WrapObject(cx, &wrapper)) { 14592 return nullptr; 14593 } 14594 MOZ_ASSERT(IsWrapper(wrapper)); 14595 return wrapper; 14596 } 14597 14598 void jit::LoadShapeWrapperContents(MacroAssembler& masm, Register obj, 14599 Register dst, Label* failure) { 14600 masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), dst); 14601 Address privateAddr(dst, 14602 js::detail::ProxyReservedSlots::offsetOfPrivateSlot()); 14603 masm.fallibleUnboxObject(privateAddr, dst, failure); 14604 masm.unboxNonDouble( 14605 Address(dst, NativeObject::getFixedSlotOffset(SHAPE_CONTAINER_SLOT)), dst, 14606 JSVAL_TYPE_PRIVATE_GCTHING); 14607 } 14608 14609 static bool CanConvertToInt32ForToNumber(const Value& v) { 14610 return v.isInt32() || v.isBoolean() || v.isNull(); 14611 } 14612 14613 static Int32OperandId EmitGuardToInt32ForToNumber(CacheIRWriter& writer, 14614 ValOperandId id, 14615 const Value& v) { 14616 if (v.isInt32()) { 14617 return writer.guardToInt32(id); 14618 } 14619 if (v.isNull()) { 14620 writer.guardIsNull(id); 14621 return writer.loadInt32Constant(0); 14622 } 14623 MOZ_ASSERT(v.isBoolean()); 14624 return writer.guardBooleanToInt32(id); 14625 } 14626 14627 static bool CanConvertToDoubleForToNumber(const Value& v) { 14628 return v.isNumber() || v.isBoolean() || v.isNullOrUndefined(); 14629 } 14630 14631 static NumberOperandId EmitGuardToDoubleForToNumber(CacheIRWriter& writer, 14632 ValOperandId id, 14633 const Value& v) { 14634 if (v.isNumber()) { 14635 return writer.guardIsNumber(id); 14636 } 14637 if (v.isBoolean()) { 14638 BooleanOperandId boolId = writer.guardToBoolean(id); 14639 return writer.booleanToNumber(boolId); 14640 } 14641 if (v.isNull()) { 14642 writer.guardIsNull(id); 14643 return writer.loadDoubleConstant(0.0); 14644 } 14645 MOZ_ASSERT(v.isUndefined()); 14646 writer.guardIsUndefined(id); 14647 return writer.loadDoubleConstant(JS::GenericNaN()); 14648 } 14649 14650 CompareIRGenerator::CompareIRGenerator(JSContext* cx, HandleScript script, 14651 jsbytecode* pc, ICState state, JSOp op, 14652 HandleValue lhsVal, HandleValue rhsVal) 14653 : IRGenerator(cx, script, pc, CacheKind::Compare, state), 14654 op_(op), 14655 lhsVal_(lhsVal), 14656 rhsVal_(rhsVal) {} 14657 14658 AttachDecision CompareIRGenerator::tryAttachString(ValOperandId lhsId, 14659 ValOperandId rhsId) { 14660 if (!lhsVal_.isString() || !rhsVal_.isString()) { 14661 return AttachDecision::NoAction; 14662 } 14663 14664 StringOperandId lhsStrId = writer.guardToString(lhsId); 14665 StringOperandId rhsStrId = writer.guardToString(rhsId); 14666 writer.compareStringResult(op_, lhsStrId, rhsStrId); 14667 writer.returnFromIC(); 14668 14669 trackAttached("Compare.String"); 14670 return AttachDecision::Attach; 14671 } 14672 14673 AttachDecision CompareIRGenerator::tryAttachObject(ValOperandId lhsId, 14674 ValOperandId rhsId) { 14675 MOZ_ASSERT(IsEqualityOp(op_)); 14676 14677 if (!lhsVal_.isObject() || !rhsVal_.isObject()) { 14678 return AttachDecision::NoAction; 14679 } 14680 14681 ObjOperandId lhsObjId = writer.guardToObject(lhsId); 14682 ObjOperandId rhsObjId = writer.guardToObject(rhsId); 14683 writer.compareObjectResult(op_, lhsObjId, rhsObjId); 14684 writer.returnFromIC(); 14685 14686 trackAttached("Compare.Object"); 14687 return AttachDecision::Attach; 14688 } 14689 14690 AttachDecision CompareIRGenerator::tryAttachSymbol(ValOperandId lhsId, 14691 ValOperandId rhsId) { 14692 MOZ_ASSERT(IsEqualityOp(op_)); 14693 14694 if (!lhsVal_.isSymbol() || !rhsVal_.isSymbol()) { 14695 return AttachDecision::NoAction; 14696 } 14697 14698 SymbolOperandId lhsSymId = writer.guardToSymbol(lhsId); 14699 SymbolOperandId rhsSymId = writer.guardToSymbol(rhsId); 14700 writer.compareSymbolResult(op_, lhsSymId, rhsSymId); 14701 writer.returnFromIC(); 14702 14703 trackAttached("Compare.Symbol"); 14704 return AttachDecision::Attach; 14705 } 14706 14707 AttachDecision CompareIRGenerator::tryAttachStrictDifferentTypes( 14708 ValOperandId lhsId, ValOperandId rhsId) { 14709 MOZ_ASSERT(IsEqualityOp(op_)); 14710 14711 if (op_ != JSOp::StrictEq && op_ != JSOp::StrictNe) { 14712 return AttachDecision::NoAction; 14713 } 14714 14715 // Probably can't hit some of these. 14716 if (SameType(lhsVal_, rhsVal_) || 14717 (lhsVal_.isNumber() && rhsVal_.isNumber())) { 14718 return AttachDecision::NoAction; 14719 } 14720 14721 // Compare tags 14722 ValueTagOperandId lhsTypeId = writer.loadValueTag(lhsId); 14723 ValueTagOperandId rhsTypeId = writer.loadValueTag(rhsId); 14724 writer.guardTagNotEqual(lhsTypeId, rhsTypeId); 14725 14726 // Now that we've passed the guard, we know differing types, so return the 14727 // bool result. 14728 writer.loadBooleanResult(op_ == JSOp::StrictNe ? true : false); 14729 writer.returnFromIC(); 14730 14731 trackAttached("Compare.StrictDifferentTypes"); 14732 return AttachDecision::Attach; 14733 } 14734 14735 AttachDecision CompareIRGenerator::tryAttachInt32(ValOperandId lhsId, 14736 ValOperandId rhsId) { 14737 if (!CanConvertToInt32ForToNumber(lhsVal_) || 14738 !CanConvertToInt32ForToNumber(rhsVal_)) { 14739 return AttachDecision::NoAction; 14740 } 14741 14742 // Strictly different types should have been handed by 14743 // tryAttachStrictDifferentTypes. 14744 MOZ_ASSERT_IF(op_ == JSOp::StrictEq || op_ == JSOp::StrictNe, 14745 lhsVal_.type() == rhsVal_.type()); 14746 14747 // Should have been handled by tryAttachAnyNullUndefined. 14748 MOZ_ASSERT_IF(lhsVal_.isNull() || rhsVal_.isNull(), !IsEqualityOp(op_)); 14749 14750 Int32OperandId lhsIntId = EmitGuardToInt32ForToNumber(writer, lhsId, lhsVal_); 14751 Int32OperandId rhsIntId = EmitGuardToInt32ForToNumber(writer, rhsId, rhsVal_); 14752 14753 writer.compareInt32Result(op_, lhsIntId, rhsIntId); 14754 writer.returnFromIC(); 14755 14756 trackAttached("Compare.Int32"); 14757 return AttachDecision::Attach; 14758 } 14759 14760 AttachDecision CompareIRGenerator::tryAttachNumber(ValOperandId lhsId, 14761 ValOperandId rhsId) { 14762 if (!CanConvertToDoubleForToNumber(lhsVal_) || 14763 !CanConvertToDoubleForToNumber(rhsVal_)) { 14764 return AttachDecision::NoAction; 14765 } 14766 14767 // Strictly different types should have been handed by 14768 // tryAttachStrictDifferentTypes. 14769 MOZ_ASSERT_IF(op_ == JSOp::StrictEq || op_ == JSOp::StrictNe, 14770 lhsVal_.type() == rhsVal_.type() || 14771 (lhsVal_.isNumber() && rhsVal_.isNumber())); 14772 14773 // Should have been handled by tryAttachAnyNullUndefined. 14774 MOZ_ASSERT_IF(lhsVal_.isNullOrUndefined() || rhsVal_.isNullOrUndefined(), 14775 !IsEqualityOp(op_)); 14776 14777 NumberOperandId lhs = EmitGuardToDoubleForToNumber(writer, lhsId, lhsVal_); 14778 NumberOperandId rhs = EmitGuardToDoubleForToNumber(writer, rhsId, rhsVal_); 14779 writer.compareDoubleResult(op_, lhs, rhs); 14780 writer.returnFromIC(); 14781 14782 trackAttached("Compare.Number"); 14783 return AttachDecision::Attach; 14784 } 14785 14786 AttachDecision CompareIRGenerator::tryAttachBigInt(ValOperandId lhsId, 14787 ValOperandId rhsId) { 14788 if (!lhsVal_.isBigInt() || !rhsVal_.isBigInt()) { 14789 return AttachDecision::NoAction; 14790 } 14791 14792 BigIntOperandId lhs = writer.guardToBigInt(lhsId); 14793 BigIntOperandId rhs = writer.guardToBigInt(rhsId); 14794 14795 writer.compareBigIntResult(op_, lhs, rhs); 14796 writer.returnFromIC(); 14797 14798 trackAttached("Compare.BigInt"); 14799 return AttachDecision::Attach; 14800 } 14801 14802 AttachDecision CompareIRGenerator::tryAttachAnyNullUndefined( 14803 ValOperandId lhsId, ValOperandId rhsId) { 14804 MOZ_ASSERT(IsEqualityOp(op_)); 14805 14806 // Either RHS or LHS needs to be null/undefined. 14807 if (!lhsVal_.isNullOrUndefined() && !rhsVal_.isNullOrUndefined()) { 14808 return AttachDecision::NoAction; 14809 } 14810 14811 // We assume that the side with null/undefined is usually constant, in 14812 // code like `if (x === undefined) { x = {}; }`. 14813 // That is why we don't attach when both sides are undefined/null, 14814 // because we would basically need to decide by chance which side is 14815 // the likely constant. 14816 // The actual generated code however handles null/undefined of course. 14817 if (lhsVal_.isNullOrUndefined() && rhsVal_.isNullOrUndefined()) { 14818 return AttachDecision::NoAction; 14819 } 14820 14821 if (rhsVal_.isNullOrUndefined()) { 14822 if (rhsVal_.isNull()) { 14823 writer.guardIsNull(rhsId); 14824 writer.compareNullUndefinedResult(op_, /* isUndefined */ false, lhsId); 14825 trackAttached("Compare.AnyNull"); 14826 } else { 14827 writer.guardIsUndefined(rhsId); 14828 writer.compareNullUndefinedResult(op_, /* isUndefined */ true, lhsId); 14829 trackAttached("Compare.AnyUndefined"); 14830 } 14831 } else { 14832 if (lhsVal_.isNull()) { 14833 writer.guardIsNull(lhsId); 14834 writer.compareNullUndefinedResult(op_, /* isUndefined */ false, rhsId); 14835 trackAttached("Compare.NullAny"); 14836 } else { 14837 writer.guardIsUndefined(lhsId); 14838 writer.compareNullUndefinedResult(op_, /* isUndefined */ true, rhsId); 14839 trackAttached("Compare.UndefinedAny"); 14840 } 14841 } 14842 14843 writer.returnFromIC(); 14844 return AttachDecision::Attach; 14845 } 14846 14847 // Handle {null/undefined} x {null,undefined} equality comparisons 14848 AttachDecision CompareIRGenerator::tryAttachNullUndefined(ValOperandId lhsId, 14849 ValOperandId rhsId) { 14850 if (!lhsVal_.isNullOrUndefined() || !rhsVal_.isNullOrUndefined()) { 14851 return AttachDecision::NoAction; 14852 } 14853 14854 if (op_ == JSOp::Eq || op_ == JSOp::Ne) { 14855 writer.guardIsNullOrUndefined(lhsId); 14856 writer.guardIsNullOrUndefined(rhsId); 14857 // Sloppy equality means we actually only care about the op: 14858 writer.loadBooleanResult(op_ == JSOp::Eq); 14859 trackAttached("Compare.SloppyNullUndefined"); 14860 } else { 14861 // Strict equality only hits this branch, and only in the 14862 // undef {!,=}== undef and null {!,=}== null cases. 14863 // The other cases should have hit tryAttachStrictDifferentTypes. 14864 MOZ_ASSERT(lhsVal_.isNull() == rhsVal_.isNull()); 14865 lhsVal_.isNull() ? writer.guardIsNull(lhsId) 14866 : writer.guardIsUndefined(lhsId); 14867 rhsVal_.isNull() ? writer.guardIsNull(rhsId) 14868 : writer.guardIsUndefined(rhsId); 14869 writer.loadBooleanResult(op_ == JSOp::StrictEq); 14870 trackAttached("Compare.StrictNullUndefinedEquality"); 14871 } 14872 14873 writer.returnFromIC(); 14874 return AttachDecision::Attach; 14875 } 14876 14877 AttachDecision CompareIRGenerator::tryAttachStringNumber(ValOperandId lhsId, 14878 ValOperandId rhsId) { 14879 // Ensure String x {Number, Boolean, Null, Undefined} 14880 if (!(lhsVal_.isString() && CanConvertToDoubleForToNumber(rhsVal_)) && 14881 !(rhsVal_.isString() && CanConvertToDoubleForToNumber(lhsVal_))) { 14882 return AttachDecision::NoAction; 14883 } 14884 14885 // Case should have been handled by tryAttachStrictDifferentTypes 14886 MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe); 14887 14888 auto createGuards = [&](const Value& v, ValOperandId vId) { 14889 if (v.isString()) { 14890 StringOperandId strId = writer.guardToString(vId); 14891 return writer.guardStringToNumber(strId); 14892 } 14893 return EmitGuardToDoubleForToNumber(writer, vId, v); 14894 }; 14895 14896 NumberOperandId lhsGuardedId = createGuards(lhsVal_, lhsId); 14897 NumberOperandId rhsGuardedId = createGuards(rhsVal_, rhsId); 14898 writer.compareDoubleResult(op_, lhsGuardedId, rhsGuardedId); 14899 writer.returnFromIC(); 14900 14901 trackAttached("Compare.StringNumber"); 14902 return AttachDecision::Attach; 14903 } 14904 14905 AttachDecision CompareIRGenerator::tryAttachPrimitiveSymbol( 14906 ValOperandId lhsId, ValOperandId rhsId) { 14907 MOZ_ASSERT(IsEqualityOp(op_)); 14908 14909 // The set of primitive cases we want to handle here (excluding null, 14910 // undefined, and symbol) 14911 auto isPrimitive = [](const Value& x) { 14912 return x.isString() || x.isBoolean() || x.isNumber() || x.isBigInt(); 14913 }; 14914 14915 // Ensure Symbol x {String, Bool, Number, BigInt}. 14916 if (!(lhsVal_.isSymbol() && isPrimitive(rhsVal_)) && 14917 !(rhsVal_.isSymbol() && isPrimitive(lhsVal_))) { 14918 return AttachDecision::NoAction; 14919 } 14920 14921 auto guardPrimitive = [&](const Value& v, ValOperandId id) { 14922 MOZ_ASSERT(isPrimitive(v)); 14923 if (v.isNumber()) { 14924 writer.guardIsNumber(id); 14925 return; 14926 } 14927 switch (v.extractNonDoubleType()) { 14928 case JSVAL_TYPE_STRING: 14929 writer.guardToString(id); 14930 return; 14931 case JSVAL_TYPE_BOOLEAN: 14932 writer.guardToBoolean(id); 14933 return; 14934 case JSVAL_TYPE_BIGINT: 14935 writer.guardToBigInt(id); 14936 return; 14937 default: 14938 MOZ_CRASH("unexpected type"); 14939 return; 14940 } 14941 }; 14942 14943 if (lhsVal_.isSymbol()) { 14944 writer.guardToSymbol(lhsId); 14945 guardPrimitive(rhsVal_, rhsId); 14946 } else { 14947 guardPrimitive(lhsVal_, lhsId); 14948 writer.guardToSymbol(rhsId); 14949 } 14950 14951 // Comparing a primitive with symbol will always be true for Ne/StrictNe, and 14952 // always be false for other compare ops. 14953 writer.loadBooleanResult(op_ == JSOp::Ne || op_ == JSOp::StrictNe); 14954 writer.returnFromIC(); 14955 14956 trackAttached("Compare.PrimitiveSymbol"); 14957 return AttachDecision::Attach; 14958 } 14959 14960 AttachDecision CompareIRGenerator::tryAttachBigIntInt32(ValOperandId lhsId, 14961 ValOperandId rhsId) { 14962 // Ensure BigInt x {Int32, Boolean, Null}. 14963 if (!(lhsVal_.isBigInt() && CanConvertToInt32ForToNumber(rhsVal_)) && 14964 !(rhsVal_.isBigInt() && CanConvertToInt32ForToNumber(lhsVal_))) { 14965 return AttachDecision::NoAction; 14966 } 14967 14968 // Case should have been handled by tryAttachStrictDifferentTypes 14969 MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe); 14970 14971 if (lhsVal_.isBigInt()) { 14972 BigIntOperandId bigIntId = writer.guardToBigInt(lhsId); 14973 Int32OperandId intId = EmitGuardToInt32ForToNumber(writer, rhsId, rhsVal_); 14974 14975 writer.compareBigIntInt32Result(op_, bigIntId, intId); 14976 } else { 14977 Int32OperandId intId = EmitGuardToInt32ForToNumber(writer, lhsId, lhsVal_); 14978 BigIntOperandId bigIntId = writer.guardToBigInt(rhsId); 14979 14980 writer.compareBigIntInt32Result(ReverseCompareOp(op_), bigIntId, intId); 14981 } 14982 writer.returnFromIC(); 14983 14984 trackAttached("Compare.BigIntInt32"); 14985 return AttachDecision::Attach; 14986 } 14987 14988 AttachDecision CompareIRGenerator::tryAttachBigIntNumber(ValOperandId lhsId, 14989 ValOperandId rhsId) { 14990 // Ensure BigInt x {Number, Undefined}. 14991 if (!(lhsVal_.isBigInt() && CanConvertToDoubleForToNumber(rhsVal_)) && 14992 !(rhsVal_.isBigInt() && CanConvertToDoubleForToNumber(lhsVal_))) { 14993 return AttachDecision::NoAction; 14994 } 14995 14996 // Case should have been handled by tryAttachStrictDifferentTypes 14997 MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe); 14998 14999 // Case should have been handled by tryAttachBigIntInt32. 15000 MOZ_ASSERT(!CanConvertToInt32ForToNumber(lhsVal_)); 15001 MOZ_ASSERT(!CanConvertToInt32ForToNumber(rhsVal_)); 15002 15003 if (lhsVal_.isBigInt()) { 15004 BigIntOperandId bigIntId = writer.guardToBigInt(lhsId); 15005 NumberOperandId numId = 15006 EmitGuardToDoubleForToNumber(writer, rhsId, rhsVal_); 15007 15008 writer.compareBigIntNumberResult(op_, bigIntId, numId); 15009 } else { 15010 NumberOperandId numId = 15011 EmitGuardToDoubleForToNumber(writer, lhsId, lhsVal_); 15012 BigIntOperandId bigIntId = writer.guardToBigInt(rhsId); 15013 15014 writer.compareBigIntNumberResult(ReverseCompareOp(op_), bigIntId, numId); 15015 } 15016 writer.returnFromIC(); 15017 15018 trackAttached("Compare.BigIntNumber"); 15019 return AttachDecision::Attach; 15020 } 15021 15022 AttachDecision CompareIRGenerator::tryAttachBigIntString(ValOperandId lhsId, 15023 ValOperandId rhsId) { 15024 // Ensure BigInt x String. 15025 if (!(lhsVal_.isBigInt() && rhsVal_.isString()) && 15026 !(rhsVal_.isBigInt() && lhsVal_.isString())) { 15027 return AttachDecision::NoAction; 15028 } 15029 15030 // Case should have been handled by tryAttachStrictDifferentTypes 15031 MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe); 15032 15033 if (lhsVal_.isBigInt()) { 15034 BigIntOperandId bigIntId = writer.guardToBigInt(lhsId); 15035 StringOperandId strId = writer.guardToString(rhsId); 15036 15037 writer.compareBigIntStringResult(op_, bigIntId, strId); 15038 } else { 15039 StringOperandId strId = writer.guardToString(lhsId); 15040 BigIntOperandId bigIntId = writer.guardToBigInt(rhsId); 15041 15042 writer.compareBigIntStringResult(ReverseCompareOp(op_), bigIntId, strId); 15043 } 15044 writer.returnFromIC(); 15045 15046 trackAttached("Compare.BigIntString"); 15047 return AttachDecision::Attach; 15048 } 15049 15050 AttachDecision CompareIRGenerator::tryAttachStub() { 15051 MOZ_ASSERT(cacheKind_ == CacheKind::Compare); 15052 MOZ_ASSERT(IsEqualityOp(op_) || IsRelationalOp(op_)); 15053 15054 AutoAssertNoPendingException aanpe(cx_); 15055 15056 constexpr uint8_t lhsIndex = 0; 15057 constexpr uint8_t rhsIndex = 1; 15058 15059 ValOperandId lhsId(writer.setInputOperandId(lhsIndex)); 15060 ValOperandId rhsId(writer.setInputOperandId(rhsIndex)); 15061 15062 // For sloppy equality ops, there are cases this IC does not handle: 15063 // - {Object} x {String, Symbol, Bool, Number, BigInt}. 15064 // 15065 // For relational comparison ops, these cases aren't handled: 15066 // - Object x {String, Bool, Number, BigInt, Object, Null, Undefined}. 15067 // Note: |Symbol x any| always throws, so it doesn't need to be handled. 15068 // 15069 // (The above lists omits the equivalent case {B} x {A} when {A} x {B} is 15070 // already present.) 15071 15072 if (IsEqualityOp(op_)) { 15073 TRY_ATTACH(tryAttachObject(lhsId, rhsId)); 15074 TRY_ATTACH(tryAttachSymbol(lhsId, rhsId)); 15075 15076 // Handles any (non null or undefined) comparison with null/undefined. 15077 TRY_ATTACH(tryAttachAnyNullUndefined(lhsId, rhsId)); 15078 15079 // This covers -strict- equality/inequality using a type tag check, so 15080 // catches all different type pairs outside of Numbers, which cannot be 15081 // checked on tags alone. 15082 TRY_ATTACH(tryAttachStrictDifferentTypes(lhsId, rhsId)); 15083 15084 TRY_ATTACH(tryAttachNullUndefined(lhsId, rhsId)); 15085 15086 TRY_ATTACH(tryAttachPrimitiveSymbol(lhsId, rhsId)); 15087 } 15088 15089 // We want these to be last, to allow us to bypass the 15090 // strictly-different-types cases in the below attachment code 15091 TRY_ATTACH(tryAttachInt32(lhsId, rhsId)); 15092 TRY_ATTACH(tryAttachNumber(lhsId, rhsId)); 15093 TRY_ATTACH(tryAttachBigInt(lhsId, rhsId)); 15094 TRY_ATTACH(tryAttachString(lhsId, rhsId)); 15095 15096 TRY_ATTACH(tryAttachStringNumber(lhsId, rhsId)); 15097 15098 TRY_ATTACH(tryAttachBigIntInt32(lhsId, rhsId)); 15099 TRY_ATTACH(tryAttachBigIntNumber(lhsId, rhsId)); 15100 TRY_ATTACH(tryAttachBigIntString(lhsId, rhsId)); 15101 15102 // Strict equality is always supported. 15103 MOZ_ASSERT(!IsStrictEqualityOp(op_)); 15104 15105 // Other operations are unsupported iff at least one operand is an object. 15106 MOZ_ASSERT(lhsVal_.isObject() || rhsVal_.isObject()); 15107 15108 trackAttached(IRGenerator::NotAttached); 15109 return AttachDecision::NoAction; 15110 } 15111 15112 void CompareIRGenerator::trackAttached(const char* name) { 15113 stubName_ = name ? name : "NotAttached"; 15114 #ifdef JS_CACHEIR_SPEW 15115 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { 15116 sp.valueProperty("lhs", lhsVal_); 15117 sp.valueProperty("rhs", rhsVal_); 15118 sp.opcodeProperty("op", op_); 15119 } 15120 #endif 15121 } 15122 15123 ToBoolIRGenerator::ToBoolIRGenerator(JSContext* cx, HandleScript script, 15124 jsbytecode* pc, ICState state, 15125 HandleValue val) 15126 : IRGenerator(cx, script, pc, CacheKind::ToBool, state), val_(val) {} 15127 15128 void ToBoolIRGenerator::trackAttached(const char* name) { 15129 stubName_ = name ? name : "NotAttached"; 15130 #ifdef JS_CACHEIR_SPEW 15131 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { 15132 sp.valueProperty("val", val_); 15133 } 15134 #endif 15135 } 15136 15137 AttachDecision ToBoolIRGenerator::tryAttachStub() { 15138 AutoAssertNoPendingException aanpe(cx_); 15139 writer.setTypeData(TypeData(JSValueType(val_.type()))); 15140 15141 TRY_ATTACH(tryAttachBool()); 15142 TRY_ATTACH(tryAttachInt32()); 15143 TRY_ATTACH(tryAttachNumber()); 15144 TRY_ATTACH(tryAttachString()); 15145 TRY_ATTACH(tryAttachNullOrUndefined()); 15146 TRY_ATTACH(tryAttachObject()); 15147 TRY_ATTACH(tryAttachSymbol()); 15148 TRY_ATTACH(tryAttachBigInt()); 15149 15150 trackAttached(IRGenerator::NotAttached); 15151 return AttachDecision::NoAction; 15152 } 15153 15154 AttachDecision ToBoolIRGenerator::tryAttachBool() { 15155 if (!val_.isBoolean()) { 15156 return AttachDecision::NoAction; 15157 } 15158 15159 ValOperandId valId(writer.setInputOperandId(0)); 15160 writer.guardNonDoubleType(valId, ValueType::Boolean); 15161 writer.loadOperandResult(valId); 15162 writer.returnFromIC(); 15163 trackAttached("ToBool.Bool"); 15164 return AttachDecision::Attach; 15165 } 15166 15167 AttachDecision ToBoolIRGenerator::tryAttachInt32() { 15168 if (!val_.isInt32()) { 15169 return AttachDecision::NoAction; 15170 } 15171 15172 ValOperandId valId(writer.setInputOperandId(0)); 15173 writer.guardNonDoubleType(valId, ValueType::Int32); 15174 writer.loadInt32TruthyResult(valId); 15175 writer.returnFromIC(); 15176 trackAttached("ToBool.Int32"); 15177 return AttachDecision::Attach; 15178 } 15179 15180 AttachDecision ToBoolIRGenerator::tryAttachNumber() { 15181 if (!val_.isNumber()) { 15182 return AttachDecision::NoAction; 15183 } 15184 15185 ValOperandId valId(writer.setInputOperandId(0)); 15186 NumberOperandId numId = writer.guardIsNumber(valId); 15187 writer.loadDoubleTruthyResult(numId); 15188 writer.returnFromIC(); 15189 trackAttached("ToBool.Number"); 15190 return AttachDecision::Attach; 15191 } 15192 15193 AttachDecision ToBoolIRGenerator::tryAttachSymbol() { 15194 if (!val_.isSymbol()) { 15195 return AttachDecision::NoAction; 15196 } 15197 15198 ValOperandId valId(writer.setInputOperandId(0)); 15199 writer.guardNonDoubleType(valId, ValueType::Symbol); 15200 writer.loadBooleanResult(true); 15201 writer.returnFromIC(); 15202 trackAttached("ToBool.Symbol"); 15203 return AttachDecision::Attach; 15204 } 15205 15206 AttachDecision ToBoolIRGenerator::tryAttachString() { 15207 if (!val_.isString()) { 15208 return AttachDecision::NoAction; 15209 } 15210 15211 ValOperandId valId(writer.setInputOperandId(0)); 15212 StringOperandId strId = writer.guardToString(valId); 15213 writer.loadStringTruthyResult(strId); 15214 writer.returnFromIC(); 15215 trackAttached("ToBool.String"); 15216 return AttachDecision::Attach; 15217 } 15218 15219 AttachDecision ToBoolIRGenerator::tryAttachNullOrUndefined() { 15220 if (!val_.isNullOrUndefined()) { 15221 return AttachDecision::NoAction; 15222 } 15223 15224 ValOperandId valId(writer.setInputOperandId(0)); 15225 writer.guardIsNullOrUndefined(valId); 15226 writer.loadBooleanResult(false); 15227 writer.returnFromIC(); 15228 trackAttached("ToBool.NullOrUndefined"); 15229 return AttachDecision::Attach; 15230 } 15231 15232 AttachDecision ToBoolIRGenerator::tryAttachObject() { 15233 if (!val_.isObject()) { 15234 return AttachDecision::NoAction; 15235 } 15236 15237 ValOperandId valId(writer.setInputOperandId(0)); 15238 ObjOperandId objId = writer.guardToObject(valId); 15239 writer.loadObjectTruthyResult(objId); 15240 writer.returnFromIC(); 15241 trackAttached("ToBool.Object"); 15242 return AttachDecision::Attach; 15243 } 15244 15245 AttachDecision ToBoolIRGenerator::tryAttachBigInt() { 15246 if (!val_.isBigInt()) { 15247 return AttachDecision::NoAction; 15248 } 15249 15250 ValOperandId valId(writer.setInputOperandId(0)); 15251 BigIntOperandId bigIntId = writer.guardToBigInt(valId); 15252 writer.loadBigIntTruthyResult(bigIntId); 15253 writer.returnFromIC(); 15254 trackAttached("ToBool.BigInt"); 15255 return AttachDecision::Attach; 15256 } 15257 15258 LazyConstantIRGenerator::LazyConstantIRGenerator(JSContext* cx, 15259 HandleScript script, 15260 jsbytecode* pc, ICState state, 15261 HandleValue val) 15262 : IRGenerator(cx, script, pc, CacheKind::LazyConstant, state), val_(val) {} 15263 15264 void LazyConstantIRGenerator::trackAttached(const char* name) { 15265 stubName_ = name ? name : "NotAttached"; 15266 #ifdef JS_CACHEIR_SPEW 15267 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { 15268 sp.valueProperty("val", val_); 15269 } 15270 #endif 15271 } 15272 15273 AttachDecision LazyConstantIRGenerator::tryAttachStub() { 15274 AutoAssertNoPendingException aanpe(cx_); 15275 writer.loadValueResult(val_); 15276 writer.returnFromIC(); 15277 trackAttached("LazyConstant"); 15278 return AttachDecision::Attach; 15279 } 15280 15281 UnaryArithIRGenerator::UnaryArithIRGenerator(JSContext* cx, HandleScript script, 15282 jsbytecode* pc, ICState state, 15283 JSOp op, HandleValue val, 15284 HandleValue res) 15285 : IRGenerator(cx, script, pc, CacheKind::UnaryArith, state), 15286 op_(op), 15287 val_(val), 15288 res_(res) {} 15289 15290 void UnaryArithIRGenerator::trackAttached(const char* name) { 15291 stubName_ = name ? name : "NotAttached"; 15292 #ifdef JS_CACHEIR_SPEW 15293 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { 15294 sp.valueProperty("val", val_); 15295 sp.valueProperty("res", res_); 15296 } 15297 #endif 15298 } 15299 15300 AttachDecision UnaryArithIRGenerator::tryAttachStub() { 15301 AutoAssertNoPendingException aanpe(cx_); 15302 TRY_ATTACH(tryAttachInt32()); 15303 TRY_ATTACH(tryAttachNumber()); 15304 TRY_ATTACH(tryAttachBitwise()); 15305 TRY_ATTACH(tryAttachBigIntPtr()); 15306 TRY_ATTACH(tryAttachBigInt()); 15307 TRY_ATTACH(tryAttachStringInt32()); 15308 TRY_ATTACH(tryAttachStringNumber()); 15309 15310 trackAttached(IRGenerator::NotAttached); 15311 return AttachDecision::NoAction; 15312 } 15313 15314 AttachDecision UnaryArithIRGenerator::tryAttachInt32() { 15315 if (op_ == JSOp::BitNot) { 15316 return AttachDecision::NoAction; 15317 } 15318 if (!CanConvertToInt32ForToNumber(val_) || !res_.isInt32()) { 15319 return AttachDecision::NoAction; 15320 } 15321 15322 ValOperandId valId(writer.setInputOperandId(0)); 15323 15324 Int32OperandId intId = EmitGuardToInt32ForToNumber(writer, valId, val_); 15325 switch (op_) { 15326 case JSOp::Pos: 15327 writer.loadInt32Result(intId); 15328 trackAttached("UnaryArith.Int32Pos"); 15329 break; 15330 case JSOp::Neg: 15331 writer.int32NegationResult(intId); 15332 trackAttached("UnaryArith.Int32Neg"); 15333 break; 15334 case JSOp::Inc: 15335 writer.int32IncResult(intId); 15336 trackAttached("UnaryArith.Int32Inc"); 15337 break; 15338 case JSOp::Dec: 15339 writer.int32DecResult(intId); 15340 trackAttached("UnaryArith.Int32Dec"); 15341 break; 15342 case JSOp::ToNumeric: 15343 writer.loadInt32Result(intId); 15344 trackAttached("UnaryArith.Int32ToNumeric"); 15345 break; 15346 default: 15347 MOZ_CRASH("unexpected OP"); 15348 } 15349 15350 writer.returnFromIC(); 15351 return AttachDecision::Attach; 15352 } 15353 15354 AttachDecision UnaryArithIRGenerator::tryAttachNumber() { 15355 if (op_ == JSOp::BitNot) { 15356 return AttachDecision::NoAction; 15357 } 15358 if (!CanConvertToDoubleForToNumber(val_)) { 15359 return AttachDecision::NoAction; 15360 } 15361 MOZ_ASSERT(res_.isNumber()); 15362 15363 ValOperandId valId(writer.setInputOperandId(0)); 15364 NumberOperandId numId = EmitGuardToDoubleForToNumber(writer, valId, val_); 15365 15366 switch (op_) { 15367 case JSOp::Pos: 15368 writer.loadDoubleResult(numId); 15369 trackAttached("UnaryArith.DoublePos"); 15370 break; 15371 case JSOp::Neg: 15372 writer.doubleNegationResult(numId); 15373 trackAttached("UnaryArith.DoubleNeg"); 15374 break; 15375 case JSOp::Inc: 15376 writer.doubleIncResult(numId); 15377 trackAttached("UnaryArith.DoubleInc"); 15378 break; 15379 case JSOp::Dec: 15380 writer.doubleDecResult(numId); 15381 trackAttached("UnaryArith.DoubleDec"); 15382 break; 15383 case JSOp::ToNumeric: 15384 writer.loadDoubleResult(numId); 15385 trackAttached("UnaryArith.DoubleToNumeric"); 15386 break; 15387 default: 15388 MOZ_CRASH("Unexpected OP"); 15389 } 15390 15391 writer.returnFromIC(); 15392 return AttachDecision::Attach; 15393 } 15394 15395 static bool CanTruncateToInt32(const Value& val) { 15396 return val.isNumber() || val.isBoolean() || val.isNullOrUndefined() || 15397 val.isString(); 15398 } 15399 15400 // Convert type into int32 for the bitwise/shift operands. 15401 static Int32OperandId EmitTruncateToInt32Guard(CacheIRWriter& writer, 15402 ValOperandId id, 15403 const Value& val) { 15404 MOZ_ASSERT(CanTruncateToInt32(val)); 15405 if (val.isInt32()) { 15406 return writer.guardToInt32(id); 15407 } 15408 if (val.isBoolean()) { 15409 return writer.guardBooleanToInt32(id); 15410 } 15411 if (val.isNullOrUndefined()) { 15412 writer.guardIsNullOrUndefined(id); 15413 return writer.loadInt32Constant(0); 15414 } 15415 NumberOperandId numId; 15416 if (val.isString()) { 15417 StringOperandId strId = writer.guardToString(id); 15418 numId = writer.guardStringToNumber(strId); 15419 } else { 15420 MOZ_ASSERT(val.isDouble()); 15421 numId = writer.guardIsNumber(id); 15422 } 15423 return writer.truncateDoubleToUInt32(numId); 15424 } 15425 15426 AttachDecision UnaryArithIRGenerator::tryAttachBitwise() { 15427 // Only bitwise operators. 15428 if (op_ != JSOp::BitNot) { 15429 return AttachDecision::NoAction; 15430 } 15431 15432 // Check guard conditions 15433 if (!CanTruncateToInt32(val_)) { 15434 return AttachDecision::NoAction; 15435 } 15436 15437 // Bitwise operators always produce Int32 values. 15438 MOZ_ASSERT(res_.isInt32()); 15439 15440 ValOperandId valId(writer.setInputOperandId(0)); 15441 Int32OperandId intId = EmitTruncateToInt32Guard(writer, valId, val_); 15442 writer.int32NotResult(intId); 15443 trackAttached("UnaryArith.BitwiseBitNot"); 15444 15445 writer.returnFromIC(); 15446 return AttachDecision::Attach; 15447 } 15448 15449 AttachDecision UnaryArithIRGenerator::tryAttachBigInt() { 15450 if (!val_.isBigInt()) { 15451 return AttachDecision::NoAction; 15452 } 15453 MOZ_ASSERT(res_.isBigInt()); 15454 15455 MOZ_ASSERT(op_ != JSOp::Pos, 15456 "Applying the unary + operator on BigInt values throws an error"); 15457 15458 ValOperandId valId(writer.setInputOperandId(0)); 15459 BigIntOperandId bigIntId = writer.guardToBigInt(valId); 15460 switch (op_) { 15461 case JSOp::BitNot: 15462 writer.bigIntNotResult(bigIntId); 15463 trackAttached("UnaryArith.BigIntNot"); 15464 break; 15465 case JSOp::Neg: 15466 writer.bigIntNegationResult(bigIntId); 15467 trackAttached("UnaryArith.BigIntNeg"); 15468 break; 15469 case JSOp::Inc: 15470 writer.bigIntIncResult(bigIntId); 15471 trackAttached("UnaryArith.BigIntInc"); 15472 break; 15473 case JSOp::Dec: 15474 writer.bigIntDecResult(bigIntId); 15475 trackAttached("UnaryArith.BigIntDec"); 15476 break; 15477 case JSOp::ToNumeric: 15478 writer.loadBigIntResult(bigIntId); 15479 trackAttached("UnaryArith.BigIntToNumeric"); 15480 break; 15481 default: 15482 MOZ_CRASH("Unexpected OP"); 15483 } 15484 15485 writer.returnFromIC(); 15486 return AttachDecision::Attach; 15487 } 15488 15489 AttachDecision UnaryArithIRGenerator::tryAttachBigIntPtr() { 15490 if (!val_.isBigInt()) { 15491 return AttachDecision::NoAction; 15492 } 15493 MOZ_ASSERT(res_.isBigInt()); 15494 15495 MOZ_ASSERT(op_ != JSOp::Pos, 15496 "Applying the unary + operator on BigInt values throws an error"); 15497 15498 switch (op_) { 15499 case JSOp::BitNot: 15500 case JSOp::Neg: 15501 case JSOp::Inc: 15502 case JSOp::Dec: 15503 break; 15504 case JSOp::ToNumeric: 15505 return AttachDecision::NoAction; 15506 default: 15507 MOZ_CRASH("Unexpected OP"); 15508 } 15509 15510 intptr_t val; 15511 if (!BigInt::isIntPtr(val_.toBigInt(), &val)) { 15512 return AttachDecision::NoAction; 15513 } 15514 15515 using CheckedIntPtr = mozilla::CheckedInt<intptr_t>; 15516 15517 switch (op_) { 15518 case JSOp::BitNot: { 15519 // Bitwise operations always return an intptr-sized result. 15520 break; 15521 } 15522 case JSOp::Neg: { 15523 auto result = -CheckedIntPtr(val); 15524 if (result.isValid()) { 15525 break; 15526 } 15527 return AttachDecision::NoAction; 15528 } 15529 case JSOp::Inc: { 15530 auto result = CheckedIntPtr(val) + intptr_t(1); 15531 if (result.isValid()) { 15532 break; 15533 } 15534 return AttachDecision::NoAction; 15535 } 15536 case JSOp::Dec: { 15537 auto result = CheckedIntPtr(val) - intptr_t(1); 15538 if (result.isValid()) { 15539 break; 15540 } 15541 return AttachDecision::NoAction; 15542 } 15543 default: 15544 MOZ_CRASH("Unexpected OP"); 15545 } 15546 15547 ValOperandId valId(writer.setInputOperandId(0)); 15548 BigIntOperandId bigIntId = writer.guardToBigInt(valId); 15549 IntPtrOperandId intPtrId = writer.bigIntToIntPtr(bigIntId); 15550 IntPtrOperandId resultId; 15551 switch (op_) { 15552 case JSOp::BitNot: 15553 resultId = writer.bigIntPtrNot(intPtrId); 15554 trackAttached("UnaryArith.BigIntPtrNot"); 15555 break; 15556 case JSOp::Neg: 15557 resultId = writer.bigIntPtrNegation(intPtrId); 15558 trackAttached("UnaryArith.BigIntPtrNeg"); 15559 break; 15560 case JSOp::Inc: 15561 resultId = writer.bigIntPtrInc(intPtrId); 15562 trackAttached("UnaryArith.BigIntPtrInc"); 15563 break; 15564 case JSOp::Dec: 15565 resultId = writer.bigIntPtrDec(intPtrId); 15566 trackAttached("UnaryArith.BigIntPtrDec"); 15567 break; 15568 default: 15569 MOZ_CRASH("Unexpected OP"); 15570 } 15571 15572 writer.intPtrToBigIntResult(resultId); 15573 writer.returnFromIC(); 15574 return AttachDecision::Attach; 15575 } 15576 15577 AttachDecision UnaryArithIRGenerator::tryAttachStringInt32() { 15578 if (!val_.isString()) { 15579 return AttachDecision::NoAction; 15580 } 15581 MOZ_ASSERT(res_.isNumber()); 15582 15583 // Case should have been handled by tryAttachBitwise. 15584 MOZ_ASSERT(op_ != JSOp::BitNot); 15585 15586 if (!res_.isInt32()) { 15587 return AttachDecision::NoAction; 15588 } 15589 15590 ValOperandId valId(writer.setInputOperandId(0)); 15591 StringOperandId stringId = writer.guardToString(valId); 15592 Int32OperandId intId = writer.guardStringToInt32(stringId); 15593 15594 switch (op_) { 15595 case JSOp::Pos: 15596 writer.loadInt32Result(intId); 15597 trackAttached("UnaryArith.StringInt32Pos"); 15598 break; 15599 case JSOp::Neg: 15600 writer.int32NegationResult(intId); 15601 trackAttached("UnaryArith.StringInt32Neg"); 15602 break; 15603 case JSOp::Inc: 15604 writer.int32IncResult(intId); 15605 trackAttached("UnaryArith.StringInt32Inc"); 15606 break; 15607 case JSOp::Dec: 15608 writer.int32DecResult(intId); 15609 trackAttached("UnaryArith.StringInt32Dec"); 15610 break; 15611 case JSOp::ToNumeric: 15612 writer.loadInt32Result(intId); 15613 trackAttached("UnaryArith.StringInt32ToNumeric"); 15614 break; 15615 default: 15616 MOZ_CRASH("Unexpected OP"); 15617 } 15618 15619 writer.returnFromIC(); 15620 return AttachDecision::Attach; 15621 } 15622 15623 AttachDecision UnaryArithIRGenerator::tryAttachStringNumber() { 15624 if (!val_.isString()) { 15625 return AttachDecision::NoAction; 15626 } 15627 MOZ_ASSERT(res_.isNumber()); 15628 15629 // Case should have been handled by tryAttachBitwise. 15630 MOZ_ASSERT(op_ != JSOp::BitNot); 15631 15632 ValOperandId valId(writer.setInputOperandId(0)); 15633 StringOperandId stringId = writer.guardToString(valId); 15634 NumberOperandId numId = writer.guardStringToNumber(stringId); 15635 15636 Int32OperandId truncatedId; 15637 switch (op_) { 15638 case JSOp::Pos: 15639 writer.loadDoubleResult(numId); 15640 trackAttached("UnaryArith.StringNumberPos"); 15641 break; 15642 case JSOp::Neg: 15643 writer.doubleNegationResult(numId); 15644 trackAttached("UnaryArith.StringNumberNeg"); 15645 break; 15646 case JSOp::Inc: 15647 writer.doubleIncResult(numId); 15648 trackAttached("UnaryArith.StringNumberInc"); 15649 break; 15650 case JSOp::Dec: 15651 writer.doubleDecResult(numId); 15652 trackAttached("UnaryArith.StringNumberDec"); 15653 break; 15654 case JSOp::ToNumeric: 15655 writer.loadDoubleResult(numId); 15656 trackAttached("UnaryArith.StringNumberToNumeric"); 15657 break; 15658 default: 15659 MOZ_CRASH("Unexpected OP"); 15660 } 15661 15662 writer.returnFromIC(); 15663 return AttachDecision::Attach; 15664 } 15665 15666 ToPropertyKeyIRGenerator::ToPropertyKeyIRGenerator(JSContext* cx, 15667 HandleScript script, 15668 jsbytecode* pc, 15669 ICState state, 15670 HandleValue val) 15671 : IRGenerator(cx, script, pc, CacheKind::ToPropertyKey, state), val_(val) {} 15672 15673 void ToPropertyKeyIRGenerator::trackAttached(const char* name) { 15674 stubName_ = name ? name : "NotAttached"; 15675 #ifdef JS_CACHEIR_SPEW 15676 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { 15677 sp.valueProperty("val", val_); 15678 } 15679 #endif 15680 } 15681 15682 AttachDecision ToPropertyKeyIRGenerator::tryAttachStub() { 15683 AutoAssertNoPendingException aanpe(cx_); 15684 TRY_ATTACH(tryAttachInt32()); 15685 TRY_ATTACH(tryAttachNumber()); 15686 TRY_ATTACH(tryAttachString()); 15687 TRY_ATTACH(tryAttachSymbol()); 15688 15689 trackAttached(IRGenerator::NotAttached); 15690 return AttachDecision::NoAction; 15691 } 15692 15693 AttachDecision ToPropertyKeyIRGenerator::tryAttachInt32() { 15694 if (!val_.isInt32()) { 15695 return AttachDecision::NoAction; 15696 } 15697 15698 ValOperandId valId(writer.setInputOperandId(0)); 15699 15700 Int32OperandId intId = writer.guardToInt32(valId); 15701 writer.loadInt32Result(intId); 15702 writer.returnFromIC(); 15703 15704 trackAttached("ToPropertyKey.Int32"); 15705 return AttachDecision::Attach; 15706 } 15707 15708 AttachDecision ToPropertyKeyIRGenerator::tryAttachNumber() { 15709 if (!val_.isNumber()) { 15710 return AttachDecision::NoAction; 15711 } 15712 15713 // We allow negative zero here because ToPropertyKey(-0.0) is 0. 15714 int32_t unused; 15715 if (!mozilla::NumberEqualsInt32(val_.toNumber(), &unused)) { 15716 return AttachDecision::NoAction; 15717 } 15718 15719 ValOperandId valId(writer.setInputOperandId(0)); 15720 15721 Int32OperandId intId = EmitGuardToInt32Index(writer, val_, valId); 15722 writer.loadInt32Result(intId); 15723 writer.returnFromIC(); 15724 15725 trackAttached("ToPropertyKey.Number"); 15726 return AttachDecision::Attach; 15727 } 15728 15729 AttachDecision ToPropertyKeyIRGenerator::tryAttachString() { 15730 if (!val_.isString()) { 15731 return AttachDecision::NoAction; 15732 } 15733 15734 ValOperandId valId(writer.setInputOperandId(0)); 15735 15736 StringOperandId strId = writer.guardToString(valId); 15737 writer.loadStringResult(strId); 15738 writer.returnFromIC(); 15739 15740 trackAttached("ToPropertyKey.String"); 15741 return AttachDecision::Attach; 15742 } 15743 15744 AttachDecision ToPropertyKeyIRGenerator::tryAttachSymbol() { 15745 if (!val_.isSymbol()) { 15746 return AttachDecision::NoAction; 15747 } 15748 15749 ValOperandId valId(writer.setInputOperandId(0)); 15750 15751 SymbolOperandId strId = writer.guardToSymbol(valId); 15752 writer.loadSymbolResult(strId); 15753 writer.returnFromIC(); 15754 15755 trackAttached("ToPropertyKey.Symbol"); 15756 return AttachDecision::Attach; 15757 } 15758 15759 BinaryArithIRGenerator::BinaryArithIRGenerator(JSContext* cx, 15760 HandleScript script, 15761 jsbytecode* pc, ICState state, 15762 JSOp op, HandleValue lhs, 15763 HandleValue rhs, HandleValue res) 15764 : IRGenerator(cx, script, pc, CacheKind::BinaryArith, state), 15765 op_(op), 15766 lhs_(lhs), 15767 rhs_(rhs), 15768 res_(res) {} 15769 15770 void BinaryArithIRGenerator::trackAttached(const char* name) { 15771 stubName_ = name ? name : "NotAttached"; 15772 #ifdef JS_CACHEIR_SPEW 15773 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { 15774 sp.opcodeProperty("op", op_); 15775 sp.valueProperty("rhs", rhs_); 15776 sp.valueProperty("lhs", lhs_); 15777 } 15778 #endif 15779 } 15780 15781 AttachDecision BinaryArithIRGenerator::tryAttachStub() { 15782 AutoAssertNoPendingException aanpe(cx_); 15783 // Arithmetic operations with Int32 operands 15784 TRY_ATTACH(tryAttachInt32()); 15785 15786 // Bitwise operations with Int32/Double/Boolean/Null/Undefined/String 15787 // operands. 15788 TRY_ATTACH(tryAttachBitwise()); 15789 15790 // Arithmetic operations with Double operands. This needs to come after 15791 // tryAttachInt32, as the guards overlap, and we'd prefer to attach the 15792 // more specialized Int32 IC if it is possible. 15793 TRY_ATTACH(tryAttachDouble()); 15794 15795 // String x {String,Number,Boolean,Null,Undefined} 15796 TRY_ATTACH(tryAttachStringConcat()); 15797 15798 // String x Object 15799 TRY_ATTACH(tryAttachStringObjectConcat()); 15800 15801 // Arithmetic operations or bitwise operations with intptr-sized BigInt 15802 // operands. 15803 TRY_ATTACH(tryAttachBigIntPtr()); 15804 15805 // Arithmetic operations or bitwise operations with BigInt operands 15806 TRY_ATTACH(tryAttachBigInt()); 15807 15808 // Arithmetic operations (without addition) with String x Int32. 15809 TRY_ATTACH(tryAttachStringInt32Arith()); 15810 15811 // Arithmetic operations (without addition) with String x Number. This needs 15812 // to come after tryAttachStringInt32Arith, as the guards overlap, and we'd 15813 // prefer to attach the more specialized Int32 IC if it is possible. 15814 TRY_ATTACH(tryAttachStringNumberArith()); 15815 15816 TRY_ATTACH(tryAttachDateArith()); 15817 15818 trackAttached(IRGenerator::NotAttached); 15819 return AttachDecision::NoAction; 15820 } 15821 15822 AttachDecision BinaryArithIRGenerator::tryAttachBitwise() { 15823 // Only bit-wise and shifts. 15824 if (op_ != JSOp::BitOr && op_ != JSOp::BitXor && op_ != JSOp::BitAnd && 15825 op_ != JSOp::Lsh && op_ != JSOp::Rsh && op_ != JSOp::Ursh) { 15826 return AttachDecision::NoAction; 15827 } 15828 15829 // Check guard conditions 15830 if (!CanTruncateToInt32(lhs_) || !CanTruncateToInt32(rhs_)) { 15831 return AttachDecision::NoAction; 15832 } 15833 15834 // All ops, with the exception of Ursh, produce Int32 values. 15835 MOZ_ASSERT_IF(op_ != JSOp::Ursh, res_.isInt32()); 15836 15837 ValOperandId lhsId(writer.setInputOperandId(0)); 15838 ValOperandId rhsId(writer.setInputOperandId(1)); 15839 15840 Int32OperandId lhsIntId = EmitTruncateToInt32Guard(writer, lhsId, lhs_); 15841 Int32OperandId rhsIntId = EmitTruncateToInt32Guard(writer, rhsId, rhs_); 15842 15843 switch (op_) { 15844 case JSOp::BitOr: 15845 writer.int32BitOrResult(lhsIntId, rhsIntId); 15846 trackAttached("BinaryArith.BitwiseBitOr"); 15847 break; 15848 case JSOp::BitXor: 15849 writer.int32BitXorResult(lhsIntId, rhsIntId); 15850 trackAttached("BinaryArith.BitwiseBitXor"); 15851 break; 15852 case JSOp::BitAnd: 15853 writer.int32BitAndResult(lhsIntId, rhsIntId); 15854 trackAttached("BinaryArith.BitwiseBitAnd"); 15855 break; 15856 case JSOp::Lsh: 15857 writer.int32LeftShiftResult(lhsIntId, rhsIntId); 15858 trackAttached("BinaryArith.BitwiseLeftShift"); 15859 break; 15860 case JSOp::Rsh: 15861 writer.int32RightShiftResult(lhsIntId, rhsIntId); 15862 trackAttached("BinaryArith.BitwiseRightShift"); 15863 break; 15864 case JSOp::Ursh: 15865 writer.int32URightShiftResult(lhsIntId, rhsIntId, res_.isDouble()); 15866 trackAttached("BinaryArith.BitwiseUnsignedRightShift"); 15867 break; 15868 default: 15869 MOZ_CRASH("Unhandled op in tryAttachBitwise"); 15870 } 15871 15872 writer.returnFromIC(); 15873 return AttachDecision::Attach; 15874 } 15875 15876 AttachDecision BinaryArithIRGenerator::tryAttachDouble() { 15877 // Check valid opcodes 15878 if (op_ != JSOp::Add && op_ != JSOp::Sub && op_ != JSOp::Mul && 15879 op_ != JSOp::Div && op_ != JSOp::Mod && op_ != JSOp::Pow) { 15880 return AttachDecision::NoAction; 15881 } 15882 15883 // Check guard conditions. 15884 if (!CanConvertToDoubleForToNumber(lhs_) || 15885 !CanConvertToDoubleForToNumber(rhs_)) { 15886 return AttachDecision::NoAction; 15887 } 15888 15889 ValOperandId lhsId(writer.setInputOperandId(0)); 15890 ValOperandId rhsId(writer.setInputOperandId(1)); 15891 15892 NumberOperandId lhs = EmitGuardToDoubleForToNumber(writer, lhsId, lhs_); 15893 NumberOperandId rhs = EmitGuardToDoubleForToNumber(writer, rhsId, rhs_); 15894 15895 switch (op_) { 15896 case JSOp::Add: 15897 writer.doubleAddResult(lhs, rhs); 15898 trackAttached("BinaryArith.DoubleAdd"); 15899 break; 15900 case JSOp::Sub: 15901 writer.doubleSubResult(lhs, rhs); 15902 trackAttached("BinaryArith.DoubleSub"); 15903 break; 15904 case JSOp::Mul: 15905 writer.doubleMulResult(lhs, rhs); 15906 trackAttached("BinaryArith.DoubleMul"); 15907 break; 15908 case JSOp::Div: 15909 writer.doubleDivResult(lhs, rhs); 15910 trackAttached("BinaryArith.DoubleDiv"); 15911 break; 15912 case JSOp::Mod: 15913 writer.doubleModResult(lhs, rhs); 15914 trackAttached("BinaryArith.DoubleMod"); 15915 break; 15916 case JSOp::Pow: 15917 writer.doublePowResult(lhs, rhs); 15918 trackAttached("BinaryArith.DoublePow"); 15919 break; 15920 default: 15921 MOZ_CRASH("Unhandled Op"); 15922 } 15923 writer.returnFromIC(); 15924 return AttachDecision::Attach; 15925 } 15926 15927 AttachDecision BinaryArithIRGenerator::tryAttachInt32() { 15928 // Check guard conditions. 15929 if (!CanConvertToInt32ForToNumber(lhs_) || 15930 !CanConvertToInt32ForToNumber(rhs_)) { 15931 return AttachDecision::NoAction; 15932 } 15933 15934 // These ICs will failure() if result can't be encoded in an Int32: 15935 // If sample result is not Int32, we should avoid IC. 15936 if (!res_.isInt32()) { 15937 return AttachDecision::NoAction; 15938 } 15939 15940 if (op_ != JSOp::Add && op_ != JSOp::Sub && op_ != JSOp::Mul && 15941 op_ != JSOp::Div && op_ != JSOp::Mod && op_ != JSOp::Pow) { 15942 return AttachDecision::NoAction; 15943 } 15944 15945 if (op_ == JSOp::Pow && !CanAttachInt32Pow(lhs_, rhs_)) { 15946 return AttachDecision::NoAction; 15947 } 15948 15949 ValOperandId lhsId(writer.setInputOperandId(0)); 15950 ValOperandId rhsId(writer.setInputOperandId(1)); 15951 15952 Int32OperandId lhsIntId = EmitGuardToInt32ForToNumber(writer, lhsId, lhs_); 15953 Int32OperandId rhsIntId = EmitGuardToInt32ForToNumber(writer, rhsId, rhs_); 15954 15955 switch (op_) { 15956 case JSOp::Add: 15957 writer.int32AddResult(lhsIntId, rhsIntId); 15958 trackAttached("BinaryArith.Int32Add"); 15959 break; 15960 case JSOp::Sub: 15961 writer.int32SubResult(lhsIntId, rhsIntId); 15962 trackAttached("BinaryArith.Int32Sub"); 15963 break; 15964 case JSOp::Mul: 15965 writer.int32MulResult(lhsIntId, rhsIntId); 15966 trackAttached("BinaryArith.Int32Mul"); 15967 break; 15968 case JSOp::Div: 15969 writer.int32DivResult(lhsIntId, rhsIntId); 15970 trackAttached("BinaryArith.Int32Div"); 15971 break; 15972 case JSOp::Mod: 15973 writer.int32ModResult(lhsIntId, rhsIntId); 15974 trackAttached("BinaryArith.Int32Mod"); 15975 break; 15976 case JSOp::Pow: 15977 writer.int32PowResult(lhsIntId, rhsIntId); 15978 trackAttached("BinaryArith.Int32Pow"); 15979 break; 15980 default: 15981 MOZ_CRASH("Unhandled op in tryAttachInt32"); 15982 } 15983 15984 writer.returnFromIC(); 15985 return AttachDecision::Attach; 15986 } 15987 15988 AttachDecision BinaryArithIRGenerator::tryAttachStringConcat() { 15989 // Only Addition 15990 if (op_ != JSOp::Add) { 15991 return AttachDecision::NoAction; 15992 } 15993 15994 // One side must be a string, the other side a primitive value we can easily 15995 // convert to a string. 15996 if (!(lhs_.isString() && CanConvertToString(rhs_)) && 15997 !(CanConvertToString(lhs_) && rhs_.isString())) { 15998 return AttachDecision::NoAction; 15999 } 16000 16001 JitCode* code = cx_->zone()->jitZone()->ensureStubExists( 16002 cx_, JitZone::StubKind::StringConcat); 16003 if (!code) { 16004 cx_->recoverFromOutOfMemory(); 16005 return AttachDecision::NoAction; 16006 } 16007 16008 ValOperandId lhsId(writer.setInputOperandId(0)); 16009 ValOperandId rhsId(writer.setInputOperandId(1)); 16010 16011 StringOperandId lhsStrId = emitToStringGuard(lhsId, lhs_); 16012 StringOperandId rhsStrId = emitToStringGuard(rhsId, rhs_); 16013 16014 writer.concatStringsResult(lhsStrId, rhsStrId, code); 16015 16016 writer.returnFromIC(); 16017 trackAttached("BinaryArith.StringConcat"); 16018 return AttachDecision::Attach; 16019 } 16020 16021 AttachDecision BinaryArithIRGenerator::tryAttachStringObjectConcat() { 16022 // Only Addition 16023 if (op_ != JSOp::Add) { 16024 return AttachDecision::NoAction; 16025 } 16026 16027 // Check Guards 16028 if (!(lhs_.isObject() && rhs_.isString()) && 16029 !(lhs_.isString() && rhs_.isObject())) 16030 return AttachDecision::NoAction; 16031 16032 ValOperandId lhsId(writer.setInputOperandId(0)); 16033 ValOperandId rhsId(writer.setInputOperandId(1)); 16034 16035 // This guard is actually overly tight, as the runtime 16036 // helper can handle lhs or rhs being a string, so long 16037 // as the other is an object. 16038 if (lhs_.isString()) { 16039 writer.guardToString(lhsId); 16040 writer.guardToObject(rhsId); 16041 } else { 16042 writer.guardToObject(lhsId); 16043 writer.guardToString(rhsId); 16044 } 16045 16046 writer.callStringObjectConcatResult(lhsId, rhsId); 16047 16048 writer.returnFromIC(); 16049 trackAttached("BinaryArith.StringObjectConcat"); 16050 return AttachDecision::Attach; 16051 } 16052 16053 AttachDecision BinaryArithIRGenerator::tryAttachBigInt() { 16054 // Check Guards 16055 if (!lhs_.isBigInt() || !rhs_.isBigInt()) { 16056 return AttachDecision::NoAction; 16057 } 16058 16059 switch (op_) { 16060 case JSOp::Add: 16061 case JSOp::Sub: 16062 case JSOp::Mul: 16063 case JSOp::Div: 16064 case JSOp::Mod: 16065 case JSOp::Pow: 16066 // Arithmetic operations. 16067 break; 16068 16069 case JSOp::BitOr: 16070 case JSOp::BitXor: 16071 case JSOp::BitAnd: 16072 case JSOp::Lsh: 16073 case JSOp::Rsh: 16074 // Bitwise operations. 16075 break; 16076 16077 default: 16078 return AttachDecision::NoAction; 16079 } 16080 16081 ValOperandId lhsId(writer.setInputOperandId(0)); 16082 ValOperandId rhsId(writer.setInputOperandId(1)); 16083 16084 BigIntOperandId lhsBigIntId = writer.guardToBigInt(lhsId); 16085 BigIntOperandId rhsBigIntId = writer.guardToBigInt(rhsId); 16086 16087 switch (op_) { 16088 case JSOp::Add: 16089 writer.bigIntAddResult(lhsBigIntId, rhsBigIntId); 16090 trackAttached("BinaryArith.BigIntAdd"); 16091 break; 16092 case JSOp::Sub: 16093 writer.bigIntSubResult(lhsBigIntId, rhsBigIntId); 16094 trackAttached("BinaryArith.BigIntSub"); 16095 break; 16096 case JSOp::Mul: 16097 writer.bigIntMulResult(lhsBigIntId, rhsBigIntId); 16098 trackAttached("BinaryArith.BigIntMul"); 16099 break; 16100 case JSOp::Div: 16101 writer.bigIntDivResult(lhsBigIntId, rhsBigIntId); 16102 trackAttached("BinaryArith.BigIntDiv"); 16103 break; 16104 case JSOp::Mod: 16105 writer.bigIntModResult(lhsBigIntId, rhsBigIntId); 16106 trackAttached("BinaryArith.BigIntMod"); 16107 break; 16108 case JSOp::Pow: 16109 writer.bigIntPowResult(lhsBigIntId, rhsBigIntId); 16110 trackAttached("BinaryArith.BigIntPow"); 16111 break; 16112 case JSOp::BitOr: 16113 writer.bigIntBitOrResult(lhsBigIntId, rhsBigIntId); 16114 trackAttached("BinaryArith.BigIntBitOr"); 16115 break; 16116 case JSOp::BitXor: 16117 writer.bigIntBitXorResult(lhsBigIntId, rhsBigIntId); 16118 trackAttached("BinaryArith.BigIntBitXor"); 16119 break; 16120 case JSOp::BitAnd: 16121 writer.bigIntBitAndResult(lhsBigIntId, rhsBigIntId); 16122 trackAttached("BinaryArith.BigIntBitAnd"); 16123 break; 16124 case JSOp::Lsh: 16125 writer.bigIntLeftShiftResult(lhsBigIntId, rhsBigIntId); 16126 trackAttached("BinaryArith.BigIntLeftShift"); 16127 break; 16128 case JSOp::Rsh: 16129 writer.bigIntRightShiftResult(lhsBigIntId, rhsBigIntId); 16130 trackAttached("BinaryArith.BigIntRightShift"); 16131 break; 16132 default: 16133 MOZ_CRASH("Unhandled op in tryAttachBigInt"); 16134 } 16135 16136 writer.returnFromIC(); 16137 return AttachDecision::Attach; 16138 } 16139 16140 AttachDecision BinaryArithIRGenerator::tryAttachBigIntPtr() { 16141 // Check Guards 16142 if (!lhs_.isBigInt() || !rhs_.isBigInt()) { 16143 return AttachDecision::NoAction; 16144 } 16145 16146 switch (op_) { 16147 case JSOp::Add: 16148 case JSOp::Sub: 16149 case JSOp::Mul: 16150 case JSOp::Div: 16151 case JSOp::Mod: 16152 case JSOp::Pow: 16153 // Arithmetic operations. 16154 break; 16155 16156 case JSOp::BitOr: 16157 case JSOp::BitXor: 16158 case JSOp::BitAnd: 16159 case JSOp::Lsh: 16160 case JSOp::Rsh: 16161 // Bitwise operations. 16162 break; 16163 16164 default: 16165 return AttachDecision::NoAction; 16166 } 16167 16168 intptr_t lhs; 16169 intptr_t rhs; 16170 if (!BigInt::isIntPtr(lhs_.toBigInt(), &lhs) || 16171 !BigInt::isIntPtr(rhs_.toBigInt(), &rhs)) { 16172 return AttachDecision::NoAction; 16173 } 16174 16175 using CheckedIntPtr = mozilla::CheckedInt<intptr_t>; 16176 16177 switch (op_) { 16178 case JSOp::Add: { 16179 auto result = CheckedIntPtr(lhs) + rhs; 16180 if (result.isValid()) { 16181 break; 16182 } 16183 return AttachDecision::NoAction; 16184 } 16185 case JSOp::Sub: { 16186 auto result = CheckedIntPtr(lhs) - rhs; 16187 if (result.isValid()) { 16188 break; 16189 } 16190 return AttachDecision::NoAction; 16191 } 16192 case JSOp::Mul: { 16193 auto result = CheckedIntPtr(lhs) * rhs; 16194 if (result.isValid()) { 16195 break; 16196 } 16197 return AttachDecision::NoAction; 16198 } 16199 case JSOp::Div: { 16200 auto result = CheckedIntPtr(lhs) / rhs; 16201 if (result.isValid()) { 16202 break; 16203 } 16204 return AttachDecision::NoAction; 16205 } 16206 case JSOp::Mod: { 16207 // We can't use mozilla::CheckedInt here, because it disallows negative 16208 // inputs. 16209 if (rhs != 0) { 16210 break; 16211 } 16212 return AttachDecision::NoAction; 16213 } 16214 case JSOp::Pow: { 16215 intptr_t result; 16216 if (BigInt::powIntPtr(lhs, rhs, &result)) { 16217 break; 16218 } 16219 return AttachDecision::NoAction; 16220 } 16221 case JSOp::BitOr: 16222 case JSOp::BitXor: 16223 case JSOp::BitAnd: { 16224 // Bitwise operations always return an intptr-sized result. 16225 break; 16226 } 16227 case JSOp::Lsh: { 16228 if (lhs == 0 || rhs <= 0) { 16229 break; 16230 } 16231 if (size_t(rhs) < BigInt::DigitBits) { 16232 intptr_t result = lhs << rhs; 16233 if ((result >> rhs) == lhs) { 16234 break; 16235 } 16236 } 16237 return AttachDecision::NoAction; 16238 } 16239 case JSOp::Rsh: { 16240 if (lhs == 0 || rhs >= 0) { 16241 break; 16242 } 16243 if (rhs > -intptr_t(BigInt::DigitBits)) { 16244 intptr_t result = lhs << -rhs; 16245 if ((result >> -rhs) == lhs) { 16246 break; 16247 } 16248 } 16249 return AttachDecision::NoAction; 16250 } 16251 default: 16252 MOZ_CRASH("Unexpected OP"); 16253 } 16254 16255 ValOperandId lhsId(writer.setInputOperandId(0)); 16256 ValOperandId rhsId(writer.setInputOperandId(1)); 16257 16258 BigIntOperandId lhsBigIntId = writer.guardToBigInt(lhsId); 16259 BigIntOperandId rhsBigIntId = writer.guardToBigInt(rhsId); 16260 16261 IntPtrOperandId lhsIntPtrId = writer.bigIntToIntPtr(lhsBigIntId); 16262 IntPtrOperandId rhsIntPtrId = writer.bigIntToIntPtr(rhsBigIntId); 16263 16264 IntPtrOperandId resultId; 16265 switch (op_) { 16266 case JSOp::Add: { 16267 resultId = writer.bigIntPtrAdd(lhsIntPtrId, rhsIntPtrId); 16268 trackAttached("BinaryArith.BigIntPtr.Add"); 16269 break; 16270 } 16271 case JSOp::Sub: { 16272 resultId = writer.bigIntPtrSub(lhsIntPtrId, rhsIntPtrId); 16273 trackAttached("BinaryArith.BigIntPtr.Sub"); 16274 break; 16275 } 16276 case JSOp::Mul: { 16277 resultId = writer.bigIntPtrMul(lhsIntPtrId, rhsIntPtrId); 16278 trackAttached("BinaryArith.BigIntPtr.Mul"); 16279 break; 16280 } 16281 case JSOp::Div: { 16282 resultId = writer.bigIntPtrDiv(lhsIntPtrId, rhsIntPtrId); 16283 trackAttached("BinaryArith.BigIntPtr.Div"); 16284 break; 16285 } 16286 case JSOp::Mod: { 16287 resultId = writer.bigIntPtrMod(lhsIntPtrId, rhsIntPtrId); 16288 trackAttached("BinaryArith.BigIntPtr.Mod"); 16289 break; 16290 } 16291 case JSOp::Pow: { 16292 resultId = writer.bigIntPtrPow(lhsIntPtrId, rhsIntPtrId); 16293 trackAttached("BinaryArith.BigIntPtr.Pow"); 16294 break; 16295 } 16296 case JSOp::BitOr: { 16297 resultId = writer.bigIntPtrBitOr(lhsIntPtrId, rhsIntPtrId); 16298 trackAttached("BinaryArith.BigIntPtr.BitOr"); 16299 break; 16300 } 16301 case JSOp::BitXor: { 16302 resultId = writer.bigIntPtrBitXor(lhsIntPtrId, rhsIntPtrId); 16303 trackAttached("BinaryArith.BigIntPtr.BitXor"); 16304 break; 16305 } 16306 case JSOp::BitAnd: { 16307 resultId = writer.bigIntPtrBitAnd(lhsIntPtrId, rhsIntPtrId); 16308 trackAttached("BinaryArith.BigIntPtr.BitAnd"); 16309 break; 16310 } 16311 case JSOp::Lsh: { 16312 resultId = writer.bigIntPtrLeftShift(lhsIntPtrId, rhsIntPtrId); 16313 trackAttached("BinaryArith.BigIntPtr.LeftShift"); 16314 break; 16315 } 16316 case JSOp::Rsh: { 16317 resultId = writer.bigIntPtrRightShift(lhsIntPtrId, rhsIntPtrId); 16318 trackAttached("BinaryArith.BigIntPtr.RightShift"); 16319 break; 16320 } 16321 default: 16322 MOZ_CRASH("Unexpected OP"); 16323 } 16324 16325 writer.intPtrToBigIntResult(resultId); 16326 writer.returnFromIC(); 16327 return AttachDecision::Attach; 16328 } 16329 16330 AttachDecision BinaryArithIRGenerator::tryAttachStringInt32Arith() { 16331 // Check for either int32 x string or string x int32. 16332 if (!(lhs_.isInt32() && rhs_.isString()) && 16333 !(lhs_.isString() && rhs_.isInt32())) { 16334 return AttachDecision::NoAction; 16335 } 16336 16337 // The created ICs will fail if the result can't be encoded as as int32. 16338 // Thus skip this IC, if the sample result is not an int32. 16339 if (!res_.isInt32()) { 16340 return AttachDecision::NoAction; 16341 } 16342 16343 // Must _not_ support Add, because it would be string concatenation instead. 16344 // For Pow we can't easily determine the CanAttachInt32Pow conditions so we 16345 // reject that as well. 16346 if (op_ != JSOp::Sub && op_ != JSOp::Mul && op_ != JSOp::Div && 16347 op_ != JSOp::Mod) { 16348 return AttachDecision::NoAction; 16349 } 16350 16351 // The string operand must be convertable to an int32 value. 16352 JSString* str = lhs_.isString() ? lhs_.toString() : rhs_.toString(); 16353 16354 double num; 16355 if (!StringToNumber(cx_, str, &num)) { 16356 cx_->recoverFromOutOfMemory(); 16357 return AttachDecision::NoAction; 16358 } 16359 16360 int32_t unused; 16361 if (!mozilla::NumberIsInt32(num, &unused)) { 16362 return AttachDecision::NoAction; 16363 } 16364 16365 ValOperandId lhsId(writer.setInputOperandId(0)); 16366 ValOperandId rhsId(writer.setInputOperandId(1)); 16367 16368 auto guardToInt32 = [&](ValOperandId id, const Value& v) { 16369 if (v.isInt32()) { 16370 return writer.guardToInt32(id); 16371 } 16372 16373 MOZ_ASSERT(v.isString()); 16374 StringOperandId strId = writer.guardToString(id); 16375 return writer.guardStringToInt32(strId); 16376 }; 16377 16378 Int32OperandId lhsIntId = guardToInt32(lhsId, lhs_); 16379 Int32OperandId rhsIntId = guardToInt32(rhsId, rhs_); 16380 16381 switch (op_) { 16382 case JSOp::Sub: 16383 writer.int32SubResult(lhsIntId, rhsIntId); 16384 trackAttached("BinaryArith.StringInt32Sub"); 16385 break; 16386 case JSOp::Mul: 16387 writer.int32MulResult(lhsIntId, rhsIntId); 16388 trackAttached("BinaryArith.StringInt32Mul"); 16389 break; 16390 case JSOp::Div: 16391 writer.int32DivResult(lhsIntId, rhsIntId); 16392 trackAttached("BinaryArith.StringInt32Div"); 16393 break; 16394 case JSOp::Mod: 16395 writer.int32ModResult(lhsIntId, rhsIntId); 16396 trackAttached("BinaryArith.StringInt32Mod"); 16397 break; 16398 default: 16399 MOZ_CRASH("Unhandled op in tryAttachStringInt32Arith"); 16400 } 16401 16402 writer.returnFromIC(); 16403 return AttachDecision::Attach; 16404 } 16405 16406 AttachDecision BinaryArithIRGenerator::tryAttachStringNumberArith() { 16407 // Check for either number x string or string x number. 16408 if (!(lhs_.isNumber() && rhs_.isString()) && 16409 !(lhs_.isString() && rhs_.isNumber())) { 16410 return AttachDecision::NoAction; 16411 } 16412 16413 // Must _not_ support Add, because it would be string concatenation instead. 16414 if (op_ != JSOp::Sub && op_ != JSOp::Mul && op_ != JSOp::Div && 16415 op_ != JSOp::Mod && op_ != JSOp::Pow) { 16416 return AttachDecision::NoAction; 16417 } 16418 16419 ValOperandId lhsId(writer.setInputOperandId(0)); 16420 ValOperandId rhsId(writer.setInputOperandId(1)); 16421 16422 auto guardToNumber = [&](ValOperandId id, const Value& v) { 16423 if (v.isNumber()) { 16424 return writer.guardIsNumber(id); 16425 } 16426 16427 MOZ_ASSERT(v.isString()); 16428 StringOperandId strId = writer.guardToString(id); 16429 return writer.guardStringToNumber(strId); 16430 }; 16431 16432 NumberOperandId lhsIntId = guardToNumber(lhsId, lhs_); 16433 NumberOperandId rhsIntId = guardToNumber(rhsId, rhs_); 16434 16435 switch (op_) { 16436 case JSOp::Sub: 16437 writer.doubleSubResult(lhsIntId, rhsIntId); 16438 trackAttached("BinaryArith.StringNumberSub"); 16439 break; 16440 case JSOp::Mul: 16441 writer.doubleMulResult(lhsIntId, rhsIntId); 16442 trackAttached("BinaryArith.StringNumberMul"); 16443 break; 16444 case JSOp::Div: 16445 writer.doubleDivResult(lhsIntId, rhsIntId); 16446 trackAttached("BinaryArith.StringNumberDiv"); 16447 break; 16448 case JSOp::Mod: 16449 writer.doubleModResult(lhsIntId, rhsIntId); 16450 trackAttached("BinaryArith.StringNumberMod"); 16451 break; 16452 case JSOp::Pow: 16453 writer.doublePowResult(lhsIntId, rhsIntId); 16454 trackAttached("BinaryArith.StringNumberPow"); 16455 break; 16456 default: 16457 MOZ_CRASH("Unhandled op in tryAttachStringNumberArith"); 16458 } 16459 16460 writer.returnFromIC(); 16461 return AttachDecision::Attach; 16462 } 16463 16464 static bool CheckPropertyIsNativeFunction(JSContext* cx, JSObject* obj, 16465 jsbytecode* pc, PropertyKey propKey, 16466 JSNative nativeFn, JSFunction** fn, 16467 NativeObject** holder, size_t* slot) { 16468 Maybe<PropertyInfo> prop; 16469 NativeGetPropKind kind = 16470 CanAttachNativeGetProp(cx, obj, propKey, holder, &prop, pc); 16471 if (kind != NativeGetPropKind::Slot) { 16472 return false; 16473 } 16474 16475 MOZ_ASSERT(holder); 16476 MOZ_ASSERT(prop->isDataProperty()); 16477 16478 *slot = prop->slot(); 16479 Value calleeVal = (*holder)->getSlot(*slot); 16480 if (!calleeVal.isObject() || !calleeVal.toObject().is<JSFunction>()) { 16481 return false; 16482 } 16483 16484 if (!IsNativeFunction(calleeVal, nativeFn)) { 16485 return false; 16486 } 16487 16488 *fn = &calleeVal.toObject().as<JSFunction>(); 16489 return true; 16490 } 16491 16492 static void EmitGuardPropertyIsNativeFunction(CacheIRWriter& writer, 16493 JSObject* dateObj, JSFunction* fn, 16494 NativeObject* holder, size_t slot, 16495 ObjOperandId objId) { 16496 MOZ_ASSERT(holder); 16497 ObjOperandId holderId = 16498 EmitReadSlotGuard(writer, &dateObj->as<NativeObject>(), holder, objId); 16499 ValOperandId calleeValId = EmitLoadSlot(writer, holder, holderId, slot); 16500 ObjOperandId calleeId = writer.guardToObject(calleeValId); 16501 writer.guardSpecificFunction(calleeId, fn); 16502 } 16503 16504 AttachDecision BinaryArithIRGenerator::tryAttachDateArith() { 16505 // Only support subtractions 16506 if (op_ != JSOp::Sub) { 16507 return AttachDecision::NoAction; 16508 } 16509 16510 // At least one side must be an object. 16511 if (!lhs_.isObject() && !rhs_.isObject()) { 16512 return AttachDecision::NoAction; 16513 } 16514 16515 // Must be either object or numbers. 16516 if (!lhs_.isObject() && !lhs_.isNumber()) { 16517 return AttachDecision::NoAction; 16518 } 16519 16520 if (!rhs_.isObject() && !rhs_.isNumber()) { 16521 return AttachDecision::NoAction; 16522 } 16523 16524 // We can only operate on Date objects. 16525 if (lhs_.isObject() && !lhs_.toObject().is<DateObject>()) { 16526 return AttachDecision::NoAction; 16527 } 16528 16529 if (rhs_.isObject() && !rhs_.toObject().is<DateObject>()) { 16530 return AttachDecision::NoAction; 16531 } 16532 16533 JSFunction* lhsDateValueOfFn = nullptr; 16534 NativeObject* lhsDateValueOfHolder = nullptr; 16535 size_t lhsDateValueOfSlot; 16536 16537 JSFunction* lhsToPrimitiveFn = nullptr; 16538 NativeObject* lhsToPrimitiveHolder = nullptr; 16539 size_t lhsToPrimitiveSlot; 16540 16541 if (lhs_.isObject()) { 16542 if (!CheckPropertyIsNativeFunction( 16543 cx_, &lhs_.toObject(), pc_, NameToId(cx_->names().valueOf), 16544 date_valueOf, &lhsDateValueOfFn, &lhsDateValueOfHolder, 16545 &lhsDateValueOfSlot)) { 16546 return AttachDecision::NoAction; 16547 } 16548 16549 if (!CheckPropertyIsNativeFunction( 16550 cx_, &lhs_.toObject(), pc_, 16551 PropertyKey::Symbol(cx_->wellKnownSymbols().toPrimitive), 16552 date_toPrimitive, &lhsToPrimitiveFn, &lhsToPrimitiveHolder, 16553 &lhsToPrimitiveSlot)) { 16554 return AttachDecision::NoAction; 16555 } 16556 } 16557 16558 JSFunction* rhsDateValueOfFn = nullptr; 16559 NativeObject* rhsDateValueOfHolder = nullptr; 16560 size_t rhsDateValueOfSlot; 16561 16562 JSFunction* rhsToPrimitiveFn = nullptr; 16563 NativeObject* rhsToPrimitiveHolder = nullptr; 16564 size_t rhsToPrimitiveSlot; 16565 16566 if (rhs_.isObject()) { 16567 if (!CheckPropertyIsNativeFunction( 16568 cx_, &rhs_.toObject(), pc_, NameToId(cx_->names().valueOf), 16569 date_valueOf, &rhsDateValueOfFn, &rhsDateValueOfHolder, 16570 &rhsDateValueOfSlot)) { 16571 return AttachDecision::NoAction; 16572 } 16573 16574 if (!CheckPropertyIsNativeFunction( 16575 cx_, &rhs_.toObject(), pc_, 16576 PropertyKey::Symbol(cx_->wellKnownSymbols().toPrimitive), 16577 date_toPrimitive, &rhsToPrimitiveFn, &rhsToPrimitiveHolder, 16578 &rhsToPrimitiveSlot)) { 16579 return AttachDecision::NoAction; 16580 } 16581 } 16582 16583 ValOperandId lhsId(writer.setInputOperandId(0)); 16584 ValOperandId rhsId(writer.setInputOperandId(1)); 16585 16586 NumberOperandId lhsNumId; 16587 NumberOperandId rhsNumId; 16588 16589 if (lhs_.isObject()) { 16590 ObjOperandId lhsObjId = writer.guardToObject(lhsId); 16591 // The shape guard in EmitGuardPropertyIsNativeFunction ensures the object 16592 // is a Date object. 16593 EmitGuardPropertyIsNativeFunction(writer, &lhs_.toObject(), 16594 lhsDateValueOfFn, lhsDateValueOfHolder, 16595 lhsDateValueOfSlot, lhsObjId); 16596 EmitGuardPropertyIsNativeFunction(writer, &lhs_.toObject(), 16597 lhsToPrimitiveFn, lhsToPrimitiveHolder, 16598 lhsToPrimitiveSlot, lhsObjId); 16599 16600 ValOperandId lhsUtcValId = 16601 writer.loadFixedSlot(lhsObjId, DateObject::offsetOfUTCTimeSlot()); 16602 lhsNumId = writer.guardIsNumber(lhsUtcValId); 16603 } else { 16604 MOZ_ASSERT(lhs_.isNumber()); 16605 lhsNumId = writer.guardIsNumber(lhsId); 16606 } 16607 16608 if (rhs_.isObject()) { 16609 ObjOperandId rhsObjId = writer.guardToObject(rhsId); 16610 EmitGuardPropertyIsNativeFunction(writer, &rhs_.toObject(), 16611 rhsDateValueOfFn, rhsDateValueOfHolder, 16612 rhsDateValueOfSlot, rhsObjId); 16613 EmitGuardPropertyIsNativeFunction(writer, &rhs_.toObject(), 16614 rhsToPrimitiveFn, rhsToPrimitiveHolder, 16615 rhsToPrimitiveSlot, rhsObjId); 16616 16617 ValOperandId rhsUtcValId = 16618 writer.loadFixedSlot(rhsObjId, DateObject::offsetOfUTCTimeSlot()); 16619 rhsNumId = writer.guardIsNumber(rhsUtcValId); 16620 } else { 16621 MOZ_ASSERT(rhs_.isNumber()); 16622 rhsNumId = writer.guardIsNumber(rhsId); 16623 } 16624 16625 writer.doubleSubResult(lhsNumId, rhsNumId); 16626 trackAttached("BinaryArith.DateSub"); 16627 16628 writer.returnFromIC(); 16629 return AttachDecision::Attach; 16630 } 16631 16632 NewArrayIRGenerator::NewArrayIRGenerator(JSContext* cx, HandleScript script, 16633 jsbytecode* pc, ICState state, JSOp op, 16634 HandleObject templateObj, 16635 BaselineFrame* frame) 16636 : IRGenerator(cx, script, pc, CacheKind::NewArray, state, frame), 16637 #ifdef JS_CACHEIR_SPEW 16638 op_(op), 16639 #endif 16640 templateObject_(templateObj) { 16641 MOZ_ASSERT(templateObject_); 16642 } 16643 16644 void NewArrayIRGenerator::trackAttached(const char* name) { 16645 stubName_ = name ? name : "NotAttached"; 16646 #ifdef JS_CACHEIR_SPEW 16647 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { 16648 sp.opcodeProperty("op", op_); 16649 } 16650 #endif 16651 } 16652 16653 AttachDecision NewArrayIRGenerator::tryAttachArrayObject() { 16654 ArrayObject* arrayObj = &templateObject_->as<ArrayObject>(); 16655 16656 MOZ_ASSERT(arrayObj->numUsedFixedSlots() == 0); 16657 MOZ_ASSERT(arrayObj->numDynamicSlots() == 0); 16658 MOZ_ASSERT(!arrayObj->isSharedMemory()); 16659 16660 // The macro assembler only supports creating arrays with fixed elements. 16661 if (arrayObj->hasDynamicElements()) { 16662 return AttachDecision::NoAction; 16663 } 16664 16665 // Stub doesn't support metadata builder 16666 if (cx_->realm()->hasAllocationMetadataBuilder()) { 16667 return AttachDecision::NoAction; 16668 } 16669 16670 writer.guardNoAllocationMetadataBuilder( 16671 cx_->realm()->addressOfMetadataBuilder()); 16672 16673 gc::AllocSite* site = maybeCreateAllocSite(); 16674 if (!site) { 16675 return AttachDecision::NoAction; 16676 } 16677 16678 Shape* shape = arrayObj->shape(); 16679 uint32_t length = arrayObj->length(); 16680 16681 writer.newArrayObjectResult(length, shape, site); 16682 16683 writer.returnFromIC(); 16684 16685 trackAttached("NewArray.Object"); 16686 return AttachDecision::Attach; 16687 } 16688 16689 AttachDecision NewArrayIRGenerator::tryAttachStub() { 16690 AutoAssertNoPendingException aanpe(cx_); 16691 16692 TRY_ATTACH(tryAttachArrayObject()); 16693 16694 trackAttached(IRGenerator::NotAttached); 16695 return AttachDecision::NoAction; 16696 } 16697 16698 NewObjectIRGenerator::NewObjectIRGenerator(JSContext* cx, HandleScript script, 16699 jsbytecode* pc, ICState state, 16700 JSOp op, HandleObject templateObj, 16701 BaselineFrame* frame) 16702 : IRGenerator(cx, script, pc, CacheKind::NewObject, state, frame), 16703 #ifdef JS_CACHEIR_SPEW 16704 op_(op), 16705 #endif 16706 templateObject_(templateObj) { 16707 MOZ_ASSERT(templateObject_); 16708 } 16709 16710 void NewObjectIRGenerator::trackAttached(const char* name) { 16711 stubName_ = name ? name : "NotAttached"; 16712 #ifdef JS_CACHEIR_SPEW 16713 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { 16714 sp.opcodeProperty("op", op_); 16715 } 16716 #endif 16717 } 16718 16719 AttachDecision NewObjectIRGenerator::tryAttachPlainObject() { 16720 // Don't optimize allocations with too many dynamic slots. We use an unrolled 16721 // loop when initializing slots and this avoids generating too much code. 16722 static const uint32_t MaxDynamicSlotsToOptimize = 64; 16723 16724 NativeObject* nativeObj = &templateObject_->as<NativeObject>(); 16725 MOZ_ASSERT(nativeObj->is<PlainObject>()); 16726 16727 // Stub doesn't support metadata builder 16728 if (cx_->realm()->hasAllocationMetadataBuilder()) { 16729 return AttachDecision::NoAction; 16730 } 16731 16732 if (nativeObj->numDynamicSlots() > MaxDynamicSlotsToOptimize) { 16733 return AttachDecision::NoAction; 16734 } 16735 16736 MOZ_ASSERT(!nativeObj->hasDynamicElements()); 16737 MOZ_ASSERT(!nativeObj->isSharedMemory()); 16738 16739 gc::AllocSite* site = maybeCreateAllocSite(); 16740 if (!site) { 16741 return AttachDecision::NoAction; 16742 } 16743 16744 uint32_t numFixedSlots = nativeObj->numUsedFixedSlots(); 16745 uint32_t numDynamicSlots = nativeObj->numDynamicSlots(); 16746 gc::AllocKind allocKind = nativeObj->allocKindForTenure(); 16747 Shape* shape = nativeObj->shape(); 16748 16749 writer.guardNoAllocationMetadataBuilder( 16750 cx_->realm()->addressOfMetadataBuilder()); 16751 writer.newPlainObjectResult(numFixedSlots, numDynamicSlots, allocKind, shape, 16752 site); 16753 16754 writer.returnFromIC(); 16755 16756 trackAttached("NewObject.PlainObject"); 16757 return AttachDecision::Attach; 16758 } 16759 16760 AttachDecision NewObjectIRGenerator::tryAttachStub() { 16761 AutoAssertNoPendingException aanpe(cx_); 16762 16763 TRY_ATTACH(tryAttachPlainObject()); 16764 16765 trackAttached(IRGenerator::NotAttached); 16766 return AttachDecision::NoAction; 16767 } 16768 16769 LambdaIRGenerator::LambdaIRGenerator(JSContext* cx, HandleScript script, 16770 jsbytecode* pc, ICState state, JSOp op, 16771 Handle<JSFunction*> canonicalFunction, 16772 BaselineFrame* frame) 16773 : IRGenerator(cx, script, pc, CacheKind::Lambda, state, frame), 16774 #ifdef JS_CACHEIR_SPEW 16775 op_(op), 16776 #endif 16777 canonicalFunction_(canonicalFunction) { 16778 MOZ_ASSERT(canonicalFunction_); 16779 } 16780 16781 void LambdaIRGenerator::trackAttached(const char* name) { 16782 stubName_ = name ? name : "NotAttached"; 16783 #ifdef JS_CACHEIR_SPEW 16784 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { 16785 sp.opcodeProperty("op", op_); 16786 } 16787 #endif 16788 } 16789 16790 AttachDecision LambdaIRGenerator::tryAttachFunctionClone() { 16791 // Don't optimize asm.js module functions. 16792 if (canonicalFunction_->isNativeFun()) { 16793 MOZ_ASSERT(IsAsmJSModule(canonicalFunction_)); 16794 return AttachDecision::NoAction; 16795 } 16796 16797 // Stub doesn't support metadata builder. 16798 if (cx_->realm()->hasAllocationMetadataBuilder()) { 16799 return AttachDecision::NoAction; 16800 } 16801 16802 gc::AllocSite* site = maybeCreateAllocSite(); 16803 if (!site) { 16804 return AttachDecision::NoAction; 16805 } 16806 16807 writer.guardNoAllocationMetadataBuilder( 16808 cx_->realm()->addressOfMetadataBuilder()); 16809 16810 gc::AllocKind allocKind = canonicalFunction_->getAllocKind(); 16811 MOZ_ASSERT(allocKind == gc::AllocKind::FUNCTION || 16812 allocKind == gc::AllocKind::FUNCTION_EXTENDED); 16813 writer.newFunctionCloneResult(canonicalFunction_, allocKind, site); 16814 writer.returnFromIC(); 16815 16816 trackAttached("Lambda.FunctionClone"); 16817 return AttachDecision::Attach; 16818 } 16819 16820 AttachDecision LambdaIRGenerator::tryAttachStub() { 16821 AutoAssertNoPendingException aanpe(cx_); 16822 16823 TRY_ATTACH(tryAttachFunctionClone()); 16824 16825 trackAttached(IRGenerator::NotAttached); 16826 return AttachDecision::NoAction; 16827 } 16828 16829 CloseIterIRGenerator::CloseIterIRGenerator(JSContext* cx, HandleScript script, 16830 jsbytecode* pc, ICState state, 16831 HandleObject iter, 16832 CompletionKind kind) 16833 : IRGenerator(cx, script, pc, CacheKind::CloseIter, state), 16834 iter_(iter), 16835 kind_(kind) {} 16836 16837 void CloseIterIRGenerator::trackAttached(const char* name) { 16838 #ifdef JS_CACHEIR_SPEW 16839 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { 16840 sp.valueProperty("iter", ObjectValue(*iter_)); 16841 } 16842 #endif 16843 } 16844 16845 AttachDecision CloseIterIRGenerator::tryAttachNoReturnMethod() { 16846 Maybe<PropertyInfo> prop; 16847 NativeObject* holder = nullptr; 16848 16849 // If we can guard that the iterator does not have a |return| method, 16850 // then this CloseIter is a no-op. 16851 NativeGetPropKind kind = CanAttachNativeGetProp( 16852 cx_, iter_, NameToId(cx_->names().return_), &holder, &prop, pc_); 16853 if (kind != NativeGetPropKind::Missing) { 16854 return AttachDecision::NoAction; 16855 } 16856 MOZ_ASSERT(!holder); 16857 16858 ObjOperandId objId(writer.setInputOperandId(0)); 16859 16860 EmitMissingPropGuard(writer, &iter_->as<NativeObject>(), objId); 16861 16862 // There is no return method, so we don't have to do anything. 16863 writer.returnFromIC(); 16864 16865 trackAttached("CloseIter.NoReturn"); 16866 return AttachDecision::Attach; 16867 } 16868 16869 AttachDecision CloseIterIRGenerator::tryAttachScriptedReturn() { 16870 if (kind_ == CompletionKind::Throw) { 16871 return AttachDecision::NoAction; 16872 } 16873 16874 Maybe<PropertyInfo> prop; 16875 NativeObject* holder = nullptr; 16876 16877 NativeGetPropKind kind = CanAttachNativeGetProp( 16878 cx_, iter_, NameToId(cx_->names().return_), &holder, &prop, pc_); 16879 if (kind != NativeGetPropKind::Slot) { 16880 return AttachDecision::NoAction; 16881 } 16882 MOZ_ASSERT(holder); 16883 MOZ_ASSERT(prop->isDataProperty()); 16884 16885 size_t slot = prop->slot(); 16886 Value calleeVal = holder->getSlot(slot); 16887 if (!calleeVal.isObject() || !calleeVal.toObject().is<JSFunction>()) { 16888 return AttachDecision::NoAction; 16889 } 16890 16891 JSFunction* callee = &calleeVal.toObject().as<JSFunction>(); 16892 if (!callee->hasJitEntry()) { 16893 return AttachDecision::NoAction; 16894 } 16895 if (callee->isClassConstructor()) { 16896 return AttachDecision::NoAction; 16897 } 16898 16899 // We don't support cross-realm |return|. 16900 if (cx_->realm() != callee->realm()) { 16901 return AttachDecision::NoAction; 16902 } 16903 16904 ObjOperandId objId(writer.setInputOperandId(0)); 16905 16906 ObjOperandId holderId = 16907 EmitReadSlotGuard(writer, &iter_->as<NativeObject>(), holder, objId); 16908 16909 ValOperandId calleeValId = EmitLoadSlot(writer, holder, holderId, slot); 16910 ObjOperandId calleeId = writer.guardToObject(calleeValId); 16911 emitCalleeGuard(calleeId, callee); 16912 16913 writer.closeIterScriptedResult(objId, calleeId, callee->nargs()); 16914 16915 writer.returnFromIC(); 16916 trackAttached("CloseIter.ScriptedReturn"); 16917 16918 return AttachDecision::Attach; 16919 } 16920 16921 AttachDecision CloseIterIRGenerator::tryAttachStub() { 16922 AutoAssertNoPendingException aanpe(cx_); 16923 16924 TRY_ATTACH(tryAttachNoReturnMethod()); 16925 TRY_ATTACH(tryAttachScriptedReturn()); 16926 16927 trackAttached(IRGenerator::NotAttached); 16928 return AttachDecision::NoAction; 16929 } 16930 16931 OptimizeGetIteratorIRGenerator::OptimizeGetIteratorIRGenerator( 16932 JSContext* cx, HandleScript script, jsbytecode* pc, ICState state, 16933 HandleValue value) 16934 : IRGenerator(cx, script, pc, CacheKind::OptimizeGetIterator, state), 16935 val_(value) {} 16936 16937 AttachDecision OptimizeGetIteratorIRGenerator::tryAttachStub() { 16938 MOZ_ASSERT(cacheKind_ == CacheKind::OptimizeGetIterator); 16939 16940 AutoAssertNoPendingException aanpe(cx_); 16941 16942 TRY_ATTACH(tryAttachArray()); 16943 TRY_ATTACH(tryAttachNotOptimizable()); 16944 16945 MOZ_CRASH("Failed to attach unoptimizable case."); 16946 } 16947 16948 AttachDecision OptimizeGetIteratorIRGenerator::tryAttachArray() { 16949 if (!isFirstStub_) { 16950 return AttachDecision::NoAction; 16951 } 16952 16953 // The value must be a packed array. 16954 if (!val_.isObject()) { 16955 return AttachDecision::NoAction; 16956 } 16957 Rooted<JSObject*> obj(cx_, &val_.toObject()); 16958 if (!IsArrayWithDefaultIterator<MustBePacked::Yes>(obj, cx_)) { 16959 return AttachDecision::NoAction; 16960 } 16961 16962 ValOperandId valId(writer.setInputOperandId(0)); 16963 ObjOperandId objId = writer.guardToObject(valId); 16964 16965 // Guard the object is a packed array with Array.prototype as proto. 16966 MOZ_ASSERT(obj->is<ArrayObject>()); 16967 writer.guardShape(objId, obj->shape()); 16968 writer.guardArrayIsPacked(objId); 16969 16970 // Guard on Array.prototype[@@iterator] and %ArrayIteratorPrototype%.next. 16971 // This fuse also ensures the prototype chain for Array Iterator is 16972 // maintained and that no return method is added. 16973 writer.guardFuse(RealmFuses::FuseIndex::OptimizeGetIteratorFuse); 16974 16975 writer.loadBooleanResult(true); 16976 writer.returnFromIC(); 16977 16978 trackAttached("OptimizeGetIterator.Array.Fuse"); 16979 return AttachDecision::Attach; 16980 } 16981 16982 AttachDecision OptimizeGetIteratorIRGenerator::tryAttachNotOptimizable() { 16983 ValOperandId valId(writer.setInputOperandId(0)); 16984 16985 writer.loadBooleanResult(false); 16986 writer.returnFromIC(); 16987 16988 trackAttached("OptimizeGetIterator.NotOptimizable"); 16989 return AttachDecision::Attach; 16990 } 16991 16992 void OptimizeGetIteratorIRGenerator::trackAttached(const char* name) { 16993 stubName_ = name ? name : "NotAttached"; 16994 16995 #ifdef JS_CACHEIR_SPEW 16996 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { 16997 sp.valueProperty("val", val_); 16998 } 16999 #endif 17000 } 17001 17002 GetImportIRGenerator::GetImportIRGenerator(JSContext* cx, HandleScript script, 17003 jsbytecode* pc, ICState state) 17004 : IRGenerator(cx, script, pc, CacheKind::GetImport, state) {} 17005 17006 void GetImportIRGenerator::trackAttached(const char* name) { 17007 #ifdef JS_CACHEIR_SPEW 17008 const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name); 17009 (void)sp; // Silence unused warning 17010 #endif 17011 } 17012 17013 AttachDecision GetImportIRGenerator::tryAttachInitialized() { 17014 ModuleEnvironmentObject* env = GetModuleEnvironmentForScript(script_); 17015 MOZ_ASSERT(env); 17016 17017 jsid id = NameToId(script_->getName(pc_)); 17018 ModuleEnvironmentObject* holderEnv; 17019 Maybe<PropertyInfo> prop; 17020 MOZ_ALWAYS_TRUE(env->lookupImport(id, &holderEnv, &prop)); 17021 17022 // Imports are initialized by this point except in rare circumstances. 17023 if (holderEnv->getSlot(prop->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) { 17024 return AttachDecision::NoAction; 17025 } 17026 17027 ObjOperandId holderEnvId = writer.loadObject(holderEnv); 17028 EmitLoadSlotResult(writer, holderEnvId, holderEnv, *prop); 17029 writer.returnFromIC(); 17030 17031 trackAttached("GetImport.Initialized"); 17032 return AttachDecision::Attach; 17033 } 17034 17035 AttachDecision GetImportIRGenerator::tryAttachStub() { 17036 AutoAssertNoPendingException aanpe(cx_); 17037 17038 TRY_ATTACH(tryAttachInitialized()); 17039 17040 trackAttached(IRGenerator::NotAttached); 17041 return AttachDecision::NoAction; 17042 } 17043 17044 #ifdef JS_SIMULATOR 17045 bool js::jit::CallAnyNative(JSContext* cx, unsigned argc, Value* vp) { 17046 CallArgs args = CallArgsFromVp(argc, vp); 17047 JSObject* calleeObj = &args.callee(); 17048 17049 MOZ_ASSERT(calleeObj->is<JSFunction>()); 17050 auto* calleeFunc = &calleeObj->as<JSFunction>(); 17051 MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry()); 17052 17053 JSNative native = calleeFunc->native(); 17054 return native(cx, args.length(), args.base()); 17055 } 17056 17057 const void* js::jit::RedirectedCallAnyNative() { 17058 // The simulator requires native calls to be redirected to a 17059 // special swi instruction. If we are calling an arbitrary native 17060 // function, we can't wrap the real target ahead of time, so we 17061 // call a wrapper function (CallAnyNative) that calls the target 17062 // itself, and redirect that wrapper. 17063 JSNative target = CallAnyNative; 17064 void* rawPtr = JS_FUNC_TO_DATA_PTR(void*, target); 17065 void* redirected = Simulator::RedirectNativeFunction(rawPtr, Args_General3); 17066 return redirected; 17067 } 17068 #endif