IonAssemblerBuffer.h (11561B)
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_IonAssemblerBuffer_h 8 #define jit_shared_IonAssemblerBuffer_h 9 10 #include "mozilla/Assertions.h" 11 #include "mozilla/MathAlgorithms.h" 12 13 #include <algorithm> 14 #include <compare> // std::strong_ordering 15 16 #include "jit/ProcessExecutableMemory.h" 17 #include "jit/shared/Assembler-shared.h" 18 19 namespace js { 20 namespace jit { 21 22 // The offset into a buffer, in bytes. 23 class BufferOffset { 24 int offset; 25 26 public: 27 friend BufferOffset nextOffset(); 28 29 constexpr BufferOffset() : offset(INT_MIN) {} 30 31 explicit BufferOffset(int offset_) : offset(offset_) { 32 MOZ_ASSERT(offset >= 0); 33 } 34 35 explicit BufferOffset(Label* l) : offset(l->offset()) { 36 MOZ_ASSERT(offset >= 0); 37 } 38 39 int getOffset() const { return offset; } 40 bool assigned() const { return offset != INT_MIN; } 41 42 // A BOffImm is a Branch Offset Immediate. It is an architecture-specific 43 // structure that holds the immediate for a pc relative branch. diffB takes 44 // the label for the destination of the branch, and encodes the immediate 45 // for the branch. This will need to be fixed up later, since A pool may be 46 // inserted between the branch and its destination. 47 template <class BOffImm> 48 BOffImm diffB(BufferOffset other) const { 49 if (!BOffImm::IsInRange(offset - other.offset)) { 50 return BOffImm(); 51 } 52 return BOffImm(offset - other.offset); 53 } 54 55 template <class BOffImm> 56 BOffImm diffB(Label* other) const { 57 MOZ_ASSERT(other->bound()); 58 if (!BOffImm::IsInRange(offset - other->offset())) { 59 return BOffImm(); 60 } 61 return BOffImm(offset - other->offset()); 62 } 63 64 constexpr auto operator<=>(const BufferOffset& other) const = default; 65 }; 66 67 template <int SliceSize> 68 class BufferSlice { 69 protected: 70 BufferSlice<SliceSize>* prev_; 71 BufferSlice<SliceSize>* next_; 72 73 size_t bytelength_; 74 75 public: 76 mozilla::Array<uint8_t, SliceSize> instructions; 77 78 public: 79 explicit BufferSlice() : prev_(nullptr), next_(nullptr), bytelength_(0) {} 80 81 size_t length() const { return bytelength_; } 82 static inline size_t Capacity() { return SliceSize; } 83 84 BufferSlice* getNext() const { return next_; } 85 BufferSlice* getPrev() const { return prev_; } 86 87 void setNext(BufferSlice<SliceSize>* next) { 88 MOZ_ASSERT(next_ == nullptr); 89 MOZ_ASSERT(next->prev_ == nullptr); 90 next_ = next; 91 next->prev_ = this; 92 } 93 94 void putBytes(size_t numBytes, const void* source) { 95 MOZ_ASSERT(bytelength_ + numBytes <= SliceSize); 96 if (source) { 97 memcpy(&instructions[length()], source, numBytes); 98 } 99 bytelength_ += numBytes; 100 } 101 102 MOZ_ALWAYS_INLINE 103 void putU32Aligned(uint32_t value) { 104 MOZ_ASSERT(bytelength_ + 4 <= SliceSize); 105 MOZ_ASSERT((bytelength_ & 3) == 0); 106 MOZ_ASSERT((uintptr_t(&instructions[0]) & 3) == 0); 107 *reinterpret_cast<uint32_t*>(&instructions[bytelength_]) = value; 108 bytelength_ += 4; 109 } 110 }; 111 112 template <int SliceSize, class Inst> 113 class AssemblerBuffer { 114 protected: 115 using Slice = BufferSlice<SliceSize>; 116 117 // Doubly-linked list of BufferSlices, with the most recent in tail position. 118 Slice* head; 119 Slice* tail; 120 121 bool m_oom; 122 123 // How many bytes has been committed to the buffer thus far. 124 // Does not include tail. 125 uint32_t bufferSize; 126 127 // How many bytes can be in the buffer. Normally this is 128 // MaxCodeBytesPerBuffer, but for pasteup buffers where we handle far jumps 129 // explicitly it can be larger. 130 uint32_t maxSize; 131 132 // Finger for speeding up accesses. 133 Slice* finger; 134 int finger_offset; 135 136 LifoAlloc lifoAlloc_; 137 138 public: 139 explicit AssemblerBuffer() 140 : head(nullptr), 141 tail(nullptr), 142 m_oom(false), 143 bufferSize(0), 144 maxSize(MaxCodeBytesPerBuffer), 145 finger(nullptr), 146 finger_offset(0), 147 lifoAlloc_(8192, js::BackgroundMallocArena) {} 148 149 public: 150 bool isAligned(size_t alignment) const { 151 MOZ_ASSERT(mozilla::IsPowerOfTwo(alignment)); 152 return !(size() & (alignment - 1)); 153 } 154 155 void setUnlimited() { maxSize = MaxCodeBytesPerProcess; } 156 157 private: 158 Slice* newSlice(LifoAlloc& a) { 159 if (size() > maxSize - sizeof(Slice)) { 160 fail_oom(); 161 return nullptr; 162 } 163 Slice* tmp = static_cast<Slice*>(a.alloc(sizeof(Slice))); 164 if (!tmp) { 165 fail_oom(); 166 return nullptr; 167 } 168 return new (tmp) Slice; 169 } 170 171 public: 172 bool ensureSpace(size_t size) { 173 // Space can exist in the most recent Slice. 174 if (tail && tail->length() + size <= tail->Capacity()) { 175 // Simulate allocation failure even when we don't need a new slice. 176 if (js::oom::ShouldFailWithOOM()) { 177 return fail_oom(); 178 } 179 180 return true; 181 } 182 183 // Otherwise, a new Slice must be added. 184 Slice* slice = newSlice(lifoAlloc_); 185 if (slice == nullptr) { 186 return fail_oom(); 187 } 188 189 // If this is the first Slice in the buffer, add to head position. 190 if (!head) { 191 head = slice; 192 finger = slice; 193 finger_offset = 0; 194 } 195 196 // Finish the last Slice and add the new Slice to the linked list. 197 if (tail) { 198 bufferSize += tail->length(); 199 tail->setNext(slice); 200 } 201 tail = slice; 202 203 return true; 204 } 205 206 BufferOffset putByte(uint8_t value) { 207 return putBytes(sizeof(value), &value); 208 } 209 210 BufferOffset putShort(uint16_t value) { 211 return putBytes(sizeof(value), &value); 212 } 213 214 BufferOffset putInt(uint32_t value) { 215 return putBytes(sizeof(value), &value); 216 } 217 218 MOZ_ALWAYS_INLINE 219 BufferOffset putU32Aligned(uint32_t value) { 220 if (!ensureSpace(sizeof(value))) { 221 return BufferOffset(); 222 } 223 224 BufferOffset ret = nextOffset(); 225 tail->putU32Aligned(value); 226 return ret; 227 } 228 229 // Add numBytes bytes to this buffer. 230 // The data must fit in a single slice. 231 BufferOffset putBytes(size_t numBytes, const void* inst) { 232 if (!ensureSpace(numBytes)) { 233 return BufferOffset(); 234 } 235 236 BufferOffset ret = nextOffset(); 237 tail->putBytes(numBytes, inst); 238 return ret; 239 } 240 241 // Add a potentially large amount of data to this buffer. 242 // The data may be distrubuted across multiple slices. 243 // Return the buffer offset of the first added byte. 244 BufferOffset putBytesLarge(size_t numBytes, const void* data) { 245 BufferOffset ret = nextOffset(); 246 while (numBytes > 0) { 247 if (!ensureSpace(1)) { 248 return BufferOffset(); 249 } 250 size_t avail = tail->Capacity() - tail->length(); 251 size_t xfer = numBytes < avail ? numBytes : avail; 252 MOZ_ASSERT(xfer > 0, "ensureSpace should have allocated a slice"); 253 tail->putBytes(xfer, data); 254 data = (const uint8_t*)data + xfer; 255 numBytes -= xfer; 256 } 257 return ret; 258 } 259 260 unsigned int size() const { 261 if (tail) { 262 return bufferSize + tail->length(); 263 } 264 return bufferSize; 265 } 266 BufferOffset nextOffset() const { return BufferOffset(size()); } 267 268 bool oom() const { return m_oom; } 269 270 bool fail_oom() { 271 m_oom = true; 272 #ifdef DEBUG 273 JitContext* context = MaybeGetJitContext(); 274 if (context) { 275 context->setOOM(); 276 } 277 #endif 278 return false; 279 } 280 281 private: 282 void update_finger(Slice* finger_, int fingerOffset_) { 283 finger = finger_; 284 finger_offset = fingerOffset_; 285 } 286 287 static const unsigned SliceDistanceRequiringFingerUpdate = 3; 288 289 Inst* getInstForwards(BufferOffset off, Slice* start, int startOffset, 290 bool updateFinger = false) { 291 const int offset = off.getOffset(); 292 293 int cursor = startOffset; 294 unsigned slicesSkipped = 0; 295 296 MOZ_ASSERT(offset >= cursor); 297 298 for (Slice* slice = start; slice != nullptr; slice = slice->getNext()) { 299 const int slicelen = slice->length(); 300 301 // Is the offset within the bounds of this slice? 302 if (offset < cursor + slicelen) { 303 if (updateFinger || 304 slicesSkipped >= SliceDistanceRequiringFingerUpdate) { 305 update_finger(slice, cursor); 306 } 307 308 MOZ_ASSERT(offset - cursor < (int)slice->length()); 309 return (Inst*)&slice->instructions[offset - cursor]; 310 } 311 312 cursor += slicelen; 313 slicesSkipped++; 314 } 315 316 MOZ_CRASH("Invalid instruction cursor."); 317 } 318 319 Inst* getInstBackwards(BufferOffset off, Slice* start, int startOffset, 320 bool updateFinger = false) { 321 const int offset = off.getOffset(); 322 323 int cursor = startOffset; // First (lowest) offset in the start Slice. 324 unsigned slicesSkipped = 0; 325 326 MOZ_ASSERT(offset < int(cursor + start->length())); 327 328 for (Slice* slice = start; slice != nullptr;) { 329 // Is the offset within the bounds of this slice? 330 if (offset >= cursor) { 331 if (updateFinger || 332 slicesSkipped >= SliceDistanceRequiringFingerUpdate) { 333 update_finger(slice, cursor); 334 } 335 336 MOZ_ASSERT(offset - cursor < (int)slice->length()); 337 return (Inst*)&slice->instructions[offset - cursor]; 338 } 339 340 // Move the cursor to the start of the previous slice. 341 Slice* prev = slice->getPrev(); 342 cursor -= prev->length(); 343 344 slice = prev; 345 slicesSkipped++; 346 } 347 348 MOZ_CRASH("Invalid instruction cursor."); 349 } 350 351 public: 352 Inst* getInstOrNull(BufferOffset off) { 353 if (!off.assigned()) { 354 return nullptr; 355 } 356 return getInst(off); 357 } 358 359 // Get a pointer to the instruction at offset |off| which must be within the 360 // bounds of the buffer. Use |getInstOrNull()| if |off| may be unassigned. 361 Inst* getInst(BufferOffset off) { 362 const int offset = off.getOffset(); 363 // This function is hot, do not make the next line a RELEASE_ASSERT. 364 MOZ_ASSERT(off.assigned() && offset >= 0 && unsigned(offset) < size()); 365 366 // Is the instruction in the last slice? 367 if (offset >= int(bufferSize)) { 368 return (Inst*)&tail->instructions[offset - bufferSize]; 369 } 370 371 // How close is this offset to the previous one we looked up? 372 // If it is sufficiently far from the start and end of the buffer, 373 // use the finger to start midway through the list. 374 int finger_dist = abs(offset - finger_offset); 375 if (finger_dist < std::min(offset, int(bufferSize - offset))) { 376 if (finger_offset < offset) { 377 return getInstForwards(off, finger, finger_offset, true); 378 } 379 return getInstBackwards(off, finger, finger_offset, true); 380 } 381 382 // Is the instruction closer to the start or to the end? 383 if (offset < int(bufferSize - offset)) { 384 return getInstForwards(off, head, 0); 385 } 386 387 // The last slice was already checked above, so start at the 388 // second-to-last. 389 Slice* prev = tail->getPrev(); 390 return getInstBackwards(off, prev, bufferSize - prev->length()); 391 } 392 393 using ThisClass = AssemblerBuffer<SliceSize, Inst>; 394 395 class AssemblerBufferInstIterator { 396 BufferOffset bo_; 397 ThisClass* buffer_; 398 399 public: 400 explicit AssemblerBufferInstIterator(BufferOffset bo, ThisClass* buffer) 401 : bo_(bo), buffer_(buffer) {} 402 void advance(int offset) { bo_ = BufferOffset(bo_.getOffset() + offset); } 403 Inst* next() { 404 advance(cur()->size()); 405 return cur(); 406 } 407 Inst* peek() { 408 return buffer_->getInst(BufferOffset(bo_.getOffset() + cur()->size())); 409 } 410 Inst* cur() const { return buffer_->getInst(bo_); } 411 }; 412 }; 413 414 } // namespace jit 415 } // namespace js 416 417 #endif // jit_shared_IonAssemblerBuffer_h