diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2019-10-23 10:25:11 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-10-23 10:25:11 -0400 |
| commit | 85f858c6d1b91fd2ed89844aae7535a48e51a4c8 (patch) | |
| tree | 982ee0330efb46cf8d99a02b6620bac5543e2b04 /source/core | |
| parent | 9c2d1766ea33101b551ac521ddc39516b98b6641 (diff) | |
Documentation around Relative types (#1089)
* * Added comments/documentation to better describe Relative/Safe types and usage
* Renamed allocate methods to newObject/newArray on RelativeContainer.
* Fix error introduced from automatic merge.
Diffstat (limited to 'source/core')
| -rw-r--r-- | source/core/slang-relative-container.h | 177 |
1 files changed, 173 insertions, 4 deletions
diff --git a/source/core/slang-relative-container.h b/source/core/slang-relative-container.h index 8416505d1..2f7e881b5 100644 --- a/source/core/slang-relative-container.h +++ b/source/core/slang-relative-container.h @@ -6,6 +6,141 @@ 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; @@ -13,6 +148,9 @@ struct RelativeBase 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 { @@ -46,6 +184,26 @@ 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 { @@ -85,6 +243,8 @@ public: 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 { @@ -110,7 +270,7 @@ public: 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 { @@ -143,6 +303,9 @@ public: 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 @@ -170,12 +333,18 @@ struct RelativeString 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> allocate() + Safe32Ptr<T> newObject() { void* data = allocate(sizeof(T), SLANG_ALIGN_OF(T)); new (data) T(); @@ -183,7 +352,7 @@ public: } template <typename T> - Safe32Array<T> allocateArray(size_t size) + Safe32Array<T> newArray(size_t size) { if (size == 0) { @@ -197,7 +366,7 @@ public: return Safe32Array<T>(Safe32Ptr<T>(getOffset(data), &m_base), uint32_t(size)); } - /// Make a pointer into a safe ptr + /// Make a raw pointer into a safe ptr template <typename T> Safe32Ptr<T> toSafe(T* in) { |
