| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #ifndef jit_shared_IonAssemblerBuffer_h |
| #define jit_shared_IonAssemblerBuffer_h |
| |
| #include "mozilla/Assertions.h" |
| |
| #include "jit/shared/Assembler-shared.h" |
| |
| namespace js { |
| namespace jit { |
| |
| // The offset into a buffer, in bytes. |
| class BufferOffset |
| { |
| int offset; |
| |
| public: |
| friend BufferOffset nextOffset(); |
| |
| BufferOffset() |
| : offset(INT_MIN) |
| { } |
| |
| explicit BufferOffset(int offset_) |
| : offset(offset_) |
| { } |
| |
| explicit BufferOffset(Label* l) |
| : offset(l->offset()) |
| { } |
| |
| explicit BufferOffset(RepatchLabel* l) |
| : offset(l->offset()) |
| { } |
| |
| int getOffset() const { return offset; } |
| bool assigned() const { return offset != INT_MIN; } |
| |
| // A BOffImm is a Branch Offset Immediate. It is an architecture-specific |
| // structure that holds the immediate for a pc relative branch. diffB takes |
| // the label for the destination of the branch, and encodes the immediate |
| // for the branch. This will need to be fixed up later, since A pool may be |
| // inserted between the branch and its destination. |
| template <class BOffImm> |
| BOffImm diffB(BufferOffset other) const { |
| return BOffImm(offset - other.offset); |
| } |
| |
| template <class BOffImm> |
| BOffImm diffB(Label* other) const { |
| MOZ_ASSERT(other->bound()); |
| return BOffImm(offset - other->offset()); |
| } |
| }; |
| |
| inline bool |
| operator<(BufferOffset a, BufferOffset b) |
| { |
| return a.getOffset() < b.getOffset(); |
| } |
| |
| inline bool |
| operator>(BufferOffset a, BufferOffset b) |
| { |
| return a.getOffset() > b.getOffset(); |
| } |
| |
| inline bool |
| operator<=(BufferOffset a, BufferOffset b) |
| { |
| return a.getOffset() <= b.getOffset(); |
| } |
| |
| inline bool |
| operator>=(BufferOffset a, BufferOffset b) |
| { |
| return a.getOffset() >= b.getOffset(); |
| } |
| |
| inline bool |
| operator==(BufferOffset a, BufferOffset b) |
| { |
| return a.getOffset() == b.getOffset(); |
| } |
| |
| inline bool |
| operator!=(BufferOffset a, BufferOffset b) |
| { |
| return a.getOffset() != b.getOffset(); |
| } |
| |
| template<int SliceSize> |
| class BufferSlice |
| { |
| protected: |
| BufferSlice<SliceSize>* prev_; |
| BufferSlice<SliceSize>* next_; |
| |
| size_t bytelength_; |
| |
| public: |
| mozilla::Array<uint8_t, SliceSize> instructions; |
| |
| public: |
| explicit BufferSlice() |
| : prev_(nullptr), next_(nullptr), bytelength_(0) |
| { } |
| |
| size_t length() const { return bytelength_; } |
| static inline size_t Capacity() { return SliceSize; } |
| |
| BufferSlice* getNext() const { return next_; } |
| BufferSlice* getPrev() const { return prev_; } |
| |
| void setNext(BufferSlice<SliceSize>* next) { |
| MOZ_ASSERT(next_ == nullptr); |
| MOZ_ASSERT(next->prev_ == nullptr); |
| next_ = next; |
| next->prev_ = this; |
| } |
| |
| void putBytes(size_t numBytes, const void* source) { |
| MOZ_ASSERT(bytelength_ + numBytes <= SliceSize); |
| if (source) |
| memcpy(&instructions[length()], source, numBytes); |
| bytelength_ += numBytes; |
| } |
| }; |
| |
| template<int SliceSize, class Inst> |
| class AssemblerBuffer |
| { |
| protected: |
| typedef BufferSlice<SliceSize> Slice; |
| typedef AssemblerBuffer<SliceSize, Inst> AssemblerBuffer_; |
| |
| // Doubly-linked list of BufferSlices, with the most recent in tail position. |
| Slice* head; |
| Slice* tail; |
| |
| bool m_oom; |
| bool m_bail; |
| |
| // How many bytes has been committed to the buffer thus far. |
| // Does not include tail. |
| uint32_t bufferSize; |
| |
| // Finger for speeding up accesses. |
| Slice* finger; |
| int finger_offset; |
| |
| LifoAlloc lifoAlloc_; |
| |
| public: |
| explicit AssemblerBuffer() |
| : head(nullptr), |
| tail(nullptr), |
| m_oom(false), |
| m_bail(false), |
| bufferSize(0), |
| finger(nullptr), |
| finger_offset(0), |
| lifoAlloc_(8192) |
| { } |
| |
| public: |
| bool isAligned(int alignment) const { |
| MOZ_ASSERT(IsPowerOfTwo(alignment)); |
| return !(size() & (alignment - 1)); |
| } |
| |
| protected: |
| virtual Slice* newSlice(LifoAlloc& a) { |
| Slice* tmp = static_cast<Slice*>(a.alloc(sizeof(Slice))); |
| if (!tmp) { |
| fail_oom(); |
| return nullptr; |
| } |
| return new (tmp) Slice; |
| } |
| |
| public: |
| bool ensureSpace(size_t size) { |
| // Space can exist in the most recent Slice. |
| if (tail && tail->length() + size <= tail->Capacity()) { |
| // Simulate allocation failure even when we don't need a new slice. |
| if (js::oom::ShouldFailWithOOM()) |
| return fail_oom(); |
| |
| return true; |
| } |
| |
| // Otherwise, a new Slice must be added. |
| Slice* slice = newSlice(lifoAlloc_); |
| if (slice == nullptr) |
| return fail_oom(); |
| |
| // If this is the first Slice in the buffer, add to head position. |
| if (!head) { |
| head = slice; |
| finger = slice; |
| finger_offset = 0; |
| } |
| |
| // Finish the last Slice and add the new Slice to the linked list. |
| if (tail) { |
| bufferSize += tail->length(); |
| tail->setNext(slice); |
| } |
| tail = slice; |
| |
| return true; |
| } |
| |
| BufferOffset putByte(uint8_t value) { |
| return putBytes(sizeof(value), &value); |
| } |
| |
| BufferOffset putShort(uint16_t value) { |
| return putBytes(sizeof(value), &value); |
| } |
| |
| BufferOffset putInt(uint32_t value) { |
| return putBytes(sizeof(value), &value); |
| } |
| |
| // Add numBytes bytes to this buffer. |
| // The data must fit in a single slice. |
| BufferOffset putBytes(size_t numBytes, const void* inst) { |
| if (!ensureSpace(numBytes)) |
| return BufferOffset(); |
| |
| BufferOffset ret = nextOffset(); |
| tail->putBytes(numBytes, inst); |
| return ret; |
| } |
| |
| // Add a potentially large amount of data to this buffer. |
| // The data may be distrubuted across multiple slices. |
| // Return the buffer offset of the first added byte. |
| BufferOffset putBytesLarge(size_t numBytes, const void* data) |
| { |
| BufferOffset ret = nextOffset(); |
| while (numBytes > 0) { |
| if (!ensureSpace(1)) |
| return BufferOffset(); |
| size_t avail = tail->Capacity() - tail->length(); |
| size_t xfer = numBytes < avail ? numBytes : avail; |
| MOZ_ASSERT(xfer > 0, "ensureSpace should have allocated a slice"); |
| tail->putBytes(xfer, data); |
| data = (const uint8_t*)data + xfer; |
| numBytes -= xfer; |
| } |
| return ret; |
| } |
| |
| unsigned int size() const { |
| if (tail) |
| return bufferSize + tail->length(); |
| return bufferSize; |
| } |
| |
| bool oom() const { return m_oom || m_bail; } |
| bool bail() const { return m_bail; } |
| |
| bool fail_oom() { |
| m_oom = true; |
| return false; |
| } |
| bool fail_bail() { |
| m_bail = true; |
| return false; |
| } |
| |
| private: |
| void update_finger(Slice* finger_, int fingerOffset_) { |
| finger = finger_; |
| finger_offset = fingerOffset_; |
| } |
| |
| static const unsigned SliceDistanceRequiringFingerUpdate = 3; |
| |
| Inst* getInstForwards(BufferOffset off, Slice* start, int startOffset, bool updateFinger = false) { |
| const int offset = off.getOffset(); |
| |
| int cursor = startOffset; |
| unsigned slicesSkipped = 0; |
| |
| MOZ_ASSERT(offset >= cursor); |
| |
| for (Slice *slice = start; slice != nullptr; slice = slice->getNext()) { |
| const int slicelen = slice->length(); |
| |
| // Is the offset within the bounds of this slice? |
| if (offset < cursor + slicelen) { |
| if (updateFinger || slicesSkipped >= SliceDistanceRequiringFingerUpdate) |
| update_finger(slice, cursor); |
| |
| MOZ_ASSERT(offset - cursor < (int)slice->length()); |
| return (Inst*)&slice->instructions[offset - cursor]; |
| } |
| |
| cursor += slicelen; |
| slicesSkipped++; |
| } |
| |
| MOZ_CRASH("Invalid instruction cursor."); |
| } |
| |
| Inst* getInstBackwards(BufferOffset off, Slice* start, int startOffset, bool updateFinger = false) { |
| const int offset = off.getOffset(); |
| |
| int cursor = startOffset; // First (lowest) offset in the start Slice. |
| unsigned slicesSkipped = 0; |
| |
| MOZ_ASSERT(offset < int(cursor + start->length())); |
| |
| for (Slice* slice = start; slice != nullptr; ) { |
| // Is the offset within the bounds of this slice? |
| if (offset >= cursor) { |
| if (updateFinger || slicesSkipped >= SliceDistanceRequiringFingerUpdate) |
| update_finger(slice, cursor); |
| |
| MOZ_ASSERT(offset - cursor < (int)slice->length()); |
| return (Inst*)&slice->instructions[offset - cursor]; |
| } |
| |
| // Move the cursor to the start of the previous slice. |
| Slice* prev = slice->getPrev(); |
| cursor -= prev->length(); |
| |
| slice = prev; |
| slicesSkipped++; |
| } |
| |
| MOZ_CRASH("Invalid instruction cursor."); |
| } |
| |
| public: |
| Inst* getInstOrNull(BufferOffset off) { |
| if (!off.assigned()) |
| return nullptr; |
| return getInst(off); |
| } |
| |
| // Get a pointer to the instruction at offset |off| which must be within the |
| // bounds of the buffer. Use |getInstOrNull()| if |off| may be unassigned. |
| Inst* getInst(BufferOffset off) { |
| const int offset = off.getOffset(); |
| MOZ_RELEASE_ASSERT(off.assigned() && offset >= 0 && (unsigned)offset < size()); |
| |
| // Is the instruction in the last slice? |
| if (offset >= int(bufferSize)) |
| return (Inst*)&tail->instructions[offset - bufferSize]; |
| |
| // How close is this offset to the previous one we looked up? |
| // If it is sufficiently far from the start and end of the buffer, |
| // use the finger to start midway through the list. |
| int finger_dist = abs(offset - finger_offset); |
| if (finger_dist < Min(offset, int(bufferSize - offset))) { |
| if (finger_offset < offset) |
| return getInstForwards(off, finger, finger_offset, true); |
| return getInstBackwards(off, finger, finger_offset, true); |
| } |
| |
| // Is the instruction closer to the start or to the end? |
| if (offset < int(bufferSize - offset)) |
| return getInstForwards(off, head, 0); |
| |
| // The last slice was already checked above, so start at the |
| // second-to-last. |
| Slice* prev = tail->getPrev(); |
| return getInstBackwards(off, prev, bufferSize - prev->length()); |
| } |
| |
| BufferOffset nextOffset() const { |
| if (tail) |
| return BufferOffset(bufferSize + tail->length()); |
| return BufferOffset(bufferSize); |
| } |
| |
| class AssemblerBufferInstIterator |
| { |
| BufferOffset bo; |
| AssemblerBuffer_* m_buffer; |
| |
| public: |
| explicit AssemblerBufferInstIterator(BufferOffset off, AssemblerBuffer_* buffer) |
| : bo(off), m_buffer(buffer) |
| { } |
| |
| Inst* next() { |
| Inst* i = m_buffer->getInst(bo); |
| bo = BufferOffset(bo.getOffset() + i->size()); |
| return cur(); |
| } |
| |
| Inst* cur() { |
| return m_buffer->getInst(bo); |
| } |
| }; |
| }; |
| |
| } // namespace ion |
| } // namespace js |
| |
| #endif // jit_shared_IonAssemblerBuffer_h |