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