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