diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2019-10-24 17:58:24 -0400 |
|---|---|---|
| committer | Tim Foley <tfoleyNV@users.noreply.github.com> | 2019-10-24 14:58:24 -0700 |
| commit | 89ddb50eaccc1b7b590dbde55032721762711fb2 (patch) | |
| tree | e61da2c1604e0d52d3a9915363769ccf950b62f3 /source/core | |
| parent | 58ad4b1a9ca43098a071c42bd752a4a48405bf0e (diff) | |
OffsetContainer serialization (#1093)
* OffsetContainer with unit tests.
* State serialization working with OffsetContainer.
* Fixes to make work with OffsetContainer.
* Added OffsetContainer documentation.
* Remove RelativeContainer.
* Fix problem with + on Offset32Ptr on windows x86 target.
* * Made OffsetBase a base class of OffsetContainer.
* Added MemoryOffsetBase to just handle being a chunk of memory.
* * Use operator[] to access contents of OffsetContainer
* Fix the type hash to work across different size_t sizes.
* Fixed some Offset type related comments.
* Fix bug around using asBase, because it returns a reference just using 'auto' will means it becomes a value type.
Remove assignment and copy ctor from OffsetBase.
* Evaluation order of assignment can lead to wrong behavior with Offset32Ptr/raw pointers. Document the fact, and fix in StateSerializeUtil.
Diffstat (limited to 'source/core')
| -rw-r--r-- | source/core/core.vcxproj | 4 | ||||
| -rw-r--r-- | source/core/core.vcxproj.filters | 12 | ||||
| -rw-r--r-- | source/core/slang-offset-container.cpp (renamed from source/core/slang-relative-container.cpp) | 83 | ||||
| -rw-r--r-- | source/core/slang-offset-container.h | 393 | ||||
| -rw-r--r-- | source/core/slang-relative-container.h | 423 |
5 files changed, 433 insertions, 482 deletions
diff --git a/source/core/core.vcxproj b/source/core/core.vcxproj index 0a1b070fe..5b42dc81b 100644 --- a/source/core/core.vcxproj +++ b/source/core/core.vcxproj @@ -188,10 +188,10 @@ <ClInclude Include="slang-math.h" /> <ClInclude Include="slang-memory-arena.h" /> <ClInclude Include="slang-object-scope-manager.h" /> + <ClInclude Include="slang-offset-container.h" /> <ClInclude Include="slang-platform.h" /> <ClInclude Include="slang-process-util.h" /> <ClInclude Include="slang-random-generator.h" /> - <ClInclude Include="slang-relative-container.h" /> <ClInclude Include="slang-render-api-util.h" /> <ClInclude Include="slang-riff.h" /> <ClInclude Include="slang-secure-crt.h" /> @@ -220,9 +220,9 @@ <ClCompile Include="slang-io.cpp" /> <ClCompile Include="slang-memory-arena.cpp" /> <ClCompile Include="slang-object-scope-manager.cpp" /> + <ClCompile Include="slang-offset-container.cpp" /> <ClCompile Include="slang-platform.cpp" /> <ClCompile Include="slang-random-generator.cpp" /> - <ClCompile Include="slang-relative-container.cpp" /> <ClCompile Include="slang-render-api-util.cpp" /> <ClCompile Include="slang-riff.cpp" /> <ClCompile Include="slang-shared-library.cpp" /> diff --git a/source/core/core.vcxproj.filters b/source/core/core.vcxproj.filters index 56bb6b8b7..8184637f2 100644 --- a/source/core/core.vcxproj.filters +++ b/source/core/core.vcxproj.filters @@ -63,6 +63,9 @@ <ClInclude Include="slang-object-scope-manager.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="slang-offset-container.h"> + <Filter>Header Files</Filter> + </ClInclude> <ClInclude Include="slang-platform.h"> <Filter>Header Files</Filter> </ClInclude> @@ -72,9 +75,6 @@ <ClInclude Include="slang-random-generator.h"> <Filter>Header Files</Filter> </ClInclude> - <ClInclude Include="slang-relative-container.h"> - <Filter>Header Files</Filter> - </ClInclude> <ClInclude Include="slang-render-api-util.h"> <Filter>Header Files</Filter> </ClInclude> @@ -155,13 +155,13 @@ <ClCompile Include="slang-object-scope-manager.cpp"> <Filter>Source Files</Filter> </ClCompile> - <ClCompile Include="slang-platform.cpp"> + <ClCompile Include="slang-offset-container.cpp"> <Filter>Source Files</Filter> </ClCompile> - <ClCompile Include="slang-random-generator.cpp"> + <ClCompile Include="slang-platform.cpp"> <Filter>Source Files</Filter> </ClCompile> - <ClCompile Include="slang-relative-container.cpp"> + <ClCompile Include="slang-random-generator.cpp"> <Filter>Source Files</Filter> </ClCompile> <ClCompile Include="slang-render-api-util.cpp"> diff --git a/source/core/slang-relative-container.cpp b/source/core/slang-offset-container.cpp index 0b52f4268..5fed2a452 100644 --- a/source/core/slang-relative-container.cpp +++ b/source/core/slang-offset-container.cpp @@ -1,14 +1,11 @@ -// slang-relative-containere.cpp -#include "slang-relative-container.h" +// slang-offset-container.cpp +#include "slang-offset-container.h" namespace Slang { +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! OffsetString !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ -/* static */RelativeBase RelativeBase::g_null = { nullptr }; - -/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! RelativeString !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ - -size_t RelativeString::calcEncodedSize(size_t size, uint8_t encode[kMaxSizeEncodeSize]) +size_t OffsetString::calcEncodedSize(size_t size, uint8_t encode[kMaxSizeEncodeSize]) { SLANG_ASSERT(size <= 0xffffffff); if (size <= kSizeBase) @@ -32,7 +29,7 @@ size_t RelativeString::calcEncodedSize(size_t size, uint8_t encode[kMaxSizeEncod return num + 1; } -/* static */const char* RelativeString::decodeSize(const char* in, size_t& outSize) +/* static */const char* OffsetString::decodeSize(const char* in, size_t& outSize) { const uint8_t* cur = (const uint8_t*)in; if (*cur <= kSizeBase) @@ -72,7 +69,7 @@ size_t RelativeString::calcEncodedSize(size_t size, uint8_t encode[kMaxSizeEncod } } -/* static */size_t RelativeString::calcAllocationSize(size_t stringSize) +/* static */size_t OffsetString::calcAllocationSize(size_t stringSize) { uint8_t encode[kMaxSizeEncodeSize]; size_t encodeSize = calcEncodedSize(stringSize, encode); @@ -80,12 +77,12 @@ size_t RelativeString::calcEncodedSize(size_t size, uint8_t encode[kMaxSizeEncod return encodeSize + stringSize + 1; } -/* static */size_t RelativeString::calcAllocationSize(const UnownedStringSlice& slice) +/* static */size_t OffsetString::calcAllocationSize(const UnownedStringSlice& slice) { return calcAllocationSize(slice.size()); } -UnownedStringSlice RelativeString::getSlice() const +UnownedStringSlice OffsetString::getSlice() const { size_t size; const char* chars = decodeSize(m_sizeThenContents, size); @@ -93,41 +90,43 @@ UnownedStringSlice RelativeString::getSlice() const return UnownedStringSlice(chars, size); } -const char* RelativeString::getCstr() const +const char* OffsetString::getCstr() const { return getSlice().begin(); } -/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! RelativeContainer !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! OffsetContainer !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ -RelativeContainer::RelativeContainer() +OffsetContainer::OffsetContainer() { - m_current = 0; m_capacity = 0; - m_base.m_data = nullptr; + m_data = nullptr; + + // We need to allocate some of the first bytes 0 can be used for nullptr. + allocateAndZero(kStartOffset, 1); } -RelativeContainer::~RelativeContainer() +OffsetContainer::~OffsetContainer() { - if (m_base.m_data) + if (m_data) { - ::free(m_base.m_data); + ::free(m_data); } } -void* RelativeContainer::allocate(size_t size) +void* OffsetContainer::allocate(size_t size) { return allocate(size, 1); } -void RelativeContainer::fixAlignment(size_t alignment) +void OffsetContainer::fixAlignment(size_t alignment) { allocate(0, alignment); } -void* RelativeContainer::allocate(size_t size, size_t alignment) +void* OffsetContainer::allocate(size_t size, size_t alignment) { - size_t offset = (m_current + alignment - 1) & ~(alignment - 1); + size_t offset = (m_dataSize + alignment - 1) & ~(alignment - 1); if (offset + size > m_capacity) { @@ -148,29 +147,29 @@ void* RelativeContainer::allocate(size_t size, size_t alignment) size_t newSize = (calcSize < minSize) ? minSize : calcSize; // Reallocate space - m_base.m_data = (uint8_t*)::realloc(m_base.m_data, newSize); + m_data = (uint8_t*)::realloc(m_data, newSize); m_capacity = newSize; } SLANG_ASSERT(offset + size <= m_capacity); - m_current = offset + size; - return m_base.m_data + offset; + m_dataSize = offset + size; + return m_data + offset; } -void* RelativeContainer::allocateAndZero(size_t size, size_t alignment) +void* OffsetContainer::allocateAndZero(size_t size, size_t alignment) { void* data = allocate(size, alignment); memset(data, 0, size); return data; } -Safe32Ptr<RelativeString> RelativeContainer::newString(const UnownedStringSlice& slice) +Offset32Ptr<OffsetString> OffsetContainer::newString(const UnownedStringSlice& slice) { size_t stringSize = slice.size(); - uint8_t head[RelativeString::kMaxSizeEncodeSize]; - size_t headSize = RelativeString::calcEncodedSize(stringSize, head); + uint8_t head[OffsetString::kMaxSizeEncodeSize]; + size_t headSize = OffsetString::calcEncodedSize(stringSize, head); size_t allocSize = headSize + stringSize + 1; uint8_t* bytes = (uint8_t*)allocate(allocSize); @@ -181,12 +180,12 @@ Safe32Ptr<RelativeString> RelativeContainer::newString(const UnownedStringSlice& // 0 terminate bytes[headSize + stringSize] = 0; - return Safe32Ptr<RelativeString>(getOffset(bytes), &m_base); + return Offset32Ptr<OffsetString>(getOffset(bytes)); } -Safe32Ptr<RelativeString> RelativeContainer::newString(const char* contents) +Offset32Ptr<OffsetString> OffsetContainer::newString(const char* contents) { - Safe32Ptr<RelativeString> relString; + Offset32Ptr<OffsetString> relString; if (contents) { relString = newString(UnownedStringSlice(contents)); @@ -194,22 +193,4 @@ Safe32Ptr<RelativeString> RelativeContainer::newString(const char* contents) return relString; } -void RelativeContainer::set(void* data, size_t size) -{ - if (m_base.m_data) - { - ::free(m_base.m_data); - m_base.m_data = nullptr; - } - - if (size > 0) - { - m_base.m_data = (uint8_t*)::malloc(size); - ::memcpy(m_base.m_data, data, size); - } - - m_current = size; - m_capacity = size; -} - } // namespace Slang diff --git a/source/core/slang-offset-container.h b/source/core/slang-offset-container.h new file mode 100644 index 000000000..95c3a6589 --- /dev/null +++ b/source/core/slang-offset-container.h @@ -0,0 +1,393 @@ +// slang-offset-container.h +#ifndef SLANG_OFFSET_CONTAINER_H_INCLUDED +#define SLANG_OFFSET_CONTAINER_H_INCLUDED + +#include "slang-basic.h" + +namespace Slang { + +/* +The purpose of OffsetContainer and related types is to provide a mechanism to easily serialize offset structures. + +The root idea here is the "offset pointer". A typical pointer in a language like C/C++ holds the absolute address +in the current address space of the thing that is being pointed to. This introduces a problem, as when data is +serialized in the contents will very likely be be placed at different addresses - meaning any absolute pointer +will point to the wrong place. There is also a related issue around pointer sizes - on some targets they are 32 bits +and on others 64 bits. + +An offset pointer means a pointer that points to something 'offset' to some base address. The OffsetPtr uses a 32 bit +offset from the pointers location in memory. This means such a pointer can address a 4Gb address space. + +Special care is needed when using offset pointers - both when constructing structures that contain them, reading +them and in general usage. + +For simplicity here we store all offset pointers within a single contiguous allocation. This allocation is +typically managed by the OffsetContainer for writing. When reading a MemoryOffsetBase can be used. + +An issue around using offset pointers, is that we cannot directly access it's contents, because it's just an +offset to some base address. Thus to access the thing being pointed to we need to turn the offset pointer back into +a 'raw' pointer. This is achieved via using the asRaw methods on the OffsetBase. For a convenience operator[] can also +be used, and this is typically the preferred mechanism. + +NOTE! That the evaluation order of a function calls parameters is undefined in C++. That whilst it might appear doing + +``` +base[thing] = container.newObject<Thing>(); +``` + +will evaluate the construction of newObject *before* the assignment, if you look at the assignment as being a function call +(as it is when it is overloaded), then base[thing] might be evaluated *before* newObject, and if it is then the result +could be wrong if the newObject needed to reallocate. Therefore when allocation is involved, a new (or any allocation backed +function call from the OffsetContainer) should always place a result in a local variable. Then assign as in + +``` +auto anotherThing = container.newObject<Thing>(); +base[thing] = anotherThing; +``` + +When creating structures - unless you know the allocated space (in the OffsetContainer or some other piece of memory) +is larger than required, then special care is needed, because when a new larger piece of memory is allocated to hold +everything, raw pointers pointers will likely be invalidated. When reading there is typically no need to move +the base address, so raw pointers remain valid through out. When doing writing if a call is made to something that +allocates memory on the OffsetContainer - any raw pointer should be assumed invalid. + +For example + +``` + +struct Thing +{ + Offset32Ptr<OffsetString> text; + int value; +}; + +void func() +{ + OffsetContainer container; + OffsetBase& base = container.asBase(); + + { + // We can allocate on the heap. BUT we can't set up a offset pointer to it + Thing thing; + // BAD!! Will assert, because thing is not in the address range recorded in base. + Offset32Ptr<Thing> thingOffsetPtr= base->asPtr(&thing); + } + + // Ok - this is now correct + Offset32Ptr<Thing> thing = container.newObject<Thing>(); + + // To write values, we need a raw pointer + { + // To get the raw pointer we can use 'asRaw' + auto rawThing = base->asRaw(thing); + + // Or more perhaps slightly more conveniently [] + auto rawThing = base[thing]; + + // We can write and read things via the Safe32Ptr + rawThing->value = 10; + const int value = rawThing->value; + + SLANG_ASSERT(value == 10); + } + + // Now lets write to it + { + // We can have raw pointer (or reference) to a thing but we need to be *careful* if we allocate + Thing* rawThing = base[thing]; + // We are okay here, nothing between getting the raw pointer and the write allocated/newed anything on the OffsetContainer + rawThing->value = 20; + + // Lets set up name + Offset32Ptr<OffsetString> text = offsetContainer.newString("Hello World!"); + + // BAD! The rawThing point could now be invalid because the call to newString may have had to allocate more memory + rawThing->text = text; + + // This is okay + base[thing]->text = text; + + // Or we can update rawThing such that is up to date + rawThing = base[thing]; + // So now this is okay again + rawThing->text = text; + + // BAD! we don't know the evaluation order here, if the lhs is evaluate before the rhs, then it could write to the wrong area of memory. + base[thing]->text = offsetContainer.newString("Hello World again!"); + + // So where there is allocation, and assignment to something that in held in offset ptr use a local for the allocation as in + { + auto text = offsetContainer.newString("Hello World again!"); + base[thing]->text = text; + } + } +} + +``` +*/ + +enum +{ + kNull32Offset = 0, + kStartOffset = uint32_t(sizeof(uint64_t)), ///< The offset to the first contained thing +}; + +template <typename T> +class Offset32Ref; + +/* A pointer to items held in OffsetContainer (or OffsetBase relative) that remains correct even if +the memory inside OffsetContainer moves. +*/ +template <typename T> +class Offset32Ptr +{ +public: + typedef Offset32Ptr ThisType; + + const ThisType& operator=(const ThisType& rhs) { m_offset = rhs.m_offset; return *this; } + bool operator==(const ThisType& rhs) const { return m_offset == rhs.m_offset; } + bool operator!=(const ThisType& rhs) const { return m_offset != rhs.m_offset; } + + bool operator<(const ThisType& rhs) const { return m_offset < rhs.m_offset; } + bool operator<=(const ThisType& rhs) const { return m_offset <= rhs.m_offset; } + bool operator>(const ThisType& rhs) const { return m_offset > rhs.m_offset; } + bool operator>=(const ThisType& rhs) const { return m_offset >= rhs.m_offset; } + + operator bool() const { return m_offset != kNull32Offset; } + + Offset32Ref<T> operator*(); + + ThisType& operator++() { m_offset += uint32_t(sizeof(T)); return *this; } + ThisType operator++(int) { const auto offset = m_offset; m_offset += uint32_t(sizeof(T)); return ThisType(offset); } + + ThisType& operator--() { m_offset -= sizeof(T); return *this; } + ThisType operator--(int) { const auto offset = m_offset; m_offset -= uint32_t(sizeof(T)); return ThisType(offset); } + + friend ThisType operator+(const ThisType& a, Index b) { return ThisType(a.m_offset + uint32_t(sizeof(T) * b)); } + friend ThisType operator+(Index a, const ThisType& b) { return ThisType(b.m_offset + uint32_t(sizeof(T) * a)); } + + bool isNull() const { return m_offset == kNull32Offset; } + + void setNull() { m_offset = kNull32Offset; } + Offset32Ptr():m_offset(kNull32Offset) {} + Offset32Ptr(const ThisType& rhs): m_offset(rhs.m_offset) {} + explicit Offset32Ptr(uint32_t offset): m_offset(offset) {} + + uint32_t m_offset; +}; + +/* A reference to items held in OffsetContainer (or OffsetBase relative) that remains correct even if +the memory inside OffsetContainer moves. +*/ +template <typename T> +class Offset32Ref +{ +public: + typedef Offset32Ref ThisType; + + const ThisType& operator=(const ThisType& rhs) { m_offset = rhs.m_offset; return *this; } + + Offset32Ptr<T> operator&() { return Offset32Ptr<T>(m_offset); } + + Offset32Ref(const ThisType& rhs) : m_offset(rhs.m_offset) {} + explicit Offset32Ref(uint32_t offset) : m_offset(offset) { SLANG_ASSERT(offset != kNull32Offset); } + + uint32_t m_offset; +}; + +// --------------------------------------------------------------------------- +template <typename T> +SLANG_FORCE_INLINE Offset32Ref<T> Offset32Ptr<T>::operator*() +{ + return Offset32Ref<T>(m_offset); +} + + +/* Much like Offset32Ptr this is an array but whose memory is stored inside the OffsetContainer. This means elements types +must be 'offset types'. */ +template <typename T> +class Offset32Array +{ +public: + Offset32Ptr<const T> begin() const { return Offset32Ptr<const T>(m_data.m_offset); } + Offset32Ptr<const T> end() const { return begin() + Index(m_count); } + + Offset32Ptr<T> begin() { return m_data; } + Offset32Ptr<T> end() { return begin() + Index(m_count); } + + Index getCount() const { return Index(m_count); } + + Offset32Ref<const T> operator[](Index i) const { SLANG_ASSERT(i >= 0 && uint32_t(i) < m_count); return Offset32Ref<const T>((m_data + i).m_offset); } + Offset32Ref<T> operator[](Index i) { SLANG_ASSERT(i >= 0 && uint32_t(i) < m_count); return Offset32Ref<T>((m_data + i).m_offset); } + + Offset32Array(Offset32Ptr<T> data, uint32_t count) :m_data(data), m_count(count) {} + + Offset32Array() :m_count(0) {} + + Offset32Ptr<T> m_data; + uint32_t m_count; +}; + +/** OffsetString is used for storing strings within a OffsetContainer. Strings are stored with the initial byte indicating the size +of the string. Note that all offset strings are stored with a terminating zero, and that the terminating zero is *NOT* included in +the encoded size. */ +struct OffsetString +{ + enum + { + kSizeBase = 251, + kMaxSizeEncodeSize = 5, + }; + + /// Get contents as a slice + UnownedStringSlice getSlice() const; + /// Get null terminated string + const char* getCstr() const; + + /// Decode the size. Returns the start of the string text, and outSize holds the size (NOT including terminating 0) + static const char* decodeSize(const char* in, size_t& outSize); + + /// Returns the amount of bytes used, end encoding in 'encode' + static size_t calcEncodedSize(size_t size, uint8_t encode[kMaxSizeEncodeSize]); + /// Calculate the total size needed to store the string *including* terminating 0 + static size_t calcAllocationSize(const UnownedStringSlice& slice); + + /// Calculate the total size needed to store string. Size should be passed *without* terminating 0 + static size_t calcAllocationSize(size_t size); + + char m_sizeThenContents[1]; +}; + +/* A type that is used to hold the base address of the contiguous memory that holds either Offset32Ptr and related types> +*/ +class OffsetBase +{ +public: + typedef OffsetBase ThisType; + + /// Turn an offset into a raw regular pointer or reference + template <typename T> + T* asRaw(const Offset32Ptr<T>& ptr) { return (T*)_getRaw(ptr.m_offset); } + template <typename T> + T& asRaw(const Offset32Ref<T>& ref) { return *(T*)_getRaw(ref.m_offset); } + + /// A more terse way to get a raw pointer/reference. Using the [] operator can be seen as 'indexing' to access the + /// object the offset relates to. Unlike 'indices' that are typically used with [] offsets are generally not contiguous. + template <typename T> + T* operator[](const Offset32Ptr<T>& ptr) { return (T*)_getRaw(ptr.m_offset); } + template <typename T> + T& operator[](const Offset32Ref<T>& ref) { return *(T*)_getRaw(ref.m_offset); } + + template <typename T> + Offset32Ptr<T> asPtr(T* ptr) { return Offset32Ptr<T>(getOffset(ptr)); } + /// Note the use of ptr when setting up a reference here - it's needed because a ref does not have to be backed by a pointer. + /// And commonly is not when the const& and the thing referenced can be held in a word. + template <typename T> + Offset32Ref<T> asRef(T* ptr) { SLANG_ASSERT(ptr); return Offset32Ref<T>(getOffset(ptr)); } + + uint32_t getOffset(const void* ptr) + { + if (ptr == nullptr) + { + return kNull32Offset; + } + ptrdiff_t diff = ((const uint8_t*)ptr) - m_data; + SLANG_ASSERT(diff > 0 && size_t(diff) < m_dataSize); + return uint32_t(diff); + } + + /// Get the contained data + SLANG_FORCE_INLINE uint8_t* getData() { return m_data; } + /// Return the last used byte of the data + SLANG_FORCE_INLINE size_t getDataCount() const { return m_dataSize; } + + /// Get the first allocated thing. Typically the root of the structure contained + void* getFirst() { return (m_dataSize < kStartOffset) ? nullptr : (m_data + kStartOffset); } + + /// Get a raw pointer from the offset + uint8_t* _getRaw(uint32_t offset) { return (offset == kNull32Offset) ? nullptr : (m_data + offset); } + + OffsetBase(): + m_data(nullptr), + m_dataSize(0) + { + } + + + + uint8_t* m_data; + size_t m_dataSize; + +protected: + /// We want protected, because we don't want copies to be made of OffsetBase by default! + OffsetBase(const ThisType& rhs) = default; + ThisType& operator=(const ThisType& rhs) = default; +}; + +class MemoryOffsetBase : public OffsetBase +{ +public: + void set(void* data, size_t dataSize) + { + m_data = (uint8_t*)data; + m_dataSize = dataSize; + } +}; + +/* OffsetContainer is a type designed to manage the construction structures around 'offset types'. In particular it allows +for construction of offset structures where their total encoded size is not known at the outset. + +The main mechanism to make this work is via the use of OffsetXXX types, which when constructed from the OffsetContainer will +maintain valid values, even if the underlying backing memories location is changed. +*/ +class OffsetContainer : public OffsetBase +{ +public: + + template <typename T> + Offset32Ptr<T> newObject() + { + void* data = allocate(sizeof(T), SLANG_ALIGN_OF(T)); + new (data) T(); + return Offset32Ptr<T>(getOffset(data)); + } + + template <typename T> + Offset32Array<T> newArray(size_t size) + { + if (size == 0) + { + return Offset32Array<T>(); + } + T* data = (T*)allocate(sizeof(T) * size, SLANG_ALIGN_OF(T)); + for (size_t i = 0; i < size; ++i) + { + new (data + i) T(); + } + return Offset32Array<T>(Offset32Ptr<T>(getOffset(data)), uint32_t(size)); + } + + /// Get the base - which is needed for turning offsets into things + OffsetBase& asBase() { return *this; } + + /// Allocate without alignment (effectively 1) + void* allocate(size_t size); + void* allocate(size_t size, size_t alignment); + void* allocateAndZero(size_t size, size_t alignment); + + void fixAlignment(size_t alignment); + + Offset32Ptr<OffsetString> newString(const UnownedStringSlice& slice); + Offset32Ptr<OffsetString> newString(const char* contents); + + /// Ctor + OffsetContainer(); + ~OffsetContainer(); + +protected: + size_t m_capacity; +}; + +} // namespace Slang + +#endif diff --git a/source/core/slang-relative-container.h b/source/core/slang-relative-container.h deleted file mode 100644 index 2f7e881b5..000000000 --- a/source/core/slang-relative-container.h +++ /dev/null @@ -1,423 +0,0 @@ -// slang-relative-container.h -#ifndef SLANG_RELATIVE_CONTAINER_H_INCLUDED -#define SLANG_RELATIVE_CONTAINER_H_INCLUDED - -#include "slang-basic.h" - -namespace Slang { - -/* -The purpose of RelativeContainer and related types is to provide a mechanism to easily serialize relative structures. - -The root idea here is the "relative pointer". A typical pointer in a language like C/C++ holds the absolute address -of the thing that is being pointed to. This introduces a problem if the structure is serialized in that -it is highly likely that the structures will be placed at different addresses. This means that the absolute -pointers will not point to the correct places, and so not be usable when read back from a serialization, or -when moved to another location. - -A relative pointer means a pointer that points to something relative -to the *location of the pointer*. The Relative32Ptr uses a 32 bit offset from the pointers location in memory. This -means such a pointer can address a 4Gb address space, but more realistically it gives it a 2Gb address space as this -is the size such that a pointer at any address can point to any other address. For specialized uses, it can be useful -to have 8 bit, 16 bit, 64 bit and scaled relative pointers. For the purposes here though 32 bits works well enough -for current use cases. - -Special care is needed when using relative pointers - both when constructing structures that contain them, reading -them and in general usage. - -For simplicity here we store all relative pointers within a single contiguous allocation. This allocation is -typically managed by the RelativeContainer. For simplicity it's easiest to claim that all relative pointers -*can only exist* in this address space. That is no relative pointer should be decared as a variable on the stack, and -similarly no struct, or other derived type holding a relative pointer should be held on the stack. - -Why? Most simply in a 64bit address space there is no guarentee that say a 32bit relative pointer *can* point to the -memory in the RelativeContainer. For similar reasons relative ptrs cannot be held in the heap, or in a typical ADT -container (like std::vector). In summary RelativePtr can *only* be stored in contiguous chunk of memory designed for -the purpose - such as RelativeContainer, or a continuous chunk of memory that has been serialized in. - -This presents a problem - in how do we create and use such structures? For reading the process is simple - in that we -can just turn the relative pointer into a regular raw pointer and use it. When creating structures - unless you know -the allocated space (in the RelativeContainer or some other piece of memory) is larger than required, then special care -is needed, because when a new larger piece of memory is allocated to hold everything, all of the absolute pointers -will likely be invalidated. - -To work around this problem we have the Safe32Ptr. This is a pointer which holds a pointer relative to the start of the -allocation as well as knowing what the base allocation is. So if there is a change in the base allocation address - -for example when the RelativeContainer resizes the backing memory, the Safe32Ptr is aware of the change and everything continues to -work as expected. Safe32Ptr are much more like normal pointers - and they can be stored on the stack or in other structures -like say a vector without problems. On the other hand a Safe32Ptr *cannot* be stored within stuctures held within the RelativeContainer, -as it would no longer have the correct properties to be serializable (it would contain an absolute pointer - the one in the SafePtr). - -We can divide types into to sets. 'Relative types' and 'Non relative types'. 'Relative types' are Relative pointers/arrays, -and value types (such as float, int etc), and POD types constructed from those types. Everything else is not a 'relative type'. -Any relative type that has a relative pointer (such as Relative32Ptr and Relative32Array) can only be allocated in a 'relative aware' -piece or memory such as RelativeContainer, or suitable contiguous piece of memory. - -So in basic usage - Relative32Ptr can only be stored inside of RelativeContainer or part of contiguous memory arranged to -support them. Conversely Safe32Ptr can only be used outside of the places RelativePtrs can be used. -They can both point to the same things though. RelativePtrs can be thought of pointers for use in serialization, and SafePtrs -as pointers used to construct things using RelativePtrs. - -With that out of the way there is one last caveat - and it is around use of Safe32Ptr. That the Safe32Ptr is safe to use -even if the underlying memory is moved. But raw pointers (which are of course absolute) from it are only valid whilst there -are no changes to the location of memory. That Relative32Ptr and Safe32Ptrs are convertible between each other. - -For example - -``` - -struct Thing -{ - Relative32Ptr<RelativeString> text; - int value; -}; - -void func() -{ - RelativeContainer container; - - // BAD! Can't construct anything containing a Relative32Ptr on the stack. - Thing thing; - - // BAD! We are closer - thing is constructed in the container, but we cannot have Relative32Ptrs held on the stack. - Relative32Ptr<Thing> thing = container.newObject<Thing>(); - - // Ok - this is now correct - Safe32Ptr<Thing> thing = container.newObject<Thing>(); - - // We can write and read things via the Safe32Ptr - thing->value = 10; - const int value = thing->value; - - // Now lets write to it - { - // We can have raw pointer (or reference) to a thing but we need to be *careful* if we allocate - Thing* rawThing = thing; - // We are okay here, nothing between getting the raw pointer and the write allocated/newed anything on the RelativeContainer - rawThing->value = 20; - - // Lets set up name - Safe32Ptr<RelativeString> text = relativeContainer.newString("Hello World!"); - - // BAD! Thr rawThing point could now be invalid because the call to newString may have had to allocate more memory - rawThing->text = text; - - // This is okay because access is through the Safe32Ptr - thing->text = text; - - // Or we can update rawThing such that is up to date - rawThing = thing; - // So now this is okay again - rawThing->text = text; - } -} - -``` - -Safe32Array and Relative32Array have very similar behaviors to Safe32Ptr and Relative32Ptr and can be used in the same places -for the same purposes, but their use revolves around arrays. The arrays data is always allocated in the *RelativeContainer* so -the arrays contents *even with* Safe32Array, can only contain Relative types. - -For example - -``` - -// BAD! The element types cannot contain any absolute pointers and that includes SafePtr -Safe32Array<Safe32Ptr<RelativeString>> array = container.newArray<Safe32Ptr<RelativeString>>(10); - -// Ok -Safe32Array<Relative32Ptr<RelativeString>> array = container.newArray<Relative32<Ptr<RelativeString>>(10); -// I can now set array element in the normal way -array[1] = container.newString("Hello"); -array[2] = container.newString("World!"); -``` - -*/ - - -/* A type that is used to hold the base address of the contiguous memory that holds either RelativePtr and related types -and/or is pointed to by Safe32Ptrs. - -The g_null member is a special singleton version that just holds m_data as nullptr, allows the representation of 'nullptr' on -a Safe32Ptr to be that RelativeBase with an offset of 0. -*/ -struct RelativeBase -{ - uint8_t* m_data; - - static RelativeBase g_null; -}; - -/* A pointer to items held in RelativeContainer that remains correct even if the memory inside RelativeContainer moves. -Safe32Ptr can be allocated on the stack, on the heap, used in containers such as List, std::vector. -*/ -template <typename T> -class Safe32Ptr -{ -public: - typedef Safe32Ptr ThisType; - - T& operator*() const { return *get(); } - T* operator->() const { return get(); } - operator T*() const { return get(); } - - const Safe32Ptr& operator=(const ThisType& rhs) { m_offset = rhs.m_offset; m_base = rhs.m_base; return *this; } - SLANG_FORCE_INLINE T* get() const { return (T*)(m_base->m_data + m_offset); } - - void setNull() - { - m_offset = 0; - m_base = &RelativeBase::g_null; - } - - Safe32Ptr(const ThisType& rhs) : m_offset(rhs.m_offset), m_base(rhs.m_base) {} - Safe32Ptr() : m_base(&RelativeBase::g_null), m_offset(0) {} - Safe32Ptr(uint32_t offset, RelativeBase* base) : m_offset(offset), m_base(base) {} - - RelativeBase* m_base; - uint32_t m_offset; -}; - - -enum -{ - kRelative32PtrNull = int32_t(0x80000000) -}; - -/* A 32 bit relative pointer. It can only be held in contiguous memory designed for it's usage (like RelativeContainer). The thing -that it points to is relative to the address *of the pointer*. - -This means that in normal usage should *not* be allocated on the stack, on the heap (unless as part of contiguous piece of memory -designed for usage), or in a container such as std::vector or List. - -That because pointers are relative, we use a special value `kRelative32PtrNull` to indicate a pointer is nullptr. 0 could not be used -because if we had - -``` -struct Thing -{ - RelativePtr<Thing> thingPtr; - int someThingElse; -}; -``` - -It might be valid for thingPtr to point to Thing and in that case it's offset would be 0, and this confused with nullptr if 0 was -used to represent nullptr. -*/ -template <typename T> -class Relative32Ptr -{ -public: - typedef Relative32Ptr ThisType; - - T& operator*() const { return *get(); } - T* operator->() const { return get(); } - operator T*() const { return get(); } - - T* get() - { - uint8_t* nonConstThis = (uint8_t*)this; - return (m_offset == kRelative32PtrNull) ? nullptr : (T*)(nonConstThis + m_offset); - } - T* get() const - { - uint8_t* nonConstThis = const_cast<uint8_t*>((const uint8_t*)this); - return (m_offset == kRelative32PtrNull) ? nullptr : (T*)(nonConstThis + m_offset); - } - - T* detach() { T* ptr = get(); m_offset = kRelative32PtrNull; } - - void setNull() { m_offset = kRelative32PtrNull; } - - SLANG_FORCE_INLINE void set(T* ptr) { m_offset = ptr ? int32_t(((uint8_t*)ptr) - ((const uint8_t*)this)) : uint32_t(kRelative32PtrNull); } - - Relative32Ptr(const Safe32Ptr<T>& rhs) { set(rhs.get()); } - Relative32Ptr(const ThisType& rhs) { set(rhs.get()); } - - Relative32Ptr() :m_offset(kRelative32PtrNull) {} - Relative32Ptr(T* ptr) { set(ptr); } - - const Relative32Ptr& operator=(const ThisType& rhs) { set(rhs.get()); return *this; } - const Relative32Ptr& operator=(const Safe32Ptr<T>& rhs) { set(rhs.get()); return *this; } - - int32_t m_offset; -}; - -/* Much like SafePtr this is an array but whose memory is stored inside the RelativeContainer. This means elements types -must be 'relative types'. */ -template <typename T> -class Safe32Array -{ -public: - const T* begin() const { return m_data; } - const T* end() const { return begin() + m_count; } - - T* begin() { return m_data; } - T* end() { return begin() + m_count; } - - Index getCount() const { return Index(m_count); } - T* getData() { return m_data.get(); } - const T* getData() const { return m_data.get(); } - - const T& operator[](Index i) const { SLANG_ASSERT(i >= 0 && uint32_t(i) < m_count); return m_data.get()[i]; } - T& operator[](Index i) { SLANG_ASSERT(i >= 0 && uint32_t(i) < m_count); return m_data.get()[i]; } - - Safe32Array(Safe32Ptr<T> data, uint32_t count):m_data(data), m_count(count) {} - - Safe32Array():m_count(0) {} - - Safe32Ptr<T> m_data; - uint32_t m_count; -}; - -/* Much like a RelativePtr this is an array whose elements are stored inside the RelativeContainer. This means element types can only be 'relative types'. */ -template <typename T> -class Relative32Array -{ -public: - typedef Relative32Array ThisType; - - const T* begin() const { return m_data; } - const T* end() const { return begin() + m_count; } - - T* begin() { return m_data; } - T* end() { return begin() + m_count; } - - Index getCount() const { return Index(m_count); } - T* getData() { return m_data.get(); } - const T* getData() const { return m_data.get(); } - - const T& operator[](Index i) const { SLANG_ASSERT(i >= 0 && uint32_t(i) < m_count); return m_data.get()[i]; } - T& operator[](Index i) { SLANG_ASSERT(i >= 0 && uint32_t(i) < m_count); return m_data.get()[i]; } - - Relative32Array(const Safe32Array<T>& rhs): - m_count(rhs.m_count), - m_data(rhs.m_data) - { - } - - Relative32Array() : m_count(0) {} - Relative32Array(const ThisType& rhs) : m_count(rhs.m_count), m_data(rhs.m_data) {} - - uint32_t m_count; ///< the size of the data - Relative32Ptr<T> m_data; ///< The data -}; - -/** RelativeString is used for storing strings within a RelativeContainer. Strings are stored with the initial byte indicating the size -of the string. Note that all relative strings are stored with a terminating zero, and that the terminating zero is *NOT* included in -the encoded size. */ -struct RelativeString -{ - enum - { - kSizeBase = 251, - kMaxSizeEncodeSize = 5, - }; - - /// Get contents as a slice - UnownedStringSlice getSlice() const; - /// Get null terminated string - const char* getCstr() const; - - /// Decode the size. Returns the start of the string text, and outSize holds the size (NOT including terminating 0) - static const char* decodeSize(const char* in, size_t& outSize); - - /// Returns the amount of bytes used, end encoding in 'encode' - static size_t calcEncodedSize(size_t size, uint8_t encode[kMaxSizeEncodeSize]); - /// Calculate the total size needed to store the string *including* terminating 0 - static size_t calcAllocationSize(const UnownedStringSlice& slice); - - /// Calculate the total size needed to store string. Size should be passed *without* terminating 0 - static size_t calcAllocationSize(size_t size); - - char m_sizeThenContents[1]; -}; - -/* RelativeContainer is a type designed to manage the construction structures around 'relative types'. In particular it allows -for construction of relative structures where their total relative encoded size is not known at the outset. - -The main mechanism to make this work is via the use of SafeXXX types, which when constructed from the RelativeContainer will -maintain valid values, even if the underlying backing memories location is changed. -*/ -class RelativeContainer -{ -public: - - template <typename T> - Safe32Ptr<T> newObject() - { - void* data = allocate(sizeof(T), SLANG_ALIGN_OF(T)); - new (data) T(); - return Safe32Ptr<T>(getOffset(data), &m_base); - } - - template <typename T> - Safe32Array<T> newArray(size_t size) - { - if (size == 0) - { - return Safe32Array<T>(); - } - T* data = (T*)allocate(sizeof(T) * size, SLANG_ALIGN_OF(T)); - for (size_t i = 0; i < size; ++i) - { - new (data + i) T(); - } - return Safe32Array<T>(Safe32Ptr<T>(getOffset(data), &m_base), uint32_t(size)); - } - - /// Make a raw pointer into a safe ptr - template <typename T> - Safe32Ptr<T> toSafe(T* in) - { - Safe32Ptr<T> dst; - if (in) - { - dst.m_base = &m_base; - dst.m_offset = getOffset(in); - } - return dst; - } - - /// Allocate without alignment (effectively 1) - void* allocate(size_t size); - void* allocate(size_t size, size_t alignment); - void* allocateAndZero(size_t size, size_t alignment); - - void fixAlignment(size_t alignment); - - SLANG_FORCE_INLINE uint32_t getOffset(const void* ptr) const - { - ptrdiff_t offset = ((const uint8_t*)ptr) - m_base.m_data; - SLANG_ASSERT(offset >= 0 && size_t(offset) < m_current); - return uint32_t(offset); - } - - Safe32Ptr<RelativeString> newString(const UnownedStringSlice& slice); - Safe32Ptr<RelativeString> newString(const char* contents); - - /// Get the contained data - uint8_t* getData() { return m_base.m_data; } - /// Return the last used byte of the data - size_t getDataCount() const { return m_current; } - - /// Set the contents - void set(void* data, size_t size); - - RelativeBase* getBase() { return &m_base; } - - /// Ctor - RelativeContainer(); - ~RelativeContainer(); - - -protected: - size_t m_current; - size_t m_capacity; - RelativeBase m_base; -}; - - -} // namespace Slang - -#endif |
