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