Bug 1874684 - Part 29: Update spec fixme notes. r=mgaudet
[gecko.git] / js / src / jit / Safepoints.cpp
blobdb1c05b70ce48b96a3046a3b4d9404a7384deae3
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/Safepoints.h"
9 #include "mozilla/MathAlgorithms.h"
11 #include "jit/BitSet.h"
12 #include "jit/IonScript.h"
13 #include "jit/JitSpewer.h"
14 #include "jit/LIR.h"
15 #include "jit/SafepointIndex.h"
17 using namespace js;
18 using namespace jit;
20 using mozilla::FloorLog2;
22 SafepointWriter::SafepointWriter(uint32_t localSlotsSize,
23 uint32_t argumentsSize)
24 : localSlots_((localSlotsSize / sizeof(intptr_t)) +
25 1), // Stack slot counts are inclusive.
26 argumentSlots_(argumentsSize / sizeof(intptr_t)) {}
28 bool SafepointWriter::init(TempAllocator& alloc) {
29 return localSlots_.init(alloc) && argumentSlots_.init(alloc);
32 uint32_t SafepointWriter::startEntry() {
33 JitSpew(JitSpew_Safepoints,
34 "Encoding safepoint (position %zu):", stream_.length());
35 return uint32_t(stream_.length());
38 void SafepointWriter::writeOsiCallPointOffset(uint32_t osiCallPointOffset) {
39 stream_.writeUnsigned(osiCallPointOffset);
42 static void WriteRegisterMask(CompactBufferWriter& stream,
43 PackedRegisterMask bits) {
44 if (sizeof(PackedRegisterMask) == 1) {
45 stream.writeByte(bits);
46 } else {
47 MOZ_ASSERT(sizeof(PackedRegisterMask) <= 4);
48 stream.writeUnsigned(bits);
52 static PackedRegisterMask ReadRegisterMask(CompactBufferReader& stream) {
53 if (sizeof(PackedRegisterMask) == 1) {
54 return stream.readByte();
56 MOZ_ASSERT(sizeof(PackedRegisterMask) <= 4);
57 return stream.readUnsigned();
60 static void WriteFloatRegisterMask(CompactBufferWriter& stream,
61 FloatRegisters::SetType bits) {
62 switch (sizeof(FloatRegisters::SetType)) {
63 #ifdef JS_CODEGEN_ARM64
64 case 16:
65 stream.writeUnsigned64(bits.low());
66 stream.writeUnsigned64(bits.high());
67 break;
68 #else
69 case 1:
70 stream.writeByte(bits);
71 break;
72 case 4:
73 stream.writeUnsigned(bits);
74 break;
75 case 8:
76 stream.writeUnsigned64(bits);
77 break;
78 #endif
79 default:
80 MOZ_CRASH("WriteFloatRegisterMask: unexpected size");
84 static FloatRegisters::SetType ReadFloatRegisterMask(
85 CompactBufferReader& stream) {
86 switch (sizeof(FloatRegisters::SetType)) {
87 #ifdef JS_CODEGEN_ARM64
88 case 16: {
89 uint64_t low = stream.readUnsigned64();
90 uint64_t high = stream.readUnsigned64();
91 return Bitset128(high, low);
93 #else
94 case 1:
95 return stream.readByte();
96 case 2:
97 case 3:
98 case 4:
99 return stream.readUnsigned();
100 case 8:
101 return stream.readUnsigned64();
102 #endif
103 default:
104 MOZ_CRASH("ReadFloatRegisterMask: unexpected size");
108 void SafepointWriter::writeGcRegs(LSafepoint* safepoint) {
109 LiveGeneralRegisterSet gc(safepoint->gcRegs());
110 LiveGeneralRegisterSet spilledGpr(safepoint->liveRegs().gprs());
111 LiveFloatRegisterSet spilledFloat(safepoint->liveRegs().fpus());
112 LiveGeneralRegisterSet slots(safepoint->slotsOrElementsRegs());
113 LiveGeneralRegisterSet wasmAnyRef(safepoint->wasmAnyRefRegs());
114 LiveGeneralRegisterSet valueRegs;
116 WriteRegisterMask(stream_, spilledGpr.bits());
117 if (!spilledGpr.empty()) {
118 WriteRegisterMask(stream_, gc.bits());
119 WriteRegisterMask(stream_, slots.bits());
120 WriteRegisterMask(stream_, wasmAnyRef.bits());
122 #ifdef JS_PUNBOX64
123 valueRegs = safepoint->valueRegs();
124 WriteRegisterMask(stream_, valueRegs.bits());
125 #endif
128 // GC registers are a subset of the spilled registers.
129 MOZ_ASSERT((valueRegs.bits() & ~spilledGpr.bits()) == 0);
130 MOZ_ASSERT((gc.bits() & ~spilledGpr.bits()) == 0);
132 WriteFloatRegisterMask(stream_, spilledFloat.bits());
134 #ifdef JS_JITSPEW
135 if (JitSpewEnabled(JitSpew_Safepoints)) {
136 for (GeneralRegisterForwardIterator iter(spilledGpr); iter.more(); ++iter) {
137 const char* type = gc.has(*iter) ? "gc"
138 : slots.has(*iter) ? "slots"
139 : valueRegs.has(*iter) ? "value"
140 : "any";
141 JitSpew(JitSpew_Safepoints, " %s reg: %s", type, (*iter).name());
143 for (FloatRegisterForwardIterator iter(spilledFloat); iter.more(); ++iter) {
144 JitSpew(JitSpew_Safepoints, " float reg: %s", (*iter).name());
147 #endif
150 static void WriteBitset(const BitSet& set, CompactBufferWriter& stream) {
151 size_t count = set.rawLength();
152 const uint32_t* words = set.raw();
153 for (size_t i = 0; i < count; i++) {
154 stream.writeUnsigned(words[i]);
158 static void MapSlotsToBitset(BitSet& stackSet, BitSet& argumentSet,
159 CompactBufferWriter& stream,
160 const LSafepoint::SlotList& slots) {
161 stackSet.clear();
162 argumentSet.clear();
164 for (uint32_t i = 0; i < slots.length(); i++) {
165 // Slots are represented at a distance from |fp|. We divide by the
166 // pointer size, since we only care about pointer-sized/aligned slots
167 // here.
168 MOZ_ASSERT(slots[i].slot % sizeof(intptr_t) == 0);
169 size_t index = slots[i].slot / sizeof(intptr_t);
170 (slots[i].stack ? stackSet : argumentSet).insert(index);
173 WriteBitset(stackSet, stream);
174 WriteBitset(argumentSet, stream);
177 void SafepointWriter::writeGcSlots(LSafepoint* safepoint) {
178 LSafepoint::SlotList& slots = safepoint->gcSlots();
180 #ifdef JS_JITSPEW
181 for (uint32_t i = 0; i < slots.length(); i++) {
182 JitSpew(JitSpew_Safepoints, " gc slot: %u", slots[i].slot);
184 #endif
186 MapSlotsToBitset(localSlots_, argumentSlots_, stream_, slots);
189 void SafepointWriter::writeSlotsOrElementsSlots(LSafepoint* safepoint) {
190 LSafepoint::SlotList& slots = safepoint->slotsOrElementsSlots();
192 stream_.writeUnsigned(slots.length());
194 for (uint32_t i = 0; i < slots.length(); i++) {
195 if (!slots[i].stack) {
196 MOZ_CRASH();
198 #ifdef JS_JITSPEW
199 JitSpew(JitSpew_Safepoints, " slots/elements slot: %u", slots[i].slot);
200 #endif
201 stream_.writeUnsigned(slots[i].slot);
205 void SafepointWriter::writeWasmAnyRefSlots(LSafepoint* safepoint) {
206 LSafepoint::SlotList& slots = safepoint->wasmAnyRefSlots();
208 stream_.writeUnsigned(slots.length());
210 for (uint32_t i = 0; i < slots.length(); i++) {
211 if (!slots[i].stack) {
212 MOZ_CRASH();
214 #ifdef JS_JITSPEW
215 JitSpew(JitSpew_Safepoints, " wasm_anyref slot: %u", slots[i].slot);
216 #endif
217 stream_.writeUnsigned(slots[i].slot);
221 #ifdef JS_PUNBOX64
222 void SafepointWriter::writeValueSlots(LSafepoint* safepoint) {
223 LSafepoint::SlotList& slots = safepoint->valueSlots();
225 # ifdef JS_JITSPEW
226 for (uint32_t i = 0; i < slots.length(); i++) {
227 JitSpew(JitSpew_Safepoints, " gc value: %u", slots[i].slot);
229 # endif
231 MapSlotsToBitset(localSlots_, argumentSlots_, stream_, slots);
233 #endif
235 #if defined(JS_JITSPEW) && defined(JS_NUNBOX32)
236 static void DumpNunboxPart(const LAllocation& a) {
237 Fprinter& out = JitSpewPrinter();
238 if (a.isStackSlot()) {
239 out.printf("stack %d", a.toStackSlot()->slot());
240 } else if (a.isArgument()) {
241 out.printf("arg %d", a.toArgument()->index());
242 } else {
243 out.printf("reg %s", a.toGeneralReg()->reg().name());
246 #endif // DEBUG
248 // Nunbox part encoding:
250 // Reg = 000
251 // Stack = 001
252 // Arg = 010
254 // [vwu] nentries:
255 // uint16_t: tttp ppXX XXXY YYYY
257 // If ttt = Reg, type is reg XXXXX
258 // If ppp = Reg, payload is reg YYYYY
260 // If ttt != Reg, type is:
261 // XXXXX if not 11111, otherwise followed by [vwu]
262 // If ppp != Reg, payload is:
263 // YYYYY if not 11111, otherwise followed by [vwu]
265 enum NunboxPartKind { Part_Reg, Part_Stack, Part_Arg };
267 static const uint32_t PART_KIND_BITS = 3;
268 static const uint32_t PART_KIND_MASK = (1 << PART_KIND_BITS) - 1;
269 static const uint32_t PART_INFO_BITS = 5;
270 static const uint32_t PART_INFO_MASK = (1 << PART_INFO_BITS) - 1;
272 static const uint32_t MAX_INFO_VALUE = (1 << PART_INFO_BITS) - 1;
273 static const uint32_t TYPE_KIND_SHIFT = 16 - PART_KIND_BITS;
274 static const uint32_t PAYLOAD_KIND_SHIFT = TYPE_KIND_SHIFT - PART_KIND_BITS;
275 static const uint32_t TYPE_INFO_SHIFT = PAYLOAD_KIND_SHIFT - PART_INFO_BITS;
276 static const uint32_t PAYLOAD_INFO_SHIFT = TYPE_INFO_SHIFT - PART_INFO_BITS;
278 static_assert(PAYLOAD_INFO_SHIFT == 0);
280 #ifdef JS_NUNBOX32
281 static inline NunboxPartKind AllocationToPartKind(const LAllocation& a) {
282 if (a.isRegister()) {
283 return Part_Reg;
285 if (a.isStackSlot()) {
286 return Part_Stack;
288 MOZ_ASSERT(a.isArgument());
289 return Part_Arg;
292 // gcc 4.5 doesn't actually inline CanEncodeInfoInHeader when only
293 // using the "inline" keyword, and miscompiles the function as well
294 // when doing block reordering with branch prediction information.
295 // See bug 799295 comment 71.
296 static MOZ_ALWAYS_INLINE bool CanEncodeInfoInHeader(const LAllocation& a,
297 uint32_t* out) {
298 if (a.isGeneralReg()) {
299 *out = a.toGeneralReg()->reg().code();
300 return true;
303 if (a.isStackSlot()) {
304 *out = a.toStackSlot()->slot();
305 } else {
306 *out = a.toArgument()->index();
309 return *out < MAX_INFO_VALUE;
312 void SafepointWriter::writeNunboxParts(LSafepoint* safepoint) {
313 LSafepoint::NunboxList& entries = safepoint->nunboxParts();
315 # ifdef JS_JITSPEW
316 if (JitSpewEnabled(JitSpew_Safepoints)) {
317 for (uint32_t i = 0; i < entries.length(); i++) {
318 SafepointNunboxEntry& entry = entries[i];
319 if (entry.type.isUse() || entry.payload.isUse()) {
320 continue;
322 JitSpewHeader(JitSpew_Safepoints);
323 Fprinter& out = JitSpewPrinter();
324 out.printf(" nunbox (type in ");
325 DumpNunboxPart(entry.type);
326 out.printf(", payload in ");
327 DumpNunboxPart(entry.payload);
328 out.printf(")\n");
331 # endif
333 // Safepoints are permitted to have partially filled in entries for nunboxes,
334 // provided that only the type is live and not the payload. Omit these from
335 // the written safepoint.
337 size_t pos = stream_.length();
338 stream_.writeUnsigned(entries.length());
340 size_t count = 0;
341 for (size_t i = 0; i < entries.length(); i++) {
342 SafepointNunboxEntry& entry = entries[i];
344 if (entry.payload.isUse()) {
345 // No allocation associated with the payload.
346 continue;
349 if (entry.type.isUse()) {
350 // No allocation associated with the type. Look for another
351 // safepoint entry with an allocation for the type.
352 entry.type = safepoint->findTypeAllocation(entry.typeVreg);
353 if (entry.type.isUse()) {
354 continue;
358 count++;
360 uint16_t header = 0;
362 header |= (AllocationToPartKind(entry.type) << TYPE_KIND_SHIFT);
363 header |= (AllocationToPartKind(entry.payload) << PAYLOAD_KIND_SHIFT);
365 uint32_t typeVal;
366 bool typeExtra = !CanEncodeInfoInHeader(entry.type, &typeVal);
367 if (!typeExtra) {
368 header |= (typeVal << TYPE_INFO_SHIFT);
369 } else {
370 header |= (MAX_INFO_VALUE << TYPE_INFO_SHIFT);
373 uint32_t payloadVal;
374 bool payloadExtra = !CanEncodeInfoInHeader(entry.payload, &payloadVal);
375 if (!payloadExtra) {
376 header |= (payloadVal << PAYLOAD_INFO_SHIFT);
377 } else {
378 header |= (MAX_INFO_VALUE << PAYLOAD_INFO_SHIFT);
381 stream_.writeFixedUint16_t(header);
382 if (typeExtra) {
383 stream_.writeUnsigned(typeVal);
385 if (payloadExtra) {
386 stream_.writeUnsigned(payloadVal);
390 // Update the stream with the actual number of safepoint entries written.
391 stream_.writeUnsignedAt(pos, count, entries.length());
393 #endif
395 void SafepointWriter::encode(LSafepoint* safepoint) {
396 uint32_t safepointOffset = startEntry();
398 MOZ_ASSERT(safepoint->osiCallPointOffset());
400 writeOsiCallPointOffset(safepoint->osiCallPointOffset());
401 writeGcRegs(safepoint);
402 writeGcSlots(safepoint);
404 #ifdef JS_PUNBOX64
405 writeValueSlots(safepoint);
406 #else
407 writeNunboxParts(safepoint);
408 #endif
410 writeSlotsOrElementsSlots(safepoint);
411 writeWasmAnyRefSlots(safepoint);
413 endEntry();
414 safepoint->setOffset(safepointOffset);
417 void SafepointWriter::endEntry() {
418 JitSpew(JitSpew_Safepoints, " -- entry ended at %u",
419 uint32_t(stream_.length()));
422 SafepointReader::SafepointReader(IonScript* script, const SafepointIndex* si)
423 : stream_(script->safepoints() + si->safepointOffset(),
424 script->safepoints() + script->safepointsSize()),
425 localSlots_((script->localSlotsSize() / sizeof(intptr_t)) +
426 1), // Stack slot counts are inclusive.
427 argumentSlots_(script->argumentSlotsSize() / sizeof(intptr_t)),
428 nunboxSlotsRemaining_(0),
429 slotsOrElementsSlotsRemaining_(0),
430 wasmAnyRefSlotsRemaining_(0) {
431 osiCallPointOffset_ = stream_.readUnsigned();
433 // gcSpills is a subset of allGprSpills.
434 allGprSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_));
435 if (allGprSpills_.empty()) {
436 gcSpills_ = allGprSpills_;
437 valueSpills_ = allGprSpills_;
438 slotsOrElementsSpills_ = allGprSpills_;
439 wasmAnyRefSpills_ = allGprSpills_;
440 } else {
441 gcSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_));
442 slotsOrElementsSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_));
443 wasmAnyRefSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_));
444 #ifdef JS_PUNBOX64
445 valueSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_));
446 #endif
449 allFloatSpills_ = FloatRegisterSet(ReadFloatRegisterMask(stream_));
451 advanceFromGcRegs();
454 uint32_t SafepointReader::osiReturnPointOffset() const {
455 return osiCallPointOffset_ + Assembler::PatchWrite_NearCallSize();
458 CodeLocationLabel SafepointReader::InvalidationPatchPoint(
459 IonScript* script, const SafepointIndex* si) {
460 SafepointReader reader(script, si);
462 return CodeLocationLabel(script->method(),
463 CodeOffset(reader.osiCallPointOffset()));
466 void SafepointReader::advanceFromGcRegs() {
467 currentSlotChunk_ = 0;
468 nextSlotChunkNumber_ = 0;
469 currentSlotsAreStack_ = true;
472 bool SafepointReader::getSlotFromBitmap(SafepointSlotEntry* entry) {
473 while (currentSlotChunk_ == 0) {
474 // Are there any more chunks to read?
475 if (currentSlotsAreStack_) {
476 if (nextSlotChunkNumber_ == BitSet::RawLengthForBits(localSlots_)) {
477 nextSlotChunkNumber_ = 0;
478 currentSlotsAreStack_ = false;
479 continue;
481 } else if (nextSlotChunkNumber_ ==
482 BitSet::RawLengthForBits(argumentSlots_)) {
483 return false;
486 // Yes, read the next chunk.
487 currentSlotChunk_ = stream_.readUnsigned();
488 nextSlotChunkNumber_++;
491 // The current chunk still has bits in it, so get the next bit, then mask
492 // it out of the slot chunk.
493 uint32_t bit = FloorLog2(currentSlotChunk_);
494 currentSlotChunk_ &= ~(1 << bit);
496 // Return the slot, and re-scale it by the pointer size, reversing the
497 // transformation in MapSlotsToBitset.
498 entry->stack = currentSlotsAreStack_;
499 entry->slot = (((nextSlotChunkNumber_ - 1) * BitSet::BitsPerWord) + bit) *
500 sizeof(intptr_t);
501 return true;
504 bool SafepointReader::getGcSlot(SafepointSlotEntry* entry) {
505 if (getSlotFromBitmap(entry)) {
506 return true;
508 advanceFromGcSlots();
509 return false;
512 void SafepointReader::advanceFromGcSlots() {
513 // No, reset the counter.
514 currentSlotChunk_ = 0;
515 nextSlotChunkNumber_ = 0;
516 currentSlotsAreStack_ = true;
517 #ifdef JS_NUNBOX32
518 // Nunbox slots are next.
519 nunboxSlotsRemaining_ = stream_.readUnsigned();
520 #else
521 // Value slots are next.
522 #endif
525 bool SafepointReader::getValueSlot(SafepointSlotEntry* entry) {
526 if (getSlotFromBitmap(entry)) {
527 return true;
529 advanceFromNunboxOrValueSlots();
530 return false;
533 static inline LAllocation PartFromStream(CompactBufferReader& stream,
534 NunboxPartKind kind, uint32_t info) {
535 if (kind == Part_Reg) {
536 return LGeneralReg(Register::FromCode(info));
539 if (info == MAX_INFO_VALUE) {
540 info = stream.readUnsigned();
543 if (kind == Part_Stack) {
544 return LStackSlot(info);
547 MOZ_ASSERT(kind == Part_Arg);
548 return LArgument(info);
551 bool SafepointReader::getNunboxSlot(LAllocation* type, LAllocation* payload) {
552 if (!nunboxSlotsRemaining_--) {
553 advanceFromNunboxOrValueSlots();
554 return false;
557 uint16_t header = stream_.readFixedUint16_t();
558 NunboxPartKind typeKind =
559 (NunboxPartKind)((header >> TYPE_KIND_SHIFT) & PART_KIND_MASK);
560 NunboxPartKind payloadKind =
561 (NunboxPartKind)((header >> PAYLOAD_KIND_SHIFT) & PART_KIND_MASK);
562 uint32_t typeInfo = (header >> TYPE_INFO_SHIFT) & PART_INFO_MASK;
563 uint32_t payloadInfo = (header >> PAYLOAD_INFO_SHIFT) & PART_INFO_MASK;
565 *type = PartFromStream(stream_, typeKind, typeInfo);
566 *payload = PartFromStream(stream_, payloadKind, payloadInfo);
567 return true;
570 void SafepointReader::advanceFromNunboxOrValueSlots() {
571 slotsOrElementsSlotsRemaining_ = stream_.readUnsigned();
574 bool SafepointReader::getSlotsOrElementsSlot(SafepointSlotEntry* entry) {
575 if (!slotsOrElementsSlotsRemaining_--) {
576 advanceFromSlotsOrElementsSlots();
577 return false;
579 entry->stack = true;
580 entry->slot = stream_.readUnsigned();
581 return true;
584 void SafepointReader::advanceFromSlotsOrElementsSlots() {
585 wasmAnyRefSlotsRemaining_ = stream_.readUnsigned();
588 bool SafepointReader::getWasmAnyRefSlot(SafepointSlotEntry* entry) {
589 if (!wasmAnyRefSlotsRemaining_--) {
590 return false;
592 entry->stack = true;
593 entry->slot = stream_.readUnsigned();
594 return true;