1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: set ts=8 sts=2 et sw=2 tw=80:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "jit/Safepoints.h"
8 
9 #include "mozilla/MathAlgorithms.h"
10 
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"
16 
17 using namespace js;
18 using namespace jit;
19 
20 using mozilla::FloorLog2;
21 
SafepointWriter(uint32_t slotCount,uint32_t argumentCount)22 SafepointWriter::SafepointWriter(uint32_t slotCount, uint32_t argumentCount)
23     : frameSlots_((slotCount / sizeof(intptr_t)) +
24                   1),  // Stack slot counts are inclusive.
25       argumentSlots_(argumentCount / sizeof(intptr_t)) {}
26 
init(TempAllocator & alloc)27 bool SafepointWriter::init(TempAllocator& alloc) {
28   return frameSlots_.init(alloc) && argumentSlots_.init(alloc);
29 }
30 
startEntry()31 uint32_t SafepointWriter::startEntry() {
32   JitSpew(JitSpew_Safepoints,
33           "Encoding safepoint (position %zu):", stream_.length());
34   return uint32_t(stream_.length());
35 }
36 
writeOsiCallPointOffset(uint32_t osiCallPointOffset)37 void SafepointWriter::writeOsiCallPointOffset(uint32_t osiCallPointOffset) {
38   stream_.writeUnsigned(osiCallPointOffset);
39 }
40 
WriteRegisterMask(CompactBufferWriter & stream,PackedRegisterMask bits)41 static void WriteRegisterMask(CompactBufferWriter& stream,
42                               PackedRegisterMask bits) {
43   if (sizeof(PackedRegisterMask) == 1) {
44     stream.writeByte(bits);
45   } else {
46     MOZ_ASSERT(sizeof(PackedRegisterMask) <= 4);
47     stream.writeUnsigned(bits);
48   }
49 }
50 
ReadRegisterMask(CompactBufferReader & stream)51 static PackedRegisterMask ReadRegisterMask(CompactBufferReader& stream) {
52   if (sizeof(PackedRegisterMask) == 1) {
53     return stream.readByte();
54   }
55   MOZ_ASSERT(sizeof(PackedRegisterMask) <= 4);
56   return stream.readUnsigned();
57 }
58 
WriteFloatRegisterMask(CompactBufferWriter & stream,FloatRegisters::SetType bits)59 static void WriteFloatRegisterMask(CompactBufferWriter& stream,
60                                    FloatRegisters::SetType bits) {
61   switch (sizeof(FloatRegisters::SetType)) {
62 #ifdef JS_CODEGEN_ARM64
63     case 16:
64       stream.writeUnsigned64(bits.low());
65       stream.writeUnsigned64(bits.high());
66       break;
67 #else
68     case 1:
69       stream.writeByte(bits);
70       break;
71     case 4:
72       stream.writeUnsigned(bits);
73       break;
74     case 8:
75       stream.writeUnsigned64(bits);
76       break;
77 #endif
78     default:
79       MOZ_CRASH("WriteFloatRegisterMask: unexpected size");
80   }
81 }
82 
ReadFloatRegisterMask(CompactBufferReader & stream)83 static FloatRegisters::SetType ReadFloatRegisterMask(
84     CompactBufferReader& stream) {
85   switch (sizeof(FloatRegisters::SetType)) {
86 #ifdef JS_CODEGEN_ARM64
87     case 16: {
88       uint64_t low = stream.readUnsigned64();
89       uint64_t high = stream.readUnsigned64();
90       return Bitset128(high, low);
91     }
92 #else
93     case 1:
94       return stream.readByte();
95     case 2:
96     case 3:
97     case 4:
98       return stream.readUnsigned();
99     case 8:
100       return stream.readUnsigned64();
101 #endif
102     default:
103       MOZ_CRASH("ReadFloatRegisterMask: unexpected size");
104   }
105 }
106 
writeGcRegs(LSafepoint * safepoint)107 void SafepointWriter::writeGcRegs(LSafepoint* safepoint) {
108   LiveGeneralRegisterSet gc(safepoint->gcRegs());
109   LiveGeneralRegisterSet spilledGpr(safepoint->liveRegs().gprs());
110   LiveFloatRegisterSet spilledFloat(safepoint->liveRegs().fpus());
111   LiveGeneralRegisterSet slots(safepoint->slotsOrElementsRegs());
112   LiveGeneralRegisterSet valueRegs;
113 
114   WriteRegisterMask(stream_, spilledGpr.bits());
115   if (!spilledGpr.empty()) {
116     WriteRegisterMask(stream_, gc.bits());
117     WriteRegisterMask(stream_, slots.bits());
118 
119 #ifdef JS_PUNBOX64
120     valueRegs = safepoint->valueRegs();
121     WriteRegisterMask(stream_, valueRegs.bits());
122 #endif
123   }
124 
125   // GC registers are a subset of the spilled registers.
126   MOZ_ASSERT((valueRegs.bits() & ~spilledGpr.bits()) == 0);
127   MOZ_ASSERT((gc.bits() & ~spilledGpr.bits()) == 0);
128 
129   WriteFloatRegisterMask(stream_, spilledFloat.bits());
130 
131 #ifdef JS_JITSPEW
132   if (JitSpewEnabled(JitSpew_Safepoints)) {
133     for (GeneralRegisterForwardIterator iter(spilledGpr); iter.more(); ++iter) {
134       const char* type = gc.has(*iter)          ? "gc"
135                          : slots.has(*iter)     ? "slots"
136                          : valueRegs.has(*iter) ? "value"
137                                                 : "any";
138       JitSpew(JitSpew_Safepoints, "    %s reg: %s", type, (*iter).name());
139     }
140     for (FloatRegisterForwardIterator iter(spilledFloat); iter.more(); ++iter) {
141       JitSpew(JitSpew_Safepoints, "    float reg: %s", (*iter).name());
142     }
143   }
144 #endif
145 }
146 
WriteBitset(const BitSet & set,CompactBufferWriter & stream)147 static void WriteBitset(const BitSet& set, CompactBufferWriter& stream) {
148   size_t count = set.rawLength();
149   const uint32_t* words = set.raw();
150   for (size_t i = 0; i < count; i++) {
151     stream.writeUnsigned(words[i]);
152   }
153 }
154 
MapSlotsToBitset(BitSet & stackSet,BitSet & argumentSet,CompactBufferWriter & stream,const LSafepoint::SlotList & slots)155 static void MapSlotsToBitset(BitSet& stackSet, BitSet& argumentSet,
156                              CompactBufferWriter& stream,
157                              const LSafepoint::SlotList& slots) {
158   stackSet.clear();
159   argumentSet.clear();
160 
161   for (uint32_t i = 0; i < slots.length(); i++) {
162     // Slots are represented at a distance from |fp|. We divide by the
163     // pointer size, since we only care about pointer-sized/aligned slots
164     // here.
165     MOZ_ASSERT(slots[i].slot % sizeof(intptr_t) == 0);
166     size_t index = slots[i].slot / sizeof(intptr_t);
167     (slots[i].stack ? stackSet : argumentSet).insert(index);
168   }
169 
170   WriteBitset(stackSet, stream);
171   WriteBitset(argumentSet, stream);
172 }
173 
writeGcSlots(LSafepoint * safepoint)174 void SafepointWriter::writeGcSlots(LSafepoint* safepoint) {
175   LSafepoint::SlotList& slots = safepoint->gcSlots();
176 
177 #ifdef JS_JITSPEW
178   for (uint32_t i = 0; i < slots.length(); i++) {
179     JitSpew(JitSpew_Safepoints, "    gc slot: %u", slots[i].slot);
180   }
181 #endif
182 
183   MapSlotsToBitset(frameSlots_, argumentSlots_, stream_, slots);
184 }
185 
writeSlotsOrElementsSlots(LSafepoint * safepoint)186 void SafepointWriter::writeSlotsOrElementsSlots(LSafepoint* safepoint) {
187   LSafepoint::SlotList& slots = safepoint->slotsOrElementsSlots();
188 
189   stream_.writeUnsigned(slots.length());
190 
191   for (uint32_t i = 0; i < slots.length(); i++) {
192     if (!slots[i].stack) {
193       MOZ_CRASH();
194     }
195 #ifdef JS_JITSPEW
196     JitSpew(JitSpew_Safepoints, "    slots/elements slot: %u", slots[i].slot);
197 #endif
198     stream_.writeUnsigned(slots[i].slot);
199   }
200 }
201 
202 #ifdef JS_PUNBOX64
writeValueSlots(LSafepoint * safepoint)203 void SafepointWriter::writeValueSlots(LSafepoint* safepoint) {
204   LSafepoint::SlotList& slots = safepoint->valueSlots();
205 
206 #  ifdef JS_JITSPEW
207   for (uint32_t i = 0; i < slots.length(); i++) {
208     JitSpew(JitSpew_Safepoints, "    gc value: %u", slots[i].slot);
209   }
210 #  endif
211 
212   MapSlotsToBitset(frameSlots_, argumentSlots_, stream_, slots);
213 }
214 #endif
215 
216 #if defined(JS_JITSPEW) && defined(JS_NUNBOX32)
DumpNunboxPart(const LAllocation & a)217 static void DumpNunboxPart(const LAllocation& a) {
218   Fprinter& out = JitSpewPrinter();
219   if (a.isStackSlot()) {
220     out.printf("stack %d", a.toStackSlot()->slot());
221   } else if (a.isArgument()) {
222     out.printf("arg %d", a.toArgument()->index());
223   } else {
224     out.printf("reg %s", a.toGeneralReg()->reg().name());
225   }
226 }
227 #endif  // DEBUG
228 
229 // Nunbox part encoding:
230 //
231 // Reg = 000
232 // Stack = 001
233 // Arg = 010
234 //
235 // [vwu] nentries:
236 //    uint16_t:  tttp ppXX XXXY YYYY
237 //
238 //     If ttt = Reg, type is reg XXXXX
239 //     If ppp = Reg, payload is reg YYYYY
240 //
241 //     If ttt != Reg, type is:
242 //          XXXXX if not 11111, otherwise followed by [vwu]
243 //     If ppp != Reg, payload is:
244 //          YYYYY if not 11111, otherwise followed by [vwu]
245 //
246 enum NunboxPartKind { Part_Reg, Part_Stack, Part_Arg };
247 
248 static const uint32_t PART_KIND_BITS = 3;
249 static const uint32_t PART_KIND_MASK = (1 << PART_KIND_BITS) - 1;
250 static const uint32_t PART_INFO_BITS = 5;
251 static const uint32_t PART_INFO_MASK = (1 << PART_INFO_BITS) - 1;
252 
253 static const uint32_t MAX_INFO_VALUE = (1 << PART_INFO_BITS) - 1;
254 static const uint32_t TYPE_KIND_SHIFT = 16 - PART_KIND_BITS;
255 static const uint32_t PAYLOAD_KIND_SHIFT = TYPE_KIND_SHIFT - PART_KIND_BITS;
256 static const uint32_t TYPE_INFO_SHIFT = PAYLOAD_KIND_SHIFT - PART_INFO_BITS;
257 static const uint32_t PAYLOAD_INFO_SHIFT = TYPE_INFO_SHIFT - PART_INFO_BITS;
258 
259 static_assert(PAYLOAD_INFO_SHIFT == 0);
260 
261 #ifdef JS_NUNBOX32
AllocationToPartKind(const LAllocation & a)262 static inline NunboxPartKind AllocationToPartKind(const LAllocation& a) {
263   if (a.isRegister()) {
264     return Part_Reg;
265   }
266   if (a.isStackSlot()) {
267     return Part_Stack;
268   }
269   MOZ_ASSERT(a.isArgument());
270   return Part_Arg;
271 }
272 
273 // gcc 4.5 doesn't actually inline CanEncodeInfoInHeader when only
274 // using the "inline" keyword, and miscompiles the function as well
275 // when doing block reordering with branch prediction information.
276 // See bug 799295 comment 71.
CanEncodeInfoInHeader(const LAllocation & a,uint32_t * out)277 static MOZ_ALWAYS_INLINE bool CanEncodeInfoInHeader(const LAllocation& a,
278                                                     uint32_t* out) {
279   if (a.isGeneralReg()) {
280     *out = a.toGeneralReg()->reg().code();
281     return true;
282   }
283 
284   if (a.isStackSlot()) {
285     *out = a.toStackSlot()->slot();
286   } else {
287     *out = a.toArgument()->index();
288   }
289 
290   return *out < MAX_INFO_VALUE;
291 }
292 
writeNunboxParts(LSafepoint * safepoint)293 void SafepointWriter::writeNunboxParts(LSafepoint* safepoint) {
294   LSafepoint::NunboxList& entries = safepoint->nunboxParts();
295 
296 #  ifdef JS_JITSPEW
297   if (JitSpewEnabled(JitSpew_Safepoints)) {
298     for (uint32_t i = 0; i < entries.length(); i++) {
299       SafepointNunboxEntry& entry = entries[i];
300       if (entry.type.isUse() || entry.payload.isUse()) {
301         continue;
302       }
303       JitSpewHeader(JitSpew_Safepoints);
304       Fprinter& out = JitSpewPrinter();
305       out.printf("    nunbox (type in ");
306       DumpNunboxPart(entry.type);
307       out.printf(", payload in ");
308       DumpNunboxPart(entry.payload);
309       out.printf(")\n");
310     }
311   }
312 #  endif
313 
314   // Safepoints are permitted to have partially filled in entries for nunboxes,
315   // provided that only the type is live and not the payload. Omit these from
316   // the written safepoint.
317 
318   size_t pos = stream_.length();
319   stream_.writeUnsigned(entries.length());
320 
321   size_t count = 0;
322   for (size_t i = 0; i < entries.length(); i++) {
323     SafepointNunboxEntry& entry = entries[i];
324 
325     if (entry.payload.isUse()) {
326       // No allocation associated with the payload.
327       continue;
328     }
329 
330     if (entry.type.isUse()) {
331       // No allocation associated with the type. Look for another
332       // safepoint entry with an allocation for the type.
333       entry.type = safepoint->findTypeAllocation(entry.typeVreg);
334       if (entry.type.isUse()) {
335         continue;
336       }
337     }
338 
339     count++;
340 
341     uint16_t header = 0;
342 
343     header |= (AllocationToPartKind(entry.type) << TYPE_KIND_SHIFT);
344     header |= (AllocationToPartKind(entry.payload) << PAYLOAD_KIND_SHIFT);
345 
346     uint32_t typeVal;
347     bool typeExtra = !CanEncodeInfoInHeader(entry.type, &typeVal);
348     if (!typeExtra) {
349       header |= (typeVal << TYPE_INFO_SHIFT);
350     } else {
351       header |= (MAX_INFO_VALUE << TYPE_INFO_SHIFT);
352     }
353 
354     uint32_t payloadVal;
355     bool payloadExtra = !CanEncodeInfoInHeader(entry.payload, &payloadVal);
356     if (!payloadExtra) {
357       header |= (payloadVal << PAYLOAD_INFO_SHIFT);
358     } else {
359       header |= (MAX_INFO_VALUE << PAYLOAD_INFO_SHIFT);
360     }
361 
362     stream_.writeFixedUint16_t(header);
363     if (typeExtra) {
364       stream_.writeUnsigned(typeVal);
365     }
366     if (payloadExtra) {
367       stream_.writeUnsigned(payloadVal);
368     }
369   }
370 
371   // Update the stream with the actual number of safepoint entries written.
372   stream_.writeUnsignedAt(pos, count, entries.length());
373 }
374 #endif
375 
encode(LSafepoint * safepoint)376 void SafepointWriter::encode(LSafepoint* safepoint) {
377   uint32_t safepointOffset = startEntry();
378 
379   MOZ_ASSERT(safepoint->osiCallPointOffset());
380 
381   writeOsiCallPointOffset(safepoint->osiCallPointOffset());
382   writeGcRegs(safepoint);
383   writeGcSlots(safepoint);
384 
385 #ifdef JS_PUNBOX64
386   writeValueSlots(safepoint);
387 #else
388   writeNunboxParts(safepoint);
389 #endif
390 
391   writeSlotsOrElementsSlots(safepoint);
392 
393   endEntry();
394   safepoint->setOffset(safepointOffset);
395 }
396 
endEntry()397 void SafepointWriter::endEntry() {
398   JitSpew(JitSpew_Safepoints, "    -- entry ended at %u",
399           uint32_t(stream_.length()));
400 }
401 
SafepointReader(IonScript * script,const SafepointIndex * si)402 SafepointReader::SafepointReader(IonScript* script, const SafepointIndex* si)
403     : stream_(script->safepoints() + si->safepointOffset(),
404               script->safepoints() + script->safepointsSize()),
405       frameSlots_((script->frameSlots() / sizeof(intptr_t)) +
406                   1),  // Stack slot counts are inclusive.
407       argumentSlots_(script->argumentSlots() / sizeof(intptr_t)),
408       nunboxSlotsRemaining_(0),
409       slotsOrElementsSlotsRemaining_(0) {
410   osiCallPointOffset_ = stream_.readUnsigned();
411 
412   // gcSpills is a subset of allGprSpills.
413   allGprSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_));
414   if (allGprSpills_.empty()) {
415     gcSpills_ = allGprSpills_;
416     valueSpills_ = allGprSpills_;
417     slotsOrElementsSpills_ = allGprSpills_;
418   } else {
419     gcSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_));
420     slotsOrElementsSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_));
421 #ifdef JS_PUNBOX64
422     valueSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_));
423 #endif
424   }
425 
426   allFloatSpills_ = FloatRegisterSet(ReadFloatRegisterMask(stream_));
427 
428   advanceFromGcRegs();
429 }
430 
osiReturnPointOffset() const431 uint32_t SafepointReader::osiReturnPointOffset() const {
432   return osiCallPointOffset_ + Assembler::PatchWrite_NearCallSize();
433 }
434 
InvalidationPatchPoint(IonScript * script,const SafepointIndex * si)435 CodeLocationLabel SafepointReader::InvalidationPatchPoint(
436     IonScript* script, const SafepointIndex* si) {
437   SafepointReader reader(script, si);
438 
439   return CodeLocationLabel(script->method(),
440                            CodeOffset(reader.osiCallPointOffset()));
441 }
442 
advanceFromGcRegs()443 void SafepointReader::advanceFromGcRegs() {
444   currentSlotChunk_ = 0;
445   nextSlotChunkNumber_ = 0;
446   currentSlotsAreStack_ = true;
447 }
448 
getSlotFromBitmap(SafepointSlotEntry * entry)449 bool SafepointReader::getSlotFromBitmap(SafepointSlotEntry* entry) {
450   while (currentSlotChunk_ == 0) {
451     // Are there any more chunks to read?
452     if (currentSlotsAreStack_) {
453       if (nextSlotChunkNumber_ == BitSet::RawLengthForBits(frameSlots_)) {
454         nextSlotChunkNumber_ = 0;
455         currentSlotsAreStack_ = false;
456         continue;
457       }
458     } else if (nextSlotChunkNumber_ ==
459                BitSet::RawLengthForBits(argumentSlots_)) {
460       return false;
461     }
462 
463     // Yes, read the next chunk.
464     currentSlotChunk_ = stream_.readUnsigned();
465     nextSlotChunkNumber_++;
466   }
467 
468   // The current chunk still has bits in it, so get the next bit, then mask
469   // it out of the slot chunk.
470   uint32_t bit = FloorLog2(currentSlotChunk_);
471   currentSlotChunk_ &= ~(1 << bit);
472 
473   // Return the slot, and re-scale it by the pointer size, reversing the
474   // transformation in MapSlotsToBitset.
475   entry->stack = currentSlotsAreStack_;
476   entry->slot = (((nextSlotChunkNumber_ - 1) * BitSet::BitsPerWord) + bit) *
477                 sizeof(intptr_t);
478   return true;
479 }
480 
getGcSlot(SafepointSlotEntry * entry)481 bool SafepointReader::getGcSlot(SafepointSlotEntry* entry) {
482   if (getSlotFromBitmap(entry)) {
483     return true;
484   }
485   advanceFromGcSlots();
486   return false;
487 }
488 
advanceFromGcSlots()489 void SafepointReader::advanceFromGcSlots() {
490   // No, reset the counter.
491   currentSlotChunk_ = 0;
492   nextSlotChunkNumber_ = 0;
493   currentSlotsAreStack_ = true;
494 #ifdef JS_NUNBOX32
495   // Nunbox slots are next.
496   nunboxSlotsRemaining_ = stream_.readUnsigned();
497 #else
498   // Value slots are next.
499 #endif
500 }
501 
getValueSlot(SafepointSlotEntry * entry)502 bool SafepointReader::getValueSlot(SafepointSlotEntry* entry) {
503   if (getSlotFromBitmap(entry)) {
504     return true;
505   }
506   advanceFromNunboxOrValueSlots();
507   return false;
508 }
509 
PartFromStream(CompactBufferReader & stream,NunboxPartKind kind,uint32_t info)510 static inline LAllocation PartFromStream(CompactBufferReader& stream,
511                                          NunboxPartKind kind, uint32_t info) {
512   if (kind == Part_Reg) {
513     return LGeneralReg(Register::FromCode(info));
514   }
515 
516   if (info == MAX_INFO_VALUE) {
517     info = stream.readUnsigned();
518   }
519 
520   if (kind == Part_Stack) {
521     return LStackSlot(info);
522   }
523 
524   MOZ_ASSERT(kind == Part_Arg);
525   return LArgument(info);
526 }
527 
getNunboxSlot(LAllocation * type,LAllocation * payload)528 bool SafepointReader::getNunboxSlot(LAllocation* type, LAllocation* payload) {
529   if (!nunboxSlotsRemaining_--) {
530     advanceFromNunboxOrValueSlots();
531     return false;
532   }
533 
534   uint16_t header = stream_.readFixedUint16_t();
535   NunboxPartKind typeKind =
536       (NunboxPartKind)((header >> TYPE_KIND_SHIFT) & PART_KIND_MASK);
537   NunboxPartKind payloadKind =
538       (NunboxPartKind)((header >> PAYLOAD_KIND_SHIFT) & PART_KIND_MASK);
539   uint32_t typeInfo = (header >> TYPE_INFO_SHIFT) & PART_INFO_MASK;
540   uint32_t payloadInfo = (header >> PAYLOAD_INFO_SHIFT) & PART_INFO_MASK;
541 
542   *type = PartFromStream(stream_, typeKind, typeInfo);
543   *payload = PartFromStream(stream_, payloadKind, payloadInfo);
544   return true;
545 }
546 
advanceFromNunboxOrValueSlots()547 void SafepointReader::advanceFromNunboxOrValueSlots() {
548   slotsOrElementsSlotsRemaining_ = stream_.readUnsigned();
549 }
550 
getSlotsOrElementsSlot(SafepointSlotEntry * entry)551 bool SafepointReader::getSlotsOrElementsSlot(SafepointSlotEntry* entry) {
552   if (!slotsOrElementsSlotsRemaining_--) {
553     return false;
554   }
555   entry->stack = true;
556   entry->slot = stream_.readUnsigned();
557   return true;
558 }
559