tor-browser

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

IonAssemblerBufferWithConstantPools.h (48559B)


      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 #ifndef jit_shared_IonAssemblerBufferWithConstantPools_h
      8 #define jit_shared_IonAssemblerBufferWithConstantPools_h
      9 
     10 #include "mozilla/CheckedInt.h"
     11 #include "mozilla/MathAlgorithms.h"
     12 
     13 #include <algorithm>
     14 
     15 #include "jit/JitSpewer.h"
     16 #include "jit/shared/IonAssemblerBuffer.h"
     17 
     18 // [SMDOC] JIT AssemblerBuffer constant pooling (ARM/ARM64/RISCV64)
     19 //
     20 // This code extends the AssemblerBuffer to support the pooling of values loaded
     21 // using program-counter relative addressing modes. This is necessary with the
     22 // ARM instruction set because it has a fixed instruction size that can not
     23 // encode all values as immediate arguments in instructions. Pooling the values
     24 // allows the values to be placed in large chunks which minimizes the number of
     25 // forced branches around them in the code. This is used for loading floating
     26 // point constants, for loading 32 bit constants on the ARMv6, for absolute
     27 // branch targets, and in future will be needed for large branches on the ARMv6.
     28 //
     29 // For simplicity of the implementation, the constant pools are always placed
     30 // after the loads referencing them. When a new constant pool load is added to
     31 // the assembler buffer, a corresponding pool entry is added to the current
     32 // pending pool. The finishPool() method copies the current pending pool entries
     33 // into the assembler buffer at the current offset and patches the pending
     34 // constant pool load instructions.
     35 //
     36 // Before inserting instructions or pool entries, it is necessary to determine
     37 // if doing so would place a pending pool entry out of reach of an instruction,
     38 // and if so then the pool must firstly be dumped. With the allocation algorithm
     39 // used below, the recalculation of all the distances between instructions and
     40 // their pool entries can be avoided by noting that there will be a limiting
     41 // instruction and pool entry pair that does not change when inserting more
     42 // instructions. Adding more instructions makes the same increase to the
     43 // distance, between instructions and their pool entries, for all such
     44 // pairs. This pair is recorded as the limiter, and it is updated when new pool
     45 // entries are added, see updateLimiter()
     46 //
     47 // The pools consist of: a guard instruction that branches around the pool, a
     48 // header word that helps identify a pool in the instruction stream, and then
     49 // the pool entries allocated in units of words. The guard instruction could be
     50 // omitted if control does not reach the pool, and this is referred to as a
     51 // natural guard below, but for simplicity the guard branch is always
     52 // emitted. The pool header is an identifiable word that in combination with the
     53 // guard uniquely identifies a pool in the instruction stream. The header also
     54 // encodes the pool size and a flag indicating if the guard is natural. It is
     55 // possible to iterate through the code instructions skipping or examining the
     56 // pools. E.g. it might be necessary to skip pools when search for, or patching,
     57 // an instruction sequence.
     58 //
     59 // It is often required to keep a reference to a pool entry, to patch it after
     60 // the buffer is finished. Each pool entry is assigned a unique index, counting
     61 // up from zero (see the poolEntryCount slot below). These can be mapped back to
     62 // the offset of the pool entry in the finished buffer, see poolEntryOffset().
     63 //
     64 // The code supports no-pool regions, and for these the size of the region, in
     65 // instructions, must be supplied. This size is used to determine if inserting
     66 // the instructions would place a pool entry out of range, and if so then a pool
     67 // is firstly flushed. The DEBUG code checks that the emitted code is within the
     68 // supplied size to detect programming errors. See enterNoPool() and
     69 // leaveNoPool().
     70 
     71 // The only planned instruction sets that require inline constant pools are the
     72 // ARM, ARM64, RISCV64 (and historically MIPS), and these all have fixed 32-bit
     73 // sized instructions so for simplicity the code below is specialized for fixed
     74 // 32-bit sized instructions and makes no attempt to support variable length
     75 // instructions. The base assembler buffer which supports variable width
     76 // instruction is used by the x86 and x64 backends.
     77 
     78 // The AssemblerBufferWithConstantPools template class uses static callbacks to
     79 // the provided Asm template argument class:
     80 //
     81 // void Asm::InsertIndexIntoTag(uint8_t* load_, uint32_t index)
     82 //
     83 //   When allocEntry() is called to add a constant pool load with an associated
     84 //   constant pool entry, this callback is called to encode the index of the
     85 //   allocated constant pool entry into the load instruction.
     86 //
     87 //   After the constant pool has been placed, PatchConstantPoolLoad() is called
     88 //   to update the load instruction with the right load offset.
     89 //
     90 // void Asm::WritePoolGuard(BufferOffset branch,
     91 //                          Instruction* dest,
     92 //                          BufferOffset afterPool)
     93 //
     94 //   Write out the constant pool guard branch before emitting the pool.
     95 //
     96 //   branch
     97 //     Offset of the guard branch in the buffer.
     98 //
     99 //   dest
    100 //     Pointer into the buffer where the guard branch should be emitted. (Same
    101 //     as getInst(branch)). Space for guardSize_ instructions has been reserved.
    102 //
    103 //   afterPool
    104 //     Offset of the first instruction after the constant pool. This includes
    105 //     both pool entries and branch veneers added after the pool data.
    106 //
    107 // void Asm::WritePoolHeader(uint8_t* start, Pool* p, bool isNatural)
    108 //
    109 //   Write out the pool header which follows the guard branch.
    110 //
    111 // void Asm::PatchConstantPoolLoad(void* loadAddr, void* constPoolAddr)
    112 //
    113 //   Re-encode a load of a constant pool entry after the location of the
    114 //   constant pool is known.
    115 //
    116 //   The load instruction at loadAddr was previously passed to
    117 //   InsertIndexIntoTag(). The constPoolAddr is the final address of the
    118 //   constant pool in the assembler buffer.
    119 //
    120 // void Asm::PatchShortRangeBranchToVeneer(AssemblerBufferWithConstantPools*,
    121 //                                         unsigned rangeIdx,
    122 //                                         BufferOffset deadline,
    123 //                                         BufferOffset veneer)
    124 //
    125 //   Patch a short-range branch to jump through a veneer before it goes out of
    126 //   range.
    127 //
    128 //   rangeIdx, deadline
    129 //     These arguments were previously passed to registerBranchDeadline(). It is
    130 //     assumed that PatchShortRangeBranchToVeneer() knows how to compute the
    131 //     offset of the short-range branch from this information.
    132 //
    133 //   veneer
    134 //     Space for a branch veneer, guaranteed to be <= deadline. At this
    135 //     position, guardSize_ * InstSize bytes are allocated. They should be
    136 //     initialized to the proper unconditional branch instruction.
    137 //
    138 //   Unbound branches to the same unbound label are organized as a linked list:
    139 //
    140 //     Label::offset -> Branch1 -> Branch2 -> Branch3 -> nil
    141 //
    142 //   This callback should insert a new veneer branch into the list:
    143 //
    144 //     Label::offset -> Branch1 -> Branch2 -> Veneer -> Branch3 -> nil
    145 //
    146 //   When Assembler::bind() rewrites the branches with the real label offset, it
    147 //   probably has to bind Branch2 to target the veneer branch instead of jumping
    148 //   straight to the label.
    149 
    150 namespace js {
    151 namespace jit {
    152 
    153 // BranchDeadlineSet - Keep track of pending branch deadlines.
    154 //
    155 // Some architectures like arm and arm64 have branch instructions with limited
    156 // range. When assembling a forward branch, it is not always known if the final
    157 // target label will be in range of the branch instruction.
    158 //
    159 // The BranchDeadlineSet data structure is used to keep track of the set of
    160 // pending forward branches. It supports the following fast operations:
    161 //
    162 // 1. Get the earliest deadline in the set.
    163 // 2. Add a new branch deadline.
    164 // 3. Remove a branch deadline.
    165 //
    166 // Architectures may have different branch encodings with different ranges. Each
    167 // supported range is assigned a small integer starting at 0. This data
    168 // structure does not care about the actual range of branch instructions, just
    169 // the latest buffer offset that can be reached - the deadline offset.
    170 //
    171 // Branched are stored as (rangeIdx, deadline) tuples. The target-specific code
    172 // can compute the location of the branch itself from this information. This
    173 // data structure does not need to know.
    174 //
    175 template <unsigned NumRanges>
    176 class BranchDeadlineSet {
    177  // Maintain a list of pending deadlines for each range separately.
    178  //
    179  // The offsets in each vector are always kept in ascending order.
    180  //
    181  // Because we have a separate vector for different ranges, as forward
    182  // branches are added to the assembler buffer, their deadlines will
    183  // always be appended to the vector corresponding to their range.
    184  //
    185  // When binding labels, we expect a more-or-less LIFO order of branch
    186  // resolutions. This would always hold if we had strictly structured control
    187  // flow.
    188  //
    189  // We allow branch deadlines to be added and removed in any order, but
    190  // performance is best in the expected case of near LIFO order.
    191  //
    192  using RangeVector = Vector<BufferOffset, 8, LifoAllocPolicy<Fallible>>;
    193 
    194  // We really just want "RangeVector deadline_[NumRanges];", but each vector
    195  // needs to be initialized with a LifoAlloc, and C++ doesn't bend that way.
    196  //
    197  // Use raw aligned storage instead and explicitly construct NumRanges
    198  // vectors in our constructor.
    199  mozilla::AlignedStorage2<RangeVector[NumRanges]> deadlineStorage_;
    200 
    201  // Always access the range vectors through this method.
    202  RangeVector& vectorForRange(unsigned rangeIdx) {
    203    MOZ_ASSERT(rangeIdx < NumRanges, "Invalid branch range index");
    204    return (*deadlineStorage_.addr())[rangeIdx];
    205  }
    206 
    207  const RangeVector& vectorForRange(unsigned rangeIdx) const {
    208    MOZ_ASSERT(rangeIdx < NumRanges, "Invalid branch range index");
    209    return (*deadlineStorage_.addr())[rangeIdx];
    210  }
    211 
    212  // Maintain a precomputed earliest deadline at all times.
    213  // This is unassigned only when all deadline vectors are empty.
    214  BufferOffset earliest_;
    215 
    216  // The range vector owning earliest_. Uninitialized when empty.
    217  unsigned earliestRange_;
    218 
    219  // Recompute the earliest deadline after it's been invalidated.
    220  void recomputeEarliest() {
    221    earliest_ = BufferOffset();
    222    for (unsigned r = 0; r < NumRanges; r++) {
    223      auto& vec = vectorForRange(r);
    224      if (!vec.empty() && (!earliest_.assigned() || vec[0] < earliest_)) {
    225        earliest_ = vec[0];
    226        earliestRange_ = r;
    227      }
    228    }
    229  }
    230 
    231  // Update the earliest deadline if needed after inserting (rangeIdx,
    232  // deadline). Always return true for convenience:
    233  // return insert() && updateEarliest().
    234  bool updateEarliest(unsigned rangeIdx, BufferOffset deadline) {
    235    if (!earliest_.assigned() || deadline < earliest_) {
    236      earliest_ = deadline;
    237      earliestRange_ = rangeIdx;
    238    }
    239    return true;
    240  }
    241 
    242 public:
    243  explicit BranchDeadlineSet(LifoAlloc& alloc) : earliestRange_(0) {
    244    // Manually construct vectors in the uninitialized aligned storage.
    245    // This is because C++ arrays can otherwise only be constructed with
    246    // the default constructor.
    247    for (unsigned r = 0; r < NumRanges; r++) {
    248      new (&vectorForRange(r)) RangeVector(alloc);
    249    }
    250  }
    251 
    252  ~BranchDeadlineSet() {
    253    // Aligned storage doesn't destruct its contents automatically.
    254    for (unsigned r = 0; r < NumRanges; r++) {
    255      vectorForRange(r).~RangeVector();
    256    }
    257  }
    258 
    259  // Is this set completely empty?
    260  bool empty() const { return !earliest_.assigned(); }
    261 
    262  // Get the total number of deadlines in the set.
    263  size_t size() const {
    264    size_t count = 0;
    265    for (unsigned r = 0; r < NumRanges; r++) {
    266      count += vectorForRange(r).length();
    267    }
    268    return count;
    269  }
    270 
    271  // Get the number of deadlines for the range with the most elements.
    272  size_t maxRangeSize() const {
    273    size_t count = 0;
    274    for (unsigned r = 0; r < NumRanges; r++) {
    275      count = std::max(count, vectorForRange(r).length());
    276    }
    277    return count;
    278  }
    279 
    280  // Get the first deadline that is still in the set.
    281  BufferOffset earliestDeadline() const {
    282    MOZ_ASSERT(!empty());
    283    return earliest_;
    284  }
    285 
    286  // Get the range index corresponding to earliestDeadlineRange().
    287  unsigned earliestDeadlineRange() const {
    288    MOZ_ASSERT(!empty());
    289    return earliestRange_;
    290  }
    291 
    292  // Add a (rangeIdx, deadline) tuple to the set.
    293  //
    294  // It is assumed that this tuple is not already in the set.
    295  // This function performs best id the added deadline is later than any
    296  // existing deadline for the same range index.
    297  //
    298  // Return true if the tuple was added, false if the tuple could not be added
    299  // because of an OOM error.
    300  bool addDeadline(unsigned rangeIdx, BufferOffset deadline) {
    301    MOZ_ASSERT(deadline.assigned(), "Can only store assigned buffer offsets");
    302    // This is the vector where deadline should be saved.
    303    auto& vec = vectorForRange(rangeIdx);
    304 
    305    // Fast case: Simple append to the relevant array. This never affects
    306    // the earliest deadline.
    307    if (!vec.empty() && vec.back() < deadline) {
    308      return vec.append(deadline);
    309    }
    310 
    311    // Fast case: First entry to the vector. We need to update earliest_.
    312    if (vec.empty()) {
    313      return vec.append(deadline) && updateEarliest(rangeIdx, deadline);
    314    }
    315 
    316    return addDeadlineSlow(rangeIdx, deadline);
    317  }
    318 
    319 private:
    320  // General case of addDeadline. This is split into two functions such that
    321  // the common case in addDeadline can be inlined while this part probably
    322  // won't inline.
    323  bool addDeadlineSlow(unsigned rangeIdx, BufferOffset deadline) {
    324    auto& vec = vectorForRange(rangeIdx);
    325 
    326    // Inserting into the middle of the vector. Use a log time binary search
    327    // and a linear time insert().
    328    // Is it worthwhile special-casing the empty vector?
    329    auto at = std::lower_bound(vec.begin(), vec.end(), deadline);
    330    MOZ_ASSERT(at == vec.end() || *at != deadline,
    331               "Cannot insert duplicate deadlines");
    332    return vec.insert(at, deadline) && updateEarliest(rangeIdx, deadline);
    333  }
    334 
    335 public:
    336  // Remove a deadline from the set.
    337  // If (rangeIdx, deadline) is not in the set, nothing happens.
    338  void removeDeadline(unsigned rangeIdx, BufferOffset deadline) {
    339    auto& vec = vectorForRange(rangeIdx);
    340 
    341    if (vec.empty()) {
    342      return;
    343    }
    344 
    345    if (deadline == vec.back()) {
    346      // Expected fast case: Structured control flow causes forward
    347      // branches to be bound in reverse order.
    348      vec.popBack();
    349    } else {
    350      // Slow case: Binary search + linear erase.
    351      auto where = std::lower_bound(vec.begin(), vec.end(), deadline);
    352      if (where == vec.end() || *where != deadline) {
    353        return;
    354      }
    355      vec.erase(where);
    356    }
    357    if (deadline == earliest_) {
    358      recomputeEarliest();
    359    }
    360  }
    361 };
    362 
    363 // Specialization for architectures that don't need to track short-range
    364 // branches.
    365 template <>
    366 class BranchDeadlineSet<0u> {
    367 public:
    368  explicit BranchDeadlineSet(LifoAlloc& alloc) {}
    369  bool empty() const { return true; }
    370  size_t size() const { return 0; }
    371  size_t maxRangeSize() const { return 0; }
    372  BufferOffset earliestDeadline() const { MOZ_CRASH(); }
    373  unsigned earliestDeadlineRange() const { MOZ_CRASH(); }
    374  bool addDeadline(unsigned rangeIdx, BufferOffset deadline) { MOZ_CRASH(); }
    375  void removeDeadline(unsigned rangeIdx, BufferOffset deadline) { MOZ_CRASH(); }
    376 };
    377 
    378 // The allocation unit size for pools.
    379 using PoolAllocUnit = int32_t;
    380 
    381 // Hysteresis given to short-range branches.
    382 //
    383 // If any short-range branches will go out of range in the next N bytes,
    384 // generate a veneer for them in the current pool. The hysteresis prevents the
    385 // creation of many tiny constant pools for branch veneers.
    386 const size_t ShortRangeBranchHysteresis = 128;
    387 
    388 struct Pool {
    389 private:
    390  // The maximum program-counter relative offset below which the instruction
    391  // set can encode. Different classes of intructions might support different
    392  // ranges but for simplicity the minimum is used here, and for the ARM this
    393  // is constrained to 1024 by the float load instructions.
    394  const size_t maxOffset_;
    395  // An offset to apply to program-counter relative offsets. The ARM has a
    396  // bias of 8.
    397  const unsigned bias_;
    398 
    399  // The content of the pool entries.
    400  Vector<PoolAllocUnit, 8, LifoAllocPolicy<Fallible>> poolData_;
    401 
    402  // Flag that tracks OOM conditions. This is set after any append failed.
    403  bool oom_;
    404 
    405  // The limiting instruction and pool-entry pair. The instruction program
    406  // counter relative offset of this limiting instruction will go out of range
    407  // first as the pool position moves forward. It is more efficient to track
    408  // just this limiting pair than to recheck all offsets when testing if the
    409  // pool needs to be dumped.
    410  //
    411  // 1. The actual offset of the limiting instruction referencing the limiting
    412  // pool entry.
    413  BufferOffset limitingUser;
    414  // 2. The pool entry index of the limiting pool entry.
    415  unsigned limitingUsee;
    416 
    417 public:
    418  // A record of the code offset of instructions that reference pool
    419  // entries. These instructions need to be patched when the actual position
    420  // of the instructions and pools are known, and for the code below this
    421  // occurs when each pool is finished, see finishPool().
    422  Vector<BufferOffset, 8, LifoAllocPolicy<Fallible>> loadOffsets;
    423 
    424  // Create a Pool. Don't allocate anything from lifoAloc, just capture its
    425  // reference.
    426  explicit Pool(size_t maxOffset, unsigned bias, LifoAlloc& lifoAlloc)
    427      : maxOffset_(maxOffset),
    428        bias_(bias),
    429        poolData_(lifoAlloc),
    430        oom_(false),
    431        limitingUser(),
    432        limitingUsee(INT_MIN),
    433        loadOffsets(lifoAlloc) {}
    434 
    435  // If poolData() returns nullptr then oom_ will also be true.
    436  const PoolAllocUnit* poolData() const { return poolData_.begin(); }
    437 
    438  unsigned numEntries() const { return poolData_.length(); }
    439 
    440  size_t getPoolSize() const { return numEntries() * sizeof(PoolAllocUnit); }
    441 
    442  bool oom() const { return oom_; }
    443 
    444  // Update the instruction/pool-entry pair that limits the position of the
    445  // pool. The nextInst is the actual offset of the new instruction being
    446  // allocated.
    447  //
    448  // This is comparing the offsets, see checkFull() below for the equation,
    449  // but common expressions on both sides have been canceled from the ranges
    450  // being compared. Notably, the poolOffset cancels out, so the limiting pair
    451  // does not depend on where the pool is placed.
    452  void updateLimiter(BufferOffset nextInst) {
    453    ptrdiff_t oldRange =
    454        limitingUsee * sizeof(PoolAllocUnit) - limitingUser.getOffset();
    455    ptrdiff_t newRange = getPoolSize() - nextInst.getOffset();
    456    if (!limitingUser.assigned() || newRange > oldRange) {
    457      // We have a new largest range!
    458      limitingUser = nextInst;
    459      limitingUsee = numEntries();
    460    }
    461  }
    462 
    463  // Check if inserting a pool at the actual offset poolOffset would place
    464  // pool entries out of reach. This is called before inserting instructions
    465  // to check that doing so would not push pool entries out of reach, and if
    466  // so then the pool would need to be firstly dumped. The poolOffset is the
    467  // first word of the pool, after the guard and header and alignment fill.
    468  bool checkFull(size_t poolOffset) const {
    469    // Not full if there are no uses.
    470    if (!limitingUser.assigned()) {
    471      return false;
    472    }
    473    size_t offset = poolOffset + limitingUsee * sizeof(PoolAllocUnit) -
    474                    (limitingUser.getOffset() + bias_);
    475    return offset >= maxOffset_;
    476  }
    477 
    478  static const unsigned OOM_FAIL = unsigned(-1);
    479 
    480  unsigned insertEntry(unsigned num, uint8_t* data, BufferOffset off,
    481                       LifoAlloc& lifoAlloc) {
    482    if (oom_) {
    483      return OOM_FAIL;
    484    }
    485    unsigned ret = numEntries();
    486    if (!poolData_.append((PoolAllocUnit*)data, num) ||
    487        !loadOffsets.append(off)) {
    488      oom_ = true;
    489      return OOM_FAIL;
    490    }
    491    return ret;
    492  }
    493 
    494  void reset() {
    495    poolData_.clear();
    496    loadOffsets.clear();
    497 
    498    limitingUser = BufferOffset();
    499    limitingUsee = -1;
    500  }
    501 };
    502 
    503 // Template arguments:
    504 //
    505 // SliceSize
    506 //   Number of bytes in each allocated BufferSlice. See
    507 //   AssemblerBuffer::SliceSize.
    508 //
    509 // InstSize
    510 //   Size in bytes of the fixed-size instructions. This should be equal to
    511 //   sizeof(Inst). This is only needed here because the buffer is defined before
    512 //   the Instruction.
    513 //
    514 // Inst
    515 //   The actual type used to represent instructions. This is only really used as
    516 //   the return type of the getInst() method.
    517 //
    518 // Asm
    519 //   Class defining the needed static callback functions. See documentation of
    520 //   the Asm::* callbacks above.
    521 //
    522 // NumShortBranchRanges
    523 //   The number of short branch ranges to support. This can be 0 if no support
    524 //   for tracking short range branches is needed. The
    525 //   AssemblerBufferWithConstantPools class does not need to know what the range
    526 //   of branches is - it deals in branch 'deadlines' which is the last buffer
    527 //   position that a short-range forward branch can reach. It is assumed that
    528 //   the Asm class is able to find the actual branch instruction given a
    529 //   (range-index, deadline) pair.
    530 //
    531 //
    532 template <size_t SliceSize, size_t InstSize, class Inst, class Asm,
    533          unsigned NumShortBranchRanges = 0>
    534 struct AssemblerBufferWithConstantPools
    535    : public AssemblerBuffer<SliceSize, Inst> {
    536 private:
    537  // The PoolEntry index counter. Each PoolEntry is given a unique index,
    538  // counting up from zero, and these can be mapped back to the actual pool
    539  // entry offset after finishing the buffer, see poolEntryOffset().
    540  size_t poolEntryCount;
    541 
    542 public:
    543  class PoolEntry {
    544    size_t index_;
    545 
    546   public:
    547    explicit PoolEntry(size_t index) : index_(index) {}
    548 
    549    PoolEntry() : index_(-1) {}
    550 
    551    size_t index() const { return index_; }
    552  };
    553 
    554 private:
    555  using Parent = AssemblerBuffer<SliceSize, Inst>;
    556  using typename Parent::Slice;
    557 
    558  // The size of a pool guard, in instructions. A branch around the pool.
    559  const unsigned guardSize_;
    560  // The size of the header that is put at the beginning of a full pool, in
    561  // instruction sized units.
    562  const unsigned headerSize_;
    563 
    564  // The maximum pc relative offset encoded in instructions that reference
    565  // pool entries. This is generally set to the maximum offset that can be
    566  // encoded by the instructions, but for testing can be lowered to affect the
    567  // pool placement and frequency of pool placement.
    568  const size_t poolMaxOffset_;
    569 
    570  // The bias on pc relative addressing mode offsets, in units of bytes. The
    571  // ARM has a bias of 8 bytes.
    572  const unsigned pcBias_;
    573 
    574  // The current working pool. Copied out as needed before resetting.
    575  Pool pool_;
    576 
    577  // The buffer should be aligned to this address.
    578  const size_t instBufferAlign_;
    579 
    580  struct PoolInfo {
    581    // The index of the first entry in this pool.
    582    // Pool entries are numbered uniquely across all pools, starting from 0.
    583    unsigned firstEntryIndex;
    584 
    585    // The location of this pool's first entry in the main assembler buffer.
    586    // Note that the pool guard and header come before this offset which
    587    // points directly at the data.
    588    BufferOffset offset;
    589 
    590    explicit PoolInfo(unsigned index, BufferOffset data)
    591        : firstEntryIndex(index), offset(data) {}
    592  };
    593 
    594  // Info for each pool that has already been dumped. This does not include
    595  // any entries in pool_.
    596  Vector<PoolInfo, 8, LifoAllocPolicy<Fallible>> poolInfo_;
    597 
    598  // Set of short-range forward branches that have not yet been bound.
    599  // We may need to insert veneers if the final label turns out to be out of
    600  // range.
    601  //
    602  // This set stores (rangeIdx, deadline) pairs instead of the actual branch
    603  // locations.
    604  BranchDeadlineSet<NumShortBranchRanges> branchDeadlines_;
    605 
    606  // When true dumping pools is inhibited.  Any value above zero indicates
    607  // inhibition of pool dumping.  These is no significance to different
    608  // above-zero values; this is a counter and not a boolean only so as to
    609  // facilitate correctly tracking nested enterNoPools/leaveNoPools calls.
    610  unsigned int inhibitPools_;
    611 
    612 #ifdef DEBUG
    613  // State for validating the 'maxInst' argument to enterNoPool() in the case
    614  // `inhibitPools_` was zero before the call (that is, when we enter the
    615  // outermost nesting level).
    616  //
    617  // The buffer offset at the start of the outermost nesting level no-pool
    618  // region.  Set to all-ones (0xFF..FF) to mean "invalid".
    619  size_t inhibitPoolsStartOffset_;
    620  // The maximum number of word sized instructions declared for the outermost
    621  // nesting level no-pool region.  Set to zero when invalid.
    622  size_t inhibitPoolsMaxInst_;
    623  // The maximum number of new deadlines that are allowed to register in the
    624  // no-pool region.
    625  size_t inhibitPoolsMaxNewDeadlines_;
    626  // The actual number of new deadlines registered in the no-pool region.
    627  size_t inhibitPoolsActualNewDeadlines_;
    628 #endif
    629 
    630  // Instruction to use for alignment fill.
    631  const uint32_t alignFillInst_;
    632 
    633  // Insert a number of NOP instructions between each requested instruction at
    634  // all locations at which a pool can potentially spill. This is useful for
    635  // checking that instruction locations are correctly referenced and/or
    636  // followed.
    637  const uint32_t nopFillInst_;
    638  const unsigned nopFill_;
    639 
    640  // For inhibiting the insertion of fill NOPs in the dynamic context in which
    641  // they are being inserted.  The zero-vs-nonzero meaning is the same as that
    642  // documented for `inhibitPools_` above.
    643  unsigned int inhibitNops_;
    644 
    645 private:
    646  // The buffer slices are in a double linked list.
    647  Slice* getHead() const { return this->head; }
    648  Slice* getTail() const { return this->tail; }
    649 
    650 public:
    651  AssemblerBufferWithConstantPools(unsigned guardSize, unsigned headerSize,
    652                                   size_t instBufferAlign, size_t poolMaxOffset,
    653                                   unsigned pcBias, uint32_t alignFillInst,
    654                                   uint32_t nopFillInst, unsigned nopFill = 0)
    655      : poolEntryCount(0),
    656        guardSize_(guardSize),
    657        headerSize_(headerSize),
    658        poolMaxOffset_(poolMaxOffset),
    659        pcBias_(pcBias),
    660        pool_(poolMaxOffset, pcBias, this->lifoAlloc_),
    661        instBufferAlign_(instBufferAlign),
    662        poolInfo_(this->lifoAlloc_),
    663        branchDeadlines_(this->lifoAlloc_),
    664        inhibitPools_(0),
    665 #ifdef DEBUG
    666        inhibitPoolsStartOffset_(~size_t(0) /*"invalid"*/),
    667        inhibitPoolsMaxInst_(0),
    668        inhibitPoolsMaxNewDeadlines_(0),
    669        inhibitPoolsActualNewDeadlines_(0),
    670 #endif
    671        alignFillInst_(alignFillInst),
    672        nopFillInst_(nopFillInst),
    673        nopFill_(nopFill),
    674        inhibitNops_(0) {
    675  }
    676 
    677 private:
    678  size_t sizeExcludingCurrentPool() const {
    679    // Return the actual size of the buffer, excluding the current pending
    680    // pool.
    681    return this->nextOffset().getOffset();
    682  }
    683 
    684 public:
    685  size_t size() const {
    686    // Return the current actual size of the buffer. This is only accurate
    687    // if there are no pending pool entries to dump, check.
    688    MOZ_ASSERT_IF(!this->oom(), pool_.numEntries() == 0);
    689    return sizeExcludingCurrentPool();
    690  }
    691 
    692 private:
    693  void insertNopFill() {
    694    // Insert fill for testing.
    695    if (nopFill_ > 0 && inhibitNops_ == 0 && inhibitPools_ == 0) {
    696      inhibitNops_++;
    697 
    698      // Fill using a branch-nop rather than a NOP so this can be
    699      // distinguished and skipped.
    700      for (size_t i = 0; i < nopFill_; i++) {
    701        putInt(nopFillInst_);
    702      }
    703 
    704      inhibitNops_--;
    705    }
    706  }
    707 
    708  static const unsigned OOM_FAIL = unsigned(-1);
    709  static const unsigned DUMMY_INDEX = unsigned(-2);
    710 
    711  // Check if it is possible to add numInst instructions and numPoolEntries
    712  // constant pool entries without needing to flush the current pool.
    713  bool hasSpaceForInsts(unsigned numInsts, unsigned numPoolEntries,
    714                        unsigned numNewDeadlines = 0) const {
    715    size_t nextOffset = sizeExcludingCurrentPool();
    716    // Earliest starting offset for the current pool after adding numInsts.
    717    // This is the beginning of the pool entries proper, after inserting a
    718    // guard branch + pool header.
    719    size_t poolOffset =
    720        nextOffset + (numInsts + guardSize_ + headerSize_) * InstSize;
    721 
    722    // Any constant pool loads that would go out of range?
    723    if (pool_.checkFull(poolOffset)) {
    724      return false;
    725    }
    726 
    727    // Any short-range branch that would go out of range?
    728    if (!branchDeadlines_.empty()) {
    729      size_t deadline = branchDeadlines_.earliestDeadline().getOffset();
    730      size_t poolEnd = poolOffset + pool_.getPoolSize() +
    731                       numPoolEntries * sizeof(PoolAllocUnit);
    732 
    733      // When NumShortBranchRanges > 1, is is possible for branch deadlines to
    734      // expire faster than we can insert veneers. Suppose branches are 4 bytes
    735      // each, we could have the following deadline set:
    736      //
    737      //   Range 0: 40, 44, 48
    738      //   Range 1: 44, 48
    739      //
    740      // It is not good enough to start inserting veneers at the 40 deadline; we
    741      // would not be able to create veneers for the second 44 deadline.
    742      // Instead, we need to start at 32:
    743      //
    744      //   32: veneer(40)
    745      //   36: veneer(44)
    746      //   40: veneer(44)
    747      //   44: veneer(48)
    748      //   48: veneer(48)
    749      //
    750      // This is a pretty conservative solution to the problem: If we begin at
    751      // the earliest deadline, we can always emit all veneers for the range
    752      // that currently has the most pending deadlines. That may not leave room
    753      // for veneers for the remaining ranges, so reserve space for those
    754      // secondary range veneers assuming the worst case deadlines.
    755 
    756      // Total pending secondary range veneer size.
    757      size_t secondaryVeneers =
    758          guardSize_ *
    759          (branchDeadlines_.size() - branchDeadlines_.maxRangeSize() +
    760           numNewDeadlines) *
    761          InstSize;
    762 
    763      if (deadline < poolEnd + secondaryVeneers) {
    764        return false;
    765      }
    766    }
    767 
    768    return true;
    769  }
    770 
    771  unsigned insertEntryForwards(unsigned numInst, unsigned numPoolEntries,
    772                               uint8_t* inst, uint8_t* data) {
    773    // If inserting pool entries then find a new limiter before we do the
    774    // range check.
    775    if (numPoolEntries) {
    776      pool_.updateLimiter(BufferOffset(sizeExcludingCurrentPool()));
    777    }
    778 
    779    if (!hasSpaceForInsts(numInst, numPoolEntries)) {
    780      if (numPoolEntries) {
    781        JitSpew(JitSpew_Pools, "Inserting pool entry caused a spill");
    782      } else {
    783        JitSpew(JitSpew_Pools, "Inserting instruction(%zu) caused a spill",
    784                sizeExcludingCurrentPool());
    785      }
    786 
    787      finishPool(numInst * InstSize);
    788      if (this->oom()) {
    789        return OOM_FAIL;
    790      }
    791      return insertEntryForwards(numInst, numPoolEntries, inst, data);
    792    }
    793    if (numPoolEntries) {
    794      unsigned result = pool_.insertEntry(numPoolEntries, data,
    795                                          this->nextOffset(), this->lifoAlloc_);
    796      if (result == Pool::OOM_FAIL) {
    797        this->fail_oom();
    798        return OOM_FAIL;
    799      }
    800      return result;
    801    }
    802 
    803    // The pool entry index is returned above when allocating an entry, but
    804    // when not allocating an entry a dummy value is returned - it is not
    805    // expected to be used by the caller.
    806    return DUMMY_INDEX;
    807  }
    808 
    809 public:
    810  // Get the next buffer offset where an instruction would be inserted.
    811  // This may flush the current constant pool before returning nextOffset().
    812  BufferOffset nextInstrOffset(int numInsts = 1) {
    813    if (!hasSpaceForInsts(numInsts, /* numPoolEntries= */ 0)) {
    814      JitSpew(JitSpew_Pools,
    815              "nextInstrOffset @ %d caused a constant pool spill",
    816              this->nextOffset().getOffset());
    817      finishPool(ShortRangeBranchHysteresis);
    818    }
    819    return this->nextOffset();
    820  }
    821 
    822  MOZ_NEVER_INLINE
    823  BufferOffset allocEntry(size_t numInst, unsigned numPoolEntries,
    824                          uint8_t* inst, uint8_t* data,
    825                          PoolEntry* pe = nullptr) {
    826    // The allocation of pool entries is not supported in a no-pool region,
    827    // check.
    828    MOZ_ASSERT_IF(numPoolEntries > 0, inhibitPools_ == 0);
    829 
    830    if (this->oom()) {
    831      return BufferOffset();
    832    }
    833 
    834    insertNopFill();
    835 
    836 #ifdef JS_JITSPEW
    837    if (numPoolEntries && JitSpewEnabled(JitSpew_Pools)) {
    838      JitSpew(JitSpew_Pools, "Inserting %d entries into pool", numPoolEntries);
    839      JitSpewStart(JitSpew_Pools, "data is: 0x");
    840      size_t length = numPoolEntries * sizeof(PoolAllocUnit);
    841      for (unsigned idx = 0; idx < length; idx++) {
    842        JitSpewCont(JitSpew_Pools, "%02x", data[length - idx - 1]);
    843        if (((idx & 3) == 3) && (idx + 1 != length)) {
    844          JitSpewCont(JitSpew_Pools, "_");
    845        }
    846      }
    847      JitSpewFin(JitSpew_Pools);
    848    }
    849 #endif
    850 
    851    // Insert the pool value.
    852    unsigned index = insertEntryForwards(numInst, numPoolEntries, inst, data);
    853    if (this->oom()) {
    854      return BufferOffset();
    855    }
    856 
    857    // Now to get an instruction to write.
    858    PoolEntry retPE;
    859    if (numPoolEntries) {
    860      JitSpew(JitSpew_Pools, "Entry has index %u, offset %zu", index,
    861              sizeExcludingCurrentPool());
    862      Asm::InsertIndexIntoTag(inst, index);
    863      // Figure out the offset within the pool entries.
    864      retPE = PoolEntry(poolEntryCount);
    865      poolEntryCount += numPoolEntries;
    866    }
    867    // Now inst is a valid thing to insert into the instruction stream.
    868    if (pe != nullptr) {
    869      *pe = retPE;
    870    }
    871    return this->putBytes(numInst * InstSize, inst);
    872  }
    873 
    874  // putInt is the workhorse for the assembler and higher-level buffer
    875  // abstractions: it places one instruction into the instruction stream.
    876  // Under normal circumstances putInt should just check that the constant
    877  // pool does not need to be flushed, that there is space for the single word
    878  // of the instruction, and write that word and update the buffer pointer.
    879  //
    880  // To do better here we need a status variable that handles both nopFill_
    881  // and capacity, so that we can quickly know whether to go the slow path.
    882  // That could be a variable that has the remaining number of simple
    883  // instructions that can be inserted before a more expensive check,
    884  // which is set to zero when nopFill_ is set.
    885  //
    886  // We assume that we don't have to check this->oom() if there is space to
    887  // insert a plain instruction; there will always come a later time when it
    888  // will be checked anyway.
    889 
    890  MOZ_ALWAYS_INLINE
    891  BufferOffset putInt(uint32_t value) {
    892    if (nopFill_ ||
    893        !hasSpaceForInsts(/* numInsts= */ 1, /* numPoolEntries= */ 0)) {
    894      return allocEntry(1, 0, (uint8_t*)&value, nullptr, nullptr);
    895    }
    896 
    897 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) ||      \
    898    defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || \
    899    defined(JS_CODEGEN_RISCV64)
    900    return this->putU32Aligned(value);
    901 #else
    902    return this->AssemblerBuffer<SliceSize, Inst>::putInt(value);
    903 #endif
    904  }
    905 
    906  // Register a short-range branch deadline.
    907  //
    908  // After inserting a short-range forward branch, call this method to
    909  // register the branch 'deadline' which is the last buffer offset that the
    910  // branch instruction can reach.
    911  //
    912  // When the branch is bound to a destination label, call
    913  // unregisterBranchDeadline() to stop tracking this branch,
    914  //
    915  // If the assembled code is about to exceed the registered branch deadline,
    916  // and unregisterBranchDeadline() has not yet been called, an
    917  // instruction-sized constant pool entry is allocated before the branch
    918  // deadline.
    919  //
    920  // rangeIdx
    921  //   A number < NumShortBranchRanges identifying the range of the branch.
    922  //
    923  // deadline
    924  //   The highest buffer offset the the short-range branch can reach
    925  //   directly.
    926  //
    927  void registerBranchDeadline(unsigned rangeIdx, BufferOffset deadline) {
    928    if (!this->oom() && !branchDeadlines_.addDeadline(rangeIdx, deadline)) {
    929      this->fail_oom();
    930    }
    931 #ifdef DEBUG
    932    if (inhibitPools_ > 0) {
    933      inhibitPoolsActualNewDeadlines_++;
    934      MOZ_ASSERT(inhibitPoolsActualNewDeadlines_ <=
    935                 inhibitPoolsMaxNewDeadlines_);
    936    }
    937 #endif
    938  }
    939 
    940  // Un-register a short-range branch deadline.
    941  //
    942  // When a short-range branch has been successfully bound to its destination
    943  // label, call this function to stop traching the branch.
    944  //
    945  // The (rangeIdx, deadline) pair must be previously registered.
    946  //
    947  void unregisterBranchDeadline(unsigned rangeIdx, BufferOffset deadline) {
    948    if (!this->oom()) {
    949      branchDeadlines_.removeDeadline(rangeIdx, deadline);
    950    }
    951 #ifdef DEBUG
    952    if (inhibitPools_ > 0) {
    953      MOZ_ASSERT(inhibitPoolsMaxNewDeadlines_ > 0);
    954      inhibitPoolsActualNewDeadlines_--;
    955    }
    956 #endif
    957  }
    958 
    959 private:
    960  // Are any short-range branches about to expire?
    961  bool hasExpirableShortRangeBranches(size_t reservedBytes) const {
    962    if (branchDeadlines_.empty()) {
    963      return false;
    964    }
    965 
    966    // Include branches that would expire in the next N bytes. The reservedBytes
    967    // argument avoids the needless creation of many tiny constant pools.
    968    //
    969    // As the reservedBytes could be of any sizes such as SIZE_MAX, in the case
    970    // of flushPool, we have to check for overflow when comparing the deadline
    971    // with our expected reserved bytes.
    972    size_t deadline = branchDeadlines_.earliestDeadline().getOffset();
    973    using CheckedSize = mozilla::CheckedInt<size_t>;
    974    CheckedSize current(this->nextOffset().getOffset());
    975    CheckedSize poolFreeSpace(reservedBytes);
    976    auto future = current + poolFreeSpace;
    977    return !future.isValid() || deadline < future.value();
    978  }
    979 
    980  bool isPoolEmptyFor(size_t bytes) const {
    981    return pool_.numEntries() == 0 && !hasExpirableShortRangeBranches(bytes);
    982  }
    983  void finishPool(size_t reservedBytes) {
    984    JitSpew(JitSpew_Pools, "Attempting to finish pool %zu with %u entries.",
    985            poolInfo_.length(), pool_.numEntries());
    986 
    987    if (reservedBytes < ShortRangeBranchHysteresis) {
    988      reservedBytes = ShortRangeBranchHysteresis;
    989    }
    990 
    991    if (isPoolEmptyFor(reservedBytes)) {
    992      // If there is no data in the pool being dumped, don't dump anything.
    993      JitSpew(JitSpew_Pools, "Aborting because the pool is empty");
    994      return;
    995    }
    996 
    997    // Should not be placing a pool in a no-pool region, check.
    998    MOZ_ASSERT(inhibitPools_ == 0);
    999 
   1000    // Dump the pool with a guard branch around the pool.
   1001    BufferOffset guard = this->putBytes(guardSize_ * InstSize, nullptr);
   1002    BufferOffset header = this->putBytes(headerSize_ * InstSize, nullptr);
   1003    BufferOffset data = this->putBytesLarge(pool_.getPoolSize(),
   1004                                            (const uint8_t*)pool_.poolData());
   1005    if (this->oom()) {
   1006      return;
   1007    }
   1008 
   1009    // Now generate branch veneers for any short-range branches that are
   1010    // about to expire.
   1011    while (hasExpirableShortRangeBranches(reservedBytes)) {
   1012      unsigned rangeIdx = branchDeadlines_.earliestDeadlineRange();
   1013      BufferOffset deadline = branchDeadlines_.earliestDeadline();
   1014 
   1015      // Stop tracking this branch. The Asm callback below may register
   1016      // new branches to track.
   1017      branchDeadlines_.removeDeadline(rangeIdx, deadline);
   1018 
   1019      // Make room for the veneer. Same as a pool guard branch.
   1020      BufferOffset veneer = this->putBytes(guardSize_ * InstSize, nullptr);
   1021      if (this->oom()) {
   1022        return;
   1023      }
   1024 
   1025      // Fix the branch so it targets the veneer.
   1026      // The Asm class knows how to find the original branch given the
   1027      // (rangeIdx, deadline) pair.
   1028      Asm::PatchShortRangeBranchToVeneer(this, rangeIdx, deadline, veneer);
   1029    }
   1030 
   1031    // We only reserved space for the guard branch and pool header.
   1032    // Fill them in.
   1033    BufferOffset afterPool = this->nextOffset();
   1034    Asm::WritePoolGuard(guard, this->getInst(guard), afterPool);
   1035    Asm::WritePoolHeader((uint8_t*)this->getInst(header), &pool_, false);
   1036 
   1037    // With the pool's final position determined it is now possible to patch
   1038    // the instructions that reference entries in this pool, and this is
   1039    // done incrementally as each pool is finished.
   1040    size_t poolOffset = data.getOffset();
   1041 
   1042    unsigned idx = 0;
   1043    for (BufferOffset* iter = pool_.loadOffsets.begin();
   1044         iter != pool_.loadOffsets.end(); ++iter, ++idx) {
   1045      // All entries should be before the pool.
   1046      MOZ_ASSERT(iter->getOffset() < guard.getOffset());
   1047 
   1048      // Everything here is known so we can safely do the necessary
   1049      // substitutions.
   1050      Inst* inst = this->getInst(*iter);
   1051      size_t codeOffset = poolOffset - iter->getOffset();
   1052 
   1053      // That is, PatchConstantPoolLoad wants to be handed the address of
   1054      // the pool entry that is being loaded.  We need to do a non-trivial
   1055      // amount of math here, since the pool that we've made does not
   1056      // actually reside there in memory.
   1057      JitSpew(JitSpew_Pools, "Fixing entry %d offset to %zu", idx, codeOffset);
   1058      Asm::PatchConstantPoolLoad(inst, (uint8_t*)inst + codeOffset);
   1059    }
   1060 
   1061    // Record the pool info.
   1062    unsigned firstEntry = poolEntryCount - pool_.numEntries();
   1063    if (!poolInfo_.append(PoolInfo(firstEntry, data))) {
   1064      this->fail_oom();
   1065      return;
   1066    }
   1067 
   1068    // Reset everything to the state that it was in when we started.
   1069    pool_.reset();
   1070  }
   1071 
   1072 public:
   1073  void flushPool() {
   1074    if (this->oom()) {
   1075      return;
   1076    }
   1077    JitSpew(JitSpew_Pools, "Requesting a pool flush");
   1078    finishPool(SIZE_MAX);
   1079  }
   1080 
   1081  void enterNoPool(size_t maxInst, size_t maxNewDeadlines = 0) {
   1082    // Calling this with a zero arg is pointless.
   1083    MOZ_ASSERT(maxInst > 0);
   1084 
   1085    if (this->oom()) {
   1086      return;
   1087    }
   1088 
   1089    if (inhibitPools_ > 0) {
   1090      // This is a nested call to enterNoPool.  Assert that the reserved area
   1091      // fits within that of the outermost call, but otherwise don't do
   1092      // anything.
   1093      //
   1094      // Assert that the outermost call set these.
   1095      MOZ_ASSERT(inhibitPoolsStartOffset_ != ~size_t(0));
   1096      MOZ_ASSERT(inhibitPoolsMaxInst_ > 0);
   1097      // Check inner area fits within that of the outermost.
   1098      MOZ_ASSERT(size_t(this->nextOffset().getOffset()) >=
   1099                 inhibitPoolsStartOffset_);
   1100      MOZ_ASSERT(size_t(this->nextOffset().getOffset()) + maxInst * InstSize <=
   1101                 inhibitPoolsStartOffset_ + inhibitPoolsMaxInst_ * InstSize);
   1102      MOZ_ASSERT(inhibitPoolsActualNewDeadlines_ + maxNewDeadlines <=
   1103                 inhibitPoolsMaxNewDeadlines_);
   1104      inhibitPools_++;
   1105      return;
   1106    }
   1107 
   1108    // This is an outermost level call to enterNoPool.
   1109    MOZ_ASSERT(inhibitPools_ == 0);
   1110    MOZ_ASSERT(inhibitPoolsStartOffset_ == ~size_t(0));
   1111    MOZ_ASSERT(inhibitPoolsMaxInst_ == 0);
   1112    MOZ_ASSERT(inhibitPoolsMaxNewDeadlines_ == 0);
   1113    MOZ_ASSERT(inhibitPoolsActualNewDeadlines_ == 0);
   1114 
   1115    insertNopFill();
   1116 
   1117    // Check if the pool will spill by adding maxInst instructions, and if
   1118    // so then finish the pool before entering the no-pool region. It is
   1119    // assumed that no pool entries are allocated in a no-pool region and
   1120    // this is asserted when allocating entries.
   1121    if (!hasSpaceForInsts(maxInst, 0, maxNewDeadlines)) {
   1122      JitSpew(JitSpew_Pools, "No-Pool instruction(%zu) caused a spill.",
   1123              sizeExcludingCurrentPool());
   1124      finishPool(maxInst * InstSize);
   1125      if (this->oom()) {
   1126        return;
   1127      }
   1128      MOZ_ASSERT(hasSpaceForInsts(maxInst, 0, maxNewDeadlines));
   1129    }
   1130 
   1131 #ifdef DEBUG
   1132    // Record the buffer position to allow validating maxInst when leaving
   1133    // the region.
   1134    inhibitPoolsStartOffset_ = this->nextOffset().getOffset();
   1135    inhibitPoolsMaxInst_ = maxInst;
   1136    inhibitPoolsMaxNewDeadlines_ = maxNewDeadlines;
   1137    inhibitPoolsActualNewDeadlines_ = 0;
   1138    MOZ_ASSERT(inhibitPoolsStartOffset_ != ~size_t(0));
   1139 #endif
   1140 
   1141    inhibitPools_ = 1;
   1142  }
   1143 
   1144  void leaveNoPool() {
   1145    if (this->oom()) {
   1146      inhibitPools_ = 0;
   1147      return;
   1148    }
   1149    MOZ_ASSERT(inhibitPools_ > 0);
   1150 
   1151    if (inhibitPools_ > 1) {
   1152      // We're leaving a non-outermost nesting level.  Note that fact, but
   1153      // otherwise do nothing.
   1154      inhibitPools_--;
   1155      return;
   1156    }
   1157 
   1158    // This is an outermost level call to leaveNoPool.
   1159    MOZ_ASSERT(inhibitPools_ == 1);
   1160    MOZ_ASSERT(inhibitPoolsStartOffset_ != ~size_t(0));
   1161    MOZ_ASSERT(inhibitPoolsMaxInst_ > 0);
   1162 
   1163    // Validate the maxInst argument supplied to enterNoPool(), in the case
   1164    // where we are leaving the outermost nesting level.
   1165    MOZ_ASSERT(this->nextOffset().getOffset() - inhibitPoolsStartOffset_ <=
   1166               inhibitPoolsMaxInst_ * InstSize);
   1167    MOZ_ASSERT(inhibitPoolsActualNewDeadlines_ <= inhibitPoolsMaxNewDeadlines_);
   1168 
   1169 #ifdef DEBUG
   1170    inhibitPoolsStartOffset_ = ~size_t(0);
   1171    inhibitPoolsMaxInst_ = 0;
   1172    inhibitPoolsMaxNewDeadlines_ = 0;
   1173    inhibitPoolsActualNewDeadlines_ = 0;
   1174 #endif
   1175 
   1176    inhibitPools_ = 0;
   1177  }
   1178 
   1179  void enterNoNops() { inhibitNops_++; }
   1180  void leaveNoNops() {
   1181    MOZ_ASSERT(inhibitNops_ > 0);
   1182    inhibitNops_--;
   1183  }
   1184  void assertNoPoolAndNoNops() {
   1185    MOZ_ASSERT(inhibitNops_ > 0);
   1186    MOZ_ASSERT_IF(!this->oom(), isPoolEmptyFor(InstSize) || inhibitPools_ > 0);
   1187  }
   1188 
   1189  void align(unsigned alignment) { align(alignment, alignFillInst_); }
   1190 
   1191  void align(unsigned alignment, uint32_t pattern) {
   1192    MOZ_ASSERT(mozilla::IsPowerOfTwo(alignment));
   1193    MOZ_ASSERT(alignment >= InstSize);
   1194 
   1195    // A pool many need to be dumped at this point, so insert NOP fill here.
   1196    insertNopFill();
   1197 
   1198    // Check if the code position can be aligned without dumping a pool.
   1199    unsigned requiredFill = sizeExcludingCurrentPool() & (alignment - 1);
   1200    if (requiredFill == 0) {
   1201      return;
   1202    }
   1203    requiredFill = alignment - requiredFill;
   1204 
   1205    // Add an InstSize because it is probably not useful for a pool to be
   1206    // dumped at the aligned code position.
   1207    if (!hasSpaceForInsts(requiredFill / InstSize + 1, 0)) {
   1208      // Alignment would cause a pool dump, so dump the pool now.
   1209      JitSpew(JitSpew_Pools, "Alignment of %d at %zu caused a spill.",
   1210              alignment, sizeExcludingCurrentPool());
   1211      finishPool(requiredFill);
   1212    }
   1213 
   1214    inhibitNops_++;
   1215    while ((sizeExcludingCurrentPool() & (alignment - 1)) && !this->oom()) {
   1216      putInt(pattern);
   1217    }
   1218    inhibitNops_--;
   1219  }
   1220 
   1221 public:
   1222  void executableCopy(uint8_t* dest) {
   1223    if (this->oom()) {
   1224      return;
   1225    }
   1226    // The pools should have all been flushed, check.
   1227    MOZ_ASSERT(pool_.numEntries() == 0);
   1228    for (Slice* cur = getHead(); cur != nullptr; cur = cur->getNext()) {
   1229      memcpy(dest, &cur->instructions[0], cur->length());
   1230      dest += cur->length();
   1231    }
   1232  }
   1233 
   1234  bool appendRawCode(const uint8_t* code, size_t numBytes) {
   1235    if (this->oom()) {
   1236      return false;
   1237    }
   1238    // The pools should have all been flushed, check.
   1239    MOZ_ASSERT(pool_.numEntries() == 0);
   1240    while (numBytes > SliceSize) {
   1241      this->putBytes(SliceSize, code);
   1242      numBytes -= SliceSize;
   1243      code += SliceSize;
   1244    }
   1245    this->putBytes(numBytes, code);
   1246    return !this->oom();
   1247  }
   1248 
   1249 public:
   1250  size_t poolEntryOffset(PoolEntry pe) const {
   1251    MOZ_ASSERT(pe.index() < poolEntryCount - pool_.numEntries(),
   1252               "Invalid pool entry, or not flushed yet.");
   1253    // Find the pool containing pe.index().
   1254    // The array is sorted, so we can use a binary search.
   1255    auto b = poolInfo_.begin(), e = poolInfo_.end();
   1256    // A note on asymmetric types in the upper_bound comparator:
   1257    // http://permalink.gmane.org/gmane.comp.compilers.clang.devel/10101
   1258    auto i = std::upper_bound(b, e, pe.index(),
   1259                              [](size_t value, const PoolInfo& entry) {
   1260                                return value < entry.firstEntryIndex;
   1261                              });
   1262    // Since upper_bound finds the first pool greater than pe,
   1263    // we want the previous one which is the last one less than or equal.
   1264    MOZ_ASSERT(i != b, "PoolInfo not sorted or empty?");
   1265    --i;
   1266    // The i iterator now points to the pool containing pe.index.
   1267    MOZ_ASSERT(i->firstEntryIndex <= pe.index() &&
   1268               (i + 1 == e || (i + 1)->firstEntryIndex > pe.index()));
   1269    // Compute the byte offset into the pool.
   1270    unsigned relativeIndex = pe.index() - i->firstEntryIndex;
   1271    return i->offset.getOffset() + relativeIndex * sizeof(PoolAllocUnit);
   1272  }
   1273 };
   1274 
   1275 }  // namespace jit
   1276 }  // namespace js
   1277 
   1278 #endif  // jit_shared_IonAssemblerBufferWithConstantPools_h