add macOS implementation

This commit is contained in:
Nick Fisher
2023-09-05 23:13:59 +08:00
parent c522cd6ee9
commit 84e3124e04
457 changed files with 169627 additions and 15 deletions

View File

@@ -0,0 +1,840 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_ALLOCATOR_H
#define TNT_UTILS_ALLOCATOR_H
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/memalign.h>
#include <utils/Mutex.h>
#include <utils/SpinLock.h>
#include <atomic>
#include <cstddef>
#include <mutex>
#include <type_traits>
#include <assert.h>
#include <stdlib.h>
namespace utils {
namespace pointermath {
template <typename P, typename T>
static inline P* add(P* a, T b) noexcept {
return (P*)(uintptr_t(a) + uintptr_t(b));
}
template <typename P>
static inline P* align(P* p, size_t alignment) noexcept {
// alignment must be a power-of-two
assert(alignment && !(alignment & alignment-1));
return (P*)((uintptr_t(p) + alignment - 1) & ~(alignment - 1));
}
template <typename P>
static inline P* align(P* p, size_t alignment, size_t offset) noexcept {
P* const r = align(add(p, offset), alignment);
assert(r >= add(p, offset));
return r;
}
}
/* ------------------------------------------------------------------------------------------------
* LinearAllocator
*
* + Allocates blocks linearly
* + Cannot free individual blocks
* + Can free top of memory back up to a specified point
* + Doesn't call destructors
* ------------------------------------------------------------------------------------------------
*/
class LinearAllocator {
public:
// use memory area provided
LinearAllocator(void* begin, void* end) noexcept;
template <typename AREA>
explicit LinearAllocator(const AREA& area) : LinearAllocator(area.begin(), area.end()) { }
// Allocators can't be copied
LinearAllocator(const LinearAllocator& rhs) = delete;
LinearAllocator& operator=(const LinearAllocator& rhs) = delete;
// Allocators can be moved
LinearAllocator(LinearAllocator&& rhs) noexcept;
LinearAllocator& operator=(LinearAllocator&& rhs) noexcept;
~LinearAllocator() noexcept = default;
// our allocator concept
void* alloc(size_t size, size_t alignment = alignof(std::max_align_t), size_t extra = 0) UTILS_RESTRICT {
// branch-less allocation
void* const p = pointermath::align(current(), alignment, extra);
void* const c = pointermath::add(p, size);
bool success = c <= end();
set_current(success ? c : current());
return success ? p : nullptr;
}
// API specific to this allocator
void *getCurrent() UTILS_RESTRICT noexcept {
return current();
}
// free memory back to the specified point
void rewind(void* p) UTILS_RESTRICT noexcept {
assert(p>=mBegin && p<end());
set_current(p);
}
// frees all allocated blocks
void reset() UTILS_RESTRICT noexcept {
rewind(mBegin);
}
size_t allocated() const UTILS_RESTRICT noexcept {
return mSize;
}
size_t available() const UTILS_RESTRICT noexcept {
return mSize - mCur;
}
void swap(LinearAllocator& rhs) noexcept;
void *base() noexcept { return mBegin; }
void free(void*, size_t) UTILS_RESTRICT noexcept { }
private:
void* end() UTILS_RESTRICT noexcept { return pointermath::add(mBegin, mSize); }
void* current() UTILS_RESTRICT noexcept { return pointermath::add(mBegin, mCur); }
void set_current(void* p) UTILS_RESTRICT noexcept {
mCur = uint32_t(uintptr_t(p) - uintptr_t(mBegin));
}
void* mBegin = nullptr;
uint32_t mSize = 0;
uint32_t mCur = 0;
};
/* ------------------------------------------------------------------------------------------------
* HeapAllocator
*
* + uses malloc() for all allocations
* + frees blocks with free()
* ------------------------------------------------------------------------------------------------
*/
class HeapAllocator {
public:
HeapAllocator() noexcept = default;
template <typename AREA>
explicit HeapAllocator(const AREA&) { }
// our allocator concept
void* alloc(size_t size, size_t alignment = alignof(std::max_align_t), size_t extra = 0) {
// this allocator doesn't support 'extra'
assert(extra == 0);
return aligned_alloc(size, alignment);
}
void free(void* p) noexcept {
aligned_free(p);
}
void free(void* p, size_t) noexcept {
this->free(p);
}
~HeapAllocator() noexcept = default;
void swap(HeapAllocator&) noexcept { }
};
// ------------------------------------------------------------------------------------------------
class FreeList {
public:
FreeList() noexcept = default;
FreeList(void* begin, void* end, size_t elementSize, size_t alignment, size_t extra) noexcept;
FreeList(const FreeList& rhs) = delete;
FreeList& operator=(const FreeList& rhs) = delete;
FreeList(FreeList&& rhs) noexcept = default;
FreeList& operator=(FreeList&& rhs) noexcept = default;
void* pop() noexcept {
Node* const head = mHead;
mHead = head ? head->next : nullptr;
// this could indicate a use after free
assert(!mHead || mHead >= mBegin && mHead < mEnd);
return head;
}
void push(void* p) noexcept {
assert(p);
assert(p >= mBegin && p < mEnd);
// TODO: assert this is one of our pointer (i.e.: it's address match one of ours)
Node* const head = static_cast<Node*>(p);
head->next = mHead;
mHead = head;
}
void *getFirst() noexcept {
return mHead;
}
private:
struct Node {
Node* next;
};
static Node* init(void* begin, void* end,
size_t elementSize, size_t alignment, size_t extra) noexcept;
Node* mHead = nullptr;
#ifndef NDEBUG
// These are needed only for debugging...
void* mBegin = nullptr;
void* mEnd = nullptr;
#endif
};
class AtomicFreeList {
public:
AtomicFreeList() noexcept = default;
AtomicFreeList(void* begin, void* end,
size_t elementSize, size_t alignment, size_t extra) noexcept;
AtomicFreeList(const FreeList& rhs) = delete;
AtomicFreeList& operator=(const FreeList& rhs) = delete;
void* pop() noexcept {
Node* const storage = mStorage;
HeadPtr currentHead = mHead.load();
while (currentHead.offset >= 0) {
// The value of "next" we load here might already contain application data if another
// thread raced ahead of us. But in that case, the computed "newHead" will be discarded
// since compare_exchange_weak fails. Then this thread will loop with the updated
// value of currentHead, and try again.
Node* const next = storage[currentHead.offset].next.load(std::memory_order_relaxed);
const HeadPtr newHead{ next ? int32_t(next - storage) : -1, currentHead.tag + 1 };
// In the rare case that the other thread that raced ahead of us already returned the
// same mHead we just loaded, but it now has a different "next" value, the tag field will not
// match, and compare_exchange_weak will fail and prevent that particular race condition.
if (mHead.compare_exchange_weak(currentHead, newHead)) {
// This assert needs to occur after we have validated that there was no race condition
// Otherwise, next might already contain application data, if another thread
// raced ahead of us after we loaded mHead, but before we loaded mHead->next.
assert(!next || next >= storage);
break;
}
}
void* p = (currentHead.offset >= 0) ? (storage + currentHead.offset) : nullptr;
assert(!p || p >= storage);
return p;
}
void push(void* p) noexcept {
Node* const storage = mStorage;
assert(p && p >= storage);
Node* const node = static_cast<Node*>(p);
HeadPtr currentHead = mHead.load();
HeadPtr newHead = { int32_t(node - storage), currentHead.tag + 1 };
do {
newHead.tag = currentHead.tag + 1;
Node* const n = (currentHead.offset >= 0) ? (storage + currentHead.offset) : nullptr;
node->next.store(n, std::memory_order_relaxed);
} while(!mHead.compare_exchange_weak(currentHead, newHead));
}
void* getFirst() noexcept {
return mStorage + mHead.load(std::memory_order_relaxed).offset;
}
private:
struct Node {
// This should be a regular (non-atomic) pointer, but this causes TSAN to complain
// about a data-race that exists but is benin. We always use this atomic<> in
// relaxed mode.
// The data race TSAN complains about is when a pop() is interrupted by a
// pop() + push() just after mHead->next is read -- it appears as though it is written
// without synchronization (by the push), however in that case, the pop's CAS will fail
// and things will auto-correct.
//
// Pop() |
// | |
// read head->next |
// | pop()
// | |
// | read head->next
// | CAS, tag++
// | |
// | push()
// | |
// [TSAN: data-race here] write head->next
// | CAS, tag++
// CAS fails
// |
// read head->next
// |
// CAS, tag++
//
std::atomic<Node*> next;
};
// This struct is using a 32-bit offset into the arena rather than
// a direct pointer, because together with the 32-bit tag, it needs to
// fit into 8 bytes. If it was any larger, it would not be possible to
// access it atomically.
struct alignas(8) HeadPtr {
int32_t offset;
uint32_t tag;
};
std::atomic<HeadPtr> mHead{};
Node* mStorage = nullptr;
};
// ------------------------------------------------------------------------------------------------
template <
size_t ELEMENT_SIZE,
size_t ALIGNMENT = alignof(std::max_align_t),
size_t OFFSET = 0,
typename FREELIST = FreeList>
class PoolAllocator {
static_assert(ELEMENT_SIZE >= sizeof(void*), "ELEMENT_SIZE must accommodate at least a pointer");
public:
// our allocator concept
void* alloc(size_t size = ELEMENT_SIZE,
size_t alignment = ALIGNMENT, size_t offset = OFFSET) noexcept {
assert(size <= ELEMENT_SIZE);
assert(alignment <= ALIGNMENT);
assert(offset == OFFSET);
return mFreeList.pop();
}
void free(void* p, size_t = ELEMENT_SIZE) noexcept {
mFreeList.push(p);
}
constexpr size_t getSize() const noexcept { return ELEMENT_SIZE; }
PoolAllocator(void* begin, void* end) noexcept
: mFreeList(begin, end, ELEMENT_SIZE, ALIGNMENT, OFFSET) {
}
template <typename AREA>
explicit PoolAllocator(const AREA& area) noexcept
: PoolAllocator(area.begin(), area.end()) {
}
// Allocators can't be copied
PoolAllocator(const PoolAllocator& rhs) = delete;
PoolAllocator& operator=(const PoolAllocator& rhs) = delete;
// Allocators can be moved
PoolAllocator(PoolAllocator&& rhs) = default;
PoolAllocator& operator=(PoolAllocator&& rhs) = default;
PoolAllocator() noexcept = default;
~PoolAllocator() noexcept = default;
// API specific to this allocator
void *getCurrent() noexcept {
return mFreeList.getFirst();
}
private:
FREELIST mFreeList;
};
#define UTILS_MAX(a,b) ((a) > (b) ? (a) : (b))
template <typename T, size_t OFFSET = 0>
using ObjectPoolAllocator = PoolAllocator<sizeof(T),
UTILS_MAX(alignof(FreeList), alignof(T)), OFFSET>;
template <typename T, size_t OFFSET = 0>
using ThreadSafeObjectPoolAllocator = PoolAllocator<sizeof(T),
UTILS_MAX(alignof(FreeList), alignof(T)), OFFSET, AtomicFreeList>;
// ------------------------------------------------------------------------------------------------
// Areas
// ------------------------------------------------------------------------------------------------
namespace AreaPolicy {
class StaticArea {
public:
StaticArea() noexcept = default;
StaticArea(void* b, void* e) noexcept
: mBegin(b), mEnd(e) {
}
~StaticArea() noexcept = default;
StaticArea(const StaticArea& rhs) = default;
StaticArea& operator=(const StaticArea& rhs) = default;
StaticArea(StaticArea&& rhs) noexcept = default;
StaticArea& operator=(StaticArea&& rhs) noexcept = default;
void* data() const noexcept { return mBegin; }
void* begin() const noexcept { return mBegin; }
void* end() const noexcept { return mEnd; }
size_t size() const noexcept { return uintptr_t(mEnd) - uintptr_t(mBegin); }
friend void swap(StaticArea& lhs, StaticArea& rhs) noexcept {
using std::swap;
swap(lhs.mBegin, rhs.mBegin);
swap(lhs.mEnd, rhs.mEnd);
}
private:
void* mBegin = nullptr;
void* mEnd = nullptr;
};
class HeapArea {
public:
HeapArea() noexcept = default;
explicit HeapArea(size_t size) {
if (size) {
// TODO: policy committing memory
mBegin = malloc(size);
mEnd = pointermath::add(mBegin, size);
}
}
~HeapArea() noexcept {
// TODO: policy for returning memory to system
free(mBegin);
}
HeapArea(const HeapArea& rhs) = delete;
HeapArea& operator=(const HeapArea& rhs) = delete;
HeapArea(HeapArea&& rhs) noexcept = delete;
HeapArea& operator=(HeapArea&& rhs) noexcept = delete;
void* data() const noexcept { return mBegin; }
void* begin() const noexcept { return mBegin; }
void* end() const noexcept { return mEnd; }
size_t size() const noexcept { return uintptr_t(mEnd) - uintptr_t(mBegin); }
friend void swap(HeapArea& lhs, HeapArea& rhs) noexcept {
using std::swap;
swap(lhs.mBegin, rhs.mBegin);
swap(lhs.mEnd, rhs.mEnd);
}
private:
void* mBegin = nullptr;
void* mEnd = nullptr;
};
class NullArea {
public:
void* data() const noexcept { return nullptr; }
size_t size() const noexcept { return 0; }
};
} // namespace AreaPolicy
// ------------------------------------------------------------------------------------------------
// Policies
// ------------------------------------------------------------------------------------------------
namespace LockingPolicy {
struct NoLock {
void lock() noexcept { }
void unlock() noexcept { }
};
using SpinLock = utils::SpinLock;
using Mutex = utils::Mutex;
} // namespace LockingPolicy
namespace TrackingPolicy {
// default no-op tracker
struct Untracked {
Untracked() noexcept = default;
Untracked(const char* name, void* base, size_t size) noexcept {
(void)name, void(base), (void)size;
}
void onAlloc(void* p, size_t size, size_t alignment, size_t extra) noexcept {
(void)p, (void)size, (void)alignment, (void)extra;
}
void onFree(void* p, size_t = 0) noexcept { (void)p; }
void onReset() noexcept { }
void onRewind(void* addr) noexcept { (void)addr; }
};
// This just track the max memory usage and logs it in the destructor
struct HighWatermark {
HighWatermark() noexcept = default;
HighWatermark(const char* name, void* base, size_t size) noexcept
: mName(name), mBase(base), mSize(uint32_t(size)) { }
~HighWatermark() noexcept;
void onAlloc(void* p, size_t size, size_t alignment, size_t extra) noexcept;
void onFree(void* p, size_t size) noexcept;
void onReset() noexcept;
void onRewind(void const* addr) noexcept;
uint32_t getHighWatermark() const noexcept { return mHighWaterMark; }
protected:
const char* mName = nullptr;
void* mBase = nullptr;
uint32_t mSize = 0;
uint32_t mCurrent = 0;
uint32_t mHighWaterMark = 0;
};
// This just fills buffers with known values to help catch uninitialized access and use after free.
struct Debug {
Debug() noexcept = default;
Debug(const char* name, void* base, size_t size) noexcept
: mName(name), mBase(base), mSize(uint32_t(size)) { }
void onAlloc(void* p, size_t size, size_t alignment, size_t extra) noexcept;
void onFree(void* p, size_t size) noexcept;
void onReset() noexcept;
void onRewind(void* addr) noexcept;
protected:
const char* mName = nullptr;
void* mBase = nullptr;
uint32_t mSize = 0;
};
struct DebugAndHighWatermark : protected HighWatermark, protected Debug {
DebugAndHighWatermark() noexcept = default;
DebugAndHighWatermark(const char* name, void* base, size_t size) noexcept
: HighWatermark(name, base, size), Debug(name, base, size) { }
void onAlloc(void* p, size_t size, size_t alignment, size_t extra) noexcept {
HighWatermark::onAlloc(p, size, alignment, extra);
Debug::onAlloc(p, size, alignment, extra);
}
void onFree(void* p, size_t size) noexcept {
HighWatermark::onFree(p, size);
Debug::onFree(p, size);
}
void onReset() noexcept {
HighWatermark::onReset();
Debug::onReset();
}
void onRewind(void* addr) noexcept {
HighWatermark::onRewind(addr);
Debug::onRewind(addr);
}
};
} // namespace TrackingPolicy
// ------------------------------------------------------------------------------------------------
// Arenas
// ------------------------------------------------------------------------------------------------
template<typename AllocatorPolicy, typename LockingPolicy,
typename TrackingPolicy = TrackingPolicy::Untracked,
typename AreaPolicy = AreaPolicy::HeapArea>
class Arena {
public:
Arena() = default;
// construct an arena with a name and forward argument to its allocator
template<typename ... ARGS>
Arena(const char* name, size_t size, ARGS&& ... args)
: mArenaName(name),
mArea(size),
mAllocator(mArea, std::forward<ARGS>(args) ... ),
mListener(name, mArea.data(), mArea.size()) {
}
template<typename ... ARGS>
Arena(const char* name, AreaPolicy&& area, ARGS&& ... args)
: mArenaName(name),
mArea(std::forward<AreaPolicy>(area)),
mAllocator(mArea, std::forward<ARGS>(args) ... ),
mListener(name, mArea.data(), mArea.size()) {
}
// allocate memory from arena with given size and alignment
// (acceptable size/alignment may depend on the allocator provided)
void* alloc(size_t size, size_t alignment = alignof(std::max_align_t), size_t extra = 0) noexcept {
std::lock_guard<LockingPolicy> guard(mLock);
void* p = mAllocator.alloc(size, alignment, extra);
mListener.onAlloc(p, size, alignment, extra);
return p;
}
// Allocate an array of trivially destructible objects
// for safety, we disable the object-based alloc method if the object type is not
// trivially destructible, since free() won't call the destructor and this is allocating
// an array.
template <typename T,
typename = typename std::enable_if<std::is_trivially_destructible<T>::value>::type>
T* alloc(size_t count, size_t alignment = alignof(T), size_t extra = 0) noexcept {
return (T*)alloc(count * sizeof(T), alignment, extra);
}
// return memory pointed by p to the arena
// (actual behaviour may depend on allocator provided)
void free(void* p) noexcept {
if (p) {
std::lock_guard<LockingPolicy> guard(mLock);
mListener.onFree(p);
mAllocator.free(p);
}
}
// some allocators require the size of the allocation for free
void free(void* p, size_t size) noexcept {
if (p) {
std::lock_guard<LockingPolicy> guard(mLock);
mListener.onFree(p, size);
mAllocator.free(p, size);
}
}
// some allocators don't have a free() call, but a single reset() or rewind() instead
void reset() noexcept {
std::lock_guard<LockingPolicy> guard(mLock);
mListener.onReset();
mAllocator.reset();
}
void* getCurrent() noexcept { return mAllocator.getCurrent(); }
void rewind(void *addr) noexcept {
std::lock_guard<LockingPolicy> guard(mLock);
mListener.onRewind(addr);
mAllocator.rewind(addr);
}
// Allocate and construct an object
template<typename T, size_t ALIGN = alignof(T), typename... ARGS>
T* make(ARGS&& ... args) noexcept {
void* const p = this->alloc(sizeof(T), ALIGN);
return p ? new(p) T(std::forward<ARGS>(args)...) : nullptr;
}
// destroys an object created with make<T>() above, and frees associated memory
template<typename T>
void destroy(T* p) noexcept {
if (p) {
p->~T();
this->free((void*)p, sizeof(T));
}
}
char const* getName() const noexcept { return mArenaName; }
AllocatorPolicy& getAllocator() noexcept { return mAllocator; }
AllocatorPolicy const& getAllocator() const noexcept { return mAllocator; }
TrackingPolicy& getListener() noexcept { return mListener; }
TrackingPolicy const& getListener() const noexcept { return mListener; }
AreaPolicy& getArea() noexcept { return mArea; }
AreaPolicy const& getArea() const noexcept { return mArea; }
void setListener(TrackingPolicy listener) noexcept {
std::swap(mListener, listener);
}
template <typename ... ARGS>
void emplaceListener(ARGS&& ... args) noexcept {
mListener.~TrackingPolicy();
new (&mListener) TrackingPolicy(std::forward<ARGS>(args)...);
}
// An arena can't be copied
Arena(Arena const& rhs) noexcept = delete;
Arena& operator=(Arena const& rhs) noexcept = delete;
friend void swap(Arena& lhs, Arena& rhs) noexcept {
using std::swap;
swap(lhs.mArea, rhs.mArea);
swap(lhs.mAllocator, rhs.mAllocator);
swap(lhs.mLock, rhs.mLock);
swap(lhs.mListener, rhs.mListener);
swap(lhs.mArenaName, rhs.mArenaName);
}
private:
char const* mArenaName = nullptr;
AreaPolicy mArea;
// note: we should use something like compressed_pair for the members below
AllocatorPolicy mAllocator;
LockingPolicy mLock;
TrackingPolicy mListener;
};
// ------------------------------------------------------------------------------------------------
template<typename TrackingPolicy = TrackingPolicy::Untracked>
using HeapArena = Arena<HeapAllocator, LockingPolicy::NoLock, TrackingPolicy>;
// ------------------------------------------------------------------------------------------------
// This doesn't implement our allocator concept, because it's too risky to use this as an allocator
// in particular, doing ArenaScope<ArenaScope>.
template<typename ARENA>
class ArenaScope {
struct Finalizer {
void (*finalizer)(void* p) = nullptr;
Finalizer* next = nullptr;
};
template <typename T>
static void destruct(void* p) noexcept {
static_cast<T*>(p)->~T();
}
public:
explicit ArenaScope(ARENA& allocator)
: mArena(allocator), mRewind(allocator.getCurrent()) {
}
ArenaScope& operator=(const ArenaScope& rhs) = delete;
ArenaScope(ArenaScope&& rhs) noexcept = delete;
ArenaScope& operator=(ArenaScope&& rhs) noexcept = delete;
~ArenaScope() {
// run the finalizer chain
Finalizer* head = mFinalizerHead;
while (head) {
void* p = pointermath::add(head, sizeof(Finalizer));
head->finalizer(p);
head = head->next;
}
// ArenaScope works only with Arena that implements rewind()
mArena.rewind(mRewind);
}
template<typename T, size_t ALIGN = alignof(T), typename... ARGS>
T* make(ARGS&& ... args) noexcept {
T* o = nullptr;
if (std::is_trivially_destructible<T>::value) {
o = mArena.template make<T, ALIGN>(std::forward<ARGS>(args)...);
} else {
void* const p = (Finalizer*)mArena.alloc(sizeof(T), ALIGN, sizeof(Finalizer));
if (p != nullptr) {
Finalizer* const f = static_cast<Finalizer*>(p) - 1;
// constructor must be called before adding the dtor to the list
// so that the ctor can allocate objects in a nested scope and have the
// finalizers called in reverse order.
o = new(p) T(std::forward<ARGS>(args)...);
f->finalizer = &destruct<T>;
f->next = mFinalizerHead;
mFinalizerHead = f;
}
}
return o;
}
void* allocate(size_t size, size_t alignment = 1) noexcept {
return mArena.template alloc<uint8_t>(size, alignment, 0);
}
template <typename T>
T* allocate(size_t size, size_t alignment = alignof(T), size_t extra = 0) noexcept {
return mArena.template alloc<T>(size, alignment, extra);
}
// use with caution
ARENA& getAllocator() noexcept { return mArena; }
private:
ARENA& mArena;
void* mRewind = nullptr;
Finalizer* mFinalizerHead = nullptr;
};
template <typename TYPE, typename ARENA>
class STLAllocator {
public:
using value_type = TYPE;
using pointer = TYPE*;
using const_pointer = const TYPE*;
using reference = TYPE&;
using const_reference = const TYPE&;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using propagate_on_container_move_assignment = std::true_type;
using is_always_equal = std::true_type;
template<typename OTHER>
struct rebind { using other = STLAllocator<OTHER, ARENA>; };
public:
// we don't make this explicit, so that we can initialize a vector using a STLAllocator
// from an Arena, avoiding to have to repeat the vector type.
STLAllocator(ARENA& arena) : mArena(arena) { } // NOLINT(google-explicit-constructor)
template<typename U>
explicit STLAllocator(STLAllocator<U, ARENA> const& rhs) : mArena(rhs.mArena) { }
TYPE* allocate(std::size_t n) {
auto p = static_cast<TYPE *>(mArena.alloc(n * sizeof(TYPE), alignof(TYPE)));
assert_invariant(p);
return p;
}
void deallocate(TYPE* p, std::size_t n) {
mArena.free(p, n * sizeof(TYPE));
}
// these should be out-of-class friends, but this doesn't seem to work with some compilers
// which complain about multiple definition each time a STLAllocator<> is instantiated.
template <typename U, typename A>
bool operator==(const STLAllocator<U, A>& rhs) const noexcept {
return std::addressof(mArena) == std::addressof(rhs.mArena);
}
template <typename U, typename A>
bool operator!=(const STLAllocator<U, A>& rhs) const noexcept {
return !operator==(rhs);
}
private:
template<typename U, typename A>
friend class STLAllocator;
ARENA& mArena;
};
} // namespace utils
#endif // TNT_UTILS_ALLOCATOR_H

View File

@@ -0,0 +1,116 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_BINARYTREEARRAY_H
#define TNT_UTILS_BINARYTREEARRAY_H
#include <utils/compiler.h>
#include <type_traits>
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
namespace utils {
class BinaryTreeArray {
// Simple fixed capacity stack
template<typename TYPE, size_t CAPACITY,
typename = typename std::enable_if<std::is_trivial<TYPE>::value>::type>
class stack {
TYPE mElements[CAPACITY];
size_t mSize = 0;
public:
bool empty() const noexcept { return mSize == 0; }
void push(TYPE const& v) noexcept {
assert(mSize < CAPACITY);
mElements[mSize++] = v;
}
void pop() noexcept {
assert(mSize > 0);
--mSize;
}
const TYPE& back() const noexcept {
return mElements[mSize - 1];
}
};
public:
static size_t count(size_t height) noexcept { return (1u << height) - 1; }
static size_t left(size_t i, size_t /*height*/) noexcept { return i + 1; }
static size_t right(size_t i, size_t height) noexcept {
return i + (size_t(1) << (height - 1));
}
// this builds the depth-first binary tree array top down (post-order)
template<typename Leaf, typename Node>
static void traverse(size_t height, Leaf leaf, Node node) noexcept {
struct TNode {
uint32_t index;
uint32_t col;
uint32_t height;
uint32_t next;
bool isLeaf() const noexcept { return height == 1; }
size_t left() const noexcept { return BinaryTreeArray::left(index, height); }
size_t right() const noexcept { return BinaryTreeArray::right(index, height); }
};
stack<TNode, 16> stack;
stack.push(TNode{ 0, 0, (uint32_t)height, (uint32_t)count(height) });
uint32_t prevLeft = 0;
uint32_t prevRight = 0;
uint32_t prevIndex = 0;
while (!stack.empty()) {
TNode const* const UTILS_RESTRICT curr = &stack.back();
const bool isLeaf = curr->isLeaf();
const uint32_t index = curr->index;
const uint32_t l = (uint32_t)curr->left();
const uint32_t r = (uint32_t)curr->right();
if (prevLeft == index || prevRight == index) {
if (!isLeaf) {
// the 'next' node of our left node's right descendants is our right child
stack.push({ l, 2 * curr->col, curr->height - 1, r });
}
} else if (l == prevIndex) {
if (!isLeaf) {
// the 'next' node of our right child is our own 'next' sibling
stack.push({ r, 2 * curr->col + 1, curr->height - 1, curr->next });
}
} else {
if (!isLeaf) {
node(index, l, r, curr->next);
} else {
leaf(index, curr->col, curr->next);
}
stack.pop();
}
prevLeft = l;
prevRight = r;
prevIndex = index;
}
}
};
} // namespace utils
#endif //TNT_UTILS_BINARYTREEARRAY_H

View File

@@ -0,0 +1,144 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_BITMASKENUM_H
#define TNT_UTILS_BITMASKENUM_H
#include <utils/compiler.h>
#include <type_traits> // for std::false_type
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
namespace utils {
template<typename Enum>
struct EnableBitMaskOperators : public std::false_type { };
template<typename Enum>
struct EnableIntegerOperators : public std::false_type { };
namespace Enum {
template<typename Enum>
size_t count();
} // namespace enum
} // namespace utils
// ------------------------------------------------------------------------------------------------
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableIntegerOperators<Enum>::value, int> = 0>
inline constexpr int operator+(Enum value) noexcept {
return int(value);
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableIntegerOperators<Enum>::value, int> = 0>
inline constexpr bool operator==(Enum lhs, size_t rhs) noexcept {
using underlying_t = std::underlying_type_t<Enum>;
return underlying_t(lhs) == rhs;
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableIntegerOperators<Enum>::value, int> = 0>
inline constexpr bool operator==(size_t lhs, Enum rhs) noexcept {
return rhs == lhs;
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableIntegerOperators<Enum>::value, int> = 0>
inline constexpr bool operator!=(Enum lhs, size_t rhs) noexcept {
return !(rhs == lhs);
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableIntegerOperators<Enum>::value, int> = 0>
inline constexpr bool operator!=(size_t lhs, Enum rhs) noexcept {
return rhs != lhs;
}
// ------------------------------------------------------------------------------------------------
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableBitMaskOperators<Enum>::value, int> = 0>
inline constexpr bool operator!(Enum rhs) noexcept {
using underlying = std::underlying_type_t<Enum>;
return underlying(rhs) == 0;
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableBitMaskOperators<Enum>::value, int> = 0>
inline constexpr Enum operator~(Enum rhs) noexcept {
using underlying = std::underlying_type_t<Enum>;
return Enum(~underlying(rhs));
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableBitMaskOperators<Enum>::value, int> = 0>
inline constexpr Enum operator|(Enum lhs, Enum rhs) noexcept {
using underlying = std::underlying_type_t<Enum>;
return Enum(underlying(lhs) | underlying(rhs));
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableBitMaskOperators<Enum>::value, int> = 0>
inline constexpr Enum operator&(Enum lhs, Enum rhs) noexcept {
using underlying = std::underlying_type_t<Enum>;
return Enum(underlying(lhs) & underlying(rhs));
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableBitMaskOperators<Enum>::value, int> = 0>
inline constexpr Enum operator^(Enum lhs, Enum rhs) noexcept {
using underlying = std::underlying_type_t<Enum>;
return Enum(underlying(lhs) ^ underlying(rhs));
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableBitMaskOperators<Enum>::value, int> = 0>
inline constexpr Enum operator|=(Enum& lhs, Enum rhs) noexcept {
return lhs = lhs | rhs;
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableBitMaskOperators<Enum>::value, int> = 0>
inline constexpr Enum operator&=(Enum& lhs, Enum rhs) noexcept {
return lhs = lhs & rhs;
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableBitMaskOperators<Enum>::value, int> = 0>
inline constexpr Enum operator^=(Enum& lhs, Enum rhs) noexcept {
return lhs = lhs ^ rhs;
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableBitMaskOperators<Enum>::value, int> = 0>
inline constexpr bool none(Enum lhs) noexcept {
return !lhs;
}
template<typename Enum, typename std::enable_if_t<
std::is_enum<Enum>::value && utils::EnableBitMaskOperators<Enum>::value, int> = 0>
inline constexpr bool any(Enum lhs) noexcept {
return !none(lhs);
}
#endif // TNT_UTILS_BITMASKENUM_H

View File

@@ -0,0 +1,230 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_CSTRING_H
#define TNT_UTILS_CSTRING_H
// NOTE: this header should not include STL headers
#include <utils/compiler.h>
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
namespace utils {
//! \privatesection
struct hashCStrings {
typedef const char* argument_type;
typedef size_t result_type;
result_type operator()(argument_type cstr) const noexcept {
size_t hash = 5381;
while (int c = *cstr++) {
hash = (hash * 33u) ^ size_t(c);
}
return hash;
}
};
template <size_t N>
using StringLiteral = const char[N];
// ------------------------------------------------------------------------------------------------
class UTILS_PUBLIC CString {
public:
using value_type = char;
using size_type = uint32_t;
using difference_type = int32_t;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = value_type*;
using const_pointer = const value_type*;
using iterator = value_type*;
using const_iterator = const value_type*;
CString() noexcept {} // NOLINT(modernize-use-equals-default), Ubuntu compiler bug
// Allocates memory and appends a null. This constructor can be used to hold arbitrary data
// inside the string (i.e. it can contain nulls or non-ASCII encodings).
CString(const char* cstr, size_t length);
// Allocates memory for a string of size length plus space for the null terminating character.
// Also initializes the memory to 0. This constructor can be used to hold arbitrary data
// inside the string.
explicit CString(size_t length);
// Allocates memory and copies traditional C string content. Unlike the above constructor, this
// does not allow embedded nulls. This is explicit because this operation is costly.
explicit CString(const char* cstr);
template<size_t N>
CString(StringLiteral<N> const& other) noexcept // NOLINT(google-explicit-constructor)
: CString(other, N - 1) {
}
CString(const CString& rhs);
CString(CString&& rhs) noexcept {
this->swap(rhs);
}
CString& operator=(const CString& rhs);
CString& operator=(CString&& rhs) noexcept {
this->swap(rhs);
return *this;
}
~CString() noexcept {
if (mData) {
free(mData - 1);
}
}
void swap(CString& other) noexcept {
// don't use std::swap(), we don't want an STL dependency in this file
auto *temp = mCStr;
mCStr = other.mCStr;
other.mCStr = temp;
}
const_pointer c_str() const noexcept { return mCStr; }
pointer c_str() noexcept { return mCStr; }
const_pointer c_str_safe() const noexcept { return mData ? c_str() : ""; }
const_pointer data() const noexcept { return c_str(); }
pointer data() noexcept { return c_str(); }
size_type size() const noexcept { return mData ? mData[-1].length : 0; }
size_type length() const noexcept { return size(); }
bool empty() const noexcept { return size() == 0; }
iterator begin() noexcept { return mCStr; }
iterator end() noexcept { return begin() + length(); }
const_iterator begin() const noexcept { return data(); }
const_iterator end() const noexcept { return begin() + length(); }
const_iterator cbegin() const noexcept { return begin(); }
const_iterator cend() const noexcept { return end(); }
CString& replace(size_type pos, size_type len, const CString& str) noexcept;
CString& insert(size_type pos, const CString& str) noexcept { return replace(pos, 0, str); }
const_reference operator[](size_type pos) const noexcept {
assert(pos < size());
return begin()[pos];
}
reference operator[](size_type pos) noexcept {
assert(pos < size());
return begin()[pos];
}
const_reference at(size_type pos) const noexcept {
assert(pos < size());
return begin()[pos];
}
reference at(size_type pos) noexcept {
assert(pos < size());
return begin()[pos];
}
reference front() noexcept {
assert(size());
return begin()[0];
}
const_reference front() const noexcept {
assert(size());
return begin()[0];
}
reference back() noexcept {
assert(size());
return begin()[size() - 1];
}
const_reference back() const noexcept {
assert(size());
return begin()[size() - 1];
}
// placement new declared as "throw" to avoid the compiler's null-check
inline void* operator new(size_t, void* ptr) {
assert(ptr);
return ptr;
}
struct Hasher : private hashCStrings {
typedef CString argument_type;
typedef size_t result_type;
result_type operator()(const argument_type& s) const noexcept {
return hashCStrings::operator()(s.c_str());
}
};
private:
struct Data {
size_type length;
};
// mCStr points to the C-string or nullptr. if non-null, mCStr is preceded by the string's size
union {
value_type *mCStr = nullptr;
Data* mData; // Data is stored at mData[-1]
};
int compare(const CString& rhs) const noexcept {
size_type lhs_size = size();
size_type rhs_size = rhs.size();
if (lhs_size < rhs_size) return -1;
if (lhs_size > rhs_size) return 1;
return strncmp(data(), rhs.data(), size());
}
friend bool operator==(CString const& lhs, CString const& rhs) noexcept {
return (lhs.data() == rhs.data()) ||
((lhs.size() == rhs.size()) && !strncmp(lhs.data(), rhs.data(), lhs.size()));
}
friend bool operator!=(CString const& lhs, CString const& rhs) noexcept {
return !(lhs == rhs);
}
friend bool operator<(CString const& lhs, CString const& rhs) noexcept {
return lhs.compare(rhs) < 0;
}
friend bool operator>(CString const& lhs, CString const& rhs) noexcept {
return lhs.compare(rhs) > 0;
}
friend bool operator>=(CString const& lhs, CString const& rhs) noexcept {
return !(lhs < rhs);
}
friend bool operator<=(CString const& lhs, CString const& rhs) noexcept {
return !(lhs > rhs);
}
};
// implement this for your type for automatic conversion to CString. Failing to do so leads
// to a compile-time failure.
template<typename T>
CString to_string(T value) noexcept;
} // namespace utils
#endif // TNT_UTILS_CSTRING_H

View File

@@ -0,0 +1,127 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UTILS_CALLSTACK_H
#define UTILS_CALLSTACK_H
#include <stddef.h>
#include <stdint.h>
#include <typeinfo>
#include <utils/CString.h>
#include <utils/Log.h>
namespace utils {
/**
* CallStack captures the current's thread call stack.
*/
class CallStack {
public:
/**
* Creates an empty call stack
* @see CallStack::capture()
*/
CallStack() = default;
CallStack(const CallStack&) = default;
~CallStack() = default;
/**
* A convenience method to create and capture the stack trace in one go.
* @param ignore number frames to ignore at the top of the stack.
* @return A CallStack object
*/
static CallStack unwind(size_t ignore = 0) noexcept;
/**
* Capture the current thread's stack and replaces the existing one if any.
* @param ignore number frames to ignore at the top of the stack.
*/
void update(size_t ignore = 0) noexcept;
/**
* Get the number of stack frames this object has recorded.
* @return How many stack frames are accessible through operator[]
*/
size_t getFrameCount() const noexcept;
/**
* Return the program-counter of each stack frame captured
* @param index of the frame between 0 and getFrameCount()-1
* @return the program-counter of the stack-frame recorded at index \p index
* @throw std::out_of_range if the index is out of range
*/
intptr_t operator [](size_t index) const;
/** Demangles a C++ type name */
static utils::CString demangleTypeName(const char* mangled);
template<typename T>
static utils::CString typeName() {
#if UTILS_HAS_RTTI
return demangleTypeName(typeid(T).name());
#else
return CString("<no-rtti>");
#endif
}
/**
* Outputs a CallStack into a stream.
* This will print, when possible, the demangled names of functions corresponding to the
* program-counter recorded.
*/
friend utils::io::ostream& operator <<(utils::io::ostream& stream, const CallStack& callstack);
bool operator <(const CallStack& rhs) const;
inline bool operator >(const CallStack& rhs) const {
return rhs < *this;
}
inline bool operator !=(const CallStack& rhs) const {
return *this < rhs || rhs < *this;
}
inline bool operator >=(const CallStack& rhs) const {
return !operator <(rhs);
}
inline bool operator <=(const CallStack& rhs) const {
return !operator >(rhs);
}
inline bool operator ==(const CallStack& rhs) const {
return !operator !=(rhs);
}
private:
void update_gcc(size_t ignore) noexcept;
static utils::CString demangle(const char* mangled);
static constexpr size_t NUM_FRAMES = 20;
struct StackFrameInfo {
intptr_t pc;
};
size_t m_frame_count = 0;
StackFrameInfo m_stack[NUM_FRAMES];
};
} // namespace utils
#endif // UTILS_CALLSTACK_H

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_CONDITION_H
#define TNT_UTILS_CONDITION_H
#if defined(__ANDROID__)
#include <utils/linux/Condition.h>
#else
#include <utils/generic/Condition.h>
#endif
#endif // TNT_UTILS_CONDITION_H

View File

@@ -0,0 +1,91 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_COUNTDOWNLATCH_H
#define TNT_UTILS_COUNTDOWNLATCH_H
#include <stddef.h>
// note: we use our version of mutex/condition to keep this public header STL free
#include <utils/Condition.h>
#include <utils/Mutex.h>
namespace utils {
/**
* A count down latch is used to block one or several threads until the latch is signaled
* a certain number of times.
*
* Threads entering the latch are blocked until the latch is signaled enough times.
*
* @see CyclicBarrier
*/
class CountDownLatch {
public:
/**
* Creates a count down latch with a specified count. The minimum useful value is 1.
* @param count the latch counter initial value
*/
explicit CountDownLatch(size_t count) noexcept;
~CountDownLatch() = default;
/**
* Blocks until latch() is called \p count times.
* @see CountDownLatch(size_t count)
*/
void await() noexcept;
/**
* Releases threads blocked in await() when called \p count times. Calling latch() more than
* \p count times has no effect.
* @see reset()
*/
void latch() noexcept;
/**
* Resets the count-down latch to the given value.
*
* @param new_count New latch count. A value of zero will immediately unblock all waiting
* threads.
*
* @warning Use with caution. It's only safe to reset the latch count when you're sure
* that no threads are waiting in await(). This can be guaranteed in various ways, for
* instance, if you have a single thread calling await(), you could call reset() from that
* thread, or you could use a CyclicBarrier to make sure all threads using the CountDownLatch
* are at a known place (i.e.: not in await()) when reset() is called.
*/
void reset(size_t new_count) noexcept;
/**
* @return the number of times latch() has been called since construction or reset.
* @see reset(), CountDownLatch(size_t count)
*/
size_t getCount() const noexcept;
CountDownLatch() = delete;
CountDownLatch(const CountDownLatch&) = delete;
CountDownLatch& operator=(const CountDownLatch&) = delete;
private:
uint32_t m_initial_count;
uint32_t m_remaining_count;
mutable Mutex m_lock;
mutable Condition m_cv;
};
} // namespace utils
#endif // TNT_UTILS_COUNTDOWNLATCH_H

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_CYCLIC_BARRIER_H
#define TNT_UTILS_CYCLIC_BARRIER_H
#include <stddef.h>
// note: we use our version of mutex/condition to keep this public header STL free
#include <utils/Condition.h>
#include <utils/Mutex.h>
namespace utils {
/**
* A cyclic barrier is used to synchronize several threads to a particular execution point.
*
* Threads entering the barrier are halted until all threads reach the barrier.
*
* @see CountDownLatch
*/
class CyclicBarrier {
public:
/**
* Creates a cyclic barrier with a specified number of threads to synchronize. The minimum
* useful value is 2. A value of 0 is invalid and is silently changed to 1.
* @param num_threads Number of threads to synchronize.
*/
explicit CyclicBarrier(size_t num_threads) noexcept;
/**
* @return The number of thread that are synchronized.
*/
size_t getThreadCount() const noexcept;
/**
* @return Number of threads currently waiting on the barrier.
*/
size_t getWaitingThreadCount() const noexcept;
/**
* Blocks until getThreadCount()-1 other threads reach await().
*/
void await() noexcept;
/**
* Resets the cyclic barrier to its original state and releases all waiting threads.
*/
void reset() noexcept;
CyclicBarrier() = delete;
CyclicBarrier(const CyclicBarrier&) = delete;
CyclicBarrier& operator=(const CyclicBarrier&) = delete;
private:
enum class State {
TRAP, RELEASE
};
const size_t m_num_threads;
mutable Mutex m_lock;
mutable Condition m_cv;
State m_state = State::TRAP;
size_t m_trapped_threads = 0;
size_t m_released_threads = 0;
};
} // namespace utils
#endif // TNT_UTILS_CYCLIC_BARRIER_H

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_ENTITY_H
#define TNT_UTILS_ENTITY_H
#include <utils/compiler.h>
#include <stdint.h>
#include <stddef.h>
namespace utils {
class UTILS_PUBLIC Entity {
public:
// this can be used to create an array of to-be-filled entities (see create())
Entity() noexcept { } // NOLINT(modernize-use-equals-default), Ubuntu compiler bug
// Entities can be copied
Entity(const Entity& e) noexcept = default;
Entity(Entity&& e) noexcept = default;
Entity& operator=(const Entity& e) noexcept = default;
Entity& operator=(Entity&& e) noexcept = default;
// Entities can be compared
bool operator==(Entity e) const { return e.mIdentity == mIdentity; }
bool operator!=(Entity e) const { return e.mIdentity != mIdentity; }
// Entities can be sorted
bool operator<(Entity e) const { return e.mIdentity < mIdentity; }
bool isNull() const noexcept {
return mIdentity == 0;
}
// an id that can be used for debugging/printing
uint32_t getId() const noexcept {
return mIdentity;
}
explicit operator bool() const noexcept { return !isNull(); }
void clear() noexcept { mIdentity = 0; }
// Exports an entity to an int32_t which can be used "as is" in the Java programing language.
static int32_t smuggle(Entity entity) noexcept {
return int32_t(entity.getId());
}
// Imports an entity from an int32_t generated by smuggle() above.
static Entity import(int32_t identity) noexcept {
return Entity{ Type(identity) };
}
struct Hasher {
typedef Entity argument_type;
typedef size_t result_type;
result_type operator()(argument_type const& e) const {
return e.getId();
}
};
private:
friend class EntityManager;
friend class EntityManagerImpl;
using Type = uint32_t;
explicit Entity(Type identity) noexcept : mIdentity(identity) { }
Type mIdentity = 0;
};
} // namespace utils
#endif // TNT_UTILS_ENTITY_H

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_ENTITYINSTANCE_H
#define TNT_UTILS_ENTITYINSTANCE_H
#include <utils/compiler.h>
#include <type_traits>
#include <stdint.h>
namespace utils {
class UTILS_PUBLIC EntityInstanceBase {
public:
using Type = uint32_t;
protected:
Type mInstance = 0;
};
template <typename T, bool EDIT = false>
class UTILS_PUBLIC EntityInstance : public EntityInstanceBase {
public:
// default Instance is invalid
constexpr EntityInstance() noexcept = default;
// check if this Instance is valid
constexpr bool isValid() const noexcept { return mInstance != 0; }
// Instances of same type can be copied/assigned
constexpr EntityInstance(EntityInstance const& other) noexcept = default;
constexpr EntityInstance& operator=(EntityInstance const& other) noexcept = default;
// EDIT instances can be converted to "read" Instances of same type
template <typename = std::enable_if<!EDIT>>
constexpr explicit EntityInstance(EntityInstance<T, true> const& other) noexcept {
mInstance = other.asValue();
}
template <typename = std::enable_if<!EDIT>>
EntityInstance& operator=(EntityInstance<T, true> const& other) noexcept {
mInstance = other.asValue();
return *this;
}
// Instances can be compared
constexpr bool operator!=(EntityInstance e) const { return mInstance != e.mInstance; }
constexpr bool operator==(EntityInstance e) const { return mInstance == e.mInstance; }
// Instances can be sorted
constexpr bool operator<(EntityInstance e) const { return mInstance < e.mInstance; }
constexpr bool operator<=(EntityInstance e) const { return mInstance <= e.mInstance; }
constexpr bool operator>(EntityInstance e) const { return mInstance > e.mInstance; }
constexpr bool operator>=(EntityInstance e) const { return mInstance >= e.mInstance; }
// and we can iterate
constexpr EntityInstance& operator++() noexcept { ++mInstance; return *this; }
constexpr EntityInstance& operator--() noexcept { --mInstance; return *this; }
constexpr const EntityInstance operator++(int) const noexcept { return EntityInstance{ mInstance + 1 }; }
constexpr const EntityInstance operator--(int) const noexcept { return EntityInstance{ mInstance - 1 }; }
// return a value for this Instance (mostly needed for debugging
constexpr uint32_t asValue() const noexcept { return mInstance; }
// auto convert to Type so it can be used as an index
constexpr operator Type() const noexcept { return mInstance; } // NOLINT(google-explicit-constructor)
// conversion from Type so we can initialize from an index
constexpr EntityInstance(Type value) noexcept { mInstance = value; } // NOLINT(google-explicit-constructor)
};
} // namespace utils
#endif // TNT_UTILS_ENTITYINSTANCE_H

View File

@@ -0,0 +1,133 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_ENTITYMANAGER_H
#define TNT_UTILS_ENTITYMANAGER_H
#include <assert.h>
#include <stdint.h>
#include <utils/Entity.h>
#include <utils/compiler.h>
#ifndef FILAMENT_UTILS_TRACK_ENTITIES
#define FILAMENT_UTILS_TRACK_ENTITIES false
#endif
#if FILAMENT_UTILS_TRACK_ENTITIES
#include <utils/ostream.h>
#include <vector>
#endif
namespace utils {
class UTILS_PUBLIC EntityManager {
public:
// Get the global EntityManager. It is recommended to cache this value.
// Thread Safe.
static EntityManager& get() noexcept;
class Listener {
public:
virtual void onEntitiesDestroyed(size_t n, Entity const* entities) noexcept = 0;
protected:
~Listener() noexcept;
};
// maximum number of entities that can exist at the same time
static size_t getMaxEntityCount() noexcept {
// because index 0 is reserved, we only have 2^GENERATION_SHIFT - 1 valid indices
return RAW_INDEX_COUNT - 1;
}
// create n entities. Thread safe.
void create(size_t n, Entity* entities);
// destroys n entities. Thread safe.
void destroy(size_t n, Entity* entities) noexcept;
// create a new Entity. Thread safe.
// Return Entity.isNull() if the entity cannot be allocated.
Entity create() {
Entity e;
create(1, &e);
return e;
}
// destroys an Entity. Thread safe.
void destroy(Entity e) noexcept {
destroy(1, &e);
}
// return whether the given Entity has been destroyed (false) or not (true).
// Thread safe.
bool isAlive(Entity e) const noexcept {
assert(getIndex(e) < RAW_INDEX_COUNT);
return (!e.isNull()) && (getGeneration(e) == mGens[getIndex(e)]);
}
// registers a listener to be called when an entity is destroyed. thread safe.
// if the listener is already register, this method has no effect.
void registerListener(Listener* l) noexcept;
// unregisters a listener.
void unregisterListener(Listener* l) noexcept;
/* no user serviceable parts below */
// current generation of the given index. Use for debugging and testing.
uint8_t getGenerationForIndex(size_t index) const noexcept {
return mGens[index];
}
// singleton, can't be copied
EntityManager(const EntityManager& rhs) = delete;
EntityManager& operator=(const EntityManager& rhs) = delete;
#if FILAMENT_UTILS_TRACK_ENTITIES
std::vector<Entity> getActiveEntities() const;
void dumpActiveEntities(utils::io::ostream& out) const;
#endif
private:
friend class EntityManagerImpl;
EntityManager();
~EntityManager();
// GENERATION_SHIFT determines how many simultaneous Entities are available, the
// minimum memory requirement is 2^GENERATION_SHIFT bytes.
static constexpr const int GENERATION_SHIFT = 17;
static constexpr const size_t RAW_INDEX_COUNT = (1 << GENERATION_SHIFT);
static constexpr const Entity::Type INDEX_MASK = (1 << GENERATION_SHIFT) - 1u;
static inline Entity::Type getGeneration(Entity e) noexcept {
return e.getId() >> GENERATION_SHIFT;
}
static inline Entity::Type getIndex(Entity e) noexcept {
return e.getId() & INDEX_MASK;
}
static inline Entity::Type makeIdentity(Entity::Type g, Entity::Type i) noexcept {
return (g << GENERATION_SHIFT) | (i & INDEX_MASK);
}
// stores the generation of each index.
uint8_t * const mGens;
};
} // namespace utils
#endif // TNT_UTILS_ENTITYMANAGER_H

View File

@@ -0,0 +1,420 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_FIXEDCAPACITYVECTOR_H
#define TNT_UTILS_FIXEDCAPACITYVECTOR_H
#include <utils/compressed_pair.h>
#include <utils/Panic.h>
#include <initializer_list>
#include <limits>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector> // TODO: is this necessary?
#include <stddef.h>
#include <stdint.h>
#ifndef NDEBUG
#define FILAMENT_FORCE_CAPACITY_CHECK true
#else
#define FILAMENT_FORCE_CAPACITY_CHECK false
#endif
namespace utils {
/**
* FixedCapacityVector is (almost) a drop-in replacement for std::vector<> except it has a
* fixed capacity decided at runtime. The vector storage is never reallocated unless reserve()
* is called. Operations that add elements to the vector can fail if there is not enough
* capacity.
*
* An empty vector with a given capacity is created with
* FixedCapacityVector<T>::with_capacity( capacity );
*
* NOTE: When passing an initial size into the FixedCapacityVector constructor, default construction
* of the elements is skipped when their construction is trivial. This behavior is different from
* std::vector. e.g., std::vector<int>(4) constructs 4 zeros while FixedCapacityVector<int>(4)
* allocates 4 uninitialized values. Note that zero initialization is easily achieved by passing in
* the optional value argument, e.g. FixedCapacityVector<int>(4, 0) or foo.resize(4, 0).
*/
template<typename T, typename A = std::allocator<T>, bool CapacityCheck = true>
class UTILS_PUBLIC FixedCapacityVector {
public:
using allocator_type = A;
using value_type = T;
using reference = T&;
using const_reference = T const&;
using size_type = uint32_t;
using difference_type = int32_t;
using pointer = T*;
using const_pointer = T const*;
using iterator = pointer;
using const_iterator = const_pointer;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
private:
using storage_traits = std::allocator_traits<allocator_type>;
public:
/** returns an empty vector with the specified capacity */
static FixedCapacityVector with_capacity(
size_type capacity, const allocator_type& allocator = allocator_type()) {
return FixedCapacityVector(construct_with_capacity, capacity, allocator);
}
FixedCapacityVector() = default;
explicit FixedCapacityVector(const allocator_type& allocator) noexcept
: mCapacityAllocator({}, allocator) {
}
explicit FixedCapacityVector(size_type size, const allocator_type& allocator = allocator_type())
: mSize(size),
mCapacityAllocator(size, allocator) {
mData = this->allocator().allocate(this->capacity());
construct(begin(), end());
}
FixedCapacityVector(std::initializer_list<T> list,
const allocator_type& alloc = allocator_type())
: mSize(list.size()),
mCapacityAllocator(list.size(), alloc) {
mData = this->allocator().allocate(this->capacity());
std::uninitialized_copy(list.begin(), list.end(), begin());
}
FixedCapacityVector(size_type size, const_reference value,
const allocator_type& alloc = allocator_type())
: mSize(size),
mCapacityAllocator(size, alloc) {
mData = this->allocator().allocate(this->capacity());
construct(begin(), end(), value);
}
FixedCapacityVector(FixedCapacityVector const& rhs)
: mSize(rhs.mSize),
mCapacityAllocator(rhs.capacity(),
storage_traits::select_on_container_copy_construction(rhs.allocator())) {
mData = allocator().allocate(capacity());
std::uninitialized_copy(rhs.begin(), rhs.end(), begin());
}
FixedCapacityVector(FixedCapacityVector&& rhs) noexcept {
this->swap(rhs);
}
~FixedCapacityVector() noexcept {
destroy(begin(), end());
allocator().deallocate(data(), capacity());
}
FixedCapacityVector& operator=(FixedCapacityVector const& rhs) {
if (this != &rhs) {
FixedCapacityVector t(rhs);
this->swap(t);
}
return *this;
}
FixedCapacityVector& operator=(FixedCapacityVector&& rhs) noexcept {
this->swap(rhs);
return *this;
}
allocator_type get_allocator() const noexcept {
return mCapacityAllocator.second();
}
// --------------------------------------------------------------------------------------------
iterator begin() noexcept { return data(); }
iterator end() noexcept { return data() + size(); }
const_iterator begin() const noexcept { return data(); }
const_iterator end() const noexcept { return data() + size(); }
reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); }
reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); }
const_iterator cbegin() const noexcept { return begin(); }
const_iterator cend() const noexcept { return end(); }
const_reverse_iterator crbegin() const noexcept { return rbegin(); }
const_reverse_iterator crend() const noexcept { return rend(); }
// --------------------------------------------------------------------------------------------
size_type size() const noexcept { return mSize; }
size_type capacity() const noexcept { return mCapacityAllocator.first(); }
bool empty() const noexcept { return size() == 0; }
size_type max_size() const noexcept {
return std::min(storage_traits::max_size(allocator()),
std::numeric_limits<size_type>::max());
}
// --------------------------------------------------------------------------------------------
reference operator[](size_type n) noexcept {
assert(n < size());
return *(begin() + n);
}
const_reference operator[](size_type n) const noexcept {
assert(n < size());
return *(begin() + n);
}
reference front() noexcept { return *begin(); }
const_reference front() const noexcept { return *begin(); }
reference back() noexcept { return *(end() - 1); }
const_reference back() const noexcept { return *(end() - 1); }
value_type* data() noexcept { return mData; }
const value_type* data() const noexcept { return mData; }
// --------------------------------------------------------------------------------------------
void push_back(const_reference v) {
auto pos = assertCapacityForSize(size() + 1);
++mSize;
storage_traits::construct(allocator(), pos, v);
}
void push_back(value_type&& v) {
auto pos = assertCapacityForSize(size() + 1);
++mSize;
storage_traits::construct(allocator(), pos, std::move(v));
}
template<typename ... ARGS>
reference emplace_back(ARGS&& ... args) {
auto pos = assertCapacityForSize(size() + 1);
++mSize;
storage_traits::construct(allocator(), pos, std::forward<ARGS>(args)...);
return *pos;
}
void pop_back() {
assert(!empty());
--mSize;
destroy(end(), end() + 1);
}
iterator insert(const_iterator position, const_reference v) {
if (position == end()) {
push_back(v);
} else {
assertCapacityForSize(size() + 1);
pointer p = const_cast<pointer>(position);
move_range(p, end(), p + 1);
++mSize;
// here we handle inserting an element of this vector!
const_pointer pv = std::addressof(v);
if (p <= pv && pv < end()) {
*p = *(pv + 1);
} else {
*p = v;
}
}
return const_cast<iterator>(position);
}
iterator insert(const_iterator position, value_type&& v) {
if (position == end()) {
push_back(std::move(v));
} else {
assertCapacityForSize(size() + 1);
pointer p = const_cast<pointer>(position);
move_range(p, end(), p + 1);
++mSize;
*p = std::move(v);
}
return const_cast<iterator>(position);
}
iterator erase(const_iterator pos) {
assert(pos != end());
return erase(pos, pos + 1);
}
iterator erase(const_iterator first, const_iterator last) {
assert(first <= last);
auto e = std::move(const_cast<iterator>(last), end(), const_cast<iterator>(first));
destroy(e, end());
mSize -= std::distance(first, last);
return const_cast<iterator>(first);
}
void clear() noexcept {
destroy(begin(), end());
mSize = 0;
}
void resize(size_type count) {
assertCapacityForSize(count);
if constexpr(std::is_trivially_constructible_v<value_type> &&
std::is_trivially_destructible_v<value_type>) {
// we check for triviality here so that the implementation could be non-inline
mSize = count;
} else {
resize_non_trivial(count);
}
}
void resize(size_type count, const_reference v) {
assertCapacityForSize(count);
resize_non_trivial(count, v);
}
void swap(FixedCapacityVector& other) {
using std::swap;
swap(mData, other.mData);
swap(mSize, other.mSize);
mCapacityAllocator.swap(other.mCapacityAllocator);
}
UTILS_NOINLINE
void reserve(size_type c) {
if (c > capacity()) {
FixedCapacityVector t(construct_with_capacity, c, allocator());
t.mSize = size();
std::uninitialized_move(begin(), end(), t.begin());
this->swap(t);
}
}
private:
enum construct_with_capacity_tag{ construct_with_capacity };
FixedCapacityVector(construct_with_capacity_tag,
size_type capacity, const allocator_type& allocator = allocator_type())
: mCapacityAllocator(capacity, allocator) {
mData = this->allocator().allocate(this->capacity());
}
allocator_type& allocator() noexcept {
return mCapacityAllocator.second();
}
allocator_type const& allocator() const noexcept {
return mCapacityAllocator.second();
}
iterator assertCapacityForSize(size_type s) {
if constexpr(CapacityCheck || FILAMENT_FORCE_CAPACITY_CHECK) {
ASSERT_PRECONDITION(capacity() >= s,
"capacity exceeded: requested size %lu, available capacity %lu.",
(unsigned long)s, (unsigned long)capacity());
}
return end();
}
inline void construct(iterator first, iterator last) noexcept {
// we check for triviality here so that the implementation could be non-inline
if constexpr(!std::is_trivially_constructible_v<value_type>) {
construct_non_trivial(first, last);
}
}
void construct(iterator first, iterator last, const_reference proto) noexcept {
UTILS_NOUNROLL
while (first != last) {
storage_traits::construct(allocator(), first++, proto);
}
}
// should this be NOINLINE?
void construct_non_trivial(iterator first, iterator last) noexcept {
UTILS_NOUNROLL
while (first != last) {
storage_traits::construct(allocator(), first++);
}
}
inline void destroy(iterator first, iterator last) noexcept {
// we check for triviality here so that the implementation could be non-inline
if constexpr(!std::is_trivially_destructible_v<value_type>) {
destroy_non_trivial(first, last);
}
}
// should this be NOINLINE?
void destroy_non_trivial(iterator first, iterator last) noexcept {
UTILS_NOUNROLL
while (first != last) {
storage_traits::destroy(allocator(), --last);
}
}
// should this be NOINLINE?
void resize_non_trivial(size_type count) {
if (count > size()) {
construct(end(), begin() + count);
} else if (count < size()) {
destroy(begin() + count, end());
}
mSize = count;
}
// should this be NOINLINE?
void resize_non_trivial(size_type count, const_reference v) {
if (count > size()) {
construct(end(), begin() + count, v);
} else if (count < size()) {
destroy(begin() + count, end());
}
mSize = count;
}
// should this be NOINLINE?
void move_range(pointer s, pointer e, pointer to) {
if constexpr(std::is_trivially_copy_assignable_v<value_type>
&& std::is_trivially_destructible_v<value_type>) {
// this generates memmove -- which doesn't happen otherwise
std::move_backward(s, e, to + std::distance(s, e));
} else {
pointer our_end = end();
difference_type n = our_end - to; // nb of elements to move by operator=
pointer i = s + n; // 1st element to move by move ctor
for (pointer d = our_end ; i < our_end ; ++i, ++d) {
storage_traits::construct(allocator(), d, std::move(*i));
}
std::move_backward(s, s + n, our_end);
}
}
template<typename TYPE>
class SizeTypeWrapper {
TYPE value{};
public:
SizeTypeWrapper() noexcept = default;
SizeTypeWrapper(SizeTypeWrapper const& rhs) noexcept = default;
explicit SizeTypeWrapper(TYPE value) noexcept : value(value) { }
SizeTypeWrapper& operator=(TYPE rhs) noexcept { value = rhs; return *this; }
SizeTypeWrapper& operator=(SizeTypeWrapper& rhs) noexcept = delete;
operator TYPE() const noexcept { return value; }
};
pointer mData{};
size_type mSize{};
compressed_pair<SizeTypeWrapper<size_type>, allocator_type> mCapacityAllocator{};
};
} // namespace utils
#endif // TNT_UTILS_FIXEDCAPACITYVECTOR_H

103
macos/include/utils/Hash.h Normal file
View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_HASH_H
#define TNT_UTILS_HASH_H
#include <functional> // for std::hash
#include <stdint.h>
#include <stddef.h>
namespace utils::hash {
// Hash function that takes an arbitrary swath of word-aligned data.
inline uint32_t murmur3(const uint32_t* key, size_t wordCount, uint32_t seed) noexcept {
uint32_t h = seed;
size_t i = wordCount;
do {
uint32_t k = *key++;
k *= 0xcc9e2d51u;
k = (k << 15u) | (k >> 17u);
k *= 0x1b873593u;
h ^= k;
h = (h << 13u) | (h >> 19u);
h = (h * 5u) + 0xe6546b64u;
} while (--i);
h ^= wordCount;
h ^= h >> 16u;
h *= 0x85ebca6bu;
h ^= h >> 13u;
h *= 0xc2b2ae35u;
h ^= h >> 16u;
return h;
}
// The hash yields the same result for a given byte sequence regardless of alignment.
inline uint32_t murmurSlow(const uint8_t* key, size_t byteCount, uint32_t seed) noexcept {
const size_t wordCount = (byteCount + 3) / 4;
const uint8_t* const last = key + byteCount;
// The remainder is identical to murmur3() except an inner loop safely "reads" an entire word.
uint32_t h = seed;
size_t i = wordCount;
do {
uint32_t k = 0;
for (int i = 0; i < 4 && key < last; ++i, ++key) {
k >>= 8;
k |= uint32_t(*key) << 24;
}
k *= 0xcc9e2d51u;
k = (k << 15u) | (k >> 17u);
k *= 0x1b873593u;
h ^= k;
h = (h << 13u) | (h >> 19u);
h = (h * 5u) + 0xe6546b64u;
} while (--i);
h ^= wordCount;
h ^= h >> 16u;
h *= 0x85ebca6bu;
h ^= h >> 13u;
h *= 0xc2b2ae35u;
h ^= h >> 16u;
return h;
}
template<typename T>
struct MurmurHashFn {
uint32_t operator()(const T& key) const noexcept {
static_assert(0 == (sizeof(key) & 3u), "Hashing requires a size that is a multiple of 4.");
return murmur3((const uint32_t*) &key, sizeof(key) / 4, 0);
}
};
// combines two hashes together
template<class T>
inline void combine(size_t& seed, const T& v) noexcept {
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9u + (seed << 6u) + (seed >> 2u);
}
// combines two hashes together, faster but less good
template<class T>
inline void combine_fast(size_t& seed, const T& v) noexcept {
std::hash<T> hasher;
seed ^= hasher(v) << 1u;
}
} // namespace utils::hash
#endif // TNT_UTILS_HASH_H

View File

@@ -0,0 +1,152 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_INVOKABLE_H
#define TNT_UTILS_INVOKABLE_H
#include <type_traits>
#include <utility>
#include <assert.h>
namespace utils {
/*
* Invocable is a move-only general purpose function wrapper. Instances can
* store and invoke lambda expressions and other function objects.
*
* It is similar to std::function, with the following differences:
* - Invocable is move only.
* - Invocable can capture move only types.
* - No conversion between 'compatible' functions.
* - No small buffer optimization
*/
// Helper for enabling methods if Fn matches the function signature
// requirements of the Invocable type.
#if __cplusplus >= 201703L
// only available on C++17 and up
template<typename Fn, typename R, typename... Args>
using EnableIfFnMatchesInvocable =
std::enable_if_t<std::is_invocable_v<Fn, Args...> &&
std::is_same_v<R, std::invoke_result_t<Fn, Args...>>, int>;
#else
template<typename Fn, typename R, typename... Args>
using EnableIfFnMatchesInvocable = std::enable_if_t<true, int>;
#endif
template<typename Signature>
class Invocable;
template<typename R, typename... Args>
class Invocable<R(Args...)> {
public:
// Creates an Invocable that does not contain a functor.
// Will evaluate to false.
Invocable() = default;
~Invocable() noexcept;
// Creates an Invocable from the functor passed in.
template<typename Fn, EnableIfFnMatchesInvocable<Fn, R, Args...> = 0>
Invocable(Fn&& fn) noexcept; // NOLINT(google-explicit-constructor)
Invocable(const Invocable&) = delete;
Invocable(Invocable&& rhs) noexcept;
Invocable& operator=(const Invocable&) = delete;
Invocable& operator=(Invocable&& rhs) noexcept;
// Invokes the invocable with the args passed in.
// If the Invocable is empty, this will assert.
template<typename... OperatorArgs>
R operator()(OperatorArgs&& ... args);
template<typename... OperatorArgs>
R operator()(OperatorArgs&& ... args) const;
// Evaluates to true if Invocable contains a functor, false otherwise.
explicit operator bool() const noexcept;
private:
void* mInvocable = nullptr;
void (*mDeleter)(void*) = nullptr;
R (* mInvoker)(void*, Args...) = nullptr;
};
template<typename R, typename... Args>
template<typename Fn, EnableIfFnMatchesInvocable<Fn, R, Args...>>
Invocable<R(Args...)>::Invocable(Fn&& fn) noexcept
: mInvocable(new Fn(std::forward<std::decay_t<Fn>>(fn))),
mDeleter(+[](void* erased_invocable) {
auto typed_invocable = static_cast<Fn*>(erased_invocable);
delete typed_invocable;
}),
mInvoker(+[](void* erased_invocable, Args... args) -> R {
auto typed_invocable = static_cast<Fn*>(erased_invocable);
return (*typed_invocable)(std::forward<Args>(args)...);
})
{
}
template<typename R, typename... Args>
Invocable<R(Args...)>::~Invocable() noexcept {
if (mDeleter) {
mDeleter(mInvocable);
}
}
template<typename R, typename... Args>
Invocable<R(Args...)>::Invocable(Invocable&& rhs) noexcept
: mInvocable(rhs.mInvocable),
mDeleter(rhs.mDeleter),
mInvoker(rhs.mInvoker) {
rhs.mInvocable = nullptr;
rhs.mDeleter = nullptr;
rhs.mInvoker = nullptr;
}
template<typename R, typename... Args>
Invocable<R(Args...)>& Invocable<R(Args...)>::operator=(Invocable&& rhs) noexcept {
if (this != &rhs) {
std::swap(mInvocable, rhs.mInvocable);
std::swap(mDeleter, rhs.mDeleter);
std::swap(mInvoker, rhs.mInvoker);
}
return *this;
}
template<typename R, typename... Args>
template<typename... OperatorArgs>
R Invocable<R(Args...)>::operator()(OperatorArgs&& ... args) {
assert(mInvoker && mInvocable);
return mInvoker(mInvocable, std::forward<OperatorArgs>(args)...);
}
template<typename R, typename... Args>
template<typename... OperatorArgs>
R Invocable<R(Args...)>::operator()(OperatorArgs&& ... args) const {
assert(mInvoker && mInvocable);
return mInvoker(mInvocable, std::forward<OperatorArgs>(args)...);
}
template<typename R, typename... Args>
Invocable<R(Args...)>::operator bool() const noexcept {
return mInvoker != nullptr && mInvocable != nullptr;
}
} // namespace utils
#endif // TNT_UTILS_INVOKABLE_H

View File

@@ -0,0 +1,532 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_JOBSYSTEM_H
#define TNT_UTILS_JOBSYSTEM_H
#include <assert.h>
#include <atomic>
#include <functional>
#include <thread>
#include <vector>
#include <tsl/robin_map.h>
#include <utils/Allocator.h>
#include <utils/architecture.h>
#include <utils/compiler.h>
#include <utils/Condition.h>
#include <utils/Log.h>
#include <utils/memalign.h>
#include <utils/Mutex.h>
#include <utils/Slice.h>
#include <utils/WorkStealingDequeue.h>
namespace utils {
class JobSystem {
static constexpr size_t MAX_JOB_COUNT = 16384;
static_assert(MAX_JOB_COUNT <= 0x7FFE, "MAX_JOB_COUNT must be <= 0x7FFE");
using WorkQueue = WorkStealingDequeue<uint16_t, MAX_JOB_COUNT>;
public:
class Job;
using JobFunc = void(*)(void*, JobSystem&, Job*);
class alignas(CACHELINE_SIZE) Job {
public:
Job() noexcept {} /* = default; */ /* clang bug */ // NOLINT(modernize-use-equals-default,cppcoreguidelines-pro-type-member-init)
Job(const Job&) = delete;
Job(Job&&) = delete;
private:
friend class JobSystem;
// Size is chosen so that we can store at least std::function<>
// the alignas() qualifier ensures we're multiple of a cache-line.
static constexpr size_t JOB_STORAGE_SIZE_BYTES =
sizeof(std::function<void()>) > 48 ? sizeof(std::function<void()>) : 48;
static constexpr size_t JOB_STORAGE_SIZE_WORDS =
(JOB_STORAGE_SIZE_BYTES + sizeof(void*) - 1) / sizeof(void*);
// keep it first, so it's correctly aligned with all architectures
// this is where we store the job's data, typically a std::function<>
// v7 | v8
void* storage[JOB_STORAGE_SIZE_WORDS]; // 48 | 48
JobFunc function; // 4 | 8
uint16_t parent; // 2 | 2
std::atomic<uint16_t> runningJobCount = { 1 }; // 2 | 2
mutable std::atomic<uint16_t> refCount = { 1 }; // 2 | 2
// 6 | 2 (padding)
// 64 | 64
};
explicit JobSystem(size_t threadCount = 0, size_t adoptableThreadsCount = 1) noexcept;
~JobSystem();
// Make the current thread part of the thread pool.
void adopt();
// Remove this adopted thread from the parent. This is intended to be used for
// shutting down a JobSystem. In particular, this doesn't allow the parent to
// adopt more thread.
void emancipate();
// If a parent is not specified when creating a job, that job will automatically take the
// root job as a parent.
// The root job is reset when waited on.
Job* setRootJob(Job* job) noexcept { return mRootJob = job; }
// use setRootJob() instead
UTILS_DEPRECATED
Job* setMasterJob(Job* job) noexcept { return setRootJob(job); }
Job* create(Job* parent, JobFunc func) noexcept;
// NOTE: All methods below must be called from the same thread and that thread must be
// owned by JobSystem's thread pool.
/*
* Job creation examples:
* ----------------------
*
* struct Functor {
* uintptr_t storage[6];
* void operator()(JobSystem&, Jobsystem::Job*);
* } functor;
*
* struct Foo {
* uintptr_t storage[6];
* void method(JobSystem&, Jobsystem::Job*);
* } foo;
*
* Functor and Foo size muse be <= uintptr_t[6]
*
* createJob()
* createJob(parent)
* createJob<Foo, &Foo::method>(parent, &foo)
* createJob<Foo, &Foo::method>(parent, foo)
* createJob<Foo, &Foo::method>(parent, std::ref(foo))
* createJob(parent, functor)
* createJob(parent, std::ref(functor))
* createJob(parent, [ up-to 6 uintptr_t ](JobSystem*, Jobsystem::Job*){ })
*
* Utility functions:
* ------------------
* These are less efficient, but handle any size objects using the heap if needed.
* (internally uses std::function<>), and don't require the callee to take
* a (JobSystem&, Jobsystem::Job*) as parameter.
*
* struct BigFoo {
* uintptr_t large[16];
* void operator()();
* void method(int answerToEverything);
* static void exec(BigFoo&) { }
* } bigFoo;
*
* jobs::createJob(js, parent, [ any-capture ](int answerToEverything){}, 42);
* jobs::createJob(js, parent, &BigFoo::method, &bigFoo, 42);
* jobs::createJob(js, parent, &BigFoo::exec, std::ref(bigFoo));
* jobs::createJob(js, parent, bigFoo);
* jobs::createJob(js, parent, std::ref(bigFoo));
* etc...
*
* struct SmallFunctor {
* uintptr_t storage[3];
* void operator()(T* data, size_t count);
* } smallFunctor;
*
* jobs::parallel_for(js, data, count, [ up-to 3 uintptr_t ](T* data, size_t count) { });
* jobs::parallel_for(js, data, count, smallFunctor);
* jobs::parallel_for(js, data, count, std::ref(smallFunctor));
*
*/
// creates an empty (no-op) job with an optional parent
Job* createJob(Job* parent = nullptr) noexcept {
return create(parent, nullptr);
}
// creates a job from a KNOWN method pointer w/ object passed by pointer
// the caller must ensure the object will outlive the Job
template<typename T, void(T::*method)(JobSystem&, Job*)>
Job* createJob(Job* parent, T* data) noexcept {
Job* job = create(parent, [](void* user, JobSystem& js, Job* job) {
(*static_cast<T**>(user)->*method)(js, job);
});
if (job) {
job->storage[0] = data;
}
return job;
}
// creates a job from a KNOWN method pointer w/ object passed by value
template<typename T, void(T::*method)(JobSystem&, Job*)>
Job* createJob(Job* parent, T data) noexcept {
static_assert(sizeof(data) <= sizeof(Job::storage), "user data too large");
Job* job = create(parent, [](void* user, JobSystem& js, Job* job) {
T* that = static_cast<T*>(user);
(that->*method)(js, job);
that->~T();
});
if (job) {
new(job->storage) T(std::move(data));
}
return job;
}
// creates a job from a functor passed by value
template<typename T>
Job* createJob(Job* parent, T functor) noexcept {
static_assert(sizeof(functor) <= sizeof(Job::storage), "functor too large");
Job* job = create(parent, [](void* user, JobSystem& js, Job* job){
T& that = *static_cast<T*>(user);
that(js, job);
that.~T();
});
if (job) {
new(job->storage) T(std::move(functor));
}
return job;
}
/*
* Jobs are normally finished automatically, this can be used to cancel a job before it is run.
*
* Never use this once a flavor of run() has been called.
*/
void cancel(Job*& job) noexcept;
/*
* Adds a reference to a Job.
*
* This allows the caller to waitAndRelease() on this job from multiple threads.
* Use runAndWait() if waiting from multiple threads is not needed.
*
* This job MUST BE waited on with waitAndRelease(), or released with release().
*/
Job* retain(Job* job) noexcept;
/*
* Releases a reference from a Job obtained with runAndRetain() or a call to retain().
*
* The job can't be used after this call.
*/
void release(Job*& job) noexcept;
void release(Job*&& job) noexcept {
Job* p = job;
release(p);
}
/*
* Add job to this thread's execution queue. It's reference will drop automatically.
* Current thread must be owned by JobSystem's thread pool. See adopt().
*
* The job can't be used after this call.
*/
void run(Job*& job) noexcept;
void run(Job*&& job) noexcept { // allows run(createJob(...));
Job* p = job;
run(p);
}
void signal() noexcept;
/*
* Add job to this thread's execution queue and and keep a reference to it.
* Current thread must be owned by JobSystem's thread pool. See adopt().
*
* This job MUST BE waited on with wait(), or released with release().
*/
Job* runAndRetain(Job* job) noexcept;
/*
* Wait on a job and destroys it.
* Current thread must be owned by JobSystem's thread pool. See adopt().
*
* The job must first be obtained from runAndRetain() or retain().
* The job can't be used after this call.
*/
void waitAndRelease(Job*& job) noexcept;
/*
* Runs and wait for a job. This is equivalent to calling
* runAndRetain(job);
* wait(job);
*
* The job can't be used after this call.
*/
void runAndWait(Job*& job) noexcept;
void runAndWait(Job*&& job) noexcept { // allows runAndWait(createJob(...));
Job* p = job;
runAndWait(p);
}
// for debugging
friend utils::io::ostream& operator << (utils::io::ostream& out, JobSystem const& js);
// utility functions...
// set the name of the current thread (on OSes that support it)
static void setThreadName(const char* threadName) noexcept;
enum class Priority {
NORMAL,
DISPLAY,
URGENT_DISPLAY
};
static void setThreadPriority(Priority priority) noexcept;
static void setThreadAffinityById(size_t id) noexcept;
size_t getParallelSplitCount() const noexcept {
return mParallelSplitCount;
}
size_t getThreadCount() const { return mThreadCount; }
private:
// this is just to avoid using std::default_random_engine, since we're in a public header.
class default_random_engine {
static constexpr uint32_t m = 0x7fffffffu;
uint32_t mState; // must be 0 < seed < 0x7fffffff
public:
inline constexpr explicit default_random_engine(uint32_t seed = 1u) noexcept
: mState(((seed % m) == 0u) ? 1u : seed % m) {
}
inline uint32_t operator()() noexcept {
return mState = uint32_t((uint64_t(mState) * 48271u) % m);
}
};
struct alignas(CACHELINE_SIZE) ThreadState { // this causes 40-bytes padding
// make sure storage is cache-line aligned
WorkQueue workQueue;
// these are not accessed by the worker threads
alignas(CACHELINE_SIZE) // this causes 56-bytes padding
JobSystem* js;
std::thread thread;
default_random_engine rndGen;
uint32_t id;
};
static_assert(sizeof(ThreadState) % CACHELINE_SIZE == 0,
"ThreadState doesn't align to a cache line");
ThreadState& getState() noexcept;
void incRef(Job const* job) noexcept;
void decRef(Job const* job) noexcept;
Job* allocateJob() noexcept;
JobSystem::ThreadState* getStateToStealFrom(JobSystem::ThreadState& state) noexcept;
bool hasJobCompleted(Job const* job) noexcept;
void requestExit() noexcept;
bool exitRequested() const noexcept;
bool hasActiveJobs() const noexcept;
void loop(ThreadState* state) noexcept;
bool execute(JobSystem::ThreadState& state) noexcept;
Job* steal(JobSystem::ThreadState& state) noexcept;
void finish(Job* job) noexcept;
void put(WorkQueue& workQueue, Job* job) noexcept;
Job* pop(WorkQueue& workQueue) noexcept;
Job* steal(WorkQueue& workQueue) noexcept;
void wait(std::unique_lock<Mutex>& lock, Job* job = nullptr) noexcept;
void wakeAll() noexcept;
void wakeOne() noexcept;
// these have thread contention, keep them together
utils::Mutex mWaiterLock;
utils::Condition mWaiterCondition;
std::atomic<uint32_t> mActiveJobs = { 0 };
utils::Arena<utils::ThreadSafeObjectPoolAllocator<Job>, LockingPolicy::NoLock> mJobPool;
template <typename T>
using aligned_vector = std::vector<T, utils::STLAlignedAllocator<T>>;
// these are essentially const, make sure they're on a different cache-lines than the
// read-write atomics.
// We can't use "alignas(CACHELINE_SIZE)" because the standard allocator can't make this
// guarantee.
char padding[CACHELINE_SIZE];
alignas(16) // at least we align to half (or quarter) cache-line
aligned_vector<ThreadState> mThreadStates; // actual data is stored offline
std::atomic<bool> mExitRequested = { false }; // this one is almost never written
std::atomic<uint16_t> mAdoptedThreads = { 0 }; // this one is almost never written
Job* const mJobStorageBase; // Base for conversion to indices
uint16_t mThreadCount = 0; // total # of threads in the pool
uint8_t mParallelSplitCount = 0; // # of split allowable in parallel_for
Job* mRootJob = nullptr;
utils::SpinLock mThreadMapLock; // this should have very little contention
tsl::robin_map<std::thread::id, ThreadState *> mThreadMap;
};
// -------------------------------------------------------------------------------------------------
// Utility functions built on top of JobSystem
namespace jobs {
// These are convenience C++11 style job creation methods that support lambdas
//
// IMPORTANT: these are less efficient to call and may perform heap allocation
// depending on the capture and parameters
//
template<typename CALLABLE, typename ... ARGS>
JobSystem::Job* createJob(JobSystem& js, JobSystem::Job* parent,
CALLABLE&& func, ARGS&&... args) noexcept {
struct Data {
std::function<void()> f;
// Renaming the method below could cause an Arrested Development.
void gob(JobSystem&, JobSystem::Job*) noexcept { f(); }
} user{ std::bind(std::forward<CALLABLE>(func),
std::forward<ARGS>(args)...) };
return js.createJob<Data, &Data::gob>(parent, std::move(user));
}
template<typename CALLABLE, typename T, typename ... ARGS,
typename = typename std::enable_if<
std::is_member_function_pointer<typename std::remove_reference<CALLABLE>::type>::value
>::type
>
JobSystem::Job* createJob(JobSystem& js, JobSystem::Job* parent,
CALLABLE&& func, T&& o, ARGS&&... args) noexcept {
struct Data {
std::function<void()> f;
// Renaming the method below could cause an Arrested Development.
void gob(JobSystem&, JobSystem::Job*) noexcept { f(); }
} user{ std::bind(std::forward<CALLABLE>(func), std::forward<T>(o),
std::forward<ARGS>(args)...) };
return js.createJob<Data, &Data::gob>(parent, std::move(user));
}
namespace details {
template<typename S, typename F>
struct ParallelForJobData {
using SplitterType = S;
using Functor = F;
using JobData = ParallelForJobData;
using size_type = uint32_t;
ParallelForJobData(size_type start, size_type count, uint8_t splits,
Functor functor,
const SplitterType& splitter) noexcept
: start(start), count(count),
functor(std::move(functor)),
splits(splits),
splitter(splitter) {
}
void parallelWithJobs(JobSystem& js, JobSystem::Job* parent) noexcept {
assert(parent);
// this branch is often miss-predicted (it both sides happen 50% of the calls)
right_side:
if (splitter.split(splits, count)) {
const size_type lc = count / 2;
JobData ld(start, lc, splits + uint8_t(1), functor, splitter);
JobSystem::Job* l = js.createJob<JobData, &JobData::parallelWithJobs>(parent, std::move(ld));
if (UTILS_UNLIKELY(l == nullptr)) {
// couldn't create a job, just pretend we're done splitting
goto execute;
}
// start the left side before attempting the right side, so we parallelize in case
// of job creation failure -- rare, but still.
js.run(l);
// don't spawn a job for the right side, just reuse us -- spawning jobs is more
// costly than we'd like.
start += lc;
count -= lc;
++splits;
goto right_side;
} else {
execute:
// we're done splitting, do the real work here!
functor(start, count);
}
}
private:
size_type start; // 4
size_type count; // 4
Functor functor; // ?
uint8_t splits; // 1
SplitterType splitter; // 1
};
} // namespace details
// parallel jobs with start/count indices
template<typename S, typename F>
JobSystem::Job* parallel_for(JobSystem& js, JobSystem::Job* parent,
uint32_t start, uint32_t count, F functor, const S& splitter) noexcept {
using JobData = details::ParallelForJobData<S, F>;
JobData jobData(start, count, 0, std::move(functor), splitter);
return js.createJob<JobData, &JobData::parallelWithJobs>(parent, std::move(jobData));
}
// parallel jobs with pointer/count
template<typename T, typename S, typename F>
JobSystem::Job* parallel_for(JobSystem& js, JobSystem::Job* parent,
T* data, uint32_t count, F functor, const S& splitter) noexcept {
auto user = [data, f = std::move(functor)](uint32_t s, uint32_t c) {
f(data + s, c);
};
using JobData = details::ParallelForJobData<S, decltype(user)>;
JobData jobData(0, count, 0, std::move(user), splitter);
return js.createJob<JobData, &JobData::parallelWithJobs>(parent, std::move(jobData));
}
// parallel jobs on a Slice<>
template<typename T, typename S, typename F>
JobSystem::Job* parallel_for(JobSystem& js, JobSystem::Job* parent,
utils::Slice<T> slice, F functor, const S& splitter) noexcept {
return parallel_for(js, parent, slice.data(), slice.size(), functor, splitter);
}
template <size_t COUNT, size_t MAX_SPLITS = 12>
class CountSplitter {
public:
bool split(size_t splits, size_t count) const noexcept {
return (splits < MAX_SPLITS && count >= COUNT * 2);
}
};
} // namespace jobs
} // namespace utils
#endif // TNT_UTILS_JOBSYSTEM_H

46
macos/include/utils/Log.h Normal file
View File

@@ -0,0 +1,46 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_LOG_H
#define TNT_UTILS_LOG_H
#include <utils/compiler.h>
#include <utils/ostream.h>
namespace utils {
struct UTILS_PUBLIC Loggers {
// DEBUG level logging stream
io::ostream& d;
// ERROR level logging stream
io::ostream& e;
// WARNING level logging stream
io::ostream& w;
// INFORMATION level logging stream
io::ostream& i;
// VERBOSE level logging stream
io::ostream& v;
};
extern UTILS_PUBLIC Loggers const slog;
} // namespace utils
#endif // TNT_UTILS_LOG_H

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_MUTEX_H
#define TNT_UTILS_MUTEX_H
#if defined(__ANDROID__)
#include <utils/linux/Mutex.h>
#else
#include <utils/generic/Mutex.h>
#endif
#endif // TNT_UTILS_MUTEX_H

View File

@@ -0,0 +1,112 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_NAMECOMPONENTMANAGER_H
#define TNT_UTILS_NAMECOMPONENTMANAGER_H
#include <utils/compiler.h>
#include <utils/CString.h>
#include <utils/Entity.h>
#include <utils/EntityInstance.h>
#include <utils/SingleInstanceComponentManager.h>
#include <stddef.h>
#include <stdint.h>
namespace utils {
class EntityManager;
/**
* \class NameComponentManager NameComponentManager.h utils/NameComponentManager.h
* \brief Allows clients to associate string labels with entities.
*
* To access the name of an existing entity, clients should first use NameComponentManager to get a
* temporary handle called an \em instance. Please note that instances are ephemeral; clients should
* store entities, not instances.
*
* Usage example:
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* auto names = new NameComponentManager(EntityManager::get());
* names->addComponent(myEntity);
* names->setName(names->getInstance(myEntity), "Jeanne d'Arc");
* ...
* printf("%s\n", names->getName(names->getInstance(myEntity));
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
class UTILS_PUBLIC NameComponentManager : public SingleInstanceComponentManager<utils::CString> {
public:
using Instance = EntityInstance<NameComponentManager>;
/**
* Creates a new name manager associated with the given entity manager.
*
* Note that multiple string labels could be associated with each entity simply by
* creating multiple instances of NameComponentManager.
*/
explicit NameComponentManager(EntityManager& em);
~NameComponentManager();
/**
* Checks if the given entity already has a name component.
*/
using SingleInstanceComponentManager::hasComponent;
/**
* Gets a temporary handle that can be used to access the name.
*
* @return Non-zero handle if the entity has a name component, 0 otherwise.
*/
Instance getInstance(Entity e) const noexcept {
return { SingleInstanceComponentManager::getInstance(e) };
}
/*! \cond PRIVATE */
// these are implemented in SingleInstanceComponentManager<>, but we need to
// reimplement them in each manager, to ensure they are generated in an implementation file
// for backward binary compatibility reasons.
size_t getComponentCount() const noexcept;
Entity const* getEntities() const noexcept;
void gc(const EntityManager& em, size_t ratio = 4) noexcept;
/*! \endcond */
/**
* Adds a name component to the given entity if it doesn't already exist.
*/
void addComponent(Entity e);
/**
* Removes the name component to the given entity if it exists.
*/
void removeComponent(Entity e);
/**
* Stores a copy of the given string and associates it with the given instance.
*/
void setName(Instance instance, const char* name) noexcept;
/**
* Retrieves the string associated with the given instance, or nullptr if none exists.
*
* @return pointer to the copy that was made during setName()
*/
const char* getName(Instance instance) const noexcept;
};
} // namespace utils
#endif // TNT_UTILS_NAMECOMPONENTMANAGER_H

563
macos/include/utils/Panic.h Normal file
View File

@@ -0,0 +1,563 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_PANIC_H
#define TNT_UTILS_PANIC_H
#include <string>
#include <utils/CallStack.h>
#include <utils/compiler.h>
#ifdef __EXCEPTIONS
# define UTILS_EXCEPTIONS 1
#else
#endif
/**
* @defgroup errors Handling Catastrophic Failures (Panics)
*
* @brief Failure detection and reporting facilities
*
* ## What's a Panic? ##
*
* In the context of this document, a _panic_ is a type of error due to a _contract violation_,
* it shouldn't be confused with a _result_ or _status_ code. The POSIX API for instance,
* unfortunately often conflates the two.
* @see <http://en.wikipedia.org/wiki/Design_by_contract>
*
*
* Here we give the following definition of a _panic_:
*
* 1. Failures to meet a function's own **postconditions**\n
* The function cannot establish one of its own postconditions, such as (but not limited to)
* producing a valid return value object.
*
* Often these failures are only detectable at runtime, for instance they can be caused by
* arithmetic errors, as it was the case for the Ariane 5 rocket. Ariane 5 crashed because it
* reused an inertial module from Ariane 4, which didn't account for the greater horizontal
* acceleration of Ariane 5 and caused an overflow in the computations. Ariane 4's module
* wasn't per-say buggy, but was improperly used and failed to meet, obviously, certain
* postconditions.
* @see <http://en.wikipedia.org/wiki/Cluster_(spacecraft)>
*
* 2. Failures to meet the **preconditions** of any of a function's callees\n
* The function cannot meet a precondition of another function it must call, such as a
* restriction on a parameter.
*
* Not to be confused with the case where the preconditions of a function are already
* violated upon entry, which indicates a programming error from the caller.
*
* Typically these failures can be avoided and arise because of programming errors.
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* ## Failure reporting vs. handling ##
*
* Very often when a panic, as defined above, is detected, the program has little other choice
* but to terminate.\n
* Typically these situations can be handled by _assert()_. However, _assert()_ also conflates two
* very different concepts: detecting and handling failures.\n
* The place where a failure is detected is rarely the place where there is enough
* context to decide what to do. _assert()_ terminates the program which, may or may not be
* appropriate. At the very least the failure must be logged (which _assert()_ does in a crude way),
* but some other actions might need to happen, such as:\n
*
* - logging the failure in the system-wide logger
* - providing enough information in development builds to analyze/debug the problem
* - cleanly releasing some resources, such as communication channels with other processes\n
* e.g.: to avoid their pre- or postconditions from being violated as well.
*
* In some _rare_ cases, the failure might even be ignored altogether because it doesn't matter in
* the context where it happened. This decision clearly doesn't always lie at the failure-site.
*
* It is therefore important to separate failure detection from handling.
*
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* ## Failure detection and handling facilities ##
*
* Clearly, catastrophic failures should be **rare**; in fact they should
* never happen, except possibly for "failures to meet a function's own postconditions", which
* may depend on external factors and should still be very rare. Yet, when a failure happens, it
* must be detected and handled appropriately.\n
* Since panics are rare, it is desirable that the handling mechanism be as unobtrusive
* as possible, without allowing such failures to go unnoticed or swallowed by mistake. Ideally, the
* programmer using an API should have nothing special to do to handle that API's failure
* conditions.\n\n
*
* An important feature of the Panic Handling facilities here is that **panics are not part of
* the API of a function or method**\n\n
*
*
* The panic handling facility has the following benefits:
* - provides an easy way to detect and report failure
* - separates failure detection from handling
* - makes it hard for detected failures to be ignored (i.e.: not handled)
* - doesn't add burden on the API design
* - doesn't add overhead (visual or otherwise) at call sites
* - has very little performance overhead for failure detection
* - has little to no performance impact for failure handling in the common (success) case
* - is flexible and extensible
*
* Since we have established that failures are **rare**, **exceptional** situations, it would be
* appropriate to handle them with an _assert_ mechanism and that's what the API below
* provides. However, under-the-hood it uses C++ exceptions as a means to separate
* _reporting_ from _handling_.
*
* \note On devices where exceptions are not supported or appropriate, these APIs can be turned
* into a regular _std::terminate()_.
*
*
* ASSERT_PRECONDITION(condition, format, ...)
* ASSERT_POSTCONDITION(condition, format, ...)
* ASSERT_ARITHMETIC(condition, format, ...)
* ASSERT_DESTRUCTOR(condition, format, ...)
*
*
* @see ASSERT_PRECONDITION, ASSERT_POSTCONDITION, ASSERT_ARITHMETIC
* @see ASSERT_DESTRUCTOR
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* ## Writing code that can assert ##
*
* Because we've separated failure reporting from failure handling, there are some considerations
* that need to be thought about when writing code that calls the macros above (i.e.: the program
* won't terminate at the point where the failure is detected).\n\n
*
*
* ### Panic guarantees ###
*
* After the failure condition is reported by a function, additional guarantees may be provided
* with regards to the state of the program. The following four levels of guarantee are
* generally recognized, each of which is a strict superset of its successors:
*
* 1. Nothrow exception guarantee\n
* The function never asserts. e.g.: This should always be the case with destructors.\n\n
*
* 2. Strong exception guarantee\n
* If the function asserts, the state of the program is rolled back to the state just before
* the function call.\n\n
*
* 3. Basic exception guarantee\n
* If the function asserts, the program is in a valid state. It may require cleanup,
* but all invariants are intact.\n\n
*
* 4. No exception guarantee\n
* If the function asserts, the program may not be in a valid state: resource leaks, memory
* corruption, or other invariant-destroying failures may have occurred.
*
* In each function, give the **strongest** safety guarantee that won't penalize callers who
* don't need it, but **always give at least the basic guarantee**. The RAII (Resource
* Acquisition Is Initialization) pattern can help with achieving these guarantees.
*
* @see [RAII](http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization)
*
* ### Special considerations for Constructors ###
*
* Constructors are a bit special because if a failure occurs during their execution, the
* destructor won't be called (how could it? since the object wasn't constructed!). This can lead
* to leaked resources allocated in the constructor prior to the failure. Thankfully there is
* a nice C++ syntax to handle this case:
*
* @code
* Foo::Foo(size_t s) try : m_size(s), m_buf(new uint32_t[s]) {
* ASSERT_POSTCONDITION(s&0xF==0,
* "object size is %u, but must be multiple of 16", s);
* } catch (...) {
* delete [] m_buf;
* // the exception will be automatically re-thrown
* }
* @endcode
*
* Unfortunately, this usage leaks the underlying, exception-based, implementation of the
* panic handling macros. For this reason, it is best to keep constructors simple and guarantee
* they can't fail. An _init()_ function with a factory can be used for actual initialization.
*
*
* ### Special considerations for Destructors ###
*
* In C++ destructors cannot throw exceptions and since the above macros internally use exceptions
* they cannot be used in destructors. Doing so will result in immediate termination of the
* program by _std::terminate()_.\n
* It is therefore best to always guarantee that destructors won't fail. In case of such a
* failure in a destructor the ASSERT_DESTRUCTOR() macro can be used instead, it
* will log the failure but won't terminate the program, instead it'll proceed as if nothing
* happened. Generally this will result in some resource leak which, eventually, will cause
* another failure (typically a postcondition violation).\n\n
*
* Rationale for this behavior: There are fundamentally no way to report a failure from a
* destructor in C++, violently terminating the process is inadequate because it again conflates
* failure reporting and failure handling; for instance a failure in glDeleteTextures() shouldn't
* be necessarily fatal (certainly not without saving the user's data first). The alternative
* would be for the caller to swallow the failure entirely, but that's not great either because the
* failure would go unnoticed. The solution retained here is a compromise.
*
* @see ASSERT_DESTRUCTOR
*
* ### Testing Code that Uses Panics ###
*
* Since panics use exceptions for their underlying implementation, you can test code that uses
* panics with EXPECT_THROW by doing the following things:
* \li Set panic mode to THROW (default is TERMINATE)
* \li Pass Panic to EXPECT_THROW as the exception type
*
* Example code for your test file:
*
* @code
* #include <MyClass.hpp> // since your code uses panics, this should include utils/Panic.hpp
*
* using utils::Panic;
*
* TEST(MyClassTest, value_that_causes_panic) {
* EXPECT_THROW(MyClass::function(value_that_causes_panic), Panic);
* }
*
* // ... other tests ...
*
* int main(int argc, char** argv) {
* ::testing::InitGoogleTest(&argc, argv);
* Panic::setMode(Panic::Mode::THROW);
* return RUN_ALL_TESTS();
* }
* @endcode
*
*/
namespace utils {
// -----------------------------------------------------------------------------------------------
/**
* @ingroup errors
*
* \brief Base class of all exceptions thrown by all the ASSERT macros
*
* The Panic class provides the std::exception protocol, it is the base exception object
* used for all thrown exceptions.
*/
class UTILS_PUBLIC Panic {
public:
virtual ~Panic() noexcept;
/**
* @return a detailed description of the error
* @see std::exception
*/
virtual const char* what() const noexcept = 0;
/**
* Get the function name where the panic was detected
* @return a C string containing the function name where the panic was detected
*/
virtual const char* getFunction() const noexcept = 0;
/**
* Get the file name where the panic was detected
* @return a C string containing the file name where the panic was detected
*/
virtual const char* getFile() const noexcept = 0;
/**
* Get the line number in the file where the panic was detected
* @return an integer containing the line number in the file where the panic was detected
*/
virtual int getLine() const noexcept = 0;
/**
* Logs this exception to the system-log
*/
virtual void log() const noexcept = 0;
/**
* Get the CallStack when the panic was detected
* @return the CallStack when the panic was detected
*/
virtual const CallStack& getCallStack() const noexcept = 0;
};
// -----------------------------------------------------------------------------------------------
/**
* @ingroup errors
*
* \brief Concrete implementation of the Panic interface.
*
* The TPanic<> class implements the std::exception protocol as well as the Panic
* interface common to all exceptions thrown by the framework.
*/
template <typename T>
class UTILS_PUBLIC TPanic : public Panic {
public:
// std::exception protocol
const char* what() const noexcept override;
// Panic interface
const char* getFunction() const noexcept override;
const char* getFile() const noexcept override;
int getLine() const noexcept override;
const CallStack& getCallStack() const noexcept override;
void log() const noexcept override;
/**
* Depending on the mode set, either throws an exception of type T with the given reason plus
* extra information about the error-site, or logs the error and calls std::terminate().
* This function never returns.
* @param function the name of the function where the error was detected
* @param file the file where the above function in implemented
* @param line the line in the above file where the error was detected
* @param format printf style string describing the error
* @see ASSERT_PRECONDITION, ASSERT_POSTCONDITION, ASSERT_ARITHMETIC
* @see PANIC_PRECONDITION, PANIC_POSTCONDITION, PANIC_ARITHMETIC
* @see setMode()
*/
static void panic(char const* function, char const* file, int line, const char* format, ...)
UTILS_NORETURN;
/**
* Depending on the mode set, either throws an exception of type T with the given reason plus
* extra information about the error-site, or logs the error and calls std::terminate().
* This function never returns.
* @param function the name of the function where the error was detected
* @param file the file where the above function in implemented
* @param line the line in the above file where the error was detected
* @param s std::string describing the error
* @see ASSERT_PRECONDITION, ASSERT_POSTCONDITION, ASSERT_ARITHMETIC
* @see PANIC_PRECONDITION, PANIC_POSTCONDITION, PANIC_ARITHMETIC
* @see setMode()
*/
static inline void panic(char const* function, char const* file, int line, const std::string& s)
UTILS_NORETURN {
panic(function, file, line, s.c_str());
}
protected:
/**
* Creates a Panic.
* @param reason a description of the cause of the error
*/
explicit TPanic(std::string reason);
/**
* Creates a Panic with extra information about the error-site.
* @param function the name of the function where the error was detected
* @param file the file where the above function in implemented
* @param line the line in the above file where the error was detected
* @param reason a description of the cause of the error
*/
TPanic(char const* function, char const* file, int line, std::string reason);
~TPanic() override;
private:
void buildMessage();
CallStack m_callstack;
std::string m_reason;
char const* const m_function = nullptr;
char const* const m_file = nullptr;
const int m_line = -1;
mutable std::string m_msg;
};
namespace details {
// these are private, don't use
void panicLog(
char const* function, char const* file, int line, const char* format, ...) noexcept;
} // namespace details
// -----------------------------------------------------------------------------------------------
/**
* @ingroup errors
*
* ASSERT_PRECONDITION uses this Panic to report a precondition failure.
* @see ASSERT_PRECONDITION
*/
class UTILS_PUBLIC PreconditionPanic : public TPanic<PreconditionPanic> {
// Programming error, can be avoided
// e.g.: invalid arguments
using TPanic<PreconditionPanic>::TPanic;
friend class TPanic<PreconditionPanic>;
};
/**
* @ingroup errors
*
* ASSERT_POSTCONDITION uses this Panic to report a postcondition failure.
* @see ASSERT_POSTCONDITION
*/
class UTILS_PUBLIC PostconditionPanic : public TPanic<PostconditionPanic> {
// Usually only detectable at runtime
// e.g.: dead-lock would occur, arithmetic errors
using TPanic<PostconditionPanic>::TPanic;
friend class TPanic<PostconditionPanic>;
};
/**
* @ingroup errors
*
* ASSERT_ARITHMETIC uses this Panic to report an arithmetic (postcondition) failure.
* @see ASSERT_ARITHMETIC
*/
class UTILS_PUBLIC ArithmeticPanic : public TPanic<ArithmeticPanic> {
// A common case of post-condition error
// e.g.: underflow, overflow, internal computations errors
using TPanic<ArithmeticPanic>::TPanic;
friend class TPanic<ArithmeticPanic>;
};
// -----------------------------------------------------------------------------------------------
} // namespace utils
#ifndef NDEBUG
# define PANIC_FILE(F) (F)
# define PANIC_FUNCTION __PRETTY_FUNCTION__
#else
# define PANIC_FILE(F) ""
# define PANIC_FUNCTION __func__
#endif
/**
* PANIC_PRECONDITION is a macro that reports a PreconditionPanic
* @param format printf-style string describing the error in more details
*/
#define PANIC_PRECONDITION(format, ...) \
::utils::PreconditionPanic::panic(PANIC_FUNCTION, \
PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__)
/**
* PANIC_POSTCONDITION is a macro that reports a PostconditionPanic
* @param format printf-style string describing the error in more details
*/
#define PANIC_POSTCONDITION(format, ...) \
::utils::PostconditionPanic::panic(PANIC_FUNCTION, \
PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__)
/**
* PANIC_ARITHMETIC is a macro that reports a ArithmeticPanic
* @param format printf-style string describing the error in more details
*/
#define PANIC_ARITHMETIC(format, ...) \
::utils::ArithmeticPanic::panic(PANIC_FUNCTION, \
PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__)
/**
* PANIC_LOG is a macro that logs a Panic, and continues as usual.
* @param format printf-style string describing the error in more details
*/
#define PANIC_LOG(format, ...) \
::utils::details::panicLog(PANIC_FUNCTION, \
PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__)
/**
* @ingroup errors
*
* ASSERT_PRECONDITION is a macro that checks the given condition and reports a PreconditionPanic
* if it evaluates to false.
* @param cond a boolean expression
* @param format printf-style string describing the error in more details
*/
#define ASSERT_PRECONDITION(cond, format, ...) \
(!UTILS_LIKELY(cond) ? PANIC_PRECONDITION(format, ##__VA_ARGS__) : (void)0)
#if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG)
#define ASSERT_PRECONDITION_NON_FATAL(cond, format, ...) \
(!UTILS_LIKELY(cond) ? PANIC_PRECONDITION(format, ##__VA_ARGS__), false : true)
#else
#define ASSERT_PRECONDITION_NON_FATAL(cond, format, ...) \
(!UTILS_LIKELY(cond) ? PANIC_LOG(format, ##__VA_ARGS__), false : true)
#endif
/**
* @ingroup errors
*
* ASSERT_POSTCONDITION is a macro that checks the given condition and reports a PostconditionPanic
* if it evaluates to false.
* @param cond a boolean expression
* @param format printf-style string describing the error in more details
*
* Example:
* @code
* int& Foo::operator[](size_t index) {
* ASSERT_POSTCONDITION(index<m_size, "cannot produce a valid return value");
* return m_array[index];
* }
* @endcode
*/
#define ASSERT_POSTCONDITION(cond, format, ...) \
(!UTILS_LIKELY(cond) ? PANIC_POSTCONDITION(format, ##__VA_ARGS__) : (void)0)
#if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG)
#define ASSERT_POSTCONDITION_NON_FATAL(cond, format, ...) \
(!UTILS_LIKELY(cond) ? PANIC_POSTCONDITION(format, ##__VA_ARGS__), false : true)
#else
#define ASSERT_POSTCONDITION_NON_FATAL(cond, format, ...) \
(!UTILS_LIKELY(cond) ? PANIC_LOG(format, ##__VA_ARGS__), false : true)
#endif
/**
* @ingroup errors
*
* ASSERT_ARITHMETIC is a macro that checks the given condition and reports a ArithmeticPanic
* if it evaluates to false.
* @param cond a boolean expression
* @param format printf-style string describing the error in more details
*
* Example:
* @code
* unt32_t floatToUInt1616(float v) {
* v *= 65536;
* ASSERT_ARITHMETIC(v>=0 && v<65536, "overflow occurred");
* return uint32_t(v);
* }
* @endcode
*/
#define ASSERT_ARITHMETIC(cond, format, ...) \
(!(cond) ? PANIC_ARITHMETIC(format, ##__VA_ARGS__) : (void)0)
#if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG)
#define ASSERT_ARITHMETIC_NON_FATAL(cond, format, ...) \
(!UTILS_LIKELY(cond) ? PANIC_ARITHMETIC(format, ##__VA_ARGS__), false : true)
#else
#define ASSERT_ARITHMETIC_NON_FATAL(cond, format, ...) \
(!UTILS_LIKELY(cond) ? PANIC_LOG(format, ##__VA_ARGS__), false : true)
#endif
/**
* @ingroup errors
*
* ASSERT_DESTRUCTOR is a macro that checks the given condition and logs an error
* if it evaluates to false.
* @param cond a boolean expression
* @param format printf-style string describing the error in more details
*
* @warning Use this macro if a destructor can fail, which should be avoided at all costs.
* Unlike the other ASSERT macros, this will never result in the process termination. Instead,
* the error will be logged and the program will continue as if nothing happened.
*
* Example:
* @code
* Foo::~Foo() {
* glDeleteTextures(1, &m_texture);
* GLint err = glGetError();
* ASSERT_DESTRUCTOR(err == GL_NO_ERROR, "cannot free GL resource!");
* }
* @endcode
*/
#define ASSERT_DESTRUCTOR(cond, format, ...) (!(cond) ? PANIC_LOG(format, ##__VA_ARGS__) : (void)0)
#endif // TNT_UTILS_PANIC_H

303
macos/include/utils/Path.h Normal file
View File

@@ -0,0 +1,303 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_PATH_H
#define TNT_UTILS_PATH_H
#include <utils/compiler.h>
#include <iosfwd>
#include <string>
#include <vector>
namespace utils {
/**
* An abstract representation of file and directory paths.
*/
class UTILS_PUBLIC Path {
public:
/**
* Creates a new empty path.
*/
Path() = default;
~Path() = default;
/**
* Creates a new path with the specified pathname.
*
* @param pathname a non-null pathname string
*/
Path(const char* pathname);
/**
* Creates a new path with the specified pathname.
*
* @param pathname a pathname string view
*/
Path(std::string_view pathname);
/**
* Creates a new path with the specified pathname.
*
* @param pathname a pathname string
*/
Path(const std::string& pathname);
/**
* Tests whether the file or directory denoted by this abstract
* pathname exists.
*
* @return true if the file or directory denoted by this
* abstract pathname exists, false otherwise
*/
bool exists() const;
/**
* Tests whether this abstract pathname represents a regular file.
* This method can only return true if the path exists.
*
* @return true if this pathname represents an existing file,
* false if the path doesn't exist or represents something
* else (directory, symlink, etc.)
*/
bool isFile() const;
/**
* Tests whether this abstract pathname represents a directory.
* This method can only return true if the path exists.
*
* @return true if this pathname represents an existing directory,
* false if the path doesn't exist or represents a file
*/
bool isDirectory() const;
/**
* Tests whether this path is empty. An empty path does not
* exist.
*
* @return true if the underlying abstract pathname is empty,
* false otherwise
*/
bool isEmpty() const { return m_path.empty(); }
const char* c_str() const { return m_path.c_str(); }
/**
* Replaces the abstract pathname of this object with the
* specified pathname.
*
* @param pathname a pathname string
*/
void setPath(const std::string& pathname) {
m_path = getCanonicalPath(pathname);
}
/**
* @return the canonical pathname this path represents
*/
const std::string& getPath() const { return m_path; }
/**
* Returns the parent of this path as Path.
* @return a new path containing the parent of this path
*/
Path getParent() const;
/**
* Returns ancestor path where "0" is the immediate parent.
* @return a new path containing the ancestor of this path
*/
Path getAncestor(int n) const;
/**
* Returns the name of the file or directory represented by
* this abstract pathname.
*
* @return the name of the file or directory represented by
* this abstract pathname, or an empty string if
* this path is empty
*/
std::string getName() const;
/**
* Returns the name of the file or directory represented by
* this abstract pathname without its extension.
*
* @return the name of the file or directory represented by
* this abstract pathname, or an empty string if
* this path is empty
*/
std::string getNameWithoutExtension() const;
/**
* Returns the file extension (after the ".") if one is present.
* Returns the empty string if no filename is present or if the
* path is a directory.
*
* @return the file extension (if one is present and
* this is not a directory), else the empty string.
*/
std::string getExtension() const;
/**
* Returns the absolute representation of this path.
* If this path's pathname starts with a leading '/',
* the returned path is equal to this path. Otherwise,
* this path's pathname is concatenated with the current
* working directory and the result is returned.
*
* @return a new path containing the absolute representation
* of this path
*/
Path getAbsolutePath() const;
/**
* @return true if this abstract pathname is not empty
* and starts with a leading '/', false otherwise
*/
bool isAbsolute() const;
/**
* Splits this object's abstract pathname in a vector of file
* and directory name. If the underlying abstract pathname
* starts with a '/', the returned vector's first element
* will be the string "/".
*
* @return a vector of strings, empty if this path is empty
*/
std::vector<std::string> split() const;
/**
* Concatenates the specified path with this path in a new
* path object.
*
* @note if the pathname to concatenate with starts with
* a leading '/' then that pathname is returned without
* being concatenated to this object's pathname.
*
* @param path the path to concatenate with
*
* @return the concatenation of the two paths
*/
Path concat(const Path& path) const;
/**
* Concatenates the specified path with this path and
* stores the result in this path.
*
* @note if the pathname to concatenate with starts with
* a leading '/' then that pathname replaces this object's
* pathname.
*
* @param path the path to concatenate with
*/
void concatToSelf(const Path& path);
operator std::string const&() const { return m_path; }
Path operator+(const Path& rhs) const { return concat(rhs); }
Path& operator+=(const Path& rhs) {
concatToSelf(rhs);
return *this;
}
bool operator==(const Path& rhs) const { return m_path == rhs.m_path; }
bool operator!=(const Path& rhs) const { return m_path != rhs.m_path; }
bool operator<(const Path& rhs) const { return m_path < rhs.m_path; }
bool operator>(const Path& rhs) const { return m_path > rhs.m_path; }
friend std::ostream& operator<<(std::ostream& os, const Path& path);
/**
* Returns a canonical copy of the specified pathname by removing
* unnecessary path segments such as ".", ".." and "/".
*
* @param pathname a pathname string
*
* @return the canonical representation of the specified pathname
*/
static std::string getCanonicalPath(const std::string& pathname);
/**
* This method is equivalent to calling root.concat(leaf).
*/
static Path concat(const std::string& root, const std::string& leaf);
/**
* @return a path representing the current working directory
*/
static Path getCurrentDirectory();
/**
* @return a path representing the current executable
*/
static Path getCurrentExecutable();
/**
* @return a path representing a directory where temporary files can be stored
*/
static Path getTemporaryDirectory();
/**
* @return a path representing a directory where settings files can be stored,
* it is recommended to append an app specific folder name to that path
*/
static Path getUserSettingsDirectory();
/**
* Creates a directory denoted by the given path.
* This is not recursive and doesn't create intermediate directories.
*
* @return True if directory was successfully created.
* When false, errno should have details on actual error.
*/
bool mkdir() const;
/**
* Creates a directory denoted by the given path.
* This is recursive and parent directories will be created if they do not
* exist.
*
* @return True if directory was successfully created or already exists.
* When false, errno should have details on actual error.
*/
bool mkdirRecursive() const;
/**
* Deletes this file.
*
* @return True if file was successfully deleted.
* When false, errno should have details on actual error.
*/
bool unlinkFile();
/**
* Lists the contents of this directory, skipping hidden files.
*
* @return A vector of paths of the contents of the directory. If the path points to a file,
* nonexistent directory, or empty directory, an empty vector is returned.
*/
std::vector<Path> listContents() const;
private:
std::string m_path;
};
} // namespace utils
#endif // TNT_UTILS_PATH_H

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UTILS_PRIVATEIMPLEMENTATION_IMPL_H
#define UTILS_PRIVATEIMPLEMENTATION_IMPL_H
/*
* This looks like a header file, but really it acts as a .cpp, because it's used to
* explicitly instantiate the methods of PrivateImplementation<>
*/
#include <utils/PrivateImplementation.h>
#include <utility>
namespace utils {
template<typename T>
PrivateImplementation<T>::PrivateImplementation() noexcept
: mImpl(new T) {
}
template<typename T>
template<typename ... ARGS>
PrivateImplementation<T>::PrivateImplementation(ARGS&& ... args) noexcept
: mImpl(new T(std::forward<ARGS>(args)...)) {
}
template<typename T>
PrivateImplementation<T>::~PrivateImplementation() noexcept {
delete mImpl;
}
#ifndef UTILS_PRIVATE_IMPLEMENTATION_NON_COPYABLE
template<typename T>
PrivateImplementation<T>::PrivateImplementation(PrivateImplementation const& rhs) noexcept
: mImpl(new T(*rhs.mImpl)) {
}
template<typename T>
PrivateImplementation<T>& PrivateImplementation<T>::operator=(PrivateImplementation<T> const& rhs) noexcept {
if (this != &rhs) {
*mImpl = *rhs.mImpl;
}
return *this;
}
#endif
} // namespace utils
#endif // UTILS_PRIVATEIMPLEMENTATION_IMPL_H

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef UTILS_PRIVATEIMPLEMENTATION_H
#define UTILS_PRIVATEIMPLEMENTATION_H
#include <utils/compiler.h>
#include <stddef.h>
namespace utils {
/**
* \privatesection
* PrivateImplementation is used to hide the implementation details of a class and ensure a higher
* level of backward binary compatibility.
* The actual implementation is in src/PrivateImplementation-impl.h"
*/
template<typename T>
class PrivateImplementation {
public:
// none of these methods must be implemented inline because it's important that their
// implementation be hidden from the public headers.
template<typename ... ARGS>
explicit PrivateImplementation(ARGS&& ...) noexcept;
PrivateImplementation() noexcept;
~PrivateImplementation() noexcept;
PrivateImplementation(PrivateImplementation const& rhs) noexcept;
PrivateImplementation& operator = (PrivateImplementation const& rhs) noexcept;
// move ctor and copy operator can be implemented inline and don't need to be exported
PrivateImplementation(PrivateImplementation&& rhs) noexcept : mImpl(rhs.mImpl) { rhs.mImpl = nullptr; }
PrivateImplementation& operator = (PrivateImplementation&& rhs) noexcept {
auto temp = mImpl;
mImpl = rhs.mImpl;
rhs.mImpl = temp;
return *this;
}
protected:
T* mImpl = nullptr;
inline T* operator->() noexcept { return mImpl; }
inline T const* operator->() const noexcept { return mImpl; }
};
} // namespace utils
#endif // UTILS_PRIVATEIMPLEMENTATION_H

View File

@@ -0,0 +1,212 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_PROFILER_H
#define TNT_UTILS_PROFILER_H
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <chrono> // note: This is safe (only used inline)
#if defined(__linux__)
# include <unistd.h>
# include <sys/ioctl.h>
# include <linux/perf_event.h>
#endif
#include <utils/compiler.h>
namespace utils {
class Profiler {
public:
enum {
INSTRUCTIONS = 0, // must be zero
CPU_CYCLES = 1,
DCACHE_REFS = 2,
DCACHE_MISSES = 3,
BRANCHES = 4,
BRANCH_MISSES = 5,
ICACHE_REFS = 6,
ICACHE_MISSES = 7,
// Must be last one
EVENT_COUNT
};
enum {
EV_CPU_CYCLES = 1u << CPU_CYCLES,
EV_L1D_REFS = 1u << DCACHE_REFS,
EV_L1D_MISSES = 1u << DCACHE_MISSES,
EV_BPU_REFS = 1u << BRANCHES,
EV_BPU_MISSES = 1u << BRANCH_MISSES,
EV_L1I_REFS = 1u << ICACHE_REFS,
EV_L1I_MISSES = 1u << ICACHE_MISSES,
// helpers
EV_L1D_RATES = EV_L1D_REFS | EV_L1D_MISSES,
EV_L1I_RATES = EV_L1I_REFS | EV_L1I_MISSES,
EV_BPU_RATES = EV_BPU_REFS | EV_BPU_MISSES,
};
Profiler() noexcept; // must call resetEvents()
explicit Profiler(uint32_t eventMask) noexcept;
~Profiler() noexcept;
Profiler(const Profiler& rhs) = delete;
Profiler(Profiler&& rhs) = delete;
Profiler& operator=(const Profiler& rhs) = delete;
Profiler& operator=(Profiler&& rhs) = delete;
// selects which events are enabled.
uint32_t resetEvents(uint32_t eventMask) noexcept;
uint32_t getEnabledEvents() const noexcept { return mEnabledEvents; }
// could return false if performance counters are not supported/enabled
bool isValid() const { return mCountersFd[0] >= 0; }
class Counters {
friend class Profiler;
uint64_t nr;
uint64_t time_enabled;
uint64_t time_running;
struct {
uint64_t value;
uint64_t id;
} counters[Profiler::EVENT_COUNT];
friend Counters operator-(Counters lhs, const Counters& rhs) noexcept {
lhs.nr -= rhs.nr;
lhs.time_enabled -= rhs.time_enabled;
lhs.time_running -= rhs.time_running;
for (size_t i = 0; i < EVENT_COUNT; ++i) {
lhs.counters[i].value -= rhs.counters[i].value;
}
return lhs;
}
public:
uint64_t getInstructions() const { return counters[INSTRUCTIONS].value; }
uint64_t getCpuCycles() const { return counters[CPU_CYCLES].value; }
uint64_t getL1DReferences() const { return counters[DCACHE_REFS].value; }
uint64_t getL1DMisses() const { return counters[DCACHE_MISSES].value; }
uint64_t getL1IReferences() const { return counters[ICACHE_REFS].value; }
uint64_t getL1IMisses() const { return counters[ICACHE_MISSES].value; }
uint64_t getBranchInstructions() const { return counters[BRANCHES].value; }
uint64_t getBranchMisses() const { return counters[BRANCH_MISSES].value; }
std::chrono::duration<uint64_t, std::nano> getWallTime() const {
return std::chrono::duration<uint64_t, std::nano>(time_enabled);
}
std::chrono::duration<uint64_t, std::nano> getRunningTime() const {
return std::chrono::duration<uint64_t, std::nano>(time_running);
}
double getIPC() const noexcept {
uint64_t cpuCycles = getCpuCycles();
uint64_t instructions = getInstructions();
return double(instructions) / double(cpuCycles);
}
double getCPI() const noexcept {
uint64_t cpuCycles = getCpuCycles();
uint64_t instructions = getInstructions();
return double(cpuCycles) / double(instructions);
}
double getL1DMissRate() const noexcept {
uint64_t cacheReferences = getL1DReferences();
uint64_t cacheMisses = getL1DMisses();
return double(cacheMisses) / double(cacheReferences);
}
double getL1DHitRate() const noexcept {
return 1.0 - getL1DMissRate();
}
double getL1IMissRate() const noexcept {
uint64_t cacheReferences = getL1IReferences();
uint64_t cacheMisses = getL1IMisses();
return double(cacheMisses) / double(cacheReferences);
}
double getL1IHitRate() const noexcept {
return 1.0 - getL1IMissRate();
}
double getBranchMissRate() const noexcept {
uint64_t branchReferences = getBranchInstructions();
uint64_t branchMisses = getBranchMisses();
return double(branchMisses) / double(branchReferences);
}
double getBranchHitRate() const noexcept {
return 1.0 - getBranchMissRate();
}
double getMPKI(uint64_t misses) const noexcept {
return (misses * 1000.0) / getInstructions();
}
};
#if defined(__linux__)
void reset() noexcept {
int fd = mCountersFd[0];
ioctl(fd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP);
}
void start() noexcept {
int fd = mCountersFd[0];
ioctl(fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP);
}
void stop() noexcept {
int fd = mCountersFd[0];
ioctl(fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP);
}
Counters readCounters() noexcept;
#else // !__linux__
void reset() noexcept { }
void start() noexcept { }
void stop() noexcept { }
Counters readCounters() noexcept { return {}; }
#endif // __linux__
bool hasBranchRates() const noexcept {
return (mCountersFd[BRANCHES] >= 0) && (mCountersFd[BRANCH_MISSES] >= 0);
}
bool hasICacheRates() const noexcept {
return (mCountersFd[ICACHE_REFS] >= 0) && (mCountersFd[ICACHE_MISSES] >= 0);
}
private:
UTILS_UNUSED uint8_t mIds[EVENT_COUNT] = {};
int mCountersFd[EVENT_COUNT];
uint32_t mEnabledEvents = 0;
};
} // namespace utils
#endif // TNT_UTILS_PROFILER_H

View File

@@ -0,0 +1,180 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_QUADTREE_H
#define TNT_UTILS_QUADTREE_H
#include <utils/compiler.h>
#include <utils/debug.h>
#include <array>
#include <type_traits>
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
namespace utils {
namespace QuadTreeUtils {
/**
* 16-bits morton encoding
* @param x 8-bits horizontal coordinate
* @param y 8-bits vertical coordinate
* @return morton encoding of (x,y)
*/
static inline constexpr uint16_t morton(uint8_t x, uint8_t y) noexcept {
uint32_t r = x | (uint32_t(y) << 16);
r = (r | (r << 4u)) & 0x0f0f0f0fu;
r = (r | (r << 2u)) & 0x33333333u;
r = (r | (r << 1u)) & 0x55555555u;
return uint16_t(r | (r >> 15u));
}
/**
* size of a quad-tree of height `height`
* @param height height of the Quad-tree
* @return the number of elements in the tree
*/
static inline constexpr size_t size(size_t height) noexcept {
return QuadTreeUtils::morton(uint8_t((1u << height) - 1u), 0u);
}
/**
* Index in the QuadTreeArray of a Quad-tree node referenced by its height and code
* @param l height of the node
* @param code morton code of the node
* @return index in the QuadTreeArray of this node
*/
static inline constexpr size_t index(size_t l, size_t code) noexcept {
return size(l) + code;
}
/**
* Index in the QuadTreeArray of the parent of the specified node
* @param l height of the node
* @param code morton code of the node
* @return index in the QuadTreeArray of this node's parent
*/
static inline constexpr size_t parent(size_t l, size_t code) noexcept {
assert_invariant(l > 0);
return index(l - 1u, code >> 2u);
}
// integrated unit-tests!
static_assert(size(0) == 0);
static_assert(size(1) == 1);
static_assert(size(2) == 5);
static_assert(size(3) == 21);
static_assert(size(4) == 85);
} // namespace QuadTreeUtils
/**
* A Quad-tree encoded as an array.
* @tparam T Type of the quad-tree nodes
* @tparam HEIGHT Height of the quad-tree, muse be <= 4
*/
template<typename T, size_t HEIGHT>
class QuadTreeArray : public std::array<T, QuadTreeUtils::size(HEIGHT)> {
static_assert(HEIGHT <= 4, "QuadTreeArray height must be <= 4");
// Simple fixed capacity stack
template<typename TYPE, size_t CAPACITY,
typename = typename std::enable_if<std::is_trivial<TYPE>::value>::type>
class stack {
TYPE mElements[CAPACITY];
size_t mSize = 0;
public:
bool empty() const noexcept { return mSize == 0; }
void push(TYPE const& v) noexcept {
assert_invariant(mSize < CAPACITY);
mElements[mSize++] = v;
}
void pop() noexcept {
assert_invariant(mSize > 0);
--mSize;
}
const TYPE& back() const noexcept {
return mElements[mSize - 1];
}
};
public:
using code_t = uint8_t;
struct NodeId {
int8_t l; // height of the node or -1 if invalid
code_t code; // morton code of the node
};
enum class TraversalResult {
EXIT, // end traversal
RECURSE, // proceed with the children
SKIP_CHILDREN // skip children
};
static inline constexpr size_t height() noexcept {
return HEIGHT;
}
/**
* non-recursive depth-first traversal
*
* @tparam Process closure to process each visited node
* @param l height of the node to start with
* @param code code of the node to start with
* @param h maximum height to visit, must be < height()
* @param process closure to process each visited node
*/
template<typename Process,
typename = typename std::enable_if<
std::is_invocable_r_v<TraversalResult, Process, NodeId>>::type>
static void traverse(int8_t l, code_t code, size_t maxHeight, Process&& process) noexcept {
assert_invariant(maxHeight < height());
const int8_t h = int8_t(maxHeight);
stack<NodeId, 4 * height()> stack;
stack.push({ l, code });
while (!stack.empty()) {
NodeId curr = stack.back();
stack.pop();
TraversalResult r = process(curr);
if (r == TraversalResult::EXIT) {
break;
}
if (r == TraversalResult::RECURSE && curr.l < h) {
for (size_t c = 0; c < 4; c++) {
stack.push({
int8_t(curr.l + 1u),
code_t((curr.code << 2u) | (3u - c))
});
}
}
}
}
template<typename Node,
typename = typename std::enable_if<
std::is_invocable_r_v<TraversalResult, Node, NodeId>>::type>
static void traverse(int8_t l, code_t code, Node&& process) noexcept {
traverse(l, code, height() - 1, std::forward<Node>(process));
}
};
} // namespace utils
#endif //TNT_UTILS_QUADTREE_H

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_RANGE_H
#define TNT_UTILS_RANGE_H
#include <stddef.h>
#include <iterator>
namespace utils {
template<typename T>
struct Range {
using value_type = T;
T first = 0;
T last = 0; // this actually refers to one past the last
size_t size() const noexcept { return last - first; }
bool empty() const noexcept { return !size(); }
bool contains(const T& t) const noexcept { return first <= t && t < last; }
bool overlaps(const Range<T>& that) const noexcept {
return that.first < this->last && that.last > this->first;
}
class const_iterator {
friend struct Range;
T value = {};
public:
const_iterator() noexcept = default;
explicit const_iterator(T value) noexcept : value(value) {}
using value_type = T;
using pointer = value_type*;
using difference_type = ptrdiff_t;
using iterator_category = std::random_access_iterator_tag;
const value_type operator*() const { return value; }
const value_type operator[](size_t n) const { return value + n; }
const_iterator& operator++() { ++value; return *this; }
const_iterator& operator--() { --value; return *this; }
const const_iterator operator++(int) { const_iterator t(value); value++; return t; }
const const_iterator operator--(int) { const_iterator t(value); value--; return t; }
const_iterator operator+(size_t rhs) const { return { value + rhs }; }
const_iterator operator+(size_t rhs) { return { value + rhs }; }
const_iterator operator-(size_t rhs) const { return { value - rhs }; }
difference_type operator-(const_iterator const& rhs) const { return value - rhs.value; }
bool operator==(const_iterator const& rhs) const { return (value == rhs.value); }
bool operator!=(const_iterator const& rhs) const { return (value != rhs.value); }
bool operator>=(const_iterator const& rhs) const { return (value >= rhs.value); }
bool operator> (const_iterator const& rhs) const { return (value > rhs.value); }
bool operator<=(const_iterator const& rhs) const { return (value <= rhs.value); }
bool operator< (const_iterator const& rhs) const { return (value < rhs.value); }
};
const_iterator begin() noexcept { return const_iterator{ first }; }
const_iterator end() noexcept { return const_iterator{ last }; }
const_iterator begin() const noexcept { return const_iterator{ first }; }
const_iterator end() const noexcept { return const_iterator{ last }; }
const_iterator front() const noexcept { return const_iterator{ first }; }
const_iterator back() const noexcept { return const_iterator{ last - 1 }; }
};
} // namespace utils
#endif // TNT_UTILS_RANGE_H

View File

@@ -0,0 +1,308 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_RANGEMAP_H
#define TNT_UTILS_RANGEMAP_H
#include <utils/Panic.h>
#include <utils/Range.h>
#include <utils/debug.h>
#include <map>
namespace utils {
/**
* Sparse container for a series of ordered non-overlapping intervals.
*
* RangeMap has a low memory footprint if it contains fairly homogeneous data. Internally, the
* intervals are automatically split and merged as elements are added or removed.
*
* Each interval maps to an instance of ValueType, which should support cheap equality checks
* and copy assignment. (simple concrete types are ideal)
*
* KeyType should support operator< because intervals are internally sorted using std::map.
*/
template<typename KeyType, typename ValueType>
class RangeMap {
public:
/**
* Replaces all slots between first (inclusive) and last (exclusive).
*/
void add(KeyType first, KeyType last, const ValueType& value) noexcept {
// First check if an existing range contains "first".
Iterator iter = findRange(first);
if (iter != end()) {
const Range<KeyType> existing = getRange(iter);
// Check if the existing range be extended.
if (getValue(iter) == value) {
if (existing.last < last) {
wipe(existing.last, last);
iter = shrink(iter, existing.first, last);
mergeRight(iter);
}
return;
}
// Split the existing range into two ranges.
if (last < existing.last && first > existing.first) {
iter = shrink(iter, existing.first, first);
insert(first, last, value);
insert(last, existing.last, getValue(iter));
return;
}
clear(first, last);
insert(first, last, value);
return;
}
// Check if an existing range contains the end of the new range.
KeyType back = last;
iter = findRange(--back);
if (iter == end()) {
wipe(first, last);
insert(first, last, value);
return;
}
const Range<KeyType> existing = getRange(iter);
// Check if the existing range be extended.
if (getValue(iter) == value) {
if (existing.first > first) {
wipe(first, existing.first);
iter = shrink(iter, first, existing.last);
mergeLeft(iter);
}
return;
}
// Clip the beginning of the existing range and potentially remove it.
if (last < existing.last) {
shrink(iter, last, existing.last);
}
wipe(first, last);
insert(first, last, value);
}
/**
* Shorthand for the "add" method that inserts a single element.
*/
void set(KeyType key, const ValueType& value) noexcept {
KeyType begin = key;
add(begin, ++key, value);
}
/**
* Checks if a range exists that encompasses the given key.
*/
bool has(KeyType key) const noexcept {
return findRange(key) != mMap.end();
}
/**
* Retrieves the element at the given location, panics if no element exists.
*/
const ValueType& get(KeyType key) const {
ConstIterator iter = findRange(key);
ASSERT_PRECONDITION(iter != end(), "RangeMap: No element exists at the given key.");
return getValue(iter);
}
/**
* Removes all elements between begin (inclusive) and end (exclusive).
*/
void clear(KeyType first, KeyType last) noexcept {
// Check if an existing range contains "first".
Iterator iter = findRange(first);
if (iter != end()) {
const Range<KeyType> existing = getRange(iter);
// Split the existing range into two ranges.
if (last < existing.last && first > existing.first) {
iter = shrink(iter, existing.first, first);
insert(last, existing.last, getValue(iter));
return;
}
// Clip one of the ends of the existing range or remove it.
if (first > existing.first) {
shrink(iter, existing.first, first);
} else if (last < existing.last) {
shrink(iter, last, existing.last);
} else {
wipe(first, last);
}
// There might be another range that intersects the cleared range, so try again.
clear(first, last);
return;
}
// Check if an existing range contains the end of the new range.
KeyType back = last;
iter = findRange(--back);
if (iter == end()) {
wipe(first, last);
return;
}
const Range<KeyType> existing = getRange(iter);
// Clip the beginning of the existing range and potentially remove it.
if (last < existing.last) {
shrink(iter, last, existing.last);
}
wipe(first, last);
}
/**
* Shorthand for the "clear" method that clears a single element.
*/
void reset(KeyType key) noexcept {
KeyType begin = key;
clear(begin, ++key);
}
/**
* Returns the number of internal interval objects (rarely used).
*/
size_t rangeCount() const noexcept { return mMap.size(); }
private:
using Map = std::map<KeyType, std::pair<Range<KeyType>, ValueType>>;
using Iterator = typename Map::iterator;
using ConstIterator = typename Map::const_iterator;
ConstIterator begin() const noexcept { return mMap.begin(); }
ConstIterator end() const noexcept { return mMap.end(); }
Iterator begin() noexcept { return mMap.begin(); }
Iterator end() noexcept { return mMap.end(); }
Range<KeyType>& getRange(Iterator iter) const { return iter->second.first; }
ValueType& getValue(Iterator iter) const { return iter->second.second; }
const Range<KeyType>& getRange(ConstIterator iter) const { return iter->second.first; }
const ValueType& getValue(ConstIterator iter) const { return iter->second.second; }
// Private helper that assumes there is no existing range that overlaps the given range.
void insert(KeyType first, KeyType last, const ValueType& value) noexcept {
assert_invariant(!has(first));
assert_invariant(!has(last - 1));
// Check if there is an adjacent range to the left than can be extended.
KeyType previous = first;
if (Iterator iter = findRange(--previous); iter != end() && getValue(iter) == value) {
getRange(iter).last = last;
mergeRight(iter);
return;
}
// Check if there is an adjacent range to the right than can be extended.
if (Iterator iter = findRange(last); iter != end() && getValue(iter) == value) {
getRange(iter).first = first;
return;
}
mMap[first] = {Range<KeyType> { first, last }, value};
}
// Private helper that erases all intervals that are wholly contained within the given range.
// Note that this is quite different from the public "clear" method.
void wipe(KeyType first, KeyType last) noexcept {
// Find the first range whose beginning is greater than or equal to "first".
Iterator iter = mMap.lower_bound(first);
while (iter != end() && getRange(iter).first < last) {
KeyType existing_last = getRange(iter).last;
if (existing_last > last) {
break;
}
iter = mMap.erase(iter);
}
}
// Checks if there is range to the right that touches the given range.
// If so, erases it, extends the given range rightwards, and returns true.
bool mergeRight(Iterator iter) {
Iterator next = iter;
if (++next == end() || getValue(next) != getValue(iter)) {
return false;
}
if (getRange(next).first != getRange(iter).last) {
return false;
}
getRange(iter).last = getRange(next).last;
mMap.erase(next);
return true;
}
// Checks if there is range to the left that touches the given range.
// If so, erases it, extends the given range leftwards, and returns true.
bool mergeLeft(Iterator iter) {
Iterator prev = iter;
if (--prev == end() || getValue(prev) != getValue(iter)) {
return false;
}
if (getRange(prev).last != getRange(iter).first) {
return false;
}
getRange(iter).first = getRange(prev).first;
mMap.erase(prev);
return true;
}
// Private helper that clips one end of an existing range.
Iterator shrink(Iterator iter, KeyType first, KeyType last) {
assert_invariant(first < last);
assert_invariant(getRange(iter).first == first || getRange(iter).last == last);
std::pair<utils::Range<KeyType>, ValueType> value = {{first, last}, iter->second.second};
mMap.erase(iter);
return mMap.insert({first, value}).first;
}
// If the given key is encompassed by an existing range, returns an iterator for that range.
// If no encompassing range exists, returns end().
ConstIterator findRange(KeyType key) const noexcept {
return findRangeT<ConstIterator>(*this, key);
}
// If the given key is encompassed by an existing range, returns an iterator for that range.
// If no encompassing range exists, returns end().
Iterator findRange(KeyType key) noexcept {
return findRangeT<Iterator>(*this, key);
}
// This template method allows us to avoid code duplication for const and non-const variants of
// findRange. C++17 has "std::as_const()" but that would not be helpful here, as we would still
// need to convert a const iterator to a non-const iterator.
template<typename IteratorType, typename SelfType>
static IteratorType findRangeT(SelfType& instance, KeyType key) noexcept {
// Find the first range whose beginning is greater than or equal to the given key.
IteratorType iter = instance.mMap.lower_bound(key);
if (iter != instance.end() && instance.getRange(iter).contains(key)) {
return iter;
}
// If that was the first range, or if the map is empty, return false.
if (iter == instance.begin()) {
return instance.end();
}
// Check the range immediately previous to the one that was found.
return instance.getRange(--iter).contains(key) ? iter : instance.end();
}
// This maps from the start value of each range to the range itself.
Map mMap;
};
} // namespace utils
#endif // TNT_UTILS_RANGEMAP_H

View File

@@ -0,0 +1,318 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_SINGLEINSTANCECOMPONENTMANAGER_H
#define TNT_UTILS_SINGLEINSTANCECOMPONENTMANAGER_H
#include <utils/compiler.h>
#include <utils/Entity.h>
#include <utils/EntityInstance.h>
#include <utils/EntityManager.h>
#include <utils/StructureOfArrays.h>
#include <tsl/robin_map.h>
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
namespace utils {
class EntityManager;
/*
* Helper class to create single instance component managers.
*
* This handles the component's storage as a structure-of-arrays, as well
* as the garbage collection.
*
* This is intended to be used as base class for a real component manager. When doing so,
* and the real component manager is a public API, make sure to forward the public methods
* to the implementation.
*
*/
template <typename ... Elements>
class UTILS_PUBLIC SingleInstanceComponentManager {
private:
// this is just to avoid using std::default_random_engine, since we're in a public header.
class default_random_engine {
uint32_t mState = 1u; // must be 0 < seed < 0x7fffffff
public:
inline uint32_t operator()() noexcept {
return mState = uint32_t((uint64_t(mState) * 48271u) % 0x7fffffffu);
}
};
protected:
static constexpr size_t ENTITY_INDEX = sizeof ... (Elements);
public:
using SoA = StructureOfArrays<Elements ..., Entity>;
using Structure = typename SoA::Structure;
using Instance = EntityInstanceBase::Type;
SingleInstanceComponentManager() noexcept {
// We always start with a dummy entry because index=0 is reserved. The component
// at index = 0, is guaranteed to be default-initialized.
// Sub-classes can use this to their advantage.
mData.push_back(Structure{});
}
SingleInstanceComponentManager(SingleInstanceComponentManager&&) noexcept {/* = default */}
SingleInstanceComponentManager& operator=(SingleInstanceComponentManager&&) noexcept {/* = default */}
~SingleInstanceComponentManager() noexcept = default;
// not copyable
SingleInstanceComponentManager(SingleInstanceComponentManager const& rhs) = delete;
SingleInstanceComponentManager& operator=(SingleInstanceComponentManager const& rhs) = delete;
// returns true if the given Entity has a component of this Manager
bool hasComponent(Entity e) const noexcept {
return getInstance(e) != 0;
}
// Get instance of this Entity to be used to retrieve components
UTILS_NOINLINE
Instance getInstance(Entity e) const noexcept {
auto const& map = mInstanceMap;
// find() generates quite a bit of code
auto pos = map.find(e);
return pos != map.end() ? pos->second : 0;
}
// returns the number of components (i.e. size of each arrays)
size_t getComponentCount() const noexcept {
// The array as an extra dummy component at index 0, so the visible count is 1 less.
return mData.size() - 1;
}
bool empty() const noexcept {
return getComponentCount() == 0;
}
// returns a pointer to the Entity array. This is basically the list
// of entities this component manager handles.
// The pointer becomes invalid when adding or removing a component.
Entity const* getEntities() const noexcept {
return begin<ENTITY_INDEX>();
}
Entity getEntity(Instance i) const noexcept {
return elementAt<ENTITY_INDEX>(i);
}
// Add a component to the given Entity. If the entity already has a component from this
// manager, this function is a no-op.
// This invalidates all pointers components.
inline Instance addComponent(Entity e);
// Removes a component from the given entity.
// This invalidates all pointers components.
inline Instance removeComponent(Entity e);
// trigger one round of garbage collection. this is intended to be called on a regular
// basis. This gc gives up after it cannot randomly free 'ratio' component in a row.
void gc(const EntityManager& em, size_t ratio = 4) noexcept {
gc(em, ratio, [this](Entity e) {
removeComponent(e);
});
}
// return the first instance
Instance begin() const noexcept { return 1u; }
// return the past-the-last instance
Instance end() const noexcept { return Instance(begin() + getComponentCount()); }
// return a pointer to the first element of the ElementIndex'th array
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex>* begin() noexcept {
return mData.template data<ElementIndex>() + 1;
}
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex> const* begin() const noexcept {
return mData.template data<ElementIndex>() + 1;
}
// return a pointer to the past-the-end element of the ElementIndex'th array
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex>* end() noexcept {
return begin<ElementIndex>() + getComponentCount();
}
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex> const* end() const noexcept {
return begin<ElementIndex>() + getComponentCount();
}
// return a Slice<>
template<size_t ElementIndex>
Slice<typename SoA::template TypeAt<ElementIndex>> slice() noexcept {
return { begin<ElementIndex>(), end<ElementIndex>() };
}
template<size_t ElementIndex>
Slice<const typename SoA::template TypeAt<ElementIndex>> slice() const noexcept {
return { begin<ElementIndex>(), end<ElementIndex>() };
}
// return a reference to the index'th element of the ElementIndex'th array
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex>& elementAt(Instance index) noexcept {
assert(index);
return data<ElementIndex>()[index];
}
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex> const& elementAt(Instance index) const noexcept {
assert(index);
return data<ElementIndex>()[index];
}
// returns a pointer to the RAW ARRAY of components including the first dummy component
// Use with caution.
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex> const* raw_array() const noexcept {
return data<ElementIndex>();
}
// We need our own version of Field because mData is private
template<size_t E>
struct Field : public SoA::template Field<E> {
Field(SingleInstanceComponentManager& soa, EntityInstanceBase::Type i) noexcept
: SoA::template Field<E>{ soa.mData, i } {
}
using SoA::template Field<E>::operator =;
};
protected:
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex>* data() noexcept {
return mData.template data<ElementIndex>();
}
template<size_t ElementIndex>
typename SoA::template TypeAt<ElementIndex> const* data() const noexcept {
return mData.template data<ElementIndex>();
}
// swap only internals
void swap(Instance i, Instance j) noexcept {
assert(i);
assert(j);
if (i && j) {
// update the index map
auto& map = mInstanceMap;
Entity& ei = elementAt<ENTITY_INDEX>(i);
Entity& ej = elementAt<ENTITY_INDEX>(j);
std::swap(ei, ej);
if (ei) {
map[ei] = i;
}
if (ej) {
map[ej] = j;
}
}
}
template<typename REMOVE>
void gc(const EntityManager& em, size_t ratio,
REMOVE removeComponent) noexcept {
Entity const* entities = getEntities();
size_t count = getComponentCount();
size_t aliveInARow = 0;
default_random_engine& rng = mRng;
UTILS_NOUNROLL
while (count && aliveInARow < ratio) {
// note: using the modulo favorizes lower number
size_t i = rng() % count;
if (UTILS_LIKELY(em.isAlive(entities[i]))) {
++aliveInARow;
continue;
}
aliveInARow = 0;
count--;
removeComponent(entities[i]);
}
}
protected:
SoA mData;
private:
// maps an entity to an instance index
tsl::robin_map<Entity, Instance, Entity::Hasher> mInstanceMap;
default_random_engine mRng;
};
// Keep these outside of the class because CLion has trouble parsing them
template<typename ... Elements>
typename SingleInstanceComponentManager<Elements ...>::Instance
SingleInstanceComponentManager<Elements ...>::addComponent(Entity e) {
Instance ci = 0;
if (!e.isNull()) {
if (!hasComponent(e)) {
// this is like a push_back(e);
mData.push_back(Structure{}).template back<ENTITY_INDEX>() = e;
// index 0 is used when the component doesn't exist
ci = Instance(mData.size() - 1);
mInstanceMap[e] = ci;
} else {
// if the entity already has this component, just return its instance
ci = mInstanceMap[e];
}
}
assert(ci != 0);
return ci;
}
// Keep these outside of the class because CLion has trouble parsing them
template <typename ... Elements>
typename SingleInstanceComponentManager<Elements ...>::Instance
SingleInstanceComponentManager<Elements ... >::removeComponent(Entity e) {
auto& map = mInstanceMap;
auto pos = map.find(e);
if (UTILS_LIKELY(pos != map.end())) {
size_t index = pos->second;
assert(index != 0);
size_t last = mData.size() - 1;
if (last != index) {
// move the last item to where we removed this component, as to keep
// the array tightly packed.
mData.forEach([index, last](auto* p) {
p[index] = std::move(p[last]);
});
Entity lastEntity = mData.template elementAt<ENTITY_INDEX>(index);
map[lastEntity] = index;
}
mData.pop_back();
map.erase(pos);
return last;
}
return 0;
}
} // namespace filament
#endif // TNT_UTILS_SINGLEINSTANCECOMPONENTMANAGER_H

148
macos/include/utils/Slice.h Normal file
View File

@@ -0,0 +1,148 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_SLICE_H
#define TNT_UTILS_SLICE_H
#include <utils/compiler.h>
#include <utility>
#include <assert.h>
#include <stddef.h>
namespace utils {
/*
* A fixed-size slice of a container
*/
template <typename T>
class Slice {
public:
using iterator = T*;
using const_iterator = T const*;
using value_type = T;
using reference = T&;
using const_reference = T const&;
using pointer = T*;
using const_pointer = T const*;
using size_type = size_t;
Slice() noexcept = default;
Slice(const_iterator begin, const_iterator end) noexcept
: mBegin(const_cast<iterator>(begin)),
mEnd(const_cast<iterator>(end)) {
}
Slice(const_pointer begin, size_type count) noexcept
: mBegin(const_cast<iterator>(begin)),
mEnd(mBegin + count) {
}
Slice(Slice const& rhs) noexcept = default;
Slice(Slice&& rhs) noexcept = default;
Slice& operator=(Slice const& rhs) noexcept = default;
Slice& operator=(Slice&& rhs) noexcept = default;
void set(pointer begin, size_type count) UTILS_RESTRICT noexcept {
mBegin = begin;
mEnd = begin + count;
}
void set(iterator begin, iterator end) UTILS_RESTRICT noexcept {
mBegin = begin;
mEnd = end;
}
void swap(Slice& rhs) UTILS_RESTRICT noexcept {
std::swap(mBegin, rhs.mBegin);
std::swap(mEnd, rhs.mEnd);
}
void clear() UTILS_RESTRICT noexcept {
mBegin = nullptr;
mEnd = nullptr;
}
// size
size_t size() const UTILS_RESTRICT noexcept { return mEnd - mBegin; }
size_t sizeInBytes() const UTILS_RESTRICT noexcept { return size() * sizeof(T); }
bool empty() const UTILS_RESTRICT noexcept { return size() == 0; }
// iterators
iterator begin() UTILS_RESTRICT noexcept { return mBegin; }
const_iterator begin() const UTILS_RESTRICT noexcept { return mBegin; }
const_iterator cbegin() const UTILS_RESTRICT noexcept { return this->begin(); }
iterator end() UTILS_RESTRICT noexcept { return mEnd; }
const_iterator end() const UTILS_RESTRICT noexcept { return mEnd; }
const_iterator cend() const UTILS_RESTRICT noexcept { return this->end(); }
// data access
reference operator[](size_t n) UTILS_RESTRICT noexcept {
assert(n < size());
return mBegin[n];
}
const_reference operator[](size_t n) const UTILS_RESTRICT noexcept {
assert(n < size());
return mBegin[n];
}
reference at(size_t n) UTILS_RESTRICT noexcept {
return operator[](n);
}
const_reference at(size_t n) const UTILS_RESTRICT noexcept {
return operator[](n);
}
reference front() UTILS_RESTRICT noexcept {
assert(!empty());
return *mBegin;
}
const_reference front() const UTILS_RESTRICT noexcept {
assert(!empty());
return *mBegin;
}
reference back() UTILS_RESTRICT noexcept {
assert(!empty());
return *(this->end() - 1);
}
const_reference back() const UTILS_RESTRICT noexcept {
assert(!empty());
return *(this->end() - 1);
}
pointer data() UTILS_RESTRICT noexcept {
return this->begin();
}
const_pointer data() const UTILS_RESTRICT noexcept {
return this->begin();
}
protected:
iterator mBegin = nullptr;
iterator mEnd = nullptr;
};
} // namespace utils
#endif // TNT_UTILS_SLICE_H

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_SPINLOCK_H
#define TNT_UTILS_SPINLOCK_H
#include <utils/compiler.h>
#include <utils/Mutex.h>
#include <atomic>
#include <type_traits>
#include <assert.h>
#include <stddef.h>
namespace utils {
namespace details {
class SpinLock {
std::atomic_flag mLock = ATOMIC_FLAG_INIT;
public:
void lock() noexcept {
UTILS_PREFETCHW(&mLock);
#ifdef __ARM_ACLE
// we signal an event on this CPU, so that the first yield() will be a no-op,
// and falls through the test_and_set(). This is more efficient than a while { }
// construct.
UTILS_SIGNAL_EVENT();
do {
yield();
} while (mLock.test_and_set(std::memory_order_acquire));
#else
goto start;
do {
yield();
start: ;
} while (mLock.test_and_set(std::memory_order_acquire));
#endif
}
void unlock() noexcept {
mLock.clear(std::memory_order_release);
#ifdef __ARM_ARCH_7A__
// on ARMv7a SEL is needed
UTILS_SIGNAL_EVENT();
// as well as a memory barrier is needed
__dsb(0xA); // ISHST = 0xA (b1010)
#else
// on ARMv8 we could avoid the call to SE, but we'd need to write the
// test_and_set() above by hand, so the WFE only happens without a STRX first.
UTILS_BROADCAST_EVENT();
#endif
}
private:
inline void yield() noexcept {
// on x86 call pause instruction, on ARM call WFE
UTILS_WAIT_FOR_EVENT();
}
};
} // namespace details
#if UTILS_HAS_SANITIZE_THREAD
// Active spins with atomics slow down execution too much under ThreadSanitizer.
using SpinLock = Mutex;
#elif defined(__ARM_ARCH_7A__)
// We've had problems with "wfe" on some ARM-V7 devices, causing spurious SIGILL
using SpinLock = Mutex;
#else
using SpinLock = details::SpinLock;
#endif
} // namespace utils
#endif // TNT_UTILS_SPINLOCK_H

View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_STOPWATCH_H
#define TNT_UTILS_STOPWATCH_H
#include <utils/Log.h>
#include <chrono>
#include <limits>
#include <stdint.h>
namespace utils {
/*
* A very basic Stopwatch class
*/
template<typename Clock = std::chrono::steady_clock>
class Stopwatch {
public:
using duration = typename Clock::duration;
using time_point = typename Clock::time_point;
private:
time_point mStart;
duration mMinLap = std::numeric_limits<duration>::max();
duration mMaxLap{};
std::chrono::duration<double, std::nano> mAvgLap{};
size_t mCount = 0;
const char* mName = nullptr;
public:
// Create a Stopwatch with a name and clock
explicit Stopwatch(const char* name) noexcept: mName(name) {}
// Logs min/avg/max lap time
~Stopwatch() noexcept;
// start the stopwatch
inline void start() noexcept {
mStart = Clock::now();
}
// stop the stopwatch
inline void stop() noexcept {
auto d = Clock::now() - mStart;
mMinLap = std::min(mMinLap, d);
mMaxLap = std::max(mMaxLap, d);
mAvgLap = (mAvgLap * mCount + d) / (mCount + 1);
mCount++;
}
// get the minimum lap time recorded
duration getMinLapTime() const noexcept { return mMinLap; }
// get the maximum lap time recorded
duration getMaxLapTime() const noexcept { return mMaxLap; }
// get the average lap time
duration getAverageLapTime() const noexcept { return mAvgLap; }
};
template<typename Clock>
Stopwatch<Clock>::~Stopwatch() noexcept {
slog.d << "Stopwatch \"" << mName << "\" : ["
<< mMinLap.count() << ", "
<< std::chrono::duration_cast<duration>(mAvgLap).count() << ", "
<< mMaxLap.count() << "] ns" << io::endl;
}
/*
* AutoStopwatch can be used to start and stop a Stopwatch automatically
* when entering and exiting a scope.
*/
template<typename Stopwatch>
class AutoStopwatch {
Stopwatch& stopwatch;
public:
inline explicit AutoStopwatch(Stopwatch& stopwatch) noexcept: stopwatch(stopwatch) {
stopwatch.start();
}
inline ~AutoStopwatch() noexcept { stopwatch.stop(); }
};
} // namespace utils
#endif // TNT_UTILS_STOPWATCH_H

View File

@@ -0,0 +1,691 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_STRUCTUREOFARRAYS_H
#define TNT_UTILS_STRUCTUREOFARRAYS_H
#include <utils/Allocator.h>
#include <utils/compiler.h>
#include <utils/Slice.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <array> // note: this is safe, see how std::array is used below (inline / private)
#include <cstddef>
#include <iterator> // for std::random_access_iterator_tag
#include <tuple>
#include <utility>
namespace utils {
template <typename Allocator, typename ... Elements>
class StructureOfArraysBase {
// number of elements
static constexpr const size_t kArrayCount = sizeof...(Elements);
public:
using SoA = StructureOfArraysBase<Allocator, Elements...>;
using Structure = std::tuple<Elements...>;
// Type of the Nth array
template<size_t N>
using TypeAt = typename std::tuple_element_t<N, Structure>;
// Number of arrays
static constexpr size_t getArrayCount() noexcept { return kArrayCount; }
// Size needed to store "size" array elements
static size_t getNeededSize(size_t size) noexcept {
return getOffset(kArrayCount - 1, size) + sizeof(TypeAt<kArrayCount - 1>) * size;
}
// --------------------------------------------------------------------------------------------
class IteratorValue;
template<typename T> class Iterator;
using iterator = Iterator<StructureOfArraysBase*>;
using const_iterator = Iterator<StructureOfArraysBase const*>;
using size_type = size_t;
using difference_type = ptrdiff_t;
/*
* An object that represents a reference to the type dereferenced by iterator.
* In other words, it's the return type of iterator::operator*(), and since it
* cannot be a C++ reference (&), it's an object that acts like it.
*/
class IteratorValueRef {
friend class IteratorValue;
friend iterator;
friend const_iterator;
StructureOfArraysBase* const UTILS_RESTRICT soa;
size_t const index;
IteratorValueRef(StructureOfArraysBase* soa, size_t index) : soa(soa), index(index) { }
// assigns a value_type to a reference (i.e. assigns to what's pointed to by the reference)
template<size_t ... Is>
IteratorValueRef& assign(IteratorValue const& rhs, std::index_sequence<Is...>);
// assigns a value_type to a reference (i.e. assigns to what's pointed to by the reference)
template<size_t ... Is>
IteratorValueRef& assign(IteratorValue&& rhs, std::index_sequence<Is...>) noexcept;
// objects pointed to by reference can be swapped, so provide the special swap() function.
friend void swap(IteratorValueRef lhs, IteratorValueRef rhs) {
lhs.soa->swap(lhs.index, rhs.index);
}
public:
// references can be created by copy-assignment only
IteratorValueRef(IteratorValueRef const& rhs) noexcept : soa(rhs.soa), index(rhs.index) { }
// copy the content of a reference to the content of this one
IteratorValueRef& operator=(IteratorValueRef const& rhs);
// move the content of a reference to the content of this one
IteratorValueRef& operator=(IteratorValueRef&& rhs) noexcept;
// copy a value_type to the content of this reference
IteratorValueRef& operator=(IteratorValue const& rhs) {
return assign(rhs, std::make_index_sequence<kArrayCount>());
}
// move a value_type to the content of this reference
IteratorValueRef& operator=(IteratorValue&& rhs) noexcept {
return assign(rhs, std::make_index_sequence<kArrayCount>());
}
// access the elements of this reference (i.e. the "fields" of the structure)
template<size_t I> TypeAt<I> const& get() const { return soa->elementAt<I>(index); }
template<size_t I> TypeAt<I>& get() { return soa->elementAt<I>(index); }
};
/*
* The value_type of iterator. This is basically the "structure" of the SoA.
* Internally we're using a tuple<> to store the data.
* This object is not trivial to construct, as it copies an entry of the SoA.
*/
class IteratorValue {
friend class IteratorValueRef;
friend iterator;
friend const_iterator;
using Type = std::tuple<typename std::decay<Elements>::type...>;
Type elements;
template<size_t ... Is>
static Type init(IteratorValueRef const& rhs, std::index_sequence<Is...>) {
return Type{ rhs.soa->template elementAt<Is>(rhs.index)... };
}
template<size_t ... Is>
static Type init(IteratorValueRef&& rhs, std::index_sequence<Is...>) noexcept {
return Type{ std::move(rhs.soa->template elementAt<Is>(rhs.index))... };
}
public:
IteratorValue(IteratorValue const& rhs) = default;
IteratorValue(IteratorValue&& rhs) noexcept = default;
IteratorValue& operator=(IteratorValue const& rhs) = default;
IteratorValue& operator=(IteratorValue&& rhs) noexcept = default;
// initialize and assign from a StructureRef
IteratorValue(IteratorValueRef const& rhs)
: elements(init(rhs, std::make_index_sequence<kArrayCount>())) {}
IteratorValue(IteratorValueRef&& rhs) noexcept
: elements(init(rhs, std::make_index_sequence<kArrayCount>())) {}
IteratorValue& operator=(IteratorValueRef const& rhs) { return operator=(IteratorValue(rhs)); }
IteratorValue& operator=(IteratorValueRef&& rhs) noexcept { return operator=(IteratorValue(rhs)); }
// access the elements of this value_Type (i.e. the "fields" of the structure)
template<size_t I> TypeAt<I> const& get() const { return std::get<I>(elements); }
template<size_t I> TypeAt<I>& get() { return std::get<I>(elements); }
};
/*
* An iterator to the SoA. This is only intended to be used with STL's algorithm, e.g.: sort().
* Normally, SoA is not iterated globally, but rather an array at a time.
* Iterating itself is not too costly, as well as dereferencing by reference. However,
* dereferencing by value is.
*/
template<typename CVQualifiedSOAPointer>
class Iterator {
friend class StructureOfArraysBase;
CVQualifiedSOAPointer soa; // don't use restrict, can have aliases if multiple iterators are created
size_t index;
Iterator(CVQualifiedSOAPointer soa, size_t index) : soa(soa), index(index) {}
public:
using value_type = IteratorValue;
using reference = IteratorValueRef;
using pointer = IteratorValueRef*; // FIXME: this should be a StructurePtr type
using difference_type = ptrdiff_t;
using iterator_category = std::random_access_iterator_tag;
Iterator(Iterator const& rhs) noexcept = default;
Iterator& operator=(Iterator const& rhs) = default;
reference operator*() const { return { soa, index }; }
reference operator*() { return { soa, index }; }
reference operator[](size_t n) { return *(*this + n); }
template<size_t I> TypeAt<I> const& get() const { return soa->template elementAt<I>(index); }
template<size_t I> TypeAt<I>& get() { return soa->template elementAt<I>(index); }
Iterator& operator++() { ++index; return *this; }
Iterator& operator--() { --index; return *this; }
Iterator& operator+=(size_t n) { index += n; return *this; }
Iterator& operator-=(size_t n) { index -= n; return *this; }
Iterator operator+(size_t n) const { return { soa, index + n }; }
Iterator operator-(size_t n) const { return { soa, index - n }; }
difference_type operator-(Iterator const& rhs) const { return index - rhs.index; }
bool operator==(Iterator const& rhs) const { return (index == rhs.index); }
bool operator!=(Iterator const& rhs) const { return (index != rhs.index); }
bool operator>=(Iterator const& rhs) const { return (index >= rhs.index); }
bool operator> (Iterator const& rhs) const { return (index > rhs.index); }
bool operator<=(Iterator const& rhs) const { return (index <= rhs.index); }
bool operator< (Iterator const& rhs) const { return (index < rhs.index); }
// Postfix operator needed by Microsoft STL.
const Iterator operator++(int) { Iterator it(*this); index++; return it; }
const Iterator operator--(int) { Iterator it(*this); index--; return it; }
};
iterator begin() noexcept { return { this, 0u }; }
iterator end() noexcept { return { this, mSize }; }
const_iterator begin() const noexcept { return { this, 0u }; }
const_iterator end() const noexcept { return { this, mSize }; }
// --------------------------------------------------------------------------------------------
StructureOfArraysBase() = default;
explicit StructureOfArraysBase(size_t capacity) {
setCapacity(capacity);
}
// not copyable for now
StructureOfArraysBase(StructureOfArraysBase const& rhs) = delete;
StructureOfArraysBase& operator=(StructureOfArraysBase const& rhs) = delete;
// movability is trivial, so support it
StructureOfArraysBase(StructureOfArraysBase&& rhs) noexcept {
using std::swap;
swap(mCapacity, rhs.mCapacity);
swap(mSize, rhs.mSize);
swap(mArrays, rhs.mArrays);
swap(mAllocator, rhs.mAllocator);
}
StructureOfArraysBase& operator=(StructureOfArraysBase&& rhs) noexcept {
if (this != &rhs) {
using std::swap;
swap(mCapacity, rhs.mCapacity);
swap(mSize, rhs.mSize);
swap(mArrays, rhs.mArrays);
swap(mAllocator, rhs.mAllocator);
}
return *this;
}
~StructureOfArraysBase() {
destroy_each(0, mSize);
mAllocator.free(std::get<0>(mArrays));
}
// --------------------------------------------------------------------------------------------
// return the size the array
size_t size() const noexcept {
return mSize;
}
// return the capacity of the array
size_t capacity() const noexcept {
return mCapacity;
}
// set the capacity of the array. the capacity cannot be smaller than the current size,
// the call is a no-op in that case.
UTILS_NOINLINE
void setCapacity(size_t capacity) {
// allocate enough space for "capacity" elements of each array
// capacity cannot change when optional storage is specified
if (capacity >= mSize) {
// TODO: not entirely sure if "max" of all alignments is always correct
constexpr size_t align = std::max({ std::max(alignof(std::max_align_t), alignof(Elements))... });
const size_t sizeNeeded = getNeededSize(capacity);
void* buffer = mAllocator.alloc(sizeNeeded, align);
auto const oldBuffer = std::get<0>(mArrays);
// move all the items (one array at a time) from the old allocation to the new
// this also update the array pointers
move_each(buffer, capacity);
// free the old buffer
mAllocator.free(oldBuffer);
// and make sure to update the capacity
mCapacity = capacity;
}
}
void ensureCapacity(size_t needed) {
if (UTILS_UNLIKELY(needed > mCapacity)) {
// not enough space, increase the capacity
const size_t capacity = (needed * 3 + 1) / 2;
setCapacity(capacity);
}
}
// grow or shrink the array to the given size. When growing, new elements are constructed
// with their default constructor. when shrinking, discarded elements are destroyed.
// If the arrays don't have enough capacity, the capacity is increased accordingly
// (the capacity is set to 3/2 of the asked size).
UTILS_NOINLINE
void resize(size_t needed) {
ensureCapacity(needed);
resizeNoCheck(needed);
if (needed <= mCapacity) {
// TODO: see if we should shrink the arrays
}
}
void clear() noexcept {
resizeNoCheck(0);
}
inline void swap(size_t i, size_t j) noexcept {
forEach([i, j](auto p) {
using std::swap;
swap(p[i], p[j]);
});
}
// remove and destroy the last element of each array
inline void pop_back() noexcept {
if (mSize) {
destroy_each(mSize - 1, mSize);
mSize--;
}
}
// create an element at the end of each array
StructureOfArraysBase& push_back() noexcept {
resize(mSize + 1);
return *this;
}
StructureOfArraysBase& push_back(Structure&& args) noexcept {
ensureCapacity(mSize + 1);
return push_back_unsafe(std::forward<Structure>(args));
}
StructureOfArraysBase& push_back(Elements const& ... args) noexcept {
ensureCapacity(mSize + 1);
return push_back_unsafe(args...);
}
StructureOfArraysBase& push_back(Elements&& ... args) noexcept {
ensureCapacity(mSize + 1);
return push_back_unsafe(std::forward<Elements>(args)...);
}
// in C++20 we could use a lambda with explicit template parameter instead
struct PushBackUnsafeClosure {
size_t last;
std::tuple<Elements...> args;
inline explicit PushBackUnsafeClosure(size_t last, Structure&& args)
: last(last), args(std::forward<Structure>(args)) {}
template<size_t I>
inline void operator()(TypeAt<I>* p) {
new(p + last) TypeAt<I>{ std::get<I>(args) };
}
};
StructureOfArraysBase& push_back_unsafe(Structure&& args) noexcept {
for_each_index(mArrays,
PushBackUnsafeClosure{ mSize++, std::forward<Structure>(args) });
return *this;
}
StructureOfArraysBase& push_back_unsafe(Elements const& ... args) noexcept {
for_each_index(mArrays,
PushBackUnsafeClosure{ mSize++, { args... } });
return *this;
}
StructureOfArraysBase& push_back_unsafe(Elements&& ... args) noexcept {
for_each_index(mArrays,
PushBackUnsafeClosure{ mSize++, { std::forward<Elements>(args)... }});
return *this;
}
template<typename F, typename ... ARGS>
void forEach(F&& f, ARGS&& ... args) {
for_each(mArrays, [&](size_t, auto* p) {
f(p, std::forward<ARGS>(args)...);
});
}
// return a pointer to the first element of the ElementIndex]th array
template<size_t ElementIndex>
TypeAt<ElementIndex>* data() noexcept {
return std::get<ElementIndex>(mArrays);
}
template<size_t ElementIndex>
TypeAt<ElementIndex> const* data() const noexcept {
return std::get<ElementIndex>(mArrays);
}
template<size_t ElementIndex>
TypeAt<ElementIndex>* begin() noexcept {
return std::get<ElementIndex>(mArrays);
}
template<size_t ElementIndex>
TypeAt<ElementIndex> const* begin() const noexcept {
return std::get<ElementIndex>(mArrays);
}
template<size_t ElementIndex>
TypeAt<ElementIndex>* end() noexcept {
return std::get<ElementIndex>(mArrays) + size();
}
template<size_t ElementIndex>
TypeAt<ElementIndex> const* end() const noexcept {
return std::get<ElementIndex>(mArrays) + size();
}
template<size_t ElementIndex>
Slice<TypeAt<ElementIndex>> slice() noexcept {
return { begin<ElementIndex>(), end<ElementIndex>() };
}
template<size_t ElementIndex>
Slice<const TypeAt<ElementIndex>> slice() const noexcept {
return { begin<ElementIndex>(), end<ElementIndex>() };
}
// return a reference to the index'th element of the ElementIndex'th array
template<size_t ElementIndex>
TypeAt<ElementIndex>& elementAt(size_t index) noexcept {
return data<ElementIndex>()[index];
}
template<size_t ElementIndex>
TypeAt<ElementIndex> const& elementAt(size_t index) const noexcept {
return data<ElementIndex>()[index];
}
// return a reference to the last element of the ElementIndex'th array
template<size_t ElementIndex>
TypeAt<ElementIndex>& back() noexcept {
return data<ElementIndex>()[size() - 1];
}
template<size_t ElementIndex>
TypeAt<ElementIndex> const& back() const noexcept {
return data<ElementIndex>()[size() - 1];
}
template <size_t E, typename IndexType = uint32_t>
struct Field {
SoA& soa;
IndexType i;
using Type = typename SoA::template TypeAt<E>;
UTILS_ALWAYS_INLINE Field& operator = (Field&& rhs) noexcept {
soa.elementAt<E>(i) = soa.elementAt<E>(rhs.i);
return *this;
}
// auto-conversion to the field's type
UTILS_ALWAYS_INLINE operator Type&() noexcept {
return soa.elementAt<E>(i);
}
UTILS_ALWAYS_INLINE operator Type const&() const noexcept {
return soa.elementAt<E>(i);
}
// dereferencing the selected field
UTILS_ALWAYS_INLINE Type& operator ->() noexcept {
return soa.elementAt<E>(i);
}
UTILS_ALWAYS_INLINE Type const& operator ->() const noexcept {
return soa.elementAt<E>(i);
}
// address-of the selected field
UTILS_ALWAYS_INLINE Type* operator &() noexcept {
return &soa.elementAt<E>(i);
}
UTILS_ALWAYS_INLINE Type const* operator &() const noexcept {
return &soa.elementAt<E>(i);
}
// assignment to the field
UTILS_ALWAYS_INLINE Type const& operator = (Type const& other) noexcept {
return (soa.elementAt<E>(i) = other);
}
UTILS_ALWAYS_INLINE Type const& operator = (Type&& other) noexcept {
return (soa.elementAt<E>(i) = other);
}
// comparisons
UTILS_ALWAYS_INLINE bool operator==(Type const& other) const {
return (soa.elementAt<E>(i) == other);
}
UTILS_ALWAYS_INLINE bool operator!=(Type const& other) const {
return (soa.elementAt<E>(i) != other);
}
// calling the field
template <typename ... ARGS>
UTILS_ALWAYS_INLINE decltype(auto) operator()(ARGS&& ... args) noexcept {
return soa.elementAt<E>(i)(std::forward<ARGS>(args)...);
}
template <typename ... ARGS>
UTILS_ALWAYS_INLINE decltype(auto) operator()(ARGS&& ... args) const noexcept {
return soa.elementAt<E>(i)(std::forward<ARGS>(args)...);
}
};
private:
template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
for_each(std::tuple<Tp...>&, FuncT) {}
template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
for_each(std::tuple<Tp...>& t, FuncT f) {
f(I, std::get<I>(t));
for_each<I + 1, FuncT, Tp...>(t, f);
}
template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
for_each_index(std::tuple<Tp...>&, FuncT) {}
template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
for_each_index(std::tuple<Tp...>& t, FuncT f) {
f.template operator()<I>(std::get<I>(t));
for_each_index<I + 1, FuncT, Tp...>(t, f);
}
inline void resizeNoCheck(size_t needed) noexcept {
assert(mCapacity >= needed);
if (needed < mSize) {
// we shrink the arrays
destroy_each(needed, mSize);
} else if (needed > mSize) {
// we grow the arrays
construct_each(mSize, needed);
}
// record the new size of the arrays
mSize = needed;
}
// this calculates the offset adjusted for all data alignment of a given array
static inline size_t getOffset(size_t index, size_t capacity) noexcept {
auto offsets = getOffsets(capacity);
return offsets[index];
}
static inline std::array<size_t, kArrayCount> getOffsets(size_t capacity) noexcept {
// compute the required size of each array
const size_t sizes[] = { (sizeof(Elements) * capacity)... };
// we align each array to at least the same alignment guaranteed by malloc
constexpr size_t const alignments[] = { std::max(alignof(std::max_align_t), alignof(Elements))... };
// hopefully most of this gets unrolled and inlined
std::array<size_t, kArrayCount> offsets;
offsets[0] = 0;
UTILS_UNROLL
for (size_t i = 1; i < kArrayCount; i++) {
size_t unalignment = (offsets[i - 1] + sizes[i - 1]) % alignments[i];
size_t alignment = unalignment ? (alignments[i] - unalignment) : 0;
offsets[i] = offsets[i - 1] + (sizes[i - 1] + alignment);
assert_invariant(offsets[i] % alignments[i] == 0);
}
return offsets;
}
void construct_each(size_t from, size_t to) noexcept {
forEach([from, to](auto p) {
using T = typename std::decay<decltype(*p)>::type;
// note: scalar types like int/float get initialized to zero
if constexpr (!std::is_trivially_default_constructible_v<T>) {
for (size_t i = from; i < to; i++) {
new(p + i) T();
}
}
});
}
void destroy_each(size_t from, size_t to) noexcept {
forEach([from, to](auto p) {
using T = typename std::decay<decltype(*p)>::type;
if constexpr (!std::is_trivially_destructible_v<T>) {
for (size_t i = from; i < to; i++) {
p[i].~T();
}
}
});
}
void move_each(void* buffer, size_t capacity) noexcept {
auto offsets = getOffsets(capacity);
size_t index = 0;
if (mSize) {
auto size = mSize; // placate a compiler warning
forEach([buffer, &index, &offsets, size](auto p) {
using T = typename std::decay<decltype(*p)>::type;
T* UTILS_RESTRICT b = static_cast<T*>(buffer);
// go through each element and move them from the old array to the new
// then destroy them from the old array
T* UTILS_RESTRICT const arrayPointer =
reinterpret_cast<T*>(uintptr_t(b) + offsets[index]);
// for trivial cases, just call memcpy()
if constexpr (std::is_trivially_copyable_v<T> &&
std::is_trivially_destructible_v<T>) {
memcpy(arrayPointer, p, size * sizeof(T));
} else {
for (size_t i = 0; i < size; i++) {
// we move an element by using the in-place move-constructor
new(arrayPointer + i) T(std::move(p[i]));
if constexpr (!std::is_trivially_destructible_v<T>) {
// and delete them by calling the destructor directly
p[i].~T();
}
}
}
index++;
});
}
// update the pointers
for_each(mArrays, [buffer, &offsets](size_t i, auto&& p) {
using Type = std::remove_reference_t<decltype(p)>;
p = Type((char*)buffer + offsets[i]);
});
}
// capacity in array elements
size_t mCapacity = 0;
// size in array elements
size_t mSize = 0;
// N pointers to each arrays
std::tuple<std::add_pointer_t<Elements>...> mArrays{};
Allocator mAllocator;
};
template<typename Allocator, typename... Elements>
inline
typename StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef&
StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef::operator=(
StructureOfArraysBase::IteratorValueRef const& rhs) {
return operator=(IteratorValue(rhs));
}
template<typename Allocator, typename... Elements>
inline
typename StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef&
StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef::operator=(
StructureOfArraysBase::IteratorValueRef&& rhs) noexcept {
return operator=(IteratorValue(rhs));
}
template<typename Allocator, typename... Elements>
template<size_t... Is>
inline
typename StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef&
StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef::assign(
StructureOfArraysBase::IteratorValue const& rhs, std::index_sequence<Is...>) {
// implements IteratorValueRef& IteratorValueRef::operator=(IteratorValue const& rhs)
auto UTILS_UNUSED l = { (soa->elementAt<Is>(index) = std::get<Is>(rhs.elements), 0)... };
return *this;
}
template<typename Allocator, typename... Elements>
template<size_t... Is>
inline
typename StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef&
StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef::assign(
StructureOfArraysBase::IteratorValue&& rhs, std::index_sequence<Is...>) noexcept {
// implements IteratorValueRef& IteratorValueRef::operator=(IteratorValue&& rhs) noexcept
auto UTILS_UNUSED l = {
(soa->elementAt<Is>(index) = std::move(std::get<Is>(rhs.elements)), 0)... };
return *this;
}
template <typename ... Elements>
using StructureOfArrays = StructureOfArraysBase<HeapArena<>, Elements ...>;
} // namespace utils
#endif // TNT_UTILS_STRUCTUREOFARRAYS_H

View File

@@ -0,0 +1,278 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_SYSTRACE_H
#define TNT_UTILS_SYSTRACE_H
#define SYSTRACE_TAG_NEVER (0)
#define SYSTRACE_TAG_ALWAYS (1<<0)
#define SYSTRACE_TAG_FILAMENT (1<<1) // don't change, used in makefiles
#define SYSTRACE_TAG_JOBSYSTEM (1<<2)
#if defined(__ANDROID__)
#include <atomic>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <utils/compiler.h>
/*
* The SYSTRACE_ macros use SYSTRACE_TAG as a the TAG, which should be defined
* before this file is included. If not, the SYSTRACE_TAG_ALWAYS tag will be used.
*/
#ifndef SYSTRACE_TAG
#define SYSTRACE_TAG (SYSTRACE_TAG_ALWAYS)
#endif
// enable tracing
#define SYSTRACE_ENABLE() ::utils::details::Systrace::enable(SYSTRACE_TAG)
// disable tracing
#define SYSTRACE_DISABLE() ::utils::details::Systrace::disable(SYSTRACE_TAG)
/**
* Creates a Systrace context in the current scope. needed for calling all other systrace
* commands below.
*/
#define SYSTRACE_CONTEXT() ::utils::details::Systrace ___tracer(SYSTRACE_TAG)
// SYSTRACE_NAME traces the beginning and end of the current scope. To trace
// the correct start and end times this macro should be declared first in the
// scope body.
// It also automatically creates a Systrace context
#define SYSTRACE_NAME(name) ::utils::details::ScopedTrace ___tracer(SYSTRACE_TAG, name)
// SYSTRACE_CALL is an SYSTRACE_NAME that uses the current function name.
#define SYSTRACE_CALL() SYSTRACE_NAME(__FUNCTION__)
#define SYSTRACE_NAME_BEGIN(name) \
___tracer.traceBegin(SYSTRACE_TAG, name)
#define SYSTRACE_NAME_END() \
___tracer.traceEnd(SYSTRACE_TAG)
/**
* Trace the beginning of an asynchronous event. Unlike ATRACE_BEGIN/ATRACE_END
* contexts, asynchronous events do not need to be nested. The name describes
* the event, and the cookie provides a unique identifier for distinguishing
* simultaneous events. The name and cookie used to begin an event must be
* used to end it.
*/
#define SYSTRACE_ASYNC_BEGIN(name, cookie) \
___tracer.asyncBegin(SYSTRACE_TAG, name, cookie)
/**
* Trace the end of an asynchronous event.
* This should have a corresponding SYSTRACE_ASYNC_BEGIN.
*/
#define SYSTRACE_ASYNC_END(name, cookie) \
___tracer.asyncEnd(SYSTRACE_TAG, name, cookie)
/**
* Traces an integer counter value. name is used to identify the counter.
* This can be used to track how a value changes over time.
*/
#define SYSTRACE_VALUE32(name, val) \
___tracer.value(SYSTRACE_TAG, name, int32_t(val))
#define SYSTRACE_VALUE64(name, val) \
___tracer.value(SYSTRACE_TAG, name, int64_t(val))
// ------------------------------------------------------------------------------------------------
// No user serviceable code below...
// ------------------------------------------------------------------------------------------------
namespace utils {
namespace details {
class Systrace {
public:
enum tags {
NEVER = SYSTRACE_TAG_NEVER,
ALWAYS = SYSTRACE_TAG_ALWAYS,
FILAMENT = SYSTRACE_TAG_FILAMENT,
JOBSYSTEM = SYSTRACE_TAG_JOBSYSTEM
// we could define more TAGS here, as we need them.
};
Systrace(uint32_t tag) noexcept {
if (tag) init(tag);
}
static void enable(uint32_t tags) noexcept;
static void disable(uint32_t tags) noexcept;
inline void traceBegin(uint32_t tag, const char* name) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
beginSection(this, name);
}
}
inline void traceEnd(uint32_t tag) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
endSection(this);
}
}
inline void asyncBegin(uint32_t tag, const char* name, int32_t cookie) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
beginAsyncSection(this, name, cookie);
}
}
inline void asyncEnd(uint32_t tag, const char* name, int32_t cookie) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
endAsyncSection(this, name, cookie);
}
}
inline void value(uint32_t tag, const char* name, int32_t value) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
setCounter(this, name, value);
}
}
inline void value(uint32_t tag, const char* name, int64_t value) noexcept {
if (tag && UTILS_UNLIKELY(mIsTracingEnabled)) {
setCounter(this, name, value);
}
}
private:
friend class ScopedTrace;
// whether tracing is supported at all by the platform
using ATrace_isEnabled_t = bool (*)(void);
using ATrace_beginSection_t = void (*)(const char* sectionName);
using ATrace_endSection_t = void (*)(void);
using ATrace_beginAsyncSection_t = void (*)(const char* sectionName, int32_t cookie);
using ATrace_endAsyncSection_t = void (*)(const char* sectionName, int32_t cookie);
using ATrace_setCounter_t = void (*)(const char* counterName, int64_t counterValue);
struct GlobalState {
bool isTracingAvailable;
std::atomic<uint32_t> isTracingEnabled;
int markerFd;
ATrace_isEnabled_t ATrace_isEnabled;
ATrace_beginSection_t ATrace_beginSection;
ATrace_endSection_t ATrace_endSection;
ATrace_beginAsyncSection_t ATrace_beginAsyncSection;
ATrace_endAsyncSection_t ATrace_endAsyncSection;
ATrace_setCounter_t ATrace_setCounter;
void (*beginSection)(Systrace* that, const char* name);
void (*endSection)(Systrace* that);
void (*beginAsyncSection)(Systrace* that, const char* name, int32_t cookie);
void (*endAsyncSection)(Systrace* that, const char* name, int32_t cookie);
void (*setCounter)(Systrace* that, const char* name, int64_t value);
};
static GlobalState sGlobalState;
// per-instance versions for better performance
ATrace_isEnabled_t ATrace_isEnabled;
ATrace_beginSection_t ATrace_beginSection;
ATrace_endSection_t ATrace_endSection;
ATrace_beginAsyncSection_t ATrace_beginAsyncSection;
ATrace_endAsyncSection_t ATrace_endAsyncSection;
ATrace_setCounter_t ATrace_setCounter;
void (*beginSection)(Systrace* that, const char* name);
void (*endSection)(Systrace* that);
void (*beginAsyncSection)(Systrace* that, const char* name, int32_t cookie);
void (*endAsyncSection)(Systrace* that, const char* name, int32_t cookie);
void (*setCounter)(Systrace* that, const char* name, int64_t value);
void init(uint32_t tag) noexcept;
// cached values for faster access, no need to be initialized
bool mIsTracingEnabled;
int mMarkerFd = -1;
pid_t mPid;
static void setup() noexcept;
static void init_once() noexcept;
static bool isTracingEnabled(uint32_t tag) noexcept;
static void begin_body(int fd, int pid, const char* name) noexcept;
static void end_body(int fd, int pid) noexcept;
static void async_begin_body(int fd, int pid, const char* name, int32_t cookie) noexcept;
static void async_end_body(int fd, int pid, const char* name, int32_t cookie) noexcept;
static void int64_body(int fd, int pid, const char* name, int64_t value) noexcept;
};
// ------------------------------------------------------------------------------------------------
class ScopedTrace {
public:
// we don't inline this because it's relatively heavy due to a global check
ScopedTrace(uint32_t tag, const char* name) noexcept : mTrace(tag), mTag(tag) {
mTrace.traceBegin(tag, name);
}
inline ~ScopedTrace() noexcept {
mTrace.traceEnd(mTag);
}
inline void value(uint32_t tag, const char* name, int32_t v) noexcept {
mTrace.value(tag, name, v);
}
inline void value(uint32_t tag, const char* name, int64_t v) noexcept {
mTrace.value(tag, name, v);
}
private:
Systrace mTrace;
const uint32_t mTag;
};
} // namespace details
} // namespace utils
// ------------------------------------------------------------------------------------------------
#else // !ANDROID
// ------------------------------------------------------------------------------------------------
#define SYSTRACE_ENABLE()
#define SYSTRACE_DISABLE()
#define SYSTRACE_CONTEXT()
#define SYSTRACE_NAME(name)
#define SYSTRACE_NAME_BEGIN(name)
#define SYSTRACE_NAME_END()
#define SYSTRACE_CALL()
#define SYSTRACE_ASYNC_BEGIN(name, cookie)
#define SYSTRACE_ASYNC_END(name, cookie)
#define SYSTRACE_VALUE32(name, val)
#define SYSTRACE_VALUE64(name, val)
#endif // ANDROID
#endif // TNT_UTILS_SYSTRACE_H

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_THERMALMANAGER_H
#define TNT_UTILS_THERMALMANAGER_H
#if defined(__ANDROID__)
#include <utils/android/ThermalManager.h>
#else
#include <utils/generic/ThermalManager.h>
#endif
#endif // TNT_UTILS_THERMALMANAGER_H

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_THREADUTILS_H
#define TNT_UTILS_THREADUTILS_H
#include <thread>
namespace utils {
class ThreadUtils {
public:
static std::thread::id getThreadId() noexcept;
static bool isThisThread(std::thread::id id) noexcept;
};
} // namespace utils
#endif // TNT_UTILS_THREADUTILS_H

View File

@@ -0,0 +1,202 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_WORKSTEALINGDEQUEUE_H
#define TNT_UTILS_WORKSTEALINGDEQUEUE_H
#include <atomic>
#include <assert.h>
#include <stddef.h>
namespace utils {
/*
* A templated, lockless, fixed-size work-stealing dequeue
*
*
* top bottom
* v v
* |----|----|----|----|----|----|
* steal() push(), pop()
* any thread main thread
*
*
*/
template <typename TYPE, size_t COUNT>
class WorkStealingDequeue {
static_assert(!(COUNT & (COUNT - 1)), "COUNT must be a power of two");
static constexpr size_t MASK = COUNT - 1;
// mTop and mBottom must be signed integers. We use 64-bits atomics so we don't have
// to worry about wrapping around.
using index_t = int64_t;
std::atomic<index_t> mTop = { 0 }; // written/read in pop()/steal()
std::atomic<index_t> mBottom = { 0 }; // written only in pop(), read in push(), steal()
TYPE mItems[COUNT];
// NOTE: it's not safe to return a reference because getItemAt() can be called
// concurrently and the caller could std::move() the item unsafely.
TYPE getItemAt(index_t index) noexcept { return mItems[index & MASK]; }
void setItemAt(index_t index, TYPE item) noexcept { mItems[index & MASK] = item; }
public:
using value_type = TYPE;
inline void push(TYPE item) noexcept;
inline TYPE pop() noexcept;
inline TYPE steal() noexcept;
size_t getSize() const noexcept { return COUNT; }
// for debugging only...
size_t getCount() const noexcept {
index_t bottom = mBottom.load(std::memory_order_relaxed);
index_t top = mTop.load(std::memory_order_relaxed);
return bottom - top;
}
};
/*
* Adds an item at the BOTTOM of the queue.
*
* Must be called from the main thread.
*/
template <typename TYPE, size_t COUNT>
void WorkStealingDequeue<TYPE, COUNT>::push(TYPE item) noexcept {
// std::memory_order_relaxed is sufficient because this load doesn't acquire anything from
// another thread. mBottom is only written in pop() which cannot be concurrent with push()
index_t bottom = mBottom.load(std::memory_order_relaxed);
setItemAt(bottom, item);
// std::memory_order_release is used because we release the item we just pushed to other
// threads which are calling steal().
mBottom.store(bottom + 1, std::memory_order_release);
}
/*
* Removes an item from the BOTTOM of the queue.
*
* Must be called from the main thread.
*/
template <typename TYPE, size_t COUNT>
TYPE WorkStealingDequeue<TYPE, COUNT>::pop() noexcept {
// std::memory_order_seq_cst is needed to guarantee ordering in steal()
// Note however that this is not a typical acquire/release operation:
// - not acquire because mBottom is only written in push() which is not concurrent
// - not release because we're not publishing anything to steal() here
//
// QUESTION: does this prevent mTop load below to be reordered before the "store" part of
// fetch_sub()? Hopefully it does. If not we'd need a full memory barrier.
//
index_t bottom = mBottom.fetch_sub(1, std::memory_order_seq_cst) - 1;
// bottom could be -1 if we tried to pop() from an empty queue. This will be corrected below.
assert( bottom >= -1 );
// std::memory_order_seq_cst is needed to guarantee ordering in steal()
// Note however that this is not a typical acquire operation
// (i.e. other thread's writes of mTop don't publish data)
index_t top = mTop.load(std::memory_order_seq_cst);
if (top < bottom) {
// Queue isn't empty and it's not the last item, just return it, this is the common case.
return getItemAt(bottom);
}
TYPE item{};
if (top == bottom) {
// we just took the last item
item = getItemAt(bottom);
// Because we know we took the last item, we could be racing with steal() -- the last
// item being both at the top and bottom of the queue.
// We resolve this potential race by also stealing that item from ourselves.
if (mTop.compare_exchange_strong(top, top + 1,
std::memory_order_seq_cst,
std::memory_order_relaxed)) {
// success: we stole our last item from ourself, meaning that a concurrent steal()
// would have failed.
// mTop now equals top + 1, we adjust top to make the queue empty.
top++;
} else {
// failure: mTop was not equal to top, which means the item was stolen under our feet.
// top now equals to mTop. Simply discard the item we just popped.
// The queue is now empty.
item = TYPE();
}
} else {
// We could be here if the item was stolen just before we read mTop, we'll adjust
// mBottom below.
assert(top - bottom == 1);
}
// std::memory_order_relaxed used because we're not publishing any data.
// no concurrent writes to mBottom possible, it's always safe to write mBottom.
mBottom.store(top, std::memory_order_relaxed);
return item;
}
/*
* Steals an item from the TOP of another thread's queue.
*
* This can be called concurrently with steal(), push() or pop()
*
* steal() never fails, either there is an item and it atomically takes it, or there isn't and
* it returns an empty item.
*/
template <typename TYPE, size_t COUNT>
TYPE WorkStealingDequeue<TYPE, COUNT>::steal() noexcept {
while (true) {
/*
* Note: A Key component of this algorithm is that mTop is read before mBottom here
* (and observed as such in other threads)
*/
// std::memory_order_seq_cst is needed to guarantee ordering in pop()
// Note however that this is not a typical acquire operation
// (i.e. other thread's writes of mTop don't publish data)
index_t top = mTop.load(std::memory_order_seq_cst);
// std::memory_order_acquire is needed because we're acquiring items published in push().
// std::memory_order_seq_cst is needed to guarantee ordering in pop()
index_t bottom = mBottom.load(std::memory_order_seq_cst);
if (top >= bottom) {
// queue is empty
return TYPE();
}
// The queue isn't empty
TYPE item(getItemAt(top));
if (mTop.compare_exchange_strong(top, top + 1,
std::memory_order_seq_cst,
std::memory_order_relaxed)) {
// success: we stole an item, just return it.
return item;
}
// failure: the item we just tried to steal was pop()'ed under our feet,
// simply discard it; nothing to do -- it's okay to try again.
}
}
} // namespace utils
#endif // TNT_UTILS_WORKSTEALINGDEQUEUE_H

View File

@@ -0,0 +1,122 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_ZIP2ITERATOR_H
#define TNT_UTILS_ZIP2ITERATOR_H
#include <iterator>
#include <type_traits>
#include <utils/compiler.h>
namespace utils {
/*
* A random access iterator that wraps two other random access iterators.
* This mostly exists so that one can sort an array using values from another.
*/
template<typename It1, typename It2>
class Zip2Iterator {
std::pair<It1, It2> mIt;
using Ref1 = typename std::iterator_traits<It1>::reference;
using Ref2 = typename std::iterator_traits<It2>::reference;
using Val1 = typename std::iterator_traits<It1>::value_type;
using Val2 = typename std::iterator_traits<It2>::value_type;
public:
struct Ref : public std::pair<Ref1, Ref2> {
using std::pair<Ref1, Ref2>::pair;
using std::pair<Ref1, Ref2>::operator=;
private:
friend void swap(Ref lhs, Ref rhs) {
using std::swap;
swap(lhs.first, rhs.first);
swap(lhs.second, rhs.second);
}
};
using value_type = std::pair<Val1, Val2>;
using reference = Ref;
using pointer = value_type*;
using difference_type = ptrdiff_t;
using iterator_category = std::random_access_iterator_tag;
Zip2Iterator() = default;
Zip2Iterator(It1 first, It2 second) : mIt({first, second}) {}
Zip2Iterator(Zip2Iterator const& rhs) noexcept = default;
Zip2Iterator& operator=(Zip2Iterator const& rhs) = default;
reference operator*() const { return { *mIt.first, *mIt.second }; }
reference operator[](size_t n) const { return *(*this + n); }
Zip2Iterator& operator++() {
++mIt.first;
++mIt.second;
return *this;
}
Zip2Iterator& operator--() {
--mIt.first;
--mIt.second;
return *this;
}
// Postfix operator needed by Microsoft C++
const Zip2Iterator operator++(int) {
Zip2Iterator t(*this);
mIt.first++;
mIt.second++;
return t;
}
const Zip2Iterator operator--(int) {
Zip2Iterator t(*this);
mIt.first--;
mIt.second--;
return t;
}
Zip2Iterator& operator+=(size_t v) {
mIt.first += v;
mIt.second += v;
return *this;
}
Zip2Iterator& operator-=(size_t v) {
mIt.first -= v;
mIt.second -= v;
return *this;
}
Zip2Iterator operator+(size_t rhs) const { return { mIt.first + rhs, mIt.second + rhs }; }
Zip2Iterator operator+(size_t rhs) { return { mIt.first + rhs, mIt.second + rhs }; }
Zip2Iterator operator-(size_t rhs) const { return { mIt.first - rhs, mIt.second - rhs }; }
difference_type operator-(Zip2Iterator const& rhs) const { return mIt.first - rhs.mIt.first; }
bool operator==(Zip2Iterator const& rhs) const { return (mIt.first == rhs.mIt.first); }
bool operator!=(Zip2Iterator const& rhs) const { return (mIt.first != rhs.mIt.first); }
bool operator>=(Zip2Iterator const& rhs) const { return (mIt.first >= rhs.mIt.first); }
bool operator> (Zip2Iterator const& rhs) const { return (mIt.first > rhs.mIt.first); }
bool operator<=(Zip2Iterator const& rhs) const { return (mIt.first <= rhs.mIt.first); }
bool operator< (Zip2Iterator const& rhs) const { return (mIt.first < rhs.mIt.first); }
};
} // namespace utils
#endif // TNT_UTILS_ZIP2ITERATOR_H

View File

@@ -0,0 +1,214 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_ALGORITHM_H
#define TNT_UTILS_ALGORITHM_H
#include <utils/compiler.h>
#include <type_traits> // for std::enable_if
#include <limits.h>
#include <stdint.h>
namespace utils {
namespace details {
template<typename T>
constexpr inline T popcount(T v) noexcept {
static_assert(sizeof(T) * CHAR_BIT <= 128, "details::popcount() only support up to 128 bits");
constexpr T ONES = ~T(0);
v = v - ((v >> 1u) & ONES / 3);
v = (v & ONES / 15 * 3) + ((v >> 2u) & ONES / 15 * 3);
v = (v + (v >> 4u)) & ONES / 255 * 15;
return (T) (v * (ONES / 255)) >> (sizeof(T) - 1) * CHAR_BIT;
}
template<typename T, typename = std::enable_if_t<std::is_unsigned<T>::value>>
constexpr inline T clz(T x) noexcept {
static_assert(sizeof(T) * CHAR_BIT <= 128, "details::clz() only support up to 128 bits");
x |= (x >> 1u);
x |= (x >> 2u);
x |= (x >> 4u);
x |= (x >> 8u);
x |= (x >> 16u);
if constexpr (sizeof(T) * CHAR_BIT >= 64) { // just to silence compiler warning
x |= (x >> 32u);
}
if constexpr (sizeof(T) * CHAR_BIT >= 128) { // just to silence compiler warning
x |= (x >> 64u);
}
return T(sizeof(T) * CHAR_BIT) - details::popcount(x);
}
template<typename T, typename = std::enable_if_t<std::is_unsigned<T>::value>>
constexpr inline T ctz(T x) noexcept {
static_assert(sizeof(T) * CHAR_BIT <= 64, "details::ctz() only support up to 64 bits");
T c = sizeof(T) * CHAR_BIT;
#if defined(_MSC_VER)
// equivalent to x & -x, but MSVC yield a warning for using unary minus operator on unsigned types
x &= (~x + 1);
#else
// equivalent to x & (~x + 1), but some compilers generate a better sequence on ARM
x &= -x;
#endif
if (x) c--;
if (sizeof(T) * CHAR_BIT >= 64) {
if (x & T(0x00000000FFFFFFFF)) c -= 32;
}
if (x & T(0x0000FFFF0000FFFF)) c -= 16;
if (x & T(0x00FF00FF00FF00FF)) c -= 8;
if (x & T(0x0F0F0F0F0F0F0F0F)) c -= 4;
if (x & T(0x3333333333333333)) c -= 2;
if (x & T(0x5555555555555555)) c -= 1;
return c;
}
} // namespace details
constexpr inline UTILS_PUBLIC UTILS_PURE
unsigned int UTILS_ALWAYS_INLINE clz(unsigned int x) noexcept {
#if __has_builtin(__builtin_clz)
return __builtin_clz(x);
#else
return details::clz(x);
#endif
}
constexpr inline UTILS_PUBLIC UTILS_PURE
unsigned long UTILS_ALWAYS_INLINE clz(unsigned long x) noexcept {
#if __has_builtin(__builtin_clzl)
return __builtin_clzl(x);
#else
return details::clz(x);
#endif
}
constexpr inline UTILS_PUBLIC UTILS_PURE
unsigned long long UTILS_ALWAYS_INLINE clz(unsigned long long x) noexcept {
#if __has_builtin(__builtin_clzll)
return __builtin_clzll(x);
#else
return details::clz(x);
#endif
}
constexpr inline UTILS_PUBLIC UTILS_PURE
unsigned int UTILS_ALWAYS_INLINE ctz(unsigned int x) noexcept {
#if __has_builtin(__builtin_ctz)
return __builtin_ctz(x);
#else
return details::ctz(x);
#endif
}
constexpr inline UTILS_PUBLIC UTILS_PURE
unsigned long UTILS_ALWAYS_INLINE ctz(unsigned long x) noexcept {
#if __has_builtin(__builtin_ctzl)
return __builtin_ctzl(x);
#else
return details::ctz(x);
#endif
}
constexpr inline UTILS_PUBLIC UTILS_PURE
unsigned long long UTILS_ALWAYS_INLINE ctz(unsigned long long x) noexcept {
#if __has_builtin(__builtin_ctzll)
return __builtin_ctzll(x);
#else
return details::ctz(x);
#endif
}
constexpr inline UTILS_PUBLIC UTILS_PURE
unsigned int UTILS_ALWAYS_INLINE popcount(unsigned int x) noexcept {
#if __has_builtin(__builtin_popcount)
return __builtin_popcount(x);
#else
return details::popcount(x);
#endif
}
constexpr inline UTILS_PUBLIC UTILS_PURE
unsigned long UTILS_ALWAYS_INLINE popcount(unsigned long x) noexcept {
#if __has_builtin(__builtin_popcountl)
return __builtin_popcountl(x);
#else
return details::popcount(x);
#endif
}
constexpr inline UTILS_PUBLIC UTILS_PURE
unsigned long long UTILS_ALWAYS_INLINE popcount(unsigned long long x) noexcept {
#if __has_builtin(__builtin_popcountll)
return __builtin_popcountll(x);
#else
return details::popcount(x);
#endif
}
constexpr inline UTILS_PUBLIC UTILS_PURE
uint8_t UTILS_ALWAYS_INLINE popcount(uint8_t x) noexcept {
return (uint8_t)popcount((unsigned int)x);
}
template<typename T,
typename = std::enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value>>
constexpr inline UTILS_PUBLIC UTILS_PURE
T log2i(T x) noexcept {
return (sizeof(x) * 8 - 1u) - clz(x);
}
template<typename RandomAccessIterator, typename COMPARE>
inline UTILS_PUBLIC
RandomAccessIterator partition_point(
RandomAccessIterator first, RandomAccessIterator last, COMPARE pred,
bool assume_power_of_two = false) {
size_t len = last - first;
if (!assume_power_of_two) {
// handle non power-of-two sized arrays. If it's POT, the next line is a no-op
// and gets optimized out if the size is known at compile time.
len = 1u << (31 - clz(uint32_t(len))); // next power of two length / 2
size_t difference = (last - first) - len;
first += !difference || pred(first[len]) ? difference : 0;
}
while (len) {
// The number of repetitions here doesn't affect the result. We manually unroll the loop
// twice, to guarantee we have at least two iterations without branches (for the case
// where the size is not known at compile time
first += pred(first[len>>=1u]) ? len : 0;
first += pred(first[len>>=1u]) ? len : 0;
}
first += pred(*first);
return first;
}
template <class To, class From>
typename std::enable_if_t<
(sizeof(To) == sizeof(From)) &&
std::is_trivially_copyable<From>::value,
To>
// constexpr support needs compiler magic
bit_cast(const From &src) noexcept {
return reinterpret_cast<const To&>(src);
}
} // namespace utils
#endif // TNT_UTILS_ALGORITHM_H

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_ANDROID_THERMALMANAGER_H
#define TNT_UTILS_ANDROID_THERMALMANAGER_H
#include <utils/compiler.h>
#include <stdint.h>
struct AThermalManager;
namespace utils {
class ThermalManager {
public:
enum class ThermalStatus : int8_t {
ERROR = -1,
NONE,
LIGHT,
MODERATE,
SEVERE,
CRITICAL,
EMERGENCY,
SHUTDOWN
};
ThermalManager();
~ThermalManager();
// Movable
ThermalManager(ThermalManager&& rhs) noexcept;
ThermalManager& operator=(ThermalManager&& rhs) noexcept;
// not copiable
ThermalManager(ThermalManager const& rhs) = delete;
ThermalManager& operator=(ThermalManager const& rhs) = delete;
ThermalStatus getCurrentThermalStatus() const noexcept;
private:
AThermalManager* mThermalManager = nullptr;
};
} // namespace utils
#endif // TNT_UTILS_ANDROID_THERMALMANAGER_H

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_APILEVEL_H
#define TNT_UTILS_APILEVEL_H
#include <utils/compiler.h>
namespace utils {
/**
* Returns this platform's API level. On Android this function will return
* the API level as defined by the SDK API level version. If a platform does
* not have an API level, this function returns 0.
*/
UTILS_PUBLIC
int api_level();
} // namespace utils
#endif // TNT_UTILS_APILEVEL_H

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_ARCHITECTURE_H
#define TNT_UTILS_ARCHITECTURE_H
#include <stddef.h>
namespace utils {
constexpr size_t CACHELINE_SIZE = 64;
} // namespace utils
#endif // TNT_UTILS_ARCHITECTURE_H

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_ASHMEM_H
#define TNT_UTILS_ASHMEM_H
#include <stddef.h>
namespace utils {
int ashmem_create_region(const char *name, size_t size);
} // namespace utils
#endif // TNT_UTILS_ASHMEM_H

View File

@@ -0,0 +1,330 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_BITSET_H
#define TNT_UTILS_BITSET_H
#include <utils/algorithm.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <algorithm> // for std::fill
#include <iterator>
#include <type_traits>
#if defined(__ARM_NEON)
# if defined(__ARM_ACLE) && defined(__aarch64__)
# include <arm_neon.h>
# define TNT_UTILS_BITSET_USE_NEON 1
# endif
#endif
namespace utils {
/*
* This bitset<> class is different from std::bitset<> in that it allows us to control
* the exact storage size. This is useful for small bitset (e.g. < 64, on 64-bits machines).
* It also allows for lexicographical compares (i.e. sorting).
*/
template<typename T, size_t N = 1,
typename = typename std::enable_if<std::is_integral<T>::value &&
std::is_unsigned<T>::value>::type>
class UTILS_PUBLIC bitset {
T storage[N];
public:
static constexpr T BITS_PER_WORD = sizeof(T) * 8;
static constexpr T BIT_COUNT = BITS_PER_WORD * N;
static constexpr T WORLD_COUNT = N;
using container_type = T;
bitset() noexcept {
std::fill(std::begin(storage), std::end(storage), 0);
}
T getBitsAt(size_t n) const noexcept {
assert_invariant(n<N);
return storage[n];
}
T& getBitsAt(size_t n) noexcept {
assert_invariant(n<N);
return storage[n];
}
T getValue() const noexcept {
static_assert(N == 1, "bitfield must only have one storage word");
return storage[0];
}
void setValue(T value) noexcept {
static_assert(N == 1, "bitfield must only have one storage word");
storage[0] = value;
}
template<typename F>
void forEachSetBit(F exec) const noexcept {
for (size_t i = 0; i < N; i++) {
T v = storage[i];
while (v) {
T k = utils::ctz(v);
v &= ~(T(1) << k);
exec(size_t(k + BITS_PER_WORD * i));
}
}
}
size_t size() const noexcept { return N * BITS_PER_WORD; }
bool test(size_t bit) const noexcept { return operator[](bit); }
void set(size_t b) noexcept {
assert_invariant(b / BITS_PER_WORD < N);
storage[b / BITS_PER_WORD] |= T(1) << (b % BITS_PER_WORD);
}
void set(size_t b, bool value) noexcept {
assert_invariant(b / BITS_PER_WORD < N);
storage[b / BITS_PER_WORD] &= ~(T(1) << (b % BITS_PER_WORD));
storage[b / BITS_PER_WORD] |= T(value) << (b % BITS_PER_WORD);
}
void unset(size_t b) noexcept {
assert_invariant(b / BITS_PER_WORD < N);
storage[b / BITS_PER_WORD] &= ~(T(1) << (b % BITS_PER_WORD));
}
void flip(size_t b) noexcept {
assert_invariant(b / BITS_PER_WORD < N);
storage[b / BITS_PER_WORD] ^= T(1) << (b % BITS_PER_WORD);
}
void reset() noexcept {
std::fill(std::begin(storage), std::end(storage), 0);
}
bool operator[](size_t b) const noexcept {
assert_invariant(b / BITS_PER_WORD < N);
return bool(storage[b / BITS_PER_WORD] & (T(1) << (b % BITS_PER_WORD)));
}
size_t count() const noexcept {
#if defined(TNT_UTILS_BITSET_USE_NEON)
if (BIT_COUNT % 128 == 0 && BIT_COUNT / 128 < 31) {
// Use NEON for bitset multiple of 128 bits.
// The intermediate computation can't handle more than 31*128 bits because
// intermediate counts must be 8 bits.
uint8x16_t const* const p = (uint8x16_t const*) storage;
uint8x16_t counts = vcntq_u8(p[0]);
for (size_t i = 1; i < BIT_COUNT / 128; ++i) {
counts += vcntq_u8(p[i]);
}
return vaddlvq_u8(counts);
} else
#endif
{
T r = utils::popcount(storage[0]);
for (size_t i = 1; i < N; ++i) {
r += utils::popcount(storage[i]);
}
return r;
}
}
bool any() const noexcept {
#if defined(TNT_UTILS_BITSET_USE_NEON)
if (BIT_COUNT % 128 == 0) {
uint64x2_t const* const p = (uint64x2_t const*) storage;
uint64x2_t r = p[0];
for (size_t i = 1; i < BIT_COUNT / 128; ++i) {
r |= p[i];
}
return bool(r[0] | r[1]);
} else
#endif
{
T r = storage[0];
for (size_t i = 1; i < N; ++i) {
r |= storage[i];
}
return bool(r);
}
}
bool none() const noexcept {
return !any();
}
bool all() const noexcept {
#if defined(TNT_UTILS_BITSET_USE_NEON)
if (BIT_COUNT % 128 == 0) {
uint64x2_t const* const p = (uint64x2_t const*) storage;
uint64x2_t r = p[0];
for (size_t i = 1; i < BIT_COUNT / 128; ++i) {
r &= p[i];
}
return T(~(r[0] & r[1])) == T(0);
} else
#endif
{
T r = storage[0];
for (size_t i = 1; i < N; ++i) {
r &= storage[i];
}
return T(~r) == T(0);
}
}
bool operator!=(const bitset& b) const noexcept {
#if defined(TNT_UTILS_BITSET_USE_NEON)
if (BIT_COUNT % 128 == 0) {
bitset temp(*this ^ b);
uint64x2_t const* const p = (uint64x2_t const*) temp.storage;
uint64x2_t r = p[0];
for (size_t i = 1; i < BIT_COUNT / 128; ++i) {
r |= p[i];
}
return bool(r[0] | r[1]);
} else
#endif
{
T r = storage[0] ^ b.storage[0];
for (size_t i = 1; i < N; ++i) {
r |= storage[i] ^ b.storage[i];
}
return bool(r);
}
}
bool operator==(const bitset& b) const noexcept {
return !operator!=(b);
}
bitset& operator&=(const bitset& b) noexcept {
#if defined(TNT_UTILS_BITSET_USE_NEON)
if (BIT_COUNT % 128 == 0) {
uint8x16_t* const p = (uint8x16_t*) storage;
uint8x16_t const* const q = (uint8x16_t const*) b.storage;
for (size_t i = 0; i < BIT_COUNT / 128; ++i) {
p[i] &= q[i];
}
} else
#endif
{
for (size_t i = 0; i < N; ++i) {
storage[i] &= b.storage[i];
}
}
return *this;
}
bitset& operator|=(const bitset& b) noexcept {
#if defined(TNT_UTILS_BITSET_USE_NEON)
if (BIT_COUNT % 128 == 0) {
uint8x16_t* const p = (uint8x16_t*) storage;
uint8x16_t const* const q = (uint8x16_t const*) b.storage;
for (size_t i = 0; i < BIT_COUNT / 128; ++i) {
p[i] |= q[i];
}
} else
#endif
{
for (size_t i = 0; i < N; ++i) {
storage[i] |= b.storage[i];
}
}
return *this;
}
bitset& operator^=(const bitset& b) noexcept {
#if defined(TNT_UTILS_BITSET_USE_NEON)
if (BIT_COUNT % 128 == 0) {
uint8x16_t* const p = (uint8x16_t*) storage;
uint8x16_t const* const q = (uint8x16_t const*) b.storage;
for (size_t i = 0; i < BIT_COUNT / 128; ++i) {
p[i] ^= q[i];
}
} else
#endif
{
for (size_t i = 0; i < N; ++i) {
storage[i] ^= b.storage[i];
}
}
return *this;
}
bitset operator~() const noexcept {
bitset r;
#if defined(TNT_UTILS_BITSET_USE_NEON)
if (BIT_COUNT % 128 == 0) {
uint8x16_t* const p = (uint8x16_t*) r.storage;
uint8x16_t const* const q = (uint8x16_t const*) storage;
for (size_t i = 0; i < BIT_COUNT / 128; ++i) {
p[i] = ~q[i];
}
} else
#endif
{
for (size_t i = 0; i < N; ++i) {
r.storage[i] = ~storage[i];
}
}
return r;
}
private:
friend bool operator<(bitset const& lhs, bitset const& rhs) noexcept {
return std::lexicographical_compare(
std::begin(lhs.storage), std::end(lhs.storage),
std::begin(rhs.storage), std::end(rhs.storage)
);
}
friend bitset operator&(const bitset& lhs, const bitset& rhs) noexcept {
return bitset(lhs) &= rhs;
}
friend bitset operator|(const bitset& lhs, const bitset& rhs) noexcept {
return bitset(lhs) |= rhs;
}
friend bitset operator^(const bitset& lhs, const bitset& rhs) noexcept {
return bitset(lhs) ^= rhs;
}
};
using bitset8 = bitset<uint8_t>;
using bitset32 = bitset<uint32_t>;
using bitset64 = bitset<uint64_t>;
using bitset128 = bitset<uint64_t, 2>;
using bitset256 = bitset<uint64_t, 4>;
static_assert(sizeof(bitset8) == 1, "bitset8 isn't 8 bits!");
static_assert(sizeof(bitset32) == 4, "bitset32 isn't 32 bits!");
static_assert(sizeof(bitset64) == 8, "bitset64 isn't 64 bits!");
static_assert(sizeof(bitset128) == 16, "bitset128 isn't 128 bits!");
static_assert(sizeof(bitset256) == 32, "bitset256 isn't 256 bits!");
} // namespace utils
#endif // TNT_UTILS_BITSET_H

View File

@@ -0,0 +1,263 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_COMPILER_H
#define TNT_UTILS_COMPILER_H
// compatibility with non-clang compilers...
#ifndef __has_attribute
#define __has_attribute(x) 0
#endif
#ifndef __has_feature
#define __has_feature(x) 0
#endif
#ifndef __has_builtin
#define __has_builtin(x) 0
#endif
#if __has_attribute(visibility)
# define UTILS_PUBLIC __attribute__((visibility("default")))
#else
# define UTILS_PUBLIC
#endif
#if __has_attribute(deprecated)
# define UTILS_DEPRECATED [[deprecated]]
#else
# define UTILS_DEPRECATED
#endif
#if __has_attribute(packed)
# define UTILS_PACKED __attribute__((packed))
#else
# define UTILS_PACKED
#endif
#if __has_attribute(noreturn)
# define UTILS_NORETURN __attribute__((noreturn))
#else
# define UTILS_NORETURN
#endif
#if __has_attribute(fallthrough)
# define UTILS_FALLTHROUGH [[fallthrough]]
#else
# define UTILS_FALLTHROUGH
#endif
#if __has_attribute(visibility)
# ifndef TNT_DEV
# define UTILS_PRIVATE __attribute__((visibility("hidden")))
# else
# define UTILS_PRIVATE
# endif
#else
# define UTILS_PRIVATE
#endif
#define UTILS_NO_SANITIZE_THREAD
#if __has_feature(thread_sanitizer)
#undef UTILS_NO_SANITIZE_THREAD
#define UTILS_NO_SANITIZE_THREAD __attribute__((no_sanitize("thread")))
#endif
#define UTILS_HAS_SANITIZE_THREAD 0
#if __has_feature(thread_sanitizer) || defined(__SANITIZE_THREAD__)
#undef UTILS_HAS_SANITIZE_THREAD
#define UTILS_HAS_SANITIZE_THREAD 1
#endif
#define UTILS_HAS_SANITIZE_MEMORY 0
#if __has_feature(memory_sanitizer)
#undef UTILS_HAS_SANITIZE_MEMORY
#define UTILS_HAS_SANITIZE_MEMORY 1
#endif
/*
* helps the compiler's optimizer predicting branches
*/
#if __has_builtin(__builtin_expect)
# ifdef __cplusplus
# define UTILS_LIKELY( exp ) (__builtin_expect( !!(exp), true ))
# define UTILS_UNLIKELY( exp ) (__builtin_expect( !!(exp), false ))
# else
# define UTILS_LIKELY( exp ) (__builtin_expect( !!(exp), 1 ))
# define UTILS_UNLIKELY( exp ) (__builtin_expect( !!(exp), 0 ))
# endif
#else
# define UTILS_LIKELY( exp ) (!!(exp))
# define UTILS_UNLIKELY( exp ) (!!(exp))
#endif
#if __has_builtin(__builtin_prefetch)
# define UTILS_PREFETCH( exp ) (__builtin_prefetch(exp))
#else
# define UTILS_PREFETCH( exp )
#endif
#if __has_builtin(__builtin_assume)
# define UTILS_ASSUME( exp ) (__builtin_assume(exp))
#else
# define UTILS_ASSUME( exp )
#endif
#if (defined(__i386__) || defined(__x86_64__))
# define UTILS_HAS_HYPER_THREADING 1 // on x86 we assume we have hyper-threading.
#else
# define UTILS_HAS_HYPER_THREADING 0
#endif
#if defined(FILAMENT_SINGLE_THREADED)
# define UTILS_HAS_THREADING 0
#elif defined(__EMSCRIPTEN__)
# if defined(__EMSCRIPTEN_PTHREADS__) && defined(FILAMENT_WASM_THREADS)
# define UTILS_HAS_THREADING 1
# else
# define UTILS_HAS_THREADING 0
# endif
#else
# define UTILS_HAS_THREADING 1
#endif
#if __has_attribute(noinline)
#define UTILS_NOINLINE __attribute__((noinline))
#else
#define UTILS_NOINLINE
#endif
#if __has_attribute(always_inline)
#define UTILS_ALWAYS_INLINE __attribute__((always_inline))
#else
#define UTILS_ALWAYS_INLINE
#endif
#if __has_attribute(pure)
#define UTILS_PURE __attribute__((pure))
#else
#define UTILS_PURE
#endif
#if __has_attribute(maybe_unused) || (defined(_MSC_VER) && _MSC_VER >= 1911)
#define UTILS_UNUSED [[maybe_unused]]
#define UTILS_UNUSED_IN_RELEASE [[maybe_unused]]
#elif __has_attribute(unused)
#define UTILS_UNUSED __attribute__((unused))
#define UTILS_UNUSED_IN_RELEASE __attribute__((unused))
#else
#define UTILS_UNUSED
#define UTILS_UNUSED_IN_RELEASE
#endif
#if defined(_MSC_VER) && _MSC_VER >= 1900
# define UTILS_RESTRICT __restrict
#elif (defined(__clang__) || defined(__GNUC__))
# define UTILS_RESTRICT __restrict__
#else
# define UTILS_RESTRICT
#endif
#if defined(_MSC_VER) && _MSC_VER >= 1900
# define UTILS_HAS_FEATURE_CXX_THREAD_LOCAL 1
#elif __has_feature(cxx_thread_local)
# define UTILS_HAS_FEATURE_CXX_THREAD_LOCAL 1
#else
# define UTILS_HAS_FEATURE_CXX_THREAD_LOCAL 0
#endif
#if defined(_MSC_VER)
// MSVC does not support loop unrolling hints
# define UTILS_UNROLL
# define UTILS_NOUNROLL
#else
// C++11 allows pragmas to be specified as part of defines using the _Pragma syntax.
# define UTILS_UNROLL _Pragma("unroll")
# define UTILS_NOUNROLL _Pragma("nounroll")
#endif
#if __has_feature(cxx_rtti) || defined(_CPPRTTI)
# define UTILS_HAS_RTTI 1
#else
# define UTILS_HAS_RTTI 0
#endif
#ifdef __ARM_ACLE
# include <arm_acle.h>
# define UTILS_WAIT_FOR_INTERRUPT() __wfi()
# define UTILS_WAIT_FOR_EVENT() __wfe()
# define UTILS_BROADCAST_EVENT() __sev()
# define UTILS_SIGNAL_EVENT() __sevl()
# define UTILS_PAUSE() __yield()
# define UTILS_PREFETCHW(addr) __pldx(1, 0, 0, addr)
#else // !__ARM_ACLE
# if (defined(__i386__) || defined(__x86_64__))
# define UTILS_X86_PAUSE {__asm__ __volatile__( "rep; nop" : : : "memory" );}
# define UTILS_WAIT_FOR_INTERRUPT() UTILS_X86_PAUSE
# define UTILS_WAIT_FOR_EVENT() UTILS_X86_PAUSE
# define UTILS_BROADCAST_EVENT()
# define UTILS_SIGNAL_EVENT()
# define UTILS_PAUSE() UTILS_X86_PAUSE
# define UTILS_PREFETCHW(addr) UTILS_PREFETCH(addr)
# else // !x86
# define UTILS_WAIT_FOR_INTERRUPT()
# define UTILS_WAIT_FOR_EVENT()
# define UTILS_BROADCAST_EVENT()
# define UTILS_SIGNAL_EVENT()
# define UTILS_PAUSE()
# define UTILS_PREFETCHW(addr) UTILS_PREFETCH(addr)
# endif // x86
#endif // __ARM_ACLE
// ssize_t is a POSIX type.
#if defined(WIN32) || defined(_WIN32)
#include <Basetsd.h>
typedef SSIZE_T ssize_t;
#endif
#ifdef _MSC_VER
# define UTILS_EMPTY_BASES __declspec(empty_bases)
#else
# define UTILS_EMPTY_BASES
#endif
#if defined(WIN32) || defined(_WIN32)
#define IMPORTSYMB __declspec(dllimport)
#else
#define IMPORTSYMB
#endif
#if defined(_MSC_VER) && !defined(__PRETTY_FUNCTION__)
# define __PRETTY_FUNCTION__ __FUNCSIG__
#endif
#if defined(_MSC_VER)
# define UTILS_WARNING_PUSH _Pragma("warning( push )")
# define UTILS_WARNING_POP _Pragma("warning( pop )")
# define UTILS_WARNING_ENABLE_PADDED _Pragma("warning(1: 4324)")
#elif defined(__clang__)
# define UTILS_WARNING_PUSH _Pragma("clang diagnostic push")
# define UTILS_WARNING_POP _Pragma("clang diagnostic pop")
# define UTILS_WARNING_ENABLE_PADDED _Pragma("clang diagnostic warning \"-Wpadded\"")
#else
# define UTILS_WARNING_PUSH
# define UTILS_WARNING_POP
# define UTILS_WARNING_ENABLE_PADDED
#endif
#endif // TNT_UTILS_COMPILER_H

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_COMPRESSED_PAIR_H
#define TNT_UTILS_COMPRESSED_PAIR_H
#include <type_traits>
#include <utility>
namespace utils {
template<typename T, bool>
struct dependent_type : public T {
};
template<typename T1, typename T2,
std::enable_if_t<!std::is_same_v<T1, T2>, bool> = true>
struct compressed_pair : private T1, private T2 {
template<bool Dummy = true, typename = std::enable_if_t<
dependent_type<std::is_default_constructible<T1>, Dummy>::value &&
dependent_type<std::is_default_constructible<T2>, Dummy>::value>>
compressed_pair() : T1(), T2() {}
template<typename U1, typename U2>
compressed_pair(U1&& other1, U2&& other2)
: T1(std::forward<U1>(other1)),
T2(std::forward<U2>(other2)) {}
T1& first() noexcept {
return static_cast<T1&>(*this);
}
T2& second() noexcept {
return static_cast<T2&>(*this);
}
T1 const& first() const noexcept {
return static_cast<T1 const&>(*this);
}
T2 const& second() const noexcept {
return static_cast<T2 const&>(*this);
}
void swap(compressed_pair& other) noexcept {
using std::swap;
swap(first(), other.first());
swap(second(), other.second());
}
};
} // namespace utils
#endif // TNT_UTILS_COMPRESSED_PAIR_H

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_DEBUG_H
#define TNT_UTILS_DEBUG_H
#include <utils/compiler.h>
namespace utils {
void panic(const char *func, const char * file, int line, const char *assertion) noexcept;
} // namespace filament
#ifdef NDEBUG
# define assert_invariant(e) ((void)0)
#else
# define assert_invariant(e) \
(UTILS_LIKELY(e) ? ((void)0) : utils::panic(__func__, __FILE__, __LINE__, #e))
#endif // NDEBUG
#endif // TNT_UTILS_DEBUG_H

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_GENERIC_CONDITION_H
#define TNT_UTILS_GENERIC_CONDITION_H
#include <condition_variable>
namespace utils {
class Condition : public std::condition_variable {
public:
using std::condition_variable::condition_variable;
inline void notify_n(size_t n) noexcept {
if (n == 1) {
notify_one();
} else if (n > 1) {
notify_all();
}
}
};
} // namespace utils
#endif // TNT_UTILS_GENERIC_CONDITION_H

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_GENERIC_MUTEX_H
#define TNT_UTILS_GENERIC_MUTEX_H
#include <mutex>
namespace utils {
using Mutex = std::mutex;
} // namespace utils
#endif // TNT_UTILS_GENERIC_MUTEX_H

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_GENERIC_THERMALMANAGER_H
#define TNT_UTILS_GENERIC_THERMALMANAGER_H
#include <utils/compiler.h>
#include <stdint.h>
namespace utils {
class ThermalManager {
public:
enum class ThermalStatus : int8_t {
ERROR = -1,
NONE,
LIGHT,
MODERATE,
SEVERE,
CRITICAL,
EMERGENCY,
SHUTDOWN
};
ThermalManager() = default;
// Movable
ThermalManager(ThermalManager&& rhs) noexcept = default;
ThermalManager& operator=(ThermalManager&& rhs) noexcept = default;
// not copiable
ThermalManager(ThermalManager const& rhs) = delete;
ThermalManager& operator=(ThermalManager const& rhs) = delete;
ThermalStatus getCurrentThermalStatus() const noexcept {
return ThermalStatus::NONE;
}
};
} // namespace utils
#endif // TNT_UTILS_GENERIC_THERMALMANAGER_H

View File

@@ -0,0 +1,123 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_LINUX_CONDITION_H
#define TNT_UTILS_LINUX_CONDITION_H
#include <atomic>
#include <chrono>
#include <condition_variable> // for cv_status
#include <limits>
#include <mutex> // for unique_lock
#include <utils/linux/Mutex.h>
#include <time.h>
namespace utils {
/*
* A very simple condition variable class that can be used as an (almost) drop-in replacement
* for std::condition_variable (doesn't have the timed wait() though).
* It is very low overhead as most of it is inlined.
*/
class Condition {
public:
Condition() noexcept = default;
Condition(const Condition&) = delete;
Condition& operator=(const Condition&) = delete;
void notify_all() noexcept {
pulse(std::numeric_limits<int>::max());
}
void notify_one() noexcept {
pulse(1);
}
void notify_n(size_t n) noexcept {
if (n > 0) pulse(n);
}
void wait(std::unique_lock<Mutex>& lock) noexcept {
wait_until(lock.mutex(), false, nullptr);
}
template <class P>
void wait(std::unique_lock<Mutex>& lock, P predicate) {
while (!predicate()) {
wait(lock);
}
}
template<typename D>
std::cv_status wait_until(std::unique_lock<Mutex>& lock,
const std::chrono::time_point<std::chrono::steady_clock, D>& timeout_time) noexcept {
// convert to nanoseconds
uint64_t ns = std::chrono::duration<uint64_t, std::nano>(timeout_time.time_since_epoch()).count();
using sec_t = decltype(timespec::tv_sec);
using nsec_t = decltype(timespec::tv_nsec);
timespec ts{ sec_t(ns / 1000000000), nsec_t(ns % 1000000000) };
return wait_until(lock.mutex(), false, &ts);
}
template<typename D>
std::cv_status wait_until(std::unique_lock<Mutex>& lock,
const std::chrono::time_point<std::chrono::system_clock, D>& timeout_time) noexcept {
// convert to nanoseconds
uint64_t ns = std::chrono::duration<uint64_t, std::nano>(timeout_time.time_since_epoch()).count();
using sec_t = decltype(timespec::tv_sec);
using nsec_t = decltype(timespec::tv_nsec);
timespec ts{ sec_t(ns / 1000000000), nsec_t(ns % 1000000000) };
return wait_until(lock.mutex(), true, &ts);
}
template<typename C, typename D, typename P>
bool wait_until(std::unique_lock<Mutex>& lock,
const std::chrono::time_point<C, D>& timeout_time, P predicate) noexcept {
while (!predicate()) {
if (wait_until(lock, timeout_time) == std::cv_status::timeout) {
return predicate();
}
}
return true;
}
template<typename R, typename Period>
std::cv_status wait_for(std::unique_lock<Mutex>& lock,
const std::chrono::duration<R, Period>& rel_time) noexcept {
return wait_until(lock, std::chrono::steady_clock::now() + rel_time);
}
template<typename R, typename Period, typename P>
bool wait_for(std::unique_lock<Mutex>& lock,
const std::chrono::duration<R, Period>& rel_time, P pred) noexcept {
return wait_until(lock, std::chrono::steady_clock::now() + rel_time, std::move(pred));
}
private:
std::atomic<uint32_t> mState = { 0 };
void pulse(int threadCount) noexcept;
std::cv_status wait_until(Mutex* lock,
bool realtimeClock, timespec* ts) noexcept;
};
} // namespace utils
#endif // TNT_UTILS_LINUX_CONDITION_H

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_LINUX_MUTEX_H
#define TNT_UTILS_LINUX_MUTEX_H
#include <atomic>
#include <utils/compiler.h>
namespace utils {
/*
* A very simple mutex class that can be used as an (almost) drop-in replacement
* for std::mutex.
* It is very low overhead as most of it is inlined.
*/
class Mutex {
public:
constexpr Mutex() noexcept = default;
Mutex(const Mutex&) = delete;
Mutex& operator=(const Mutex&) = delete;
void lock() noexcept {
uint32_t old_state = UNLOCKED;
if (UTILS_UNLIKELY(!mState.compare_exchange_strong(old_state,
LOCKED, std::memory_order_acquire, std::memory_order_relaxed))) {
wait();
}
}
void unlock() noexcept {
if (UTILS_UNLIKELY(mState.exchange(UNLOCKED, std::memory_order_release) == LOCKED_CONTENDED)) {
wake();
}
}
private:
enum {
UNLOCKED = 0, LOCKED = 1, LOCKED_CONTENDED = 2
};
std::atomic<uint32_t> mState = { UNLOCKED };
void wait() noexcept;
void wake() noexcept;
};
} // namespace utils
#endif // TNT_UTILS_LINUX_MUTEX_H

View File

@@ -0,0 +1,113 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_MEMALIGN_H
#define TNT_UTILS_MEMALIGN_H
#include <type_traits>
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#if defined(WIN32)
#include <malloc.h>
#endif
namespace utils {
inline void* aligned_alloc(size_t size, size_t align) noexcept {
// 'align' must be a power of two and a multiple of sizeof(void*)
align = (align < sizeof(void*)) ? sizeof(void*) : align;
assert(align && !(align & align - 1));
assert((align % sizeof(void*)) == 0);
void* p = nullptr;
#if defined(WIN32)
p = ::_aligned_malloc(size, align);
#else
::posix_memalign(&p, align, size);
#endif
return p;
}
inline void aligned_free(void* p) noexcept {
#if defined(WIN32)
::_aligned_free(p);
#else
::free(p);
#endif
}
/*
* This allocator can be used with std::vector for instance to ensure all items are aligned
* to their alignof(). e.g.
*
* template<typename T>
* using aligned_vector = std::vector<T, utils::STLAlignedAllocator<T>>;
*
* aligned_vector<Foo> foos;
*
*/
template<typename TYPE>
class STLAlignedAllocator {
static_assert(!(alignof(TYPE) & (alignof(TYPE) - 1)), "alignof(T) must be a power of two");
public:
using value_type = TYPE;
using pointer = TYPE*;
using const_pointer = const TYPE*;
using reference = TYPE&;
using const_reference = const TYPE&;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using propagate_on_container_move_assignment = std::true_type;
using is_always_equal = std::true_type;
template<typename T>
struct rebind { using other = STLAlignedAllocator<T>; };
inline STLAlignedAllocator() noexcept = default;
template<typename T>
inline explicit STLAlignedAllocator(const STLAlignedAllocator<T>&) noexcept {}
inline ~STLAlignedAllocator() noexcept = default;
inline pointer allocate(size_type n) noexcept {
return (pointer)aligned_alloc(n * sizeof(value_type), alignof(TYPE));
}
inline void deallocate(pointer p, size_type) {
aligned_free(p);
}
// stateless allocators are always equal
template<typename T>
bool operator==(const STLAlignedAllocator<T>&) const noexcept {
return true;
}
template<typename T>
bool operator!=(const STLAlignedAllocator<T>&) const noexcept {
return false;
}
};
} // namespace utils
#endif // TNT_UTILS_MEMALIGN_H

View File

@@ -0,0 +1,140 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_OSTREAM_H
#define TNT_UTILS_OSTREAM_H
#include <utils/bitset.h>
#include <utils/compiler.h>
#include <utils/PrivateImplementation.h>
#include <string>
#include <string_view>
#include <utility>
namespace utils::io {
struct ostream_;
class UTILS_PUBLIC ostream : protected utils::PrivateImplementation<ostream_> {
friend struct ostream_;
public:
virtual ~ostream();
ostream& operator<<(short value) noexcept;
ostream& operator<<(unsigned short value) noexcept;
ostream& operator<<(char value) noexcept;
ostream& operator<<(unsigned char value) noexcept;
ostream& operator<<(int value) noexcept;
ostream& operator<<(unsigned int value) noexcept;
ostream& operator<<(long value) noexcept;
ostream& operator<<(unsigned long value) noexcept;
ostream& operator<<(long long value) noexcept;
ostream& operator<<(unsigned long long value) noexcept;
ostream& operator<<(float value) noexcept;
ostream& operator<<(double value) noexcept;
ostream& operator<<(long double value) noexcept;
ostream& operator<<(bool value) noexcept;
ostream& operator<<(const void* value) noexcept;
ostream& operator<<(const char* string) noexcept;
ostream& operator<<(const unsigned char* string) noexcept;
ostream& operator<<(std::string const& s) noexcept;
ostream& operator<<(std::string_view const& s) noexcept;
ostream& operator<<(ostream& (* f)(ostream&)) noexcept { return f(*this); }
ostream& dec() noexcept;
ostream& hex() noexcept;
protected:
ostream& print(const char* format, ...) noexcept;
class Buffer {
public:
Buffer() noexcept;
~Buffer() noexcept;
Buffer(const Buffer&) = delete;
Buffer& operator=(const Buffer&) = delete;
const char* get() const noexcept { return buffer; }
std::pair<char*, size_t> grow(size_t s) noexcept;
void advance(ssize_t n) noexcept;
void reset() noexcept;
private:
void reserve(size_t newSize) noexcept;
char* buffer = nullptr; // buffer address
char* curr = nullptr; // current pointer
size_t size = 0; // size remaining
size_t capacity = 0; // total capacity of the buffer
};
Buffer& getBuffer() noexcept;
Buffer const& getBuffer() const noexcept;
private:
virtual ostream& flush() noexcept = 0;
friend ostream& hex(ostream& s) noexcept;
friend ostream& dec(ostream& s) noexcept;
friend ostream& endl(ostream& s) noexcept;
friend ostream& flush(ostream& s) noexcept;
enum type {
SHORT, USHORT, CHAR, UCHAR, INT, UINT, LONG, ULONG, LONG_LONG, ULONG_LONG, FLOAT, DOUBLE,
LONG_DOUBLE
};
const char* getFormat(type t) const noexcept;
};
// handles utils::bitset
inline ostream& operator << (ostream& o, utils::bitset32 const& s) noexcept {
return o << (void*)uintptr_t(s.getValue());
}
// handles vectors from libmath (but we do this generically, without needing a dependency on libmath)
template<template<typename T> class VECTOR, typename T>
inline ostream& operator<<(ostream& stream, const VECTOR<T>& v) {
stream << "< ";
for (size_t i = 0; i < v.size() - 1; i++) {
stream << v[i] << ", ";
}
stream << v[v.size() - 1] << " >";
return stream;
}
inline ostream& hex(ostream& s) noexcept { return s.hex(); }
inline ostream& dec(ostream& s) noexcept { return s.dec(); }
inline ostream& endl(ostream& s) noexcept { s << '\n'; return s.flush(); }
inline ostream& flush(ostream& s) noexcept { return s.flush(); }
} // namespace utils::io
#endif // TNT_UTILS_OSTREAM_H

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_SSTREAM_H
#define TNT_UTILS_SSTREAM_H
#include <utils/ostream.h>
namespace utils::io {
class sstream : public ostream {
public:
ostream& flush() noexcept override;
const char* c_str() const noexcept;
};
} // namespace utils::io
#endif // TNT_UTILS_SSTREAM_H

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_STRING_H
#define TNT_UTILS_STRING_H
#include <utils/ostream.h>
namespace utils {
float strtof_c(const char* start, char** end);
} // namespace utils
#endif // TNT_UTILS_STRING_H

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_TRAP_H
#define TNT_UTILS_TRAP_H
#include <csignal>
#if defined(WIN32)
#include <Windows.h>
#include <utils/unwindows.h>
#endif
namespace utils {
// This can be used as a programmatic breakpoint.
inline void debug_trap() noexcept {
#if defined(WIN32)
DebugBreak();
#else
std::raise(SIGINT);
#endif
}
} // namespace utils
#endif // TNT_UTILS_TRAP_H

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if defined (WIN32)
#ifdef max
#undef max
#endif
#ifdef min
#undef min
#endif
#ifdef far
#undef far
#endif
#ifdef near
#undef near
#endif
#ifdef ERROR
#undef ERROR
#endif
#ifdef OPAQUE
#undef OPAQUE
#endif
#ifdef TRANSPARENT
#undef TRANSPARENT
#endif
#ifdef PURE
#undef PURE
#endif
#endif

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_VECTOR_H
#define TNT_UTILS_VECTOR_H
#include <algorithm>
#include <vector>
namespace utils {
/**
* Inserts the specified item in the vector at its sorted position.
*/
template <typename T>
static inline void insert_sorted(std::vector<T>& v, T item) {
auto pos = std::lower_bound(v.begin(), v.end(), item);
v.insert(pos, std::move(item));
}
/**
* Inserts the specified item in the vector at its sorted position.
* The item type must implement the < operator. If the specified
* item is already present in the vector, this method returns without
* inserting the item again.
*
* @return True if the item was inserted at is sorted position, false
* if the item already exists in the vector.
*/
template <typename T>
static inline bool insert_sorted_unique(std::vector<T>& v, T item) {
if (UTILS_LIKELY(v.size() == 0 || v.back() < item)) {
v.push_back(item);
return true;
}
auto pos = std::lower_bound(v.begin(), v.end(), item);
if (UTILS_LIKELY(pos == v.end() || item < *pos)) {
v.insert(pos, std::move(item));
return true;
}
return false;
}
} // end utils namespace
#endif // TNT_UTILS_VECTOR_H

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_UTILS_WIN32_STDTYPES_H
#define TNT_UTILS_WIN32_STDTYPES_H
#if defined(WIN32)
#include <windows.h>
// Copied from linux libc sys/stat.h:
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#define PATH_MAX (MAX_PATH)
// For getcwd
#include <direct.h>
#define getcwd _getcwd
#endif
#endif // TNT_UTILS_WIN32_STDTYPES_H