tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }