Add new string type rdcinflexiblestr specifically for structured data

* This is a string type which heavily optimises for immutability and minimal
  storage. It only contains one pointer to the string data and always
  reallocates on modify. For compile-time literals it doesn't modify or
  allocate.
* On x64 we use the top bit in a tagged pointer to store a flag of whether it's
  heap or literal, on other platforms it uses a separate field (meaning another
  pointer sized value effectively, including padding).
* This is best for structured data which tends to use a lot of immutable strings
  for type/name information, and only a few for actual string data (which are
  only allocated once and aren't modified after that). Similarly we rarely want
  to know only the size of any of these strings, we want the whole string so not
  explicitly storing the size is not a big deal.
* Overall this reduces SDObject from 128 bytes to 80 bytes.
This commit is contained in:
baldurk
2020-10-27 15:15:19 +00:00
parent c58f3edafa
commit 935cb113ed
18 changed files with 396 additions and 71 deletions
+194
View File
@@ -33,6 +33,8 @@
void RENDERDOC_OutOfMemory(uint64_t sz);
#endif
class rdcinflexiblestr;
// special type for storing literals. This allows functions to force callers to pass them literals
class rdcliteral
{
@@ -42,6 +44,9 @@ class rdcliteral
// make the literal operator a friend so it can construct fixed strings. No-one else can.
friend rdcliteral operator"" _lit(const char *str, size_t len);
// similarly friend inflexible strings to allow them to decompose to a literal
friend class rdcinflexiblestr;
rdcliteral(const char *s, size_t l) : str(s), len(l) {}
rdcliteral() = delete;
@@ -128,6 +133,9 @@ private:
bool is_alloc() const { return !!(d.fixed.flags & ALLOC_STATE); }
bool is_fixed() const { return !!(d.fixed.flags & FIXED_STATE); }
bool is_array() const { return !is_alloc() && !is_fixed(); }
// allow inflexible string to introspect to see if we're a literal
friend class rdcinflexiblestr;
/////////////////////////////////////////////////////////////////
// memory management, in a dll safe way
@@ -938,3 +946,189 @@ inline rdcstr operator+(const QChar &left, const rdcstr &right)
return rdcstr(left) += right;
}
#endif
// this class generally should not be used directly. You almost always want rdcstr (or rarely
// rdcliteral) instead. This class is used for structured data where the vast majority of the time
// it stores a literal and is only accessed for the string contents, but it has to allow for
// modification just in case the structured data is dynamically generated.
// It is optimised for storage space and uses a single tagged pointer on x64 - if the tag is set,
// the pointer is to a null-terminated literal, otherwise it is to an allocated null-terminated
// string. There are no built-in modification functions but it can be assigned from an rdcstr and
// converted to an rdcstr - whenever it's assigned, the old storage is deallocated and new storage
// is allocated so it is highly inefficient if the string is being modified.
class rdcinflexiblestr
{
static char *allocate(size_t count)
{
char *ret = NULL;
#ifdef RENDERDOC_EXPORTS
ret = (char *)malloc(count);
if(ret == NULL)
RENDERDOC_OutOfMemory(count);
#else
ret = (char *)RENDERDOC_AllocArrayMem(count);
#endif
return ret;
}
static void deallocate(char *p)
{
#ifdef RENDERDOC_EXPORTS
free((void *)p);
#else
RENDERDOC_FreeArrayMem((void *)p);
#endif
}
// we use tagged pointers on x86-64 to minimise storage. On other architecture this isn't safe
// so we have to keep it separate. This is still a storage win over rdcstr
#if defined(__x86_64__) || defined(_M_X64)
// use a signed pointer to sign-extend for canonical form
intptr_t pointer : 63;
intptr_t is_literal : 1;
#else
intptr_t pointer;
intptr_t is_literal;
#endif
public:
rdcinflexiblestr()
{
pointer = (intptr_t)(void *)"";
is_literal |= 0x1;
}
~rdcinflexiblestr()
{
if(is_literal == 0)
deallocate((char *)pointer);
pointer = 0;
is_literal = 0;
}
rdcinflexiblestr(rdcinflexiblestr &&in)
{
pointer = in.pointer;
is_literal = in.is_literal;
in.pointer = 0;
in.is_literal = 0;
}
rdcinflexiblestr &operator=(rdcinflexiblestr &&in)
{
if(is_literal == 0)
deallocate((char *)pointer);
pointer = in.pointer;
is_literal = in.is_literal;
in.pointer = 0;
in.is_literal = 0;
return *this;
}
rdcinflexiblestr(const rdcinflexiblestr &in)
{
pointer = 0;
is_literal = 0;
*this = in;
}
rdcinflexiblestr(const rdcliteral &lit)
{
pointer = (intptr_t)lit.c_str();
is_literal |= 0x1;
}
rdcinflexiblestr &operator=(const rdcliteral &in)
{
if(is_literal == 0)
deallocate((char *)pointer);
pointer = (intptr_t)in.c_str();
is_literal |= 0x1;
return *this;
}
rdcinflexiblestr(const rdcstr &in)
{
pointer = 0;
is_literal = 0;
*this = in;
}
rdcinflexiblestr &operator=(const rdcstr &in)
{
if(is_literal == 0)
deallocate((char *)pointer);
// unbox a literal from the rdcstr if it has one
if(in.is_fixed())
{
pointer = (intptr_t)in.c_str();
is_literal |= 0x1;
}
else
{
// always allocate for rdcstr, don't try to unbox a literal if one exists
size_t size = in.size() + 1;
void *dst = allocate(size);
memcpy(dst, in.c_str(), size);
pointer = (intptr_t)dst;
is_literal = 0;
}
return *this;
}
rdcinflexiblestr &operator=(const rdcinflexiblestr &in)
{
if(is_literal == 0)
deallocate((char *)pointer);
if(in.is_literal != 0)
{
pointer = in.pointer;
is_literal = in.is_literal;
}
else
{
size_t size = in.size() + 1;
void *dst = allocate(size);
memcpy(dst, in.c_str(), size);
pointer = (intptr_t)dst;
is_literal = 0;
}
return *this;
}
bool operator==(const rdcstr &o) const
{
if(o.c_str()[0] == 0)
return c_str()[0] == 0;
return !strcmp(o.c_str(), c_str());
}
bool operator==(const rdcinflexiblestr &o) const
{
if(o.c_str()[0] == 0)
return c_str()[0] == 0;
return !strcmp(o.c_str(), c_str());
}
bool operator==(const rdcliteral &o) const
{
if(o.c_str()[0] == 0)
return c_str()[0] == 0;
return !strcmp(o.c_str(), c_str());
}
bool operator!=(const rdcinflexiblestr &o) const { return !(*this == o); }
bool operator!=(const rdcstr &o) const { return !(*this == o); }
bool operator<(const rdcinflexiblestr &o) const { return strcmp(c_str(), o.c_str()) < 0; }
bool operator>(const rdcinflexiblestr &o) const { return strcmp(c_str(), o.c_str()) > 0; }
bool empty() const { return c_str()[0] == 0; }
const char *c_str() const { return (const char *)pointer; }
size_t size() const { return strlen(c_str()); }
operator rdcstr() const
{
if(is_literal == 0)
return rdcstr(c_str());
else
return rdcstr(rdcliteral(c_str(), size()));
}
#if defined(RENDERDOC_QT_COMPAT)
operator QString() const { return QString::fromUtf8(c_str(), (int32_t)size()); }
operator QVariant() const { return QVariant(QString::fromUtf8(c_str(), (int32_t)size())); }
#endif
};
+47 -32
View File
@@ -166,13 +166,19 @@ struct SDChunk;
DOCUMENT("Details the name and properties of a structured type");
struct SDType
{
SDType(const rdcstr &n)
SDType(const rdcinflexiblestr &n)
: name(n), basetype(SDBasic::Struct), flags(SDTypeFlags::NoFlags), byteSize(0)
{
}
#if !defined(SWIG)
SDType(rdcinflexiblestr &&n)
: name(std::move(n)), basetype(SDBasic::Struct), flags(SDTypeFlags::NoFlags), byteSize(0)
{
}
#endif
DOCUMENT("The name of this type.");
rdcstr name;
rdcinflexiblestr name;
DOCUMENT("The :class:`SDBasic` category that this type belongs to.");
SDBasic basetype;
@@ -355,7 +361,7 @@ struct SDObjectData
SDObjectPODData basic;
DOCUMENT("The string contents of the object.");
rdcstr str;
rdcinflexiblestr str;
SDObjectData(const SDObjectData &) = delete;
SDObjectData &operator=(const SDObjectData &other) = delete;
@@ -468,12 +474,18 @@ struct SDObject
void *operator new[](size_t count) = delete;
void operator delete[](void *p) = delete;
SDObject(const rdcstr &n, const rdcstr &t) : type(t)
SDObject(const rdcinflexiblestr &n, const rdcinflexiblestr &t) : name(n), type(t)
{
name = n;
data.basic.u = 0;
m_Lazy = NULL;
}
#if !defined(SWIG)
SDObject(rdcinflexiblestr &&n, rdcinflexiblestr &&t) : name(std::move(n)), type(std::move(t))
{
data.basic.u = 0;
m_Lazy = NULL;
}
#endif
~SDObject()
{
@@ -506,7 +518,7 @@ struct SDObject
}
DOCUMENT("The name of this object.");
rdcstr name;
rdcinflexiblestr name;
DOCUMENT("The :class:`SDType` of this object.");
SDType type;
@@ -715,7 +727,7 @@ returned.
inline double AsDouble() const { return data.basic.d; }
inline float AsFloat() const { return (float)data.basic.d; }
inline char AsChar() const { return data.basic.c; }
inline const rdcstr &AsString() const { return data.str; }
inline const rdcinflexiblestr &AsString() const { return data.str; }
inline uint64_t AsUInt64() const { return (uint64_t)data.basic.u; }
inline int64_t AsInt64() const { return (int64_t)data.basic.i; }
inline uint32_t AsUInt32() const { return (uint32_t)data.basic.u; }
@@ -901,7 +913,7 @@ private:
DECLARE_REFLECTION_STRUCT(SDObject);
#if defined(RENDERDOC_QT_COMPAT)
inline SDObject *makeSDObject(const char *name, QVariant val)
inline SDObject *makeSDObject(const rdcinflexiblestr &name, QVariant val)
{
SDObject *ret = new SDObject(name, "QVariant"_lit);
ret->type.basetype = SDBasic::Null;
@@ -988,7 +1000,7 @@ inline SDObject *makeSDObject(const char *name, QVariant val)
ret->type.basetype = SDBasic::Array;
ret->ReserveChildren(list.size());
for(int i = 0; i < list.size(); i++)
ret->AddAndOwnChild(makeSDObject("[]", list.at(i)));
ret->AddAndOwnChild(makeSDObject("[]"_lit, list.at(i)));
ret->type.byteSize = list.size();
break;
}
@@ -999,7 +1011,7 @@ inline SDObject *makeSDObject(const char *name, QVariant val)
ret->type.basetype = SDBasic::Struct;
ret->ReserveChildren(map.size());
for(const QString &str : map.keys())
ret->AddAndOwnChild(makeSDObject(str.toUtf8().data(), map[str]));
ret->AddAndOwnChild(makeSDObject(rdcstr(str.toUtf8().data()), map[str]));
ret->type.byteSize = map.size();
break;
}
@@ -1011,7 +1023,7 @@ inline SDObject *makeSDObject(const char *name, QVariant val)
#endif
DOCUMENT("Make a structured object out of a signed integer");
inline SDObject *makeSDInt64(const char *name, int64_t val)
inline SDObject *makeSDInt64(const rdcinflexiblestr &name, int64_t val)
{
SDObject *ret = new SDObject(name, "int64_t"_lit);
ret->type.basetype = SDBasic::SignedInteger;
@@ -1021,7 +1033,7 @@ inline SDObject *makeSDInt64(const char *name, int64_t val)
}
DOCUMENT("Make a structured object out of an unsigned integer");
inline SDObject *makeSDUInt64(const char *name, uint64_t val)
inline SDObject *makeSDUInt64(const rdcinflexiblestr &name, uint64_t val)
{
SDObject *ret = new SDObject(name, "uint64_t"_lit);
ret->type.basetype = SDBasic::UnsignedInteger;
@@ -1031,7 +1043,7 @@ inline SDObject *makeSDUInt64(const char *name, uint64_t val)
}
DOCUMENT("Make a structured object out of a integer, stored as signed 32-bits");
inline SDObject *makeSDInt32(const char *name, int32_t val)
inline SDObject *makeSDInt32(const rdcinflexiblestr &name, int32_t val)
{
SDObject *ret = new SDObject(name, "int32_t"_lit);
ret->type.basetype = SDBasic::SignedInteger;
@@ -1041,7 +1053,7 @@ inline SDObject *makeSDInt32(const char *name, int32_t val)
}
DOCUMENT("Make a structured object out of a integer, stored as unsigned 32-bits");
inline SDObject *makeSDUInt32(const char *name, uint32_t val)
inline SDObject *makeSDUInt32(const rdcinflexiblestr &name, uint32_t val)
{
SDObject *ret = new SDObject(name, "uint32_t"_lit);
ret->type.basetype = SDBasic::UnsignedInteger;
@@ -1051,7 +1063,7 @@ inline SDObject *makeSDUInt32(const char *name, uint32_t val)
}
DOCUMENT("Make a structured object out of a floating point value");
inline SDObject *makeSDFloat(const char *name, float val)
inline SDObject *makeSDFloat(const rdcinflexiblestr &name, float val)
{
SDObject *ret = new SDObject(name, "float"_lit);
ret->type.basetype = SDBasic::Float;
@@ -1061,7 +1073,7 @@ inline SDObject *makeSDFloat(const char *name, float val)
}
DOCUMENT("Make a structured object out of a boolean value");
inline SDObject *makeSDBool(const char *name, bool val)
inline SDObject *makeSDBool(const rdcinflexiblestr &name, bool val)
{
SDObject *ret = new SDObject(name, "bool"_lit);
ret->type.basetype = SDBasic::Boolean;
@@ -1071,7 +1083,7 @@ inline SDObject *makeSDBool(const char *name, bool val)
}
DOCUMENT("Make a structured object out of a string");
inline SDObject *makeSDString(const char *name, const rdcstr &val)
inline SDObject *makeSDString(const rdcinflexiblestr &name, const rdcstr &val)
{
SDObject *ret = new SDObject(name, "string"_lit);
ret->type.basetype = SDBasic::String;
@@ -1081,7 +1093,7 @@ inline SDObject *makeSDString(const char *name, const rdcstr &val)
}
DOCUMENT("Make a structured object out of a ResourceId");
inline SDObject *makeSDResourceId(const char *name, ResourceId val)
inline SDObject *makeSDResourceId(const rdcinflexiblestr &name, ResourceId val)
{
SDObject *ret = new SDObject(name, "ResourceId"_lit);
ret->type.basetype = SDBasic::Resource;
@@ -1091,7 +1103,7 @@ inline SDObject *makeSDResourceId(const char *name, ResourceId val)
}
DOCUMENT("Make a structured object out of an enumeration value");
inline SDObject *makeSDEnum(const char *name, uint32_t val)
inline SDObject *makeSDEnum(const rdcinflexiblestr &name, uint32_t val)
{
SDObject *ret = new SDObject(name, "enum"_lit);
ret->type.basetype = SDBasic::Enum;
@@ -1101,7 +1113,7 @@ inline SDObject *makeSDEnum(const char *name, uint32_t val)
}
DOCUMENT("Make an array-type structured object");
inline SDObject *makeSDArray(const char *name)
inline SDObject *makeSDArray(const rdcinflexiblestr &name)
{
SDObject *ret = new SDObject(name, "array"_lit);
ret->type.basetype = SDBasic::Array;
@@ -1109,7 +1121,7 @@ inline SDObject *makeSDArray(const char *name)
}
DOCUMENT("Make an struct-type structured object");
inline SDObject *makeSDStruct(const char *name, const char *structtype)
inline SDObject *makeSDStruct(const rdcinflexiblestr &name, const rdcinflexiblestr &structtype)
{
SDObject *ret = new SDObject(name, structtype);
ret->type.basetype = SDBasic::Struct;
@@ -1120,16 +1132,16 @@ inline SDObject *makeSDStruct(const char *name, const char *structtype)
// concept of different width types like 32-bit vs 64-bit ints
#if !defined(SWIG)
#define SDOBJECT_MAKER(basetype, makeSDFunc) \
inline SDObject *makeSDObject(const char *name, basetype value, const char *customString = NULL, \
const char *customTypeName = NULL) \
{ \
SDObject *ptr = makeSDFunc(name, value); \
if(customString) \
ptr->SetCustomString(customString); \
if(customTypeName) \
ptr->SetTypeName(customTypeName); \
return ptr; \
#define SDOBJECT_MAKER(basetype, makeSDFunc) \
inline SDObject *makeSDObject(const rdcinflexiblestr &name, basetype value, \
const char *customString = NULL, const char *customTypeName = NULL) \
{ \
SDObject *ptr = makeSDFunc(name, value); \
if(customString) \
ptr->SetCustomString(customString); \
if(customTypeName) \
ptr->SetTypeName(customTypeName); \
return ptr; \
}
SDOBJECT_MAKER(int64_t, makeSDInt64);
@@ -1174,7 +1186,10 @@ struct SDChunk : public SDObject
void *operator new[](size_t count) = delete;
void operator delete[](void *p) = delete;
SDChunk(const char *name) : SDObject(name, "Chunk"_lit) { type.basetype = SDBasic::Chunk; }
SDChunk(const rdcinflexiblestr &name) : SDObject(name, "Chunk"_lit)
{
type.basetype = SDBasic::Chunk;
}
DOCUMENT("The :class:`SDChunkMetaData` with the metadata for this chunk.");
SDChunkMetaData metadata;