yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
551d0c365
master
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{ 68Offset32Ptr<OffsetString> text; 69int value; 70}; 71 72void func() 73{ 74OffsetContainer container; 75OffsetBase& base = container.asBase(); 76 77{ 78// We can allocate on the heap. BUT we can't set up a offset pointer to it 79Thing thing; 80// BAD!! Will assert, because thing is not in the address range recorded in base. 81Offset32Ptr<Thing> thingOffsetPtr= base->asPtr(&thing); 82} 83 84// Ok - this is now correct 85Offset32Ptr<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' 90auto rawThing = base->asRaw(thing); 91 92// Or more perhaps slightly more conveniently [] 93auto rawThing = base[thing]; 94 95// We can write and read things via the Safe32Ptr 96rawThing->value = 10; 97const int value = rawThing->value; 98 99SLANG_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 110Offset32Ptr<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 116base[thing]->text = text; 117 118// Or we can update rawThing such that is up to date 119rawThing = base[thing]; 120// So now this is okay again 121rawThing->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{ 130auto text = offsetContainer.newString("Hello World again!"); 131base[thing]->text = text; 132} 133} 134} 135 136``` 137*/ 138 139enum 140{ 141kNull32Offset = 0 , 142kStartOffset = 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 : 155typedef Offset32Ptr ThisType ; 156 157const ThisType & operator = (const ThisType & rhs ) 158 { 159m_offset = rhs .m_offset ; 160return * this ; 161 } 162bool operator == (const ThisType & rhs )const {return m_offset == rhs .m_offset ; } 163bool operator != (const ThisType & rhs )const {return m_offset != rhs .m_offset ; } 164 165bool operator < (const ThisType & rhs )const {return m_offset < rhs .m_offset ; } 166bool operator <=(const ThisType & rhs )const {return m_offset <=rhs .m_offset ; } 167bool operator > (const ThisType & rhs )const {return m_offset > rhs .m_offset ; } 168bool operator >=(const ThisType & rhs )const {return m_offset >=rhs .m_offset ; } 169 170operator bool ()const {return m_offset != kNull32Offset ; } 171 172Offset32Ref < T > operator * (); 173 174ThisType & operator ++ () 175{ 176m_offset += uint32_t ( sizeof ( T )); 177return * this; 178} 179ThisType operator ++ ( int ) 180{ 181const auto offset = m_offset; 182m_offset += uint32_t ( sizeof ( T )); 183return ThisType (offset); 184} 185 186ThisType & operator -- () 187{ 188m_offset -= sizeof ( T ); 189return * this; 190} 191ThisType operator -- ( int ) 192{ 193const auto offset = m_offset; 194m_offset -= uint32_t ( sizeof ( T )); 195return ThisType (offset); 196} 197 198friend ThisType operator + ( const ThisType & a, Index b) 199{ 200return ThisType (a. m_offset + uint32_t ( sizeof ( T ) * b)); 201} 202friend ThisType operator + (Index a, const ThisType & b) 203{ 204return ThisType (b. m_offset + uint32_t ( sizeof ( T ) * a)); 205} 206 207bool isNull () const { return m_offset == kNull32Offset; } 208 209void setNull () { m_offset = kNull32Offset; } 210Offset32Ptr () 211: m_offset (kNull32Offset) 212{ 213} 214Offset32Ptr( const ThisType & rhs) 215: m_offset ( rhs .m_offset) 216{ 217} 218explicit Offset32Ptr ( uint32_t offset) 219: m_offset (offset) 220{ 221} 222 223uint32_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 : 233typedef Offset32Ref ThisType; 234 235const ThisType & operator = ( const ThisType & rhs) 236{ 237m_offset = rhs. m_offset ; 238return * this; 239} 240 241Offset32Ptr < T > operator & () { return Offset32Ptr < T > (m_offset); } 242 243Offset32Ref ( const ThisType & rhs) 244: m_offset (rhs. m_offset ) 245{ 246} 247explicit Offset32Ref ( uint32_t offset) 248: m_offset( offset ) 249{ 250SLANG_ASSERT (offset != kNull32Offset); 251} 252 253uint32_t m_offset; 254}; 255 256// --------------------------------------------------------------------------- 257template < typename T > 258SLANG_FORCE_INLINE Offset32Ref < T > Offset32Ptr < T > ::operator * () 259{ 260return 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 : 270Offset32Ptr < const T > begin () const { return Offset32Ptr < const T > (m_data. m_offset ); } 271Offset32Ptr < const T > end () const { return begin () + Index (m_count); } 272 273Offset32Ptr < T > begin () { return m_data; } 274Offset32Ptr < T > end () { return begin () + Index (m_count); } 275 276Index getCount () const { return Index (m_count); } 277 278Offset32Ref < const T > operator[](Index i) const 279{ 280SLANG_ASSERT (i >= 0 && uint32_t (i) < m_count); 281return Offset32Ref < const T > ((m_data + i). m_offset ); 282} 283Offset32Ref < T > operator[](Index i) 284{ 285SLANG_ASSERT (i >= 0 && uint32_t (i) < m_count); 286return Offset32Ref < T > ((m_data + i). m_offset ); 287} 288 289Offset32Array (Offset32Ptr < T > data, uint32_t count) 290: m_data (data), m_count (count) 291{ 292} 293 294Offset32Array () 295: m_count ( 0 ) 296{ 297} 298 299Offset32Ptr < T > m_data; 300uint32_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{ 308enum 309{ 310kSizeBase = 251 , 311kMaxSizeEncodeSize = 5 , 312}; 313 314/// Get contents as a slice 315UnownedStringSlice getSlice () const; 316/// Get null terminated string 317const 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) 321static const char * decodeSize ( const char * in, size_t & outSize); 322 323/// Returns the amount of bytes used, end encoding in 'encode' 324static size_t calcEncodedSize ( size_t size, uint8_t encode[kMaxSizeEncodeSize]); 325/// Calculate the total size needed to store the string *including* terminating 0 326static size_t calcAllocationSize ( const UnownedStringSlice & slice); 327 328/// Calculate the total size needed to store string. Size should be passed *without* terminating 329/// 0 330static size_t calcAllocationSize ( size_t size); 331 332char 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 : 341typedef OffsetBase ThisType; 342 343/// Turn an offset into a raw regular pointer or reference 344template < typename T > 345T * asRaw (const Offset32Ptr < T >& ptr) 346{ 347return ( T * ) _getRaw (ptr. m_offset ); 348} 349template < typename T > 350T & asRaw (const Offset32Ref < T >& ref) 351{ 352return * ( 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. 358template < typename T > 359T * operator[](const Offset32Ptr < T >& ptr) 360{ 361return ( T * ) _getRaw (ptr. m_offset ); 362} 363template < typename T > 364T & operator[](const Offset32Ref < T >& ref) 365{ 366return * ( T * ) _getRaw (ref. m_offset ); 367} 368 369template < typename T > 370Offset32Ptr < T > asPtr ( T * ptr) 371{ 372return 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. 377template < typename T > 378Offset32Ref < T > asRef ( T * ptr) 379{ 380SLANG_ASSERT (ptr); 381return Offset32Ref < T > ( getOffset (ptr)); 382} 383 384uint32_t getOffset ( const void * ptr) 385{ 386if (ptr == nullptr ) 387{ 388return kNull32Offset; 389} 390ptrdiff_t diff = (( const uint8_t * )ptr) - m_data; 391SLANG_ASSERT (diff > 0 && size_t (diff) < m_dataSize); 392return uint32_t (diff); 393} 394 395/// Get the contained data 396SLANG_FORCE_INLINE uint8_t * getData () { return m_data; } 397/// Return the last used byte of the data 398SLANG_FORCE_INLINE size_t getDataCount () const { return m_dataSize; } 399 400/// Get the first allocated thing. Typically the root of the structure contained 401void * getFirst () { return (m_dataSize < kStartOffset) ? nullptr : (m_data + kStartOffset); } 402 403/// Get a raw pointer from the offset 404uint8_t * _getRaw ( uint32_t offset) 405{ 406return (offset == kNull32Offset) ? nullptr : (m_data + offset); 407} 408 409OffsetBase () 410: m_data ( nullptr ), m_dataSize ( 0 ) 411{ 412} 413 414 415uint8_t * m_data; 416size_t m_dataSize; 417 418protected : 419/// We want protected, because we don't want copies to be made of OffsetBase by default! 420OffsetBase( const ThisType & rhs) = default; 421ThisType & operator = ( const ThisType & rhs) = default; 422}; 423 424class MemoryOffsetBase : public OffsetBase 425{ 426public : 427void set ( void * data, size_t dataSize) 428{ 429m_data = ( uint8_t * )data; 430m_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 : 445template < typename T > 446Offset32Ptr < T > newObject () 447{ 448void * data = allocate ( sizeof ( T ), alignof( T )); 449new ( data ) T (); 450return Offset32Ptr < T > ( getOffset (data)); 451} 452 453template < typename T > 454Offset32Array < T > newArray (size_t size) 455{ 456if (size == 0 ) 457{ 458return Offset32Array < T > (); 459} 460T * data = ( T * ) allocate ( sizeof ( T ) * size, alignof( T )); 461for ( size_t i = 0 ; i < size; ++ i) 462{ 463new (data + i) T (); 464} 465return Offset32Array < T > (Offset32Ptr < T > ( getOffset (data)), uint32_t (size)); 466} 467 468/// Get the base - which is needed for turning offsets into things 469OffsetBase & asBase () { return * this; } 470 471/// Allocate without alignment (effectively 1) 472void * allocate ( size_t size); 473void * allocate ( size_t size, size_t alignment); 474void * allocateAndZero ( size_t size, size_t alignment); 475 476void fixAlignment ( size_t alignment); 477 478Offset32Ptr < OffsetString > newString (const UnownedStringSlice & slice); 479Offset32Ptr < OffsetString > newString (const char * contents); 480 481/// Ctor 482OffsetContainer (); 483~ OffsetContainer (); 484 485protected : 486size_t m_capacity; 487}; 488 489} // namespace Slang 490 491#endif