1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "jit/MacroAssembler-inl.h"
9 #include "mozilla/FloatingPoint.h"
10 #include "mozilla/Latin1.h"
11 #include "mozilla/MathAlgorithms.h"
12 #include "mozilla/XorShift128PlusRNG.h"
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/WasmInstanceData.h"
54 #include "wasm/WasmMemory.h"
55 #include "wasm/WasmTypeDef.h"
56 #include "wasm/WasmValidate.h"
58 #include "jit/TemplateObject-inl.h"
59 #include "vm/BytecodeUtil-inl.h"
60 #include "vm/Interpreter-inl.h"
61 #include "vm/JSObject-inl.h"
62 #include "wasm/WasmGcObject-inl.h"
65 using namespace js::jit
;
69 using mozilla::CheckedInt
;
71 TrampolinePtr
MacroAssembler::preBarrierTrampoline(MIRType type
) {
72 const JitRuntime
* rt
= runtime()->jitRuntime();
73 return rt
->preBarrier(type
);
77 static void StoreToTypedFloatArray(MacroAssembler
& masm
, Scalar::Type arrayType
,
78 FloatRegister value
, const T
& dest
,
80 LiveRegisterSet volatileLiveRegs
) {
83 masm
.storeFloat16(value
, dest
, temp
, volatileLiveRegs
);
85 case Scalar::Float32
: {
86 if (value
.isDouble()) {
87 ScratchFloat32Scope
fpscratch(masm
);
88 masm
.convertDoubleToFloat32(value
, fpscratch
);
89 masm
.storeFloat32(fpscratch
, dest
);
91 MOZ_ASSERT(value
.isSingle());
92 masm
.storeFloat32(value
, dest
);
97 MOZ_ASSERT(value
.isDouble());
98 masm
.storeDouble(value
, dest
);
101 MOZ_CRASH("Invalid typed array type");
105 void MacroAssembler::storeToTypedFloatArray(Scalar::Type arrayType
,
107 const BaseIndex
& dest
,
109 LiveRegisterSet volatileLiveRegs
) {
110 StoreToTypedFloatArray(*this, arrayType
, value
, dest
, temp
, volatileLiveRegs
);
112 void MacroAssembler::storeToTypedFloatArray(Scalar::Type arrayType
,
114 const Address
& dest
, Register temp
,
115 LiveRegisterSet volatileLiveRegs
) {
116 StoreToTypedFloatArray(*this, arrayType
, value
, dest
, temp
, volatileLiveRegs
);
119 template <typename S
, typename T
>
120 static void StoreToTypedBigIntArray(MacroAssembler
& masm
,
121 Scalar::Type arrayType
, const S
& value
,
123 MOZ_ASSERT(Scalar::isBigIntType(arrayType
));
124 masm
.store64(value
, dest
);
127 void MacroAssembler::storeToTypedBigIntArray(Scalar::Type arrayType
,
129 const BaseIndex
& dest
) {
130 StoreToTypedBigIntArray(*this, arrayType
, value
, dest
);
132 void MacroAssembler::storeToTypedBigIntArray(Scalar::Type arrayType
,
134 const Address
& dest
) {
135 StoreToTypedBigIntArray(*this, arrayType
, value
, dest
);
138 void MacroAssembler::boxUint32(Register source
, ValueOperand dest
,
139 Uint32Mode mode
, Label
* fail
) {
141 // Fail if the value does not fit in an int32.
142 case Uint32Mode::FailOnDouble
: {
143 branchTest32(Assembler::Signed
, source
, source
, fail
);
144 tagValue(JSVAL_TYPE_INT32
, source
, dest
);
147 case Uint32Mode::ForceDouble
: {
148 // Always convert the value to double.
149 ScratchDoubleScope
fpscratch(*this);
150 convertUInt32ToDouble(source
, fpscratch
);
151 boxDouble(fpscratch
, dest
, fpscratch
);
157 template <typename T
>
158 void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType
, const T
& src
,
159 AnyRegister dest
, Register temp1
,
160 Register temp2
, Label
* fail
,
161 LiveRegisterSet volatileLiveRegs
) {
164 load8SignExtend(src
, dest
.gpr());
167 case Scalar::Uint8Clamped
:
168 load8ZeroExtend(src
, dest
.gpr());
171 load16SignExtend(src
, dest
.gpr());
174 load16ZeroExtend(src
, dest
.gpr());
177 load32(src
, dest
.gpr());
180 if (dest
.isFloat()) {
182 convertUInt32ToDouble(temp1
, dest
.fpu());
184 load32(src
, dest
.gpr());
186 // Bail out if the value doesn't fit into a signed int32 value. This
187 // is what allows MLoadUnboxedScalar to have a type() of
188 // MIRType::Int32 for UInt32 array loads.
189 branchTest32(Assembler::Signed
, dest
.gpr(), dest
.gpr(), fail
);
192 case Scalar::Float16
:
193 loadFloat16(src
, dest
.fpu(), temp1
, temp2
, volatileLiveRegs
);
194 canonicalizeFloat(dest
.fpu());
196 case Scalar::Float32
:
197 loadFloat32(src
, dest
.fpu());
198 canonicalizeFloat(dest
.fpu());
200 case Scalar::Float64
:
201 loadDouble(src
, dest
.fpu());
202 canonicalizeDouble(dest
.fpu());
204 case Scalar::BigInt64
:
205 case Scalar::BigUint64
:
207 MOZ_CRASH("Invalid typed array type");
211 template void MacroAssembler::loadFromTypedArray(
212 Scalar::Type arrayType
, const Address
& src
, AnyRegister dest
,
213 Register temp1
, Register temp2
, Label
* fail
,
214 LiveRegisterSet volatileLiveRegs
);
215 template void MacroAssembler::loadFromTypedArray(
216 Scalar::Type arrayType
, const BaseIndex
& src
, AnyRegister dest
,
217 Register temp1
, Register temp2
, Label
* fail
,
218 LiveRegisterSet volatileLiveRegs
);
220 template <typename T
>
221 void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType
, const T
& src
,
222 const ValueOperand
& dest
,
223 Uint32Mode uint32Mode
, Register temp
,
225 LiveRegisterSet volatileLiveRegs
) {
229 case Scalar::Uint8Clamped
:
233 loadFromTypedArray(arrayType
, src
, AnyRegister(dest
.scratchReg()),
234 InvalidReg
, InvalidReg
, nullptr, LiveRegisterSet
{});
235 tagValue(JSVAL_TYPE_INT32
, dest
.scratchReg(), dest
);
238 load32(src
, dest
.scratchReg());
239 boxUint32(dest
.scratchReg(), dest
, uint32Mode
, fail
);
241 case Scalar::Float16
: {
242 ScratchDoubleScope
dscratch(*this);
243 FloatRegister fscratch
= dscratch
.asSingle();
244 loadFromTypedArray(arrayType
, src
, AnyRegister(fscratch
),
245 dest
.scratchReg(), temp
, nullptr, volatileLiveRegs
);
246 convertFloat32ToDouble(fscratch
, dscratch
);
247 boxDouble(dscratch
, dest
, dscratch
);
250 case Scalar::Float32
: {
251 ScratchDoubleScope
dscratch(*this);
252 FloatRegister fscratch
= dscratch
.asSingle();
253 loadFromTypedArray(arrayType
, src
, AnyRegister(fscratch
), InvalidReg
,
254 InvalidReg
, nullptr, LiveRegisterSet
{});
255 convertFloat32ToDouble(fscratch
, dscratch
);
256 boxDouble(dscratch
, dest
, dscratch
);
259 case Scalar::Float64
: {
260 ScratchDoubleScope
fpscratch(*this);
261 loadFromTypedArray(arrayType
, src
, AnyRegister(fpscratch
), InvalidReg
,
262 InvalidReg
, nullptr, LiveRegisterSet
{});
263 boxDouble(fpscratch
, dest
, fpscratch
);
266 case Scalar::BigInt64
:
267 case Scalar::BigUint64
:
269 MOZ_CRASH("Invalid typed array type");
273 template void MacroAssembler::loadFromTypedArray(
274 Scalar::Type arrayType
, const Address
& src
, const ValueOperand
& dest
,
275 Uint32Mode uint32Mode
, Register temp
, Label
* fail
,
276 LiveRegisterSet volatileLiveRegs
);
277 template void MacroAssembler::loadFromTypedArray(
278 Scalar::Type arrayType
, const BaseIndex
& src
, const ValueOperand
& dest
,
279 Uint32Mode uint32Mode
, Register temp
, Label
* fail
,
280 LiveRegisterSet volatileLiveRegs
);
282 template <typename T
>
283 void MacroAssembler::loadFromTypedBigIntArray(Scalar::Type arrayType
,
284 const T
& src
, Register bigInt
,
286 MOZ_ASSERT(Scalar::isBigIntType(arrayType
));
289 initializeBigInt64(arrayType
, bigInt
, temp
);
292 template void MacroAssembler::loadFromTypedBigIntArray(Scalar::Type arrayType
,
296 template void MacroAssembler::loadFromTypedBigIntArray(Scalar::Type arrayType
,
297 const BaseIndex
& src
,
301 // Inlined version of gc::CheckAllocatorState that checks the bare essentials
302 // and bails for anything that cannot be handled with our jit allocators.
303 void MacroAssembler::checkAllocatorState(Register temp
, gc::AllocKind allocKind
,
305 // Don't execute the inline path if GC probes are built in.
311 // Don't execute the inline path if gc zeal or tracing are active.
312 const uint32_t* ptrZealModeBits
= runtime()->addressOfGCZealModeBits();
313 branch32(Assembler::NotEqual
, AbsoluteAddress(ptrZealModeBits
), Imm32(0),
317 // If the zone has a realm with an object allocation metadata hook, emit a
318 // guard for this. Note that IC stubs and some other trampolines can be shared
319 // across realms, so we don't bake in a realm pointer.
320 if (gc::IsObjectAllocKind(allocKind
) &&
321 realm()->zone()->hasRealmWithAllocMetadataBuilder()) {
323 loadPtr(Address(temp
, JSContext::offsetOfRealm()), temp
);
324 branchPtr(Assembler::NotEqual
,
325 Address(temp
, Realm::offsetOfAllocationMetadataBuilder()),
330 bool MacroAssembler::shouldNurseryAllocate(gc::AllocKind allocKind
,
331 gc::Heap initialHeap
) {
332 // Note that Ion elides barriers on writes to objects known to be in the
333 // nursery, so any allocation that can be made into the nursery must be made
334 // into the nursery, even if the nursery is disabled. At runtime these will
335 // take the out-of-line path, which is required to insert a barrier for the
336 // initializing writes.
337 return IsNurseryAllocable(allocKind
) && initialHeap
!= gc::Heap::Tenured
;
340 // Inline version of Nursery::allocateObject. If the object has dynamic slots,
341 // this fills in the slots_ pointer.
342 void MacroAssembler::nurseryAllocateObject(Register result
, Register temp
,
343 gc::AllocKind allocKind
,
344 size_t nDynamicSlots
, Label
* fail
,
345 const AllocSiteInput
& allocSite
) {
346 MOZ_ASSERT(IsNurseryAllocable(allocKind
));
348 // Currently the JIT does not nursery allocate foreground finalized
349 // objects. This is allowed for objects that support this and have the
350 // JSCLASS_SKIP_NURSERY_FINALIZE class flag set. It's hard to assert that here
351 // though so disallow all foreground finalized objects for now.
352 MOZ_ASSERT(!IsForegroundFinalized(allocKind
));
354 // We still need to allocate in the nursery, per the comment in
355 // shouldNurseryAllocate; however, we need to insert into the
356 // mallocedBuffers set, so bail to do the nursery allocation in the
358 if (nDynamicSlots
>= Nursery::MaxNurseryBufferSize
/ sizeof(Value
)) {
363 // Check whether this allocation site needs pretenuring. This dynamic check
364 // only happens for baseline code.
365 if (allocSite
.is
<Register
>()) {
366 Register site
= allocSite
.as
<Register
>();
367 branchTestPtr(Assembler::NonZero
,
368 Address(site
, gc::AllocSite::offsetOfScriptAndState()),
369 Imm32(gc::AllocSite::LONG_LIVED_BIT
), fail
);
372 // No explicit check for nursery.isEnabled() is needed, as the comparison
373 // with the nursery's end will always fail in such cases.
374 CompileZone
* zone
= realm()->zone();
375 size_t thingSize
= gc::Arena::thingSize(allocKind
);
376 size_t totalSize
= thingSize
;
378 totalSize
+= ObjectSlots::allocSize(nDynamicSlots
);
380 MOZ_ASSERT(totalSize
< INT32_MAX
);
381 MOZ_ASSERT(totalSize
% gc::CellAlignBytes
== 0);
383 bumpPointerAllocate(result
, temp
, fail
, zone
, JS::TraceKind::Object
,
384 totalSize
, allocSite
);
387 store32(Imm32(nDynamicSlots
),
388 Address(result
, thingSize
+ ObjectSlots::offsetOfCapacity()));
391 Address(result
, thingSize
+ ObjectSlots::offsetOfDictionarySlotSpan()));
392 store64(Imm64(ObjectSlots::NoUniqueIdInDynamicSlots
),
393 Address(result
, thingSize
+ ObjectSlots::offsetOfMaybeUniqueId()));
394 computeEffectiveAddress(
395 Address(result
, thingSize
+ ObjectSlots::offsetOfSlots()), temp
);
396 storePtr(temp
, Address(result
, NativeObject::offsetOfSlots()));
400 // Inlined version of FreeSpan::allocate. This does not fill in slots_.
401 void MacroAssembler::freeListAllocate(Register result
, Register temp
,
402 gc::AllocKind allocKind
, Label
* fail
) {
403 CompileZone
* zone
= realm()->zone();
404 int thingSize
= int(gc::Arena::thingSize(allocKind
));
409 // Load the first and last offsets of |zone|'s free list for |allocKind|.
410 // If there is no room remaining in the span, fall back to get the next one.
411 gc::FreeSpan
** ptrFreeList
= zone
->addressOfFreeList(allocKind
);
412 loadPtr(AbsoluteAddress(ptrFreeList
), temp
);
413 load16ZeroExtend(Address(temp
, js::gc::FreeSpan::offsetOfFirst()), result
);
414 load16ZeroExtend(Address(temp
, js::gc::FreeSpan::offsetOfLast()), temp
);
415 branch32(Assembler::AboveOrEqual
, result
, temp
, &fallback
);
417 // Bump the offset for the next allocation.
418 add32(Imm32(thingSize
), result
);
419 loadPtr(AbsoluteAddress(ptrFreeList
), temp
);
420 store16(result
, Address(temp
, js::gc::FreeSpan::offsetOfFirst()));
421 sub32(Imm32(thingSize
), result
);
422 addPtr(temp
, result
); // Turn the offset into a pointer.
426 // If there are no free spans left, we bail to finish the allocation. The
427 // interpreter will call the GC allocator to set up a new arena to allocate
428 // from, after which we can resume allocating in the jit.
429 branchTest32(Assembler::Zero
, result
, result
, fail
);
430 loadPtr(AbsoluteAddress(ptrFreeList
), temp
);
431 addPtr(temp
, result
); // Turn the offset into a pointer.
433 // Update the free list to point to the next span (which may be empty).
434 load32(Address(result
, 0), result
);
435 store32(result
, Address(temp
, js::gc::FreeSpan::offsetOfFirst()));
440 if (runtime()->geckoProfiler().enabled()) {
441 uint32_t* countAddress
= zone
->addressOfTenuredAllocCount();
442 movePtr(ImmPtr(countAddress
), temp
);
443 add32(Imm32(1), Address(temp
, 0));
447 void MacroAssembler::callFreeStub(Register slots
) {
448 // This register must match the one in JitRuntime::generateFreeStub.
449 const Register regSlots
= CallTempReg0
;
452 movePtr(slots
, regSlots
);
453 call(runtime()->jitRuntime()->freeStub());
457 // Inlined equivalent of gc::AllocateObject, without failure case handling.
458 void MacroAssembler::allocateObject(Register result
, Register temp
,
459 gc::AllocKind allocKind
,
460 uint32_t nDynamicSlots
,
461 gc::Heap initialHeap
, Label
* fail
,
462 const AllocSiteInput
& allocSite
) {
463 MOZ_ASSERT(gc::IsObjectAllocKind(allocKind
));
465 checkAllocatorState(temp
, allocKind
, fail
);
467 if (shouldNurseryAllocate(allocKind
, initialHeap
)) {
468 MOZ_ASSERT(initialHeap
== gc::Heap::Default
);
469 return nurseryAllocateObject(result
, temp
, allocKind
, nDynamicSlots
, fail
,
473 // Fall back to calling into the VM to allocate objects in the tenured heap
474 // that have dynamic slots.
480 return freeListAllocate(result
, temp
, allocKind
, fail
);
483 void MacroAssembler::createGCObject(Register obj
, Register temp
,
484 const TemplateObject
& templateObj
,
485 gc::Heap initialHeap
, Label
* fail
,
486 bool initContents
/* = true */) {
487 gc::AllocKind allocKind
= templateObj
.getAllocKind();
488 MOZ_ASSERT(gc::IsObjectAllocKind(allocKind
));
490 uint32_t nDynamicSlots
= 0;
491 if (templateObj
.isNativeObject()) {
492 const TemplateNativeObject
& ntemplate
=
493 templateObj
.asTemplateNativeObject();
494 nDynamicSlots
= ntemplate
.numDynamicSlots();
497 allocateObject(obj
, temp
, allocKind
, nDynamicSlots
, initialHeap
, fail
);
498 initGCThing(obj
, temp
, templateObj
, initContents
);
501 void MacroAssembler::createPlainGCObject(
502 Register result
, Register shape
, Register temp
, Register temp2
,
503 uint32_t numFixedSlots
, uint32_t numDynamicSlots
, gc::AllocKind allocKind
,
504 gc::Heap initialHeap
, Label
* fail
, const AllocSiteInput
& allocSite
,
505 bool initContents
/* = true */) {
506 MOZ_ASSERT(gc::IsObjectAllocKind(allocKind
));
507 MOZ_ASSERT(shape
!= temp
, "shape can overlap with temp2, but not temp");
510 allocateObject(result
, temp
, allocKind
, numDynamicSlots
, initialHeap
, fail
,
513 // Initialize shape field.
514 storePtr(shape
, Address(result
, JSObject::offsetOfShape()));
516 // If the object has dynamic slots, allocateObject will initialize
517 // the slots field. If not, we must initialize it now.
518 if (numDynamicSlots
== 0) {
519 storePtr(ImmPtr(emptyObjectSlots
),
520 Address(result
, NativeObject::offsetOfSlots()));
523 // Initialize elements field.
524 storePtr(ImmPtr(emptyObjectElements
),
525 Address(result
, NativeObject::offsetOfElements()));
527 // Initialize fixed slots.
529 fillSlotsWithUndefined(Address(result
, NativeObject::getFixedSlotOffset(0)),
530 temp
, 0, numFixedSlots
);
533 // Initialize dynamic slots.
534 if (numDynamicSlots
> 0) {
535 loadPtr(Address(result
, NativeObject::offsetOfSlots()), temp2
);
536 fillSlotsWithUndefined(Address(temp2
, 0), temp
, 0, numDynamicSlots
);
540 void MacroAssembler::createArrayWithFixedElements(
541 Register result
, Register shape
, Register temp
, Register dynamicSlotsTemp
,
542 uint32_t arrayLength
, uint32_t arrayCapacity
, uint32_t numUsedDynamicSlots
,
543 uint32_t numDynamicSlots
, gc::AllocKind allocKind
, gc::Heap initialHeap
,
544 Label
* fail
, const AllocSiteInput
& allocSite
) {
545 MOZ_ASSERT(gc::IsObjectAllocKind(allocKind
));
546 MOZ_ASSERT(shape
!= temp
, "shape can overlap with temp2, but not temp");
547 MOZ_ASSERT(result
!= temp
);
549 // This only supports allocating arrays with fixed elements and does not
550 // support any dynamic elements.
551 MOZ_ASSERT(arrayCapacity
>= arrayLength
);
552 MOZ_ASSERT(gc::GetGCKindSlots(allocKind
) >=
553 arrayCapacity
+ ObjectElements::VALUES_PER_HEADER
);
555 MOZ_ASSERT(numUsedDynamicSlots
<= numDynamicSlots
);
558 allocateObject(result
, temp
, allocKind
, numDynamicSlots
, initialHeap
, fail
,
561 // Initialize shape field.
562 storePtr(shape
, Address(result
, JSObject::offsetOfShape()));
564 // If the object has dynamic slots, allocateObject will initialize
565 // the slots field. If not, we must initialize it now.
566 if (numDynamicSlots
== 0) {
567 storePtr(ImmPtr(emptyObjectSlots
),
568 Address(result
, NativeObject::offsetOfSlots()));
571 // Initialize elements pointer for fixed (inline) elements.
572 computeEffectiveAddress(
573 Address(result
, NativeObject::offsetOfFixedElements()), temp
);
574 storePtr(temp
, Address(result
, NativeObject::offsetOfElements()));
576 // Initialize elements header.
577 store32(Imm32(ObjectElements::FIXED
),
578 Address(temp
, ObjectElements::offsetOfFlags()));
579 store32(Imm32(0), Address(temp
, ObjectElements::offsetOfInitializedLength()));
580 store32(Imm32(arrayCapacity
),
581 Address(temp
, ObjectElements::offsetOfCapacity()));
582 store32(Imm32(arrayLength
), Address(temp
, ObjectElements::offsetOfLength()));
584 // Initialize dynamic slots.
585 if (numUsedDynamicSlots
> 0) {
586 MOZ_ASSERT(dynamicSlotsTemp
!= temp
);
587 MOZ_ASSERT(dynamicSlotsTemp
!= InvalidReg
);
588 loadPtr(Address(result
, NativeObject::offsetOfSlots()), dynamicSlotsTemp
);
589 fillSlotsWithUndefined(Address(dynamicSlotsTemp
, 0), temp
, 0,
590 numUsedDynamicSlots
);
594 // Inline version of Nursery::allocateString.
595 void MacroAssembler::nurseryAllocateString(Register result
, Register temp
,
596 gc::AllocKind allocKind
,
598 MOZ_ASSERT(IsNurseryAllocable(allocKind
));
600 // No explicit check for nursery.isEnabled() is needed, as the comparison
601 // with the nursery's end will always fail in such cases.
603 CompileZone
* zone
= realm()->zone();
604 size_t thingSize
= gc::Arena::thingSize(allocKind
);
605 bumpPointerAllocate(result
, temp
, fail
, zone
, JS::TraceKind::String
,
609 // Inline version of Nursery::allocateBigInt.
610 void MacroAssembler::nurseryAllocateBigInt(Register result
, Register temp
,
612 MOZ_ASSERT(IsNurseryAllocable(gc::AllocKind::BIGINT
));
614 // No explicit check for nursery.isEnabled() is needed, as the comparison
615 // with the nursery's end will always fail in such cases.
617 CompileZone
* zone
= realm()->zone();
618 size_t thingSize
= gc::Arena::thingSize(gc::AllocKind::BIGINT
);
620 bumpPointerAllocate(result
, temp
, fail
, zone
, JS::TraceKind::BigInt
,
624 static bool IsNurseryAllocEnabled(CompileZone
* zone
, JS::TraceKind kind
) {
626 case JS::TraceKind::Object
:
627 return zone
->allocNurseryObjects();
628 case JS::TraceKind::String
:
629 return zone
->allocNurseryStrings();
630 case JS::TraceKind::BigInt
:
631 return zone
->allocNurseryBigInts();
633 MOZ_CRASH("Bad nursery allocation kind");
637 // This function handles nursery allocations for JS. For wasm, see
638 // MacroAssembler::wasmBumpPointerAllocate.
639 void MacroAssembler::bumpPointerAllocate(Register result
, Register temp
,
640 Label
* fail
, CompileZone
* zone
,
641 JS::TraceKind traceKind
, uint32_t size
,
642 const AllocSiteInput
& allocSite
) {
643 MOZ_ASSERT(size
>= gc::MinCellSize
);
645 uint32_t totalSize
= size
+ Nursery::nurseryCellHeaderSize();
646 MOZ_ASSERT(totalSize
< INT32_MAX
, "Nursery allocation too large");
647 MOZ_ASSERT(totalSize
% gc::CellAlignBytes
== 0);
649 // We know statically whether nursery allocation is enable for a particular
650 // kind because we discard JIT code when this changes.
651 if (!IsNurseryAllocEnabled(zone
, traceKind
)) {
656 // Use a relative 32 bit offset to the Nursery position_ to currentEnd_ to
657 // avoid 64-bit immediate loads.
658 void* posAddr
= zone
->addressOfNurseryPosition();
659 int32_t endOffset
= Nursery::offsetOfCurrentEndFromPosition();
661 movePtr(ImmPtr(posAddr
), temp
);
662 loadPtr(Address(temp
, 0), result
);
663 addPtr(Imm32(totalSize
), result
);
664 branchPtr(Assembler::Below
, Address(temp
, endOffset
), result
, fail
);
665 storePtr(result
, Address(temp
, 0));
666 subPtr(Imm32(size
), result
);
668 if (allocSite
.is
<gc::CatchAllAllocSite
>()) {
669 // No allocation site supplied. This is the case when called from Warp, or
670 // from places that don't support pretenuring.
671 gc::CatchAllAllocSite siteKind
= allocSite
.as
<gc::CatchAllAllocSite
>();
672 gc::AllocSite
* site
= zone
->catchAllAllocSite(traceKind
, siteKind
);
673 uintptr_t headerWord
= gc::NurseryCellHeader::MakeValue(site
, traceKind
);
674 storePtr(ImmWord(headerWord
),
675 Address(result
, -js::Nursery::nurseryCellHeaderSize()));
677 if (traceKind
!= JS::TraceKind::Object
||
678 runtime()->geckoProfiler().enabled()) {
679 // Update the catch all allocation site, which his is used to calculate
680 // nursery allocation counts so we can determine whether to disable
681 // nursery allocation of strings and bigints.
682 uint32_t* countAddress
= site
->nurseryAllocCountAddress();
683 CheckedInt
<int32_t> counterOffset
=
684 (CheckedInt
<uintptr_t>(uintptr_t(countAddress
)) -
685 CheckedInt
<uintptr_t>(uintptr_t(posAddr
)))
686 .toChecked
<int32_t>();
687 if (counterOffset
.isValid()) {
688 add32(Imm32(1), Address(temp
, counterOffset
.value()));
690 movePtr(ImmPtr(countAddress
), temp
);
691 add32(Imm32(1), Address(temp
, 0));
695 // Update allocation site and store pointer in the nursery cell header. This
696 // is only used from baseline.
697 Register site
= allocSite
.as
<Register
>();
698 updateAllocSite(temp
, result
, zone
, site
);
699 // See NurseryCellHeader::MakeValue.
700 orPtr(Imm32(int32_t(traceKind
)), site
);
701 storePtr(site
, Address(result
, -js::Nursery::nurseryCellHeaderSize()));
705 // Update the allocation site in the same way as Nursery::allocateCell.
706 void MacroAssembler::updateAllocSite(Register temp
, Register result
,
707 CompileZone
* zone
, Register site
) {
710 add32(Imm32(1), Address(site
, gc::AllocSite::offsetOfNurseryAllocCount()));
712 branch32(Assembler::NotEqual
,
713 Address(site
, gc::AllocSite::offsetOfNurseryAllocCount()),
714 Imm32(js::gc::NormalSiteAttentionThreshold
), &done
);
716 loadPtr(AbsoluteAddress(zone
->addressOfNurseryAllocatedSites()), temp
);
717 storePtr(temp
, Address(site
, gc::AllocSite::offsetOfNextNurseryAllocated()));
718 storePtr(site
, AbsoluteAddress(zone
->addressOfNurseryAllocatedSites()));
723 // Inlined equivalent of gc::AllocateString, jumping to fail if nursery
724 // allocation requested but unsuccessful.
725 void MacroAssembler::allocateString(Register result
, Register temp
,
726 gc::AllocKind allocKind
,
727 gc::Heap initialHeap
, Label
* fail
) {
728 MOZ_ASSERT(allocKind
== gc::AllocKind::STRING
||
729 allocKind
== gc::AllocKind::FAT_INLINE_STRING
);
731 checkAllocatorState(temp
, allocKind
, fail
);
733 if (shouldNurseryAllocate(allocKind
, initialHeap
)) {
734 MOZ_ASSERT(initialHeap
== gc::Heap::Default
);
735 return nurseryAllocateString(result
, temp
, allocKind
, fail
);
738 freeListAllocate(result
, temp
, allocKind
, fail
);
741 void MacroAssembler::newGCString(Register result
, Register temp
,
742 gc::Heap initialHeap
, Label
* fail
) {
743 allocateString(result
, temp
, js::gc::AllocKind::STRING
, initialHeap
, fail
);
746 void MacroAssembler::newGCFatInlineString(Register result
, Register temp
,
747 gc::Heap initialHeap
, Label
* fail
) {
748 allocateString(result
, temp
, js::gc::AllocKind::FAT_INLINE_STRING
,
752 void MacroAssembler::newGCBigInt(Register result
, Register temp
,
753 gc::Heap initialHeap
, Label
* fail
) {
754 constexpr gc::AllocKind allocKind
= gc::AllocKind::BIGINT
;
756 checkAllocatorState(temp
, allocKind
, fail
);
758 if (shouldNurseryAllocate(allocKind
, initialHeap
)) {
759 MOZ_ASSERT(initialHeap
== gc::Heap::Default
);
760 return nurseryAllocateBigInt(result
, temp
, fail
);
763 freeListAllocate(result
, temp
, allocKind
, fail
);
766 void MacroAssembler::copySlotsFromTemplate(
767 Register obj
, const TemplateNativeObject
& templateObj
, uint32_t start
,
769 uint32_t nfixed
= std::min(templateObj
.numFixedSlots(), end
);
770 for (unsigned i
= start
; i
< nfixed
; i
++) {
771 // Template objects are not exposed to script and therefore immutable.
772 // However, regexp template objects are sometimes used directly (when
773 // the cloning is not observable), and therefore we can end up with a
774 // non-zero lastIndex. Detect this case here and just substitute 0, to
775 // avoid racing with the main thread updating this slot.
777 if (templateObj
.isRegExpObject() && i
== RegExpObject::lastIndexSlot()) {
780 v
= templateObj
.getSlot(i
);
782 storeValue(v
, Address(obj
, NativeObject::getFixedSlotOffset(i
)));
786 void MacroAssembler::fillSlotsWithConstantValue(Address base
, Register temp
,
787 uint32_t start
, uint32_t end
,
789 MOZ_ASSERT(v
.isUndefined() || IsUninitializedLexical(v
));
796 // We only have a single spare register, so do the initialization as two
797 // strided writes of the tag and body.
799 move32(Imm32(v
.toNunboxPayload()), temp
);
800 for (unsigned i
= start
; i
< end
; ++i
, addr
.offset
+= sizeof(GCPtr
<Value
>)) {
801 store32(temp
, ToPayload(addr
));
805 move32(Imm32(v
.toNunboxTag()), temp
);
806 for (unsigned i
= start
; i
< end
; ++i
, addr
.offset
+= sizeof(GCPtr
<Value
>)) {
807 store32(temp
, ToType(addr
));
810 moveValue(v
, ValueOperand(temp
));
811 for (uint32_t i
= start
; i
< end
; ++i
, base
.offset
+= sizeof(GCPtr
<Value
>)) {
812 storePtr(temp
, base
);
817 void MacroAssembler::fillSlotsWithUndefined(Address base
, Register temp
,
818 uint32_t start
, uint32_t end
) {
819 fillSlotsWithConstantValue(base
, temp
, start
, end
, UndefinedValue());
822 void MacroAssembler::fillSlotsWithUninitialized(Address base
, Register temp
,
823 uint32_t start
, uint32_t end
) {
824 fillSlotsWithConstantValue(base
, temp
, start
, end
,
825 MagicValue(JS_UNINITIALIZED_LEXICAL
));
828 static std::pair
<uint32_t, uint32_t> FindStartOfUninitializedAndUndefinedSlots(
829 const TemplateNativeObject
& templateObj
, uint32_t nslots
) {
830 MOZ_ASSERT(nslots
== templateObj
.slotSpan());
831 MOZ_ASSERT(nslots
> 0);
833 uint32_t first
= nslots
;
834 for (; first
!= 0; --first
) {
835 if (templateObj
.getSlot(first
- 1) != UndefinedValue()) {
839 uint32_t startOfUndefined
= first
;
841 if (first
!= 0 && IsUninitializedLexical(templateObj
.getSlot(first
- 1))) {
842 for (; first
!= 0; --first
) {
843 if (!IsUninitializedLexical(templateObj
.getSlot(first
- 1))) {
848 uint32_t startOfUninitialized
= first
;
850 return {startOfUninitialized
, startOfUndefined
};
853 void MacroAssembler::initTypedArraySlots(
854 Register obj
, Register temp
, Register lengthReg
, LiveRegisterSet liveRegs
,
855 Label
* fail
, FixedLengthTypedArrayObject
* templateObj
,
856 TypedArrayLength lengthKind
) {
857 MOZ_ASSERT(!templateObj
->hasBuffer());
859 constexpr size_t dataSlotOffset
= ArrayBufferViewObject::dataOffset();
860 constexpr size_t dataOffset
= dataSlotOffset
+ sizeof(HeapSlot
);
863 FixedLengthTypedArrayObject::FIXED_DATA_START
==
864 FixedLengthTypedArrayObject::DATA_SLOT
+ 1,
865 "fixed inline element data assumed to begin after the data slot");
868 FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT
==
869 JSObject::MAX_BYTE_SIZE
- dataOffset
,
870 "typed array inline buffer is limited by the maximum object byte size");
872 // Initialise data elements to zero.
873 size_t length
= templateObj
->length();
874 MOZ_ASSERT(length
<= INT32_MAX
,
875 "Template objects are only created for int32 lengths");
876 size_t nbytes
= length
* templateObj
->bytesPerElement();
878 if (lengthKind
== TypedArrayLength::Fixed
&&
879 nbytes
<= FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT
) {
880 MOZ_ASSERT(dataOffset
+ nbytes
<= templateObj
->tenuredSizeOfThis());
882 // Store data elements inside the remaining JSObject slots.
883 computeEffectiveAddress(Address(obj
, dataOffset
), temp
);
884 storePrivateValue(temp
, Address(obj
, dataSlotOffset
));
886 // Write enough zero pointers into fixed data to zero every
887 // element. (This zeroes past the end of a byte count that's
888 // not a multiple of pointer size. That's okay, because fixed
889 // data is a count of 8-byte HeapSlots (i.e. <= pointer size),
890 // and we won't inline unless the desired memory fits in that
892 static_assert(sizeof(HeapSlot
) == 8, "Assumed 8 bytes alignment");
894 size_t numZeroPointers
= ((nbytes
+ 7) & ~0x7) / sizeof(char*);
895 for (size_t i
= 0; i
< numZeroPointers
; i
++) {
896 storePtr(ImmWord(0), Address(obj
, dataOffset
+ i
* sizeof(char*)));
898 MOZ_ASSERT(nbytes
> 0, "Zero-length TypedArrays need ZeroLengthArrayData");
900 if (lengthKind
== TypedArrayLength::Fixed
) {
901 move32(Imm32(length
), lengthReg
);
904 // Ensure volatile |obj| is saved across the call.
905 if (obj
.volatile_()) {
906 liveRegs
.addUnchecked(obj
);
909 // Allocate a buffer on the heap to store the data elements.
910 PushRegsInMask(liveRegs
);
911 using Fn
= void (*)(JSContext
* cx
, TypedArrayObject
* obj
, int32_t count
);
912 setupUnalignedABICall(temp
);
916 passABIArg(lengthReg
);
917 callWithABI
<Fn
, AllocateAndInitTypedArrayBuffer
>();
918 PopRegsInMask(liveRegs
);
920 // Fail when data slot is UndefinedValue.
921 branchTestUndefined(Assembler::Equal
, Address(obj
, dataSlotOffset
), fail
);
925 void MacroAssembler::initGCSlots(Register obj
, Register temp
,
926 const TemplateNativeObject
& templateObj
) {
927 MOZ_ASSERT(!templateObj
.isArrayObject());
929 // Slots of non-array objects are required to be initialized.
930 // Use the values currently in the template object.
931 uint32_t nslots
= templateObj
.slotSpan();
936 uint32_t nfixed
= templateObj
.numUsedFixedSlots();
937 uint32_t ndynamic
= templateObj
.numDynamicSlots();
939 // Attempt to group slot writes such that we minimize the amount of
940 // duplicated data we need to embed in code and load into registers. In
941 // general, most template object slots will be undefined except for any
942 // reserved slots. Since reserved slots come first, we split the object
943 // logically into independent non-UndefinedValue writes to the head and
944 // duplicated writes of UndefinedValue to the tail. For the majority of
945 // objects, the "tail" will be the entire slot range.
947 // The template object may be a CallObject, in which case we need to
948 // account for uninitialized lexical slots as well as undefined
949 // slots. Uninitialized lexical slots appears in CallObjects if the function
950 // has parameter expressions, in which case closed over parameters have
951 // TDZ. Uninitialized slots come before undefined slots in CallObjects.
952 auto [startOfUninitialized
, startOfUndefined
] =
953 FindStartOfUninitializedAndUndefinedSlots(templateObj
, nslots
);
954 MOZ_ASSERT(startOfUninitialized
<= nfixed
); // Reserved slots must be fixed.
955 MOZ_ASSERT(startOfUndefined
>= startOfUninitialized
);
956 MOZ_ASSERT_IF(!templateObj
.isCallObject() &&
957 !templateObj
.isBlockLexicalEnvironmentObject(),
958 startOfUninitialized
== startOfUndefined
);
960 // Copy over any preserved reserved slots.
961 copySlotsFromTemplate(obj
, templateObj
, 0, startOfUninitialized
);
963 // Fill the rest of the fixed slots with undefined and uninitialized.
964 size_t offset
= NativeObject::getFixedSlotOffset(startOfUninitialized
);
965 fillSlotsWithUninitialized(Address(obj
, offset
), temp
, startOfUninitialized
,
966 std::min(startOfUndefined
, nfixed
));
968 if (startOfUndefined
< nfixed
) {
969 offset
= NativeObject::getFixedSlotOffset(startOfUndefined
);
970 fillSlotsWithUndefined(Address(obj
, offset
), temp
, startOfUndefined
,
975 // We are short one register to do this elegantly. Borrow the obj
976 // register briefly for our slots base address.
978 loadPtr(Address(obj
, NativeObject::offsetOfSlots()), obj
);
980 // Fill uninitialized slots if necessary. Otherwise initialize all
981 // slots to undefined.
982 if (startOfUndefined
> nfixed
) {
983 MOZ_ASSERT(startOfUninitialized
!= startOfUndefined
);
984 fillSlotsWithUninitialized(Address(obj
, 0), temp
, 0,
985 startOfUndefined
- nfixed
);
986 size_t offset
= (startOfUndefined
- nfixed
) * sizeof(Value
);
987 fillSlotsWithUndefined(Address(obj
, offset
), temp
,
988 startOfUndefined
- nfixed
, ndynamic
);
990 fillSlotsWithUndefined(Address(obj
, 0), temp
, 0, ndynamic
);
997 void MacroAssembler::initGCThing(Register obj
, Register temp
,
998 const TemplateObject
& templateObj
,
1000 // Fast initialization of an empty object returned by allocateObject().
1002 storePtr(ImmGCPtr(templateObj
.shape()),
1003 Address(obj
, JSObject::offsetOfShape()));
1005 if (templateObj
.isNativeObject()) {
1006 const TemplateNativeObject
& ntemplate
=
1007 templateObj
.asTemplateNativeObject();
1008 MOZ_ASSERT(!ntemplate
.hasDynamicElements());
1010 // If the object has dynamic slots, the slots member has already been
1012 if (ntemplate
.numDynamicSlots() == 0) {
1013 storePtr(ImmPtr(emptyObjectSlots
),
1014 Address(obj
, NativeObject::offsetOfSlots()));
1017 if (ntemplate
.isArrayObject()) {
1018 // Can't skip initializing reserved slots.
1019 MOZ_ASSERT(initContents
);
1021 int elementsOffset
= NativeObject::offsetOfFixedElements();
1023 computeEffectiveAddress(Address(obj
, elementsOffset
), temp
);
1024 storePtr(temp
, Address(obj
, NativeObject::offsetOfElements()));
1026 // Fill in the elements header.
1028 Imm32(ntemplate
.getDenseCapacity()),
1029 Address(obj
, elementsOffset
+ ObjectElements::offsetOfCapacity()));
1030 store32(Imm32(ntemplate
.getDenseInitializedLength()),
1031 Address(obj
, elementsOffset
+
1032 ObjectElements::offsetOfInitializedLength()));
1033 store32(Imm32(ntemplate
.getArrayLength()),
1034 Address(obj
, elementsOffset
+ ObjectElements::offsetOfLength()));
1035 store32(Imm32(ObjectElements::FIXED
),
1036 Address(obj
, elementsOffset
+ ObjectElements::offsetOfFlags()));
1037 } else if (ntemplate
.isArgumentsObject()) {
1038 // The caller will initialize the reserved slots.
1039 MOZ_ASSERT(!initContents
);
1040 storePtr(ImmPtr(emptyObjectElements
),
1041 Address(obj
, NativeObject::offsetOfElements()));
1043 // If the target type could be a TypedArray that maps shared memory
1044 // then this would need to store emptyObjectElementsShared in that case.
1045 MOZ_ASSERT(!ntemplate
.isSharedMemory());
1047 // Can't skip initializing reserved slots.
1048 MOZ_ASSERT(initContents
);
1050 storePtr(ImmPtr(emptyObjectElements
),
1051 Address(obj
, NativeObject::offsetOfElements()));
1053 initGCSlots(obj
, temp
, ntemplate
);
1056 MOZ_CRASH("Unknown object");
1060 AllocatableRegisterSet
regs(RegisterSet::Volatile());
1061 LiveRegisterSet
save(regs
.asLiveSet());
1062 PushRegsInMask(save
);
1064 regs
.takeUnchecked(obj
);
1065 Register temp2
= regs
.takeAnyGeneral();
1067 using Fn
= void (*)(JSObject
* obj
);
1068 setupUnalignedABICall(temp2
);
1070 callWithABI
<Fn
, TraceCreateObject
>();
1072 PopRegsInMask(save
);
1076 static size_t StringCharsByteLength(const JSLinearString
* linear
) {
1077 CharEncoding encoding
=
1078 linear
->hasLatin1Chars() ? CharEncoding::Latin1
: CharEncoding::TwoByte
;
1079 size_t encodingSize
= encoding
== CharEncoding::Latin1
1080 ? sizeof(JS::Latin1Char
)
1082 return linear
->length() * encodingSize
;
1085 bool MacroAssembler::canCompareStringCharsInline(const JSLinearString
* linear
) {
1086 // Limit the number of inline instructions used for character comparisons. Use
1087 // the same instruction limit for both encodings, i.e. two-byte uses half the
1088 // limit of Latin-1 strings.
1089 constexpr size_t ByteLengthCompareCutoff
= 32;
1091 size_t byteLength
= StringCharsByteLength(linear
);
1092 return 0 < byteLength
&& byteLength
<= ByteLengthCompareCutoff
;
1095 template <typename T
, typename CharT
>
1096 static inline T
CopyCharacters(const CharT
* chars
) {
1098 std::memcpy(&value
, chars
, sizeof(T
));
1102 template <typename T
>
1103 static inline T
CopyCharacters(const JSLinearString
* linear
, size_t index
) {
1104 JS::AutoCheckCannotGC nogc
;
1106 if (linear
->hasLatin1Chars()) {
1107 MOZ_ASSERT(index
+ sizeof(T
) / sizeof(JS::Latin1Char
) <= linear
->length());
1108 return CopyCharacters
<T
>(linear
->latin1Chars(nogc
) + index
);
1111 MOZ_ASSERT(sizeof(T
) >= sizeof(char16_t
));
1112 MOZ_ASSERT(index
+ sizeof(T
) / sizeof(char16_t
) <= linear
->length());
1113 return CopyCharacters
<T
>(linear
->twoByteChars(nogc
) + index
);
1116 void MacroAssembler::branchIfNotStringCharsEquals(Register stringChars
,
1117 const JSLinearString
* linear
,
1119 CharEncoding encoding
=
1120 linear
->hasLatin1Chars() ? CharEncoding::Latin1
: CharEncoding::TwoByte
;
1121 size_t encodingSize
= encoding
== CharEncoding::Latin1
1122 ? sizeof(JS::Latin1Char
)
1124 size_t byteLength
= StringCharsByteLength(linear
);
1127 for (size_t stride
: {8, 4, 2, 1}) {
1128 while (byteLength
>= stride
) {
1129 Address
addr(stringChars
, pos
* encodingSize
);
1132 auto x
= CopyCharacters
<uint64_t>(linear
, pos
);
1133 branch64(Assembler::NotEqual
, addr
, Imm64(x
), label
);
1137 auto x
= CopyCharacters
<uint32_t>(linear
, pos
);
1138 branch32(Assembler::NotEqual
, addr
, Imm32(x
), label
);
1142 auto x
= CopyCharacters
<uint16_t>(linear
, pos
);
1143 branch16(Assembler::NotEqual
, addr
, Imm32(x
), label
);
1147 auto x
= CopyCharacters
<uint8_t>(linear
, pos
);
1148 branch8(Assembler::NotEqual
, addr
, Imm32(x
), label
);
1153 byteLength
-= stride
;
1154 pos
+= stride
/ encodingSize
;
1157 // Prefer a single comparison for trailing bytes instead of doing
1158 // multiple consecutive comparisons.
1160 // For example when comparing against the string "example", emit two
1161 // four-byte comparisons against "exam" and "mple" instead of doing
1162 // three comparisons against "exam", "pl", and finally "e".
1163 if (pos
> 0 && byteLength
> stride
/ 2) {
1164 MOZ_ASSERT(stride
== 8 || stride
== 4);
1166 size_t prev
= pos
- (stride
- byteLength
) / encodingSize
;
1167 Address
addr(stringChars
, prev
* encodingSize
);
1170 auto x
= CopyCharacters
<uint64_t>(linear
, prev
);
1171 branch64(Assembler::NotEqual
, addr
, Imm64(x
), label
);
1175 auto x
= CopyCharacters
<uint32_t>(linear
, prev
);
1176 branch32(Assembler::NotEqual
, addr
, Imm32(x
), label
);
1181 // Break from the loop, because we've finished the complete string.
1187 void MacroAssembler::loadStringCharsForCompare(Register input
,
1188 const JSLinearString
* linear
,
1189 Register stringChars
,
1191 CharEncoding encoding
=
1192 linear
->hasLatin1Chars() ? CharEncoding::Latin1
: CharEncoding::TwoByte
;
1194 // Take the slow path when the string is a rope or has a different character
1196 branchIfRope(input
, fail
);
1197 if (encoding
== CharEncoding::Latin1
) {
1198 branchTwoByteString(input
, fail
);
1200 JS::AutoCheckCannotGC nogc
;
1201 if (mozilla::IsUtf16Latin1(linear
->twoByteRange(nogc
))) {
1202 branchLatin1String(input
, fail
);
1204 // This case was already handled in the caller.
1207 branchTwoByteString(input
, &ok
);
1208 assumeUnreachable("Unexpected Latin-1 string");
1216 size_t length
= linear
->length();
1217 MOZ_ASSERT(length
> 0);
1220 branch32(Assembler::AboveOrEqual
,
1221 Address(input
, JSString::offsetOfLength()), Imm32(length
), &ok
);
1222 assumeUnreachable("Input mustn't be smaller than search string");
1227 // Load the input string's characters.
1228 loadStringChars(input
, stringChars
, encoding
);
1231 void MacroAssembler::compareStringChars(JSOp op
, Register stringChars
,
1232 const JSLinearString
* linear
,
1234 MOZ_ASSERT(IsEqualityOp(op
));
1236 size_t byteLength
= StringCharsByteLength(linear
);
1238 // Prefer a single compare-and-set instruction if possible.
1239 if (byteLength
== 1 || byteLength
== 2 || byteLength
== 4 ||
1241 auto cond
= JSOpToCondition(op
, /* isSigned = */ false);
1243 Address
addr(stringChars
, 0);
1244 switch (byteLength
) {
1246 auto x
= CopyCharacters
<uint64_t>(linear
, 0);
1247 cmp64Set(cond
, addr
, Imm64(x
), output
);
1251 auto x
= CopyCharacters
<uint32_t>(linear
, 0);
1252 cmp32Set(cond
, addr
, Imm32(x
), output
);
1256 auto x
= CopyCharacters
<uint16_t>(linear
, 0);
1257 cmp16Set(cond
, addr
, Imm32(x
), output
);
1261 auto x
= CopyCharacters
<uint8_t>(linear
, 0);
1262 cmp8Set(cond
, addr
, Imm32(x
), output
);
1267 Label setNotEqualResult
;
1268 branchIfNotStringCharsEquals(stringChars
, linear
, &setNotEqualResult
);
1270 // Falls through if both strings are equal.
1273 move32(Imm32(op
== JSOp::Eq
|| op
== JSOp::StrictEq
), output
);
1276 bind(&setNotEqualResult
);
1277 move32(Imm32(op
== JSOp::Ne
|| op
== JSOp::StrictNe
), output
);
1283 void MacroAssembler::compareStrings(JSOp op
, Register left
, Register right
,
1284 Register result
, Label
* fail
) {
1285 MOZ_ASSERT(left
!= result
);
1286 MOZ_ASSERT(right
!= result
);
1287 MOZ_ASSERT(IsEqualityOp(op
) || IsRelationalOp(op
));
1289 Label notPointerEqual
;
1290 // If operands point to the same instance, the strings are trivially equal.
1291 branchPtr(Assembler::NotEqual
, left
, right
,
1292 IsEqualityOp(op
) ? ¬PointerEqual
: fail
);
1293 move32(Imm32(op
== JSOp::Eq
|| op
== JSOp::StrictEq
|| op
== JSOp::Le
||
1297 if (IsEqualityOp(op
)) {
1301 bind(¬PointerEqual
);
1303 Label leftIsNotAtom
;
1304 Label setNotEqualResult
;
1305 // Atoms cannot be equal to each other if they point to different strings.
1306 Imm32
atomBit(JSString::ATOM_BIT
);
1307 branchTest32(Assembler::Zero
, Address(left
, JSString::offsetOfFlags()),
1308 atomBit
, &leftIsNotAtom
);
1309 branchTest32(Assembler::NonZero
, Address(right
, JSString::offsetOfFlags()),
1310 atomBit
, &setNotEqualResult
);
1312 bind(&leftIsNotAtom
);
1313 // Strings of different length can never be equal.
1314 loadStringLength(left
, result
);
1315 branch32(Assembler::Equal
, Address(right
, JSString::offsetOfLength()),
1318 bind(&setNotEqualResult
);
1319 move32(Imm32(op
== JSOp::Ne
|| op
== JSOp::StrictNe
), result
);
1325 void MacroAssembler::loadStringChars(Register str
, Register dest
,
1326 CharEncoding encoding
) {
1327 MOZ_ASSERT(str
!= dest
);
1329 if (JitOptions
.spectreStringMitigations
) {
1330 if (encoding
== CharEncoding::Latin1
) {
1331 // If the string is a rope, zero the |str| register. The code below
1332 // depends on str->flags so this should block speculative execution.
1333 movePtr(ImmWord(0), dest
);
1334 test32MovePtr(Assembler::Zero
, Address(str
, JSString::offsetOfFlags()),
1335 Imm32(JSString::LINEAR_BIT
), dest
, str
);
1337 // If we're loading TwoByte chars, there's an additional risk:
1338 // if the string has Latin1 chars, we could read out-of-bounds. To
1339 // prevent this, we check both the Linear and Latin1 bits. We don't
1340 // have a scratch register, so we use these flags also to block
1341 // speculative execution, similar to the use of 0 above.
1342 MOZ_ASSERT(encoding
== CharEncoding::TwoByte
);
1343 static constexpr uint32_t Mask
=
1344 JSString::LINEAR_BIT
| JSString::LATIN1_CHARS_BIT
;
1345 static_assert(Mask
< 2048,
1346 "Mask should be a small, near-null value to ensure we "
1347 "block speculative execution when it's used as string "
1349 move32(Imm32(Mask
), dest
);
1350 and32(Address(str
, JSString::offsetOfFlags()), dest
);
1351 cmp32MovePtr(Assembler::NotEqual
, dest
, Imm32(JSString::LINEAR_BIT
), dest
,
1356 // Load the inline chars.
1357 computeEffectiveAddress(Address(str
, JSInlineString::offsetOfInlineStorage()),
1360 // If it's not an inline string, load the non-inline chars. Use a
1361 // conditional move to prevent speculative execution.
1362 test32LoadPtr(Assembler::Zero
, Address(str
, JSString::offsetOfFlags()),
1363 Imm32(JSString::INLINE_CHARS_BIT
),
1364 Address(str
, JSString::offsetOfNonInlineChars()), dest
);
1367 void MacroAssembler::loadNonInlineStringChars(Register str
, Register dest
,
1368 CharEncoding encoding
) {
1369 MOZ_ASSERT(str
!= dest
);
1371 if (JitOptions
.spectreStringMitigations
) {
1372 // If the string is a rope, has inline chars, or has a different
1373 // character encoding, set str to a near-null value to prevent
1374 // speculative execution below (when reading str->nonInlineChars).
1376 static constexpr uint32_t Mask
= JSString::LINEAR_BIT
|
1377 JSString::INLINE_CHARS_BIT
|
1378 JSString::LATIN1_CHARS_BIT
;
1379 static_assert(Mask
< 2048,
1380 "Mask should be a small, near-null value to ensure we "
1381 "block speculative execution when it's used as string "
1384 uint32_t expectedBits
= JSString::LINEAR_BIT
;
1385 if (encoding
== CharEncoding::Latin1
) {
1386 expectedBits
|= JSString::LATIN1_CHARS_BIT
;
1389 move32(Imm32(Mask
), dest
);
1390 and32(Address(str
, JSString::offsetOfFlags()), dest
);
1392 cmp32MovePtr(Assembler::NotEqual
, dest
, Imm32(expectedBits
), dest
, str
);
1395 loadPtr(Address(str
, JSString::offsetOfNonInlineChars()), dest
);
1398 void MacroAssembler::storeNonInlineStringChars(Register chars
, Register str
) {
1399 MOZ_ASSERT(chars
!= str
);
1400 storePtr(chars
, Address(str
, JSString::offsetOfNonInlineChars()));
1403 void MacroAssembler::loadInlineStringCharsForStore(Register str
,
1405 computeEffectiveAddress(Address(str
, JSInlineString::offsetOfInlineStorage()),
1409 void MacroAssembler::loadInlineStringChars(Register str
, Register dest
,
1410 CharEncoding encoding
) {
1411 MOZ_ASSERT(str
!= dest
);
1413 if (JitOptions
.spectreStringMitigations
) {
1414 // Making this Spectre-safe is a bit complicated: using
1415 // computeEffectiveAddress and then zeroing the output register if
1416 // non-inline is not sufficient: when the index is very large, it would
1417 // allow reading |nullptr + index|. Just fall back to loadStringChars
1419 loadStringChars(str
, dest
, encoding
);
1421 computeEffectiveAddress(
1422 Address(str
, JSInlineString::offsetOfInlineStorage()), dest
);
1426 void MacroAssembler::loadRopeLeftChild(Register str
, Register dest
) {
1427 MOZ_ASSERT(str
!= dest
);
1429 if (JitOptions
.spectreStringMitigations
) {
1430 // Zero the output register if the input was not a rope.
1431 movePtr(ImmWord(0), dest
);
1432 test32LoadPtr(Assembler::Zero
, Address(str
, JSString::offsetOfFlags()),
1433 Imm32(JSString::LINEAR_BIT
),
1434 Address(str
, JSRope::offsetOfLeft()), dest
);
1436 loadPtr(Address(str
, JSRope::offsetOfLeft()), dest
);
1440 void MacroAssembler::loadRopeRightChild(Register str
, Register dest
) {
1441 MOZ_ASSERT(str
!= dest
);
1443 if (JitOptions
.spectreStringMitigations
) {
1444 // Zero the output register if the input was not a rope.
1445 movePtr(ImmWord(0), dest
);
1446 test32LoadPtr(Assembler::Zero
, Address(str
, JSString::offsetOfFlags()),
1447 Imm32(JSString::LINEAR_BIT
),
1448 Address(str
, JSRope::offsetOfRight()), dest
);
1450 loadPtr(Address(str
, JSRope::offsetOfRight()), dest
);
1454 void MacroAssembler::storeRopeChildren(Register left
, Register right
,
1456 storePtr(left
, Address(str
, JSRope::offsetOfLeft()));
1457 storePtr(right
, Address(str
, JSRope::offsetOfRight()));
1460 void MacroAssembler::loadDependentStringBase(Register str
, Register dest
) {
1461 MOZ_ASSERT(str
!= dest
);
1463 if (JitOptions
.spectreStringMitigations
) {
1464 // If the string is not a dependent string, zero the |str| register.
1465 // The code below loads str->base so this should block speculative
1467 movePtr(ImmWord(0), dest
);
1468 test32MovePtr(Assembler::Zero
, Address(str
, JSString::offsetOfFlags()),
1469 Imm32(JSString::DEPENDENT_BIT
), dest
, str
);
1472 loadPtr(Address(str
, JSDependentString::offsetOfBase()), dest
);
1475 void MacroAssembler::storeDependentStringBase(Register base
, Register str
) {
1476 storePtr(base
, Address(str
, JSDependentString::offsetOfBase()));
1479 void MacroAssembler::branchIfMaybeSplitSurrogatePair(Register leftChild
,
1484 // If |index| is the last character of the left child and the left child
1485 // is a two-byte string, it's possible that a surrogate pair is split
1486 // between the left and right child of a rope.
1488 // Can't be a split surrogate when the left child is a Latin-1 string.
1489 branchLatin1String(leftChild
, notSplit
);
1491 // Can't be a split surrogate when |index + 1| is in the left child.
1492 add32(Imm32(1), index
, scratch
);
1493 branch32(Assembler::Above
, Address(leftChild
, JSString::offsetOfLength()),
1496 // Jump to |maybeSplit| if the left child is another rope.
1497 branchIfRope(leftChild
, maybeSplit
);
1499 // Load the character at |index|.
1500 loadStringChars(leftChild
, scratch
, CharEncoding::TwoByte
);
1501 loadChar(scratch
, index
, scratch
, CharEncoding::TwoByte
);
1503 // Jump to |maybeSplit| if the last character is a lead surrogate.
1504 branchIfLeadSurrogate(scratch
, scratch
, maybeSplit
);
1507 void MacroAssembler::loadRopeChild(CharKind kind
, Register str
, Register index
,
1508 Register output
, Register maybeScratch
,
1509 Label
* isLinear
, Label
* splitSurrogate
) {
1510 // This follows JSString::getChar.
1511 branchIfNotRope(str
, isLinear
);
1513 loadRopeLeftChild(str
, output
);
1516 if (kind
== CharKind::CharCode
) {
1517 // Check if |index| is contained in the left child.
1518 branch32(Assembler::Above
, Address(output
, JSString::offsetOfLength()),
1519 index
, &loadedChild
);
1521 MOZ_ASSERT(maybeScratch
!= InvalidReg
);
1523 // Check if |index| is contained in the left child.
1525 branch32(Assembler::BelowOrEqual
,
1526 Address(output
, JSString::offsetOfLength()), index
, &loadRight
);
1528 // Handle possible split surrogate pairs.
1529 branchIfMaybeSplitSurrogatePair(output
, index
, maybeScratch
,
1530 splitSurrogate
, &loadedChild
);
1536 // The index must be in the rightChild.
1537 loadRopeRightChild(str
, output
);
1542 void MacroAssembler::branchIfCanLoadStringChar(CharKind kind
, Register str
,
1543 Register index
, Register scratch
,
1544 Register maybeScratch
,
1546 Label splitSurrogate
;
1547 loadRopeChild(kind
, str
, index
, scratch
, maybeScratch
, label
,
1550 // Branch if the left resp. right side is linear.
1551 branchIfNotRope(scratch
, label
);
1553 if (kind
== CharKind::CodePoint
) {
1554 bind(&splitSurrogate
);
1558 void MacroAssembler::branchIfNotCanLoadStringChar(CharKind kind
, Register str
,
1561 Register maybeScratch
,
1564 loadRopeChild(kind
, str
, index
, scratch
, maybeScratch
, &done
, label
);
1566 // Branch if the left or right side is another rope.
1567 branchIfRope(scratch
, label
);
1572 void MacroAssembler::loadStringChar(CharKind kind
, Register str
, Register index
,
1573 Register output
, Register scratch1
,
1574 Register scratch2
, Label
* fail
) {
1575 MOZ_ASSERT(str
!= output
);
1576 MOZ_ASSERT(str
!= index
);
1577 MOZ_ASSERT(index
!= output
);
1578 MOZ_ASSERT_IF(kind
== CharKind::CodePoint
, index
!= scratch1
);
1579 MOZ_ASSERT(output
!= scratch1
);
1580 MOZ_ASSERT(output
!= scratch2
);
1582 // Use scratch1 for the index (adjusted below).
1583 if (index
!= scratch1
) {
1584 move32(index
, scratch1
);
1586 movePtr(str
, output
);
1588 // This follows JSString::getChar.
1590 branchIfNotRope(str
, ¬Rope
);
1592 loadRopeLeftChild(str
, output
);
1594 // Check if the index is contained in the leftChild.
1595 Label loadedChild
, notInLeft
;
1596 spectreBoundsCheck32(scratch1
, Address(output
, JSString::offsetOfLength()),
1597 scratch2
, ¬InLeft
);
1598 if (kind
== CharKind::CodePoint
) {
1599 branchIfMaybeSplitSurrogatePair(output
, scratch1
, scratch2
, fail
,
1604 // The index must be in the rightChild.
1605 // index -= rope->leftChild()->length()
1607 sub32(Address(output
, JSString::offsetOfLength()), scratch1
);
1608 loadRopeRightChild(str
, output
);
1610 // If the left or right side is another rope, give up.
1612 branchIfRope(output
, fail
);
1616 Label isLatin1
, done
;
1617 branchLatin1String(output
, &isLatin1
);
1619 loadStringChars(output
, scratch2
, CharEncoding::TwoByte
);
1621 if (kind
== CharKind::CharCode
) {
1622 loadChar(scratch2
, scratch1
, output
, CharEncoding::TwoByte
);
1624 // Load the first character.
1625 addToCharPtr(scratch2
, scratch1
, CharEncoding::TwoByte
);
1626 loadChar(Address(scratch2
, 0), output
, CharEncoding::TwoByte
);
1628 // If the first character isn't a lead surrogate, go to |done|.
1629 branchIfNotLeadSurrogate(output
, &done
);
1631 // branchIfMaybeSplitSurrogatePair ensures that the surrogate pair can't
1632 // split between two rope children. So if |index + 1 < str.length|, then
1633 // |index| and |index + 1| are in the same rope child.
1635 // NB: We use the non-adjusted |index| and |str| inputs, because |output|
1636 // was overwritten and no longer contains the rope child.
1638 // If |index + 1| is a valid index into |str|.
1639 add32(Imm32(1), index
, scratch1
);
1640 spectreBoundsCheck32(scratch1
, Address(str
, JSString::offsetOfLength()),
1643 // Then load the next character at |scratch2 + sizeof(char16_t)|.
1644 loadChar(Address(scratch2
, sizeof(char16_t
)), scratch1
,
1645 CharEncoding::TwoByte
);
1647 // If the next character isn't a trail surrogate, go to |done|.
1648 branchIfNotTrailSurrogate(scratch1
, scratch2
, &done
);
1650 // Inlined unicode::UTF16Decode(char16_t, char16_t).
1651 lshift32(Imm32(10), output
);
1652 add32(Imm32(unicode::NonBMPMin
- (unicode::LeadSurrogateMin
<< 10) -
1653 unicode::TrailSurrogateMin
),
1655 add32(scratch1
, output
);
1662 loadStringChars(output
, scratch2
, CharEncoding::Latin1
);
1663 loadChar(scratch2
, scratch1
, output
, CharEncoding::Latin1
);
1669 void MacroAssembler::loadStringChar(Register str
, int32_t index
,
1670 Register output
, Register scratch1
,
1671 Register scratch2
, Label
* fail
) {
1672 MOZ_ASSERT(str
!= output
);
1673 MOZ_ASSERT(output
!= scratch1
);
1674 MOZ_ASSERT(output
!= scratch2
);
1677 movePtr(str
, scratch1
);
1679 // This follows JSString::getChar.
1681 branchIfNotRope(str
, ¬Rope
);
1683 loadRopeLeftChild(str
, scratch1
);
1685 // Rope children can't be empty, so the index can't be in the right side.
1687 // If the left side is another rope, give up.
1688 branchIfRope(scratch1
, fail
);
1692 Label isLatin1
, done
;
1693 branchLatin1String(scratch1
, &isLatin1
);
1694 loadStringChars(scratch1
, scratch2
, CharEncoding::TwoByte
);
1695 loadChar(Address(scratch2
, 0), output
, CharEncoding::TwoByte
);
1699 loadStringChars(scratch1
, scratch2
, CharEncoding::Latin1
);
1700 loadChar(Address(scratch2
, 0), output
, CharEncoding::Latin1
);
1704 move32(Imm32(index
), scratch1
);
1705 loadStringChar(str
, scratch1
, output
, scratch1
, scratch2
, fail
);
1709 void MacroAssembler::loadStringIndexValue(Register str
, Register dest
,
1711 MOZ_ASSERT(str
!= dest
);
1713 load32(Address(str
, JSString::offsetOfFlags()), dest
);
1715 // Does not have a cached index value.
1716 branchTest32(Assembler::Zero
, dest
, Imm32(JSString::INDEX_VALUE_BIT
), fail
);
1718 // Extract the index.
1719 rshift32(Imm32(JSString::INDEX_VALUE_SHIFT
), dest
);
1722 void MacroAssembler::loadChar(Register chars
, Register index
, Register dest
,
1723 CharEncoding encoding
, int32_t offset
/* = 0 */) {
1724 if (encoding
== CharEncoding::Latin1
) {
1725 loadChar(BaseIndex(chars
, index
, TimesOne
, offset
), dest
, encoding
);
1727 loadChar(BaseIndex(chars
, index
, TimesTwo
, offset
), dest
, encoding
);
1731 void MacroAssembler::addToCharPtr(Register chars
, Register index
,
1732 CharEncoding encoding
) {
1733 if (encoding
== CharEncoding::Latin1
) {
1734 static_assert(sizeof(char) == 1,
1735 "Latin-1 string index shouldn't need scaling");
1736 addPtr(index
, chars
);
1738 computeEffectiveAddress(BaseIndex(chars
, index
, TimesTwo
), chars
);
1742 void MacroAssembler::branchIfNotLeadSurrogate(Register src
, Label
* label
) {
1743 branch32(Assembler::Below
, src
, Imm32(unicode::LeadSurrogateMin
), label
);
1744 branch32(Assembler::Above
, src
, Imm32(unicode::LeadSurrogateMax
), label
);
1747 void MacroAssembler::branchSurrogate(Assembler::Condition cond
, Register src
,
1748 Register scratch
, Label
* label
,
1749 SurrogateChar surrogateChar
) {
1750 // For TrailSurrogateMin ≤ x ≤ TrailSurrogateMax and
1751 // LeadSurrogateMin ≤ x ≤ LeadSurrogateMax, the following equations hold.
1753 // SurrogateMin ≤ x ≤ SurrogateMax
1754 // <> SurrogateMin ≤ x ≤ SurrogateMin + 2^10 - 1
1755 // <> ((x - SurrogateMin) >>> 10) = 0 where >>> is an unsigned-shift
1756 // See Hacker's Delight, section 4-1 for details.
1758 // ((x - SurrogateMin) >>> 10) = 0
1759 // <> floor((x - SurrogateMin) / 1024) = 0
1760 // <> floor((x / 1024) - (SurrogateMin / 1024)) = 0
1761 // <> floor(x / 1024) = SurrogateMin / 1024
1762 // <> floor(x / 1024) * 1024 = SurrogateMin
1763 // <> (x >>> 10) << 10 = SurrogateMin
1764 // <> x & ~(2^10 - 1) = SurrogateMin
1766 constexpr char16_t SurrogateMask
= 0xFC00;
1767 char16_t SurrogateMin
= surrogateChar
== SurrogateChar::Lead
1768 ? unicode::LeadSurrogateMin
1769 : unicode::TrailSurrogateMin
;
1771 if (src
!= scratch
) {
1772 move32(src
, scratch
);
1775 and32(Imm32(SurrogateMask
), scratch
);
1776 branch32(cond
, scratch
, Imm32(SurrogateMin
), label
);
1779 void MacroAssembler::loadStringFromUnit(Register unit
, Register dest
,
1780 const StaticStrings
& staticStrings
) {
1781 movePtr(ImmPtr(&staticStrings
.unitStaticTable
), dest
);
1782 loadPtr(BaseIndex(dest
, unit
, ScalePointer
), dest
);
1785 void MacroAssembler::loadLengthTwoString(Register c1
, Register c2
,
1787 const StaticStrings
& staticStrings
) {
1788 // Compute (toSmallCharTable[c1] << SMALL_CHAR_BITS) + toSmallCharTable[c2]
1789 // to obtain the index into `StaticStrings::length2StaticTable`.
1790 static_assert(sizeof(StaticStrings::SmallChar
) == 1);
1792 movePtr(ImmPtr(&StaticStrings::toSmallCharTable
.storage
), dest
);
1793 load8ZeroExtend(BaseIndex(dest
, c1
, Scale::TimesOne
), c1
);
1794 load8ZeroExtend(BaseIndex(dest
, c2
, Scale::TimesOne
), c2
);
1796 lshift32(Imm32(StaticStrings::SMALL_CHAR_BITS
), c1
);
1799 // Look up the string from the computed index.
1800 movePtr(ImmPtr(&staticStrings
.length2StaticTable
), dest
);
1801 loadPtr(BaseIndex(dest
, c1
, ScalePointer
), dest
);
1804 void MacroAssembler::lookupStaticString(Register ch
, Register dest
,
1805 const StaticStrings
& staticStrings
) {
1806 MOZ_ASSERT(ch
!= dest
);
1808 movePtr(ImmPtr(&staticStrings
.unitStaticTable
), dest
);
1809 loadPtr(BaseIndex(dest
, ch
, ScalePointer
), dest
);
1812 void MacroAssembler::lookupStaticString(Register ch
, Register dest
,
1813 const StaticStrings
& staticStrings
,
1815 MOZ_ASSERT(ch
!= dest
);
1817 boundsCheck32PowerOfTwo(ch
, StaticStrings::UNIT_STATIC_LIMIT
, fail
);
1818 movePtr(ImmPtr(&staticStrings
.unitStaticTable
), dest
);
1819 loadPtr(BaseIndex(dest
, ch
, ScalePointer
), dest
);
1822 void MacroAssembler::lookupStaticString(Register ch1
, Register ch2
,
1824 const StaticStrings
& staticStrings
,
1826 MOZ_ASSERT(ch1
!= dest
);
1827 MOZ_ASSERT(ch2
!= dest
);
1829 branch32(Assembler::AboveOrEqual
, ch1
,
1830 Imm32(StaticStrings::SMALL_CHAR_TABLE_SIZE
), fail
);
1831 branch32(Assembler::AboveOrEqual
, ch2
,
1832 Imm32(StaticStrings::SMALL_CHAR_TABLE_SIZE
), fail
);
1834 movePtr(ImmPtr(&StaticStrings::toSmallCharTable
.storage
), dest
);
1835 load8ZeroExtend(BaseIndex(dest
, ch1
, Scale::TimesOne
), ch1
);
1836 load8ZeroExtend(BaseIndex(dest
, ch2
, Scale::TimesOne
), ch2
);
1838 branch32(Assembler::Equal
, ch1
, Imm32(StaticStrings::INVALID_SMALL_CHAR
),
1840 branch32(Assembler::Equal
, ch2
, Imm32(StaticStrings::INVALID_SMALL_CHAR
),
1843 lshift32(Imm32(StaticStrings::SMALL_CHAR_BITS
), ch1
);
1846 // Look up the string from the computed index.
1847 movePtr(ImmPtr(&staticStrings
.length2StaticTable
), dest
);
1848 loadPtr(BaseIndex(dest
, ch1
, ScalePointer
), dest
);
1851 void MacroAssembler::lookupStaticIntString(Register integer
, Register dest
,
1853 const StaticStrings
& staticStrings
,
1855 MOZ_ASSERT(integer
!= scratch
);
1857 boundsCheck32PowerOfTwo(integer
, StaticStrings::INT_STATIC_LIMIT
, fail
);
1858 movePtr(ImmPtr(&staticStrings
.intStaticTable
), scratch
);
1859 loadPtr(BaseIndex(scratch
, integer
, ScalePointer
), dest
);
1862 void MacroAssembler::loadInt32ToStringWithBase(
1863 Register input
, Register base
, Register dest
, Register scratch1
,
1864 Register scratch2
, const StaticStrings
& staticStrings
,
1865 const LiveRegisterSet
& volatileRegs
, bool lowerCase
, Label
* fail
) {
1867 Label baseBad
, baseOk
;
1868 branch32(Assembler::LessThan
, base
, Imm32(2), &baseBad
);
1869 branch32(Assembler::LessThanOrEqual
, base
, Imm32(36), &baseOk
);
1871 assumeUnreachable("base must be in range [2, 36]");
1875 // Compute |"0123456789abcdefghijklmnopqrstuvwxyz"[r]|.
1876 auto toChar
= [this, base
, lowerCase
](Register r
) {
1879 branch32(Assembler::Below
, r
, base
, &ok
);
1880 assumeUnreachable("bad digit");
1883 // Silence unused lambda capture warning.
1888 add32(Imm32('0'), r
);
1889 branch32(Assembler::BelowOrEqual
, r
, Imm32('9'), &done
);
1890 add32(Imm32((lowerCase
? 'a' : 'A') - '0' - 10), r
);
1894 // Perform a "unit" lookup when |unsigned(input) < unsigned(base)|.
1895 Label lengthTwo
, done
;
1896 branch32(Assembler::AboveOrEqual
, input
, base
, &lengthTwo
);
1898 move32(input
, scratch1
);
1901 loadStringFromUnit(scratch1
, dest
, staticStrings
);
1907 // Compute |base * base|.
1908 move32(base
, scratch1
);
1909 mul32(scratch1
, scratch1
);
1911 // Perform a "length2" lookup when |unsigned(input) < unsigned(base * base)|.
1912 branch32(Assembler::AboveOrEqual
, input
, scratch1
, fail
);
1914 // Compute |scratch1 = input / base| and |scratch2 = input % base|.
1915 move32(input
, scratch1
);
1916 flexibleDivMod32(base
, scratch1
, scratch2
, true, volatileRegs
);
1918 // Compute the digits of the divisor and remainder.
1922 // Look up the 2-character digit string in the small-char table.
1923 loadLengthTwoString(scratch1
, scratch2
, dest
, staticStrings
);
1928 void MacroAssembler::loadInt32ToStringWithBase(
1929 Register input
, int32_t base
, Register dest
, Register scratch1
,
1930 Register scratch2
, const StaticStrings
& staticStrings
, bool lowerCase
,
1932 MOZ_ASSERT(2 <= base
&& base
<= 36, "base must be in range [2, 36]");
1934 // Compute |"0123456789abcdefghijklmnopqrstuvwxyz"[r]|.
1935 auto toChar
= [this, base
, lowerCase
](Register r
) {
1938 branch32(Assembler::Below
, r
, Imm32(base
), &ok
);
1939 assumeUnreachable("bad digit");
1944 add32(Imm32('0'), r
);
1947 add32(Imm32('0'), r
);
1948 branch32(Assembler::BelowOrEqual
, r
, Imm32('9'), &done
);
1949 add32(Imm32((lowerCase
? 'a' : 'A') - '0' - 10), r
);
1954 // Perform a "unit" lookup when |unsigned(input) < unsigned(base)|.
1955 Label lengthTwo
, done
;
1956 branch32(Assembler::AboveOrEqual
, input
, Imm32(base
), &lengthTwo
);
1958 move32(input
, scratch1
);
1961 loadStringFromUnit(scratch1
, dest
, staticStrings
);
1967 // Perform a "length2" lookup when |unsigned(input) < unsigned(base * base)|.
1968 branch32(Assembler::AboveOrEqual
, input
, Imm32(base
* base
), fail
);
1970 // Compute |scratch1 = input / base| and |scratch2 = input % base|.
1971 if (mozilla::IsPowerOfTwo(uint32_t(base
))) {
1972 uint32_t shift
= mozilla::FloorLog2(base
);
1974 move32(input
, scratch1
);
1975 rshift32(Imm32(shift
), scratch1
);
1977 move32(input
, scratch2
);
1978 and32(Imm32((uint32_t(1) << shift
) - 1), scratch2
);
1980 // The following code matches CodeGenerator::visitUDivOrModConstant()
1981 // for x86-shared. Also see Hacker's Delight 2nd edition, chapter 10-8
1982 // "Unsigned Division by 7" for the case when |rmc.multiplier| exceeds
1983 // UINT32_MAX and we need to adjust the shift amount.
1985 auto rmc
= ReciprocalMulConstants::computeUnsignedDivisionConstants(base
);
1987 // We first compute |q = (M * n) >> 32), where M = rmc.multiplier.
1988 mulHighUnsigned32(Imm32(rmc
.multiplier
), input
, scratch1
);
1990 if (rmc
.multiplier
> UINT32_MAX
) {
1991 // M >= 2^32 and shift == 0 is impossible, as d >= 2 implies that
1992 // ((M * n) >> (32 + shift)) >= n > floor(n/d) whenever n >= d,
1993 // contradicting the proof of correctness in computeDivisionConstants.
1994 MOZ_ASSERT(rmc
.shiftAmount
> 0);
1995 MOZ_ASSERT(rmc
.multiplier
< (int64_t(1) << 33));
1997 // Compute |t = (n - q) / 2|.
1998 move32(input
, scratch2
);
1999 sub32(scratch1
, scratch2
);
2000 rshift32(Imm32(1), scratch2
);
2002 // Compute |t = (n - q) / 2 + q = (n + q) / 2|.
2003 add32(scratch2
, scratch1
);
2005 // Finish the computation |q = floor(n / d)|.
2006 rshift32(Imm32(rmc
.shiftAmount
- 1), scratch1
);
2008 rshift32(Imm32(rmc
.shiftAmount
), scratch1
);
2011 // Compute the remainder from |r = n - q * d|.
2012 move32(scratch1
, dest
);
2013 mul32(Imm32(base
), dest
);
2014 move32(input
, scratch2
);
2015 sub32(dest
, scratch2
);
2018 // Compute the digits of the divisor and remainder.
2022 // Look up the 2-character digit string in the small-char table.
2023 loadLengthTwoString(scratch1
, scratch2
, dest
, staticStrings
);
2028 void MacroAssembler::loadBigIntDigits(Register bigInt
, Register digits
) {
2029 MOZ_ASSERT(digits
!= bigInt
);
2031 // Load the inline digits.
2032 computeEffectiveAddress(Address(bigInt
, BigInt::offsetOfInlineDigits()),
2035 // If inline digits aren't used, load the heap digits. Use a conditional move
2036 // to prevent speculative execution.
2037 cmp32LoadPtr(Assembler::Above
, Address(bigInt
, BigInt::offsetOfLength()),
2038 Imm32(int32_t(BigInt::inlineDigitsLength())),
2039 Address(bigInt
, BigInt::offsetOfHeapDigits()), digits
);
2042 void MacroAssembler::loadBigInt64(Register bigInt
, Register64 dest
) {
2043 // This code follows the implementation of |BigInt::toUint64()|. We're also
2044 // using it for inline callers of |BigInt::toInt64()|, which works, because
2045 // all supported Jit architectures use a two's complement representation for
2046 // int64 values, which means the WrapToSigned call in toInt64() is a no-op.
2048 Label done
, nonZero
;
2050 branchIfBigIntIsNonZero(bigInt
, &nonZero
);
2052 move64(Imm64(0), dest
);
2058 Register digits
= dest
.reg
;
2060 Register digits
= dest
.high
;
2063 loadBigIntDigits(bigInt
, digits
);
2066 // Load the first digit into the destination register.
2067 load64(Address(digits
, 0), dest
);
2069 // Load the first digit into the destination register's low value.
2070 load32(Address(digits
, 0), dest
.low
);
2072 // And conditionally load the second digit into the high value register.
2073 Label twoDigits
, digitsDone
;
2074 branch32(Assembler::Above
, Address(bigInt
, BigInt::offsetOfLength()),
2075 Imm32(1), &twoDigits
);
2077 move32(Imm32(0), dest
.high
);
2082 load32(Address(digits
, sizeof(BigInt::Digit
)), dest
.high
);
2087 branchTest32(Assembler::Zero
, Address(bigInt
, BigInt::offsetOfFlags()),
2088 Imm32(BigInt::signBitMask()), &done
);
2094 void MacroAssembler::loadBigIntDigit(Register bigInt
, Register dest
) {
2095 Label done
, nonZero
;
2096 branchIfBigIntIsNonZero(bigInt
, &nonZero
);
2098 movePtr(ImmWord(0), dest
);
2103 loadBigIntDigits(bigInt
, dest
);
2105 // Load the first digit into the destination register.
2106 loadPtr(Address(dest
, 0), dest
);
2111 void MacroAssembler::loadBigIntDigit(Register bigInt
, Register dest
,
2113 MOZ_ASSERT(bigInt
!= dest
);
2115 branch32(Assembler::Above
, Address(bigInt
, BigInt::offsetOfLength()),
2118 static_assert(BigInt::inlineDigitsLength() > 0,
2119 "Single digit BigInts use inline storage");
2121 // Load the first inline digit into the destination register.
2122 movePtr(ImmWord(0), dest
);
2123 cmp32LoadPtr(Assembler::NotEqual
, Address(bigInt
, BigInt::offsetOfLength()),
2124 Imm32(0), Address(bigInt
, BigInt::offsetOfInlineDigits()), dest
);
2127 void MacroAssembler::loadBigIntPtr(Register bigInt
, Register dest
,
2129 loadBigIntDigit(bigInt
, dest
, fail
);
2131 // BigInt digits are stored as unsigned numbers. Take the failure path when
2132 // the digit can't be stored in intptr_t.
2134 Label nonNegative
, done
;
2135 branchIfBigIntIsNonNegative(bigInt
, &nonNegative
);
2137 // Negate |dest| when the BigInt is negative.
2140 // Test after negating to handle INTPTR_MIN correctly.
2141 branchTestPtr(Assembler::NotSigned
, dest
, dest
, fail
);
2145 branchTestPtr(Assembler::Signed
, dest
, dest
, fail
);
2149 void MacroAssembler::initializeBigInt64(Scalar::Type type
, Register bigInt
,
2150 Register64 val
, Register64 temp
) {
2151 MOZ_ASSERT(Scalar::isBigIntType(type
));
2153 store32(Imm32(0), Address(bigInt
, BigInt::offsetOfFlags()));
2155 Label done
, nonZero
;
2156 branch64(Assembler::NotEqual
, val
, Imm64(0), &nonZero
);
2158 store32(Imm32(0), Address(bigInt
, BigInt::offsetOfLength()));
2163 if (type
== Scalar::BigInt64
) {
2164 // Copy the input when we're not allowed to clobber it.
2165 if (temp
!= Register64::Invalid()) {
2170 // Set the sign-bit for negative values and then continue with the two's
2173 branch64(Assembler::GreaterThan
, val
, Imm64(0), &isPositive
);
2175 store32(Imm32(BigInt::signBitMask()),
2176 Address(bigInt
, BigInt::offsetOfFlags()));
2182 store32(Imm32(1), Address(bigInt
, BigInt::offsetOfLength()));
2184 static_assert(sizeof(BigInt::Digit
) == sizeof(uintptr_t),
2185 "BigInt Digit size matches uintptr_t, so there's a single "
2186 "store on 64-bit and up to two stores on 32-bit");
2190 branchTest32(Assembler::Zero
, val
.high
, val
.high
, &singleDigit
);
2191 store32(Imm32(2), Address(bigInt
, BigInt::offsetOfLength()));
2194 // We can perform a single store64 on 32-bit platforms, because inline
2195 // storage can store at least two 32-bit integers.
2196 static_assert(BigInt::inlineDigitsLength() >= 2,
2197 "BigInt inline storage can store at least two digits");
2200 store64(val
, Address(bigInt
, js::BigInt::offsetOfInlineDigits()));
2205 void MacroAssembler::initializeBigIntPtr(Register bigInt
, Register val
) {
2206 store32(Imm32(0), Address(bigInt
, BigInt::offsetOfFlags()));
2208 Label done
, nonZero
;
2209 branchTestPtr(Assembler::NonZero
, val
, val
, &nonZero
);
2211 store32(Imm32(0), Address(bigInt
, BigInt::offsetOfLength()));
2216 // Set the sign-bit for negative values and then continue with the two's
2219 branchTestPtr(Assembler::NotSigned
, val
, val
, &isPositive
);
2221 store32(Imm32(BigInt::signBitMask()),
2222 Address(bigInt
, BigInt::offsetOfFlags()));
2227 store32(Imm32(1), Address(bigInt
, BigInt::offsetOfLength()));
2229 static_assert(sizeof(BigInt::Digit
) == sizeof(uintptr_t),
2230 "BigInt Digit size matches uintptr_t");
2232 storePtr(val
, Address(bigInt
, js::BigInt::offsetOfInlineDigits()));
2237 void MacroAssembler::copyBigIntWithInlineDigits(Register src
, Register dest
,
2239 gc::Heap initialHeap
,
2241 branch32(Assembler::Above
, Address(src
, BigInt::offsetOfLength()),
2242 Imm32(int32_t(BigInt::inlineDigitsLength())), fail
);
2244 newGCBigInt(dest
, temp
, initialHeap
, fail
);
2246 // Copy the sign-bit, but not any of the other bits used by the GC.
2247 load32(Address(src
, BigInt::offsetOfFlags()), temp
);
2248 and32(Imm32(BigInt::signBitMask()), temp
);
2249 store32(temp
, Address(dest
, BigInt::offsetOfFlags()));
2252 load32(Address(src
, BigInt::offsetOfLength()), temp
);
2253 store32(temp
, Address(dest
, BigInt::offsetOfLength()));
2256 Address
srcDigits(src
, js::BigInt::offsetOfInlineDigits());
2257 Address
destDigits(dest
, js::BigInt::offsetOfInlineDigits());
2259 for (size_t i
= 0; i
< BigInt::inlineDigitsLength(); i
++) {
2260 static_assert(sizeof(BigInt::Digit
) == sizeof(uintptr_t),
2261 "BigInt Digit size matches uintptr_t");
2263 loadPtr(srcDigits
, temp
);
2264 storePtr(temp
, destDigits
);
2266 srcDigits
= Address(src
, srcDigits
.offset
+ sizeof(BigInt::Digit
));
2267 destDigits
= Address(dest
, destDigits
.offset
+ sizeof(BigInt::Digit
));
2271 void MacroAssembler::compareBigIntAndInt32(JSOp op
, Register bigInt
,
2272 Register int32
, Register scratch1
,
2273 Register scratch2
, Label
* ifTrue
,
2275 MOZ_ASSERT(IsLooseEqualityOp(op
) || IsRelationalOp(op
));
2277 static_assert(std::is_same_v
<BigInt::Digit
, uintptr_t>,
2278 "BigInt digit can be loaded in a pointer-sized register");
2279 static_assert(sizeof(BigInt::Digit
) >= sizeof(uint32_t),
2280 "BigInt digit stores at least an uint32");
2282 // Test for too large numbers.
2284 // If the unsigned value of the BigInt can't be expressed in an uint32/uint64,
2285 // the result of the comparison is a constant.
2286 if (op
== JSOp::Eq
|| op
== JSOp::Ne
) {
2287 Label
* tooLarge
= op
== JSOp::Eq
? ifFalse
: ifTrue
;
2288 branch32(Assembler::GreaterThan
,
2289 Address(bigInt
, BigInt::offsetOfDigitLength()), Imm32(1),
2293 branch32(Assembler::LessThanOrEqual
,
2294 Address(bigInt
, BigInt::offsetOfDigitLength()), Imm32(1),
2297 // Still need to take the sign-bit into account for relational operations.
2298 if (op
== JSOp::Lt
|| op
== JSOp::Le
) {
2299 branchIfBigIntIsNegative(bigInt
, ifTrue
);
2302 branchIfBigIntIsNegative(bigInt
, ifFalse
);
2309 // Test for mismatched signs and, if the signs are equal, load |abs(x)| in
2310 // |scratch1| and |abs(y)| in |scratch2| and then compare the unsigned numbers
2311 // against each other.
2313 // Jump to |ifTrue| resp. |ifFalse| if the BigInt is strictly less than
2314 // resp. strictly greater than the int32 value, depending on the comparison
2318 if (op
== JSOp::Eq
) {
2319 greaterThan
= ifFalse
;
2321 } else if (op
== JSOp::Ne
) {
2322 greaterThan
= ifTrue
;
2324 } else if (op
== JSOp::Lt
|| op
== JSOp::Le
) {
2325 greaterThan
= ifFalse
;
2328 MOZ_ASSERT(op
== JSOp::Gt
|| op
== JSOp::Ge
);
2329 greaterThan
= ifTrue
;
2333 // BigInt digits are always stored as an unsigned number.
2334 loadBigIntDigit(bigInt
, scratch1
);
2336 // Load the int32 into |scratch2| and negate it for negative numbers.
2337 move32(int32
, scratch2
);
2339 Label isNegative
, doCompare
;
2340 branchIfBigIntIsNegative(bigInt
, &isNegative
);
2341 branch32(Assembler::LessThan
, int32
, Imm32(0), greaterThan
);
2344 // We rely on |neg32(INT32_MIN)| staying INT32_MIN, because we're using an
2345 // unsigned comparison below.
2347 branch32(Assembler::GreaterThanOrEqual
, int32
, Imm32(0), lessThan
);
2350 // Not all supported platforms (e.g. MIPS64) zero-extend 32-bit operations,
2351 // so we need to explicitly clear any high 32-bits.
2352 move32ZeroExtendToPtr(scratch2
, scratch2
);
2354 // Reverse the relational comparator for negative numbers.
2355 // |-x < -y| <=> |+x > +y|.
2356 // |-x ≤ -y| <=> |+x ≥ +y|.
2357 // |-x > -y| <=> |+x < +y|.
2358 // |-x ≥ -y| <=> |+x ≤ +y|.
2359 JSOp reversed
= ReverseCompareOp(op
);
2360 if (reversed
!= op
) {
2361 branchPtr(JSOpToCondition(reversed
, /* isSigned = */ false), scratch1
,
2367 branchPtr(JSOpToCondition(op
, /* isSigned = */ false), scratch1
, scratch2
,
2372 void MacroAssembler::compareBigIntAndInt32(JSOp op
, Register bigInt
,
2373 Imm32 int32
, Register scratch
,
2374 Label
* ifTrue
, Label
* ifFalse
) {
2375 MOZ_ASSERT(IsLooseEqualityOp(op
) || IsRelationalOp(op
));
2377 static_assert(std::is_same_v
<BigInt::Digit
, uintptr_t>,
2378 "BigInt digit can be loaded in a pointer-sized register");
2379 static_assert(sizeof(BigInt::Digit
) >= sizeof(uint32_t),
2380 "BigInt digit stores at least an uint32");
2382 // Comparison against zero doesn't require loading any BigInt digits.
2383 if (int32
.value
== 0) {
2386 branchIfBigIntIsZero(bigInt
, ifTrue
);
2389 branchIfBigIntIsNonZero(bigInt
, ifTrue
);
2392 branchIfBigIntIsNegative(bigInt
, ifTrue
);
2395 branchIfBigIntIsZero(bigInt
, ifTrue
);
2396 branchIfBigIntIsNegative(bigInt
, ifTrue
);
2399 branchIfBigIntIsZero(bigInt
, ifFalse
);
2400 branchIfBigIntIsNonNegative(bigInt
, ifTrue
);
2403 branchIfBigIntIsNonNegative(bigInt
, ifTrue
);
2406 MOZ_CRASH("bad comparison operator");
2409 // Fall through to the false case.
2413 // Jump to |ifTrue| resp. |ifFalse| if the BigInt is strictly less than
2414 // resp. strictly greater than the int32 value, depending on the comparison
2418 if (op
== JSOp::Eq
) {
2419 greaterThan
= ifFalse
;
2421 } else if (op
== JSOp::Ne
) {
2422 greaterThan
= ifTrue
;
2424 } else if (op
== JSOp::Lt
|| op
== JSOp::Le
) {
2425 greaterThan
= ifFalse
;
2428 MOZ_ASSERT(op
== JSOp::Gt
|| op
== JSOp::Ge
);
2429 greaterThan
= ifTrue
;
2433 // Test for mismatched signs.
2434 if (int32
.value
> 0) {
2435 branchIfBigIntIsNegative(bigInt
, lessThan
);
2437 branchIfBigIntIsNonNegative(bigInt
, greaterThan
);
2440 // Both signs are equal, load |abs(x)| in |scratch| and then compare the
2441 // unsigned numbers against each other.
2443 // If the unsigned value of the BigInt can't be expressed in an uint32/uint64,
2444 // the result of the comparison is a constant.
2445 Label
* tooLarge
= int32
.value
> 0 ? greaterThan
: lessThan
;
2446 loadBigIntDigit(bigInt
, scratch
, tooLarge
);
2448 // Use the unsigned value of the immediate.
2449 ImmWord uint32
= ImmWord(mozilla::Abs(int32
.value
));
2451 // Reverse the relational comparator for negative numbers.
2452 // |-x < -y| <=> |+x > +y|.
2453 // |-x ≤ -y| <=> |+x ≥ +y|.
2454 // |-x > -y| <=> |+x < +y|.
2455 // |-x ≥ -y| <=> |+x ≤ +y|.
2456 if (int32
.value
< 0) {
2457 op
= ReverseCompareOp(op
);
2460 branchPtr(JSOpToCondition(op
, /* isSigned = */ false), scratch
, uint32
,
2464 void MacroAssembler::equalBigInts(Register left
, Register right
, Register temp1
,
2465 Register temp2
, Register temp3
,
2466 Register temp4
, Label
* notSameSign
,
2467 Label
* notSameLength
, Label
* notSameDigit
) {
2468 MOZ_ASSERT(left
!= temp1
);
2469 MOZ_ASSERT(right
!= temp1
);
2470 MOZ_ASSERT(right
!= temp2
);
2472 // Jump to |notSameSign| when the sign aren't the same.
2473 load32(Address(left
, BigInt::offsetOfFlags()), temp1
);
2474 xor32(Address(right
, BigInt::offsetOfFlags()), temp1
);
2475 branchTest32(Assembler::NonZero
, temp1
, Imm32(BigInt::signBitMask()),
2478 // Jump to |notSameLength| when the digits length is different.
2479 load32(Address(right
, BigInt::offsetOfLength()), temp1
);
2480 branch32(Assembler::NotEqual
, Address(left
, BigInt::offsetOfLength()), temp1
,
2483 // Both BigInts have the same sign and the same number of digits. Loop
2484 // over each digit, starting with the left-most one, and break from the
2485 // loop when the first non-matching digit was found.
2487 loadBigIntDigits(left
, temp2
);
2488 loadBigIntDigits(right
, temp3
);
2490 static_assert(sizeof(BigInt::Digit
) == sizeof(void*),
2491 "BigInt::Digit is pointer sized");
2493 computeEffectiveAddress(BaseIndex(temp2
, temp1
, ScalePointer
), temp2
);
2494 computeEffectiveAddress(BaseIndex(temp3
, temp1
, ScalePointer
), temp3
);
2500 subPtr(Imm32(sizeof(BigInt::Digit
)), temp2
);
2501 subPtr(Imm32(sizeof(BigInt::Digit
)), temp3
);
2503 loadPtr(Address(temp3
, 0), temp4
);
2504 branchPtr(Assembler::NotEqual
, Address(temp2
, 0), temp4
, notSameDigit
);
2507 branchSub32(Assembler::NotSigned
, Imm32(1), temp1
, &loop
);
2509 // No different digits were found, both BigInts are equal to each other.
2512 void MacroAssembler::typeOfObject(Register obj
, Register scratch
, Label
* slow
,
2513 Label
* isObject
, Label
* isCallable
,
2514 Label
* isUndefined
) {
2515 loadObjClassUnsafe(obj
, scratch
);
2517 // Proxies can emulate undefined and have complex isCallable behavior.
2518 branchTestClassIsProxy(true, scratch
, slow
);
2520 // JSFunctions are always callable.
2521 branchTestClassIsFunction(Assembler::Equal
, scratch
, isCallable
);
2523 // Objects that emulate undefined.
2524 Address
flags(scratch
, JSClass::offsetOfFlags());
2525 branchTest32(Assembler::NonZero
, flags
, Imm32(JSCLASS_EMULATES_UNDEFINED
),
2528 // Handle classes with a call hook.
2529 branchPtr(Assembler::Equal
, Address(scratch
, offsetof(JSClass
, cOps
)),
2530 ImmPtr(nullptr), isObject
);
2532 loadPtr(Address(scratch
, offsetof(JSClass
, cOps
)), scratch
);
2533 branchPtr(Assembler::Equal
, Address(scratch
, offsetof(JSClassOps
, call
)),
2534 ImmPtr(nullptr), isObject
);
2539 void MacroAssembler::isCallableOrConstructor(bool isCallable
, Register obj
,
2540 Register output
, Label
* isProxy
) {
2541 MOZ_ASSERT(obj
!= output
);
2543 Label notFunction
, hasCOps
, done
;
2544 loadObjClassUnsafe(obj
, output
);
2546 // An object is callable iff:
2547 // is<JSFunction>() || (getClass()->cOps && getClass()->cOps->call).
2548 // An object is constructor iff:
2549 // ((is<JSFunction>() && as<JSFunction>().isConstructor) ||
2550 // (getClass()->cOps && getClass()->cOps->construct)).
2551 branchTestClassIsFunction(Assembler::NotEqual
, output
, ¬Function
);
2553 move32(Imm32(1), output
);
2555 static_assert(mozilla::IsPowerOfTwo(uint32_t(FunctionFlags::CONSTRUCTOR
)),
2556 "FunctionFlags::CONSTRUCTOR has only one bit set");
2558 load32(Address(obj
, JSFunction::offsetOfFlagsAndArgCount()), output
);
2559 rshift32(Imm32(mozilla::FloorLog2(uint32_t(FunctionFlags::CONSTRUCTOR
))),
2561 and32(Imm32(1), output
);
2568 // For bound functions, we need to check the isConstructor flag.
2569 Label notBoundFunction
;
2570 branchPtr(Assembler::NotEqual
, output
, ImmPtr(&BoundFunctionObject::class_
),
2573 static_assert(BoundFunctionObject::IsConstructorFlag
== 0b1,
2574 "AND operation results in boolean value");
2575 unboxInt32(Address(obj
, BoundFunctionObject::offsetOfFlagsSlot()), output
);
2576 and32(Imm32(BoundFunctionObject::IsConstructorFlag
), output
);
2579 bind(¬BoundFunction
);
2582 // Just skim proxies off. Their notion of isCallable()/isConstructor() is
2583 // more complicated.
2584 branchTestClassIsProxy(true, output
, isProxy
);
2586 branchPtr(Assembler::NonZero
, Address(output
, offsetof(JSClass
, cOps
)),
2587 ImmPtr(nullptr), &hasCOps
);
2588 move32(Imm32(0), output
);
2592 loadPtr(Address(output
, offsetof(JSClass
, cOps
)), output
);
2594 isCallable
? offsetof(JSClassOps
, call
) : offsetof(JSClassOps
, construct
);
2595 cmpPtrSet(Assembler::NonZero
, Address(output
, opsOffset
), ImmPtr(nullptr),
2601 void MacroAssembler::loadJSContext(Register dest
) {
2602 movePtr(ImmPtr(runtime()->mainContextPtr()), dest
);
2605 static const uint8_t* ContextRealmPtr(CompileRuntime
* rt
) {
2606 return (static_cast<const uint8_t*>(rt
->mainContextPtr()) +
2607 JSContext::offsetOfRealm());
2610 void MacroAssembler::loadGlobalObjectData(Register dest
) {
2611 loadPtr(AbsoluteAddress(ContextRealmPtr(runtime())), dest
);
2612 loadPtr(Address(dest
, Realm::offsetOfActiveGlobal()), dest
);
2613 loadPrivate(Address(dest
, GlobalObject::offsetOfGlobalDataSlot()), dest
);
2616 void MacroAssembler::switchToRealm(Register realm
) {
2617 storePtr(realm
, AbsoluteAddress(ContextRealmPtr(runtime())));
2620 void MacroAssembler::loadRealmFuse(RealmFuses::FuseIndex index
, Register dest
) {
2621 // Load Realm pointer
2622 loadPtr(AbsoluteAddress(ContextRealmPtr(runtime())), dest
);
2623 loadPtr(Address(dest
, RealmFuses::offsetOfFuseWordRelativeToRealm(index
)),
2627 void MacroAssembler::switchToRealm(const void* realm
, Register scratch
) {
2630 movePtr(ImmPtr(realm
), scratch
);
2631 switchToRealm(scratch
);
2634 void MacroAssembler::switchToObjectRealm(Register obj
, Register scratch
) {
2635 loadPtr(Address(obj
, JSObject::offsetOfShape()), scratch
);
2636 loadPtr(Address(scratch
, Shape::offsetOfBaseShape()), scratch
);
2637 loadPtr(Address(scratch
, BaseShape::offsetOfRealm()), scratch
);
2638 switchToRealm(scratch
);
2641 void MacroAssembler::switchToBaselineFrameRealm(Register scratch
) {
2642 Address
envChain(FramePointer
,
2643 BaselineFrame::reverseOffsetOfEnvironmentChain());
2644 loadPtr(envChain
, scratch
);
2645 switchToObjectRealm(scratch
, scratch
);
2648 void MacroAssembler::switchToWasmInstanceRealm(Register scratch1
,
2649 Register scratch2
) {
2650 loadPtr(Address(InstanceReg
, wasm::Instance::offsetOfCx()), scratch1
);
2651 loadPtr(Address(InstanceReg
, wasm::Instance::offsetOfRealm()), scratch2
);
2652 storePtr(scratch2
, Address(scratch1
, JSContext::offsetOfRealm()));
2655 template <typename ValueType
>
2656 void MacroAssembler::storeLocalAllocSite(ValueType value
, Register scratch
) {
2657 loadPtr(AbsoluteAddress(ContextRealmPtr(runtime())), scratch
);
2658 storePtr(value
, Address(scratch
, JS::Realm::offsetOfLocalAllocSite()));
2661 template void MacroAssembler::storeLocalAllocSite(Register
, Register
);
2662 template void MacroAssembler::storeLocalAllocSite(ImmWord
, Register
);
2663 template void MacroAssembler::storeLocalAllocSite(ImmPtr
, Register
);
2665 void MacroAssembler::debugAssertContextRealm(const void* realm
,
2669 movePtr(ImmPtr(realm
), scratch
);
2670 branchPtr(Assembler::Equal
, AbsoluteAddress(ContextRealmPtr(runtime())),
2672 assumeUnreachable("Unexpected context realm");
2677 void MacroAssembler::setIsCrossRealmArrayConstructor(Register obj
,
2681 branchTestObjectIsProxy(false, obj
, output
, ¬Proxy
);
2682 assumeUnreachable("Unexpected proxy in setIsCrossRealmArrayConstructor");
2686 // The object's realm must not be cx->realm.
2687 Label isFalse
, done
;
2688 loadPtr(Address(obj
, JSObject::offsetOfShape()), output
);
2689 loadPtr(Address(output
, Shape::offsetOfBaseShape()), output
);
2690 loadPtr(Address(output
, BaseShape::offsetOfRealm()), output
);
2691 branchPtr(Assembler::Equal
, AbsoluteAddress(ContextRealmPtr(runtime())),
2694 // The object must be a function.
2695 branchTestObjIsFunction(Assembler::NotEqual
, obj
, output
, obj
, &isFalse
);
2697 // The function must be the ArrayConstructor native.
2698 branchPtr(Assembler::NotEqual
,
2699 Address(obj
, JSFunction::offsetOfNativeOrEnv()),
2700 ImmPtr(js::ArrayConstructor
), &isFalse
);
2702 move32(Imm32(1), output
);
2706 move32(Imm32(0), output
);
2711 void MacroAssembler::setIsDefinitelyTypedArrayConstructor(Register obj
,
2713 Label isFalse
, isTrue
, done
;
2715 // The object must be a function. (Wrappers are not supported.)
2716 branchTestObjIsFunction(Assembler::NotEqual
, obj
, output
, obj
, &isFalse
);
2718 // Load the native into |output|.
2719 loadPtr(Address(obj
, JSFunction::offsetOfNativeOrEnv()), output
);
2721 auto branchIsTypedArrayCtor
= [&](Scalar::Type type
) {
2722 // The function must be a TypedArrayConstructor native (from any realm).
2723 JSNative constructor
= TypedArrayConstructorNative(type
);
2724 branchPtr(Assembler::Equal
, output
, ImmPtr(constructor
), &isTrue
);
2727 #define TYPED_ARRAY_CONSTRUCTOR_NATIVE(_, T, N) \
2728 branchIsTypedArrayCtor(Scalar::N);
2729 JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_CONSTRUCTOR_NATIVE
)
2730 #undef TYPED_ARRAY_CONSTRUCTOR_NATIVE
2732 // Falls through to the false case.
2735 move32(Imm32(0), output
);
2739 move32(Imm32(1), output
);
2744 void MacroAssembler::loadMegamorphicCache(Register dest
) {
2745 movePtr(ImmPtr(runtime()->addressOfMegamorphicCache()), dest
);
2747 void MacroAssembler::loadMegamorphicSetPropCache(Register dest
) {
2748 movePtr(ImmPtr(runtime()->addressOfMegamorphicSetPropCache()), dest
);
2751 void MacroAssembler::tryFastAtomize(Register str
, Register scratch
,
2752 Register output
, Label
* fail
) {
2753 Label found
, done
, notAtomRef
;
2755 branchTest32(Assembler::Zero
, Address(str
, JSString::offsetOfFlags()),
2756 Imm32(JSString::ATOM_REF_BIT
), ¬AtomRef
);
2757 loadPtr(Address(str
, JSAtomRefString::offsetOfAtom()), output
);
2761 uintptr_t cachePtr
= uintptr_t(runtime()->addressOfStringToAtomCache());
2762 void* offset
= (void*)(cachePtr
+ StringToAtomCache::offsetOfLastLookups());
2763 movePtr(ImmPtr(offset
), scratch
);
2765 static_assert(StringToAtomCache::NumLastLookups
== 2);
2766 size_t stringOffset
= StringToAtomCache::LastLookup::offsetOfString();
2767 size_t lookupSize
= sizeof(StringToAtomCache::LastLookup
);
2768 branchPtr(Assembler::Equal
, Address(scratch
, stringOffset
), str
, &found
);
2769 branchPtr(Assembler::NotEqual
, Address(scratch
, lookupSize
+ stringOffset
),
2771 addPtr(Imm32(lookupSize
), scratch
);
2773 // We found a hit in the lastLookups_ array! Load the associated atom
2774 // and jump back up to our usual atom handling code
2776 size_t atomOffset
= StringToAtomCache::LastLookup::offsetOfAtom();
2777 loadPtr(Address(scratch
, atomOffset
), output
);
2781 void MacroAssembler::loadAtomHash(Register id
, Register outHash
, Label
* done
) {
2782 Label doneInner
, fatInline
;
2786 move32(Imm32(JSString::FAT_INLINE_MASK
), outHash
);
2787 and32(Address(id
, JSString::offsetOfFlags()), outHash
);
2789 branch32(Assembler::Equal
, outHash
, Imm32(JSString::FAT_INLINE_MASK
),
2791 load32(Address(id
, NormalAtom::offsetOfHash()), outHash
);
2794 load32(Address(id
, FatInlineAtom::offsetOfHash()), outHash
);
2799 void MacroAssembler::loadAtomOrSymbolAndHash(ValueOperand value
, Register outId
,
2802 Label isString
, isSymbol
, isNull
, isUndefined
, done
, nonAtom
, atom
;
2805 ScratchTagScope
tag(*this, value
);
2806 splitTagForTest(value
, tag
);
2807 branchTestString(Assembler::Equal
, tag
, &isString
);
2808 branchTestSymbol(Assembler::Equal
, tag
, &isSymbol
);
2809 branchTestNull(Assembler::Equal
, tag
, &isNull
);
2810 branchTestUndefined(Assembler::NotEqual
, tag
, cacheMiss
);
2813 const JSAtomState
& names
= runtime()->names();
2814 movePropertyKey(NameToId(names
.undefined
), outId
);
2815 move32(Imm32(names
.undefined
->hash()), outHash
);
2819 movePropertyKey(NameToId(names
.null
), outId
);
2820 move32(Imm32(names
.null
->hash()), outHash
);
2824 unboxSymbol(value
, outId
);
2825 load32(Address(outId
, JS::Symbol::offsetOfHash()), outHash
);
2826 orPtr(Imm32(PropertyKey::SymbolTypeTag
), outId
);
2830 unboxString(value
, outId
);
2831 branchTest32(Assembler::Zero
, Address(outId
, JSString::offsetOfFlags()),
2832 Imm32(JSString::ATOM_BIT
), &nonAtom
);
2835 loadAtomHash(outId
, outHash
, &done
);
2838 tryFastAtomize(outId
, outHash
, outId
, cacheMiss
);
2844 void MacroAssembler::emitExtractValueFromMegamorphicCacheEntry(
2845 Register obj
, Register entry
, Register scratch1
, Register scratch2
,
2846 ValueOperand output
, Label
* cacheHit
, Label
* cacheMiss
) {
2847 Label isMissing
, dynamicSlot
, protoLoopHead
, protoLoopTail
;
2849 // scratch2 = entry->hopsAndKind_
2851 Address(entry
, MegamorphicCache::Entry::offsetOfHopsAndKind()), scratch2
);
2852 // if (scratch2 == NumHopsForMissingProperty) goto isMissing
2853 branch32(Assembler::Equal
, scratch2
,
2854 Imm32(MegamorphicCache::Entry::NumHopsForMissingProperty
),
2856 // if (scratch2 & NonDataPropertyFlag) goto cacheMiss
2857 branchTest32(Assembler::NonZero
, scratch2
,
2858 Imm32(MegamorphicCache::Entry::NonDataPropertyFlag
), cacheMiss
);
2860 // NOTE: Where this is called, `output` can actually alias `obj`, and before
2861 // the last cacheMiss branch above we can't write to `obj`, so we can't
2862 // use `output`'s scratch register there. However a cache miss is impossible
2863 // now, so we're free to use `output` as we like.
2864 Register outputScratch
= output
.scratchReg();
2865 if (!outputScratch
.aliases(obj
)) {
2866 // We're okay with paying this very slight extra cost to avoid a potential
2867 // footgun of writing to what callers understand as only an input register.
2868 movePtr(obj
, outputScratch
);
2870 branchTest32(Assembler::Zero
, scratch2
, scratch2
, &protoLoopTail
);
2871 bind(&protoLoopHead
);
2872 loadObjProto(outputScratch
, outputScratch
);
2873 branchSub32(Assembler::NonZero
, Imm32(1), scratch2
, &protoLoopHead
);
2874 bind(&protoLoopTail
);
2876 // scratch1 = entry->slotOffset()
2877 load32(Address(entry
, MegamorphicCacheEntry::offsetOfSlotOffset()), scratch1
);
2879 // scratch2 = slotOffset.offset()
2880 move32(scratch1
, scratch2
);
2881 rshift32(Imm32(TaggedSlotOffset::OffsetShift
), scratch2
);
2883 // if (!slotOffset.isFixedSlot()) goto dynamicSlot
2884 branchTest32(Assembler::Zero
, scratch1
,
2885 Imm32(TaggedSlotOffset::IsFixedSlotFlag
), &dynamicSlot
);
2886 // output = outputScratch[scratch2]
2887 loadValue(BaseIndex(outputScratch
, scratch2
, TimesOne
), output
);
2891 // output = outputScratch->slots_[scratch2]
2892 loadPtr(Address(outputScratch
, NativeObject::offsetOfSlots()), outputScratch
);
2893 loadValue(BaseIndex(outputScratch
, scratch2
, TimesOne
), output
);
2897 // output = undefined
2898 moveValue(UndefinedValue(), output
);
2902 template <typename IdOperandType
>
2903 void MacroAssembler::emitMegamorphicCacheLookupByValueCommon(
2904 IdOperandType id
, Register obj
, Register scratch1
, Register scratch2
,
2905 Register outEntryPtr
, Label
* cacheMiss
, Label
* cacheMissWithEntry
) {
2906 // A lot of this code is shared with emitMegamorphicCacheLookup. It would
2907 // be nice to be able to avoid the duplication here, but due to a few
2908 // differences like taking the id in a ValueOperand instead of being able
2909 // to bake it in as an immediate, and only needing a Register for the output
2910 // value, it seemed more awkward to read once it was deduplicated.
2912 // outEntryPtr = obj->shape()
2913 loadPtr(Address(obj
, JSObject::offsetOfShape()), outEntryPtr
);
2915 movePtr(outEntryPtr
, scratch2
);
2917 // outEntryPtr = (outEntryPtr >> 3) ^ (outEntryPtr >> 13) + idHash
2918 rshiftPtr(Imm32(MegamorphicCache::ShapeHashShift1
), outEntryPtr
);
2919 rshiftPtr(Imm32(MegamorphicCache::ShapeHashShift2
), scratch2
);
2920 xorPtr(scratch2
, outEntryPtr
);
2922 if constexpr (std::is_same
<IdOperandType
, ValueOperand
>::value
) {
2923 loadAtomOrSymbolAndHash(id
, scratch1
, scratch2
, cacheMiss
);
2925 static_assert(std::is_same
<IdOperandType
, Register
>::value
);
2926 movePtr(id
, scratch1
);
2927 loadAtomHash(scratch1
, scratch2
, nullptr);
2929 addPtr(scratch2
, outEntryPtr
);
2931 // outEntryPtr %= MegamorphicCache::NumEntries
2932 constexpr size_t cacheSize
= MegamorphicCache::NumEntries
;
2933 static_assert(mozilla::IsPowerOfTwo(cacheSize
));
2934 size_t cacheMask
= cacheSize
- 1;
2935 and32(Imm32(cacheMask
), outEntryPtr
);
2937 loadMegamorphicCache(scratch2
);
2938 // outEntryPtr = &scratch2->entries_[outEntryPtr]
2939 constexpr size_t entrySize
= sizeof(MegamorphicCache::Entry
);
2940 static_assert(sizeof(void*) == 4 || entrySize
== 24);
2941 if constexpr (sizeof(void*) == 4) {
2942 mul32(Imm32(entrySize
), outEntryPtr
);
2943 computeEffectiveAddress(BaseIndex(scratch2
, outEntryPtr
, TimesOne
,
2944 MegamorphicCache::offsetOfEntries()),
2947 computeEffectiveAddress(BaseIndex(outEntryPtr
, outEntryPtr
, TimesTwo
),
2949 computeEffectiveAddress(BaseIndex(scratch2
, outEntryPtr
, TimesEight
,
2950 MegamorphicCache::offsetOfEntries()),
2954 // if (outEntryPtr->key_ != scratch1) goto cacheMissWithEntry
2955 branchPtr(Assembler::NotEqual
,
2956 Address(outEntryPtr
, MegamorphicCache::Entry::offsetOfKey()),
2957 scratch1
, cacheMissWithEntry
);
2958 loadPtr(Address(obj
, JSObject::offsetOfShape()), scratch1
);
2960 // if (outEntryPtr->shape_ != scratch1) goto cacheMissWithEntry
2961 branchPtr(Assembler::NotEqual
,
2962 Address(outEntryPtr
, MegamorphicCache::Entry::offsetOfShape()),
2963 scratch1
, cacheMissWithEntry
);
2965 // scratch2 = scratch2->generation_
2966 load16ZeroExtend(Address(scratch2
, MegamorphicCache::offsetOfGeneration()),
2969 Address(outEntryPtr
, MegamorphicCache::Entry::offsetOfGeneration()),
2971 // if (outEntryPtr->generation_ != scratch2) goto cacheMissWithEntry
2972 branch32(Assembler::NotEqual
, scratch1
, scratch2
, cacheMissWithEntry
);
2975 void MacroAssembler::emitMegamorphicCacheLookup(
2976 PropertyKey id
, Register obj
, Register scratch1
, Register scratch2
,
2977 Register outEntryPtr
, ValueOperand output
, Label
* cacheHit
) {
2978 Label cacheMiss
, isMissing
, dynamicSlot
, protoLoopHead
, protoLoopTail
;
2980 // scratch1 = obj->shape()
2981 loadPtr(Address(obj
, JSObject::offsetOfShape()), scratch1
);
2983 movePtr(scratch1
, outEntryPtr
);
2984 movePtr(scratch1
, scratch2
);
2986 // outEntryPtr = (scratch1 >> 3) ^ (scratch1 >> 13) + hash(id)
2987 rshiftPtr(Imm32(MegamorphicCache::ShapeHashShift1
), outEntryPtr
);
2988 rshiftPtr(Imm32(MegamorphicCache::ShapeHashShift2
), scratch2
);
2989 xorPtr(scratch2
, outEntryPtr
);
2990 addPtr(Imm32(HashAtomOrSymbolPropertyKey(id
)), outEntryPtr
);
2992 // outEntryPtr %= MegamorphicCache::NumEntries
2993 constexpr size_t cacheSize
= MegamorphicCache::NumEntries
;
2994 static_assert(mozilla::IsPowerOfTwo(cacheSize
));
2995 size_t cacheMask
= cacheSize
- 1;
2996 and32(Imm32(cacheMask
), outEntryPtr
);
2998 loadMegamorphicCache(scratch2
);
2999 // outEntryPtr = &scratch2->entries_[outEntryPtr]
3000 constexpr size_t entrySize
= sizeof(MegamorphicCache::Entry
);
3001 static_assert(sizeof(void*) == 4 || entrySize
== 24);
3002 if constexpr (sizeof(void*) == 4) {
3003 mul32(Imm32(entrySize
), outEntryPtr
);
3004 computeEffectiveAddress(BaseIndex(scratch2
, outEntryPtr
, TimesOne
,
3005 MegamorphicCache::offsetOfEntries()),
3008 computeEffectiveAddress(BaseIndex(outEntryPtr
, outEntryPtr
, TimesTwo
),
3010 computeEffectiveAddress(BaseIndex(scratch2
, outEntryPtr
, TimesEight
,
3011 MegamorphicCache::offsetOfEntries()),
3015 // if (outEntryPtr->shape_ != scratch1) goto cacheMiss
3016 branchPtr(Assembler::NotEqual
,
3017 Address(outEntryPtr
, MegamorphicCache::Entry::offsetOfShape()),
3018 scratch1
, &cacheMiss
);
3020 // if (outEntryPtr->key_ != id) goto cacheMiss
3021 movePropertyKey(id
, scratch1
);
3022 branchPtr(Assembler::NotEqual
,
3023 Address(outEntryPtr
, MegamorphicCache::Entry::offsetOfKey()),
3024 scratch1
, &cacheMiss
);
3026 // scratch2 = scratch2->generation_
3027 load16ZeroExtend(Address(scratch2
, MegamorphicCache::offsetOfGeneration()),
3030 Address(outEntryPtr
, MegamorphicCache::Entry::offsetOfGeneration()),
3032 // if (outEntryPtr->generation_ != scratch2) goto cacheMiss
3033 branch32(Assembler::NotEqual
, scratch1
, scratch2
, &cacheMiss
);
3035 emitExtractValueFromMegamorphicCacheEntry(
3036 obj
, outEntryPtr
, scratch1
, scratch2
, output
, cacheHit
, &cacheMiss
);
3041 template <typename IdOperandType
>
3042 void MacroAssembler::emitMegamorphicCacheLookupByValue(
3043 IdOperandType id
, Register obj
, Register scratch1
, Register scratch2
,
3044 Register outEntryPtr
, ValueOperand output
, Label
* cacheHit
) {
3045 Label cacheMiss
, cacheMissWithEntry
;
3046 emitMegamorphicCacheLookupByValueCommon(id
, obj
, scratch1
, scratch2
,
3047 outEntryPtr
, &cacheMiss
,
3048 &cacheMissWithEntry
);
3049 emitExtractValueFromMegamorphicCacheEntry(obj
, outEntryPtr
, scratch1
,
3050 scratch2
, output
, cacheHit
,
3051 &cacheMissWithEntry
);
3053 xorPtr(outEntryPtr
, outEntryPtr
);
3054 bind(&cacheMissWithEntry
);
3057 template void MacroAssembler::emitMegamorphicCacheLookupByValue
<ValueOperand
>(
3058 ValueOperand id
, Register obj
, Register scratch1
, Register scratch2
,
3059 Register outEntryPtr
, ValueOperand output
, Label
* cacheHit
);
3061 template void MacroAssembler::emitMegamorphicCacheLookupByValue
<Register
>(
3062 Register id
, Register obj
, Register scratch1
, Register scratch2
,
3063 Register outEntryPtr
, ValueOperand output
, Label
* cacheHit
);
3065 void MacroAssembler::emitMegamorphicCacheLookupExists(
3066 ValueOperand id
, Register obj
, Register scratch1
, Register scratch2
,
3067 Register outEntryPtr
, Register output
, Label
* cacheHit
, bool hasOwn
) {
3068 Label cacheMiss
, cacheMissWithEntry
, cacheHitFalse
;
3069 emitMegamorphicCacheLookupByValueCommon(id
, obj
, scratch1
, scratch2
,
3070 outEntryPtr
, &cacheMiss
,
3071 &cacheMissWithEntry
);
3073 // scratch1 = outEntryPtr->hopsAndKind_
3075 Address(outEntryPtr
, MegamorphicCache::Entry::offsetOfHopsAndKind()),
3078 branch32(Assembler::Equal
, scratch1
,
3079 Imm32(MegamorphicCache::Entry::NumHopsForMissingProperty
),
3081 branchTest32(Assembler::NonZero
, scratch1
,
3082 Imm32(MegamorphicCache::Entry::NonDataPropertyFlag
),
3083 &cacheMissWithEntry
);
3086 branch32(Assembler::NotEqual
, scratch1
, Imm32(0), &cacheHitFalse
);
3089 move32(Imm32(1), output
);
3092 bind(&cacheHitFalse
);
3093 xor32(output
, output
);
3097 xorPtr(outEntryPtr
, outEntryPtr
);
3098 bind(&cacheMissWithEntry
);
3101 void MacroAssembler::extractCurrentIndexAndKindFromIterator(Register iterator
,
3104 // Load iterator object
3105 Address
nativeIterAddr(iterator
,
3106 PropertyIteratorObject::offsetOfIteratorSlot());
3107 loadPrivate(nativeIterAddr
, outIndex
);
3109 // Compute offset of propertyCursor_ from propertiesBegin()
3110 loadPtr(Address(outIndex
, NativeIterator::offsetOfPropertyCursor()), outKind
);
3111 subPtr(Address(outIndex
, NativeIterator::offsetOfShapesEnd()), outKind
);
3113 // Compute offset of current index from indicesBegin(). Note that because
3114 // propertyCursor has already been incremented, this is actually the offset
3115 // of the next index. We adjust accordingly below.
3116 size_t indexAdjustment
=
3117 sizeof(GCPtr
<JSLinearString
*>) / sizeof(PropertyIndex
);
3118 if (indexAdjustment
!= 1) {
3119 MOZ_ASSERT(indexAdjustment
== 2);
3120 rshift32(Imm32(1), outKind
);
3123 // Load current index.
3124 loadPtr(Address(outIndex
, NativeIterator::offsetOfPropertiesEnd()), outIndex
);
3125 load32(BaseIndex(outIndex
, outKind
, Scale::TimesOne
,
3126 -int32_t(sizeof(PropertyIndex
))),
3130 move32(outIndex
, outKind
);
3131 rshift32(Imm32(PropertyIndex::KindShift
), outKind
);
3134 and32(Imm32(PropertyIndex::IndexMask
), outIndex
);
3137 template <typename IdType
>
3138 void MacroAssembler::emitMegamorphicCachedSetSlot(
3139 IdType id
, Register obj
, Register scratch1
,
3140 #ifndef JS_CODEGEN_X86 // See MegamorphicSetElement in LIROps.yaml
3141 Register scratch2
, Register scratch3
,
3143 ValueOperand value
, const LiveRegisterSet
& liveRegs
, Label
* cacheHit
,
3144 void (*emitPreBarrier
)(MacroAssembler
&, const Address
&, MIRType
)) {
3145 Label cacheMiss
, dynamicSlot
, doAdd
, doSet
, doAddDynamic
, doSetDynamic
;
3147 #ifdef JS_CODEGEN_X86
3149 Register scratch2
= value
.typeReg();
3150 Register scratch3
= value
.payloadReg();
3153 // outEntryPtr = obj->shape()
3154 loadPtr(Address(obj
, JSObject::offsetOfShape()), scratch3
);
3156 movePtr(scratch3
, scratch2
);
3158 // scratch3 = (scratch3 >> 3) ^ (scratch3 >> 13) + idHash
3159 rshiftPtr(Imm32(MegamorphicSetPropCache::ShapeHashShift1
), scratch3
);
3160 rshiftPtr(Imm32(MegamorphicSetPropCache::ShapeHashShift2
), scratch2
);
3161 xorPtr(scratch2
, scratch3
);
3163 if constexpr (std::is_same
<IdType
, ValueOperand
>::value
) {
3164 loadAtomOrSymbolAndHash(id
, scratch1
, scratch2
, &cacheMiss
);
3165 addPtr(scratch2
, scratch3
);
3167 static_assert(std::is_same
<IdType
, PropertyKey
>::value
);
3168 addPtr(Imm32(HashAtomOrSymbolPropertyKey(id
)), scratch3
);
3169 movePropertyKey(id
, scratch1
);
3172 // scratch3 %= MegamorphicSetPropCache::NumEntries
3173 constexpr size_t cacheSize
= MegamorphicSetPropCache::NumEntries
;
3174 static_assert(mozilla::IsPowerOfTwo(cacheSize
));
3175 size_t cacheMask
= cacheSize
- 1;
3176 and32(Imm32(cacheMask
), scratch3
);
3178 loadMegamorphicSetPropCache(scratch2
);
3179 // scratch3 = &scratch2->entries_[scratch3]
3180 constexpr size_t entrySize
= sizeof(MegamorphicSetPropCache::Entry
);
3181 mul32(Imm32(entrySize
), scratch3
);
3182 computeEffectiveAddress(BaseIndex(scratch2
, scratch3
, TimesOne
,
3183 MegamorphicSetPropCache::offsetOfEntries()),
3186 // if (scratch3->key_ != scratch1) goto cacheMiss
3187 branchPtr(Assembler::NotEqual
,
3188 Address(scratch3
, MegamorphicSetPropCache::Entry::offsetOfKey()),
3189 scratch1
, &cacheMiss
);
3191 loadPtr(Address(obj
, JSObject::offsetOfShape()), scratch1
);
3192 // if (scratch3->shape_ != scratch1) goto cacheMiss
3193 branchPtr(Assembler::NotEqual
,
3194 Address(scratch3
, MegamorphicSetPropCache::Entry::offsetOfShape()),
3195 scratch1
, &cacheMiss
);
3197 // scratch2 = scratch2->generation_
3199 Address(scratch2
, MegamorphicSetPropCache::offsetOfGeneration()),
3202 Address(scratch3
, MegamorphicSetPropCache::Entry::offsetOfGeneration()),
3204 // if (scratch3->generation_ != scratch2) goto cacheMiss
3205 branch32(Assembler::NotEqual
, scratch1
, scratch2
, &cacheMiss
);
3207 // scratch2 = entry->slotOffset()
3209 Address(scratch3
, MegamorphicSetPropCache::Entry::offsetOfSlotOffset()),
3212 // scratch1 = slotOffset.offset()
3213 move32(scratch2
, scratch1
);
3214 rshift32(Imm32(TaggedSlotOffset::OffsetShift
), scratch1
);
3216 Address
afterShapePtr(scratch3
,
3217 MegamorphicSetPropCache::Entry::offsetOfAfterShape());
3219 // if (!slotOffset.isFixedSlot()) goto dynamicSlot
3220 branchTest32(Assembler::Zero
, scratch2
,
3221 Imm32(TaggedSlotOffset::IsFixedSlotFlag
), &dynamicSlot
);
3223 // Calculate slot address in scratch1. Jump to doSet if scratch3 == nullptr,
3224 // else jump (or fall-through) to doAdd.
3225 addPtr(obj
, scratch1
);
3226 branchPtr(Assembler::Equal
, afterShapePtr
, ImmPtr(nullptr), &doSet
);
3230 branchPtr(Assembler::Equal
, afterShapePtr
, ImmPtr(nullptr), &doSetDynamic
);
3232 Address
slotAddr(scratch1
, 0);
3234 // If entry->newCapacity_ is nonzero, we need to grow the slots on the
3235 // object. Otherwise just jump straight to a dynamic add.
3237 Address(scratch3
, MegamorphicSetPropCache::Entry::offsetOfNewCapacity()),
3239 branchTest32(Assembler::Zero
, scratch2
, scratch2
, &doAddDynamic
);
3241 LiveRegisterSet save
;
3242 save
.set() = RegisterSet::Intersect(liveRegs
.set(), RegisterSet::Volatile());
3243 save
.addUnchecked(scratch1
); // Used as call temp below.
3244 save
.takeUnchecked(scratch2
); // Used for the return value.
3245 PushRegsInMask(save
);
3247 using Fn
= bool (*)(JSContext
* cx
, NativeObject
* obj
, uint32_t newCount
);
3248 setupUnalignedABICall(scratch1
);
3249 loadJSContext(scratch1
);
3250 passABIArg(scratch1
);
3252 passABIArg(scratch2
);
3253 callWithABI
<Fn
, NativeObject::growSlotsPure
>();
3254 storeCallPointerResult(scratch2
);
3256 MOZ_ASSERT(!save
.has(scratch2
));
3257 PopRegsInMask(save
);
3259 branchIfFalseBool(scratch2
, &cacheMiss
);
3261 bind(&doAddDynamic
);
3262 addPtr(Address(obj
, NativeObject::offsetOfSlots()), scratch1
);
3265 // scratch3 = entry->afterShape()
3267 Address(scratch3
, MegamorphicSetPropCache::Entry::offsetOfAfterShape()),
3270 storeObjShape(scratch3
, obj
,
3271 [emitPreBarrier
](MacroAssembler
& masm
, const Address
& addr
) {
3272 emitPreBarrier(masm
, addr
, MIRType::Shape
);
3274 #ifdef JS_CODEGEN_X86
3277 storeValue(value
, slotAddr
);
3280 bind(&doSetDynamic
);
3281 addPtr(Address(obj
, NativeObject::offsetOfSlots()), scratch1
);
3283 guardedCallPreBarrier(slotAddr
, MIRType::Value
);
3285 #ifdef JS_CODEGEN_X86
3288 storeValue(value
, slotAddr
);
3292 #ifdef JS_CODEGEN_X86
3297 template void MacroAssembler::emitMegamorphicCachedSetSlot
<PropertyKey
>(
3298 PropertyKey id
, Register obj
, Register scratch1
,
3299 #ifndef JS_CODEGEN_X86 // See MegamorphicSetElement in LIROps.yaml
3300 Register scratch2
, Register scratch3
,
3302 ValueOperand value
, const LiveRegisterSet
& liveRegs
, Label
* cacheHit
,
3303 void (*emitPreBarrier
)(MacroAssembler
&, const Address
&, MIRType
));
3305 template void MacroAssembler::emitMegamorphicCachedSetSlot
<ValueOperand
>(
3306 ValueOperand id
, Register obj
, Register scratch1
,
3307 #ifndef JS_CODEGEN_X86 // See MegamorphicSetElement in LIROps.yaml
3308 Register scratch2
, Register scratch3
,
3310 ValueOperand value
, const LiveRegisterSet
& liveRegs
, Label
* cacheHit
,
3311 void (*emitPreBarrier
)(MacroAssembler
&, const Address
&, MIRType
));
3313 void MacroAssembler::guardNonNegativeIntPtrToInt32(Register reg
, Label
* fail
) {
3316 branchPtr(Assembler::NotSigned
, reg
, reg
, &ok
);
3317 assumeUnreachable("Unexpected negative value");
3322 branchPtr(Assembler::Above
, reg
, Imm32(INT32_MAX
), fail
);
3326 void MacroAssembler::loadArrayBufferByteLengthIntPtr(Register obj
,
3328 Address
slotAddr(obj
, ArrayBufferObject::offsetOfByteLengthSlot());
3329 loadPrivate(slotAddr
, output
);
3332 void MacroAssembler::loadArrayBufferViewByteOffsetIntPtr(Register obj
,
3334 Address
slotAddr(obj
, ArrayBufferViewObject::byteOffsetOffset());
3335 loadPrivate(slotAddr
, output
);
3338 void MacroAssembler::loadArrayBufferViewLengthIntPtr(Register obj
,
3340 Address
slotAddr(obj
, ArrayBufferViewObject::lengthOffset());
3341 loadPrivate(slotAddr
, output
);
3344 void MacroAssembler::loadGrowableSharedArrayBufferByteLengthIntPtr(
3345 Synchronization sync
, Register obj
, Register output
) {
3346 // Load the SharedArrayRawBuffer.
3347 loadPrivate(Address(obj
, SharedArrayBufferObject::rawBufferOffset()), output
);
3349 memoryBarrierBefore(sync
);
3351 // Load the byteLength of the SharedArrayRawBuffer into |output|.
3352 static_assert(sizeof(mozilla::Atomic
<size_t>) == sizeof(size_t));
3353 loadPtr(Address(output
, SharedArrayRawBuffer::offsetOfByteLength()), output
);
3355 memoryBarrierAfter(sync
);
3358 void MacroAssembler::loadResizableArrayBufferViewLengthIntPtr(
3359 ResizableArrayBufferView view
, Synchronization sync
, Register obj
,
3360 Register output
, Register scratch
) {
3361 // Inline implementation of ArrayBufferViewObject::length(), when the input is
3362 // guaranteed to be a resizable arraybuffer view object.
3364 loadArrayBufferViewLengthIntPtr(obj
, output
);
3367 branchPtr(Assembler::NotEqual
, output
, ImmWord(0), &done
);
3369 // Load obj->elements in |scratch|.
3370 loadPtr(Address(obj
, NativeObject::offsetOfElements()), scratch
);
3372 // If backed by non-shared memory, detached and out-of-bounds both return
3373 // zero, so we're done here.
3374 branchTest32(Assembler::Zero
,
3375 Address(scratch
, ObjectElements::offsetOfFlags()),
3376 Imm32(ObjectElements::SHARED_MEMORY
), &done
);
3378 // Load the auto-length slot.
3379 unboxBoolean(Address(obj
, ArrayBufferViewObject::autoLengthOffset()),
3382 // If non-auto length, there's nothing to do.
3383 branchTest32(Assembler::Zero
, scratch
, scratch
, &done
);
3385 // Load bufferByteLength into |output|.
3387 // Resizable TypedArrays are guaranteed to have an ArrayBuffer.
3388 unboxObject(Address(obj
, ArrayBufferViewObject::bufferOffset()), output
);
3390 // Load the byte length from the raw-buffer of growable SharedArrayBuffers.
3391 loadGrowableSharedArrayBufferByteLengthIntPtr(sync
, output
, output
);
3394 // Load the byteOffset into |scratch|.
3395 loadArrayBufferViewByteOffsetIntPtr(obj
, scratch
);
3397 // Compute the accessible byte length |bufferByteLength - byteOffset|.
3398 subPtr(scratch
, output
);
3400 if (view
== ResizableArrayBufferView::TypedArray
) {
3401 // Compute the array length from the byte length.
3402 resizableTypedArrayElementShiftBy(obj
, output
, scratch
);
3408 void MacroAssembler::loadResizableTypedArrayByteOffsetMaybeOutOfBoundsIntPtr(
3409 Register obj
, Register output
, Register scratch
) {
3410 // Inline implementation of TypedArrayObject::byteOffsetMaybeOutOfBounds(),
3411 // when the input is guaranteed to be a resizable typed array object.
3413 loadArrayBufferViewByteOffsetIntPtr(obj
, output
);
3415 // TypedArray is neither detached nor out-of-bounds when byteOffset non-zero.
3417 branchPtr(Assembler::NotEqual
, output
, ImmWord(0), &done
);
3419 // We're done when the initial byteOffset is zero.
3420 loadPrivate(Address(obj
, ArrayBufferViewObject::initialByteOffsetOffset()),
3422 branchPtr(Assembler::Equal
, output
, ImmWord(0), &done
);
3424 // If the buffer is attached, return initialByteOffset.
3425 branchIfHasAttachedArrayBuffer(obj
, scratch
, &done
);
3427 // Otherwise return zero to match the result for fixed-length TypedArrays.
3428 movePtr(ImmWord(0), output
);
3433 void MacroAssembler::dateFillLocalTimeSlots(
3434 Register obj
, Register scratch
, const LiveRegisterSet
& volatileRegs
) {
3435 // Inline implementation of the cache check from
3436 // DateObject::fillLocalTimeSlots().
3440 // Check if the cache is already populated.
3441 branchTestUndefined(Assembler::Equal
,
3442 Address(obj
, DateObject::offsetOfLocalTimeSlot()),
3445 unboxInt32(Address(obj
, DateObject::offsetOfUTCTimeZoneOffsetSlot()),
3448 branch32(Assembler::Equal
,
3449 AbsoluteAddress(DateTimeInfo::addressOfUTCToLocalOffsetSeconds()),
3454 PushRegsInMask(volatileRegs
);
3456 using Fn
= void (*)(DateObject
*);
3457 setupUnalignedABICall(scratch
);
3459 callWithABI
<Fn
, jit::DateFillLocalTimeSlots
>();
3461 PopRegsInMask(volatileRegs
);
3467 void MacroAssembler::udiv32ByConstant(Register src
, uint32_t divisor
,
3469 auto rmc
= ReciprocalMulConstants::computeUnsignedDivisionConstants(divisor
);
3470 MOZ_ASSERT(rmc
.multiplier
<= UINT32_MAX
, "division needs scratch register");
3472 // We first compute |q = (M * n) >> 32), where M = rmc.multiplier.
3473 mulHighUnsigned32(Imm32(rmc
.multiplier
), src
, dest
);
3475 // Finish the computation |q = floor(n / d)|.
3476 rshift32(Imm32(rmc
.shiftAmount
), dest
);
3479 void MacroAssembler::umod32ByConstant(Register src
, uint32_t divisor
,
3480 Register dest
, Register scratch
) {
3481 MOZ_ASSERT(dest
!= scratch
);
3483 auto rmc
= ReciprocalMulConstants::computeUnsignedDivisionConstants(divisor
);
3484 MOZ_ASSERT(rmc
.multiplier
<= UINT32_MAX
, "division needs scratch register");
3490 // We first compute |q = (M * n) >> 32), where M = rmc.multiplier.
3491 mulHighUnsigned32(Imm32(rmc
.multiplier
), dest
, scratch
);
3493 // Finish the computation |q = floor(n / d)|.
3494 rshift32(Imm32(rmc
.shiftAmount
), scratch
);
3496 // Compute the remainder from |r = n - q * d|.
3497 mul32(Imm32(divisor
), scratch
);
3498 sub32(scratch
, dest
);
3501 template <typename GetTimeFn
>
3502 void MacroAssembler::dateTimeFromSecondsIntoYear(ValueOperand secondsIntoYear
,
3503 ValueOperand output
,
3506 GetTimeFn getTimeFn
) {
3509 branchTestInt32(Assembler::Equal
, secondsIntoYear
, &okValue
);
3510 branchTestValue(Assembler::Equal
, secondsIntoYear
, JS::NaNValue(), &okValue
);
3511 assumeUnreachable("secondsIntoYear is an int32 or NaN");
3515 moveValue(secondsIntoYear
, output
);
3518 fallibleUnboxInt32(secondsIntoYear
, scratch1
, &done
);
3522 branchTest32(Assembler::NotSigned
, scratch1
, scratch1
, &okInt
);
3523 assumeUnreachable("secondsIntoYear is an unsigned int32");
3527 getTimeFn(scratch1
, scratch1
, scratch2
);
3529 tagValue(JSVAL_TYPE_INT32
, scratch1
, output
);
3534 void MacroAssembler::dateHoursFromSecondsIntoYear(ValueOperand secondsIntoYear
,
3535 ValueOperand output
,
3537 Register scratch2
) {
3538 // Inline implementation of seconds-into-year to local hours computation from
3541 // Compute `(yearSeconds / SecondsPerHour) % HoursPerDay`.
3542 auto hoursFromSecondsIntoYear
= [this](Register src
, Register dest
,
3544 udiv32ByConstant(src
, SecondsPerHour
, dest
);
3545 umod32ByConstant(dest
, HoursPerDay
, dest
, scratch
);
3548 dateTimeFromSecondsIntoYear(secondsIntoYear
, output
, scratch1
, scratch2
,
3549 hoursFromSecondsIntoYear
);
3552 void MacroAssembler::dateMinutesFromSecondsIntoYear(
3553 ValueOperand secondsIntoYear
, ValueOperand output
, Register scratch1
,
3554 Register scratch2
) {
3555 // Inline implementation of seconds-into-year to local minutes computation
3556 // from date_getMinutes.
3558 // Compute `(yearSeconds / SecondsPerMinute) % MinutesPerHour`.
3559 auto minutesFromSecondsIntoYear
= [this](Register src
, Register dest
,
3561 udiv32ByConstant(src
, SecondsPerMinute
, dest
);
3562 umod32ByConstant(dest
, MinutesPerHour
, dest
, scratch
);
3565 dateTimeFromSecondsIntoYear(secondsIntoYear
, output
, scratch1
, scratch2
,
3566 minutesFromSecondsIntoYear
);
3569 void MacroAssembler::dateSecondsFromSecondsIntoYear(
3570 ValueOperand secondsIntoYear
, ValueOperand output
, Register scratch1
,
3571 Register scratch2
) {
3572 // Inline implementation of seconds-into-year to local seconds computation
3573 // from date_getSeconds.
3575 // Compute `yearSeconds % SecondsPerMinute`.
3576 auto secondsFromSecondsIntoYear
= [this](Register src
, Register dest
,
3578 umod32ByConstant(src
, SecondsPerMinute
, dest
, scratch
);
3581 dateTimeFromSecondsIntoYear(secondsIntoYear
, output
, scratch1
, scratch2
,
3582 secondsFromSecondsIntoYear
);
3585 void MacroAssembler::computeImplicitThis(Register env
, ValueOperand output
,
3587 // Inline implementation of ComputeImplicitThis.
3589 Register scratch
= output
.scratchReg();
3590 MOZ_ASSERT(scratch
!= env
);
3592 loadObjClassUnsafe(env
, scratch
);
3594 // Go to the slow path for possible debug environment proxies.
3595 branchTestClassIsProxy(true, scratch
, slowPath
);
3597 // WithEnvironmentObjects have an actual implicit |this|.
3598 Label nonWithEnv
, done
;
3599 branchPtr(Assembler::NotEqual
, scratch
,
3600 ImmPtr(&WithEnvironmentObject::class_
), &nonWithEnv
);
3602 if (JitOptions
.spectreObjectMitigations
) {
3603 spectreZeroRegister(Assembler::NotEqual
, scratch
, env
);
3606 loadValue(Address(env
, WithEnvironmentObject::offsetOfThisSlot()), output
);
3612 // The implicit |this| is |undefined| for all environment types except
3613 // WithEnvironmentObject.
3614 moveValue(JS::UndefinedValue(), output
);
3619 void MacroAssembler::loadDOMExpandoValueGuardGeneration(
3620 Register obj
, ValueOperand output
,
3621 JS::ExpandoAndGeneration
* expandoAndGeneration
, uint64_t generation
,
3623 loadPtr(Address(obj
, ProxyObject::offsetOfReservedSlots()),
3624 output
.scratchReg());
3625 loadValue(Address(output
.scratchReg(),
3626 js::detail::ProxyReservedSlots::offsetOfPrivateSlot()),
3629 // Guard the ExpandoAndGeneration* matches the proxy's ExpandoAndGeneration
3631 branchTestValue(Assembler::NotEqual
, output
,
3632 PrivateValue(expandoAndGeneration
), fail
);
3634 // Guard expandoAndGeneration->generation matches the expected generation.
3635 Address
generationAddr(output
.payloadOrValueReg(),
3636 JS::ExpandoAndGeneration::offsetOfGeneration());
3637 branch64(Assembler::NotEqual
, generationAddr
, Imm64(generation
), fail
);
3639 // Load expandoAndGeneration->expando into the output Value register.
3640 loadValue(Address(output
.payloadOrValueReg(),
3641 JS::ExpandoAndGeneration::offsetOfExpando()),
3645 void MacroAssembler::loadJitActivation(Register dest
) {
3646 loadJSContext(dest
);
3647 loadPtr(Address(dest
, offsetof(JSContext
, activation_
)), dest
);
3650 void MacroAssembler::guardSpecificAtom(Register str
, JSAtom
* atom
,
3652 const LiveRegisterSet
& volatileRegs
,
3654 Label done
, notCachedAtom
;
3655 branchPtr(Assembler::Equal
, str
, ImmGCPtr(atom
), &done
);
3657 // The pointers are not equal, so if the input string is also an atom it
3658 // must be a different string.
3659 branchTest32(Assembler::NonZero
, Address(str
, JSString::offsetOfFlags()),
3660 Imm32(JSString::ATOM_BIT
), fail
);
3662 // Try to do a cheap atomize on the string and repeat the above test
3663 tryFastAtomize(str
, scratch
, scratch
, ¬CachedAtom
);
3664 branchPtr(Assembler::Equal
, scratch
, ImmGCPtr(atom
), &done
);
3666 bind(¬CachedAtom
);
3668 // Check the length.
3669 branch32(Assembler::NotEqual
, Address(str
, JSString::offsetOfLength()),
3670 Imm32(atom
->length()), fail
);
3672 // Compare short atoms using inline assembly.
3673 if (canCompareStringCharsInline(atom
)) {
3674 // Pure two-byte strings can't be equal to Latin-1 strings.
3675 if (atom
->hasTwoByteChars()) {
3676 JS::AutoCheckCannotGC nogc
;
3677 if (!mozilla::IsUtf16Latin1(atom
->twoByteRange(nogc
))) {
3678 branchLatin1String(str
, fail
);
3682 // Call into the VM when the input is a rope or has a different encoding.
3685 // Load the input string's characters.
3686 Register stringChars
= scratch
;
3687 loadStringCharsForCompare(str
, atom
, stringChars
, &vmCall
);
3689 // Start comparing character by character.
3690 branchIfNotStringCharsEquals(stringChars
, atom
, fail
);
3692 // Falls through if both strings are equal.
3698 // We have a non-atomized string with the same length. Call a helper
3699 // function to do the comparison.
3700 PushRegsInMask(volatileRegs
);
3702 using Fn
= bool (*)(JSString
* str1
, JSString
* str2
);
3703 setupUnalignedABICall(scratch
);
3704 movePtr(ImmGCPtr(atom
), scratch
);
3705 passABIArg(scratch
);
3707 callWithABI
<Fn
, EqualStringsHelperPure
>();
3708 storeCallPointerResult(scratch
);
3710 MOZ_ASSERT(!volatileRegs
.has(scratch
));
3711 PopRegsInMask(volatileRegs
);
3712 branchIfFalseBool(scratch
, fail
);
3717 void MacroAssembler::guardStringToInt32(Register str
, Register output
,
3719 LiveRegisterSet volatileRegs
,
3722 // Use indexed value as fast path if possible.
3723 loadStringIndexValue(str
, output
, &vmCall
);
3728 // Reserve space for holding the result int32_t of the call. Use
3729 // pointer-size to avoid misaligning the stack on 64-bit platforms.
3730 reserveStack(sizeof(uintptr_t));
3731 moveStackPtrTo(output
);
3733 volatileRegs
.takeUnchecked(scratch
);
3734 if (output
.volatile_()) {
3735 volatileRegs
.addUnchecked(output
);
3737 PushRegsInMask(volatileRegs
);
3739 using Fn
= bool (*)(JSContext
* cx
, JSString
* str
, int32_t* result
);
3740 setupUnalignedABICall(scratch
);
3741 loadJSContext(scratch
);
3742 passABIArg(scratch
);
3745 callWithABI
<Fn
, GetInt32FromStringPure
>();
3746 storeCallPointerResult(scratch
);
3748 PopRegsInMask(volatileRegs
);
3751 branchIfTrueBool(scratch
, &ok
);
3753 // OOM path, recovered by GetInt32FromStringPure.
3755 // Use addToStackPtr instead of freeStack as freeStack tracks stack height
3756 // flow-insensitively, and using it twice would confuse the stack height
3758 addToStackPtr(Imm32(sizeof(uintptr_t)));
3762 load32(Address(output
, 0), output
);
3763 freeStack(sizeof(uintptr_t));
3768 void MacroAssembler::generateBailoutTail(Register scratch
,
3769 Register bailoutInfo
) {
3770 Label bailoutFailed
;
3771 branchIfFalseBool(ReturnReg
, &bailoutFailed
);
3773 // Finish bailing out to Baseline.
3775 // Prepare a register set for use in this case.
3776 AllocatableGeneralRegisterSet
regs(GeneralRegisterSet::All());
3777 MOZ_ASSERT_IF(!IsHiddenSP(getStackPointer()),
3778 !regs
.has(AsRegister(getStackPointer())));
3779 regs
.take(bailoutInfo
);
3781 Register temp
= regs
.takeAny();
3784 // Assert the stack pointer points to the JitFrameLayout header. Copying
3787 loadPtr(Address(bailoutInfo
, offsetof(BaselineBailoutInfo
, incomingStack
)),
3789 branchStackPtr(Assembler::Equal
, temp
, &ok
);
3790 assumeUnreachable("Unexpected stack pointer value");
3794 Register copyCur
= regs
.takeAny();
3795 Register copyEnd
= regs
.takeAny();
3797 // Copy data onto stack.
3798 loadPtr(Address(bailoutInfo
, offsetof(BaselineBailoutInfo
, copyStackTop
)),
3801 Address(bailoutInfo
, offsetof(BaselineBailoutInfo
, copyStackBottom
)),
3807 branchPtr(Assembler::BelowOrEqual
, copyCur
, copyEnd
, &endOfCopy
);
3808 subPtr(Imm32(sizeof(uintptr_t)), copyCur
);
3809 subFromStackPtr(Imm32(sizeof(uintptr_t)));
3810 loadPtr(Address(copyCur
, 0), temp
);
3811 storePtr(temp
, Address(getStackPointer(), 0));
3816 loadPtr(Address(bailoutInfo
, offsetof(BaselineBailoutInfo
, resumeFramePtr
)),
3819 // Enter exit frame for the FinishBailoutToBaseline call.
3820 pushFrameDescriptor(FrameType::BaselineJS
);
3821 push(Address(bailoutInfo
, offsetof(BaselineBailoutInfo
, resumeAddr
)));
3823 // No GC things to mark on the stack, push a bare token.
3824 loadJSContext(scratch
);
3825 enterFakeExitFrame(scratch
, scratch
, ExitFrameType::Bare
);
3827 // Save needed values onto stack temporarily.
3828 push(Address(bailoutInfo
, offsetof(BaselineBailoutInfo
, resumeAddr
)));
3830 // Call a stub to free allocated memory and create arguments objects.
3831 using Fn
= bool (*)(BaselineBailoutInfo
* bailoutInfoArg
);
3832 setupUnalignedABICall(temp
);
3833 passABIArg(bailoutInfo
);
3834 callWithABI
<Fn
, FinishBailoutToBaseline
>(
3835 ABIType::General
, CheckUnsafeCallWithABI::DontCheckHasExitFrame
);
3836 branchIfFalseBool(ReturnReg
, exceptionLabel());
3838 // Restore values where they need to be and resume execution.
3839 AllocatableGeneralRegisterSet
enterRegs(GeneralRegisterSet::All());
3840 MOZ_ASSERT(!enterRegs
.has(FramePointer
));
3841 Register jitcodeReg
= enterRegs
.takeAny();
3845 // Discard exit frame.
3846 addToStackPtr(Imm32(ExitFrameLayout::SizeWithFooter()));
3851 bind(&bailoutFailed
);
3853 // jit::Bailout or jit::InvalidationBailout failed and returned false. The
3854 // Ion frame has already been discarded and the stack pointer points to the
3855 // JitFrameLayout header. Turn it into an ExitFrameLayout, similar to
3856 // EnsureUnwoundJitExitFrame, and call the exception handler.
3857 loadJSContext(scratch
);
3858 enterFakeExitFrame(scratch
, scratch
, ExitFrameType::UnwoundJit
);
3859 jump(exceptionLabel());
3863 void MacroAssembler::loadJitCodeRaw(Register func
, Register dest
) {
3864 static_assert(BaseScript::offsetOfJitCodeRaw() ==
3865 SelfHostedLazyScript::offsetOfJitCodeRaw(),
3866 "SelfHostedLazyScript and BaseScript must use same layout for "
3869 BaseScript::offsetOfJitCodeRaw() == wasm::JumpTableJitEntryOffset
,
3870 "Wasm exported functions jit entries must use same layout for "
3872 loadPrivate(Address(func
, JSFunction::offsetOfJitInfoOrScript()), dest
);
3873 loadPtr(Address(dest
, BaseScript::offsetOfJitCodeRaw()), dest
);
3876 void MacroAssembler::loadBaselineJitCodeRaw(Register func
, Register dest
,
3879 loadPrivate(Address(func
, JSFunction::offsetOfJitInfoOrScript()), dest
);
3881 branchIfScriptHasNoJitScript(dest
, failure
);
3883 loadJitScript(dest
, dest
);
3885 // Load BaselineScript
3886 loadPtr(Address(dest
, JitScript::offsetOfBaselineScript()), dest
);
3888 static_assert(BaselineDisabledScript
== 0x1);
3889 branchPtr(Assembler::BelowOrEqual
, dest
, ImmWord(BaselineDisabledScript
),
3893 // Load Baseline jitcode
3894 loadPtr(Address(dest
, BaselineScript::offsetOfMethod()), dest
);
3895 loadPtr(Address(dest
, JitCode::offsetOfCode()), dest
);
3898 void MacroAssembler::loadBaselineFramePtr(Register framePtr
, Register dest
) {
3899 if (framePtr
!= dest
) {
3900 movePtr(framePtr
, dest
);
3902 subPtr(Imm32(BaselineFrame::Size()), dest
);
3905 static const uint8_t* ContextInlinedICScriptPtr(CompileRuntime
* rt
) {
3906 return (static_cast<const uint8_t*>(rt
->mainContextPtr()) +
3907 JSContext::offsetOfInlinedICScript());
3910 void MacroAssembler::storeICScriptInJSContext(Register icScript
) {
3911 storePtr(icScript
, AbsoluteAddress(ContextInlinedICScriptPtr(runtime())));
3914 void MacroAssembler::handleFailure() {
3915 // Re-entry code is irrelevant because the exception will leave the
3916 // running function and never come back
3917 TrampolinePtr excTail
= runtime()->jitRuntime()->getExceptionTail();
3921 void MacroAssembler::assumeUnreachable(const char* output
) {
3922 #ifdef JS_MASM_VERBOSE
3923 if (!IsCompilingWasm()) {
3924 AllocatableRegisterSet
regs(RegisterSet::Volatile());
3925 LiveRegisterSet
save(regs
.asLiveSet());
3926 PushRegsInMask(save
);
3927 Register temp
= regs
.takeAnyGeneral();
3929 using Fn
= void (*)(const char* output
);
3930 setupUnalignedABICall(temp
);
3931 movePtr(ImmPtr(output
), temp
);
3933 callWithABI
<Fn
, AssumeUnreachable
>(ABIType::General
,
3934 CheckUnsafeCallWithABI::DontCheckOther
);
3936 PopRegsInMask(save
);
3943 void MacroAssembler::printf(const char* output
) {
3944 #ifdef JS_MASM_VERBOSE
3945 AllocatableRegisterSet
regs(RegisterSet::Volatile());
3946 LiveRegisterSet
save(regs
.asLiveSet());
3947 PushRegsInMask(save
);
3949 Register temp
= regs
.takeAnyGeneral();
3951 using Fn
= void (*)(const char* output
);
3952 setupUnalignedABICall(temp
);
3953 movePtr(ImmPtr(output
), temp
);
3955 callWithABI
<Fn
, Printf0
>();
3957 PopRegsInMask(save
);
3961 void MacroAssembler::printf(const char* output
, Register value
) {
3962 #ifdef JS_MASM_VERBOSE
3963 AllocatableRegisterSet
regs(RegisterSet::Volatile());
3964 LiveRegisterSet
save(regs
.asLiveSet());
3965 PushRegsInMask(save
);
3967 regs
.takeUnchecked(value
);
3969 Register temp
= regs
.takeAnyGeneral();
3971 using Fn
= void (*)(const char* output
, uintptr_t value
);
3972 setupUnalignedABICall(temp
);
3973 movePtr(ImmPtr(output
), temp
);
3976 callWithABI
<Fn
, Printf1
>();
3978 PopRegsInMask(save
);
3982 void MacroAssembler::convertInt32ValueToDouble(ValueOperand val
) {
3984 branchTestInt32(Assembler::NotEqual
, val
, &done
);
3985 ScratchDoubleScope
fpscratch(*this);
3986 convertInt32ToDouble(val
.payloadOrValueReg(), fpscratch
);
3987 boxDouble(fpscratch
, val
, fpscratch
);
3991 void MacroAssembler::convertValueToFloatingPoint(
3992 ValueOperand value
, FloatRegister output
, Register maybeTemp
,
3993 LiveRegisterSet volatileLiveRegs
, Label
* fail
,
3994 FloatingPointType outputType
) {
3995 Label isDouble
, isInt32OrBool
, isNull
, done
;
3998 ScratchTagScope
tag(*this, value
);
3999 splitTagForTest(value
, tag
);
4001 branchTestDouble(Assembler::Equal
, tag
, &isDouble
);
4002 branchTestInt32(Assembler::Equal
, tag
, &isInt32OrBool
);
4003 branchTestBoolean(Assembler::Equal
, tag
, &isInt32OrBool
);
4004 branchTestNull(Assembler::Equal
, tag
, &isNull
);
4005 branchTestUndefined(Assembler::NotEqual
, tag
, fail
);
4008 // fall-through: undefined
4009 if (outputType
== FloatingPointType::Float16
||
4010 outputType
== FloatingPointType::Float32
) {
4011 loadConstantFloat32(float(GenericNaN()), output
);
4013 loadConstantDouble(GenericNaN(), output
);
4018 if (outputType
== FloatingPointType::Float16
||
4019 outputType
== FloatingPointType::Float32
) {
4020 loadConstantFloat32(0.0f
, output
);
4022 loadConstantDouble(0.0, output
);
4026 bind(&isInt32OrBool
);
4027 if (outputType
== FloatingPointType::Float16
) {
4028 convertInt32ToFloat16(value
.payloadOrValueReg(), output
, maybeTemp
,
4030 } else if (outputType
== FloatingPointType::Float32
) {
4031 convertInt32ToFloat32(value
.payloadOrValueReg(), output
);
4033 convertInt32ToDouble(value
.payloadOrValueReg(), output
);
4037 // On some non-multiAlias platforms, unboxDouble may use the scratch register,
4038 // so do not merge code paths here.
4040 if ((outputType
== FloatingPointType::Float16
||
4041 outputType
== FloatingPointType::Float32
) &&
4043 ScratchDoubleScope
tmp(*this);
4044 unboxDouble(value
, tmp
);
4046 if (outputType
== FloatingPointType::Float16
) {
4047 convertDoubleToFloat16(tmp
, output
, maybeTemp
, volatileLiveRegs
);
4049 convertDoubleToFloat32(tmp
, output
);
4052 FloatRegister tmp
= output
.asDouble();
4053 unboxDouble(value
, tmp
);
4055 if (outputType
== FloatingPointType::Float16
) {
4056 convertDoubleToFloat16(tmp
, output
, maybeTemp
, volatileLiveRegs
);
4057 } else if (outputType
== FloatingPointType::Float32
) {
4058 convertDoubleToFloat32(tmp
, output
);
4065 void MacroAssembler::outOfLineTruncateSlow(FloatRegister src
, Register dest
,
4066 bool widenFloatToDouble
,
4068 wasm::BytecodeOffset callOffset
) {
4069 if (compilingWasm
) {
4072 int32_t framePushedAfterInstance
= framePushed();
4074 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \
4075 defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) || \
4076 defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64)
4077 ScratchDoubleScope
fpscratch(*this);
4078 if (widenFloatToDouble
) {
4079 convertFloat32ToDouble(src
, fpscratch
);
4082 #elif defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
4083 FloatRegister srcSingle
;
4084 if (widenFloatToDouble
) {
4085 MOZ_ASSERT(src
.isSingle());
4087 src
= src
.asDouble();
4089 convertFloat32ToDouble(srcSingle
, src
);
4093 MOZ_CRASH("MacroAssembler platform hook: outOfLineTruncateSlow");
4096 MOZ_ASSERT(src
.isDouble());
4098 if (compilingWasm
) {
4099 int32_t instanceOffset
= framePushed() - framePushedAfterInstance
;
4101 passABIArg(src
, ABIType::Float64
);
4102 callWithABI(callOffset
, wasm::SymbolicAddress::ToInt32
,
4103 mozilla::Some(instanceOffset
));
4105 using Fn
= int32_t (*)(double);
4106 setupUnalignedABICall(dest
);
4107 passABIArg(src
, ABIType::Float64
);
4108 callWithABI
<Fn
, JS::ToInt32
>(ABIType::General
,
4109 CheckUnsafeCallWithABI::DontCheckOther
);
4111 storeCallInt32Result(dest
);
4113 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \
4114 defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) || \
4115 defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64)
4117 #elif defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
4118 if (widenFloatToDouble
) {
4122 MOZ_CRASH("MacroAssembler platform hook: outOfLineTruncateSlow");
4125 if (compilingWasm
) {
4130 void MacroAssembler::convertDoubleToInt(FloatRegister src
, Register output
,
4131 FloatRegister temp
, Label
* truncateFail
,
4133 IntConversionBehavior behavior
) {
4135 case IntConversionBehavior::Normal
:
4136 case IntConversionBehavior::NegativeZeroCheck
:
4137 convertDoubleToInt32(
4139 behavior
== IntConversionBehavior::NegativeZeroCheck
);
4141 case IntConversionBehavior::Truncate
:
4142 branchTruncateDoubleMaybeModUint32(src
, output
,
4143 truncateFail
? truncateFail
: fail
);
4145 case IntConversionBehavior::ClampToUint8
:
4146 // Clamping clobbers the input register, so use a temp.
4148 moveDouble(src
, temp
);
4150 clampDoubleToUint8(temp
, output
);
4155 void MacroAssembler::convertValueToInt(
4156 ValueOperand value
, Label
* handleStringEntry
, Label
* handleStringRejoin
,
4157 Label
* truncateDoubleSlow
, Register stringReg
, FloatRegister temp
,
4158 Register output
, Label
* fail
, IntConversionBehavior behavior
,
4159 IntConversionInputKind conversion
) {
4160 Label done
, isInt32
, isBool
, isDouble
, isNull
, isString
;
4162 bool handleStrings
= (behavior
== IntConversionBehavior::Truncate
||
4163 behavior
== IntConversionBehavior::ClampToUint8
) &&
4164 handleStringEntry
&& handleStringRejoin
;
4166 MOZ_ASSERT_IF(handleStrings
, conversion
== IntConversionInputKind::Any
);
4169 ScratchTagScope
tag(*this, value
);
4170 splitTagForTest(value
, tag
);
4172 branchTestInt32(Equal
, tag
, &isInt32
);
4173 if (conversion
== IntConversionInputKind::Any
) {
4174 branchTestBoolean(Equal
, tag
, &isBool
);
4176 branchTestDouble(Equal
, tag
, &isDouble
);
4178 if (conversion
== IntConversionInputKind::Any
) {
4179 // If we are not truncating, we fail for anything that's not
4180 // null. Otherwise we might be able to handle strings and undefined.
4182 case IntConversionBehavior::Normal
:
4183 case IntConversionBehavior::NegativeZeroCheck
:
4184 branchTestNull(Assembler::NotEqual
, tag
, fail
);
4187 case IntConversionBehavior::Truncate
:
4188 case IntConversionBehavior::ClampToUint8
:
4189 branchTestNull(Equal
, tag
, &isNull
);
4190 if (handleStrings
) {
4191 branchTestString(Equal
, tag
, &isString
);
4193 branchTestUndefined(Assembler::NotEqual
, tag
, fail
);
4201 // The value is null or undefined in truncation contexts - just emit 0.
4202 if (conversion
== IntConversionInputKind::Any
) {
4203 if (isNull
.used()) {
4206 mov(ImmWord(0), output
);
4210 // |output| needs to be different from |stringReg| to load string indices.
4211 bool handleStringIndices
= handleStrings
&& output
!= stringReg
;
4213 // First try loading a string index. If that fails, try converting a string
4214 // into a double, then jump to the double case.
4215 Label handleStringIndex
;
4216 if (handleStrings
) {
4218 unboxString(value
, stringReg
);
4219 if (handleStringIndices
) {
4220 loadStringIndexValue(stringReg
, output
, handleStringEntry
);
4221 jump(&handleStringIndex
);
4223 jump(handleStringEntry
);
4227 // Try converting double into integer.
4228 if (isDouble
.used() || handleStrings
) {
4229 if (isDouble
.used()) {
4231 unboxDouble(value
, temp
);
4234 if (handleStrings
) {
4235 bind(handleStringRejoin
);
4238 convertDoubleToInt(temp
, output
, temp
, truncateDoubleSlow
, fail
, behavior
);
4242 // Just unbox a bool, the result is 0 or 1.
4243 if (isBool
.used()) {
4245 unboxBoolean(value
, output
);
4249 // Integers can be unboxed.
4250 if (isInt32
.used() || handleStringIndices
) {
4251 if (isInt32
.used()) {
4253 unboxInt32(value
, output
);
4256 if (handleStringIndices
) {
4257 bind(&handleStringIndex
);
4260 if (behavior
== IntConversionBehavior::ClampToUint8
) {
4261 clampIntToUint8(output
);
4268 void MacroAssembler::finish() {
4269 if (failureLabel_
.used()) {
4270 bind(&failureLabel_
);
4274 MacroAssemblerSpecific::finish();
4277 size() <= MaxCodeBytesPerProcess
,
4278 "AssemblerBuffer should ensure we don't exceed MaxCodeBytesPerProcess");
4280 if (bytesNeeded() > MaxCodeBytesPerProcess
) {
4285 void MacroAssembler::link(JitCode
* code
) {
4287 linkProfilerCallSites(code
);
4290 MacroAssembler::AutoProfilerCallInstrumentation::
4291 AutoProfilerCallInstrumentation(MacroAssembler
& masm
) {
4292 if (!masm
.emitProfilingInstrumentation_
) {
4296 Register reg
= CallTempReg0
;
4297 Register reg2
= CallTempReg1
;
4301 CodeOffset label
= masm
.movWithPatch(ImmWord(uintptr_t(-1)), reg
);
4302 masm
.loadJSContext(reg2
);
4303 masm
.loadPtr(Address(reg2
, offsetof(JSContext
, profilingActivation_
)), reg2
);
4305 Address(reg2
, JitActivation::offsetOfLastProfilingCallSite()));
4307 masm
.appendProfilerCallSite(label
);
4313 void MacroAssembler::linkProfilerCallSites(JitCode
* code
) {
4314 for (size_t i
= 0; i
< profilerCallSites_
.length(); i
++) {
4315 CodeOffset offset
= profilerCallSites_
[i
];
4316 CodeLocationLabel
location(code
, offset
);
4317 PatchDataWithValueCheck(location
, ImmPtr(location
.raw()),
4322 void MacroAssembler::alignJitStackBasedOnNArgs(Register nargs
,
4323 bool countIncludesThis
) {
4324 // The stack should already be aligned to the size of a value.
4325 assertStackAlignment(sizeof(Value
), 0);
4327 static_assert(JitStackValueAlignment
== 1 || JitStackValueAlignment
== 2,
4328 "JitStackValueAlignment is either 1 or 2.");
4329 if (JitStackValueAlignment
== 1) {
4332 // A jit frame is composed of the following:
4334 // [padding?] [argN] .. [arg1] [this] [[argc] [callee] [descr] [raddr]]
4335 // \________JitFrameLayout_________/
4336 // (The stack grows this way --->)
4338 // We want to ensure that |raddr|, the return address, is 16-byte aligned.
4339 // (Note: if 8-byte alignment was sufficient, we would have already
4342 // JitFrameLayout does not affect the alignment, so we can ignore it.
4343 static_assert(sizeof(JitFrameLayout
) % JitStackAlignment
== 0,
4344 "JitFrameLayout doesn't affect stack alignment");
4346 // Therefore, we need to ensure that |this| is aligned.
4347 // This implies that |argN| must be aligned if N is even,
4348 // and offset by |sizeof(Value)| if N is odd.
4350 // Depending on the context of the caller, it may be easier to pass in a
4351 // register that has already been modified to include |this|. If that is the
4352 // case, we want to flip the direction of the test.
4353 Assembler::Condition condition
=
4354 countIncludesThis
? Assembler::NonZero
: Assembler::Zero
;
4356 Label alignmentIsOffset
, end
;
4357 branchTestPtr(condition
, nargs
, Imm32(1), &alignmentIsOffset
);
4359 // |argN| should be aligned to 16 bytes.
4360 andToStackPtr(Imm32(~(JitStackAlignment
- 1)));
4363 // |argN| should be offset by 8 bytes from 16-byte alignment.
4364 // We already know that it is 8-byte aligned, so the only possibilities are:
4365 // a) It is 16-byte aligned, and we must offset it by 8 bytes.
4366 // b) It is not 16-byte aligned, and therefore already has the right offset.
4367 // Therefore, we test to see if it is 16-byte aligned, and adjust it if it is.
4368 bind(&alignmentIsOffset
);
4369 branchTestStackPtr(Assembler::NonZero
, Imm32(JitStackAlignment
- 1), &end
);
4370 subFromStackPtr(Imm32(sizeof(Value
)));
4375 void MacroAssembler::alignJitStackBasedOnNArgs(uint32_t argc
,
4376 bool countIncludesThis
) {
4377 // The stack should already be aligned to the size of a value.
4378 assertStackAlignment(sizeof(Value
), 0);
4380 static_assert(JitStackValueAlignment
== 1 || JitStackValueAlignment
== 2,
4381 "JitStackValueAlignment is either 1 or 2.");
4382 if (JitStackValueAlignment
== 1) {
4386 // See above for full explanation.
4387 uint32_t nArgs
= argc
+ !countIncludesThis
;
4388 if (nArgs
% 2 == 0) {
4389 // |argN| should be 16-byte aligned
4390 andToStackPtr(Imm32(~(JitStackAlignment
- 1)));
4392 // |argN| must be 16-byte aligned if argc is even,
4393 // and offset by 8 if argc is odd.
4395 branchTestStackPtr(Assembler::NonZero
, Imm32(JitStackAlignment
- 1), &end
);
4396 subFromStackPtr(Imm32(sizeof(Value
)));
4398 assertStackAlignment(JitStackAlignment
, sizeof(Value
));
4402 // ===============================================================
4404 MacroAssembler::MacroAssembler(TempAllocator
& alloc
,
4405 CompileRuntime
* maybeRuntime
,
4406 CompileRealm
* maybeRealm
)
4407 : maybeRuntime_(maybeRuntime
),
4408 maybeRealm_(maybeRealm
),
4413 dynamicAlignment_(false),
4414 emitProfilingInstrumentation_(false) {
4415 moveResolver_
.setAllocator(alloc
);
4418 StackMacroAssembler::StackMacroAssembler(JSContext
* cx
, TempAllocator
& alloc
)
4419 : MacroAssembler(alloc
, CompileRuntime::get(cx
->runtime()),
4420 CompileRealm::get(cx
->realm())) {}
4422 IonHeapMacroAssembler::IonHeapMacroAssembler(TempAllocator
& alloc
,
4423 CompileRealm
* realm
)
4424 : MacroAssembler(alloc
, realm
->runtime(), realm
) {
4425 MOZ_ASSERT(CurrentThreadIsIonCompiling());
4428 WasmMacroAssembler::WasmMacroAssembler(TempAllocator
& alloc
, bool limitedSize
)
4429 : MacroAssembler(alloc
) {
4430 #if defined(JS_CODEGEN_ARM64)
4431 // Stubs + builtins + the baseline compiler all require the native SP,
4433 SetStackPointer64(sp
);
4436 setUnlimitedBuffer();
4440 bool MacroAssembler::icBuildOOLFakeExitFrame(void* fakeReturnAddr
,
4441 AutoSaveLiveRegisters
& save
) {
4442 return buildOOLFakeExitFrame(fakeReturnAddr
);
4445 #ifndef JS_CODEGEN_ARM64
4446 void MacroAssembler::subFromStackPtr(Register reg
) {
4447 subPtr(reg
, getStackPointer());
4449 #endif // JS_CODEGEN_ARM64
4451 //{{{ check_macroassembler_style
4452 // ===============================================================
4453 // Stack manipulation functions.
4455 void MacroAssembler::PushRegsInMask(LiveGeneralRegisterSet set
) {
4456 PushRegsInMask(LiveRegisterSet(set
.set(), FloatRegisterSet()));
4459 void MacroAssembler::PopRegsInMask(LiveRegisterSet set
) {
4460 PopRegsInMaskIgnore(set
, LiveRegisterSet());
4463 void MacroAssembler::PopRegsInMask(LiveGeneralRegisterSet set
) {
4464 PopRegsInMask(LiveRegisterSet(set
.set(), FloatRegisterSet()));
4467 void MacroAssembler::Push(PropertyKey key
, Register scratchReg
) {
4468 if (key
.isGCThing()) {
4469 // If we're pushing a gcthing, then we can't just push the tagged key
4470 // value since the GC won't have any idea that the push instruction
4471 // carries a reference to a gcthing. Need to unpack the pointer,
4472 // push it using ImmGCPtr, and then rematerialize the PropertyKey at
4475 if (key
.isString()) {
4476 JSString
* str
= key
.toString();
4477 MOZ_ASSERT((uintptr_t(str
) & PropertyKey::TypeMask
) == 0);
4478 static_assert(PropertyKey::StringTypeTag
== 0,
4479 "need to orPtr StringTypeTag if it's not 0");
4480 Push(ImmGCPtr(str
));
4482 MOZ_ASSERT(key
.isSymbol());
4483 movePropertyKey(key
, scratchReg
);
4487 MOZ_ASSERT(key
.isInt());
4488 Push(ImmWord(key
.asRawBits()));
4492 void MacroAssembler::movePropertyKey(PropertyKey key
, Register dest
) {
4493 if (key
.isGCThing()) {
4494 // See comment in |Push(PropertyKey, ...)| above for an explanation.
4495 if (key
.isString()) {
4496 JSString
* str
= key
.toString();
4497 MOZ_ASSERT((uintptr_t(str
) & PropertyKey::TypeMask
) == 0);
4498 static_assert(PropertyKey::StringTypeTag
== 0,
4499 "need to orPtr StringTypeTag tag if it's not 0");
4500 movePtr(ImmGCPtr(str
), dest
);
4502 MOZ_ASSERT(key
.isSymbol());
4503 JS::Symbol
* sym
= key
.toSymbol();
4504 movePtr(ImmGCPtr(sym
), dest
);
4505 orPtr(Imm32(PropertyKey::SymbolTypeTag
), dest
);
4508 MOZ_ASSERT(key
.isInt());
4509 movePtr(ImmWord(key
.asRawBits()), dest
);
4513 void MacroAssembler::Push(TypedOrValueRegister v
) {
4516 } else if (IsFloatingPointType(v
.type())) {
4517 FloatRegister reg
= v
.typedReg().fpu();
4518 if (v
.type() == MIRType::Float32
) {
4519 ScratchDoubleScope
fpscratch(*this);
4520 convertFloat32ToDouble(reg
, fpscratch
);
4521 PushBoxed(fpscratch
);
4526 Push(ValueTypeFromMIRType(v
.type()), v
.typedReg().gpr());
4530 void MacroAssembler::Push(const ConstantOrRegister
& v
) {
4538 void MacroAssembler::Push(const Address
& addr
) {
4540 framePushed_
+= sizeof(uintptr_t);
4543 void MacroAssembler::Push(const ValueOperand
& val
) {
4545 framePushed_
+= sizeof(Value
);
4548 void MacroAssembler::Push(const Value
& val
) {
4550 framePushed_
+= sizeof(Value
);
4553 void MacroAssembler::Push(JSValueType type
, Register reg
) {
4554 pushValue(type
, reg
);
4555 framePushed_
+= sizeof(Value
);
4558 void MacroAssembler::Push(const Register64 reg
) {
4559 #if JS_BITS_PER_WORD == 64
4562 MOZ_ASSERT(MOZ_LITTLE_ENDIAN(), "Big-endian not supported.");
4568 void MacroAssembler::Pop(const Register64 reg
) {
4569 #if JS_BITS_PER_WORD == 64
4572 MOZ_ASSERT(MOZ_LITTLE_ENDIAN(), "Big-endian not supported.");
4578 void MacroAssembler::PushEmptyRooted(VMFunctionData::RootType rootType
) {
4580 case VMFunctionData::RootNone
:
4581 MOZ_CRASH("Handle must have root type");
4582 case VMFunctionData::RootObject
:
4583 case VMFunctionData::RootString
:
4584 case VMFunctionData::RootCell
:
4585 case VMFunctionData::RootBigInt
:
4586 Push(ImmPtr(nullptr));
4588 case VMFunctionData::RootValue
:
4589 Push(UndefinedValue());
4591 case VMFunctionData::RootId
:
4592 Push(ImmWord(JS::PropertyKey::Void().asRawBits()));
4597 void MacroAssembler::adjustStack(int amount
) {
4600 } else if (amount
< 0) {
4601 reserveStack(-amount
);
4605 void MacroAssembler::freeStack(uint32_t amount
) {
4606 MOZ_ASSERT(amount
<= framePushed_
);
4608 addToStackPtr(Imm32(amount
));
4610 framePushed_
-= amount
;
4613 void MacroAssembler::freeStack(Register amount
) { addToStackPtr(amount
); }
4615 void MacroAssembler::reserveVMFunctionOutParamSpace(const VMFunctionData
& f
) {
4616 switch (f
.outParam
) {
4618 PushEmptyRooted(f
.outParamRootType
);
4626 reserveStack(f
.sizeOfOutParamStackSlot());
4633 MOZ_CRASH("Unexpected outparam type");
4637 void MacroAssembler::loadVMFunctionOutParam(const VMFunctionData
& f
,
4638 const Address
& addr
) {
4639 switch (f
.outParam
) {
4641 switch (f
.outParamRootType
) {
4642 case VMFunctionData::RootNone
:
4643 MOZ_CRASH("Handle must have root type");
4644 case VMFunctionData::RootObject
:
4645 case VMFunctionData::RootString
:
4646 case VMFunctionData::RootCell
:
4647 case VMFunctionData::RootBigInt
:
4648 case VMFunctionData::RootId
:
4649 loadPtr(addr
, ReturnReg
);
4651 case VMFunctionData::RootValue
:
4652 loadValue(addr
, JSReturnOperand
);
4658 loadValue(addr
, JSReturnOperand
);
4662 load32(addr
, ReturnReg
);
4666 load8ZeroExtend(addr
, ReturnReg
);
4670 loadDouble(addr
, ReturnDoubleReg
);
4674 loadPtr(addr
, ReturnReg
);
4681 MOZ_CRASH("Unexpected outparam type");
4685 // ===============================================================
4686 // ABI function calls.
4687 template <class ABIArgGeneratorT
>
4688 void MacroAssembler::setupABICallHelper() {
4690 MOZ_ASSERT(!inCall_
);
4698 // Reinitialize the ABIArg generator.
4699 abiArgs_
= ABIArgGeneratorT();
4701 #if defined(JS_CODEGEN_ARM)
4702 // On ARM, we need to know what ABI we are using.
4703 abiArgs_
.setUseHardFp(ARMFlags::UseHardFpABI());
4706 #if defined(JS_CODEGEN_MIPS32)
4707 // On MIPS, the system ABI use general registers pairs to encode double
4708 // arguments, after one or 2 integer-like arguments. Unfortunately, the
4709 // Lowering phase is not capable to express it at the moment. So we enforce
4710 // the system ABI here.
4711 abiArgs_
.enforceO32ABI();
4715 void MacroAssembler::setupNativeABICall() {
4716 setupABICallHelper
<ABIArgGenerator
>();
4719 void MacroAssembler::setupWasmABICall() {
4720 MOZ_ASSERT(IsCompilingWasm(), "non-wasm should use setupAlignedABICall");
4721 setupABICallHelper
<WasmABIArgGenerator
>();
4723 #if defined(JS_CODEGEN_ARM)
4724 // The builtin thunk does the FP -> GPR moving on soft-FP, so
4725 // use hard fp unconditionally.
4726 abiArgs_
.setUseHardFp(true);
4728 dynamicAlignment_
= false;
4731 void MacroAssembler::setupUnalignedABICallDontSaveRestoreSP() {
4732 andToStackPtr(Imm32(~(ABIStackAlignment
- 1)));
4733 setFramePushed(0); // Required for aligned callWithABI.
4734 setupAlignedABICall();
4737 void MacroAssembler::setupAlignedABICall() {
4738 MOZ_ASSERT(!IsCompilingWasm(), "wasm should use setupWasmABICall");
4739 setupNativeABICall();
4740 dynamicAlignment_
= false;
4743 void MacroAssembler::passABIArg(const MoveOperand
& from
, ABIType type
) {
4744 MOZ_ASSERT(inCall_
);
4745 appendSignatureType(type
);
4748 MoveOp::Type moveType
;
4750 case ABIType::Float32
:
4751 arg
= abiArgs_
.next(MIRType::Float32
);
4752 moveType
= MoveOp::FLOAT32
;
4754 case ABIType::Float64
:
4755 arg
= abiArgs_
.next(MIRType::Double
);
4756 moveType
= MoveOp::DOUBLE
;
4758 case ABIType::General
:
4759 arg
= abiArgs_
.next(MIRType::Pointer
);
4760 moveType
= MoveOp::GENERAL
;
4763 MOZ_CRASH("Unexpected argument type");
4766 MoveOperand
to(*this, arg
);
4774 propagateOOM(moveResolver_
.addMove(from
, to
, moveType
));
4777 void MacroAssembler::callWithABINoProfiler(void* fun
, ABIType result
,
4778 CheckUnsafeCallWithABI check
) {
4779 appendSignatureType(result
);
4781 fun
= Simulator::RedirectNativeFunction(fun
, signature());
4784 uint32_t stackAdjust
;
4785 callWithABIPre(&stackAdjust
);
4787 #ifdef JS_CHECK_UNSAFE_CALL_WITH_ABI
4788 if (check
== CheckUnsafeCallWithABI::Check
) {
4789 // Set the JSContext::inUnsafeCallWithABI flag.
4791 loadJSContext(ReturnReg
);
4792 Address
flagAddr(ReturnReg
, JSContext::offsetOfInUnsafeCallWithABI());
4793 store32(Imm32(1), flagAddr
);
4795 // On arm64, SP may be < PSP now (that's OK).
4796 // eg testcase: tests/bug1375074.js
4802 callWithABIPost(stackAdjust
, result
);
4804 #ifdef JS_CHECK_UNSAFE_CALL_WITH_ABI
4805 if (check
== CheckUnsafeCallWithABI::Check
) {
4806 // Check JSContext::inUnsafeCallWithABI was cleared as expected.
4809 loadJSContext(ReturnReg
);
4810 Address
flagAddr(ReturnReg
, JSContext::offsetOfInUnsafeCallWithABI());
4811 branch32(Assembler::Equal
, flagAddr
, Imm32(0), &ok
);
4812 assumeUnreachable("callWithABI: callee did not use AutoUnsafeCallWithABI");
4815 // On arm64, SP may be < PSP now (that's OK).
4816 // eg testcase: tests/bug1375074.js
4821 CodeOffset
MacroAssembler::callWithABI(wasm::BytecodeOffset bytecode
,
4822 wasm::SymbolicAddress imm
,
4823 mozilla::Maybe
<int32_t> instanceOffset
,
4825 MOZ_ASSERT(wasm::NeedsBuiltinThunk(imm
));
4827 uint32_t stackAdjust
;
4828 callWithABIPre(&stackAdjust
, /* callFromWasm = */ true);
4830 // The instance register is used in builtin thunks and must be set.
4831 if (instanceOffset
) {
4832 loadPtr(Address(getStackPointer(), *instanceOffset
+ stackAdjust
),
4835 MOZ_CRASH("instanceOffset is Nothing only for unsupported abi calls.");
4837 CodeOffset raOffset
= call(
4838 wasm::CallSiteDesc(bytecode
.offset(), wasm::CallSite::Symbolic
), imm
);
4840 callWithABIPost(stackAdjust
, result
, /* callFromWasm = */ true);
4845 void MacroAssembler::callDebugWithABI(wasm::SymbolicAddress imm
,
4847 MOZ_ASSERT(!wasm::NeedsBuiltinThunk(imm
));
4848 uint32_t stackAdjust
;
4849 callWithABIPre(&stackAdjust
, /* callFromWasm = */ false);
4851 callWithABIPost(stackAdjust
, result
, /* callFromWasm = */ false);
4854 // ===============================================================
4855 // Exit frame footer.
4857 void MacroAssembler::linkExitFrame(Register cxreg
, Register scratch
) {
4858 loadPtr(Address(cxreg
, JSContext::offsetOfActivation()), scratch
);
4859 storeStackPtr(Address(scratch
, JitActivation::offsetOfPackedExitFP()));
4862 // ===============================================================
4863 // Simple value-shuffling helpers, to hide MoveResolver verbosity
4866 void MacroAssembler::moveRegPair(Register src0
, Register src1
, Register dst0
,
4867 Register dst1
, MoveOp::Type type
) {
4868 MoveResolver
& moves
= moveResolver();
4870 propagateOOM(moves
.addMove(MoveOperand(src0
), MoveOperand(dst0
), type
));
4873 propagateOOM(moves
.addMove(MoveOperand(src1
), MoveOperand(dst1
), type
));
4875 propagateOOM(moves
.resolve());
4880 MoveEmitter
emitter(*this);
4881 emitter
.emit(moves
);
4885 // ===============================================================
4886 // Arithmetic functions
4888 void MacroAssembler::pow32(Register base
, Register power
, Register dest
,
4889 Register temp1
, Register temp2
, Label
* onOver
) {
4890 // Inline int32-specialized implementation of js::powi with overflow
4893 move32(Imm32(1), dest
); // result = 1
4895 // x^y where x == 1 returns 1 for any y.
4897 branch32(Assembler::Equal
, base
, Imm32(1), &done
);
4899 // x^y where y < 0 returns a non-int32 value for any x != 1. Except when y is
4900 // large enough so that the result is no longer representable as a double with
4901 // fractional parts. We can't easily determine when y is too large, so we bail
4903 // Note: it's important for this condition to match the code in CacheIR.cpp
4904 // (CanAttachInt32Pow) to prevent failure loops.
4905 branchTest32(Assembler::Signed
, power
, power
, onOver
);
4907 move32(base
, temp1
); // runningSquare = x
4908 move32(power
, temp2
); // n = y
4916 // runningSquare *= runningSquare
4917 branchMul32(Assembler::Overflow
, temp1
, temp1
, onOver
);
4921 // if ((n & 1) != 0) result *= runningSquare
4923 branchTest32(Assembler::Zero
, temp2
, Imm32(1), &even
);
4924 branchMul32(Assembler::Overflow
, temp1
, dest
, onOver
);
4928 // if (n == 0) return result
4929 branchRshift32(Assembler::NonZero
, Imm32(1), temp2
, &loop
);
4934 void MacroAssembler::powPtr(Register base
, Register power
, Register dest
,
4935 Register temp1
, Register temp2
, Label
* onOver
) {
4936 // Inline intptr-specialized implementation of BigInt::pow with overflow
4939 // Negative exponents are disallowed for any BigInts.
4940 branchTestPtr(Assembler::Signed
, power
, power
, onOver
);
4942 movePtr(ImmWord(1), dest
); // result = 1
4944 // x^y where x == 1 returns 1 for any y.
4946 branchPtr(Assembler::Equal
, base
, ImmWord(1), &done
);
4948 // x^y where x == -1 returns 1 for even y, and -1 for odd y.
4949 Label notNegativeOne
;
4950 branchPtr(Assembler::NotEqual
, base
, ImmWord(-1), ¬NegativeOne
);
4951 test32MovePtr(Assembler::NonZero
, power
, Imm32(1), base
, dest
);
4953 bind(¬NegativeOne
);
4955 // x ** y with |x| > 1 and y >= DigitBits can't be pointer-sized.
4956 branchPtr(Assembler::GreaterThanOrEqual
, power
, Imm32(BigInt::DigitBits
),
4959 movePtr(base
, temp1
); // runningSquare = x
4960 movePtr(power
, temp2
); // n = y
4968 // runningSquare *= runningSquare
4969 branchMulPtr(Assembler::Overflow
, temp1
, temp1
, onOver
);
4973 // if ((n & 1) != 0) result *= runningSquare
4975 branchTest32(Assembler::Zero
, temp2
, Imm32(1), &even
);
4976 branchMulPtr(Assembler::Overflow
, temp1
, dest
, onOver
);
4980 // if (n == 0) return result
4981 branchRshift32(Assembler::NonZero
, Imm32(1), temp2
, &loop
);
4986 void MacroAssembler::signInt32(Register input
, Register output
) {
4987 MOZ_ASSERT(input
!= output
);
4989 move32(input
, output
);
4990 rshift32Arithmetic(Imm32(31), output
);
4991 or32(Imm32(1), output
);
4992 cmp32Move32(Assembler::Equal
, input
, Imm32(0), input
, output
);
4995 void MacroAssembler::signDouble(FloatRegister input
, FloatRegister output
) {
4996 MOZ_ASSERT(input
!= output
);
4998 Label done
, zeroOrNaN
, negative
;
4999 loadConstantDouble(0.0, output
);
5000 branchDouble(Assembler::DoubleEqualOrUnordered
, input
, output
, &zeroOrNaN
);
5001 branchDouble(Assembler::DoubleLessThan
, input
, output
, &negative
);
5003 loadConstantDouble(1.0, output
);
5007 loadConstantDouble(-1.0, output
);
5011 moveDouble(input
, output
);
5016 void MacroAssembler::signDoubleToInt32(FloatRegister input
, Register output
,
5017 FloatRegister temp
, Label
* fail
) {
5018 MOZ_ASSERT(input
!= temp
);
5020 Label done
, zeroOrNaN
, negative
;
5021 loadConstantDouble(0.0, temp
);
5022 branchDouble(Assembler::DoubleEqualOrUnordered
, input
, temp
, &zeroOrNaN
);
5023 branchDouble(Assembler::DoubleLessThan
, input
, temp
, &negative
);
5025 move32(Imm32(1), output
);
5029 move32(Imm32(-1), output
);
5032 // Fail for NaN and negative zero.
5034 branchDouble(Assembler::DoubleUnordered
, input
, input
, fail
);
5036 // The easiest way to distinguish -0.0 from 0.0 is that 1.0/-0.0
5037 // is -Infinity instead of Infinity.
5038 loadConstantDouble(1.0, temp
);
5039 divDouble(input
, temp
);
5040 branchDouble(Assembler::DoubleLessThan
, temp
, input
, fail
);
5041 move32(Imm32(0), output
);
5046 void MacroAssembler::randomDouble(Register rng
, FloatRegister dest
,
5047 Register64 temp0
, Register64 temp1
) {
5048 using mozilla::non_crypto::XorShift128PlusRNG
;
5051 sizeof(XorShift128PlusRNG
) == 2 * sizeof(uint64_t),
5052 "Code below assumes XorShift128PlusRNG contains two uint64_t values");
5054 Address
state0Addr(rng
, XorShift128PlusRNG::offsetOfState0());
5055 Address
state1Addr(rng
, XorShift128PlusRNG::offsetOfState1());
5057 Register64 s0Reg
= temp0
;
5058 Register64 s1Reg
= temp1
;
5060 // uint64_t s1 = mState[0];
5061 load64(state0Addr
, s1Reg
);
5064 move64(s1Reg
, s0Reg
);
5065 lshift64(Imm32(23), s1Reg
);
5066 xor64(s0Reg
, s1Reg
);
5069 move64(s1Reg
, s0Reg
);
5070 rshift64(Imm32(17), s1Reg
);
5071 xor64(s0Reg
, s1Reg
);
5073 // const uint64_t s0 = mState[1];
5074 load64(state1Addr
, s0Reg
);
5077 store64(s0Reg
, state0Addr
);
5080 xor64(s0Reg
, s1Reg
);
5083 rshift64(Imm32(26), s0Reg
);
5084 xor64(s0Reg
, s1Reg
);
5087 store64(s1Reg
, state1Addr
);
5090 load64(state0Addr
, s0Reg
);
5091 add64(s0Reg
, s1Reg
);
5093 // See comment in XorShift128PlusRNG::nextDouble().
5094 static constexpr int MantissaBits
=
5095 mozilla::FloatingPoint
<double>::kExponentShift
+ 1;
5096 static constexpr double ScaleInv
= double(1) / (1ULL << MantissaBits
);
5098 and64(Imm64((1ULL << MantissaBits
) - 1), s1Reg
);
5100 // Note: we know s1Reg isn't signed after the and64 so we can use the faster
5101 // convertInt64ToDouble instead of convertUInt64ToDouble.
5102 convertInt64ToDouble(s1Reg
, dest
);
5105 mulDoublePtr(ImmPtr(&ScaleInv
), s0Reg
.scratchReg(), dest
);
5108 void MacroAssembler::sameValueDouble(FloatRegister left
, FloatRegister right
,
5109 FloatRegister temp
, Register dest
) {
5110 Label nonEqual
, isSameValue
, isNotSameValue
;
5111 branchDouble(Assembler::DoubleNotEqualOrUnordered
, left
, right
, &nonEqual
);
5113 // First, test for being equal to 0.0, which also includes -0.0.
5114 loadConstantDouble(0.0, temp
);
5115 branchDouble(Assembler::DoubleNotEqual
, left
, temp
, &isSameValue
);
5117 // The easiest way to distinguish -0.0 from 0.0 is that 1.0/-0.0
5118 // is -Infinity instead of Infinity.
5120 loadConstantDouble(1.0, temp
);
5121 divDouble(left
, temp
);
5122 branchDouble(Assembler::DoubleLessThan
, temp
, left
, &isNegInf
);
5124 loadConstantDouble(1.0, temp
);
5125 divDouble(right
, temp
);
5126 branchDouble(Assembler::DoubleGreaterThan
, temp
, right
, &isSameValue
);
5127 jump(&isNotSameValue
);
5131 loadConstantDouble(1.0, temp
);
5132 divDouble(right
, temp
);
5133 branchDouble(Assembler::DoubleLessThan
, temp
, right
, &isSameValue
);
5134 jump(&isNotSameValue
);
5139 // Test if both values are NaN.
5140 branchDouble(Assembler::DoubleOrdered
, left
, left
, &isNotSameValue
);
5141 branchDouble(Assembler::DoubleOrdered
, right
, right
, &isNotSameValue
);
5146 move32(Imm32(1), dest
);
5149 bind(&isNotSameValue
);
5150 move32(Imm32(0), dest
);
5155 void MacroAssembler::minMaxArrayInt32(Register array
, Register result
,
5156 Register temp1
, Register temp2
,
5157 Register temp3
, bool isMax
, Label
* fail
) {
5158 // array must be a packed array. Load its elements.
5159 Register elements
= temp1
;
5160 loadPtr(Address(array
, NativeObject::offsetOfElements()), elements
);
5162 // Load the length and guard that it is non-zero.
5163 Address
lengthAddr(elements
, ObjectElements::offsetOfInitializedLength());
5164 load32(lengthAddr
, temp3
);
5165 branchTest32(Assembler::Zero
, temp3
, temp3
, fail
);
5167 // Compute the address of the last element.
5168 Register elementsEnd
= temp2
;
5169 BaseObjectElementIndex
elementsEndAddr(elements
, temp3
,
5170 -int32_t(sizeof(Value
)));
5171 computeEffectiveAddress(elementsEndAddr
, elementsEnd
);
5173 // Load the first element into result.
5174 fallibleUnboxInt32(Address(elements
, 0), result
, fail
);
5179 // Check whether we're done.
5180 branchPtr(Assembler::Equal
, elements
, elementsEnd
, &done
);
5182 // If not, advance to the next element and load it.
5183 addPtr(Imm32(sizeof(Value
)), elements
);
5184 fallibleUnboxInt32(Address(elements
, 0), temp3
, fail
);
5186 // Update result if necessary.
5187 Assembler::Condition cond
=
5188 isMax
? Assembler::GreaterThan
: Assembler::LessThan
;
5189 cmp32Move32(cond
, temp3
, result
, temp3
, result
);
5195 void MacroAssembler::minMaxArrayNumber(Register array
, FloatRegister result
,
5196 FloatRegister floatTemp
, Register temp1
,
5197 Register temp2
, bool isMax
,
5199 // array must be a packed array. Load its elements.
5200 Register elements
= temp1
;
5201 loadPtr(Address(array
, NativeObject::offsetOfElements()), elements
);
5203 // Load the length and check if the array is empty.
5205 Address
lengthAddr(elements
, ObjectElements::offsetOfInitializedLength());
5206 load32(lengthAddr
, temp2
);
5207 branchTest32(Assembler::Zero
, temp2
, temp2
, &isEmpty
);
5209 // Compute the address of the last element.
5210 Register elementsEnd
= temp2
;
5211 BaseObjectElementIndex
elementsEndAddr(elements
, temp2
,
5212 -int32_t(sizeof(Value
)));
5213 computeEffectiveAddress(elementsEndAddr
, elementsEnd
);
5215 // Load the first element into result.
5216 ensureDouble(Address(elements
, 0), result
, fail
);
5221 // Check whether we're done.
5222 branchPtr(Assembler::Equal
, elements
, elementsEnd
, &done
);
5224 // If not, advance to the next element and load it into floatTemp.
5225 addPtr(Imm32(sizeof(Value
)), elements
);
5226 ensureDouble(Address(elements
, 0), floatTemp
, fail
);
5228 // Update result if necessary.
5230 maxDouble(floatTemp
, result
, /* handleNaN = */ true);
5232 minDouble(floatTemp
, result
, /* handleNaN = */ true);
5236 // With no arguments, min/max return +Infinity/-Infinity respectively.
5239 loadConstantDouble(mozilla::NegativeInfinity
<double>(), result
);
5241 loadConstantDouble(mozilla::PositiveInfinity
<double>(), result
);
5247 void MacroAssembler::branchIfNotRegExpPrototypeOptimizable(
5248 Register proto
, Register temp
, const GlobalObject
* maybeGlobal
,
5251 movePtr(ImmGCPtr(maybeGlobal
), temp
);
5252 loadPrivate(Address(temp
, GlobalObject::offsetOfGlobalDataSlot()), temp
);
5254 loadGlobalObjectData(temp
);
5256 size_t offset
= GlobalObjectData::offsetOfRegExpRealm() +
5257 RegExpRealm::offsetOfOptimizableRegExpPrototypeShape();
5258 loadPtr(Address(temp
, offset
), temp
);
5259 branchTestObjShapeUnsafe(Assembler::NotEqual
, proto
, temp
, fail
);
5262 void MacroAssembler::branchIfNotRegExpInstanceOptimizable(
5263 Register regexp
, Register temp
, const GlobalObject
* maybeGlobal
,
5266 movePtr(ImmGCPtr(maybeGlobal
), temp
);
5267 loadPrivate(Address(temp
, GlobalObject::offsetOfGlobalDataSlot()), temp
);
5269 loadGlobalObjectData(temp
);
5271 size_t offset
= GlobalObjectData::offsetOfRegExpRealm() +
5272 RegExpRealm::offsetOfOptimizableRegExpInstanceShape();
5273 loadPtr(Address(temp
, offset
), temp
);
5274 branchTestObjShapeUnsafe(Assembler::NotEqual
, regexp
, temp
, label
);
5277 void MacroAssembler::loadRegExpLastIndex(Register regexp
, Register string
,
5279 Label
* notFoundZeroLastIndex
) {
5280 Address
flagsSlot(regexp
, RegExpObject::offsetOfFlags());
5281 Address
lastIndexSlot(regexp
, RegExpObject::offsetOfLastIndex());
5282 Address
stringLength(string
, JSString::offsetOfLength());
5284 Label notGlobalOrSticky
, loadedLastIndex
;
5286 branchTest32(Assembler::Zero
, flagsSlot
,
5287 Imm32(JS::RegExpFlag::Global
| JS::RegExpFlag::Sticky
),
5288 ¬GlobalOrSticky
);
5290 // It's a global or sticky regular expression. Emit the following code:
5292 // lastIndex = regexp.lastIndex
5293 // if lastIndex > string.length:
5294 // jump to notFoundZeroLastIndex (skip the regexp match/test operation)
5296 // The `notFoundZeroLastIndex` code should set regexp.lastIndex to 0 and
5297 // treat this as a not-found result.
5299 // See steps 5-8 in js::RegExpBuiltinExec.
5301 // Earlier guards must have ensured regexp.lastIndex is a non-negative
5306 branchTestInt32(Assembler::Equal
, lastIndexSlot
, &ok
);
5307 assumeUnreachable("Expected int32 value for lastIndex");
5311 unboxInt32(lastIndexSlot
, lastIndex
);
5315 branchTest32(Assembler::NotSigned
, lastIndex
, lastIndex
, &ok
);
5316 assumeUnreachable("Expected non-negative lastIndex");
5320 branch32(Assembler::Below
, stringLength
, lastIndex
, notFoundZeroLastIndex
);
5321 jump(&loadedLastIndex
);
5324 bind(¬GlobalOrSticky
);
5325 move32(Imm32(0), lastIndex
);
5327 bind(&loadedLastIndex
);
5330 void MacroAssembler::loadAndClearRegExpSearcherLastLimit(Register result
,
5332 MOZ_ASSERT(result
!= scratch
);
5334 loadJSContext(scratch
);
5336 Address
limitField(scratch
, JSContext::offsetOfRegExpSearcherLastLimit());
5337 load32(limitField
, result
);
5341 branch32(Assembler::NotEqual
, result
, Imm32(RegExpSearcherLastLimitSentinel
),
5343 assumeUnreachable("Unexpected sentinel for regExpSearcherLastLimit");
5345 store32(Imm32(RegExpSearcherLastLimitSentinel
), limitField
);
5349 void MacroAssembler::loadParsedRegExpShared(Register regexp
, Register result
,
5351 Address
sharedSlot(regexp
, RegExpObject::offsetOfShared());
5352 branchTestUndefined(Assembler::Equal
, sharedSlot
, unparsed
);
5353 unboxNonDouble(sharedSlot
, result
, JSVAL_TYPE_PRIVATE_GCTHING
);
5355 static_assert(sizeof(RegExpShared::Kind
) == sizeof(uint32_t));
5356 branch32(Assembler::Equal
, Address(result
, RegExpShared::offsetOfKind()),
5357 Imm32(int32_t(RegExpShared::Kind::Unparsed
)), unparsed
);
5360 // ===============================================================
5363 void MacroAssembler::loadFunctionLength(Register func
,
5364 Register funFlagsAndArgCount
,
5365 Register output
, Label
* slowPath
) {
5368 // These flags should already have been checked by caller.
5370 uint32_t FlagsToCheck
=
5371 FunctionFlags::SELFHOSTLAZY
| FunctionFlags::RESOLVED_LENGTH
;
5372 branchTest32(Assembler::Zero
, funFlagsAndArgCount
, Imm32(FlagsToCheck
),
5374 assumeUnreachable("The function flags should already have been checked.");
5379 // NOTE: `funFlagsAndArgCount` and `output` must be allowed to alias.
5381 // Load the target function's length.
5382 Label isInterpreted
, lengthLoaded
;
5383 branchTest32(Assembler::NonZero
, funFlagsAndArgCount
,
5384 Imm32(FunctionFlags::BASESCRIPT
), &isInterpreted
);
5386 // The length property of a native function stored with the flags.
5387 move32(funFlagsAndArgCount
, output
);
5388 rshift32(Imm32(JSFunction::ArgCountShift
), output
);
5389 jump(&lengthLoaded
);
5391 bind(&isInterpreted
);
5393 // Load the length property of an interpreted function.
5394 loadPrivate(Address(func
, JSFunction::offsetOfJitInfoOrScript()), output
);
5395 loadPtr(Address(output
, JSScript::offsetOfSharedData()), output
);
5396 branchTestPtr(Assembler::Zero
, output
, output
, slowPath
);
5397 loadPtr(Address(output
, SharedImmutableScriptData::offsetOfISD()), output
);
5398 load16ZeroExtend(Address(output
, ImmutableScriptData::offsetOfFunLength()),
5401 bind(&lengthLoaded
);
5404 void MacroAssembler::loadFunctionName(Register func
, Register output
,
5405 ImmGCPtr emptyString
, Label
* slowPath
) {
5406 MOZ_ASSERT(func
!= output
);
5408 // Get the JSFunction flags.
5409 load32(Address(func
, JSFunction::offsetOfFlagsAndArgCount()), output
);
5411 // If the name was previously resolved, the name property may be shadowed.
5412 // If the function is an accessor with lazy name, AtomSlot contains the
5415 Assembler::NonZero
, output
,
5416 Imm32(FunctionFlags::RESOLVED_NAME
| FunctionFlags::LAZY_ACCESSOR_NAME
),
5420 branchTest32(Assembler::NonZero
, output
,
5421 Imm32(FunctionFlags::HAS_GUESSED_ATOM
), &noName
);
5423 Address
atomAddr(func
, JSFunction::offsetOfAtom());
5424 branchTestUndefined(Assembler::Equal
, atomAddr
, &noName
);
5425 unboxString(atomAddr
, output
);
5431 // An absent name property defaults to the empty string.
5432 movePtr(emptyString
, output
);
5438 void MacroAssembler::assertFunctionIsExtended(Register func
) {
5441 branchTestFunctionFlags(func
, FunctionFlags::EXTENDED
, Assembler::NonZero
,
5443 assumeUnreachable("Function is not extended");
5448 void MacroAssembler::branchTestType(Condition cond
, Register tag
,
5449 JSValueType type
, Label
* label
) {
5451 case JSVAL_TYPE_DOUBLE
:
5452 branchTestDouble(cond
, tag
, label
);
5454 case JSVAL_TYPE_INT32
:
5455 branchTestInt32(cond
, tag
, label
);
5457 case JSVAL_TYPE_BOOLEAN
:
5458 branchTestBoolean(cond
, tag
, label
);
5460 case JSVAL_TYPE_UNDEFINED
:
5461 branchTestUndefined(cond
, tag
, label
);
5463 case JSVAL_TYPE_NULL
:
5464 branchTestNull(cond
, tag
, label
);
5466 case JSVAL_TYPE_MAGIC
:
5467 branchTestMagic(cond
, tag
, label
);
5469 case JSVAL_TYPE_STRING
:
5470 branchTestString(cond
, tag
, label
);
5472 case JSVAL_TYPE_SYMBOL
:
5473 branchTestSymbol(cond
, tag
, label
);
5475 case JSVAL_TYPE_BIGINT
:
5476 branchTestBigInt(cond
, tag
, label
);
5478 case JSVAL_TYPE_OBJECT
:
5479 branchTestObject(cond
, tag
, label
);
5482 MOZ_CRASH("Unexpected value type");
5486 void MacroAssembler::branchTestObjShapeList(
5487 Condition cond
, Register obj
, Register shapeElements
, Register shapeScratch
,
5488 Register endScratch
, Register spectreScratch
, Label
* label
) {
5489 MOZ_ASSERT(cond
== Assembler::Equal
|| cond
== Assembler::NotEqual
);
5491 bool needSpectreMitigations
= spectreScratch
!= InvalidReg
;
5494 Label
* onMatch
= cond
== Assembler::Equal
? label
: &done
;
5495 Label
* onNoMatch
= cond
== Assembler::Equal
? &done
: label
;
5497 // Load the object's shape pointer into shapeScratch, and prepare to compare
5498 // it with the shapes in the list. The shapes are stored as private values so
5499 // we can compare directly.
5500 loadPtr(Address(obj
, JSObject::offsetOfShape()), shapeScratch
);
5502 // Compute end pointer.
5503 Address
lengthAddr(shapeElements
,
5504 ObjectElements::offsetOfInitializedLength());
5505 load32(lengthAddr
, endScratch
);
5506 branch32(Assembler::Equal
, endScratch
, Imm32(0), onNoMatch
);
5507 BaseObjectElementIndex
endPtrAddr(shapeElements
, endScratch
);
5508 computeEffectiveAddress(endPtrAddr
, endScratch
);
5513 // Compare the object's shape with a shape from the list. Note that on 64-bit
5514 // this includes the tag bits, but on 32-bit we only compare the low word of
5515 // the value. This is fine because the list of shapes is never exposed and the
5516 // tag is guaranteed to be PrivateGCThing.
5517 if (needSpectreMitigations
) {
5518 move32(Imm32(0), spectreScratch
);
5520 branchPtr(Assembler::Equal
, Address(shapeElements
, 0), shapeScratch
, onMatch
);
5521 if (needSpectreMitigations
) {
5522 spectreMovePtr(Assembler::Equal
, spectreScratch
, obj
);
5525 // Advance to next shape and loop if not finished.
5526 addPtr(Imm32(sizeof(Value
)), shapeElements
);
5527 branchPtr(Assembler::Below
, shapeElements
, endScratch
, &loop
);
5529 if (cond
== Assembler::NotEqual
) {
5535 void MacroAssembler::branchTestObjCompartment(Condition cond
, Register obj
,
5536 const Address
& compartment
,
5537 Register scratch
, Label
* label
) {
5538 MOZ_ASSERT(obj
!= scratch
);
5539 loadPtr(Address(obj
, JSObject::offsetOfShape()), scratch
);
5540 loadPtr(Address(scratch
, Shape::offsetOfBaseShape()), scratch
);
5541 loadPtr(Address(scratch
, BaseShape::offsetOfRealm()), scratch
);
5542 loadPtr(Address(scratch
, Realm::offsetOfCompartment()), scratch
);
5543 branchPtr(cond
, compartment
, scratch
, label
);
5546 void MacroAssembler::branchTestObjCompartment(
5547 Condition cond
, Register obj
, const JS::Compartment
* compartment
,
5548 Register scratch
, Label
* label
) {
5549 MOZ_ASSERT(obj
!= scratch
);
5550 loadPtr(Address(obj
, JSObject::offsetOfShape()), scratch
);
5551 loadPtr(Address(scratch
, Shape::offsetOfBaseShape()), scratch
);
5552 loadPtr(Address(scratch
, BaseShape::offsetOfRealm()), scratch
);
5553 loadPtr(Address(scratch
, Realm::offsetOfCompartment()), scratch
);
5554 branchPtr(cond
, scratch
, ImmPtr(compartment
), label
);
5557 void MacroAssembler::branchIfNonNativeObj(Register obj
, Register scratch
,
5559 loadPtr(Address(obj
, JSObject::offsetOfShape()), scratch
);
5560 branchTest32(Assembler::Zero
,
5561 Address(scratch
, Shape::offsetOfImmutableFlags()),
5562 Imm32(Shape::isNativeBit()), label
);
5565 void MacroAssembler::branchIfObjectNotExtensible(Register obj
, Register scratch
,
5567 loadPtr(Address(obj
, JSObject::offsetOfShape()), scratch
);
5569 // Spectre-style checks are not needed here because we do not interpret data
5570 // based on this check.
5571 static_assert(sizeof(ObjectFlags
) == sizeof(uint16_t));
5572 load16ZeroExtend(Address(scratch
, Shape::offsetOfObjectFlags()), scratch
);
5573 branchTest32(Assembler::NonZero
, scratch
,
5574 Imm32(uint32_t(ObjectFlag::NotExtensible
)), label
);
5577 void MacroAssembler::branchTestObjectNeedsProxyResultValidation(
5578 Condition cond
, Register obj
, Register scratch
, Label
* label
) {
5579 MOZ_ASSERT(cond
== Assembler::Zero
|| cond
== Assembler::NonZero
);
5582 Label
* doValidation
= cond
== NonZero
? label
: &done
;
5583 Label
* skipValidation
= cond
== NonZero
? &done
: label
;
5585 loadPtr(Address(obj
, JSObject::offsetOfShape()), scratch
);
5586 branchTest32(Assembler::Zero
,
5587 Address(scratch
, Shape::offsetOfImmutableFlags()),
5588 Imm32(Shape::isNativeBit()), doValidation
);
5589 static_assert(sizeof(ObjectFlags
) == sizeof(uint16_t));
5590 load16ZeroExtend(Address(scratch
, Shape::offsetOfObjectFlags()), scratch
);
5591 branchTest32(Assembler::NonZero
, scratch
,
5592 Imm32(uint32_t(ObjectFlag::NeedsProxyGetSetResultValidation
)),
5595 loadPtr(Address(obj
, JSObject::offsetOfShape()), scratch
);
5596 loadPtr(Address(scratch
, Shape::offsetOfBaseShape()), scratch
);
5597 loadPtr(Address(scratch
, BaseShape::offsetOfClasp()), scratch
);
5598 loadPtr(Address(scratch
, offsetof(JSClass
, cOps
)), scratch
);
5599 branchTestPtr(Assembler::Zero
, scratch
, scratch
, skipValidation
);
5600 loadPtr(Address(scratch
, offsetof(JSClassOps
, resolve
)), scratch
);
5601 branchTestPtr(Assembler::NonZero
, scratch
, scratch
, doValidation
);
5605 void MacroAssembler::wasmTrap(wasm::Trap trap
,
5606 wasm::BytecodeOffset bytecodeOffset
) {
5607 FaultingCodeOffset fco
= wasmTrapInstruction();
5608 MOZ_ASSERT_IF(!oom(),
5609 currentOffset() - fco
.get() == WasmTrapInstructionLength
);
5611 append(trap
, wasm::TrapSite(wasm::TrapMachineInsn::OfficialUD
, fco
,
5615 std::pair
<CodeOffset
, uint32_t> MacroAssembler::wasmReserveStackChecked(
5616 uint32_t amount
, wasm::BytecodeOffset trapOffset
) {
5617 if (amount
> MAX_UNCHECKED_LEAF_FRAME_SIZE
) {
5618 // The frame is large. Don't bump sp until after the stack limit check so
5619 // that the trap handler isn't called with a wild sp.
5621 Register scratch
= ABINonArgReg0
;
5622 moveStackPtrTo(scratch
);
5625 branchPtr(Assembler::Below
, scratch
, Imm32(amount
), &trap
);
5626 subPtr(Imm32(amount
), scratch
);
5627 branchPtr(Assembler::Below
,
5628 Address(InstanceReg
, wasm::Instance::offsetOfStackLimit()),
5632 wasmTrap(wasm::Trap::StackOverflow
, trapOffset
);
5633 CodeOffset trapInsnOffset
= CodeOffset(currentOffset());
5636 reserveStack(amount
);
5637 return std::pair
<CodeOffset
, uint32_t>(trapInsnOffset
, 0);
5640 reserveStack(amount
);
5642 branchStackPtrRhs(Assembler::Below
,
5643 Address(InstanceReg
, wasm::Instance::offsetOfStackLimit()),
5645 wasmTrap(wasm::Trap::StackOverflow
, trapOffset
);
5646 CodeOffset trapInsnOffset
= CodeOffset(currentOffset());
5648 return std::pair
<CodeOffset
, uint32_t>(trapInsnOffset
, amount
);
5651 static void MoveDataBlock(MacroAssembler
& masm
, Register base
, int32_t from
,
5652 int32_t to
, uint32_t size
) {
5653 MOZ_ASSERT(base
!= masm
.getStackPointer());
5654 if (from
== to
|| size
== 0) {
5658 #ifdef JS_CODEGEN_ARM64
5659 vixl::UseScratchRegisterScope
temps(&masm
);
5660 const Register scratch
= temps
.AcquireX().asUnsized();
5661 #elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_X86)
5662 static constexpr Register scratch
= ABINonArgReg0
;
5664 #elif defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_MIPS64) || \
5665 defined(JS_CODEGEN_RISCV64)
5666 ScratchRegisterScope
scratch(masm
);
5667 #elif !defined(JS_CODEGEN_NONE)
5668 const Register scratch
= ScratchReg
;
5670 const Register scratch
= InvalidReg
;
5674 for (uint32_t i
= 0; i
< size
; i
+= sizeof(void*)) {
5675 masm
.loadPtr(Address(base
, from
+ i
), scratch
);
5676 masm
.storePtr(scratch
, Address(base
, to
+ i
));
5679 for (uint32_t i
= size
; i
> 0;) {
5681 masm
.loadPtr(Address(base
, from
+ i
), scratch
);
5682 masm
.storePtr(scratch
, Address(base
, to
+ i
));
5686 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_X86)
5691 struct ReturnCallTrampolineData
{
5692 #ifdef JS_CODEGEN_ARM
5693 uint32_t trampolineOffset
;
5695 CodeLabel trampoline
;
5699 static ReturnCallTrampolineData
MakeReturnCallTrampoline(MacroAssembler
& masm
) {
5700 uint32_t savedPushed
= masm
.framePushed();
5702 ReturnCallTrampolineData data
;
5705 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64)
5706 AutoForbidPoolsAndNops
afp(&masm
, 1);
5707 #elif defined(JS_CODEGEN_RISCV64)
5708 BlockTrampolinePoolScope
block_trampoline_pool(&masm
, 1);
5711 // Build simple trampoline code: load the instance slot from the frame,
5712 // restore FP, and return to prevous caller.
5713 #ifdef JS_CODEGEN_ARM
5714 data
.trampolineOffset
= masm
.currentOffset();
5716 masm
.bind(&data
.trampoline
);
5719 masm
.setFramePushed(AlignBytes(
5720 wasm::FrameWithInstances::sizeOfInstanceFieldsAndShadowStack(),
5721 WasmStackAlignment
));
5723 masm
.wasmMarkCallAsSlow();
5727 Address(masm
.getStackPointer(), WasmCallerInstanceOffsetBeforeCall
),
5729 masm
.switchToWasmInstanceRealm(ABINonArgReturnReg0
, ABINonArgReturnReg1
);
5730 masm
.moveToStackPtr(FramePointer
);
5731 #ifdef JS_CODEGEN_ARM64
5732 masm
.pop(FramePointer
, lr
);
5733 masm
.append(wasm::CodeRangeUnwindInfo::UseFpLr
, masm
.currentOffset());
5734 masm
.Mov(PseudoStackPointer64
, vixl::sp
);
5737 masm
.pop(FramePointer
);
5738 masm
.append(wasm::CodeRangeUnwindInfo::UseFp
, masm
.currentOffset());
5742 masm
.append(wasm::CodeRangeUnwindInfo::Normal
, masm
.currentOffset());
5743 masm
.setFramePushed(savedPushed
);
5747 // CollapseWasmFrame methods merge frames fields: callee parameters, instance
5748 // slots, and caller RA. See the diagram below. The C0 is the previous caller,
5749 // the C1 is the caller of the return call, and the C2 is the callee.
5751 // +-------------------+ +--------------------+
5752 // |C0 instance slots | |C0 instance slots |
5753 // +-------------------+ -+ +--------------------+ -+
5754 // | RA | | | RA | |
5755 // +-------------------+ | C0 +--------------------+ |C0
5756 // | FP | v | FP | v
5757 // +-------------------+ +--------------------+
5758 // |C0 private frame | |C0 private frame |
5759 // +-------------------+ +--------------------+
5760 // |C1 results area | |C1/C2 results area |
5761 // +-------------------+ +--------------------+
5762 // |C1 parameters | |? trampoline frame |
5763 // +-------------------+ +--------------------+
5764 // |C1 instance slots | |C2 parameters |
5765 // +-------------------+ -+ +--------------------+
5766 // |C0 RA | | |C2 instance slots’ |
5767 // +-------------------+ | C1 +--------------------+ -+
5768 // |C0 FP | v |C0 RA’ | |
5769 // +-------------------+ +--------------------+ | C2
5770 // |C1 private frame | |C0 FP’ | v
5771 // +-------------------+ +--------------------+ <= start of C2
5773 // +-------------------+
5774 // |C2 instance slots |
5775 // +-------------------+ <= call C2
5777 // The C2 parameters are moved in place of the C1 parameters, and the
5778 // C1 frame data is removed. The instance slots, return address, and
5779 // frame pointer to the C0 callsite are saved or adjusted.
5781 // For cross-instance calls, the trampoline frame will be introduced
5782 // if the C0 callsite has no ability to restore instance registers and realm.
5784 static void CollapseWasmFrameFast(MacroAssembler
& masm
,
5785 const ReturnCallAdjustmentInfo
& retCallInfo
) {
5786 uint32_t framePushedAtStart
= masm
.framePushed();
5787 static_assert(sizeof(wasm::Frame
) == 2 * sizeof(void*));
5789 // The instance slots + stack arguments are expected to be padded and
5790 // aligned to the WasmStackAlignment boundary. There is no data expected
5791 // in the padded region, such as results stack area or locals, to avoid
5792 // unwanted stack growth.
5793 uint32_t newSlotsAndStackArgBytes
=
5794 AlignBytes(retCallInfo
.newSlotsAndStackArgBytes
, WasmStackAlignment
);
5795 uint32_t oldSlotsAndStackArgBytes
=
5796 AlignBytes(retCallInfo
.oldSlotsAndStackArgBytes
, WasmStackAlignment
);
5798 static constexpr Register tempForCaller
= WasmTailCallInstanceScratchReg
;
5799 static constexpr Register tempForFP
= WasmTailCallFPScratchReg
;
5800 static constexpr Register tempForRA
= WasmTailCallRAScratchReg
;
5801 #ifndef JS_USE_LINK_REGISTER
5802 masm
.push(tempForRA
);
5805 // Load the FP, RA, and instance slots into registers to preserve them while
5806 // the new frame is collapsed over the current one.
5807 masm
.loadPtr(Address(FramePointer
, wasm::Frame::callerFPOffset()), tempForFP
);
5808 masm
.loadPtr(Address(FramePointer
, wasm::Frame::returnAddressOffset()),
5810 masm
.append(wasm::CodeRangeUnwindInfo::RestoreFpRa
, masm
.currentOffset());
5811 bool copyCallerSlot
= oldSlotsAndStackArgBytes
!= newSlotsAndStackArgBytes
;
5812 if (copyCallerSlot
) {
5814 Address(FramePointer
, wasm::FrameWithInstances::callerInstanceOffset()),
5818 // Copy parameters data, ignoring shadow data and instance slots.
5819 // Make all offsets relative to the FramePointer.
5820 int32_t newArgSrc
= -framePushedAtStart
;
5821 int32_t newArgDest
=
5822 sizeof(wasm::Frame
) + oldSlotsAndStackArgBytes
- newSlotsAndStackArgBytes
;
5823 const uint32_t SlotsSize
=
5824 wasm::FrameWithInstances::sizeOfInstanceFieldsAndShadowStack();
5825 MoveDataBlock(masm
, FramePointer
, newArgSrc
+ SlotsSize
,
5826 newArgDest
+ SlotsSize
,
5827 retCallInfo
.newSlotsAndStackArgBytes
- SlotsSize
);
5829 // Copy caller instance slots from the current frame.
5830 if (copyCallerSlot
) {
5833 Address(FramePointer
, newArgDest
+ WasmCallerInstanceOffsetBeforeCall
));
5836 // Store current instance as the new callee instance slot.
5839 Address(FramePointer
, newArgDest
+ WasmCalleeInstanceOffsetBeforeCall
));
5841 #ifdef JS_USE_LINK_REGISTER
5842 // RA is already in its place, just move stack.
5843 masm
.addToStackPtr(Imm32(framePushedAtStart
+ newArgDest
));
5845 // Push RA to new frame: store RA, restore temp, and move stack.
5846 int32_t newFrameOffset
= newArgDest
- sizeof(wasm::Frame
);
5847 masm
.storePtr(tempForRA
,
5848 Address(FramePointer
,
5849 newFrameOffset
+ wasm::Frame::returnAddressOffset()));
5850 // Restore tempForRA, but keep RA on top of the stack.
5851 // There is no non-locking exchange instruction between register and memory.
5852 // Using tempForCaller as scratch register.
5853 masm
.loadPtr(Address(masm
.getStackPointer(), 0), tempForCaller
);
5854 masm
.storePtr(tempForRA
, Address(masm
.getStackPointer(), 0));
5855 masm
.mov(tempForCaller
, tempForRA
);
5856 masm
.append(wasm::CodeRangeUnwindInfo::RestoreFp
, masm
.currentOffset());
5857 masm
.addToStackPtr(Imm32(framePushedAtStart
+ newFrameOffset
+
5858 wasm::Frame::returnAddressOffset() + sizeof(void*)));
5861 masm
.movePtr(tempForFP
, FramePointer
);
5862 // Setting framePushed to pre-collapse state, to properly set that in the
5864 masm
.setFramePushed(framePushedAtStart
);
5867 static void CollapseWasmFrameSlow(MacroAssembler
& masm
,
5868 const ReturnCallAdjustmentInfo
& retCallInfo
,
5869 wasm::CallSiteDesc desc
,
5870 ReturnCallTrampolineData data
) {
5871 uint32_t framePushedAtStart
= masm
.framePushed();
5872 static constexpr Register tempForCaller
= WasmTailCallInstanceScratchReg
;
5873 static constexpr Register tempForFP
= WasmTailCallFPScratchReg
;
5874 static constexpr Register tempForRA
= WasmTailCallRAScratchReg
;
5876 static_assert(sizeof(wasm::Frame
) == 2 * sizeof(void*));
5878 // The hidden frame will "break" after wasm::Frame data fields.
5879 // Calculate sum of wasm stack alignment before and after the break as
5880 // the size to reserve.
5881 const uint32_t HiddenFrameAfterSize
=
5882 AlignBytes(wasm::FrameWithInstances::sizeOfInstanceFieldsAndShadowStack(),
5883 WasmStackAlignment
);
5884 const uint32_t HiddenFrameSize
=
5885 AlignBytes(sizeof(wasm::Frame
), WasmStackAlignment
) +
5886 HiddenFrameAfterSize
;
5888 // If it is not slow, prepare two frame: one is regular wasm frame, and
5889 // another one is hidden. The hidden frame contains one instance slots
5890 // for unwind and recovering pinned registers.
5891 // The instance slots + stack arguments are expected to be padded and
5892 // aligned to the WasmStackAlignment boundary. There is no data expected
5893 // in the padded region, such as results stack area or locals, to avoid
5894 // unwanted stack growth.
5895 // The Hidden frame will be inserted with this constraint too.
5896 uint32_t newSlotsAndStackArgBytes
=
5897 AlignBytes(retCallInfo
.newSlotsAndStackArgBytes
, WasmStackAlignment
);
5898 uint32_t oldSlotsAndStackArgBytes
=
5899 AlignBytes(retCallInfo
.oldSlotsAndStackArgBytes
, WasmStackAlignment
);
5901 // Make all offsets relative to the FramePointer.
5902 int32_t newArgSrc
= -framePushedAtStart
;
5903 int32_t newArgDest
= sizeof(wasm::Frame
) + oldSlotsAndStackArgBytes
-
5904 HiddenFrameSize
- newSlotsAndStackArgBytes
;
5905 int32_t hiddenFrameArgsDest
=
5906 sizeof(wasm::Frame
) + oldSlotsAndStackArgBytes
- HiddenFrameAfterSize
;
5908 // It will be possible to overwrite data (on the top of the stack) due to
5909 // the added hidden frame, reserve needed space.
5910 uint32_t reserved
= newArgDest
- int32_t(sizeof(void*)) < newArgSrc
5911 ? newArgSrc
- newArgDest
+ sizeof(void*)
5913 masm
.reserveStack(reserved
);
5915 #ifndef JS_USE_LINK_REGISTER
5916 masm
.push(tempForRA
);
5919 // Load FP, RA and instance slots to preserve them from being overwritten.
5920 masm
.loadPtr(Address(FramePointer
, wasm::Frame::callerFPOffset()), tempForFP
);
5921 masm
.loadPtr(Address(FramePointer
, wasm::Frame::returnAddressOffset()),
5923 masm
.append(wasm::CodeRangeUnwindInfo::RestoreFpRa
, masm
.currentOffset());
5925 Address(FramePointer
, newArgSrc
+ WasmCallerInstanceOffsetBeforeCall
),
5928 // Copy parameters data, ignoring shadow data and instance slots.
5929 const uint32_t SlotsSize
=
5930 wasm::FrameWithInstances::sizeOfInstanceFieldsAndShadowStack();
5931 MoveDataBlock(masm
, FramePointer
, newArgSrc
+ SlotsSize
,
5932 newArgDest
+ SlotsSize
,
5933 retCallInfo
.newSlotsAndStackArgBytes
- SlotsSize
);
5935 // Form hidden frame for trampoline.
5936 int32_t newFPOffset
= hiddenFrameArgsDest
- sizeof(wasm::Frame
);
5939 Address(FramePointer
, newFPOffset
+ wasm::Frame::returnAddressOffset()));
5941 // Copy original FP.
5944 Address(FramePointer
, newFPOffset
+ wasm::Frame::callerFPOffset()));
5946 // Set up instance slots.
5949 Address(FramePointer
,
5950 newFPOffset
+ wasm::FrameWithInstances::calleeInstanceOffset()));
5953 Address(FramePointer
, newArgDest
+ WasmCallerInstanceOffsetBeforeCall
));
5956 Address(FramePointer
, newArgDest
+ WasmCalleeInstanceOffsetBeforeCall
));
5958 #ifdef JS_CODEGEN_ARM
5959 // ARM has no CodeLabel -- calculate PC directly.
5960 masm
.mov(pc
, tempForRA
);
5961 masm
.computeEffectiveAddress(
5963 int32_t(data
.trampolineOffset
- masm
.currentOffset() - 4)),
5965 masm
.append(desc
, CodeOffset(data
.trampolineOffset
));
5967 masm
.mov(&data
.trampoline
, tempForRA
);
5969 masm
.addCodeLabel(data
.trampoline
);
5970 // Add slow trampoline callsite description, to be annotated in
5971 // stack/frame iterators.
5972 masm
.append(desc
, *data
.trampoline
.target());
5975 #ifdef JS_USE_LINK_REGISTER
5976 masm
.freeStack(reserved
);
5977 // RA is already in its place, just move stack.
5978 masm
.addToStackPtr(Imm32(framePushedAtStart
+ newArgDest
));
5980 // Push RA to new frame: store RA, restore temp, and move stack.
5981 int32_t newFrameOffset
= newArgDest
- sizeof(wasm::Frame
);
5982 masm
.storePtr(tempForRA
,
5983 Address(FramePointer
,
5984 newFrameOffset
+ wasm::Frame::returnAddressOffset()));
5985 // Restore tempForRA, but keep RA on top of the stack.
5986 // There is no non-locking exchange instruction between register and memory.
5987 // Using tempForCaller as scratch register.
5988 masm
.loadPtr(Address(masm
.getStackPointer(), 0), tempForCaller
);
5989 masm
.storePtr(tempForRA
, Address(masm
.getStackPointer(), 0));
5990 masm
.mov(tempForCaller
, tempForRA
);
5991 masm
.append(wasm::CodeRangeUnwindInfo::RestoreFp
, masm
.currentOffset());
5992 masm
.addToStackPtr(Imm32(framePushedAtStart
+ newFrameOffset
+
5993 wasm::Frame::returnAddressOffset() + reserved
+
5997 // Point FramePointer to hidden frame.
5998 masm
.computeEffectiveAddress(Address(FramePointer
, newFPOffset
),
6000 // Setting framePushed to pre-collapse state, to properly set that in the
6002 masm
.setFramePushed(framePushedAtStart
);
6005 void MacroAssembler::wasmCollapseFrameFast(
6006 const ReturnCallAdjustmentInfo
& retCallInfo
) {
6007 CollapseWasmFrameFast(*this, retCallInfo
);
6010 void MacroAssembler::wasmCollapseFrameSlow(
6011 const ReturnCallAdjustmentInfo
& retCallInfo
, wasm::CallSiteDesc desc
) {
6012 static constexpr Register temp1
= ABINonArgReg1
;
6013 static constexpr Register temp2
= ABINonArgReg3
;
6015 // Check if RA has slow marker. If there is no marker, generate a trampoline
6016 // frame to restore register state when this tail call returns.
6019 loadPtr(Address(FramePointer
, wasm::Frame::returnAddressOffset()), temp1
);
6020 wasmCheckSlowCallsite(temp1
, &slow
, temp1
, temp2
);
6021 CollapseWasmFrameFast(*this, retCallInfo
);
6023 append(wasm::CodeRangeUnwindInfo::Normal
, currentOffset());
6025 ReturnCallTrampolineData data
= MakeReturnCallTrampoline(*this);
6028 CollapseWasmFrameSlow(*this, retCallInfo
, desc
, data
);
6033 CodeOffset
MacroAssembler::wasmCallImport(const wasm::CallSiteDesc
& desc
,
6034 const wasm::CalleeDesc
& callee
) {
6035 storePtr(InstanceReg
,
6036 Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall
));
6038 // Load the callee, before the caller's registers are clobbered.
6039 uint32_t instanceDataOffset
= callee
.importInstanceDataOffset();
6041 Address(InstanceReg
, wasm::Instance::offsetInData(
6042 instanceDataOffset
+
6043 offsetof(wasm::FuncImportInstanceData
, code
))),
6046 #if !defined(JS_CODEGEN_NONE) && !defined(JS_CODEGEN_WASM32)
6047 static_assert(ABINonArgReg0
!= InstanceReg
, "by constraint");
6050 // Switch to the callee's realm.
6052 Address(InstanceReg
, wasm::Instance::offsetInData(
6053 instanceDataOffset
+
6054 offsetof(wasm::FuncImportInstanceData
, realm
))),
6056 loadPtr(Address(InstanceReg
, wasm::Instance::offsetOfCx()), ABINonArgReg2
);
6057 storePtr(ABINonArgReg1
, Address(ABINonArgReg2
, JSContext::offsetOfRealm()));
6059 // Switch to the callee's instance and pinned registers and make the call.
6060 loadPtr(Address(InstanceReg
,
6061 wasm::Instance::offsetInData(
6062 instanceDataOffset
+
6063 offsetof(wasm::FuncImportInstanceData
, instance
))),
6066 storePtr(InstanceReg
,
6067 Address(getStackPointer(), WasmCalleeInstanceOffsetBeforeCall
));
6068 loadWasmPinnedRegsFromInstance();
6070 return wasmMarkedSlowCall(desc
, ABINonArgReg0
);
6073 CodeOffset
MacroAssembler::wasmReturnCallImport(
6074 const wasm::CallSiteDesc
& desc
, const wasm::CalleeDesc
& callee
,
6075 const ReturnCallAdjustmentInfo
& retCallInfo
) {
6076 storePtr(InstanceReg
,
6077 Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall
));
6079 // Load the callee, before the caller's registers are clobbered.
6080 uint32_t instanceDataOffset
= callee
.importInstanceDataOffset();
6082 Address(InstanceReg
, wasm::Instance::offsetInData(
6083 instanceDataOffset
+
6084 offsetof(wasm::FuncImportInstanceData
, code
))),
6087 #if !defined(JS_CODEGEN_NONE) && !defined(JS_CODEGEN_WASM32)
6088 static_assert(ABINonArgReg0
!= InstanceReg
, "by constraint");
6091 // Switch to the callee's realm.
6093 Address(InstanceReg
, wasm::Instance::offsetInData(
6094 instanceDataOffset
+
6095 offsetof(wasm::FuncImportInstanceData
, realm
))),
6097 loadPtr(Address(InstanceReg
, wasm::Instance::offsetOfCx()), ABINonArgReg2
);
6098 storePtr(ABINonArgReg1
, Address(ABINonArgReg2
, JSContext::offsetOfRealm()));
6100 // Switch to the callee's instance and pinned registers and make the call.
6101 loadPtr(Address(InstanceReg
,
6102 wasm::Instance::offsetInData(
6103 instanceDataOffset
+
6104 offsetof(wasm::FuncImportInstanceData
, instance
))),
6107 storePtr(InstanceReg
,
6108 Address(getStackPointer(), WasmCalleeInstanceOffsetBeforeCall
));
6109 loadWasmPinnedRegsFromInstance();
6111 wasm::CallSiteDesc
stubDesc(desc
.lineOrBytecode(),
6112 wasm::CallSiteDesc::ReturnStub
);
6113 wasmCollapseFrameSlow(retCallInfo
, stubDesc
);
6114 jump(ABINonArgReg0
);
6115 append(wasm::CodeRangeUnwindInfo::Normal
, currentOffset());
6116 return CodeOffset(currentOffset());
6119 CodeOffset
MacroAssembler::wasmReturnCall(
6120 const wasm::CallSiteDesc
& desc
, uint32_t funcDefIndex
,
6121 const ReturnCallAdjustmentInfo
& retCallInfo
) {
6122 wasmCollapseFrameFast(retCallInfo
);
6123 CodeOffset offset
= farJumpWithPatch();
6124 append(desc
, offset
, funcDefIndex
);
6125 append(wasm::CodeRangeUnwindInfo::Normal
, currentOffset());
6129 CodeOffset
MacroAssembler::wasmCallBuiltinInstanceMethod(
6130 const wasm::CallSiteDesc
& desc
, const ABIArg
& instanceArg
,
6131 wasm::SymbolicAddress builtin
, wasm::FailureMode failureMode
) {
6132 MOZ_ASSERT(instanceArg
!= ABIArg());
6134 storePtr(InstanceReg
,
6135 Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall
));
6136 storePtr(InstanceReg
,
6137 Address(getStackPointer(), WasmCalleeInstanceOffsetBeforeCall
));
6139 if (instanceArg
.kind() == ABIArg::GPR
) {
6140 movePtr(InstanceReg
, instanceArg
.gpr());
6141 } else if (instanceArg
.kind() == ABIArg::Stack
) {
6142 storePtr(InstanceReg
,
6143 Address(getStackPointer(), instanceArg
.offsetFromArgBase()));
6145 MOZ_CRASH("Unknown abi passing style for pointer");
6148 CodeOffset ret
= call(desc
, builtin
);
6149 wasmTrapOnFailedInstanceCall(ReturnReg
, failureMode
,
6150 wasm::BytecodeOffset(desc
.lineOrBytecode()));
6155 void MacroAssembler::wasmTrapOnFailedInstanceCall(
6156 Register resultRegister
, wasm::FailureMode failureMode
,
6157 wasm::BytecodeOffset bytecodeOffset
) {
6159 switch (failureMode
) {
6160 case wasm::FailureMode::Infallible
:
6162 case wasm::FailureMode::FailOnNegI32
:
6163 branchTest32(Assembler::NotSigned
, resultRegister
, resultRegister
,
6166 case wasm::FailureMode::FailOnMaxI32
:
6167 branchPtr(Assembler::NotEqual
, resultRegister
,
6168 ImmWord(uintptr_t(INT32_MAX
)), &noTrap
);
6170 case wasm::FailureMode::FailOnNullPtr
:
6171 branchTestPtr(Assembler::NonZero
, resultRegister
, resultRegister
,
6174 case wasm::FailureMode::FailOnInvalidRef
:
6175 branchPtr(Assembler::NotEqual
, resultRegister
,
6176 ImmWord(uintptr_t(wasm::AnyRef::invalid().forCompiledCode())),
6180 wasmTrap(wasm::Trap::ThrowReported
, bytecodeOffset
);
6184 CodeOffset
MacroAssembler::asmCallIndirect(const wasm::CallSiteDesc
& desc
,
6185 const wasm::CalleeDesc
& callee
) {
6186 MOZ_ASSERT(callee
.which() == wasm::CalleeDesc::AsmJSTable
);
6188 const Register scratch
= WasmTableCallScratchReg0
;
6189 const Register index
= WasmTableCallIndexReg
;
6191 // Optimization opportunity: when offsetof(FunctionTableElem, code) == 0, as
6192 // it is at present, we can probably generate better code here by folding
6193 // the address computation into the load.
6195 static_assert(sizeof(wasm::FunctionTableElem
) == 8 ||
6196 sizeof(wasm::FunctionTableElem
) == 16,
6197 "elements of function tables are two words");
6199 // asm.js tables require no signature check, and have had their index
6200 // masked into range and thus need no bounds check.
6202 Address(InstanceReg
, wasm::Instance::offsetInData(
6203 callee
.tableFunctionBaseInstanceDataOffset())),
6205 if (sizeof(wasm::FunctionTableElem
) == 8) {
6206 computeEffectiveAddress(BaseIndex(scratch
, index
, TimesEight
), scratch
);
6208 lshift32(Imm32(4), index
);
6209 addPtr(index
, scratch
);
6211 loadPtr(Address(scratch
, offsetof(wasm::FunctionTableElem
, code
)), scratch
);
6212 storePtr(InstanceReg
,
6213 Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall
));
6214 storePtr(InstanceReg
,
6215 Address(getStackPointer(), WasmCalleeInstanceOffsetBeforeCall
));
6216 return call(desc
, scratch
);
6219 // In principle, call_indirect requires an expensive context switch to the
6220 // callee's instance and realm before the call and an almost equally expensive
6221 // switch back to the caller's ditto after. However, if the caller's instance
6222 // is the same as the callee's instance then no context switch is required, and
6223 // it only takes a compare-and-branch at run-time to test this - all values are
6224 // in registers already. We therefore generate two call paths, one for the fast
6225 // call without the context switch (which additionally avoids a null check) and
6226 // one for the slow call with the context switch.
6228 void MacroAssembler::wasmCallIndirect(const wasm::CallSiteDesc
& desc
,
6229 const wasm::CalleeDesc
& callee
,
6230 Label
* boundsCheckFailedLabel
,
6231 Label
* nullCheckFailedLabel
,
6232 mozilla::Maybe
<uint32_t> tableSize
,
6233 CodeOffset
* fastCallOffset
,
6234 CodeOffset
* slowCallOffset
) {
6235 static_assert(sizeof(wasm::FunctionTableElem
) == 2 * sizeof(void*),
6236 "Exactly two pointers or index scaling won't work correctly");
6237 MOZ_ASSERT(callee
.which() == wasm::CalleeDesc::WasmTable
);
6239 const int shift
= sizeof(wasm::FunctionTableElem
) == 8 ? 3 : 4;
6240 wasm::BytecodeOffset
trapOffset(desc
.lineOrBytecode());
6241 const Register calleeScratch
= WasmTableCallScratchReg0
;
6242 const Register index
= WasmTableCallIndexReg
;
6244 // Check the table index and throw if out-of-bounds.
6246 // Frequently the table size is known, so optimize for that. Otherwise
6247 // compare with a memory operand when that's possible. (There's little sense
6248 // in hoisting the load of the bound into a register at a higher level and
6249 // reusing that register, because a hoisted value would either have to be
6250 // spilled and re-loaded before the next call_indirect, or would be abandoned
6251 // because we could not trust that a hoisted value would not have changed.)
6253 if (boundsCheckFailedLabel
) {
6254 if (tableSize
.isSome()) {
6255 branch32(Assembler::Condition::AboveOrEqual
, index
, Imm32(*tableSize
),
6256 boundsCheckFailedLabel
);
6259 Assembler::Condition::BelowOrEqual
,
6260 Address(InstanceReg
, wasm::Instance::offsetInData(
6261 callee
.tableLengthInstanceDataOffset())),
6262 index
, boundsCheckFailedLabel
);
6266 // Write the functype-id into the ABI functype-id register.
6268 const wasm::CallIndirectId callIndirectId
= callee
.wasmTableSigId();
6269 switch (callIndirectId
.kind()) {
6270 case wasm::CallIndirectIdKind::Global
:
6271 loadPtr(Address(InstanceReg
, wasm::Instance::offsetInData(
6272 callIndirectId
.instanceDataOffset() +
6273 offsetof(wasm::TypeDefInstanceData
,
6275 WasmTableCallSigReg
);
6277 case wasm::CallIndirectIdKind::Immediate
:
6278 move32(Imm32(callIndirectId
.immediate()), WasmTableCallSigReg
);
6280 case wasm::CallIndirectIdKind::AsmJS
:
6281 case wasm::CallIndirectIdKind::None
:
6285 // Load the base pointer of the table and compute the address of the callee in
6289 Address(InstanceReg
, wasm::Instance::offsetInData(
6290 callee
.tableFunctionBaseInstanceDataOffset())),
6292 shiftIndex32AndAdd(index
, shift
, calleeScratch
);
6294 // Load the callee instance and decide whether to take the fast path or the
6299 const Register newInstanceTemp
= WasmTableCallScratchReg1
;
6300 loadPtr(Address(calleeScratch
, offsetof(wasm::FunctionTableElem
, instance
)),
6302 branchPtr(Assembler::Equal
, InstanceReg
, newInstanceTemp
, &fastCall
);
6304 // Slow path: Save context, check for null, setup new context, call, restore
6307 // TODO: The slow path could usefully be out-of-line and the test above would
6308 // just fall through to the fast path. This keeps the fast-path code dense,
6309 // and has correct static prediction for the branch (forward conditional
6310 // branches predicted not taken, normally).
6312 storePtr(InstanceReg
,
6313 Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall
));
6314 movePtr(newInstanceTemp
, InstanceReg
);
6315 storePtr(InstanceReg
,
6316 Address(getStackPointer(), WasmCalleeInstanceOffsetBeforeCall
));
6318 #ifdef WASM_HAS_HEAPREG
6319 // Use the null pointer exception resulting from loading HeapReg from a null
6320 // instance to handle a call to a null slot.
6321 MOZ_ASSERT(nullCheckFailedLabel
== nullptr);
6322 loadWasmPinnedRegsFromInstance(mozilla::Some(trapOffset
));
6324 MOZ_ASSERT(nullCheckFailedLabel
!= nullptr);
6325 branchTestPtr(Assembler::Zero
, InstanceReg
, InstanceReg
,
6326 nullCheckFailedLabel
);
6328 loadWasmPinnedRegsFromInstance();
6330 switchToWasmInstanceRealm(index
, WasmTableCallScratchReg1
);
6332 loadPtr(Address(calleeScratch
, offsetof(wasm::FunctionTableElem
, code
)),
6335 *slowCallOffset
= wasmMarkedSlowCall(desc
, calleeScratch
);
6337 // Restore registers and realm and join up with the fast path.
6339 loadPtr(Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall
),
6341 loadWasmPinnedRegsFromInstance();
6342 switchToWasmInstanceRealm(ABINonArgReturnReg0
, ABINonArgReturnReg1
);
6345 // Fast path: just load the code pointer and go. The instance and heap
6346 // register are the same as in the caller, and nothing will be null.
6348 // (In particular, the code pointer will not be null: if it were, the instance
6349 // would have been null, and then it would not have been equivalent to our
6350 // current instance. So no null check is needed on the fast path.)
6354 loadPtr(Address(calleeScratch
, offsetof(wasm::FunctionTableElem
, code
)),
6357 // We use a different type of call site for the fast call since the instance
6358 // slots in the frame do not have valid values.
6360 wasm::CallSiteDesc
newDesc(desc
.lineOrBytecode(),
6361 wasm::CallSiteDesc::IndirectFast
);
6362 *fastCallOffset
= call(newDesc
, calleeScratch
);
6367 void MacroAssembler::wasmReturnCallIndirect(
6368 const wasm::CallSiteDesc
& desc
, const wasm::CalleeDesc
& callee
,
6369 Label
* boundsCheckFailedLabel
, Label
* nullCheckFailedLabel
,
6370 mozilla::Maybe
<uint32_t> tableSize
,
6371 const ReturnCallAdjustmentInfo
& retCallInfo
) {
6372 static_assert(sizeof(wasm::FunctionTableElem
) == 2 * sizeof(void*),
6373 "Exactly two pointers or index scaling won't work correctly");
6374 MOZ_ASSERT(callee
.which() == wasm::CalleeDesc::WasmTable
);
6376 const int shift
= sizeof(wasm::FunctionTableElem
) == 8 ? 3 : 4;
6377 wasm::BytecodeOffset
trapOffset(desc
.lineOrBytecode());
6378 const Register calleeScratch
= WasmTableCallScratchReg0
;
6379 const Register index
= WasmTableCallIndexReg
;
6381 // Check the table index and throw if out-of-bounds.
6383 // Frequently the table size is known, so optimize for that. Otherwise
6384 // compare with a memory operand when that's possible. (There's little sense
6385 // in hoisting the load of the bound into a register at a higher level and
6386 // reusing that register, because a hoisted value would either have to be
6387 // spilled and re-loaded before the next call_indirect, or would be abandoned
6388 // because we could not trust that a hoisted value would not have changed.)
6390 if (boundsCheckFailedLabel
) {
6391 if (tableSize
.isSome()) {
6392 branch32(Assembler::Condition::AboveOrEqual
, index
, Imm32(*tableSize
),
6393 boundsCheckFailedLabel
);
6396 Assembler::Condition::BelowOrEqual
,
6397 Address(InstanceReg
, wasm::Instance::offsetInData(
6398 callee
.tableLengthInstanceDataOffset())),
6399 index
, boundsCheckFailedLabel
);
6403 // Write the functype-id into the ABI functype-id register.
6405 const wasm::CallIndirectId callIndirectId
= callee
.wasmTableSigId();
6406 switch (callIndirectId
.kind()) {
6407 case wasm::CallIndirectIdKind::Global
:
6408 loadPtr(Address(InstanceReg
, wasm::Instance::offsetInData(
6409 callIndirectId
.instanceDataOffset() +
6410 offsetof(wasm::TypeDefInstanceData
,
6412 WasmTableCallSigReg
);
6414 case wasm::CallIndirectIdKind::Immediate
:
6415 move32(Imm32(callIndirectId
.immediate()), WasmTableCallSigReg
);
6417 case wasm::CallIndirectIdKind::AsmJS
:
6418 case wasm::CallIndirectIdKind::None
:
6422 // Load the base pointer of the table and compute the address of the callee in
6426 Address(InstanceReg
, wasm::Instance::offsetInData(
6427 callee
.tableFunctionBaseInstanceDataOffset())),
6429 shiftIndex32AndAdd(index
, shift
, calleeScratch
);
6431 // Load the callee instance and decide whether to take the fast path or the
6436 const Register newInstanceTemp
= WasmTableCallScratchReg1
;
6437 loadPtr(Address(calleeScratch
, offsetof(wasm::FunctionTableElem
, instance
)),
6439 branchPtr(Assembler::Equal
, InstanceReg
, newInstanceTemp
, &fastCall
);
6441 // Slow path: Save context, check for null, setup new context.
6443 storePtr(InstanceReg
,
6444 Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall
));
6445 movePtr(newInstanceTemp
, InstanceReg
);
6447 #ifdef WASM_HAS_HEAPREG
6448 // Use the null pointer exception resulting from loading HeapReg from a null
6449 // instance to handle a call to a null slot.
6450 MOZ_ASSERT(nullCheckFailedLabel
== nullptr);
6451 loadWasmPinnedRegsFromInstance(mozilla::Some(trapOffset
));
6453 MOZ_ASSERT(nullCheckFailedLabel
!= nullptr);
6454 branchTestPtr(Assembler::Zero
, InstanceReg
, InstanceReg
,
6455 nullCheckFailedLabel
);
6457 loadWasmPinnedRegsFromInstance();
6459 switchToWasmInstanceRealm(index
, WasmTableCallScratchReg1
);
6461 loadPtr(Address(calleeScratch
, offsetof(wasm::FunctionTableElem
, code
)),
6464 wasm::CallSiteDesc
stubDesc(desc
.lineOrBytecode(),
6465 wasm::CallSiteDesc::ReturnStub
);
6466 wasmCollapseFrameSlow(retCallInfo
, stubDesc
);
6467 jump(calleeScratch
);
6468 append(wasm::CodeRangeUnwindInfo::Normal
, currentOffset());
6470 // Fast path: just load the code pointer and go.
6474 loadPtr(Address(calleeScratch
, offsetof(wasm::FunctionTableElem
, code
)),
6477 wasmCollapseFrameFast(retCallInfo
);
6478 jump(calleeScratch
);
6479 append(wasm::CodeRangeUnwindInfo::Normal
, currentOffset());
6482 void MacroAssembler::wasmCallRef(const wasm::CallSiteDesc
& desc
,
6483 const wasm::CalleeDesc
& callee
,
6484 CodeOffset
* fastCallOffset
,
6485 CodeOffset
* slowCallOffset
) {
6486 MOZ_ASSERT(callee
.which() == wasm::CalleeDesc::FuncRef
);
6487 const Register calleeScratch
= WasmCallRefCallScratchReg0
;
6488 const Register calleeFnObj
= WasmCallRefReg
;
6490 // Load from the function's WASM_INSTANCE_SLOT extended slot, and decide
6491 // whether to take the fast path or the slow path. Register this load
6492 // instruction to be source of a trap -- null pointer check.
6496 const Register newInstanceTemp
= WasmCallRefCallScratchReg1
;
6497 size_t instanceSlotOffset
= FunctionExtended::offsetOfExtendedSlot(
6498 FunctionExtended::WASM_INSTANCE_SLOT
);
6499 static_assert(FunctionExtended::WASM_INSTANCE_SLOT
< wasm::NullPtrGuardSize
);
6500 wasm::BytecodeOffset
trapOffset(desc
.lineOrBytecode());
6501 FaultingCodeOffset fco
=
6502 loadPtr(Address(calleeFnObj
, instanceSlotOffset
), newInstanceTemp
);
6503 append(wasm::Trap::NullPointerDereference
,
6504 wasm::TrapSite(wasm::TrapMachineInsnForLoadWord(), fco
, trapOffset
));
6505 branchPtr(Assembler::Equal
, InstanceReg
, newInstanceTemp
, &fastCall
);
6507 storePtr(InstanceReg
,
6508 Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall
));
6509 movePtr(newInstanceTemp
, InstanceReg
);
6510 storePtr(InstanceReg
,
6511 Address(getStackPointer(), WasmCalleeInstanceOffsetBeforeCall
));
6513 loadWasmPinnedRegsFromInstance();
6514 switchToWasmInstanceRealm(WasmCallRefCallScratchReg0
,
6515 WasmCallRefCallScratchReg1
);
6517 // Get funcUncheckedCallEntry() from the function's
6518 // WASM_FUNC_UNCHECKED_ENTRY_SLOT extended slot.
6519 size_t uncheckedEntrySlotOffset
= FunctionExtended::offsetOfExtendedSlot(
6520 FunctionExtended::WASM_FUNC_UNCHECKED_ENTRY_SLOT
);
6521 loadPtr(Address(calleeFnObj
, uncheckedEntrySlotOffset
), calleeScratch
);
6523 *slowCallOffset
= wasmMarkedSlowCall(desc
, calleeScratch
);
6525 // Restore registers and realm and back to this caller's.
6526 loadPtr(Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall
),
6528 loadWasmPinnedRegsFromInstance();
6529 switchToWasmInstanceRealm(ABINonArgReturnReg0
, ABINonArgReturnReg1
);
6532 // Fast path: just load WASM_FUNC_UNCHECKED_ENTRY_SLOT value and go.
6533 // The instance and pinned registers are the same as in the caller.
6537 loadPtr(Address(calleeFnObj
, uncheckedEntrySlotOffset
), calleeScratch
);
6539 // We use a different type of call site for the fast call since the instance
6540 // slots in the frame do not have valid values.
6542 wasm::CallSiteDesc
newDesc(desc
.lineOrBytecode(),
6543 wasm::CallSiteDesc::FuncRefFast
);
6544 *fastCallOffset
= call(newDesc
, calleeScratch
);
6549 void MacroAssembler::wasmReturnCallRef(
6550 const wasm::CallSiteDesc
& desc
, const wasm::CalleeDesc
& callee
,
6551 const ReturnCallAdjustmentInfo
& retCallInfo
) {
6552 MOZ_ASSERT(callee
.which() == wasm::CalleeDesc::FuncRef
);
6553 const Register calleeScratch
= WasmCallRefCallScratchReg0
;
6554 const Register calleeFnObj
= WasmCallRefReg
;
6556 // Load from the function's WASM_INSTANCE_SLOT extended slot, and decide
6557 // whether to take the fast path or the slow path. Register this load
6558 // instruction to be source of a trap -- null pointer check.
6562 const Register newInstanceTemp
= WasmCallRefCallScratchReg1
;
6563 size_t instanceSlotOffset
= FunctionExtended::offsetOfExtendedSlot(
6564 FunctionExtended::WASM_INSTANCE_SLOT
);
6565 static_assert(FunctionExtended::WASM_INSTANCE_SLOT
< wasm::NullPtrGuardSize
);
6566 wasm::BytecodeOffset
trapOffset(desc
.lineOrBytecode());
6567 FaultingCodeOffset fco
=
6568 loadPtr(Address(calleeFnObj
, instanceSlotOffset
), newInstanceTemp
);
6569 append(wasm::Trap::NullPointerDereference
,
6570 wasm::TrapSite(wasm::TrapMachineInsnForLoadWord(), fco
, trapOffset
));
6571 branchPtr(Assembler::Equal
, InstanceReg
, newInstanceTemp
, &fastCall
);
6573 storePtr(InstanceReg
,
6574 Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall
));
6575 movePtr(newInstanceTemp
, InstanceReg
);
6576 storePtr(InstanceReg
,
6577 Address(getStackPointer(), WasmCalleeInstanceOffsetBeforeCall
));
6579 loadWasmPinnedRegsFromInstance();
6580 switchToWasmInstanceRealm(WasmCallRefCallScratchReg0
,
6581 WasmCallRefCallScratchReg1
);
6583 // Get funcUncheckedCallEntry() from the function's
6584 // WASM_FUNC_UNCHECKED_ENTRY_SLOT extended slot.
6585 size_t uncheckedEntrySlotOffset
= FunctionExtended::offsetOfExtendedSlot(
6586 FunctionExtended::WASM_FUNC_UNCHECKED_ENTRY_SLOT
);
6587 loadPtr(Address(calleeFnObj
, uncheckedEntrySlotOffset
), calleeScratch
);
6589 wasm::CallSiteDesc
stubDesc(desc
.lineOrBytecode(),
6590 wasm::CallSiteDesc::ReturnStub
);
6591 wasmCollapseFrameSlow(retCallInfo
, stubDesc
);
6592 jump(calleeScratch
);
6593 append(wasm::CodeRangeUnwindInfo::Normal
, currentOffset());
6595 // Fast path: just load WASM_FUNC_UNCHECKED_ENTRY_SLOT value and go.
6596 // The instance and pinned registers are the same as in the caller.
6600 loadPtr(Address(calleeFnObj
, uncheckedEntrySlotOffset
), calleeScratch
);
6602 wasmCollapseFrameFast(retCallInfo
);
6603 jump(calleeScratch
);
6604 append(wasm::CodeRangeUnwindInfo::Normal
, currentOffset());
6607 void MacroAssembler::wasmBoundsCheckRange32(
6608 Register index
, Register length
, Register limit
, Register tmp
,
6609 wasm::BytecodeOffset bytecodeOffset
) {
6614 branchAdd32(Assembler::CarrySet
, length
, tmp
, &fail
);
6615 branch32(Assembler::Above
, tmp
, limit
, &fail
);
6619 wasmTrap(wasm::Trap::OutOfBounds
, bytecodeOffset
);
6624 #ifdef ENABLE_WASM_MEMORY64
6625 void MacroAssembler::wasmClampTable64Index(Register64 index
, Register out
) {
6628 branch64(Assembler::Above
, index
, Imm64(UINT32_MAX
), &oob
);
6629 move64To32(index
, out
);
6632 static_assert(wasm::MaxTableElemsRuntime
< UINT32_MAX
);
6633 move32(Imm32(UINT32_MAX
), out
);
6638 BranchWasmRefIsSubtypeRegisters
MacroAssembler::regsForBranchWasmRefIsSubtype(
6639 wasm::RefType type
) {
6640 MOZ_ASSERT(type
.isValid());
6641 switch (type
.hierarchy()) {
6642 case wasm::RefTypeHierarchy::Any
:
6643 return BranchWasmRefIsSubtypeRegisters
{
6644 .needSuperSTV
= type
.isTypeRef(),
6645 .needScratch1
= !type
.isNone() && !type
.isAny(),
6647 type
.isTypeRef() && type
.typeDef()->subTypingDepth() >=
6648 wasm::MinSuperTypeVectorLength
,
6650 case wasm::RefTypeHierarchy::Func
:
6651 return BranchWasmRefIsSubtypeRegisters
{
6652 .needSuperSTV
= type
.isTypeRef(),
6653 .needScratch1
= type
.isTypeRef(),
6655 type
.isTypeRef() && type
.typeDef()->subTypingDepth() >=
6656 wasm::MinSuperTypeVectorLength
,
6658 case wasm::RefTypeHierarchy::Extern
:
6659 case wasm::RefTypeHierarchy::Exn
:
6660 return BranchWasmRefIsSubtypeRegisters
{
6661 .needSuperSTV
= false,
6662 .needScratch1
= false,
6663 .needScratch2
= false,
6666 MOZ_CRASH("unknown type hierarchy for cast");
6670 void MacroAssembler::branchWasmRefIsSubtype(
6671 Register ref
, wasm::RefType sourceType
, wasm::RefType destType
,
6672 Label
* label
, bool onSuccess
, Register superSTV
, Register scratch1
,
6673 Register scratch2
) {
6674 switch (destType
.hierarchy()) {
6675 case wasm::RefTypeHierarchy::Any
: {
6676 branchWasmRefIsSubtypeAny(ref
, sourceType
, destType
, label
, onSuccess
,
6677 superSTV
, scratch1
, scratch2
);
6679 case wasm::RefTypeHierarchy::Func
: {
6680 branchWasmRefIsSubtypeFunc(ref
, sourceType
, destType
, label
, onSuccess
,
6681 superSTV
, scratch1
, scratch2
);
6683 case wasm::RefTypeHierarchy::Extern
: {
6684 branchWasmRefIsSubtypeExtern(ref
, sourceType
, destType
, label
, onSuccess
);
6686 case wasm::RefTypeHierarchy::Exn
: {
6687 branchWasmRefIsSubtypeExn(ref
, sourceType
, destType
, label
, onSuccess
);
6690 MOZ_CRASH("unknown type hierarchy for wasm cast");
6694 void MacroAssembler::branchWasmRefIsSubtypeAny(
6695 Register ref
, wasm::RefType sourceType
, wasm::RefType destType
,
6696 Label
* label
, bool onSuccess
, Register superSTV
, Register scratch1
,
6697 Register scratch2
) {
6698 MOZ_ASSERT(sourceType
.isValid());
6699 MOZ_ASSERT(destType
.isValid());
6700 MOZ_ASSERT(sourceType
.isAnyHierarchy());
6701 MOZ_ASSERT(destType
.isAnyHierarchy());
6703 mozilla::DebugOnly
<BranchWasmRefIsSubtypeRegisters
> needs
=
6704 regsForBranchWasmRefIsSubtype(destType
);
6705 MOZ_ASSERT_IF(needs
.value
.needSuperSTV
, superSTV
!= Register::Invalid());
6706 MOZ_ASSERT_IF(needs
.value
.needScratch1
, scratch1
!= Register::Invalid());
6707 MOZ_ASSERT_IF(needs
.value
.needScratch2
, scratch2
!= Register::Invalid());
6710 Label
* successLabel
= onSuccess
? label
: &fallthrough
;
6711 Label
* failLabel
= onSuccess
? &fallthrough
: label
;
6712 Label
* nullLabel
= destType
.isNullable() ? successLabel
: failLabel
;
6715 if (sourceType
.isNullable()) {
6716 branchWasmAnyRefIsNull(true, ref
, nullLabel
);
6719 // The only value that can inhabit 'none' is null. So, early out if we got
6721 if (destType
.isNone()) {
6727 if (destType
.isAny()) {
6728 // No further checks for 'any'
6734 // 'type' is now 'eq' or lower, which currently will either be a gc object or
6737 // Check first for i31 values, and get them out of the way. i31 values are
6738 // valid when casting to i31 or eq, and invalid otherwise.
6739 if (destType
.isI31() || destType
.isEq()) {
6740 branchWasmAnyRefIsI31(true, ref
, successLabel
);
6742 if (destType
.isI31()) {
6743 // No further checks for 'i31'
6750 // Then check for any kind of gc object.
6751 MOZ_ASSERT(scratch1
!= Register::Invalid());
6752 if (!wasm::RefType::isSubTypeOf(sourceType
, wasm::RefType::struct_()) &&
6753 !wasm::RefType::isSubTypeOf(sourceType
, wasm::RefType::array())) {
6754 branchWasmAnyRefIsObjectOrNull(false, ref
, failLabel
);
6755 branchObjectIsWasmGcObject(false, ref
, scratch1
, failLabel
);
6758 if (destType
.isEq()) {
6759 // No further checks for 'eq'
6765 // 'type' is now 'struct', 'array', or a concrete type. (Bottom types and i31
6766 // were handled above.)
6768 // Casting to a concrete type only requires a simple check on the
6769 // object's super type vector. Casting to an abstract type (struct, array)
6770 // requires loading the object's superTypeVector->typeDef->kind, and checking
6771 // that it is correct.
6773 loadPtr(Address(ref
, int32_t(WasmGcObject::offsetOfSuperTypeVector())),
6775 if (destType
.isTypeRef()) {
6776 // concrete type, do superTypeVector check
6777 branchWasmSTVIsSubtype(scratch1
, superSTV
, scratch2
,
6778 destType
.typeDef()->subTypingDepth(), successLabel
,
6781 // abstract type, do kind check
6782 loadPtr(Address(scratch1
,
6783 int32_t(wasm::SuperTypeVector::offsetOfSelfTypeDef())),
6785 load8ZeroExtend(Address(scratch1
, int32_t(wasm::TypeDef::offsetOfKind())),
6787 branch32(Assembler::Equal
, scratch1
, Imm32(int32_t(destType
.typeDefKind())),
6796 void MacroAssembler::branchWasmRefIsSubtypeFunc(
6797 Register ref
, wasm::RefType sourceType
, wasm::RefType destType
,
6798 Label
* label
, bool onSuccess
, Register superSTV
, Register scratch1
,
6799 Register scratch2
) {
6800 MOZ_ASSERT(sourceType
.isValid());
6801 MOZ_ASSERT(destType
.isValid());
6802 MOZ_ASSERT(sourceType
.isFuncHierarchy());
6803 MOZ_ASSERT(destType
.isFuncHierarchy());
6805 mozilla::DebugOnly
<BranchWasmRefIsSubtypeRegisters
> needs
=
6806 regsForBranchWasmRefIsSubtype(destType
);
6807 MOZ_ASSERT_IF(needs
.value
.needSuperSTV
, superSTV
!= Register::Invalid());
6808 MOZ_ASSERT_IF(needs
.value
.needScratch1
, scratch1
!= Register::Invalid());
6809 MOZ_ASSERT_IF(needs
.value
.needScratch2
, scratch2
!= Register::Invalid());
6812 Label
* successLabel
= onSuccess
? label
: &fallthrough
;
6813 Label
* failLabel
= onSuccess
? &fallthrough
: label
;
6814 Label
* nullLabel
= destType
.isNullable() ? successLabel
: failLabel
;
6817 if (sourceType
.isNullable()) {
6818 branchTestPtr(Assembler::Zero
, ref
, ref
, nullLabel
);
6821 // The only value that can inhabit 'nofunc' is null. So, early out if we got
6823 if (destType
.isNoFunc()) {
6829 if (destType
.isFunc()) {
6830 // No further checks for 'func' (any func)
6836 // In the func hierarchy, a supertype vector check is now sufficient for all
6838 loadPrivate(Address(ref
, int32_t(FunctionExtended::offsetOfWasmSTV())),
6840 branchWasmSTVIsSubtype(scratch1
, superSTV
, scratch2
,
6841 destType
.typeDef()->subTypingDepth(), successLabel
,
6844 // If we didn't branch away, the cast failed.
6849 void MacroAssembler::branchWasmRefIsSubtypeExtern(Register ref
,
6850 wasm::RefType sourceType
,
6851 wasm::RefType destType
,
6854 MOZ_ASSERT(sourceType
.isValid());
6855 MOZ_ASSERT(destType
.isValid());
6856 MOZ_ASSERT(sourceType
.isExternHierarchy());
6857 MOZ_ASSERT(destType
.isExternHierarchy());
6860 Label
* successLabel
= onSuccess
? label
: &fallthrough
;
6861 Label
* failLabel
= onSuccess
? &fallthrough
: label
;
6862 Label
* nullLabel
= destType
.isNullable() ? successLabel
: failLabel
;
6865 if (sourceType
.isNullable()) {
6866 branchTestPtr(Assembler::Zero
, ref
, ref
, nullLabel
);
6869 // The only value that can inhabit 'noextern' is null. So, early out if we got
6871 if (destType
.isNoExtern()) {
6877 // There are no other possible types except externref, so succeed!
6882 void MacroAssembler::branchWasmRefIsSubtypeExn(Register ref
,
6883 wasm::RefType sourceType
,
6884 wasm::RefType destType
,
6885 Label
* label
, bool onSuccess
) {
6886 MOZ_ASSERT(sourceType
.isValid());
6887 MOZ_ASSERT(destType
.isValid());
6888 MOZ_ASSERT(sourceType
.isExnHierarchy());
6889 MOZ_ASSERT(destType
.isExnHierarchy());
6892 Label
* successLabel
= onSuccess
? label
: &fallthrough
;
6893 Label
* failLabel
= onSuccess
? &fallthrough
: label
;
6894 Label
* nullLabel
= destType
.isNullable() ? successLabel
: failLabel
;
6897 if (sourceType
.isNullable()) {
6898 branchTestPtr(Assembler::Zero
, ref
, ref
, nullLabel
);
6901 // The only value that can inhabit 'noexn' is null. So, early out if we got
6903 if (destType
.isNoExn()) {
6909 // There are no other possible types except exnref, so succeed!
6914 void MacroAssembler::branchWasmSTVIsSubtype(Register subSTV
, Register superSTV
,
6916 uint32_t superDepth
, Label
* label
,
6918 MOZ_ASSERT_IF(superDepth
>= wasm::MinSuperTypeVectorLength
,
6919 scratch
!= Register::Invalid());
6921 Label
* failed
= onSuccess
? &fallthrough
: label
;
6923 // At this point, we could generate a fast success check which jumps to
6924 // `success` if `subSTV == superSTV`. However,
6925 // profiling of Barista-3 seems to show this is hardly worth anything,
6926 // whereas it is worth us generating smaller code and in particular one
6927 // fewer conditional branch.
6929 // Emit a bounds check if the super type depth may be out-of-bounds.
6930 if (superDepth
>= wasm::MinSuperTypeVectorLength
) {
6931 load32(Address(subSTV
, wasm::SuperTypeVector::offsetOfLength()), scratch
);
6932 branch32(Assembler::BelowOrEqual
, scratch
, Imm32(superDepth
), failed
);
6935 // Load the `superTypeDepth` entry from subSTV. This will be `superSTV` if
6936 // `subSTV` is indeed a subtype.
6938 Address(subSTV
, wasm::SuperTypeVector::offsetOfSTVInVector(superDepth
)),
6941 // We succeed iff the entries are equal
6942 branchPtr(onSuccess
? Assembler::Equal
: Assembler::NotEqual
, subSTV
,
6948 void MacroAssembler::branchWasmSTVIsSubtypeDynamicDepth(
6949 Register subSTV
, Register superSTV
, Register superDepth
, Register scratch
,
6950 Label
* label
, bool onSuccess
) {
6952 Label
* failed
= onSuccess
? &fallthrough
: label
;
6954 // Bounds check of the super type vector
6955 load32(Address(subSTV
, wasm::SuperTypeVector::offsetOfLength()), scratch
);
6956 branch32(Assembler::BelowOrEqual
, scratch
, superDepth
, failed
);
6958 // Load `subSTV[superTypeDepth]`. This will be `superSTV` if `subSTV` is
6959 // indeed a subtype.
6960 loadPtr(BaseIndex(subSTV
, superDepth
, ScalePointer
,
6961 offsetof(wasm::SuperTypeVector
, types_
)),
6964 // We succeed iff the entries are equal
6965 branchPtr(onSuccess
? Assembler::Equal
: Assembler::NotEqual
, subSTV
,
6971 void MacroAssembler::extractWasmAnyRefTag(Register src
, Register dest
) {
6973 andPtr(Imm32(int32_t(wasm::AnyRef::TagMask
)), dest
);
6976 void MacroAssembler::branchWasmAnyRefIsNull(bool isNull
, Register src
,
6978 branchTestPtr(isNull
? Assembler::Zero
: Assembler::NonZero
, src
, src
, label
);
6981 void MacroAssembler::branchWasmAnyRefIsI31(bool isI31
, Register src
,
6983 branchTestPtr(isI31
? Assembler::NonZero
: Assembler::Zero
, src
,
6984 Imm32(int32_t(wasm::AnyRefTag::I31
)), label
);
6987 void MacroAssembler::branchWasmAnyRefIsObjectOrNull(bool isObject
, Register src
,
6989 branchTestPtr(isObject
? Assembler::Zero
: Assembler::NonZero
, src
,
6990 Imm32(int32_t(wasm::AnyRef::TagMask
)), label
);
6993 void MacroAssembler::branchWasmAnyRefIsJSString(bool isJSString
, Register src
,
6994 Register temp
, Label
* label
) {
6995 extractWasmAnyRefTag(src
, temp
);
6996 branch32(isJSString
? Assembler::Equal
: Assembler::NotEqual
, temp
,
6997 Imm32(int32_t(wasm::AnyRefTag::String
)), label
);
7000 void MacroAssembler::branchWasmAnyRefIsGCThing(bool isGCThing
, Register src
,
7003 Label
* isGCThingLabel
= isGCThing
? label
: &fallthrough
;
7004 Label
* isNotGCThingLabel
= isGCThing
? &fallthrough
: label
;
7006 // A null value or i31 value are not GC things.
7007 branchWasmAnyRefIsNull(true, src
, isNotGCThingLabel
);
7008 branchWasmAnyRefIsI31(true, src
, isNotGCThingLabel
);
7009 jump(isGCThingLabel
);
7013 void MacroAssembler::branchWasmAnyRefIsNurseryCell(bool isNurseryCell
,
7014 Register src
, Register temp
,
7017 branchWasmAnyRefIsGCThing(false, src
, isNurseryCell
? &done
: label
);
7019 getWasmAnyRefGCThingChunk(src
, temp
);
7020 branchPtr(isNurseryCell
? Assembler::NotEqual
: Assembler::Equal
,
7021 Address(temp
, gc::ChunkStoreBufferOffset
), ImmWord(0), label
);
7025 void MacroAssembler::truncate32ToWasmI31Ref(Register src
, Register dest
) {
7026 // This will either zero-extend or sign-extend the high 32-bits on 64-bit
7027 // platforms (see comments on invariants in MacroAssembler.h). Either case
7028 // is fine, as we won't use this bits.
7030 // Move the payload of the integer over by 1 to make room for the tag. This
7031 // will perform the truncation required by the spec.
7032 lshift32(Imm32(1), dest
);
7033 // Add the i31 tag to the integer.
7034 orPtr(Imm32(int32_t(wasm::AnyRefTag::I31
)), dest
);
7036 debugAssertCanonicalInt32(dest
);
7040 void MacroAssembler::convertWasmI31RefTo32Signed(Register src
, Register dest
) {
7042 debugAssertCanonicalInt32(src
);
7044 // This will either zero-extend or sign-extend the high 32-bits on 64-bit
7045 // platforms (see comments on invariants in MacroAssembler.h). Either case
7046 // is fine, as we won't use this bits.
7048 // Shift the payload back (clobbering the tag). This will sign-extend, giving
7049 // us the unsigned behavior we want.
7050 rshift32Arithmetic(Imm32(1), dest
);
7053 void MacroAssembler::convertWasmI31RefTo32Unsigned(Register src
,
7056 debugAssertCanonicalInt32(src
);
7058 // This will either zero-extend or sign-extend the high 32-bits on 64-bit
7059 // platforms (see comments on invariants in MacroAssembler.h). Either case
7060 // is fine, as we won't use this bits.
7062 // Shift the payload back (clobbering the tag). This will zero-extend, giving
7063 // us the unsigned behavior we want.
7064 rshift32(Imm32(1), dest
);
7067 void MacroAssembler::branchValueConvertsToWasmAnyRefInline(
7068 ValueOperand src
, Register scratchInt
, FloatRegister scratchFloat
,
7070 // We can convert objects, strings, 31-bit integers and null without boxing.
7074 ScratchTagScope
tag(*this, src
);
7075 splitTagForTest(src
, tag
);
7076 branchTestObject(Assembler::Equal
, tag
, label
);
7077 branchTestString(Assembler::Equal
, tag
, label
);
7078 branchTestNull(Assembler::Equal
, tag
, label
);
7079 branchTestInt32(Assembler::Equal
, tag
, &checkInt32
);
7080 branchTestDouble(Assembler::Equal
, tag
, &checkDouble
);
7084 unboxInt32(src
, scratchInt
);
7085 branch32(Assembler::GreaterThan
, scratchInt
, Imm32(wasm::AnyRef::MaxI31Value
),
7087 branch32(Assembler::LessThan
, scratchInt
, Imm32(wasm::AnyRef::MinI31Value
),
7093 ScratchTagScopeRelease
_(&tag
);
7094 convertValueToInt32(src
, scratchFloat
, scratchInt
, &fallthrough
, true,
7095 IntConversionInputKind::NumbersOnly
);
7097 branch32(Assembler::GreaterThan
, scratchInt
, Imm32(wasm::AnyRef::MaxI31Value
),
7099 branch32(Assembler::LessThan
, scratchInt
, Imm32(wasm::AnyRef::MinI31Value
),
7106 void MacroAssembler::convertValueToWasmAnyRef(ValueOperand src
, Register dest
,
7107 FloatRegister scratchFloat
,
7108 Label
* oolConvert
) {
7109 Label doubleValue
, int32Value
, nullValue
, stringValue
, objectValue
, done
;
7111 ScratchTagScope
tag(*this, src
);
7112 splitTagForTest(src
, tag
);
7113 branchTestObject(Assembler::Equal
, tag
, &objectValue
);
7114 branchTestString(Assembler::Equal
, tag
, &stringValue
);
7115 branchTestNull(Assembler::Equal
, tag
, &nullValue
);
7116 branchTestInt32(Assembler::Equal
, tag
, &int32Value
);
7117 branchTestDouble(Assembler::Equal
, tag
, &doubleValue
);
7122 convertValueToInt32(src
, scratchFloat
, dest
, oolConvert
, true,
7123 IntConversionInputKind::NumbersOnly
);
7124 branch32(Assembler::GreaterThan
, dest
, Imm32(wasm::AnyRef::MaxI31Value
),
7126 branch32(Assembler::LessThan
, dest
, Imm32(wasm::AnyRef::MinI31Value
),
7128 lshiftPtr(Imm32(1), dest
);
7129 or32(Imm32((int32_t)wasm::AnyRefTag::I31
), dest
);
7133 unboxInt32(src
, dest
);
7134 branch32(Assembler::GreaterThan
, dest
, Imm32(wasm::AnyRef::MaxI31Value
),
7136 branch32(Assembler::LessThan
, dest
, Imm32(wasm::AnyRef::MinI31Value
),
7138 truncate32ToWasmI31Ref(dest
, dest
);
7142 static_assert(wasm::AnyRef::NullRefValue
== 0);
7147 unboxString(src
, dest
);
7148 orPtr(Imm32((int32_t)wasm::AnyRefTag::String
), dest
);
7152 unboxObject(src
, dest
);
7157 void MacroAssembler::convertObjectToWasmAnyRef(Register src
, Register dest
) {
7158 // JS objects are represented without any tagging.
7162 void MacroAssembler::convertStringToWasmAnyRef(Register src
, Register dest
) {
7163 // JS strings require a tag.
7165 orPtr(Imm32(int32_t(wasm::AnyRefTag::String
)), dest
);
7168 void MacroAssembler::branchObjectIsWasmGcObject(bool isGcObject
, Register src
,
7171 constexpr uint32_t ShiftedMask
= (Shape::kindMask() << Shape::kindShift());
7172 constexpr uint32_t ShiftedKind
=
7173 (uint32_t(Shape::Kind::WasmGC
) << Shape::kindShift());
7174 MOZ_ASSERT(src
!= scratch
);
7176 loadPtr(Address(src
, JSObject::offsetOfShape()), scratch
);
7177 load32(Address(scratch
, Shape::offsetOfImmutableFlags()), scratch
);
7178 and32(Imm32(ShiftedMask
), scratch
);
7179 branch32(isGcObject
? Assembler::Equal
: Assembler::NotEqual
, scratch
,
7180 Imm32(ShiftedKind
), label
);
7183 void MacroAssembler::wasmNewStructObject(Register instance
, Register result
,
7184 Register typeDefData
, Register temp1
,
7185 Register temp2
, Label
* fail
,
7186 gc::AllocKind allocKind
,
7188 // Don't execute the inline path if GC probes are built in.
7194 // Don't execute the inline path if gc zeal or tracing are active.
7195 loadPtr(Address(instance
, wasm::Instance::offsetOfAddressOfGCZealModeBits()),
7197 loadPtr(Address(temp1
, 0), temp1
);
7198 branch32(Assembler::NotEqual
, temp1
, Imm32(0), fail
);
7201 // If the alloc site is long lived, immediately fall back to the OOL path,
7202 // which will handle that.
7203 loadPtr(Address(typeDefData
, wasm::TypeDefInstanceData::offsetOfAllocSite()),
7205 branchTestPtr(Assembler::NonZero
,
7206 Address(temp1
, gc::AllocSite::offsetOfScriptAndState()),
7207 Imm32(gc::AllocSite::LONG_LIVED_BIT
), fail
);
7209 size_t sizeBytes
= gc::Arena::thingSize(allocKind
);
7210 wasmBumpPointerAllocate(instance
, result
, typeDefData
, temp1
, temp2
, fail
,
7212 loadPtr(Address(typeDefData
, wasm::TypeDefInstanceData::offsetOfShape()),
7214 loadPtr(Address(typeDefData
,
7215 wasm::TypeDefInstanceData::offsetOfSuperTypeVector()),
7217 storePtr(temp1
, Address(result
, WasmStructObject::offsetOfShape()));
7218 storePtr(temp2
, Address(result
, WasmStructObject::offsetOfSuperTypeVector()));
7219 storePtr(ImmWord(0),
7220 Address(result
, WasmStructObject::offsetOfOutlineData()));
7223 MOZ_ASSERT(sizeBytes
% sizeof(void*) == 0);
7224 for (size_t i
= WasmStructObject::offsetOfInlineData(); i
< sizeBytes
;
7225 i
+= sizeof(void*)) {
7226 storePtr(ImmWord(0), Address(result
, i
));
7231 void MacroAssembler::wasmNewArrayObject(Register instance
, Register result
,
7232 Register numElements
,
7233 Register typeDefData
, Register temp
,
7234 Label
* fail
, uint32_t elemSize
,
7236 // Don't execute the inline path if GC probes are built in.
7242 // Don't execute the inline path if gc zeal or tracing are active.
7243 loadPtr(Address(instance
, wasm::Instance::offsetOfAddressOfGCZealModeBits()),
7245 loadPtr(Address(temp
, 0), temp
);
7246 branch32(Assembler::NotEqual
, temp
, Imm32(0), fail
);
7249 // If the alloc site is long lived, immediately fall back to the OOL path,
7250 // which will handle that.
7251 loadPtr(Address(typeDefData
, wasm::TypeDefInstanceData::offsetOfAllocSite()),
7253 branchTestPtr(Assembler::NonZero
,
7254 Address(temp
, gc::AllocSite::offsetOfScriptAndState()),
7255 Imm32(gc::AllocSite::LONG_LIVED_BIT
), fail
);
7257 // Ensure that the numElements is small enough to fit in inline storage.
7258 branch32(Assembler::Above
, numElements
,
7259 Imm32(WasmArrayObject::maxInlineElementsForElemSize(elemSize
)),
7262 // Push numElements for later; numElements will be used as a temp in the
7263 // meantime. Make sure that all exit paths pop the value again!
7265 #ifdef JS_CODEGEN_ARM64
7266 // On arm64, we must maintain 16-alignment of both the actual and pseudo stack
7268 push(numElements
, xzr
);
7274 // Compute the size of the allocation in bytes. The final size must correspond
7275 // to an AllocKind. See WasmArrayObject::calcStorageBytes and
7276 // WasmArrayObject::allocKindForIL.
7278 // Compute the size of all array element data.
7279 mul32(Imm32(elemSize
), numElements
);
7280 // Add the data header.
7281 add32(Imm32(sizeof(WasmArrayObject::DataHeader
)), numElements
);
7282 // Round up to gc::CellAlignBytes to play nice with the GC and to simplify the
7283 // zeroing logic below.
7284 add32(Imm32(gc::CellAlignBytes
- 1), numElements
);
7285 and32(Imm32(~int32_t(gc::CellAlignBytes
- 1)), numElements
);
7286 // Add the size of the WasmArrayObject to get the full allocation size.
7287 static_assert(WasmArrayObject_MaxInlineBytes
+ sizeof(WasmArrayObject
) <
7289 add32(Imm32(sizeof(WasmArrayObject
)), numElements
);
7290 // Per gc::slotsToAllocKindBytes, subtract sizeof(NativeObject),
7291 // divide by sizeof(js::Value), then look up the final AllocKind-based
7292 // allocation size from a table.
7293 movePtr(wasm::SymbolicAddress::SlotsToAllocKindBytesTable
, temp
);
7294 move32ZeroExtendToPtr(numElements
, numElements
);
7295 subPtr(Imm32(sizeof(NativeObject
)), numElements
);
7296 static_assert(sizeof(js::Value
) == 8);
7297 rshiftPtr(Imm32(3), numElements
);
7298 static_assert(sizeof(gc::slotsToAllocKindBytes
[0]) == 4);
7299 load32(BaseIndex(temp
, numElements
, Scale::TimesFour
), numElements
);
7301 wasmBumpPointerAllocateDynamic(instance
, result
, typeDefData
,
7302 /*size=*/numElements
, temp
, &popAndFail
);
7304 // Initialize the shape and STV
7305 loadPtr(Address(typeDefData
, wasm::TypeDefInstanceData::offsetOfShape()),
7307 storePtr(temp
, Address(result
, WasmArrayObject::offsetOfShape()));
7308 loadPtr(Address(typeDefData
,
7309 wasm::TypeDefInstanceData::offsetOfSuperTypeVector()),
7311 storePtr(temp
, Address(result
, WasmArrayObject::offsetOfSuperTypeVector()));
7313 // Store inline data header and data pointer
7314 storePtr(ImmWord(WasmArrayObject::DataIsIL
),
7315 Address(result
, WasmArrayObject::offsetOfInlineStorage()));
7316 computeEffectiveAddress(
7317 Address(result
, WasmArrayObject::offsetOfInlineArrayData()), temp
);
7318 // temp now points at the base of the array data; this will be used later
7319 storePtr(temp
, Address(result
, WasmArrayObject::offsetOfData()));
7320 // numElements will be saved to the array object later; for now we want to
7321 // continue using numElements as a temp.
7323 // Zero the array elements. This loop depends on the size of the array data
7324 // being a multiple of the machine word size. This is currently always the
7325 // case since WasmArrayObject::calcStorageBytes rounds up to
7326 // gc::CellAlignBytes.
7327 static_assert(gc::CellAlignBytes
% sizeof(void*) == 0);
7330 // numElements currently stores the total size of the allocation. temp
7331 // points at the base of the inline array data. We will zero the memory by
7332 // advancing numElements to the end of the allocation, then counting down
7333 // toward temp, zeroing one word at a time. The following aliases make this
7335 Register current
= numElements
;
7336 Register inlineArrayData
= temp
;
7338 // We first need to update current to actually point at the end of the
7339 // allocation. We can compute this from the data pointer, since the data
7340 // pointer points at a known offset within the array.
7342 // It is easier to understand the code below as first subtracting the offset
7343 // (to get back to the start of the allocation), then adding the total size
7344 // of the allocation (using Scale::TimesOne).
7345 computeEffectiveAddress(
7346 BaseIndex(inlineArrayData
, current
, Scale::TimesOne
,
7347 -int32_t(WasmArrayObject::offsetOfInlineArrayData())),
7350 // Exit immediately if the array has zero elements.
7351 branchPtr(Assembler::Equal
, current
, inlineArrayData
, &zeroed
);
7353 // Loop, counting down until current == inlineArrayData.
7356 subPtr(Imm32(sizeof(void*)), current
);
7357 storePtr(ImmWord(0), Address(current
, 0));
7358 branchPtr(Assembler::NotEqual
, current
, inlineArrayData
, &loop
);
7362 // Finally, store the actual numElements in the array object.
7363 #ifdef JS_CODEGEN_ARM64
7364 pop(xzr
, numElements
);
7369 store32(numElements
, Address(result
, WasmArrayObject::offsetOfNumElements()));
7375 #ifdef JS_CODEGEN_ARM64
7376 pop(xzr
, numElements
);
7386 void MacroAssembler::wasmNewArrayObjectFixed(Register instance
, Register result
,
7387 Register typeDefData
,
7388 Register temp1
, Register temp2
,
7389 Label
* fail
, uint32_t numElements
,
7390 uint32_t storageBytes
,
7392 MOZ_ASSERT(storageBytes
<= WasmArrayObject_MaxInlineBytes
);
7394 // Don't execute the inline path if GC probes are built in.
7400 // Don't execute the inline path if gc zeal or tracing are active.
7401 loadPtr(Address(instance
, wasm::Instance::offsetOfAddressOfGCZealModeBits()),
7403 loadPtr(Address(temp1
, 0), temp1
);
7404 branch32(Assembler::NotEqual
, temp1
, Imm32(0), fail
);
7407 // If the alloc site is long lived, immediately fall back to the OOL path,
7408 // which will handle that.
7409 loadPtr(Address(typeDefData
, wasm::TypeDefInstanceData::offsetOfAllocSite()),
7411 branchTestPtr(Assembler::NonZero
,
7412 Address(temp1
, gc::AllocSite::offsetOfScriptAndState()),
7413 Imm32(gc::AllocSite::LONG_LIVED_BIT
), fail
);
7415 gc::AllocKind allocKind
= WasmArrayObject::allocKindForIL(storageBytes
);
7416 uint32_t totalSize
= gc::Arena::thingSize(allocKind
);
7417 wasmBumpPointerAllocate(instance
, result
, typeDefData
, temp1
, temp2
, fail
,
7419 loadPtr(Address(typeDefData
, wasm::TypeDefInstanceData::offsetOfShape()),
7421 loadPtr(Address(typeDefData
,
7422 wasm::TypeDefInstanceData::offsetOfSuperTypeVector()),
7424 storePtr(temp1
, Address(result
, WasmArrayObject::offsetOfShape()));
7425 storePtr(temp2
, Address(result
, WasmArrayObject::offsetOfSuperTypeVector()));
7426 store32(Imm32(numElements
),
7427 Address(result
, WasmArrayObject::offsetOfNumElements()));
7429 // Store inline data header and data pointer
7430 storePtr(ImmWord(WasmArrayObject::DataIsIL
),
7431 Address(result
, WasmArrayObject::offsetOfInlineStorage()));
7432 computeEffectiveAddress(
7433 Address(result
, WasmArrayObject::offsetOfInlineArrayData()), temp2
);
7434 // temp2 now points at the base of the array data; this will be used later
7435 storePtr(temp2
, Address(result
, WasmArrayObject::offsetOfData()));
7438 MOZ_ASSERT(storageBytes
% sizeof(void*) == 0);
7440 // Advance temp1 to the end of the allocation
7441 // (note that temp2 is already past the data header)
7443 computeEffectiveAddress(
7444 Address(temp2
, -sizeof(WasmArrayObject::DataHeader
) + storageBytes
),
7446 branchPtr(Assembler::Equal
, temp1
, temp2
, &done
);
7448 // Count temp2 down toward temp1, zeroing one word at a time
7451 subPtr(Imm32(sizeof(void*)), temp1
);
7452 storePtr(ImmWord(0), Address(temp1
, 0));
7453 branchPtr(Assembler::NotEqual
, temp1
, temp2
, &loop
);
7459 void MacroAssembler::wasmBumpPointerAllocate(Register instance
, Register result
,
7460 Register typeDefData
,
7461 Register temp1
, Register temp2
,
7462 Label
* fail
, uint32_t size
) {
7463 MOZ_ASSERT(size
>= gc::MinCellSize
);
7465 uint32_t totalSize
= size
+ Nursery::nurseryCellHeaderSize();
7466 MOZ_ASSERT(totalSize
< INT32_MAX
, "Nursery allocation too large");
7467 MOZ_ASSERT(totalSize
% gc::CellAlignBytes
== 0);
7469 int32_t endOffset
= Nursery::offsetOfCurrentEndFromPosition();
7471 // Bail to OOL code if the alloc site needs to be pushed onto the active
7472 // list. Keep allocCount in temp2 for later.
7473 computeEffectiveAddress(
7474 Address(typeDefData
, wasm::TypeDefInstanceData::offsetOfAllocSite()),
7476 load32(Address(temp1
, gc::AllocSite::offsetOfNurseryAllocCount()), temp2
);
7477 branch32(Assembler::Equal
, temp2
,
7478 Imm32(js::gc::NormalSiteAttentionThreshold
- 1), fail
);
7480 // Bump allocate in the nursery, bailing if there is not enough room.
7481 loadPtr(Address(instance
, wasm::Instance::offsetOfAddressOfNurseryPosition()),
7483 loadPtr(Address(temp1
, 0), result
);
7484 addPtr(Imm32(totalSize
), result
);
7485 branchPtr(Assembler::Below
, Address(temp1
, endOffset
), result
, fail
);
7486 storePtr(result
, Address(temp1
, 0));
7487 subPtr(Imm32(size
), result
);
7489 // Increment the alloc count in the allocation site and store pointer in the
7490 // nursery cell header. See NurseryCellHeader::MakeValue.
7491 computeEffectiveAddress(
7492 Address(typeDefData
, wasm::TypeDefInstanceData::offsetOfAllocSite()),
7494 add32(Imm32(1), temp2
);
7495 store32(temp2
, Address(temp1
, gc::AllocSite::offsetOfNurseryAllocCount()));
7496 // Because JS::TraceKind::Object is zero, there is no need to explicitly set
7497 // it in the nursery cell header.
7498 static_assert(int(JS::TraceKind::Object
) == 0);
7499 storePtr(temp1
, Address(result
, -js::Nursery::nurseryCellHeaderSize()));
7502 void MacroAssembler::wasmBumpPointerAllocateDynamic(
7503 Register instance
, Register result
, Register typeDefData
, Register size
,
7504 Register temp1
, Label
* fail
) {
7506 // Replaces MOZ_ASSERT(size >= gc::MinCellSize);
7508 branch32(Assembler::AboveOrEqual
, size
, Imm32(gc::MinCellSize
), &ok1
);
7513 branch32(Assembler::BelowOrEqual
, size
, Imm32(JSObject::MAX_BYTE_SIZE
), &ok2
);
7518 int32_t endOffset
= Nursery::offsetOfCurrentEndFromPosition();
7520 // Bail to OOL code if the alloc site needs to be initialized.
7521 load32(Address(typeDefData
, wasm::TypeDefInstanceData::offsetOfAllocSite() +
7522 gc::AllocSite::offsetOfNurseryAllocCount()),
7524 branch32(Assembler::Equal
, temp1
,
7525 Imm32(js::gc::NormalSiteAttentionThreshold
- 1), fail
);
7527 // Bump allocate in the nursery, bailing if there is not enough room.
7528 loadPtr(Address(instance
, wasm::Instance::offsetOfAddressOfNurseryPosition()),
7530 loadPtr(Address(temp1
, 0), result
);
7531 computeEffectiveAddress(BaseIndex(result
, size
, Scale::TimesOne
,
7532 Nursery::nurseryCellHeaderSize()),
7534 branchPtr(Assembler::Below
, Address(temp1
, endOffset
), result
, fail
);
7535 storePtr(result
, Address(temp1
, 0));
7536 subPtr(size
, result
);
7538 // Increment the alloc count in the allocation site and store pointer in the
7539 // nursery cell header. See NurseryCellHeader::MakeValue.
7540 int32_t offsetOfNurseryAllocCount
=
7541 wasm::TypeDefInstanceData::offsetOfAllocSite() +
7542 gc::AllocSite::offsetOfNurseryAllocCount();
7543 add32(Imm32(1), Address(typeDefData
, offsetOfNurseryAllocCount
));
7544 // Because JS::TraceKind::Object is zero, there is no need to explicitly set
7545 // it in the nursery cell header.
7546 static_assert(int(JS::TraceKind::Object
) == 0);
7547 computeEffectiveAddress(
7548 Address(typeDefData
, wasm::TypeDefInstanceData::offsetOfAllocSite()),
7550 storePtr(temp1
, Address(result
, -js::Nursery::nurseryCellHeaderSize()));
7553 // Unboxing is branchy and contorted because of Spectre mitigations - we don't
7554 // have enough scratch registers. Were it not for the spectre mitigations in
7555 // branchTestObjClass, the branch nest below would be restructured significantly
7556 // by inverting branches and using fewer registers.
7558 // Unbox an anyref in src (clobbering src in the process) and then re-box it as
7559 // a Value in *dst. See the definition of AnyRef for a discussion of pointer
7561 void MacroAssembler::convertWasmAnyRefToValue(Register instance
, Register src
,
7564 MOZ_ASSERT(src
!= scratch
);
7565 #if JS_BITS_PER_WORD == 32
7566 MOZ_ASSERT(dst
.typeReg() != scratch
);
7567 MOZ_ASSERT(dst
.payloadReg() != scratch
);
7569 MOZ_ASSERT(dst
.valueReg() != scratch
);
7572 Label isI31
, isObjectOrNull
, isObject
, isWasmValueBox
, done
;
7574 // Check for if this is an i31 value first
7575 branchTestPtr(Assembler::NonZero
, src
, Imm32(int32_t(wasm::AnyRefTag::I31
)),
7577 // Then check for the object or null tag
7578 branchTestPtr(Assembler::Zero
, src
, Imm32(wasm::AnyRef::TagMask
),
7581 // If we're not i31, object, or null, we must be a string
7582 rshiftPtr(Imm32(wasm::AnyRef::TagShift
), src
);
7583 lshiftPtr(Imm32(wasm::AnyRef::TagShift
), src
);
7584 moveValue(TypedOrValueRegister(MIRType::String
, AnyRegister(src
)), dst
);
7587 // This is an i31 value, convert to an int32 JS value
7589 convertWasmI31RefTo32Signed(src
, src
);
7590 moveValue(TypedOrValueRegister(MIRType::Int32
, AnyRegister(src
)), dst
);
7593 // Check for the null value
7594 bind(&isObjectOrNull
);
7595 branchTestPtr(Assembler::NonZero
, src
, src
, &isObject
);
7596 moveValue(NullValue(), dst
);
7599 // Otherwise we must be a non-null object. We next to check if it's storing a
7602 // The type test will clear src if the test fails, so store early.
7603 moveValue(TypedOrValueRegister(MIRType::Object
, AnyRegister(src
)), dst
);
7604 // Spectre mitigations: see comment above about efficiency.
7605 branchTestObjClass(Assembler::Equal
, src
,
7606 Address(instance
, wasm::Instance::offsetOfValueBoxClass()),
7607 scratch
, src
, &isWasmValueBox
);
7610 // This is a boxed JS value, unbox it.
7611 bind(&isWasmValueBox
);
7612 loadValue(Address(src
, wasm::AnyRef::valueBoxOffsetOfValue()), dst
);
7617 void MacroAssembler::convertWasmAnyRefToValue(Register instance
, Register src
,
7620 MOZ_ASSERT(src
!= scratch
);
7622 Label isI31
, isObjectOrNull
, isObject
, isWasmValueBox
, done
;
7624 // Check for if this is an i31 value first
7625 branchTestPtr(Assembler::NonZero
, src
, Imm32(int32_t(wasm::AnyRefTag::I31
)),
7627 // Then check for the object or null tag
7628 branchTestPtr(Assembler::Zero
, src
, Imm32(wasm::AnyRef::TagMask
),
7631 // If we're not i31, object, or null, we must be a string
7632 rshiftPtr(Imm32(wasm::AnyRef::TagShift
), src
);
7633 lshiftPtr(Imm32(wasm::AnyRef::TagShift
), src
);
7634 storeValue(JSVAL_TYPE_STRING
, src
, dst
);
7637 // This is an i31 value, convert to an int32 JS value
7639 convertWasmI31RefTo32Signed(src
, src
);
7640 storeValue(JSVAL_TYPE_INT32
, src
, dst
);
7643 // Check for the null value
7644 bind(&isObjectOrNull
);
7645 branchTestPtr(Assembler::NonZero
, src
, src
, &isObject
);
7646 storeValue(NullValue(), dst
);
7649 // Otherwise we must be a non-null object. We next to check if it's storing a
7652 // The type test will clear src if the test fails, so store early.
7653 storeValue(JSVAL_TYPE_OBJECT
, src
, dst
);
7654 // Spectre mitigations: see comment above about efficiency.
7655 branchTestObjClass(Assembler::Equal
, src
,
7656 Address(instance
, wasm::Instance::offsetOfValueBoxClass()),
7657 scratch
, src
, &isWasmValueBox
);
7660 // This is a boxed JS value, unbox it.
7661 bind(&isWasmValueBox
);
7662 copy64(Address(src
, wasm::AnyRef::valueBoxOffsetOfValue()), dst
, scratch
);
7667 void MacroAssembler::nopPatchableToCall(const wasm::CallSiteDesc
& desc
) {
7668 CodeOffset offset
= nopPatchableToCall();
7669 append(desc
, offset
);
7672 void MacroAssembler::emitPreBarrierFastPath(JSRuntime
* rt
, MIRType type
,
7673 Register temp1
, Register temp2
,
7674 Register temp3
, Label
* noBarrier
) {
7675 MOZ_ASSERT(temp1
!= PreBarrierReg
);
7676 MOZ_ASSERT(temp2
!= PreBarrierReg
);
7677 MOZ_ASSERT(temp3
!= PreBarrierReg
);
7679 // Load the GC thing in temp1.
7680 if (type
== MIRType::Value
) {
7681 unboxGCThingForGCBarrier(Address(PreBarrierReg
, 0), temp1
);
7682 } else if (type
== MIRType::WasmAnyRef
) {
7683 unboxWasmAnyRefGCThingForGCBarrier(Address(PreBarrierReg
, 0), temp1
);
7685 MOZ_ASSERT(type
== MIRType::Object
|| type
== MIRType::String
||
7686 type
== MIRType::Shape
);
7687 loadPtr(Address(PreBarrierReg
, 0), temp1
);
7691 // The caller should have checked for null pointers.
7693 branchTestPtr(Assembler::NonZero
, temp1
, temp1
, &nonZero
);
7694 assumeUnreachable("JIT pre-barrier: unexpected nullptr");
7698 // Load the chunk address in temp2.
7699 movePtr(temp1
, temp2
);
7700 andPtr(Imm32(int32_t(~gc::ChunkMask
)), temp2
);
7702 // If the GC thing is in the nursery, we don't need to barrier it.
7703 if (type
== MIRType::Value
|| type
== MIRType::Object
||
7704 type
== MIRType::String
|| type
== MIRType::WasmAnyRef
) {
7705 branchPtr(Assembler::NotEqual
, Address(temp2
, gc::ChunkStoreBufferOffset
),
7706 ImmWord(0), noBarrier
);
7710 branchPtr(Assembler::Equal
, Address(temp2
, gc::ChunkStoreBufferOffset
),
7711 ImmWord(0), &isTenured
);
7712 assumeUnreachable("JIT pre-barrier: unexpected nursery pointer");
7717 // Determine the bit index and store in temp1.
7719 // bit = (addr & js::gc::ChunkMask) / js::gc::CellBytesPerMarkBit +
7720 // static_cast<uint32_t>(colorBit);
7721 static_assert(gc::CellBytesPerMarkBit
== 8,
7722 "Calculation below relies on this");
7723 static_assert(size_t(gc::ColorBit::BlackBit
) == 0,
7724 "Calculation below relies on this");
7725 andPtr(Imm32(gc::ChunkMask
), temp1
);
7726 rshiftPtr(Imm32(3), temp1
);
7728 static_assert(gc::MarkBitmapWordBits
== JS_BITS_PER_WORD
,
7729 "Calculation below relies on this");
7731 // Load the bitmap word in temp2.
7733 // word = chunk.bitmap[bit / MarkBitmapWordBits];
7735 // Fold the adjustment for the fact that arenas don't start at the beginning
7736 // of the chunk into the offset to the chunk bitmap.
7737 const size_t firstArenaAdjustment
=
7738 gc::ChunkMarkBitmap::FirstThingAdjustmentBits
/ CHAR_BIT
;
7739 const intptr_t offset
=
7740 intptr_t(gc::ChunkMarkBitmapOffset
) - intptr_t(firstArenaAdjustment
);
7742 movePtr(temp1
, temp3
);
7743 #if JS_BITS_PER_WORD == 64
7744 rshiftPtr(Imm32(6), temp1
);
7745 loadPtr(BaseIndex(temp2
, temp1
, TimesEight
, offset
), temp2
);
7747 rshiftPtr(Imm32(5), temp1
);
7748 loadPtr(BaseIndex(temp2
, temp1
, TimesFour
, offset
), temp2
);
7751 // Load the mask in temp1.
7753 // mask = uintptr_t(1) << (bit % MarkBitmapWordBits);
7754 andPtr(Imm32(gc::MarkBitmapWordBits
- 1), temp3
);
7755 move32(Imm32(1), temp1
);
7756 #ifdef JS_CODEGEN_X64
7757 MOZ_ASSERT(temp3
== rcx
);
7759 #elif JS_CODEGEN_X86
7760 MOZ_ASSERT(temp3
== ecx
);
7762 #elif JS_CODEGEN_ARM
7763 ma_lsl(temp3
, temp1
, temp1
);
7764 #elif JS_CODEGEN_ARM64
7765 Lsl(ARMRegister(temp1
, 64), ARMRegister(temp1
, 64), ARMRegister(temp3
, 64));
7766 #elif JS_CODEGEN_MIPS32
7767 ma_sll(temp1
, temp1
, temp3
);
7768 #elif JS_CODEGEN_MIPS64
7769 ma_dsll(temp1
, temp1
, temp3
);
7770 #elif JS_CODEGEN_LOONG64
7771 as_sll_d(temp1
, temp1
, temp3
);
7772 #elif JS_CODEGEN_RISCV64
7773 sll(temp1
, temp1
, temp3
);
7774 #elif JS_CODEGEN_WASM32
7776 #elif JS_CODEGEN_NONE
7779 # error "Unknown architecture"
7782 // No barrier is needed if the bit is set, |word & mask != 0|.
7783 branchTestPtr(Assembler::NonZero
, temp2
, temp1
, noBarrier
);
7786 // ========================================================================
7787 // JS atomic operations.
7789 void MacroAssembler::atomicIsLockFreeJS(Register value
, Register output
) {
7790 // Keep this in sync with isLockfreeJS() in jit/AtomicOperations.h.
7791 static_assert(AtomicOperations::isLockfreeJS(1)); // Implementation artifact
7792 static_assert(AtomicOperations::isLockfreeJS(2)); // Implementation artifact
7793 static_assert(AtomicOperations::isLockfreeJS(4)); // Spec requirement
7794 static_assert(AtomicOperations::isLockfreeJS(8)); // Implementation artifact
7797 move32(Imm32(1), output
);
7798 branch32(Assembler::Equal
, value
, Imm32(8), &done
);
7799 branch32(Assembler::Equal
, value
, Imm32(4), &done
);
7800 branch32(Assembler::Equal
, value
, Imm32(2), &done
);
7801 branch32(Assembler::Equal
, value
, Imm32(1), &done
);
7802 move32(Imm32(0), output
);
7806 // ========================================================================
7807 // Spectre Mitigations.
7809 void MacroAssembler::spectreMaskIndex32(Register index
, Register length
,
7811 MOZ_ASSERT(JitOptions
.spectreIndexMasking
);
7812 MOZ_ASSERT(length
!= output
);
7813 MOZ_ASSERT(index
!= output
);
7815 move32(Imm32(0), output
);
7816 cmp32Move32(Assembler::Below
, index
, length
, index
, output
);
7819 void MacroAssembler::spectreMaskIndex32(Register index
, const Address
& length
,
7821 MOZ_ASSERT(JitOptions
.spectreIndexMasking
);
7822 MOZ_ASSERT(index
!= length
.base
);
7823 MOZ_ASSERT(length
.base
!= output
);
7824 MOZ_ASSERT(index
!= output
);
7826 move32(Imm32(0), output
);
7827 cmp32Move32(Assembler::Below
, index
, length
, index
, output
);
7830 void MacroAssembler::spectreMaskIndexPtr(Register index
, Register length
,
7832 MOZ_ASSERT(JitOptions
.spectreIndexMasking
);
7833 MOZ_ASSERT(length
!= output
);
7834 MOZ_ASSERT(index
!= output
);
7836 movePtr(ImmWord(0), output
);
7837 cmpPtrMovePtr(Assembler::Below
, index
, length
, index
, output
);
7840 void MacroAssembler::spectreMaskIndexPtr(Register index
, const Address
& length
,
7842 MOZ_ASSERT(JitOptions
.spectreIndexMasking
);
7843 MOZ_ASSERT(index
!= length
.base
);
7844 MOZ_ASSERT(length
.base
!= output
);
7845 MOZ_ASSERT(index
!= output
);
7847 movePtr(ImmWord(0), output
);
7848 cmpPtrMovePtr(Assembler::Below
, index
, length
, index
, output
);
7851 void MacroAssembler::boundsCheck32PowerOfTwo(Register index
, uint32_t length
,
7853 MOZ_ASSERT(mozilla::IsPowerOfTwo(length
));
7854 branch32(Assembler::AboveOrEqual
, index
, Imm32(length
), failure
);
7856 // Note: it's fine to clobber the input register, as this is a no-op: it
7857 // only affects speculative execution.
7858 if (JitOptions
.spectreIndexMasking
) {
7859 and32(Imm32(length
- 1), index
);
7863 void MacroAssembler::loadWasmPinnedRegsFromInstance(
7864 mozilla::Maybe
<wasm::BytecodeOffset
> trapOffset
) {
7865 #ifdef WASM_HAS_HEAPREG
7866 static_assert(wasm::Instance::offsetOfMemory0Base() < 4096,
7867 "We count only on the low page being inaccessible");
7868 FaultingCodeOffset fco
= loadPtr(
7869 Address(InstanceReg
, wasm::Instance::offsetOfMemory0Base()), HeapReg
);
7872 wasm::Trap::IndirectCallToNull
,
7873 wasm::TrapSite(wasm::TrapMachineInsnForLoadWord(), fco
, *trapOffset
));
7876 MOZ_ASSERT(!trapOffset
);
7880 //}}} check_macroassembler_style
7883 void MacroAssembler::debugAssertCanonicalInt32(Register r
) {
7885 if (!js::jit::JitOptions
.lessDebugCode
) {
7886 # if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM64)
7888 branchPtr(Assembler::BelowOrEqual
, r
, ImmWord(UINT32_MAX
), &ok
);
7891 # elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64)
7893 ScratchRegisterScope
scratch(asMasm());
7894 move32SignExtendToPtr(r
, scratch
);
7895 branchPtr(Assembler::Equal
, r
, scratch
, &ok
);
7899 MOZ_CRASH("IMPLEMENT ME");
7906 void MacroAssembler::memoryBarrierBefore(Synchronization sync
) {
7907 memoryBarrier(sync
.barrierBefore
);
7910 void MacroAssembler::memoryBarrierAfter(Synchronization sync
) {
7911 memoryBarrier(sync
.barrierAfter
);
7914 void MacroAssembler::convertDoubleToFloat16(FloatRegister src
,
7915 FloatRegister dest
, Register temp
,
7916 LiveRegisterSet volatileLiveRegs
) {
7917 if (MacroAssembler::SupportsFloat64To16()) {
7918 convertDoubleToFloat16(src
, dest
);
7920 // Float16 is currently passed as Float32, so expand again to Float32.
7921 convertFloat16ToFloat32(dest
, dest
);
7925 LiveRegisterSet save
= volatileLiveRegs
;
7926 save
.takeUnchecked(dest
);
7927 save
.takeUnchecked(dest
.asDouble());
7928 save
.takeUnchecked(temp
);
7930 PushRegsInMask(save
);
7932 using Fn
= float (*)(double);
7933 setupUnalignedABICall(temp
);
7934 passABIArg(src
, ABIType::Float64
);
7935 callWithABI
<Fn
, jit::RoundFloat16ToFloat32
>(ABIType::Float32
);
7936 storeCallFloatResult(dest
);
7938 PopRegsInMask(save
);
7941 void MacroAssembler::convertDoubleToFloat16(FloatRegister src
,
7942 FloatRegister dest
, Register temp1
,
7944 MOZ_ASSERT(MacroAssembler::SupportsFloat64To16() ||
7945 MacroAssembler::SupportsFloat32To16());
7946 MOZ_ASSERT(src
!= dest
);
7948 if (MacroAssembler::SupportsFloat64To16()) {
7949 convertDoubleToFloat16(src
, dest
);
7951 // Float16 is currently passed as Float32, so expand again to Float32.
7952 convertFloat16ToFloat32(dest
, dest
);
7956 using Float32
= mozilla::FloatingPoint
<float>;
7959 static auto float32Bits
= [](float16 f16
) {
7960 // Cast to float and reinterpret to bit representation.
7961 return mozilla::BitwiseCast
<Float32::Bits
>(static_cast<float>(f16
));
7964 static auto nextExponent
= [](float16 f16
, int32_t direction
) {
7965 constexpr auto kSignificandWidth
= Float32::kSignificandWidth
;
7967 // Shift out mantissa and then adjust exponent.
7968 auto bits
= float32Bits(f16
);
7969 return ((bits
>> kSignificandWidth
) + direction
) << kSignificandWidth
;
7973 // Float32 larger or equals to |overflow| are infinity (or NaN) in Float16.
7974 constexpr uint32_t overflow
= 0x4780'0000;
7975 MOZ_ASSERT(overflow
== nextExponent(std::numeric_limits
<float16
>::max(), 1));
7977 // Float32 smaller than |underflow| are zero in Float16.
7978 constexpr uint32_t underflow
= 0x3300'0000;
7979 MOZ_ASSERT(underflow
==
7980 nextExponent(std::numeric_limits
<float16
>::denorm_min(), -1));
7982 // Float32 larger or equals to |normal| are normal numbers in Float16.
7983 constexpr uint32_t normal
= 0x3880'0000;
7984 MOZ_ASSERT(normal
== float32Bits(std::numeric_limits
<float16
>::min()));
7986 // There are five possible cases to consider:
7987 // 1. Non-finite (infinity and NaN)
7988 // 2. Overflow to infinity
7989 // 3. Normal numbers
7990 // 4. Denormal numbers
7991 // 5. Underflow to zero
7993 // Cases 1-2 and 4-5 don't need separate code paths, so we only need to be
7994 // concerned about incorrect double rounding for cases 3-4.
7998 // Conversion from float64 -> float32 -> float16 can introduce double rounding
7999 // errors when compared to a direct conversion float64 -> float16.
8001 // Number of bits in the exponent and mantissa. These are relevant below.
8003 // exponent mantissa
8004 // -----------------------
8011 // Input (f64): 0.0000610649585723877
8012 // Bits (f64): 3f10'0200'0000'0000
8013 // Bits (f32): 3880'1000
8016 // Ignore the three left-most nibbles of the f64 bits (those are the sign and
8017 // exponent). Shift the f64 mantissa right by (52 - 23) = 29 bits. The bits
8018 // of the f32 mantissa are therefore 00'1000. Converting from f32 to f16 will
8019 // right shift the mantissa by (23 - 10) = 13 bits. `001000 >> 13` is all
8020 // zero. Directly converting from f64 to f16 right shifts the f64 mantissa by
8021 // (52 - 10) = 42 bits. `0'0200'0000'0000 >> 42` is also all zero. So in this
8022 // case no double rounding did take place.
8024 // Input (f64): 0.00006106495857238771
8025 // Bits (f64): 3f10'0200'0000'0001
8026 // Bits (f32): 3880'1000
8029 // The f64 to f32 conversion returns the same result 3880'1000 as in the first
8030 // example, but the direct f64 to f16 conversion must return 0401. Let's look
8031 // at the binary representation of the mantissa.
8033 // Mantissa of 3f10'0200'0000'0001 in binary representation:
8036 // __________________|__________________
8038 // 0000 0000 0010 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001
8047 // The guard, round, and sticky bits control when to round: If the round bit
8048 // is one and at least one of guard or sticky is one, then round up. The
8049 // sticky bit is the or-ed value of all bits right of the round bit.
8051 // When rounding to float16, GRS is 011, so we have to round up, whereas when
8052 // rounding to float32, GRS is 001, so no rounding takes place.
8054 // Mantissa of 3880'1000 in binary representation:
8056 // e000 0000 0001 0000 0000 0000
8061 // The round bit is set, but neither the guard nor sticky bit is set, so no
8062 // rounding takes place for the f32 -> f16 conversion. We can attempt to
8063 // recover the missing sticky bit from the f64 -> f16 conversion by looking at
8064 // the low 32-bits of the f64 mantissa. If at least one bit is set in the
8065 // low 32-bits (and the MSB is zero), then add one to the f32 mantissa.
8066 // Modified mantissa now looks like:
8068 // e000 0000 0001 0000 0000 0001
8073 // GRS is now 011, so we round up and get the correctly rounded result 0401.
8075 // Input (f64): 0.00006112456321716307
8076 // Bits (f64): 3f10'05ff'ffff'ffff
8077 // Bits (f32): 3880'3000
8081 // __________________|__________________
8083 // 0000 0000 0101 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
8091 // When rounding to float16, GRS is 101, so we don't round, whereas when
8092 // rounding to float32, GRS is 111, so we have to round up.
8094 // Mantissa of 3880'3000 in binary representation:
8096 // e000 0000 0011 0000 0000 0000
8101 // The guard and sticky bits are set, so the float32 -> float16 conversion
8102 // incorrectly rounds up when compared to the direct float64 -> float16
8103 // conversion. To avoid rounding twice we subtract one if the MSB of the low
8104 // 32-bits of the f64 mantissa is set. Modified mantissa now looks like:
8106 // e000 0000 0010 1111 1111 1111
8111 // GRS is now 101, so we don't round and get the correct result 0401.
8113 // Approach used to avoid double rounding:
8115 // 1. For normal numbers, inspect the f32 mantissa and if its round bit is set
8116 // and the sticky bits are all zero, then possibly adjust the f32 mantissa
8117 // depending on the low 32-bits of the f64 mantissa.
8119 // 2. For denormal numbers, possibly adjust the f32 mantissa if the round and
8120 // sticky bits are all zero.
8122 // First round to float32 and reinterpret to bit representation.
8123 convertDoubleToFloat32(src
, dest
);
8124 moveFloat32ToGPR(dest
, temp1
);
8126 // Mask off sign bit to simplify range checks.
8127 and32(Imm32(~Float32::kSignBit
), temp1
);
8131 // No changes necessary for underflow or overflow, including zero and
8132 // non-finite numbers.
8133 branch32(Assembler::Below
, temp1
, Imm32(underflow
), &done
);
8134 branch32(Assembler::AboveOrEqual
, temp1
, Imm32(overflow
), &done
);
8136 // Compute 0x1000 for normal and 0x0000 for denormal numbers.
8137 cmp32Set(Assembler::AboveOrEqual
, temp1
, Imm32(normal
), temp2
);
8138 lshift32(Imm32(12), temp2
);
8140 // Look at the last thirteen bits of the mantissa which will be shifted out
8141 // when converting from float32 to float16. (The round and sticky bits.)
8143 // Normal numbers: If the round bit is set and sticky bits are zero, then
8144 // adjust the float32 mantissa.
8145 // Denormal numbers: If all bits are zero, then adjust the mantissa.
8146 and32(Imm32(0x1fff), temp1
);
8147 branch32(Assembler::NotEqual
, temp1
, temp2
, &done
);
8149 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
8150 // x86 can use SIMD instructions to avoid GPR<>XMM register moves.
8151 ScratchSimd128Scope
scratch(*this);
8153 int32_t one
[] = {1, 0, 0, 0};
8154 loadConstantSimd128(SimdConstant::CreateX4(one
), scratch
);
8156 // 1. If the low 32-bits of |src| are all zero, then set |scratch| to 0.
8157 // 2. If the MSB of the low 32-bits is set, then set |scratch| to -1.
8158 // 3. Otherwise set |scratch| to 1.
8159 vpsignd(Operand(src
), scratch
, scratch
);
8161 // Add |scratch| to the mantissa.
8162 vpaddd(Operand(scratch
), dest
, dest
);
8164 // Determine in which direction to round. When the low 32-bits are all zero,
8165 // then we don't have to round.
8166 moveLowDoubleToGPR(src
, temp2
);
8167 branch32(Assembler::Equal
, temp2
, Imm32(0), &done
);
8169 // Load either -1 or +1 into |temp2|.
8170 rshift32Arithmetic(Imm32(31), temp2
);
8171 or32(Imm32(1), temp2
);
8173 // Add or subtract one to the mantissa.
8174 moveFloat32ToGPR(dest
, temp1
);
8175 add32(temp2
, temp1
);
8176 moveGPRToFloat32(temp1
, dest
);
8182 // Perform the actual float16 conversion.
8183 convertFloat32ToFloat16(dest
, dest
);
8185 // Float16 is currently passed as Float32, so expand again to Float32.
8186 convertFloat16ToFloat32(dest
, dest
);
8189 void MacroAssembler::convertFloat32ToFloat16(FloatRegister src
,
8190 FloatRegister dest
, Register temp
,
8191 LiveRegisterSet volatileLiveRegs
) {
8192 if (MacroAssembler::SupportsFloat32To16()) {
8193 convertFloat32ToFloat16(src
, dest
);
8195 // Float16 is currently passed as Float32, so expand again to Float32.
8196 convertFloat16ToFloat32(dest
, dest
);
8200 LiveRegisterSet save
= volatileLiveRegs
;
8201 save
.takeUnchecked(dest
);
8202 save
.takeUnchecked(dest
.asDouble());
8203 save
.takeUnchecked(temp
);
8205 PushRegsInMask(save
);
8207 using Fn
= float (*)(float);
8208 setupUnalignedABICall(temp
);
8209 passABIArg(src
, ABIType::Float32
);
8210 callWithABI
<Fn
, jit::RoundFloat16ToFloat32
>(ABIType::Float32
);
8211 storeCallFloatResult(dest
);
8213 PopRegsInMask(save
);
8216 void MacroAssembler::convertInt32ToFloat16(Register src
, FloatRegister dest
,
8218 LiveRegisterSet volatileLiveRegs
) {
8219 if (MacroAssembler::SupportsFloat32To16()) {
8220 convertInt32ToFloat16(src
, dest
);
8222 // Float16 is currently passed as Float32, so expand again to Float32.
8223 convertFloat16ToFloat32(dest
, dest
);
8227 LiveRegisterSet save
= volatileLiveRegs
;
8228 save
.takeUnchecked(dest
);
8229 save
.takeUnchecked(dest
.asDouble());
8230 save
.takeUnchecked(temp
);
8232 PushRegsInMask(save
);
8234 using Fn
= float (*)(int32_t);
8235 setupUnalignedABICall(temp
);
8237 callWithABI
<Fn
, jit::RoundFloat16ToFloat32
>(ABIType::Float32
);
8238 storeCallFloatResult(dest
);
8240 PopRegsInMask(save
);
8243 template <typename T
>
8244 void MacroAssembler::loadFloat16(const T
& src
, FloatRegister dest
,
8245 Register temp1
, Register temp2
,
8246 LiveRegisterSet volatileLiveRegs
) {
8247 if (MacroAssembler::SupportsFloat32To16()) {
8248 loadFloat16(src
, dest
, temp1
);
8250 // Float16 is currently passed as Float32, so expand again to Float32.
8251 convertFloat16ToFloat32(dest
, dest
);
8255 load16ZeroExtend(src
, temp1
);
8257 LiveRegisterSet save
= volatileLiveRegs
;
8258 save
.takeUnchecked(dest
);
8259 save
.takeUnchecked(dest
.asDouble());
8260 save
.takeUnchecked(temp1
);
8261 save
.takeUnchecked(temp2
);
8263 PushRegsInMask(save
);
8265 using Fn
= float (*)(int32_t);
8266 setupUnalignedABICall(temp2
);
8268 callWithABI
<Fn
, jit::Float16ToFloat32
>(ABIType::Float32
);
8269 storeCallFloatResult(dest
);
8271 PopRegsInMask(save
);
8274 template void MacroAssembler::loadFloat16(const Address
& src
,
8275 FloatRegister dest
, Register temp1
,
8277 LiveRegisterSet volatileLiveRegs
);
8279 template void MacroAssembler::loadFloat16(const BaseIndex
& src
,
8280 FloatRegister dest
, Register temp1
,
8282 LiveRegisterSet volatileLiveRegs
);
8284 template <typename T
>
8285 void MacroAssembler::storeFloat16(FloatRegister src
, const T
& dest
,
8287 LiveRegisterSet volatileLiveRegs
) {
8288 ScratchFloat32Scope
fpscratch(*this);
8290 if (src
.isDouble()) {
8291 if (MacroAssembler::SupportsFloat64To16()) {
8292 canonicalizeDoubleIfDeterministic(src
);
8293 convertDoubleToFloat16(src
, fpscratch
);
8294 storeUncanonicalizedFloat16(fpscratch
, dest
, temp
);
8298 convertDoubleToFloat16(src
, fpscratch
, temp
, volatileLiveRegs
);
8301 MOZ_ASSERT(src
.isSingle());
8303 if (MacroAssembler::SupportsFloat32To16()) {
8304 canonicalizeFloatIfDeterministic(src
);
8305 convertFloat32ToFloat16(src
, fpscratch
);
8306 storeUncanonicalizedFloat16(fpscratch
, dest
, temp
);
8310 canonicalizeFloatIfDeterministic(src
);
8311 moveFloat16ToGPR(src
, temp
, volatileLiveRegs
);
8312 store16(temp
, dest
);
8315 template void MacroAssembler::storeFloat16(FloatRegister src
,
8316 const Address
& dest
, Register temp
,
8317 LiveRegisterSet volatileLiveRegs
);
8319 template void MacroAssembler::storeFloat16(FloatRegister src
,
8320 const BaseIndex
& dest
, Register temp
,
8321 LiveRegisterSet volatileLiveRegs
);
8323 void MacroAssembler::moveFloat16ToGPR(FloatRegister src
, Register dest
,
8324 LiveRegisterSet volatileLiveRegs
) {
8325 if (MacroAssembler::SupportsFloat32To16()) {
8326 ScratchFloat32Scope
fpscratch(*this);
8328 // Float16 is currently passed as Float32, so first narrow to Float16.
8329 convertFloat32ToFloat16(src
, fpscratch
);
8331 moveFloat16ToGPR(fpscratch
, dest
);
8335 LiveRegisterSet save
= volatileLiveRegs
;
8336 save
.takeUnchecked(dest
);
8338 PushRegsInMask(save
);
8340 using Fn
= int32_t (*)(float);
8341 setupUnalignedABICall(dest
);
8342 passABIArg(src
, ABIType::Float32
);
8343 callWithABI
<Fn
, jit::Float32ToFloat16
>();
8344 storeCallInt32Result(dest
);
8346 PopRegsInMask(save
);
8349 void MacroAssembler::moveGPRToFloat16(Register src
, FloatRegister dest
,
8351 LiveRegisterSet volatileLiveRegs
) {
8352 if (MacroAssembler::SupportsFloat32To16()) {
8353 moveGPRToFloat16(src
, dest
);
8355 // Float16 is currently passed as Float32, so expand again to Float32.
8356 convertFloat16ToFloat32(dest
, dest
);
8360 LiveRegisterSet save
= volatileLiveRegs
;
8361 save
.takeUnchecked(dest
);
8362 save
.takeUnchecked(dest
.asDouble());
8363 save
.takeUnchecked(temp
);
8365 PushRegsInMask(save
);
8367 using Fn
= float (*)(int32_t);
8368 setupUnalignedABICall(temp
);
8370 callWithABI
<Fn
, jit::Float16ToFloat32
>(ABIType::Float32
);
8371 storeCallFloatResult(dest
);
8373 PopRegsInMask(save
);
8376 void MacroAssembler::debugAssertIsObject(const ValueOperand
& val
) {
8379 branchTestObject(Assembler::Equal
, val
, &ok
);
8380 assumeUnreachable("Expected an object!");
8385 void MacroAssembler::debugAssertObjHasFixedSlots(Register obj
,
8388 Label hasFixedSlots
;
8389 loadPtr(Address(obj
, JSObject::offsetOfShape()), scratch
);
8390 branchTest32(Assembler::NonZero
,
8391 Address(scratch
, Shape::offsetOfImmutableFlags()),
8392 Imm32(NativeShape::fixedSlotsMask()), &hasFixedSlots
);
8393 assumeUnreachable("Expected a fixed slot");
8394 bind(&hasFixedSlots
);
8398 void MacroAssembler::debugAssertObjectHasClass(Register obj
, Register scratch
,
8399 const JSClass
* clasp
) {
8402 branchTestObjClassNoSpectreMitigations(Assembler::Equal
, obj
, clasp
, scratch
,
8404 assumeUnreachable("Class check failed");
8409 void MacroAssembler::debugAssertGCThingIsTenured(Register ptr
, Register temp
) {
8412 branchPtrInNurseryChunk(Assembler::NotEqual
, ptr
, temp
, &done
);
8413 assumeUnreachable("Expected a tenured pointer");
8418 void MacroAssembler::branchArrayIsNotPacked(Register array
, Register temp1
,
8419 Register temp2
, Label
* label
) {
8420 loadPtr(Address(array
, NativeObject::offsetOfElements()), temp1
);
8422 // Test length == initializedLength.
8423 Address
initLength(temp1
, ObjectElements::offsetOfInitializedLength());
8424 load32(Address(temp1
, ObjectElements::offsetOfLength()), temp2
);
8425 branch32(Assembler::NotEqual
, initLength
, temp2
, label
);
8427 // Test the NON_PACKED flag.
8428 Address
flags(temp1
, ObjectElements::offsetOfFlags());
8429 branchTest32(Assembler::NonZero
, flags
, Imm32(ObjectElements::NON_PACKED
),
8433 void MacroAssembler::setIsPackedArray(Register obj
, Register output
,
8435 // Ensure it's an ArrayObject.
8436 Label notPackedArray
;
8437 branchTestObjClass(Assembler::NotEqual
, obj
, &ArrayObject::class_
, temp
, obj
,
8440 branchArrayIsNotPacked(obj
, temp
, output
, ¬PackedArray
);
8443 move32(Imm32(1), output
);
8446 bind(¬PackedArray
);
8447 move32(Imm32(0), output
);
8452 void MacroAssembler::packedArrayPop(Register array
, ValueOperand output
,
8453 Register temp1
, Register temp2
,
8455 // Load obj->elements in temp1.
8456 loadPtr(Address(array
, NativeObject::offsetOfElements()), temp1
);
8459 static constexpr uint32_t UnhandledFlags
=
8460 ObjectElements::Flags::NON_PACKED
|
8461 ObjectElements::Flags::NONWRITABLE_ARRAY_LENGTH
|
8462 ObjectElements::Flags::NOT_EXTENSIBLE
|
8463 ObjectElements::Flags::MAYBE_IN_ITERATION
;
8464 Address
flags(temp1
, ObjectElements::offsetOfFlags());
8465 branchTest32(Assembler::NonZero
, flags
, Imm32(UnhandledFlags
), fail
);
8467 // Load length in temp2. Ensure length == initializedLength.
8468 Address
lengthAddr(temp1
, ObjectElements::offsetOfLength());
8469 Address
initLengthAddr(temp1
, ObjectElements::offsetOfInitializedLength());
8470 load32(lengthAddr
, temp2
);
8471 branch32(Assembler::NotEqual
, initLengthAddr
, temp2
, fail
);
8473 // Result is |undefined| if length == 0.
8474 Label notEmpty
, done
;
8475 branchTest32(Assembler::NonZero
, temp2
, temp2
, ¬Empty
);
8477 moveValue(UndefinedValue(), output
);
8483 // Load the last element.
8484 sub32(Imm32(1), temp2
);
8485 BaseObjectElementIndex
elementAddr(temp1
, temp2
);
8486 loadValue(elementAddr
, output
);
8488 // Pre-barrier the element because we're removing it from the array.
8489 EmitPreBarrier(*this, elementAddr
, MIRType::Value
);
8491 // Update length and initializedLength.
8492 store32(temp2
, lengthAddr
);
8493 store32(temp2
, initLengthAddr
);
8498 void MacroAssembler::packedArrayShift(Register array
, ValueOperand output
,
8499 Register temp1
, Register temp2
,
8500 LiveRegisterSet volatileRegs
,
8502 // Load obj->elements in temp1.
8503 loadPtr(Address(array
, NativeObject::offsetOfElements()), temp1
);
8506 static constexpr uint32_t UnhandledFlags
=
8507 ObjectElements::Flags::NON_PACKED
|
8508 ObjectElements::Flags::NONWRITABLE_ARRAY_LENGTH
|
8509 ObjectElements::Flags::NOT_EXTENSIBLE
|
8510 ObjectElements::Flags::MAYBE_IN_ITERATION
;
8511 Address
flags(temp1
, ObjectElements::offsetOfFlags());
8512 branchTest32(Assembler::NonZero
, flags
, Imm32(UnhandledFlags
), fail
);
8514 // Load length in temp2. Ensure length == initializedLength.
8515 Address
lengthAddr(temp1
, ObjectElements::offsetOfLength());
8516 Address
initLengthAddr(temp1
, ObjectElements::offsetOfInitializedLength());
8517 load32(lengthAddr
, temp2
);
8518 branch32(Assembler::NotEqual
, initLengthAddr
, temp2
, fail
);
8520 // Result is |undefined| if length == 0.
8521 Label notEmpty
, done
;
8522 branchTest32(Assembler::NonZero
, temp2
, temp2
, ¬Empty
);
8524 moveValue(UndefinedValue(), output
);
8530 // Load the first element.
8531 Address
elementAddr(temp1
, 0);
8532 loadValue(elementAddr
, output
);
8534 // Move the other elements and update the initializedLength/length. This will
8535 // also trigger pre-barriers.
8537 // Ensure output is in volatileRegs. Don't preserve temp1 and temp2.
8538 volatileRegs
.takeUnchecked(temp1
);
8539 volatileRegs
.takeUnchecked(temp2
);
8540 if (output
.hasVolatileReg()) {
8541 volatileRegs
.addUnchecked(output
);
8544 PushRegsInMask(volatileRegs
);
8546 using Fn
= void (*)(ArrayObject
* arr
);
8547 setupUnalignedABICall(temp1
);
8549 callWithABI
<Fn
, ArrayShiftMoveElements
>();
8551 PopRegsInMask(volatileRegs
);
8557 void MacroAssembler::loadArgumentsObjectElement(Register obj
, Register index
,
8558 ValueOperand output
,
8559 Register temp
, Label
* fail
) {
8560 Register temp2
= output
.scratchReg();
8562 // Get initial length value.
8563 unboxInt32(Address(obj
, ArgumentsObject::getInitialLengthSlotOffset()), temp
);
8565 // Ensure no overridden elements.
8566 branchTest32(Assembler::NonZero
, temp
,
8567 Imm32(ArgumentsObject::ELEMENT_OVERRIDDEN_BIT
), fail
);
8570 rshift32(Imm32(ArgumentsObject::PACKED_BITS_COUNT
), temp
);
8571 spectreBoundsCheck32(index
, temp
, temp2
, fail
);
8573 // Load ArgumentsData.
8574 loadPrivate(Address(obj
, ArgumentsObject::getDataSlotOffset()), temp
);
8576 // Guard the argument is not a FORWARD_TO_CALL_SLOT MagicValue.
8577 BaseValueIndex
argValue(temp
, index
, ArgumentsData::offsetOfArgs());
8578 branchTestMagic(Assembler::Equal
, argValue
, fail
);
8579 loadValue(argValue
, output
);
8582 void MacroAssembler::loadArgumentsObjectElementHole(Register obj
,
8584 ValueOperand output
,
8587 Register temp2
= output
.scratchReg();
8589 // Get initial length value.
8590 unboxInt32(Address(obj
, ArgumentsObject::getInitialLengthSlotOffset()), temp
);
8592 // Ensure no overridden elements.
8593 branchTest32(Assembler::NonZero
, temp
,
8594 Imm32(ArgumentsObject::ELEMENT_OVERRIDDEN_BIT
), fail
);
8597 Label outOfBounds
, done
;
8598 rshift32(Imm32(ArgumentsObject::PACKED_BITS_COUNT
), temp
);
8599 spectreBoundsCheck32(index
, temp
, temp2
, &outOfBounds
);
8601 // Load ArgumentsData.
8602 loadPrivate(Address(obj
, ArgumentsObject::getDataSlotOffset()), temp
);
8604 // Guard the argument is not a FORWARD_TO_CALL_SLOT MagicValue.
8605 BaseValueIndex
argValue(temp
, index
, ArgumentsData::offsetOfArgs());
8606 branchTestMagic(Assembler::Equal
, argValue
, fail
);
8607 loadValue(argValue
, output
);
8611 branch32(Assembler::LessThan
, index
, Imm32(0), fail
);
8612 moveValue(UndefinedValue(), output
);
8617 void MacroAssembler::loadArgumentsObjectElementExists(
8618 Register obj
, Register index
, Register output
, Register temp
, Label
* fail
) {
8619 // Ensure the index is non-negative.
8620 branch32(Assembler::LessThan
, index
, Imm32(0), fail
);
8622 // Get initial length value.
8623 unboxInt32(Address(obj
, ArgumentsObject::getInitialLengthSlotOffset()), temp
);
8625 // Ensure no overridden or deleted elements.
8626 branchTest32(Assembler::NonZero
, temp
,
8627 Imm32(ArgumentsObject::ELEMENT_OVERRIDDEN_BIT
), fail
);
8629 // Compare index against the length.
8630 rshift32(Imm32(ArgumentsObject::PACKED_BITS_COUNT
), temp
);
8631 cmp32Set(Assembler::LessThan
, index
, temp
, output
);
8634 void MacroAssembler::loadArgumentsObjectLength(Register obj
, Register output
,
8636 // Get initial length value.
8637 unboxInt32(Address(obj
, ArgumentsObject::getInitialLengthSlotOffset()),
8640 // Test if length has been overridden.
8641 branchTest32(Assembler::NonZero
, output
,
8642 Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT
), fail
);
8644 // Shift out arguments length and return it.
8645 rshift32(Imm32(ArgumentsObject::PACKED_BITS_COUNT
), output
);
8648 void MacroAssembler::loadArgumentsObjectLength(Register obj
, Register output
) {
8649 // Get initial length value.
8650 unboxInt32(Address(obj
, ArgumentsObject::getInitialLengthSlotOffset()),
8654 // Assert length hasn't been overridden.
8656 branchTest32(Assembler::Zero
, output
,
8657 Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT
), &ok
);
8658 assumeUnreachable("arguments object length has been overridden");
8662 // Shift out arguments length and return it.
8663 rshift32(Imm32(ArgumentsObject::PACKED_BITS_COUNT
), output
);
8666 void MacroAssembler::branchTestArgumentsObjectFlags(Register obj
, Register temp
,
8670 MOZ_ASSERT((flags
& ~ArgumentsObject::PACKED_BITS_MASK
) == 0);
8672 // Get initial length value.
8673 unboxInt32(Address(obj
, ArgumentsObject::getInitialLengthSlotOffset()), temp
);
8676 branchTest32(cond
, temp
, Imm32(flags
), label
);
8679 static constexpr bool ValidateSizeRange(Scalar::Type from
, Scalar::Type to
) {
8680 for (Scalar::Type type
= from
; type
< to
; type
= Scalar::Type(type
+ 1)) {
8681 if (TypedArrayElemSize(type
) != TypedArrayElemSize(from
)) {
8688 void MacroAssembler::typedArrayElementSize(Register obj
, Register output
) {
8689 loadObjClassUnsafe(obj
, output
);
8691 // Map resizable to fixed-length TypedArray classes.
8693 branchPtr(Assembler::Below
, output
,
8694 ImmPtr(std::end(TypedArrayObject::fixedLengthClasses
)),
8697 MOZ_ASSERT(std::end(TypedArrayObject::fixedLengthClasses
) ==
8698 std::begin(TypedArrayObject::resizableClasses
),
8699 "TypedArray classes are in contiguous memory");
8701 const auto* firstFixedLengthTypedArrayClass
=
8702 std::begin(TypedArrayObject::fixedLengthClasses
);
8703 const auto* firstResizableTypedArrayClass
=
8704 std::begin(TypedArrayObject::resizableClasses
);
8706 MOZ_ASSERT(firstFixedLengthTypedArrayClass
< firstResizableTypedArrayClass
);
8709 firstResizableTypedArrayClass
- firstFixedLengthTypedArrayClass
;
8711 mozilla::CheckedInt
<int32_t> checked
= diff
;
8712 checked
*= sizeof(JSClass
);
8713 MOZ_ASSERT(checked
.isValid(), "pointer difference fits in int32");
8715 subPtr(Imm32(int32_t(checked
.value())), output
);
8720 Label invalidClass
, validClass
;
8721 branchPtr(Assembler::Below
, output
,
8722 ImmPtr(std::begin(TypedArrayObject::fixedLengthClasses
)),
8724 branchPtr(Assembler::Below
, output
,
8725 ImmPtr(std::end(TypedArrayObject::fixedLengthClasses
)),
8727 bind(&invalidClass
);
8728 assumeUnreachable("value isn't a valid FixedLengthTypedArray class");
8732 auto classForType
= [](Scalar::Type type
) {
8733 MOZ_ASSERT(type
< Scalar::MaxTypedArrayViewType
);
8734 return &TypedArrayObject::fixedLengthClasses
[type
];
8737 Label one
, two
, four
, eight
, done
;
8739 static_assert(ValidateSizeRange(Scalar::Int8
, Scalar::Int16
),
8740 "element size is one in [Int8, Int16)");
8741 branchPtr(Assembler::Below
, output
, ImmPtr(classForType(Scalar::Int16
)),
8744 static_assert(ValidateSizeRange(Scalar::Int16
, Scalar::Int32
),
8745 "element size is two in [Int16, Int32)");
8746 branchPtr(Assembler::Below
, output
, ImmPtr(classForType(Scalar::Int32
)),
8749 static_assert(ValidateSizeRange(Scalar::Int32
, Scalar::Float64
),
8750 "element size is four in [Int32, Float64)");
8751 branchPtr(Assembler::Below
, output
, ImmPtr(classForType(Scalar::Float64
)),
8754 static_assert(ValidateSizeRange(Scalar::Float64
, Scalar::Uint8Clamped
),
8755 "element size is eight in [Float64, Uint8Clamped)");
8756 branchPtr(Assembler::Below
, output
,
8757 ImmPtr(classForType(Scalar::Uint8Clamped
)), &eight
);
8759 static_assert(ValidateSizeRange(Scalar::Uint8Clamped
, Scalar::BigInt64
),
8760 "element size is one in [Uint8Clamped, BigInt64)");
8761 branchPtr(Assembler::Below
, output
, ImmPtr(classForType(Scalar::BigInt64
)),
8764 static_assert(ValidateSizeRange(Scalar::BigInt64
, Scalar::Float16
),
8765 "element size is eight in [BigInt64, Float16)");
8766 branchPtr(Assembler::Below
, output
, ImmPtr(classForType(Scalar::Float16
)),
8770 ValidateSizeRange(Scalar::Float16
, Scalar::MaxTypedArrayViewType
),
8771 "element size is two in [Float16, MaxTypedArrayViewType)");
8775 move32(Imm32(8), output
);
8779 move32(Imm32(4), output
);
8783 move32(Imm32(2), output
);
8787 move32(Imm32(1), output
);
8792 void MacroAssembler::resizableTypedArrayElementShiftBy(Register obj
,
8795 loadObjClassUnsafe(obj
, scratch
);
8798 Label invalidClass
, validClass
;
8799 branchPtr(Assembler::Below
, scratch
,
8800 ImmPtr(std::begin(TypedArrayObject::resizableClasses
)),
8802 branchPtr(Assembler::Below
, scratch
,
8803 ImmPtr(std::end(TypedArrayObject::resizableClasses
)), &validClass
);
8804 bind(&invalidClass
);
8805 assumeUnreachable("value isn't a valid ResizableLengthTypedArray class");
8809 auto classForType
= [](Scalar::Type type
) {
8810 MOZ_ASSERT(type
< Scalar::MaxTypedArrayViewType
);
8811 return &TypedArrayObject::resizableClasses
[type
];
8814 Label zero
, one
, two
, three
;
8816 static_assert(ValidateSizeRange(Scalar::Int8
, Scalar::Int16
),
8817 "element shift is zero in [Int8, Int16)");
8818 branchPtr(Assembler::Below
, scratch
, ImmPtr(classForType(Scalar::Int16
)),
8821 static_assert(ValidateSizeRange(Scalar::Int16
, Scalar::Int32
),
8822 "element shift is one in [Int16, Int32)");
8823 branchPtr(Assembler::Below
, scratch
, ImmPtr(classForType(Scalar::Int32
)),
8826 static_assert(ValidateSizeRange(Scalar::Int32
, Scalar::Float64
),
8827 "element shift is two in [Int32, Float64)");
8828 branchPtr(Assembler::Below
, scratch
, ImmPtr(classForType(Scalar::Float64
)),
8831 static_assert(ValidateSizeRange(Scalar::Float64
, Scalar::Uint8Clamped
),
8832 "element shift is three in [Float64, Uint8Clamped)");
8833 branchPtr(Assembler::Below
, scratch
,
8834 ImmPtr(classForType(Scalar::Uint8Clamped
)), &three
);
8836 static_assert(ValidateSizeRange(Scalar::Uint8Clamped
, Scalar::BigInt64
),
8837 "element shift is zero in [Uint8Clamped, BigInt64)");
8838 branchPtr(Assembler::Below
, scratch
, ImmPtr(classForType(Scalar::BigInt64
)),
8841 static_assert(ValidateSizeRange(Scalar::BigInt64
, Scalar::Float16
),
8842 "element shift is three in [BigInt64, Float16)");
8843 branchPtr(Assembler::Below
, scratch
, ImmPtr(classForType(Scalar::Float16
)),
8847 ValidateSizeRange(Scalar::Float16
, Scalar::MaxTypedArrayViewType
),
8848 "element shift is one in [Float16, MaxTypedArrayViewType)");
8852 rshiftPtr(Imm32(3), output
);
8856 rshiftPtr(Imm32(2), output
);
8860 rshiftPtr(Imm32(1), output
);
8865 void MacroAssembler::branchIfClassIsNotTypedArray(Register clasp
,
8866 Label
* notTypedArray
) {
8867 // Inline implementation of IsTypedArrayClass().
8869 const auto* firstTypedArrayClass
=
8870 std::begin(TypedArrayObject::fixedLengthClasses
);
8871 const auto* lastTypedArrayClass
=
8872 std::prev(std::end(TypedArrayObject::resizableClasses
));
8873 MOZ_ASSERT(std::end(TypedArrayObject::fixedLengthClasses
) ==
8874 std::begin(TypedArrayObject::resizableClasses
),
8875 "TypedArray classes are in contiguous memory");
8877 branchPtr(Assembler::Below
, clasp
, ImmPtr(firstTypedArrayClass
),
8879 branchPtr(Assembler::Above
, clasp
, ImmPtr(lastTypedArrayClass
),
8883 void MacroAssembler::branchIfClassIsNotFixedLengthTypedArray(
8884 Register clasp
, Label
* notTypedArray
) {
8885 // Inline implementation of IsFixedLengthTypedArrayClass().
8887 const auto* firstTypedArrayClass
=
8888 std::begin(TypedArrayObject::fixedLengthClasses
);
8889 const auto* lastTypedArrayClass
=
8890 std::prev(std::end(TypedArrayObject::fixedLengthClasses
));
8892 branchPtr(Assembler::Below
, clasp
, ImmPtr(firstTypedArrayClass
),
8894 branchPtr(Assembler::Above
, clasp
, ImmPtr(lastTypedArrayClass
),
8898 void MacroAssembler::branchIfClassIsNotResizableTypedArray(
8899 Register clasp
, Label
* notTypedArray
) {
8900 // Inline implementation of IsResizableTypedArrayClass().
8902 const auto* firstTypedArrayClass
=
8903 std::begin(TypedArrayObject::resizableClasses
);
8904 const auto* lastTypedArrayClass
=
8905 std::prev(std::end(TypedArrayObject::resizableClasses
));
8907 branchPtr(Assembler::Below
, clasp
, ImmPtr(firstTypedArrayClass
),
8909 branchPtr(Assembler::Above
, clasp
, ImmPtr(lastTypedArrayClass
),
8913 void MacroAssembler::branchIfHasDetachedArrayBuffer(BranchIfDetached branchIf
,
8914 Register obj
, Register temp
,
8916 // Inline implementation of ArrayBufferViewObject::hasDetachedBuffer().
8918 // TODO: The data-slot of detached views is set to undefined, which would be
8919 // a faster way to detect detached buffers.
8921 // auto cond = branchIf == BranchIfDetached::Yes ? Assembler::Equal
8922 // : Assembler::NotEqual;
8923 // branchTestUndefined(cond, Address(obj,
8924 // ArrayBufferViewObject::dataOffset()), label);
8927 Label
* ifNotDetached
= branchIf
== BranchIfDetached::Yes
? &done
: label
;
8928 Condition detachedCond
=
8929 branchIf
== BranchIfDetached::Yes
? Assembler::NonZero
: Assembler::Zero
;
8931 // Load obj->elements in temp.
8932 loadPtr(Address(obj
, NativeObject::offsetOfElements()), temp
);
8934 // Shared buffers can't be detached.
8935 branchTest32(Assembler::NonZero
,
8936 Address(temp
, ObjectElements::offsetOfFlags()),
8937 Imm32(ObjectElements::SHARED_MEMORY
), ifNotDetached
);
8939 // An ArrayBufferView with a null/true buffer has never had its buffer
8940 // exposed, so nothing can possibly detach it.
8941 fallibleUnboxObject(Address(obj
, ArrayBufferViewObject::bufferOffset()), temp
,
8944 // Load the ArrayBuffer flags and branch if the detached flag is (not) set.
8945 unboxInt32(Address(temp
, ArrayBufferObject::offsetOfFlagsSlot()), temp
);
8946 branchTest32(detachedCond
, temp
, Imm32(ArrayBufferObject::DETACHED
), label
);
8948 if (branchIf
== BranchIfDetached::Yes
) {
8953 void MacroAssembler::branchIfResizableArrayBufferViewOutOfBounds(Register obj
,
8956 // Implementation of ArrayBufferViewObject::isOutOfBounds().
8960 loadArrayBufferViewLengthIntPtr(obj
, temp
);
8961 branchPtr(Assembler::NotEqual
, temp
, ImmWord(0), &done
);
8963 loadArrayBufferViewByteOffsetIntPtr(obj
, temp
);
8964 branchPtr(Assembler::NotEqual
, temp
, ImmWord(0), &done
);
8966 loadPrivate(Address(obj
, ArrayBufferViewObject::initialLengthOffset()), temp
);
8967 branchPtr(Assembler::NotEqual
, temp
, ImmWord(0), label
);
8969 loadPrivate(Address(obj
, ArrayBufferViewObject::initialByteOffsetOffset()),
8971 branchPtr(Assembler::NotEqual
, temp
, ImmWord(0), label
);
8976 void MacroAssembler::branchIfResizableArrayBufferViewInBounds(Register obj
,
8979 // Implementation of ArrayBufferViewObject::isOutOfBounds().
8983 loadArrayBufferViewLengthIntPtr(obj
, temp
);
8984 branchPtr(Assembler::NotEqual
, temp
, ImmWord(0), label
);
8986 loadArrayBufferViewByteOffsetIntPtr(obj
, temp
);
8987 branchPtr(Assembler::NotEqual
, temp
, ImmWord(0), label
);
8989 loadPrivate(Address(obj
, ArrayBufferViewObject::initialLengthOffset()), temp
);
8990 branchPtr(Assembler::NotEqual
, temp
, ImmWord(0), &done
);
8992 loadPrivate(Address(obj
, ArrayBufferViewObject::initialByteOffsetOffset()),
8994 branchPtr(Assembler::Equal
, temp
, ImmWord(0), label
);
8999 void MacroAssembler::branchIfNativeIteratorNotReusable(Register ni
,
9000 Label
* notReusable
) {
9001 // See NativeIterator::isReusable.
9002 Address
flagsAddr(ni
, NativeIterator::offsetOfFlagsAndCount());
9005 Label niIsInitialized
;
9006 branchTest32(Assembler::NonZero
, flagsAddr
,
9007 Imm32(NativeIterator::Flags::Initialized
), &niIsInitialized
);
9009 "Expected a NativeIterator that's been completely "
9011 bind(&niIsInitialized
);
9014 branchTest32(Assembler::NonZero
, flagsAddr
,
9015 Imm32(NativeIterator::Flags::NotReusable
), notReusable
);
9018 void MacroAssembler::branchNativeIteratorIndices(Condition cond
, Register ni
,
9020 NativeIteratorIndices kind
,
9022 Address
iterFlagsAddr(ni
, NativeIterator::offsetOfFlagsAndCount());
9023 load32(iterFlagsAddr
, temp
);
9024 and32(Imm32(NativeIterator::IndicesMask
), temp
);
9025 uint32_t shiftedKind
= uint32_t(kind
) << NativeIterator::IndicesShift
;
9026 branch32(cond
, temp
, Imm32(shiftedKind
), label
);
9029 static void LoadNativeIterator(MacroAssembler
& masm
, Register obj
,
9031 MOZ_ASSERT(obj
!= dest
);
9034 // Assert we have a PropertyIteratorObject.
9036 masm
.branchTestObjClass(Assembler::Equal
, obj
,
9037 &PropertyIteratorObject::class_
, dest
, obj
, &ok
);
9038 masm
.assumeUnreachable("Expected PropertyIteratorObject!");
9042 // Load NativeIterator object.
9043 Address
slotAddr(obj
, PropertyIteratorObject::offsetOfIteratorSlot());
9044 masm
.loadPrivate(slotAddr
, dest
);
9047 // The ShapeCachePtr may be used to cache an iterator for for-in. Return that
9048 // iterator in |dest| if:
9049 // - the shape cache pointer exists and stores a native iterator
9050 // - the iterator is reusable
9051 // - the iterated object has no dense elements
9052 // - the shapes of each object on the proto chain of |obj| match the cached
9054 // - the proto chain has no dense elements
9055 // Otherwise, jump to |failure|.
9056 void MacroAssembler::maybeLoadIteratorFromShape(Register obj
, Register dest
,
9057 Register temp
, Register temp2
,
9061 // obj: always contains the input object
9062 // temp: walks the obj->shape->baseshape->proto->shape->... chain
9063 // temp2: points to the native iterator. Incremented to walk the shapes array.
9064 // temp3: scratch space
9065 // dest: stores the resulting PropertyIteratorObject on success
9068 Register shapeAndProto
= temp
;
9069 Register nativeIterator
= temp2
;
9071 // Load ShapeCache from shape.
9072 loadPtr(Address(obj
, JSObject::offsetOfShape()), shapeAndProto
);
9073 loadPtr(Address(shapeAndProto
, Shape::offsetOfCachePtr()), dest
);
9075 // Check if it's an iterator.
9076 movePtr(dest
, temp3
);
9077 andPtr(Imm32(ShapeCachePtr::MASK
), temp3
);
9078 branch32(Assembler::NotEqual
, temp3
, Imm32(ShapeCachePtr::ITERATOR
), failure
);
9080 // If we've cached an iterator, |obj| must be a native object.
9083 branchIfNonNativeObj(obj
, temp3
, &nonNative
);
9086 // Verify that |obj| has no dense elements.
9087 loadPtr(Address(obj
, NativeObject::offsetOfElements()), temp3
);
9088 branch32(Assembler::NotEqual
,
9089 Address(temp3
, ObjectElements::offsetOfInitializedLength()),
9092 // Clear tag bits from iterator object. |dest| is now valid.
9093 // Load the native iterator and verify that it's reusable.
9094 andPtr(Imm32(~ShapeCachePtr::MASK
), dest
);
9095 LoadNativeIterator(*this, dest
, nativeIterator
);
9096 branchIfNativeIteratorNotReusable(nativeIterator
, failure
);
9098 // We have to compare the shapes in the native iterator with the shapes on the
9099 // proto chain to ensure the cached iterator is still valid. The shape array
9100 // always starts at a fixed offset from the base of the NativeIterator, so
9101 // instead of using an instruction outside the loop to initialize a pointer to
9102 // the shapes array, we can bake it into the offset and reuse the pointer to
9103 // the NativeIterator. We add |sizeof(Shape*)| to start at the second shape.
9104 // (The first shape corresponds to the object itself. We don't have to check
9105 // it, because we got the iterator via the shape.)
9106 size_t nativeIteratorProtoShapeOffset
=
9107 NativeIterator::offsetOfFirstShape() + sizeof(Shape
*);
9109 // Loop over the proto chain. At the head of the loop, |shape| is the shape of
9110 // the current object, and |iteratorShapes| points to the expected shape of
9115 // Load the proto. If the proto is null, then we're done.
9116 loadPtr(Address(shapeAndProto
, Shape::offsetOfBaseShape()), shapeAndProto
);
9117 loadPtr(Address(shapeAndProto
, BaseShape::offsetOfProto()), shapeAndProto
);
9118 branchPtr(Assembler::Equal
, shapeAndProto
, ImmPtr(nullptr), &success
);
9121 // We have guarded every shape up until this point, so we know that the proto
9122 // is a native object.
9123 branchIfNonNativeObj(shapeAndProto
, temp3
, &nonNative
);
9126 // Verify that the proto has no dense elements.
9127 loadPtr(Address(shapeAndProto
, NativeObject::offsetOfElements()), temp3
);
9128 branch32(Assembler::NotEqual
,
9129 Address(temp3
, ObjectElements::offsetOfInitializedLength()),
9132 // Compare the shape of the proto to the expected shape.
9133 loadPtr(Address(shapeAndProto
, JSObject::offsetOfShape()), shapeAndProto
);
9134 loadPtr(Address(nativeIterator
, nativeIteratorProtoShapeOffset
), temp3
);
9135 branchPtr(Assembler::NotEqual
, shapeAndProto
, temp3
, failure
);
9137 // Increment |iteratorShapes| and jump back to the top of the loop.
9138 addPtr(Imm32(sizeof(Shape
*)), nativeIterator
);
9143 assumeUnreachable("Expected NativeObject in maybeLoadIteratorFromShape");
9149 void MacroAssembler::iteratorMore(Register obj
, ValueOperand output
,
9152 Register outputScratch
= output
.scratchReg();
9153 LoadNativeIterator(*this, obj
, outputScratch
);
9155 // If propertyCursor_ < propertiesEnd_, load the next string and advance
9156 // the cursor. Otherwise return MagicValue(JS_NO_ITER_VALUE).
9158 Address
cursorAddr(outputScratch
, NativeIterator::offsetOfPropertyCursor());
9159 Address
cursorEndAddr(outputScratch
, NativeIterator::offsetOfPropertiesEnd());
9160 loadPtr(cursorAddr
, temp
);
9161 branchPtr(Assembler::BelowOrEqual
, cursorEndAddr
, temp
, &iterDone
);
9164 loadPtr(Address(temp
, 0), temp
);
9166 // Increase the cursor.
9167 addPtr(Imm32(sizeof(GCPtr
<JSLinearString
*>)), cursorAddr
);
9169 tagValue(JSVAL_TYPE_STRING
, temp
, output
);
9173 moveValue(MagicValue(JS_NO_ITER_VALUE
), output
);
9178 void MacroAssembler::iteratorClose(Register obj
, Register temp1
, Register temp2
,
9180 LoadNativeIterator(*this, obj
, temp1
);
9182 // The shared iterator used for for-in with null/undefined is immutable and
9183 // unlinked. See NativeIterator::isEmptyIteratorSingleton.
9185 branchTest32(Assembler::NonZero
,
9186 Address(temp1
, NativeIterator::offsetOfFlagsAndCount()),
9187 Imm32(NativeIterator::Flags::IsEmptyIteratorSingleton
), &done
);
9189 // Clear active bit.
9190 and32(Imm32(~NativeIterator::Flags::Active
),
9191 Address(temp1
, NativeIterator::offsetOfFlagsAndCount()));
9193 // Clear objectBeingIterated.
9194 Address
iterObjAddr(temp1
, NativeIterator::offsetOfObjectBeingIterated());
9195 guardedCallPreBarrierAnyZone(iterObjAddr
, MIRType::Object
, temp2
);
9196 storePtr(ImmPtr(nullptr), iterObjAddr
);
9198 // Reset property cursor.
9199 loadPtr(Address(temp1
, NativeIterator::offsetOfShapesEnd()), temp2
);
9200 storePtr(temp2
, Address(temp1
, NativeIterator::offsetOfPropertyCursor()));
9202 // Unlink from the iterator list.
9203 const Register next
= temp2
;
9204 const Register prev
= temp3
;
9205 loadPtr(Address(temp1
, NativeIterator::offsetOfNext()), next
);
9206 loadPtr(Address(temp1
, NativeIterator::offsetOfPrev()), prev
);
9207 storePtr(prev
, Address(next
, NativeIterator::offsetOfPrev()));
9208 storePtr(next
, Address(prev
, NativeIterator::offsetOfNext()));
9210 storePtr(ImmPtr(nullptr), Address(temp1
, NativeIterator::offsetOfNext()));
9211 storePtr(ImmPtr(nullptr), Address(temp1
, NativeIterator::offsetOfPrev()));
9217 void MacroAssembler::registerIterator(Register enumeratorsList
, Register iter
,
9219 // iter->next = list
9220 storePtr(enumeratorsList
, Address(iter
, NativeIterator::offsetOfNext()));
9222 // iter->prev = list->prev
9223 loadPtr(Address(enumeratorsList
, NativeIterator::offsetOfPrev()), temp
);
9224 storePtr(temp
, Address(iter
, NativeIterator::offsetOfPrev()));
9226 // list->prev->next = iter
9227 storePtr(iter
, Address(temp
, NativeIterator::offsetOfNext()));
9229 // list->prev = iter
9230 storePtr(iter
, Address(enumeratorsList
, NativeIterator::offsetOfPrev()));
9233 void MacroAssembler::toHashableNonGCThing(ValueOperand value
,
9234 ValueOperand result
,
9235 FloatRegister tempFloat
) {
9236 // Inline implementation of |HashableValue::setValue()|.
9240 branchTestGCThing(Assembler::NotEqual
, value
, &ok
);
9241 assumeUnreachable("Unexpected GC thing");
9245 Label useInput
, done
;
9246 branchTestDouble(Assembler::NotEqual
, value
, &useInput
);
9248 Register int32
= result
.scratchReg();
9249 unboxDouble(value
, tempFloat
);
9251 // Normalize int32-valued doubles to int32 and negative zero to +0.
9253 convertDoubleToInt32(tempFloat
, int32
, &canonicalize
, false);
9255 tagValue(JSVAL_TYPE_INT32
, int32
, result
);
9258 bind(&canonicalize
);
9260 // Normalize the sign bit of a NaN.
9261 branchDouble(Assembler::DoubleOrdered
, tempFloat
, tempFloat
, &useInput
);
9262 moveValue(JS::NaNValue(), result
);
9268 moveValue(value
, result
);
9273 void MacroAssembler::toHashableValue(ValueOperand value
, ValueOperand result
,
9274 FloatRegister tempFloat
,
9275 Label
* atomizeString
, Label
* tagString
) {
9276 // Inline implementation of |HashableValue::setValue()|.
9278 ScratchTagScope
tag(*this, value
);
9279 splitTagForTest(value
, tag
);
9281 Label notString
, useInput
, done
;
9282 branchTestString(Assembler::NotEqual
, tag
, ¬String
);
9284 ScratchTagScopeRelease
_(&tag
);
9286 Register str
= result
.scratchReg();
9287 unboxString(value
, str
);
9289 branchTest32(Assembler::NonZero
, Address(str
, JSString::offsetOfFlags()),
9290 Imm32(JSString::ATOM_BIT
), &useInput
);
9292 jump(atomizeString
);
9295 tagValue(JSVAL_TYPE_STRING
, str
, result
);
9299 branchTestDouble(Assembler::NotEqual
, tag
, &useInput
);
9301 ScratchTagScopeRelease
_(&tag
);
9303 Register int32
= result
.scratchReg();
9304 unboxDouble(value
, tempFloat
);
9307 convertDoubleToInt32(tempFloat
, int32
, &canonicalize
, false);
9309 tagValue(JSVAL_TYPE_INT32
, int32
, result
);
9312 bind(&canonicalize
);
9314 branchDouble(Assembler::DoubleOrdered
, tempFloat
, tempFloat
, &useInput
);
9315 moveValue(JS::NaNValue(), result
);
9321 moveValue(value
, result
);
9326 void MacroAssembler::scrambleHashCode(Register result
) {
9327 // Inline implementation of |mozilla::ScrambleHashCode()|.
9329 mul32(Imm32(mozilla::kGoldenRatioU32
), result
);
9332 void MacroAssembler::prepareHashNonGCThing(ValueOperand value
, Register result
,
9334 // Inline implementation of |OrderedHashTableImpl::prepareHash()| and
9335 // |mozilla::HashGeneric(v.asRawBits())|.
9339 branchTestGCThing(Assembler::NotEqual
, value
, &ok
);
9340 assumeUnreachable("Unexpected GC thing");
9344 // uint32_t v1 = static_cast<uint32_t>(aValue);
9346 move64To32(value
.toRegister64(), result
);
9348 move32(value
.payloadReg(), result
);
9351 // uint32_t v2 = static_cast<uint32_t>(static_cast<uint64_t>(aValue) >> 32);
9353 auto r64
= Register64(temp
);
9354 move64(value
.toRegister64(), r64
);
9355 rshift64Arithmetic(Imm32(32), r64
);
9357 move32(value
.typeReg(), temp
);
9360 // mozilla::WrappingMultiply(kGoldenRatioU32, RotateLeft5(aHash) ^ aValue);
9361 // with |aHash = 0| and |aValue = v1|.
9362 mul32(Imm32(mozilla::kGoldenRatioU32
), result
);
9364 // mozilla::WrappingMultiply(kGoldenRatioU32, RotateLeft5(aHash) ^ aValue);
9365 // with |aHash = <above hash>| and |aValue = v2|.
9366 rotateLeft(Imm32(5), result
, result
);
9367 xor32(temp
, result
);
9369 // Combine |mul32| and |scrambleHashCode| by directly multiplying with
9370 // |kGoldenRatioU32 * kGoldenRatioU32|.
9372 // mul32(Imm32(mozilla::kGoldenRatioU32), result);
9374 // scrambleHashCode(result);
9375 mul32(Imm32(mozilla::kGoldenRatioU32
* mozilla::kGoldenRatioU32
), result
);
9378 void MacroAssembler::prepareHashString(Register str
, Register result
,
9380 // Inline implementation of |OrderedHashTableImpl::prepareHash()| and
9381 // |JSAtom::hash()|.
9385 branchTest32(Assembler::NonZero
, Address(str
, JSString::offsetOfFlags()),
9386 Imm32(JSString::ATOM_BIT
), &ok
);
9387 assumeUnreachable("Unexpected non-atom string");
9392 static_assert(FatInlineAtom::offsetOfHash() == NormalAtom::offsetOfHash());
9393 load32(Address(str
, NormalAtom::offsetOfHash()), result
);
9395 move32(Imm32(JSString::FAT_INLINE_MASK
), temp
);
9396 and32(Address(str
, JSString::offsetOfFlags()), temp
);
9398 // Set |result| to 1 for FatInlineAtoms.
9399 move32(Imm32(0), result
);
9400 cmp32Set(Assembler::Equal
, temp
, Imm32(JSString::FAT_INLINE_MASK
), result
);
9402 // Use a computed load for branch-free code.
9404 static_assert(FatInlineAtom::offsetOfHash() > NormalAtom::offsetOfHash());
9406 constexpr size_t offsetDiff
=
9407 FatInlineAtom::offsetOfHash() - NormalAtom::offsetOfHash();
9408 static_assert(mozilla::IsPowerOfTwo(offsetDiff
));
9410 uint8_t shift
= mozilla::FloorLog2Size(offsetDiff
);
9411 if (IsShiftInScaleRange(shift
)) {
9413 BaseIndex(str
, result
, ShiftToScale(shift
), NormalAtom::offsetOfHash()),
9416 lshift32(Imm32(shift
), result
);
9417 load32(BaseIndex(str
, result
, TimesOne
, NormalAtom::offsetOfHash()),
9422 scrambleHashCode(result
);
9425 void MacroAssembler::prepareHashSymbol(Register sym
, Register result
) {
9426 // Inline implementation of |OrderedHashTableImpl::prepareHash()| and
9427 // |Symbol::hash()|.
9429 load32(Address(sym
, JS::Symbol::offsetOfHash()), result
);
9431 scrambleHashCode(result
);
9434 void MacroAssembler::prepareHashBigInt(Register bigInt
, Register result
,
9435 Register temp1
, Register temp2
,
9437 // Inline implementation of |OrderedHashTableImpl::prepareHash()| and
9438 // |BigInt::hash()|.
9440 // Inline implementation of |mozilla::AddU32ToHash()|.
9441 auto addU32ToHash
= [&](auto toAdd
) {
9442 rotateLeft(Imm32(5), result
, result
);
9443 xor32(toAdd
, result
);
9444 mul32(Imm32(mozilla::kGoldenRatioU32
), result
);
9447 move32(Imm32(0), result
);
9449 // Inline |mozilla::HashBytes()|.
9451 load32(Address(bigInt
, BigInt::offsetOfLength()), temp1
);
9452 loadBigIntDigits(bigInt
, temp2
);
9459 // Compute |AddToHash(AddToHash(hash, data), sizeof(Digit))|.
9460 #if defined(JS_CODEGEN_MIPS64)
9461 // Hash the lower 32-bits.
9462 addU32ToHash(Address(temp2
, 0));
9464 // Hash the upper 32-bits.
9465 addU32ToHash(Address(temp2
, sizeof(int32_t)));
9467 // Use a single 64-bit load on non-MIPS64 platforms.
9468 loadPtr(Address(temp2
, 0), temp3
);
9470 // Hash the lower 32-bits.
9471 addU32ToHash(temp3
);
9473 // Hash the upper 32-bits.
9474 rshiftPtr(Imm32(32), temp3
);
9475 addU32ToHash(temp3
);
9477 addU32ToHash(Address(temp2
, 0));
9480 addPtr(Imm32(sizeof(BigInt::Digit
)), temp2
);
9483 branchSub32(Assembler::NotSigned
, Imm32(1), temp1
, &loop
);
9485 // Compute |mozilla::AddToHash(h, isNegative())|.
9487 static_assert(mozilla::IsPowerOfTwo(BigInt::signBitMask()));
9489 load32(Address(bigInt
, BigInt::offsetOfFlags()), temp1
);
9490 and32(Imm32(BigInt::signBitMask()), temp1
);
9491 rshift32(Imm32(mozilla::FloorLog2(BigInt::signBitMask())), temp1
);
9493 addU32ToHash(temp1
);
9496 scrambleHashCode(result
);
9499 void MacroAssembler::prepareHashObject(Register setObj
, ValueOperand value
,
9500 Register result
, Register temp1
,
9501 Register temp2
, Register temp3
,
9504 // Inline implementation of |OrderedHashTableImpl::prepareHash()| and
9505 // |HashCodeScrambler::scramble(v.asRawBits())|.
9507 // Load |HashCodeScrambler*|.
9508 static_assert(MapObject::offsetOfHashCodeScrambler() ==
9509 SetObject::offsetOfHashCodeScrambler());
9510 loadPrivate(Address(setObj
, SetObject::offsetOfHashCodeScrambler()), temp1
);
9512 // Load |HashCodeScrambler::mK0| and |HashCodeScrambler::mK1|.
9513 auto k0
= Register64(temp1
);
9514 auto k1
= Register64(temp2
);
9515 load64(Address(temp1
, mozilla::HashCodeScrambler::offsetOfMK1()), k1
);
9516 load64(Address(temp1
, mozilla::HashCodeScrambler::offsetOfMK0()), k0
);
9518 // Hash numbers are 32-bit values, so only hash the lower double-word.
9519 static_assert(sizeof(mozilla::HashNumber
) == 4);
9520 move32To64ZeroExtend(value
.valueReg(), Register64(result
));
9522 // Inline implementation of |SipHasher::sipHash()|.
9523 auto m
= Register64(result
);
9524 auto v0
= Register64(temp3
);
9525 auto v1
= Register64(temp4
);
9529 auto sipRound
= [&]() {
9530 // mV0 = WrappingAdd(mV0, mV1);
9533 // mV1 = RotateLeft(mV1, 13);
9534 rotateLeft64(Imm32(13), v1
, v1
, InvalidReg
);
9539 // mV0 = RotateLeft(mV0, 32);
9540 rotateLeft64(Imm32(32), v0
, v0
, InvalidReg
);
9542 // mV2 = WrappingAdd(mV2, mV3);
9545 // mV3 = RotateLeft(mV3, 16);
9546 rotateLeft64(Imm32(16), v3
, v3
, InvalidReg
);
9551 // mV0 = WrappingAdd(mV0, mV3);
9554 // mV3 = RotateLeft(mV3, 21);
9555 rotateLeft64(Imm32(21), v3
, v3
, InvalidReg
);
9560 // mV2 = WrappingAdd(mV2, mV1);
9563 // mV1 = RotateLeft(mV1, 17);
9564 rotateLeft64(Imm32(17), v1
, v1
, InvalidReg
);
9569 // mV2 = RotateLeft(mV2, 32);
9570 rotateLeft64(Imm32(32), v2
, v2
, InvalidReg
);
9573 // 1. Initialization.
9574 // mV0 = aK0 ^ UINT64_C(0x736f6d6570736575);
9575 move64(Imm64(0x736f6d6570736575), v0
);
9578 // mV1 = aK1 ^ UINT64_C(0x646f72616e646f6d);
9579 move64(Imm64(0x646f72616e646f6d), v1
);
9582 // mV2 = aK0 ^ UINT64_C(0x6c7967656e657261);
9583 MOZ_ASSERT(v2
== k0
);
9584 xor64(Imm64(0x6c7967656e657261), v2
);
9586 // mV3 = aK1 ^ UINT64_C(0x7465646279746573);
9587 MOZ_ASSERT(v3
== k1
);
9588 xor64(Imm64(0x7465646279746573), v3
);
9602 xor64(Imm64(0xff), v2
);
9604 // for (int i = 0; i < 3; i++) sipRound();
9605 for (int i
= 0; i
< 3; i
++) {
9609 // return mV0 ^ mV1 ^ mV2 ^ mV3;
9614 move64To32(v0
, result
);
9616 scrambleHashCode(result
);
9618 MOZ_CRASH("Not implemented");
9622 void MacroAssembler::prepareHashValue(Register setObj
, ValueOperand value
,
9623 Register result
, Register temp1
,
9624 Register temp2
, Register temp3
,
9626 Label isString
, isObject
, isSymbol
, isBigInt
;
9628 ScratchTagScope
tag(*this, value
);
9629 splitTagForTest(value
, tag
);
9631 branchTestString(Assembler::Equal
, tag
, &isString
);
9632 branchTestObject(Assembler::Equal
, tag
, &isObject
);
9633 branchTestSymbol(Assembler::Equal
, tag
, &isSymbol
);
9634 branchTestBigInt(Assembler::Equal
, tag
, &isBigInt
);
9639 prepareHashNonGCThing(value
, result
, temp1
);
9644 unboxString(value
, temp1
);
9645 prepareHashString(temp1
, result
, temp2
);
9650 prepareHashObject(setObj
, value
, result
, temp1
, temp2
, temp3
, temp4
);
9655 unboxSymbol(value
, temp1
);
9656 prepareHashSymbol(temp1
, result
);
9661 unboxBigInt(value
, temp1
);
9662 prepareHashBigInt(temp1
, result
, temp2
, temp3
, temp4
);
9664 // Fallthrough to |done|.
9670 template <typename TableObject
>
9671 void MacroAssembler::orderedHashTableLookup(Register setOrMapObj
,
9672 ValueOperand value
, Register hash
,
9673 Register entryTemp
, Register temp1
,
9674 Register temp2
, Register temp3
,
9675 Register temp4
, Label
* found
,
9676 IsBigInt isBigInt
) {
9677 // Inline implementation of |OrderedHashTableImpl::lookup()|.
9679 MOZ_ASSERT_IF(isBigInt
== IsBigInt::No
, temp3
== InvalidReg
);
9680 MOZ_ASSERT_IF(isBigInt
== IsBigInt::No
, temp4
== InvalidReg
);
9684 if (isBigInt
== IsBigInt::No
) {
9685 branchTestBigInt(Assembler::NotEqual
, value
, &ok
);
9686 assumeUnreachable("Unexpected BigInt");
9687 } else if (isBigInt
== IsBigInt::Yes
) {
9688 branchTestBigInt(Assembler::Equal
, value
, &ok
);
9689 assumeUnreachable("Unexpected non-BigInt");
9695 PushRegsInMask(LiveRegisterSet(RegisterSet::Volatile()));
9698 moveStackPtrTo(temp2
);
9700 setupUnalignedABICall(temp1
);
9701 loadJSContext(temp1
);
9703 passABIArg(setOrMapObj
);
9707 if constexpr (std::is_same_v
<TableObject
, SetObject
>) {
9709 void (*)(JSContext
*, SetObject
*, const Value
*, mozilla::HashNumber
);
9710 callWithABI
<Fn
, jit::AssertSetObjectHash
>();
9712 static_assert(std::is_same_v
<TableObject
, MapObject
>);
9714 void (*)(JSContext
*, MapObject
*, const Value
*, mozilla::HashNumber
);
9715 callWithABI
<Fn
, jit::AssertMapObjectHash
>();
9719 PopRegsInMask(LiveRegisterSet(RegisterSet::Volatile()));
9722 // Determine the bucket by computing |hash >> object->hashShift|. The hash
9723 // shift is stored as PrivateUint32Value.
9724 move32(hash
, entryTemp
);
9725 unboxInt32(Address(setOrMapObj
, TableObject::offsetOfHashShift()), temp2
);
9726 flexibleRshift32(temp2
, entryTemp
);
9728 loadPrivate(Address(setOrMapObj
, TableObject::offsetOfHashTable()), temp2
);
9729 loadPtr(BaseIndex(temp2
, entryTemp
, ScalePointer
), entryTemp
);
9731 // Search for a match in this bucket.
9736 // Inline implementation of |HashableValue::operator==|.
9738 static_assert(TableObject::Table::offsetOfImplDataElement() == 0,
9739 "offsetof(Data, element) is 0");
9740 auto keyAddr
= Address(entryTemp
, TableObject::Table::offsetOfEntryKey());
9742 if (isBigInt
== IsBigInt::No
) {
9743 // Two HashableValues are equal if they have equal bits.
9744 branch64(Assembler::Equal
, keyAddr
, value
.toRegister64(), found
);
9747 auto key
= ValueOperand(temp1
);
9749 auto key
= ValueOperand(temp1
, temp2
);
9752 loadValue(keyAddr
, key
);
9754 // Two HashableValues are equal if they have equal bits.
9755 branch64(Assembler::Equal
, key
.toRegister64(), value
.toRegister64(),
9758 // BigInt values are considered equal if they represent the same
9759 // mathematical value.
9761 fallibleUnboxBigInt(key
, temp2
, &next
);
9762 if (isBigInt
== IsBigInt::Yes
) {
9763 unboxBigInt(value
, temp1
);
9765 fallibleUnboxBigInt(value
, temp1
, &next
);
9767 equalBigInts(temp1
, temp2
, temp3
, temp4
, temp1
, temp2
, &next
, &next
,
9773 loadPtr(Address(entryTemp
, TableObject::Table::offsetOfImplDataChain()),
9776 branchTestPtr(Assembler::NonZero
, entryTemp
, entryTemp
, &loop
);
9779 void MacroAssembler::setObjectHas(Register setObj
, ValueOperand value
,
9780 Register hash
, Register result
,
9781 Register temp1
, Register temp2
,
9782 Register temp3
, Register temp4
,
9783 IsBigInt isBigInt
) {
9785 orderedHashTableLookup
<SetObject
>(setObj
, value
, hash
, result
, temp1
, temp2
,
9786 temp3
, temp4
, &found
, isBigInt
);
9789 move32(Imm32(0), result
);
9793 move32(Imm32(1), result
);
9797 void MacroAssembler::mapObjectHas(Register mapObj
, ValueOperand value
,
9798 Register hash
, Register result
,
9799 Register temp1
, Register temp2
,
9800 Register temp3
, Register temp4
,
9801 IsBigInt isBigInt
) {
9803 orderedHashTableLookup
<MapObject
>(mapObj
, value
, hash
, result
, temp1
, temp2
,
9804 temp3
, temp4
, &found
, isBigInt
);
9807 move32(Imm32(0), result
);
9811 move32(Imm32(1), result
);
9815 void MacroAssembler::mapObjectGet(Register mapObj
, ValueOperand value
,
9816 Register hash
, ValueOperand result
,
9817 Register temp1
, Register temp2
,
9818 Register temp3
, Register temp4
,
9819 Register temp5
, IsBigInt isBigInt
) {
9821 orderedHashTableLookup
<MapObject
>(mapObj
, value
, hash
, temp1
, temp2
, temp3
,
9822 temp4
, temp5
, &found
, isBigInt
);
9825 moveValue(UndefinedValue(), result
);
9828 // |temp1| holds the found entry.
9830 loadValue(Address(temp1
, MapObject::Table::Entry::offsetOfValue()), result
);
9835 template <typename TableObject
>
9836 void MacroAssembler::loadOrderedHashTableCount(Register setOrMapObj
,
9838 // Inline implementation of |OrderedHashTableImpl::count()|.
9840 // Load the live count, stored as PrivateUint32Value.
9841 unboxInt32(Address(setOrMapObj
, TableObject::offsetOfLiveCount()), result
);
9844 void MacroAssembler::loadSetObjectSize(Register setObj
, Register result
) {
9845 loadOrderedHashTableCount
<SetObject
>(setObj
, result
);
9848 void MacroAssembler::loadMapObjectSize(Register mapObj
, Register result
) {
9849 loadOrderedHashTableCount
<MapObject
>(mapObj
, result
);
9852 // Can't push large frames blindly on windows, so we must touch frame memory
9853 // incrementally, with no more than 4096 - 1 bytes between touches.
9855 // This is used across all platforms for simplicity.
9856 void MacroAssembler::touchFrameValues(Register numStackValues
,
9857 Register scratch1
, Register scratch2
) {
9858 const size_t FRAME_TOUCH_INCREMENT
= 2048;
9859 static_assert(FRAME_TOUCH_INCREMENT
< 4096 - 1,
9860 "Frame increment is too large");
9862 moveStackPtrTo(scratch2
);
9864 mov(numStackValues
, scratch1
);
9865 lshiftPtr(Imm32(3), scratch1
);
9867 // Note: this loop needs to update the stack pointer register because older
9868 // Linux kernels check the distance between the touched address and RSP.
9869 // See bug 1839669 comment 47.
9870 Label touchFrameLoop
;
9871 Label touchFrameLoopEnd
;
9872 bind(&touchFrameLoop
);
9873 branchSub32(Assembler::Signed
, Imm32(FRAME_TOUCH_INCREMENT
), scratch1
,
9874 &touchFrameLoopEnd
);
9875 subFromStackPtr(Imm32(FRAME_TOUCH_INCREMENT
));
9876 store32(Imm32(0), Address(getStackPointer(), 0));
9877 jump(&touchFrameLoop
);
9878 bind(&touchFrameLoopEnd
);
9881 moveToStackPtr(scratch2
);
9884 #ifdef FUZZING_JS_FUZZILLI
9885 void MacroAssembler::fuzzilliHashDouble(FloatRegister src
, Register result
,
9887 canonicalizeDouble(src
);
9890 Register64
r64(temp
);
9892 Register64
r64(temp
, result
);
9895 moveDoubleToGPR64(src
, r64
);
9898 // Move the high word into |result|.
9899 move64(r64
, Register64(result
));
9900 rshift64(Imm32(32), Register64(result
));
9903 // Add the high and low words of |r64|.
9904 add32(temp
, result
);
9907 void MacroAssembler::fuzzilliStoreHash(Register value
, Register temp1
,
9909 loadJSContext(temp1
);
9912 Address
addrExecHashInputs(temp1
, offsetof(JSContext
, executionHashInputs
));
9913 add32(Imm32(1), addrExecHashInputs
);
9916 Address
addrExecHash(temp1
, offsetof(JSContext
, executionHash
));
9917 load32(addrExecHash
, temp2
);
9918 add32(value
, temp2
);
9919 rotateLeft(Imm32(1), temp2
, temp2
);
9920 store32(temp2
, addrExecHash
);
9928 template <class RegisterType
>
9929 AutoGenericRegisterScope
<RegisterType
>::AutoGenericRegisterScope(
9930 MacroAssembler
& masm
, RegisterType reg
)
9931 : RegisterType(reg
), masm_(masm
), released_(false) {
9932 masm
.debugTrackedRegisters_
.add(reg
);
9935 template AutoGenericRegisterScope
<Register
>::AutoGenericRegisterScope(
9936 MacroAssembler
& masm
, Register reg
);
9937 template AutoGenericRegisterScope
<FloatRegister
>::AutoGenericRegisterScope(
9938 MacroAssembler
& masm
, FloatRegister reg
);
9942 template <class RegisterType
>
9943 AutoGenericRegisterScope
<RegisterType
>::~AutoGenericRegisterScope() {
9949 template AutoGenericRegisterScope
<Register
>::~AutoGenericRegisterScope();
9950 template AutoGenericRegisterScope
<FloatRegister
>::~AutoGenericRegisterScope();
9952 template <class RegisterType
>
9953 void AutoGenericRegisterScope
<RegisterType
>::release() {
9954 MOZ_ASSERT(!released_
);
9956 const RegisterType
& reg
= *dynamic_cast<RegisterType
*>(this);
9957 masm_
.debugTrackedRegisters_
.take(reg
);
9960 template void AutoGenericRegisterScope
<Register
>::release();
9961 template void AutoGenericRegisterScope
<FloatRegister
>::release();
9963 template <class RegisterType
>
9964 void AutoGenericRegisterScope
<RegisterType
>::reacquire() {
9965 MOZ_ASSERT(released_
);
9967 const RegisterType
& reg
= *dynamic_cast<RegisterType
*>(this);
9968 masm_
.debugTrackedRegisters_
.add(reg
);
9971 template void AutoGenericRegisterScope
<Register
>::reacquire();
9972 template void AutoGenericRegisterScope
<FloatRegister
>::reacquire();