summaryrefslogtreecommitdiffstats
path: root/js/src/jit/shared/IonAssemblerBuffer.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit/shared/IonAssemblerBuffer.h')
-rw-r--r--js/src/jit/shared/IonAssemblerBuffer.h437
1 files changed, 437 insertions, 0 deletions
diff --git a/js/src/jit/shared/IonAssemblerBuffer.h b/js/src/jit/shared/IonAssemblerBuffer.h
new file mode 100644
index 0000000000..cf19ef1a6a
--- /dev/null
+++ b/js/src/jit/shared/IonAssemblerBuffer.h
@@ -0,0 +1,437 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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 "mozilla/MathAlgorithms.h"
+
+#include <algorithm>
+
+#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_) {
+ MOZ_ASSERT(offset >= 0);
+ }
+
+ explicit BufferOffset(Label* l) : offset(l->offset()) {
+ MOZ_ASSERT(offset >= 0);
+ }
+
+ 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 {
+ if (!BOffImm::IsInRange(offset - other.offset)) {
+ return BOffImm();
+ }
+ return BOffImm(offset - other.offset);
+ }
+
+ template <class BOffImm>
+ BOffImm diffB(Label* other) const {
+ MOZ_ASSERT(other->bound());
+ if (!BOffImm::IsInRange(offset - other->offset())) {
+ return BOffImm();
+ }
+ 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;
+ }
+
+ MOZ_ALWAYS_INLINE
+ void putU32Aligned(uint32_t value) {
+ MOZ_ASSERT(bytelength_ + 4 <= SliceSize);
+ MOZ_ASSERT((bytelength_ & 3) == 0);
+ MOZ_ASSERT((uintptr_t(&instructions[0]) & 3) == 0);
+ *reinterpret_cast<uint32_t*>(&instructions[bytelength_]) = value;
+ bytelength_ += 4;
+ }
+};
+
+template <int SliceSize, class Inst>
+class AssemblerBuffer {
+ protected:
+ typedef BufferSlice<SliceSize> Slice;
+
+ // Doubly-linked list of BufferSlices, with the most recent in tail position.
+ Slice* head;
+ Slice* tail;
+
+ bool m_oom;
+
+ // How many bytes has been committed to the buffer thus far.
+ // Does not include tail.
+ uint32_t bufferSize;
+
+ // How many bytes can be in the buffer. Normally this is
+ // MaxCodeBytesPerBuffer, but for pasteup buffers where we handle far jumps
+ // explicitly it can be larger.
+ uint32_t maxSize;
+
+ // Finger for speeding up accesses.
+ Slice* finger;
+ int finger_offset;
+
+ LifoAlloc lifoAlloc_;
+
+ public:
+ explicit AssemblerBuffer()
+ : head(nullptr),
+ tail(nullptr),
+ m_oom(false),
+ bufferSize(0),
+ maxSize(MaxCodeBytesPerBuffer),
+ finger(nullptr),
+ finger_offset(0),
+ lifoAlloc_(8192) {}
+
+ public:
+ bool isAligned(size_t alignment) const {
+ MOZ_ASSERT(mozilla::IsPowerOfTwo(alignment));
+ return !(size() & (alignment - 1));
+ }
+
+ void setUnlimited() { maxSize = MaxCodeBytesPerProcess; }
+
+ private:
+ Slice* newSlice(LifoAlloc& a) {
+ if (size() > maxSize - sizeof(Slice)) {
+ fail_oom();
+ return nullptr;
+ }
+ 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);
+ }
+
+ MOZ_ALWAYS_INLINE
+ BufferOffset putU32Aligned(uint32_t value) {
+ if (!ensureSpace(sizeof(value))) {
+ return BufferOffset();
+ }
+
+ BufferOffset ret = nextOffset();
+ tail->putU32Aligned(value);
+ return ret;
+ }
+
+ // 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;
+ }
+ BufferOffset nextOffset() const { return BufferOffset(size()); }
+
+ bool oom() const { return m_oom; }
+
+ bool fail_oom() {
+ m_oom = true;
+#ifdef DEBUG
+ JitContext* context = MaybeGetJitContext();
+ if (context) {
+ context->setOOM();
+ }
+#endif
+ 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();
+ // This function is hot, do not make the next line a RELEASE_ASSERT.
+ MOZ_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 < std::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());
+ }
+
+ typedef AssemblerBuffer<SliceSize, Inst> ThisClass;
+
+ class AssemblerBufferInstIterator {
+ BufferOffset bo_;
+ ThisClass* buffer_;
+
+ public:
+ explicit AssemblerBufferInstIterator(BufferOffset bo, ThisClass* buffer)
+ : bo_(bo), buffer_(buffer) {}
+ void advance(int offset) { bo_ = BufferOffset(bo_.getOffset() + offset); }
+ Inst* next() {
+ advance(cur()->size());
+ return cur();
+ }
+ Inst* peek() {
+ return buffer_->getInst(BufferOffset(bo_.getOffset() + cur()->size()));
+ }
+ Inst* cur() const { return buffer_->getInst(bo_); }
+ };
+};
+
+} // namespace jit
+} // namespace js
+
+#endif // jit_shared_IonAssemblerBuffer_h