Bug 1930352 - Fix android lint failure. a=lint-fix CLOSED TREE
[gecko.git] / js / src / jit / MacroAssembler.cpp
blob2eeb1f9833c75fe5d490d021ab15af90f464adb1
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"
14 #include <algorithm>
15 #include <limits>
16 #include <utility>
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"
64 using namespace js;
65 using namespace js::jit;
67 using JS::GenericNaN;
69 using mozilla::CheckedInt;
71 TrampolinePtr MacroAssembler::preBarrierTrampoline(MIRType type) {
72 const JitRuntime* rt = runtime()->jitRuntime();
73 return rt->preBarrier(type);
76 template <typename T>
77 static void StoreToTypedFloatArray(MacroAssembler& masm, Scalar::Type arrayType,
78 FloatRegister value, const T& dest,
79 Register temp,
80 LiveRegisterSet volatileLiveRegs) {
81 switch (arrayType) {
82 case Scalar::Float16:
83 masm.storeFloat16(value, dest, temp, volatileLiveRegs);
84 break;
85 case Scalar::Float32: {
86 if (value.isDouble()) {
87 ScratchFloat32Scope fpscratch(masm);
88 masm.convertDoubleToFloat32(value, fpscratch);
89 masm.storeFloat32(fpscratch, dest);
90 } else {
91 MOZ_ASSERT(value.isSingle());
92 masm.storeFloat32(value, dest);
94 break;
96 case Scalar::Float64:
97 MOZ_ASSERT(value.isDouble());
98 masm.storeDouble(value, dest);
99 break;
100 default:
101 MOZ_CRASH("Invalid typed array type");
105 void MacroAssembler::storeToTypedFloatArray(Scalar::Type arrayType,
106 FloatRegister value,
107 const BaseIndex& dest,
108 Register temp,
109 LiveRegisterSet volatileLiveRegs) {
110 StoreToTypedFloatArray(*this, arrayType, value, dest, temp, volatileLiveRegs);
112 void MacroAssembler::storeToTypedFloatArray(Scalar::Type arrayType,
113 FloatRegister value,
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,
122 const T& dest) {
123 MOZ_ASSERT(Scalar::isBigIntType(arrayType));
124 masm.store64(value, dest);
127 void MacroAssembler::storeToTypedBigIntArray(Scalar::Type arrayType,
128 Register64 value,
129 const BaseIndex& dest) {
130 StoreToTypedBigIntArray(*this, arrayType, value, dest);
132 void MacroAssembler::storeToTypedBigIntArray(Scalar::Type arrayType,
133 Register64 value,
134 const Address& dest) {
135 StoreToTypedBigIntArray(*this, arrayType, value, dest);
138 void MacroAssembler::boxUint32(Register source, ValueOperand dest,
139 Uint32Mode mode, Label* fail) {
140 switch (mode) {
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);
145 break;
147 case Uint32Mode::ForceDouble: {
148 // Always convert the value to double.
149 ScratchDoubleScope fpscratch(*this);
150 convertUInt32ToDouble(source, fpscratch);
151 boxDouble(fpscratch, dest, fpscratch);
152 break;
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) {
162 switch (arrayType) {
163 case Scalar::Int8:
164 load8SignExtend(src, dest.gpr());
165 break;
166 case Scalar::Uint8:
167 case Scalar::Uint8Clamped:
168 load8ZeroExtend(src, dest.gpr());
169 break;
170 case Scalar::Int16:
171 load16SignExtend(src, dest.gpr());
172 break;
173 case Scalar::Uint16:
174 load16ZeroExtend(src, dest.gpr());
175 break;
176 case Scalar::Int32:
177 load32(src, dest.gpr());
178 break;
179 case Scalar::Uint32:
180 if (dest.isFloat()) {
181 load32(src, temp1);
182 convertUInt32ToDouble(temp1, dest.fpu());
183 } else {
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);
191 break;
192 case Scalar::Float16:
193 loadFloat16(src, dest.fpu(), temp1, temp2, volatileLiveRegs);
194 canonicalizeFloat(dest.fpu());
195 break;
196 case Scalar::Float32:
197 loadFloat32(src, dest.fpu());
198 canonicalizeFloat(dest.fpu());
199 break;
200 case Scalar::Float64:
201 loadDouble(src, dest.fpu());
202 canonicalizeDouble(dest.fpu());
203 break;
204 case Scalar::BigInt64:
205 case Scalar::BigUint64:
206 default:
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,
224 Label* fail,
225 LiveRegisterSet volatileLiveRegs) {
226 switch (arrayType) {
227 case Scalar::Int8:
228 case Scalar::Uint8:
229 case Scalar::Uint8Clamped:
230 case Scalar::Int16:
231 case Scalar::Uint16:
232 case Scalar::Int32:
233 loadFromTypedArray(arrayType, src, AnyRegister(dest.scratchReg()),
234 InvalidReg, InvalidReg, nullptr, LiveRegisterSet{});
235 tagValue(JSVAL_TYPE_INT32, dest.scratchReg(), dest);
236 break;
237 case Scalar::Uint32:
238 load32(src, dest.scratchReg());
239 boxUint32(dest.scratchReg(), dest, uint32Mode, fail);
240 break;
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);
248 break;
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);
257 break;
259 case Scalar::Float64: {
260 ScratchDoubleScope fpscratch(*this);
261 loadFromTypedArray(arrayType, src, AnyRegister(fpscratch), InvalidReg,
262 InvalidReg, nullptr, LiveRegisterSet{});
263 boxDouble(fpscratch, dest, fpscratch);
264 break;
266 case Scalar::BigInt64:
267 case Scalar::BigUint64:
268 default:
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,
285 Register64 temp) {
286 MOZ_ASSERT(Scalar::isBigIntType(arrayType));
288 load64(src, temp);
289 initializeBigInt64(arrayType, bigInt, temp);
292 template void MacroAssembler::loadFromTypedBigIntArray(Scalar::Type arrayType,
293 const Address& src,
294 Register bigInt,
295 Register64 temp);
296 template void MacroAssembler::loadFromTypedBigIntArray(Scalar::Type arrayType,
297 const BaseIndex& src,
298 Register bigInt,
299 Register64 temp);
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,
304 Label* fail) {
305 // Don't execute the inline path if GC probes are built in.
306 #ifdef JS_GC_PROBES
307 jump(fail);
308 #endif
310 #ifdef JS_GC_ZEAL
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),
314 fail);
315 #endif
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()) {
322 loadJSContext(temp);
323 loadPtr(Address(temp, JSContext::offsetOfRealm()), temp);
324 branchPtr(Assembler::NotEqual,
325 Address(temp, Realm::offsetOfAllocationMetadataBuilder()),
326 ImmWord(0), fail);
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
357 // interpreter.
358 if (nDynamicSlots >= Nursery::MaxNurseryBufferSize / sizeof(Value)) {
359 jump(fail);
360 return;
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;
377 if (nDynamicSlots) {
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);
386 if (nDynamicSlots) {
387 store32(Imm32(nDynamicSlots),
388 Address(result, thingSize + ObjectSlots::offsetOfCapacity()));
389 store32(
390 Imm32(0),
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));
406 Label fallback;
407 Label success;
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.
423 jump(&success);
425 bind(&fallback);
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.
432 Push(result);
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()));
436 Pop(result);
438 bind(&success);
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;
451 push(regSlots);
452 movePtr(slots, regSlots);
453 call(runtime()->jitRuntime()->freeStub());
454 pop(regSlots);
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,
470 allocSite);
473 // Fall back to calling into the VM to allocate objects in the tenured heap
474 // that have dynamic slots.
475 if (nDynamicSlots) {
476 jump(fail);
477 return;
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");
509 // Allocate object.
510 allocateObject(result, temp, allocKind, numDynamicSlots, initialHeap, fail,
511 allocSite);
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.
528 if (initContents) {
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);
557 // Allocate object.
558 allocateObject(result, temp, allocKind, numDynamicSlots, initialHeap, fail,
559 allocSite);
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,
597 Label* fail) {
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,
606 thingSize);
609 // Inline version of Nursery::allocateBigInt.
610 void MacroAssembler::nurseryAllocateBigInt(Register result, Register temp,
611 Label* fail) {
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,
621 thingSize);
624 static bool IsNurseryAllocEnabled(CompileZone* zone, JS::TraceKind kind) {
625 switch (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();
632 default:
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)) {
652 jump(fail);
653 return;
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()));
689 } else {
690 movePtr(ImmPtr(countAddress), temp);
691 add32(Imm32(1), Address(temp, 0));
694 } else {
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) {
708 Label done;
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()));
720 bind(&done);
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,
749 initialHeap, fail);
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,
768 uint32_t end) {
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.
776 Value v;
777 if (templateObj.isRegExpObject() && i == RegExpObject::lastIndexSlot()) {
778 v = Int32Value(0);
779 } else {
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,
788 const Value& v) {
789 MOZ_ASSERT(v.isUndefined() || IsUninitializedLexical(v));
791 if (start >= end) {
792 return;
795 #ifdef JS_NUNBOX32
796 // We only have a single spare register, so do the initialization as two
797 // strided writes of the tag and body.
798 Address addr = base;
799 move32(Imm32(v.toNunboxPayload()), temp);
800 for (unsigned i = start; i < end; ++i, addr.offset += sizeof(GCPtr<Value>)) {
801 store32(temp, ToPayload(addr));
804 addr = base;
805 move32(Imm32(v.toNunboxTag()), temp);
806 for (unsigned i = start; i < end; ++i, addr.offset += sizeof(GCPtr<Value>)) {
807 store32(temp, ToType(addr));
809 #else
810 moveValue(v, ValueOperand(temp));
811 for (uint32_t i = start; i < end; ++i, base.offset += sizeof(GCPtr<Value>)) {
812 storePtr(temp, base);
814 #endif
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()) {
836 break;
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))) {
844 break;
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);
862 static_assert(
863 FixedLengthTypedArrayObject::FIXED_DATA_START ==
864 FixedLengthTypedArrayObject::DATA_SLOT + 1,
865 "fixed inline element data assumed to begin after the data slot");
867 static_assert(
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
891 // space.)
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");
899 } else {
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);
913 loadJSContext(temp);
914 passABIArg(temp);
915 passABIArg(obj);
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();
932 if (nslots == 0) {
933 return;
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,
971 nfixed);
974 if (ndynamic) {
975 // We are short one register to do this elegantly. Borrow the obj
976 // register briefly for our slots base address.
977 push(obj);
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);
989 } else {
990 fillSlotsWithUndefined(Address(obj, 0), temp, 0, ndynamic);
993 pop(obj);
997 void MacroAssembler::initGCThing(Register obj, Register temp,
998 const TemplateObject& templateObj,
999 bool initContents) {
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
1011 // filled in.
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.
1027 store32(
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()));
1042 } else {
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);
1055 } else {
1056 MOZ_CRASH("Unknown object");
1059 #ifdef JS_GC_PROBES
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);
1069 passABIArg(obj);
1070 callWithABI<Fn, TraceCreateObject>();
1072 PopRegsInMask(save);
1073 #endif
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)
1081 : sizeof(char16_t);
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) {
1097 T value = 0;
1098 std::memcpy(&value, chars, sizeof(T));
1099 return value;
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,
1118 Label* label) {
1119 CharEncoding encoding =
1120 linear->hasLatin1Chars() ? CharEncoding::Latin1 : CharEncoding::TwoByte;
1121 size_t encodingSize = encoding == CharEncoding::Latin1
1122 ? sizeof(JS::Latin1Char)
1123 : sizeof(char16_t);
1124 size_t byteLength = StringCharsByteLength(linear);
1126 size_t pos = 0;
1127 for (size_t stride : {8, 4, 2, 1}) {
1128 while (byteLength >= stride) {
1129 Address addr(stringChars, pos * encodingSize);
1130 switch (stride) {
1131 case 8: {
1132 auto x = CopyCharacters<uint64_t>(linear, pos);
1133 branch64(Assembler::NotEqual, addr, Imm64(x), label);
1134 break;
1136 case 4: {
1137 auto x = CopyCharacters<uint32_t>(linear, pos);
1138 branch32(Assembler::NotEqual, addr, Imm32(x), label);
1139 break;
1141 case 2: {
1142 auto x = CopyCharacters<uint16_t>(linear, pos);
1143 branch16(Assembler::NotEqual, addr, Imm32(x), label);
1144 break;
1146 case 1: {
1147 auto x = CopyCharacters<uint8_t>(linear, pos);
1148 branch8(Assembler::NotEqual, addr, Imm32(x), label);
1149 break;
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);
1168 switch (stride) {
1169 case 8: {
1170 auto x = CopyCharacters<uint64_t>(linear, prev);
1171 branch64(Assembler::NotEqual, addr, Imm64(x), label);
1172 break;
1174 case 4: {
1175 auto x = CopyCharacters<uint32_t>(linear, prev);
1176 branch32(Assembler::NotEqual, addr, Imm32(x), label);
1177 break;
1181 // Break from the loop, because we've finished the complete string.
1182 break;
1187 void MacroAssembler::loadStringCharsForCompare(Register input,
1188 const JSLinearString* linear,
1189 Register stringChars,
1190 Label* fail) {
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
1195 // representation.
1196 branchIfRope(input, fail);
1197 if (encoding == CharEncoding::Latin1) {
1198 branchTwoByteString(input, fail);
1199 } else {
1200 JS::AutoCheckCannotGC nogc;
1201 if (mozilla::IsUtf16Latin1(linear->twoByteRange(nogc))) {
1202 branchLatin1String(input, fail);
1203 } else {
1204 // This case was already handled in the caller.
1205 #ifdef DEBUG
1206 Label ok;
1207 branchTwoByteString(input, &ok);
1208 assumeUnreachable("Unexpected Latin-1 string");
1209 bind(&ok);
1210 #endif
1214 #ifdef DEBUG
1216 size_t length = linear->length();
1217 MOZ_ASSERT(length > 0);
1219 Label ok;
1220 branch32(Assembler::AboveOrEqual,
1221 Address(input, JSString::offsetOfLength()), Imm32(length), &ok);
1222 assumeUnreachable("Input mustn't be smaller than search string");
1223 bind(&ok);
1225 #endif
1227 // Load the input string's characters.
1228 loadStringChars(input, stringChars, encoding);
1231 void MacroAssembler::compareStringChars(JSOp op, Register stringChars,
1232 const JSLinearString* linear,
1233 Register output) {
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 ||
1240 byteLength == 8) {
1241 auto cond = JSOpToCondition(op, /* isSigned = */ false);
1243 Address addr(stringChars, 0);
1244 switch (byteLength) {
1245 case 8: {
1246 auto x = CopyCharacters<uint64_t>(linear, 0);
1247 cmp64Set(cond, addr, Imm64(x), output);
1248 break;
1250 case 4: {
1251 auto x = CopyCharacters<uint32_t>(linear, 0);
1252 cmp32Set(cond, addr, Imm32(x), output);
1253 break;
1255 case 2: {
1256 auto x = CopyCharacters<uint16_t>(linear, 0);
1257 cmp16Set(cond, addr, Imm32(x), output);
1258 break;
1260 case 1: {
1261 auto x = CopyCharacters<uint8_t>(linear, 0);
1262 cmp8Set(cond, addr, Imm32(x), output);
1263 break;
1266 } else {
1267 Label setNotEqualResult;
1268 branchIfNotStringCharsEquals(stringChars, linear, &setNotEqualResult);
1270 // Falls through if both strings are equal.
1272 Label done;
1273 move32(Imm32(op == JSOp::Eq || op == JSOp::StrictEq), output);
1274 jump(&done);
1276 bind(&setNotEqualResult);
1277 move32(Imm32(op == JSOp::Ne || op == JSOp::StrictNe), output);
1279 bind(&done);
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) ? &notPointerEqual : fail);
1293 move32(Imm32(op == JSOp::Eq || op == JSOp::StrictEq || op == JSOp::Le ||
1294 op == JSOp::Ge),
1295 result);
1297 if (IsEqualityOp(op)) {
1298 Label done;
1299 jump(&done);
1301 bind(&notPointerEqual);
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()),
1316 result, fail);
1318 bind(&setNotEqualResult);
1319 move32(Imm32(op == JSOp::Ne || op == JSOp::StrictNe), result);
1321 bind(&done);
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);
1336 } else {
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 "
1348 "pointer");
1349 move32(Imm32(Mask), dest);
1350 and32(Address(str, JSString::offsetOfFlags()), dest);
1351 cmp32MovePtr(Assembler::NotEqual, dest, Imm32(JSString::LINEAR_BIT), dest,
1352 str);
1356 // Load the inline chars.
1357 computeEffectiveAddress(Address(str, JSInlineString::offsetOfInlineStorage()),
1358 dest);
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 "
1382 "pointer");
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,
1404 Register dest) {
1405 computeEffectiveAddress(Address(str, JSInlineString::offsetOfInlineStorage()),
1406 dest);
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
1418 // for now.
1419 loadStringChars(str, dest, encoding);
1420 } else {
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);
1435 } else {
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);
1449 } else {
1450 loadPtr(Address(str, JSRope::offsetOfRight()), dest);
1454 void MacroAssembler::storeRopeChildren(Register left, Register right,
1455 Register str) {
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
1466 // execution.
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,
1480 Register index,
1481 Register scratch,
1482 Label* maybeSplit,
1483 Label* notSplit) {
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()),
1494 scratch, notSplit);
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);
1515 Label loadedChild;
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);
1520 } else {
1521 MOZ_ASSERT(maybeScratch != InvalidReg);
1523 // Check if |index| is contained in the left child.
1524 Label loadRight;
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);
1531 jump(&loadedChild);
1533 bind(&loadRight);
1536 // The index must be in the rightChild.
1537 loadRopeRightChild(str, output);
1539 bind(&loadedChild);
1542 void MacroAssembler::branchIfCanLoadStringChar(CharKind kind, Register str,
1543 Register index, Register scratch,
1544 Register maybeScratch,
1545 Label* label) {
1546 Label splitSurrogate;
1547 loadRopeChild(kind, str, index, scratch, maybeScratch, label,
1548 &splitSurrogate);
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,
1559 Register index,
1560 Register scratch,
1561 Register maybeScratch,
1562 Label* label) {
1563 Label done;
1564 loadRopeChild(kind, str, index, scratch, maybeScratch, &done, label);
1566 // Branch if the left or right side is another rope.
1567 branchIfRope(scratch, label);
1569 bind(&done);
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.
1589 Label notRope;
1590 branchIfNotRope(str, &notRope);
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, &notInLeft);
1598 if (kind == CharKind::CodePoint) {
1599 branchIfMaybeSplitSurrogatePair(output, scratch1, scratch2, fail,
1600 &loadedChild);
1602 jump(&loadedChild);
1604 // The index must be in the rightChild.
1605 // index -= rope->leftChild()->length()
1606 bind(&notInLeft);
1607 sub32(Address(output, JSString::offsetOfLength()), scratch1);
1608 loadRopeRightChild(str, output);
1610 // If the left or right side is another rope, give up.
1611 bind(&loadedChild);
1612 branchIfRope(output, fail);
1614 bind(&notRope);
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);
1623 } else {
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()),
1641 InvalidReg, &done);
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),
1654 scratch1);
1655 add32(scratch1, output);
1658 jump(&done);
1660 bind(&isLatin1);
1662 loadStringChars(output, scratch2, CharEncoding::Latin1);
1663 loadChar(scratch2, scratch1, output, CharEncoding::Latin1);
1666 bind(&done);
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);
1676 if (index == 0) {
1677 movePtr(str, scratch1);
1679 // This follows JSString::getChar.
1680 Label notRope;
1681 branchIfNotRope(str, &notRope);
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);
1690 bind(&notRope);
1692 Label isLatin1, done;
1693 branchLatin1String(scratch1, &isLatin1);
1694 loadStringChars(scratch1, scratch2, CharEncoding::TwoByte);
1695 loadChar(Address(scratch2, 0), output, CharEncoding::TwoByte);
1696 jump(&done);
1698 bind(&isLatin1);
1699 loadStringChars(scratch1, scratch2, CharEncoding::Latin1);
1700 loadChar(Address(scratch2, 0), output, CharEncoding::Latin1);
1702 bind(&done);
1703 } else {
1704 move32(Imm32(index), scratch1);
1705 loadStringChar(str, scratch1, output, scratch1, scratch2, fail);
1709 void MacroAssembler::loadStringIndexValue(Register str, Register dest,
1710 Label* fail) {
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);
1726 } else {
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);
1737 } else {
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,
1786 Register dest,
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);
1797 add32(c2, 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,
1814 Label* fail) {
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,
1823 Register dest,
1824 const StaticStrings& staticStrings,
1825 Label* fail) {
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),
1839 fail);
1840 branch32(Assembler::Equal, ch2, Imm32(StaticStrings::INVALID_SMALL_CHAR),
1841 fail);
1843 lshift32(Imm32(StaticStrings::SMALL_CHAR_BITS), ch1);
1844 add32(ch2, 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,
1852 Register scratch,
1853 const StaticStrings& staticStrings,
1854 Label* fail) {
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) {
1866 #ifdef DEBUG
1867 Label baseBad, baseOk;
1868 branch32(Assembler::LessThan, base, Imm32(2), &baseBad);
1869 branch32(Assembler::LessThanOrEqual, base, Imm32(36), &baseOk);
1870 bind(&baseBad);
1871 assumeUnreachable("base must be in range [2, 36]");
1872 bind(&baseOk);
1873 #endif
1875 // Compute |"0123456789abcdefghijklmnopqrstuvwxyz"[r]|.
1876 auto toChar = [this, base, lowerCase](Register r) {
1877 #ifdef DEBUG
1878 Label ok;
1879 branch32(Assembler::Below, r, base, &ok);
1880 assumeUnreachable("bad digit");
1881 bind(&ok);
1882 #else
1883 // Silence unused lambda capture warning.
1884 (void)base;
1885 #endif
1887 Label done;
1888 add32(Imm32('0'), r);
1889 branch32(Assembler::BelowOrEqual, r, Imm32('9'), &done);
1890 add32(Imm32((lowerCase ? 'a' : 'A') - '0' - 10), r);
1891 bind(&done);
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);
1899 toChar(scratch1);
1901 loadStringFromUnit(scratch1, dest, staticStrings);
1903 jump(&done);
1905 bind(&lengthTwo);
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.
1919 toChar(scratch1);
1920 toChar(scratch2);
1922 // Look up the 2-character digit string in the small-char table.
1923 loadLengthTwoString(scratch1, scratch2, dest, staticStrings);
1925 bind(&done);
1928 void MacroAssembler::loadInt32ToStringWithBase(
1929 Register input, int32_t base, Register dest, Register scratch1,
1930 Register scratch2, const StaticStrings& staticStrings, bool lowerCase,
1931 Label* fail) {
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) {
1936 #ifdef DEBUG
1937 Label ok;
1938 branch32(Assembler::Below, r, Imm32(base), &ok);
1939 assumeUnreachable("bad digit");
1940 bind(&ok);
1941 #endif
1943 if (base <= 10) {
1944 add32(Imm32('0'), r);
1945 } else {
1946 Label done;
1947 add32(Imm32('0'), r);
1948 branch32(Assembler::BelowOrEqual, r, Imm32('9'), &done);
1949 add32(Imm32((lowerCase ? 'a' : 'A') - '0' - 10), r);
1950 bind(&done);
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);
1959 toChar(scratch1);
1961 loadStringFromUnit(scratch1, dest, staticStrings);
1963 jump(&done);
1965 bind(&lengthTwo);
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);
1979 } else {
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);
2007 } else {
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.
2019 toChar(scratch1);
2020 toChar(scratch2);
2022 // Look up the 2-character digit string in the small-char table.
2023 loadLengthTwoString(scratch1, scratch2, dest, staticStrings);
2025 bind(&done);
2028 void MacroAssembler::loadBigIntDigits(Register bigInt, Register digits) {
2029 MOZ_ASSERT(digits != bigInt);
2031 // Load the inline digits.
2032 computeEffectiveAddress(Address(bigInt, BigInt::offsetOfInlineDigits()),
2033 digits);
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);
2053 jump(&done);
2055 bind(&nonZero);
2057 #ifdef JS_PUNBOX64
2058 Register digits = dest.reg;
2059 #else
2060 Register digits = dest.high;
2061 #endif
2063 loadBigIntDigits(bigInt, digits);
2065 #if JS_PUNBOX64
2066 // Load the first digit into the destination register.
2067 load64(Address(digits, 0), dest);
2068 #else
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);
2078 jump(&digitsDone);
2081 bind(&twoDigits);
2082 load32(Address(digits, sizeof(BigInt::Digit)), dest.high);
2084 bind(&digitsDone);
2085 #endif
2087 branchTest32(Assembler::Zero, Address(bigInt, BigInt::offsetOfFlags()),
2088 Imm32(BigInt::signBitMask()), &done);
2089 neg64(dest);
2091 bind(&done);
2094 void MacroAssembler::loadBigIntDigit(Register bigInt, Register dest) {
2095 Label done, nonZero;
2096 branchIfBigIntIsNonZero(bigInt, &nonZero);
2098 movePtr(ImmWord(0), dest);
2099 jump(&done);
2101 bind(&nonZero);
2103 loadBigIntDigits(bigInt, dest);
2105 // Load the first digit into the destination register.
2106 loadPtr(Address(dest, 0), dest);
2108 bind(&done);
2111 void MacroAssembler::loadBigIntDigit(Register bigInt, Register dest,
2112 Label* fail) {
2113 MOZ_ASSERT(bigInt != dest);
2115 branch32(Assembler::Above, Address(bigInt, BigInt::offsetOfLength()),
2116 Imm32(1), fail);
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,
2128 Label* fail) {
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.
2138 negPtr(dest);
2140 // Test after negating to handle INTPTR_MIN correctly.
2141 branchTestPtr(Assembler::NotSigned, dest, dest, fail);
2142 jump(&done);
2144 bind(&nonNegative);
2145 branchTestPtr(Assembler::Signed, dest, dest, fail);
2146 bind(&done);
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()));
2159 jump(&done);
2161 bind(&nonZero);
2163 if (type == Scalar::BigInt64) {
2164 // Copy the input when we're not allowed to clobber it.
2165 if (temp != Register64::Invalid()) {
2166 move64(val, temp);
2167 val = temp;
2170 // Set the sign-bit for negative values and then continue with the two's
2171 // complement.
2172 Label isPositive;
2173 branch64(Assembler::GreaterThan, val, Imm64(0), &isPositive);
2175 store32(Imm32(BigInt::signBitMask()),
2176 Address(bigInt, BigInt::offsetOfFlags()));
2177 neg64(val);
2179 bind(&isPositive);
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");
2188 #ifndef JS_PUNBOX64
2189 Label singleDigit;
2190 branchTest32(Assembler::Zero, val.high, val.high, &singleDigit);
2191 store32(Imm32(2), Address(bigInt, BigInt::offsetOfLength()));
2192 bind(&singleDigit);
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");
2198 #endif
2200 store64(val, Address(bigInt, js::BigInt::offsetOfInlineDigits()));
2202 bind(&done);
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()));
2212 jump(&done);
2214 bind(&nonZero);
2216 // Set the sign-bit for negative values and then continue with the two's
2217 // complement.
2218 Label isPositive;
2219 branchTestPtr(Assembler::NotSigned, val, val, &isPositive);
2221 store32(Imm32(BigInt::signBitMask()),
2222 Address(bigInt, BigInt::offsetOfFlags()));
2223 negPtr(val);
2225 bind(&isPositive);
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()));
2234 bind(&done);
2237 void MacroAssembler::copyBigIntWithInlineDigits(Register src, Register dest,
2238 Register temp,
2239 gc::Heap initialHeap,
2240 Label* fail) {
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()));
2251 // Copy the length.
2252 load32(Address(src, BigInt::offsetOfLength()), temp);
2253 store32(temp, Address(dest, BigInt::offsetOfLength()));
2255 // Copy the digits.
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,
2274 Label* ifFalse) {
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),
2290 tooLarge);
2291 } else {
2292 Label doCompare;
2293 branch32(Assembler::LessThanOrEqual,
2294 Address(bigInt, BigInt::offsetOfDigitLength()), Imm32(1),
2295 &doCompare);
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);
2300 jump(ifFalse);
2301 } else {
2302 branchIfBigIntIsNegative(bigInt, ifFalse);
2303 jump(ifTrue);
2306 bind(&doCompare);
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
2315 // operator.
2316 Label* greaterThan;
2317 Label* lessThan;
2318 if (op == JSOp::Eq) {
2319 greaterThan = ifFalse;
2320 lessThan = ifFalse;
2321 } else if (op == JSOp::Ne) {
2322 greaterThan = ifTrue;
2323 lessThan = ifTrue;
2324 } else if (op == JSOp::Lt || op == JSOp::Le) {
2325 greaterThan = ifFalse;
2326 lessThan = ifTrue;
2327 } else {
2328 MOZ_ASSERT(op == JSOp::Gt || op == JSOp::Ge);
2329 greaterThan = ifTrue;
2330 lessThan = ifFalse;
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);
2342 jump(&doCompare);
2344 // We rely on |neg32(INT32_MIN)| staying INT32_MIN, because we're using an
2345 // unsigned comparison below.
2346 bind(&isNegative);
2347 branch32(Assembler::GreaterThanOrEqual, int32, Imm32(0), lessThan);
2348 neg32(scratch2);
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,
2362 scratch2, ifTrue);
2363 jump(ifFalse);
2366 bind(&doCompare);
2367 branchPtr(JSOpToCondition(op, /* isSigned = */ false), scratch1, scratch2,
2368 ifTrue);
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) {
2384 switch (op) {
2385 case JSOp::Eq:
2386 branchIfBigIntIsZero(bigInt, ifTrue);
2387 break;
2388 case JSOp::Ne:
2389 branchIfBigIntIsNonZero(bigInt, ifTrue);
2390 break;
2391 case JSOp::Lt:
2392 branchIfBigIntIsNegative(bigInt, ifTrue);
2393 break;
2394 case JSOp::Le:
2395 branchIfBigIntIsZero(bigInt, ifTrue);
2396 branchIfBigIntIsNegative(bigInt, ifTrue);
2397 break;
2398 case JSOp::Gt:
2399 branchIfBigIntIsZero(bigInt, ifFalse);
2400 branchIfBigIntIsNonNegative(bigInt, ifTrue);
2401 break;
2402 case JSOp::Ge:
2403 branchIfBigIntIsNonNegative(bigInt, ifTrue);
2404 break;
2405 default:
2406 MOZ_CRASH("bad comparison operator");
2409 // Fall through to the false case.
2410 return;
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
2415 // operator.
2416 Label* greaterThan;
2417 Label* lessThan;
2418 if (op == JSOp::Eq) {
2419 greaterThan = ifFalse;
2420 lessThan = ifFalse;
2421 } else if (op == JSOp::Ne) {
2422 greaterThan = ifTrue;
2423 lessThan = ifTrue;
2424 } else if (op == JSOp::Lt || op == JSOp::Le) {
2425 greaterThan = ifFalse;
2426 lessThan = ifTrue;
2427 } else {
2428 MOZ_ASSERT(op == JSOp::Gt || op == JSOp::Ge);
2429 greaterThan = ifTrue;
2430 lessThan = ifFalse;
2433 // Test for mismatched signs.
2434 if (int32.value > 0) {
2435 branchIfBigIntIsNegative(bigInt, lessThan);
2436 } else {
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,
2461 ifTrue);
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()),
2476 notSameSign);
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,
2481 notSameLength);
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);
2496 Label start, loop;
2497 jump(&start);
2498 bind(&loop);
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);
2506 bind(&start);
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),
2526 isUndefined);
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);
2536 jump(isCallable);
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, &notFunction);
2552 if (isCallable) {
2553 move32(Imm32(1), output);
2554 } else {
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))),
2560 output);
2561 and32(Imm32(1), output);
2563 jump(&done);
2565 bind(&notFunction);
2567 if (!isCallable) {
2568 // For bound functions, we need to check the isConstructor flag.
2569 Label notBoundFunction;
2570 branchPtr(Assembler::NotEqual, output, ImmPtr(&BoundFunctionObject::class_),
2571 &notBoundFunction);
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);
2577 jump(&done);
2579 bind(&notBoundFunction);
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);
2589 jump(&done);
2591 bind(&hasCOps);
2592 loadPtr(Address(output, offsetof(JSClass, cOps)), output);
2593 size_t opsOffset =
2594 isCallable ? offsetof(JSClassOps, call) : offsetof(JSClassOps, construct);
2595 cmpPtrSet(Assembler::NonZero, Address(output, opsOffset), ImmPtr(nullptr),
2596 output);
2598 bind(&done);
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)),
2624 dest);
2627 void MacroAssembler::switchToRealm(const void* realm, Register scratch) {
2628 MOZ_ASSERT(realm);
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,
2666 Register scratch) {
2667 #ifdef DEBUG
2668 Label ok;
2669 movePtr(ImmPtr(realm), scratch);
2670 branchPtr(Assembler::Equal, AbsoluteAddress(ContextRealmPtr(runtime())),
2671 scratch, &ok);
2672 assumeUnreachable("Unexpected context realm");
2673 bind(&ok);
2674 #endif
2677 void MacroAssembler::setIsCrossRealmArrayConstructor(Register obj,
2678 Register output) {
2679 #ifdef DEBUG
2680 Label notProxy;
2681 branchTestObjectIsProxy(false, obj, output, &notProxy);
2682 assumeUnreachable("Unexpected proxy in setIsCrossRealmArrayConstructor");
2683 bind(&notProxy);
2684 #endif
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())),
2692 output, &isFalse);
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);
2703 jump(&done);
2705 bind(&isFalse);
2706 move32(Imm32(0), output);
2708 bind(&done);
2711 void MacroAssembler::setIsDefinitelyTypedArrayConstructor(Register obj,
2712 Register output) {
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.
2734 bind(&isFalse);
2735 move32(Imm32(0), output);
2736 jump(&done);
2738 bind(&isTrue);
2739 move32(Imm32(1), output);
2741 bind(&done);
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), &notAtomRef);
2757 loadPtr(Address(str, JSAtomRefString::offsetOfAtom()), output);
2758 jump(&done);
2759 bind(&notAtomRef);
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),
2770 str, fail);
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
2775 bind(&found);
2776 size_t atomOffset = StringToAtomCache::LastLookup::offsetOfAtom();
2777 loadPtr(Address(scratch, atomOffset), output);
2778 bind(&done);
2781 void MacroAssembler::loadAtomHash(Register id, Register outHash, Label* done) {
2782 Label doneInner, fatInline;
2783 if (!done) {
2784 done = &doneInner;
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),
2790 &fatInline);
2791 load32(Address(id, NormalAtom::offsetOfHash()), outHash);
2792 jump(done);
2793 bind(&fatInline);
2794 load32(Address(id, FatInlineAtom::offsetOfHash()), outHash);
2795 jump(done);
2796 bind(&doneInner);
2799 void MacroAssembler::loadAtomOrSymbolAndHash(ValueOperand value, Register outId,
2800 Register outHash,
2801 Label* cacheMiss) {
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);
2816 jump(&done);
2818 bind(&isNull);
2819 movePropertyKey(NameToId(names.null), outId);
2820 move32(Imm32(names.null->hash()), outHash);
2821 jump(&done);
2823 bind(&isSymbol);
2824 unboxSymbol(value, outId);
2825 load32(Address(outId, JS::Symbol::offsetOfHash()), outHash);
2826 orPtr(Imm32(PropertyKey::SymbolTypeTag), outId);
2827 jump(&done);
2829 bind(&isString);
2830 unboxString(value, outId);
2831 branchTest32(Assembler::Zero, Address(outId, JSString::offsetOfFlags()),
2832 Imm32(JSString::ATOM_BIT), &nonAtom);
2834 bind(&atom);
2835 loadAtomHash(outId, outHash, &done);
2837 bind(&nonAtom);
2838 tryFastAtomize(outId, outHash, outId, cacheMiss);
2839 jump(&atom);
2841 bind(&done);
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_
2850 load8ZeroExtend(
2851 Address(entry, MegamorphicCache::Entry::offsetOfHopsAndKind()), scratch2);
2852 // if (scratch2 == NumHopsForMissingProperty) goto isMissing
2853 branch32(Assembler::Equal, scratch2,
2854 Imm32(MegamorphicCache::Entry::NumHopsForMissingProperty),
2855 &isMissing);
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);
2888 jump(cacheHit);
2890 bind(&dynamicSlot);
2891 // output = outputScratch->slots_[scratch2]
2892 loadPtr(Address(outputScratch, NativeObject::offsetOfSlots()), outputScratch);
2893 loadValue(BaseIndex(outputScratch, scratch2, TimesOne), output);
2894 jump(cacheHit);
2896 bind(&isMissing);
2897 // output = undefined
2898 moveValue(UndefinedValue(), output);
2899 jump(cacheHit);
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);
2924 } else {
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()),
2945 outEntryPtr);
2946 } else {
2947 computeEffectiveAddress(BaseIndex(outEntryPtr, outEntryPtr, TimesTwo),
2948 outEntryPtr);
2949 computeEffectiveAddress(BaseIndex(scratch2, outEntryPtr, TimesEight,
2950 MegamorphicCache::offsetOfEntries()),
2951 outEntryPtr);
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()),
2967 scratch2);
2968 load16ZeroExtend(
2969 Address(outEntryPtr, MegamorphicCache::Entry::offsetOfGeneration()),
2970 scratch1);
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()),
3006 outEntryPtr);
3007 } else {
3008 computeEffectiveAddress(BaseIndex(outEntryPtr, outEntryPtr, TimesTwo),
3009 outEntryPtr);
3010 computeEffectiveAddress(BaseIndex(scratch2, outEntryPtr, TimesEight,
3011 MegamorphicCache::offsetOfEntries()),
3012 outEntryPtr);
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()),
3028 scratch2);
3029 load16ZeroExtend(
3030 Address(outEntryPtr, MegamorphicCache::Entry::offsetOfGeneration()),
3031 scratch1);
3032 // if (outEntryPtr->generation_ != scratch2) goto cacheMiss
3033 branch32(Assembler::NotEqual, scratch1, scratch2, &cacheMiss);
3035 emitExtractValueFromMegamorphicCacheEntry(
3036 obj, outEntryPtr, scratch1, scratch2, output, cacheHit, &cacheMiss);
3038 bind(&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);
3052 bind(&cacheMiss);
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_
3074 load8ZeroExtend(
3075 Address(outEntryPtr, MegamorphicCache::Entry::offsetOfHopsAndKind()),
3076 scratch1);
3078 branch32(Assembler::Equal, scratch1,
3079 Imm32(MegamorphicCache::Entry::NumHopsForMissingProperty),
3080 &cacheHitFalse);
3081 branchTest32(Assembler::NonZero, scratch1,
3082 Imm32(MegamorphicCache::Entry::NonDataPropertyFlag),
3083 &cacheMissWithEntry);
3085 if (hasOwn) {
3086 branch32(Assembler::NotEqual, scratch1, Imm32(0), &cacheHitFalse);
3089 move32(Imm32(1), output);
3090 jump(cacheHit);
3092 bind(&cacheHitFalse);
3093 xor32(output, output);
3094 jump(cacheHit);
3096 bind(&cacheMiss);
3097 xorPtr(outEntryPtr, outEntryPtr);
3098 bind(&cacheMissWithEntry);
3101 void MacroAssembler::extractCurrentIndexAndKindFromIterator(Register iterator,
3102 Register outIndex,
3103 Register outKind) {
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))),
3127 outIndex);
3129 // Extract kind.
3130 move32(outIndex, outKind);
3131 rshift32(Imm32(PropertyIndex::KindShift), outKind);
3133 // Extract index.
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,
3142 #endif
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
3148 pushValue(value);
3149 Register scratch2 = value.typeReg();
3150 Register scratch3 = value.payloadReg();
3151 #endif
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);
3166 } else {
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()),
3184 scratch3);
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_
3198 load16ZeroExtend(
3199 Address(scratch2, MegamorphicSetPropCache::offsetOfGeneration()),
3200 scratch2);
3201 load16ZeroExtend(
3202 Address(scratch3, MegamorphicSetPropCache::Entry::offsetOfGeneration()),
3203 scratch1);
3204 // if (scratch3->generation_ != scratch2) goto cacheMiss
3205 branch32(Assembler::NotEqual, scratch1, scratch2, &cacheMiss);
3207 // scratch2 = entry->slotOffset()
3208 load32(
3209 Address(scratch3, MegamorphicSetPropCache::Entry::offsetOfSlotOffset()),
3210 scratch2);
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);
3227 jump(&doAdd);
3229 bind(&dynamicSlot);
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.
3236 load16ZeroExtend(
3237 Address(scratch3, MegamorphicSetPropCache::Entry::offsetOfNewCapacity()),
3238 scratch2);
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);
3251 passABIArg(obj);
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);
3264 bind(&doAdd);
3265 // scratch3 = entry->afterShape()
3266 loadPtr(
3267 Address(scratch3, MegamorphicSetPropCache::Entry::offsetOfAfterShape()),
3268 scratch3);
3270 storeObjShape(scratch3, obj,
3271 [emitPreBarrier](MacroAssembler& masm, const Address& addr) {
3272 emitPreBarrier(masm, addr, MIRType::Shape);
3274 #ifdef JS_CODEGEN_X86
3275 popValue(value);
3276 #endif
3277 storeValue(value, slotAddr);
3278 jump(cacheHit);
3280 bind(&doSetDynamic);
3281 addPtr(Address(obj, NativeObject::offsetOfSlots()), scratch1);
3282 bind(&doSet);
3283 guardedCallPreBarrier(slotAddr, MIRType::Value);
3285 #ifdef JS_CODEGEN_X86
3286 popValue(value);
3287 #endif
3288 storeValue(value, slotAddr);
3289 jump(cacheHit);
3291 bind(&cacheMiss);
3292 #ifdef JS_CODEGEN_X86
3293 popValue(value);
3294 #endif
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,
3301 #endif
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,
3309 #endif
3310 ValueOperand value, const LiveRegisterSet& liveRegs, Label* cacheHit,
3311 void (*emitPreBarrier)(MacroAssembler&, const Address&, MIRType));
3313 void MacroAssembler::guardNonNegativeIntPtrToInt32(Register reg, Label* fail) {
3314 #ifdef DEBUG
3315 Label ok;
3316 branchPtr(Assembler::NotSigned, reg, reg, &ok);
3317 assumeUnreachable("Unexpected negative value");
3318 bind(&ok);
3319 #endif
3321 #ifdef JS_64BIT
3322 branchPtr(Assembler::Above, reg, Imm32(INT32_MAX), fail);
3323 #endif
3326 void MacroAssembler::loadArrayBufferByteLengthIntPtr(Register obj,
3327 Register output) {
3328 Address slotAddr(obj, ArrayBufferObject::offsetOfByteLengthSlot());
3329 loadPrivate(slotAddr, output);
3332 void MacroAssembler::loadArrayBufferViewByteOffsetIntPtr(Register obj,
3333 Register output) {
3334 Address slotAddr(obj, ArrayBufferViewObject::byteOffsetOffset());
3335 loadPrivate(slotAddr, output);
3338 void MacroAssembler::loadArrayBufferViewLengthIntPtr(Register obj,
3339 Register output) {
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);
3366 Label done;
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()),
3380 scratch);
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);
3405 bind(&done);
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.
3416 Label done;
3417 branchPtr(Assembler::NotEqual, output, ImmWord(0), &done);
3419 // We're done when the initial byteOffset is zero.
3420 loadPrivate(Address(obj, ArrayBufferViewObject::initialByteOffsetOffset()),
3421 output);
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);
3430 bind(&done);
3433 void MacroAssembler::dateFillLocalTimeSlots(
3434 Register obj, Register scratch, const LiveRegisterSet& volatileRegs) {
3435 // Inline implementation of the cache check from
3436 // DateObject::fillLocalTimeSlots().
3438 Label callVM, done;
3440 // Check if the cache is already populated.
3441 branchTestUndefined(Assembler::Equal,
3442 Address(obj, DateObject::offsetOfLocalTimeSlot()),
3443 &callVM);
3445 unboxInt32(Address(obj, DateObject::offsetOfUTCTimeZoneOffsetSlot()),
3446 scratch);
3448 branch32(Assembler::Equal,
3449 AbsoluteAddress(DateTimeInfo::addressOfUTCToLocalOffsetSeconds()),
3450 scratch, &done);
3452 bind(&callVM);
3454 PushRegsInMask(volatileRegs);
3456 using Fn = void (*)(DateObject*);
3457 setupUnalignedABICall(scratch);
3458 passABIArg(obj);
3459 callWithABI<Fn, jit::DateFillLocalTimeSlots>();
3461 PopRegsInMask(volatileRegs);
3464 bind(&done);
3467 void MacroAssembler::udiv32ByConstant(Register src, uint32_t divisor,
3468 Register dest) {
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");
3486 if (src != dest) {
3487 move32(src, dest);
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,
3504 Register scratch1,
3505 Register scratch2,
3506 GetTimeFn getTimeFn) {
3507 #ifdef DEBUG
3508 Label okValue;
3509 branchTestInt32(Assembler::Equal, secondsIntoYear, &okValue);
3510 branchTestValue(Assembler::Equal, secondsIntoYear, JS::NaNValue(), &okValue);
3511 assumeUnreachable("secondsIntoYear is an int32 or NaN");
3512 bind(&okValue);
3513 #endif
3515 moveValue(secondsIntoYear, output);
3517 Label done;
3518 fallibleUnboxInt32(secondsIntoYear, scratch1, &done);
3520 #ifdef DEBUG
3521 Label okInt;
3522 branchTest32(Assembler::NotSigned, scratch1, scratch1, &okInt);
3523 assumeUnreachable("secondsIntoYear is an unsigned int32");
3524 bind(&okInt);
3525 #endif
3527 getTimeFn(scratch1, scratch1, scratch2);
3529 tagValue(JSVAL_TYPE_INT32, scratch1, output);
3531 bind(&done);
3534 void MacroAssembler::dateHoursFromSecondsIntoYear(ValueOperand secondsIntoYear,
3535 ValueOperand output,
3536 Register scratch1,
3537 Register scratch2) {
3538 // Inline implementation of seconds-into-year to local hours computation from
3539 // date_getHours.
3541 // Compute `(yearSeconds / SecondsPerHour) % HoursPerDay`.
3542 auto hoursFromSecondsIntoYear = [this](Register src, Register dest,
3543 Register scratch) {
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,
3560 Register scratch) {
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,
3577 Register scratch) {
3578 umod32ByConstant(src, SecondsPerMinute, dest, scratch);
3581 dateTimeFromSecondsIntoYear(secondsIntoYear, output, scratch1, scratch2,
3582 secondsFromSecondsIntoYear);
3585 void MacroAssembler::computeImplicitThis(Register env, ValueOperand output,
3586 Label* slowPath) {
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);
3608 jump(&done);
3610 bind(&nonWithEnv);
3612 // The implicit |this| is |undefined| for all environment types except
3613 // WithEnvironmentObject.
3614 moveValue(JS::UndefinedValue(), output);
3616 bind(&done);
3619 void MacroAssembler::loadDOMExpandoValueGuardGeneration(
3620 Register obj, ValueOperand output,
3621 JS::ExpandoAndGeneration* expandoAndGeneration, uint64_t generation,
3622 Label* fail) {
3623 loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()),
3624 output.scratchReg());
3625 loadValue(Address(output.scratchReg(),
3626 js::detail::ProxyReservedSlots::offsetOfPrivateSlot()),
3627 output);
3629 // Guard the ExpandoAndGeneration* matches the proxy's ExpandoAndGeneration
3630 // privateSlot.
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()),
3642 output);
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,
3651 Register scratch,
3652 const LiveRegisterSet& volatileRegs,
3653 Label* fail) {
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, &notCachedAtom);
3664 branchPtr(Assembler::Equal, scratch, ImmGCPtr(atom), &done);
3665 jump(fail);
3666 bind(&notCachedAtom);
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.
3683 Label vmCall;
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.
3693 jump(&done);
3695 bind(&vmCall);
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);
3706 passABIArg(str);
3707 callWithABI<Fn, EqualStringsHelperPure>();
3708 storeCallPointerResult(scratch);
3710 MOZ_ASSERT(!volatileRegs.has(scratch));
3711 PopRegsInMask(volatileRegs);
3712 branchIfFalseBool(scratch, fail);
3714 bind(&done);
3717 void MacroAssembler::guardStringToInt32(Register str, Register output,
3718 Register scratch,
3719 LiveRegisterSet volatileRegs,
3720 Label* fail) {
3721 Label vmCall, done;
3722 // Use indexed value as fast path if possible.
3723 loadStringIndexValue(str, output, &vmCall);
3724 jump(&done);
3726 bind(&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);
3743 passABIArg(str);
3744 passABIArg(output);
3745 callWithABI<Fn, GetInt32FromStringPure>();
3746 storeCallPointerResult(scratch);
3748 PopRegsInMask(volatileRegs);
3750 Label ok;
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
3757 // tracking.
3758 addToStackPtr(Imm32(sizeof(uintptr_t)));
3759 jump(fail);
3761 bind(&ok);
3762 load32(Address(output, 0), output);
3763 freeStack(sizeof(uintptr_t));
3765 bind(&done);
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();
3783 #ifdef DEBUG
3784 // Assert the stack pointer points to the JitFrameLayout header. Copying
3785 // starts here.
3786 Label ok;
3787 loadPtr(Address(bailoutInfo, offsetof(BaselineBailoutInfo, incomingStack)),
3788 temp);
3789 branchStackPtr(Assembler::Equal, temp, &ok);
3790 assumeUnreachable("Unexpected stack pointer value");
3791 bind(&ok);
3792 #endif
3794 Register copyCur = regs.takeAny();
3795 Register copyEnd = regs.takeAny();
3797 // Copy data onto stack.
3798 loadPtr(Address(bailoutInfo, offsetof(BaselineBailoutInfo, copyStackTop)),
3799 copyCur);
3800 loadPtr(
3801 Address(bailoutInfo, offsetof(BaselineBailoutInfo, copyStackBottom)),
3802 copyEnd);
3804 Label copyLoop;
3805 Label endOfCopy;
3806 bind(&copyLoop);
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));
3812 jump(&copyLoop);
3813 bind(&endOfCopy);
3816 loadPtr(Address(bailoutInfo, offsetof(BaselineBailoutInfo, resumeFramePtr)),
3817 FramePointer);
3819 // Enter exit frame for the FinishBailoutToBaseline call.
3820 pushFrameDescriptor(FrameType::BaselineJS);
3821 push(Address(bailoutInfo, offsetof(BaselineBailoutInfo, resumeAddr)));
3822 push(FramePointer);
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();
3843 pop(jitcodeReg);
3845 // Discard exit frame.
3846 addToStackPtr(Imm32(ExitFrameLayout::SizeWithFooter()));
3848 jump(jitcodeReg);
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 "
3867 "jitCodeRaw_");
3868 static_assert(
3869 BaseScript::offsetOfJitCodeRaw() == wasm::JumpTableJitEntryOffset,
3870 "Wasm exported functions jit entries must use same layout for "
3871 "jitCodeRaw_");
3872 loadPrivate(Address(func, JSFunction::offsetOfJitInfoOrScript()), dest);
3873 loadPtr(Address(dest, BaseScript::offsetOfJitCodeRaw()), dest);
3876 void MacroAssembler::loadBaselineJitCodeRaw(Register func, Register dest,
3877 Label* failure) {
3878 // Load JitScript
3879 loadPrivate(Address(func, JSFunction::offsetOfJitInfoOrScript()), dest);
3880 if (failure) {
3881 branchIfScriptHasNoJitScript(dest, failure);
3883 loadJitScript(dest, dest);
3885 // Load BaselineScript
3886 loadPtr(Address(dest, JitScript::offsetOfBaselineScript()), dest);
3887 if (failure) {
3888 static_assert(BaselineDisabledScript == 0x1);
3889 branchPtr(Assembler::BelowOrEqual, dest, ImmWord(BaselineDisabledScript),
3890 failure);
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();
3918 jump(excTail);
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);
3932 passABIArg(temp);
3933 callWithABI<Fn, AssumeUnreachable>(ABIType::General,
3934 CheckUnsafeCallWithABI::DontCheckOther);
3936 PopRegsInMask(save);
3938 #endif
3940 breakpoint();
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);
3954 passABIArg(temp);
3955 callWithABI<Fn, Printf0>();
3957 PopRegsInMask(save);
3958 #endif
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);
3974 passABIArg(temp);
3975 passABIArg(value);
3976 callWithABI<Fn, Printf1>();
3978 PopRegsInMask(save);
3979 #endif
3982 void MacroAssembler::convertInt32ValueToDouble(ValueOperand val) {
3983 Label done;
3984 branchTestInt32(Assembler::NotEqual, val, &done);
3985 ScratchDoubleScope fpscratch(*this);
3986 convertInt32ToDouble(val.payloadOrValueReg(), fpscratch);
3987 boxDouble(fpscratch, val, fpscratch);
3988 bind(&done);
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);
4012 } else {
4013 loadConstantDouble(GenericNaN(), output);
4015 jump(&done);
4017 bind(&isNull);
4018 if (outputType == FloatingPointType::Float16 ||
4019 outputType == FloatingPointType::Float32) {
4020 loadConstantFloat32(0.0f, output);
4021 } else {
4022 loadConstantDouble(0.0, output);
4024 jump(&done);
4026 bind(&isInt32OrBool);
4027 if (outputType == FloatingPointType::Float16) {
4028 convertInt32ToFloat16(value.payloadOrValueReg(), output, maybeTemp,
4029 volatileLiveRegs);
4030 } else if (outputType == FloatingPointType::Float32) {
4031 convertInt32ToFloat32(value.payloadOrValueReg(), output);
4032 } else {
4033 convertInt32ToDouble(value.payloadOrValueReg(), output);
4035 jump(&done);
4037 // On some non-multiAlias platforms, unboxDouble may use the scratch register,
4038 // so do not merge code paths here.
4039 bind(&isDouble);
4040 if ((outputType == FloatingPointType::Float16 ||
4041 outputType == FloatingPointType::Float32) &&
4042 hasMultiAlias()) {
4043 ScratchDoubleScope tmp(*this);
4044 unboxDouble(value, tmp);
4046 if (outputType == FloatingPointType::Float16) {
4047 convertDoubleToFloat16(tmp, output, maybeTemp, volatileLiveRegs);
4048 } else {
4049 convertDoubleToFloat32(tmp, output);
4051 } else {
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);
4062 bind(&done);
4065 void MacroAssembler::outOfLineTruncateSlow(FloatRegister src, Register dest,
4066 bool widenFloatToDouble,
4067 bool compilingWasm,
4068 wasm::BytecodeOffset callOffset) {
4069 if (compilingWasm) {
4070 Push(InstanceReg);
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);
4080 src = fpscratch;
4082 #elif defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
4083 FloatRegister srcSingle;
4084 if (widenFloatToDouble) {
4085 MOZ_ASSERT(src.isSingle());
4086 srcSingle = src;
4087 src = src.asDouble();
4088 Push(srcSingle);
4089 convertFloat32ToDouble(srcSingle, src);
4091 #else
4092 // Also see below
4093 MOZ_CRASH("MacroAssembler platform hook: outOfLineTruncateSlow");
4094 #endif
4096 MOZ_ASSERT(src.isDouble());
4098 if (compilingWasm) {
4099 int32_t instanceOffset = framePushed() - framePushedAfterInstance;
4100 setupWasmABICall();
4101 passABIArg(src, ABIType::Float64);
4102 callWithABI(callOffset, wasm::SymbolicAddress::ToInt32,
4103 mozilla::Some(instanceOffset));
4104 } else {
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)
4116 // Nothing
4117 #elif defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
4118 if (widenFloatToDouble) {
4119 Pop(srcSingle);
4121 #else
4122 MOZ_CRASH("MacroAssembler platform hook: outOfLineTruncateSlow");
4123 #endif
4125 if (compilingWasm) {
4126 Pop(InstanceReg);
4130 void MacroAssembler::convertDoubleToInt(FloatRegister src, Register output,
4131 FloatRegister temp, Label* truncateFail,
4132 Label* fail,
4133 IntConversionBehavior behavior) {
4134 switch (behavior) {
4135 case IntConversionBehavior::Normal:
4136 case IntConversionBehavior::NegativeZeroCheck:
4137 convertDoubleToInt32(
4138 src, output, fail,
4139 behavior == IntConversionBehavior::NegativeZeroCheck);
4140 break;
4141 case IntConversionBehavior::Truncate:
4142 branchTruncateDoubleMaybeModUint32(src, output,
4143 truncateFail ? truncateFail : fail);
4144 break;
4145 case IntConversionBehavior::ClampToUint8:
4146 // Clamping clobbers the input register, so use a temp.
4147 if (src != temp) {
4148 moveDouble(src, temp);
4150 clampDoubleToUint8(temp, output);
4151 break;
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.
4181 switch (behavior) {
4182 case IntConversionBehavior::Normal:
4183 case IntConversionBehavior::NegativeZeroCheck:
4184 branchTestNull(Assembler::NotEqual, tag, fail);
4185 break;
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);
4194 break;
4196 } else {
4197 jump(fail);
4201 // The value is null or undefined in truncation contexts - just emit 0.
4202 if (conversion == IntConversionInputKind::Any) {
4203 if (isNull.used()) {
4204 bind(&isNull);
4206 mov(ImmWord(0), output);
4207 jump(&done);
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) {
4217 bind(&isString);
4218 unboxString(value, stringReg);
4219 if (handleStringIndices) {
4220 loadStringIndexValue(stringReg, output, handleStringEntry);
4221 jump(&handleStringIndex);
4222 } else {
4223 jump(handleStringEntry);
4227 // Try converting double into integer.
4228 if (isDouble.used() || handleStrings) {
4229 if (isDouble.used()) {
4230 bind(&isDouble);
4231 unboxDouble(value, temp);
4234 if (handleStrings) {
4235 bind(handleStringRejoin);
4238 convertDoubleToInt(temp, output, temp, truncateDoubleSlow, fail, behavior);
4239 jump(&done);
4242 // Just unbox a bool, the result is 0 or 1.
4243 if (isBool.used()) {
4244 bind(&isBool);
4245 unboxBoolean(value, output);
4246 jump(&done);
4249 // Integers can be unboxed.
4250 if (isInt32.used() || handleStringIndices) {
4251 if (isInt32.used()) {
4252 bind(&isInt32);
4253 unboxInt32(value, output);
4256 if (handleStringIndices) {
4257 bind(&handleStringIndex);
4260 if (behavior == IntConversionBehavior::ClampToUint8) {
4261 clampIntToUint8(output);
4265 bind(&done);
4268 void MacroAssembler::finish() {
4269 if (failureLabel_.used()) {
4270 bind(&failureLabel_);
4271 handleFailure();
4274 MacroAssemblerSpecific::finish();
4276 MOZ_RELEASE_ASSERT(
4277 size() <= MaxCodeBytesPerProcess,
4278 "AssemblerBuffer should ensure we don't exceed MaxCodeBytesPerProcess");
4280 if (bytesNeeded() > MaxCodeBytesPerProcess) {
4281 setOOM();
4285 void MacroAssembler::link(JitCode* code) {
4286 MOZ_ASSERT(!oom());
4287 linkProfilerCallSites(code);
4290 MacroAssembler::AutoProfilerCallInstrumentation::
4291 AutoProfilerCallInstrumentation(MacroAssembler& masm) {
4292 if (!masm.emitProfilingInstrumentation_) {
4293 return;
4296 Register reg = CallTempReg0;
4297 Register reg2 = CallTempReg1;
4298 masm.push(reg);
4299 masm.push(reg2);
4301 CodeOffset label = masm.movWithPatch(ImmWord(uintptr_t(-1)), reg);
4302 masm.loadJSContext(reg2);
4303 masm.loadPtr(Address(reg2, offsetof(JSContext, profilingActivation_)), reg2);
4304 masm.storePtr(reg,
4305 Address(reg2, JitActivation::offsetOfLastProfilingCallSite()));
4307 masm.appendProfilerCallSite(label);
4309 masm.pop(reg2);
4310 masm.pop(reg);
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()),
4318 ImmPtr((void*)-1));
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) {
4330 return;
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
4340 // returned above.)
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)));
4361 jump(&end);
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)));
4372 bind(&end);
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) {
4383 return;
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)));
4391 } else {
4392 // |argN| must be 16-byte aligned if argc is even,
4393 // and offset by 8 if argc is odd.
4394 Label end;
4395 branchTestStackPtr(Assembler::NonZero, Imm32(JitStackAlignment - 1), &end);
4396 subFromStackPtr(Imm32(sizeof(Value)));
4397 bind(&end);
4398 assertStackAlignment(JitStackAlignment, sizeof(Value));
4402 // ===============================================================
4404 MacroAssembler::MacroAssembler(TempAllocator& alloc,
4405 CompileRuntime* maybeRuntime,
4406 CompileRealm* maybeRealm)
4407 : maybeRuntime_(maybeRuntime),
4408 maybeRealm_(maybeRealm),
4409 framePushed_(0),
4410 #ifdef DEBUG
4411 inCall_(false),
4412 #endif
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,
4432 // not the PSP.
4433 SetStackPointer64(sp);
4434 #endif
4435 if (!limitedSize) {
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
4473 // runtime.
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));
4481 } else {
4482 MOZ_ASSERT(key.isSymbol());
4483 movePropertyKey(key, scratchReg);
4484 Push(scratchReg);
4486 } else {
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);
4501 } else {
4502 MOZ_ASSERT(key.isSymbol());
4503 JS::Symbol* sym = key.toSymbol();
4504 movePtr(ImmGCPtr(sym), dest);
4505 orPtr(Imm32(PropertyKey::SymbolTypeTag), dest);
4507 } else {
4508 MOZ_ASSERT(key.isInt());
4509 movePtr(ImmWord(key.asRawBits()), dest);
4513 void MacroAssembler::Push(TypedOrValueRegister v) {
4514 if (v.hasValue()) {
4515 Push(v.valueReg());
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);
4522 } else {
4523 PushBoxed(reg);
4525 } else {
4526 Push(ValueTypeFromMIRType(v.type()), v.typedReg().gpr());
4530 void MacroAssembler::Push(const ConstantOrRegister& v) {
4531 if (v.constant()) {
4532 Push(v.value());
4533 } else {
4534 Push(v.reg());
4538 void MacroAssembler::Push(const Address& addr) {
4539 push(addr);
4540 framePushed_ += sizeof(uintptr_t);
4543 void MacroAssembler::Push(const ValueOperand& val) {
4544 pushValue(val);
4545 framePushed_ += sizeof(Value);
4548 void MacroAssembler::Push(const Value& val) {
4549 pushValue(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
4560 Push(reg.reg);
4561 #else
4562 MOZ_ASSERT(MOZ_LITTLE_ENDIAN(), "Big-endian not supported.");
4563 Push(reg.high);
4564 Push(reg.low);
4565 #endif
4568 void MacroAssembler::Pop(const Register64 reg) {
4569 #if JS_BITS_PER_WORD == 64
4570 Pop(reg.reg);
4571 #else
4572 MOZ_ASSERT(MOZ_LITTLE_ENDIAN(), "Big-endian not supported.");
4573 Pop(reg.low);
4574 Pop(reg.high);
4575 #endif
4578 void MacroAssembler::PushEmptyRooted(VMFunctionData::RootType rootType) {
4579 switch (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));
4587 break;
4588 case VMFunctionData::RootValue:
4589 Push(UndefinedValue());
4590 break;
4591 case VMFunctionData::RootId:
4592 Push(ImmWord(JS::PropertyKey::Void().asRawBits()));
4593 break;
4597 void MacroAssembler::adjustStack(int amount) {
4598 if (amount > 0) {
4599 freeStack(amount);
4600 } else if (amount < 0) {
4601 reserveStack(-amount);
4605 void MacroAssembler::freeStack(uint32_t amount) {
4606 MOZ_ASSERT(amount <= framePushed_);
4607 if (amount) {
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) {
4617 case Type_Handle:
4618 PushEmptyRooted(f.outParamRootType);
4619 break;
4621 case Type_Value:
4622 case Type_Double:
4623 case Type_Pointer:
4624 case Type_Int32:
4625 case Type_Bool:
4626 reserveStack(f.sizeOfOutParamStackSlot());
4627 break;
4629 case Type_Void:
4630 break;
4632 case Type_Cell:
4633 MOZ_CRASH("Unexpected outparam type");
4637 void MacroAssembler::loadVMFunctionOutParam(const VMFunctionData& f,
4638 const Address& addr) {
4639 switch (f.outParam) {
4640 case Type_Handle:
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);
4650 break;
4651 case VMFunctionData::RootValue:
4652 loadValue(addr, JSReturnOperand);
4653 break;
4655 break;
4657 case Type_Value:
4658 loadValue(addr, JSReturnOperand);
4659 break;
4661 case Type_Int32:
4662 load32(addr, ReturnReg);
4663 break;
4665 case Type_Bool:
4666 load8ZeroExtend(addr, ReturnReg);
4667 break;
4669 case Type_Double:
4670 loadDouble(addr, ReturnDoubleReg);
4671 break;
4673 case Type_Pointer:
4674 loadPtr(addr, ReturnReg);
4675 break;
4677 case Type_Void:
4678 break;
4680 case Type_Cell:
4681 MOZ_CRASH("Unexpected outparam type");
4685 // ===============================================================
4686 // ABI function calls.
4687 template <class ABIArgGeneratorT>
4688 void MacroAssembler::setupABICallHelper() {
4689 #ifdef DEBUG
4690 MOZ_ASSERT(!inCall_);
4691 inCall_ = true;
4692 #endif
4694 #ifdef JS_SIMULATOR
4695 signature_ = 0;
4696 #endif
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());
4704 #endif
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();
4712 #endif
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);
4727 #endif
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);
4747 ABIArg arg;
4748 MoveOp::Type moveType;
4749 switch (type) {
4750 case ABIType::Float32:
4751 arg = abiArgs_.next(MIRType::Float32);
4752 moveType = MoveOp::FLOAT32;
4753 break;
4754 case ABIType::Float64:
4755 arg = abiArgs_.next(MIRType::Double);
4756 moveType = MoveOp::DOUBLE;
4757 break;
4758 case ABIType::General:
4759 arg = abiArgs_.next(MIRType::Pointer);
4760 moveType = MoveOp::GENERAL;
4761 break;
4762 default:
4763 MOZ_CRASH("Unexpected argument type");
4766 MoveOperand to(*this, arg);
4767 if (from == to) {
4768 return;
4771 if (oom()) {
4772 return;
4774 propagateOOM(moveResolver_.addMove(from, to, moveType));
4777 void MacroAssembler::callWithABINoProfiler(void* fun, ABIType result,
4778 CheckUnsafeCallWithABI check) {
4779 appendSignatureType(result);
4780 #ifdef JS_SIMULATOR
4781 fun = Simulator::RedirectNativeFunction(fun, signature());
4782 #endif
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.
4790 push(ReturnReg);
4791 loadJSContext(ReturnReg);
4792 Address flagAddr(ReturnReg, JSContext::offsetOfInUnsafeCallWithABI());
4793 store32(Imm32(1), flagAddr);
4794 pop(ReturnReg);
4795 // On arm64, SP may be < PSP now (that's OK).
4796 // eg testcase: tests/bug1375074.js
4798 #endif
4800 call(ImmPtr(fun));
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.
4807 Label ok;
4808 push(ReturnReg);
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");
4813 bind(&ok);
4814 pop(ReturnReg);
4815 // On arm64, SP may be < PSP now (that's OK).
4816 // eg testcase: tests/bug1375074.js
4818 #endif
4821 CodeOffset MacroAssembler::callWithABI(wasm::BytecodeOffset bytecode,
4822 wasm::SymbolicAddress imm,
4823 mozilla::Maybe<int32_t> instanceOffset,
4824 ABIType result) {
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),
4833 InstanceReg);
4834 } else {
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);
4842 return raOffset;
4845 void MacroAssembler::callDebugWithABI(wasm::SymbolicAddress imm,
4846 ABIType result) {
4847 MOZ_ASSERT(!wasm::NeedsBuiltinThunk(imm));
4848 uint32_t stackAdjust;
4849 callWithABIPre(&stackAdjust, /* callFromWasm = */ false);
4850 call(imm);
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
4864 // in common cases.
4866 void MacroAssembler::moveRegPair(Register src0, Register src1, Register dst0,
4867 Register dst1, MoveOp::Type type) {
4868 MoveResolver& moves = moveResolver();
4869 if (src0 != dst0) {
4870 propagateOOM(moves.addMove(MoveOperand(src0), MoveOperand(dst0), type));
4872 if (src1 != dst1) {
4873 propagateOOM(moves.addMove(MoveOperand(src1), MoveOperand(dst1), type));
4875 propagateOOM(moves.resolve());
4876 if (oom()) {
4877 return;
4880 MoveEmitter emitter(*this);
4881 emitter.emit(moves);
4882 emitter.finish();
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
4891 // detection.
4893 move32(Imm32(1), dest); // result = 1
4895 // x^y where x == 1 returns 1 for any y.
4896 Label done;
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
4902 // here.
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
4910 Label start;
4911 jump(&start);
4913 Label loop;
4914 bind(&loop);
4916 // runningSquare *= runningSquare
4917 branchMul32(Assembler::Overflow, temp1, temp1, onOver);
4919 bind(&start);
4921 // if ((n & 1) != 0) result *= runningSquare
4922 Label even;
4923 branchTest32(Assembler::Zero, temp2, Imm32(1), &even);
4924 branchMul32(Assembler::Overflow, temp1, dest, onOver);
4925 bind(&even);
4927 // n >>= 1
4928 // if (n == 0) return result
4929 branchRshift32(Assembler::NonZero, Imm32(1), temp2, &loop);
4931 bind(&done);
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
4937 // detection.
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.
4945 Label done;
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), &notNegativeOne);
4951 test32MovePtr(Assembler::NonZero, power, Imm32(1), base, dest);
4952 jump(&done);
4953 bind(&notNegativeOne);
4955 // x ** y with |x| > 1 and y >= DigitBits can't be pointer-sized.
4956 branchPtr(Assembler::GreaterThanOrEqual, power, Imm32(BigInt::DigitBits),
4957 onOver);
4959 movePtr(base, temp1); // runningSquare = x
4960 movePtr(power, temp2); // n = y
4962 Label start;
4963 jump(&start);
4965 Label loop;
4966 bind(&loop);
4968 // runningSquare *= runningSquare
4969 branchMulPtr(Assembler::Overflow, temp1, temp1, onOver);
4971 bind(&start);
4973 // if ((n & 1) != 0) result *= runningSquare
4974 Label even;
4975 branchTest32(Assembler::Zero, temp2, Imm32(1), &even);
4976 branchMulPtr(Assembler::Overflow, temp1, dest, onOver);
4977 bind(&even);
4979 // n >>= 1
4980 // if (n == 0) return result
4981 branchRshift32(Assembler::NonZero, Imm32(1), temp2, &loop);
4983 bind(&done);
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);
5004 jump(&done);
5006 bind(&negative);
5007 loadConstantDouble(-1.0, output);
5008 jump(&done);
5010 bind(&zeroOrNaN);
5011 moveDouble(input, output);
5013 bind(&done);
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);
5026 jump(&done);
5028 bind(&negative);
5029 move32(Imm32(-1), output);
5030 jump(&done);
5032 // Fail for NaN and negative zero.
5033 bind(&zeroOrNaN);
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);
5043 bind(&done);
5046 void MacroAssembler::randomDouble(Register rng, FloatRegister dest,
5047 Register64 temp0, Register64 temp1) {
5048 using mozilla::non_crypto::XorShift128PlusRNG;
5050 static_assert(
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);
5063 // s1 ^= s1 << 23;
5064 move64(s1Reg, s0Reg);
5065 lshift64(Imm32(23), s1Reg);
5066 xor64(s0Reg, s1Reg);
5068 // s1 ^= s1 >> 17
5069 move64(s1Reg, s0Reg);
5070 rshift64(Imm32(17), s1Reg);
5071 xor64(s0Reg, s1Reg);
5073 // const uint64_t s0 = mState[1];
5074 load64(state1Addr, s0Reg);
5076 // mState[0] = s0;
5077 store64(s0Reg, state0Addr);
5079 // s1 ^= s0
5080 xor64(s0Reg, s1Reg);
5082 // s1 ^= s0 >> 26
5083 rshift64(Imm32(26), s0Reg);
5084 xor64(s0Reg, s1Reg);
5086 // mState[1] = s1
5087 store64(s1Reg, state1Addr);
5089 // s1 += mState[0]
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);
5104 // dest *= ScaleInv
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.
5119 Label isNegInf;
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);
5129 bind(&isNegInf);
5131 loadConstantDouble(1.0, temp);
5132 divDouble(right, temp);
5133 branchDouble(Assembler::DoubleLessThan, temp, right, &isSameValue);
5134 jump(&isNotSameValue);
5137 bind(&nonEqual);
5139 // Test if both values are NaN.
5140 branchDouble(Assembler::DoubleOrdered, left, left, &isNotSameValue);
5141 branchDouble(Assembler::DoubleOrdered, right, right, &isNotSameValue);
5144 Label done;
5145 bind(&isSameValue);
5146 move32(Imm32(1), dest);
5147 jump(&done);
5149 bind(&isNotSameValue);
5150 move32(Imm32(0), dest);
5152 bind(&done);
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);
5176 Label loop, done;
5177 bind(&loop);
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);
5191 jump(&loop);
5192 bind(&done);
5195 void MacroAssembler::minMaxArrayNumber(Register array, FloatRegister result,
5196 FloatRegister floatTemp, Register temp1,
5197 Register temp2, bool isMax,
5198 Label* fail) {
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.
5204 Label isEmpty;
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);
5218 Label loop, done;
5219 bind(&loop);
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.
5229 if (isMax) {
5230 maxDouble(floatTemp, result, /* handleNaN = */ true);
5231 } else {
5232 minDouble(floatTemp, result, /* handleNaN = */ true);
5234 jump(&loop);
5236 // With no arguments, min/max return +Infinity/-Infinity respectively.
5237 bind(&isEmpty);
5238 if (isMax) {
5239 loadConstantDouble(mozilla::NegativeInfinity<double>(), result);
5240 } else {
5241 loadConstantDouble(mozilla::PositiveInfinity<double>(), result);
5244 bind(&done);
5247 void MacroAssembler::branchIfNotRegExpPrototypeOptimizable(
5248 Register proto, Register temp, const GlobalObject* maybeGlobal,
5249 Label* fail) {
5250 if (maybeGlobal) {
5251 movePtr(ImmGCPtr(maybeGlobal), temp);
5252 loadPrivate(Address(temp, GlobalObject::offsetOfGlobalDataSlot()), temp);
5253 } else {
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,
5264 Label* label) {
5265 if (maybeGlobal) {
5266 movePtr(ImmGCPtr(maybeGlobal), temp);
5267 loadPrivate(Address(temp, GlobalObject::offsetOfGlobalDataSlot()), temp);
5268 } else {
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,
5278 Register lastIndex,
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 &notGlobalOrSticky);
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
5302 // integer.
5303 #ifdef DEBUG
5305 Label ok;
5306 branchTestInt32(Assembler::Equal, lastIndexSlot, &ok);
5307 assumeUnreachable("Expected int32 value for lastIndex");
5308 bind(&ok);
5310 #endif
5311 unboxInt32(lastIndexSlot, lastIndex);
5312 #ifdef DEBUG
5314 Label ok;
5315 branchTest32(Assembler::NotSigned, lastIndex, lastIndex, &ok);
5316 assumeUnreachable("Expected non-negative lastIndex");
5317 bind(&ok);
5319 #endif
5320 branch32(Assembler::Below, stringLength, lastIndex, notFoundZeroLastIndex);
5321 jump(&loadedLastIndex);
5324 bind(&notGlobalOrSticky);
5325 move32(Imm32(0), lastIndex);
5327 bind(&loadedLastIndex);
5330 void MacroAssembler::loadAndClearRegExpSearcherLastLimit(Register result,
5331 Register scratch) {
5332 MOZ_ASSERT(result != scratch);
5334 loadJSContext(scratch);
5336 Address limitField(scratch, JSContext::offsetOfRegExpSearcherLastLimit());
5337 load32(limitField, result);
5339 #ifdef DEBUG
5340 Label ok;
5341 branch32(Assembler::NotEqual, result, Imm32(RegExpSearcherLastLimitSentinel),
5342 &ok);
5343 assumeUnreachable("Unexpected sentinel for regExpSearcherLastLimit");
5344 bind(&ok);
5345 store32(Imm32(RegExpSearcherLastLimitSentinel), limitField);
5346 #endif
5349 void MacroAssembler::loadParsedRegExpShared(Register regexp, Register result,
5350 Label* unparsed) {
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 // ===============================================================
5361 // Branch functions
5363 void MacroAssembler::loadFunctionLength(Register func,
5364 Register funFlagsAndArgCount,
5365 Register output, Label* slowPath) {
5366 #ifdef DEBUG
5368 // These flags should already have been checked by caller.
5369 Label ok;
5370 uint32_t FlagsToCheck =
5371 FunctionFlags::SELFHOSTLAZY | FunctionFlags::RESOLVED_LENGTH;
5372 branchTest32(Assembler::Zero, funFlagsAndArgCount, Imm32(FlagsToCheck),
5373 &ok);
5374 assumeUnreachable("The function flags should already have been checked.");
5375 bind(&ok);
5377 #endif // DEBUG
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()),
5399 output);
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
5413 // unprefixed name.
5414 branchTest32(
5415 Assembler::NonZero, output,
5416 Imm32(FunctionFlags::RESOLVED_NAME | FunctionFlags::LAZY_ACCESSOR_NAME),
5417 slowPath);
5419 Label noName, done;
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);
5426 jump(&done);
5429 bind(&noName);
5431 // An absent name property defaults to the empty string.
5432 movePtr(emptyString, output);
5435 bind(&done);
5438 void MacroAssembler::assertFunctionIsExtended(Register func) {
5439 #ifdef DEBUG
5440 Label extended;
5441 branchTestFunctionFlags(func, FunctionFlags::EXTENDED, Assembler::NonZero,
5442 &extended);
5443 assumeUnreachable("Function is not extended");
5444 bind(&extended);
5445 #endif
5448 void MacroAssembler::branchTestType(Condition cond, Register tag,
5449 JSValueType type, Label* label) {
5450 switch (type) {
5451 case JSVAL_TYPE_DOUBLE:
5452 branchTestDouble(cond, tag, label);
5453 break;
5454 case JSVAL_TYPE_INT32:
5455 branchTestInt32(cond, tag, label);
5456 break;
5457 case JSVAL_TYPE_BOOLEAN:
5458 branchTestBoolean(cond, tag, label);
5459 break;
5460 case JSVAL_TYPE_UNDEFINED:
5461 branchTestUndefined(cond, tag, label);
5462 break;
5463 case JSVAL_TYPE_NULL:
5464 branchTestNull(cond, tag, label);
5465 break;
5466 case JSVAL_TYPE_MAGIC:
5467 branchTestMagic(cond, tag, label);
5468 break;
5469 case JSVAL_TYPE_STRING:
5470 branchTestString(cond, tag, label);
5471 break;
5472 case JSVAL_TYPE_SYMBOL:
5473 branchTestSymbol(cond, tag, label);
5474 break;
5475 case JSVAL_TYPE_BIGINT:
5476 branchTestBigInt(cond, tag, label);
5477 break;
5478 case JSVAL_TYPE_OBJECT:
5479 branchTestObject(cond, tag, label);
5480 break;
5481 default:
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;
5493 Label done;
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);
5510 Label loop;
5511 bind(&loop);
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) {
5530 jump(label);
5532 bind(&done);
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,
5558 Label* label) {
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,
5566 Label* label) {
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);
5581 Label done;
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)),
5593 doValidation);
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);
5602 bind(&done);
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,
5612 bytecodeOffset));
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.
5620 Label ok;
5621 Register scratch = ABINonArgReg0;
5622 moveStackPtrTo(scratch);
5624 Label trap;
5625 branchPtr(Assembler::Below, scratch, Imm32(amount), &trap);
5626 subPtr(Imm32(amount), scratch);
5627 branchPtr(Assembler::Below,
5628 Address(InstanceReg, wasm::Instance::offsetOfStackLimit()),
5629 scratch, &ok);
5631 bind(&trap);
5632 wasmTrap(wasm::Trap::StackOverflow, trapOffset);
5633 CodeOffset trapInsnOffset = CodeOffset(currentOffset());
5635 bind(&ok);
5636 reserveStack(amount);
5637 return std::pair<CodeOffset, uint32_t>(trapInsnOffset, 0);
5640 reserveStack(amount);
5641 Label ok;
5642 branchStackPtrRhs(Assembler::Below,
5643 Address(InstanceReg, wasm::Instance::offsetOfStackLimit()),
5644 &ok);
5645 wasmTrap(wasm::Trap::StackOverflow, trapOffset);
5646 CodeOffset trapInsnOffset = CodeOffset(currentOffset());
5647 bind(&ok);
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) {
5655 return; // noop
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;
5663 masm.push(scratch);
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;
5669 #else
5670 const Register scratch = InvalidReg;
5671 #endif
5673 if (to < from) {
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));
5678 } else {
5679 for (uint32_t i = size; i > 0;) {
5680 i -= sizeof(void*);
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)
5687 masm.pop(scratch);
5688 #endif
5691 struct ReturnCallTrampolineData {
5692 #ifdef JS_CODEGEN_ARM
5693 uint32_t trampolineOffset;
5694 #else
5695 CodeLabel trampoline;
5696 #endif
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);
5709 #endif
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();
5715 #else
5716 masm.bind(&data.trampoline);
5717 #endif
5719 masm.setFramePushed(AlignBytes(
5720 wasm::FrameWithInstances::sizeOfInstanceFieldsAndShadowStack(),
5721 WasmStackAlignment));
5723 masm.wasmMarkCallAsSlow();
5726 masm.loadPtr(
5727 Address(masm.getStackPointer(), WasmCallerInstanceOffsetBeforeCall),
5728 InstanceReg);
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);
5735 masm.abiret();
5736 #else
5737 masm.pop(FramePointer);
5738 masm.append(wasm::CodeRangeUnwindInfo::UseFp, masm.currentOffset());
5739 masm.ret();
5740 #endif
5742 masm.append(wasm::CodeRangeUnwindInfo::Normal, masm.currentOffset());
5743 masm.setFramePushed(savedPushed);
5744 return data;
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
5772 // |C2 parameters |
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);
5803 #endif
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()),
5809 tempForRA);
5810 masm.append(wasm::CodeRangeUnwindInfo::RestoreFpRa, masm.currentOffset());
5811 bool copyCallerSlot = oldSlotsAndStackArgBytes != newSlotsAndStackArgBytes;
5812 if (copyCallerSlot) {
5813 masm.loadPtr(
5814 Address(FramePointer, wasm::FrameWithInstances::callerInstanceOffset()),
5815 tempForCaller);
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) {
5831 masm.storePtr(
5832 tempForCaller,
5833 Address(FramePointer, newArgDest + WasmCallerInstanceOffsetBeforeCall));
5836 // Store current instance as the new callee instance slot.
5837 masm.storePtr(
5838 InstanceReg,
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));
5844 #else
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*)));
5859 #endif
5861 masm.movePtr(tempForFP, FramePointer);
5862 // Setting framePushed to pre-collapse state, to properly set that in the
5863 // following code.
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*)
5912 : 0;
5913 masm.reserveStack(reserved);
5915 #ifndef JS_USE_LINK_REGISTER
5916 masm.push(tempForRA);
5917 #endif
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()),
5922 tempForRA);
5923 masm.append(wasm::CodeRangeUnwindInfo::RestoreFpRa, masm.currentOffset());
5924 masm.loadPtr(
5925 Address(FramePointer, newArgSrc + WasmCallerInstanceOffsetBeforeCall),
5926 tempForCaller);
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);
5937 masm.storePtr(
5938 tempForRA,
5939 Address(FramePointer, newFPOffset + wasm::Frame::returnAddressOffset()));
5941 // Copy original FP.
5942 masm.storePtr(
5943 tempForFP,
5944 Address(FramePointer, newFPOffset + wasm::Frame::callerFPOffset()));
5946 // Set up instance slots.
5947 masm.storePtr(
5948 tempForCaller,
5949 Address(FramePointer,
5950 newFPOffset + wasm::FrameWithInstances::calleeInstanceOffset()));
5951 masm.storePtr(
5952 tempForCaller,
5953 Address(FramePointer, newArgDest + WasmCallerInstanceOffsetBeforeCall));
5954 masm.storePtr(
5955 InstanceReg,
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(
5962 Address(tempForRA,
5963 int32_t(data.trampolineOffset - masm.currentOffset() - 4)),
5964 tempForRA);
5965 masm.append(desc, CodeOffset(data.trampolineOffset));
5966 #else
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());
5973 #endif
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));
5979 #else
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 +
5994 sizeof(void*)));
5995 #endif
5997 // Point FramePointer to hidden frame.
5998 masm.computeEffectiveAddress(Address(FramePointer, newFPOffset),
5999 FramePointer);
6000 // Setting framePushed to pre-collapse state, to properly set that in the
6001 // following code.
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.
6018 Label slow, done;
6019 loadPtr(Address(FramePointer, wasm::Frame::returnAddressOffset()), temp1);
6020 wasmCheckSlowCallsite(temp1, &slow, temp1, temp2);
6021 CollapseWasmFrameFast(*this, retCallInfo);
6022 jump(&done);
6023 append(wasm::CodeRangeUnwindInfo::Normal, currentOffset());
6025 ReturnCallTrampolineData data = MakeReturnCallTrampoline(*this);
6027 bind(&slow);
6028 CollapseWasmFrameSlow(*this, retCallInfo, desc, data);
6030 bind(&done);
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();
6040 loadPtr(
6041 Address(InstanceReg, wasm::Instance::offsetInData(
6042 instanceDataOffset +
6043 offsetof(wasm::FuncImportInstanceData, code))),
6044 ABINonArgReg0);
6046 #if !defined(JS_CODEGEN_NONE) && !defined(JS_CODEGEN_WASM32)
6047 static_assert(ABINonArgReg0 != InstanceReg, "by constraint");
6048 #endif
6050 // Switch to the callee's realm.
6051 loadPtr(
6052 Address(InstanceReg, wasm::Instance::offsetInData(
6053 instanceDataOffset +
6054 offsetof(wasm::FuncImportInstanceData, realm))),
6055 ABINonArgReg1);
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))),
6064 InstanceReg);
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();
6081 loadPtr(
6082 Address(InstanceReg, wasm::Instance::offsetInData(
6083 instanceDataOffset +
6084 offsetof(wasm::FuncImportInstanceData, code))),
6085 ABINonArgReg0);
6087 #if !defined(JS_CODEGEN_NONE) && !defined(JS_CODEGEN_WASM32)
6088 static_assert(ABINonArgReg0 != InstanceReg, "by constraint");
6089 #endif
6091 // Switch to the callee's realm.
6092 loadPtr(
6093 Address(InstanceReg, wasm::Instance::offsetInData(
6094 instanceDataOffset +
6095 offsetof(wasm::FuncImportInstanceData, realm))),
6096 ABINonArgReg1);
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))),
6105 InstanceReg);
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());
6126 return offset;
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()));
6144 } else {
6145 MOZ_CRASH("Unknown abi passing style for pointer");
6148 CodeOffset ret = call(desc, builtin);
6149 wasmTrapOnFailedInstanceCall(ReturnReg, failureMode,
6150 wasm::BytecodeOffset(desc.lineOrBytecode()));
6152 return ret;
6155 void MacroAssembler::wasmTrapOnFailedInstanceCall(
6156 Register resultRegister, wasm::FailureMode failureMode,
6157 wasm::BytecodeOffset bytecodeOffset) {
6158 Label noTrap;
6159 switch (failureMode) {
6160 case wasm::FailureMode::Infallible:
6161 return;
6162 case wasm::FailureMode::FailOnNegI32:
6163 branchTest32(Assembler::NotSigned, resultRegister, resultRegister,
6164 &noTrap);
6165 break;
6166 case wasm::FailureMode::FailOnMaxI32:
6167 branchPtr(Assembler::NotEqual, resultRegister,
6168 ImmWord(uintptr_t(INT32_MAX)), &noTrap);
6169 break;
6170 case wasm::FailureMode::FailOnNullPtr:
6171 branchTestPtr(Assembler::NonZero, resultRegister, resultRegister,
6172 &noTrap);
6173 break;
6174 case wasm::FailureMode::FailOnInvalidRef:
6175 branchPtr(Assembler::NotEqual, resultRegister,
6176 ImmWord(uintptr_t(wasm::AnyRef::invalid().forCompiledCode())),
6177 &noTrap);
6178 break;
6180 wasmTrap(wasm::Trap::ThrowReported, bytecodeOffset);
6181 bind(&noTrap);
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.
6201 loadPtr(
6202 Address(InstanceReg, wasm::Instance::offsetInData(
6203 callee.tableFunctionBaseInstanceDataOffset())),
6204 scratch);
6205 if (sizeof(wasm::FunctionTableElem) == 8) {
6206 computeEffectiveAddress(BaseIndex(scratch, index, TimesEight), scratch);
6207 } else {
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);
6257 } else {
6258 branch32(
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,
6274 superTypeVector))),
6275 WasmTableCallSigReg);
6276 break;
6277 case wasm::CallIndirectIdKind::Immediate:
6278 move32(Imm32(callIndirectId.immediate()), WasmTableCallSigReg);
6279 break;
6280 case wasm::CallIndirectIdKind::AsmJS:
6281 case wasm::CallIndirectIdKind::None:
6282 break;
6285 // Load the base pointer of the table and compute the address of the callee in
6286 // the table.
6288 loadPtr(
6289 Address(InstanceReg, wasm::Instance::offsetInData(
6290 callee.tableFunctionBaseInstanceDataOffset())),
6291 calleeScratch);
6292 shiftIndex32AndAdd(index, shift, calleeScratch);
6294 // Load the callee instance and decide whether to take the fast path or the
6295 // slow path.
6297 Label fastCall;
6298 Label done;
6299 const Register newInstanceTemp = WasmTableCallScratchReg1;
6300 loadPtr(Address(calleeScratch, offsetof(wasm::FunctionTableElem, instance)),
6301 newInstanceTemp);
6302 branchPtr(Assembler::Equal, InstanceReg, newInstanceTemp, &fastCall);
6304 // Slow path: Save context, check for null, setup new context, call, restore
6305 // context.
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));
6323 #else
6324 MOZ_ASSERT(nullCheckFailedLabel != nullptr);
6325 branchTestPtr(Assembler::Zero, InstanceReg, InstanceReg,
6326 nullCheckFailedLabel);
6328 loadWasmPinnedRegsFromInstance();
6329 #endif
6330 switchToWasmInstanceRealm(index, WasmTableCallScratchReg1);
6332 loadPtr(Address(calleeScratch, offsetof(wasm::FunctionTableElem, code)),
6333 calleeScratch);
6335 *slowCallOffset = wasmMarkedSlowCall(desc, calleeScratch);
6337 // Restore registers and realm and join up with the fast path.
6339 loadPtr(Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall),
6340 InstanceReg);
6341 loadWasmPinnedRegsFromInstance();
6342 switchToWasmInstanceRealm(ABINonArgReturnReg0, ABINonArgReturnReg1);
6343 jump(&done);
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.)
6352 bind(&fastCall);
6354 loadPtr(Address(calleeScratch, offsetof(wasm::FunctionTableElem, code)),
6355 calleeScratch);
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);
6364 bind(&done);
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);
6394 } else {
6395 branch32(
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,
6411 superTypeVector))),
6412 WasmTableCallSigReg);
6413 break;
6414 case wasm::CallIndirectIdKind::Immediate:
6415 move32(Imm32(callIndirectId.immediate()), WasmTableCallSigReg);
6416 break;
6417 case wasm::CallIndirectIdKind::AsmJS:
6418 case wasm::CallIndirectIdKind::None:
6419 break;
6422 // Load the base pointer of the table and compute the address of the callee in
6423 // the table.
6425 loadPtr(
6426 Address(InstanceReg, wasm::Instance::offsetInData(
6427 callee.tableFunctionBaseInstanceDataOffset())),
6428 calleeScratch);
6429 shiftIndex32AndAdd(index, shift, calleeScratch);
6431 // Load the callee instance and decide whether to take the fast path or the
6432 // slow path.
6434 Label fastCall;
6435 Label done;
6436 const Register newInstanceTemp = WasmTableCallScratchReg1;
6437 loadPtr(Address(calleeScratch, offsetof(wasm::FunctionTableElem, instance)),
6438 newInstanceTemp);
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));
6452 #else
6453 MOZ_ASSERT(nullCheckFailedLabel != nullptr);
6454 branchTestPtr(Assembler::Zero, InstanceReg, InstanceReg,
6455 nullCheckFailedLabel);
6457 loadWasmPinnedRegsFromInstance();
6458 #endif
6459 switchToWasmInstanceRealm(index, WasmTableCallScratchReg1);
6461 loadPtr(Address(calleeScratch, offsetof(wasm::FunctionTableElem, code)),
6462 calleeScratch);
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.
6472 bind(&fastCall);
6474 loadPtr(Address(calleeScratch, offsetof(wasm::FunctionTableElem, code)),
6475 calleeScratch);
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.
6494 Label fastCall;
6495 Label done;
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),
6527 InstanceReg);
6528 loadWasmPinnedRegsFromInstance();
6529 switchToWasmInstanceRealm(ABINonArgReturnReg0, ABINonArgReturnReg1);
6530 jump(&done);
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.
6535 bind(&fastCall);
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);
6546 bind(&done);
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.
6560 Label fastCall;
6561 Label done;
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.
6598 bind(&fastCall);
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) {
6610 Label ok;
6611 Label fail;
6613 mov(index, tmp);
6614 branchAdd32(Assembler::CarrySet, length, tmp, &fail);
6615 branch32(Assembler::Above, tmp, limit, &fail);
6616 jump(&ok);
6618 bind(&fail);
6619 wasmTrap(wasm::Trap::OutOfBounds, bytecodeOffset);
6621 bind(&ok);
6624 #ifdef ENABLE_WASM_MEMORY64
6625 void MacroAssembler::wasmClampTable64Index(Register64 index, Register out) {
6626 Label oob;
6627 Label ret;
6628 branch64(Assembler::Above, index, Imm64(UINT32_MAX), &oob);
6629 move64To32(index, out);
6630 jump(&ret);
6631 bind(&oob);
6632 static_assert(wasm::MaxTableElemsRuntime < UINT32_MAX);
6633 move32(Imm32(UINT32_MAX), out);
6634 bind(&ret);
6636 #endif
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(),
6646 .needScratch2 =
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(),
6654 .needScratch2 =
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,
6665 default:
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);
6678 } break;
6679 case wasm::RefTypeHierarchy::Func: {
6680 branchWasmRefIsSubtypeFunc(ref, sourceType, destType, label, onSuccess,
6681 superSTV, scratch1, scratch2);
6682 } break;
6683 case wasm::RefTypeHierarchy::Extern: {
6684 branchWasmRefIsSubtypeExtern(ref, sourceType, destType, label, onSuccess);
6685 } break;
6686 case wasm::RefTypeHierarchy::Exn: {
6687 branchWasmRefIsSubtypeExn(ref, sourceType, destType, label, onSuccess);
6688 } break;
6689 default:
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());
6709 Label fallthrough;
6710 Label* successLabel = onSuccess ? label : &fallthrough;
6711 Label* failLabel = onSuccess ? &fallthrough : label;
6712 Label* nullLabel = destType.isNullable() ? successLabel : failLabel;
6714 // Check for null.
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
6720 // not-null.
6721 if (destType.isNone()) {
6722 jump(failLabel);
6723 bind(&fallthrough);
6724 return;
6727 if (destType.isAny()) {
6728 // No further checks for 'any'
6729 jump(successLabel);
6730 bind(&fallthrough);
6731 return;
6734 // 'type' is now 'eq' or lower, which currently will either be a gc object or
6735 // an i31.
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'
6744 jump(failLabel);
6745 bind(&fallthrough);
6746 return;
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'
6760 jump(successLabel);
6761 bind(&fallthrough);
6762 return;
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())),
6774 scratch1);
6775 if (destType.isTypeRef()) {
6776 // concrete type, do superTypeVector check
6777 branchWasmSTVIsSubtype(scratch1, superSTV, scratch2,
6778 destType.typeDef()->subTypingDepth(), successLabel,
6779 true);
6780 } else {
6781 // abstract type, do kind check
6782 loadPtr(Address(scratch1,
6783 int32_t(wasm::SuperTypeVector::offsetOfSelfTypeDef())),
6784 scratch1);
6785 load8ZeroExtend(Address(scratch1, int32_t(wasm::TypeDef::offsetOfKind())),
6786 scratch1);
6787 branch32(Assembler::Equal, scratch1, Imm32(int32_t(destType.typeDefKind())),
6788 successLabel);
6791 // The cast failed.
6792 jump(failLabel);
6793 bind(&fallthrough);
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());
6811 Label fallthrough;
6812 Label* successLabel = onSuccess ? label : &fallthrough;
6813 Label* failLabel = onSuccess ? &fallthrough : label;
6814 Label* nullLabel = destType.isNullable() ? successLabel : failLabel;
6816 // Check for null.
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
6822 // not-null.
6823 if (destType.isNoFunc()) {
6824 jump(failLabel);
6825 bind(&fallthrough);
6826 return;
6829 if (destType.isFunc()) {
6830 // No further checks for 'func' (any func)
6831 jump(successLabel);
6832 bind(&fallthrough);
6833 return;
6836 // In the func hierarchy, a supertype vector check is now sufficient for all
6837 // remaining cases.
6838 loadPrivate(Address(ref, int32_t(FunctionExtended::offsetOfWasmSTV())),
6839 scratch1);
6840 branchWasmSTVIsSubtype(scratch1, superSTV, scratch2,
6841 destType.typeDef()->subTypingDepth(), successLabel,
6842 true);
6844 // If we didn't branch away, the cast failed.
6845 jump(failLabel);
6846 bind(&fallthrough);
6849 void MacroAssembler::branchWasmRefIsSubtypeExtern(Register ref,
6850 wasm::RefType sourceType,
6851 wasm::RefType destType,
6852 Label* label,
6853 bool onSuccess) {
6854 MOZ_ASSERT(sourceType.isValid());
6855 MOZ_ASSERT(destType.isValid());
6856 MOZ_ASSERT(sourceType.isExternHierarchy());
6857 MOZ_ASSERT(destType.isExternHierarchy());
6859 Label fallthrough;
6860 Label* successLabel = onSuccess ? label : &fallthrough;
6861 Label* failLabel = onSuccess ? &fallthrough : label;
6862 Label* nullLabel = destType.isNullable() ? successLabel : failLabel;
6864 // Check for null.
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
6870 // not-null.
6871 if (destType.isNoExtern()) {
6872 jump(failLabel);
6873 bind(&fallthrough);
6874 return;
6877 // There are no other possible types except externref, so succeed!
6878 jump(successLabel);
6879 bind(&fallthrough);
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());
6891 Label fallthrough;
6892 Label* successLabel = onSuccess ? label : &fallthrough;
6893 Label* failLabel = onSuccess ? &fallthrough : label;
6894 Label* nullLabel = destType.isNullable() ? successLabel : failLabel;
6896 // Check for null.
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
6902 // not-null.
6903 if (destType.isNoExn()) {
6904 jump(failLabel);
6905 bind(&fallthrough);
6906 return;
6909 // There are no other possible types except exnref, so succeed!
6910 jump(successLabel);
6911 bind(&fallthrough);
6914 void MacroAssembler::branchWasmSTVIsSubtype(Register subSTV, Register superSTV,
6915 Register scratch,
6916 uint32_t superDepth, Label* label,
6917 bool onSuccess) {
6918 MOZ_ASSERT_IF(superDepth >= wasm::MinSuperTypeVectorLength,
6919 scratch != Register::Invalid());
6920 Label fallthrough;
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.
6937 loadPtr(
6938 Address(subSTV, wasm::SuperTypeVector::offsetOfSTVInVector(superDepth)),
6939 subSTV);
6941 // We succeed iff the entries are equal
6942 branchPtr(onSuccess ? Assembler::Equal : Assembler::NotEqual, subSTV,
6943 superSTV, label);
6945 bind(&fallthrough);
6948 void MacroAssembler::branchWasmSTVIsSubtypeDynamicDepth(
6949 Register subSTV, Register superSTV, Register superDepth, Register scratch,
6950 Label* label, bool onSuccess) {
6951 Label fallthrough;
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_)),
6962 subSTV);
6964 // We succeed iff the entries are equal
6965 branchPtr(onSuccess ? Assembler::Equal : Assembler::NotEqual, subSTV,
6966 superSTV, label);
6968 bind(&fallthrough);
6971 void MacroAssembler::extractWasmAnyRefTag(Register src, Register dest) {
6972 movePtr(src, dest);
6973 andPtr(Imm32(int32_t(wasm::AnyRef::TagMask)), dest);
6976 void MacroAssembler::branchWasmAnyRefIsNull(bool isNull, Register src,
6977 Label* label) {
6978 branchTestPtr(isNull ? Assembler::Zero : Assembler::NonZero, src, src, label);
6981 void MacroAssembler::branchWasmAnyRefIsI31(bool isI31, Register src,
6982 Label* label) {
6983 branchTestPtr(isI31 ? Assembler::NonZero : Assembler::Zero, src,
6984 Imm32(int32_t(wasm::AnyRefTag::I31)), label);
6987 void MacroAssembler::branchWasmAnyRefIsObjectOrNull(bool isObject, Register src,
6988 Label* label) {
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,
7001 Label* label) {
7002 Label fallthrough;
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);
7010 bind(&fallthrough);
7013 void MacroAssembler::branchWasmAnyRefIsNurseryCell(bool isNurseryCell,
7014 Register src, Register temp,
7015 Label* label) {
7016 Label done;
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);
7022 bind(&done);
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.
7029 move32(src, dest);
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);
7035 #ifdef JS_64BIT
7036 debugAssertCanonicalInt32(dest);
7037 #endif
7040 void MacroAssembler::convertWasmI31RefTo32Signed(Register src, Register dest) {
7041 #ifdef JS_64BIT
7042 debugAssertCanonicalInt32(src);
7043 #endif
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.
7047 move32(src, dest);
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,
7054 Register dest) {
7055 #ifdef JS_64BIT
7056 debugAssertCanonicalInt32(src);
7057 #endif
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.
7061 move32(src, dest);
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,
7069 Label* label) {
7070 // We can convert objects, strings, 31-bit integers and null without boxing.
7071 Label checkInt32;
7072 Label checkDouble;
7073 Label fallthrough;
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);
7081 jump(&fallthrough);
7083 bind(&checkInt32);
7084 unboxInt32(src, scratchInt);
7085 branch32(Assembler::GreaterThan, scratchInt, Imm32(wasm::AnyRef::MaxI31Value),
7086 &fallthrough);
7087 branch32(Assembler::LessThan, scratchInt, Imm32(wasm::AnyRef::MinI31Value),
7088 &fallthrough);
7089 jump(label);
7091 bind(&checkDouble);
7093 ScratchTagScopeRelease _(&tag);
7094 convertValueToInt32(src, scratchFloat, scratchInt, &fallthrough, true,
7095 IntConversionInputKind::NumbersOnly);
7097 branch32(Assembler::GreaterThan, scratchInt, Imm32(wasm::AnyRef::MaxI31Value),
7098 &fallthrough);
7099 branch32(Assembler::LessThan, scratchInt, Imm32(wasm::AnyRef::MinI31Value),
7100 &fallthrough);
7101 jump(label);
7103 bind(&fallthrough);
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);
7118 jump(oolConvert);
7121 bind(&doubleValue);
7122 convertValueToInt32(src, scratchFloat, dest, oolConvert, true,
7123 IntConversionInputKind::NumbersOnly);
7124 branch32(Assembler::GreaterThan, dest, Imm32(wasm::AnyRef::MaxI31Value),
7125 oolConvert);
7126 branch32(Assembler::LessThan, dest, Imm32(wasm::AnyRef::MinI31Value),
7127 oolConvert);
7128 lshiftPtr(Imm32(1), dest);
7129 or32(Imm32((int32_t)wasm::AnyRefTag::I31), dest);
7130 jump(&done);
7132 bind(&int32Value);
7133 unboxInt32(src, dest);
7134 branch32(Assembler::GreaterThan, dest, Imm32(wasm::AnyRef::MaxI31Value),
7135 oolConvert);
7136 branch32(Assembler::LessThan, dest, Imm32(wasm::AnyRef::MinI31Value),
7137 oolConvert);
7138 truncate32ToWasmI31Ref(dest, dest);
7139 jump(&done);
7141 bind(&nullValue);
7142 static_assert(wasm::AnyRef::NullRefValue == 0);
7143 xorPtr(dest, dest);
7144 jump(&done);
7146 bind(&stringValue);
7147 unboxString(src, dest);
7148 orPtr(Imm32((int32_t)wasm::AnyRefTag::String), dest);
7149 jump(&done);
7151 bind(&objectValue);
7152 unboxObject(src, dest);
7154 bind(&done);
7157 void MacroAssembler::convertObjectToWasmAnyRef(Register src, Register dest) {
7158 // JS objects are represented without any tagging.
7159 movePtr(src, dest);
7162 void MacroAssembler::convertStringToWasmAnyRef(Register src, Register dest) {
7163 // JS strings require a tag.
7164 movePtr(src, dest);
7165 orPtr(Imm32(int32_t(wasm::AnyRefTag::String)), dest);
7168 void MacroAssembler::branchObjectIsWasmGcObject(bool isGcObject, Register src,
7169 Register scratch,
7170 Label* label) {
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,
7187 bool zeroFields) {
7188 // Don't execute the inline path if GC probes are built in.
7189 #ifdef JS_GC_PROBES
7190 jump(fail);
7191 #endif
7193 #ifdef JS_GC_ZEAL
7194 // Don't execute the inline path if gc zeal or tracing are active.
7195 loadPtr(Address(instance, wasm::Instance::offsetOfAddressOfGCZealModeBits()),
7196 temp1);
7197 loadPtr(Address(temp1, 0), temp1);
7198 branch32(Assembler::NotEqual, temp1, Imm32(0), fail);
7199 #endif
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()),
7204 temp1);
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,
7211 sizeBytes);
7212 loadPtr(Address(typeDefData, wasm::TypeDefInstanceData::offsetOfShape()),
7213 temp1);
7214 loadPtr(Address(typeDefData,
7215 wasm::TypeDefInstanceData::offsetOfSuperTypeVector()),
7216 temp2);
7217 storePtr(temp1, Address(result, WasmStructObject::offsetOfShape()));
7218 storePtr(temp2, Address(result, WasmStructObject::offsetOfSuperTypeVector()));
7219 storePtr(ImmWord(0),
7220 Address(result, WasmStructObject::offsetOfOutlineData()));
7222 if (zeroFields) {
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,
7235 bool zeroFields) {
7236 // Don't execute the inline path if GC probes are built in.
7237 #ifdef JS_GC_PROBES
7238 jump(fail);
7239 #endif
7241 #ifdef JS_GC_ZEAL
7242 // Don't execute the inline path if gc zeal or tracing are active.
7243 loadPtr(Address(instance, wasm::Instance::offsetOfAddressOfGCZealModeBits()),
7244 temp);
7245 loadPtr(Address(temp, 0), temp);
7246 branch32(Assembler::NotEqual, temp, Imm32(0), fail);
7247 #endif
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()),
7252 temp);
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)),
7260 fail);
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!
7264 Label popAndFail;
7265 #ifdef JS_CODEGEN_ARM64
7266 // On arm64, we must maintain 16-alignment of both the actual and pseudo stack
7267 // pointers.
7268 push(numElements, xzr);
7269 syncStackPtr();
7270 #else
7271 push(numElements);
7272 #endif
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) <
7288 INT32_MAX);
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()),
7306 temp);
7307 storePtr(temp, Address(result, WasmArrayObject::offsetOfShape()));
7308 loadPtr(Address(typeDefData,
7309 wasm::TypeDefInstanceData::offsetOfSuperTypeVector()),
7310 temp);
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);
7328 Label zeroed;
7329 if (zeroFields) {
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
7334 // clearer.
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())),
7348 current);
7350 // Exit immediately if the array has zero elements.
7351 branchPtr(Assembler::Equal, current, inlineArrayData, &zeroed);
7353 // Loop, counting down until current == inlineArrayData.
7354 Label loop;
7355 bind(&loop);
7356 subPtr(Imm32(sizeof(void*)), current);
7357 storePtr(ImmWord(0), Address(current, 0));
7358 branchPtr(Assembler::NotEqual, current, inlineArrayData, &loop);
7360 bind(&zeroed);
7362 // Finally, store the actual numElements in the array object.
7363 #ifdef JS_CODEGEN_ARM64
7364 pop(xzr, numElements);
7365 syncStackPtr();
7366 #else
7367 pop(numElements);
7368 #endif
7369 store32(numElements, Address(result, WasmArrayObject::offsetOfNumElements()));
7371 Label done;
7372 jump(&done);
7374 bind(&popAndFail);
7375 #ifdef JS_CODEGEN_ARM64
7376 pop(xzr, numElements);
7377 syncStackPtr();
7378 #else
7379 pop(numElements);
7380 #endif
7381 jump(fail);
7383 bind(&done);
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,
7391 bool zeroFields) {
7392 MOZ_ASSERT(storageBytes <= WasmArrayObject_MaxInlineBytes);
7394 // Don't execute the inline path if GC probes are built in.
7395 #ifdef JS_GC_PROBES
7396 jump(fail);
7397 #endif
7399 #ifdef JS_GC_ZEAL
7400 // Don't execute the inline path if gc zeal or tracing are active.
7401 loadPtr(Address(instance, wasm::Instance::offsetOfAddressOfGCZealModeBits()),
7402 temp1);
7403 loadPtr(Address(temp1, 0), temp1);
7404 branch32(Assembler::NotEqual, temp1, Imm32(0), fail);
7405 #endif
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()),
7410 temp1);
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,
7418 totalSize);
7419 loadPtr(Address(typeDefData, wasm::TypeDefInstanceData::offsetOfShape()),
7420 temp1);
7421 loadPtr(Address(typeDefData,
7422 wasm::TypeDefInstanceData::offsetOfSuperTypeVector()),
7423 temp2);
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()));
7437 if (zeroFields) {
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)
7442 Label done;
7443 computeEffectiveAddress(
7444 Address(temp2, -sizeof(WasmArrayObject::DataHeader) + storageBytes),
7445 temp1);
7446 branchPtr(Assembler::Equal, temp1, temp2, &done);
7448 // Count temp2 down toward temp1, zeroing one word at a time
7449 Label loop;
7450 bind(&loop);
7451 subPtr(Imm32(sizeof(void*)), temp1);
7452 storePtr(ImmWord(0), Address(temp1, 0));
7453 branchPtr(Assembler::NotEqual, temp1, temp2, &loop);
7455 bind(&done);
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()),
7475 temp1);
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()),
7482 temp1);
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()),
7493 temp1);
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) {
7505 #ifdef DEBUG
7506 // Replaces MOZ_ASSERT(size >= gc::MinCellSize);
7507 Label ok1;
7508 branch32(Assembler::AboveOrEqual, size, Imm32(gc::MinCellSize), &ok1);
7509 breakpoint();
7510 bind(&ok1);
7512 Label ok2;
7513 branch32(Assembler::BelowOrEqual, size, Imm32(JSObject::MAX_BYTE_SIZE), &ok2);
7514 breakpoint();
7515 bind(&ok2);
7516 #endif
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()),
7523 temp1);
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()),
7529 temp1);
7530 loadPtr(Address(temp1, 0), result);
7531 computeEffectiveAddress(BaseIndex(result, size, Scale::TimesOne,
7532 Nursery::nurseryCellHeaderSize()),
7533 result);
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()),
7549 temp1);
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
7560 // representation.
7561 void MacroAssembler::convertWasmAnyRefToValue(Register instance, Register src,
7562 ValueOperand dst,
7563 Register scratch) {
7564 MOZ_ASSERT(src != scratch);
7565 #if JS_BITS_PER_WORD == 32
7566 MOZ_ASSERT(dst.typeReg() != scratch);
7567 MOZ_ASSERT(dst.payloadReg() != scratch);
7568 #else
7569 MOZ_ASSERT(dst.valueReg() != scratch);
7570 #endif
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)),
7576 &isI31);
7577 // Then check for the object or null tag
7578 branchTestPtr(Assembler::Zero, src, Imm32(wasm::AnyRef::TagMask),
7579 &isObjectOrNull);
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);
7585 jump(&done);
7587 // This is an i31 value, convert to an int32 JS value
7588 bind(&isI31);
7589 convertWasmI31RefTo32Signed(src, src);
7590 moveValue(TypedOrValueRegister(MIRType::Int32, AnyRegister(src)), dst);
7591 jump(&done);
7593 // Check for the null value
7594 bind(&isObjectOrNull);
7595 branchTestPtr(Assembler::NonZero, src, src, &isObject);
7596 moveValue(NullValue(), dst);
7597 jump(&done);
7599 // Otherwise we must be a non-null object. We next to check if it's storing a
7600 // boxed value
7601 bind(&isObject);
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);
7608 jump(&done);
7610 // This is a boxed JS value, unbox it.
7611 bind(&isWasmValueBox);
7612 loadValue(Address(src, wasm::AnyRef::valueBoxOffsetOfValue()), dst);
7614 bind(&done);
7617 void MacroAssembler::convertWasmAnyRefToValue(Register instance, Register src,
7618 const Address& dst,
7619 Register scratch) {
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)),
7626 &isI31);
7627 // Then check for the object or null tag
7628 branchTestPtr(Assembler::Zero, src, Imm32(wasm::AnyRef::TagMask),
7629 &isObjectOrNull);
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);
7635 jump(&done);
7637 // This is an i31 value, convert to an int32 JS value
7638 bind(&isI31);
7639 convertWasmI31RefTo32Signed(src, src);
7640 storeValue(JSVAL_TYPE_INT32, src, dst);
7641 jump(&done);
7643 // Check for the null value
7644 bind(&isObjectOrNull);
7645 branchTestPtr(Assembler::NonZero, src, src, &isObject);
7646 storeValue(NullValue(), dst);
7647 jump(&done);
7649 // Otherwise we must be a non-null object. We next to check if it's storing a
7650 // boxed value
7651 bind(&isObject);
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);
7658 jump(&done);
7660 // This is a boxed JS value, unbox it.
7661 bind(&isWasmValueBox);
7662 copy64(Address(src, wasm::AnyRef::valueBoxOffsetOfValue()), dst, scratch);
7664 bind(&done);
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);
7684 } else {
7685 MOZ_ASSERT(type == MIRType::Object || type == MIRType::String ||
7686 type == MIRType::Shape);
7687 loadPtr(Address(PreBarrierReg, 0), temp1);
7690 #ifdef DEBUG
7691 // The caller should have checked for null pointers.
7692 Label nonZero;
7693 branchTestPtr(Assembler::NonZero, temp1, temp1, &nonZero);
7694 assumeUnreachable("JIT pre-barrier: unexpected nullptr");
7695 bind(&nonZero);
7696 #endif
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);
7707 } else {
7708 #ifdef DEBUG
7709 Label isTenured;
7710 branchPtr(Assembler::Equal, Address(temp2, gc::ChunkStoreBufferOffset),
7711 ImmWord(0), &isTenured);
7712 assumeUnreachable("JIT pre-barrier: unexpected nursery pointer");
7713 bind(&isTenured);
7714 #endif
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);
7746 #else
7747 rshiftPtr(Imm32(5), temp1);
7748 loadPtr(BaseIndex(temp2, temp1, TimesFour, offset), temp2);
7749 #endif
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);
7758 shlq_cl(temp1);
7759 #elif JS_CODEGEN_X86
7760 MOZ_ASSERT(temp3 == ecx);
7761 shll_cl(temp1);
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
7775 MOZ_CRASH();
7776 #elif JS_CODEGEN_NONE
7777 MOZ_CRASH();
7778 #else
7779 # error "Unknown architecture"
7780 #endif
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
7796 Label done;
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);
7803 bind(&done);
7806 // ========================================================================
7807 // Spectre Mitigations.
7809 void MacroAssembler::spectreMaskIndex32(Register index, Register length,
7810 Register output) {
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,
7820 Register output) {
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,
7831 Register output) {
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,
7841 Register output) {
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,
7852 Label* failure) {
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);
7870 if (trapOffset) {
7871 append(
7872 wasm::Trap::IndirectCallToNull,
7873 wasm::TrapSite(wasm::TrapMachineInsnForLoadWord(), fco, *trapOffset));
7875 #else
7876 MOZ_ASSERT(!trapOffset);
7877 #endif
7880 //}}} check_macroassembler_style
7882 #ifdef JS_64BIT
7883 void MacroAssembler::debugAssertCanonicalInt32(Register r) {
7884 # ifdef DEBUG
7885 if (!js::jit::JitOptions.lessDebugCode) {
7886 # if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM64)
7887 Label ok;
7888 branchPtr(Assembler::BelowOrEqual, r, ImmWord(UINT32_MAX), &ok);
7889 breakpoint();
7890 bind(&ok);
7891 # elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64)
7892 Label ok;
7893 ScratchRegisterScope scratch(asMasm());
7894 move32SignExtendToPtr(r, scratch);
7895 branchPtr(Assembler::Equal, r, scratch, &ok);
7896 breakpoint();
7897 bind(&ok);
7898 # else
7899 MOZ_CRASH("IMPLEMENT ME");
7900 # endif
7902 # endif
7904 #endif
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);
7922 return;
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,
7943 Register temp2) {
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);
7953 return;
7956 using Float32 = mozilla::FloatingPoint<float>;
7958 #ifdef DEBUG
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;
7971 #endif
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.
7996 // Double rounding:
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 // -----------------------
8005 // f16 | 5 10
8006 // f32 | 8 23
8007 // f64 | 11 52
8009 // Examples:
8011 // Input (f64): 0.0000610649585723877
8012 // Bits (f64): 3f10'0200'0000'0000
8013 // Bits (f32): 3880'1000
8014 // Bits (f16): 0400
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
8027 // Bits (f16): 0401
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:
8035 // Low 32-bits
8036 // __________________|__________________
8037 // / |
8038 // 0000 0000 0010 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001
8039 // | |
8040 // | GRS
8041 // | 001
8042 // |
8043 // GRS (G)uard bit
8044 // 011 (R)ound bit
8045 // (S)ticky bit
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
8057 // |
8058 // GRS
8059 // 010
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
8069 // |
8070 // GRS
8071 // 011
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
8078 // Bits (f16): 0401
8080 // Low 32-bits
8081 // __________________|__________________
8082 // / |
8083 // 0000 0000 0101 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
8084 // | |
8085 // | GRS
8086 // | 111
8087 // |
8088 // GRS
8089 // 101
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
8097 // |
8098 // GRS
8099 // 110
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
8107 // |
8108 // GRS
8109 // 101
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);
8129 Label done;
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);
8163 #else
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);
8177 #endif
8180 bind(&done);
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);
8197 return;
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,
8217 Register temp,
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);
8224 return;
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);
8236 passABIArg(src);
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);
8252 return;
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);
8267 passABIArg(temp1);
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,
8276 Register temp2,
8277 LiveRegisterSet volatileLiveRegs);
8279 template void MacroAssembler::loadFloat16(const BaseIndex& src,
8280 FloatRegister dest, Register temp1,
8281 Register temp2,
8282 LiveRegisterSet volatileLiveRegs);
8284 template <typename T>
8285 void MacroAssembler::storeFloat16(FloatRegister src, const T& dest,
8286 Register temp,
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);
8295 return;
8298 convertDoubleToFloat16(src, fpscratch, temp, volatileLiveRegs);
8299 src = fpscratch;
8301 MOZ_ASSERT(src.isSingle());
8303 if (MacroAssembler::SupportsFloat32To16()) {
8304 canonicalizeFloatIfDeterministic(src);
8305 convertFloat32ToFloat16(src, fpscratch);
8306 storeUncanonicalizedFloat16(fpscratch, dest, temp);
8307 return;
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);
8332 return;
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,
8350 Register temp,
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);
8357 return;
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);
8369 passABIArg(src);
8370 callWithABI<Fn, jit::Float16ToFloat32>(ABIType::Float32);
8371 storeCallFloatResult(dest);
8373 PopRegsInMask(save);
8376 void MacroAssembler::debugAssertIsObject(const ValueOperand& val) {
8377 #ifdef DEBUG
8378 Label ok;
8379 branchTestObject(Assembler::Equal, val, &ok);
8380 assumeUnreachable("Expected an object!");
8381 bind(&ok);
8382 #endif
8385 void MacroAssembler::debugAssertObjHasFixedSlots(Register obj,
8386 Register scratch) {
8387 #ifdef DEBUG
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);
8395 #endif
8398 void MacroAssembler::debugAssertObjectHasClass(Register obj, Register scratch,
8399 const JSClass* clasp) {
8400 #ifdef DEBUG
8401 Label done;
8402 branchTestObjClassNoSpectreMitigations(Assembler::Equal, obj, clasp, scratch,
8403 &done);
8404 assumeUnreachable("Class check failed");
8405 bind(&done);
8406 #endif
8409 void MacroAssembler::debugAssertGCThingIsTenured(Register ptr, Register temp) {
8410 #ifdef DEBUG
8411 Label done;
8412 branchPtrInNurseryChunk(Assembler::NotEqual, ptr, temp, &done);
8413 assumeUnreachable("Expected a tenured pointer");
8414 bind(&done);
8415 #endif
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),
8430 label);
8433 void MacroAssembler::setIsPackedArray(Register obj, Register output,
8434 Register temp) {
8435 // Ensure it's an ArrayObject.
8436 Label notPackedArray;
8437 branchTestObjClass(Assembler::NotEqual, obj, &ArrayObject::class_, temp, obj,
8438 &notPackedArray);
8440 branchArrayIsNotPacked(obj, temp, output, &notPackedArray);
8442 Label done;
8443 move32(Imm32(1), output);
8444 jump(&done);
8446 bind(&notPackedArray);
8447 move32(Imm32(0), output);
8449 bind(&done);
8452 void MacroAssembler::packedArrayPop(Register array, ValueOperand output,
8453 Register temp1, Register temp2,
8454 Label* fail) {
8455 // Load obj->elements in temp1.
8456 loadPtr(Address(array, NativeObject::offsetOfElements()), temp1);
8458 // Check flags.
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, &notEmpty);
8477 moveValue(UndefinedValue(), output);
8478 jump(&done);
8481 bind(&notEmpty);
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);
8495 bind(&done);
8498 void MacroAssembler::packedArrayShift(Register array, ValueOperand output,
8499 Register temp1, Register temp2,
8500 LiveRegisterSet volatileRegs,
8501 Label* fail) {
8502 // Load obj->elements in temp1.
8503 loadPtr(Address(array, NativeObject::offsetOfElements()), temp1);
8505 // Check flags.
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, &notEmpty);
8524 moveValue(UndefinedValue(), output);
8525 jump(&done);
8528 bind(&notEmpty);
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);
8548 passABIArg(array);
8549 callWithABI<Fn, ArrayShiftMoveElements>();
8551 PopRegsInMask(volatileRegs);
8554 bind(&done);
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);
8569 // Bounds check.
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,
8583 Register index,
8584 ValueOperand output,
8585 Register temp,
8586 Label* fail) {
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);
8596 // Bounds check.
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);
8608 jump(&done);
8610 bind(&outOfBounds);
8611 branch32(Assembler::LessThan, index, Imm32(0), fail);
8612 moveValue(UndefinedValue(), output);
8614 bind(&done);
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,
8635 Label* fail) {
8636 // Get initial length value.
8637 unboxInt32(Address(obj, ArgumentsObject::getInitialLengthSlotOffset()),
8638 output);
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()),
8651 output);
8653 #ifdef DEBUG
8654 // Assert length hasn't been overridden.
8655 Label ok;
8656 branchTest32(Assembler::Zero, output,
8657 Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT), &ok);
8658 assumeUnreachable("arguments object length has been overridden");
8659 bind(&ok);
8660 #endif
8662 // Shift out arguments length and return it.
8663 rshift32(Imm32(ArgumentsObject::PACKED_BITS_COUNT), output);
8666 void MacroAssembler::branchTestArgumentsObjectFlags(Register obj, Register temp,
8667 uint32_t flags,
8668 Condition cond,
8669 Label* label) {
8670 MOZ_ASSERT((flags & ~ArgumentsObject::PACKED_BITS_MASK) == 0);
8672 // Get initial length value.
8673 unboxInt32(Address(obj, ArgumentsObject::getInitialLengthSlotOffset()), temp);
8675 // Test flags.
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)) {
8682 return false;
8685 return true;
8688 void MacroAssembler::typedArrayElementSize(Register obj, Register output) {
8689 loadObjClassUnsafe(obj, output);
8691 // Map resizable to fixed-length TypedArray classes.
8692 Label fixedLength;
8693 branchPtr(Assembler::Below, output,
8694 ImmPtr(std::end(TypedArrayObject::fixedLengthClasses)),
8695 &fixedLength);
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);
8708 ptrdiff_t diff =
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);
8717 bind(&fixedLength);
8719 #ifdef DEBUG
8720 Label invalidClass, validClass;
8721 branchPtr(Assembler::Below, output,
8722 ImmPtr(std::begin(TypedArrayObject::fixedLengthClasses)),
8723 &invalidClass);
8724 branchPtr(Assembler::Below, output,
8725 ImmPtr(std::end(TypedArrayObject::fixedLengthClasses)),
8726 &validClass);
8727 bind(&invalidClass);
8728 assumeUnreachable("value isn't a valid FixedLengthTypedArray class");
8729 bind(&validClass);
8730 #endif
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)),
8742 &one);
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)),
8747 &two);
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)),
8752 &four);
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)),
8762 &one);
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)),
8767 &eight);
8769 static_assert(
8770 ValidateSizeRange(Scalar::Float16, Scalar::MaxTypedArrayViewType),
8771 "element size is two in [Float16, MaxTypedArrayViewType)");
8772 jump(&two);
8774 bind(&eight);
8775 move32(Imm32(8), output);
8776 jump(&done);
8778 bind(&four);
8779 move32(Imm32(4), output);
8780 jump(&done);
8782 bind(&two);
8783 move32(Imm32(2), output);
8784 jump(&done);
8786 bind(&one);
8787 move32(Imm32(1), output);
8789 bind(&done);
8792 void MacroAssembler::resizableTypedArrayElementShiftBy(Register obj,
8793 Register output,
8794 Register scratch) {
8795 loadObjClassUnsafe(obj, scratch);
8797 #ifdef DEBUG
8798 Label invalidClass, validClass;
8799 branchPtr(Assembler::Below, scratch,
8800 ImmPtr(std::begin(TypedArrayObject::resizableClasses)),
8801 &invalidClass);
8802 branchPtr(Assembler::Below, scratch,
8803 ImmPtr(std::end(TypedArrayObject::resizableClasses)), &validClass);
8804 bind(&invalidClass);
8805 assumeUnreachable("value isn't a valid ResizableLengthTypedArray class");
8806 bind(&validClass);
8807 #endif
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)),
8819 &zero);
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)),
8824 &one);
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)),
8829 &two);
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)),
8839 &zero);
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)),
8844 &three);
8846 static_assert(
8847 ValidateSizeRange(Scalar::Float16, Scalar::MaxTypedArrayViewType),
8848 "element shift is one in [Float16, MaxTypedArrayViewType)");
8849 jump(&one);
8851 bind(&three);
8852 rshiftPtr(Imm32(3), output);
8853 jump(&zero);
8855 bind(&two);
8856 rshiftPtr(Imm32(2), output);
8857 jump(&zero);
8859 bind(&one);
8860 rshiftPtr(Imm32(1), output);
8862 bind(&zero);
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),
8878 notTypedArray);
8879 branchPtr(Assembler::Above, clasp, ImmPtr(lastTypedArrayClass),
8880 notTypedArray);
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),
8893 notTypedArray);
8894 branchPtr(Assembler::Above, clasp, ImmPtr(lastTypedArrayClass),
8895 notTypedArray);
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),
8908 notTypedArray);
8909 branchPtr(Assembler::Above, clasp, ImmPtr(lastTypedArrayClass),
8910 notTypedArray);
8913 void MacroAssembler::branchIfHasDetachedArrayBuffer(BranchIfDetached branchIf,
8914 Register obj, Register temp,
8915 Label* label) {
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);
8926 Label done;
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,
8942 ifNotDetached);
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) {
8949 bind(&done);
8953 void MacroAssembler::branchIfResizableArrayBufferViewOutOfBounds(Register obj,
8954 Register temp,
8955 Label* label) {
8956 // Implementation of ArrayBufferViewObject::isOutOfBounds().
8958 Label done;
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()),
8970 temp);
8971 branchPtr(Assembler::NotEqual, temp, ImmWord(0), label);
8973 bind(&done);
8976 void MacroAssembler::branchIfResizableArrayBufferViewInBounds(Register obj,
8977 Register temp,
8978 Label* label) {
8979 // Implementation of ArrayBufferViewObject::isOutOfBounds().
8981 Label done;
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()),
8993 temp);
8994 branchPtr(Assembler::Equal, temp, ImmWord(0), label);
8996 bind(&done);
8999 void MacroAssembler::branchIfNativeIteratorNotReusable(Register ni,
9000 Label* notReusable) {
9001 // See NativeIterator::isReusable.
9002 Address flagsAddr(ni, NativeIterator::offsetOfFlagsAndCount());
9004 #ifdef DEBUG
9005 Label niIsInitialized;
9006 branchTest32(Assembler::NonZero, flagsAddr,
9007 Imm32(NativeIterator::Flags::Initialized), &niIsInitialized);
9008 assumeUnreachable(
9009 "Expected a NativeIterator that's been completely "
9010 "initialized");
9011 bind(&niIsInitialized);
9012 #endif
9014 branchTest32(Assembler::NonZero, flagsAddr,
9015 Imm32(NativeIterator::Flags::NotReusable), notReusable);
9018 void MacroAssembler::branchNativeIteratorIndices(Condition cond, Register ni,
9019 Register temp,
9020 NativeIteratorIndices kind,
9021 Label* label) {
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,
9030 Register dest) {
9031 MOZ_ASSERT(obj != dest);
9033 #ifdef DEBUG
9034 // Assert we have a PropertyIteratorObject.
9035 Label ok;
9036 masm.branchTestObjClass(Assembler::Equal, obj,
9037 &PropertyIteratorObject::class_, dest, obj, &ok);
9038 masm.assumeUnreachable("Expected PropertyIteratorObject!");
9039 masm.bind(&ok);
9040 #endif
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
9053 // shapes
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,
9058 Register temp3,
9059 Label* failure) {
9060 // Register usage:
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
9067 Label 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.
9081 #ifdef DEBUG
9082 Label nonNative;
9083 branchIfNonNativeObj(obj, temp3, &nonNative);
9084 #endif
9086 // Verify that |obj| has no dense elements.
9087 loadPtr(Address(obj, NativeObject::offsetOfElements()), temp3);
9088 branch32(Assembler::NotEqual,
9089 Address(temp3, ObjectElements::offsetOfInitializedLength()),
9090 Imm32(0), failure);
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
9111 // its proto.
9112 Label protoLoop;
9113 bind(&protoLoop);
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);
9120 #ifdef DEBUG
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);
9124 #endif
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()),
9130 Imm32(0), failure);
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);
9139 jump(&protoLoop);
9141 #ifdef DEBUG
9142 bind(&nonNative);
9143 assumeUnreachable("Expected NativeObject in maybeLoadIteratorFromShape");
9144 #endif
9146 bind(&success);
9149 void MacroAssembler::iteratorMore(Register obj, ValueOperand output,
9150 Register temp) {
9151 Label done;
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).
9157 Label iterDone;
9158 Address cursorAddr(outputScratch, NativeIterator::offsetOfPropertyCursor());
9159 Address cursorEndAddr(outputScratch, NativeIterator::offsetOfPropertiesEnd());
9160 loadPtr(cursorAddr, temp);
9161 branchPtr(Assembler::BelowOrEqual, cursorEndAddr, temp, &iterDone);
9163 // Get next string.
9164 loadPtr(Address(temp, 0), temp);
9166 // Increase the cursor.
9167 addPtr(Imm32(sizeof(GCPtr<JSLinearString*>)), cursorAddr);
9169 tagValue(JSVAL_TYPE_STRING, temp, output);
9170 jump(&done);
9172 bind(&iterDone);
9173 moveValue(MagicValue(JS_NO_ITER_VALUE), output);
9175 bind(&done);
9178 void MacroAssembler::iteratorClose(Register obj, Register temp1, Register temp2,
9179 Register temp3) {
9180 LoadNativeIterator(*this, obj, temp1);
9182 // The shared iterator used for for-in with null/undefined is immutable and
9183 // unlinked. See NativeIterator::isEmptyIteratorSingleton.
9184 Label done;
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()));
9209 #ifdef DEBUG
9210 storePtr(ImmPtr(nullptr), Address(temp1, NativeIterator::offsetOfNext()));
9211 storePtr(ImmPtr(nullptr), Address(temp1, NativeIterator::offsetOfPrev()));
9212 #endif
9214 bind(&done);
9217 void MacroAssembler::registerIterator(Register enumeratorsList, Register iter,
9218 Register temp) {
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()|.
9238 #ifdef DEBUG
9239 Label ok;
9240 branchTestGCThing(Assembler::NotEqual, value, &ok);
9241 assumeUnreachable("Unexpected GC thing");
9242 bind(&ok);
9243 #endif
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.
9252 Label canonicalize;
9253 convertDoubleToInt32(tempFloat, int32, &canonicalize, false);
9255 tagValue(JSVAL_TYPE_INT32, int32, result);
9256 jump(&done);
9258 bind(&canonicalize);
9260 // Normalize the sign bit of a NaN.
9261 branchDouble(Assembler::DoubleOrdered, tempFloat, tempFloat, &useInput);
9262 moveValue(JS::NaNValue(), result);
9263 jump(&done);
9267 bind(&useInput);
9268 moveValue(value, result);
9270 bind(&done);
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, &notString);
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);
9293 bind(tagString);
9295 tagValue(JSVAL_TYPE_STRING, str, result);
9296 jump(&done);
9298 bind(&notString);
9299 branchTestDouble(Assembler::NotEqual, tag, &useInput);
9301 ScratchTagScopeRelease _(&tag);
9303 Register int32 = result.scratchReg();
9304 unboxDouble(value, tempFloat);
9306 Label canonicalize;
9307 convertDoubleToInt32(tempFloat, int32, &canonicalize, false);
9309 tagValue(JSVAL_TYPE_INT32, int32, result);
9310 jump(&done);
9312 bind(&canonicalize);
9314 branchDouble(Assembler::DoubleOrdered, tempFloat, tempFloat, &useInput);
9315 moveValue(JS::NaNValue(), result);
9316 jump(&done);
9320 bind(&useInput);
9321 moveValue(value, result);
9323 bind(&done);
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,
9333 Register temp) {
9334 // Inline implementation of |OrderedHashTableImpl::prepareHash()| and
9335 // |mozilla::HashGeneric(v.asRawBits())|.
9337 #ifdef DEBUG
9338 Label ok;
9339 branchTestGCThing(Assembler::NotEqual, value, &ok);
9340 assumeUnreachable("Unexpected GC thing");
9341 bind(&ok);
9342 #endif
9344 // uint32_t v1 = static_cast<uint32_t>(aValue);
9345 #ifdef JS_PUNBOX64
9346 move64To32(value.toRegister64(), result);
9347 #else
9348 move32(value.payloadReg(), result);
9349 #endif
9351 // uint32_t v2 = static_cast<uint32_t>(static_cast<uint64_t>(aValue) >> 32);
9352 #ifdef JS_PUNBOX64
9353 auto r64 = Register64(temp);
9354 move64(value.toRegister64(), r64);
9355 rshift64Arithmetic(Imm32(32), r64);
9356 #else
9357 move32(value.typeReg(), temp);
9358 #endif
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,
9379 Register temp) {
9380 // Inline implementation of |OrderedHashTableImpl::prepareHash()| and
9381 // |JSAtom::hash()|.
9383 #ifdef DEBUG
9384 Label ok;
9385 branchTest32(Assembler::NonZero, Address(str, JSString::offsetOfFlags()),
9386 Imm32(JSString::ATOM_BIT), &ok);
9387 assumeUnreachable("Unexpected non-atom string");
9388 bind(&ok);
9389 #endif
9391 #ifdef JS_64BIT
9392 static_assert(FatInlineAtom::offsetOfHash() == NormalAtom::offsetOfHash());
9393 load32(Address(str, NormalAtom::offsetOfHash()), result);
9394 #else
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)) {
9412 load32(
9413 BaseIndex(str, result, ShiftToScale(shift), NormalAtom::offsetOfHash()),
9414 result);
9415 } else {
9416 lshift32(Imm32(shift), result);
9417 load32(BaseIndex(str, result, TimesOne, NormalAtom::offsetOfHash()),
9418 result);
9420 #endif
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,
9436 Register temp3) {
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);
9454 Label start, loop;
9455 jump(&start);
9456 bind(&loop);
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)));
9466 #elif JS_PUNBOX64
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);
9476 #else
9477 addU32ToHash(Address(temp2, 0));
9478 #endif
9480 addPtr(Imm32(sizeof(BigInt::Digit)), temp2);
9482 bind(&start);
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,
9502 Register temp4) {
9503 #ifdef JS_PUNBOX64
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);
9526 auto v2 = k0;
9527 auto v3 = k1;
9529 auto sipRound = [&]() {
9530 // mV0 = WrappingAdd(mV0, mV1);
9531 add64(v1, v0);
9533 // mV1 = RotateLeft(mV1, 13);
9534 rotateLeft64(Imm32(13), v1, v1, InvalidReg);
9536 // mV1 ^= mV0;
9537 xor64(v0, v1);
9539 // mV0 = RotateLeft(mV0, 32);
9540 rotateLeft64(Imm32(32), v0, v0, InvalidReg);
9542 // mV2 = WrappingAdd(mV2, mV3);
9543 add64(v3, v2);
9545 // mV3 = RotateLeft(mV3, 16);
9546 rotateLeft64(Imm32(16), v3, v3, InvalidReg);
9548 // mV3 ^= mV2;
9549 xor64(v2, v3);
9551 // mV0 = WrappingAdd(mV0, mV3);
9552 add64(v3, v0);
9554 // mV3 = RotateLeft(mV3, 21);
9555 rotateLeft64(Imm32(21), v3, v3, InvalidReg);
9557 // mV3 ^= mV0;
9558 xor64(v0, v3);
9560 // mV2 = WrappingAdd(mV2, mV1);
9561 add64(v1, v2);
9563 // mV1 = RotateLeft(mV1, 17);
9564 rotateLeft64(Imm32(17), v1, v1, InvalidReg);
9566 // mV1 ^= mV2;
9567 xor64(v2, v1);
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);
9576 xor64(k0, v0);
9578 // mV1 = aK1 ^ UINT64_C(0x646f72616e646f6d);
9579 move64(Imm64(0x646f72616e646f6d), v1);
9580 xor64(k1, 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);
9590 // 2. Compression.
9591 // mV3 ^= aM;
9592 xor64(m, v3);
9594 // sipRound();
9595 sipRound();
9597 // mV0 ^= aM;
9598 xor64(m, v0);
9600 // 3. Finalization.
9601 // mV2 ^= 0xff;
9602 xor64(Imm64(0xff), v2);
9604 // for (int i = 0; i < 3; i++) sipRound();
9605 for (int i = 0; i < 3; i++) {
9606 sipRound();
9609 // return mV0 ^ mV1 ^ mV2 ^ mV3;
9610 xor64(v1, v0);
9611 xor64(v2, v3);
9612 xor64(v3, v0);
9614 move64To32(v0, result);
9616 scrambleHashCode(result);
9617 #else
9618 MOZ_CRASH("Not implemented");
9619 #endif
9622 void MacroAssembler::prepareHashValue(Register setObj, ValueOperand value,
9623 Register result, Register temp1,
9624 Register temp2, Register temp3,
9625 Register temp4) {
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);
9637 Label done;
9639 prepareHashNonGCThing(value, result, temp1);
9640 jump(&done);
9642 bind(&isString);
9644 unboxString(value, temp1);
9645 prepareHashString(temp1, result, temp2);
9646 jump(&done);
9648 bind(&isObject);
9650 prepareHashObject(setObj, value, result, temp1, temp2, temp3, temp4);
9651 jump(&done);
9653 bind(&isSymbol);
9655 unboxSymbol(value, temp1);
9656 prepareHashSymbol(temp1, result);
9657 jump(&done);
9659 bind(&isBigInt);
9661 unboxBigInt(value, temp1);
9662 prepareHashBigInt(temp1, result, temp2, temp3, temp4);
9664 // Fallthrough to |done|.
9667 bind(&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);
9682 #ifdef DEBUG
9683 Label ok;
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");
9691 bind(&ok);
9692 #endif
9694 #ifdef DEBUG
9695 PushRegsInMask(LiveRegisterSet(RegisterSet::Volatile()));
9697 pushValue(value);
9698 moveStackPtrTo(temp2);
9700 setupUnalignedABICall(temp1);
9701 loadJSContext(temp1);
9702 passABIArg(temp1);
9703 passABIArg(setOrMapObj);
9704 passABIArg(temp2);
9705 passABIArg(hash);
9707 if constexpr (std::is_same_v<TableObject, SetObject>) {
9708 using Fn =
9709 void (*)(JSContext*, SetObject*, const Value*, mozilla::HashNumber);
9710 callWithABI<Fn, jit::AssertSetObjectHash>();
9711 } else {
9712 static_assert(std::is_same_v<TableObject, MapObject>);
9713 using Fn =
9714 void (*)(JSContext*, MapObject*, const Value*, mozilla::HashNumber);
9715 callWithABI<Fn, jit::AssertMapObjectHash>();
9718 popValue(value);
9719 PopRegsInMask(LiveRegisterSet(RegisterSet::Volatile()));
9720 #endif
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.
9732 Label start, loop;
9733 jump(&start);
9734 bind(&loop);
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);
9745 } else {
9746 #ifdef JS_PUNBOX64
9747 auto key = ValueOperand(temp1);
9748 #else
9749 auto key = ValueOperand(temp1, temp2);
9750 #endif
9752 loadValue(keyAddr, key);
9754 // Two HashableValues are equal if they have equal bits.
9755 branch64(Assembler::Equal, key.toRegister64(), value.toRegister64(),
9756 found);
9758 // BigInt values are considered equal if they represent the same
9759 // mathematical value.
9760 Label next;
9761 fallibleUnboxBigInt(key, temp2, &next);
9762 if (isBigInt == IsBigInt::Yes) {
9763 unboxBigInt(value, temp1);
9764 } else {
9765 fallibleUnboxBigInt(value, temp1, &next);
9767 equalBigInts(temp1, temp2, temp3, temp4, temp1, temp2, &next, &next,
9768 &next);
9769 jump(found);
9770 bind(&next);
9773 loadPtr(Address(entryTemp, TableObject::Table::offsetOfImplDataChain()),
9774 entryTemp);
9775 bind(&start);
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) {
9784 Label found;
9785 orderedHashTableLookup<SetObject>(setObj, value, hash, result, temp1, temp2,
9786 temp3, temp4, &found, isBigInt);
9788 Label done;
9789 move32(Imm32(0), result);
9790 jump(&done);
9792 bind(&found);
9793 move32(Imm32(1), result);
9794 bind(&done);
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) {
9802 Label found;
9803 orderedHashTableLookup<MapObject>(mapObj, value, hash, result, temp1, temp2,
9804 temp3, temp4, &found, isBigInt);
9806 Label done;
9807 move32(Imm32(0), result);
9808 jump(&done);
9810 bind(&found);
9811 move32(Imm32(1), result);
9812 bind(&done);
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) {
9820 Label found;
9821 orderedHashTableLookup<MapObject>(mapObj, value, hash, temp1, temp2, temp3,
9822 temp4, temp5, &found, isBigInt);
9824 Label done;
9825 moveValue(UndefinedValue(), result);
9826 jump(&done);
9828 // |temp1| holds the found entry.
9829 bind(&found);
9830 loadValue(Address(temp1, MapObject::Table::Entry::offsetOfValue()), result);
9832 bind(&done);
9835 template <typename TableObject>
9836 void MacroAssembler::loadOrderedHashTableCount(Register setOrMapObj,
9837 Register result) {
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,
9886 Register temp) {
9887 canonicalizeDouble(src);
9889 # ifdef JS_PUNBOX64
9890 Register64 r64(temp);
9891 # else
9892 Register64 r64(temp, result);
9893 # endif
9895 moveDoubleToGPR64(src, r64);
9897 # ifdef JS_PUNBOX64
9898 // Move the high word into |result|.
9899 move64(r64, Register64(result));
9900 rshift64(Imm32(32), Register64(result));
9901 # endif
9903 // Add the high and low words of |r64|.
9904 add32(temp, result);
9907 void MacroAssembler::fuzzilliStoreHash(Register value, Register temp1,
9908 Register temp2) {
9909 loadJSContext(temp1);
9911 // stats
9912 Address addrExecHashInputs(temp1, offsetof(JSContext, executionHashInputs));
9913 add32(Imm32(1), addrExecHashInputs);
9915 // hash
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);
9922 #endif
9924 namespace js {
9925 namespace jit {
9927 #ifdef DEBUG
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);
9939 #endif // DEBUG
9941 #ifdef DEBUG
9942 template <class RegisterType>
9943 AutoGenericRegisterScope<RegisterType>::~AutoGenericRegisterScope() {
9944 if (!released_) {
9945 release();
9949 template AutoGenericRegisterScope<Register>::~AutoGenericRegisterScope();
9950 template AutoGenericRegisterScope<FloatRegister>::~AutoGenericRegisterScope();
9952 template <class RegisterType>
9953 void AutoGenericRegisterScope<RegisterType>::release() {
9954 MOZ_ASSERT(!released_);
9955 released_ = true;
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_);
9966 released_ = false;
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();
9974 #endif // DEBUG
9976 } // namespace jit
9978 } // namespace js