Safepoints.cpp (19675B)
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 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)) {} 27 28 bool SafepointWriter::init(TempAllocator& alloc) { 29 return localSlots_.init(alloc) && argumentSlots_.init(alloc); 30 } 31 32 uint32_t SafepointWriter::startEntry() { 33 JitSpew(JitSpew_Safepoints, 34 "Encoding safepoint (position %zu):", stream_.length()); 35 return uint32_t(stream_.length()); 36 } 37 38 void SafepointWriter::writeOsiCallPointOffset(uint32_t osiCallPointOffset) { 39 stream_.writeUnsigned(osiCallPointOffset); 40 } 41 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); 49 } 50 } 51 52 static PackedRegisterMask ReadRegisterMask(CompactBufferReader& stream) { 53 if (sizeof(PackedRegisterMask) == 1) { 54 return stream.readByte(); 55 } 56 MOZ_ASSERT(sizeof(PackedRegisterMask) <= 4); 57 return stream.readUnsigned(); 58 } 59 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"); 81 } 82 } 83 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); 92 } 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"); 105 } 106 } 107 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; 115 116 WriteRegisterMask(stream_, spilledGpr.bits()); 117 if (!spilledGpr.empty()) { 118 WriteRegisterMask(stream_, gc.bits()); 119 WriteRegisterMask(stream_, slots.bits()); 120 WriteRegisterMask(stream_, wasmAnyRef.bits()); 121 122 #ifdef JS_PUNBOX64 123 valueRegs = safepoint->valueRegs(); 124 WriteRegisterMask(stream_, valueRegs.bits()); 125 #endif 126 } 127 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); 131 132 WriteFloatRegisterMask(stream_, spilledFloat.bits()); 133 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()); 142 } 143 for (FloatRegisterForwardIterator iter(spilledFloat); iter.more(); ++iter) { 144 JitSpew(JitSpew_Safepoints, " float reg: %s", (*iter).name()); 145 } 146 } 147 #endif 148 } 149 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]); 155 } 156 } 157 158 static void MapSlotsToBitset(BitSet& stackSet, BitSet& argumentSet, 159 CompactBufferWriter& stream, 160 const LSafepoint::SlotList& slots) { 161 stackSet.clear(); 162 argumentSet.clear(); 163 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); 171 } 172 173 WriteBitset(stackSet, stream); 174 WriteBitset(argumentSet, stream); 175 } 176 177 void SafepointWriter::writeGcSlots(LSafepoint* safepoint) { 178 LSafepoint::SlotList& slots = safepoint->gcSlots(); 179 180 #ifdef JS_JITSPEW 181 for (uint32_t i = 0; i < slots.length(); i++) { 182 JitSpew(JitSpew_Safepoints, " gc slot: %u", slots[i].slot); 183 } 184 #endif 185 186 MapSlotsToBitset(localSlots_, argumentSlots_, stream_, slots); 187 } 188 189 void SafepointWriter::writeSlotsOrElementsSlots(LSafepoint* safepoint) { 190 LSafepoint::SlotList& slots = safepoint->slotsOrElementsSlots(); 191 192 stream_.writeUnsigned(slots.length()); 193 194 for (uint32_t i = 0; i < slots.length(); i++) { 195 if (!slots[i].stack) { 196 MOZ_CRASH(); 197 } 198 #ifdef JS_JITSPEW 199 JitSpew(JitSpew_Safepoints, " slots/elements slot: %u", slots[i].slot); 200 #endif 201 stream_.writeUnsigned(slots[i].slot); 202 } 203 } 204 205 void SafepointWriter::writeWasmAnyRefSlots(LSafepoint* safepoint) { 206 LSafepoint::SlotList& slots = safepoint->wasmAnyRefSlots(); 207 208 stream_.writeUnsigned(slots.length()); 209 210 for (uint32_t i = 0; i < slots.length(); i++) { 211 if (!slots[i].stack) { 212 MOZ_CRASH(); 213 } 214 #ifdef JS_JITSPEW 215 JitSpew(JitSpew_Safepoints, " wasm_anyref slot: %u", slots[i].slot); 216 #endif 217 stream_.writeUnsigned(slots[i].slot); 218 } 219 } 220 221 #ifdef JS_PUNBOX64 222 void SafepointWriter::writeValueSlots(LSafepoint* safepoint) { 223 LSafepoint::SlotList& slots = safepoint->valueSlots(); 224 225 # ifdef JS_JITSPEW 226 for (uint32_t i = 0; i < slots.length(); i++) { 227 JitSpew(JitSpew_Safepoints, " gc value: %u", slots[i].slot); 228 } 229 # endif 230 231 MapSlotsToBitset(localSlots_, argumentSlots_, stream_, slots); 232 } 233 #endif 234 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()); 244 } 245 } 246 #endif // DEBUG 247 248 // Nunbox part encoding: 249 // 250 // Reg = 000 251 // Stack = 001 252 // Arg = 010 253 // 254 // [vwu] nentries: 255 // uint16_t: tttp ppXX XXXY YYYY 256 // 257 // If ttt = Reg, type is reg XXXXX 258 // If ppp = Reg, payload is reg YYYYY 259 // 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] 264 // 265 enum NunboxPartKind { Part_Reg, Part_Stack, Part_Arg }; 266 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; 271 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; 277 278 static_assert(PAYLOAD_INFO_SHIFT == 0); 279 280 #ifdef JS_NUNBOX32 281 static inline NunboxPartKind AllocationToPartKind(const LAllocation& a) { 282 if (a.isGeneralReg()) { 283 return Part_Reg; 284 } 285 if (a.isStackSlot()) { 286 return Part_Stack; 287 } 288 MOZ_ASSERT(a.isArgument()); 289 return Part_Arg; 290 } 291 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; 301 } 302 303 if (a.isStackSlot()) { 304 *out = a.toStackSlot()->slot(); 305 } else { 306 *out = a.toArgument()->index(); 307 } 308 309 return *out < MAX_INFO_VALUE; 310 } 311 312 void SafepointWriter::writeNunboxParts(LSafepoint* safepoint) { 313 LSafepoint::NunboxList& entries = safepoint->nunboxParts(); 314 315 // This function assumes Values have `payloadVreg == typeVreg + 1`. 316 static_assert(VREG_TYPE_OFFSET == 0); 317 static_assert(VREG_DATA_OFFSET == 1); 318 319 // Sort the entries by vreg in ascending order to simplify the code below and 320 // to avoid quadratic behavior. If there are multiple entries for the same 321 // vreg, we also sort them by the LAllocation bits to ensure we get the same 322 // order for different `std::sort` implementations. 323 auto compareEntries = [](auto a, auto b) -> bool { 324 if (a.vreg() != b.vreg()) { 325 return a.vreg() < b.vreg(); 326 } 327 MOZ_ASSERT(a.isType() == b.isType()); 328 return a.alloc().asRawBits() < b.alloc().asRawBits(); 329 }; 330 std::sort(entries.begin(), entries.end(), compareEntries); 331 332 // We need to write an entry for Values where we have both a type half and a 333 // payload half. If the type part has vreg `x`, then the corresponding payload 334 // part must have vreg `x + 1`. Because we sorted the vector by vreg, we'll 335 // always see the type parts of a Value before its payload parts when we 336 // iterate over the entries. 337 // 338 // If there are multiple allocations for the payload half, we need to include 339 // all of them. This is important for Generational and Compacting GC because 340 // they can change the payload part when moving GC things in memory. 341 // 342 // However if there are multiple allocations for the type half, it doesn't 343 // matter which one we pick because the GC never changes the Value's type tag. 344 // 345 // For example, if the vector contains the following data: 346 // 347 // (isType: true, vreg: 0, allocation: eax) 348 // (isType: true, vreg: 0, allocation: stackslot0) 349 // (isType: false, vreg: 1, allocation: ebx) 350 // (isType: false, vreg: 1, allocation: stackslot4) 351 // 352 // We need to write the following (type, payload) entries: 353 // 354 // (eax or stackslot0, ebx) 355 // (eax or stackslot0, stackslot4) 356 // 357 // With the Backtracking allocator it's possible that we only have the type 358 // half or the payload half (when the allocator uses a longer range than 359 // strictly necessary for one of the spill bundles). We ignore these entries 360 // because the Value is effectively dead in this case. 361 362 size_t pos = stream_.length(); 363 stream_.writeUnsigned(entries.length()); 364 365 size_t count = 0; 366 mozilla::Maybe<SafepointNunboxEntry> lastTypeEntry; 367 for (SafepointNunboxEntry entry : entries) { 368 if (entry.isType()) { 369 lastTypeEntry = mozilla::Some(entry); 370 continue; 371 } 372 373 // Ignore payload parts without a corresponding type part. 374 SafepointNunboxEntry payloadEntry = entry; 375 if (lastTypeEntry.isNothing() || 376 lastTypeEntry->vreg() + 1 != payloadEntry.vreg()) { 377 continue; 378 } 379 380 SafepointNunboxEntry typeEntry = *lastTypeEntry; 381 MOZ_ASSERT(typeEntry.isType()); 382 MOZ_ASSERT(!payloadEntry.isType()); 383 384 # ifdef JS_JITSPEW 385 if (JitSpewEnabled(JitSpew_Safepoints)) { 386 JitSpewHeader(JitSpew_Safepoints); 387 Fprinter& out = JitSpewPrinter(); 388 out.printf(" nunbox (type in "); 389 DumpNunboxPart(typeEntry.alloc()); 390 out.printf(", payload in "); 391 DumpNunboxPart(payloadEntry.alloc()); 392 out.printf(")\n"); 393 } 394 # endif 395 396 count++; 397 398 uint16_t header = 0; 399 400 header |= (AllocationToPartKind(typeEntry.alloc()) << TYPE_KIND_SHIFT); 401 header |= 402 (AllocationToPartKind(payloadEntry.alloc()) << PAYLOAD_KIND_SHIFT); 403 404 uint32_t typeVal; 405 bool typeExtra = !CanEncodeInfoInHeader(typeEntry.alloc(), &typeVal); 406 if (!typeExtra) { 407 header |= (typeVal << TYPE_INFO_SHIFT); 408 } else { 409 header |= (MAX_INFO_VALUE << TYPE_INFO_SHIFT); 410 } 411 412 uint32_t payloadVal; 413 bool payloadExtra = 414 !CanEncodeInfoInHeader(payloadEntry.alloc(), &payloadVal); 415 if (!payloadExtra) { 416 header |= (payloadVal << PAYLOAD_INFO_SHIFT); 417 } else { 418 header |= (MAX_INFO_VALUE << PAYLOAD_INFO_SHIFT); 419 } 420 421 stream_.writeFixedUint16_t(header); 422 if (typeExtra) { 423 stream_.writeUnsigned(typeVal); 424 } 425 if (payloadExtra) { 426 stream_.writeUnsigned(payloadVal); 427 } 428 } 429 430 // Update the stream with the actual number of safepoint entries written. 431 stream_.writeUnsignedAt(pos, count, entries.length()); 432 } 433 #endif 434 435 void SafepointWriter::encode(LSafepoint* safepoint) { 436 uint32_t safepointOffset = startEntry(); 437 438 MOZ_ASSERT(safepoint->osiCallPointOffset()); 439 440 writeOsiCallPointOffset(safepoint->osiCallPointOffset()); 441 writeGcRegs(safepoint); 442 writeGcSlots(safepoint); 443 444 #ifdef JS_PUNBOX64 445 writeValueSlots(safepoint); 446 #else 447 writeNunboxParts(safepoint); 448 #endif 449 450 writeSlotsOrElementsSlots(safepoint); 451 writeWasmAnyRefSlots(safepoint); 452 453 endEntry(); 454 safepoint->setOffset(safepointOffset); 455 } 456 457 void SafepointWriter::endEntry() { 458 JitSpew(JitSpew_Safepoints, " -- entry ended at %u", 459 uint32_t(stream_.length())); 460 } 461 462 SafepointReader::SafepointReader(IonScript* script, const SafepointIndex* si) 463 : stream_(script->safepoints() + si->safepointOffset(), 464 script->safepoints() + script->safepointsSize()), 465 localSlots_((script->localSlotsSize() / sizeof(intptr_t)) + 466 1), // Stack slot counts are inclusive. 467 argumentSlots_(script->argumentSlotsSize() / sizeof(intptr_t)), 468 nunboxSlotsRemaining_(0), 469 slotsOrElementsSlotsRemaining_(0), 470 wasmAnyRefSlotsRemaining_(0) { 471 osiCallPointOffset_ = stream_.readUnsigned(); 472 473 // gcSpills is a subset of allGprSpills. 474 allGprSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_)); 475 if (allGprSpills_.empty()) { 476 gcSpills_ = allGprSpills_; 477 valueSpills_ = allGprSpills_; 478 slotsOrElementsSpills_ = allGprSpills_; 479 wasmAnyRefSpills_ = allGprSpills_; 480 } else { 481 gcSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_)); 482 slotsOrElementsSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_)); 483 wasmAnyRefSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_)); 484 #ifdef JS_PUNBOX64 485 valueSpills_ = GeneralRegisterSet(ReadRegisterMask(stream_)); 486 #endif 487 } 488 489 allFloatSpills_ = FloatRegisterSet(ReadFloatRegisterMask(stream_)); 490 491 advanceFromGcRegs(); 492 } 493 494 uint32_t SafepointReader::osiReturnPointOffset() const { 495 return osiCallPointOffset_ + Assembler::PatchWrite_NearCallSize(); 496 } 497 498 CodeLocationLabel SafepointReader::InvalidationPatchPoint( 499 IonScript* script, const SafepointIndex* si) { 500 SafepointReader reader(script, si); 501 502 return CodeLocationLabel(script->method(), 503 CodeOffset(reader.osiCallPointOffset())); 504 } 505 506 void SafepointReader::advanceFromGcRegs() { 507 currentSlotChunk_ = 0; 508 nextSlotChunkNumber_ = 0; 509 currentSlotsAreStack_ = true; 510 } 511 512 bool SafepointReader::getSlotFromBitmap(SafepointSlotEntry* entry) { 513 while (currentSlotChunk_ == 0) { 514 // Are there any more chunks to read? 515 if (currentSlotsAreStack_) { 516 if (nextSlotChunkNumber_ == BitSet::RawLengthForBits(localSlots_)) { 517 nextSlotChunkNumber_ = 0; 518 currentSlotsAreStack_ = false; 519 continue; 520 } 521 } else if (nextSlotChunkNumber_ == 522 BitSet::RawLengthForBits(argumentSlots_)) { 523 return false; 524 } 525 526 // Yes, read the next chunk. 527 currentSlotChunk_ = stream_.readUnsigned(); 528 nextSlotChunkNumber_++; 529 } 530 531 // The current chunk still has bits in it, so get the next bit, then mask 532 // it out of the slot chunk. 533 uint32_t bit = FloorLog2(currentSlotChunk_); 534 currentSlotChunk_ &= ~(1 << bit); 535 536 // Return the slot, and re-scale it by the pointer size, reversing the 537 // transformation in MapSlotsToBitset. 538 entry->stack = currentSlotsAreStack_; 539 entry->slot = (((nextSlotChunkNumber_ - 1) * BitSet::BitsPerWord) + bit) * 540 sizeof(intptr_t); 541 return true; 542 } 543 544 bool SafepointReader::getGcSlot(SafepointSlotEntry* entry) { 545 if (getSlotFromBitmap(entry)) { 546 return true; 547 } 548 advanceFromGcSlots(); 549 return false; 550 } 551 552 void SafepointReader::advanceFromGcSlots() { 553 // No, reset the counter. 554 currentSlotChunk_ = 0; 555 nextSlotChunkNumber_ = 0; 556 currentSlotsAreStack_ = true; 557 #ifdef JS_NUNBOX32 558 // Nunbox slots are next. 559 nunboxSlotsRemaining_ = stream_.readUnsigned(); 560 #else 561 // Value slots are next. 562 #endif 563 } 564 565 bool SafepointReader::getValueSlot(SafepointSlotEntry* entry) { 566 if (getSlotFromBitmap(entry)) { 567 return true; 568 } 569 advanceFromNunboxOrValueSlots(); 570 return false; 571 } 572 573 static inline LAllocation PartFromStream(CompactBufferReader& stream, 574 NunboxPartKind kind, uint32_t info) { 575 if (kind == Part_Reg) { 576 return LGeneralReg(Register::FromCode(info)); 577 } 578 579 if (info == MAX_INFO_VALUE) { 580 info = stream.readUnsigned(); 581 } 582 583 if (kind == Part_Stack) { 584 return LStackSlot(info, LStackSlot::Word); 585 } 586 587 MOZ_ASSERT(kind == Part_Arg); 588 return LArgument(info); 589 } 590 591 bool SafepointReader::getNunboxSlot(LAllocation* type, LAllocation* payload) { 592 if (!nunboxSlotsRemaining_--) { 593 advanceFromNunboxOrValueSlots(); 594 return false; 595 } 596 597 uint16_t header = stream_.readFixedUint16_t(); 598 NunboxPartKind typeKind = 599 (NunboxPartKind)((header >> TYPE_KIND_SHIFT) & PART_KIND_MASK); 600 NunboxPartKind payloadKind = 601 (NunboxPartKind)((header >> PAYLOAD_KIND_SHIFT) & PART_KIND_MASK); 602 uint32_t typeInfo = (header >> TYPE_INFO_SHIFT) & PART_INFO_MASK; 603 uint32_t payloadInfo = (header >> PAYLOAD_INFO_SHIFT) & PART_INFO_MASK; 604 605 *type = PartFromStream(stream_, typeKind, typeInfo); 606 *payload = PartFromStream(stream_, payloadKind, payloadInfo); 607 return true; 608 } 609 610 void SafepointReader::advanceFromNunboxOrValueSlots() { 611 slotsOrElementsSlotsRemaining_ = stream_.readUnsigned(); 612 } 613 614 bool SafepointReader::getSlotsOrElementsSlot(SafepointSlotEntry* entry) { 615 if (!slotsOrElementsSlotsRemaining_--) { 616 advanceFromSlotsOrElementsSlots(); 617 return false; 618 } 619 entry->stack = true; 620 entry->slot = stream_.readUnsigned(); 621 return true; 622 } 623 624 void SafepointReader::advanceFromSlotsOrElementsSlots() { 625 wasmAnyRefSlotsRemaining_ = stream_.readUnsigned(); 626 } 627 628 bool SafepointReader::getWasmAnyRefSlot(SafepointSlotEntry* entry) { 629 if (!wasmAnyRefSlotsRemaining_--) { 630 return false; 631 } 632 entry->stack = true; 633 entry->slot = stream_.readUnsigned(); 634 return true; 635 }