yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Julius IkkalaReplace SLANG_ALIGN_OF with C++11 alignof (#7523)551d0c365

master
14.9 KiB491 linesraw
1// slang-offset-container.h
2#ifndef SLANG_OFFSET_CONTAINER_H_INCLUDED
3#define SLANG_OFFSET_CONTAINER_H_INCLUDED
4
5#include "slang-basic.h"
6
7namespace Slang
8{
9
10/*
11The purpose of OffsetContainer and related types is to provide a mechanism to easily serialize
12offset structures.
13
14The root idea here is the "offset pointer". A typical pointer in a language like C/C++ holds the
15absolute address in the current address space of the thing that is being pointed to. This introduces
16a problem, as when data is serialized in the contents will very likely be be placed at different
17addresses - meaning any absolute pointer will point to the wrong place. There is also a related
18issue around pointer sizes - on some targets they are 32 bits and on others 64 bits.
19
20An offset pointer means a pointer that points to something 'offset' to some base address. The
21OffsetPtr uses a 32 bit offset from the pointers location in memory. This means such a pointer can
22address a 4Gb address space.
23
24Special care is needed when using offset pointers - both when constructing structures that contain
25them, reading them and in general usage.
26
27For simplicity here we store all offset pointers within a single contiguous allocation. This
28allocation is typically managed by the OffsetContainer for writing. When reading a MemoryOffsetBase
29can be used.
30
31An issue around using offset pointers, is that we cannot directly access it's contents, because it's
32just an offset to some base address. Thus to access the thing being pointed to we need to turn the
33offset pointer back into a 'raw' pointer. This is achieved via using the asRaw methods on the
34OffsetBase. For a convenience operator[] can also be used, and this is typically the preferred
35mechanism.
36
37NOTE! That the evaluation order of a function calls parameters is undefined in C++. That whilst it
38might appear doing
39
40```
41base[thing] = container.newObject<Thing>();
42```
43
44will evaluate the construction of newObject *before* the assignment, if you look at the assignment
45as being a function call (as it is when it is overloaded), then base[thing] might be evaluated
46*before* newObject, and if it is then the result could be wrong if the newObject needed to
47reallocate. Therefore when allocation is involved, a new (or any allocation backed function call
48from the OffsetContainer) should always place a result in a local variable. Then assign as in
49
50```
51auto anotherThing = container.newObject<Thing>();
52base[thing] = anotherThing;
53```
54
55When creating structures - unless you know the allocated space (in the OffsetContainer or some other
56piece of memory) is larger than required, then special care is needed, because when a new larger
57piece of memory is allocated to hold everything, raw pointers pointers will likely be invalidated.
58When reading there is typically no need to move the base address, so raw pointers remain valid
59through out. When doing writing if a call is made to something that allocates memory on the
60OffsetContainer - any raw pointer should be assumed invalid.
61
62For example
63
64```
65
66struct Thing
67{
68    Offset32Ptr<OffsetString> text;
69    int value;
70};
71
72void func()
73{
74    OffsetContainer container;
75    OffsetBase& base = container.asBase();
76
77    {
78        // We can allocate on the heap. BUT we can't set up a offset pointer to it
79        Thing thing;
80        // BAD!! Will assert, because thing is not in the address range recorded in base.
81        Offset32Ptr<Thing> thingOffsetPtr= base->asPtr(&thing);
82    }
83
84    // Ok - this is now correct
85    Offset32Ptr<Thing> thing = container.newObject<Thing>();
86
87    // To write values, we need a raw pointer
88    {
89        // To get the raw pointer we can use 'asRaw'
90        auto rawThing = base->asRaw(thing);
91
92        // Or more perhaps slightly more conveniently []
93        auto rawThing = base[thing];
94
95        // We can write and read things via the Safe32Ptr
96        rawThing->value = 10;
97        const int value = rawThing->value;
98
99        SLANG_ASSERT(value == 10);
100    }
101
102    // Now lets write to it
103    {
104        // We can have raw pointer (or reference) to a thing but we need to be *careful* if we
105allocate Thing* rawThing = base[thing];
106        // We are okay here, nothing between getting the raw pointer and the write allocated/newed
107anything on the OffsetContainer rawThing->value = 20;
108
109        // Lets set up name
110        Offset32Ptr<OffsetString> text = offsetContainer.newString("Hello World!");
111
112        // BAD! The rawThing point could now be invalid because the call to newString may have had
113to allocate more memory rawThing->text = text;
114
115        // This is okay
116        base[thing]->text = text;
117
118        // Or we can update rawThing such that is up to date
119        rawThing = base[thing];
120        // So now this is okay again
121        rawThing->text = text;
122
123        // BAD! we don't know the evaluation order here, if the lhs is evaluate before the rhs, then
124it could write to the wrong area of memory. base[thing]->text = offsetContainer.newString("Hello
125World again!");
126
127        // So where there is allocation, and assignment to something that in held in offset ptr use
128a local for the allocation as in
129        {
130            auto text = offsetContainer.newString("Hello World again!");
131            base[thing]->text = text;
132        }
133    }
134}
135
136```
137*/
138
139enum
140{
141    kNull32Offset = 0,
142    kStartOffset = uint32_t(sizeof(uint64_t)), ///< The offset to the first contained thing
143};
144
145template<typename T>
146class Offset32Ref;
147
148/* A pointer to items held in OffsetContainer (or OffsetBase relative) that remains correct even if
149the memory inside OffsetContainer moves.
150*/
151template<typename T>
152class Offset32Ptr
153{
154public:
155    typedef Offset32Ptr ThisType;
156
157    const ThisType& operator=(const ThisType& rhs)
158    {
159        m_offset = rhs.m_offset;
160        return *this;
161    }
162    bool operator==(const ThisType& rhs) const { return m_offset == rhs.m_offset; }
163    bool operator!=(const ThisType& rhs) const { return m_offset != rhs.m_offset; }
164
165    bool operator<(const ThisType& rhs) const { return m_offset < rhs.m_offset; }
166    bool operator<=(const ThisType& rhs) const { return m_offset <= rhs.m_offset; }
167    bool operator>(const ThisType& rhs) const { return m_offset > rhs.m_offset; }
168    bool operator>=(const ThisType& rhs) const { return m_offset >= rhs.m_offset; }
169
170    operator bool() const { return m_offset != kNull32Offset; }
171
172    Offset32Ref<T> operator*();
173
174    ThisType& operator++()
175    {
176        m_offset += uint32_t(sizeof(T));
177        return *this;
178    }
179    ThisType operator++(int)
180    {
181        const auto offset = m_offset;
182        m_offset += uint32_t(sizeof(T));
183        return ThisType(offset);
184    }
185
186    ThisType& operator--()
187    {
188        m_offset -= sizeof(T);
189        return *this;
190    }
191    ThisType operator--(int)
192    {
193        const auto offset = m_offset;
194        m_offset -= uint32_t(sizeof(T));
195        return ThisType(offset);
196    }
197
198    friend ThisType operator+(const ThisType& a, Index b)
199    {
200        return ThisType(a.m_offset + uint32_t(sizeof(T) * b));
201    }
202    friend ThisType operator+(Index a, const ThisType& b)
203    {
204        return ThisType(b.m_offset + uint32_t(sizeof(T) * a));
205    }
206
207    bool isNull() const { return m_offset == kNull32Offset; }
208
209    void setNull() { m_offset = kNull32Offset; }
210    Offset32Ptr()
211        : m_offset(kNull32Offset)
212    {
213    }
214    Offset32Ptr(const ThisType& rhs)
215        : m_offset(rhs.m_offset)
216    {
217    }
218    explicit Offset32Ptr(uint32_t offset)
219        : m_offset(offset)
220    {
221    }
222
223    uint32_t m_offset;
224};
225
226/* A reference to items held in OffsetContainer (or OffsetBase relative) that remains correct even
227if the memory inside OffsetContainer moves.
228*/
229template<typename T>
230class Offset32Ref
231{
232public:
233    typedef Offset32Ref ThisType;
234
235    const ThisType& operator=(const ThisType& rhs)
236    {
237        m_offset = rhs.m_offset;
238        return *this;
239    }
240
241    Offset32Ptr<T> operator&() { return Offset32Ptr<T>(m_offset); }
242
243    Offset32Ref(const ThisType& rhs)
244        : m_offset(rhs.m_offset)
245    {
246    }
247    explicit Offset32Ref(uint32_t offset)
248        : m_offset(offset)
249    {
250        SLANG_ASSERT(offset != kNull32Offset);
251    }
252
253    uint32_t m_offset;
254};
255
256// ---------------------------------------------------------------------------
257template<typename T>
258SLANG_FORCE_INLINE Offset32Ref<T> Offset32Ptr<T>::operator*()
259{
260    return Offset32Ref<T>(m_offset);
261}
262
263
264/* Much like Offset32Ptr this is an array but whose memory is stored inside the OffsetContainer.
265This means elements types must be 'offset types'. */
266template<typename T>
267class Offset32Array
268{
269public:
270    Offset32Ptr<const T> begin() const { return Offset32Ptr<const T>(m_data.m_offset); }
271    Offset32Ptr<const T> end() const { return begin() + Index(m_count); }
272
273    Offset32Ptr<T> begin() { return m_data; }
274    Offset32Ptr<T> end() { return begin() + Index(m_count); }
275
276    Index getCount() const { return Index(m_count); }
277
278    Offset32Ref<const T> operator[](Index i) const
279    {
280        SLANG_ASSERT(i >= 0 && uint32_t(i) < m_count);
281        return Offset32Ref<const T>((m_data + i).m_offset);
282    }
283    Offset32Ref<T> operator[](Index i)
284    {
285        SLANG_ASSERT(i >= 0 && uint32_t(i) < m_count);
286        return Offset32Ref<T>((m_data + i).m_offset);
287    }
288
289    Offset32Array(Offset32Ptr<T> data, uint32_t count)
290        : m_data(data), m_count(count)
291    {
292    }
293
294    Offset32Array()
295        : m_count(0)
296    {
297    }
298
299    Offset32Ptr<T> m_data;
300    uint32_t m_count;
301};
302
303/** OffsetString is used for storing strings within a OffsetContainer. Strings are stored with the
304initial byte indicating the size of the string. Note that all offset strings are stored with a
305terminating zero, and that the terminating zero is *NOT* included in the encoded size. */
306struct OffsetString
307{
308    enum
309    {
310        kSizeBase = 251,
311        kMaxSizeEncodeSize = 5,
312    };
313
314    /// Get contents as a slice
315    UnownedStringSlice getSlice() const;
316    /// Get null terminated string
317    const char* getCstr() const;
318
319    /// Decode the size. Returns the start of the string text, and outSize holds the size (NOT
320    /// including terminating 0)
321    static const char* decodeSize(const char* in, size_t& outSize);
322
323    /// Returns the amount of bytes used, end encoding in 'encode'
324    static size_t calcEncodedSize(size_t size, uint8_t encode[kMaxSizeEncodeSize]);
325    /// Calculate the total size needed to store the string *including* terminating 0
326    static size_t calcAllocationSize(const UnownedStringSlice& slice);
327
328    /// Calculate the total size needed to store string. Size should be passed *without* terminating
329    /// 0
330    static size_t calcAllocationSize(size_t size);
331
332    char m_sizeThenContents[1];
333};
334
335/* A type that is used to hold the base address of the contiguous memory that holds either
336 * Offset32Ptr and related types>
337 */
338class OffsetBase
339{
340public:
341    typedef OffsetBase ThisType;
342
343    /// Turn an offset into a raw regular pointer or reference
344    template<typename T>
345    T* asRaw(const Offset32Ptr<T>& ptr)
346    {
347        return (T*)_getRaw(ptr.m_offset);
348    }
349    template<typename T>
350    T& asRaw(const Offset32Ref<T>& ref)
351    {
352        return *(T*)_getRaw(ref.m_offset);
353    }
354
355    /// A more terse way to get a raw pointer/reference. Using the [] operator can be seen as
356    /// 'indexing' to access the object the offset relates to. Unlike 'indices' that are typically
357    /// used with [] offsets are generally not contiguous.
358    template<typename T>
359    T* operator[](const Offset32Ptr<T>& ptr)
360    {
361        return (T*)_getRaw(ptr.m_offset);
362    }
363    template<typename T>
364    T& operator[](const Offset32Ref<T>& ref)
365    {
366        return *(T*)_getRaw(ref.m_offset);
367    }
368
369    template<typename T>
370    Offset32Ptr<T> asPtr(T* ptr)
371    {
372        return Offset32Ptr<T>(getOffset(ptr));
373    }
374    /// Note the use of ptr when setting up a reference here - it's needed because a ref does not
375    /// have to be backed by a pointer. And commonly is not when the const& and the thing referenced
376    /// can be held in a word.
377    template<typename T>
378    Offset32Ref<T> asRef(T* ptr)
379    {
380        SLANG_ASSERT(ptr);
381        return Offset32Ref<T>(getOffset(ptr));
382    }
383
384    uint32_t getOffset(const void* ptr)
385    {
386        if (ptr == nullptr)
387        {
388            return kNull32Offset;
389        }
390        ptrdiff_t diff = ((const uint8_t*)ptr) - m_data;
391        SLANG_ASSERT(diff > 0 && size_t(diff) < m_dataSize);
392        return uint32_t(diff);
393    }
394
395    /// Get the contained data
396    SLANG_FORCE_INLINE uint8_t* getData() { return m_data; }
397    /// Return the last used byte of the data
398    SLANG_FORCE_INLINE size_t getDataCount() const { return m_dataSize; }
399
400    /// Get the first allocated thing. Typically the root of the structure contained
401    void* getFirst() { return (m_dataSize < kStartOffset) ? nullptr : (m_data + kStartOffset); }
402
403    /// Get a raw pointer from the offset
404    uint8_t* _getRaw(uint32_t offset)
405    {
406        return (offset == kNull32Offset) ? nullptr : (m_data + offset);
407    }
408
409    OffsetBase()
410        : m_data(nullptr), m_dataSize(0)
411    {
412    }
413
414
415    uint8_t* m_data;
416    size_t m_dataSize;
417
418protected:
419    /// We want protected, because we don't want copies to be made of OffsetBase by default!
420    OffsetBase(const ThisType& rhs) = default;
421    ThisType& operator=(const ThisType& rhs) = default;
422};
423
424class MemoryOffsetBase : public OffsetBase
425{
426public:
427    void set(void* data, size_t dataSize)
428    {
429        m_data = (uint8_t*)data;
430        m_dataSize = dataSize;
431    }
432};
433
434/* OffsetContainer is a type designed to manage the construction structures around 'offset types'.
435In particular it allows for construction of offset structures where their total encoded size is not
436known at the outset.
437
438The main mechanism to make this work is via the use of OffsetXXX types, which when constructed from
439the OffsetContainer will maintain valid values, even if the underlying backing memories location is
440changed.
441*/
442class OffsetContainer : public OffsetBase
443{
444public:
445    template<typename T>
446    Offset32Ptr<T> newObject()
447    {
448        void* data = allocate(sizeof(T), alignof(T));
449        new (data) T();
450        return Offset32Ptr<T>(getOffset(data));
451    }
452
453    template<typename T>
454    Offset32Array<T> newArray(size_t size)
455    {
456        if (size == 0)
457        {
458            return Offset32Array<T>();
459        }
460        T* data = (T*)allocate(sizeof(T) * size, alignof(T));
461        for (size_t i = 0; i < size; ++i)
462        {
463            new (data + i) T();
464        }
465        return Offset32Array<T>(Offset32Ptr<T>(getOffset(data)), uint32_t(size));
466    }
467
468    /// Get the base - which is needed for turning offsets into things
469    OffsetBase& asBase() { return *this; }
470
471    /// Allocate without alignment (effectively 1)
472    void* allocate(size_t size);
473    void* allocate(size_t size, size_t alignment);
474    void* allocateAndZero(size_t size, size_t alignment);
475
476    void fixAlignment(size_t alignment);
477
478    Offset32Ptr<OffsetString> newString(const UnownedStringSlice& slice);
479    Offset32Ptr<OffsetString> newString(const char* contents);
480
481    /// Ctor
482    OffsetContainer();
483    ~OffsetContainer();
484
485protected:
486    size_t m_capacity;
487};
488
489} // namespace Slang
490
491#endif