MacroAssembler.cpp (386905B)
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/MacroAssembler-inl.h" 8 9 #include "mozilla/FloatingPoint.h" 10 #include "mozilla/Latin1.h" 11 #include "mozilla/MathAlgorithms.h" 12 #include "mozilla/XorShift128PlusRNG.h" 13 14 #include <algorithm> 15 #include <limits> 16 #include <utility> 17 18 #include "jit/AtomicOp.h" 19 #include "jit/AtomicOperations.h" 20 #include "jit/Bailouts.h" 21 #include "jit/BaselineFrame.h" 22 #include "jit/BaselineJIT.h" 23 #include "jit/JitFrames.h" 24 #include "jit/JitOptions.h" 25 #include "jit/JitRuntime.h" 26 #include "jit/JitScript.h" 27 #include "jit/MoveEmitter.h" 28 #include "jit/ReciprocalMulConstants.h" 29 #include "jit/SharedICHelpers.h" 30 #include "jit/SharedICRegisters.h" 31 #include "jit/Simulator.h" 32 #include "jit/VMFunctions.h" 33 #include "js/Conversions.h" 34 #include "js/friend/DOMProxy.h" // JS::ExpandoAndGeneration 35 #include "js/GCAPI.h" // JS::AutoCheckCannotGC 36 #include "js/ScalarType.h" // js::Scalar::Type 37 #include "util/Unicode.h" 38 #include "vm/ArgumentsObject.h" 39 #include "vm/ArrayBufferViewObject.h" 40 #include "vm/BoundFunctionObject.h" 41 #include "vm/DateObject.h" 42 #include "vm/DateTime.h" 43 #include "vm/Float16.h" 44 #include "vm/FunctionFlags.h" // js::FunctionFlags 45 #include "vm/Iteration.h" 46 #include "vm/JSContext.h" 47 #include "vm/JSFunction.h" 48 #include "vm/StringType.h" 49 #include "vm/TypedArrayObject.h" 50 #include "wasm/WasmBuiltins.h" 51 #include "wasm/WasmCodegenConstants.h" 52 #include "wasm/WasmCodegenTypes.h" 53 #include "wasm/WasmInstance.h" 54 #include "wasm/WasmInstanceData.h" 55 #include "wasm/WasmMemory.h" 56 #include "wasm/WasmTypeDef.h" 57 #include "wasm/WasmValidate.h" 58 59 #include "jit/TemplateObject-inl.h" 60 #include "vm/BytecodeUtil-inl.h" 61 #include "vm/Interpreter-inl.h" 62 #include "vm/JSObject-inl.h" 63 #include "wasm/WasmGcObject-inl.h" 64 65 using namespace js; 66 using namespace js::jit; 67 68 using JS::GenericNaN; 69 70 using mozilla::CheckedInt; 71 72 TrampolinePtr MacroAssembler::preBarrierTrampoline(MIRType type) { 73 const JitRuntime* rt = runtime()->jitRuntime(); 74 return rt->preBarrier(type); 75 } 76 77 template <typename T> 78 void MacroAssembler::storeToTypedFloatArray(Scalar::Type arrayType, 79 FloatRegister value, const T& dest, 80 Register temp, 81 LiveRegisterSet volatileLiveRegs) { 82 switch (arrayType) { 83 case Scalar::Float16: 84 storeFloat16(value, dest, temp, volatileLiveRegs); 85 break; 86 case Scalar::Float32: { 87 if (value.isDouble()) { 88 ScratchFloat32Scope fpscratch(*this); 89 convertDoubleToFloat32(value, fpscratch); 90 storeFloat32(fpscratch, dest); 91 } else { 92 MOZ_ASSERT(value.isSingle()); 93 storeFloat32(value, dest); 94 } 95 break; 96 } 97 case Scalar::Float64: 98 MOZ_ASSERT(value.isDouble()); 99 storeDouble(value, dest); 100 break; 101 default: 102 MOZ_CRASH("Invalid typed array type"); 103 } 104 } 105 106 template void MacroAssembler::storeToTypedFloatArray( 107 Scalar::Type arrayType, FloatRegister value, const BaseIndex& dest, 108 Register temp, LiveRegisterSet volatileLiveRegs); 109 template void MacroAssembler::storeToTypedFloatArray( 110 Scalar::Type arrayType, FloatRegister value, const Address& dest, 111 Register temp, LiveRegisterSet volatileLiveRegs); 112 113 void MacroAssembler::boxUint32(Register source, ValueOperand dest, 114 Uint32Mode mode, Label* fail) { 115 switch (mode) { 116 // Fail if the value does not fit in an int32. 117 case Uint32Mode::FailOnDouble: { 118 branchTest32(Assembler::Signed, source, source, fail); 119 tagValue(JSVAL_TYPE_INT32, source, dest); 120 break; 121 } 122 case Uint32Mode::ForceDouble: { 123 // Always convert the value to double. 124 ScratchDoubleScope fpscratch(*this); 125 convertUInt32ToDouble(source, fpscratch); 126 boxDouble(fpscratch, dest, fpscratch); 127 break; 128 } 129 } 130 } 131 132 template <typename T> 133 void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const T& src, 134 AnyRegister dest, Register temp1, 135 Register temp2, Label* fail, 136 LiveRegisterSet volatileLiveRegs) { 137 switch (arrayType) { 138 case Scalar::Int8: 139 load8SignExtend(src, dest.gpr()); 140 break; 141 case Scalar::Uint8: 142 case Scalar::Uint8Clamped: 143 load8ZeroExtend(src, dest.gpr()); 144 break; 145 case Scalar::Int16: 146 load16SignExtend(src, dest.gpr()); 147 break; 148 case Scalar::Uint16: 149 load16ZeroExtend(src, dest.gpr()); 150 break; 151 case Scalar::Int32: 152 load32(src, dest.gpr()); 153 break; 154 case Scalar::Uint32: 155 if (dest.isFloat()) { 156 load32(src, temp1); 157 convertUInt32ToDouble(temp1, dest.fpu()); 158 } else { 159 load32(src, dest.gpr()); 160 161 // Bail out if the value doesn't fit into a signed int32 value. This 162 // is what allows MLoadUnboxedScalar to have a type() of 163 // MIRType::Int32 for UInt32 array loads. 164 branchTest32(Assembler::Signed, dest.gpr(), dest.gpr(), fail); 165 } 166 break; 167 case Scalar::Float16: 168 loadFloat16(src, dest.fpu(), temp1, temp2, volatileLiveRegs); 169 canonicalizeFloat(dest.fpu()); 170 break; 171 case Scalar::Float32: 172 loadFloat32(src, dest.fpu()); 173 canonicalizeFloat(dest.fpu()); 174 break; 175 case Scalar::Float64: 176 loadDouble(src, dest.fpu()); 177 canonicalizeDouble(dest.fpu()); 178 break; 179 case Scalar::BigInt64: 180 case Scalar::BigUint64: 181 default: 182 MOZ_CRASH("Invalid typed array type"); 183 } 184 } 185 186 template void MacroAssembler::loadFromTypedArray( 187 Scalar::Type arrayType, const Address& src, AnyRegister dest, 188 Register temp1, Register temp2, Label* fail, 189 LiveRegisterSet volatileLiveRegs); 190 template void MacroAssembler::loadFromTypedArray( 191 Scalar::Type arrayType, const BaseIndex& src, AnyRegister dest, 192 Register temp1, Register temp2, Label* fail, 193 LiveRegisterSet volatileLiveRegs); 194 195 void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, 196 const BaseIndex& src, 197 const ValueOperand& dest, 198 Uint32Mode uint32Mode, Register temp, 199 Label* fail, 200 LiveRegisterSet volatileLiveRegs) { 201 switch (arrayType) { 202 case Scalar::Int8: 203 case Scalar::Uint8: 204 case Scalar::Uint8Clamped: 205 case Scalar::Int16: 206 case Scalar::Uint16: 207 case Scalar::Int32: 208 loadFromTypedArray(arrayType, src, AnyRegister(dest.scratchReg()), 209 InvalidReg, InvalidReg, nullptr, LiveRegisterSet{}); 210 tagValue(JSVAL_TYPE_INT32, dest.scratchReg(), dest); 211 break; 212 case Scalar::Uint32: 213 load32(src, dest.scratchReg()); 214 boxUint32(dest.scratchReg(), dest, uint32Mode, fail); 215 break; 216 case Scalar::Float16: { 217 ScratchDoubleScope dscratch(*this); 218 FloatRegister fscratch = dscratch.asSingle(); 219 loadFromTypedArray(arrayType, src, AnyRegister(fscratch), 220 dest.scratchReg(), temp, nullptr, volatileLiveRegs); 221 convertFloat32ToDouble(fscratch, dscratch); 222 boxDouble(dscratch, dest, dscratch); 223 break; 224 } 225 case Scalar::Float32: { 226 ScratchDoubleScope dscratch(*this); 227 FloatRegister fscratch = dscratch.asSingle(); 228 loadFromTypedArray(arrayType, src, AnyRegister(fscratch), InvalidReg, 229 InvalidReg, nullptr, LiveRegisterSet{}); 230 convertFloat32ToDouble(fscratch, dscratch); 231 boxDouble(dscratch, dest, dscratch); 232 break; 233 } 234 case Scalar::Float64: { 235 ScratchDoubleScope fpscratch(*this); 236 loadFromTypedArray(arrayType, src, AnyRegister(fpscratch), InvalidReg, 237 InvalidReg, nullptr, LiveRegisterSet{}); 238 boxDouble(fpscratch, dest, fpscratch); 239 break; 240 } 241 case Scalar::BigInt64: 242 case Scalar::BigUint64: 243 default: 244 MOZ_CRASH("Invalid typed array type"); 245 } 246 } 247 248 void MacroAssembler::loadFromTypedBigIntArray(Scalar::Type arrayType, 249 const BaseIndex& src, 250 const ValueOperand& dest, 251 Register bigInt, 252 Register64 temp) { 253 MOZ_ASSERT(Scalar::isBigIntType(arrayType)); 254 255 load64(src, temp); 256 initializeBigInt64(arrayType, bigInt, temp); 257 tagValue(JSVAL_TYPE_BIGINT, bigInt, dest); 258 } 259 260 // Inlined version of gc::CheckAllocatorState that checks the bare essentials 261 // and bails for anything that cannot be handled with our jit allocators. 262 void MacroAssembler::checkAllocatorState(Register temp, gc::AllocKind allocKind, 263 Label* fail) { 264 // Don't execute the inline path if GC probes are built in. 265 #ifdef JS_GC_PROBES 266 jump(fail); 267 #endif 268 269 #ifdef JS_GC_ZEAL 270 // Don't execute the inline path if gc zeal or tracing are active. 271 const uint32_t* ptrZealModeBits = runtime()->addressOfGCZealModeBits(); 272 branch32(Assembler::NotEqual, AbsoluteAddress(ptrZealModeBits), Imm32(0), 273 fail); 274 #endif 275 276 // If the zone has a realm with an object allocation metadata hook, emit a 277 // guard for this. Note that IC stubs and some other trampolines can be shared 278 // across realms, so we don't bake in a realm pointer. 279 if (gc::IsObjectAllocKind(allocKind) && 280 realm()->zone()->hasRealmWithAllocMetadataBuilder()) { 281 loadJSContext(temp); 282 loadPtr(Address(temp, JSContext::offsetOfRealm()), temp); 283 branchPtr(Assembler::NotEqual, 284 Address(temp, Realm::offsetOfAllocationMetadataBuilder()), 285 ImmWord(0), fail); 286 } 287 } 288 289 bool MacroAssembler::shouldNurseryAllocate(gc::AllocKind allocKind, 290 gc::Heap initialHeap) { 291 // Note that Ion elides barriers on writes to objects known to be in the 292 // nursery, so any allocation that can be made into the nursery must be made 293 // into the nursery, even if the nursery is disabled. At runtime these will 294 // take the out-of-line path, which is required to insert a barrier for the 295 // initializing writes. 296 return IsNurseryAllocable(allocKind) && initialHeap != gc::Heap::Tenured; 297 } 298 299 // Inline version of Nursery::allocateObject. If the object has dynamic slots, 300 // this fills in the slots_ pointer. 301 void MacroAssembler::nurseryAllocateObject(Register result, Register temp, 302 gc::AllocKind allocKind, 303 size_t nDynamicSlots, Label* fail, 304 const AllocSiteInput& allocSite) { 305 MOZ_ASSERT(IsNurseryAllocable(allocKind)); 306 307 // Currently the JIT does not nursery allocate foreground finalized 308 // objects. This is allowed for objects that support this and have the 309 // JSCLASS_SKIP_NURSERY_FINALIZE class flag set. It's hard to assert that here 310 // though so disallow all foreground finalized objects for now. 311 MOZ_ASSERT(!IsForegroundFinalized(allocKind)); 312 313 // We still need to allocate in the nursery, per the comment in 314 // shouldNurseryAllocate; however, we need to insert into the 315 // mallocedBuffers set, so bail to do the nursery allocation in the 316 // interpreter. 317 if (nDynamicSlots >= Nursery::MaxNurseryBufferSize / sizeof(Value)) { 318 jump(fail); 319 return; 320 } 321 322 // Check whether this allocation site needs pretenuring. This dynamic check 323 // only happens for baseline code. 324 if (allocSite.is<Register>()) { 325 Register site = allocSite.as<Register>(); 326 branchTestPtr(Assembler::NonZero, 327 Address(site, gc::AllocSite::offsetOfScriptAndState()), 328 Imm32(gc::AllocSite::LONG_LIVED_BIT), fail); 329 } 330 331 // No explicit check for nursery.isEnabled() is needed, as the comparison 332 // with the nursery's end will always fail in such cases. 333 CompileZone* zone = realm()->zone(); 334 size_t thingSize = gc::Arena::thingSize(allocKind); 335 size_t totalSize = thingSize; 336 if (nDynamicSlots) { 337 totalSize += ObjectSlots::allocSize(nDynamicSlots); 338 } 339 MOZ_ASSERT(totalSize < INT32_MAX); 340 MOZ_ASSERT(totalSize % gc::CellAlignBytes == 0); 341 342 bumpPointerAllocate(result, temp, fail, zone, JS::TraceKind::Object, 343 totalSize, allocSite); 344 345 if (nDynamicSlots) { 346 store32(Imm32(nDynamicSlots), 347 Address(result, thingSize + ObjectSlots::offsetOfCapacity())); 348 store32( 349 Imm32(0), 350 Address(result, thingSize + ObjectSlots::offsetOfDictionarySlotSpan())); 351 store64(Imm64(ObjectSlots::NoUniqueIdInDynamicSlots), 352 Address(result, thingSize + ObjectSlots::offsetOfMaybeUniqueId())); 353 computeEffectiveAddress( 354 Address(result, thingSize + ObjectSlots::offsetOfSlots()), temp); 355 storePtr(temp, Address(result, NativeObject::offsetOfSlots())); 356 } 357 } 358 359 // Inlined version of FreeSpan::allocate. This does not fill in slots_. 360 void MacroAssembler::freeListAllocate(Register result, Register temp, 361 gc::AllocKind allocKind, Label* fail) { 362 CompileZone* zone = realm()->zone(); 363 int thingSize = int(gc::Arena::thingSize(allocKind)); 364 365 Label fallback; 366 Label success; 367 368 // Load the first and last offsets of |zone|'s free list for |allocKind|. 369 // If there is no room remaining in the span, fall back to get the next one. 370 gc::FreeSpan** ptrFreeList = zone->addressOfFreeList(allocKind); 371 loadPtr(AbsoluteAddress(ptrFreeList), temp); 372 load16ZeroExtend(Address(temp, js::gc::FreeSpan::offsetOfFirst()), result); 373 load16ZeroExtend(Address(temp, js::gc::FreeSpan::offsetOfLast()), temp); 374 branch32(Assembler::AboveOrEqual, result, temp, &fallback); 375 376 // Bump the offset for the next allocation. 377 add32(Imm32(thingSize), result); 378 loadPtr(AbsoluteAddress(ptrFreeList), temp); 379 store16(result, Address(temp, js::gc::FreeSpan::offsetOfFirst())); 380 sub32(Imm32(thingSize), result); 381 addPtr(temp, result); // Turn the offset into a pointer. 382 jump(&success); 383 384 bind(&fallback); 385 // If there are no free spans left, we bail to finish the allocation. The 386 // interpreter will call the GC allocator to set up a new arena to allocate 387 // from, after which we can resume allocating in the jit. 388 branchTest32(Assembler::Zero, result, result, fail); 389 loadPtr(AbsoluteAddress(ptrFreeList), temp); 390 addPtr(temp, result); // Turn the offset into a pointer. 391 Push(result); 392 // Update the free list to point to the next span (which may be empty). 393 load32(Address(result, 0), result); 394 store32(result, Address(temp, js::gc::FreeSpan::offsetOfFirst())); 395 Pop(result); 396 397 bind(&success); 398 399 if (runtime()->geckoProfiler().enabled()) { 400 uint32_t* countAddress = zone->addressOfTenuredAllocCount(); 401 movePtr(ImmPtr(countAddress), temp); 402 add32(Imm32(1), Address(temp, 0)); 403 } 404 } 405 406 // Inlined equivalent of gc::AllocateObject, without failure case handling. 407 void MacroAssembler::allocateObject(Register result, Register temp, 408 gc::AllocKind allocKind, 409 uint32_t nDynamicSlots, 410 gc::Heap initialHeap, Label* fail, 411 const AllocSiteInput& allocSite) { 412 MOZ_ASSERT(gc::IsObjectAllocKind(allocKind)); 413 414 checkAllocatorState(temp, allocKind, fail); 415 416 if (shouldNurseryAllocate(allocKind, initialHeap)) { 417 MOZ_ASSERT(initialHeap == gc::Heap::Default); 418 return nurseryAllocateObject(result, temp, allocKind, nDynamicSlots, fail, 419 allocSite); 420 } 421 422 // Fall back to calling into the VM to allocate objects in the tenured heap 423 // that have dynamic slots. 424 if (nDynamicSlots) { 425 jump(fail); 426 return; 427 } 428 429 return freeListAllocate(result, temp, allocKind, fail); 430 } 431 432 void MacroAssembler::createGCObject(Register obj, Register temp, 433 const TemplateObject& templateObj, 434 gc::Heap initialHeap, Label* fail, 435 bool initContents /* = true */, 436 const AllocSiteInput& allocSite) { 437 gc::AllocKind allocKind = templateObj.getAllocKind(); 438 MOZ_ASSERT(gc::IsObjectAllocKind(allocKind)); 439 440 uint32_t nDynamicSlots = 0; 441 if (templateObj.isNativeObject()) { 442 const TemplateNativeObject& ntemplate = 443 templateObj.asTemplateNativeObject(); 444 nDynamicSlots = ntemplate.numDynamicSlots(); 445 } 446 447 allocateObject(obj, temp, allocKind, nDynamicSlots, initialHeap, fail, 448 allocSite); 449 initGCThing(obj, temp, templateObj, initContents); 450 } 451 452 void MacroAssembler::createPlainGCObject( 453 Register result, Register shape, Register temp, Register temp2, 454 uint32_t numFixedSlots, uint32_t numDynamicSlots, gc::AllocKind allocKind, 455 gc::Heap initialHeap, Label* fail, const AllocSiteInput& allocSite, 456 bool initContents /* = true */) { 457 MOZ_ASSERT(gc::IsObjectAllocKind(allocKind)); 458 MOZ_ASSERT(shape != temp, "shape can overlap with temp2, but not temp"); 459 460 // Allocate object. 461 allocateObject(result, temp, allocKind, numDynamicSlots, initialHeap, fail, 462 allocSite); 463 464 // Initialize shape field. 465 storePtr(shape, Address(result, JSObject::offsetOfShape())); 466 467 // If the object has dynamic slots, allocateObject will initialize 468 // the slots field. If not, we must initialize it now. 469 if (numDynamicSlots == 0) { 470 storePtr(ImmPtr(emptyObjectSlots), 471 Address(result, NativeObject::offsetOfSlots())); 472 } 473 474 // Initialize elements field. 475 storePtr(ImmPtr(emptyObjectElements), 476 Address(result, NativeObject::offsetOfElements())); 477 478 // Initialize fixed slots. 479 if (initContents) { 480 fillSlotsWithUndefined(Address(result, NativeObject::getFixedSlotOffset(0)), 481 temp, 0, numFixedSlots); 482 } 483 484 // Initialize dynamic slots. 485 if (numDynamicSlots > 0) { 486 loadPtr(Address(result, NativeObject::offsetOfSlots()), temp2); 487 fillSlotsWithUndefined(Address(temp2, 0), temp, 0, numDynamicSlots); 488 } 489 } 490 491 void MacroAssembler::createArrayWithFixedElements( 492 Register result, Register shape, Register temp, Register dynamicSlotsTemp, 493 uint32_t arrayLength, uint32_t arrayCapacity, uint32_t numUsedDynamicSlots, 494 uint32_t numDynamicSlots, gc::AllocKind allocKind, gc::Heap initialHeap, 495 Label* fail, const AllocSiteInput& allocSite) { 496 MOZ_ASSERT(gc::IsObjectAllocKind(allocKind)); 497 MOZ_ASSERT(shape != temp, "shape can overlap with temp2, but not temp"); 498 MOZ_ASSERT(result != temp); 499 500 // This only supports allocating arrays with fixed elements and does not 501 // support any dynamic elements. 502 MOZ_ASSERT(arrayCapacity >= arrayLength); 503 MOZ_ASSERT(gc::GetGCKindSlots(allocKind) >= 504 arrayCapacity + ObjectElements::VALUES_PER_HEADER); 505 506 MOZ_ASSERT(numUsedDynamicSlots <= numDynamicSlots); 507 508 // Allocate object. 509 allocateObject(result, temp, allocKind, numDynamicSlots, initialHeap, fail, 510 allocSite); 511 512 // Initialize shape field. 513 storePtr(shape, Address(result, JSObject::offsetOfShape())); 514 515 // If the object has dynamic slots, allocateObject will initialize 516 // the slots field. If not, we must initialize it now. 517 if (numDynamicSlots == 0) { 518 storePtr(ImmPtr(emptyObjectSlots), 519 Address(result, NativeObject::offsetOfSlots())); 520 } 521 522 // Initialize elements pointer for fixed (inline) elements. 523 computeEffectiveAddress( 524 Address(result, NativeObject::offsetOfFixedElements()), temp); 525 storePtr(temp, Address(result, NativeObject::offsetOfElements())); 526 527 // Initialize elements header. 528 store32(Imm32(ObjectElements::FIXED), 529 Address(temp, ObjectElements::offsetOfFlags())); 530 store32(Imm32(0), Address(temp, ObjectElements::offsetOfInitializedLength())); 531 store32(Imm32(arrayCapacity), 532 Address(temp, ObjectElements::offsetOfCapacity())); 533 store32(Imm32(arrayLength), Address(temp, ObjectElements::offsetOfLength())); 534 535 // Initialize dynamic slots. 536 if (numUsedDynamicSlots > 0) { 537 MOZ_ASSERT(dynamicSlotsTemp != temp); 538 MOZ_ASSERT(dynamicSlotsTemp != InvalidReg); 539 loadPtr(Address(result, NativeObject::offsetOfSlots()), dynamicSlotsTemp); 540 fillSlotsWithUndefined(Address(dynamicSlotsTemp, 0), temp, 0, 541 numUsedDynamicSlots); 542 } 543 } 544 545 void MacroAssembler::createFunctionClone(Register result, Register canonical, 546 Register envChain, Register temp, 547 gc::AllocKind allocKind, Label* fail, 548 const AllocSiteInput& allocSite) { 549 MOZ_ASSERT(allocKind == gc::AllocKind::FUNCTION || 550 allocKind == gc::AllocKind::FUNCTION_EXTENDED); 551 MOZ_ASSERT(result != temp); 552 553 // Allocate object. 554 size_t numDynamicSlots = 0; 555 gc::Heap initialHeap = gc::Heap::Default; 556 allocateObject(result, temp, allocKind, numDynamicSlots, initialHeap, fail, 557 allocSite); 558 559 // Initialize shape field. 560 loadPtr(Address(canonical, JSObject::offsetOfShape()), temp); 561 storePtr(temp, Address(result, JSObject::offsetOfShape())); 562 563 // Initialize dynamic slots and elements pointers. 564 storePtr(ImmPtr(emptyObjectSlots), 565 Address(result, NativeObject::offsetOfSlots())); 566 storePtr(ImmPtr(emptyObjectElements), 567 Address(result, NativeObject::offsetOfElements())); 568 569 // Initialize FlagsAndArgCountSlot. 570 storeValue(Address(canonical, JSFunction::offsetOfFlagsAndArgCount()), 571 Address(result, JSFunction::offsetOfFlagsAndArgCount()), temp); 572 573 // Initialize NativeFuncOrInterpretedEnvSlot. 574 storeValue(JSVAL_TYPE_OBJECT, envChain, 575 Address(result, JSFunction::offsetOfEnvironment())); 576 577 #ifdef DEBUG 578 // The new function must be allocated in the nursery if the nursery is 579 // enabled. Assert no post-barrier is needed. 580 Label ok; 581 branchPtrInNurseryChunk(Assembler::Equal, result, temp, &ok); 582 branchPtrInNurseryChunk(Assembler::NotEqual, envChain, temp, &ok); 583 assumeUnreachable("Missing post write barrier in createFunctionClone"); 584 bind(&ok); 585 #endif 586 587 // Initialize NativeJitInfoOrInterpretedScriptSlot. This is a BaseScript* 588 // pointer stored as PrivateValue. 589 loadPrivate(Address(canonical, JSFunction::offsetOfJitInfoOrScript()), temp); 590 storePrivateValue(temp, 591 Address(result, JSFunction::offsetOfJitInfoOrScript())); 592 593 // Initialize AtomSlot. 594 storeValue(Address(canonical, JSFunction::offsetOfAtom()), 595 Address(result, JSFunction::offsetOfAtom()), temp); 596 597 // Initialize extended slots. 598 if (allocKind == gc::AllocKind::FUNCTION_EXTENDED) { 599 for (size_t i = 0; i < FunctionExtended::NUM_EXTENDED_SLOTS; i++) { 600 Address addr(result, FunctionExtended::offsetOfExtendedSlot(i)); 601 storeValue(UndefinedValue(), addr); 602 } 603 } 604 } 605 606 // Inline version of Nursery::allocateString. 607 void MacroAssembler::nurseryAllocateString(Register result, Register temp, 608 gc::AllocKind allocKind, 609 Label* fail) { 610 MOZ_ASSERT(IsNurseryAllocable(allocKind)); 611 612 // No explicit check for nursery.isEnabled() is needed, as the comparison 613 // with the nursery's end will always fail in such cases. 614 615 CompileZone* zone = realm()->zone(); 616 size_t thingSize = gc::Arena::thingSize(allocKind); 617 bumpPointerAllocate(result, temp, fail, zone, JS::TraceKind::String, 618 thingSize); 619 } 620 621 // Inline version of Nursery::allocateBigInt. 622 void MacroAssembler::nurseryAllocateBigInt(Register result, Register temp, 623 Label* fail) { 624 MOZ_ASSERT(IsNurseryAllocable(gc::AllocKind::BIGINT)); 625 626 // No explicit check for nursery.isEnabled() is needed, as the comparison 627 // with the nursery's end will always fail in such cases. 628 629 CompileZone* zone = realm()->zone(); 630 size_t thingSize = gc::Arena::thingSize(gc::AllocKind::BIGINT); 631 632 bumpPointerAllocate(result, temp, fail, zone, JS::TraceKind::BigInt, 633 thingSize); 634 } 635 636 static bool IsNurseryAllocEnabled(CompileZone* zone, JS::TraceKind kind) { 637 switch (kind) { 638 case JS::TraceKind::Object: 639 return zone->allocNurseryObjects(); 640 case JS::TraceKind::String: 641 return zone->allocNurseryStrings(); 642 case JS::TraceKind::BigInt: 643 return zone->allocNurseryBigInts(); 644 default: 645 MOZ_CRASH("Bad nursery allocation kind"); 646 } 647 } 648 649 // This function handles nursery allocations for JS. For wasm, see 650 // MacroAssembler::wasmBumpPointerAllocate. 651 void MacroAssembler::bumpPointerAllocate(Register result, Register temp, 652 Label* fail, CompileZone* zone, 653 JS::TraceKind traceKind, uint32_t size, 654 const AllocSiteInput& allocSite) { 655 MOZ_ASSERT(size >= gc::MinCellSize); 656 657 uint32_t totalSize = size + Nursery::nurseryCellHeaderSize(); 658 MOZ_ASSERT(totalSize < INT32_MAX, "Nursery allocation too large"); 659 MOZ_ASSERT(totalSize % gc::CellAlignBytes == 0); 660 661 // We know statically whether nursery allocation is enable for a particular 662 // kind because we discard JIT code when this changes. 663 if (!IsNurseryAllocEnabled(zone, traceKind)) { 664 jump(fail); 665 return; 666 } 667 668 // Use a relative 32 bit offset to the Nursery position_ to currentEnd_ to 669 // avoid 64-bit immediate loads. 670 void* posAddr = zone->addressOfNurseryPosition(); 671 int32_t endOffset = Nursery::offsetOfCurrentEndFromPosition(); 672 673 movePtr(ImmPtr(posAddr), temp); 674 loadPtr(Address(temp, 0), result); 675 addPtr(Imm32(totalSize), result); 676 branchPtr(Assembler::Below, Address(temp, endOffset), result, fail); 677 storePtr(result, Address(temp, 0)); 678 subPtr(Imm32(size), result); 679 680 if (allocSite.is<gc::CatchAllAllocSite>()) { 681 // No allocation site supplied. This is the case when called from Warp, or 682 // from places that don't support pretenuring. 683 gc::CatchAllAllocSite siteKind = allocSite.as<gc::CatchAllAllocSite>(); 684 gc::AllocSite* site = zone->catchAllAllocSite(traceKind, siteKind); 685 uintptr_t headerWord = gc::NurseryCellHeader::MakeValue(site, traceKind); 686 storePtr(ImmWord(headerWord), 687 Address(result, -js::Nursery::nurseryCellHeaderSize())); 688 689 if (traceKind != JS::TraceKind::Object || 690 runtime()->geckoProfiler().enabled()) { 691 // Update the catch all allocation site, which his is used to calculate 692 // nursery allocation counts so we can determine whether to disable 693 // nursery allocation of strings and bigints. 694 uint32_t* countAddress = site->nurseryAllocCountAddress(); 695 CheckedInt<int32_t> counterOffset = 696 (CheckedInt<uintptr_t>(uintptr_t(countAddress)) - 697 CheckedInt<uintptr_t>(uintptr_t(posAddr))) 698 .toChecked<int32_t>(); 699 if (counterOffset.isValid()) { 700 add32(Imm32(1), Address(temp, counterOffset.value())); 701 } else { 702 movePtr(ImmPtr(countAddress), temp); 703 add32(Imm32(1), Address(temp, 0)); 704 } 705 } 706 } else { 707 // Update allocation site and store pointer in the nursery cell header. This 708 // is only used from baseline. 709 Register site = allocSite.as<Register>(); 710 updateAllocSite(temp, result, zone, site); 711 // See NurseryCellHeader::MakeValue. 712 orPtr(Imm32(int32_t(traceKind)), site); 713 storePtr(site, Address(result, -js::Nursery::nurseryCellHeaderSize())); 714 } 715 } 716 717 // Update the allocation site in the same way as Nursery::allocateCell. 718 void MacroAssembler::updateAllocSite(Register temp, Register result, 719 CompileZone* zone, Register site) { 720 Label done; 721 722 add32(Imm32(1), Address(site, gc::AllocSite::offsetOfNurseryAllocCount())); 723 724 branch32(Assembler::NotEqual, 725 Address(site, gc::AllocSite::offsetOfNurseryAllocCount()), 726 Imm32(js::gc::NormalSiteAttentionThreshold), &done); 727 728 loadPtr(AbsoluteAddress(zone->addressOfNurseryAllocatedSites()), temp); 729 storePtr(temp, Address(site, gc::AllocSite::offsetOfNextNurseryAllocated())); 730 storePtr(site, AbsoluteAddress(zone->addressOfNurseryAllocatedSites())); 731 732 bind(&done); 733 } 734 735 // Inlined equivalent of gc::AllocateString, jumping to fail if nursery 736 // allocation requested but unsuccessful. 737 void MacroAssembler::allocateString(Register result, Register temp, 738 gc::AllocKind allocKind, 739 gc::Heap initialHeap, Label* fail) { 740 MOZ_ASSERT(allocKind == gc::AllocKind::STRING || 741 allocKind == gc::AllocKind::FAT_INLINE_STRING); 742 743 checkAllocatorState(temp, allocKind, fail); 744 745 if (shouldNurseryAllocate(allocKind, initialHeap)) { 746 MOZ_ASSERT(initialHeap == gc::Heap::Default); 747 return nurseryAllocateString(result, temp, allocKind, fail); 748 } 749 750 freeListAllocate(result, temp, allocKind, fail); 751 } 752 753 void MacroAssembler::newGCString(Register result, Register temp, 754 gc::Heap initialHeap, Label* fail) { 755 allocateString(result, temp, js::gc::AllocKind::STRING, initialHeap, fail); 756 } 757 758 void MacroAssembler::newGCFatInlineString(Register result, Register temp, 759 gc::Heap initialHeap, Label* fail) { 760 allocateString(result, temp, js::gc::AllocKind::FAT_INLINE_STRING, 761 initialHeap, fail); 762 } 763 764 void MacroAssembler::newGCBigInt(Register result, Register temp, 765 gc::Heap initialHeap, Label* fail) { 766 constexpr gc::AllocKind allocKind = gc::AllocKind::BIGINT; 767 768 checkAllocatorState(temp, allocKind, fail); 769 770 if (shouldNurseryAllocate(allocKind, initialHeap)) { 771 MOZ_ASSERT(initialHeap == gc::Heap::Default); 772 return nurseryAllocateBigInt(result, temp, fail); 773 } 774 775 freeListAllocate(result, temp, allocKind, fail); 776 } 777 778 void MacroAssembler::preserveWrapper(Register wrapper, Register scratchSuccess, 779 Register scratch2, 780 const LiveRegisterSet& liveRegs) { 781 Label done, abiCall; 782 CompileZone* zone = realm()->zone(); 783 784 loadPtr(AbsoluteAddress(zone->zone()->addressOfPreservedWrappersCount()), 785 scratchSuccess); 786 branchPtr(Assembler::Equal, 787 AbsoluteAddress(zone->zone()->addressOfPreservedWrappersCapacity()), 788 scratchSuccess, &abiCall); 789 loadPtr(AbsoluteAddress(zone->zone()->addressOfPreservedWrappers()), 790 scratch2); 791 792 storePtr(wrapper, BaseIndex(scratch2, scratchSuccess, ScalePointer)); 793 addPtr(Imm32(1), scratchSuccess); 794 storePtr(scratchSuccess, 795 AbsoluteAddress(zone->zone()->addressOfPreservedWrappersCount())); 796 move32(Imm32(1), scratchSuccess); 797 798 jump(&done); 799 bind(&abiCall); 800 LiveRegisterSet save; 801 save.set() = RegisterSet::Intersect(liveRegs.set(), RegisterSet::Volatile()); 802 PushRegsInMask(save); 803 804 using Fn = bool (*)(JSContext* cx, JSObject* wrapper); 805 setupUnalignedABICall(scratch2); 806 loadJSContext(scratch2); 807 passABIArg(scratch2); 808 passABIArg(wrapper); 809 callWithABI<Fn, js::jit::PreserveWrapper>(); 810 storeCallBoolResult(scratchSuccess); 811 812 MOZ_ASSERT(!save.has(scratchSuccess)); 813 PopRegsInMask(save); 814 bind(&done); 815 } 816 817 void MacroAssembler::copySlotsFromTemplate( 818 Register obj, const TemplateNativeObject& templateObj, uint32_t start, 819 uint32_t end) { 820 uint32_t nfixed = std::min(templateObj.numFixedSlots(), end); 821 for (unsigned i = start; i < nfixed; i++) { 822 // Template objects are not exposed to script and therefore immutable. 823 // However, regexp template objects are sometimes used directly (when 824 // the cloning is not observable), and therefore we can end up with a 825 // non-zero lastIndex. Detect this case here and just substitute 0, to 826 // avoid racing with the main thread updating this slot. 827 Value v; 828 if (templateObj.isRegExpObject() && i == RegExpObject::lastIndexSlot()) { 829 v = Int32Value(0); 830 } else { 831 v = templateObj.getSlot(i); 832 } 833 storeValue(v, Address(obj, NativeObject::getFixedSlotOffset(i))); 834 } 835 } 836 837 void MacroAssembler::fillSlotsWithConstantValue(Address base, Register temp, 838 uint32_t start, uint32_t end, 839 const Value& v) { 840 MOZ_ASSERT(v.isUndefined() || IsUninitializedLexical(v)); 841 842 if (start >= end) { 843 return; 844 } 845 846 #ifdef JS_NUNBOX32 847 // We only have a single spare register, so do the initialization as two 848 // strided writes of the tag and body. 849 Address addr = base; 850 move32(Imm32(v.toNunboxPayload()), temp); 851 for (unsigned i = start; i < end; ++i, addr.offset += sizeof(GCPtr<Value>)) { 852 store32(temp, ToPayload(addr)); 853 } 854 855 addr = base; 856 move32(Imm32(v.toNunboxTag()), temp); 857 for (unsigned i = start; i < end; ++i, addr.offset += sizeof(GCPtr<Value>)) { 858 store32(temp, ToType(addr)); 859 } 860 #else 861 moveValue(v, ValueOperand(temp)); 862 for (uint32_t i = start; i < end; ++i, base.offset += sizeof(GCPtr<Value>)) { 863 storePtr(temp, base); 864 } 865 #endif 866 } 867 868 void MacroAssembler::fillSlotsWithUndefined(Address base, Register temp, 869 uint32_t start, uint32_t end) { 870 fillSlotsWithConstantValue(base, temp, start, end, UndefinedValue()); 871 } 872 873 void MacroAssembler::fillSlotsWithUninitialized(Address base, Register temp, 874 uint32_t start, uint32_t end) { 875 fillSlotsWithConstantValue(base, temp, start, end, 876 MagicValue(JS_UNINITIALIZED_LEXICAL)); 877 } 878 879 static std::pair<uint32_t, uint32_t> FindStartOfUninitializedAndUndefinedSlots( 880 const TemplateNativeObject& templateObj, uint32_t nslots) { 881 MOZ_ASSERT(nslots == templateObj.slotSpan()); 882 MOZ_ASSERT(nslots > 0); 883 884 uint32_t first = nslots; 885 for (; first != 0; --first) { 886 if (templateObj.getSlot(first - 1) != UndefinedValue()) { 887 break; 888 } 889 } 890 uint32_t startOfUndefined = first; 891 892 if (first != 0 && IsUninitializedLexical(templateObj.getSlot(first - 1))) { 893 for (; first != 0; --first) { 894 if (!IsUninitializedLexical(templateObj.getSlot(first - 1))) { 895 break; 896 } 897 } 898 } 899 uint32_t startOfUninitialized = first; 900 901 return {startOfUninitialized, startOfUndefined}; 902 } 903 904 void MacroAssembler::initTypedArraySlots( 905 Register obj, Register length, Register temp1, Register temp2, Label* fail, 906 const FixedLengthTypedArrayObject* templateObj) { 907 MOZ_ASSERT(!templateObj->hasBuffer()); 908 MOZ_ASSERT(obj != length); 909 MOZ_ASSERT(obj != temp1); 910 MOZ_ASSERT(obj != temp2); 911 MOZ_ASSERT(length != temp1); 912 MOZ_ASSERT(length != temp2); 913 MOZ_ASSERT(temp1 != temp2); 914 915 constexpr size_t dataSlotOffset = ArrayBufferViewObject::dataOffset(); 916 constexpr size_t dataOffset = dataSlotOffset + sizeof(HeapSlot); 917 918 static_assert( 919 FixedLengthTypedArrayObject::FIXED_DATA_START == 920 FixedLengthTypedArrayObject::DATA_SLOT + 1, 921 "fixed inline element data assumed to begin after the data slot"); 922 923 static_assert( 924 FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT == 925 JSObject::MAX_BYTE_SIZE - dataOffset, 926 "typed array inline buffer is limited by the maximum object byte size"); 927 928 MOZ_ASSERT(templateObj->tenuredSizeOfThis() >= dataOffset); 929 930 // Capacity for inline elements. 931 size_t inlineCapacity = templateObj->tenuredSizeOfThis() - dataOffset; 932 movePtr(ImmWord(inlineCapacity), temp2); 933 934 // Ensure volatile |obj| and |length| are saved across the call. 935 if (obj.volatile_()) { 936 Push(obj); 937 } 938 if (length.volatile_()) { 939 Push(length); 940 } 941 // Allocate a buffer on the heap to store the data elements. 942 using Fn = 943 void (*)(JSContext*, FixedLengthTypedArrayObject*, int32_t, size_t); 944 setupUnalignedABICall(temp1); 945 loadJSContext(temp1); 946 passABIArg(temp1); 947 passABIArg(obj); 948 passABIArg(length); 949 passABIArg(temp2); 950 callWithABI<Fn, AllocateAndInitTypedArrayBuffer>(); 951 if (length.volatile_()) { 952 Pop(length); 953 } 954 if (obj.volatile_()) { 955 Pop(obj); 956 } 957 958 // Fail when data slot is UndefinedValue. 959 branchTestUndefined(Assembler::Equal, Address(obj, dataSlotOffset), fail); 960 } 961 962 void MacroAssembler::initTypedArraySlotsInline( 963 Register obj, Register temp, 964 const FixedLengthTypedArrayObject* templateObj) { 965 MOZ_ASSERT(!templateObj->hasBuffer()); 966 967 constexpr size_t dataSlotOffset = ArrayBufferViewObject::dataOffset(); 968 constexpr size_t dataOffset = dataSlotOffset + sizeof(HeapSlot); 969 970 static_assert( 971 FixedLengthTypedArrayObject::FIXED_DATA_START == 972 FixedLengthTypedArrayObject::DATA_SLOT + 1, 973 "fixed inline element data assumed to begin after the data slot"); 974 975 static_assert( 976 FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT == 977 JSObject::MAX_BYTE_SIZE - dataOffset, 978 "typed array inline buffer is limited by the maximum object byte size"); 979 980 // Initialise data elements to zero. 981 size_t nbytes = templateObj->byteLength(); 982 MOZ_ASSERT(nbytes <= FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT); 983 984 MOZ_ASSERT(dataOffset + nbytes <= templateObj->tenuredSizeOfThis()); 985 MOZ_ASSERT(templateObj->tenuredSizeOfThis() > dataOffset, 986 "enough inline capacity to tag with ZeroLengthArrayData"); 987 988 // Store data elements inside the remaining JSObject slots. 989 computeEffectiveAddress(Address(obj, dataOffset), temp); 990 storePrivateValue(temp, Address(obj, dataSlotOffset)); 991 992 // Write enough zero pointers into fixed data to zero every 993 // element. (This zeroes past the end of a byte count that's 994 // not a multiple of pointer size. That's okay, because fixed 995 // data is a count of 8-byte HeapSlots (i.e. <= pointer size), 996 // and we won't inline unless the desired memory fits in that 997 // space.) 998 static_assert(sizeof(HeapSlot) == 8, "Assumed 8 bytes alignment"); 999 1000 size_t numZeroPointers = ((nbytes + 7) & ~0x7) / sizeof(char*); 1001 for (size_t i = 0; i < numZeroPointers; i++) { 1002 storePtr(ImmWord(0), Address(obj, dataOffset + i * sizeof(char*))); 1003 } 1004 1005 #ifdef DEBUG 1006 if (nbytes == 0) { 1007 store8(Imm32(ArrayBufferViewObject::ZeroLengthArrayData), 1008 Address(obj, dataOffset)); 1009 } 1010 #endif 1011 } 1012 1013 void MacroAssembler::initGCSlots(Register obj, Register temp, 1014 const TemplateNativeObject& templateObj) { 1015 MOZ_ASSERT(!templateObj.isArrayObject()); 1016 1017 // Slots of non-array objects are required to be initialized. 1018 // Use the values currently in the template object. 1019 uint32_t nslots = templateObj.slotSpan(); 1020 if (nslots == 0) { 1021 return; 1022 } 1023 1024 uint32_t nfixed = templateObj.numUsedFixedSlots(); 1025 uint32_t ndynamic = templateObj.numDynamicSlots(); 1026 1027 // Attempt to group slot writes such that we minimize the amount of 1028 // duplicated data we need to embed in code and load into registers. In 1029 // general, most template object slots will be undefined except for any 1030 // reserved slots. Since reserved slots come first, we split the object 1031 // logically into independent non-UndefinedValue writes to the head and 1032 // duplicated writes of UndefinedValue to the tail. For the majority of 1033 // objects, the "tail" will be the entire slot range. 1034 // 1035 // The template object may be a CallObject, in which case we need to 1036 // account for uninitialized lexical slots as well as undefined 1037 // slots. Uninitialized lexical slots appears in CallObjects if the function 1038 // has parameter expressions, in which case closed over parameters have 1039 // TDZ. Uninitialized slots come before undefined slots in CallObjects. 1040 auto [startOfUninitialized, startOfUndefined] = 1041 FindStartOfUninitializedAndUndefinedSlots(templateObj, nslots); 1042 MOZ_ASSERT(startOfUninitialized <= nfixed); // Reserved slots must be fixed. 1043 MOZ_ASSERT(startOfUndefined >= startOfUninitialized); 1044 MOZ_ASSERT_IF(!templateObj.isCallObject() && 1045 !templateObj.isBlockLexicalEnvironmentObject(), 1046 startOfUninitialized == startOfUndefined); 1047 1048 // Copy over any preserved reserved slots. 1049 copySlotsFromTemplate(obj, templateObj, 0, startOfUninitialized); 1050 1051 // Fill the rest of the fixed slots with undefined and uninitialized. 1052 size_t offset = NativeObject::getFixedSlotOffset(startOfUninitialized); 1053 fillSlotsWithUninitialized(Address(obj, offset), temp, startOfUninitialized, 1054 std::min(startOfUndefined, nfixed)); 1055 1056 if (startOfUndefined < nfixed) { 1057 offset = NativeObject::getFixedSlotOffset(startOfUndefined); 1058 fillSlotsWithUndefined(Address(obj, offset), temp, startOfUndefined, 1059 nfixed); 1060 } 1061 1062 if (ndynamic) { 1063 // We are short one register to do this elegantly. Borrow the obj 1064 // register briefly for our slots base address. 1065 push(obj); 1066 loadPtr(Address(obj, NativeObject::offsetOfSlots()), obj); 1067 1068 // Fill uninitialized slots if necessary. Otherwise initialize all 1069 // slots to undefined. 1070 if (startOfUndefined > nfixed) { 1071 MOZ_ASSERT(startOfUninitialized != startOfUndefined); 1072 fillSlotsWithUninitialized(Address(obj, 0), temp, 0, 1073 startOfUndefined - nfixed); 1074 size_t offset = (startOfUndefined - nfixed) * sizeof(Value); 1075 fillSlotsWithUndefined(Address(obj, offset), temp, 1076 startOfUndefined - nfixed, ndynamic); 1077 } else { 1078 fillSlotsWithUndefined(Address(obj, 0), temp, 0, ndynamic); 1079 } 1080 1081 pop(obj); 1082 } 1083 } 1084 1085 void MacroAssembler::initGCThing(Register obj, Register temp, 1086 const TemplateObject& templateObj, 1087 bool initContents) { 1088 // Fast initialization of an empty object returned by allocateObject(). 1089 1090 storePtr(ImmGCPtr(templateObj.shape()), 1091 Address(obj, JSObject::offsetOfShape())); 1092 1093 if (templateObj.isNativeObject()) { 1094 const TemplateNativeObject& ntemplate = 1095 templateObj.asTemplateNativeObject(); 1096 MOZ_ASSERT(!ntemplate.hasDynamicElements()); 1097 1098 // If the object has dynamic slots, the slots member has already been 1099 // filled in. 1100 if (ntemplate.numDynamicSlots() == 0) { 1101 storePtr(ImmPtr(emptyObjectSlots), 1102 Address(obj, NativeObject::offsetOfSlots())); 1103 } 1104 1105 if (ntemplate.isArrayObject()) { 1106 // Can't skip initializing reserved slots. 1107 MOZ_ASSERT(initContents); 1108 1109 int elementsOffset = NativeObject::offsetOfFixedElements(); 1110 1111 computeEffectiveAddress(Address(obj, elementsOffset), temp); 1112 storePtr(temp, Address(obj, NativeObject::offsetOfElements())); 1113 1114 // Fill in the elements header. 1115 store32( 1116 Imm32(ntemplate.getDenseCapacity()), 1117 Address(obj, elementsOffset + ObjectElements::offsetOfCapacity())); 1118 store32(Imm32(ntemplate.getDenseInitializedLength()), 1119 Address(obj, elementsOffset + 1120 ObjectElements::offsetOfInitializedLength())); 1121 store32(Imm32(ntemplate.getArrayLength()), 1122 Address(obj, elementsOffset + ObjectElements::offsetOfLength())); 1123 store32(Imm32(ObjectElements::FIXED), 1124 Address(obj, elementsOffset + ObjectElements::offsetOfFlags())); 1125 } else if (ntemplate.isArgumentsObject()) { 1126 // The caller will initialize the reserved slots. 1127 MOZ_ASSERT(!initContents); 1128 storePtr(ImmPtr(emptyObjectElements), 1129 Address(obj, NativeObject::offsetOfElements())); 1130 } else { 1131 // If the target type could be a TypedArray that maps shared memory 1132 // then this would need to store emptyObjectElementsShared in that case. 1133 MOZ_ASSERT(!ntemplate.isSharedMemory()); 1134 1135 // Can't skip initializing reserved slots. 1136 MOZ_ASSERT(initContents); 1137 1138 storePtr(ImmPtr(emptyObjectElements), 1139 Address(obj, NativeObject::offsetOfElements())); 1140 1141 initGCSlots(obj, temp, ntemplate); 1142 } 1143 } else { 1144 MOZ_CRASH("Unknown object"); 1145 } 1146 1147 #ifdef JS_GC_PROBES 1148 AllocatableRegisterSet regs(RegisterSet::Volatile()); 1149 LiveRegisterSet save(regs.asLiveSet()); 1150 PushRegsInMask(save); 1151 1152 regs.takeUnchecked(obj); 1153 Register temp2 = regs.takeAnyGeneral(); 1154 1155 using Fn = void (*)(JSObject* obj); 1156 setupUnalignedABICall(temp2); 1157 passABIArg(obj); 1158 callWithABI<Fn, TraceCreateObject>(); 1159 1160 PopRegsInMask(save); 1161 #endif 1162 } 1163 1164 static size_t StringCharsByteLength(const JSOffThreadAtom* str) { 1165 CharEncoding encoding = 1166 str->hasLatin1Chars() ? CharEncoding::Latin1 : CharEncoding::TwoByte; 1167 size_t encodingSize = encoding == CharEncoding::Latin1 1168 ? sizeof(JS::Latin1Char) 1169 : sizeof(char16_t); 1170 return str->length() * encodingSize; 1171 } 1172 1173 bool MacroAssembler::canCompareStringCharsInline(const JSOffThreadAtom* str) { 1174 // Limit the number of inline instructions used for character comparisons. Use 1175 // the same instruction limit for both encodings, i.e. two-byte uses half the 1176 // limit of Latin-1 strings. 1177 constexpr size_t ByteLengthCompareCutoff = 32; 1178 1179 size_t byteLength = StringCharsByteLength(str); 1180 return 0 < byteLength && byteLength <= ByteLengthCompareCutoff; 1181 } 1182 1183 template <typename T, typename CharT> 1184 static inline T CopyCharacters(const CharT* chars) { 1185 T value = 0; 1186 std::memcpy(&value, chars, sizeof(T)); 1187 return value; 1188 } 1189 1190 template <typename T> 1191 static inline T CopyCharacters(const JSOffThreadAtom* str, size_t index) { 1192 JS::AutoCheckCannotGC nogc; 1193 1194 if (str->hasLatin1Chars()) { 1195 MOZ_ASSERT(index + sizeof(T) / sizeof(JS::Latin1Char) <= str->length()); 1196 return CopyCharacters<T>(str->latin1Chars(nogc) + index); 1197 } 1198 1199 MOZ_ASSERT(sizeof(T) >= sizeof(char16_t)); 1200 MOZ_ASSERT(index + sizeof(T) / sizeof(char16_t) <= str->length()); 1201 return CopyCharacters<T>(str->twoByteChars(nogc) + index); 1202 } 1203 1204 void MacroAssembler::branchIfNotStringCharsEquals(Register stringChars, 1205 const JSOffThreadAtom* str, 1206 Label* label) { 1207 CharEncoding encoding = 1208 str->hasLatin1Chars() ? CharEncoding::Latin1 : CharEncoding::TwoByte; 1209 size_t encodingSize = encoding == CharEncoding::Latin1 1210 ? sizeof(JS::Latin1Char) 1211 : sizeof(char16_t); 1212 size_t byteLength = StringCharsByteLength(str); 1213 1214 size_t pos = 0; 1215 for (size_t stride : {8, 4, 2, 1}) { 1216 while (byteLength >= stride) { 1217 Address addr(stringChars, pos * encodingSize); 1218 switch (stride) { 1219 case 8: { 1220 auto x = CopyCharacters<uint64_t>(str, pos); 1221 branch64(Assembler::NotEqual, addr, Imm64(x), label); 1222 break; 1223 } 1224 case 4: { 1225 auto x = CopyCharacters<uint32_t>(str, pos); 1226 branch32(Assembler::NotEqual, addr, Imm32(x), label); 1227 break; 1228 } 1229 case 2: { 1230 auto x = CopyCharacters<uint16_t>(str, pos); 1231 branch16(Assembler::NotEqual, addr, Imm32(x), label); 1232 break; 1233 } 1234 case 1: { 1235 auto x = CopyCharacters<uint8_t>(str, pos); 1236 branch8(Assembler::NotEqual, addr, Imm32(x), label); 1237 break; 1238 } 1239 } 1240 1241 byteLength -= stride; 1242 pos += stride / encodingSize; 1243 } 1244 1245 // Prefer a single comparison for trailing bytes instead of doing 1246 // multiple consecutive comparisons. 1247 // 1248 // For example when comparing against the string "example", emit two 1249 // four-byte comparisons against "exam" and "mple" instead of doing 1250 // three comparisons against "exam", "pl", and finally "e". 1251 if (pos > 0 && byteLength > stride / 2) { 1252 MOZ_ASSERT(stride == 8 || stride == 4); 1253 1254 size_t prev = pos - (stride - byteLength) / encodingSize; 1255 Address addr(stringChars, prev * encodingSize); 1256 switch (stride) { 1257 case 8: { 1258 auto x = CopyCharacters<uint64_t>(str, prev); 1259 branch64(Assembler::NotEqual, addr, Imm64(x), label); 1260 break; 1261 } 1262 case 4: { 1263 auto x = CopyCharacters<uint32_t>(str, prev); 1264 branch32(Assembler::NotEqual, addr, Imm32(x), label); 1265 break; 1266 } 1267 } 1268 1269 // Break from the loop, because we've finished the complete string. 1270 break; 1271 } 1272 } 1273 } 1274 1275 void MacroAssembler::loadStringCharsForCompare(Register input, 1276 const JSOffThreadAtom* str, 1277 Register stringChars, 1278 Label* fail) { 1279 CharEncoding encoding = 1280 str->hasLatin1Chars() ? CharEncoding::Latin1 : CharEncoding::TwoByte; 1281 1282 // Take the slow path when the string is a rope or has a different character 1283 // representation. 1284 branchIfRope(input, fail); 1285 if (encoding == CharEncoding::Latin1) { 1286 branchTwoByteString(input, fail); 1287 } else { 1288 JS::AutoCheckCannotGC nogc; 1289 if (mozilla::IsUtf16Latin1(str->twoByteRange(nogc))) { 1290 branchLatin1String(input, fail); 1291 } else { 1292 // This case was already handled in the caller. 1293 #ifdef DEBUG 1294 Label ok; 1295 branchTwoByteString(input, &ok); 1296 assumeUnreachable("Unexpected Latin-1 string"); 1297 bind(&ok); 1298 #endif 1299 } 1300 } 1301 1302 #ifdef DEBUG 1303 { 1304 size_t length = str->length(); 1305 MOZ_ASSERT(length > 0); 1306 1307 Label ok; 1308 branch32(Assembler::AboveOrEqual, 1309 Address(input, JSString::offsetOfLength()), Imm32(length), &ok); 1310 assumeUnreachable("Input mustn't be smaller than search string"); 1311 bind(&ok); 1312 } 1313 #endif 1314 1315 // Load the input string's characters. 1316 loadStringChars(input, stringChars, encoding); 1317 } 1318 1319 void MacroAssembler::compareStringChars(JSOp op, Register stringChars, 1320 const JSOffThreadAtom* str, 1321 Register output) { 1322 MOZ_ASSERT(IsEqualityOp(op)); 1323 1324 size_t byteLength = StringCharsByteLength(str); 1325 1326 // Prefer a single compare-and-set instruction if possible. 1327 if (byteLength == 1 || byteLength == 2 || byteLength == 4 || 1328 byteLength == 8) { 1329 auto cond = JSOpToCondition(op, /* isSigned = */ false); 1330 1331 Address addr(stringChars, 0); 1332 switch (byteLength) { 1333 case 8: { 1334 auto x = CopyCharacters<uint64_t>(str, 0); 1335 cmp64Set(cond, addr, Imm64(x), output); 1336 break; 1337 } 1338 case 4: { 1339 auto x = CopyCharacters<uint32_t>(str, 0); 1340 cmp32Set(cond, addr, Imm32(x), output); 1341 break; 1342 } 1343 case 2: { 1344 auto x = CopyCharacters<uint16_t>(str, 0); 1345 cmp16Set(cond, addr, Imm32(x), output); 1346 break; 1347 } 1348 case 1: { 1349 auto x = CopyCharacters<uint8_t>(str, 0); 1350 cmp8Set(cond, addr, Imm32(x), output); 1351 break; 1352 } 1353 } 1354 } else { 1355 Label setNotEqualResult; 1356 branchIfNotStringCharsEquals(stringChars, str, &setNotEqualResult); 1357 1358 // Falls through if both strings are equal. 1359 1360 Label done; 1361 move32(Imm32(op == JSOp::Eq || op == JSOp::StrictEq), output); 1362 jump(&done); 1363 1364 bind(&setNotEqualResult); 1365 move32(Imm32(op == JSOp::Ne || op == JSOp::StrictNe), output); 1366 1367 bind(&done); 1368 } 1369 } 1370 1371 void MacroAssembler::compareStrings(JSOp op, Register left, Register right, 1372 Register result, Label* fail) { 1373 MOZ_ASSERT(left != result); 1374 MOZ_ASSERT(right != result); 1375 MOZ_ASSERT(IsEqualityOp(op) || IsRelationalOp(op)); 1376 1377 Label notPointerEqual; 1378 // If operands point to the same instance, the strings are trivially equal. 1379 branchPtr(Assembler::NotEqual, left, right, 1380 IsEqualityOp(op) ? ¬PointerEqual : fail); 1381 move32(Imm32(op == JSOp::Eq || op == JSOp::StrictEq || op == JSOp::Le || 1382 op == JSOp::Ge), 1383 result); 1384 1385 if (IsEqualityOp(op)) { 1386 Label done; 1387 jump(&done); 1388 1389 bind(¬PointerEqual); 1390 1391 Label leftIsNotAtom; 1392 Label setNotEqualResult; 1393 // Atoms cannot be equal to each other if they point to different strings. 1394 Imm32 atomBit(JSString::ATOM_BIT); 1395 branchTest32(Assembler::Zero, Address(left, JSString::offsetOfFlags()), 1396 atomBit, &leftIsNotAtom); 1397 branchTest32(Assembler::NonZero, Address(right, JSString::offsetOfFlags()), 1398 atomBit, &setNotEqualResult); 1399 1400 bind(&leftIsNotAtom); 1401 // Strings of different length can never be equal. 1402 loadStringLength(left, result); 1403 branch32(Assembler::Equal, Address(right, JSString::offsetOfLength()), 1404 result, fail); 1405 1406 bind(&setNotEqualResult); 1407 move32(Imm32(op == JSOp::Ne || op == JSOp::StrictNe), result); 1408 1409 bind(&done); 1410 } 1411 } 1412 1413 void MacroAssembler::loadStringChars(Register str, Register dest, 1414 CharEncoding encoding) { 1415 MOZ_ASSERT(str != dest); 1416 1417 if (JitOptions.spectreStringMitigations) { 1418 if (encoding == CharEncoding::Latin1) { 1419 // If the string is a rope, zero the |str| register. The code below 1420 // depends on str->flags so this should block speculative execution. 1421 movePtr(ImmWord(0), dest); 1422 test32MovePtr(Assembler::Zero, Address(str, JSString::offsetOfFlags()), 1423 Imm32(JSString::LINEAR_BIT), dest, str); 1424 } else { 1425 // If we're loading TwoByte chars, there's an additional risk: 1426 // if the string has Latin1 chars, we could read out-of-bounds. To 1427 // prevent this, we check both the Linear and Latin1 bits. We don't 1428 // have a scratch register, so we use these flags also to block 1429 // speculative execution, similar to the use of 0 above. 1430 MOZ_ASSERT(encoding == CharEncoding::TwoByte); 1431 static constexpr uint32_t Mask = 1432 JSString::LINEAR_BIT | JSString::LATIN1_CHARS_BIT; 1433 static_assert(Mask < 2048, 1434 "Mask should be a small, near-null value to ensure we " 1435 "block speculative execution when it's used as string " 1436 "pointer"); 1437 move32(Imm32(Mask), dest); 1438 and32(Address(str, JSString::offsetOfFlags()), dest); 1439 cmp32MovePtr(Assembler::NotEqual, dest, Imm32(JSString::LINEAR_BIT), dest, 1440 str); 1441 } 1442 } 1443 1444 // Load the inline chars. 1445 computeEffectiveAddress(Address(str, JSInlineString::offsetOfInlineStorage()), 1446 dest); 1447 1448 // If it's not an inline string, load the non-inline chars. Use a 1449 // conditional move to prevent speculative execution. 1450 test32LoadPtr(Assembler::Zero, Address(str, JSString::offsetOfFlags()), 1451 Imm32(JSString::INLINE_CHARS_BIT), 1452 Address(str, JSString::offsetOfNonInlineChars()), dest); 1453 } 1454 1455 void MacroAssembler::loadNonInlineStringChars(Register str, Register dest, 1456 CharEncoding encoding) { 1457 MOZ_ASSERT(str != dest); 1458 1459 if (JitOptions.spectreStringMitigations) { 1460 // If the string is a rope, has inline chars, or has a different 1461 // character encoding, set str to a near-null value to prevent 1462 // speculative execution below (when reading str->nonInlineChars). 1463 1464 static constexpr uint32_t Mask = JSString::LINEAR_BIT | 1465 JSString::INLINE_CHARS_BIT | 1466 JSString::LATIN1_CHARS_BIT; 1467 static_assert(Mask < 2048, 1468 "Mask should be a small, near-null value to ensure we " 1469 "block speculative execution when it's used as string " 1470 "pointer"); 1471 1472 uint32_t expectedBits = JSString::LINEAR_BIT; 1473 if (encoding == CharEncoding::Latin1) { 1474 expectedBits |= JSString::LATIN1_CHARS_BIT; 1475 } 1476 1477 move32(Imm32(Mask), dest); 1478 and32(Address(str, JSString::offsetOfFlags()), dest); 1479 1480 cmp32MovePtr(Assembler::NotEqual, dest, Imm32(expectedBits), dest, str); 1481 } 1482 1483 loadPtr(Address(str, JSString::offsetOfNonInlineChars()), dest); 1484 } 1485 1486 void MacroAssembler::storeNonInlineStringChars(Register chars, Register str) { 1487 MOZ_ASSERT(chars != str); 1488 storePtr(chars, Address(str, JSString::offsetOfNonInlineChars())); 1489 } 1490 1491 void MacroAssembler::loadInlineStringCharsForStore(Register str, 1492 Register dest) { 1493 computeEffectiveAddress(Address(str, JSInlineString::offsetOfInlineStorage()), 1494 dest); 1495 } 1496 1497 void MacroAssembler::loadInlineStringChars(Register str, Register dest, 1498 CharEncoding encoding) { 1499 MOZ_ASSERT(str != dest); 1500 1501 if (JitOptions.spectreStringMitigations) { 1502 // Making this Spectre-safe is a bit complicated: using 1503 // computeEffectiveAddress and then zeroing the output register if 1504 // non-inline is not sufficient: when the index is very large, it would 1505 // allow reading |nullptr + index|. Just fall back to loadStringChars 1506 // for now. 1507 loadStringChars(str, dest, encoding); 1508 } else { 1509 computeEffectiveAddress( 1510 Address(str, JSInlineString::offsetOfInlineStorage()), dest); 1511 } 1512 } 1513 1514 void MacroAssembler::loadRopeLeftChild(Register str, Register dest) { 1515 MOZ_ASSERT(str != dest); 1516 1517 if (JitOptions.spectreStringMitigations) { 1518 // Zero the output register if the input was not a rope. 1519 movePtr(ImmWord(0), dest); 1520 test32LoadPtr(Assembler::Zero, Address(str, JSString::offsetOfFlags()), 1521 Imm32(JSString::LINEAR_BIT), 1522 Address(str, JSRope::offsetOfLeft()), dest); 1523 } else { 1524 loadPtr(Address(str, JSRope::offsetOfLeft()), dest); 1525 } 1526 } 1527 1528 void MacroAssembler::loadRopeRightChild(Register str, Register dest) { 1529 MOZ_ASSERT(str != dest); 1530 1531 if (JitOptions.spectreStringMitigations) { 1532 // Zero the output register if the input was not a rope. 1533 movePtr(ImmWord(0), dest); 1534 test32LoadPtr(Assembler::Zero, Address(str, JSString::offsetOfFlags()), 1535 Imm32(JSString::LINEAR_BIT), 1536 Address(str, JSRope::offsetOfRight()), dest); 1537 } else { 1538 loadPtr(Address(str, JSRope::offsetOfRight()), dest); 1539 } 1540 } 1541 1542 void MacroAssembler::storeRopeChildren(Register left, Register right, 1543 Register str) { 1544 storePtr(left, Address(str, JSRope::offsetOfLeft())); 1545 storePtr(right, Address(str, JSRope::offsetOfRight())); 1546 } 1547 1548 void MacroAssembler::loadDependentStringBase(Register str, Register dest) { 1549 MOZ_ASSERT(str != dest); 1550 1551 if (JitOptions.spectreStringMitigations) { 1552 // If the string is not a dependent string, zero the |str| register. 1553 // The code below loads str->base so this should block speculative 1554 // execution. 1555 movePtr(ImmWord(0), dest); 1556 test32MovePtr(Assembler::Zero, Address(str, JSString::offsetOfFlags()), 1557 Imm32(JSString::DEPENDENT_BIT), dest, str); 1558 } 1559 1560 loadPtr(Address(str, JSDependentString::offsetOfBase()), dest); 1561 } 1562 1563 void MacroAssembler::storeDependentStringBase(Register base, Register str) { 1564 storePtr(base, Address(str, JSDependentString::offsetOfBase())); 1565 } 1566 1567 void MacroAssembler::branchIfMaybeSplitSurrogatePair(Register leftChild, 1568 Register index, 1569 Register scratch, 1570 Label* maybeSplit, 1571 Label* notSplit) { 1572 // If |index| is the last character of the left child and the left child 1573 // is a two-byte string, it's possible that a surrogate pair is split 1574 // between the left and right child of a rope. 1575 1576 // Can't be a split surrogate when the left child is a Latin-1 string. 1577 branchLatin1String(leftChild, notSplit); 1578 1579 // Can't be a split surrogate when |index + 1| is in the left child. 1580 add32(Imm32(1), index, scratch); 1581 branch32(Assembler::Above, Address(leftChild, JSString::offsetOfLength()), 1582 scratch, notSplit); 1583 1584 // Jump to |maybeSplit| if the left child is another rope. 1585 branchIfRope(leftChild, maybeSplit); 1586 1587 // Load the character at |index|. 1588 loadStringChars(leftChild, scratch, CharEncoding::TwoByte); 1589 loadChar(scratch, index, scratch, CharEncoding::TwoByte); 1590 1591 // Jump to |maybeSplit| if the last character is a lead surrogate. 1592 branchIfLeadSurrogate(scratch, scratch, maybeSplit); 1593 } 1594 1595 void MacroAssembler::loadRopeChild(CharKind kind, Register str, Register index, 1596 Register output, Register maybeScratch, 1597 Label* isLinear, Label* splitSurrogate) { 1598 // This follows JSString::getChar. 1599 branchIfNotRope(str, isLinear); 1600 1601 loadRopeLeftChild(str, output); 1602 1603 Label loadedChild; 1604 if (kind == CharKind::CharCode) { 1605 // Check if |index| is contained in the left child. 1606 branch32(Assembler::Above, Address(output, JSString::offsetOfLength()), 1607 index, &loadedChild); 1608 } else { 1609 MOZ_ASSERT(maybeScratch != InvalidReg); 1610 1611 // Check if |index| is contained in the left child. 1612 Label loadRight; 1613 branch32(Assembler::BelowOrEqual, 1614 Address(output, JSString::offsetOfLength()), index, &loadRight); 1615 { 1616 // Handle possible split surrogate pairs. 1617 branchIfMaybeSplitSurrogatePair(output, index, maybeScratch, 1618 splitSurrogate, &loadedChild); 1619 jump(&loadedChild); 1620 } 1621 bind(&loadRight); 1622 } 1623 1624 // The index must be in the rightChild. 1625 loadRopeRightChild(str, output); 1626 1627 bind(&loadedChild); 1628 } 1629 1630 void MacroAssembler::branchIfCanLoadStringChar(CharKind kind, Register str, 1631 Register index, Register scratch, 1632 Register maybeScratch, 1633 Label* label) { 1634 Label splitSurrogate; 1635 loadRopeChild(kind, str, index, scratch, maybeScratch, label, 1636 &splitSurrogate); 1637 1638 // Branch if the left resp. right side is linear. 1639 branchIfNotRope(scratch, label); 1640 1641 if (kind == CharKind::CodePoint) { 1642 bind(&splitSurrogate); 1643 } 1644 } 1645 1646 void MacroAssembler::branchIfNotCanLoadStringChar(CharKind kind, Register str, 1647 Register index, 1648 Register scratch, 1649 Register maybeScratch, 1650 Label* label) { 1651 Label done; 1652 loadRopeChild(kind, str, index, scratch, maybeScratch, &done, label); 1653 1654 // Branch if the left or right side is another rope. 1655 branchIfRope(scratch, label); 1656 1657 bind(&done); 1658 } 1659 1660 void MacroAssembler::loadStringChar(CharKind kind, Register str, Register index, 1661 Register output, Register scratch1, 1662 Register scratch2, Label* fail) { 1663 MOZ_ASSERT(str != output); 1664 MOZ_ASSERT(str != index); 1665 MOZ_ASSERT(index != output); 1666 MOZ_ASSERT_IF(kind == CharKind::CodePoint, index != scratch1); 1667 MOZ_ASSERT(output != scratch1); 1668 MOZ_ASSERT(output != scratch2); 1669 1670 // Use scratch1 for the index (adjusted below). 1671 if (index != scratch1) { 1672 move32(index, scratch1); 1673 } 1674 movePtr(str, output); 1675 1676 // This follows JSString::getChar. 1677 Label notRope; 1678 branchIfNotRope(str, ¬Rope); 1679 1680 loadRopeLeftChild(str, output); 1681 1682 // Check if the index is contained in the leftChild. 1683 Label loadedChild, notInLeft; 1684 spectreBoundsCheck32(scratch1, Address(output, JSString::offsetOfLength()), 1685 scratch2, ¬InLeft); 1686 if (kind == CharKind::CodePoint) { 1687 branchIfMaybeSplitSurrogatePair(output, scratch1, scratch2, fail, 1688 &loadedChild); 1689 } 1690 jump(&loadedChild); 1691 1692 // The index must be in the rightChild. 1693 // index -= rope->leftChild()->length() 1694 bind(¬InLeft); 1695 sub32(Address(output, JSString::offsetOfLength()), scratch1); 1696 loadRopeRightChild(str, output); 1697 1698 // If the left or right side is another rope, give up. 1699 bind(&loadedChild); 1700 branchIfRope(output, fail); 1701 1702 bind(¬Rope); 1703 1704 Label isLatin1, done; 1705 branchLatin1String(output, &isLatin1); 1706 { 1707 loadStringChars(output, scratch2, CharEncoding::TwoByte); 1708 1709 if (kind == CharKind::CharCode) { 1710 loadChar(scratch2, scratch1, output, CharEncoding::TwoByte); 1711 } else { 1712 // Load the first character. 1713 addToCharPtr(scratch2, scratch1, CharEncoding::TwoByte); 1714 loadChar(Address(scratch2, 0), output, CharEncoding::TwoByte); 1715 1716 // If the first character isn't a lead surrogate, go to |done|. 1717 branchIfNotLeadSurrogate(output, &done); 1718 1719 // branchIfMaybeSplitSurrogatePair ensures that the surrogate pair can't 1720 // split between two rope children. So if |index + 1 < str.length|, then 1721 // |index| and |index + 1| are in the same rope child. 1722 // 1723 // NB: We use the non-adjusted |index| and |str| inputs, because |output| 1724 // was overwritten and no longer contains the rope child. 1725 1726 // If |index + 1| is a valid index into |str|. 1727 add32(Imm32(1), index, scratch1); 1728 spectreBoundsCheck32(scratch1, Address(str, JSString::offsetOfLength()), 1729 InvalidReg, &done); 1730 1731 // Then load the next character at |scratch2 + sizeof(char16_t)|. 1732 loadChar(Address(scratch2, sizeof(char16_t)), scratch1, 1733 CharEncoding::TwoByte); 1734 1735 // If the next character isn't a trail surrogate, go to |done|. 1736 branchIfNotTrailSurrogate(scratch1, scratch2, &done); 1737 1738 // Inlined unicode::UTF16Decode(char16_t, char16_t). 1739 lshift32(Imm32(10), output); 1740 add32(Imm32(unicode::NonBMPMin - (unicode::LeadSurrogateMin << 10) - 1741 unicode::TrailSurrogateMin), 1742 scratch1); 1743 add32(scratch1, output); 1744 } 1745 1746 jump(&done); 1747 } 1748 bind(&isLatin1); 1749 { 1750 loadStringChars(output, scratch2, CharEncoding::Latin1); 1751 loadChar(scratch2, scratch1, output, CharEncoding::Latin1); 1752 } 1753 1754 bind(&done); 1755 } 1756 1757 void MacroAssembler::loadStringChar(Register str, int32_t index, 1758 Register output, Register scratch1, 1759 Register scratch2, Label* fail) { 1760 MOZ_ASSERT(str != output); 1761 MOZ_ASSERT(output != scratch1); 1762 MOZ_ASSERT(output != scratch2); 1763 1764 if (index == 0) { 1765 movePtr(str, scratch1); 1766 1767 // This follows JSString::getChar. 1768 Label notRope; 1769 branchIfNotRope(str, ¬Rope); 1770 1771 loadRopeLeftChild(str, scratch1); 1772 1773 // Rope children can't be empty, so the index can't be in the right side. 1774 1775 // If the left side is another rope, give up. 1776 branchIfRope(scratch1, fail); 1777 1778 bind(¬Rope); 1779 1780 Label isLatin1, done; 1781 branchLatin1String(scratch1, &isLatin1); 1782 loadStringChars(scratch1, scratch2, CharEncoding::TwoByte); 1783 loadChar(Address(scratch2, 0), output, CharEncoding::TwoByte); 1784 jump(&done); 1785 1786 bind(&isLatin1); 1787 loadStringChars(scratch1, scratch2, CharEncoding::Latin1); 1788 loadChar(Address(scratch2, 0), output, CharEncoding::Latin1); 1789 1790 bind(&done); 1791 } else { 1792 move32(Imm32(index), scratch1); 1793 loadStringChar(str, scratch1, output, scratch1, scratch2, fail); 1794 } 1795 } 1796 1797 void MacroAssembler::loadStringIndexValue(Register str, Register dest, 1798 Label* fail) { 1799 MOZ_ASSERT(str != dest); 1800 1801 load32(Address(str, JSString::offsetOfFlags()), dest); 1802 1803 // Does not have a cached index value. 1804 branchTest32(Assembler::Zero, dest, Imm32(JSString::INDEX_VALUE_BIT), fail); 1805 1806 // Extract the index. 1807 rshift32(Imm32(JSString::INDEX_VALUE_SHIFT), dest); 1808 } 1809 1810 void MacroAssembler::loadChar(Register chars, Register index, Register dest, 1811 CharEncoding encoding, int32_t offset /* = 0 */) { 1812 if (encoding == CharEncoding::Latin1) { 1813 loadChar(BaseIndex(chars, index, TimesOne, offset), dest, encoding); 1814 } else { 1815 loadChar(BaseIndex(chars, index, TimesTwo, offset), dest, encoding); 1816 } 1817 } 1818 1819 void MacroAssembler::addToCharPtr(Register chars, Register index, 1820 CharEncoding encoding) { 1821 if (encoding == CharEncoding::Latin1) { 1822 static_assert(sizeof(char) == 1, 1823 "Latin-1 string index shouldn't need scaling"); 1824 addPtr(index, chars); 1825 } else { 1826 computeEffectiveAddress(BaseIndex(chars, index, TimesTwo), chars); 1827 } 1828 } 1829 1830 void MacroAssembler::branchIfNotLeadSurrogate(Register src, Label* label) { 1831 branch32(Assembler::Below, src, Imm32(unicode::LeadSurrogateMin), label); 1832 branch32(Assembler::Above, src, Imm32(unicode::LeadSurrogateMax), label); 1833 } 1834 1835 void MacroAssembler::branchSurrogate(Assembler::Condition cond, Register src, 1836 Register scratch, Label* label, 1837 SurrogateChar surrogateChar) { 1838 // For TrailSurrogateMin ≤ x ≤ TrailSurrogateMax and 1839 // LeadSurrogateMin ≤ x ≤ LeadSurrogateMax, the following equations hold. 1840 // 1841 // SurrogateMin ≤ x ≤ SurrogateMax 1842 // <> SurrogateMin ≤ x ≤ SurrogateMin + 2^10 - 1 1843 // <> ((x - SurrogateMin) >>> 10) = 0 where >>> is an unsigned-shift 1844 // See Hacker's Delight, section 4-1 for details. 1845 // 1846 // ((x - SurrogateMin) >>> 10) = 0 1847 // <> floor((x - SurrogateMin) / 1024) = 0 1848 // <> floor((x / 1024) - (SurrogateMin / 1024)) = 0 1849 // <> floor(x / 1024) = SurrogateMin / 1024 1850 // <> floor(x / 1024) * 1024 = SurrogateMin 1851 // <> (x >>> 10) << 10 = SurrogateMin 1852 // <> x & ~(2^10 - 1) = SurrogateMin 1853 1854 constexpr char16_t SurrogateMask = 0xFC00; 1855 char16_t SurrogateMin = surrogateChar == SurrogateChar::Lead 1856 ? unicode::LeadSurrogateMin 1857 : unicode::TrailSurrogateMin; 1858 1859 and32(Imm32(SurrogateMask), src, scratch); 1860 branch32(cond, scratch, Imm32(SurrogateMin), label); 1861 } 1862 1863 void MacroAssembler::loadStringFromUnit(Register unit, Register dest, 1864 const StaticStrings& staticStrings) { 1865 movePtr(ImmPtr(&staticStrings.unitStaticTable), dest); 1866 loadPtr(BaseIndex(dest, unit, ScalePointer), dest); 1867 } 1868 1869 void MacroAssembler::loadLengthTwoString(Register c1, Register c2, 1870 Register dest, 1871 const StaticStrings& staticStrings) { 1872 // Compute (toSmallCharTable[c1] << SMALL_CHAR_BITS) + toSmallCharTable[c2] 1873 // to obtain the index into `StaticStrings::length2StaticTable`. 1874 static_assert(sizeof(StaticStrings::SmallChar) == 1); 1875 1876 movePtr(ImmPtr(&StaticStrings::toSmallCharTable.storage), dest); 1877 load8ZeroExtend(BaseIndex(dest, c1, Scale::TimesOne), c1); 1878 load8ZeroExtend(BaseIndex(dest, c2, Scale::TimesOne), c2); 1879 1880 lshift32(Imm32(StaticStrings::SMALL_CHAR_BITS), c1); 1881 add32(c2, c1); 1882 1883 // Look up the string from the computed index. 1884 movePtr(ImmPtr(&staticStrings.length2StaticTable), dest); 1885 loadPtr(BaseIndex(dest, c1, ScalePointer), dest); 1886 } 1887 1888 void MacroAssembler::lookupStaticString(Register ch, Register dest, 1889 const StaticStrings& staticStrings) { 1890 MOZ_ASSERT(ch != dest); 1891 1892 movePtr(ImmPtr(&staticStrings.unitStaticTable), dest); 1893 loadPtr(BaseIndex(dest, ch, ScalePointer), dest); 1894 } 1895 1896 void MacroAssembler::lookupStaticString(Register ch, Register dest, 1897 const StaticStrings& staticStrings, 1898 Label* fail) { 1899 MOZ_ASSERT(ch != dest); 1900 1901 boundsCheck32PowerOfTwo(ch, StaticStrings::UNIT_STATIC_LIMIT, fail); 1902 movePtr(ImmPtr(&staticStrings.unitStaticTable), dest); 1903 loadPtr(BaseIndex(dest, ch, ScalePointer), dest); 1904 } 1905 1906 void MacroAssembler::lookupStaticString(Register ch1, Register ch2, 1907 Register dest, 1908 const StaticStrings& staticStrings, 1909 Label* fail) { 1910 MOZ_ASSERT(ch1 != dest); 1911 MOZ_ASSERT(ch2 != dest); 1912 1913 branch32(Assembler::AboveOrEqual, ch1, 1914 Imm32(StaticStrings::SMALL_CHAR_TABLE_SIZE), fail); 1915 branch32(Assembler::AboveOrEqual, ch2, 1916 Imm32(StaticStrings::SMALL_CHAR_TABLE_SIZE), fail); 1917 1918 movePtr(ImmPtr(&StaticStrings::toSmallCharTable.storage), dest); 1919 load8ZeroExtend(BaseIndex(dest, ch1, Scale::TimesOne), ch1); 1920 load8ZeroExtend(BaseIndex(dest, ch2, Scale::TimesOne), ch2); 1921 1922 branch32(Assembler::Equal, ch1, Imm32(StaticStrings::INVALID_SMALL_CHAR), 1923 fail); 1924 branch32(Assembler::Equal, ch2, Imm32(StaticStrings::INVALID_SMALL_CHAR), 1925 fail); 1926 1927 lshift32(Imm32(StaticStrings::SMALL_CHAR_BITS), ch1); 1928 add32(ch2, ch1); 1929 1930 // Look up the string from the computed index. 1931 movePtr(ImmPtr(&staticStrings.length2StaticTable), dest); 1932 loadPtr(BaseIndex(dest, ch1, ScalePointer), dest); 1933 } 1934 1935 void MacroAssembler::lookupStaticIntString(Register integer, Register dest, 1936 Register scratch, 1937 const StaticStrings& staticStrings, 1938 Label* fail) { 1939 MOZ_ASSERT(integer != scratch); 1940 1941 boundsCheck32PowerOfTwo(integer, StaticStrings::INT_STATIC_LIMIT, fail); 1942 movePtr(ImmPtr(&staticStrings.intStaticTable), scratch); 1943 loadPtr(BaseIndex(scratch, integer, ScalePointer), dest); 1944 } 1945 1946 void MacroAssembler::loadInt32ToStringWithBase( 1947 Register input, Register base, Register dest, Register scratch1, 1948 Register scratch2, const StaticStrings& staticStrings, 1949 const LiveRegisterSet& volatileRegs, bool lowerCase, Label* fail) { 1950 #ifdef DEBUG 1951 Label baseBad, baseOk; 1952 branch32(Assembler::LessThan, base, Imm32(2), &baseBad); 1953 branch32(Assembler::LessThanOrEqual, base, Imm32(36), &baseOk); 1954 bind(&baseBad); 1955 assumeUnreachable("base must be in range [2, 36]"); 1956 bind(&baseOk); 1957 #endif 1958 1959 // Compute |"0123456789abcdefghijklmnopqrstuvwxyz"[r]|. 1960 auto toChar = [this, base, lowerCase](Register r) { 1961 #ifdef DEBUG 1962 Label ok; 1963 branch32(Assembler::Below, r, base, &ok); 1964 assumeUnreachable("bad digit"); 1965 bind(&ok); 1966 #else 1967 // Silence unused lambda capture warning. 1968 (void)base; 1969 #endif 1970 1971 Label done; 1972 add32(Imm32('0'), r); 1973 branch32(Assembler::BelowOrEqual, r, Imm32('9'), &done); 1974 add32(Imm32((lowerCase ? 'a' : 'A') - '0' - 10), r); 1975 bind(&done); 1976 }; 1977 1978 // Perform a "unit" lookup when |unsigned(input) < unsigned(base)|. 1979 Label lengthTwo, done; 1980 branch32(Assembler::AboveOrEqual, input, base, &lengthTwo); 1981 { 1982 move32(input, scratch1); 1983 toChar(scratch1); 1984 1985 loadStringFromUnit(scratch1, dest, staticStrings); 1986 1987 jump(&done); 1988 } 1989 bind(&lengthTwo); 1990 1991 // Compute |base * base|. 1992 move32(base, scratch1); 1993 mul32(scratch1, scratch1); 1994 1995 // Perform a "length2" lookup when |unsigned(input) < unsigned(base * base)|. 1996 branch32(Assembler::AboveOrEqual, input, scratch1, fail); 1997 { 1998 // Compute |scratch1 = input / base| and |scratch2 = input % base|. 1999 flexibleDivMod32(input, base, scratch1, scratch2, true, volatileRegs); 2000 2001 // Compute the digits of the divisor and remainder. 2002 toChar(scratch1); 2003 toChar(scratch2); 2004 2005 // Look up the 2-character digit string in the small-char table. 2006 loadLengthTwoString(scratch1, scratch2, dest, staticStrings); 2007 } 2008 bind(&done); 2009 } 2010 2011 void MacroAssembler::loadInt32ToStringWithBase( 2012 Register input, int32_t base, Register dest, Register scratch1, 2013 Register scratch2, const StaticStrings& staticStrings, bool lowerCase, 2014 Label* fail) { 2015 MOZ_ASSERT(2 <= base && base <= 36, "base must be in range [2, 36]"); 2016 2017 // Compute |"0123456789abcdefghijklmnopqrstuvwxyz"[r]|. 2018 auto toChar = [this, base, lowerCase](Register r) { 2019 #ifdef DEBUG 2020 Label ok; 2021 branch32(Assembler::Below, r, Imm32(base), &ok); 2022 assumeUnreachable("bad digit"); 2023 bind(&ok); 2024 #endif 2025 2026 if (base <= 10) { 2027 add32(Imm32('0'), r); 2028 } else { 2029 Label done; 2030 add32(Imm32('0'), r); 2031 branch32(Assembler::BelowOrEqual, r, Imm32('9'), &done); 2032 add32(Imm32((lowerCase ? 'a' : 'A') - '0' - 10), r); 2033 bind(&done); 2034 } 2035 }; 2036 2037 // Perform a "unit" lookup when |unsigned(input) < unsigned(base)|. 2038 Label lengthTwo, done; 2039 branch32(Assembler::AboveOrEqual, input, Imm32(base), &lengthTwo); 2040 { 2041 move32(input, scratch1); 2042 toChar(scratch1); 2043 2044 loadStringFromUnit(scratch1, dest, staticStrings); 2045 2046 jump(&done); 2047 } 2048 bind(&lengthTwo); 2049 2050 // Perform a "length2" lookup when |unsigned(input) < unsigned(base * base)|. 2051 branch32(Assembler::AboveOrEqual, input, Imm32(base * base), fail); 2052 { 2053 // Compute |scratch1 = input / base| and |scratch2 = input % base|. 2054 if (mozilla::IsPowerOfTwo(uint32_t(base))) { 2055 uint32_t shift = mozilla::FloorLog2(base); 2056 2057 rshift32(Imm32(shift), input, scratch1); 2058 and32(Imm32((uint32_t(1) << shift) - 1), input, scratch2); 2059 } else { 2060 // The following code matches CodeGenerator::visitUDivOrModConstant() 2061 // for x86-shared. Also see Hacker's Delight 2nd edition, chapter 10-8 2062 // "Unsigned Division by 7" for the case when |rmc.multiplier| exceeds 2063 // UINT32_MAX and we need to adjust the shift amount. 2064 2065 auto rmc = ReciprocalMulConstants::computeUnsignedDivisionConstants( 2066 uint32_t(base)); 2067 2068 // We first compute |q = (M * n) >> 32), where M = rmc.multiplier. 2069 mulHighUnsigned32(Imm32(rmc.multiplier), input, scratch1); 2070 2071 if (rmc.multiplier > UINT32_MAX) { 2072 // M >= 2^32 and shift == 0 is impossible, as d >= 2 implies that 2073 // ((M * n) >> (32 + shift)) >= n > floor(n/d) whenever n >= d, 2074 // contradicting the proof of correctness in computeDivisionConstants. 2075 MOZ_ASSERT(rmc.shiftAmount > 0); 2076 MOZ_ASSERT(rmc.multiplier < (int64_t(1) << 33)); 2077 2078 // Compute |t = (n - q) / 2|. 2079 move32(input, scratch2); 2080 sub32(scratch1, scratch2); 2081 rshift32(Imm32(1), scratch2); 2082 2083 // Compute |t = (n - q) / 2 + q = (n + q) / 2|. 2084 add32(scratch2, scratch1); 2085 2086 // Finish the computation |q = floor(n / d)|. 2087 rshift32(Imm32(rmc.shiftAmount - 1), scratch1); 2088 } else { 2089 rshift32(Imm32(rmc.shiftAmount), scratch1); 2090 } 2091 2092 // Compute the remainder from |r = n - q * d|. 2093 move32(scratch1, dest); 2094 mul32(Imm32(base), dest); 2095 move32(input, scratch2); 2096 sub32(dest, scratch2); 2097 } 2098 2099 // Compute the digits of the divisor and remainder. 2100 toChar(scratch1); 2101 toChar(scratch2); 2102 2103 // Look up the 2-character digit string in the small-char table. 2104 loadLengthTwoString(scratch1, scratch2, dest, staticStrings); 2105 } 2106 bind(&done); 2107 } 2108 2109 void MacroAssembler::loadBigIntDigits(Register bigInt, Register digits) { 2110 MOZ_ASSERT(digits != bigInt); 2111 2112 // Load the inline digits. 2113 computeEffectiveAddress(Address(bigInt, BigInt::offsetOfInlineDigits()), 2114 digits); 2115 2116 // If inline digits aren't used, load the heap digits. Use a conditional move 2117 // to prevent speculative execution. 2118 cmp32LoadPtr(Assembler::Above, Address(bigInt, BigInt::offsetOfLength()), 2119 Imm32(int32_t(BigInt::inlineDigitsLength())), 2120 Address(bigInt, BigInt::offsetOfHeapDigits()), digits); 2121 } 2122 2123 void MacroAssembler::loadBigInt64(Register bigInt, Register64 dest) { 2124 // This code follows the implementation of |BigInt::toUint64()|. We're also 2125 // using it for inline callers of |BigInt::toInt64()|, which works, because 2126 // all supported Jit architectures use a two's complement representation for 2127 // int64 values, which means the WrapToSigned call in toInt64() is a no-op. 2128 2129 Label done, nonZero; 2130 2131 branchIfBigIntIsNonZero(bigInt, &nonZero); 2132 { 2133 move64(Imm64(0), dest); 2134 jump(&done); 2135 } 2136 bind(&nonZero); 2137 2138 #ifdef JS_PUNBOX64 2139 Register digits = dest.reg; 2140 #else 2141 Register digits = dest.high; 2142 #endif 2143 2144 loadBigIntDigits(bigInt, digits); 2145 2146 #if JS_PUNBOX64 2147 // Load the first digit into the destination register. 2148 load64(Address(digits, 0), dest); 2149 #else 2150 // Load the first digit into the destination register's low value. 2151 load32(Address(digits, 0), dest.low); 2152 2153 // And conditionally load the second digit into the high value register. 2154 Label twoDigits, digitsDone; 2155 branch32(Assembler::Above, Address(bigInt, BigInt::offsetOfLength()), 2156 Imm32(1), &twoDigits); 2157 { 2158 move32(Imm32(0), dest.high); 2159 jump(&digitsDone); 2160 } 2161 { 2162 bind(&twoDigits); 2163 load32(Address(digits, sizeof(BigInt::Digit)), dest.high); 2164 } 2165 bind(&digitsDone); 2166 #endif 2167 2168 branchTest32(Assembler::Zero, Address(bigInt, BigInt::offsetOfFlags()), 2169 Imm32(BigInt::signBitMask()), &done); 2170 neg64(dest); 2171 2172 bind(&done); 2173 } 2174 2175 void MacroAssembler::loadBigIntDigit(Register bigInt, Register dest) { 2176 Label done, nonZero; 2177 branchIfBigIntIsNonZero(bigInt, &nonZero); 2178 { 2179 movePtr(ImmWord(0), dest); 2180 jump(&done); 2181 } 2182 bind(&nonZero); 2183 2184 loadBigIntDigits(bigInt, dest); 2185 2186 // Load the first digit into the destination register. 2187 loadPtr(Address(dest, 0), dest); 2188 2189 bind(&done); 2190 } 2191 2192 void MacroAssembler::loadBigIntDigit(Register bigInt, Register dest, 2193 Label* fail) { 2194 MOZ_ASSERT(bigInt != dest); 2195 2196 branch32(Assembler::Above, Address(bigInt, BigInt::offsetOfLength()), 2197 Imm32(1), fail); 2198 2199 static_assert(BigInt::inlineDigitsLength() > 0, 2200 "Single digit BigInts use inline storage"); 2201 2202 // Load the first inline digit into the destination register. 2203 movePtr(ImmWord(0), dest); 2204 cmp32LoadPtr(Assembler::NotEqual, Address(bigInt, BigInt::offsetOfLength()), 2205 Imm32(0), Address(bigInt, BigInt::offsetOfInlineDigits()), dest); 2206 } 2207 2208 void MacroAssembler::loadBigIntPtr(Register bigInt, Register dest, 2209 Label* fail) { 2210 loadBigIntDigit(bigInt, dest, fail); 2211 2212 // BigInt digits are stored as unsigned numbers. Take the failure path when 2213 // the digit can't be stored in intptr_t. 2214 2215 Label nonNegative, done; 2216 branchIfBigIntIsNonNegative(bigInt, &nonNegative); 2217 { 2218 // Negate |dest| when the BigInt is negative. 2219 negPtr(dest); 2220 2221 // Test after negating to handle INTPTR_MIN correctly. 2222 branchTestPtr(Assembler::NotSigned, dest, dest, fail); 2223 jump(&done); 2224 } 2225 bind(&nonNegative); 2226 branchTestPtr(Assembler::Signed, dest, dest, fail); 2227 bind(&done); 2228 } 2229 2230 void MacroAssembler::initializeBigInt64(Scalar::Type type, Register bigInt, 2231 Register64 val, Register64 temp) { 2232 MOZ_ASSERT(Scalar::isBigIntType(type)); 2233 2234 store32(Imm32(0), Address(bigInt, BigInt::offsetOfFlags())); 2235 2236 Label done, nonZero; 2237 branch64(Assembler::NotEqual, val, Imm64(0), &nonZero); 2238 { 2239 store32(Imm32(0), Address(bigInt, BigInt::offsetOfLength())); 2240 jump(&done); 2241 } 2242 bind(&nonZero); 2243 2244 if (type == Scalar::BigInt64) { 2245 // Copy the input when we're not allowed to clobber it. 2246 if (temp != Register64::Invalid()) { 2247 move64(val, temp); 2248 val = temp; 2249 } 2250 2251 // Set the sign-bit for negative values and then continue with the two's 2252 // complement. 2253 Label isPositive; 2254 branch64(Assembler::GreaterThan, val, Imm64(0), &isPositive); 2255 { 2256 store32(Imm32(BigInt::signBitMask()), 2257 Address(bigInt, BigInt::offsetOfFlags())); 2258 neg64(val); 2259 } 2260 bind(&isPositive); 2261 } 2262 2263 store32(Imm32(1), Address(bigInt, BigInt::offsetOfLength())); 2264 2265 static_assert(sizeof(BigInt::Digit) == sizeof(uintptr_t), 2266 "BigInt Digit size matches uintptr_t, so there's a single " 2267 "store on 64-bit and up to two stores on 32-bit"); 2268 2269 #ifndef JS_PUNBOX64 2270 Label singleDigit; 2271 branchTest32(Assembler::Zero, val.high, val.high, &singleDigit); 2272 store32(Imm32(2), Address(bigInt, BigInt::offsetOfLength())); 2273 bind(&singleDigit); 2274 2275 // We can perform a single store64 on 32-bit platforms, because inline 2276 // storage can store at least two 32-bit integers. 2277 static_assert(BigInt::inlineDigitsLength() >= 2, 2278 "BigInt inline storage can store at least two digits"); 2279 #endif 2280 2281 store64(val, Address(bigInt, js::BigInt::offsetOfInlineDigits())); 2282 2283 bind(&done); 2284 } 2285 2286 void MacroAssembler::initializeBigIntPtr(Register bigInt, Register val) { 2287 store32(Imm32(0), Address(bigInt, BigInt::offsetOfFlags())); 2288 2289 Label done, nonZero; 2290 branchTestPtr(Assembler::NonZero, val, val, &nonZero); 2291 { 2292 store32(Imm32(0), Address(bigInt, BigInt::offsetOfLength())); 2293 jump(&done); 2294 } 2295 bind(&nonZero); 2296 2297 // Set the sign-bit for negative values and then continue with the two's 2298 // complement. 2299 Label isPositive; 2300 branchTestPtr(Assembler::NotSigned, val, val, &isPositive); 2301 { 2302 store32(Imm32(BigInt::signBitMask()), 2303 Address(bigInt, BigInt::offsetOfFlags())); 2304 negPtr(val); 2305 } 2306 bind(&isPositive); 2307 2308 store32(Imm32(1), Address(bigInt, BigInt::offsetOfLength())); 2309 2310 static_assert(sizeof(BigInt::Digit) == sizeof(uintptr_t), 2311 "BigInt Digit size matches uintptr_t"); 2312 2313 storePtr(val, Address(bigInt, js::BigInt::offsetOfInlineDigits())); 2314 2315 bind(&done); 2316 } 2317 2318 void MacroAssembler::copyBigIntWithInlineDigits(Register src, Register dest, 2319 Register temp, 2320 gc::Heap initialHeap, 2321 Label* fail) { 2322 branch32(Assembler::Above, Address(src, BigInt::offsetOfLength()), 2323 Imm32(int32_t(BigInt::inlineDigitsLength())), fail); 2324 2325 newGCBigInt(dest, temp, initialHeap, fail); 2326 2327 // Copy the sign-bit, but not any of the other bits used by the GC. 2328 load32(Address(src, BigInt::offsetOfFlags()), temp); 2329 and32(Imm32(BigInt::signBitMask()), temp); 2330 store32(temp, Address(dest, BigInt::offsetOfFlags())); 2331 2332 // Copy the length. 2333 load32(Address(src, BigInt::offsetOfLength()), temp); 2334 store32(temp, Address(dest, BigInt::offsetOfLength())); 2335 2336 // Copy the digits. 2337 Address srcDigits(src, js::BigInt::offsetOfInlineDigits()); 2338 Address destDigits(dest, js::BigInt::offsetOfInlineDigits()); 2339 2340 for (size_t i = 0; i < BigInt::inlineDigitsLength(); i++) { 2341 static_assert(sizeof(BigInt::Digit) == sizeof(uintptr_t), 2342 "BigInt Digit size matches uintptr_t"); 2343 2344 loadPtr(srcDigits, temp); 2345 storePtr(temp, destDigits); 2346 2347 srcDigits = Address(src, srcDigits.offset + sizeof(BigInt::Digit)); 2348 destDigits = Address(dest, destDigits.offset + sizeof(BigInt::Digit)); 2349 } 2350 } 2351 2352 void MacroAssembler::compareBigIntAndInt32(JSOp op, Register bigInt, 2353 Register int32, Register scratch1, 2354 Register scratch2, Label* ifTrue, 2355 Label* ifFalse) { 2356 MOZ_ASSERT(IsLooseEqualityOp(op) || IsRelationalOp(op)); 2357 2358 static_assert(std::is_same_v<BigInt::Digit, uintptr_t>, 2359 "BigInt digit can be loaded in a pointer-sized register"); 2360 static_assert(sizeof(BigInt::Digit) >= sizeof(uint32_t), 2361 "BigInt digit stores at least an uint32"); 2362 2363 // Test for too large numbers. 2364 // 2365 // If the unsigned value of the BigInt can't be expressed in an uint32/uint64, 2366 // the result of the comparison is a constant. 2367 if (op == JSOp::Eq || op == JSOp::Ne) { 2368 Label* tooLarge = op == JSOp::Eq ? ifFalse : ifTrue; 2369 branch32(Assembler::GreaterThan, 2370 Address(bigInt, BigInt::offsetOfDigitLength()), Imm32(1), 2371 tooLarge); 2372 } else { 2373 Label doCompare; 2374 branch32(Assembler::LessThanOrEqual, 2375 Address(bigInt, BigInt::offsetOfDigitLength()), Imm32(1), 2376 &doCompare); 2377 2378 // Still need to take the sign-bit into account for relational operations. 2379 if (op == JSOp::Lt || op == JSOp::Le) { 2380 branchIfBigIntIsNegative(bigInt, ifTrue); 2381 jump(ifFalse); 2382 } else { 2383 branchIfBigIntIsNegative(bigInt, ifFalse); 2384 jump(ifTrue); 2385 } 2386 2387 bind(&doCompare); 2388 } 2389 2390 // Test for mismatched signs and, if the signs are equal, load |abs(x)| in 2391 // |scratch1| and |abs(y)| in |scratch2| and then compare the unsigned numbers 2392 // against each other. 2393 { 2394 // Jump to |ifTrue| resp. |ifFalse| if the BigInt is strictly less than 2395 // resp. strictly greater than the int32 value, depending on the comparison 2396 // operator. 2397 Label* greaterThan; 2398 Label* lessThan; 2399 if (op == JSOp::Eq) { 2400 greaterThan = ifFalse; 2401 lessThan = ifFalse; 2402 } else if (op == JSOp::Ne) { 2403 greaterThan = ifTrue; 2404 lessThan = ifTrue; 2405 } else if (op == JSOp::Lt || op == JSOp::Le) { 2406 greaterThan = ifFalse; 2407 lessThan = ifTrue; 2408 } else { 2409 MOZ_ASSERT(op == JSOp::Gt || op == JSOp::Ge); 2410 greaterThan = ifTrue; 2411 lessThan = ifFalse; 2412 } 2413 2414 // BigInt digits are always stored as an unsigned number. 2415 loadBigIntDigit(bigInt, scratch1); 2416 2417 // Load the int32 into |scratch2| and negate it for negative numbers. 2418 move32(int32, scratch2); 2419 2420 Label isNegative, doCompare; 2421 branchIfBigIntIsNegative(bigInt, &isNegative); 2422 branch32(Assembler::LessThan, int32, Imm32(0), greaterThan); 2423 jump(&doCompare); 2424 2425 // We rely on |neg32(INT32_MIN)| staying INT32_MIN, because we're using an 2426 // unsigned comparison below. 2427 bind(&isNegative); 2428 branch32(Assembler::GreaterThanOrEqual, int32, Imm32(0), lessThan); 2429 neg32(scratch2); 2430 2431 // Not all supported platforms (e.g. MIPS64) zero-extend 32-bit operations, 2432 // so we need to explicitly clear any high 32-bits. 2433 move32ZeroExtendToPtr(scratch2, scratch2); 2434 2435 // Reverse the relational comparator for negative numbers. 2436 // |-x < -y| <=> |+x > +y|. 2437 // |-x ≤ -y| <=> |+x ≥ +y|. 2438 // |-x > -y| <=> |+x < +y|. 2439 // |-x ≥ -y| <=> |+x ≤ +y|. 2440 JSOp reversed = ReverseCompareOp(op); 2441 if (reversed != op) { 2442 branchPtr(JSOpToCondition(reversed, /* isSigned = */ false), scratch1, 2443 scratch2, ifTrue); 2444 jump(ifFalse); 2445 } 2446 2447 bind(&doCompare); 2448 branchPtr(JSOpToCondition(op, /* isSigned = */ false), scratch1, scratch2, 2449 ifTrue); 2450 } 2451 } 2452 2453 void MacroAssembler::compareBigIntAndInt32(JSOp op, Register bigInt, 2454 Imm32 int32, Register scratch, 2455 Label* ifTrue, Label* ifFalse) { 2456 MOZ_ASSERT(IsLooseEqualityOp(op) || IsRelationalOp(op)); 2457 2458 static_assert(std::is_same_v<BigInt::Digit, uintptr_t>, 2459 "BigInt digit can be loaded in a pointer-sized register"); 2460 static_assert(sizeof(BigInt::Digit) >= sizeof(uint32_t), 2461 "BigInt digit stores at least an uint32"); 2462 2463 // Comparison against zero doesn't require loading any BigInt digits. 2464 if (int32.value == 0) { 2465 switch (op) { 2466 case JSOp::Eq: 2467 branchIfBigIntIsZero(bigInt, ifTrue); 2468 break; 2469 case JSOp::Ne: 2470 branchIfBigIntIsNonZero(bigInt, ifTrue); 2471 break; 2472 case JSOp::Lt: 2473 branchIfBigIntIsNegative(bigInt, ifTrue); 2474 break; 2475 case JSOp::Le: 2476 branchIfBigIntIsZero(bigInt, ifTrue); 2477 branchIfBigIntIsNegative(bigInt, ifTrue); 2478 break; 2479 case JSOp::Gt: 2480 branchIfBigIntIsZero(bigInt, ifFalse); 2481 branchIfBigIntIsNonNegative(bigInt, ifTrue); 2482 break; 2483 case JSOp::Ge: 2484 branchIfBigIntIsNonNegative(bigInt, ifTrue); 2485 break; 2486 default: 2487 MOZ_CRASH("bad comparison operator"); 2488 } 2489 2490 // Fall through to the false case. 2491 return; 2492 } 2493 2494 // Jump to |ifTrue| resp. |ifFalse| if the BigInt is strictly less than 2495 // resp. strictly greater than the int32 value, depending on the comparison 2496 // operator. 2497 Label* greaterThan; 2498 Label* lessThan; 2499 if (op == JSOp::Eq) { 2500 greaterThan = ifFalse; 2501 lessThan = ifFalse; 2502 } else if (op == JSOp::Ne) { 2503 greaterThan = ifTrue; 2504 lessThan = ifTrue; 2505 } else if (op == JSOp::Lt || op == JSOp::Le) { 2506 greaterThan = ifFalse; 2507 lessThan = ifTrue; 2508 } else { 2509 MOZ_ASSERT(op == JSOp::Gt || op == JSOp::Ge); 2510 greaterThan = ifTrue; 2511 lessThan = ifFalse; 2512 } 2513 2514 // Test for mismatched signs. 2515 if (int32.value > 0) { 2516 branchIfBigIntIsNegative(bigInt, lessThan); 2517 } else { 2518 branchIfBigIntIsNonNegative(bigInt, greaterThan); 2519 } 2520 2521 // Both signs are equal, load |abs(x)| in |scratch| and then compare the 2522 // unsigned numbers against each other. 2523 // 2524 // If the unsigned value of the BigInt can't be expressed in an uint32/uint64, 2525 // the result of the comparison is a constant. 2526 Label* tooLarge = int32.value > 0 ? greaterThan : lessThan; 2527 loadBigIntDigit(bigInt, scratch, tooLarge); 2528 2529 // Use the unsigned value of the immediate. 2530 ImmWord uint32 = ImmWord(mozilla::Abs(int32.value)); 2531 2532 // Reverse the relational comparator for negative numbers. 2533 // |-x < -y| <=> |+x > +y|. 2534 // |-x ≤ -y| <=> |+x ≥ +y|. 2535 // |-x > -y| <=> |+x < +y|. 2536 // |-x ≥ -y| <=> |+x ≤ +y|. 2537 if (int32.value < 0) { 2538 op = ReverseCompareOp(op); 2539 } 2540 2541 branchPtr(JSOpToCondition(op, /* isSigned = */ false), scratch, uint32, 2542 ifTrue); 2543 } 2544 2545 void MacroAssembler::equalBigInts(Register left, Register right, Register temp1, 2546 Register temp2, Register temp3, 2547 Register temp4, Label* notSameSign, 2548 Label* notSameLength, Label* notSameDigit) { 2549 MOZ_ASSERT(left != temp1); 2550 MOZ_ASSERT(right != temp1); 2551 MOZ_ASSERT(right != temp2); 2552 2553 // Jump to |notSameSign| when the sign aren't the same. 2554 load32(Address(left, BigInt::offsetOfFlags()), temp1); 2555 xor32(Address(right, BigInt::offsetOfFlags()), temp1); 2556 branchTest32(Assembler::NonZero, temp1, Imm32(BigInt::signBitMask()), 2557 notSameSign); 2558 2559 // Jump to |notSameLength| when the digits length is different. 2560 load32(Address(right, BigInt::offsetOfLength()), temp1); 2561 branch32(Assembler::NotEqual, Address(left, BigInt::offsetOfLength()), temp1, 2562 notSameLength); 2563 2564 // Both BigInts have the same sign and the same number of digits. Loop 2565 // over each digit, starting with the left-most one, and break from the 2566 // loop when the first non-matching digit was found. 2567 2568 loadBigIntDigits(left, temp2); 2569 loadBigIntDigits(right, temp3); 2570 2571 static_assert(sizeof(BigInt::Digit) == sizeof(void*), 2572 "BigInt::Digit is pointer sized"); 2573 2574 computeEffectiveAddress(BaseIndex(temp2, temp1, ScalePointer), temp2); 2575 computeEffectiveAddress(BaseIndex(temp3, temp1, ScalePointer), temp3); 2576 2577 Label start, loop; 2578 jump(&start); 2579 bind(&loop); 2580 2581 subPtr(Imm32(sizeof(BigInt::Digit)), temp2); 2582 subPtr(Imm32(sizeof(BigInt::Digit)), temp3); 2583 2584 loadPtr(Address(temp3, 0), temp4); 2585 branchPtr(Assembler::NotEqual, Address(temp2, 0), temp4, notSameDigit); 2586 2587 bind(&start); 2588 branchSub32(Assembler::NotSigned, Imm32(1), temp1, &loop); 2589 2590 // No different digits were found, both BigInts are equal to each other. 2591 } 2592 2593 void MacroAssembler::typeOfObject(Register obj, Register scratch, Label* slow, 2594 Label* isObject, Label* isCallable, 2595 Label* isUndefined) { 2596 loadObjClassUnsafe(obj, scratch); 2597 2598 // Proxies can emulate undefined and have complex isCallable behavior. 2599 branchTestClassIsProxy(true, scratch, slow); 2600 2601 // JSFunctions are always callable. 2602 branchTestClassIsFunction(Assembler::Equal, scratch, isCallable); 2603 2604 // Objects that emulate undefined. 2605 Address flags(scratch, JSClass::offsetOfFlags()); 2606 branchTest32(Assembler::NonZero, flags, Imm32(JSCLASS_EMULATES_UNDEFINED), 2607 isUndefined); 2608 2609 // Handle classes with a call hook. 2610 branchPtr(Assembler::Equal, Address(scratch, offsetof(JSClass, cOps)), 2611 ImmPtr(nullptr), isObject); 2612 2613 loadPtr(Address(scratch, offsetof(JSClass, cOps)), scratch); 2614 branchPtr(Assembler::Equal, Address(scratch, offsetof(JSClassOps, call)), 2615 ImmPtr(nullptr), isObject); 2616 2617 jump(isCallable); 2618 } 2619 2620 void MacroAssembler::isCallableOrConstructor(bool isCallable, Register obj, 2621 Register output, Label* isProxy) { 2622 MOZ_ASSERT(obj != output); 2623 2624 Label notFunction, hasCOps, done; 2625 loadObjClassUnsafe(obj, output); 2626 2627 // An object is callable iff: 2628 // is<JSFunction>() || (getClass()->cOps && getClass()->cOps->call). 2629 // An object is constructor iff: 2630 // ((is<JSFunction>() && as<JSFunction>().isConstructor) || 2631 // (getClass()->cOps && getClass()->cOps->construct)). 2632 branchTestClassIsFunction(Assembler::NotEqual, output, ¬Function); 2633 if (isCallable) { 2634 move32(Imm32(1), output); 2635 } else { 2636 static_assert(mozilla::IsPowerOfTwo(uint32_t(FunctionFlags::CONSTRUCTOR)), 2637 "FunctionFlags::CONSTRUCTOR has only one bit set"); 2638 2639 load32(Address(obj, JSFunction::offsetOfFlagsAndArgCount()), output); 2640 rshift32(Imm32(mozilla::FloorLog2(uint32_t(FunctionFlags::CONSTRUCTOR))), 2641 output); 2642 and32(Imm32(1), output); 2643 } 2644 jump(&done); 2645 2646 bind(¬Function); 2647 2648 if (!isCallable) { 2649 // For bound functions, we need to check the isConstructor flag. 2650 Label notBoundFunction; 2651 branchPtr(Assembler::NotEqual, output, ImmPtr(&BoundFunctionObject::class_), 2652 ¬BoundFunction); 2653 2654 static_assert(BoundFunctionObject::IsConstructorFlag == 0b1, 2655 "AND operation results in boolean value"); 2656 unboxInt32(Address(obj, BoundFunctionObject::offsetOfFlagsSlot()), output); 2657 and32(Imm32(BoundFunctionObject::IsConstructorFlag), output); 2658 jump(&done); 2659 2660 bind(¬BoundFunction); 2661 } 2662 2663 // Just skim proxies off. Their notion of isCallable()/isConstructor() is 2664 // more complicated. 2665 branchTestClassIsProxy(true, output, isProxy); 2666 2667 branchPtr(Assembler::NonZero, Address(output, offsetof(JSClass, cOps)), 2668 ImmPtr(nullptr), &hasCOps); 2669 move32(Imm32(0), output); 2670 jump(&done); 2671 2672 bind(&hasCOps); 2673 loadPtr(Address(output, offsetof(JSClass, cOps)), output); 2674 size_t opsOffset = 2675 isCallable ? offsetof(JSClassOps, call) : offsetof(JSClassOps, construct); 2676 cmpPtrSet(Assembler::NonZero, Address(output, opsOffset), ImmPtr(nullptr), 2677 output); 2678 2679 bind(&done); 2680 } 2681 2682 void MacroAssembler::loadJSContext(Register dest) { 2683 movePtr(ImmPtr(runtime()->mainContextPtr()), dest); 2684 } 2685 2686 static const uint8_t* ContextRealmPtr(CompileRuntime* rt) { 2687 return (static_cast<const uint8_t*>(rt->mainContextPtr()) + 2688 JSContext::offsetOfRealm()); 2689 } 2690 2691 void MacroAssembler::loadGlobalObjectData(Register dest) { 2692 loadPtr(AbsoluteAddress(ContextRealmPtr(runtime())), dest); 2693 loadPtr(Address(dest, Realm::offsetOfActiveGlobal()), dest); 2694 loadPrivate(Address(dest, GlobalObject::offsetOfGlobalDataSlot()), dest); 2695 } 2696 2697 void MacroAssembler::switchToRealm(Register realm) { 2698 storePtr(realm, AbsoluteAddress(ContextRealmPtr(runtime()))); 2699 } 2700 2701 void MacroAssembler::loadRealmFuse(RealmFuses::FuseIndex index, Register dest) { 2702 // Load Realm pointer 2703 loadPtr(AbsoluteAddress(ContextRealmPtr(runtime())), dest); 2704 loadPtr(Address(dest, RealmFuses::offsetOfFuseWordRelativeToRealm(index)), 2705 dest); 2706 } 2707 2708 void MacroAssembler::loadRuntimeFuse(RuntimeFuses::FuseIndex index, 2709 Register dest) { 2710 loadPtr(AbsoluteAddress(runtime()->addressOfRuntimeFuse(index)), dest); 2711 } 2712 2713 void MacroAssembler::guardRuntimeFuse(RuntimeFuses::FuseIndex index, 2714 Label* fail) { 2715 AbsoluteAddress addr(runtime()->addressOfRuntimeFuse(index)); 2716 branchPtr(Assembler::NotEqual, addr, ImmWord(0), fail); 2717 } 2718 2719 void MacroAssembler::switchToRealm(const void* realm, Register scratch) { 2720 MOZ_ASSERT(realm); 2721 2722 movePtr(ImmPtr(realm), scratch); 2723 switchToRealm(scratch); 2724 } 2725 2726 void MacroAssembler::switchToObjectRealm(Register obj, Register scratch) { 2727 loadPtr(Address(obj, JSObject::offsetOfShape()), scratch); 2728 loadPtr(Address(scratch, Shape::offsetOfBaseShape()), scratch); 2729 loadPtr(Address(scratch, BaseShape::offsetOfRealm()), scratch); 2730 switchToRealm(scratch); 2731 } 2732 2733 void MacroAssembler::switchToBaselineFrameRealm(Register scratch) { 2734 Address envChain(FramePointer, 2735 BaselineFrame::reverseOffsetOfEnvironmentChain()); 2736 loadPtr(envChain, scratch); 2737 switchToObjectRealm(scratch, scratch); 2738 } 2739 2740 void MacroAssembler::switchToWasmInstanceRealm(Register scratch1, 2741 Register scratch2) { 2742 loadPtr(Address(InstanceReg, wasm::Instance::offsetOfCx()), scratch1); 2743 loadPtr(Address(InstanceReg, wasm::Instance::offsetOfRealm()), scratch2); 2744 storePtr(scratch2, Address(scratch1, JSContext::offsetOfRealm())); 2745 } 2746 2747 template <typename ValueType> 2748 void MacroAssembler::storeLocalAllocSite(ValueType value, Register scratch) { 2749 loadPtr(AbsoluteAddress(ContextRealmPtr(runtime())), scratch); 2750 storePtr(value, Address(scratch, JS::Realm::offsetOfLocalAllocSite())); 2751 } 2752 2753 template void MacroAssembler::storeLocalAllocSite(Register, Register); 2754 template void MacroAssembler::storeLocalAllocSite(ImmWord, Register); 2755 template void MacroAssembler::storeLocalAllocSite(ImmPtr, Register); 2756 2757 void MacroAssembler::debugAssertContextRealm(const void* realm, 2758 Register scratch) { 2759 #ifdef DEBUG 2760 Label ok; 2761 movePtr(ImmPtr(realm), scratch); 2762 branchPtr(Assembler::Equal, AbsoluteAddress(ContextRealmPtr(runtime())), 2763 scratch, &ok); 2764 assumeUnreachable("Unexpected context realm"); 2765 bind(&ok); 2766 #endif 2767 } 2768 2769 void MacroAssembler::setIsCrossRealmArrayConstructor(Register obj, 2770 Register output) { 2771 #ifdef DEBUG 2772 Label notProxy; 2773 branchTestObjectIsProxy(false, obj, output, ¬Proxy); 2774 assumeUnreachable("Unexpected proxy in setIsCrossRealmArrayConstructor"); 2775 bind(¬Proxy); 2776 #endif 2777 2778 // The object's realm must not be cx->realm. 2779 Label isFalse, done; 2780 loadPtr(Address(obj, JSObject::offsetOfShape()), output); 2781 loadPtr(Address(output, Shape::offsetOfBaseShape()), output); 2782 loadPtr(Address(output, BaseShape::offsetOfRealm()), output); 2783 branchPtr(Assembler::Equal, AbsoluteAddress(ContextRealmPtr(runtime())), 2784 output, &isFalse); 2785 2786 // The object must be a function. 2787 branchTestObjIsFunction(Assembler::NotEqual, obj, output, obj, &isFalse); 2788 2789 // The function must be the ArrayConstructor native. 2790 branchPtr(Assembler::NotEqual, 2791 Address(obj, JSFunction::offsetOfNativeOrEnv()), 2792 ImmPtr(js::ArrayConstructor), &isFalse); 2793 2794 move32(Imm32(1), output); 2795 jump(&done); 2796 2797 bind(&isFalse); 2798 move32(Imm32(0), output); 2799 2800 bind(&done); 2801 } 2802 2803 void MacroAssembler::guardObjectHasSameRealm(Register obj, Register scratch, 2804 Label* fail) { 2805 loadPtr(Address(obj, JSObject::offsetOfShape()), scratch); 2806 loadPtr(Address(scratch, Shape::offsetOfBaseShape()), scratch); 2807 loadPtr(Address(scratch, BaseShape::offsetOfRealm()), scratch); 2808 branchPtr(Assembler::NotEqual, AbsoluteAddress(ContextRealmPtr(runtime())), 2809 scratch, fail); 2810 } 2811 2812 void MacroAssembler::setIsDefinitelyTypedArrayConstructor(Register obj, 2813 Register output) { 2814 Label isFalse, isTrue, done; 2815 2816 // The object must be a function. (Wrappers are not supported.) 2817 branchTestObjIsFunction(Assembler::NotEqual, obj, output, obj, &isFalse); 2818 2819 // Load the native into |output|. 2820 loadPtr(Address(obj, JSFunction::offsetOfNativeOrEnv()), output); 2821 2822 auto branchIsTypedArrayCtor = [&](Scalar::Type type) { 2823 // The function must be a TypedArrayConstructor native (from any realm). 2824 JSNative constructor = TypedArrayConstructorNative(type); 2825 branchPtr(Assembler::Equal, output, ImmPtr(constructor), &isTrue); 2826 }; 2827 2828 #define TYPED_ARRAY_CONSTRUCTOR_NATIVE(_, T, N) \ 2829 branchIsTypedArrayCtor(Scalar::N); 2830 JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_CONSTRUCTOR_NATIVE) 2831 #undef TYPED_ARRAY_CONSTRUCTOR_NATIVE 2832 2833 // Falls through to the false case. 2834 2835 bind(&isFalse); 2836 move32(Imm32(0), output); 2837 jump(&done); 2838 2839 bind(&isTrue); 2840 move32(Imm32(1), output); 2841 2842 bind(&done); 2843 } 2844 2845 void MacroAssembler::loadMegamorphicCache(Register dest) { 2846 movePtr(ImmPtr(runtime()->addressOfMegamorphicCache()), dest); 2847 } 2848 void MacroAssembler::loadMegamorphicSetPropCache(Register dest) { 2849 movePtr(ImmPtr(runtime()->addressOfMegamorphicSetPropCache()), dest); 2850 } 2851 2852 void MacroAssembler::tryFastAtomize(Register str, Register scratch, 2853 Register output, Label* fail) { 2854 Label found, done, notAtomRef; 2855 2856 branchTest32(Assembler::Zero, Address(str, JSString::offsetOfFlags()), 2857 Imm32(JSString::ATOM_REF_BIT), ¬AtomRef); 2858 loadPtr(Address(str, JSAtomRefString::offsetOfAtom()), output); 2859 jump(&done); 2860 bind(¬AtomRef); 2861 2862 uintptr_t cachePtr = uintptr_t(runtime()->addressOfStringToAtomCache()); 2863 void* offset = (void*)(cachePtr + StringToAtomCache::offsetOfLastLookups()); 2864 movePtr(ImmPtr(offset), scratch); 2865 2866 static_assert(StringToAtomCache::NumLastLookups == 2); 2867 size_t stringOffset = StringToAtomCache::LastLookup::offsetOfString(); 2868 size_t lookupSize = sizeof(StringToAtomCache::LastLookup); 2869 branchPtr(Assembler::Equal, Address(scratch, stringOffset), str, &found); 2870 branchPtr(Assembler::NotEqual, Address(scratch, lookupSize + stringOffset), 2871 str, fail); 2872 addPtr(Imm32(lookupSize), scratch); 2873 2874 // We found a hit in the lastLookups_ array! Load the associated atom 2875 // and jump back up to our usual atom handling code 2876 bind(&found); 2877 size_t atomOffset = StringToAtomCache::LastLookup::offsetOfAtom(); 2878 loadPtr(Address(scratch, atomOffset), output); 2879 bind(&done); 2880 } 2881 2882 void MacroAssembler::loadAtomHash(Register id, Register outHash, Label* done) { 2883 Label doneInner, fatInline; 2884 if (!done) { 2885 done = &doneInner; 2886 } 2887 move32(Imm32(JSString::FAT_INLINE_MASK), outHash); 2888 and32(Address(id, JSString::offsetOfFlags()), outHash); 2889 2890 branch32(Assembler::Equal, outHash, Imm32(JSString::FAT_INLINE_MASK), 2891 &fatInline); 2892 load32(Address(id, NormalAtom::offsetOfHash()), outHash); 2893 jump(done); 2894 bind(&fatInline); 2895 load32(Address(id, FatInlineAtom::offsetOfHash()), outHash); 2896 jump(done); 2897 bind(&doneInner); 2898 } 2899 2900 void MacroAssembler::loadAtomOrSymbolAndHash(ValueOperand value, Register outId, 2901 Register outHash, 2902 Label* cacheMiss) { 2903 Label isString, isSymbol, isNull, isUndefined, done, nonAtom, atom; 2904 2905 { 2906 ScratchTagScope tag(*this, value); 2907 splitTagForTest(value, tag); 2908 branchTestString(Assembler::Equal, tag, &isString); 2909 branchTestSymbol(Assembler::Equal, tag, &isSymbol); 2910 branchTestNull(Assembler::Equal, tag, &isNull); 2911 branchTestUndefined(Assembler::NotEqual, tag, cacheMiss); 2912 } 2913 2914 const JSAtomState& names = runtime()->names(); 2915 movePropertyKey(NameToId(names.undefined), outId); 2916 move32(Imm32(names.undefined->hash()), outHash); 2917 jump(&done); 2918 2919 bind(&isNull); 2920 movePropertyKey(NameToId(names.null), outId); 2921 move32(Imm32(names.null->hash()), outHash); 2922 jump(&done); 2923 2924 bind(&isSymbol); 2925 unboxSymbol(value, outId); 2926 load32(Address(outId, JS::Symbol::offsetOfHash()), outHash); 2927 orPtr(Imm32(PropertyKey::SymbolTypeTag), outId); 2928 jump(&done); 2929 2930 bind(&isString); 2931 unboxString(value, outId); 2932 branchTest32(Assembler::Zero, Address(outId, JSString::offsetOfFlags()), 2933 Imm32(JSString::ATOM_BIT), &nonAtom); 2934 2935 bind(&atom); 2936 loadAtomHash(outId, outHash, &done); 2937 2938 bind(&nonAtom); 2939 tryFastAtomize(outId, outHash, outId, cacheMiss); 2940 jump(&atom); 2941 2942 bind(&done); 2943 } 2944 2945 void MacroAssembler::emitExtractValueFromMegamorphicCacheEntry( 2946 Register obj, Register entry, Register scratch1, Register scratch2, 2947 ValueOperand output, Label* cacheHit, Label* cacheMiss, 2948 Label* cacheHitGetter) { 2949 Label isMissing, dynamicSlot, protoLoopHead, protoLoopTail; 2950 2951 // scratch2 = entry->hopsAndKind_ 2952 load8ZeroExtend( 2953 Address(entry, MegamorphicCache::Entry::offsetOfHopsAndKind()), scratch2); 2954 // if (scratch2 == NumHopsForMissingProperty) goto isMissing 2955 branch32(Assembler::Equal, scratch2, 2956 Imm32(MegamorphicCache::Entry::NumHopsForMissingProperty), 2957 &isMissing); 2958 2959 if (cacheHitGetter) { 2960 // Here we're going to set scratch1 to 0 for a data property and 1 for a 2961 // getter and scratch2 to the number of hops 2962 Label dataProperty; 2963 2964 // if (scratch2 & NonDataPropertyFlag == 0) goto dataProperty 2965 move32(Imm32(0), scratch1); 2966 branchTest32(Assembler::Zero, scratch2, 2967 Imm32(MegamorphicCache::Entry::NonDataPropertyFlag), 2968 &dataProperty); 2969 2970 // if (scratch2 > NonDataPropertyFlag | MaxHopsForAccessorProperty) goto 2971 // cacheMiss 2972 branch32(Assembler::GreaterThan, scratch2, 2973 Imm32(MegamorphicCache::Entry::NonDataPropertyFlag | 2974 MegamorphicCache::Entry::MaxHopsForAccessorProperty), 2975 cacheMiss); 2976 2977 and32(Imm32(~MegamorphicCache::Entry::NonDataPropertyFlag), scratch2); 2978 move32(Imm32(1), scratch1); 2979 2980 bind(&dataProperty); 2981 } else { 2982 // if (scratch2 & NonDataPropertyFlag) goto cacheMiss 2983 branchTest32(Assembler::NonZero, scratch2, 2984 Imm32(MegamorphicCache::Entry::NonDataPropertyFlag), 2985 cacheMiss); 2986 } 2987 2988 // NOTE: Where this is called, `output` can actually alias `obj`, and before 2989 // the last cacheMiss branch above we can't write to `obj`, so we can't 2990 // use `output`'s scratch register there. However a cache miss is impossible 2991 // now, so we're free to use `output` as we like. 2992 Register outputScratch = output.scratchReg(); 2993 if (!outputScratch.aliases(obj)) { 2994 // We're okay with paying this very slight extra cost to avoid a potential 2995 // footgun of writing to what callers understand as only an input register. 2996 movePtr(obj, outputScratch); 2997 } 2998 branchTest32(Assembler::Zero, scratch2, scratch2, &protoLoopTail); 2999 bind(&protoLoopHead); 3000 loadObjProto(outputScratch, outputScratch); 3001 branchSub32(Assembler::NonZero, Imm32(1), scratch2, &protoLoopHead); 3002 bind(&protoLoopTail); 3003 3004 // entry = entry->slotOffset() 3005 load32(Address(entry, MegamorphicCacheEntry::offsetOfSlotOffset()), entry); 3006 3007 // scratch2 = slotOffset.offset() 3008 rshift32(Imm32(TaggedSlotOffset::OffsetShift), entry, scratch2); 3009 3010 // if (!slotOffset.isFixedSlot()) goto dynamicSlot 3011 branchTest32(Assembler::Zero, entry, Imm32(TaggedSlotOffset::IsFixedSlotFlag), 3012 &dynamicSlot); 3013 // output = outputScratch[scratch2] 3014 loadValue(BaseIndex(outputScratch, scratch2, TimesOne), output); 3015 if (cacheHitGetter) { 3016 branchTest32(Assembler::NonZero, scratch1, scratch1, cacheHitGetter); 3017 } 3018 jump(cacheHit); 3019 3020 bind(&dynamicSlot); 3021 // output = outputScratch->slots_[scratch2] 3022 loadPtr(Address(outputScratch, NativeObject::offsetOfSlots()), outputScratch); 3023 loadValue(BaseIndex(outputScratch, scratch2, TimesOne), output); 3024 if (cacheHitGetter) { 3025 branchTest32(Assembler::NonZero, scratch1, scratch1, cacheHitGetter); 3026 } 3027 jump(cacheHit); 3028 3029 bind(&isMissing); 3030 // output = undefined 3031 moveValue(UndefinedValue(), output); 3032 jump(cacheHit); 3033 } 3034 3035 template <typename IdOperandType> 3036 void MacroAssembler::emitMegamorphicCacheLookupByValueCommon( 3037 IdOperandType id, Register obj, Register scratch1, Register scratch2, 3038 Register outEntryPtr, Label* cacheMiss, Label* cacheMissWithEntry) { 3039 // A lot of this code is shared with emitMegamorphicCacheLookup. It would 3040 // be nice to be able to avoid the duplication here, but due to a few 3041 // differences like taking the id in a ValueOperand instead of being able 3042 // to bake it in as an immediate, and only needing a Register for the output 3043 // value, it seemed more awkward to read once it was deduplicated. 3044 3045 // outEntryPtr = obj->shape() 3046 loadPtr(Address(obj, JSObject::offsetOfShape()), outEntryPtr); 3047 3048 movePtr(outEntryPtr, scratch2); 3049 3050 // outEntryPtr = (outEntryPtr >> 3) ^ (outEntryPtr >> 13) + idHash 3051 rshiftPtr(Imm32(MegamorphicCache::ShapeHashShift1), outEntryPtr); 3052 rshiftPtr(Imm32(MegamorphicCache::ShapeHashShift2), scratch2); 3053 xorPtr(scratch2, outEntryPtr); 3054 3055 if constexpr (std::is_same<IdOperandType, ValueOperand>::value) { 3056 loadAtomOrSymbolAndHash(id, scratch1, scratch2, cacheMiss); 3057 } else { 3058 static_assert(std::is_same<IdOperandType, Register>::value); 3059 movePtr(id, scratch1); 3060 loadAtomHash(scratch1, scratch2, nullptr); 3061 } 3062 addPtr(scratch2, outEntryPtr); 3063 3064 // outEntryPtr %= MegamorphicCache::NumEntries 3065 constexpr size_t cacheSize = MegamorphicCache::NumEntries; 3066 static_assert(mozilla::IsPowerOfTwo(cacheSize)); 3067 size_t cacheMask = cacheSize - 1; 3068 and32(Imm32(cacheMask), outEntryPtr); 3069 3070 loadMegamorphicCache(scratch2); 3071 // outEntryPtr = &scratch2->entries_[outEntryPtr] 3072 constexpr size_t entrySize = sizeof(MegamorphicCache::Entry); 3073 static_assert(sizeof(void*) == 4 || entrySize == 24); 3074 if constexpr (sizeof(void*) == 4) { 3075 mul32(Imm32(entrySize), outEntryPtr); 3076 computeEffectiveAddress(BaseIndex(scratch2, outEntryPtr, TimesOne, 3077 MegamorphicCache::offsetOfEntries()), 3078 outEntryPtr); 3079 } else { 3080 computeEffectiveAddress(BaseIndex(outEntryPtr, outEntryPtr, TimesTwo), 3081 outEntryPtr); 3082 computeEffectiveAddress(BaseIndex(scratch2, outEntryPtr, TimesEight, 3083 MegamorphicCache::offsetOfEntries()), 3084 outEntryPtr); 3085 } 3086 3087 // if (outEntryPtr->key_ != scratch1) goto cacheMissWithEntry 3088 branchPtr(Assembler::NotEqual, 3089 Address(outEntryPtr, MegamorphicCache::Entry::offsetOfKey()), 3090 scratch1, cacheMissWithEntry); 3091 loadPtr(Address(obj, JSObject::offsetOfShape()), scratch1); 3092 3093 // if (outEntryPtr->shape_ != scratch1) goto cacheMissWithEntry 3094 branchPtr(Assembler::NotEqual, 3095 Address(outEntryPtr, MegamorphicCache::Entry::offsetOfShape()), 3096 scratch1, cacheMissWithEntry); 3097 3098 // scratch2 = scratch2->generation_ 3099 load16ZeroExtend(Address(scratch2, MegamorphicCache::offsetOfGeneration()), 3100 scratch2); 3101 load16ZeroExtend( 3102 Address(outEntryPtr, MegamorphicCache::Entry::offsetOfGeneration()), 3103 scratch1); 3104 // if (outEntryPtr->generation_ != scratch2) goto cacheMissWithEntry 3105 branch32(Assembler::NotEqual, scratch1, scratch2, cacheMissWithEntry); 3106 } 3107 3108 void MacroAssembler::emitMegamorphicCacheLookup( 3109 PropertyKey id, Register obj, Register scratch1, Register scratch2, 3110 Register outEntryPtr, ValueOperand output, Label* cacheHit, 3111 Label* cacheHitGetter) { 3112 Label cacheMiss, isMissing, dynamicSlot, protoLoopHead, protoLoopTail; 3113 3114 // scratch1 = obj->shape() 3115 loadPtr(Address(obj, JSObject::offsetOfShape()), scratch1); 3116 3117 movePtr(scratch1, outEntryPtr); 3118 movePtr(scratch1, scratch2); 3119 3120 // outEntryPtr = (scratch1 >> 3) ^ (scratch1 >> 13) + hash(id) 3121 rshiftPtr(Imm32(MegamorphicCache::ShapeHashShift1), outEntryPtr); 3122 rshiftPtr(Imm32(MegamorphicCache::ShapeHashShift2), scratch2); 3123 xorPtr(scratch2, outEntryPtr); 3124 addPtr(Imm32(HashPropertyKeyThreadSafe(id)), outEntryPtr); 3125 3126 // outEntryPtr %= MegamorphicCache::NumEntries 3127 constexpr size_t cacheSize = MegamorphicCache::NumEntries; 3128 static_assert(mozilla::IsPowerOfTwo(cacheSize)); 3129 size_t cacheMask = cacheSize - 1; 3130 and32(Imm32(cacheMask), outEntryPtr); 3131 3132 loadMegamorphicCache(scratch2); 3133 // outEntryPtr = &scratch2->entries_[outEntryPtr] 3134 constexpr size_t entrySize = sizeof(MegamorphicCache::Entry); 3135 static_assert(sizeof(void*) == 4 || entrySize == 24); 3136 if constexpr (sizeof(void*) == 4) { 3137 mul32(Imm32(entrySize), outEntryPtr); 3138 computeEffectiveAddress(BaseIndex(scratch2, outEntryPtr, TimesOne, 3139 MegamorphicCache::offsetOfEntries()), 3140 outEntryPtr); 3141 } else { 3142 computeEffectiveAddress(BaseIndex(outEntryPtr, outEntryPtr, TimesTwo), 3143 outEntryPtr); 3144 computeEffectiveAddress(BaseIndex(scratch2, outEntryPtr, TimesEight, 3145 MegamorphicCache::offsetOfEntries()), 3146 outEntryPtr); 3147 } 3148 3149 // if (outEntryPtr->shape_ != scratch1) goto cacheMiss 3150 branchPtr(Assembler::NotEqual, 3151 Address(outEntryPtr, MegamorphicCache::Entry::offsetOfShape()), 3152 scratch1, &cacheMiss); 3153 3154 // if (outEntryPtr->key_ != id) goto cacheMiss 3155 movePropertyKey(id, scratch1); 3156 branchPtr(Assembler::NotEqual, 3157 Address(outEntryPtr, MegamorphicCache::Entry::offsetOfKey()), 3158 scratch1, &cacheMiss); 3159 3160 // scratch2 = scratch2->generation_ 3161 load16ZeroExtend(Address(scratch2, MegamorphicCache::offsetOfGeneration()), 3162 scratch2); 3163 load16ZeroExtend( 3164 Address(outEntryPtr, MegamorphicCache::Entry::offsetOfGeneration()), 3165 scratch1); 3166 // if (outEntryPtr->generation_ != scratch2) goto cacheMiss 3167 branch32(Assembler::NotEqual, scratch1, scratch2, &cacheMiss); 3168 3169 emitExtractValueFromMegamorphicCacheEntry(obj, outEntryPtr, scratch1, 3170 scratch2, output, cacheHit, 3171 &cacheMiss, cacheHitGetter); 3172 3173 bind(&cacheMiss); 3174 } 3175 3176 template <typename IdOperandType> 3177 void MacroAssembler::emitMegamorphicCacheLookupByValue( 3178 IdOperandType id, Register obj, Register scratch1, Register scratch2, 3179 Register outEntryPtr, ValueOperand output, Label* cacheHit, 3180 Label* cacheHitGetter) { 3181 Label cacheMiss, cacheMissWithEntry; 3182 emitMegamorphicCacheLookupByValueCommon(id, obj, scratch1, scratch2, 3183 outEntryPtr, &cacheMiss, 3184 &cacheMissWithEntry); 3185 emitExtractValueFromMegamorphicCacheEntry( 3186 obj, outEntryPtr, scratch1, scratch2, output, cacheHit, 3187 &cacheMissWithEntry, cacheHitGetter); 3188 bind(&cacheMiss); 3189 xorPtr(outEntryPtr, outEntryPtr); 3190 bind(&cacheMissWithEntry); 3191 } 3192 3193 template void MacroAssembler::emitMegamorphicCacheLookupByValue<ValueOperand>( 3194 ValueOperand id, Register obj, Register scratch1, Register scratch2, 3195 Register outEntryPtr, ValueOperand output, Label* cacheHit, 3196 Label* cacheHitGetter); 3197 3198 template void MacroAssembler::emitMegamorphicCacheLookupByValue<Register>( 3199 Register id, Register obj, Register scratch1, Register scratch2, 3200 Register outEntryPtr, ValueOperand output, Label* cacheHit, 3201 Label* cacheHitGetter); 3202 3203 void MacroAssembler::emitMegamorphicCacheLookupExists( 3204 ValueOperand id, Register obj, Register scratch1, Register scratch2, 3205 Register outEntryPtr, Register output, Label* cacheHit, bool hasOwn) { 3206 Label cacheMiss, cacheMissWithEntry, cacheHitFalse; 3207 emitMegamorphicCacheLookupByValueCommon(id, obj, scratch1, scratch2, 3208 outEntryPtr, &cacheMiss, 3209 &cacheMissWithEntry); 3210 3211 // scratch1 = outEntryPtr->hopsAndKind_ 3212 load8ZeroExtend( 3213 Address(outEntryPtr, MegamorphicCache::Entry::offsetOfHopsAndKind()), 3214 scratch1); 3215 3216 branch32(Assembler::Equal, scratch1, 3217 Imm32(MegamorphicCache::Entry::NumHopsForMissingProperty), 3218 &cacheHitFalse); 3219 branchTest32(Assembler::NonZero, scratch1, 3220 Imm32(MegamorphicCache::Entry::NonDataPropertyFlag), 3221 &cacheMissWithEntry); 3222 3223 if (hasOwn) { 3224 branch32(Assembler::NotEqual, scratch1, Imm32(0), &cacheHitFalse); 3225 } 3226 3227 move32(Imm32(1), output); 3228 jump(cacheHit); 3229 3230 bind(&cacheHitFalse); 3231 xor32(output, output); 3232 jump(cacheHit); 3233 3234 bind(&cacheMiss); 3235 xorPtr(outEntryPtr, outEntryPtr); 3236 bind(&cacheMissWithEntry); 3237 } 3238 3239 void MacroAssembler::extractCurrentIndexAndKindFromIterator(Register iterator, 3240 Register outIndex, 3241 Register outKind) { 3242 // Load iterator object 3243 Address nativeIterAddr(iterator, 3244 PropertyIteratorObject::offsetOfIteratorSlot()); 3245 loadPrivate(nativeIterAddr, outIndex); 3246 3247 // Load the property count into outKind. 3248 load32(Address(outIndex, NativeIterator::offsetOfPropertyCount()), outKind); 3249 3250 // We need two bits of wiggle room in a u32 here for the logic below. 3251 static_assert(NativeIterator::PropCountLimit <= 1 << 30); 3252 3253 // Shift up the property count on 64 bit. Ultimately we want 3254 // sizeof(IteratorProperty) * count + sizeof(PropertyIndex) * cursor. 3255 // If we shift up our count to be on the same scale as cursor right now, 3256 // we can do this all with one register. 3257 static_assert(sizeof(IteratorProperty) == sizeof(PropertyIndex) || 3258 sizeof(IteratorProperty) == sizeof(PropertyIndex) * 2); 3259 if constexpr (sizeof(IteratorProperty) > sizeof(PropertyIndex)) { 3260 lshift32(Imm32(1), outKind); 3261 } 3262 3263 // Add the current cursor. This is a uint32_t which has already been 3264 // incremented in iteration to index the *next* property, so we'll want to 3265 // keep that in mind in our final address calculation. 3266 add32(Address(outIndex, NativeIterator::offsetOfPropertyCursor()), outKind); 3267 3268 // outKind holds the offset in u32's to our PropertyIndex, so just multiply 3269 // by four, add it to the offset of the first property, and subtract a 3270 // PropertyIndex since we know we already incremented. 3271 load32(BaseIndex(outIndex, outKind, Scale::TimesFour, 3272 NativeIterator::offsetOfFirstProperty() - 3273 int32_t(sizeof(PropertyIndex))), 3274 outIndex); 3275 3276 // Extract kind. 3277 rshift32(Imm32(PropertyIndex::KindShift), outIndex, outKind); 3278 3279 // Extract index. 3280 and32(Imm32(PropertyIndex::IndexMask), outIndex); 3281 } 3282 3283 void MacroAssembler::extractIndexAndKindFromIteratorByIterIndex( 3284 Register iterator, Register inIndex, Register outKind, Register outIndex) { 3285 // Load iterator object 3286 Address nativeIterAddr(iterator, 3287 PropertyIteratorObject::offsetOfIteratorSlot()); 3288 loadPrivate(nativeIterAddr, outIndex); 3289 3290 // Load the property count into outKind. 3291 load32(Address(outIndex, NativeIterator::offsetOfPropertyCount()), outKind); 3292 3293 // We need two bits of wiggle room in a u32 here for the logic below. 3294 static_assert(NativeIterator::PropCountLimit <= 1 << 30); 3295 3296 // Shift up the property count on 64 bit. Ultimately we want 3297 // sizeof(IteratorProperty) * count + sizeof(PropertyIndex) * cursor. 3298 // If we shift up our count to be on the same scale as cursor right now, 3299 // we can do this all with one register. 3300 static_assert(sizeof(IteratorProperty) == sizeof(PropertyIndex) || 3301 sizeof(IteratorProperty) == sizeof(PropertyIndex) * 2); 3302 if constexpr (sizeof(IteratorProperty) > sizeof(PropertyIndex)) { 3303 lshift32(Imm32(1), outKind); 3304 } 3305 3306 // Add the index 3307 add32(inIndex, outKind); 3308 3309 // outKind holds the offset in u32's to our PropertyIndex, so just multiply 3310 // by four and add it to the offset of the first property 3311 load32(BaseIndex(outIndex, outKind, Scale::TimesFour, 3312 NativeIterator::offsetOfFirstProperty()), 3313 outIndex); 3314 3315 // Extract kind. 3316 rshift32(Imm32(PropertyIndex::KindShift), outIndex, outKind); 3317 3318 // Extract index. 3319 and32(Imm32(PropertyIndex::IndexMask), outIndex); 3320 } 3321 3322 template <typename IdType> 3323 void MacroAssembler::emitMegamorphicCachedSetSlot( 3324 IdType id, Register obj, Register scratch1, 3325 #ifndef JS_CODEGEN_X86 // See MegamorphicSetElement in LIROps.yaml 3326 Register scratch2, Register scratch3, 3327 #endif 3328 ValueOperand value, const LiveRegisterSet& liveRegs, Label* cacheHit, 3329 void (*emitPreBarrier)(MacroAssembler&, const Address&, MIRType)) { 3330 Label cacheMiss, dynamicSlot, doAdd, doSet, doAddDynamic, doSetDynamic; 3331 3332 #ifdef JS_CODEGEN_X86 3333 pushValue(value); 3334 Register scratch2 = value.typeReg(); 3335 Register scratch3 = value.payloadReg(); 3336 #endif 3337 3338 // outEntryPtr = obj->shape() 3339 loadPtr(Address(obj, JSObject::offsetOfShape()), scratch3); 3340 3341 movePtr(scratch3, scratch2); 3342 3343 // scratch3 = (scratch3 >> 3) ^ (scratch3 >> 13) + idHash 3344 rshiftPtr(Imm32(MegamorphicSetPropCache::ShapeHashShift1), scratch3); 3345 rshiftPtr(Imm32(MegamorphicSetPropCache::ShapeHashShift2), scratch2); 3346 xorPtr(scratch2, scratch3); 3347 3348 if constexpr (std::is_same<IdType, ValueOperand>::value) { 3349 loadAtomOrSymbolAndHash(id, scratch1, scratch2, &cacheMiss); 3350 addPtr(scratch2, scratch3); 3351 } else { 3352 static_assert(std::is_same<IdType, PropertyKey>::value); 3353 addPtr(Imm32(HashPropertyKeyThreadSafe(id)), scratch3); 3354 movePropertyKey(id, scratch1); 3355 } 3356 3357 // scratch3 %= MegamorphicSetPropCache::NumEntries 3358 constexpr size_t cacheSize = MegamorphicSetPropCache::NumEntries; 3359 static_assert(mozilla::IsPowerOfTwo(cacheSize)); 3360 size_t cacheMask = cacheSize - 1; 3361 and32(Imm32(cacheMask), scratch3); 3362 3363 loadMegamorphicSetPropCache(scratch2); 3364 // scratch3 = &scratch2->entries_[scratch3] 3365 constexpr size_t entrySize = sizeof(MegamorphicSetPropCache::Entry); 3366 mul32(Imm32(entrySize), scratch3); 3367 computeEffectiveAddress(BaseIndex(scratch2, scratch3, TimesOne, 3368 MegamorphicSetPropCache::offsetOfEntries()), 3369 scratch3); 3370 3371 // if (scratch3->key_ != scratch1) goto cacheMiss 3372 branchPtr(Assembler::NotEqual, 3373 Address(scratch3, MegamorphicSetPropCache::Entry::offsetOfKey()), 3374 scratch1, &cacheMiss); 3375 3376 loadPtr(Address(obj, JSObject::offsetOfShape()), scratch1); 3377 // if (scratch3->shape_ != scratch1) goto cacheMiss 3378 branchPtr(Assembler::NotEqual, 3379 Address(scratch3, MegamorphicSetPropCache::Entry::offsetOfShape()), 3380 scratch1, &cacheMiss); 3381 3382 // scratch2 = scratch2->generation_ 3383 load16ZeroExtend( 3384 Address(scratch2, MegamorphicSetPropCache::offsetOfGeneration()), 3385 scratch2); 3386 load16ZeroExtend( 3387 Address(scratch3, MegamorphicSetPropCache::Entry::offsetOfGeneration()), 3388 scratch1); 3389 // if (scratch3->generation_ != scratch2) goto cacheMiss 3390 branch32(Assembler::NotEqual, scratch1, scratch2, &cacheMiss); 3391 3392 // scratch2 = entry->slotOffset() 3393 load32( 3394 Address(scratch3, MegamorphicSetPropCache::Entry::offsetOfSlotOffset()), 3395 scratch2); 3396 3397 // scratch1 = slotOffset.offset() 3398 rshift32(Imm32(TaggedSlotOffset::OffsetShift), scratch2, scratch1); 3399 3400 Address afterShapePtr(scratch3, 3401 MegamorphicSetPropCache::Entry::offsetOfAfterShape()); 3402 3403 // if (!slotOffset.isFixedSlot()) goto dynamicSlot 3404 branchTest32(Assembler::Zero, scratch2, 3405 Imm32(TaggedSlotOffset::IsFixedSlotFlag), &dynamicSlot); 3406 3407 // Calculate slot address in scratch1. Jump to doSet if scratch3 == nullptr, 3408 // else jump (or fall-through) to doAdd. 3409 addPtr(obj, scratch1); 3410 branchPtr(Assembler::Equal, afterShapePtr, ImmPtr(nullptr), &doSet); 3411 jump(&doAdd); 3412 3413 bind(&dynamicSlot); 3414 branchPtr(Assembler::Equal, afterShapePtr, ImmPtr(nullptr), &doSetDynamic); 3415 3416 Address slotAddr(scratch1, 0); 3417 3418 // If entry->newCapacity_ is nonzero, we need to grow the slots on the 3419 // object. Otherwise just jump straight to a dynamic add. 3420 load16ZeroExtend( 3421 Address(scratch3, MegamorphicSetPropCache::Entry::offsetOfNewCapacity()), 3422 scratch2); 3423 branchTest32(Assembler::Zero, scratch2, scratch2, &doAddDynamic); 3424 3425 LiveRegisterSet save; 3426 save.set() = RegisterSet::Intersect(liveRegs.set(), RegisterSet::Volatile()); 3427 save.addUnchecked(scratch1); // Used as call temp below. 3428 save.takeUnchecked(scratch2); // Used for the return value. 3429 PushRegsInMask(save); 3430 3431 using Fn = bool (*)(JSContext* cx, NativeObject* obj, uint32_t newCount); 3432 setupUnalignedABICall(scratch1); 3433 loadJSContext(scratch1); 3434 passABIArg(scratch1); 3435 passABIArg(obj); 3436 passABIArg(scratch2); 3437 callWithABI<Fn, NativeObject::growSlotsPure>(); 3438 storeCallPointerResult(scratch2); 3439 3440 MOZ_ASSERT(!save.has(scratch2)); 3441 PopRegsInMask(save); 3442 3443 branchIfFalseBool(scratch2, &cacheMiss); 3444 3445 bind(&doAddDynamic); 3446 addPtr(Address(obj, NativeObject::offsetOfSlots()), scratch1); 3447 3448 bind(&doAdd); 3449 // scratch3 = entry->afterShape() 3450 loadPtr( 3451 Address(scratch3, MegamorphicSetPropCache::Entry::offsetOfAfterShape()), 3452 scratch3); 3453 3454 storeObjShape(scratch3, obj, 3455 [emitPreBarrier](MacroAssembler& masm, const Address& addr) { 3456 emitPreBarrier(masm, addr, MIRType::Shape); 3457 }); 3458 #ifdef JS_CODEGEN_X86 3459 popValue(value); 3460 #endif 3461 storeValue(value, slotAddr); 3462 jump(cacheHit); 3463 3464 bind(&doSetDynamic); 3465 addPtr(Address(obj, NativeObject::offsetOfSlots()), scratch1); 3466 bind(&doSet); 3467 guardedCallPreBarrier(slotAddr, MIRType::Value); 3468 3469 #ifdef JS_CODEGEN_X86 3470 popValue(value); 3471 #endif 3472 storeValue(value, slotAddr); 3473 jump(cacheHit); 3474 3475 bind(&cacheMiss); 3476 #ifdef JS_CODEGEN_X86 3477 popValue(value); 3478 #endif 3479 } 3480 3481 template void MacroAssembler::emitMegamorphicCachedSetSlot<PropertyKey>( 3482 PropertyKey id, Register obj, Register scratch1, 3483 #ifndef JS_CODEGEN_X86 // See MegamorphicSetElement in LIROps.yaml 3484 Register scratch2, Register scratch3, 3485 #endif 3486 ValueOperand value, const LiveRegisterSet& liveRegs, Label* cacheHit, 3487 void (*emitPreBarrier)(MacroAssembler&, const Address&, MIRType)); 3488 3489 template void MacroAssembler::emitMegamorphicCachedSetSlot<ValueOperand>( 3490 ValueOperand id, Register obj, Register scratch1, 3491 #ifndef JS_CODEGEN_X86 // See MegamorphicSetElement in LIROps.yaml 3492 Register scratch2, Register scratch3, 3493 #endif 3494 ValueOperand value, const LiveRegisterSet& liveRegs, Label* cacheHit, 3495 void (*emitPreBarrier)(MacroAssembler&, const Address&, MIRType)); 3496 3497 void MacroAssembler::guardNonNegativeIntPtrToInt32(Register reg, Label* fail) { 3498 #ifdef DEBUG 3499 Label ok; 3500 branchTestPtr(Assembler::NotSigned, reg, reg, &ok); 3501 assumeUnreachable("Unexpected negative value"); 3502 bind(&ok); 3503 #endif 3504 3505 #ifdef JS_64BIT 3506 branchPtr(Assembler::Above, reg, Imm32(INT32_MAX), fail); 3507 #endif 3508 } 3509 3510 void MacroAssembler::loadArrayBufferByteLengthIntPtr(Register obj, 3511 Register output) { 3512 Address slotAddr(obj, ArrayBufferObject::offsetOfByteLengthSlot()); 3513 loadPrivate(slotAddr, output); 3514 } 3515 3516 void MacroAssembler::loadArrayBufferViewByteOffsetIntPtr(Register obj, 3517 Register output) { 3518 Address slotAddr(obj, ArrayBufferViewObject::byteOffsetOffset()); 3519 loadPrivate(slotAddr, output); 3520 } 3521 3522 void MacroAssembler::loadArrayBufferViewLengthIntPtr(Register obj, 3523 Register output) { 3524 Address slotAddr(obj, ArrayBufferViewObject::lengthOffset()); 3525 loadPrivate(slotAddr, output); 3526 } 3527 3528 void MacroAssembler::loadGrowableSharedArrayBufferByteLengthIntPtr( 3529 Synchronization sync, Register obj, Register output) { 3530 // Load the SharedArrayRawBuffer. 3531 loadPrivate(Address(obj, SharedArrayBufferObject::rawBufferOffset()), output); 3532 3533 memoryBarrierBefore(sync); 3534 3535 // Load the byteLength of the SharedArrayRawBuffer into |output|. 3536 static_assert(sizeof(mozilla::Atomic<size_t>) == sizeof(size_t)); 3537 loadPtr(Address(output, SharedArrayRawBuffer::offsetOfByteLength()), output); 3538 3539 memoryBarrierAfter(sync); 3540 } 3541 3542 void MacroAssembler::loadResizableArrayBufferViewLengthIntPtr( 3543 ResizableArrayBufferView view, Synchronization sync, Register obj, 3544 Register output, Register scratch) { 3545 // Inline implementation of ArrayBufferViewObject::length(), when the input is 3546 // guaranteed to be a resizable arraybuffer view object. 3547 3548 loadArrayBufferViewLengthIntPtr(obj, output); 3549 3550 Label done; 3551 branchPtr(Assembler::NotEqual, output, ImmWord(0), &done); 3552 3553 // Load obj->elements in |scratch|. 3554 loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch); 3555 3556 // If backed by non-shared memory, detached and out-of-bounds both return 3557 // zero, so we're done here. 3558 branchTest32(Assembler::Zero, 3559 Address(scratch, ObjectElements::offsetOfFlags()), 3560 Imm32(ObjectElements::SHARED_MEMORY), &done); 3561 3562 // Load the auto-length slot. 3563 unboxBoolean(Address(obj, ArrayBufferViewObject::autoLengthOffset()), 3564 scratch); 3565 3566 // If non-auto length, there's nothing to do. 3567 branchTest32(Assembler::Zero, scratch, scratch, &done); 3568 3569 // Load bufferByteLength into |output|. 3570 { 3571 // Resizable TypedArrays are guaranteed to have an ArrayBuffer. 3572 unboxObject(Address(obj, ArrayBufferViewObject::bufferOffset()), output); 3573 3574 // Load the byte length from the raw-buffer of growable SharedArrayBuffers. 3575 loadGrowableSharedArrayBufferByteLengthIntPtr(sync, output, output); 3576 } 3577 3578 // Load the byteOffset into |scratch|. 3579 loadArrayBufferViewByteOffsetIntPtr(obj, scratch); 3580 3581 // Compute the accessible byte length |bufferByteLength - byteOffset|. 3582 subPtr(scratch, output); 3583 3584 if (view == ResizableArrayBufferView::TypedArray) { 3585 // Compute the array length from the byte length. 3586 resizableTypedArrayElementShiftBy(obj, output, scratch); 3587 } 3588 3589 bind(&done); 3590 } 3591 3592 void MacroAssembler::dateFillLocalTimeSlots( 3593 Register obj, Register scratch, const LiveRegisterSet& volatileRegs) { 3594 // Inline implementation of the cache check from 3595 // DateObject::fillLocalTimeSlots(). Only used when no realm time zone 3596 // override is active. 3597 3598 Label callVM, done; 3599 3600 // Check if the cache is already populated. 3601 branchTestUndefined(Assembler::Equal, 3602 Address(obj, DateObject::offsetOfLocalTimeSlot()), 3603 &callVM); 3604 3605 unboxInt32(Address(obj, DateObject::offsetOfTimeZoneCacheKeySlot()), scratch); 3606 3607 branch32(Assembler::Equal, 3608 AbsoluteAddress(DateTimeInfo::addressOfUTCToLocalOffsetSeconds()), 3609 scratch, &done); 3610 3611 bind(&callVM); 3612 { 3613 PushRegsInMask(volatileRegs); 3614 3615 using Fn = void (*)(DateObject*); 3616 setupUnalignedABICall(scratch); 3617 passABIArg(obj); 3618 callWithABI<Fn, jit::DateFillLocalTimeSlots>(); 3619 3620 PopRegsInMask(volatileRegs); 3621 } 3622 3623 bind(&done); 3624 } 3625 3626 void MacroAssembler::udiv32ByConstant(Register src, uint32_t divisor, 3627 Register dest) { 3628 auto rmc = ReciprocalMulConstants::computeUnsignedDivisionConstants(divisor); 3629 MOZ_ASSERT(rmc.multiplier <= UINT32_MAX, "division needs scratch register"); 3630 3631 // We first compute |q = (M * n) >> 32), where M = rmc.multiplier. 3632 mulHighUnsigned32(Imm32(rmc.multiplier), src, dest); 3633 3634 // Finish the computation |q = floor(n / d)|. 3635 rshift32(Imm32(rmc.shiftAmount), dest); 3636 } 3637 3638 void MacroAssembler::umod32ByConstant(Register src, uint32_t divisor, 3639 Register dest, Register scratch) { 3640 MOZ_ASSERT(dest != scratch); 3641 3642 auto rmc = ReciprocalMulConstants::computeUnsignedDivisionConstants(divisor); 3643 MOZ_ASSERT(rmc.multiplier <= UINT32_MAX, "division needs scratch register"); 3644 3645 if (src != dest) { 3646 move32(src, dest); 3647 } 3648 3649 // We first compute |q = (M * n) >> 32), where M = rmc.multiplier. 3650 mulHighUnsigned32(Imm32(rmc.multiplier), dest, scratch); 3651 3652 // Finish the computation |q = floor(n / d)|. 3653 rshift32(Imm32(rmc.shiftAmount), scratch); 3654 3655 // Compute the remainder from |r = n - q * d|. 3656 mul32(Imm32(divisor), scratch); 3657 sub32(scratch, dest); 3658 } 3659 3660 template <typename GetTimeFn> 3661 void MacroAssembler::dateTimeFromSecondsIntoYear(ValueOperand secondsIntoYear, 3662 ValueOperand output, 3663 Register scratch1, 3664 Register scratch2, 3665 GetTimeFn getTimeFn) { 3666 #ifdef DEBUG 3667 Label okValue; 3668 branchTestInt32(Assembler::Equal, secondsIntoYear, &okValue); 3669 branchTestNaNValue(Assembler::Equal, secondsIntoYear, scratch1, &okValue); 3670 assumeUnreachable("secondsIntoYear is an int32 or NaN"); 3671 bind(&okValue); 3672 #endif 3673 3674 moveValue(secondsIntoYear, output); 3675 3676 Label done; 3677 fallibleUnboxInt32(secondsIntoYear, scratch1, &done); 3678 3679 #ifdef DEBUG 3680 Label okInt; 3681 branchTest32(Assembler::NotSigned, scratch1, scratch1, &okInt); 3682 assumeUnreachable("secondsIntoYear is an unsigned int32"); 3683 bind(&okInt); 3684 #endif 3685 3686 getTimeFn(scratch1, scratch1, scratch2); 3687 3688 tagValue(JSVAL_TYPE_INT32, scratch1, output); 3689 3690 bind(&done); 3691 } 3692 3693 void MacroAssembler::dateHoursFromSecondsIntoYear(ValueOperand secondsIntoYear, 3694 ValueOperand output, 3695 Register scratch1, 3696 Register scratch2) { 3697 // Inline implementation of seconds-into-year to local hours computation from 3698 // date_getHours. 3699 3700 // Compute `(yearSeconds / SecondsPerHour) % HoursPerDay`. 3701 auto hoursFromSecondsIntoYear = [this](Register src, Register dest, 3702 Register scratch) { 3703 udiv32ByConstant(src, SecondsPerHour, dest); 3704 umod32ByConstant(dest, HoursPerDay, dest, scratch); 3705 }; 3706 3707 dateTimeFromSecondsIntoYear(secondsIntoYear, output, scratch1, scratch2, 3708 hoursFromSecondsIntoYear); 3709 } 3710 3711 void MacroAssembler::dateMinutesFromSecondsIntoYear( 3712 ValueOperand secondsIntoYear, ValueOperand output, Register scratch1, 3713 Register scratch2) { 3714 // Inline implementation of seconds-into-year to local minutes computation 3715 // from date_getMinutes. 3716 3717 // Compute `(yearSeconds / SecondsPerMinute) % MinutesPerHour`. 3718 auto minutesFromSecondsIntoYear = [this](Register src, Register dest, 3719 Register scratch) { 3720 udiv32ByConstant(src, SecondsPerMinute, dest); 3721 umod32ByConstant(dest, MinutesPerHour, dest, scratch); 3722 }; 3723 3724 dateTimeFromSecondsIntoYear(secondsIntoYear, output, scratch1, scratch2, 3725 minutesFromSecondsIntoYear); 3726 } 3727 3728 void MacroAssembler::dateSecondsFromSecondsIntoYear( 3729 ValueOperand secondsIntoYear, ValueOperand output, Register scratch1, 3730 Register scratch2) { 3731 // Inline implementation of seconds-into-year to local seconds computation 3732 // from date_getSeconds. 3733 3734 // Compute `yearSeconds % SecondsPerMinute`. 3735 auto secondsFromSecondsIntoYear = [this](Register src, Register dest, 3736 Register scratch) { 3737 umod32ByConstant(src, SecondsPerMinute, dest, scratch); 3738 }; 3739 3740 dateTimeFromSecondsIntoYear(secondsIntoYear, output, scratch1, scratch2, 3741 secondsFromSecondsIntoYear); 3742 } 3743 3744 void MacroAssembler::computeImplicitThis(Register env, ValueOperand output, 3745 Label* slowPath) { 3746 // Inline implementation of ComputeImplicitThis. 3747 3748 Register scratch = output.scratchReg(); 3749 MOZ_ASSERT(scratch != env); 3750 3751 loadObjClassUnsafe(env, scratch); 3752 3753 // Go to the slow path for possible debug environment proxies. 3754 branchTestClassIsProxy(true, scratch, slowPath); 3755 3756 // WithEnvironmentObjects have an actual implicit |this|. 3757 Label nonWithEnv, done; 3758 branchPtr(Assembler::NotEqual, scratch, 3759 ImmPtr(&WithEnvironmentObject::class_), &nonWithEnv); 3760 { 3761 if (JitOptions.spectreObjectMitigations) { 3762 spectreZeroRegister(Assembler::NotEqual, scratch, env); 3763 } 3764 3765 loadValue(Address(env, WithEnvironmentObject::offsetOfThisSlot()), output); 3766 3767 jump(&done); 3768 } 3769 bind(&nonWithEnv); 3770 3771 // The implicit |this| is |undefined| for all environment types except 3772 // WithEnvironmentObject. 3773 moveValue(JS::UndefinedValue(), output); 3774 3775 bind(&done); 3776 } 3777 3778 void MacroAssembler::loadDOMExpandoValueGuardGeneration( 3779 Register obj, ValueOperand output, 3780 JS::ExpandoAndGeneration* expandoAndGeneration, uint64_t generation, 3781 Label* fail) { 3782 loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), 3783 output.scratchReg()); 3784 loadValue(Address(output.scratchReg(), 3785 js::detail::ProxyReservedSlots::offsetOfPrivateSlot()), 3786 output); 3787 3788 // Guard the ExpandoAndGeneration* matches the proxy's ExpandoAndGeneration 3789 // privateSlot. 3790 branchTestValue(Assembler::NotEqual, output, 3791 PrivateValue(expandoAndGeneration), fail); 3792 3793 // Guard expandoAndGeneration->generation matches the expected generation. 3794 Address generationAddr(output.payloadOrValueReg(), 3795 JS::ExpandoAndGeneration::offsetOfGeneration()); 3796 branch64(Assembler::NotEqual, generationAddr, Imm64(generation), fail); 3797 3798 // Load expandoAndGeneration->expando into the output Value register. 3799 loadValue(Address(output.payloadOrValueReg(), 3800 JS::ExpandoAndGeneration::offsetOfExpando()), 3801 output); 3802 } 3803 3804 void MacroAssembler::loadJitActivation(Register dest) { 3805 loadJSContext(dest); 3806 loadPtr(Address(dest, offsetof(JSContext, activation_)), dest); 3807 } 3808 3809 void MacroAssembler::loadBaselineCompileQueue(Register dest) { 3810 loadPtr(AbsoluteAddress(ContextRealmPtr(runtime())), dest); 3811 computeEffectiveAddress(Address(dest, Realm::offsetOfBaselineCompileQueue()), 3812 dest); 3813 } 3814 3815 void MacroAssembler::guardSpecificAtom(Register str, JSOffThreadAtom* atom, 3816 Register scratch, 3817 const LiveRegisterSet& volatileRegs, 3818 Label* fail) { 3819 Label done, notCachedAtom; 3820 branchPtr(Assembler::Equal, str, ImmGCPtr(atom), &done); 3821 3822 // The pointers are not equal, so if the input string is also an atom it 3823 // must be a different string. 3824 branchTest32(Assembler::NonZero, Address(str, JSString::offsetOfFlags()), 3825 Imm32(JSString::ATOM_BIT), fail); 3826 3827 // Try to do a cheap atomize on the string and repeat the above test 3828 tryFastAtomize(str, scratch, scratch, ¬CachedAtom); 3829 branchPtr(Assembler::Equal, scratch, ImmGCPtr(atom), &done); 3830 jump(fail); 3831 bind(¬CachedAtom); 3832 3833 // Check the length. 3834 branch32(Assembler::NotEqual, Address(str, JSString::offsetOfLength()), 3835 Imm32(atom->length()), fail); 3836 3837 // Compare short atoms using inline assembly. 3838 if (canCompareStringCharsInline(atom)) { 3839 // Pure two-byte strings can't be equal to Latin-1 strings. 3840 if (atom->hasTwoByteChars()) { 3841 JS::AutoCheckCannotGC nogc; 3842 if (!mozilla::IsUtf16Latin1(atom->twoByteRange(nogc))) { 3843 branchLatin1String(str, fail); 3844 } 3845 } 3846 3847 // Call into the VM when the input is a rope or has a different encoding. 3848 Label vmCall; 3849 3850 // Load the input string's characters. 3851 Register stringChars = scratch; 3852 loadStringCharsForCompare(str, atom, stringChars, &vmCall); 3853 3854 // Start comparing character by character. 3855 branchIfNotStringCharsEquals(stringChars, atom, fail); 3856 3857 // Falls through if both strings are equal. 3858 jump(&done); 3859 3860 bind(&vmCall); 3861 } 3862 3863 // We have a non-atomized string with the same length. Call a helper 3864 // function to do the comparison. 3865 PushRegsInMask(volatileRegs); 3866 3867 using Fn = bool (*)(JSString* str1, JSString* str2); 3868 setupUnalignedABICall(scratch); 3869 movePtr(ImmGCPtr(atom), scratch); 3870 passABIArg(scratch); 3871 passABIArg(str); 3872 callWithABI<Fn, EqualStringsHelperPure>(); 3873 storeCallPointerResult(scratch); 3874 3875 MOZ_ASSERT(!volatileRegs.has(scratch)); 3876 PopRegsInMask(volatileRegs); 3877 branchIfFalseBool(scratch, fail); 3878 3879 bind(&done); 3880 } 3881 3882 void MacroAssembler::guardStringToInt32(Register str, Register output, 3883 Register scratch, 3884 LiveRegisterSet volatileRegs, 3885 Label* fail) { 3886 Label vmCall, done; 3887 // Use indexed value as fast path if possible. 3888 loadStringIndexValue(str, output, &vmCall); 3889 jump(&done); 3890 { 3891 bind(&vmCall); 3892 3893 // Reserve space for holding the result int32_t of the call. Use 3894 // pointer-size to avoid misaligning the stack on 64-bit platforms. 3895 reserveStack(sizeof(uintptr_t)); 3896 moveStackPtrTo(output); 3897 3898 volatileRegs.takeUnchecked(scratch); 3899 if (output.volatile_()) { 3900 volatileRegs.addUnchecked(output); 3901 } 3902 PushRegsInMask(volatileRegs); 3903 3904 using Fn = bool (*)(JSContext* cx, JSString* str, int32_t* result); 3905 setupUnalignedABICall(scratch); 3906 loadJSContext(scratch); 3907 passABIArg(scratch); 3908 passABIArg(str); 3909 passABIArg(output); 3910 callWithABI<Fn, GetInt32FromStringPure>(); 3911 storeCallPointerResult(scratch); 3912 3913 PopRegsInMask(volatileRegs); 3914 3915 Label ok; 3916 branchIfTrueBool(scratch, &ok); 3917 { 3918 // OOM path, recovered by GetInt32FromStringPure. 3919 // 3920 // Use addToStackPtr instead of freeStack as freeStack tracks stack height 3921 // flow-insensitively, and using it twice would confuse the stack height 3922 // tracking. 3923 addToStackPtr(Imm32(sizeof(uintptr_t))); 3924 jump(fail); 3925 } 3926 bind(&ok); 3927 load32(Address(output, 0), output); 3928 freeStack(sizeof(uintptr_t)); 3929 } 3930 bind(&done); 3931 } 3932 3933 void MacroAssembler::generateBailoutTail(Register scratch, 3934 Register bailoutInfo) { 3935 Label bailoutFailed; 3936 branchIfFalseBool(ReturnReg, &bailoutFailed); 3937 3938 // Finish bailing out to Baseline. 3939 { 3940 // Prepare a register set for use in this case. 3941 AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All()); 3942 MOZ_ASSERT_IF(!IsHiddenSP(getStackPointer()), 3943 !regs.has(AsRegister(getStackPointer()))); 3944 regs.take(bailoutInfo); 3945 3946 Register temp = regs.takeAny(); 3947 3948 #ifdef DEBUG 3949 // Assert the stack pointer points to the JitFrameLayout header. Copying 3950 // starts here. 3951 Label ok; 3952 loadPtr(Address(bailoutInfo, offsetof(BaselineBailoutInfo, incomingStack)), 3953 temp); 3954 branchStackPtr(Assembler::Equal, temp, &ok); 3955 assumeUnreachable("Unexpected stack pointer value"); 3956 bind(&ok); 3957 #endif 3958 3959 Register copyCur = regs.takeAny(); 3960 Register copyEnd = regs.takeAny(); 3961 3962 // Copy data onto stack. 3963 loadPtr(Address(bailoutInfo, offsetof(BaselineBailoutInfo, copyStackTop)), 3964 copyCur); 3965 loadPtr( 3966 Address(bailoutInfo, offsetof(BaselineBailoutInfo, copyStackBottom)), 3967 copyEnd); 3968 { 3969 Label copyLoop; 3970 Label endOfCopy; 3971 bind(©Loop); 3972 branchPtr(Assembler::BelowOrEqual, copyCur, copyEnd, &endOfCopy); 3973 subPtr(Imm32(sizeof(uintptr_t)), copyCur); 3974 subFromStackPtr(Imm32(sizeof(uintptr_t))); 3975 loadPtr(Address(copyCur, 0), temp); 3976 storePtr(temp, Address(getStackPointer(), 0)); 3977 jump(©Loop); 3978 bind(&endOfCopy); 3979 } 3980 3981 loadPtr(Address(bailoutInfo, offsetof(BaselineBailoutInfo, resumeFramePtr)), 3982 FramePointer); 3983 3984 // Enter exit frame for the FinishBailoutToBaseline call. 3985 push(FrameDescriptor(FrameType::BaselineJS)); 3986 push(Address(bailoutInfo, offsetof(BaselineBailoutInfo, resumeAddr))); 3987 push(FramePointer); 3988 // No GC things to mark on the stack, push a bare token. 3989 loadJSContext(scratch); 3990 enterFakeExitFrame(scratch, scratch, ExitFrameType::Bare); 3991 3992 // Save needed values onto stack temporarily. 3993 push(Address(bailoutInfo, offsetof(BaselineBailoutInfo, resumeAddr))); 3994 3995 // Call a stub to free allocated memory and create arguments objects. 3996 using Fn = bool (*)(BaselineBailoutInfo* bailoutInfoArg); 3997 setupUnalignedABICall(temp); 3998 passABIArg(bailoutInfo); 3999 callWithABI<Fn, FinishBailoutToBaseline>( 4000 ABIType::General, CheckUnsafeCallWithABI::DontCheckHasExitFrame); 4001 branchIfFalseBool(ReturnReg, exceptionLabel()); 4002 4003 // Restore values where they need to be and resume execution. 4004 AllocatableGeneralRegisterSet enterRegs(GeneralRegisterSet::All()); 4005 MOZ_ASSERT(!enterRegs.has(FramePointer)); 4006 Register jitcodeReg = enterRegs.takeAny(); 4007 4008 pop(jitcodeReg); 4009 4010 // Discard exit frame. 4011 addToStackPtr(Imm32(ExitFrameLayout::SizeWithFooter())); 4012 4013 jump(jitcodeReg); 4014 } 4015 4016 bind(&bailoutFailed); 4017 { 4018 // jit::Bailout or jit::InvalidationBailout failed and returned false. The 4019 // Ion frame has already been discarded and the stack pointer points to the 4020 // JitFrameLayout header. Turn it into an ExitFrameLayout, similar to 4021 // EnsureUnwoundJitExitFrame, and call the exception handler. 4022 loadJSContext(scratch); 4023 enterFakeExitFrame(scratch, scratch, ExitFrameType::UnwoundJit); 4024 jump(exceptionLabel()); 4025 } 4026 } 4027 4028 void MacroAssembler::loadJitCodeRaw(Register func, Register dest) { 4029 static_assert(BaseScript::offsetOfJitCodeRaw() == 4030 SelfHostedLazyScript::offsetOfJitCodeRaw(), 4031 "SelfHostedLazyScript and BaseScript must use same layout for " 4032 "jitCodeRaw_"); 4033 static_assert( 4034 BaseScript::offsetOfJitCodeRaw() == wasm::JumpTableJitEntryOffset, 4035 "Wasm exported functions jit entries must use same layout for " 4036 "jitCodeRaw_"); 4037 loadPrivate(Address(func, JSFunction::offsetOfJitInfoOrScript()), dest); 4038 loadPtr(Address(dest, BaseScript::offsetOfJitCodeRaw()), dest); 4039 } 4040 4041 void MacroAssembler::loadJitCodeRawNoIon(Register func, Register dest, 4042 Register scratch) { 4043 // This is used when calling a trial-inlined script using a private 4044 // ICScript to collect callsite-specific CacheIR. Ion doesn't use 4045 // the baseline ICScript, so we want to enter at the highest 4046 // available non-Ion tier. 4047 4048 Label useJitCodeRaw, done; 4049 loadPrivate(Address(func, JSFunction::offsetOfJitInfoOrScript()), dest); 4050 branchIfScriptHasNoJitScript(dest, &useJitCodeRaw); 4051 loadJitScript(dest, scratch); 4052 4053 // If we have an IonScript, jitCodeRaw_ will point to it, so we have 4054 // to load the baseline entry out of the BaselineScript. 4055 branchPtr(Assembler::BelowOrEqual, 4056 Address(scratch, JitScript::offsetOfIonScript()), 4057 ImmPtr(IonCompilingScriptPtr), &useJitCodeRaw); 4058 loadPtr(Address(scratch, JitScript::offsetOfBaselineScript()), scratch); 4059 4060 #ifdef DEBUG 4061 // If we have an IonScript, we must also have a BaselineScript. 4062 Label hasBaselineScript; 4063 branchPtr(Assembler::Above, scratch, ImmPtr(BaselineCompilingScriptPtr), 4064 &hasBaselineScript); 4065 assumeUnreachable("JitScript has IonScript without BaselineScript"); 4066 bind(&hasBaselineScript); 4067 #endif 4068 4069 loadPtr(Address(scratch, BaselineScript::offsetOfMethod()), scratch); 4070 loadPtr(Address(scratch, JitCode::offsetOfCode()), dest); 4071 jump(&done); 4072 4073 // If there's no IonScript, we can just use jitCodeRaw_. 4074 bind(&useJitCodeRaw); 4075 loadPtr(Address(dest, BaseScript::offsetOfJitCodeRaw()), dest); 4076 bind(&done); 4077 } 4078 4079 void MacroAssembler::loadBaselineFramePtr(Register framePtr, Register dest) { 4080 if (framePtr != dest) { 4081 movePtr(framePtr, dest); 4082 } 4083 subPtr(Imm32(BaselineFrame::Size()), dest); 4084 } 4085 4086 void MacroAssembler::handleFailure() { 4087 // Re-entry code is irrelevant because the exception will leave the 4088 // running function and never come back 4089 TrampolinePtr excTail = runtime()->jitRuntime()->getExceptionTail(); 4090 jump(excTail); 4091 } 4092 4093 void MacroAssembler::assumeUnreachable(const char* output) { 4094 #ifdef JS_MASM_VERBOSE 4095 if (!IsCompilingWasm()) { 4096 AllocatableRegisterSet regs(RegisterSet::Volatile()); 4097 LiveRegisterSet save(regs.asLiveSet()); 4098 PushRegsInMask(save); 4099 Register temp = regs.takeAnyGeneral(); 4100 4101 using Fn = void (*)(const char* output); 4102 setupUnalignedABICall(temp); 4103 movePtr(ImmPtr(output), temp); 4104 passABIArg(temp); 4105 callWithABI<Fn, AssumeUnreachable>(ABIType::General, 4106 CheckUnsafeCallWithABI::DontCheckOther); 4107 4108 PopRegsInMask(save); 4109 } 4110 #endif 4111 4112 breakpoint(); 4113 } 4114 4115 void MacroAssembler::printf(const char* output) { 4116 #ifdef JS_MASM_VERBOSE 4117 AllocatableRegisterSet regs(RegisterSet::Volatile()); 4118 LiveRegisterSet save(regs.asLiveSet()); 4119 PushRegsInMask(save); 4120 4121 Register temp = regs.takeAnyGeneral(); 4122 4123 using Fn = void (*)(const char* output); 4124 setupUnalignedABICall(temp); 4125 movePtr(ImmPtr(output), temp); 4126 passABIArg(temp); 4127 callWithABI<Fn, Printf0>(); 4128 4129 PopRegsInMask(save); 4130 #endif 4131 } 4132 4133 void MacroAssembler::printf(const char* output, Register value) { 4134 #ifdef JS_MASM_VERBOSE 4135 AllocatableRegisterSet regs(RegisterSet::Volatile()); 4136 LiveRegisterSet save(regs.asLiveSet()); 4137 PushRegsInMask(save); 4138 4139 regs.takeUnchecked(value); 4140 4141 Register temp = regs.takeAnyGeneral(); 4142 4143 using Fn = void (*)(const char* output, uintptr_t value); 4144 setupUnalignedABICall(temp); 4145 movePtr(ImmPtr(output), temp); 4146 passABIArg(temp); 4147 passABIArg(value); 4148 callWithABI<Fn, Printf1>(); 4149 4150 PopRegsInMask(save); 4151 #endif 4152 } 4153 4154 void MacroAssembler::convertInt32ValueToDouble(ValueOperand val) { 4155 Label done; 4156 branchTestInt32(Assembler::NotEqual, val, &done); 4157 ScratchDoubleScope fpscratch(*this); 4158 convertInt32ToDouble(val.payloadOrValueReg(), fpscratch); 4159 boxDouble(fpscratch, val, fpscratch); 4160 bind(&done); 4161 } 4162 4163 void MacroAssembler::convertValueToFloatingPoint( 4164 ValueOperand value, FloatRegister output, Register maybeTemp, 4165 LiveRegisterSet volatileLiveRegs, Label* fail, 4166 FloatingPointType outputType) { 4167 Label isDouble, isInt32OrBool, isNull, done; 4168 4169 { 4170 ScratchTagScope tag(*this, value); 4171 splitTagForTest(value, tag); 4172 4173 branchTestDouble(Assembler::Equal, tag, &isDouble); 4174 branchTestInt32(Assembler::Equal, tag, &isInt32OrBool); 4175 branchTestBoolean(Assembler::Equal, tag, &isInt32OrBool); 4176 branchTestNull(Assembler::Equal, tag, &isNull); 4177 branchTestUndefined(Assembler::NotEqual, tag, fail); 4178 } 4179 4180 // fall-through: undefined 4181 if (outputType == FloatingPointType::Float16 || 4182 outputType == FloatingPointType::Float32) { 4183 loadConstantFloat32(float(GenericNaN()), output); 4184 } else { 4185 loadConstantDouble(GenericNaN(), output); 4186 } 4187 jump(&done); 4188 4189 bind(&isNull); 4190 if (outputType == FloatingPointType::Float16 || 4191 outputType == FloatingPointType::Float32) { 4192 loadConstantFloat32(0.0f, output); 4193 } else { 4194 loadConstantDouble(0.0, output); 4195 } 4196 jump(&done); 4197 4198 bind(&isInt32OrBool); 4199 if (outputType == FloatingPointType::Float16) { 4200 convertInt32ToFloat16(value.payloadOrValueReg(), output, maybeTemp, 4201 volatileLiveRegs); 4202 } else if (outputType == FloatingPointType::Float32) { 4203 convertInt32ToFloat32(value.payloadOrValueReg(), output); 4204 } else { 4205 convertInt32ToDouble(value.payloadOrValueReg(), output); 4206 } 4207 jump(&done); 4208 4209 // On some non-multiAlias platforms, unboxDouble may use the scratch register, 4210 // so do not merge code paths here. 4211 bind(&isDouble); 4212 if ((outputType == FloatingPointType::Float16 || 4213 outputType == FloatingPointType::Float32) && 4214 hasMultiAlias()) { 4215 ScratchDoubleScope tmp(*this); 4216 unboxDouble(value, tmp); 4217 4218 if (outputType == FloatingPointType::Float16) { 4219 convertDoubleToFloat16(tmp, output, maybeTemp, volatileLiveRegs); 4220 } else { 4221 convertDoubleToFloat32(tmp, output); 4222 } 4223 } else { 4224 FloatRegister tmp = output.asDouble(); 4225 unboxDouble(value, tmp); 4226 4227 if (outputType == FloatingPointType::Float16) { 4228 convertDoubleToFloat16(tmp, output, maybeTemp, volatileLiveRegs); 4229 } else if (outputType == FloatingPointType::Float32) { 4230 convertDoubleToFloat32(tmp, output); 4231 } 4232 } 4233 4234 bind(&done); 4235 } 4236 4237 void MacroAssembler::outOfLineTruncateSlow(FloatRegister src, Register dest, 4238 bool widenFloatToDouble, 4239 bool compilingWasm, 4240 wasm::BytecodeOffset callOffset) { 4241 ScratchDoubleScope fpscratch(*this); 4242 if (widenFloatToDouble) { 4243 convertFloat32ToDouble(src, fpscratch); 4244 src = fpscratch; 4245 } 4246 MOZ_ASSERT(src.isDouble()); 4247 4248 if (compilingWasm) { 4249 Push(InstanceReg); 4250 int32_t framePushedAfterInstance = framePushed(); 4251 4252 setupWasmABICall(wasm::SymbolicAddress::ToInt32); 4253 passABIArg(src, ABIType::Float64); 4254 4255 int32_t instanceOffset = framePushed() - framePushedAfterInstance; 4256 callWithABI(callOffset, wasm::SymbolicAddress::ToInt32, 4257 mozilla::Some(instanceOffset)); 4258 storeCallInt32Result(dest); 4259 4260 Pop(InstanceReg); 4261 } else { 4262 using Fn = int32_t (*)(double); 4263 setupUnalignedABICall(dest); 4264 passABIArg(src, ABIType::Float64); 4265 callWithABI<Fn, JS::ToInt32>(ABIType::General, 4266 CheckUnsafeCallWithABI::DontCheckOther); 4267 storeCallInt32Result(dest); 4268 } 4269 } 4270 4271 void MacroAssembler::convertValueToInt32(ValueOperand value, FloatRegister temp, 4272 Register output, Label* fail, 4273 bool negativeZeroCheck, 4274 IntConversionInputKind conversion) { 4275 Label done, isInt32, isBool, isDouble, isString; 4276 4277 { 4278 ScratchTagScope tag(*this, value); 4279 splitTagForTest(value, tag); 4280 4281 branchTestInt32(Equal, tag, &isInt32); 4282 branchTestDouble(Equal, tag, &isDouble); 4283 if (conversion == IntConversionInputKind::Any) { 4284 branchTestBoolean(Equal, tag, &isBool); 4285 branchTestNull(Assembler::NotEqual, tag, fail); 4286 } else { 4287 jump(fail); 4288 } 4289 } 4290 4291 // The value is null - just emit 0. 4292 if (conversion == IntConversionInputKind::Any) { 4293 move32(Imm32(0), output); 4294 jump(&done); 4295 } 4296 4297 // Try converting double into integer. 4298 { 4299 bind(&isDouble); 4300 unboxDouble(value, temp); 4301 convertDoubleToInt32(temp, output, fail, negativeZeroCheck); 4302 jump(&done); 4303 } 4304 4305 // Just unbox a bool, the result is 0 or 1. 4306 if (conversion == IntConversionInputKind::Any) { 4307 bind(&isBool); 4308 unboxBoolean(value, output); 4309 jump(&done); 4310 } 4311 4312 // Integers can be unboxed. 4313 { 4314 bind(&isInt32); 4315 unboxInt32(value, output); 4316 } 4317 4318 bind(&done); 4319 } 4320 4321 void MacroAssembler::truncateValueToInt32( 4322 ValueOperand value, Label* handleStringEntry, Label* handleStringRejoin, 4323 Label* truncateDoubleSlow, Register stringReg, FloatRegister temp, 4324 Register output, Label* fail) { 4325 Label done, isInt32, isBool, isDouble, isNull, isString; 4326 4327 bool handleStrings = handleStringEntry && handleStringRejoin; 4328 4329 // |output| needs to be different from |stringReg| to load string indices. 4330 MOZ_ASSERT_IF(handleStrings, stringReg != output); 4331 4332 { 4333 ScratchTagScope tag(*this, value); 4334 splitTagForTest(value, tag); 4335 4336 branchTestInt32(Equal, tag, &isInt32); 4337 branchTestDouble(Equal, tag, &isDouble); 4338 branchTestBoolean(Equal, tag, &isBool); 4339 branchTestNull(Equal, tag, &isNull); 4340 if (handleStrings) { 4341 branchTestString(Equal, tag, &isString); 4342 } 4343 branchTestUndefined(Assembler::NotEqual, tag, fail); 4344 } 4345 4346 // The value is null or undefined in truncation contexts - just emit 0. 4347 { 4348 bind(&isNull); 4349 move32(Imm32(0), output); 4350 jump(&done); 4351 } 4352 4353 // First try loading a string index. If that fails, try converting a string 4354 // into a double, then jump to the double case. 4355 Label handleStringIndex; 4356 if (handleStrings) { 4357 bind(&isString); 4358 unboxString(value, stringReg); 4359 loadStringIndexValue(stringReg, output, handleStringEntry); 4360 jump(&done); 4361 } 4362 4363 // Try converting double into integer. 4364 { 4365 bind(&isDouble); 4366 unboxDouble(value, temp); 4367 4368 if (handleStrings) { 4369 bind(handleStringRejoin); 4370 } 4371 branchTruncateDoubleMaybeModUint32( 4372 temp, output, truncateDoubleSlow ? truncateDoubleSlow : fail); 4373 jump(&done); 4374 } 4375 4376 // Just unbox a bool, the result is 0 or 1. 4377 { 4378 bind(&isBool); 4379 unboxBoolean(value, output); 4380 jump(&done); 4381 } 4382 4383 // Integers can be unboxed. 4384 { 4385 bind(&isInt32); 4386 unboxInt32(value, output); 4387 } 4388 4389 bind(&done); 4390 } 4391 4392 void MacroAssembler::clampValueToUint8(ValueOperand value, 4393 Label* handleStringEntry, 4394 Label* handleStringRejoin, 4395 Register stringReg, FloatRegister temp, 4396 Register output, Label* fail) { 4397 Label done, isInt32, isBool, isDouble, isNull, isString; 4398 { 4399 ScratchTagScope tag(*this, value); 4400 splitTagForTest(value, tag); 4401 4402 branchTestInt32(Equal, tag, &isInt32); 4403 branchTestDouble(Equal, tag, &isDouble); 4404 branchTestBoolean(Equal, tag, &isBool); 4405 branchTestNull(Equal, tag, &isNull); 4406 branchTestString(Equal, tag, &isString); 4407 branchTestUndefined(Assembler::NotEqual, tag, fail); 4408 } 4409 4410 // The value is null or undefined in truncation contexts - just emit 0. 4411 { 4412 bind(&isNull); 4413 move32(Imm32(0), output); 4414 jump(&done); 4415 } 4416 4417 // Try converting a string into a double, then jump to the double case. 4418 { 4419 bind(&isString); 4420 unboxString(value, stringReg); 4421 jump(handleStringEntry); 4422 } 4423 4424 // Try converting double into integer. 4425 { 4426 bind(&isDouble); 4427 unboxDouble(value, temp); 4428 bind(handleStringRejoin); 4429 clampDoubleToUint8(temp, output); 4430 jump(&done); 4431 } 4432 4433 // Just unbox a bool, the result is 0 or 1. 4434 { 4435 bind(&isBool); 4436 unboxBoolean(value, output); 4437 jump(&done); 4438 } 4439 4440 // Integers can be unboxed. 4441 { 4442 bind(&isInt32); 4443 unboxInt32(value, output); 4444 clampIntToUint8(output); 4445 } 4446 4447 bind(&done); 4448 } 4449 4450 void MacroAssembler::finish() { 4451 if (failureLabel_.used()) { 4452 bind(&failureLabel_); 4453 handleFailure(); 4454 } 4455 4456 MacroAssemblerSpecific::finish(); 4457 4458 MOZ_RELEASE_ASSERT( 4459 size() <= MaxCodeBytesPerProcess, 4460 "AssemblerBuffer should ensure we don't exceed MaxCodeBytesPerProcess"); 4461 4462 if (bytesNeeded() > MaxCodeBytesPerProcess) { 4463 setOOM(); 4464 } 4465 } 4466 4467 void MacroAssembler::link(JitCode* code) { 4468 MOZ_ASSERT(!oom()); 4469 linkProfilerCallSites(code); 4470 } 4471 4472 MacroAssembler::AutoProfilerCallInstrumentation:: 4473 AutoProfilerCallInstrumentation(MacroAssembler& masm) { 4474 if (!masm.emitProfilingInstrumentation_) { 4475 return; 4476 } 4477 4478 Register reg = CallTempReg0; 4479 Register reg2 = CallTempReg1; 4480 masm.push(reg); 4481 masm.push(reg2); 4482 4483 CodeOffset label = masm.movWithPatch(ImmWord(uintptr_t(-1)), reg); 4484 masm.loadJSContext(reg2); 4485 masm.loadPtr(Address(reg2, offsetof(JSContext, profilingActivation_)), reg2); 4486 masm.storePtr(reg, 4487 Address(reg2, JitActivation::offsetOfLastProfilingCallSite())); 4488 4489 masm.appendProfilerCallSite(label); 4490 4491 masm.pop(reg2); 4492 masm.pop(reg); 4493 } 4494 4495 void MacroAssembler::linkProfilerCallSites(JitCode* code) { 4496 for (size_t i = 0; i < profilerCallSites_.length(); i++) { 4497 CodeOffset offset = profilerCallSites_[i]; 4498 CodeLocationLabel location(code, offset); 4499 PatchDataWithValueCheck(location, ImmPtr(location.raw()), 4500 ImmPtr((void*)-1)); 4501 } 4502 } 4503 4504 void MacroAssembler::alignJitStackBasedOnNArgs(Register nargs, 4505 bool countIncludesThis) { 4506 // The stack should already be aligned to the size of a value. 4507 assertStackAlignment(sizeof(Value), 0); 4508 4509 static_assert(JitStackValueAlignment == 1 || JitStackValueAlignment == 2, 4510 "JitStackValueAlignment is either 1 or 2."); 4511 if (JitStackValueAlignment == 1) { 4512 return; 4513 } 4514 // A jit frame is composed of the following: 4515 // 4516 // [padding?] [argN] .. [arg1] [this] [[argc] [callee] [descr] [raddr]] 4517 // \________JitFrameLayout_________/ 4518 // (The stack grows this way --->) 4519 // 4520 // We want to ensure that |raddr|, the return address, is 16-byte aligned. 4521 // (Note: if 8-byte alignment was sufficient, we would have already 4522 // returned above.) 4523 4524 // JitFrameLayout does not affect the alignment, so we can ignore it. 4525 static_assert(sizeof(JitFrameLayout) % JitStackAlignment == 0, 4526 "JitFrameLayout doesn't affect stack alignment"); 4527 4528 // Therefore, we need to ensure that |this| is aligned. 4529 // This implies that |argN| must be aligned if N is even, 4530 // and offset by |sizeof(Value)| if N is odd. 4531 4532 // Depending on the context of the caller, it may be easier to pass in a 4533 // register that has already been modified to include |this|. If that is the 4534 // case, we want to flip the direction of the test. 4535 Assembler::Condition condition = 4536 countIncludesThis ? Assembler::NonZero : Assembler::Zero; 4537 4538 Label alignmentIsOffset, end; 4539 branchTestPtr(condition, nargs, Imm32(1), &alignmentIsOffset); 4540 4541 // |argN| should be aligned to 16 bytes. 4542 andToStackPtr(Imm32(~(JitStackAlignment - 1))); 4543 jump(&end); 4544 4545 // |argN| should be offset by 8 bytes from 16-byte alignment. 4546 // We already know that it is 8-byte aligned, so the only possibilities are: 4547 // a) It is 16-byte aligned, and we must offset it by 8 bytes. 4548 // b) It is not 16-byte aligned, and therefore already has the right offset. 4549 // Therefore, we test to see if it is 16-byte aligned, and adjust it if it is. 4550 bind(&alignmentIsOffset); 4551 branchTestStackPtr(Assembler::NonZero, Imm32(JitStackAlignment - 1), &end); 4552 subFromStackPtr(Imm32(sizeof(Value))); 4553 4554 bind(&end); 4555 } 4556 4557 void MacroAssembler::alignJitStackBasedOnNArgs(uint32_t argc, 4558 bool countIncludesThis) { 4559 // The stack should already be aligned to the size of a value. 4560 assertStackAlignment(sizeof(Value), 0); 4561 4562 static_assert(JitStackValueAlignment == 1 || JitStackValueAlignment == 2, 4563 "JitStackValueAlignment is either 1 or 2."); 4564 if (JitStackValueAlignment == 1) { 4565 return; 4566 } 4567 4568 // See above for full explanation. 4569 uint32_t nArgs = argc + !countIncludesThis; 4570 if (nArgs % 2 == 0) { 4571 // |argN| should be 16-byte aligned 4572 andToStackPtr(Imm32(~(JitStackAlignment - 1))); 4573 } else { 4574 // |argN| must be 16-byte aligned if argc is even, 4575 // and offset by 8 if argc is odd. 4576 Label end; 4577 branchTestStackPtr(Assembler::NonZero, Imm32(JitStackAlignment - 1), &end); 4578 subFromStackPtr(Imm32(sizeof(Value))); 4579 bind(&end); 4580 assertStackAlignment(JitStackAlignment, sizeof(Value)); 4581 } 4582 } 4583 4584 // =============================================================== 4585 4586 MacroAssembler::MacroAssembler(TempAllocator& alloc, 4587 CompileRuntime* maybeRuntime, 4588 CompileRealm* maybeRealm) 4589 : maybeRuntime_(maybeRuntime), 4590 maybeRealm_(maybeRealm), 4591 framePushed_(0), 4592 abiArgs_(/* This will be overwritten for every ABI call, the initial value 4593 doesn't matter */ 4594 ABIKind::System), 4595 #ifdef DEBUG 4596 inCall_(false), 4597 #endif 4598 dynamicAlignment_(false), 4599 emitProfilingInstrumentation_(false) { 4600 moveResolver_.setAllocator(alloc); 4601 } 4602 4603 StackMacroAssembler::StackMacroAssembler(JSContext* cx, TempAllocator& alloc) 4604 : MacroAssembler(alloc, CompileRuntime::get(cx->runtime()), 4605 CompileRealm::get(cx->realm())) {} 4606 4607 OffThreadMacroAssembler::OffThreadMacroAssembler(TempAllocator& alloc, 4608 CompileRealm* realm) 4609 : MacroAssembler(alloc, realm->runtime(), realm) { 4610 MOZ_ASSERT(CurrentThreadIsOffThreadCompiling()); 4611 } 4612 4613 WasmMacroAssembler::WasmMacroAssembler(TempAllocator& alloc, bool limitedSize) 4614 : MacroAssembler(alloc) { 4615 #if defined(JS_CODEGEN_ARM64) 4616 // Stubs + builtins + the baseline compiler all require the native SP, 4617 // not the PSP. 4618 SetStackPointer64(sp); 4619 #endif 4620 if (!limitedSize) { 4621 setUnlimitedBuffer(); 4622 } 4623 } 4624 4625 bool MacroAssembler::icBuildOOLFakeExitFrame(void* fakeReturnAddr, 4626 AutoSaveLiveRegisters& save) { 4627 return buildOOLFakeExitFrame(fakeReturnAddr); 4628 } 4629 4630 #ifndef JS_CODEGEN_ARM64 4631 void MacroAssembler::subFromStackPtr(Register reg) { 4632 subPtr(reg, getStackPointer()); 4633 } 4634 #endif // JS_CODEGEN_ARM64 4635 4636 //{{{ check_macroassembler_style 4637 // =============================================================== 4638 // Stack manipulation functions. 4639 4640 void MacroAssembler::PushRegsInMask(LiveGeneralRegisterSet set) { 4641 PushRegsInMask(LiveRegisterSet(set.set(), FloatRegisterSet())); 4642 } 4643 4644 void MacroAssembler::PopRegsInMask(LiveRegisterSet set) { 4645 PopRegsInMaskIgnore(set, LiveRegisterSet()); 4646 } 4647 4648 void MacroAssembler::PopRegsInMask(LiveGeneralRegisterSet set) { 4649 PopRegsInMask(LiveRegisterSet(set.set(), FloatRegisterSet())); 4650 } 4651 4652 void MacroAssembler::Push(PropertyKey key, Register scratchReg) { 4653 if (key.isGCThing()) { 4654 // If we're pushing a gcthing, then we can't just push the tagged key 4655 // value since the GC won't have any idea that the push instruction 4656 // carries a reference to a gcthing. Need to unpack the pointer, 4657 // push it using ImmGCPtr, and then rematerialize the PropertyKey at 4658 // runtime. 4659 4660 if (key.isString()) { 4661 JSString* str = key.toString(); 4662 MOZ_ASSERT((uintptr_t(str) & PropertyKey::TypeMask) == 0); 4663 static_assert(PropertyKey::StringTypeTag == 0, 4664 "need to orPtr StringTypeTag if it's not 0"); 4665 Push(ImmGCPtr(str)); 4666 } else { 4667 MOZ_ASSERT(key.isSymbol()); 4668 movePropertyKey(key, scratchReg); 4669 Push(scratchReg); 4670 } 4671 } else { 4672 MOZ_ASSERT(key.isInt()); 4673 Push(ImmWord(key.asRawBits())); 4674 } 4675 } 4676 4677 void MacroAssembler::moveValue(const TypedOrValueRegister& src, 4678 const ValueOperand& dest) { 4679 if (src.hasValue()) { 4680 moveValue(src.valueReg(), dest); 4681 return; 4682 } 4683 4684 MIRType type = src.type(); 4685 AnyRegister reg = src.typedReg(); 4686 4687 if (!IsFloatingPointType(type)) { 4688 tagValue(ValueTypeFromMIRType(type), reg.gpr(), dest); 4689 return; 4690 } 4691 4692 ScratchDoubleScope scratch(*this); 4693 FloatRegister freg = reg.fpu(); 4694 if (type == MIRType::Float32) { 4695 convertFloat32ToDouble(freg, scratch); 4696 freg = scratch; 4697 } 4698 boxDouble(freg, dest, scratch); 4699 } 4700 4701 void MacroAssembler::movePropertyKey(PropertyKey key, Register dest) { 4702 if (key.isGCThing()) { 4703 // See comment in |Push(PropertyKey, ...)| above for an explanation. 4704 if (key.isString()) { 4705 JSString* str = key.toString(); 4706 MOZ_ASSERT((uintptr_t(str) & PropertyKey::TypeMask) == 0); 4707 static_assert(PropertyKey::StringTypeTag == 0, 4708 "need to orPtr StringTypeTag tag if it's not 0"); 4709 movePtr(ImmGCPtr(str), dest); 4710 } else { 4711 MOZ_ASSERT(key.isSymbol()); 4712 JS::Symbol* sym = key.toSymbol(); 4713 movePtr(ImmGCPtr(sym), dest); 4714 orPtr(Imm32(PropertyKey::SymbolTypeTag), dest); 4715 } 4716 } else { 4717 MOZ_ASSERT(key.isInt()); 4718 movePtr(ImmWord(key.asRawBits()), dest); 4719 } 4720 } 4721 4722 void MacroAssembler::Push(TypedOrValueRegister v) { 4723 if (v.hasValue()) { 4724 Push(v.valueReg()); 4725 } else if (IsFloatingPointType(v.type())) { 4726 FloatRegister reg = v.typedReg().fpu(); 4727 if (v.type() == MIRType::Float32) { 4728 ScratchDoubleScope fpscratch(*this); 4729 convertFloat32ToDouble(reg, fpscratch); 4730 PushBoxed(fpscratch); 4731 } else { 4732 PushBoxed(reg); 4733 } 4734 } else { 4735 Push(ValueTypeFromMIRType(v.type()), v.typedReg().gpr()); 4736 } 4737 } 4738 4739 void MacroAssembler::Push(const ConstantOrRegister& v) { 4740 if (v.constant()) { 4741 Push(v.value()); 4742 } else { 4743 Push(v.reg()); 4744 } 4745 } 4746 4747 void MacroAssembler::Push(const Address& addr) { 4748 push(addr); 4749 framePushed_ += sizeof(uintptr_t); 4750 } 4751 4752 void MacroAssembler::Push(const ValueOperand& val) { 4753 pushValue(val); 4754 framePushed_ += sizeof(Value); 4755 } 4756 4757 void MacroAssembler::Push(const Value& val) { 4758 pushValue(val); 4759 framePushed_ += sizeof(Value); 4760 } 4761 4762 void MacroAssembler::Push(JSValueType type, Register reg) { 4763 pushValue(type, reg); 4764 framePushed_ += sizeof(Value); 4765 } 4766 4767 void MacroAssembler::Push(const Register64 reg) { 4768 #if JS_BITS_PER_WORD == 64 4769 Push(reg.reg); 4770 #else 4771 MOZ_ASSERT(MOZ_LITTLE_ENDIAN(), "Big-endian not supported."); 4772 Push(reg.high); 4773 Push(reg.low); 4774 #endif 4775 } 4776 4777 void MacroAssembler::Pop(const Register64 reg) { 4778 #if JS_BITS_PER_WORD == 64 4779 Pop(reg.reg); 4780 #else 4781 MOZ_ASSERT(MOZ_LITTLE_ENDIAN(), "Big-endian not supported."); 4782 Pop(reg.low); 4783 Pop(reg.high); 4784 #endif 4785 } 4786 4787 void MacroAssembler::PushEmptyRooted(VMFunctionData::RootType rootType) { 4788 switch (rootType) { 4789 case VMFunctionData::RootNone: 4790 MOZ_CRASH("Handle must have root type"); 4791 case VMFunctionData::RootObject: 4792 case VMFunctionData::RootString: 4793 case VMFunctionData::RootCell: 4794 case VMFunctionData::RootBigInt: 4795 Push(ImmPtr(nullptr)); 4796 break; 4797 case VMFunctionData::RootValue: 4798 Push(UndefinedValue()); 4799 break; 4800 case VMFunctionData::RootId: 4801 Push(ImmWord(JS::PropertyKey::Void().asRawBits())); 4802 break; 4803 } 4804 } 4805 4806 void MacroAssembler::adjustStack(int amount) { 4807 if (amount > 0) { 4808 freeStack(amount); 4809 } else if (amount < 0) { 4810 reserveStack(-amount); 4811 } 4812 } 4813 4814 void MacroAssembler::freeStack(uint32_t amount) { 4815 MOZ_ASSERT(amount <= framePushed_); 4816 if (amount) { 4817 addToStackPtr(Imm32(amount)); 4818 } 4819 framePushed_ -= amount; 4820 } 4821 4822 void MacroAssembler::reserveVMFunctionOutParamSpace(const VMFunctionData& f) { 4823 switch (f.outParam) { 4824 case Type_Handle: 4825 PushEmptyRooted(f.outParamRootType); 4826 break; 4827 4828 case Type_Value: 4829 case Type_Double: 4830 case Type_Pointer: 4831 case Type_Int32: 4832 case Type_Bool: 4833 reserveStack(f.sizeOfOutParamStackSlot()); 4834 break; 4835 4836 case Type_Void: 4837 break; 4838 4839 case Type_Cell: 4840 MOZ_CRASH("Unexpected outparam type"); 4841 } 4842 } 4843 4844 void MacroAssembler::loadVMFunctionOutParam(const VMFunctionData& f, 4845 const Address& addr) { 4846 switch (f.outParam) { 4847 case Type_Handle: 4848 switch (f.outParamRootType) { 4849 case VMFunctionData::RootNone: 4850 MOZ_CRASH("Handle must have root type"); 4851 case VMFunctionData::RootObject: 4852 case VMFunctionData::RootString: 4853 case VMFunctionData::RootCell: 4854 case VMFunctionData::RootBigInt: 4855 case VMFunctionData::RootId: 4856 loadPtr(addr, ReturnReg); 4857 break; 4858 case VMFunctionData::RootValue: 4859 loadValue(addr, JSReturnOperand); 4860 break; 4861 } 4862 break; 4863 4864 case Type_Value: 4865 loadValue(addr, JSReturnOperand); 4866 break; 4867 4868 case Type_Int32: 4869 load32(addr, ReturnReg); 4870 break; 4871 4872 case Type_Bool: 4873 load8ZeroExtend(addr, ReturnReg); 4874 break; 4875 4876 case Type_Double: 4877 loadDouble(addr, ReturnDoubleReg); 4878 break; 4879 4880 case Type_Pointer: 4881 loadPtr(addr, ReturnReg); 4882 break; 4883 4884 case Type_Void: 4885 break; 4886 4887 case Type_Cell: 4888 MOZ_CRASH("Unexpected outparam type"); 4889 } 4890 } 4891 4892 // =============================================================== 4893 // ABI function calls. 4894 void MacroAssembler::setupABICallHelper(ABIKind kind) { 4895 #ifdef DEBUG 4896 MOZ_ASSERT(!inCall_); 4897 inCall_ = true; 4898 #endif 4899 4900 #ifdef JS_SIMULATOR 4901 signature_ = 0; 4902 #endif 4903 4904 // Reinitialize the ABIArg generator. 4905 abiArgs_ = ABIArgGenerator(kind); 4906 4907 #if defined(JS_CODEGEN_ARM) 4908 if (kind != ABIKind::Wasm) { 4909 // On ARM, we need to know what ABI we are using. 4910 abiArgs_.setUseHardFp(ARMFlags::UseHardFpABI()); 4911 } 4912 #endif 4913 } 4914 4915 void MacroAssembler::setupNativeABICall() { 4916 setupABICallHelper(ABIKind::System); 4917 } 4918 4919 void MacroAssembler::setupWasmABICall(wasm::SymbolicAddress builtin) { 4920 MOZ_ASSERT(IsCompilingWasm(), "non-wasm should use setupAlignedABICall"); 4921 setupABICallHelper(wasm::ABIForBuiltin(builtin)); 4922 dynamicAlignment_ = false; 4923 } 4924 4925 void MacroAssembler::setupUnalignedABICallDontSaveRestoreSP() { 4926 andToStackPtr(Imm32(~(ABIStackAlignment - 1))); 4927 setFramePushed(0); // Required for aligned callWithABI. 4928 setupAlignedABICall(); 4929 } 4930 4931 void MacroAssembler::setupAlignedABICall() { 4932 MOZ_ASSERT(!IsCompilingWasm(), "wasm should use setupWasmABICall"); 4933 setupNativeABICall(); 4934 dynamicAlignment_ = false; 4935 } 4936 4937 #ifdef JS_CHECK_UNSAFE_CALL_WITH_ABI 4938 void MacroAssembler::wasmCheckUnsafeCallWithABIPre() { 4939 // Set the JSContext::inUnsafeCallWithABI flag. 4940 loadPtr(Address(InstanceReg, wasm::Instance::offsetOfCx()), 4941 ABINonArgReturnReg0); 4942 Address flagAddr(ABINonArgReturnReg0, 4943 JSContext::offsetOfInUnsafeCallWithABI()); 4944 store32(Imm32(1), flagAddr); 4945 } 4946 4947 void MacroAssembler::wasmCheckUnsafeCallWithABIPost() { 4948 // Check JSContext::inUnsafeCallWithABI was cleared as expected. 4949 Label ok; 4950 // InstanceReg is invariant in the system ABI, so we can use it here. 4951 loadPtr(Address(InstanceReg, wasm::Instance::offsetOfCx()), 4952 ABINonArgReturnReg0); 4953 Address flagAddr(ABINonArgReturnReg0, 4954 JSContext::offsetOfInUnsafeCallWithABI()); 4955 branch32(Assembler::Equal, flagAddr, Imm32(0), &ok); 4956 assumeUnreachable("callWithABI: callee did not use AutoUnsafeCallWithABI"); 4957 bind(&ok); 4958 } 4959 #endif // JS_CHECK_UNSAFE_CALL_WITH_ABI 4960 4961 void MacroAssembler::passABIArg(const MoveOperand& from, ABIType type) { 4962 MOZ_ASSERT(inCall_); 4963 appendSignatureType(type); 4964 4965 ABIArg arg; 4966 MoveOp::Type moveType; 4967 switch (type) { 4968 case ABIType::Float32: 4969 arg = abiArgs_.next(MIRType::Float32); 4970 moveType = MoveOp::FLOAT32; 4971 break; 4972 case ABIType::Float64: 4973 arg = abiArgs_.next(MIRType::Double); 4974 moveType = MoveOp::DOUBLE; 4975 break; 4976 case ABIType::General: 4977 arg = abiArgs_.next(MIRType::Pointer); 4978 moveType = MoveOp::GENERAL; 4979 break; 4980 default: 4981 MOZ_CRASH("Unexpected argument type"); 4982 } 4983 4984 MoveOperand to(*this, arg); 4985 if (from == to) { 4986 return; 4987 } 4988 4989 if (oom()) { 4990 return; 4991 } 4992 propagateOOM(moveResolver_.addMove(from, to, moveType)); 4993 } 4994 4995 void MacroAssembler::passABIArg(Register64 reg) { 4996 MOZ_ASSERT(inCall_); 4997 appendSignatureType(ABIType::Int64); 4998 4999 ABIArg arg = abiArgs_.next(MIRType::Int64); 5000 MoveOperand to(*this, arg); 5001 5002 auto addMove = [&](const MoveOperand& from, const MoveOperand& to) { 5003 if (from == to) { 5004 return; 5005 } 5006 if (oom()) { 5007 return; 5008 } 5009 propagateOOM(moveResolver_.addMove(from, to, MoveOp::GENERAL)); 5010 }; 5011 5012 #ifdef JS_PUNBOX64 5013 addMove(MoveOperand(reg.reg), to); 5014 #else 5015 if (to.isMemory()) { 5016 Address addr(to.base(), to.disp()); 5017 addMove(MoveOperand(reg.high), MoveOperand(HighWord(addr))); 5018 addMove(MoveOperand(reg.low), MoveOperand(LowWord(addr))); 5019 } else if (to.isGeneralRegPair()) { 5020 addMove(MoveOperand(reg.high), MoveOperand(to.oddReg())); 5021 addMove(MoveOperand(reg.low), MoveOperand(to.evenReg())); 5022 } else { 5023 MOZ_CRASH("Unsupported move operand"); 5024 } 5025 #endif 5026 } 5027 5028 void MacroAssembler::callWithABINoProfiler(void* fun, ABIType result, 5029 CheckUnsafeCallWithABI check) { 5030 appendSignatureType(result); 5031 #ifdef JS_SIMULATOR 5032 fun = Simulator::RedirectNativeFunction(fun, signature()); 5033 #endif 5034 5035 uint32_t stackAdjust; 5036 callWithABIPre(&stackAdjust); 5037 5038 #ifdef JS_CHECK_UNSAFE_CALL_WITH_ABI 5039 if (check == CheckUnsafeCallWithABI::Check) { 5040 // Set the JSContext::inUnsafeCallWithABI flag. 5041 push(ReturnReg); 5042 loadJSContext(ReturnReg); 5043 Address flagAddr(ReturnReg, JSContext::offsetOfInUnsafeCallWithABI()); 5044 store32(Imm32(1), flagAddr); 5045 pop(ReturnReg); 5046 // On arm64, SP may be < PSP now (that's OK). 5047 // eg testcase: tests/bug1375074.js 5048 } 5049 #endif 5050 5051 call(ImmPtr(fun)); 5052 5053 callWithABIPost(stackAdjust, result); 5054 5055 #ifdef JS_CHECK_UNSAFE_CALL_WITH_ABI 5056 if (check == CheckUnsafeCallWithABI::Check) { 5057 // Check JSContext::inUnsafeCallWithABI was cleared as expected. 5058 Label ok; 5059 push(ReturnReg); 5060 loadJSContext(ReturnReg); 5061 Address flagAddr(ReturnReg, JSContext::offsetOfInUnsafeCallWithABI()); 5062 branch32(Assembler::Equal, flagAddr, Imm32(0), &ok); 5063 assumeUnreachable("callWithABI: callee did not use AutoUnsafeCallWithABI"); 5064 bind(&ok); 5065 pop(ReturnReg); 5066 // On arm64, SP may be < PSP now (that's OK). 5067 // eg testcase: tests/bug1375074.js 5068 } 5069 #endif 5070 } 5071 5072 CodeOffset MacroAssembler::callWithABI(wasm::BytecodeOffset bytecode, 5073 wasm::SymbolicAddress imm, 5074 mozilla::Maybe<int32_t> instanceOffset, 5075 ABIType result) { 5076 uint32_t stackAdjust; 5077 callWithABIPre(&stackAdjust, /* callFromWasm = */ true); 5078 5079 // The instance register is used in builtin thunks and must be set. 5080 bool needsBuiltinThunk = wasm::NeedsBuiltinThunk(imm); 5081 #ifdef JS_CHECK_UNSAFE_CALL_WITH_ABI 5082 // The builtin thunk exits the JIT activation, if we don't have one we must 5083 // use AutoUnsafeCallWithABI inside the builtin and check that here. 5084 bool checkUnsafeCallWithABI = !needsBuiltinThunk; 5085 #else 5086 bool checkUnsafeCallWithABI = false; 5087 #endif 5088 if (needsBuiltinThunk || checkUnsafeCallWithABI) { 5089 if (instanceOffset) { 5090 loadPtr(Address(getStackPointer(), *instanceOffset + stackAdjust), 5091 InstanceReg); 5092 } else { 5093 MOZ_CRASH("callWithABI missing instanceOffset"); 5094 } 5095 } 5096 5097 #ifdef JS_CHECK_UNSAFE_CALL_WITH_ABI 5098 if (checkUnsafeCallWithABI) { 5099 wasmCheckUnsafeCallWithABIPre(); 5100 } 5101 #endif 5102 5103 CodeOffset raOffset = call( 5104 wasm::CallSiteDesc(bytecode.offset(), wasm::CallSiteKind::Symbolic), imm); 5105 5106 callWithABIPost(stackAdjust, result); 5107 5108 #ifdef JS_CHECK_UNSAFE_CALL_WITH_ABI 5109 if (checkUnsafeCallWithABI) { 5110 wasmCheckUnsafeCallWithABIPost(); 5111 } 5112 #endif 5113 5114 return raOffset; 5115 } 5116 5117 void MacroAssembler::callDebugWithABI(wasm::SymbolicAddress imm, 5118 ABIType result) { 5119 uint32_t stackAdjust; 5120 callWithABIPre(&stackAdjust, /* callFromWasm = */ false); 5121 call(imm); 5122 callWithABIPost(stackAdjust, result); 5123 } 5124 5125 // =============================================================== 5126 // Exit frame footer. 5127 5128 void MacroAssembler::linkExitFrame(Register cxreg, Register scratch) { 5129 loadPtr(Address(cxreg, JSContext::offsetOfActivation()), scratch); 5130 storeStackPtr(Address(scratch, JitActivation::offsetOfPackedExitFP())); 5131 } 5132 5133 // =============================================================== 5134 // Simple value-shuffling helpers, to hide MoveResolver verbosity 5135 // in common cases. 5136 5137 void MacroAssembler::moveRegPair(Register src0, Register src1, Register dst0, 5138 Register dst1, MoveOp::Type type) { 5139 MoveResolver& moves = moveResolver(); 5140 if (src0 != dst0) { 5141 propagateOOM(moves.addMove(MoveOperand(src0), MoveOperand(dst0), type)); 5142 } 5143 if (src1 != dst1) { 5144 propagateOOM(moves.addMove(MoveOperand(src1), MoveOperand(dst1), type)); 5145 } 5146 propagateOOM(moves.resolve()); 5147 if (oom()) { 5148 return; 5149 } 5150 5151 MoveEmitter emitter(*this); 5152 emitter.emit(moves); 5153 emitter.finish(); 5154 } 5155 5156 // =============================================================== 5157 // Arithmetic functions 5158 5159 void MacroAssembler::pow32(Register base, Register power, Register dest, 5160 Register temp1, Register temp2, Label* onOver) { 5161 // Inline int32-specialized implementation of js::powi with overflow 5162 // detection. 5163 5164 move32(Imm32(1), dest); // result = 1 5165 5166 // x^y where x == 1 returns 1 for any y. 5167 Label done; 5168 branch32(Assembler::Equal, base, Imm32(1), &done); 5169 5170 // x^y where y < 0 returns a non-int32 value for any x != 1. Except when y is 5171 // large enough so that the result is no longer representable as a double with 5172 // fractional parts. We can't easily determine when y is too large, so we bail 5173 // here. 5174 // Note: it's important for this condition to match the code in CacheIR.cpp 5175 // (CanAttachInt32Pow) to prevent failure loops. 5176 branchTest32(Assembler::Signed, power, power, onOver); 5177 5178 move32(base, temp1); // runningSquare = x 5179 move32(power, temp2); // n = y 5180 5181 Label start; 5182 jump(&start); 5183 5184 Label loop; 5185 bind(&loop); 5186 5187 // runningSquare *= runningSquare 5188 branchMul32(Assembler::Overflow, temp1, temp1, onOver); 5189 5190 bind(&start); 5191 5192 // if ((n & 1) != 0) result *= runningSquare 5193 Label even; 5194 branchTest32(Assembler::Zero, temp2, Imm32(1), &even); 5195 branchMul32(Assembler::Overflow, temp1, dest, onOver); 5196 bind(&even); 5197 5198 // n >>= 1 5199 // if (n == 0) return result 5200 branchRshift32(Assembler::NonZero, Imm32(1), temp2, &loop); 5201 5202 bind(&done); 5203 } 5204 5205 void MacroAssembler::powPtr(Register base, Register power, Register dest, 5206 Register temp1, Register temp2, Label* onOver) { 5207 // Inline intptr-specialized implementation of BigInt::pow with overflow 5208 // detection. 5209 5210 // Negative exponents are disallowed for any BigInts. 5211 branchTestPtr(Assembler::Signed, power, power, onOver); 5212 5213 movePtr(ImmWord(1), dest); // result = 1 5214 5215 // x^y where x == 1 returns 1 for any y. 5216 Label done; 5217 branchPtr(Assembler::Equal, base, ImmWord(1), &done); 5218 5219 // x^y where x == -1 returns 1 for even y, and -1 for odd y. 5220 Label notNegativeOne; 5221 branchPtr(Assembler::NotEqual, base, ImmWord(-1), ¬NegativeOne); 5222 test32MovePtr(Assembler::NonZero, power, Imm32(1), base, dest); 5223 jump(&done); 5224 bind(¬NegativeOne); 5225 5226 // x ** y with |x| > 1 and y >= DigitBits can't be pointer-sized. 5227 branchPtr(Assembler::GreaterThanOrEqual, power, Imm32(BigInt::DigitBits), 5228 onOver); 5229 5230 movePtr(base, temp1); // runningSquare = x 5231 movePtr(power, temp2); // n = y 5232 5233 Label start; 5234 jump(&start); 5235 5236 Label loop; 5237 bind(&loop); 5238 5239 // runningSquare *= runningSquare 5240 branchMulPtr(Assembler::Overflow, temp1, temp1, onOver); 5241 5242 bind(&start); 5243 5244 // if ((n & 1) != 0) result *= runningSquare 5245 Label even; 5246 branchTest32(Assembler::Zero, temp2, Imm32(1), &even); 5247 branchMulPtr(Assembler::Overflow, temp1, dest, onOver); 5248 bind(&even); 5249 5250 // n >>= 1 5251 // if (n == 0) return result 5252 branchRshift32(Assembler::NonZero, Imm32(1), temp2, &loop); 5253 5254 bind(&done); 5255 } 5256 5257 void MacroAssembler::signInt32(Register input, Register output) { 5258 MOZ_ASSERT(input != output); 5259 5260 rshift32Arithmetic(Imm32(31), input, output); 5261 or32(Imm32(1), output); 5262 cmp32Move32(Assembler::Equal, input, Imm32(0), input, output); 5263 } 5264 5265 void MacroAssembler::signDouble(FloatRegister input, FloatRegister output) { 5266 MOZ_ASSERT(input != output); 5267 5268 Label done, zeroOrNaN, negative; 5269 loadConstantDouble(0.0, output); 5270 branchDouble(Assembler::DoubleEqualOrUnordered, input, output, &zeroOrNaN); 5271 branchDouble(Assembler::DoubleLessThan, input, output, &negative); 5272 5273 loadConstantDouble(1.0, output); 5274 jump(&done); 5275 5276 bind(&negative); 5277 loadConstantDouble(-1.0, output); 5278 jump(&done); 5279 5280 bind(&zeroOrNaN); 5281 moveDouble(input, output); 5282 5283 bind(&done); 5284 } 5285 5286 void MacroAssembler::signDoubleToInt32(FloatRegister input, Register output, 5287 FloatRegister temp, Label* fail) { 5288 MOZ_ASSERT(input != temp); 5289 5290 Label done, zeroOrNaN, negative; 5291 loadConstantDouble(0.0, temp); 5292 branchDouble(Assembler::DoubleEqualOrUnordered, input, temp, &zeroOrNaN); 5293 branchDouble(Assembler::DoubleLessThan, input, temp, &negative); 5294 5295 move32(Imm32(1), output); 5296 jump(&done); 5297 5298 bind(&negative); 5299 move32(Imm32(-1), output); 5300 jump(&done); 5301 5302 // Fail for NaN and negative zero. 5303 bind(&zeroOrNaN); 5304 branchDouble(Assembler::DoubleUnordered, input, input, fail); 5305 5306 // The easiest way to distinguish -0.0 from 0.0 is that 1.0/-0.0 5307 // is -Infinity instead of Infinity. 5308 loadConstantDouble(1.0, temp); 5309 divDouble(input, temp); 5310 branchDouble(Assembler::DoubleLessThan, temp, input, fail); 5311 move32(Imm32(0), output); 5312 5313 bind(&done); 5314 } 5315 5316 void MacroAssembler::randomDouble(Register rng, FloatRegister dest, 5317 Register64 temp0, Register64 temp1) { 5318 using mozilla::non_crypto::XorShift128PlusRNG; 5319 5320 static_assert( 5321 sizeof(XorShift128PlusRNG) == 2 * sizeof(uint64_t), 5322 "Code below assumes XorShift128PlusRNG contains two uint64_t values"); 5323 5324 Address state0Addr(rng, XorShift128PlusRNG::offsetOfState0()); 5325 Address state1Addr(rng, XorShift128PlusRNG::offsetOfState1()); 5326 5327 Register64 s0Reg = temp0; 5328 Register64 s1Reg = temp1; 5329 5330 // uint64_t s1 = mState[0]; 5331 load64(state0Addr, s1Reg); 5332 5333 // s1 ^= s1 << 23; 5334 move64(s1Reg, s0Reg); 5335 lshift64(Imm32(23), s1Reg); 5336 xor64(s0Reg, s1Reg); 5337 5338 // s1 ^= s1 >> 17 5339 move64(s1Reg, s0Reg); 5340 rshift64(Imm32(17), s1Reg); 5341 xor64(s0Reg, s1Reg); 5342 5343 // const uint64_t s0 = mState[1]; 5344 load64(state1Addr, s0Reg); 5345 5346 // mState[0] = s0; 5347 store64(s0Reg, state0Addr); 5348 5349 // s1 ^= s0 5350 xor64(s0Reg, s1Reg); 5351 5352 // s1 ^= s0 >> 26 5353 rshift64(Imm32(26), s0Reg); 5354 xor64(s0Reg, s1Reg); 5355 5356 // mState[1] = s1 5357 store64(s1Reg, state1Addr); 5358 5359 // s1 += mState[0] 5360 load64(state0Addr, s0Reg); 5361 add64(s0Reg, s1Reg); 5362 5363 // See comment in XorShift128PlusRNG::nextDouble(). 5364 static constexpr int MantissaBits = 5365 mozilla::FloatingPoint<double>::kExponentShift + 1; 5366 static constexpr double ScaleInv = double(1) / (1ULL << MantissaBits); 5367 5368 and64(Imm64((1ULL << MantissaBits) - 1), s1Reg); 5369 5370 // Note: we know s1Reg isn't signed after the and64 so we can use the faster 5371 // convertInt64ToDouble instead of convertUInt64ToDouble. 5372 convertInt64ToDouble(s1Reg, dest); 5373 5374 // dest *= ScaleInv 5375 mulDoublePtr(ImmPtr(&ScaleInv), s0Reg.scratchReg(), dest); 5376 } 5377 5378 void MacroAssembler::roundFloat32(FloatRegister src, FloatRegister dest) { 5379 MOZ_ASSERT(HasRoundInstruction(RoundingMode::Up)); 5380 MOZ_ASSERT(src != dest); 5381 5382 nearbyIntFloat32(RoundingMode::Up, src, dest); 5383 5384 ScratchFloat32Scope scratch(*this); 5385 loadConstantFloat32(-0.5f, scratch); 5386 addFloat32(dest, scratch); 5387 5388 Label done; 5389 branchFloat(Assembler::DoubleLessThanOrEqualOrUnordered, scratch, src, &done); 5390 { 5391 loadConstantFloat32(1.0f, scratch); 5392 subFloat32(scratch, dest); 5393 } 5394 bind(&done); 5395 } 5396 5397 void MacroAssembler::roundDouble(FloatRegister src, FloatRegister dest) { 5398 MOZ_ASSERT(HasRoundInstruction(RoundingMode::Up)); 5399 MOZ_ASSERT(src != dest); 5400 5401 nearbyIntDouble(RoundingMode::Up, src, dest); 5402 5403 ScratchDoubleScope scratch(*this); 5404 loadConstantDouble(-0.5, scratch); 5405 addDouble(dest, scratch); 5406 5407 Label done; 5408 branchDouble(Assembler::DoubleLessThanOrEqualOrUnordered, scratch, src, 5409 &done); 5410 { 5411 loadConstantDouble(1.0, scratch); 5412 subDouble(scratch, dest); 5413 } 5414 bind(&done); 5415 } 5416 5417 void MacroAssembler::sameValueDouble(FloatRegister left, FloatRegister right, 5418 FloatRegister temp, Register dest) { 5419 Label nonEqual, isSameValue, isNotSameValue; 5420 branchDouble(Assembler::DoubleNotEqualOrUnordered, left, right, &nonEqual); 5421 { 5422 // First, test for being equal to 0.0, which also includes -0.0. 5423 loadConstantDouble(0.0, temp); 5424 branchDouble(Assembler::DoubleNotEqual, left, temp, &isSameValue); 5425 5426 // The easiest way to distinguish -0.0 from 0.0 is that 1.0/-0.0 5427 // is -Infinity instead of Infinity. 5428 Label isNegInf; 5429 loadConstantDouble(1.0, temp); 5430 divDouble(left, temp); 5431 branchDouble(Assembler::DoubleLessThan, temp, left, &isNegInf); 5432 { 5433 loadConstantDouble(1.0, temp); 5434 divDouble(right, temp); 5435 branchDouble(Assembler::DoubleGreaterThan, temp, right, &isSameValue); 5436 jump(&isNotSameValue); 5437 } 5438 bind(&isNegInf); 5439 { 5440 loadConstantDouble(1.0, temp); 5441 divDouble(right, temp); 5442 branchDouble(Assembler::DoubleLessThan, temp, right, &isSameValue); 5443 jump(&isNotSameValue); 5444 } 5445 } 5446 bind(&nonEqual); 5447 { 5448 // Test if both values are NaN. 5449 branchDouble(Assembler::DoubleOrdered, left, left, &isNotSameValue); 5450 branchDouble(Assembler::DoubleOrdered, right, right, &isNotSameValue); 5451 } 5452 5453 Label done; 5454 bind(&isSameValue); 5455 move32(Imm32(1), dest); 5456 jump(&done); 5457 5458 bind(&isNotSameValue); 5459 move32(Imm32(0), dest); 5460 5461 bind(&done); 5462 } 5463 5464 void MacroAssembler::minMaxArrayInt32(Register array, Register result, 5465 Register temp1, Register temp2, 5466 Register temp3, bool isMax, Label* fail) { 5467 // array must be a packed array. Load its elements. 5468 Register elements = temp1; 5469 loadPtr(Address(array, NativeObject::offsetOfElements()), elements); 5470 5471 // Load the length and guard that it is non-zero. 5472 Address lengthAddr(elements, ObjectElements::offsetOfInitializedLength()); 5473 load32(lengthAddr, temp3); 5474 branchTest32(Assembler::Zero, temp3, temp3, fail); 5475 5476 // Compute the address of the last element. 5477 Register elementsEnd = temp2; 5478 BaseObjectElementIndex elementsEndAddr(elements, temp3, 5479 -int32_t(sizeof(Value))); 5480 computeEffectiveAddress(elementsEndAddr, elementsEnd); 5481 5482 // Load the first element into result. 5483 fallibleUnboxInt32(Address(elements, 0), result, fail); 5484 5485 Label loop, done; 5486 bind(&loop); 5487 5488 // Check whether we're done. 5489 branchPtr(Assembler::Equal, elements, elementsEnd, &done); 5490 5491 // If not, advance to the next element and load it. 5492 addPtr(Imm32(sizeof(Value)), elements); 5493 fallibleUnboxInt32(Address(elements, 0), temp3, fail); 5494 5495 // Update result if necessary. 5496 if (isMax) { 5497 max32(result, temp3, result); 5498 } else { 5499 min32(result, temp3, result); 5500 } 5501 5502 jump(&loop); 5503 bind(&done); 5504 } 5505 5506 void MacroAssembler::minMaxArrayNumber(Register array, FloatRegister result, 5507 FloatRegister floatTemp, Register temp1, 5508 Register temp2, bool isMax, 5509 Label* fail) { 5510 // array must be a packed array. Load its elements. 5511 Register elements = temp1; 5512 loadPtr(Address(array, NativeObject::offsetOfElements()), elements); 5513 5514 // Load the length and check if the array is empty. 5515 Label isEmpty; 5516 Address lengthAddr(elements, ObjectElements::offsetOfInitializedLength()); 5517 load32(lengthAddr, temp2); 5518 branchTest32(Assembler::Zero, temp2, temp2, &isEmpty); 5519 5520 // Compute the address of the last element. 5521 Register elementsEnd = temp2; 5522 BaseObjectElementIndex elementsEndAddr(elements, temp2, 5523 -int32_t(sizeof(Value))); 5524 computeEffectiveAddress(elementsEndAddr, elementsEnd); 5525 5526 // Load the first element into result. 5527 ensureDouble(Address(elements, 0), result, fail); 5528 5529 Label loop, done; 5530 bind(&loop); 5531 5532 // Check whether we're done. 5533 branchPtr(Assembler::Equal, elements, elementsEnd, &done); 5534 5535 // If not, advance to the next element and load it into floatTemp. 5536 addPtr(Imm32(sizeof(Value)), elements); 5537 ensureDouble(Address(elements, 0), floatTemp, fail); 5538 5539 // Update result if necessary. 5540 if (isMax) { 5541 maxDouble(floatTemp, result, /* handleNaN = */ true); 5542 } else { 5543 minDouble(floatTemp, result, /* handleNaN = */ true); 5544 } 5545 jump(&loop); 5546 5547 // With no arguments, min/max return +Infinity/-Infinity respectively. 5548 bind(&isEmpty); 5549 if (isMax) { 5550 loadConstantDouble(mozilla::NegativeInfinity<double>(), result); 5551 } else { 5552 loadConstantDouble(mozilla::PositiveInfinity<double>(), result); 5553 } 5554 5555 bind(&done); 5556 } 5557 5558 void MacroAssembler::loadRegExpLastIndex(Register regexp, Register string, 5559 Register lastIndex, 5560 Label* notFoundZeroLastIndex) { 5561 Address flagsSlot(regexp, RegExpObject::offsetOfFlags()); 5562 Address lastIndexSlot(regexp, RegExpObject::offsetOfLastIndex()); 5563 Address stringLength(string, JSString::offsetOfLength()); 5564 5565 Label notGlobalOrSticky, loadedLastIndex; 5566 5567 branchTest32(Assembler::Zero, flagsSlot, 5568 Imm32(JS::RegExpFlag::Global | JS::RegExpFlag::Sticky), 5569 ¬GlobalOrSticky); 5570 { 5571 // It's a global or sticky regular expression. Emit the following code: 5572 // 5573 // lastIndex = regexp.lastIndex 5574 // if lastIndex > string.length: 5575 // jump to notFoundZeroLastIndex (skip the regexp match/test operation) 5576 // 5577 // The `notFoundZeroLastIndex` code should set regexp.lastIndex to 0 and 5578 // treat this as a not-found result. 5579 // 5580 // See steps 5-8 in js::RegExpBuiltinExec. 5581 // 5582 // Earlier guards must have ensured regexp.lastIndex is a non-negative 5583 // integer. 5584 #ifdef DEBUG 5585 { 5586 Label ok; 5587 branchTestInt32(Assembler::Equal, lastIndexSlot, &ok); 5588 assumeUnreachable("Expected int32 value for lastIndex"); 5589 bind(&ok); 5590 } 5591 #endif 5592 unboxInt32(lastIndexSlot, lastIndex); 5593 #ifdef DEBUG 5594 { 5595 Label ok; 5596 branchTest32(Assembler::NotSigned, lastIndex, lastIndex, &ok); 5597 assumeUnreachable("Expected non-negative lastIndex"); 5598 bind(&ok); 5599 } 5600 #endif 5601 branch32(Assembler::Below, stringLength, lastIndex, notFoundZeroLastIndex); 5602 jump(&loadedLastIndex); 5603 } 5604 5605 bind(¬GlobalOrSticky); 5606 move32(Imm32(0), lastIndex); 5607 5608 bind(&loadedLastIndex); 5609 } 5610 5611 void MacroAssembler::loadAndClearRegExpSearcherLastLimit(Register result, 5612 Register scratch) { 5613 MOZ_ASSERT(result != scratch); 5614 5615 loadJSContext(scratch); 5616 5617 Address limitField(scratch, JSContext::offsetOfRegExpSearcherLastLimit()); 5618 load32(limitField, result); 5619 5620 #ifdef DEBUG 5621 Label ok; 5622 branch32(Assembler::NotEqual, result, Imm32(RegExpSearcherLastLimitSentinel), 5623 &ok); 5624 assumeUnreachable("Unexpected sentinel for regExpSearcherLastLimit"); 5625 bind(&ok); 5626 store32(Imm32(RegExpSearcherLastLimitSentinel), limitField); 5627 #endif 5628 } 5629 5630 void MacroAssembler::loadParsedRegExpShared(Register regexp, Register result, 5631 Label* unparsed) { 5632 Address sharedSlot(regexp, RegExpObject::offsetOfShared()); 5633 branchTestUndefined(Assembler::Equal, sharedSlot, unparsed); 5634 unboxNonDouble(sharedSlot, result, JSVAL_TYPE_PRIVATE_GCTHING); 5635 5636 static_assert(sizeof(RegExpShared::Kind) == sizeof(uint32_t)); 5637 branch32(Assembler::Equal, Address(result, RegExpShared::offsetOfKind()), 5638 Imm32(int32_t(RegExpShared::Kind::Unparsed)), unparsed); 5639 } 5640 5641 // =============================================================== 5642 // Branch functions 5643 5644 void MacroAssembler::loadFunctionLength(Register func, 5645 Register funFlagsAndArgCount, 5646 Register output, Label* slowPath) { 5647 #ifdef DEBUG 5648 { 5649 // These flags should already have been checked by caller. 5650 Label ok; 5651 uint32_t FlagsToCheck = 5652 FunctionFlags::SELFHOSTLAZY | FunctionFlags::RESOLVED_LENGTH; 5653 branchTest32(Assembler::Zero, funFlagsAndArgCount, Imm32(FlagsToCheck), 5654 &ok); 5655 assumeUnreachable("The function flags should already have been checked."); 5656 bind(&ok); 5657 } 5658 #endif // DEBUG 5659 5660 // NOTE: `funFlagsAndArgCount` and `output` must be allowed to alias. 5661 5662 // Load the target function's length. 5663 Label isInterpreted, lengthLoaded; 5664 branchTest32(Assembler::NonZero, funFlagsAndArgCount, 5665 Imm32(FunctionFlags::BASESCRIPT), &isInterpreted); 5666 { 5667 // The length property of a native function stored with the flags. 5668 rshift32(Imm32(JSFunction::ArgCountShift), funFlagsAndArgCount, output); 5669 jump(&lengthLoaded); 5670 } 5671 bind(&isInterpreted); 5672 { 5673 // Load the length property of an interpreted function. 5674 loadPrivate(Address(func, JSFunction::offsetOfJitInfoOrScript()), output); 5675 loadPtr(Address(output, JSScript::offsetOfSharedData()), output); 5676 branchTestPtr(Assembler::Zero, output, output, slowPath); 5677 loadPtr(Address(output, SharedImmutableScriptData::offsetOfISD()), output); 5678 load16ZeroExtend(Address(output, ImmutableScriptData::offsetOfFunLength()), 5679 output); 5680 } 5681 bind(&lengthLoaded); 5682 } 5683 5684 void MacroAssembler::loadFunctionName(Register func, Register output, 5685 ImmGCPtr emptyString, Label* slowPath) { 5686 MOZ_ASSERT(func != output); 5687 5688 // Get the JSFunction flags. 5689 load32(Address(func, JSFunction::offsetOfFlagsAndArgCount()), output); 5690 5691 // If the name was previously resolved, the name property may be shadowed. 5692 // If the function is an accessor with lazy name, AtomSlot contains the 5693 // unprefixed name. 5694 branchTest32( 5695 Assembler::NonZero, output, 5696 Imm32(FunctionFlags::RESOLVED_NAME | FunctionFlags::LAZY_ACCESSOR_NAME), 5697 slowPath); 5698 5699 Label noName, done; 5700 branchTest32(Assembler::NonZero, output, 5701 Imm32(FunctionFlags::HAS_GUESSED_ATOM), &noName); 5702 5703 Address atomAddr(func, JSFunction::offsetOfAtom()); 5704 branchTestUndefined(Assembler::Equal, atomAddr, &noName); 5705 unboxString(atomAddr, output); 5706 jump(&done); 5707 5708 { 5709 bind(&noName); 5710 5711 // An absent name property defaults to the empty string. 5712 movePtr(emptyString, output); 5713 } 5714 5715 bind(&done); 5716 } 5717 5718 void MacroAssembler::assertFunctionIsExtended(Register func) { 5719 #ifdef DEBUG 5720 Label extended; 5721 branchTestFunctionFlags(func, FunctionFlags::EXTENDED, Assembler::NonZero, 5722 &extended); 5723 assumeUnreachable("Function is not extended"); 5724 bind(&extended); 5725 #endif 5726 } 5727 5728 void MacroAssembler::branchTestType(Condition cond, Register tag, 5729 JSValueType type, Label* label) { 5730 switch (type) { 5731 case JSVAL_TYPE_DOUBLE: 5732 branchTestDouble(cond, tag, label); 5733 break; 5734 case JSVAL_TYPE_INT32: 5735 branchTestInt32(cond, tag, label); 5736 break; 5737 case JSVAL_TYPE_BOOLEAN: 5738 branchTestBoolean(cond, tag, label); 5739 break; 5740 case JSVAL_TYPE_UNDEFINED: 5741 branchTestUndefined(cond, tag, label); 5742 break; 5743 case JSVAL_TYPE_NULL: 5744 branchTestNull(cond, tag, label); 5745 break; 5746 case JSVAL_TYPE_MAGIC: 5747 branchTestMagic(cond, tag, label); 5748 break; 5749 case JSVAL_TYPE_STRING: 5750 branchTestString(cond, tag, label); 5751 break; 5752 case JSVAL_TYPE_SYMBOL: 5753 branchTestSymbol(cond, tag, label); 5754 break; 5755 case JSVAL_TYPE_BIGINT: 5756 branchTestBigInt(cond, tag, label); 5757 break; 5758 case JSVAL_TYPE_OBJECT: 5759 branchTestObject(cond, tag, label); 5760 break; 5761 default: 5762 MOZ_CRASH("Unexpected value type"); 5763 } 5764 } 5765 5766 void MacroAssembler::branchTestObjShapeListImpl( 5767 Register obj, Register shapeElements, size_t itemSize, 5768 Register shapeScratch, Register endScratch, Register spectreScratch, 5769 Label* fail) { 5770 bool needSpectreMitigations = spectreScratch != InvalidReg; 5771 5772 Label done; 5773 5774 // Load the object's shape pointer into shapeScratch, and prepare to compare 5775 // it with the shapes in the list. The shapes are stored as private values so 5776 // we can compare directly. 5777 loadPtr(Address(obj, JSObject::offsetOfShape()), shapeScratch); 5778 5779 // Compute end pointer. 5780 Address lengthAddr(shapeElements, 5781 ObjectElements::offsetOfInitializedLength()); 5782 load32(lengthAddr, endScratch); 5783 branch32(Assembler::Equal, endScratch, Imm32(0), fail); 5784 BaseObjectElementIndex endPtrAddr(shapeElements, endScratch); 5785 computeEffectiveAddress(endPtrAddr, endScratch); 5786 5787 Label loop; 5788 bind(&loop); 5789 5790 // Compare the object's shape with a shape from the list. Note that on 64-bit 5791 // this includes the tag bits, but on 32-bit we only compare the low word of 5792 // the value. This is fine because the list of shapes is never exposed and the 5793 // tag is guaranteed to be PrivateGCThing. 5794 if (needSpectreMitigations) { 5795 move32(Imm32(0), spectreScratch); 5796 } 5797 branchPtr(Assembler::Equal, Address(shapeElements, 0), shapeScratch, &done); 5798 if (needSpectreMitigations) { 5799 spectreMovePtr(Assembler::Equal, spectreScratch, obj); 5800 } 5801 5802 // Advance to next shape and loop if not finished. 5803 addPtr(Imm32(itemSize), shapeElements); 5804 branchPtr(Assembler::Below, shapeElements, endScratch, &loop); 5805 5806 jump(fail); 5807 bind(&done); 5808 } 5809 5810 void MacroAssembler::branchTestObjShapeList( 5811 Register obj, Register shapeElements, Register shapeScratch, 5812 Register endScratch, Register spectreScratch, Label* fail) { 5813 branchTestObjShapeListImpl(obj, shapeElements, sizeof(Value), shapeScratch, 5814 endScratch, spectreScratch, fail); 5815 } 5816 5817 void MacroAssembler::branchTestObjShapeListSetOffset( 5818 Register obj, Register shapeElements, Register offset, 5819 Register shapeScratch, Register endScratch, Register spectreScratch, 5820 Label* fail) { 5821 branchTestObjShapeListImpl(obj, shapeElements, 2 * sizeof(Value), 5822 shapeScratch, endScratch, spectreScratch, fail); 5823 5824 // The shapeElements register points to the matched shape (if found). 5825 // The corresponding offset is saved in the array as the next value. 5826 load32(Address(shapeElements, sizeof(Value)), offset); 5827 } 5828 5829 void MacroAssembler::branchTestObjCompartment(Condition cond, Register obj, 5830 const Address& compartment, 5831 Register scratch, Label* label) { 5832 MOZ_ASSERT(obj != scratch); 5833 loadPtr(Address(obj, JSObject::offsetOfShape()), scratch); 5834 loadPtr(Address(scratch, Shape::offsetOfBaseShape()), scratch); 5835 loadPtr(Address(scratch, BaseShape::offsetOfRealm()), scratch); 5836 loadPtr(Address(scratch, Realm::offsetOfCompartment()), scratch); 5837 branchPtr(cond, compartment, scratch, label); 5838 } 5839 5840 void MacroAssembler::branchTestObjCompartment( 5841 Condition cond, Register obj, const JS::Compartment* compartment, 5842 Register scratch, Label* label) { 5843 MOZ_ASSERT(obj != scratch); 5844 loadPtr(Address(obj, JSObject::offsetOfShape()), scratch); 5845 loadPtr(Address(scratch, Shape::offsetOfBaseShape()), scratch); 5846 loadPtr(Address(scratch, BaseShape::offsetOfRealm()), scratch); 5847 loadPtr(Address(scratch, Realm::offsetOfCompartment()), scratch); 5848 branchPtr(cond, scratch, ImmPtr(compartment), label); 5849 } 5850 5851 void MacroAssembler::branchIfNonNativeObj(Register obj, Register scratch, 5852 Label* label) { 5853 loadPtr(Address(obj, JSObject::offsetOfShape()), scratch); 5854 branchTest32(Assembler::Zero, 5855 Address(scratch, Shape::offsetOfImmutableFlags()), 5856 Imm32(Shape::isNativeBit()), label); 5857 } 5858 5859 void MacroAssembler::branchIfObjectNotExtensible(Register obj, Register scratch, 5860 Label* label) { 5861 loadPtr(Address(obj, JSObject::offsetOfShape()), scratch); 5862 5863 // Spectre-style checks are not needed here because we do not interpret data 5864 // based on this check. 5865 static_assert(sizeof(ObjectFlags) == sizeof(uint32_t)); 5866 load32(Address(scratch, Shape::offsetOfObjectFlags()), scratch); 5867 branchTest32(Assembler::NonZero, scratch, 5868 Imm32(uint32_t(ObjectFlag::NotExtensible)), label); 5869 } 5870 5871 void MacroAssembler::branchTestObjectNeedsProxyResultValidation( 5872 Condition cond, Register obj, Register scratch, Label* label) { 5873 MOZ_ASSERT(cond == Assembler::Zero || cond == Assembler::NonZero); 5874 5875 Label done; 5876 Label* doValidation = cond == NonZero ? label : &done; 5877 Label* skipValidation = cond == NonZero ? &done : label; 5878 5879 loadPtr(Address(obj, JSObject::offsetOfShape()), scratch); 5880 branchTest32(Assembler::Zero, 5881 Address(scratch, Shape::offsetOfImmutableFlags()), 5882 Imm32(Shape::isNativeBit()), doValidation); 5883 static_assert(sizeof(ObjectFlags) == sizeof(uint32_t)); 5884 load32(Address(scratch, Shape::offsetOfObjectFlags()), scratch); 5885 branchTest32(Assembler::NonZero, scratch, 5886 Imm32(uint32_t(ObjectFlag::NeedsProxyGetSetResultValidation)), 5887 doValidation); 5888 5889 loadPtr(Address(obj, JSObject::offsetOfShape()), scratch); 5890 loadPtr(Address(scratch, Shape::offsetOfBaseShape()), scratch); 5891 loadPtr(Address(scratch, BaseShape::offsetOfClasp()), scratch); 5892 loadPtr(Address(scratch, offsetof(JSClass, cOps)), scratch); 5893 branchTestPtr(Assembler::Zero, scratch, scratch, skipValidation); 5894 loadPtr(Address(scratch, offsetof(JSClassOps, resolve)), scratch); 5895 branchTestPtr(Assembler::NonZero, scratch, scratch, doValidation); 5896 bind(&done); 5897 } 5898 5899 void MacroAssembler::wasmTrap(wasm::Trap trap, 5900 const wasm::TrapSiteDesc& trapSiteDesc) { 5901 FaultingCodeOffset fco = wasmTrapInstruction(); 5902 MOZ_ASSERT_IF(!oom(), 5903 currentOffset() - fco.get() == WasmTrapInstructionLength); 5904 5905 append(trap, wasm::TrapMachineInsn::OfficialUD, fco.get(), trapSiteDesc); 5906 } 5907 5908 uint32_t MacroAssembler::wasmReserveStackChecked(uint32_t amount, Label* fail) { 5909 Register scratch1 = ABINonArgReg0; 5910 Register scratch2 = ABINonArgReg1; 5911 loadPtr(Address(InstanceReg, wasm::Instance::offsetOfCx()), scratch2); 5912 5913 if (amount > MAX_UNCHECKED_LEAF_FRAME_SIZE) { 5914 // The frame is large. Don't bump sp until after the stack limit check so 5915 // that the trap handler isn't called with a wild sp. 5916 moveStackPtrTo(scratch1); 5917 branchPtr(Assembler::Below, scratch1, Imm32(amount), fail); 5918 subPtr(Imm32(amount), scratch1); 5919 branchPtr(Assembler::AboveOrEqual, 5920 Address(scratch2, JSContext::offsetOfWasm() + 5921 wasm::Context::offsetOfStackLimit()), 5922 scratch1, fail); 5923 reserveStack(amount); 5924 // The stack amount was reserved after branching to the fail label. 5925 return 0; 5926 } 5927 5928 reserveStack(amount); 5929 branchStackPtrRhs(Assembler::AboveOrEqual, 5930 Address(scratch2, JSContext::offsetOfWasm() + 5931 wasm::Context::offsetOfStackLimit()), 5932 fail); 5933 // The stack amount was reserved before branching to the fail label. 5934 return amount; 5935 } 5936 5937 static void MoveDataBlock(MacroAssembler& masm, Register base, int32_t from, 5938 int32_t to, uint32_t size) { 5939 MOZ_ASSERT(base != masm.getStackPointer()); 5940 if (from == to || size == 0) { 5941 return; // noop 5942 } 5943 5944 #ifdef JS_CODEGEN_ARM64 5945 vixl::UseScratchRegisterScope temps(&masm); 5946 const Register scratch = temps.AcquireX().asUnsized(); 5947 #elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_X86) 5948 static constexpr Register scratch = ABINonArgReg0; 5949 masm.push(scratch); 5950 #elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || \ 5951 defined(JS_CODEGEN_RISCV64) 5952 UseScratchRegisterScope temps(masm); 5953 Register scratch = temps.Acquire(); 5954 #elif !defined(JS_CODEGEN_NONE) 5955 const Register scratch = ScratchReg; 5956 #else 5957 const Register scratch = InvalidReg; 5958 #endif 5959 5960 if (to < from) { 5961 for (uint32_t i = 0; i < size; i += sizeof(void*)) { 5962 masm.loadPtr(Address(base, from + i), scratch); 5963 masm.storePtr(scratch, Address(base, to + i)); 5964 } 5965 } else { 5966 for (uint32_t i = size; i > 0;) { 5967 i -= sizeof(void*); 5968 masm.loadPtr(Address(base, from + i), scratch); 5969 masm.storePtr(scratch, Address(base, to + i)); 5970 } 5971 } 5972 5973 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_X86) 5974 masm.pop(scratch); 5975 #endif 5976 } 5977 5978 struct ReturnCallTrampolineData { 5979 #ifdef JS_CODEGEN_ARM 5980 uint32_t trampolineOffset; 5981 #else 5982 CodeLabel trampoline; 5983 #endif 5984 }; 5985 5986 static ReturnCallTrampolineData MakeReturnCallTrampoline(MacroAssembler& masm) { 5987 uint32_t savedPushed = masm.framePushed(); 5988 5989 ReturnCallTrampolineData data; 5990 5991 { 5992 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) 5993 AutoForbidPoolsAndNops afp(&masm, 1); 5994 #elif defined(JS_CODEGEN_RISCV64) 5995 BlockTrampolinePoolScope block_trampoline_pool(&masm, 1); 5996 #endif 5997 5998 // Build simple trampoline code: load the instance slot from the frame, 5999 // restore FP, and return to prevous caller. 6000 #ifdef JS_CODEGEN_ARM 6001 data.trampolineOffset = masm.currentOffset(); 6002 #else 6003 masm.bind(&data.trampoline); 6004 #endif 6005 6006 masm.setFramePushed(AlignBytes( 6007 wasm::FrameWithInstances::sizeOfInstanceFieldsAndShadowStack(), 6008 WasmStackAlignment)); 6009 6010 masm.wasmMarkCallAsSlow(); 6011 } 6012 6013 masm.loadPtr( 6014 Address(masm.getStackPointer(), WasmCallerInstanceOffsetBeforeCall), 6015 InstanceReg); 6016 masm.switchToWasmInstanceRealm(ABINonArgReturnReg0, ABINonArgReturnReg1); 6017 masm.moveToStackPtr(FramePointer); 6018 #ifdef JS_CODEGEN_ARM64 6019 masm.pop(FramePointer, lr); 6020 masm.append(wasm::CodeRangeUnwindInfo::UseFpLr, masm.currentOffset()); 6021 masm.Mov(PseudoStackPointer64, vixl::sp); 6022 masm.abiret(); 6023 #elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) 6024 masm.loadPtr(Address(FramePointer, wasm::Frame::returnAddressOffset()), ra); 6025 masm.loadPtr(Address(FramePointer, wasm::Frame::callerFPOffset()), 6026 FramePointer); 6027 masm.append(wasm::CodeRangeUnwindInfo::UseFpLr, masm.currentOffset()); 6028 masm.addToStackPtr(Imm32(sizeof(wasm::Frame))); 6029 masm.abiret(); 6030 #else 6031 masm.pop(FramePointer); 6032 masm.append(wasm::CodeRangeUnwindInfo::UseFp, masm.currentOffset()); 6033 masm.ret(); 6034 #endif 6035 6036 masm.append(wasm::CodeRangeUnwindInfo::Normal, masm.currentOffset()); 6037 masm.setFramePushed(savedPushed); 6038 return data; 6039 } 6040 6041 // CollapseWasmFrame methods merge frames fields: callee parameters, instance 6042 // slots, and caller RA. See the diagram below. The C0 is the previous caller, 6043 // the C1 is the caller of the return call, and the C2 is the callee. 6044 // 6045 // +-------------------+ +--------------------+ 6046 // |C0 instance slots | |C0 instance slots | 6047 // +-------------------+ -+ +--------------------+ -+ 6048 // | RA | | | RA | | 6049 // +-------------------+ | C0 +--------------------+ |C0 6050 // | FP | v | FP | v 6051 // +-------------------+ +--------------------+ 6052 // |C0 private frame | |C0 private frame | 6053 // +-------------------+ +--------------------+ 6054 // |C1 results area | |C1/C2 results area | 6055 // +-------------------+ +--------------------+ 6056 // |C1 parameters | |? trampoline frame | 6057 // +-------------------+ +--------------------+ 6058 // |C1 instance slots | |C2 parameters | 6059 // +-------------------+ -+ +--------------------+ 6060 // |C0 RA | | |C2 instance slots’ | 6061 // +-------------------+ | C1 +--------------------+ -+ 6062 // |C0 FP | v |C0 RA’ | | 6063 // +-------------------+ +--------------------+ | C2 6064 // |C1 private frame | |C0 FP’ | v 6065 // +-------------------+ +--------------------+ <= start of C2 6066 // |C2 parameters | 6067 // +-------------------+ 6068 // |C2 instance slots | 6069 // +-------------------+ <= call C2 6070 // 6071 // The C2 parameters are moved in place of the C1 parameters, and the 6072 // C1 frame data is removed. The instance slots, return address, and 6073 // frame pointer to the C0 callsite are saved or adjusted. 6074 // 6075 // For cross-instance calls, the trampoline frame will be introduced 6076 // if the C0 callsite has no ability to restore instance registers and realm. 6077 6078 static void CollapseWasmFrameFast(MacroAssembler& masm, 6079 const ReturnCallAdjustmentInfo& retCallInfo) { 6080 uint32_t framePushedAtStart = masm.framePushed(); 6081 static_assert(sizeof(wasm::Frame) == 2 * sizeof(void*)); 6082 6083 // The instance slots + stack arguments are expected to be padded and 6084 // aligned to the WasmStackAlignment boundary. There is no data expected 6085 // in the padded region, such as results stack area or locals, to avoid 6086 // unwanted stack growth. 6087 uint32_t newSlotsAndStackArgBytes = 6088 AlignBytes(retCallInfo.newSlotsAndStackArgBytes, WasmStackAlignment); 6089 uint32_t oldSlotsAndStackArgBytes = 6090 AlignBytes(retCallInfo.oldSlotsAndStackArgBytes, WasmStackAlignment); 6091 6092 static constexpr Register tempForCaller = WasmTailCallInstanceScratchReg; 6093 static constexpr Register tempForFP = WasmTailCallFPScratchReg; 6094 static constexpr Register tempForRA = WasmTailCallRAScratchReg; 6095 #ifndef JS_USE_LINK_REGISTER 6096 masm.push(tempForRA); 6097 #endif 6098 6099 // Load the FP, RA, and instance slots into registers to preserve them while 6100 // the new frame is collapsed over the current one. 6101 masm.loadPtr(Address(FramePointer, wasm::Frame::callerFPOffset()), tempForFP); 6102 masm.loadPtr(Address(FramePointer, wasm::Frame::returnAddressOffset()), 6103 tempForRA); 6104 masm.append(wasm::CodeRangeUnwindInfo::RestoreFpRa, masm.currentOffset()); 6105 bool copyCallerSlot = oldSlotsAndStackArgBytes != newSlotsAndStackArgBytes; 6106 if (copyCallerSlot) { 6107 masm.loadPtr( 6108 Address(FramePointer, wasm::FrameWithInstances::callerInstanceOffset()), 6109 tempForCaller); 6110 } 6111 6112 // Copy parameters data, ignoring shadow data and instance slots. 6113 // Make all offsets relative to the FramePointer. 6114 int32_t newArgSrc = -framePushedAtStart; 6115 int32_t newArgDest = 6116 sizeof(wasm::Frame) + oldSlotsAndStackArgBytes - newSlotsAndStackArgBytes; 6117 const uint32_t SlotsSize = 6118 wasm::FrameWithInstances::sizeOfInstanceFieldsAndShadowStack(); 6119 MoveDataBlock(masm, FramePointer, newArgSrc + SlotsSize, 6120 newArgDest + SlotsSize, 6121 retCallInfo.newSlotsAndStackArgBytes - SlotsSize); 6122 6123 // Copy caller instance slots from the current frame. 6124 if (copyCallerSlot) { 6125 masm.storePtr( 6126 tempForCaller, 6127 Address(FramePointer, newArgDest + WasmCallerInstanceOffsetBeforeCall)); 6128 } 6129 6130 // Store current instance as the new callee instance slot. 6131 masm.storePtr( 6132 InstanceReg, 6133 Address(FramePointer, newArgDest + WasmCalleeInstanceOffsetBeforeCall)); 6134 6135 #ifdef JS_USE_LINK_REGISTER 6136 // RA is already in its place, just move stack. 6137 masm.addToStackPtr(Imm32(framePushedAtStart + newArgDest)); 6138 #else 6139 // Push RA to new frame: store RA, restore temp, and move stack. 6140 int32_t newFrameOffset = newArgDest - sizeof(wasm::Frame); 6141 masm.storePtr(tempForRA, 6142 Address(FramePointer, 6143 newFrameOffset + wasm::Frame::returnAddressOffset())); 6144 // Restore tempForRA, but keep RA on top of the stack. 6145 // There is no non-locking exchange instruction between register and memory. 6146 // Using tempForCaller as scratch register. 6147 masm.loadPtr(Address(masm.getStackPointer(), 0), tempForCaller); 6148 masm.storePtr(tempForRA, Address(masm.getStackPointer(), 0)); 6149 masm.mov(tempForCaller, tempForRA); 6150 masm.append(wasm::CodeRangeUnwindInfo::RestoreFp, masm.currentOffset()); 6151 masm.addToStackPtr(Imm32(framePushedAtStart + newFrameOffset + 6152 wasm::Frame::returnAddressOffset() + sizeof(void*))); 6153 #endif 6154 6155 masm.movePtr(tempForFP, FramePointer); 6156 // Setting framePushed to pre-collapse state, to properly set that in the 6157 // following code. 6158 masm.setFramePushed(framePushedAtStart); 6159 } 6160 6161 static void CollapseWasmFrameSlow(MacroAssembler& masm, 6162 const ReturnCallAdjustmentInfo& retCallInfo, 6163 wasm::CallSiteDesc desc, 6164 ReturnCallTrampolineData data) { 6165 uint32_t framePushedAtStart = masm.framePushed(); 6166 static constexpr Register tempForCaller = WasmTailCallInstanceScratchReg; 6167 static constexpr Register tempForFP = WasmTailCallFPScratchReg; 6168 static constexpr Register tempForRA = WasmTailCallRAScratchReg; 6169 6170 static_assert(sizeof(wasm::Frame) == 2 * sizeof(void*)); 6171 6172 // The hidden frame will "break" after wasm::Frame data fields. 6173 // Calculate sum of wasm stack alignment before and after the break as 6174 // the size to reserve. 6175 const uint32_t HiddenFrameAfterSize = 6176 AlignBytes(wasm::FrameWithInstances::sizeOfInstanceFieldsAndShadowStack(), 6177 WasmStackAlignment); 6178 const uint32_t HiddenFrameSize = 6179 AlignBytes(sizeof(wasm::Frame), WasmStackAlignment) + 6180 HiddenFrameAfterSize; 6181 6182 // If it is not slow, prepare two frame: one is regular wasm frame, and 6183 // another one is hidden. The hidden frame contains one instance slots 6184 // for unwind and recovering pinned registers. 6185 // The instance slots + stack arguments are expected to be padded and 6186 // aligned to the WasmStackAlignment boundary. There is no data expected 6187 // in the padded region, such as results stack area or locals, to avoid 6188 // unwanted stack growth. 6189 // The Hidden frame will be inserted with this constraint too. 6190 uint32_t newSlotsAndStackArgBytes = 6191 AlignBytes(retCallInfo.newSlotsAndStackArgBytes, WasmStackAlignment); 6192 uint32_t oldSlotsAndStackArgBytes = 6193 AlignBytes(retCallInfo.oldSlotsAndStackArgBytes, WasmStackAlignment); 6194 6195 // Make all offsets relative to the FramePointer. 6196 int32_t newArgSrc = -framePushedAtStart; 6197 int32_t newArgDest = sizeof(wasm::Frame) + oldSlotsAndStackArgBytes - 6198 HiddenFrameSize - newSlotsAndStackArgBytes; 6199 int32_t hiddenFrameArgsDest = 6200 sizeof(wasm::Frame) + oldSlotsAndStackArgBytes - HiddenFrameAfterSize; 6201 6202 // It will be possible to overwrite data (on the top of the stack) due to 6203 // the added hidden frame, reserve needed space. 6204 uint32_t reserved = newArgDest - int32_t(sizeof(void*)) < newArgSrc 6205 ? newArgSrc - newArgDest + sizeof(void*) 6206 : 0; 6207 masm.reserveStack(reserved); 6208 6209 #ifndef JS_USE_LINK_REGISTER 6210 masm.push(tempForRA); 6211 #endif 6212 6213 // Load FP, RA and instance slots to preserve them from being overwritten. 6214 masm.loadPtr(Address(FramePointer, wasm::Frame::callerFPOffset()), tempForFP); 6215 masm.loadPtr(Address(FramePointer, wasm::Frame::returnAddressOffset()), 6216 tempForRA); 6217 masm.append(wasm::CodeRangeUnwindInfo::RestoreFpRa, masm.currentOffset()); 6218 masm.loadPtr( 6219 Address(FramePointer, newArgSrc + WasmCallerInstanceOffsetBeforeCall), 6220 tempForCaller); 6221 6222 // Copy parameters data, ignoring shadow data and instance slots. 6223 const uint32_t SlotsSize = 6224 wasm::FrameWithInstances::sizeOfInstanceFieldsAndShadowStack(); 6225 MoveDataBlock(masm, FramePointer, newArgSrc + SlotsSize, 6226 newArgDest + SlotsSize, 6227 retCallInfo.newSlotsAndStackArgBytes - SlotsSize); 6228 6229 // Form hidden frame for trampoline. 6230 int32_t newFPOffset = hiddenFrameArgsDest - sizeof(wasm::Frame); 6231 masm.storePtr( 6232 tempForRA, 6233 Address(FramePointer, newFPOffset + wasm::Frame::returnAddressOffset())); 6234 6235 // Copy original FP. 6236 masm.storePtr( 6237 tempForFP, 6238 Address(FramePointer, newFPOffset + wasm::Frame::callerFPOffset())); 6239 6240 // Set up instance slots. 6241 masm.storePtr( 6242 tempForCaller, 6243 Address(FramePointer, 6244 newFPOffset + wasm::FrameWithInstances::calleeInstanceOffset())); 6245 masm.storePtr( 6246 tempForCaller, 6247 Address(FramePointer, newArgDest + WasmCallerInstanceOffsetBeforeCall)); 6248 masm.storePtr( 6249 InstanceReg, 6250 Address(FramePointer, newArgDest + WasmCalleeInstanceOffsetBeforeCall)); 6251 6252 #ifdef JS_CODEGEN_ARM 6253 // ARM has no CodeLabel -- calculate PC directly. 6254 masm.mov(pc, tempForRA); 6255 masm.computeEffectiveAddress( 6256 Address(tempForRA, 6257 int32_t(data.trampolineOffset - masm.currentOffset() - 4)), 6258 tempForRA); 6259 masm.append(desc, CodeOffset(data.trampolineOffset)); 6260 #else 6261 6262 # if defined(JS_CODEGEN_MIPS64) 6263 // intermediate values in ra can break the unwinder. 6264 masm.mov(&data.trampoline, ScratchRegister); 6265 // thus, modify ra in only one instruction. 6266 masm.mov(ScratchRegister, tempForRA); 6267 # elif defined(JS_CODEGEN_LOONG64) 6268 // intermediate values in ra can break the unwinder. 6269 masm.mov(&data.trampoline, SavedScratchRegister); 6270 // thus, modify ra in only one instruction. 6271 masm.mov(SavedScratchRegister, tempForRA); 6272 # else 6273 masm.mov(&data.trampoline, tempForRA); 6274 # endif 6275 6276 masm.addCodeLabel(data.trampoline); 6277 // Add slow trampoline callsite description, to be annotated in 6278 // stack/frame iterators. 6279 masm.append(desc, *data.trampoline.target()); 6280 #endif 6281 6282 #ifdef JS_USE_LINK_REGISTER 6283 masm.freeStack(reserved); 6284 // RA is already in its place, just move stack. 6285 masm.addToStackPtr(Imm32(framePushedAtStart + newArgDest)); 6286 #else 6287 // Push RA to new frame: store RA, restore temp, and move stack. 6288 int32_t newFrameOffset = newArgDest - sizeof(wasm::Frame); 6289 masm.storePtr(tempForRA, 6290 Address(FramePointer, 6291 newFrameOffset + wasm::Frame::returnAddressOffset())); 6292 // Restore tempForRA, but keep RA on top of the stack. 6293 // There is no non-locking exchange instruction between register and memory. 6294 // Using tempForCaller as scratch register. 6295 masm.loadPtr(Address(masm.getStackPointer(), 0), tempForCaller); 6296 masm.storePtr(tempForRA, Address(masm.getStackPointer(), 0)); 6297 masm.mov(tempForCaller, tempForRA); 6298 masm.append(wasm::CodeRangeUnwindInfo::RestoreFp, masm.currentOffset()); 6299 masm.addToStackPtr(Imm32(framePushedAtStart + newFrameOffset + 6300 wasm::Frame::returnAddressOffset() + reserved + 6301 sizeof(void*))); 6302 #endif 6303 6304 // Point FramePointer to hidden frame. 6305 masm.computeEffectiveAddress(Address(FramePointer, newFPOffset), 6306 FramePointer); 6307 // Setting framePushed to pre-collapse state, to properly set that in the 6308 // following code. 6309 masm.setFramePushed(framePushedAtStart); 6310 } 6311 6312 void MacroAssembler::wasmCollapseFrameFast( 6313 const ReturnCallAdjustmentInfo& retCallInfo) { 6314 CollapseWasmFrameFast(*this, retCallInfo); 6315 } 6316 6317 void MacroAssembler::wasmCollapseFrameSlow( 6318 const ReturnCallAdjustmentInfo& retCallInfo, wasm::CallSiteDesc desc) { 6319 static constexpr Register temp1 = ABINonArgReg1; 6320 static constexpr Register temp2 = ABINonArgReg3; 6321 6322 // Check if RA has slow marker. If there is no marker, generate a trampoline 6323 // frame to restore register state when this tail call returns. 6324 6325 Label slow, done; 6326 loadPtr(Address(FramePointer, wasm::Frame::returnAddressOffset()), temp1); 6327 wasmCheckSlowCallsite(temp1, &slow, temp1, temp2); 6328 CollapseWasmFrameFast(*this, retCallInfo); 6329 jump(&done); 6330 append(wasm::CodeRangeUnwindInfo::Normal, currentOffset()); 6331 6332 ReturnCallTrampolineData data = MakeReturnCallTrampoline(*this); 6333 6334 bind(&slow); 6335 CollapseWasmFrameSlow(*this, retCallInfo, desc, data); 6336 6337 bind(&done); 6338 } 6339 6340 CodeOffset MacroAssembler::wasmCallImport(const wasm::CallSiteDesc& desc, 6341 const wasm::CalleeDesc& callee) { 6342 storePtr(InstanceReg, 6343 Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall)); 6344 6345 // Load the callee, before the caller's registers are clobbered. 6346 uint32_t instanceDataOffset = callee.importInstanceDataOffset(); 6347 loadPtr( 6348 Address(InstanceReg, wasm::Instance::offsetInData( 6349 instanceDataOffset + 6350 offsetof(wasm::FuncImportInstanceData, code))), 6351 ABINonArgReg0); 6352 6353 #if !defined(JS_CODEGEN_NONE) && !defined(JS_CODEGEN_WASM32) 6354 static_assert(ABINonArgReg0 != InstanceReg, "by constraint"); 6355 #endif 6356 6357 // Switch to the callee's realm. 6358 loadPtr( 6359 Address(InstanceReg, wasm::Instance::offsetInData( 6360 instanceDataOffset + 6361 offsetof(wasm::FuncImportInstanceData, realm))), 6362 ABINonArgReg1); 6363 loadPtr(Address(InstanceReg, wasm::Instance::offsetOfCx()), ABINonArgReg2); 6364 storePtr(ABINonArgReg1, Address(ABINonArgReg2, JSContext::offsetOfRealm())); 6365 6366 // Switch to the callee's instance and pinned registers and make the call. 6367 loadPtr(Address(InstanceReg, 6368 wasm::Instance::offsetInData( 6369 instanceDataOffset + 6370 offsetof(wasm::FuncImportInstanceData, instance))), 6371 InstanceReg); 6372 6373 storePtr(InstanceReg, 6374 Address(getStackPointer(), WasmCalleeInstanceOffsetBeforeCall)); 6375 loadWasmPinnedRegsFromInstance(mozilla::Nothing()); 6376 6377 return wasmMarkedSlowCall(desc, ABINonArgReg0); 6378 } 6379 6380 CodeOffset MacroAssembler::wasmReturnCallImport( 6381 const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee, 6382 const ReturnCallAdjustmentInfo& retCallInfo) { 6383 storePtr(InstanceReg, 6384 Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall)); 6385 6386 // Load the callee, before the caller's registers are clobbered. 6387 uint32_t instanceDataOffset = callee.importInstanceDataOffset(); 6388 loadPtr( 6389 Address(InstanceReg, wasm::Instance::offsetInData( 6390 instanceDataOffset + 6391 offsetof(wasm::FuncImportInstanceData, code))), 6392 ABINonArgReg0); 6393 6394 #if !defined(JS_CODEGEN_NONE) && !defined(JS_CODEGEN_WASM32) 6395 static_assert(ABINonArgReg0 != InstanceReg, "by constraint"); 6396 #endif 6397 6398 // Switch to the callee's realm. 6399 loadPtr( 6400 Address(InstanceReg, wasm::Instance::offsetInData( 6401 instanceDataOffset + 6402 offsetof(wasm::FuncImportInstanceData, realm))), 6403 ABINonArgReg1); 6404 loadPtr(Address(InstanceReg, wasm::Instance::offsetOfCx()), ABINonArgReg2); 6405 storePtr(ABINonArgReg1, Address(ABINonArgReg2, JSContext::offsetOfRealm())); 6406 6407 // Switch to the callee's instance and pinned registers and make the call. 6408 loadPtr(Address(InstanceReg, 6409 wasm::Instance::offsetInData( 6410 instanceDataOffset + 6411 offsetof(wasm::FuncImportInstanceData, instance))), 6412 InstanceReg); 6413 6414 storePtr(InstanceReg, 6415 Address(getStackPointer(), WasmCalleeInstanceOffsetBeforeCall)); 6416 loadWasmPinnedRegsFromInstance(mozilla::Nothing()); 6417 6418 wasm::CallSiteDesc stubDesc(desc.lineOrBytecode(), 6419 wasm::CallSiteKind::ReturnStub); 6420 wasmCollapseFrameSlow(retCallInfo, stubDesc); 6421 jump(ABINonArgReg0); 6422 append(wasm::CodeRangeUnwindInfo::Normal, currentOffset()); 6423 return CodeOffset(currentOffset()); 6424 } 6425 6426 CodeOffset MacroAssembler::wasmReturnCall( 6427 const wasm::CallSiteDesc& desc, uint32_t funcDefIndex, 6428 const ReturnCallAdjustmentInfo& retCallInfo) { 6429 wasmCollapseFrameFast(retCallInfo); 6430 CodeOffset offset = farJumpWithPatch(); 6431 append(desc, offset, funcDefIndex); 6432 append(wasm::CodeRangeUnwindInfo::Normal, currentOffset()); 6433 return offset; 6434 } 6435 6436 CodeOffset MacroAssembler::wasmCallBuiltinInstanceMethod( 6437 const wasm::CallSiteDesc& desc, const ABIArg& instanceArg, 6438 wasm::SymbolicAddress builtin, wasm::FailureMode failureMode, 6439 wasm::Trap failureTrap) { 6440 MOZ_ASSERT(instanceArg != ABIArg()); 6441 MOZ_ASSERT_IF(!wasm::NeedsBuiltinThunk(builtin), 6442 failureMode == wasm::FailureMode::Infallible || 6443 failureTrap != wasm::Trap::ThrowReported); 6444 6445 // Instance methods take the instance as the first argument. This is in 6446 // addition to the builtin thunk (if any) requiring the InstanceReg to be 6447 // set. 6448 if (instanceArg.kind() == ABIArg::GPR) { 6449 movePtr(InstanceReg, instanceArg.gpr()); 6450 } else if (instanceArg.kind() == ABIArg::Stack) { 6451 storePtr(InstanceReg, 6452 Address(getStackPointer(), instanceArg.offsetFromArgBase())); 6453 } else { 6454 MOZ_CRASH("Unknown abi passing style for pointer"); 6455 } 6456 6457 #ifdef JS_CHECK_UNSAFE_CALL_WITH_ABI 6458 // The builtin thunk exits the JIT activation, if we don't have one we must 6459 // use AutoUnsafeCallWithABI inside the builtin and check that here. 6460 bool checkUnsafeCallWithABI = !wasm::NeedsBuiltinThunk(builtin); 6461 if (checkUnsafeCallWithABI) { 6462 wasmCheckUnsafeCallWithABIPre(); 6463 } 6464 #endif 6465 6466 CodeOffset ret = call(desc, builtin); 6467 6468 #ifdef JS_CHECK_UNSAFE_CALL_WITH_ABI 6469 if (checkUnsafeCallWithABI) { 6470 wasmCheckUnsafeCallWithABIPost(); 6471 } 6472 #endif 6473 6474 wasmTrapOnFailedInstanceCall(ReturnReg, failureMode, failureTrap, 6475 desc.toTrapSiteDesc()); 6476 return ret; 6477 } 6478 6479 void MacroAssembler::wasmTrapOnFailedInstanceCall( 6480 Register resultRegister, wasm::FailureMode failureMode, 6481 wasm::Trap failureTrap, const wasm::TrapSiteDesc& trapSiteDesc) { 6482 Label noTrap; 6483 switch (failureMode) { 6484 case wasm::FailureMode::Infallible: 6485 MOZ_ASSERT(failureTrap == wasm::Trap::Limit); 6486 return; 6487 case wasm::FailureMode::FailOnNegI32: 6488 branchTest32(Assembler::NotSigned, resultRegister, resultRegister, 6489 &noTrap); 6490 break; 6491 case wasm::FailureMode::FailOnMaxI32: 6492 branchPtr(Assembler::NotEqual, resultRegister, 6493 ImmWord(uintptr_t(INT32_MAX)), &noTrap); 6494 break; 6495 case wasm::FailureMode::FailOnNullPtr: 6496 branchTestPtr(Assembler::NonZero, resultRegister, resultRegister, 6497 &noTrap); 6498 break; 6499 case wasm::FailureMode::FailOnInvalidRef: 6500 branchPtr(Assembler::NotEqual, resultRegister, 6501 ImmWord(uintptr_t(wasm::AnyRef::invalid().forCompiledCode())), 6502 &noTrap); 6503 break; 6504 } 6505 wasmTrap(failureTrap, trapSiteDesc); 6506 bind(&noTrap); 6507 } 6508 6509 CodeOffset MacroAssembler::asmCallIndirect(const wasm::CallSiteDesc& desc, 6510 const wasm::CalleeDesc& callee) { 6511 MOZ_ASSERT(callee.which() == wasm::CalleeDesc::AsmJSTable); 6512 6513 const Register scratch = WasmTableCallScratchReg0; 6514 const Register index = WasmTableCallIndexReg; 6515 6516 // Optimization opportunity: when offsetof(FunctionTableElem, code) == 0, as 6517 // it is at present, we can probably generate better code here by folding 6518 // the address computation into the load. 6519 6520 static_assert(sizeof(wasm::FunctionTableElem) == 8 || 6521 sizeof(wasm::FunctionTableElem) == 16, 6522 "elements of function tables are two words"); 6523 6524 // asm.js tables require no signature check, and have had their index 6525 // masked into range and thus need no bounds check. 6526 loadPtr( 6527 Address(InstanceReg, wasm::Instance::offsetInData( 6528 callee.tableFunctionBaseInstanceDataOffset())), 6529 scratch); 6530 if (sizeof(wasm::FunctionTableElem) == 8) { 6531 computeEffectiveAddress(BaseIndex(scratch, index, TimesEight), scratch); 6532 } else { 6533 lshift32(Imm32(4), index); 6534 addPtr(index, scratch); 6535 } 6536 loadPtr(Address(scratch, offsetof(wasm::FunctionTableElem, code)), scratch); 6537 storePtr(InstanceReg, 6538 Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall)); 6539 storePtr(InstanceReg, 6540 Address(getStackPointer(), WasmCalleeInstanceOffsetBeforeCall)); 6541 return call(desc, scratch); 6542 } 6543 6544 // In principle, call_indirect requires an expensive context switch to the 6545 // callee's instance and realm before the call and an almost equally expensive 6546 // switch back to the caller's ditto after. However, if the caller's instance 6547 // is the same as the callee's instance then no context switch is required, and 6548 // it only takes a compare-and-branch at run-time to test this - all values are 6549 // in registers already. We therefore generate two call paths, one for the fast 6550 // call without the context switch (which additionally avoids a null check) and 6551 // one for the slow call with the context switch. 6552 6553 void MacroAssembler::wasmCallIndirect(const wasm::CallSiteDesc& desc, 6554 const wasm::CalleeDesc& callee, 6555 Label* nullCheckFailedLabel, 6556 CodeOffset* fastCallOffset, 6557 CodeOffset* slowCallOffset) { 6558 static_assert(sizeof(wasm::FunctionTableElem) == 2 * sizeof(void*), 6559 "Exactly two pointers or index scaling won't work correctly"); 6560 MOZ_ASSERT(callee.which() == wasm::CalleeDesc::WasmTable); 6561 6562 const int shift = sizeof(wasm::FunctionTableElem) == 8 ? 3 : 4; 6563 const Register calleeScratch = WasmTableCallScratchReg0; 6564 const Register index = WasmTableCallIndexReg; 6565 6566 // Write the functype-id into the ABI functype-id register. 6567 6568 const wasm::CallIndirectId callIndirectId = callee.wasmTableSigId(); 6569 switch (callIndirectId.kind()) { 6570 case wasm::CallIndirectIdKind::Global: 6571 loadPtr(Address(InstanceReg, wasm::Instance::offsetInData( 6572 callIndirectId.instanceDataOffset() + 6573 offsetof(wasm::TypeDefInstanceData, 6574 superTypeVector))), 6575 WasmTableCallSigReg); 6576 break; 6577 case wasm::CallIndirectIdKind::Immediate: 6578 move32(Imm32(callIndirectId.immediate()), WasmTableCallSigReg); 6579 break; 6580 case wasm::CallIndirectIdKind::AsmJS: 6581 case wasm::CallIndirectIdKind::None: 6582 break; 6583 } 6584 6585 // Load the base pointer of the table and compute the address of the callee in 6586 // the table. 6587 6588 loadPtr( 6589 Address(InstanceReg, wasm::Instance::offsetInData( 6590 callee.tableFunctionBaseInstanceDataOffset())), 6591 calleeScratch); 6592 shiftIndex32AndAdd(index, shift, calleeScratch); 6593 6594 // Load the callee instance and decide whether to take the fast path or the 6595 // slow path. 6596 6597 Label fastCall; 6598 Label done; 6599 const Register newInstanceTemp = WasmTableCallScratchReg1; 6600 loadPtr(Address(calleeScratch, offsetof(wasm::FunctionTableElem, instance)), 6601 newInstanceTemp); 6602 branchPtr(Assembler::Equal, InstanceReg, newInstanceTemp, &fastCall); 6603 6604 // Slow path: Save context, check for null, setup new context, call, restore 6605 // context. 6606 // 6607 // TODO: The slow path could usefully be out-of-line and the test above would 6608 // just fall through to the fast path. This keeps the fast-path code dense, 6609 // and has correct static prediction for the branch (forward conditional 6610 // branches predicted not taken, normally). 6611 6612 storePtr(InstanceReg, 6613 Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall)); 6614 movePtr(newInstanceTemp, InstanceReg); 6615 storePtr(InstanceReg, 6616 Address(getStackPointer(), WasmCalleeInstanceOffsetBeforeCall)); 6617 6618 #ifdef WASM_HAS_HEAPREG 6619 // Use the null pointer exception resulting from loading HeapReg from a null 6620 // instance to handle a call to a null slot. 6621 MOZ_ASSERT(nullCheckFailedLabel == nullptr); 6622 loadWasmPinnedRegsFromInstance(mozilla::Some(desc.toTrapSiteDesc())); 6623 #else 6624 MOZ_ASSERT(nullCheckFailedLabel != nullptr); 6625 branchTestPtr(Assembler::Zero, InstanceReg, InstanceReg, 6626 nullCheckFailedLabel); 6627 6628 loadWasmPinnedRegsFromInstance(mozilla::Nothing()); 6629 #endif 6630 switchToWasmInstanceRealm(index, WasmTableCallScratchReg1); 6631 6632 loadPtr(Address(calleeScratch, offsetof(wasm::FunctionTableElem, code)), 6633 calleeScratch); 6634 6635 *slowCallOffset = wasmMarkedSlowCall(desc, calleeScratch); 6636 6637 // Restore registers and realm and join up with the fast path. 6638 6639 loadPtr(Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall), 6640 InstanceReg); 6641 loadWasmPinnedRegsFromInstance(mozilla::Nothing()); 6642 switchToWasmInstanceRealm(ABINonArgReturnReg0, ABINonArgReturnReg1); 6643 jump(&done); 6644 6645 // Fast path: just load the code pointer and go. The instance and heap 6646 // register are the same as in the caller, and nothing will be null. 6647 // 6648 // (In particular, the code pointer will not be null: if it were, the instance 6649 // would have been null, and then it would not have been equivalent to our 6650 // current instance. So no null check is needed on the fast path.) 6651 6652 bind(&fastCall); 6653 6654 loadPtr(Address(calleeScratch, offsetof(wasm::FunctionTableElem, code)), 6655 calleeScratch); 6656 6657 // We use a different type of call site for the fast call since the instance 6658 // slots in the frame do not have valid values. 6659 6660 wasm::CallSiteDesc newDesc(desc.lineOrBytecode(), 6661 wasm::CallSiteKind::IndirectFast); 6662 *fastCallOffset = call(newDesc, calleeScratch); 6663 6664 bind(&done); 6665 } 6666 6667 void MacroAssembler::wasmReturnCallIndirect( 6668 const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee, 6669 Label* nullCheckFailedLabel, const ReturnCallAdjustmentInfo& retCallInfo) { 6670 static_assert(sizeof(wasm::FunctionTableElem) == 2 * sizeof(void*), 6671 "Exactly two pointers or index scaling won't work correctly"); 6672 MOZ_ASSERT(callee.which() == wasm::CalleeDesc::WasmTable); 6673 6674 const int shift = sizeof(wasm::FunctionTableElem) == 8 ? 3 : 4; 6675 const Register calleeScratch = WasmTableCallScratchReg0; 6676 const Register index = WasmTableCallIndexReg; 6677 6678 // Write the functype-id into the ABI functype-id register. 6679 6680 const wasm::CallIndirectId callIndirectId = callee.wasmTableSigId(); 6681 switch (callIndirectId.kind()) { 6682 case wasm::CallIndirectIdKind::Global: 6683 loadPtr(Address(InstanceReg, wasm::Instance::offsetInData( 6684 callIndirectId.instanceDataOffset() + 6685 offsetof(wasm::TypeDefInstanceData, 6686 superTypeVector))), 6687 WasmTableCallSigReg); 6688 break; 6689 case wasm::CallIndirectIdKind::Immediate: 6690 move32(Imm32(callIndirectId.immediate()), WasmTableCallSigReg); 6691 break; 6692 case wasm::CallIndirectIdKind::AsmJS: 6693 case wasm::CallIndirectIdKind::None: 6694 break; 6695 } 6696 6697 // Load the base pointer of the table and compute the address of the callee in 6698 // the table. 6699 6700 loadPtr( 6701 Address(InstanceReg, wasm::Instance::offsetInData( 6702 callee.tableFunctionBaseInstanceDataOffset())), 6703 calleeScratch); 6704 shiftIndex32AndAdd(index, shift, calleeScratch); 6705 6706 // Load the callee instance and decide whether to take the fast path or the 6707 // slow path. 6708 6709 Label fastCall; 6710 Label done; 6711 const Register newInstanceTemp = WasmTableCallScratchReg1; 6712 loadPtr(Address(calleeScratch, offsetof(wasm::FunctionTableElem, instance)), 6713 newInstanceTemp); 6714 branchPtr(Assembler::Equal, InstanceReg, newInstanceTemp, &fastCall); 6715 6716 // Slow path: Save context, check for null, setup new context. 6717 6718 storePtr(InstanceReg, 6719 Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall)); 6720 movePtr(newInstanceTemp, InstanceReg); 6721 6722 #ifdef WASM_HAS_HEAPREG 6723 // Use the null pointer exception resulting from loading HeapReg from a null 6724 // instance to handle a call to a null slot. 6725 MOZ_ASSERT(nullCheckFailedLabel == nullptr); 6726 loadWasmPinnedRegsFromInstance(mozilla::Some(desc.toTrapSiteDesc())); 6727 #else 6728 MOZ_ASSERT(nullCheckFailedLabel != nullptr); 6729 branchTestPtr(Assembler::Zero, InstanceReg, InstanceReg, 6730 nullCheckFailedLabel); 6731 6732 loadWasmPinnedRegsFromInstance(mozilla::Nothing()); 6733 #endif 6734 switchToWasmInstanceRealm(index, WasmTableCallScratchReg1); 6735 6736 loadPtr(Address(calleeScratch, offsetof(wasm::FunctionTableElem, code)), 6737 calleeScratch); 6738 6739 wasm::CallSiteDesc stubDesc(desc.lineOrBytecode(), 6740 wasm::CallSiteKind::ReturnStub); 6741 wasmCollapseFrameSlow(retCallInfo, stubDesc); 6742 jump(calleeScratch); 6743 append(wasm::CodeRangeUnwindInfo::Normal, currentOffset()); 6744 6745 // Fast path: just load the code pointer and go. 6746 6747 bind(&fastCall); 6748 6749 loadPtr(Address(calleeScratch, offsetof(wasm::FunctionTableElem, code)), 6750 calleeScratch); 6751 6752 wasmCollapseFrameFast(retCallInfo); 6753 jump(calleeScratch); 6754 append(wasm::CodeRangeUnwindInfo::Normal, currentOffset()); 6755 } 6756 6757 void MacroAssembler::wasmCallRef(const wasm::CallSiteDesc& desc, 6758 const wasm::CalleeDesc& callee, 6759 CodeOffset* fastCallOffset, 6760 CodeOffset* slowCallOffset) { 6761 MOZ_ASSERT(callee.which() == wasm::CalleeDesc::FuncRef); 6762 const Register calleeScratch = WasmCallRefCallScratchReg0; 6763 const Register calleeFnObj = WasmCallRefReg; 6764 6765 // Load from the function's WASM_INSTANCE_SLOT extended slot, and decide 6766 // whether to take the fast path or the slow path. Register this load 6767 // instruction to be source of a trap -- null pointer check. 6768 6769 Label fastCall; 6770 Label done; 6771 const Register newInstanceTemp = WasmCallRefCallScratchReg1; 6772 size_t instanceSlotOffset = FunctionExtended::offsetOfExtendedSlot( 6773 FunctionExtended::WASM_INSTANCE_SLOT); 6774 static_assert(FunctionExtended::WASM_INSTANCE_SLOT < wasm::NullPtrGuardSize); 6775 FaultingCodeOffset fco = 6776 loadPtr(Address(calleeFnObj, instanceSlotOffset), newInstanceTemp); 6777 append(wasm::Trap::NullPointerDereference, wasm::TrapMachineInsnForLoadWord(), 6778 fco.get(), desc.toTrapSiteDesc()); 6779 branchPtr(Assembler::Equal, InstanceReg, newInstanceTemp, &fastCall); 6780 6781 storePtr(InstanceReg, 6782 Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall)); 6783 movePtr(newInstanceTemp, InstanceReg); 6784 storePtr(InstanceReg, 6785 Address(getStackPointer(), WasmCalleeInstanceOffsetBeforeCall)); 6786 6787 loadWasmPinnedRegsFromInstance(mozilla::Nothing()); 6788 switchToWasmInstanceRealm(WasmCallRefCallScratchReg0, 6789 WasmCallRefCallScratchReg1); 6790 6791 // Get funcUncheckedCallEntry() from the function's 6792 // WASM_FUNC_UNCHECKED_ENTRY_SLOT extended slot. 6793 size_t uncheckedEntrySlotOffset = FunctionExtended::offsetOfExtendedSlot( 6794 FunctionExtended::WASM_FUNC_UNCHECKED_ENTRY_SLOT); 6795 loadPtr(Address(calleeFnObj, uncheckedEntrySlotOffset), calleeScratch); 6796 6797 *slowCallOffset = wasmMarkedSlowCall(desc, calleeScratch); 6798 6799 // Restore registers and realm and back to this caller's. 6800 loadPtr(Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall), 6801 InstanceReg); 6802 loadWasmPinnedRegsFromInstance(mozilla::Nothing()); 6803 switchToWasmInstanceRealm(ABINonArgReturnReg0, ABINonArgReturnReg1); 6804 jump(&done); 6805 6806 // Fast path: just load WASM_FUNC_UNCHECKED_ENTRY_SLOT value and go. 6807 // The instance and pinned registers are the same as in the caller. 6808 6809 bind(&fastCall); 6810 6811 loadPtr(Address(calleeFnObj, uncheckedEntrySlotOffset), calleeScratch); 6812 6813 // We use a different type of call site for the fast call since the instance 6814 // slots in the frame do not have valid values. 6815 6816 wasm::CallSiteDesc newDesc(desc.lineOrBytecode(), 6817 wasm::CallSiteKind::FuncRefFast); 6818 *fastCallOffset = call(newDesc, calleeScratch); 6819 6820 bind(&done); 6821 } 6822 6823 void MacroAssembler::wasmReturnCallRef( 6824 const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee, 6825 const ReturnCallAdjustmentInfo& retCallInfo) { 6826 MOZ_ASSERT(callee.which() == wasm::CalleeDesc::FuncRef); 6827 const Register calleeScratch = WasmCallRefCallScratchReg0; 6828 const Register calleeFnObj = WasmCallRefReg; 6829 6830 // Load from the function's WASM_INSTANCE_SLOT extended slot, and decide 6831 // whether to take the fast path or the slow path. Register this load 6832 // instruction to be source of a trap -- null pointer check. 6833 6834 Label fastCall; 6835 Label done; 6836 const Register newInstanceTemp = WasmCallRefCallScratchReg1; 6837 size_t instanceSlotOffset = FunctionExtended::offsetOfExtendedSlot( 6838 FunctionExtended::WASM_INSTANCE_SLOT); 6839 static_assert(FunctionExtended::WASM_INSTANCE_SLOT < wasm::NullPtrGuardSize); 6840 FaultingCodeOffset fco = 6841 loadPtr(Address(calleeFnObj, instanceSlotOffset), newInstanceTemp); 6842 append(wasm::Trap::NullPointerDereference, wasm::TrapMachineInsnForLoadWord(), 6843 fco.get(), desc.toTrapSiteDesc()); 6844 branchPtr(Assembler::Equal, InstanceReg, newInstanceTemp, &fastCall); 6845 6846 storePtr(InstanceReg, 6847 Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall)); 6848 movePtr(newInstanceTemp, InstanceReg); 6849 storePtr(InstanceReg, 6850 Address(getStackPointer(), WasmCalleeInstanceOffsetBeforeCall)); 6851 6852 loadWasmPinnedRegsFromInstance(mozilla::Nothing()); 6853 switchToWasmInstanceRealm(WasmCallRefCallScratchReg0, 6854 WasmCallRefCallScratchReg1); 6855 6856 // Get funcUncheckedCallEntry() from the function's 6857 // WASM_FUNC_UNCHECKED_ENTRY_SLOT extended slot. 6858 size_t uncheckedEntrySlotOffset = FunctionExtended::offsetOfExtendedSlot( 6859 FunctionExtended::WASM_FUNC_UNCHECKED_ENTRY_SLOT); 6860 loadPtr(Address(calleeFnObj, uncheckedEntrySlotOffset), calleeScratch); 6861 6862 wasm::CallSiteDesc stubDesc(desc.lineOrBytecode(), 6863 wasm::CallSiteKind::ReturnStub); 6864 wasmCollapseFrameSlow(retCallInfo, stubDesc); 6865 jump(calleeScratch); 6866 append(wasm::CodeRangeUnwindInfo::Normal, currentOffset()); 6867 6868 // Fast path: just load WASM_FUNC_UNCHECKED_ENTRY_SLOT value and go. 6869 // The instance and pinned registers are the same as in the caller. 6870 6871 bind(&fastCall); 6872 6873 loadPtr(Address(calleeFnObj, uncheckedEntrySlotOffset), calleeScratch); 6874 6875 wasmCollapseFrameFast(retCallInfo); 6876 jump(calleeScratch); 6877 append(wasm::CodeRangeUnwindInfo::Normal, currentOffset()); 6878 } 6879 6880 void MacroAssembler::wasmBoundsCheckRange32( 6881 Register index, Register length, Register limit, Register tmp, 6882 const wasm::TrapSiteDesc& trapSiteDesc) { 6883 Label ok; 6884 Label fail; 6885 6886 mov(index, tmp); 6887 branchAdd32(Assembler::CarrySet, length, tmp, &fail); 6888 branch32(Assembler::Above, tmp, limit, &fail); 6889 jump(&ok); 6890 6891 bind(&fail); 6892 wasmTrap(wasm::Trap::OutOfBounds, trapSiteDesc); 6893 6894 bind(&ok); 6895 } 6896 6897 void MacroAssembler::wasmClampTable64Address(Register64 address, Register out) { 6898 Label oob; 6899 Label ret; 6900 branch64(Assembler::Above, address, Imm64(UINT32_MAX), &oob); 6901 move64To32(address, out); 6902 jump(&ret); 6903 bind(&oob); 6904 static_assert(wasm::MaxTableElemsRuntime < UINT32_MAX); 6905 move32(Imm32(UINT32_MAX), out); 6906 bind(&ret); 6907 }; 6908 6909 BranchWasmRefIsSubtypeRegisters MacroAssembler::regsForBranchWasmRefIsSubtype( 6910 wasm::RefType type) { 6911 MOZ_ASSERT(type.isValid()); 6912 switch (type.hierarchy()) { 6913 case wasm::RefTypeHierarchy::Any: 6914 return BranchWasmRefIsSubtypeRegisters{ 6915 .needSuperSTV = type.isTypeRef(), 6916 .needScratch1 = !type.isNone() && !type.isAny(), 6917 .needScratch2 = type.isTypeRef() && !type.typeDef()->isFinal() && 6918 type.typeDef()->subTypingDepth() >= 6919 wasm::MinSuperTypeVectorLength, 6920 }; 6921 case wasm::RefTypeHierarchy::Func: 6922 return BranchWasmRefIsSubtypeRegisters{ 6923 .needSuperSTV = type.isTypeRef(), 6924 .needScratch1 = type.isTypeRef(), 6925 .needScratch2 = type.isTypeRef() && !type.typeDef()->isFinal() && 6926 type.typeDef()->subTypingDepth() >= 6927 wasm::MinSuperTypeVectorLength, 6928 }; 6929 case wasm::RefTypeHierarchy::Extern: 6930 case wasm::RefTypeHierarchy::Exn: 6931 return BranchWasmRefIsSubtypeRegisters{ 6932 .needSuperSTV = false, 6933 .needScratch1 = false, 6934 .needScratch2 = false, 6935 }; 6936 default: 6937 MOZ_CRASH("unknown type hierarchy for cast"); 6938 } 6939 } 6940 6941 FaultingCodeOffset MacroAssembler::branchWasmRefIsSubtype( 6942 Register ref, wasm::MaybeRefType sourceType, wasm::RefType destType, 6943 Label* label, bool onSuccess, bool signalNullChecks, Register superSTV, 6944 Register scratch1, Register scratch2) { 6945 FaultingCodeOffset result = FaultingCodeOffset(); 6946 switch (destType.hierarchy()) { 6947 case wasm::RefTypeHierarchy::Any: { 6948 result = branchWasmRefIsSubtypeAny( 6949 ref, sourceType.valueOr(wasm::RefType::any()), destType, label, 6950 onSuccess, signalNullChecks, superSTV, scratch1, scratch2); 6951 } break; 6952 case wasm::RefTypeHierarchy::Func: { 6953 branchWasmRefIsSubtypeFunc(ref, sourceType.valueOr(wasm::RefType::func()), 6954 destType, label, onSuccess, superSTV, scratch1, 6955 scratch2); 6956 } break; 6957 case wasm::RefTypeHierarchy::Extern: { 6958 branchWasmRefIsSubtypeExtern(ref, 6959 sourceType.valueOr(wasm::RefType::extern_()), 6960 destType, label, onSuccess); 6961 } break; 6962 case wasm::RefTypeHierarchy::Exn: { 6963 branchWasmRefIsSubtypeExn(ref, sourceType.valueOr(wasm::RefType::exn()), 6964 destType, label, onSuccess); 6965 } break; 6966 default: 6967 MOZ_CRASH("unknown type hierarchy for wasm cast"); 6968 } 6969 MOZ_ASSERT_IF(!signalNullChecks, !result.isValid()); 6970 return result; 6971 } 6972 6973 FaultingCodeOffset MacroAssembler::branchWasmRefIsSubtypeAny( 6974 Register ref, wasm::RefType sourceType, wasm::RefType destType, 6975 Label* label, bool onSuccess, bool signalNullChecks, Register superSTV, 6976 Register scratch1, Register scratch2) { 6977 MOZ_ASSERT(sourceType.isValid()); 6978 MOZ_ASSERT(destType.isValid()); 6979 MOZ_ASSERT(sourceType.isAnyHierarchy()); 6980 MOZ_ASSERT(destType.isAnyHierarchy()); 6981 6982 mozilla::DebugOnly<BranchWasmRefIsSubtypeRegisters> needs = 6983 regsForBranchWasmRefIsSubtype(destType); 6984 MOZ_ASSERT_IF(needs.value.needSuperSTV, superSTV != Register::Invalid()); 6985 MOZ_ASSERT_IF(needs.value.needScratch1, scratch1 != Register::Invalid()); 6986 MOZ_ASSERT_IF(needs.value.needScratch2, scratch2 != Register::Invalid()); 6987 6988 Label fallthrough; 6989 Label* successLabel = onSuccess ? label : &fallthrough; 6990 Label* failLabel = onSuccess ? &fallthrough : label; 6991 Label* nullLabel = destType.isNullable() ? successLabel : failLabel; 6992 6993 auto finishSuccess = [&]() { 6994 if (successLabel != &fallthrough) { 6995 jump(successLabel); 6996 } 6997 bind(&fallthrough); 6998 }; 6999 auto finishFail = [&]() { 7000 if (failLabel != &fallthrough) { 7001 jump(failLabel); 7002 } 7003 bind(&fallthrough); 7004 }; 7005 7006 // We can omit the null check, and catch nulls in signal handling, if the dest 7007 // type is non-nullable and the cast process will attempt to load from the 7008 // (null) ref. This logic must be kept in sync with the flow of checks below; 7009 // this will be checked by fcoLogicCheck. 7010 bool willLoadShape = 7011 // Dest type is a GC object 7012 (wasm::RefType::isSubTypeOf(destType, wasm::RefType::eq()) && 7013 !destType.isI31() && !destType.isNone()) && 7014 // Source type is not already known to be a GC object 7015 !(wasm::RefType::isSubTypeOf(sourceType, wasm::RefType::struct_()) || 7016 wasm::RefType::isSubTypeOf(sourceType, wasm::RefType::array())); 7017 bool willLoadSTV = 7018 // Dest type is struct or array or concrete type 7019 (wasm::RefType::isSubTypeOf(destType, wasm::RefType::struct_()) || 7020 wasm::RefType::isSubTypeOf(destType, wasm::RefType::array())) && 7021 !destType.isNone(); 7022 bool canOmitNullCheck = signalNullChecks && !destType.isNullable() && 7023 (willLoadShape || willLoadSTV); 7024 7025 FaultingCodeOffset fco = FaultingCodeOffset(); 7026 auto trackFCO = [&](FaultingCodeOffset newFco) { 7027 if (signalNullChecks && !destType.isNullable() && !fco.isValid()) { 7028 fco = newFco; 7029 } 7030 }; 7031 auto fcoLogicCheck = mozilla::DebugOnly(mozilla::MakeScopeExit([&]() { 7032 // When we think we can omit the null check, we should get a valid FCO, and 7033 // vice versa. These are asserted to match because: 7034 // - If we think we cannot omit the null check, but get a valid FCO, then 7035 // we wasted an optimization opportunity. 7036 // - If we think we *can* omit the null check, but do not get a valid FCO, 7037 // then we will segfault. 7038 // We could ignore the former check, but better to be precise and ensure 7039 // that we are getting the optimizations we expect. 7040 MOZ_ASSERT_IF(signalNullChecks && fco.isValid(), canOmitNullCheck); 7041 MOZ_ASSERT_IF(signalNullChecks && !fco.isValid(), !canOmitNullCheck); 7042 7043 // We should never get a valid FCO if the caller doesn't expect signal 7044 // handling. This simplifies life for the caller. 7045 MOZ_ASSERT_IF(!signalNullChecks, !fco.isValid()); 7046 })); 7047 7048 // ----------------------------------- 7049 // Actual test/cast logic begins here. 7050 7051 // Check for null. 7052 if (sourceType.isNullable() && !canOmitNullCheck) { 7053 branchWasmAnyRefIsNull(true, ref, nullLabel); 7054 } 7055 7056 // The only value that can inhabit 'none' is null. So, early out if we got 7057 // not-null. 7058 if (destType.isNone()) { 7059 finishFail(); 7060 MOZ_ASSERT(!willLoadShape && !willLoadSTV); 7061 return fco; 7062 } 7063 7064 if (destType.isAny()) { 7065 // No further checks for 'any' 7066 finishSuccess(); 7067 MOZ_ASSERT(!willLoadShape && !willLoadSTV); 7068 return fco; 7069 } 7070 7071 // 'type' is now 'eq' or lower, which currently will either be a gc object or 7072 // an i31. 7073 7074 // Check first for i31 values, and get them out of the way. i31 values are 7075 // valid when casting to i31 or eq, and invalid otherwise. 7076 if (destType.isI31() || destType.isEq()) { 7077 branchWasmAnyRefIsI31(true, ref, successLabel); 7078 7079 if (destType.isI31()) { 7080 // No further checks for 'i31' 7081 finishFail(); 7082 MOZ_ASSERT(!willLoadShape && !willLoadSTV); 7083 return fco; 7084 } 7085 } 7086 7087 // Then check for any kind of gc object. 7088 MOZ_ASSERT(scratch1 != Register::Invalid()); 7089 if (!wasm::RefType::isSubTypeOf(sourceType, wasm::RefType::struct_()) && 7090 !wasm::RefType::isSubTypeOf(sourceType, wasm::RefType::array())) { 7091 branchWasmAnyRefIsObjectOrNull(false, ref, failLabel); 7092 7093 MOZ_ASSERT(willLoadShape); 7094 trackFCO(branchObjectIsWasmGcObject(false, ref, scratch1, failLabel)); 7095 } else { 7096 MOZ_ASSERT(!willLoadShape); 7097 } 7098 7099 if (destType.isEq()) { 7100 // No further checks for 'eq' 7101 finishSuccess(); 7102 MOZ_ASSERT(!willLoadSTV); 7103 return fco; 7104 } 7105 7106 // 'type' is now 'struct', 'array', or a concrete type. (Bottom types and i31 7107 // were handled above.) 7108 // 7109 // Casting to a concrete type only requires a simple check on the 7110 // object's super type vector. Casting to an abstract type (struct, array) 7111 // requires loading the object's superTypeVector->typeDef->kind, and checking 7112 // that it is correct. 7113 7114 MOZ_ASSERT(willLoadSTV); 7115 trackFCO( 7116 loadPtr(Address(ref, int32_t(WasmGcObject::offsetOfSuperTypeVector())), 7117 scratch1)); 7118 if (destType.isTypeRef()) { 7119 // Concrete type, do superTypeVector check. 7120 branchWasmSTVIsSubtype(scratch1, superSTV, scratch2, destType.typeDef(), 7121 label, onSuccess); 7122 bind(&fallthrough); 7123 return fco; 7124 } 7125 7126 // Abstract type, do kind check 7127 loadPtr( 7128 Address(scratch1, int32_t(wasm::SuperTypeVector::offsetOfSelfTypeDef())), 7129 scratch1); 7130 load8ZeroExtend(Address(scratch1, int32_t(wasm::TypeDef::offsetOfKind())), 7131 scratch1); 7132 branch32(onSuccess ? Assembler::Equal : Assembler::NotEqual, scratch1, 7133 Imm32(int32_t(destType.typeDefKind())), label); 7134 bind(&fallthrough); 7135 return fco; 7136 } 7137 7138 void MacroAssembler::branchWasmRefIsSubtypeFunc( 7139 Register ref, wasm::RefType sourceType, wasm::RefType destType, 7140 Label* label, bool onSuccess, Register superSTV, Register scratch1, 7141 Register scratch2) { 7142 MOZ_ASSERT(sourceType.isValid()); 7143 MOZ_ASSERT(destType.isValid()); 7144 MOZ_ASSERT(sourceType.isFuncHierarchy()); 7145 MOZ_ASSERT(destType.isFuncHierarchy()); 7146 7147 mozilla::DebugOnly<BranchWasmRefIsSubtypeRegisters> needs = 7148 regsForBranchWasmRefIsSubtype(destType); 7149 MOZ_ASSERT_IF(needs.value.needSuperSTV, superSTV != Register::Invalid()); 7150 MOZ_ASSERT_IF(needs.value.needScratch1, scratch1 != Register::Invalid()); 7151 MOZ_ASSERT_IF(needs.value.needScratch2, scratch2 != Register::Invalid()); 7152 7153 Label fallthrough; 7154 Label* successLabel = onSuccess ? label : &fallthrough; 7155 Label* failLabel = onSuccess ? &fallthrough : label; 7156 Label* nullLabel = destType.isNullable() ? successLabel : failLabel; 7157 7158 auto finishSuccess = [&]() { 7159 if (successLabel != &fallthrough) { 7160 jump(successLabel); 7161 } 7162 bind(&fallthrough); 7163 }; 7164 auto finishFail = [&]() { 7165 if (failLabel != &fallthrough) { 7166 jump(failLabel); 7167 } 7168 bind(&fallthrough); 7169 }; 7170 7171 // Check for null. 7172 if (sourceType.isNullable()) { 7173 branchTestPtr(Assembler::Zero, ref, ref, nullLabel); 7174 } 7175 7176 // The only value that can inhabit 'nofunc' is null. So, early out if we got 7177 // not-null. 7178 if (destType.isNoFunc()) { 7179 finishFail(); 7180 return; 7181 } 7182 7183 if (destType.isFunc()) { 7184 // No further checks for 'func' (any func) 7185 finishSuccess(); 7186 return; 7187 } 7188 7189 // In the func hierarchy, a supertype vector check is now sufficient for all 7190 // remaining cases. 7191 loadPrivate(Address(ref, int32_t(FunctionExtended::offsetOfWasmSTV())), 7192 scratch1); 7193 branchWasmSTVIsSubtype(scratch1, superSTV, scratch2, destType.typeDef(), 7194 label, onSuccess); 7195 bind(&fallthrough); 7196 } 7197 7198 void MacroAssembler::branchWasmRefIsSubtypeExtern(Register ref, 7199 wasm::RefType sourceType, 7200 wasm::RefType destType, 7201 Label* label, 7202 bool onSuccess) { 7203 MOZ_ASSERT(sourceType.isValid()); 7204 MOZ_ASSERT(destType.isValid()); 7205 MOZ_ASSERT(sourceType.isExternHierarchy()); 7206 MOZ_ASSERT(destType.isExternHierarchy()); 7207 7208 Label fallthrough; 7209 Label* successLabel = onSuccess ? label : &fallthrough; 7210 Label* failLabel = onSuccess ? &fallthrough : label; 7211 Label* nullLabel = destType.isNullable() ? successLabel : failLabel; 7212 7213 auto finishSuccess = [&]() { 7214 if (successLabel != &fallthrough) { 7215 jump(successLabel); 7216 } 7217 bind(&fallthrough); 7218 }; 7219 auto finishFail = [&]() { 7220 if (failLabel != &fallthrough) { 7221 jump(failLabel); 7222 } 7223 bind(&fallthrough); 7224 }; 7225 7226 // Check for null. 7227 if (sourceType.isNullable()) { 7228 branchTestPtr(Assembler::Zero, ref, ref, nullLabel); 7229 } 7230 7231 // The only value that can inhabit 'noextern' is null. So, early out if we got 7232 // not-null. 7233 if (destType.isNoExtern()) { 7234 finishFail(); 7235 return; 7236 } 7237 7238 // There are no other possible types except externref, so succeed! 7239 finishSuccess(); 7240 } 7241 7242 void MacroAssembler::branchWasmRefIsSubtypeExn(Register ref, 7243 wasm::RefType sourceType, 7244 wasm::RefType destType, 7245 Label* label, bool onSuccess) { 7246 MOZ_ASSERT(sourceType.isValid()); 7247 MOZ_ASSERT(destType.isValid()); 7248 MOZ_ASSERT(sourceType.isExnHierarchy()); 7249 MOZ_ASSERT(destType.isExnHierarchy()); 7250 7251 Label fallthrough; 7252 Label* successLabel = onSuccess ? label : &fallthrough; 7253 Label* failLabel = onSuccess ? &fallthrough : label; 7254 Label* nullLabel = destType.isNullable() ? successLabel : failLabel; 7255 7256 auto finishSuccess = [&]() { 7257 if (successLabel != &fallthrough) { 7258 jump(successLabel); 7259 } 7260 bind(&fallthrough); 7261 }; 7262 auto finishFail = [&]() { 7263 if (failLabel != &fallthrough) { 7264 jump(failLabel); 7265 } 7266 bind(&fallthrough); 7267 }; 7268 7269 // Check for null. 7270 if (sourceType.isNullable()) { 7271 branchTestPtr(Assembler::Zero, ref, ref, nullLabel); 7272 } 7273 7274 // The only value that can inhabit 'noexn' is null. So, early out if we got 7275 // not-null. 7276 if (destType.isNoExn()) { 7277 finishFail(); 7278 return; 7279 } 7280 7281 // There are no other possible types except exnref, so succeed! 7282 finishSuccess(); 7283 } 7284 7285 void MacroAssembler::branchWasmSTVIsSubtype(Register subSTV, Register superSTV, 7286 Register scratch, 7287 const wasm::TypeDef* destType, 7288 Label* label, bool onSuccess) { 7289 if (destType->isFinal()) { 7290 // A final type cannot have subtypes, and therefore a simple equality check 7291 // is sufficient. 7292 MOZ_ASSERT(scratch == Register::Invalid()); 7293 branchPtr(onSuccess ? Assembler::Equal : Assembler::NotEqual, subSTV, 7294 superSTV, label); 7295 return; 7296 } 7297 7298 MOZ_ASSERT((destType->subTypingDepth() >= wasm::MinSuperTypeVectorLength) == 7299 (scratch != Register::Invalid())); 7300 7301 Label fallthrough; 7302 Label* successLabel = onSuccess ? label : &fallthrough; 7303 Label* failLabel = onSuccess ? &fallthrough : label; 7304 7305 // Emit a fast success path for if subSTV == superSTV. 7306 // If they are unequal, they still may be subtypes. 7307 branchPtr(Assembler::Equal, subSTV, superSTV, successLabel); 7308 7309 // Emit a bounds check if the super type depth may be out-of-bounds. 7310 if (destType->subTypingDepth() >= wasm::MinSuperTypeVectorLength) { 7311 load32(Address(subSTV, wasm::SuperTypeVector::offsetOfLength()), scratch); 7312 branch32(Assembler::BelowOrEqual, scratch, 7313 Imm32(destType->subTypingDepth()), failLabel); 7314 } 7315 7316 // Load the `superTypeDepth` entry from subSTV. This will be `superSTV` if 7317 // `subSTV` is indeed a subtype. 7318 loadPtr(Address(subSTV, wasm::SuperTypeVector::offsetOfSTVInVector( 7319 destType->subTypingDepth())), 7320 subSTV); 7321 7322 // We succeed iff the entries are equal. 7323 branchPtr(onSuccess ? Assembler::Equal : Assembler::NotEqual, subSTV, 7324 superSTV, label); 7325 7326 bind(&fallthrough); 7327 } 7328 7329 void MacroAssembler::branchWasmSTVIsSubtypeDynamicDepth( 7330 Register subSTV, Register superSTV, Register superDepth, Register scratch, 7331 Label* label, bool onSuccess) { 7332 Label fallthrough; 7333 Label* failed = onSuccess ? &fallthrough : label; 7334 7335 // Bounds check of the super type vector 7336 load32(Address(subSTV, wasm::SuperTypeVector::offsetOfLength()), scratch); 7337 branch32(Assembler::BelowOrEqual, scratch, superDepth, failed); 7338 7339 // Load `subSTV[superTypeDepth]`. This will be `superSTV` if `subSTV` is 7340 // indeed a subtype. 7341 loadPtr(BaseIndex(subSTV, superDepth, ScalePointer, 7342 offsetof(wasm::SuperTypeVector, types_)), 7343 subSTV); 7344 7345 // We succeed iff the entries are equal 7346 branchPtr(onSuccess ? Assembler::Equal : Assembler::NotEqual, subSTV, 7347 superSTV, label); 7348 7349 bind(&fallthrough); 7350 } 7351 7352 void MacroAssembler::extractWasmAnyRefTag(Register src, Register dest) { 7353 andPtr(Imm32(int32_t(wasm::AnyRef::TagMask)), src, dest); 7354 } 7355 7356 void MacroAssembler::untagWasmAnyRef(Register src, Register dest, 7357 wasm::AnyRefTag tag) { 7358 MOZ_ASSERT(tag != wasm::AnyRefTag::ObjectOrNull, "No untagging needed"); 7359 computeEffectiveAddress(Address(src, -int32_t(tag)), dest); 7360 } 7361 7362 void MacroAssembler::branchWasmAnyRefIsNull(bool isNull, Register src, 7363 Label* label) { 7364 branchTestPtr(isNull ? Assembler::Zero : Assembler::NonZero, src, src, label); 7365 } 7366 7367 void MacroAssembler::branchWasmAnyRefIsI31(bool isI31, Register src, 7368 Label* label) { 7369 branchTestPtr(isI31 ? Assembler::NonZero : Assembler::Zero, src, 7370 Imm32(int32_t(wasm::AnyRefTag::I31)), label); 7371 } 7372 7373 void MacroAssembler::branchWasmAnyRefIsObjectOrNull(bool isObject, Register src, 7374 Label* label) { 7375 branchTestPtr(isObject ? Assembler::Zero : Assembler::NonZero, src, 7376 Imm32(int32_t(wasm::AnyRef::TagMask)), label); 7377 } 7378 7379 void MacroAssembler::branchWasmAnyRefIsJSString(bool isJSString, Register src, 7380 Register temp, Label* label) { 7381 extractWasmAnyRefTag(src, temp); 7382 branch32(isJSString ? Assembler::Equal : Assembler::NotEqual, temp, 7383 Imm32(int32_t(wasm::AnyRefTag::String)), label); 7384 } 7385 7386 void MacroAssembler::branchWasmAnyRefIsGCThing(bool isGCThing, Register src, 7387 Label* label) { 7388 Label fallthrough; 7389 Label* isGCThingLabel = isGCThing ? label : &fallthrough; 7390 Label* isNotGCThingLabel = isGCThing ? &fallthrough : label; 7391 7392 // A null value or i31 value are not GC things. 7393 branchWasmAnyRefIsNull(true, src, isNotGCThingLabel); 7394 branchWasmAnyRefIsI31(true, src, isNotGCThingLabel); 7395 jump(isGCThingLabel); 7396 bind(&fallthrough); 7397 } 7398 7399 void MacroAssembler::branchWasmAnyRefIsNurseryCell(bool isNurseryCell, 7400 Register src, Register temp, 7401 Label* label) { 7402 Label done; 7403 branchWasmAnyRefIsGCThing(false, src, isNurseryCell ? &done : label); 7404 7405 getWasmAnyRefGCThingChunk(src, temp); 7406 branchPtr(isNurseryCell ? Assembler::NotEqual : Assembler::Equal, 7407 Address(temp, gc::ChunkStoreBufferOffset), ImmWord(0), label); 7408 bind(&done); 7409 } 7410 7411 void MacroAssembler::truncate32ToWasmI31Ref(Register src, Register dest) { 7412 // This will either zero-extend or sign-extend the high 32-bits on 64-bit 7413 // platforms (see comments on invariants in MacroAssembler.h). Either case 7414 // is fine, as we won't use this bits. 7415 // 7416 // Move the payload of the integer over by 1 to make room for the tag. This 7417 // will perform the truncation required by the spec. 7418 lshift32(Imm32(1), src, dest); 7419 // Add the i31 tag to the integer. 7420 orPtr(Imm32(int32_t(wasm::AnyRefTag::I31)), dest); 7421 #ifdef JS_64BIT 7422 debugAssertCanonicalInt32(dest); 7423 #endif 7424 } 7425 7426 void MacroAssembler::convertWasmI31RefTo32Signed(Register src, Register dest) { 7427 #ifdef JS_64BIT 7428 debugAssertCanonicalInt32(src); 7429 #endif 7430 // This will either zero-extend or sign-extend the high 32-bits on 64-bit 7431 // platforms (see comments on invariants in MacroAssembler.h). Either case 7432 // is fine, as we won't use this bits. 7433 // 7434 // Shift the payload back (clobbering the tag). This will sign-extend, giving 7435 // us the unsigned behavior we want. 7436 rshift32Arithmetic(Imm32(1), src, dest); 7437 } 7438 7439 void MacroAssembler::convertWasmI31RefTo32Unsigned(Register src, 7440 Register dest) { 7441 #ifdef JS_64BIT 7442 debugAssertCanonicalInt32(src); 7443 #endif 7444 // This will either zero-extend or sign-extend the high 32-bits on 64-bit 7445 // platforms (see comments on invariants in MacroAssembler.h). Either case 7446 // is fine, as we won't use this bits. 7447 // 7448 // Shift the payload back (clobbering the tag). This will zero-extend, giving 7449 // us the unsigned behavior we want. 7450 rshift32(Imm32(1), src, dest); 7451 } 7452 7453 void MacroAssembler::branchValueConvertsToWasmAnyRefInline( 7454 ValueOperand src, Register scratchInt, FloatRegister scratchFloat, 7455 Label* label) { 7456 // We can convert objects, strings, 31-bit integers and null without boxing. 7457 Label checkInt32; 7458 Label checkDouble; 7459 Label fallthrough; 7460 { 7461 ScratchTagScope tag(*this, src); 7462 splitTagForTest(src, tag); 7463 branchTestObject(Assembler::Equal, tag, label); 7464 branchTestString(Assembler::Equal, tag, label); 7465 branchTestNull(Assembler::Equal, tag, label); 7466 branchTestInt32(Assembler::Equal, tag, &checkInt32); 7467 branchTestDouble(Assembler::Equal, tag, &checkDouble); 7468 } 7469 jump(&fallthrough); 7470 7471 bind(&checkInt32); 7472 { 7473 unboxInt32(src, scratchInt); 7474 branch32(Assembler::GreaterThan, scratchInt, 7475 Imm32(wasm::AnyRef::MaxI31Value), &fallthrough); 7476 branch32(Assembler::LessThan, scratchInt, Imm32(wasm::AnyRef::MinI31Value), 7477 &fallthrough); 7478 jump(label); 7479 } 7480 7481 bind(&checkDouble); 7482 { 7483 unboxDouble(src, scratchFloat); 7484 convertDoubleToInt32(scratchFloat, scratchInt, &fallthrough, 7485 /*negativeZeroCheck=*/false); 7486 branch32(Assembler::GreaterThan, scratchInt, 7487 Imm32(wasm::AnyRef::MaxI31Value), &fallthrough); 7488 branch32(Assembler::LessThan, scratchInt, Imm32(wasm::AnyRef::MinI31Value), 7489 &fallthrough); 7490 jump(label); 7491 } 7492 7493 bind(&fallthrough); 7494 } 7495 7496 void MacroAssembler::convertValueToWasmAnyRef(ValueOperand src, Register dest, 7497 FloatRegister scratchFloat, 7498 Label* oolConvert) { 7499 Label doubleValue, int32Value, nullValue, stringValue, objectValue, done; 7500 { 7501 ScratchTagScope tag(*this, src); 7502 splitTagForTest(src, tag); 7503 branchTestObject(Assembler::Equal, tag, &objectValue); 7504 branchTestString(Assembler::Equal, tag, &stringValue); 7505 branchTestNull(Assembler::Equal, tag, &nullValue); 7506 branchTestInt32(Assembler::Equal, tag, &int32Value); 7507 branchTestDouble(Assembler::Equal, tag, &doubleValue); 7508 jump(oolConvert); 7509 } 7510 7511 bind(&doubleValue); 7512 { 7513 unboxDouble(src, scratchFloat); 7514 convertDoubleToInt32(scratchFloat, dest, oolConvert, 7515 /*negativeZeroCheck=*/false); 7516 branch32(Assembler::GreaterThan, dest, Imm32(wasm::AnyRef::MaxI31Value), 7517 oolConvert); 7518 branch32(Assembler::LessThan, dest, Imm32(wasm::AnyRef::MinI31Value), 7519 oolConvert); 7520 truncate32ToWasmI31Ref(dest, dest); 7521 jump(&done); 7522 } 7523 7524 bind(&int32Value); 7525 { 7526 unboxInt32(src, dest); 7527 branch32(Assembler::GreaterThan, dest, Imm32(wasm::AnyRef::MaxI31Value), 7528 oolConvert); 7529 branch32(Assembler::LessThan, dest, Imm32(wasm::AnyRef::MinI31Value), 7530 oolConvert); 7531 truncate32ToWasmI31Ref(dest, dest); 7532 jump(&done); 7533 } 7534 7535 bind(&nullValue); 7536 { 7537 static_assert(wasm::AnyRef::NullRefValue == 0); 7538 xorPtr(dest, dest); 7539 jump(&done); 7540 } 7541 7542 bind(&stringValue); 7543 { 7544 unboxString(src, dest); 7545 orPtr(Imm32((int32_t)wasm::AnyRefTag::String), dest); 7546 jump(&done); 7547 } 7548 7549 bind(&objectValue); 7550 { 7551 unboxObject(src, dest); 7552 } 7553 7554 bind(&done); 7555 } 7556 7557 void MacroAssembler::convertObjectToWasmAnyRef(Register src, Register dest) { 7558 // JS objects are represented without any tagging. 7559 movePtr(src, dest); 7560 } 7561 7562 void MacroAssembler::convertStringToWasmAnyRef(Register src, Register dest) { 7563 // JS strings require a tag. 7564 orPtr(Imm32(int32_t(wasm::AnyRefTag::String)), src, dest); 7565 } 7566 7567 FaultingCodeOffset MacroAssembler::branchObjectIsWasmGcObject(bool isGcObject, 7568 Register src, 7569 Register scratch, 7570 Label* label) { 7571 constexpr uint32_t ShiftedMask = (Shape::kindMask() << Shape::kindShift()); 7572 constexpr uint32_t ShiftedKind = 7573 (uint32_t(Shape::Kind::WasmGC) << Shape::kindShift()); 7574 MOZ_ASSERT(src != scratch); 7575 7576 FaultingCodeOffset fco = 7577 loadPtr(Address(src, JSObject::offsetOfShape()), scratch); 7578 load32(Address(scratch, Shape::offsetOfImmutableFlags()), scratch); 7579 and32(Imm32(ShiftedMask), scratch); 7580 branch32(isGcObject ? Assembler::Equal : Assembler::NotEqual, scratch, 7581 Imm32(ShiftedKind), label); 7582 return fco; 7583 } 7584 7585 void MacroAssembler::wasmNewStructObject(Register instance, Register result, 7586 Register allocSite, Register temp, 7587 size_t offsetOfTypeDefData, 7588 Label* fail, gc::AllocKind allocKind, 7589 bool zeroFields) { 7590 MOZ_ASSERT(instance != result); 7591 7592 // Don't execute the inline path if GC probes are built in. 7593 #ifdef JS_GC_PROBES 7594 jump(fail); 7595 #endif 7596 7597 // Don't execute the inline path if there is an allocation metadata builder 7598 // on the realm. 7599 branchPtr( 7600 Assembler::NotEqual, 7601 Address(instance, wasm::Instance::offsetOfAllocationMetadataBuilder()), 7602 ImmWord(0), fail); 7603 7604 #ifdef JS_GC_ZEAL 7605 // Don't execute the inline path if gc zeal or tracing are active. 7606 loadPtr(Address(instance, wasm::Instance::offsetOfAddressOfGCZealModeBits()), 7607 temp); 7608 load32(Address(temp, 0), temp); 7609 branch32(Assembler::NotEqual, temp, Imm32(0), fail); 7610 #endif 7611 7612 // If the alloc site is long lived, immediately fall back to the OOL path, 7613 // which will handle that. 7614 branchTestPtr(Assembler::NonZero, 7615 Address(allocSite, gc::AllocSite::offsetOfScriptAndState()), 7616 Imm32(gc::AllocSite::LONG_LIVED_BIT), fail); 7617 7618 size_t sizeBytes = gc::Arena::thingSize(allocKind); 7619 wasmBumpPointerAllocate(instance, result, allocSite, temp, fail, sizeBytes); 7620 7621 loadPtr(Address(instance, offsetOfTypeDefData + 7622 wasm::TypeDefInstanceData::offsetOfShape()), 7623 temp); 7624 storePtr(temp, Address(result, WasmArrayObject::offsetOfShape())); 7625 loadPtr(Address(instance, 7626 offsetOfTypeDefData + 7627 wasm::TypeDefInstanceData::offsetOfSuperTypeVector()), 7628 temp); 7629 storePtr(temp, Address(result, WasmArrayObject::offsetOfSuperTypeVector())); 7630 7631 if (zeroFields) { 7632 static_assert(wasm::WasmStructObject_Size_ASSUMED % sizeof(void*) == 0); 7633 MOZ_ASSERT(sizeBytes % sizeof(void*) == 0); 7634 for (size_t i = wasm::WasmStructObject_Size_ASSUMED; i < sizeBytes; 7635 i += sizeof(void*)) { 7636 storePtr(ImmWord(0), Address(result, i)); 7637 } 7638 } 7639 } 7640 7641 void MacroAssembler::wasmNewArrayObject(Register instance, Register result, 7642 Register numElements, 7643 Register allocSite, Register temp, 7644 size_t offsetOfTypeDefData, Label* fail, 7645 uint32_t elemSize, bool zeroFields) { 7646 MOZ_ASSERT(instance != result); 7647 7648 // Don't execute the inline path if GC probes are built in. 7649 #ifdef JS_GC_PROBES 7650 jump(fail); 7651 #endif 7652 7653 // Don't execute the inline path if there is an allocation metadata builder 7654 // on the realm. 7655 branchPtr( 7656 Assembler::NotEqual, 7657 Address(instance, wasm::Instance::offsetOfAllocationMetadataBuilder()), 7658 ImmWord(0), fail); 7659 7660 #ifdef JS_GC_ZEAL 7661 // Don't execute the inline path if gc zeal or tracing are active. 7662 loadPtr(Address(instance, wasm::Instance::offsetOfAddressOfGCZealModeBits()), 7663 temp); 7664 load32(Address(temp, 0), temp); 7665 branch32(Assembler::NotEqual, temp, Imm32(0), fail); 7666 #endif 7667 7668 // If the alloc site is long lived, immediately fall back to the OOL path, 7669 // which will handle that. 7670 branchTestPtr(Assembler::NonZero, 7671 Address(allocSite, gc::AllocSite::offsetOfScriptAndState()), 7672 Imm32(gc::AllocSite::LONG_LIVED_BIT), fail); 7673 7674 // Ensure that the numElements is small enough to fit in inline storage. 7675 branch32(Assembler::Above, numElements, 7676 Imm32(WasmArrayObject::maxInlineElementsForElemSize(elemSize)), 7677 fail); 7678 7679 // Push numElements for later; numElements will be used as a temp in the 7680 // meantime. Make sure that all exit paths pop the value again! 7681 Label popAndFail; 7682 #ifdef JS_CODEGEN_ARM64 7683 // On arm64, we must maintain 16-alignment of both the actual and pseudo stack 7684 // pointers. 7685 push(numElements, xzr); 7686 syncStackPtr(); 7687 #else 7688 push(numElements); 7689 #endif 7690 7691 // Compute the size of the allocation in bytes. The final size must correspond 7692 // to an AllocKind. See WasmArrayObject::calcStorageBytes and 7693 // WasmArrayObject::allocKindForIL. 7694 7695 // Compute the size of all array element data. 7696 mul32(Imm32(elemSize), numElements); 7697 // Add the data header. 7698 add32(Imm32(sizeof(WasmArrayObject::DataHeader)), numElements); 7699 // Round up to gc::CellAlignBytes to play nice with the GC and to simplify the 7700 // zeroing logic below. 7701 add32(Imm32(gc::CellAlignBytes - 1), numElements); 7702 and32(Imm32(~int32_t(gc::CellAlignBytes - 1)), numElements); 7703 // Add the size of the WasmArrayObject to get the full allocation size. 7704 static_assert(WasmArrayObject_MaxInlineBytes + sizeof(WasmArrayObject) < 7705 INT32_MAX); 7706 add32(Imm32(sizeof(WasmArrayObject)), numElements); 7707 // Per gc::slotsToAllocKindBytes, subtract sizeof(NativeObject), 7708 // divide by sizeof(js::Value), then look up the final AllocKind-based 7709 // allocation size from a table. 7710 movePtr(wasm::SymbolicAddress::SlotsToAllocKindBytesTable, temp); 7711 move32ZeroExtendToPtr(numElements, numElements); 7712 subPtr(Imm32(sizeof(NativeObject)), numElements); 7713 static_assert(sizeof(js::Value) == 8); 7714 rshiftPtr(Imm32(3), numElements); 7715 static_assert(sizeof(gc::slotsToAllocKindBytes[0]) == 4); 7716 load32(BaseIndex(temp, numElements, Scale::TimesFour), numElements); 7717 7718 wasmBumpPointerAllocateDynamic(instance, result, allocSite, 7719 /*size=*/numElements, temp, &popAndFail); 7720 7721 // Initialize the shape and STV 7722 loadPtr(Address(instance, offsetOfTypeDefData + 7723 wasm::TypeDefInstanceData::offsetOfShape()), 7724 temp); 7725 storePtr(temp, Address(result, WasmArrayObject::offsetOfShape())); 7726 loadPtr(Address(instance, 7727 offsetOfTypeDefData + 7728 wasm::TypeDefInstanceData::offsetOfSuperTypeVector()), 7729 temp); 7730 storePtr(temp, Address(result, WasmArrayObject::offsetOfSuperTypeVector())); 7731 7732 // Store inline data header and data pointer 7733 storePtr(ImmWord(WasmArrayObject::DataIsIL), 7734 Address(result, WasmArrayObject::offsetOfInlineStorage())); 7735 computeEffectiveAddress( 7736 Address(result, WasmArrayObject::offsetOfInlineArrayData()), temp); 7737 // temp now points at the base of the array data; this will be used later 7738 storePtr(temp, Address(result, WasmArrayObject::offsetOfData())); 7739 // numElements will be saved to the array object later; for now we want to 7740 // continue using numElements as a temp. 7741 7742 // Zero the array elements. This loop depends on the size of the array data 7743 // being a multiple of the machine word size. This is currently always the 7744 // case since WasmArrayObject::calcStorageBytes rounds up to 7745 // gc::CellAlignBytes. 7746 static_assert(gc::CellAlignBytes % sizeof(void*) == 0); 7747 Label zeroed; 7748 if (zeroFields) { 7749 // numElements currently stores the total size of the allocation. temp 7750 // points at the base of the inline array data. We will zero the memory by 7751 // advancing numElements to the end of the allocation, then counting down 7752 // toward temp, zeroing one word at a time. The following aliases make this 7753 // clearer. 7754 Register current = numElements; 7755 Register inlineArrayData = temp; 7756 7757 // We first need to update current to actually point at the end of the 7758 // allocation. We can compute this from the data pointer, since the data 7759 // pointer points at a known offset within the array. 7760 // 7761 // It is easier to understand the code below as first subtracting the offset 7762 // (to get back to the start of the allocation), then adding the total size 7763 // of the allocation (using Scale::TimesOne). 7764 computeEffectiveAddress( 7765 BaseIndex(inlineArrayData, current, Scale::TimesOne, 7766 -int32_t(WasmArrayObject::offsetOfInlineArrayData())), 7767 current); 7768 7769 // Exit immediately if the array has zero elements. 7770 branchPtr(Assembler::Equal, current, inlineArrayData, &zeroed); 7771 7772 // Loop, counting down until current == inlineArrayData. 7773 Label loop; 7774 bind(&loop); 7775 subPtr(Imm32(sizeof(void*)), current); 7776 storePtr(ImmWord(0), Address(current, 0)); 7777 branchPtr(Assembler::NotEqual, current, inlineArrayData, &loop); 7778 } 7779 bind(&zeroed); 7780 7781 // Finally, store the actual numElements in the array object. 7782 #ifdef JS_CODEGEN_ARM64 7783 pop(xzr, numElements); 7784 syncStackPtr(); 7785 #else 7786 pop(numElements); 7787 #endif 7788 store32(numElements, Address(result, WasmArrayObject::offsetOfNumElements())); 7789 7790 Label done; 7791 jump(&done); 7792 7793 bind(&popAndFail); 7794 #ifdef JS_CODEGEN_ARM64 7795 pop(xzr, numElements); 7796 syncStackPtr(); 7797 #else 7798 pop(numElements); 7799 #endif 7800 jump(fail); 7801 7802 bind(&done); 7803 } 7804 7805 void MacroAssembler::wasmNewArrayObjectFixed( 7806 Register instance, Register result, Register allocSite, Register temp1, 7807 Register temp2, size_t offsetOfTypeDefData, Label* fail, 7808 uint32_t numElements, uint32_t storageBytes, bool zeroFields) { 7809 MOZ_ASSERT(storageBytes <= WasmArrayObject_MaxInlineBytes); 7810 MOZ_ASSERT(instance != result); 7811 7812 // Don't execute the inline path if GC probes are built in. 7813 #ifdef JS_GC_PROBES 7814 jump(fail); 7815 #endif 7816 7817 // Don't execute the inline path if there is an allocation metadata builder 7818 // on the realm. 7819 branchPtr( 7820 Assembler::NotEqual, 7821 Address(instance, wasm::Instance::offsetOfAllocationMetadataBuilder()), 7822 ImmWord(0), fail); 7823 7824 #ifdef JS_GC_ZEAL 7825 // Don't execute the inline path if gc zeal or tracing are active. 7826 loadPtr(Address(instance, wasm::Instance::offsetOfAddressOfGCZealModeBits()), 7827 temp1); 7828 load32(Address(temp1, 0), temp1); 7829 branch32(Assembler::NotEqual, temp1, Imm32(0), fail); 7830 #endif 7831 7832 // If the alloc site is long lived, immediately fall back to the OOL path, 7833 // which will handle that. 7834 branchTestPtr(Assembler::NonZero, 7835 Address(allocSite, gc::AllocSite::offsetOfScriptAndState()), 7836 Imm32(gc::AllocSite::LONG_LIVED_BIT), fail); 7837 7838 gc::AllocKind allocKind = WasmArrayObject::allocKindForIL(storageBytes); 7839 uint32_t totalSize = gc::Arena::thingSize(allocKind); 7840 wasmBumpPointerAllocate(instance, result, allocSite, temp1, fail, totalSize); 7841 7842 loadPtr(Address(instance, offsetOfTypeDefData + 7843 wasm::TypeDefInstanceData::offsetOfShape()), 7844 temp1); 7845 loadPtr(Address(instance, 7846 offsetOfTypeDefData + 7847 wasm::TypeDefInstanceData::offsetOfSuperTypeVector()), 7848 temp2); 7849 storePtr(temp1, Address(result, WasmArrayObject::offsetOfShape())); 7850 storePtr(temp2, Address(result, WasmArrayObject::offsetOfSuperTypeVector())); 7851 store32(Imm32(numElements), 7852 Address(result, WasmArrayObject::offsetOfNumElements())); 7853 7854 // Store inline data header and data pointer 7855 storePtr(ImmWord(WasmArrayObject::DataIsIL), 7856 Address(result, WasmArrayObject::offsetOfInlineStorage())); 7857 computeEffectiveAddress( 7858 Address(result, WasmArrayObject::offsetOfInlineArrayData()), temp2); 7859 // temp2 now points at the base of the array data; this will be used later 7860 storePtr(temp2, Address(result, WasmArrayObject::offsetOfData())); 7861 7862 if (zeroFields) { 7863 MOZ_ASSERT(storageBytes % sizeof(void*) == 0); 7864 7865 // Advance temp1 to the end of the allocation 7866 // (note that temp2 is already past the data header) 7867 Label done; 7868 computeEffectiveAddress( 7869 Address(temp2, -sizeof(WasmArrayObject::DataHeader) + storageBytes), 7870 temp1); 7871 branchPtr(Assembler::Equal, temp1, temp2, &done); 7872 7873 // Count temp2 down toward temp1, zeroing one word at a time 7874 Label loop; 7875 bind(&loop); 7876 subPtr(Imm32(sizeof(void*)), temp1); 7877 storePtr(ImmWord(0), Address(temp1, 0)); 7878 branchPtr(Assembler::NotEqual, temp1, temp2, &loop); 7879 7880 bind(&done); 7881 } 7882 } 7883 7884 void MacroAssembler::wasmBumpPointerAllocate(Register instance, Register result, 7885 Register allocSite, Register temp1, 7886 Label* fail, uint32_t size) { 7887 MOZ_ASSERT(size >= gc::MinCellSize); 7888 7889 uint32_t totalSize = size + Nursery::nurseryCellHeaderSize(); 7890 MOZ_ASSERT(totalSize < INT32_MAX, "Nursery allocation too large"); 7891 MOZ_ASSERT(totalSize % gc::CellAlignBytes == 0); 7892 7893 int32_t endOffset = Nursery::offsetOfCurrentEndFromPosition(); 7894 7895 // Bail to OOL code if the alloc site needs to be pushed onto the active 7896 // list. 7897 load32(Address(allocSite, gc::AllocSite::offsetOfNurseryAllocCount()), temp1); 7898 branch32(Assembler::Equal, temp1, 7899 Imm32(js::gc::NormalSiteAttentionThreshold - 1), fail); 7900 7901 // Bump allocate in the nursery, bailing if there is not enough room. 7902 loadPtr(Address(instance, wasm::Instance::offsetOfAddressOfNurseryPosition()), 7903 temp1); 7904 loadPtr(Address(temp1, 0), result); 7905 addPtr(Imm32(totalSize), result); 7906 branchPtr(Assembler::Below, Address(temp1, endOffset), result, fail); 7907 storePtr(result, Address(temp1, 0)); 7908 subPtr(Imm32(size), result); 7909 7910 // Increment the alloc count in the allocation site and store pointer in the 7911 // nursery cell header. See NurseryCellHeader::MakeValue. 7912 add32(Imm32(1), 7913 Address(allocSite, gc::AllocSite::offsetOfNurseryAllocCount())); 7914 // Because JS::TraceKind::Object is zero, there is no need to explicitly set 7915 // it in the nursery cell header. 7916 static_assert(int(JS::TraceKind::Object) == 0); 7917 storePtr(allocSite, Address(result, -js::Nursery::nurseryCellHeaderSize())); 7918 } 7919 7920 void MacroAssembler::wasmBumpPointerAllocateDynamic( 7921 Register instance, Register result, Register allocSite, Register size, 7922 Register temp1, Label* fail) { 7923 #ifdef DEBUG 7924 // Replaces MOZ_ASSERT(size >= gc::MinCellSize); 7925 Label ok1; 7926 branch32(Assembler::AboveOrEqual, size, Imm32(gc::MinCellSize), &ok1); 7927 breakpoint(); 7928 bind(&ok1); 7929 7930 Label ok2; 7931 branch32(Assembler::BelowOrEqual, size, Imm32(JSObject::MAX_BYTE_SIZE), &ok2); 7932 breakpoint(); 7933 bind(&ok2); 7934 #endif 7935 7936 int32_t endOffset = Nursery::offsetOfCurrentEndFromPosition(); 7937 7938 // Bail to OOL code if the alloc site needs to be initialized. 7939 load32(Address(allocSite, gc::AllocSite::offsetOfNurseryAllocCount()), temp1); 7940 branch32(Assembler::Equal, temp1, 7941 Imm32(js::gc::NormalSiteAttentionThreshold - 1), fail); 7942 7943 // Bump allocate in the nursery, bailing if there is not enough room. 7944 loadPtr(Address(instance, wasm::Instance::offsetOfAddressOfNurseryPosition()), 7945 temp1); 7946 loadPtr(Address(temp1, 0), result); 7947 computeEffectiveAddress(BaseIndex(result, size, Scale::TimesOne, 7948 Nursery::nurseryCellHeaderSize()), 7949 result); 7950 branchPtr(Assembler::Below, Address(temp1, endOffset), result, fail); 7951 storePtr(result, Address(temp1, 0)); 7952 subPtr(size, result); 7953 7954 // Increment the alloc count in the allocation site and store pointer in the 7955 // nursery cell header. See NurseryCellHeader::MakeValue. 7956 7957 add32(Imm32(1), 7958 Address(allocSite, gc::AllocSite::offsetOfNurseryAllocCount())); 7959 // Because JS::TraceKind::Object is zero, there is no need to explicitly set 7960 // it in the nursery cell header. 7961 static_assert(int(JS::TraceKind::Object) == 0); 7962 storePtr(allocSite, Address(result, -js::Nursery::nurseryCellHeaderSize())); 7963 } 7964 7965 // Unboxing is branchy and contorted because of Spectre mitigations - we don't 7966 // have enough scratch registers. Were it not for the spectre mitigations in 7967 // branchTestObjClass, the branch nest below would be restructured significantly 7968 // by inverting branches and using fewer registers. 7969 7970 // Unbox an anyref in src (clobbering src in the process) and then re-box it as 7971 // a Value in *dst. See the definition of AnyRef for a discussion of pointer 7972 // representation. 7973 void MacroAssembler::convertWasmAnyRefToValue(Register instance, Register src, 7974 ValueOperand dst, 7975 Register scratch) { 7976 MOZ_ASSERT(src != scratch); 7977 #if JS_BITS_PER_WORD == 32 7978 MOZ_ASSERT(dst.typeReg() != scratch); 7979 MOZ_ASSERT(dst.payloadReg() != scratch); 7980 #else 7981 MOZ_ASSERT(dst.valueReg() != scratch); 7982 #endif 7983 7984 Label isI31, isObjectOrNull, isObject, isWasmValueBox, done; 7985 7986 // Check for if this is an i31 value first 7987 branchTestPtr(Assembler::NonZero, src, Imm32(int32_t(wasm::AnyRefTag::I31)), 7988 &isI31); 7989 // Then check for the object or null tag 7990 branchTestPtr(Assembler::Zero, src, Imm32(wasm::AnyRef::TagMask), 7991 &isObjectOrNull); 7992 7993 // If we're not i31, object, or null, we must be a string 7994 untagWasmAnyRef(src, src, wasm::AnyRefTag::String); 7995 moveValue(TypedOrValueRegister(MIRType::String, AnyRegister(src)), dst); 7996 jump(&done); 7997 7998 // This is an i31 value, convert to an int32 JS value 7999 bind(&isI31); 8000 convertWasmI31RefTo32Signed(src, src); 8001 moveValue(TypedOrValueRegister(MIRType::Int32, AnyRegister(src)), dst); 8002 jump(&done); 8003 8004 // Check for the null value 8005 bind(&isObjectOrNull); 8006 branchTestPtr(Assembler::NonZero, src, src, &isObject); 8007 moveValue(NullValue(), dst); 8008 jump(&done); 8009 8010 // Otherwise we must be a non-null object. We next to check if it's storing a 8011 // boxed value 8012 bind(&isObject); 8013 // The type test will clear src if the test fails, so store early. 8014 moveValue(TypedOrValueRegister(MIRType::Object, AnyRegister(src)), dst); 8015 // Spectre mitigations: see comment above about efficiency. 8016 branchTestObjClass(Assembler::Equal, src, 8017 Address(instance, wasm::Instance::offsetOfValueBoxClass()), 8018 scratch, src, &isWasmValueBox); 8019 jump(&done); 8020 8021 // This is a boxed JS value, unbox it. 8022 bind(&isWasmValueBox); 8023 loadValue(Address(src, wasm::AnyRef::valueBoxOffsetOfValue()), dst); 8024 8025 bind(&done); 8026 } 8027 8028 void MacroAssembler::convertWasmAnyRefToValue(Register instance, Register src, 8029 const Address& dst, 8030 Register scratch) { 8031 MOZ_ASSERT(src != scratch); 8032 8033 Label isI31, isObjectOrNull, isObject, isWasmValueBox, done; 8034 8035 // Check for if this is an i31 value first 8036 branchTestPtr(Assembler::NonZero, src, Imm32(int32_t(wasm::AnyRefTag::I31)), 8037 &isI31); 8038 // Then check for the object or null tag 8039 branchTestPtr(Assembler::Zero, src, Imm32(wasm::AnyRef::TagMask), 8040 &isObjectOrNull); 8041 8042 // If we're not i31, object, or null, we must be a string 8043 andPtr(Imm32(int32_t(~wasm::AnyRef::TagMask)), src); 8044 storeValue(JSVAL_TYPE_STRING, src, dst); 8045 jump(&done); 8046 8047 // This is an i31 value, convert to an int32 JS value 8048 bind(&isI31); 8049 convertWasmI31RefTo32Signed(src, src); 8050 storeValue(JSVAL_TYPE_INT32, src, dst); 8051 jump(&done); 8052 8053 // Check for the null value 8054 bind(&isObjectOrNull); 8055 branchTestPtr(Assembler::NonZero, src, src, &isObject); 8056 storeValue(NullValue(), dst); 8057 jump(&done); 8058 8059 // Otherwise we must be a non-null object. We next to check if it's storing a 8060 // boxed value 8061 bind(&isObject); 8062 // The type test will clear src if the test fails, so store early. 8063 storeValue(JSVAL_TYPE_OBJECT, src, dst); 8064 // Spectre mitigations: see comment above about efficiency. 8065 branchTestObjClass(Assembler::Equal, src, 8066 Address(instance, wasm::Instance::offsetOfValueBoxClass()), 8067 scratch, src, &isWasmValueBox); 8068 jump(&done); 8069 8070 // This is a boxed JS value, unbox it. 8071 bind(&isWasmValueBox); 8072 copy64(Address(src, wasm::AnyRef::valueBoxOffsetOfValue()), dst, scratch); 8073 8074 bind(&done); 8075 } 8076 8077 void MacroAssembler::nopPatchableToCall(const wasm::CallSiteDesc& desc) { 8078 CodeOffset offset = nopPatchableToCall(); 8079 append(desc, offset); 8080 } 8081 8082 // Given a cell and the chunk containing that cell, load the word containing 8083 // the mark bits for that cell, and compute the bitIndex for a particular mark 8084 // color. 8085 void MacroAssembler::loadMarkBits(Register cell, Register chunk, 8086 Register markWord, Register bitIndex, 8087 Register temp, gc::ColorBit color) { 8088 MOZ_ASSERT(temp != bitIndex); 8089 MOZ_ASSERT(temp != chunk); 8090 MOZ_ASSERT(chunk != bitIndex); 8091 8092 // Determine the bit index and store in bitIndex. 8093 // 8094 // bit = (addr & js::gc::ChunkMask) / js::gc::CellBytesPerMarkBit + 8095 // static_cast<uint32_t>(colorBit); 8096 static_assert(gc::CellBytesPerMarkBit == 8, 8097 "Calculation below relies on this"); 8098 andPtr(Imm32(gc::ChunkMask), cell, bitIndex); 8099 rshiftPtr(Imm32(3), bitIndex); 8100 if (int32_t(color) != 0) { 8101 addPtr(Imm32(int32_t(color)), bitIndex); 8102 } 8103 8104 static_assert(gc::ChunkMarkBitmap::BitsPerWord == JS_BITS_PER_WORD, 8105 "Calculation below relies on this"); 8106 8107 // Load the mark word 8108 // 8109 // word = chunk.bitmap[bit / WordBits]; 8110 8111 // Fold the adjustment for the fact that arenas don't start at the beginning 8112 // of the chunk into the offset to the chunk bitmap. 8113 const size_t firstArenaAdjustment = 8114 gc::ChunkMarkBitmap::FirstThingAdjustmentBits / CHAR_BIT; 8115 const intptr_t offset = 8116 intptr_t(gc::ChunkMarkBitmapOffset) - intptr_t(firstArenaAdjustment); 8117 8118 uint8_t shift = mozilla::FloorLog2Size(JS_BITS_PER_WORD); 8119 rshiftPtr(Imm32(shift), bitIndex, temp); 8120 loadPtr(BaseIndex(chunk, temp, ScalePointer, offset), markWord); 8121 } 8122 8123 void MacroAssembler::emitPreBarrierFastPath(MIRType type, Register temp1, 8124 Register temp2, Register temp3, 8125 Label* noBarrier) { 8126 MOZ_ASSERT(temp1 != PreBarrierReg); 8127 MOZ_ASSERT(temp2 != PreBarrierReg); 8128 MOZ_ASSERT(temp3 != PreBarrierReg); 8129 8130 #ifdef JS_CODEGEN_X64 8131 MOZ_ASSERT(temp3 == rcx); 8132 #elif JS_CODEGEN_X86 8133 MOZ_ASSERT(temp3 == ecx); 8134 #endif 8135 8136 // Load the GC thing in temp1. 8137 if (type == MIRType::Value) { 8138 unboxGCThingForGCBarrier(Address(PreBarrierReg, 0), temp1); 8139 } else if (type == MIRType::WasmAnyRef) { 8140 unboxWasmAnyRefGCThingForGCBarrier(Address(PreBarrierReg, 0), temp1); 8141 } else { 8142 MOZ_ASSERT(type == MIRType::Object || type == MIRType::String || 8143 type == MIRType::Shape); 8144 loadPtr(Address(PreBarrierReg, 0), temp1); 8145 } 8146 8147 #ifdef DEBUG 8148 // The caller should have checked for null pointers. 8149 Label nonZero; 8150 branchTestPtr(Assembler::NonZero, temp1, temp1, &nonZero); 8151 assumeUnreachable("JIT pre-barrier: unexpected nullptr"); 8152 bind(&nonZero); 8153 #endif 8154 8155 // Load the chunk address in temp2. 8156 andPtr(Imm32(int32_t(~gc::ChunkMask)), temp1, temp2); 8157 8158 // If the GC thing is in the nursery, we don't need to barrier it. 8159 if (type == MIRType::Value || type == MIRType::Object || 8160 type == MIRType::String || type == MIRType::WasmAnyRef) { 8161 branchPtr(Assembler::NotEqual, Address(temp2, gc::ChunkStoreBufferOffset), 8162 ImmWord(0), noBarrier); 8163 } else { 8164 #ifdef DEBUG 8165 Label isTenured; 8166 branchPtr(Assembler::Equal, Address(temp2, gc::ChunkStoreBufferOffset), 8167 ImmWord(0), &isTenured); 8168 assumeUnreachable("JIT pre-barrier: unexpected nursery pointer"); 8169 bind(&isTenured); 8170 #endif 8171 } 8172 8173 // Check if the cell is marked black. 8174 // Load the bitindex into temp3 and the bitmap word into temp2. 8175 loadMarkBits(temp1, temp2, temp2, temp3, temp1, gc::ColorBit::BlackBit); 8176 8177 // Load the mask in temp1. 8178 // mask = uintptr_t(1) << (bit % WordBits); 8179 andPtr(Imm32(gc::ChunkMarkBitmap::BitsPerWord - 1), temp3); 8180 move32(Imm32(1), temp1); 8181 lshiftPtr(temp3, temp1); 8182 8183 // No barrier is needed if the bit is set, |word & mask != 0|. 8184 branchTestPtr(Assembler::NonZero, temp2, temp1, noBarrier); 8185 } 8186 8187 void MacroAssembler::emitValueReadBarrierFastPath( 8188 ValueOperand value, Register cell, Register temp1, Register temp2, 8189 Register temp3, Register temp4, Label* barrier) { 8190 Label done; 8191 8192 // No barrier needed for non-GC types 8193 branchTestGCThing(Assembler::NotEqual, value, &done); 8194 8195 // Load the GC thing in `cell`. 8196 unboxGCThingForGCBarrier(value, cell); 8197 8198 // Load the chunk address. 8199 Register chunk = temp1; 8200 andPtr(Imm32(int32_t(~gc::ChunkMask)), cell, chunk); 8201 8202 // If the GC thing is in the nursery, we don't need to barrier it. 8203 branchPtr(Assembler::NotEqual, Address(chunk, gc::ChunkStoreBufferOffset), 8204 ImmWord(0), &done); 8205 8206 // Load the mark word and bit index for the black bit. 8207 Register markWord = temp2; 8208 Register bitIndex = temp3; 8209 loadMarkBits(cell, chunk, markWord, bitIndex, temp4, gc::ColorBit::BlackBit); 8210 8211 // The mask for the black bit is 1 << (bitIndex % WordBits). 8212 Register mask = temp4; 8213 andPtr(Imm32(gc::ChunkMarkBitmap::BitsPerWord - 1), bitIndex); 8214 move32(Imm32(1), mask); 8215 flexibleLshiftPtr(bitIndex, mask); 8216 8217 // No barrier is needed if the black bit is set. 8218 branchTestPtr(Assembler::NonZero, markWord, mask, &done); 8219 8220 // The bit index for the gray bit is bitIndex + 1. If the black bit 8221 // is any bit except the highest bit of the mark word, we can reuse 8222 // the mark word we've already loaded, and simply shift the mask by 8223 // 1. 8224 Label noMaskOverflow; 8225 lshiftPtr(Imm32(1), mask); 8226 branchTestPtr(Assembler::NonZero, mask, mask, &noMaskOverflow); 8227 8228 // If the black bit was the high bit of the mark word, we need to load 8229 // a new mark word. In this case the mask for the gray bit will always 8230 // be 1 (the lowest bit of the next word). 8231 loadMarkBits(cell, chunk, markWord, bitIndex, temp4, 8232 gc::ColorBit::GrayOrBlackBit); 8233 move32(Imm32(1), mask); 8234 bind(&noMaskOverflow); 8235 8236 // If the gray bit is set, then we *do* need a barrier. 8237 branchTestPtr(Assembler::NonZero, markWord, mask, barrier); 8238 8239 // Otherwise, we don't need a barrier unless we're in the middle of 8240 // an incremental GC. 8241 branchTestNeedsIncrementalBarrierAnyZone(Assembler::NonZero, barrier, temp1); 8242 bind(&done); 8243 } 8244 8245 // ======================================================================== 8246 // JS atomic operations. 8247 8248 void MacroAssembler::atomicIsLockFreeJS(Register value, Register output) { 8249 // Keep this in sync with isLockfreeJS() in jit/AtomicOperations.h. 8250 static_assert(AtomicOperations::isLockfreeJS(1)); // Implementation artifact 8251 static_assert(AtomicOperations::isLockfreeJS(2)); // Implementation artifact 8252 static_assert(AtomicOperations::isLockfreeJS(4)); // Spec requirement 8253 static_assert(AtomicOperations::isLockfreeJS(8)); // Implementation artifact 8254 8255 Label done; 8256 move32(Imm32(1), output); 8257 branch32(Assembler::Equal, value, Imm32(8), &done); 8258 branch32(Assembler::Equal, value, Imm32(4), &done); 8259 branch32(Assembler::Equal, value, Imm32(2), &done); 8260 branch32(Assembler::Equal, value, Imm32(1), &done); 8261 move32(Imm32(0), output); 8262 bind(&done); 8263 } 8264 8265 // ======================================================================== 8266 // Spectre Mitigations. 8267 8268 void MacroAssembler::spectreMaskIndex32(Register index, Register length, 8269 Register output) { 8270 MOZ_ASSERT(JitOptions.spectreIndexMasking); 8271 MOZ_ASSERT(length != output); 8272 MOZ_ASSERT(index != output); 8273 8274 move32(Imm32(0), output); 8275 cmp32Move32(Assembler::Below, index, length, index, output); 8276 } 8277 8278 void MacroAssembler::spectreMaskIndex32(Register index, const Address& length, 8279 Register output) { 8280 MOZ_ASSERT(JitOptions.spectreIndexMasking); 8281 MOZ_ASSERT(index != length.base); 8282 MOZ_ASSERT(length.base != output); 8283 MOZ_ASSERT(index != output); 8284 8285 move32(Imm32(0), output); 8286 cmp32Move32(Assembler::Below, index, length, index, output); 8287 } 8288 8289 void MacroAssembler::spectreMaskIndexPtr(Register index, Register length, 8290 Register output) { 8291 MOZ_ASSERT(JitOptions.spectreIndexMasking); 8292 MOZ_ASSERT(length != output); 8293 MOZ_ASSERT(index != output); 8294 8295 movePtr(ImmWord(0), output); 8296 cmpPtrMovePtr(Assembler::Below, index, length, index, output); 8297 } 8298 8299 void MacroAssembler::spectreMaskIndexPtr(Register index, const Address& length, 8300 Register output) { 8301 MOZ_ASSERT(JitOptions.spectreIndexMasking); 8302 MOZ_ASSERT(index != length.base); 8303 MOZ_ASSERT(length.base != output); 8304 MOZ_ASSERT(index != output); 8305 8306 movePtr(ImmWord(0), output); 8307 cmpPtrMovePtr(Assembler::Below, index, length, index, output); 8308 } 8309 8310 void MacroAssembler::boundsCheck32PowerOfTwo(Register index, uint32_t length, 8311 Label* failure) { 8312 MOZ_ASSERT(mozilla::IsPowerOfTwo(length)); 8313 branch32(Assembler::AboveOrEqual, index, Imm32(length), failure); 8314 8315 // Note: it's fine to clobber the input register, as this is a no-op: it 8316 // only affects speculative execution. 8317 if (JitOptions.spectreIndexMasking) { 8318 and32(Imm32(length - 1), index); 8319 } 8320 } 8321 8322 void MacroAssembler::loadWasmPinnedRegsFromInstance( 8323 const wasm::MaybeTrapSiteDesc& trapSiteDesc) { 8324 #ifdef WASM_HAS_HEAPREG 8325 static_assert(wasm::Instance::offsetOfMemory0Base() < 4096, 8326 "We count only on the low page being inaccessible"); 8327 FaultingCodeOffset fco = loadPtr( 8328 Address(InstanceReg, wasm::Instance::offsetOfMemory0Base()), HeapReg); 8329 if (trapSiteDesc) { 8330 append(wasm::Trap::IndirectCallToNull, wasm::TrapMachineInsnForLoadWord(), 8331 fco.get(), *trapSiteDesc); 8332 } 8333 #else 8334 MOZ_ASSERT(!trapSiteDesc); 8335 #endif 8336 } 8337 8338 //}}} check_macroassembler_style 8339 8340 #ifdef JS_64BIT 8341 void MacroAssembler::debugAssertCanonicalInt32(Register r) { 8342 # ifdef DEBUG 8343 if (!js::jit::JitOptions.lessDebugCode) { 8344 # if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM64) 8345 Label ok; 8346 branchPtr(Assembler::BelowOrEqual, r, ImmWord(UINT32_MAX), &ok); 8347 breakpoint(); 8348 bind(&ok); 8349 # elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || \ 8350 defined(JS_CODEGEN_RISCV64) 8351 Label ok; 8352 UseScratchRegisterScope temps(*this); 8353 Register scratch = temps.Acquire(); 8354 move32SignExtendToPtr(r, scratch); 8355 branchPtr(Assembler::Equal, r, scratch, &ok); 8356 breakpoint(); 8357 bind(&ok); 8358 # else 8359 MOZ_CRASH("IMPLEMENT ME"); 8360 # endif 8361 } 8362 # endif 8363 } 8364 #endif 8365 8366 void MacroAssembler::memoryBarrierBefore(Synchronization sync) { 8367 memoryBarrier(sync.barrierBefore); 8368 } 8369 8370 void MacroAssembler::memoryBarrierAfter(Synchronization sync) { 8371 memoryBarrier(sync.barrierAfter); 8372 } 8373 8374 void MacroAssembler::convertDoubleToFloat16(FloatRegister src, 8375 FloatRegister dest, Register temp, 8376 LiveRegisterSet volatileLiveRegs) { 8377 if (MacroAssembler::SupportsFloat64To16()) { 8378 convertDoubleToFloat16(src, dest); 8379 8380 // Float16 is currently passed as Float32, so expand again to Float32. 8381 convertFloat16ToFloat32(dest, dest); 8382 return; 8383 } 8384 8385 LiveRegisterSet save = volatileLiveRegs; 8386 save.takeUnchecked(dest); 8387 save.takeUnchecked(dest.asDouble()); 8388 save.takeUnchecked(temp); 8389 8390 PushRegsInMask(save); 8391 8392 using Fn = float (*)(double); 8393 setupUnalignedABICall(temp); 8394 passABIArg(src, ABIType::Float64); 8395 callWithABI<Fn, jit::RoundFloat16ToFloat32>(ABIType::Float32); 8396 storeCallFloatResult(dest); 8397 8398 PopRegsInMask(save); 8399 } 8400 8401 void MacroAssembler::convertDoubleToFloat16(FloatRegister src, 8402 FloatRegister dest, Register temp1, 8403 Register temp2) { 8404 MOZ_ASSERT(MacroAssembler::SupportsFloat64To16() || 8405 MacroAssembler::SupportsFloat32To16()); 8406 MOZ_ASSERT(src != dest); 8407 8408 if (MacroAssembler::SupportsFloat64To16()) { 8409 convertDoubleToFloat16(src, dest); 8410 8411 // Float16 is currently passed as Float32, so expand again to Float32. 8412 convertFloat16ToFloat32(dest, dest); 8413 return; 8414 } 8415 8416 using Float32 = mozilla::FloatingPoint<float>; 8417 8418 #ifdef DEBUG 8419 static auto float32Bits = [](float16 f16) { 8420 // Cast to float and reinterpret to bit representation. 8421 return mozilla::BitwiseCast<Float32::Bits>(static_cast<float>(f16)); 8422 }; 8423 8424 static auto nextExponent = [](float16 f16, int32_t direction) { 8425 constexpr auto kSignificandWidth = Float32::kSignificandWidth; 8426 8427 // Shift out mantissa and then adjust exponent. 8428 auto bits = float32Bits(f16); 8429 return ((bits >> kSignificandWidth) + direction) << kSignificandWidth; 8430 }; 8431 #endif 8432 8433 // Float32 larger or equals to |overflow| are infinity (or NaN) in Float16. 8434 constexpr uint32_t overflow = 0x4780'0000; 8435 MOZ_ASSERT(overflow == nextExponent(std::numeric_limits<float16>::max(), 1)); 8436 8437 // Float32 smaller than |underflow| are zero in Float16. 8438 constexpr uint32_t underflow = 0x3300'0000; 8439 MOZ_ASSERT(underflow == 8440 nextExponent(std::numeric_limits<float16>::denorm_min(), -1)); 8441 8442 // Float32 larger or equals to |normal| are normal numbers in Float16. 8443 constexpr uint32_t normal = 0x3880'0000; 8444 MOZ_ASSERT(normal == float32Bits(std::numeric_limits<float16>::min())); 8445 8446 // There are five possible cases to consider: 8447 // 1. Non-finite (infinity and NaN) 8448 // 2. Overflow to infinity 8449 // 3. Normal numbers 8450 // 4. Denormal numbers 8451 // 5. Underflow to zero 8452 // 8453 // Cases 1-2 and 4-5 don't need separate code paths, so we only need to be 8454 // concerned about incorrect double rounding for cases 3-4. 8455 // 8456 // Double rounding: 8457 // 8458 // Conversion from float64 -> float32 -> float16 can introduce double rounding 8459 // errors when compared to a direct conversion float64 -> float16. 8460 // 8461 // Number of bits in the exponent and mantissa. These are relevant below. 8462 // 8463 // exponent mantissa 8464 // ----------------------- 8465 // f16 | 5 10 8466 // f32 | 8 23 8467 // f64 | 11 52 8468 // 8469 // Examples: 8470 // 8471 // Input (f64): 0.0000610649585723877 8472 // Bits (f64): 3f10'0200'0000'0000 8473 // Bits (f32): 3880'1000 8474 // Bits (f16): 0400 8475 // 8476 // Ignore the three left-most nibbles of the f64 bits (those are the sign and 8477 // exponent). Shift the f64 mantissa right by (52 - 23) = 29 bits. The bits 8478 // of the f32 mantissa are therefore 00'1000. Converting from f32 to f16 will 8479 // right shift the mantissa by (23 - 10) = 13 bits. `001000 >> 13` is all 8480 // zero. Directly converting from f64 to f16 right shifts the f64 mantissa by 8481 // (52 - 10) = 42 bits. `0'0200'0000'0000 >> 42` is also all zero. So in this 8482 // case no double rounding did take place. 8483 // 8484 // Input (f64): 0.00006106495857238771 8485 // Bits (f64): 3f10'0200'0000'0001 8486 // Bits (f32): 3880'1000 8487 // Bits (f16): 0401 8488 // 8489 // The f64 to f32 conversion returns the same result 3880'1000 as in the first 8490 // example, but the direct f64 to f16 conversion must return 0401. Let's look 8491 // at the binary representation of the mantissa. 8492 // 8493 // Mantissa of 3f10'0200'0000'0001 in binary representation: 8494 // 8495 // Low 32-bits 8496 // __________________|__________________ 8497 // / | 8498 // 0000 0000 0010 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 8499 // | | 8500 // | GRS 8501 // | 001 8502 // | 8503 // GRS (G)uard bit 8504 // 011 (R)ound bit 8505 // (S)ticky bit 8506 // 8507 // The guard, round, and sticky bits control when to round: If the round bit 8508 // is one and at least one of guard or sticky is one, then round up. The 8509 // sticky bit is the or-ed value of all bits right of the round bit. 8510 // 8511 // When rounding to float16, GRS is 011, so we have to round up, whereas when 8512 // rounding to float32, GRS is 001, so no rounding takes place. 8513 // 8514 // Mantissa of 3880'1000 in binary representation: 8515 // 8516 // e000 0000 0001 0000 0000 0000 8517 // | 8518 // GRS 8519 // 010 8520 // 8521 // The round bit is set, but neither the guard nor sticky bit is set, so no 8522 // rounding takes place for the f32 -> f16 conversion. We can attempt to 8523 // recover the missing sticky bit from the f64 -> f16 conversion by looking at 8524 // the low 32-bits of the f64 mantissa. If at least one bit is set in the 8525 // low 32-bits (and the MSB is zero), then add one to the f32 mantissa. 8526 // Modified mantissa now looks like: 8527 // 8528 // e000 0000 0001 0000 0000 0001 8529 // | 8530 // GRS 8531 // 011 8532 // 8533 // GRS is now 011, so we round up and get the correctly rounded result 0401. 8534 // 8535 // Input (f64): 0.00006112456321716307 8536 // Bits (f64): 3f10'05ff'ffff'ffff 8537 // Bits (f32): 3880'3000 8538 // Bits (f16): 0401 8539 // 8540 // Low 32-bits 8541 // __________________|__________________ 8542 // / | 8543 // 0000 0000 0101 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 8544 // | | 8545 // | GRS 8546 // | 111 8547 // | 8548 // GRS 8549 // 101 8550 // 8551 // When rounding to float16, GRS is 101, so we don't round, whereas when 8552 // rounding to float32, GRS is 111, so we have to round up. 8553 // 8554 // Mantissa of 3880'3000 in binary representation: 8555 // 8556 // e000 0000 0011 0000 0000 0000 8557 // | 8558 // GRS 8559 // 110 8560 // 8561 // The guard and sticky bits are set, so the float32 -> float16 conversion 8562 // incorrectly rounds up when compared to the direct float64 -> float16 8563 // conversion. To avoid rounding twice we subtract one if the MSB of the low 8564 // 32-bits of the f64 mantissa is set. Modified mantissa now looks like: 8565 // 8566 // e000 0000 0010 1111 1111 1111 8567 // | 8568 // GRS 8569 // 101 8570 // 8571 // GRS is now 101, so we don't round and get the correct result 0401. 8572 // 8573 // Approach used to avoid double rounding: 8574 // 8575 // 1. For normal numbers, inspect the f32 mantissa and if its round bit is set 8576 // and the sticky bits are all zero, then possibly adjust the f32 mantissa 8577 // depending on the low 32-bits of the f64 mantissa. 8578 // 8579 // 2. For denormal numbers, possibly adjust the f32 mantissa if the round and 8580 // sticky bits are all zero. 8581 8582 // First round to float32 and reinterpret to bit representation. 8583 convertDoubleToFloat32(src, dest); 8584 moveFloat32ToGPR(dest, temp1); 8585 8586 // Mask off sign bit to simplify range checks. 8587 and32(Imm32(~Float32::kSignBit), temp1); 8588 8589 Label done; 8590 8591 // No changes necessary for underflow or overflow, including zero and 8592 // non-finite numbers. 8593 branch32(Assembler::Below, temp1, Imm32(underflow), &done); 8594 branch32(Assembler::AboveOrEqual, temp1, Imm32(overflow), &done); 8595 8596 // Compute 0x1000 for normal and 0x0000 for denormal numbers. 8597 cmp32Set(Assembler::AboveOrEqual, temp1, Imm32(normal), temp2); 8598 lshift32(Imm32(12), temp2); 8599 8600 // Look at the last thirteen bits of the mantissa which will be shifted out 8601 // when converting from float32 to float16. (The round and sticky bits.) 8602 // 8603 // Normal numbers: If the round bit is set and sticky bits are zero, then 8604 // adjust the float32 mantissa. 8605 // Denormal numbers: If all bits are zero, then adjust the mantissa. 8606 and32(Imm32(0x1fff), temp1); 8607 branch32(Assembler::NotEqual, temp1, temp2, &done); 8608 { 8609 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) 8610 // x86 can use SIMD instructions to avoid GPR<>XMM register moves. 8611 ScratchSimd128Scope scratch(*this); 8612 8613 int32_t one[] = {1, 0, 0, 0}; 8614 loadConstantSimd128(SimdConstant::CreateX4(one), scratch); 8615 8616 // 1. If the low 32-bits of |src| are all zero, then set |scratch| to 0. 8617 // 2. If the MSB of the low 32-bits is set, then set |scratch| to -1. 8618 // 3. Otherwise set |scratch| to 1. 8619 vpsignd(Operand(src), scratch, scratch); 8620 8621 // Add |scratch| to the mantissa. 8622 vpaddd(Operand(scratch), dest, dest); 8623 #else 8624 // Determine in which direction to round. When the low 32-bits are all zero, 8625 // then we don't have to round. 8626 moveLowDoubleToGPR(src, temp2); 8627 branch32(Assembler::Equal, temp2, Imm32(0), &done); 8628 8629 // Load either -1 or +1 into |temp2|. 8630 rshift32Arithmetic(Imm32(31), temp2); 8631 or32(Imm32(1), temp2); 8632 8633 // Add or subtract one to the mantissa. 8634 moveFloat32ToGPR(dest, temp1); 8635 add32(temp2, temp1); 8636 moveGPRToFloat32(temp1, dest); 8637 #endif 8638 } 8639 8640 bind(&done); 8641 8642 // Perform the actual float16 conversion. 8643 convertFloat32ToFloat16(dest, dest); 8644 8645 // Float16 is currently passed as Float32, so expand again to Float32. 8646 convertFloat16ToFloat32(dest, dest); 8647 } 8648 8649 void MacroAssembler::convertFloat32ToFloat16(FloatRegister src, 8650 FloatRegister dest, Register temp, 8651 LiveRegisterSet volatileLiveRegs) { 8652 if (MacroAssembler::SupportsFloat32To16()) { 8653 convertFloat32ToFloat16(src, dest); 8654 8655 // Float16 is currently passed as Float32, so expand again to Float32. 8656 convertFloat16ToFloat32(dest, dest); 8657 return; 8658 } 8659 8660 LiveRegisterSet save = volatileLiveRegs; 8661 save.takeUnchecked(dest); 8662 save.takeUnchecked(dest.asDouble()); 8663 save.takeUnchecked(temp); 8664 8665 PushRegsInMask(save); 8666 8667 using Fn = float (*)(float); 8668 setupUnalignedABICall(temp); 8669 passABIArg(src, ABIType::Float32); 8670 callWithABI<Fn, jit::RoundFloat16ToFloat32>(ABIType::Float32); 8671 storeCallFloatResult(dest); 8672 8673 PopRegsInMask(save); 8674 } 8675 8676 void MacroAssembler::convertInt32ToFloat16(Register src, FloatRegister dest, 8677 Register temp, 8678 LiveRegisterSet volatileLiveRegs) { 8679 if (MacroAssembler::SupportsFloat32To16()) { 8680 convertInt32ToFloat16(src, dest); 8681 8682 // Float16 is currently passed as Float32, so expand again to Float32. 8683 convertFloat16ToFloat32(dest, dest); 8684 return; 8685 } 8686 8687 LiveRegisterSet save = volatileLiveRegs; 8688 save.takeUnchecked(dest); 8689 save.takeUnchecked(dest.asDouble()); 8690 save.takeUnchecked(temp); 8691 8692 PushRegsInMask(save); 8693 8694 using Fn = float (*)(int32_t); 8695 setupUnalignedABICall(temp); 8696 passABIArg(src); 8697 callWithABI<Fn, jit::RoundFloat16ToFloat32>(ABIType::Float32); 8698 storeCallFloatResult(dest); 8699 8700 PopRegsInMask(save); 8701 } 8702 8703 template <typename T> 8704 void MacroAssembler::loadFloat16(const T& src, FloatRegister dest, 8705 Register temp1, Register temp2, 8706 LiveRegisterSet volatileLiveRegs) { 8707 if (MacroAssembler::SupportsFloat32To16()) { 8708 loadFloat16(src, dest, temp1); 8709 8710 // Float16 is currently passed as Float32, so expand again to Float32. 8711 convertFloat16ToFloat32(dest, dest); 8712 return; 8713 } 8714 8715 load16ZeroExtend(src, temp1); 8716 8717 LiveRegisterSet save = volatileLiveRegs; 8718 save.takeUnchecked(dest); 8719 save.takeUnchecked(dest.asDouble()); 8720 save.takeUnchecked(temp1); 8721 save.takeUnchecked(temp2); 8722 8723 PushRegsInMask(save); 8724 8725 using Fn = float (*)(int32_t); 8726 setupUnalignedABICall(temp2); 8727 passABIArg(temp1); 8728 callWithABI<Fn, jit::Float16ToFloat32>(ABIType::Float32); 8729 storeCallFloatResult(dest); 8730 8731 PopRegsInMask(save); 8732 } 8733 8734 template void MacroAssembler::loadFloat16(const Address& src, 8735 FloatRegister dest, Register temp1, 8736 Register temp2, 8737 LiveRegisterSet volatileLiveRegs); 8738 8739 template void MacroAssembler::loadFloat16(const BaseIndex& src, 8740 FloatRegister dest, Register temp1, 8741 Register temp2, 8742 LiveRegisterSet volatileLiveRegs); 8743 8744 template <typename T> 8745 void MacroAssembler::storeFloat16(FloatRegister src, const T& dest, 8746 Register temp, 8747 LiveRegisterSet volatileLiveRegs) { 8748 ScratchFloat32Scope fpscratch(*this); 8749 8750 if (src.isDouble()) { 8751 if (MacroAssembler::SupportsFloat64To16()) { 8752 convertDoubleToFloat16(src, fpscratch); 8753 storeFloat16(fpscratch, dest, temp); 8754 return; 8755 } 8756 8757 convertDoubleToFloat16(src, fpscratch, temp, volatileLiveRegs); 8758 src = fpscratch; 8759 } 8760 MOZ_ASSERT(src.isSingle()); 8761 8762 if (MacroAssembler::SupportsFloat32To16()) { 8763 convertFloat32ToFloat16(src, fpscratch); 8764 storeFloat16(fpscratch, dest, temp); 8765 return; 8766 } 8767 8768 moveFloat16ToGPR(src, temp, volatileLiveRegs); 8769 store16(temp, dest); 8770 } 8771 8772 template void MacroAssembler::storeFloat16(FloatRegister src, 8773 const Address& dest, Register temp, 8774 LiveRegisterSet volatileLiveRegs); 8775 8776 template void MacroAssembler::storeFloat16(FloatRegister src, 8777 const BaseIndex& dest, Register temp, 8778 LiveRegisterSet volatileLiveRegs); 8779 8780 void MacroAssembler::moveFloat16ToGPR(FloatRegister src, Register dest, 8781 LiveRegisterSet volatileLiveRegs) { 8782 if (MacroAssembler::SupportsFloat32To16()) { 8783 ScratchFloat32Scope fpscratch(*this); 8784 8785 // Float16 is currently passed as Float32, so first narrow to Float16. 8786 convertFloat32ToFloat16(src, fpscratch); 8787 8788 moveFloat16ToGPR(fpscratch, dest); 8789 return; 8790 } 8791 8792 LiveRegisterSet save = volatileLiveRegs; 8793 save.takeUnchecked(dest); 8794 8795 PushRegsInMask(save); 8796 8797 using Fn = int32_t (*)(float); 8798 setupUnalignedABICall(dest); 8799 passABIArg(src, ABIType::Float32); 8800 callWithABI<Fn, jit::Float32ToFloat16>(); 8801 storeCallInt32Result(dest); 8802 8803 PopRegsInMask(save); 8804 } 8805 8806 void MacroAssembler::moveGPRToFloat16(Register src, FloatRegister dest, 8807 Register temp, 8808 LiveRegisterSet volatileLiveRegs) { 8809 if (MacroAssembler::SupportsFloat32To16()) { 8810 moveGPRToFloat16(src, dest); 8811 8812 // Float16 is currently passed as Float32, so expand again to Float32. 8813 convertFloat16ToFloat32(dest, dest); 8814 return; 8815 } 8816 8817 LiveRegisterSet save = volatileLiveRegs; 8818 save.takeUnchecked(dest); 8819 save.takeUnchecked(dest.asDouble()); 8820 save.takeUnchecked(temp); 8821 8822 PushRegsInMask(save); 8823 8824 using Fn = float (*)(int32_t); 8825 setupUnalignedABICall(temp); 8826 passABIArg(src); 8827 callWithABI<Fn, jit::Float16ToFloat32>(ABIType::Float32); 8828 storeCallFloatResult(dest); 8829 8830 PopRegsInMask(save); 8831 } 8832 8833 void MacroAssembler::debugAssertIsObject(const ValueOperand& val) { 8834 #ifdef DEBUG 8835 Label ok; 8836 branchTestObject(Assembler::Equal, val, &ok); 8837 assumeUnreachable("Expected an object!"); 8838 bind(&ok); 8839 #endif 8840 } 8841 8842 void MacroAssembler::debugAssertObjHasFixedSlots(Register obj, 8843 Register scratch) { 8844 #ifdef DEBUG 8845 Label hasFixedSlots; 8846 loadPtr(Address(obj, JSObject::offsetOfShape()), scratch); 8847 branchTest32(Assembler::NonZero, 8848 Address(scratch, Shape::offsetOfImmutableFlags()), 8849 Imm32(NativeShape::fixedSlotsMask()), &hasFixedSlots); 8850 assumeUnreachable("Expected a fixed slot"); 8851 bind(&hasFixedSlots); 8852 #endif 8853 } 8854 8855 void MacroAssembler::debugAssertObjectHasClass(Register obj, Register scratch, 8856 const JSClass* clasp) { 8857 #ifdef DEBUG 8858 Label done; 8859 branchTestObjClassNoSpectreMitigations(Assembler::Equal, obj, clasp, scratch, 8860 &done); 8861 assumeUnreachable("Class check failed"); 8862 bind(&done); 8863 #endif 8864 } 8865 8866 void MacroAssembler::debugAssertGCThingIsTenured(Register ptr, Register temp) { 8867 #ifdef DEBUG 8868 Label done; 8869 branchPtrInNurseryChunk(Assembler::NotEqual, ptr, temp, &done); 8870 assumeUnreachable("Expected a tenured pointer"); 8871 bind(&done); 8872 #endif 8873 } 8874 8875 void MacroAssembler::branchArrayIsNotPacked(Register array, Register temp1, 8876 Register temp2, Label* label) { 8877 loadPtr(Address(array, NativeObject::offsetOfElements()), temp1); 8878 8879 // Test length == initializedLength. 8880 Address initLength(temp1, ObjectElements::offsetOfInitializedLength()); 8881 load32(Address(temp1, ObjectElements::offsetOfLength()), temp2); 8882 branch32(Assembler::NotEqual, initLength, temp2, label); 8883 8884 // Test the NON_PACKED flag. 8885 Address flags(temp1, ObjectElements::offsetOfFlags()); 8886 branchTest32(Assembler::NonZero, flags, Imm32(ObjectElements::NON_PACKED), 8887 label); 8888 } 8889 8890 void MacroAssembler::setIsPackedArray(Register obj, Register output, 8891 Register temp) { 8892 // Ensure it's an ArrayObject. 8893 Label notPackedArray; 8894 branchTestObjClass(Assembler::NotEqual, obj, &ArrayObject::class_, temp, obj, 8895 ¬PackedArray); 8896 8897 branchArrayIsNotPacked(obj, temp, output, ¬PackedArray); 8898 8899 Label done; 8900 move32(Imm32(1), output); 8901 jump(&done); 8902 8903 bind(¬PackedArray); 8904 move32(Imm32(0), output); 8905 8906 bind(&done); 8907 } 8908 8909 void MacroAssembler::packedArrayPop(Register array, ValueOperand output, 8910 Register temp1, Register temp2, 8911 Label* fail) { 8912 // Load obj->elements in temp1. 8913 loadPtr(Address(array, NativeObject::offsetOfElements()), temp1); 8914 8915 // Check flags. 8916 static constexpr uint32_t UnhandledFlags = 8917 ObjectElements::Flags::NON_PACKED | 8918 ObjectElements::Flags::NONWRITABLE_ARRAY_LENGTH | 8919 ObjectElements::Flags::NOT_EXTENSIBLE | 8920 ObjectElements::Flags::MAYBE_IN_ITERATION; 8921 Address flags(temp1, ObjectElements::offsetOfFlags()); 8922 branchTest32(Assembler::NonZero, flags, Imm32(UnhandledFlags), fail); 8923 8924 // Load length in temp2. Ensure length == initializedLength. 8925 Address lengthAddr(temp1, ObjectElements::offsetOfLength()); 8926 Address initLengthAddr(temp1, ObjectElements::offsetOfInitializedLength()); 8927 load32(lengthAddr, temp2); 8928 branch32(Assembler::NotEqual, initLengthAddr, temp2, fail); 8929 8930 // Result is |undefined| if length == 0. 8931 Label notEmpty, done; 8932 branchTest32(Assembler::NonZero, temp2, temp2, ¬Empty); 8933 { 8934 moveValue(UndefinedValue(), output); 8935 jump(&done); 8936 } 8937 8938 bind(¬Empty); 8939 8940 // Load the last element. 8941 sub32(Imm32(1), temp2); 8942 BaseObjectElementIndex elementAddr(temp1, temp2); 8943 loadValue(elementAddr, output); 8944 8945 // Pre-barrier the element because we're removing it from the array. 8946 EmitPreBarrier(*this, elementAddr, MIRType::Value); 8947 8948 // Update length and initializedLength. 8949 store32(temp2, lengthAddr); 8950 store32(temp2, initLengthAddr); 8951 8952 bind(&done); 8953 } 8954 8955 void MacroAssembler::packedArrayShift(Register array, ValueOperand output, 8956 Register temp1, Register temp2, 8957 LiveRegisterSet volatileRegs, 8958 Label* fail) { 8959 // Load obj->elements in temp1. 8960 loadPtr(Address(array, NativeObject::offsetOfElements()), temp1); 8961 8962 // Check flags. 8963 static constexpr uint32_t UnhandledFlags = 8964 ObjectElements::Flags::NON_PACKED | 8965 ObjectElements::Flags::NONWRITABLE_ARRAY_LENGTH | 8966 ObjectElements::Flags::NOT_EXTENSIBLE | 8967 ObjectElements::Flags::MAYBE_IN_ITERATION; 8968 Address flags(temp1, ObjectElements::offsetOfFlags()); 8969 branchTest32(Assembler::NonZero, flags, Imm32(UnhandledFlags), fail); 8970 8971 // Load length in temp2. Ensure length == initializedLength. 8972 Address lengthAddr(temp1, ObjectElements::offsetOfLength()); 8973 Address initLengthAddr(temp1, ObjectElements::offsetOfInitializedLength()); 8974 load32(lengthAddr, temp2); 8975 branch32(Assembler::NotEqual, initLengthAddr, temp2, fail); 8976 8977 // Result is |undefined| if length == 0. 8978 Label notEmpty, done; 8979 branchTest32(Assembler::NonZero, temp2, temp2, ¬Empty); 8980 { 8981 moveValue(UndefinedValue(), output); 8982 jump(&done); 8983 } 8984 8985 bind(¬Empty); 8986 8987 // Load the first element. 8988 Address elementAddr(temp1, 0); 8989 loadValue(elementAddr, output); 8990 8991 // Move the other elements and update the initializedLength/length. This will 8992 // also trigger pre-barriers. 8993 { 8994 // Ensure output is in volatileRegs. Don't preserve temp1 and temp2. 8995 volatileRegs.takeUnchecked(temp1); 8996 volatileRegs.takeUnchecked(temp2); 8997 if (output.hasVolatileReg()) { 8998 volatileRegs.addUnchecked(output); 8999 } 9000 9001 PushRegsInMask(volatileRegs); 9002 9003 using Fn = void (*)(ArrayObject* arr); 9004 setupUnalignedABICall(temp1); 9005 passABIArg(array); 9006 callWithABI<Fn, ArrayShiftMoveElements>(); 9007 9008 PopRegsInMask(volatileRegs); 9009 } 9010 9011 bind(&done); 9012 } 9013 9014 void MacroAssembler::loadArgumentsObjectElement(Register obj, Register index, 9015 ValueOperand output, 9016 Register temp, Label* fail) { 9017 Register temp2 = output.scratchReg(); 9018 9019 // Get initial length value. 9020 unboxInt32(Address(obj, ArgumentsObject::getInitialLengthSlotOffset()), temp); 9021 9022 // Ensure no overridden elements. 9023 branchTest32(Assembler::NonZero, temp, 9024 Imm32(ArgumentsObject::ELEMENT_OVERRIDDEN_BIT), fail); 9025 9026 // Bounds check. 9027 rshift32(Imm32(ArgumentsObject::PACKED_BITS_COUNT), temp); 9028 spectreBoundsCheck32(index, temp, temp2, fail); 9029 9030 // Load ArgumentsData. 9031 loadPrivate(Address(obj, ArgumentsObject::getDataSlotOffset()), temp); 9032 9033 // Guard the argument is not a FORWARD_TO_CALL_SLOT MagicValue. 9034 BaseValueIndex argValue(temp, index, ArgumentsData::offsetOfArgs()); 9035 branchTestMagic(Assembler::Equal, argValue, fail); 9036 loadValue(argValue, output); 9037 } 9038 9039 void MacroAssembler::loadArgumentsObjectElementHole(Register obj, 9040 Register index, 9041 ValueOperand output, 9042 Register temp, 9043 Label* fail) { 9044 Register temp2 = output.scratchReg(); 9045 9046 // Get initial length value. 9047 unboxInt32(Address(obj, ArgumentsObject::getInitialLengthSlotOffset()), temp); 9048 9049 // Ensure no overridden elements. 9050 branchTest32(Assembler::NonZero, temp, 9051 Imm32(ArgumentsObject::ELEMENT_OVERRIDDEN_BIT), fail); 9052 9053 // Bounds check. 9054 Label outOfBounds, done; 9055 rshift32(Imm32(ArgumentsObject::PACKED_BITS_COUNT), temp); 9056 spectreBoundsCheck32(index, temp, temp2, &outOfBounds); 9057 9058 // Load ArgumentsData. 9059 loadPrivate(Address(obj, ArgumentsObject::getDataSlotOffset()), temp); 9060 9061 // Guard the argument is not a FORWARD_TO_CALL_SLOT MagicValue. 9062 BaseValueIndex argValue(temp, index, ArgumentsData::offsetOfArgs()); 9063 branchTestMagic(Assembler::Equal, argValue, fail); 9064 loadValue(argValue, output); 9065 jump(&done); 9066 9067 bind(&outOfBounds); 9068 branch32(Assembler::LessThan, index, Imm32(0), fail); 9069 moveValue(UndefinedValue(), output); 9070 9071 bind(&done); 9072 } 9073 9074 void MacroAssembler::loadArgumentsObjectElementExists( 9075 Register obj, Register index, Register output, Register temp, Label* fail) { 9076 // Ensure the index is non-negative. 9077 branch32(Assembler::LessThan, index, Imm32(0), fail); 9078 9079 // Get initial length value. 9080 unboxInt32(Address(obj, ArgumentsObject::getInitialLengthSlotOffset()), temp); 9081 9082 // Ensure no overridden or deleted elements. 9083 branchTest32(Assembler::NonZero, temp, 9084 Imm32(ArgumentsObject::ELEMENT_OVERRIDDEN_BIT), fail); 9085 9086 // Compare index against the length. 9087 rshift32(Imm32(ArgumentsObject::PACKED_BITS_COUNT), temp); 9088 cmp32Set(Assembler::LessThan, index, temp, output); 9089 } 9090 9091 void MacroAssembler::loadArgumentsObjectLength(Register obj, Register output, 9092 Label* fail) { 9093 // Get initial length value. 9094 unboxInt32(Address(obj, ArgumentsObject::getInitialLengthSlotOffset()), 9095 output); 9096 9097 // Test if length has been overridden. 9098 branchTest32(Assembler::NonZero, output, 9099 Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT), fail); 9100 9101 // Shift out arguments length and return it. 9102 rshift32(Imm32(ArgumentsObject::PACKED_BITS_COUNT), output); 9103 } 9104 9105 void MacroAssembler::loadArgumentsObjectLength(Register obj, Register output) { 9106 // Get initial length value. 9107 unboxInt32(Address(obj, ArgumentsObject::getInitialLengthSlotOffset()), 9108 output); 9109 9110 #ifdef DEBUG 9111 // Assert length hasn't been overridden. 9112 Label ok; 9113 branchTest32(Assembler::Zero, output, 9114 Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT), &ok); 9115 assumeUnreachable("arguments object length has been overridden"); 9116 bind(&ok); 9117 #endif 9118 9119 // Shift out arguments length and return it. 9120 rshift32(Imm32(ArgumentsObject::PACKED_BITS_COUNT), output); 9121 } 9122 9123 void MacroAssembler::branchTestArgumentsObjectFlags(Register obj, Register temp, 9124 uint32_t flags, 9125 Condition cond, 9126 Label* label) { 9127 MOZ_ASSERT((flags & ~ArgumentsObject::PACKED_BITS_MASK) == 0); 9128 9129 // Get initial length value. 9130 unboxInt32(Address(obj, ArgumentsObject::getInitialLengthSlotOffset()), temp); 9131 9132 // Test flags. 9133 branchTest32(cond, temp, Imm32(flags), label); 9134 } 9135 9136 static constexpr bool ValidateSizeRange(Scalar::Type from, Scalar::Type to) { 9137 for (Scalar::Type type = from; type < to; type = Scalar::Type(type + 1)) { 9138 if (TypedArrayElemSize(type) != TypedArrayElemSize(from)) { 9139 return false; 9140 } 9141 } 9142 return true; 9143 } 9144 9145 void MacroAssembler::typedArrayElementSize(Register obj, Register output) { 9146 static_assert(std::end(TypedArrayObject::fixedLengthClasses) == 9147 std::begin(TypedArrayObject::immutableClasses) && 9148 std::end(TypedArrayObject::immutableClasses) == 9149 std::begin(TypedArrayObject::resizableClasses), 9150 "TypedArray classes are in contiguous memory"); 9151 9152 // Constexpr subtraction requires using elements of the same array, so we have 9153 // to use `std::end` instead of `std::begin`. We still get the right results, 9154 // because the classes are in contiguous memory, as asserted above. 9155 constexpr ptrdiff_t diffFirstImmutableToFirstFixedLength = 9156 std::end(TypedArrayObject::fixedLengthClasses) - 9157 std::begin(TypedArrayObject::fixedLengthClasses); 9158 constexpr ptrdiff_t diffFirstResizableToFirstImmutable = 9159 std::end(TypedArrayObject::immutableClasses) - 9160 std::begin(TypedArrayObject::immutableClasses); 9161 constexpr ptrdiff_t diffFirstResizableToFirstFixedLength = 9162 diffFirstResizableToFirstImmutable + diffFirstImmutableToFirstFixedLength; 9163 9164 loadObjClassUnsafe(obj, output); 9165 9166 // Map immutable and resizable to fixed-length TypedArray classes. 9167 Label fixedLength, immutable; 9168 branchPtr(Assembler::Below, output, 9169 ImmPtr(std::end(TypedArrayObject::fixedLengthClasses)), 9170 &fixedLength); 9171 branchPtr(Assembler::Below, output, 9172 ImmPtr(std::end(TypedArrayObject::immutableClasses)), &immutable); 9173 { 9174 // NB: constexpr evaluation doesn't allow overflow, so the difference is 9175 // guaranteed to fit into an int32. 9176 constexpr int32_t diff = static_cast<int32_t>( 9177 diffFirstResizableToFirstFixedLength * sizeof(JSClass)); 9178 9179 subPtr(Imm32(diff), output); 9180 jump(&fixedLength); 9181 } 9182 bind(&immutable); 9183 { 9184 // NB: constexpr evaluation doesn't allow overflow, so the difference is 9185 // guaranteed to fit into an int32. 9186 constexpr int32_t diff = static_cast<int32_t>( 9187 diffFirstImmutableToFirstFixedLength * sizeof(JSClass)); 9188 9189 subPtr(Imm32(diff), output); 9190 } 9191 bind(&fixedLength); 9192 9193 #ifdef DEBUG 9194 Label invalidClass, validClass; 9195 branchPtr(Assembler::Below, output, 9196 ImmPtr(std::begin(TypedArrayObject::fixedLengthClasses)), 9197 &invalidClass); 9198 branchPtr(Assembler::Below, output, 9199 ImmPtr(std::end(TypedArrayObject::fixedLengthClasses)), 9200 &validClass); 9201 bind(&invalidClass); 9202 assumeUnreachable("value isn't a valid FixedLengthTypedArray class"); 9203 bind(&validClass); 9204 #endif 9205 9206 auto classForType = [](Scalar::Type type) { 9207 MOZ_ASSERT(type < Scalar::MaxTypedArrayViewType); 9208 return &TypedArrayObject::fixedLengthClasses[type]; 9209 }; 9210 9211 Label one, two, four, eight, done; 9212 9213 static_assert(ValidateSizeRange(Scalar::Int8, Scalar::Int16), 9214 "element size is one in [Int8, Int16)"); 9215 branchPtr(Assembler::Below, output, ImmPtr(classForType(Scalar::Int16)), 9216 &one); 9217 9218 static_assert(ValidateSizeRange(Scalar::Int16, Scalar::Int32), 9219 "element size is two in [Int16, Int32)"); 9220 branchPtr(Assembler::Below, output, ImmPtr(classForType(Scalar::Int32)), 9221 &two); 9222 9223 static_assert(ValidateSizeRange(Scalar::Int32, Scalar::Float64), 9224 "element size is four in [Int32, Float64)"); 9225 branchPtr(Assembler::Below, output, ImmPtr(classForType(Scalar::Float64)), 9226 &four); 9227 9228 static_assert(ValidateSizeRange(Scalar::Float64, Scalar::Uint8Clamped), 9229 "element size is eight in [Float64, Uint8Clamped)"); 9230 branchPtr(Assembler::Below, output, 9231 ImmPtr(classForType(Scalar::Uint8Clamped)), &eight); 9232 9233 static_assert(ValidateSizeRange(Scalar::Uint8Clamped, Scalar::BigInt64), 9234 "element size is one in [Uint8Clamped, BigInt64)"); 9235 branchPtr(Assembler::Below, output, ImmPtr(classForType(Scalar::BigInt64)), 9236 &one); 9237 9238 static_assert(ValidateSizeRange(Scalar::BigInt64, Scalar::Float16), 9239 "element size is eight in [BigInt64, Float16)"); 9240 branchPtr(Assembler::Below, output, ImmPtr(classForType(Scalar::Float16)), 9241 &eight); 9242 9243 static_assert( 9244 ValidateSizeRange(Scalar::Float16, Scalar::MaxTypedArrayViewType), 9245 "element size is two in [Float16, MaxTypedArrayViewType)"); 9246 jump(&two); 9247 9248 bind(&eight); 9249 move32(Imm32(8), output); 9250 jump(&done); 9251 9252 bind(&four); 9253 move32(Imm32(4), output); 9254 jump(&done); 9255 9256 bind(&two); 9257 move32(Imm32(2), output); 9258 jump(&done); 9259 9260 bind(&one); 9261 move32(Imm32(1), output); 9262 9263 bind(&done); 9264 } 9265 9266 void MacroAssembler::resizableTypedArrayElementShiftBy(Register obj, 9267 Register output, 9268 Register scratch) { 9269 loadObjClassUnsafe(obj, scratch); 9270 9271 #ifdef DEBUG 9272 Label invalidClass, validClass; 9273 branchPtr(Assembler::Below, scratch, 9274 ImmPtr(std::begin(TypedArrayObject::resizableClasses)), 9275 &invalidClass); 9276 branchPtr(Assembler::Below, scratch, 9277 ImmPtr(std::end(TypedArrayObject::resizableClasses)), &validClass); 9278 bind(&invalidClass); 9279 assumeUnreachable("value isn't a valid ResizableLengthTypedArray class"); 9280 bind(&validClass); 9281 #endif 9282 9283 auto classForType = [](Scalar::Type type) { 9284 MOZ_ASSERT(type < Scalar::MaxTypedArrayViewType); 9285 return &TypedArrayObject::resizableClasses[type]; 9286 }; 9287 9288 Label zero, one, two, three; 9289 9290 static_assert(ValidateSizeRange(Scalar::Int8, Scalar::Int16), 9291 "element shift is zero in [Int8, Int16)"); 9292 branchPtr(Assembler::Below, scratch, ImmPtr(classForType(Scalar::Int16)), 9293 &zero); 9294 9295 static_assert(ValidateSizeRange(Scalar::Int16, Scalar::Int32), 9296 "element shift is one in [Int16, Int32)"); 9297 branchPtr(Assembler::Below, scratch, ImmPtr(classForType(Scalar::Int32)), 9298 &one); 9299 9300 static_assert(ValidateSizeRange(Scalar::Int32, Scalar::Float64), 9301 "element shift is two in [Int32, Float64)"); 9302 branchPtr(Assembler::Below, scratch, ImmPtr(classForType(Scalar::Float64)), 9303 &two); 9304 9305 static_assert(ValidateSizeRange(Scalar::Float64, Scalar::Uint8Clamped), 9306 "element shift is three in [Float64, Uint8Clamped)"); 9307 branchPtr(Assembler::Below, scratch, 9308 ImmPtr(classForType(Scalar::Uint8Clamped)), &three); 9309 9310 static_assert(ValidateSizeRange(Scalar::Uint8Clamped, Scalar::BigInt64), 9311 "element shift is zero in [Uint8Clamped, BigInt64)"); 9312 branchPtr(Assembler::Below, scratch, ImmPtr(classForType(Scalar::BigInt64)), 9313 &zero); 9314 9315 static_assert(ValidateSizeRange(Scalar::BigInt64, Scalar::Float16), 9316 "element shift is three in [BigInt64, Float16)"); 9317 branchPtr(Assembler::Below, scratch, ImmPtr(classForType(Scalar::Float16)), 9318 &three); 9319 9320 static_assert( 9321 ValidateSizeRange(Scalar::Float16, Scalar::MaxTypedArrayViewType), 9322 "element shift is one in [Float16, MaxTypedArrayViewType)"); 9323 jump(&one); 9324 9325 bind(&three); 9326 rshiftPtr(Imm32(3), output); 9327 jump(&zero); 9328 9329 bind(&two); 9330 rshiftPtr(Imm32(2), output); 9331 jump(&zero); 9332 9333 bind(&one); 9334 rshiftPtr(Imm32(1), output); 9335 9336 bind(&zero); 9337 } 9338 9339 void MacroAssembler::branchIfClassIsNotTypedArray(Register clasp, 9340 Label* notTypedArray) { 9341 // Inline implementation of IsTypedArrayClass(). 9342 9343 const auto* firstTypedArrayClass = 9344 std::begin(TypedArrayObject::fixedLengthClasses); 9345 const auto* lastTypedArrayClass = 9346 std::prev(std::end(TypedArrayObject::resizableClasses)); 9347 MOZ_ASSERT(std::end(TypedArrayObject::fixedLengthClasses) == 9348 std::begin(TypedArrayObject::immutableClasses) && 9349 std::end(TypedArrayObject::immutableClasses) == 9350 std::begin(TypedArrayObject::resizableClasses), 9351 "TypedArray classes are in contiguous memory"); 9352 9353 branchPtr(Assembler::Below, clasp, ImmPtr(firstTypedArrayClass), 9354 notTypedArray); 9355 branchPtr(Assembler::Above, clasp, ImmPtr(lastTypedArrayClass), 9356 notTypedArray); 9357 } 9358 9359 void MacroAssembler::branchIfClassIsNotNonResizableTypedArray( 9360 Register clasp, Label* notTypedArray) { 9361 // Inline implementation of IsFixedLengthTypedArrayClass() and 9362 // IsImmutableTypedArrayClass(). 9363 9364 const auto* firstTypedArrayClass = 9365 std::begin(TypedArrayObject::fixedLengthClasses); 9366 const auto* lastTypedArrayClass = 9367 std::prev(std::end(TypedArrayObject::immutableClasses)); 9368 MOZ_ASSERT(std::end(TypedArrayObject::fixedLengthClasses) == 9369 std::begin(TypedArrayObject::immutableClasses), 9370 "TypedArray classes are in contiguous memory"); 9371 9372 branchPtr(Assembler::Below, clasp, ImmPtr(firstTypedArrayClass), 9373 notTypedArray); 9374 branchPtr(Assembler::Above, clasp, ImmPtr(lastTypedArrayClass), 9375 notTypedArray); 9376 } 9377 9378 void MacroAssembler::branchIfClassIsNotResizableTypedArray( 9379 Register clasp, Label* notTypedArray) { 9380 // Inline implementation of IsResizableTypedArrayClass(). 9381 9382 const auto* firstTypedArrayClass = 9383 std::begin(TypedArrayObject::resizableClasses); 9384 const auto* lastTypedArrayClass = 9385 std::prev(std::end(TypedArrayObject::resizableClasses)); 9386 9387 branchPtr(Assembler::Below, clasp, ImmPtr(firstTypedArrayClass), 9388 notTypedArray); 9389 branchPtr(Assembler::Above, clasp, ImmPtr(lastTypedArrayClass), 9390 notTypedArray); 9391 } 9392 9393 void MacroAssembler::branchIfIsNotArrayBuffer(Register obj, Register temp, 9394 Label* label) { 9395 Label ok; 9396 9397 loadObjClassUnsafe(obj, temp); 9398 9399 branchPtr(Assembler::Equal, temp, 9400 ImmPtr(&FixedLengthArrayBufferObject::class_), &ok); 9401 branchPtr(Assembler::Equal, temp, ImmPtr(&ResizableArrayBufferObject::class_), 9402 &ok); 9403 branchPtr(Assembler::NotEqual, temp, 9404 ImmPtr(&ImmutableArrayBufferObject::class_), label); 9405 9406 bind(&ok); 9407 9408 if (JitOptions.spectreObjectMitigations) { 9409 spectreZeroRegister(Assembler::NotEqual, temp, obj); 9410 } 9411 } 9412 9413 void MacroAssembler::branchIfIsNotSharedArrayBuffer(Register obj, Register temp, 9414 Label* label) { 9415 Label ok; 9416 9417 loadObjClassUnsafe(obj, temp); 9418 9419 branchPtr(Assembler::Equal, temp, 9420 ImmPtr(&FixedLengthSharedArrayBufferObject::class_), &ok); 9421 branchPtr(Assembler::NotEqual, temp, 9422 ImmPtr(&GrowableSharedArrayBufferObject::class_), label); 9423 9424 bind(&ok); 9425 9426 if (JitOptions.spectreObjectMitigations) { 9427 spectreZeroRegister(Assembler::NotEqual, temp, obj); 9428 } 9429 } 9430 9431 void MacroAssembler::branchIfIsArrayBufferMaybeShared(Register obj, 9432 Register temp, 9433 Label* label) { 9434 loadObjClassUnsafe(obj, temp); 9435 9436 branchPtr(Assembler::Equal, temp, 9437 ImmPtr(&FixedLengthArrayBufferObject::class_), label); 9438 branchPtr(Assembler::Equal, temp, 9439 ImmPtr(&FixedLengthSharedArrayBufferObject::class_), label); 9440 branchPtr(Assembler::Equal, temp, ImmPtr(&ResizableArrayBufferObject::class_), 9441 label); 9442 branchPtr(Assembler::Equal, temp, 9443 ImmPtr(&GrowableSharedArrayBufferObject::class_), label); 9444 branchPtr(Assembler::Equal, temp, ImmPtr(&ImmutableArrayBufferObject::class_), 9445 label); 9446 } 9447 9448 void MacroAssembler::branchIfHasDetachedArrayBuffer(BranchIfDetached branchIf, 9449 Register obj, Register temp, 9450 Label* label) { 9451 // Inline implementation of ArrayBufferViewObject::hasDetachedBuffer(). 9452 9453 // TODO: The data-slot of detached views is set to undefined, which would be 9454 // a faster way to detect detached buffers. 9455 9456 // auto cond = branchIf == BranchIfDetached::Yes ? Assembler::Equal 9457 // : Assembler::NotEqual; 9458 // branchTestUndefined(cond, Address(obj, 9459 // ArrayBufferViewObject::dataOffset()), label); 9460 9461 Label done; 9462 Label* ifNotDetached = branchIf == BranchIfDetached::Yes ? &done : label; 9463 Condition detachedCond = 9464 branchIf == BranchIfDetached::Yes ? Assembler::NonZero : Assembler::Zero; 9465 9466 // Load obj->elements in temp. 9467 loadPtr(Address(obj, NativeObject::offsetOfElements()), temp); 9468 9469 // Shared buffers can't be detached. 9470 branchTest32(Assembler::NonZero, 9471 Address(temp, ObjectElements::offsetOfFlags()), 9472 Imm32(ObjectElements::SHARED_MEMORY), ifNotDetached); 9473 9474 // An ArrayBufferView with a null/true buffer has never had its buffer 9475 // exposed, so nothing can possibly detach it. 9476 fallibleUnboxObject(Address(obj, ArrayBufferViewObject::bufferOffset()), temp, 9477 ifNotDetached); 9478 9479 // Load the ArrayBuffer flags and branch if the detached flag is (not) set. 9480 unboxInt32(Address(temp, ArrayBufferObject::offsetOfFlagsSlot()), temp); 9481 branchTest32(detachedCond, temp, Imm32(ArrayBufferObject::DETACHED), label); 9482 9483 if (branchIf == BranchIfDetached::Yes) { 9484 bind(&done); 9485 } 9486 } 9487 9488 void MacroAssembler::branchIfResizableArrayBufferViewOutOfBounds(Register obj, 9489 Register temp, 9490 Label* label) { 9491 // Implementation of ArrayBufferViewObject::isOutOfBounds(). 9492 9493 Label done; 9494 9495 loadArrayBufferViewLengthIntPtr(obj, temp); 9496 branchPtr(Assembler::NotEqual, temp, ImmWord(0), &done); 9497 9498 loadArrayBufferViewByteOffsetIntPtr(obj, temp); 9499 branchPtr(Assembler::NotEqual, temp, ImmWord(0), &done); 9500 9501 loadPrivate(Address(obj, ArrayBufferViewObject::initialLengthOffset()), temp); 9502 branchPtr(Assembler::NotEqual, temp, ImmWord(0), label); 9503 9504 loadPrivate(Address(obj, ArrayBufferViewObject::initialByteOffsetOffset()), 9505 temp); 9506 branchPtr(Assembler::NotEqual, temp, ImmWord(0), label); 9507 9508 bind(&done); 9509 } 9510 9511 void MacroAssembler::branchIfResizableArrayBufferViewInBounds(Register obj, 9512 Register temp, 9513 Label* label) { 9514 // Implementation of ArrayBufferViewObject::isOutOfBounds(). 9515 9516 Label done; 9517 9518 loadArrayBufferViewLengthIntPtr(obj, temp); 9519 branchPtr(Assembler::NotEqual, temp, ImmWord(0), label); 9520 9521 loadArrayBufferViewByteOffsetIntPtr(obj, temp); 9522 branchPtr(Assembler::NotEqual, temp, ImmWord(0), label); 9523 9524 loadPrivate(Address(obj, ArrayBufferViewObject::initialLengthOffset()), temp); 9525 branchPtr(Assembler::NotEqual, temp, ImmWord(0), &done); 9526 9527 loadPrivate(Address(obj, ArrayBufferViewObject::initialByteOffsetOffset()), 9528 temp); 9529 branchPtr(Assembler::Equal, temp, ImmWord(0), label); 9530 9531 bind(&done); 9532 } 9533 9534 void MacroAssembler::branchIfNativeIteratorNotReusable(Register ni, 9535 Label* notReusable) { 9536 // See NativeIterator::isReusable. 9537 Address flagsAddr(ni, NativeIterator::offsetOfFlags()); 9538 9539 #ifdef DEBUG 9540 Label niIsInitialized; 9541 branchTest32(Assembler::NonZero, flagsAddr, 9542 Imm32(NativeIterator::Flags::Initialized), &niIsInitialized); 9543 assumeUnreachable( 9544 "Expected a NativeIterator that's been completely " 9545 "initialized"); 9546 bind(&niIsInitialized); 9547 #endif 9548 9549 branchTest32(Assembler::NonZero, flagsAddr, 9550 Imm32(NativeIterator::Flags::NotReusable), notReusable); 9551 } 9552 9553 static void LoadNativeIterator(MacroAssembler& masm, Register obj, 9554 Register dest) { 9555 MOZ_ASSERT(obj != dest); 9556 9557 #ifdef DEBUG 9558 // Assert we have a PropertyIteratorObject. 9559 Label ok; 9560 masm.branchTestObjClass(Assembler::Equal, obj, 9561 &PropertyIteratorObject::class_, dest, obj, &ok); 9562 masm.assumeUnreachable("Expected PropertyIteratorObject!"); 9563 masm.bind(&ok); 9564 #endif 9565 9566 // Load NativeIterator object. 9567 Address slotAddr(obj, PropertyIteratorObject::offsetOfIteratorSlot()); 9568 masm.loadPrivate(slotAddr, dest); 9569 } 9570 9571 // The ShapeCachePtr may be used to cache an iterator for for-in. Return that 9572 // iterator in |dest| if: 9573 // - the shape cache pointer exists and stores a native iterator 9574 // - the iterator is reusable 9575 // - the iterated object has no dense elements 9576 // - the shapes of each object on the proto chain of |obj| match the cached 9577 // shapes 9578 // - the proto chain has no dense elements 9579 // Otherwise, jump to |failure|. 9580 void MacroAssembler::maybeLoadIteratorFromShape(Register obj, Register dest, 9581 Register temp, Register temp2, 9582 Register temp3, Label* failure, 9583 bool exclusive) { 9584 // Register usage: 9585 // obj: always contains the input object 9586 // temp: walks the obj->shape->baseshape->proto->shape->... chain 9587 // temp2: points to the native iterator. Incremented to walk the shapes array. 9588 // temp3: scratch space 9589 // dest: stores the resulting PropertyIteratorObject on success 9590 9591 Label success; 9592 Register shapeAndProto = temp; 9593 Register nativeIterator = temp2; 9594 9595 // Load ShapeCache from shape. 9596 loadPtr(Address(obj, JSObject::offsetOfShape()), shapeAndProto); 9597 loadPtr(Address(shapeAndProto, Shape::offsetOfCachePtr()), dest); 9598 9599 // Check if it's an iterator. 9600 andPtr(Imm32(ShapeCachePtr::MASK), dest, temp3); 9601 branch32(Assembler::NotEqual, temp3, Imm32(ShapeCachePtr::ITERATOR), failure); 9602 9603 // If we've cached an iterator, |obj| must be a native object. 9604 #ifdef DEBUG 9605 Label nonNative; 9606 branchIfNonNativeObj(obj, temp3, &nonNative); 9607 #endif 9608 9609 // Verify that |obj| has no dense elements. 9610 loadPtr(Address(obj, NativeObject::offsetOfElements()), temp3); 9611 branch32(Assembler::NotEqual, 9612 Address(temp3, ObjectElements::offsetOfInitializedLength()), 9613 Imm32(0), failure); 9614 9615 // Clear tag bits from iterator object. |dest| is now valid. 9616 // Load the native iterator and verify that it's reusable. 9617 andPtr(Imm32(~ShapeCachePtr::MASK), dest); 9618 LoadNativeIterator(*this, dest, nativeIterator); 9619 9620 if (exclusive) { 9621 branchIfNativeIteratorNotReusable(nativeIterator, failure); 9622 } 9623 9624 Label skipIndices; 9625 load32(Address(nativeIterator, NativeIterator::offsetOfPropertyCount()), 9626 temp3); 9627 branchTest32(Assembler::Zero, 9628 Address(nativeIterator, NativeIterator::offsetOfFlags()), 9629 Imm32(NativeIterator::Flags::IndicesAllocated), &skipIndices); 9630 9631 computeEffectiveAddress(BaseIndex(nativeIterator, temp3, Scale::TimesFour), 9632 nativeIterator); 9633 9634 bind(&skipIndices); 9635 computeEffectiveAddress(BaseIndex(nativeIterator, temp3, ScalePointer, 9636 NativeIterator::offsetOfFirstProperty()), 9637 nativeIterator); 9638 9639 Register expectedProtoShape = nativeIterator; 9640 9641 // We have to compare the shapes in the native iterator with the shapes on the 9642 // proto chain to ensure the cached iterator is still valid. 9643 9644 // Loop over the proto chain. At the head of the loop, |shape| is the shape of 9645 // the current object, and |iteratorShapes| points to the expected shape of 9646 // its proto. 9647 Label protoLoop; 9648 bind(&protoLoop); 9649 9650 // Load the proto. If the proto is null, then we're done. 9651 loadPtr(Address(shapeAndProto, Shape::offsetOfBaseShape()), shapeAndProto); 9652 loadPtr(Address(shapeAndProto, BaseShape::offsetOfProto()), shapeAndProto); 9653 branchPtr(Assembler::Equal, shapeAndProto, ImmPtr(nullptr), &success); 9654 9655 #ifdef DEBUG 9656 // We have guarded every shape up until this point, so we know that the proto 9657 // is a native object. 9658 branchIfNonNativeObj(shapeAndProto, temp3, &nonNative); 9659 #endif 9660 9661 // Verify that the proto has no dense elements. 9662 loadPtr(Address(shapeAndProto, NativeObject::offsetOfElements()), temp3); 9663 branch32(Assembler::NotEqual, 9664 Address(temp3, ObjectElements::offsetOfInitializedLength()), 9665 Imm32(0), failure); 9666 9667 // Compare the shape of the proto to the expected shape. 9668 loadPtr(Address(shapeAndProto, JSObject::offsetOfShape()), shapeAndProto); 9669 loadPtr(Address(expectedProtoShape, 0), temp3); 9670 branchPtr(Assembler::NotEqual, shapeAndProto, temp3, failure); 9671 9672 // Increment |iteratorShapes| and jump back to the top of the loop. 9673 addPtr(Imm32(sizeof(Shape*)), expectedProtoShape); 9674 jump(&protoLoop); 9675 9676 #ifdef DEBUG 9677 bind(&nonNative); 9678 assumeUnreachable("Expected NativeObject in maybeLoadIteratorFromShape"); 9679 #endif 9680 9681 bind(&success); 9682 } 9683 9684 void MacroAssembler::iteratorMore(Register obj, ValueOperand output, 9685 Register temp) { 9686 Label done; 9687 Register outputScratch = output.scratchReg(); 9688 LoadNativeIterator(*this, obj, outputScratch); 9689 9690 // If propertyCursor_ < propertiesEnd_, load the next string and advance 9691 // the cursor. Otherwise return MagicValue(JS_NO_ITER_VALUE). 9692 Label iterDone, restart; 9693 bind(&restart); 9694 Address cursorAddr(outputScratch, NativeIterator::offsetOfPropertyCursor()); 9695 Address cursorEndAddr(outputScratch, NativeIterator::offsetOfPropertyCount()); 9696 load32(cursorAddr, temp); 9697 branch32(Assembler::BelowOrEqual, cursorEndAddr, temp, &iterDone); 9698 9699 // Get next string. 9700 BaseIndex propAddr(outputScratch, temp, ScalePointer, 9701 NativeIterator::offsetOfFirstProperty()); 9702 loadPtr(propAddr, temp); 9703 9704 // Increase the cursor. 9705 addPtr(Imm32(1), cursorAddr); 9706 9707 // Check if the property has been deleted while iterating. Skip it if so. 9708 branchTestPtr(Assembler::NonZero, temp, 9709 Imm32(uint32_t(IteratorProperty::DeletedBit)), &restart); 9710 9711 tagValue(JSVAL_TYPE_STRING, temp, output); 9712 jump(&done); 9713 9714 bind(&iterDone); 9715 moveValue(MagicValue(JS_NO_ITER_VALUE), output); 9716 9717 bind(&done); 9718 } 9719 9720 void MacroAssembler::iteratorLength(Register obj, Register output) { 9721 LoadNativeIterator(*this, obj, output); 9722 load32(Address(output, NativeIterator::offsetOfOwnPropertyCount()), output); 9723 } 9724 9725 void MacroAssembler::iteratorLoadElement(Register obj, Register index, 9726 Register output) { 9727 LoadNativeIterator(*this, obj, output); 9728 loadPtr(BaseIndex(output, index, ScalePointer, 9729 NativeIterator::offsetOfFirstProperty()), 9730 output); 9731 andPtr(Imm32(int32_t(~IteratorProperty::DeletedBit)), output); 9732 } 9733 9734 void MacroAssembler::iteratorLoadElement(Register obj, int32_t index, 9735 Register output) { 9736 LoadNativeIterator(*this, obj, output); 9737 loadPtr(Address(output, index * sizeof(IteratorProperty) + 9738 NativeIterator::offsetOfFirstProperty()), 9739 output); 9740 andPtr(Imm32(int32_t(~IteratorProperty::DeletedBit)), output); 9741 } 9742 9743 void MacroAssembler::iteratorClose(Register obj, Register temp1, Register temp2, 9744 Register temp3) { 9745 LoadNativeIterator(*this, obj, temp1); 9746 9747 Address flagsAddr(temp1, NativeIterator::offsetOfFlags()); 9748 9749 // The shared iterator used for for-in with null/undefined is immutable and 9750 // unlinked. See NativeIterator::isEmptyIteratorSingleton. 9751 Label done; 9752 branchTest32(Assembler::NonZero, flagsAddr, 9753 Imm32(NativeIterator::Flags::IsEmptyIteratorSingleton), &done); 9754 9755 // Clear objectBeingIterated. 9756 Address iterObjAddr(temp1, NativeIterator::offsetOfObjectBeingIterated()); 9757 guardedCallPreBarrierAnyZone(iterObjAddr, MIRType::Object, temp2); 9758 storePtr(ImmPtr(nullptr), iterObjAddr); 9759 9760 // Reset property cursor. 9761 store32(Imm32(0), Address(temp1, NativeIterator::offsetOfPropertyCursor())); 9762 9763 // Clear deleted bits (only if we have unvisited deletions) 9764 Label clearDeletedLoopStart, clearDeletedLoopEnd; 9765 branchTest32(Assembler::Zero, flagsAddr, 9766 Imm32(NativeIterator::Flags::HasUnvisitedPropertyDeletion), 9767 &clearDeletedLoopEnd); 9768 9769 load32(Address(temp1, NativeIterator::offsetOfPropertyCount()), temp3); 9770 9771 computeEffectiveAddress(BaseIndex(temp1, temp3, ScalePointer, 9772 NativeIterator::offsetOfFirstProperty()), 9773 temp3); 9774 computeEffectiveAddress( 9775 Address(temp1, NativeIterator::offsetOfFirstProperty()), temp2); 9776 9777 bind(&clearDeletedLoopStart); 9778 and32(Imm32(~uint32_t(IteratorProperty::DeletedBit)), Address(temp2, 0)); 9779 addPtr(Imm32(sizeof(IteratorProperty)), temp2); 9780 branchPtr(Assembler::Below, temp2, temp3, &clearDeletedLoopStart); 9781 9782 bind(&clearDeletedLoopEnd); 9783 9784 // Clear active and unvisited deletions bits 9785 and32(Imm32(~(NativeIterator::Flags::Active | 9786 NativeIterator::Flags::HasUnvisitedPropertyDeletion)), 9787 flagsAddr); 9788 9789 // Unlink from the iterator list. 9790 const Register next = temp2; 9791 const Register prev = temp3; 9792 loadPtr(Address(temp1, NativeIterator::offsetOfNext()), next); 9793 loadPtr(Address(temp1, NativeIterator::offsetOfPrev()), prev); 9794 storePtr(prev, Address(next, NativeIterator::offsetOfPrev())); 9795 storePtr(next, Address(prev, NativeIterator::offsetOfNext())); 9796 #ifdef DEBUG 9797 storePtr(ImmPtr(nullptr), Address(temp1, NativeIterator::offsetOfNext())); 9798 storePtr(ImmPtr(nullptr), Address(temp1, NativeIterator::offsetOfPrev())); 9799 #endif 9800 9801 bind(&done); 9802 } 9803 9804 void MacroAssembler::registerIterator(Register enumeratorsList, Register iter, 9805 Register temp) { 9806 // iter->next = list 9807 storePtr(enumeratorsList, Address(iter, NativeIterator::offsetOfNext())); 9808 9809 // iter->prev = list->prev 9810 loadPtr(Address(enumeratorsList, NativeIterator::offsetOfPrev()), temp); 9811 storePtr(temp, Address(iter, NativeIterator::offsetOfPrev())); 9812 9813 // list->prev->next = iter 9814 storePtr(iter, Address(temp, NativeIterator::offsetOfNext())); 9815 9816 // list->prev = iter 9817 storePtr(iter, Address(enumeratorsList, NativeIterator::offsetOfPrev())); 9818 } 9819 9820 void MacroAssembler::prepareOOBStoreElement(Register object, Register index, 9821 Register elements, 9822 Register maybeTemp, Label* failure, 9823 LiveRegisterSet volatileLiveRegs) { 9824 Address length(elements, ObjectElements::offsetOfLength()); 9825 Address initLength(elements, ObjectElements::offsetOfInitializedLength()); 9826 Address capacity(elements, ObjectElements::offsetOfCapacity()); 9827 Address flags(elements, ObjectElements::offsetOfFlags()); 9828 9829 // If index < capacity, we can add a dense element inline. If not, we 9830 // need to allocate more elements. 9831 Label allocElement, enoughCapacity; 9832 spectreBoundsCheck32(index, capacity, maybeTemp, &allocElement); 9833 jump(&enoughCapacity); 9834 9835 bind(&allocElement); 9836 9837 // We currently only support storing one past the current capacity. 9838 // We could add support for stores beyond that point by calling a different 9839 // function, but then we'd have to think carefully about when to go sparse. 9840 branch32(Assembler::NotEqual, capacity, index, failure); 9841 9842 volatileLiveRegs.takeUnchecked(elements); 9843 if (maybeTemp != InvalidReg) { 9844 volatileLiveRegs.takeUnchecked(maybeTemp); 9845 } 9846 PushRegsInMask(volatileLiveRegs); 9847 9848 // Use `elements` as a scratch register because we're about to reallocate it. 9849 using Fn = bool (*)(JSContext* cx, NativeObject* obj); 9850 setupUnalignedABICall(elements); 9851 loadJSContext(elements); 9852 passABIArg(elements); 9853 passABIArg(object); 9854 callWithABI<Fn, NativeObject::addDenseElementPure>(); 9855 storeCallPointerResult(elements); 9856 9857 PopRegsInMask(volatileLiveRegs); 9858 branchIfFalseBool(elements, failure); 9859 9860 // Load the reallocated elements pointer. 9861 loadPtr(Address(object, NativeObject::offsetOfElements()), elements); 9862 9863 bind(&enoughCapacity); 9864 9865 // If our caller couldn't give us a temp register, use `object`. 9866 Register temp; 9867 if (maybeTemp == InvalidReg) { 9868 push(object); 9869 temp = object; 9870 } else { 9871 temp = maybeTemp; 9872 } 9873 9874 // Load the index of the first uninitialized element into `temp`. 9875 load32(initLength, temp); 9876 9877 // If it is not `index`, mark this elements array as non-packed. 9878 Label noHoles, loop, done; 9879 branch32(Assembler::Equal, temp, index, &noHoles); 9880 or32(Imm32(ObjectElements::NON_PACKED), flags); 9881 9882 // Loop over intermediate elements and fill them with the magic hole value. 9883 bind(&loop); 9884 storeValue(MagicValue(JS_ELEMENTS_HOLE), BaseValueIndex(elements, temp)); 9885 add32(Imm32(1), temp); 9886 branch32(Assembler::NotEqual, temp, index, &loop); 9887 9888 bind(&noHoles); 9889 9890 // The new initLength is index + 1. Update it. 9891 add32(Imm32(1), temp); 9892 store32(temp, initLength); 9893 9894 // If necessary, update length as well. 9895 branch32(Assembler::Above, length, temp, &done); 9896 store32(temp, length); 9897 bind(&done); 9898 9899 if (maybeTemp == InvalidReg) { 9900 pop(object); 9901 } 9902 } 9903 9904 void MacroAssembler::toHashableNonGCThing(ValueOperand value, 9905 ValueOperand result, 9906 FloatRegister tempFloat) { 9907 // Inline implementation of |HashableValue::setValue()|. 9908 9909 #ifdef DEBUG 9910 Label ok; 9911 branchTestGCThing(Assembler::NotEqual, value, &ok); 9912 assumeUnreachable("Unexpected GC thing"); 9913 bind(&ok); 9914 #endif 9915 9916 Label useInput, done; 9917 branchTestDouble(Assembler::NotEqual, value, &useInput); 9918 { 9919 Register int32 = result.scratchReg(); 9920 unboxDouble(value, tempFloat); 9921 9922 // Normalize int32-valued doubles to int32 and negative zero to +0. 9923 Label canonicalize; 9924 convertDoubleToInt32(tempFloat, int32, &canonicalize, false); 9925 { 9926 tagValue(JSVAL_TYPE_INT32, int32, result); 9927 jump(&done); 9928 } 9929 bind(&canonicalize); 9930 { 9931 // Normalize the sign bit of a NaN. 9932 branchDouble(Assembler::DoubleOrdered, tempFloat, tempFloat, &useInput); 9933 moveValue(JS::NaNValue(), result); 9934 jump(&done); 9935 } 9936 } 9937 9938 bind(&useInput); 9939 moveValue(value, result); 9940 9941 bind(&done); 9942 } 9943 9944 void MacroAssembler::toHashableValue(ValueOperand value, ValueOperand result, 9945 FloatRegister tempFloat, 9946 Label* atomizeString, Label* tagString) { 9947 // Inline implementation of |HashableValue::setValue()|. 9948 9949 ScratchTagScope tag(*this, value); 9950 splitTagForTest(value, tag); 9951 9952 Label notString, useInput, done; 9953 branchTestString(Assembler::NotEqual, tag, ¬String); 9954 { 9955 ScratchTagScopeRelease _(&tag); 9956 9957 Register str = result.scratchReg(); 9958 unboxString(value, str); 9959 9960 branchTest32(Assembler::NonZero, Address(str, JSString::offsetOfFlags()), 9961 Imm32(JSString::ATOM_BIT), &useInput); 9962 9963 jump(atomizeString); 9964 bind(tagString); 9965 9966 tagValue(JSVAL_TYPE_STRING, str, result); 9967 jump(&done); 9968 } 9969 bind(¬String); 9970 branchTestDouble(Assembler::NotEqual, tag, &useInput); 9971 { 9972 ScratchTagScopeRelease _(&tag); 9973 9974 Register int32 = result.scratchReg(); 9975 unboxDouble(value, tempFloat); 9976 9977 Label canonicalize; 9978 convertDoubleToInt32(tempFloat, int32, &canonicalize, false); 9979 { 9980 tagValue(JSVAL_TYPE_INT32, int32, result); 9981 jump(&done); 9982 } 9983 bind(&canonicalize); 9984 { 9985 branchDouble(Assembler::DoubleOrdered, tempFloat, tempFloat, &useInput); 9986 moveValue(JS::NaNValue(), result); 9987 jump(&done); 9988 } 9989 } 9990 9991 bind(&useInput); 9992 moveValue(value, result); 9993 9994 bind(&done); 9995 } 9996 9997 void MacroAssembler::scrambleHashCode(Register result) { 9998 // Inline implementation of |mozilla::ScrambleHashCode()|. 9999 10000 mul32(Imm32(mozilla::kGoldenRatioU32), result); 10001 } 10002 10003 void MacroAssembler::hashAndScrambleValue(ValueOperand value, Register result, 10004 Register temp) { 10005 // Inline implementation of: 10006 // mozilla::ScrambleHashCode(mozilla::HashGeneric(v.asRawBits())) 10007 // Note that this uses the raw bits, which will change if a GC thing moves. 10008 // This function should only be used for non-GC things, or in cases where 10009 // moving GC things are handled specially (eg WeakMapObject). 10010 10011 // uint32_t v1 = static_cast<uint32_t>(aValue); 10012 #ifdef JS_PUNBOX64 10013 move64To32(value.toRegister64(), result); 10014 #else 10015 move32(value.payloadReg(), result); 10016 #endif 10017 10018 // uint32_t v2 = static_cast<uint32_t>(static_cast<uint64_t>(aValue) >> 32); 10019 #ifdef JS_PUNBOX64 10020 auto r64 = Register64(temp); 10021 move64(value.toRegister64(), r64); 10022 rshift64Arithmetic(Imm32(32), r64); 10023 #else 10024 move32(value.typeReg(), temp); 10025 #endif 10026 10027 // mozilla::WrappingMultiply(kGoldenRatioU32, RotateLeft5(aHash) ^ aValue); 10028 // with |aHash = 0| and |aValue = v1|. 10029 mul32(Imm32(mozilla::kGoldenRatioU32), result); 10030 10031 // mozilla::WrappingMultiply(kGoldenRatioU32, RotateLeft5(aHash) ^ aValue); 10032 // with |aHash = <above hash>| and |aValue = v2|. 10033 rotateLeft(Imm32(5), result, result); 10034 xor32(temp, result); 10035 10036 // Combine |mul32| and |scrambleHashCode| by directly multiplying with 10037 // |kGoldenRatioU32 * kGoldenRatioU32|. 10038 // 10039 // mul32(Imm32(mozilla::kGoldenRatioU32), result); 10040 // 10041 // scrambleHashCode(result); 10042 mul32(Imm32(mozilla::kGoldenRatioU32 * mozilla::kGoldenRatioU32), result); 10043 } 10044 10045 void MacroAssembler::prepareHashNonGCThing(ValueOperand value, Register result, 10046 Register temp) { 10047 // Inline implementation of |OrderedHashTableImpl::prepareHash()| and 10048 // |mozilla::HashGeneric(v.asRawBits())|. 10049 10050 #ifdef DEBUG 10051 Label ok; 10052 branchTestGCThing(Assembler::NotEqual, value, &ok); 10053 assumeUnreachable("Unexpected GC thing"); 10054 bind(&ok); 10055 #endif 10056 10057 hashAndScrambleValue(value, result, temp); 10058 } 10059 10060 void MacroAssembler::prepareHashString(Register str, Register result, 10061 Register temp) { 10062 // Inline implementation of |OrderedHashTableImpl::prepareHash()| and 10063 // |JSAtom::hash()|. 10064 10065 #ifdef DEBUG 10066 Label ok; 10067 branchTest32(Assembler::NonZero, Address(str, JSString::offsetOfFlags()), 10068 Imm32(JSString::ATOM_BIT), &ok); 10069 assumeUnreachable("Unexpected non-atom string"); 10070 bind(&ok); 10071 #endif 10072 10073 #ifdef JS_64BIT 10074 static_assert(FatInlineAtom::offsetOfHash() == NormalAtom::offsetOfHash()); 10075 load32(Address(str, NormalAtom::offsetOfHash()), result); 10076 #else 10077 move32(Imm32(JSString::FAT_INLINE_MASK), temp); 10078 and32(Address(str, JSString::offsetOfFlags()), temp); 10079 10080 // Set |result| to 1 for FatInlineAtoms. 10081 move32(Imm32(0), result); 10082 cmp32Set(Assembler::Equal, temp, Imm32(JSString::FAT_INLINE_MASK), result); 10083 10084 // Use a computed load for branch-free code. 10085 10086 static_assert(FatInlineAtom::offsetOfHash() > NormalAtom::offsetOfHash()); 10087 10088 constexpr size_t offsetDiff = 10089 FatInlineAtom::offsetOfHash() - NormalAtom::offsetOfHash(); 10090 static_assert(mozilla::IsPowerOfTwo(offsetDiff)); 10091 10092 uint8_t shift = mozilla::FloorLog2Size(offsetDiff); 10093 if (IsShiftInScaleRange(shift)) { 10094 load32( 10095 BaseIndex(str, result, ShiftToScale(shift), NormalAtom::offsetOfHash()), 10096 result); 10097 } else { 10098 lshift32(Imm32(shift), result); 10099 load32(BaseIndex(str, result, TimesOne, NormalAtom::offsetOfHash()), 10100 result); 10101 } 10102 #endif 10103 10104 scrambleHashCode(result); 10105 } 10106 10107 void MacroAssembler::prepareHashSymbol(Register sym, Register result) { 10108 // Inline implementation of |OrderedHashTableImpl::prepareHash()| and 10109 // |Symbol::hash()|. 10110 10111 load32(Address(sym, JS::Symbol::offsetOfHash()), result); 10112 10113 scrambleHashCode(result); 10114 } 10115 10116 void MacroAssembler::prepareHashBigInt(Register bigInt, Register result, 10117 Register temp1, Register temp2, 10118 Register temp3) { 10119 // Inline implementation of |OrderedHashTableImpl::prepareHash()| and 10120 // |BigInt::hash()|. 10121 10122 // Inline implementation of |mozilla::AddU32ToHash()|. 10123 auto addU32ToHash = [&](auto toAdd) { 10124 rotateLeft(Imm32(5), result, result); 10125 xor32(toAdd, result); 10126 mul32(Imm32(mozilla::kGoldenRatioU32), result); 10127 }; 10128 10129 move32(Imm32(0), result); 10130 10131 // Inline |mozilla::HashBytes()|. 10132 10133 load32(Address(bigInt, BigInt::offsetOfLength()), temp1); 10134 loadBigIntDigits(bigInt, temp2); 10135 10136 Label start, loop; 10137 jump(&start); 10138 bind(&loop); 10139 10140 { 10141 // Compute |AddToHash(AddToHash(hash, data), sizeof(Digit))|. 10142 #if defined(JS_CODEGEN_MIPS64) 10143 // Hash the lower 32-bits. 10144 addU32ToHash(Address(temp2, 0)); 10145 10146 // Hash the upper 32-bits. 10147 addU32ToHash(Address(temp2, sizeof(int32_t))); 10148 #elif JS_PUNBOX64 10149 // Use a single 64-bit load on non-MIPS64 platforms. 10150 loadPtr(Address(temp2, 0), temp3); 10151 10152 // Hash the lower 32-bits. 10153 addU32ToHash(temp3); 10154 10155 // Hash the upper 32-bits. 10156 rshiftPtr(Imm32(32), temp3); 10157 addU32ToHash(temp3); 10158 #else 10159 addU32ToHash(Address(temp2, 0)); 10160 #endif 10161 } 10162 addPtr(Imm32(sizeof(BigInt::Digit)), temp2); 10163 10164 bind(&start); 10165 branchSub32(Assembler::NotSigned, Imm32(1), temp1, &loop); 10166 10167 // Compute |mozilla::AddToHash(h, isNegative())|. 10168 { 10169 static_assert(mozilla::IsPowerOfTwo(BigInt::signBitMask())); 10170 10171 load32(Address(bigInt, BigInt::offsetOfFlags()), temp1); 10172 and32(Imm32(BigInt::signBitMask()), temp1); 10173 rshift32(Imm32(mozilla::FloorLog2(BigInt::signBitMask())), temp1); 10174 10175 addU32ToHash(temp1); 10176 } 10177 10178 scrambleHashCode(result); 10179 } 10180 10181 void MacroAssembler::prepareHashObject(Register setObj, ValueOperand value, 10182 Register result, Register temp1, 10183 Register temp2, Register temp3, 10184 Register temp4) { 10185 #ifdef JS_PUNBOX64 10186 // Inline implementation of |OrderedHashTableImpl::prepareHash()| and 10187 // |HashCodeScrambler::scramble(v.asRawBits())|. 10188 10189 // Load |HashCodeScrambler*|. If the object has no buffer yet this will be 10190 // nullptr. In this case we use 0 as hash number because the hash won't be 10191 // used in MacroAssembler::orderedHashTableLookup when there are no entries. 10192 Label done; 10193 static_assert(MapObject::offsetOfHashCodeScrambler() == 10194 SetObject::offsetOfHashCodeScrambler()); 10195 loadPrivate(Address(setObj, SetObject::offsetOfHashCodeScrambler()), temp1); 10196 move32(Imm32(0), result); 10197 branchTestPtr(Assembler::Zero, temp1, temp1, &done); 10198 10199 // Load |HashCodeScrambler::mK0| and |HashCodeScrambler::mK1|. 10200 auto k0 = Register64(temp1); 10201 auto k1 = Register64(temp2); 10202 load64(Address(temp1, mozilla::HashCodeScrambler::offsetOfMK1()), k1); 10203 load64(Address(temp1, mozilla::HashCodeScrambler::offsetOfMK0()), k0); 10204 10205 // Hash numbers are 32-bit values, so only hash the lower double-word. 10206 static_assert(sizeof(mozilla::HashNumber) == 4); 10207 move32To64ZeroExtend(value.valueReg(), Register64(result)); 10208 10209 // Inline implementation of |SipHasher::sipHash()|. 10210 auto m = Register64(result); 10211 auto v0 = Register64(temp3); 10212 auto v1 = Register64(temp4); 10213 auto v2 = k0; 10214 auto v3 = k1; 10215 10216 auto sipRound = [&]() { 10217 // mV0 = WrappingAdd(mV0, mV1); 10218 add64(v1, v0); 10219 10220 // mV1 = RotateLeft(mV1, 13); 10221 rotateLeft64(Imm32(13), v1, v1, InvalidReg); 10222 10223 // mV1 ^= mV0; 10224 xor64(v0, v1); 10225 10226 // mV0 = RotateLeft(mV0, 32); 10227 rotateLeft64(Imm32(32), v0, v0, InvalidReg); 10228 10229 // mV2 = WrappingAdd(mV2, mV3); 10230 add64(v3, v2); 10231 10232 // mV3 = RotateLeft(mV3, 16); 10233 rotateLeft64(Imm32(16), v3, v3, InvalidReg); 10234 10235 // mV3 ^= mV2; 10236 xor64(v2, v3); 10237 10238 // mV0 = WrappingAdd(mV0, mV3); 10239 add64(v3, v0); 10240 10241 // mV3 = RotateLeft(mV3, 21); 10242 rotateLeft64(Imm32(21), v3, v3, InvalidReg); 10243 10244 // mV3 ^= mV0; 10245 xor64(v0, v3); 10246 10247 // mV2 = WrappingAdd(mV2, mV1); 10248 add64(v1, v2); 10249 10250 // mV1 = RotateLeft(mV1, 17); 10251 rotateLeft64(Imm32(17), v1, v1, InvalidReg); 10252 10253 // mV1 ^= mV2; 10254 xor64(v2, v1); 10255 10256 // mV2 = RotateLeft(mV2, 32); 10257 rotateLeft64(Imm32(32), v2, v2, InvalidReg); 10258 }; 10259 10260 // 1. Initialization. 10261 // mV0 = aK0 ^ UINT64_C(0x736f6d6570736575); 10262 move64(Imm64(0x736f6d6570736575), v0); 10263 xor64(k0, v0); 10264 10265 // mV1 = aK1 ^ UINT64_C(0x646f72616e646f6d); 10266 move64(Imm64(0x646f72616e646f6d), v1); 10267 xor64(k1, v1); 10268 10269 // mV2 = aK0 ^ UINT64_C(0x6c7967656e657261); 10270 MOZ_ASSERT(v2 == k0); 10271 xor64(Imm64(0x6c7967656e657261), v2); 10272 10273 // mV3 = aK1 ^ UINT64_C(0x7465646279746573); 10274 MOZ_ASSERT(v3 == k1); 10275 xor64(Imm64(0x7465646279746573), v3); 10276 10277 // 2. Compression. 10278 // mV3 ^= aM; 10279 xor64(m, v3); 10280 10281 // sipRound(); 10282 sipRound(); 10283 10284 // mV0 ^= aM; 10285 xor64(m, v0); 10286 10287 // 3. Finalization. 10288 // mV2 ^= 0xff; 10289 xor64(Imm64(0xff), v2); 10290 10291 // for (int i = 0; i < 3; i++) sipRound(); 10292 for (int i = 0; i < 3; i++) { 10293 sipRound(); 10294 } 10295 10296 // return mV0 ^ mV1 ^ mV2 ^ mV3; 10297 xor64(v1, v0); 10298 xor64(v2, v3); 10299 xor64(v3, v0); 10300 10301 move64To32(v0, result); 10302 10303 scrambleHashCode(result); 10304 10305 bind(&done); 10306 #else 10307 MOZ_CRASH("Not implemented"); 10308 #endif 10309 } 10310 10311 void MacroAssembler::prepareHashValue(Register setObj, ValueOperand value, 10312 Register result, Register temp1, 10313 Register temp2, Register temp3, 10314 Register temp4) { 10315 Label isString, isObject, isSymbol, isBigInt; 10316 { 10317 ScratchTagScope tag(*this, value); 10318 splitTagForTest(value, tag); 10319 10320 branchTestString(Assembler::Equal, tag, &isString); 10321 branchTestObject(Assembler::Equal, tag, &isObject); 10322 branchTestSymbol(Assembler::Equal, tag, &isSymbol); 10323 branchTestBigInt(Assembler::Equal, tag, &isBigInt); 10324 } 10325 10326 Label done; 10327 { 10328 prepareHashNonGCThing(value, result, temp1); 10329 jump(&done); 10330 } 10331 bind(&isString); 10332 { 10333 unboxString(value, temp1); 10334 prepareHashString(temp1, result, temp2); 10335 jump(&done); 10336 } 10337 bind(&isObject); 10338 { 10339 prepareHashObject(setObj, value, result, temp1, temp2, temp3, temp4); 10340 jump(&done); 10341 } 10342 bind(&isSymbol); 10343 { 10344 unboxSymbol(value, temp1); 10345 prepareHashSymbol(temp1, result); 10346 jump(&done); 10347 } 10348 bind(&isBigInt); 10349 { 10350 unboxBigInt(value, temp1); 10351 prepareHashBigInt(temp1, result, temp2, temp3, temp4); 10352 10353 // Fallthrough to |done|. 10354 } 10355 10356 bind(&done); 10357 } 10358 10359 template <typename TableObject> 10360 void MacroAssembler::orderedHashTableLookup(Register setOrMapObj, 10361 ValueOperand value, Register hash, 10362 Register entryTemp, Register temp1, 10363 Register temp2, Register temp3, 10364 Register temp4, Label* found, 10365 IsBigInt isBigInt) { 10366 // Inline implementation of |OrderedHashTableImpl::lookup()|. 10367 10368 MOZ_ASSERT_IF(isBigInt == IsBigInt::No, temp3 == InvalidReg); 10369 MOZ_ASSERT_IF(isBigInt == IsBigInt::No, temp4 == InvalidReg); 10370 10371 #ifdef DEBUG 10372 Label ok; 10373 if (isBigInt == IsBigInt::No) { 10374 branchTestBigInt(Assembler::NotEqual, value, &ok); 10375 assumeUnreachable("Unexpected BigInt"); 10376 } else if (isBigInt == IsBigInt::Yes) { 10377 branchTestBigInt(Assembler::Equal, value, &ok); 10378 assumeUnreachable("Unexpected non-BigInt"); 10379 } 10380 bind(&ok); 10381 #endif 10382 10383 // Jump to notFound if the hash table has no entries and may not have a 10384 // buffer. Check this before calling Assert{Map,Set}ObjectHash because |hash| 10385 // may be 0 when there's no hash code scrambler. 10386 Label notFound; 10387 unboxInt32(Address(setOrMapObj, TableObject::offsetOfLiveCount()), temp1); 10388 branchTest32(Assembler::Zero, temp1, temp1, ¬Found); 10389 10390 #ifdef DEBUG 10391 PushRegsInMask(LiveRegisterSet(RegisterSet::Volatile())); 10392 10393 pushValue(value); 10394 moveStackPtrTo(temp2); 10395 10396 setupUnalignedABICall(temp1); 10397 loadJSContext(temp1); 10398 passABIArg(temp1); 10399 passABIArg(setOrMapObj); 10400 passABIArg(temp2); 10401 passABIArg(hash); 10402 10403 if constexpr (std::is_same_v<TableObject, SetObject>) { 10404 using Fn = 10405 void (*)(JSContext*, SetObject*, const Value*, mozilla::HashNumber); 10406 callWithABI<Fn, jit::AssertSetObjectHash>(); 10407 } else { 10408 static_assert(std::is_same_v<TableObject, MapObject>); 10409 using Fn = 10410 void (*)(JSContext*, MapObject*, const Value*, mozilla::HashNumber); 10411 callWithABI<Fn, jit::AssertMapObjectHash>(); 10412 } 10413 10414 popValue(value); 10415 PopRegsInMask(LiveRegisterSet(RegisterSet::Volatile())); 10416 #endif 10417 10418 // Determine the bucket by computing |hash >> object->hashShift|. The hash 10419 // shift is stored as PrivateUint32Value. 10420 move32(hash, entryTemp); 10421 unboxInt32(Address(setOrMapObj, TableObject::offsetOfHashShift()), temp2); 10422 flexibleRshift32(temp2, entryTemp); 10423 10424 loadPrivate(Address(setOrMapObj, TableObject::offsetOfHashTable()), temp2); 10425 loadPtr(BaseIndex(temp2, entryTemp, ScalePointer), entryTemp); 10426 10427 // Search for a match in this bucket. 10428 Label start, loop; 10429 jump(&start); 10430 bind(&loop); 10431 { 10432 // Inline implementation of |HashableValue::operator==|. 10433 10434 static_assert(TableObject::Table::offsetOfImplDataElement() == 0, 10435 "offsetof(Data, element) is 0"); 10436 auto keyAddr = Address(entryTemp, TableObject::Table::offsetOfEntryKey()); 10437 10438 if (isBigInt == IsBigInt::No) { 10439 // Two HashableValues are equal if they have equal bits. 10440 branch64(Assembler::Equal, keyAddr, value.toRegister64(), found); 10441 } else { 10442 #ifdef JS_PUNBOX64 10443 auto key = ValueOperand(temp1); 10444 #else 10445 auto key = ValueOperand(temp1, temp2); 10446 #endif 10447 10448 loadValue(keyAddr, key); 10449 10450 // Two HashableValues are equal if they have equal bits. 10451 branch64(Assembler::Equal, key.toRegister64(), value.toRegister64(), 10452 found); 10453 10454 // BigInt values are considered equal if they represent the same 10455 // mathematical value. 10456 Label next; 10457 fallibleUnboxBigInt(key, temp2, &next); 10458 if (isBigInt == IsBigInt::Yes) { 10459 unboxBigInt(value, temp1); 10460 } else { 10461 fallibleUnboxBigInt(value, temp1, &next); 10462 } 10463 equalBigInts(temp1, temp2, temp3, temp4, temp1, temp2, &next, &next, 10464 &next); 10465 jump(found); 10466 bind(&next); 10467 } 10468 } 10469 loadPtr(Address(entryTemp, TableObject::Table::offsetOfImplDataChain()), 10470 entryTemp); 10471 bind(&start); 10472 branchTestPtr(Assembler::NonZero, entryTemp, entryTemp, &loop); 10473 10474 bind(¬Found); 10475 } 10476 10477 void MacroAssembler::setObjectHas(Register setObj, ValueOperand value, 10478 Register hash, Register result, 10479 Register temp1, Register temp2, 10480 Register temp3, Register temp4, 10481 IsBigInt isBigInt) { 10482 Label found; 10483 orderedHashTableLookup<SetObject>(setObj, value, hash, result, temp1, temp2, 10484 temp3, temp4, &found, isBigInt); 10485 10486 Label done; 10487 move32(Imm32(0), result); 10488 jump(&done); 10489 10490 bind(&found); 10491 move32(Imm32(1), result); 10492 bind(&done); 10493 } 10494 10495 void MacroAssembler::mapObjectHas(Register mapObj, ValueOperand value, 10496 Register hash, Register result, 10497 Register temp1, Register temp2, 10498 Register temp3, Register temp4, 10499 IsBigInt isBigInt) { 10500 Label found; 10501 orderedHashTableLookup<MapObject>(mapObj, value, hash, result, temp1, temp2, 10502 temp3, temp4, &found, isBigInt); 10503 10504 Label done; 10505 move32(Imm32(0), result); 10506 jump(&done); 10507 10508 bind(&found); 10509 move32(Imm32(1), result); 10510 bind(&done); 10511 } 10512 10513 void MacroAssembler::mapObjectGet(Register mapObj, ValueOperand value, 10514 Register hash, ValueOperand result, 10515 Register temp1, Register temp2, 10516 Register temp3, Register temp4, 10517 Register temp5, IsBigInt isBigInt) { 10518 Label found; 10519 orderedHashTableLookup<MapObject>(mapObj, value, hash, temp1, temp2, temp3, 10520 temp4, temp5, &found, isBigInt); 10521 10522 Label done; 10523 moveValue(UndefinedValue(), result); 10524 jump(&done); 10525 10526 // |temp1| holds the found entry. 10527 bind(&found); 10528 loadValue(Address(temp1, MapObject::Table::Entry::offsetOfValue()), result); 10529 10530 bind(&done); 10531 } 10532 10533 template <typename TableObject> 10534 void MacroAssembler::loadOrderedHashTableCount(Register setOrMapObj, 10535 Register result) { 10536 // Inline implementation of |OrderedHashTableImpl::count()|. 10537 10538 // Load the live count, stored as PrivateUint32Value. 10539 unboxInt32(Address(setOrMapObj, TableObject::offsetOfLiveCount()), result); 10540 } 10541 10542 void MacroAssembler::loadSetObjectSize(Register setObj, Register result) { 10543 loadOrderedHashTableCount<SetObject>(setObj, result); 10544 } 10545 10546 void MacroAssembler::loadMapObjectSize(Register mapObj, Register result) { 10547 loadOrderedHashTableCount<MapObject>(mapObj, result); 10548 } 10549 10550 void MacroAssembler::prepareHashMFBT(Register hashCode, bool alreadyScrambled) { 10551 // Inline implementation of |mozilla::HashTable::prepareHash()|. 10552 static_assert(sizeof(HashNumber) == sizeof(uint32_t)); 10553 10554 // In some cases scrambling can be more efficiently folded into the 10555 // computation of the hash itself. 10556 if (!alreadyScrambled) { 10557 // HashNumber keyHash = ScrambleHashCode(aInputHash); 10558 scrambleHashCode(hashCode); 10559 } 10560 10561 const mozilla::HashNumber RemovedKey = mozilla::detail::kHashTableRemovedKey; 10562 const mozilla::HashNumber CollisionBit = 10563 mozilla::detail::kHashTableCollisionBit; 10564 10565 // Avoid reserved hash codes: 10566 // if (!isLiveHash(keyHash)) { 10567 Label isLive; 10568 branch32(Assembler::Above, hashCode, Imm32(RemovedKey), &isLive); 10569 // keyHash -= (sRemovedKey + 1); 10570 sub32(Imm32(RemovedKey + 1), hashCode); 10571 bind(&isLive); 10572 10573 // return keyHash & ~sCollisionBit; 10574 and32(Imm32(~CollisionBit), hashCode); 10575 } 10576 10577 template <typename Table> 10578 void MacroAssembler::computeHash1MFBT(Register hashTable, Register hashCode, 10579 Register hash1, Register scratch) { 10580 // Inline implementation of |mozilla::HashTable::hash1| 10581 // return aHash0 >> hashShift(); 10582 move32(hashCode, hash1); 10583 load8ZeroExtend(Address(hashTable, Table::offsetOfHashShift()), scratch); 10584 flexibleRshift32(scratch, hash1); 10585 } 10586 10587 template void MacroAssembler::computeHash1MFBT<WeakMapObject::Map>( 10588 Register hashTable, Register hashCode, Register hash1, Register scratch); 10589 10590 template <typename Table> 10591 void MacroAssembler::computeHash2MFBT(Register hashTable, Register hashCode, 10592 Register hash2, Register sizeMask, 10593 Register scratch) { 10594 // Inline implementation of |mozilla::detail::HashTable::hash2| 10595 10596 // Load hashShift into sizeMask 10597 load8ZeroExtend(Address(hashTable, Table::offsetOfHashShift()), sizeMask); 10598 10599 // uint32_t sizeLog2 = kHashNumberBits - hashShift(); 10600 move32(Imm32(kHashNumberBits), scratch); 10601 sub32(sizeMask, scratch); 10602 10603 // DoubleHash dh = {((aCurKeyHash << sizeLog2) >> hashShift()) | 1, 10604 move32(hashCode, hash2); 10605 flexibleLshift32(scratch, hash2); 10606 flexibleRshift32(sizeMask, hash2); 10607 or32(Imm32(1), hash2); 10608 10609 // sizeMask = (HashNumber(1) << sizeLog2) - 1}; 10610 move32(Imm32(1), sizeMask); 10611 flexibleLshift32(scratch, sizeMask); 10612 sub32(Imm32(1), sizeMask); 10613 } 10614 10615 template void MacroAssembler::computeHash2MFBT<WeakMapObject::Map>( 10616 Register hashTable, Register hashCode, Register hash2, Register sizeMask, 10617 Register scratch); 10618 10619 void MacroAssembler::applyDoubleHashMFBT(Register hash1, Register hash2, 10620 Register sizeMask) { 10621 // Inline implementation of |mozilla::detail::HashTable::applyDoubleHash| 10622 10623 // return WrappingSubtract(aHash1, aDoubleHash.mHash2) & aDoubleHash.mSizeMask 10624 sub32(hash2, hash1); 10625 and32(sizeMask, hash1); 10626 } 10627 10628 template <typename Table> 10629 void MacroAssembler::checkForMatchMFBT(Register hashTable, Register hashIndex, 10630 Register hashCode, Register scratch, 10631 Register scratch2, Label* missing, 10632 Label* collision) { 10633 // Helper for inline implementation of |mozilla::detail::HashTable::lookup| 10634 // The following code is used twice in |lookup|: 10635 // 10636 // Slot slot = slotForIndex(h1); 10637 // if (slot.isFree()) { 10638 // <not found> 10639 // } 10640 // if (slot.matchHash(aKeyHash) && match(slot.get(), aLookup)) { 10641 // <found> 10642 // } 10643 // 10644 // To reduce register pressure, we do some inlining and reorder some 10645 // intermediate computation. We inline the following functions: 10646 // 10647 // Slot slotForIndex(HashNumber aIndex) const { 10648 // auto hashes = reinterpret_cast<HashNumber*>(mTable); 10649 // auto entries = reinterpret_cast<Entry*>(&hashes[capacity()]); 10650 // return Slot(&entries[aIndex], &hashes[aIndex]); 10651 // } 10652 // 10653 // uint32_t rawCapacity() { return 1u << (kHashNumberBits - hashShift()); } 10654 // 10655 // bool isFree() const { return *mKeyHash == Entry::sFreeKey; } 10656 // 10657 // bool matchHash(HashNumber hn) { 10658 // return (*mKeyHash & ~Entry::sCollisionBit) == hn; 10659 // } 10660 // 10661 // Reordered, we implement: 10662 // 10663 // auto hashes = reinterpret_cast<HashNumber*>(mTable); 10664 // HashNumber hashInTable = hashes[hashIndex]; 10665 // if (hashInTable == Entry::sFreeKey) { 10666 // <jump to missing label> 10667 // } 10668 // if (hashInTable & ~CollisionBit != hashCode) { 10669 // <jump to collision label> 10670 // } 10671 // auto entries = hashes[capacity()]; 10672 // Entry* entry = entries[hashIndex] 10673 // <fall through to entry-specific match code> 10674 const mozilla::HashNumber FreeKey = mozilla::detail::kHashTableFreeKey; 10675 const mozilla::HashNumber CollisionBit = 10676 mozilla::detail::kHashTableCollisionBit; 10677 10678 Address tableAddr(hashTable, Table::offsetOfTable()); 10679 Address hashShiftAddr(hashTable, Table::offsetOfHashShift()); 10680 10681 // auto hashes = reinterpret_cast<HashNumber*>(mTable); 10682 Register hashes = scratch; 10683 loadPtr(tableAddr, scratch); 10684 10685 // HashNumber hashInTable = hashes[hashIndex]; 10686 Register hashInTable = scratch2; 10687 static_assert(sizeof(HashNumber) == 4); 10688 load32(BaseIndex(hashes, hashIndex, Scale::TimesFour), hashInTable); 10689 10690 // if (hashInTable == Entry::sFreeKey) { 10691 // <jump to missing label> 10692 // } 10693 branch32(Assembler::Equal, hashInTable, Imm32(FreeKey), missing); 10694 10695 // if (hashInTable & ~CollisionBit != hashCode) { 10696 // <jump to collision label> 10697 // } 10698 and32(Imm32(~CollisionBit), hashInTable); 10699 branch32(Assembler::NotEqual, hashInTable, hashCode, collision); 10700 10701 // entries = hashes[capacity()] 10702 // = hashes[1 << (kHashNumberBits - hashShift()] 10703 // = &hashes + (1 << (kHashNumberBits - hashShift())) * sizeof(HashNumber) 10704 // = &hashes + sizeof(HashNumber) << (kHashNumberBits - hashShift()) 10705 // = &hashes + sizeof(HashNumber) << (kHashNumberBits + -hashShift()) 10706 Register capacityOffset = scratch; 10707 load8ZeroExtend(hashShiftAddr, scratch2); 10708 neg32(scratch2); 10709 add32(Imm32(kHashNumberBits), scratch2); 10710 move32(Imm32(sizeof(mozilla::HashNumber)), capacityOffset); 10711 flexibleLshift32(scratch2, capacityOffset); 10712 Register entries = scratch2; 10713 loadPtr(tableAddr, entries); 10714 addPtr(capacityOffset, entries); 10715 10716 // Load entries[hashIndex] into |scratch| 10717 size_t EntrySize = sizeof(typename Table::Entry); 10718 if (mozilla::IsPowerOfTwo(EntrySize)) { 10719 uint32_t shift = mozilla::FloorLog2(EntrySize); 10720 lshiftPtr(Imm32(shift), hashIndex, scratch); 10721 } else { 10722 // Note: this is provided as a fallback. Faster paths are possible for many 10723 // non-power-of-two constants. If you add a use of this code that requires a 10724 // non-power-of-two EntrySize, consider extending this code. 10725 move32(hashIndex, scratch); 10726 mulPtr(ImmWord(EntrySize), scratch); 10727 } 10728 computeEffectiveAddress(BaseIndex(entries, scratch, Scale::TimesOne), 10729 scratch); 10730 } 10731 10732 template void MacroAssembler::checkForMatchMFBT<WeakMapObject::Map>( 10733 Register hashTable, Register hashIndex, Register hashCode, Register scratch, 10734 Register scratch2, Label* missing, Label* collision); 10735 10736 // Can't push large frames blindly on windows, so we must touch frame memory 10737 // incrementally, with no more than 4096 - 1 bytes between touches. 10738 // 10739 // This is used across all platforms for simplicity. 10740 void MacroAssembler::touchFrameValues(Register numStackValues, 10741 Register scratch1, Register scratch2) { 10742 const size_t FRAME_TOUCH_INCREMENT = 2048; 10743 static_assert(FRAME_TOUCH_INCREMENT < 4096 - 1, 10744 "Frame increment is too large"); 10745 10746 moveStackPtrTo(scratch2); 10747 10748 lshiftPtr(Imm32(3), numStackValues, scratch1); 10749 { 10750 // Note: this loop needs to update the stack pointer register because older 10751 // Linux kernels check the distance between the touched address and RSP. 10752 // See bug 1839669 comment 47. 10753 Label touchFrameLoop; 10754 Label touchFrameLoopEnd; 10755 bind(&touchFrameLoop); 10756 branchSub32(Assembler::Signed, Imm32(FRAME_TOUCH_INCREMENT), scratch1, 10757 &touchFrameLoopEnd); 10758 subFromStackPtr(Imm32(FRAME_TOUCH_INCREMENT)); 10759 store32(Imm32(0), Address(getStackPointer(), 0)); 10760 jump(&touchFrameLoop); 10761 bind(&touchFrameLoopEnd); 10762 } 10763 10764 moveToStackPtr(scratch2); 10765 } 10766 10767 #ifdef FUZZING_JS_FUZZILLI 10768 void MacroAssembler::fuzzilliHashDouble(FloatRegister src, Register result, 10769 Register temp) { 10770 canonicalizeDouble(src); 10771 10772 # ifdef JS_PUNBOX64 10773 Register64 r64(temp); 10774 # else 10775 Register64 r64(temp, result); 10776 # endif 10777 10778 moveDoubleToGPR64(src, r64); 10779 10780 # ifdef JS_PUNBOX64 10781 // Move the high word into |result|. 10782 move64(r64, Register64(result)); 10783 rshift64(Imm32(32), Register64(result)); 10784 # endif 10785 10786 // Add the high and low words of |r64|. 10787 add32(temp, result); 10788 } 10789 10790 void MacroAssembler::fuzzilliStoreHash(Register value, Register temp1, 10791 Register temp2) { 10792 loadJSContext(temp1); 10793 10794 // stats 10795 Address addrExecHashInputs(temp1, offsetof(JSContext, executionHashInputs)); 10796 add32(Imm32(1), addrExecHashInputs); 10797 10798 // hash 10799 Address addrExecHash(temp1, offsetof(JSContext, executionHash)); 10800 load32(addrExecHash, temp2); 10801 add32(value, temp2); 10802 rotateLeft(Imm32(1), temp2, temp2); 10803 store32(temp2, addrExecHash); 10804 } 10805 #endif 10806 10807 namespace js { 10808 namespace jit { 10809 10810 #ifdef DEBUG 10811 template <class RegisterType> 10812 AutoGenericRegisterScope<RegisterType>::AutoGenericRegisterScope( 10813 MacroAssembler& masm, RegisterType reg) 10814 : RegisterType(reg), masm_(masm), released_(false) { 10815 masm.debugTrackedRegisters_.add(reg); 10816 } 10817 10818 template AutoGenericRegisterScope<Register>::AutoGenericRegisterScope( 10819 MacroAssembler& masm, Register reg); 10820 template AutoGenericRegisterScope<FloatRegister>::AutoGenericRegisterScope( 10821 MacroAssembler& masm, FloatRegister reg); 10822 #endif // DEBUG 10823 10824 #ifdef DEBUG 10825 template <class RegisterType> 10826 AutoGenericRegisterScope<RegisterType>::~AutoGenericRegisterScope() { 10827 if (!released_) { 10828 release(); 10829 } 10830 } 10831 10832 template AutoGenericRegisterScope<Register>::~AutoGenericRegisterScope(); 10833 template AutoGenericRegisterScope<FloatRegister>::~AutoGenericRegisterScope(); 10834 10835 template <class RegisterType> 10836 void AutoGenericRegisterScope<RegisterType>::release() { 10837 MOZ_ASSERT(!released_); 10838 released_ = true; 10839 const RegisterType& reg = *dynamic_cast<RegisterType*>(this); 10840 masm_.debugTrackedRegisters_.take(reg); 10841 } 10842 10843 template void AutoGenericRegisterScope<Register>::release(); 10844 template void AutoGenericRegisterScope<FloatRegister>::release(); 10845 10846 template <class RegisterType> 10847 void AutoGenericRegisterScope<RegisterType>::reacquire() { 10848 MOZ_ASSERT(released_); 10849 released_ = false; 10850 const RegisterType& reg = *dynamic_cast<RegisterType*>(this); 10851 masm_.debugTrackedRegisters_.add(reg); 10852 } 10853 10854 template void AutoGenericRegisterScope<Register>::reacquire(); 10855 template void AutoGenericRegisterScope<FloatRegister>::reacquire(); 10856 10857 #endif // DEBUG 10858 10859 } // namespace jit 10860 10861 } // namespace js