// This file is part of AsmJit project // // See asmjit.h or LICENSE.md for license and copyright information // SPDX-License-Identifier: Zlib #include "../core/api-build_p.h" #include "../core/support.h" #include "../core/zone.h" #include "../core/zonevector.h" ASMJIT_BEGIN_NAMESPACE // ZoneVectorBase - Helpers // ======================== Error ZoneVectorBase::_grow(ZoneAllocator* allocator, uint32_t sizeOfT, uint32_t n) noexcept { uint32_t threshold = Globals::kGrowThreshold / sizeOfT; uint32_t capacity = _capacity; uint32_t after = _size; if (ASMJIT_UNLIKELY(std::numeric_limits::max() - n < after)) return DebugUtils::errored(kErrorOutOfMemory); after += n; if (capacity >= after) return kErrorOk; // ZoneVector is used as an array to hold short-lived data structures used // during code generation. The growing strategy is simple - use small capacity // at the beginning (very good for ZoneAllocator) and then grow quicker to // prevent successive reallocations. if (capacity < 4) capacity = 4; else if (capacity < 8) capacity = 8; else if (capacity < 16) capacity = 16; else if (capacity < 64) capacity = 64; else if (capacity < 256) capacity = 256; while (capacity < after) { if (capacity < threshold) capacity *= 2; else capacity += threshold; } return _reserve(allocator, sizeOfT, capacity); } Error ZoneVectorBase::_reserve(ZoneAllocator* allocator, uint32_t sizeOfT, uint32_t n) noexcept { uint32_t oldCapacity = _capacity; if (oldCapacity >= n) return kErrorOk; uint32_t nBytes = n * sizeOfT; if (ASMJIT_UNLIKELY(nBytes < n)) return DebugUtils::errored(kErrorOutOfMemory); size_t allocatedBytes; uint8_t* newData = static_cast(allocator->alloc(nBytes, allocatedBytes)); if (ASMJIT_UNLIKELY(!newData)) return DebugUtils::errored(kErrorOutOfMemory); void* oldData = _data; if (_size) memcpy(newData, oldData, size_t(_size) * sizeOfT); if (oldData) allocator->release(oldData, size_t(oldCapacity) * sizeOfT); _capacity = uint32_t(allocatedBytes / sizeOfT); ASMJIT_ASSERT(_capacity >= n); _data = newData; return kErrorOk; } Error ZoneVectorBase::_resize(ZoneAllocator* allocator, uint32_t sizeOfT, uint32_t n) noexcept { uint32_t size = _size; if (_capacity < n) { ASMJIT_PROPAGATE(_grow(allocator, sizeOfT, n - size)); ASMJIT_ASSERT(_capacity >= n); } if (size < n) memset(static_cast(_data) + size_t(size) * sizeOfT, 0, size_t(n - size) * sizeOfT); _size = n; return kErrorOk; } // ZoneBitVector - Operations // ========================== Error ZoneBitVector::copyFrom(ZoneAllocator* allocator, const ZoneBitVector& other) noexcept { BitWord* data = _data; uint32_t newSize = other.size(); if (!newSize) { _size = 0; return kErrorOk; } if (newSize > _capacity) { // Realloc needed... Calculate the minimum capacity (in bytes) required. uint32_t minimumCapacityInBits = Support::alignUp(newSize, kBitWordSizeInBits); if (ASMJIT_UNLIKELY(minimumCapacityInBits < newSize)) return DebugUtils::errored(kErrorOutOfMemory); // Normalize to bytes. uint32_t minimumCapacity = minimumCapacityInBits / 8; size_t allocatedCapacity; BitWord* newData = static_cast(allocator->alloc(minimumCapacity, allocatedCapacity)); if (ASMJIT_UNLIKELY(!newData)) return DebugUtils::errored(kErrorOutOfMemory); // `allocatedCapacity` now contains number in bytes, we need bits. size_t allocatedCapacityInBits = allocatedCapacity * 8; // Arithmetic overflow should normally not happen. If it happens we just // change the `allocatedCapacityInBits` to the `minimumCapacityInBits` as // this value is still safe to be used to call `_allocator->release(...)`. if (ASMJIT_UNLIKELY(allocatedCapacityInBits < allocatedCapacity)) allocatedCapacityInBits = minimumCapacityInBits; if (data) allocator->release(data, _capacity / 8); data = newData; _data = data; _capacity = uint32_t(allocatedCapacityInBits); } _size = newSize; _copyBits(data, other.data(), _wordsPerBits(newSize)); return kErrorOk; } Error ZoneBitVector::_resize(ZoneAllocator* allocator, uint32_t newSize, uint32_t idealCapacity, bool newBitsValue) noexcept { ASMJIT_ASSERT(idealCapacity >= newSize); if (newSize <= _size) { // The size after the resize is lesser than or equal to the current size. uint32_t idx = newSize / kBitWordSizeInBits; uint32_t bit = newSize % kBitWordSizeInBits; // Just set all bits outside of the new size in the last word to zero. // There is a case that there are not bits to set if `bit` is zero. This // happens when `newSize` is a multiply of `kBitWordSizeInBits` like 64, 128, // and so on. In that case don't change anything as that would mean settings // bits outside of the `_size`. if (bit) _data[idx] &= (BitWord(1) << bit) - 1u; _size = newSize; return kErrorOk; } uint32_t oldSize = _size; BitWord* data = _data; if (newSize > _capacity) { // Realloc needed, calculate the minimum capacity (in bytes) required. uint32_t minimumCapacityInBits = Support::alignUp(idealCapacity, kBitWordSizeInBits); if (ASMJIT_UNLIKELY(minimumCapacityInBits < newSize)) return DebugUtils::errored(kErrorOutOfMemory); // Normalize to bytes. uint32_t minimumCapacity = minimumCapacityInBits / 8; size_t allocatedCapacity; BitWord* newData = static_cast(allocator->alloc(minimumCapacity, allocatedCapacity)); if (ASMJIT_UNLIKELY(!newData)) return DebugUtils::errored(kErrorOutOfMemory); // `allocatedCapacity` now contains number in bytes, we need bits. size_t allocatedCapacityInBits = allocatedCapacity * 8; // Arithmetic overflow should normally not happen. If it happens we just // change the `allocatedCapacityInBits` to the `minimumCapacityInBits` as // this value is still safe to be used to call `_allocator->release(...)`. if (ASMJIT_UNLIKELY(allocatedCapacityInBits < allocatedCapacity)) allocatedCapacityInBits = minimumCapacityInBits; _copyBits(newData, data, _wordsPerBits(oldSize)); if (data) allocator->release(data, _capacity / 8); data = newData; _data = data; _capacity = uint32_t(allocatedCapacityInBits); } // Start (of the old size) and end (of the new size) bits uint32_t idx = oldSize / kBitWordSizeInBits; uint32_t startBit = oldSize % kBitWordSizeInBits; uint32_t endBit = newSize % kBitWordSizeInBits; // Set new bits to either 0 or 1. The `pattern` is used to set multiple // bits per bit-word and contains either all zeros or all ones. BitWord pattern = Support::bitMaskFromBool(newBitsValue); // First initialize the last bit-word of the old size. if (startBit) { uint32_t nBits = 0; if (idx == (newSize / kBitWordSizeInBits)) { // The number of bit-words is the same after the resize. In that case // we need to set only bits necessary in the current last bit-word. ASMJIT_ASSERT(startBit < endBit); nBits = endBit - startBit; } else { // There is be more bit-words after the resize. In that case we don't // have to be extra careful about the last bit-word of the old size. nBits = kBitWordSizeInBits - startBit; } data[idx++] |= pattern << nBits; } // Initialize all bit-words after the last bit-word of the old size. uint32_t endIdx = _wordsPerBits(newSize); while (idx < endIdx) data[idx++] = pattern; // Clear unused bits of the last bit-word. if (endBit) data[endIdx - 1] = pattern & ((BitWord(1) << endBit) - 1); _size = newSize; return kErrorOk; } Error ZoneBitVector::_append(ZoneAllocator* allocator, bool value) noexcept { uint32_t kThreshold = Globals::kGrowThreshold * 8; uint32_t newSize = _size + 1; uint32_t idealCapacity = _capacity; if (idealCapacity < 128) idealCapacity = 128; else if (idealCapacity <= kThreshold) idealCapacity *= 2; else idealCapacity += kThreshold; if (ASMJIT_UNLIKELY(idealCapacity < _capacity)) { if (ASMJIT_UNLIKELY(_size == std::numeric_limits::max())) return DebugUtils::errored(kErrorOutOfMemory); idealCapacity = newSize; } return _resize(allocator, newSize, idealCapacity, value); } // ZoneVector / ZoneBitVector - Tests // ================================== #if defined(ASMJIT_TEST) template static void test_zone_vector(ZoneAllocator* allocator, const char* typeName) { int i; int kMax = 100000; ZoneVector vec; INFO("ZoneVector<%s> basic tests", typeName); EXPECT(vec.append(allocator, 0) == kErrorOk); EXPECT(vec.empty() == false); EXPECT(vec.size() == 1); EXPECT(vec.capacity() >= 1); EXPECT(vec.indexOf(0) == 0); EXPECT(vec.indexOf(-11) == Globals::kNotFound); vec.clear(); EXPECT(vec.empty()); EXPECT(vec.size() == 0); EXPECT(vec.indexOf(0) == Globals::kNotFound); for (i = 0; i < kMax; i++) { EXPECT(vec.append(allocator, T(i)) == kErrorOk); } EXPECT(vec.empty() == false); EXPECT(vec.size() == uint32_t(kMax)); EXPECT(vec.indexOf(T(kMax - 1)) == uint32_t(kMax - 1)); EXPECT(vec.rbegin()[0] == kMax - 1); vec.release(allocator); } static void test_zone_bitvector(ZoneAllocator* allocator) { Zone zone(8096 - Zone::kBlockOverhead); uint32_t i, count; uint32_t kMaxCount = 100; ZoneBitVector vec; EXPECT(vec.empty()); EXPECT(vec.size() == 0); INFO("ZoneBitVector::resize()"); for (count = 1; count < kMaxCount; count++) { vec.clear(); EXPECT(vec.resize(allocator, count, false) == kErrorOk); EXPECT(vec.size() == count); for (i = 0; i < count; i++) EXPECT(vec.bitAt(i) == false); vec.clear(); EXPECT(vec.resize(allocator, count, true) == kErrorOk); EXPECT(vec.size() == count); for (i = 0; i < count; i++) EXPECT(vec.bitAt(i) == true); } INFO("ZoneBitVector::fillBits() / clearBits()"); for (count = 1; count < kMaxCount; count += 2) { vec.clear(); EXPECT(vec.resize(allocator, count) == kErrorOk); EXPECT(vec.size() == count); for (i = 0; i < (count + 1) / 2; i++) { bool value = bool(i & 1); if (value) vec.fillBits(i, count - i * 2); else vec.clearBits(i, count - i * 2); } for (i = 0; i < count; i++) { EXPECT(vec.bitAt(i) == bool(i & 1)); } } } UNIT(zone_vector) { Zone zone(8096 - Zone::kBlockOverhead); ZoneAllocator allocator(&zone); test_zone_vector(&allocator, "int"); test_zone_vector(&allocator, "int64_t"); test_zone_bitvector(&allocator); } #endif ASMJIT_END_NAMESPACE