summaryrefslogtreecommitdiffstats
path: root/source/core/slang-offset-container.h
blob: 4e6920bed800201f69cfc994f6ab2b38e8ae4f62 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
// 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