summaryrefslogtreecommitdiffstats
path: root/source/core
diff options
context:
space:
mode:
authorEllie Hermaszewska <ellieh@nvidia.com>2023-08-16 08:57:47 +0800
committerGitHub <noreply@github.com>2023-08-16 08:57:47 +0800
commit45d9961a6a86d184248ef84f6a07125b0c224f97 (patch)
treec91d9b9aa722ceb727f7f1c8c2041d7d2bb13466 /source/core
parente34b005c47d265105e7bba509cadaa3e225237af (diff)
Use ankerl/unordered_dense as a hashmap implementation (#3036)
* Correct namespace for getClockFrequency * missing const * Add missing assignment operator * Remove unused variables * Return correct modified variable * Use stable hash code for file system identity * terse static_assert * Structured binding for map iteration * Make (==) and getHashCode const on many structs * Add ConstIterator for LinkedList * Replace uses of ItemProxy::getValue with Dictionary::at * Extract list of loads from gradientsMap before updating it * Const correctness in type layout * Add unordered_dense hashmap submodule * Use wyhash or getHashCode in slang-hash.h * refactor slang-hash.h * Use ankerl/unordered_dense as a hashmap implementation Notable changes: - The subscript operator returns a reference directly to the value, rather than a lazy ItemProxy (pair of dict pointer and key) slang-profile time (95% over 10 runs): - Before: 6.3913906 (±0.0746) - After: 5.9276123 (±0.0964) * 64 bit hash for strings So they have the same hash as char buffers with the same contents * Narrowing warnings for gcc to match msvc * revert back to c++17 * Correct c++ version for msvc * Use path to unordered_dense which keeps tests happy * Do not assign to and read from map in same expression * Remove redundant map operations in primal-hoist * Split out stable hash functions into slang-stable-hash.h * 64 bit hash by default * regenerate vs projects * Correct return type from HashSetBase::getCount() * correct width for call to Dictionary::reserve * Use stable hash for obfuscated module ids * Signed int for reserve * clearer variable naming * Parameterize Dictionary on hash and equality functors * Allow heterogenous lookup for Dictionary * missing const * Use set over operator[] in some places * Remove unused function * s/at/getValue
Diffstat (limited to 'source/core')
-rw-r--r--source/core/slang-dictionary.h637
-rw-r--r--source/core/slang-file-system.cpp19
-rw-r--r--source/core/slang-hash.h282
-rw-r--r--source/core/slang-hex-dump-util.cpp10
-rw-r--r--source/core/slang-linked-list.h39
-rw-r--r--source/core/slang-memory-file-system.cpp10
-rw-r--r--source/core/slang-riff-file-system.cpp18
-rw-r--r--source/core/slang-smart-pointer.h2
-rw-r--r--source/core/slang-stable-hash.h99
-rw-r--r--source/core/slang-string.cpp18
-rw-r--r--source/core/slang-string.h27
11 files changed, 477 insertions, 684 deletions
diff --git a/source/core/slang-dictionary.h b/source/core/slang-dictionary.h
index 2c683abfe..c7c0c8e05 100644
--- a/source/core/slang-dictionary.h
+++ b/source/core/slang-dictionary.h
@@ -8,6 +8,7 @@
#include "slang-exception.h"
#include "slang-math.h"
#include "slang-hash.h"
+#include "../../external/unordered_dense/include/ankerl/unordered_dense.h"
namespace Slang
{
@@ -55,7 +56,7 @@ namespace Slang
value = that.value;
return *this;
}
- HashCode getHashCode()
+ HashCode getHashCode() const
{
return combineHash(
Slang::getHashCode(key),
@@ -75,495 +76,188 @@ namespace Slang
const float kMaxLoadFactor = 0.7f;
- template<typename TKey, typename TValue>
+ template<typename TKey, typename TValue, typename Hash = Slang::Hash<TKey>, typename KeyEqual = std::equal_to<TKey>>
class Dictionary
{
- friend class Iterator;
- friend class ItemProxy;
+ using InnerMap = ankerl::unordered_dense::map<
+ TKey,
+ TValue,
+ Hash,
+ KeyEqual>;
+ using ThisType = Dictionary<TKey, TValue, Hash, KeyEqual>;
+ InnerMap map;
public:
- typedef TValue ValueType;
- typedef TKey KeyType;
- typedef Dictionary ThisType;
- private:
- inline int getProbeOffset(int /*probeId*/) const
- {
- // linear probing
- return 1;
- }
- private:
- int m_bucketCountMinusOne;
- int m_count;
- UIntSet m_marks;
- KeyValuePair<TKey, TValue>* m_hashMap;
- void deallocateAll()
- {
- if (m_hashMap)
- delete[] m_hashMap;
- m_hashMap = nullptr;
- }
- inline bool isDeleted(int pos) const
- {
- return m_marks.contains((pos << 1) + 1);
- }
- inline bool isEmpty(int pos) const
- {
- return !m_marks.contains((pos << 1));
- }
- inline void setDeleted(int pos, bool val)
- {
- if (val)
- m_marks.add((pos << 1) + 1);
- else
- m_marks.remove((pos << 1) + 1);
- }
- inline void setEmpty(int pos, bool val)
- {
- if (val)
- m_marks.remove((pos << 1));
- else
- m_marks.add((pos << 1));
- }
- struct FindPositionResult
- {
- int objectPosition;
- int insertionPosition;
+ //
+ // Types
+ //
+ using Iterator = typename InnerMap::iterator;
+ using ConstIterator = typename InnerMap::const_iterator;
+ using KeyType = TKey;
+ using ValueType = TValue;
- FindPositionResult()
- {
- objectPosition = -1;
- insertionPosition = -1;
- }
- FindPositionResult(int objPos, int insertPos)
- {
- objectPosition = objPos;
- insertionPosition = insertPos;
- }
- };
- template<typename KeyType>
- inline int getHashPos(KeyType& key) const
- {
- SLANG_ASSERT(m_bucketCountMinusOne > 0);
- const unsigned int hash = (unsigned int)getHashCode(key);
- return (hash * 2654435761u) % (unsigned int)(m_bucketCountMinusOne);
- }
- template<typename KeyType>
- FindPositionResult findPosition(const KeyType& key) const
- {
- int hashPos = getHashPos(const_cast<KeyType&>(key));
- int insertPos = -1;
- int numProbes = 0;
- while (numProbes <= m_bucketCountMinusOne)
- {
- if (isEmpty(hashPos))
- {
- if (insertPos == -1)
- return FindPositionResult(-1, hashPos);
- else
- return FindPositionResult(-1, insertPos);
- }
- else if (isDeleted(hashPos))
- {
- if (insertPos == -1)
- insertPos = hashPos;
- }
- else if (m_hashMap[hashPos].key == key)
- {
- return FindPositionResult(hashPos, -1);
- }
- numProbes++;
- hashPos = (hashPos + getProbeOffset(numProbes)) & m_bucketCountMinusOne;
- }
- if (insertPos != -1)
- return FindPositionResult(-1, insertPos);
- SLANG_ASSERT_FAILURE("Hash map is full. This indicates an error in Key::Equal or Key::getHashCode.");
- }
- TValue& _insert(KeyValuePair<TKey, TValue>&& kvPair, int pos)
- {
- m_hashMap[pos] = _Move(kvPair);
- setEmpty(pos, false);
- setDeleted(pos, false);
- return m_hashMap[pos].value;
- }
- void maybeRehash()
- {
- if (m_bucketCountMinusOne == -1 || m_count >= int(kMaxLoadFactor * m_bucketCountMinusOne))
- {
- int newSize = (m_bucketCountMinusOne + 1) * 2;
- if (newSize == 0)
- {
- newSize = 64;
- }
- reserve(newSize);
- }
- }
+ //
+ // Iterators
+ //
- bool addIfNotExists(KeyValuePair<TKey, TValue>&& kvPair)
- {
- maybeRehash();
- auto pos = findPosition(kvPair.key);
- if (pos.objectPosition != -1)
- return false;
- else if (pos.insertionPosition != -1)
- {
- m_count++;
- _insert(_Move(kvPair), pos.insertionPosition);
- return true;
- }
- else
- SLANG_ASSERT_FAILURE("Inconsistent find result returned. This is a bug in Dictionary implementation.");
- }
- void add(KeyValuePair<TKey, TValue>&& kvPair)
- {
- if (!addIfNotExists(_Move(kvPair)))
- SLANG_ASSERT_FAILURE("The key already exists in Dictionary.");
- }
- TValue& set(KeyValuePair<TKey, TValue>&& kvPair)
- {
- maybeRehash();
- auto pos = findPosition(kvPair.key);
- if (pos.objectPosition != -1)
- return _insert(_Move(kvPair), pos.objectPosition);
- else if (pos.insertionPosition != -1)
- {
- m_count++;
- return _insert(_Move(kvPair), pos.insertionPosition);
- }
- else
- SLANG_ASSERT_FAILURE("Inconsistent find result returned. This is a bug in Dictionary implementation.");
- }
- public:
- class Iterator
- {
- private:
- const Dictionary<TKey, TValue>* dict;
- int pos;
- public:
- KeyValuePair<TKey, TValue>& operator*() const
- {
- return dict->m_hashMap[pos];
- }
- KeyValuePair<TKey, TValue>* operator->() const
- {
- return dict->m_hashMap + pos;
- }
- Iterator& operator++()
- {
- if (pos > dict->m_bucketCountMinusOne)
- return *this;
- pos++;
- while (pos <= dict->m_bucketCountMinusOne && (dict->isDeleted(pos) || dict->isEmpty(pos)))
- {
- pos++;
- }
- return *this;
- }
- Iterator operator++(int)
- {
- Iterator rs = *this;
- operator++();
- return rs;
- }
- bool operator!=(const Iterator& that) const
- {
- return pos != that.pos || dict != that.dict;
- }
- bool operator==(const Iterator& that) const
- {
- return pos == that.pos && dict == that.dict;
- }
- Iterator(const Dictionary<TKey, TValue>* inDict, int inPos)
- {
- this->dict = inDict;
- this->pos = inPos;
- }
- Iterator()
- {
- this->dict = nullptr;
- this->pos = 0;
- }
- };
+ auto begin() { return map.begin(); }
+ auto begin() const { return map.begin(); }
+ auto end() { return map.end(); }
+ auto end() const { return map.end(); }
- Iterator begin() const
- {
- int pos = 0;
- while (pos < m_bucketCountMinusOne + 1)
- {
- if (isEmpty(pos) || isDeleted(pos))
- pos++;
- else
- break;
- }
- return Iterator(this, pos);
- }
- Iterator end() const
- {
- return Iterator(this, m_bucketCountMinusOne + 1);
- }
- public:
- void add(const TKey& key, const TValue& value)
- {
- add(KeyValuePair<TKey, TValue>(key, value));
- }
- void add(TKey&& key, TValue&& value)
- {
- add(KeyValuePair<TKey, TValue>(_Move(key), _Move(value)));
- }
- bool addIfNotExists(const TKey& key, const TValue& value)
- {
- return addIfNotExists(KeyValuePair<TKey, TValue>(key, value));
- }
- bool addIfNotExists(TKey&& key, TValue&& value)
- {
- return addIfNotExists(KeyValuePair<TKey, TValue>(_Move(key), _Move(value)));
- }
- void remove(const TKey& key)
- {
- if (m_count == 0)
- return;
- auto pos = findPosition(key);
- if (pos.objectPosition != -1)
- {
- setDeleted(pos.objectPosition, true);
- m_count--;
- }
- }
- void clear()
- {
- m_count = 0;
- m_marks.clear();
- }
+ //
+ // Modifiers
+ //
- void reserve(int newSize)
- {
- if (newSize <= m_bucketCountMinusOne + 1)
- return;
+ // Removes all values from the map
+ void clear() { map.clear(); }
- Dictionary<TKey, TValue> newDict;
- newDict.m_bucketCountMinusOne = newSize - 1;
- newDict.m_hashMap = new KeyValuePair<TKey, TValue>[newSize];
- newDict.m_marks.resizeAndClear(newSize * 2);
- if (m_hashMap)
- {
- for (auto& kvPair : *this)
- {
- newDict.add(_Move(kvPair));
- }
- }
- *this = _Move(newDict);
- }
+ // Erases the value at the specified key if it exists
+ void remove(const TKey& key) { map.erase(key); }
- TValue* tryGetValueOrAdd(const TKey& key, const TValue& value)
- {
- maybeRehash();
- auto pos = findPosition(key);
- if (pos.objectPosition != -1)
- {
- return &m_hashMap[pos.objectPosition].value;
- }
- else if (pos.insertionPosition != -1)
- {
- // Make pair
- KeyValuePair<TKey, TValue> kvPair(_Move(key), _Move(value));
- m_count++;
- _insert(_Move(kvPair), pos.insertionPosition);
- return nullptr;
- }
- else
- SLANG_ASSERT_FAILURE("Inconsistent find result returned. This is a bug in Dictionary implementation.");
- }
+ // Reserves enough space for the specified number of values
+ void reserve(Index size) { map.reserve(std::size_t(size)); };
- /// This differs from tryGetValueOrAdd, in that it always returns the Value held in the Dictionary.
- /// If there isn't already an entry for 'key', a value is added with defaultValue.
- TValue& getOrAddValue(const TKey& key, const TValue& defaultValue)
+ // Swap with another map
+ void swapWith(ThisType& rhs) { std::swap(*this, rhs); }
+
+ //
+ // Query capacity
+ //
+
+ std::size_t getCount() const { return map.size(); }
+
+ //
+ // Lookup
+ //
+
+ // Returns true if the map contains an equivalent key
+ template<typename K>
+ bool containsKey(const K& k) const { return map.contains(k); }
+
+ // Returns a valid pointer to the requested element, or nullptr if it
+ // doesn't exist
+ template<typename K>
+ const TValue* tryGetValue(const K& key) const
{
- maybeRehash();
- auto pos = findPosition(key);
- if (pos.objectPosition != -1)
- {
- return m_hashMap[pos.objectPosition].value;
- }
- else if (pos.insertionPosition != -1)
- {
- // Make pair
- KeyValuePair<TKey, TValue> kvPair(_Move(key), _Move(defaultValue));
- m_count++;
- return _insert(_Move(kvPair), pos.insertionPosition);
- }
- else
- SLANG_ASSERT_FAILURE("Inconsistent find result returned. This is a bug in Dictionary implementation.");
+ auto i = map.find(key);
+ return i == map.end() ? nullptr : &(i->second);
}
- void set(const TKey& key, const TValue& value)
+ // Returns a valid pointer to the requested element, or nullptr if it
+ // doesn't exist
+ template<typename K>
+ TValue* tryGetValue(const K& key)
{
- if (auto ptr = tryGetValueOrAdd(key, value))
- {
- *ptr = value;
- }
+ auto i = map.find(key);
+ return i == map.end() ? nullptr : std::addressof(i->second);
}
- template<typename KeyType>
- bool containsKey(const KeyType& key) const
+ // Returns true and copies the element into 'value' if present.
+ // Otherwise returns false and value unmodified.
+ template<typename K>
+ bool tryGetValue(const K& key, TValue& value) const
{
- if (m_bucketCountMinusOne == -1)
+ auto i = map.find(key);
+ if(i == map.end())
return false;
- auto pos = findPosition(key);
- return pos.objectPosition != -1;
- }
- template<typename KeyType>
- bool tryGetValue(const KeyType& key, TValue& value) const
- {
- if (m_bucketCountMinusOne == -1)
- return false;
- auto pos = findPosition(key);
- if (pos.objectPosition != -1)
- {
- value = m_hashMap[pos.objectPosition].value;
- return true;
- }
- return false;
- }
- template<typename KeyType>
- TValue* tryGetValue(const KeyType& key) const
- {
- if (m_bucketCountMinusOne == -1)
- return nullptr;
- auto pos = findPosition(key);
- if (pos.objectPosition != -1)
- {
- return &m_hashMap[pos.objectPosition].value;
- }
- return nullptr;
+ value = i->second;
+ return true;
}
- class ItemProxy
+ // Returns a const reference to the value at the given key. Asserts if
+ // the value doesn't exist
+ const TValue& getValue(const TKey& key) const
{
- private:
- const Dictionary<TKey, TValue>* dict;
- TKey key;
- public:
- ItemProxy(const TKey& _key, const Dictionary<TKey, TValue>* _dict)
- {
- this->dict = _dict;
- this->key = _key;
- }
- ItemProxy(TKey&& _key, const Dictionary<TKey, TValue>* _dict)
- {
- this->dict = _dict;
- this->key = _Move(_key);
- }
- TValue& getValue() const
- {
- auto pos = dict->findPosition(key);
- if (pos.objectPosition != -1)
- {
- return dict->m_hashMap[pos.objectPosition].value;
- }
- else
- SLANG_ASSERT_FAILURE("The key does not exist in dictionary.");
- }
- inline TValue& operator()() const
- {
- return getValue();
- }
- operator TValue&() const
- {
- return getValue();
- }
- TValue& operator=(const TValue& val) const
- {
- return ((Dictionary<TKey, TValue>*)dict)->set(KeyValuePair<TKey, TValue>(_Move(key), val));
- }
- TValue& operator=(TValue&& val) const
- {
- return ((Dictionary<TKey, TValue>*)dict)->set(KeyValuePair<TKey, TValue>(_Move(key), _Move(val)));
- }
- };
- ItemProxy operator[](const TKey& key) const
- {
- return ItemProxy(key, this);
+ if(const auto x = tryGetValue(key))
+ return *x;
+ SLANG_ASSERT_FAILURE("The key does not exist in dictionary.");
}
- ItemProxy operator[](TKey&& key) const
- {
- return ItemProxy(_Move(key), this);
- }
- int getCount() const
+
+ // Returns a reference to the value at the given key. Asserts if the
+ // value doesn't exist
+ TValue& getValue(const TKey& key)
{
- return m_count;
+ if(const auto x = tryGetValue(key))
+ return *x;
+ SLANG_ASSERT_FAILURE("The key does not exist in dictionary.");
}
- /// Swap this with rhs
- void swapWith(ThisType& rhs);
+ //
+ // Combined Lookup and Insertion
+ //
- private:
- template<typename... Args>
- void init(const KeyValuePair<TKey, TValue>& kvPair, Args... args)
- {
- add(kvPair);
- init(args...);
- }
- public:
- Dictionary()
+ // Tries to insert the given element, if a value was already present at
+ // the given key then returns a pointer to that element instead.
+ // Returns nullptr if insertion was successful.
+ TValue* tryGetValueOrAdd(const typename InnerMap::value_type& kvPair)
{
- m_bucketCountMinusOne = -1;
- m_count = 0;
- m_hashMap = nullptr;
- }
- template<typename Arg, typename... Args>
- Dictionary(Arg arg, Args... args)
- {
- init(arg, args...);
- }
- Dictionary(const Dictionary<TKey, TValue>& other)
- : m_bucketCountMinusOne(-1), m_count(0), m_hashMap(nullptr)
- {
- *this = other;
- }
- Dictionary(Dictionary<TKey, TValue>&& other)
- : m_bucketCountMinusOne(-1), m_count(0), m_hashMap(nullptr)
- {
- *this = (_Move(other));
+ const auto& [iterator, inserted] = map.insert(kvPair);
+ return inserted ? nullptr : std::addressof(iterator->second);
}
- Dictionary<TKey, TValue>& operator=(const Dictionary<TKey, TValue>& other)
+ // Tries to insert the given element, if a value was already present at
+ // the given key then returns a pointer to that element instead.
+ // Returns nullptr if insertion was successful.
+ TValue* tryGetValueOrAdd(typename InnerMap::value_type&& kvPair)
{
- if (this == &other)
- return *this;
- deallocateAll();
- m_bucketCountMinusOne = other.m_bucketCountMinusOne;
- m_count = other.m_count;
- m_hashMap = new KeyValuePair<TKey, TValue>[other.m_bucketCountMinusOne + 1];
- m_marks = other.m_marks;
- for (int i = 0; i <= m_bucketCountMinusOne; i++)
- m_hashMap[i] = other.m_hashMap[i];
- return *this;
+ const auto& [iterator, inserted] = map.insert(std::move(kvPair));
+ return inserted ? nullptr : std::addressof(iterator->second);
}
- Dictionary<TKey, TValue>& operator=(Dictionary<TKey, TValue>&& other)
+ // Tries to insert the given element, if a value was already present at
+ // the given key then returns a pointer to that element instead.
+ // Returns nullptr if insertion was successful.
+ TValue* tryGetValueOrAdd(const TKey& key, const TValue& value) { return tryGetValueOrAdd({key, value}); }
+
+ // Inserts the given value if it doesn't exist already
+ // Return a reference to the (possibly new) value in the map
+ TValue& getOrAddValue(const TKey& key, const TValue& defaultValue)
{
- if (this == &other)
- return *this;
- deallocateAll();
- m_bucketCountMinusOne = other.m_bucketCountMinusOne;
- m_count = other.m_count;
- m_hashMap = other.m_hashMap;
- m_marks = _Move(other.m_marks);
- other.m_hashMap = nullptr;
- other.m_count = 0;
- other.m_bucketCountMinusOne = -1;
- return *this;
+ auto [iterator, inserted] = map.insert({key, defaultValue});
+ return iterator->second;
+ }
+
+ // Returns a reference to the value at the specified key, default
+ // initializing it if it doesn't already exist
+ TValue& operator[]( const TKey& key ) { return map[key]; }
+ // Returns a reference to the value at the specified key, default
+ // initializing it if it doesn't already exist
+ TValue& operator[]( TKey&& key ) { return map[std::move(key)]; }
+
+ //
+ // Insertion
+ //
+
+ // Returns true if the value was inserted, returns false if the map
+ // already has a value associated with this key
+ bool addIfNotExists(typename InnerMap::value_type&& kvPair) { return !tryGetValueOrAdd(std::move(kvPair)); }
+ // Returns true if the value was inserted, returns false if the map
+ // already has a value associated with this key
+ bool addIfNotExists(const typename InnerMap::value_type& kvPair) { return !tryGetValueOrAdd(kvPair); }
+ // Returns true if the value was inserted, returns false if the map
+ // already has a value associated with this key
+ bool addIfNotExists(const TKey& k, const TValue& v) { return addIfNotExists({k, v}); }
+ // Returns true if the value was inserted, returns false if the map
+ // already has a value associated with this key
+ bool addIfNotExists(TKey&& k, TValue&& v) { return addIfNotExists({std::move(k), std::move(v)}); }
+
+ // Asserts if the key already exists in the dictionary
+ void add(typename InnerMap::value_type&& kvPair)
+ {
+ if (!addIfNotExists(std::move(kvPair)))
+ SLANG_ASSERT_FAILURE("The key already exists in Dictionary.");
}
- ~Dictionary()
+ // Asserts if the key already exists in the dictionary
+ void add(const typename InnerMap::value_type& kvPair)
{
- deallocateAll();
+ if (!addIfNotExists(kvPair))
+ SLANG_ASSERT_FAILURE("The key already exists in Dictionary.");
}
- };
+ // Asserts if the key already exists in the dictionary
+ void add(const TKey& key, const TValue& value) { add({key, value}); }
+ // Asserts if the key already exists in the dictionary
+ void add(TKey&& key, TValue&& value) { add({std::move(key), std::move(value)}); }
- // ---------------------------------------------------------
- template<typename TKey, typename TValue>
- void Dictionary<TKey, TValue>::swapWith(ThisType& rhs)
- {
- Swap(m_bucketCountMinusOne, rhs.m_bucketCountMinusOne);
- Swap(m_count, rhs.m_count);
- m_marks.swapWith(rhs.m_marks);
- Swap(m_hashMap, rhs.m_hashMap);
- }
+ // Inserts into the dictionary or assigns if the key already exists
+ void set(const TKey& key, const TValue& value) { map.insert_or_assign(key, value); }
+ };
/* We may want to rename this, as strictly speaking _Caps names are reserved */
class _DummyClass
@@ -611,16 +305,18 @@ namespace Slang
class Iterator
{
private:
- typename DictionaryType::Iterator iter;
+ typename DictionaryType::ConstIterator iter;
public:
Iterator() = default;
- T& operator*() const
+ const T& operator*() const
{
- return (*iter).key;
+ const auto& [k, v] = *iter;
+ return k;
}
- T* operator->() const
+ const T* operator->() const
{
- return &(*iter).key;
+ const auto& [k, v] = *iter;
+ return &k;
}
Iterator& operator++()
{
@@ -641,7 +337,7 @@ namespace Slang
{
return iter == that.iter;
}
- Iterator(const typename DictionaryType::Iterator& _iter)
+ Iterator(const typename DictionaryType::ConstIterator& _iter)
{
this->iter = _iter;
}
@@ -655,7 +351,7 @@ namespace Slang
return Iterator(dict.end());
}
public:
- int getCount() const
+ auto getCount() const
{
return dict.getCount();
}
@@ -849,16 +545,13 @@ namespace Slang
}
public:
- typedef typename LinkedList<KeyValuePair<TKey, TValue>>::Iterator Iterator;
+ using Iterator = typename LinkedList<KeyValuePair<TKey, TValue>>::Iterator;
+ using ConstIterator = typename LinkedList<KeyValuePair<TKey, TValue>>::ConstIterator;
- typename LinkedList<KeyValuePair<TKey, TValue>>::Iterator begin() const
- {
- return m_kvPairs.begin();
- }
- typename LinkedList<KeyValuePair<TKey, TValue>>::Iterator end() const
- {
- return m_kvPairs.end();
- }
+ Iterator begin() { return m_kvPairs.begin(); }
+ Iterator end() { return m_kvPairs.end(); }
+ ConstIterator begin() const { return m_kvPairs.begin(); }
+ ConstIterator end() const { return m_kvPairs.end(); }
public:
void add(const TKey& key, const TValue& value)
diff --git a/source/core/slang-file-system.cpp b/source/core/slang-file-system.cpp
index e6d84cac1..7c3cd83bb 100644
--- a/source/core/slang-file-system.cpp
+++ b/source/core/slang-file-system.cpp
@@ -319,11 +319,8 @@ CacheFileSystem::CacheFileSystem(ISlangFileSystem* fileSystem, UniqueIdentityMod
CacheFileSystem::~CacheFileSystem()
{
- for (const auto& pair : m_uniqueIdentityMap)
- {
- PathInfo* pathInfo = pair.value;
+ for (const auto& [_, pathInfo] : m_uniqueIdentityMap)
delete pathInfo;
- }
}
void CacheFileSystem::setInnerFileSystem(ISlangFileSystem* fileSystem, UniqueIdentityMode uniqueIdentityMode, PathStyle pathStyle)
@@ -374,11 +371,8 @@ void CacheFileSystem::setInnerFileSystem(ISlangFileSystem* fileSystem, UniqueIde
void CacheFileSystem::clearCache()
{
- for (const auto& pair : m_uniqueIdentityMap)
- {
- PathInfo* pathInfo = pair.value;
+ for (const auto& [_, pathInfo] : m_uniqueIdentityMap)
delete pathInfo;
- }
m_uniqueIdentityMap.clear();
m_pathMap.clear();
@@ -434,11 +428,10 @@ SlangResult CacheFileSystem::enumeratePathContents(const char* path, FileSystemC
simplifiedPath = "";
}
- for (auto& pair : m_pathMap)
+ for (const auto& [currentPath, pathInfo] : m_pathMap)
{
// NOTE! The currentPath can be a *non* simplified path (the m_pathMap is the cache of paths simplified and other to a file/directory)
// Also note that there will always be the simplified version of the path in cache.
- const String& currentPath = pair.key;
// If it doesn't start with simplified path, then it can't be a hit
if (!currentPath.startsWith(simplifiedPath))
@@ -468,8 +461,6 @@ SlangResult CacheFileSystem::enumeratePathContents(const char* path, FileSystemC
// Let's check that fact...
SLANG_ASSERT(foundPath[remaining.getLength()] == 0);
- PathInfo* pathInfo = pair.value;
-
SlangPathType pathType;
if (SLANG_FAILED(_getPathType(pathInfo, currentPath.getBuffer(), &pathType)))
{
@@ -548,7 +539,7 @@ SlangResult CacheFileSystem::_calcUniqueIdentity(const String& path, String& out
}
// Calculate the hash on the contents
- const uint64_t hash = getHashCode64((const char*)outFileContents->getBufferPointer(), outFileContents->getBufferSize());
+ const StableHashCode64 hash = getStableHashCode64((const char*)outFileContents->getBufferPointer(), outFileContents->getBufferSize());
String hashString = Path::getFileName(path);
hashString = hashString.toLower();
@@ -556,7 +547,7 @@ SlangResult CacheFileSystem::_calcUniqueIdentity(const String& path, String& out
hashString.append(':');
// The uniqueIdentity is a combination of name and hash
- hashString.append(hash, 16);
+ hashString.append(hash);
outUniqueIdentity = hashString;
return SLANG_OK;
diff --git a/source/core/slang-hash.h b/source/core/slang-hash.h
index 5f6b1b060..49685ee65 100644
--- a/source/core/slang-hash.h
+++ b/source/core/slang-hash.h
@@ -1,196 +1,171 @@
#ifndef SLANG_CORE_HASH_H
#define SLANG_CORE_HASH_H
+#include "../../slang.h"
#include "slang-math.h"
-#include <string.h>
+#include "../../external/unordered_dense/include/ankerl/unordered_dense.h"
+#include <cstring>
#include <type_traits>
namespace Slang
{
- // Ideally Hash codes should be unsigned types - makes accumulation simpler (as overflow/underflow behavior are defined)
- // Only downside is around multiply, where unsigned multiply can be slightly slower on some targets.
-
- // HashCode - size may vary by platform. Typically has 'best' combination of bits/performance. Should not be exposed externally as value from same input may change depending on compilation platform.
- typedef unsigned int HashCode;
+ //
+ // Types
+ //
// A fixed 64bit wide hash on all targets.
typedef uint64_t HashCode64;
+ typedef HashCode64 HashCode;
// A fixed 32bit wide hash on all targets.
typedef uint32_t HashCode32;
- SLANG_FORCE_INLINE HashCode32 toHash32(HashCode value) { return (sizeof(HashCode) == sizeof(int64_t)) ? (HashCode32(uint64_t(value) >> 32) ^ HashCode(value)) : HashCode32(value); }
- SLANG_FORCE_INLINE HashCode64 toHash64(HashCode value) { return (sizeof(HashCode) == sizeof(int64_t)) ? HashCode(value) : ((HashCode64(value) << 32) | value); }
-
- SLANG_FORCE_INLINE HashCode getHashCode(int64_t value)
- {
- return (sizeof(HashCode) == sizeof(int64_t)) ? HashCode(value) : (HashCode(uint64_t(value) >> 32) ^ HashCode(value));
- }
- SLANG_FORCE_INLINE HashCode getHashCode(uint64_t value)
+ //
+ // Some helpers to determine which hash to use for a type
+ //
+
+ // Forward declare Hash
+ template<typename T> struct Hash;
+
+ template<typename T, typename = void>
+ constexpr static bool HasSlangHash = false;
+ template<typename T>
+ constexpr static bool HasSlangHash<
+ T,
+ std::enable_if_t<std::is_convertible_v<
+ decltype((std::declval<const T&>()).getHashCode()),
+ HashCode64>>>
+ = true;
+
+ // Does the hashmap implementation provide a uniform hash for this type.
+ template<typename T, typename = void>
+ constexpr static bool HasWyhash = false;
+ template<typename T>
+ constexpr static bool HasWyhash<T, typename ankerl::unordered_dense::hash<T>::is_avalanching> = true;
+
+ // We want to have an associated type 'is_avalanching = void' iff we have a
+ // hash with good uniformity, the two specializations here add that member
+ // when appropriate (since we can't declare an associated type with
+ // constexpr if or something terse like that)
+ template <typename T, typename = void>
+ struct DetectAvalanchingHash {};
+ template <typename T>
+ struct DetectAvalanchingHash<T, std::enable_if_t<HasWyhash<T>>>
{
- return (sizeof(HashCode) == sizeof(uint64_t)) ? HashCode(value) : (HashCode(value >> 32) ^ HashCode(value));
- }
-
- inline HashCode getHashCode(double key)
- {
- return getHashCode(DoubleAsInt64(key));
- }
- inline HashCode getHashCode(float key)
- {
- return FloatAsInt(key);
- }
- inline HashCode getHashCode(const char* buffer)
- {
- if (!buffer)
- return 0;
- HashCode hash = 0;
- auto str = buffer;
- HashCode c = HashCode(*str++);
- while (c)
- {
- hash = c + (hash << 6) + (hash << 16) - hash;
- c = HashCode(*str++);
- }
- return hash;
- }
- inline HashCode getHashCode(char* buffer)
- {
- return getHashCode(const_cast<const char *>(buffer));
- }
- inline HashCode getHashCode(const char* buffer, size_t numChars)
+ using is_avalanching = void;
+ };
+ // Have we marked 'getHashCode' as having good uniformity properties.
+ template <typename T>
+ struct DetectAvalanchingHash<T, std::enable_if_t<T::kHasUniformHash>>
{
- HashCode hash = 0;
- for (size_t i = 0; i < numChars; ++i)
- {
- hash = HashCode(buffer[i]) + (hash << 6) + (hash << 16) - hash;
- }
- return hash;
- }
-
- /* The 'Stable' hash code functions produce hashes that must be
-
- * The same result for the same inputs on all targets
- * Rarely change - as their values can change the output of the Slang API/Serialization
-
- Hash value used from the 'Stable' functions can also be used as part of serialization -
- so it is in effect part of the API.
+ using is_avalanching = void;
+ };
- In effect this means changing a 'Stable' algorithm will typically require doing a new release.
- */
- inline HashCode32 getStableHashCode32(const char* buffer, size_t numChars)
+ // A helper for hashing according to the bit representation
+ template<typename T, typename U>
+ struct BitCastHash : DetectAvalanchingHash<U>
{
- HashCode32 hash = 0;
- for (size_t i = 0; i < numChars; ++i)
+ auto operator()(const T& t) const
{
- hash = HashCode32(buffer[i]) + (hash << 6) + (hash << 16) - hash;
+ // Doesn't discard or invent bits
+ static_assert(sizeof(T) == sizeof(U));
+ // Can we copy bytes to and fro
+ static_assert(std::is_trivially_copyable_v<T>);
+ static_assert(std::is_trivially_copyable_v<U>);
+ // Because we construct a U to memcpy into
+ static_assert(std::is_trivially_constructible_v<U>);
+
+ U u;
+ memcpy(&u, &t, sizeof(T));
+ return Hash<U>{}(u);
}
- return hash;
- }
+ };
- inline HashCode64 getStableHashCode64(const char* buffer, size_t numChars)
+ //
+ // Our hashing functor which disptaches to the most appropriate hashing
+ // function for the type
+ //
+
+ template<typename T>
+ struct Hash : DetectAvalanchingHash<T>
{
- // Use HashCode64 is assumed unsigned because hash requires wrap around behavior and int is undefined on over/underflows
- HashCode64 hash = 0;
- for (size_t i = 0; i < numChars; ++i)
+ auto operator()(const T& t) const
{
- hash = HashCode64(HashCode64(buffer[i])) + (hash << 6) + (hash << 16) - hash;
+ // Our preference is for any hash we've defined ourselves
+ if constexpr (HasSlangHash<T>)
+ return t.getHashCode();
+ // Otherwise fall back to any good hash provided by the hashmap
+ // library
+ else if constexpr (HasWyhash<T>)
+ return ankerl::unordered_dense::hash<T>{}(t);
+ // Otherwise fail
+ else
+ {
+ // !sizeof(T*) is a 'false' which is dependent on T (pending P2593R0)
+ static_assert(!sizeof(T*), "No hash implementation found for this type");
+ // This is to avoid the return type being deduced as 'void' and creating further errors.
+ return HashCode64(0);
+ }
}
- return hash;
- }
+ };
- // Hash functions with specific sized results
- // TODO(JS): We might want to implement HashCode as just an alias a suitable Hash32/Hash32 based on target.
- // For now just use Stable for 64bit.
- SLANG_FORCE_INLINE HashCode64 getHashCode64(const char* buffer, size_t numChars) { return getStableHashCode64(buffer, numChars); }
- SLANG_FORCE_INLINE HashCode32 getHashCode32(const char* buffer, size_t numChars) { return toHash32(getHashCode(buffer, numChars)); }
+ // Specializations for float and double which hash 0 and -0 to distinct values
+ template<>
+ struct Hash<float> : BitCastHash<float, uint32_t> {};
+ template<>
+ struct Hash<double> : BitCastHash<double, uint64_t> {};
- template<int IsInt>
- class Hash
- {
- public:
- };
- template<>
- class Hash<1>
- {
- public:
- template<typename TKey>
- static HashCode getHashCode(TKey& key)
- {
- return (HashCode)key;
- }
- };
- template<>
- class Hash<0>
- {
- public:
- template<typename TKey>
- static HashCode getHashCode(TKey& key)
- {
- return HashCode(key.getHashCode());
- }
- };
- template<int IsPointer>
- class PointerHash
- {};
- template<>
- class PointerHash<1>
- {
- public:
- template<typename TKey>
- static HashCode getHashCode(TKey const& key)
- {
- return (HashCode)((PtrInt)key) >> 2; // sizeof(typename std::remove_pointer<TKey>::type);
- }
- };
- template<>
- class PointerHash<0>
- {
- public:
- template<typename TKey>
- static HashCode getHashCode(TKey& key)
- {
- return Hash<std::is_integral<TKey>::value || std::is_enum<TKey>::value>::getHashCode(key);
- }
- };
+ //
+ // Utility functions for using hashes
+ //
+ // A wrapper for Hash<TKey>
template<typename TKey>
- HashCode getHashCode(const TKey& key)
+ auto getHashCode(const TKey& key)
{
- return PointerHash<std::is_pointer<TKey>::value>::getHashCode(key);
+ return Hash<TKey>{}(key);
}
- template<typename TKey>
- HashCode getHashCode(TKey& key)
+ inline HashCode64 getHashCode(const char* buffer, std::size_t len)
{
- return PointerHash<std::is_pointer<TKey>::value>::getHashCode(key);
+ return ankerl::unordered_dense::detail::wyhash::hash(buffer, len);
}
- template<typename TKey>
- HashCode getHashCodeBytewise(const TKey& t)
+ template<typename T>
+ HashCode64 hashObjectBytes(const T& t)
{
- static_assert(std::has_unique_object_representations_v<TKey>);
- return getHashCode(reinterpret_cast<const char*>(&t), sizeof(TKey));
+ static_assert(std::has_unique_object_representations_v<T>,
+ "This type must have a unique object representation to use hashObjectBytes");
+ return getHashCode(reinterpret_cast<const char*>(&t), sizeof(t));
}
- inline HashCode combineHash(HashCode left, HashCode right)
+ // Use in a struct to declare a uniform hash which doens't care about the
+ // structure of the members.
+# define SLANG_BYTEWISE_HASHABLE \
+ static constexpr bool kHasUniformHash = true; \
+ ::Slang::HashCode64 getHashCode() const \
+ { \
+ return ::Slang::hashObjectBytes(*this); \
+ }
+
+ inline HashCode64 combineHash(HashCode64 h)
{
- return (left * 16777619) ^ right;
+ return h;
}
- inline HashCode combineHash(HashCode hash0, HashCode hash1, HashCode hash2)
+ inline HashCode32 combineHash(HashCode32 h)
{
- auto h = hash0;
- h = combineHash(h, hash1);
- h = combineHash(h, hash2);
return h;
}
- inline HashCode combineHash(HashCode hash0, HashCode hash1, HashCode hash2, HashCode hash3)
+ // A left fold of a mixing operation
+ template<typename H1, typename H2, typename... Hs>
+ auto combineHash(H1 n, H2 m, Hs... args)
{
- auto h = hash0;
- h = combineHash(h, hash1);
- h = combineHash(h, hash2);
- h = combineHash(h, hash3);
- return h;
+ // TODO: restrict the types here more, currently we tend to throw
+ // unhashed integers in here along with proper hashes of objects.
+ static_assert(std::is_convertible_v<H1, HashCode64> || std::is_convertible_v<H1, HashCode32>);
+ static_assert(std::is_convertible_v<H2, HashCode64> || std::is_convertible_v<H2, HashCode32>);
+ return combineHash((n * 16777619) ^ m, args...);
}
struct Hasher
@@ -209,17 +184,6 @@ namespace Slang
m_hashCode = combineHash(m_hashCode, getHashCode(value));
}
- /// Hash the given `object` and combine it into this hash state
- template<typename T>
- void hashObject(T const& object)
- {
- // TODO: Eventually, we should replace `getHashCode`
- // with a "hash into" operation that takes the value
- // and a `Hasher`.
-
- m_hashCode = combineHash(m_hashCode, object->getHashCode());
- }
-
/// Combine the given `hash` code into the hash state.
///
/// Note: users should prefer to use `hashValue` or `hashObject`
diff --git a/source/core/slang-hex-dump-util.cpp b/source/core/slang-hex-dump-util.cpp
index bbee0a199..3a78c256a 100644
--- a/source/core/slang-hex-dump-util.cpp
+++ b/source/core/slang-hex-dump-util.cpp
@@ -28,8 +28,8 @@ static const char s_hex[] = "0123456789abcdef";
SLANG_RETURN_ON_FAIL(helper.write(s_start.begin(), s_start.getLength()));
SLANG_RETURN_ON_FAIL(helper.print(" %zu", dataCount));
- const HashCode32 hash = getStableHashCode32((const char*)data, dataCount);
- SLANG_RETURN_ON_FAIL(helper.print(" %d\n", int(hash) ));
+ const StableHashCode32 hash = getStableHashCode32((const char*)data, dataCount);
+ SLANG_RETURN_ON_FAIL(helper.print(" %d\n", hash.hash ));
SLANG_RETURN_ON_FAIL(dump(data, dataCount, maxBytesPerLine, writer));
@@ -216,7 +216,7 @@ static SlangResult _findLine(const UnownedStringSlice& find, UnownedStringSlice&
UnownedStringSlice startLine, endLine;
SLANG_RETURN_ON_FAIL(findStartAndEndLines(lines, startLine, endLine));
- HashCode32 hash;
+ StableHashCode32 hash;
size_t size;
{
// Get the size and the hash
@@ -228,13 +228,13 @@ static SlangResult _findLine(const UnownedStringSlice& find, UnownedStringSlice&
}
// Extract the size
size = stringToInt(String(slices[1]));
- hash = HashCode32(stringToInt(String(slices[2])));
+ hash = StableHashCode32{stringToUInt(String(slices[2]))};
}
SLANG_RETURN_ON_FAIL(parse(UnownedStringSlice(startLine.end(), endLine.begin()), outBytes));
// Calc the hash
- const HashCode32 readHash = getStableHashCode32((const char*)outBytes.begin(), outBytes.getCount());
+ const StableHashCode32 readHash = getStableHashCode32((const char*)outBytes.begin(), outBytes.getCount());
if (readHash != hash || size_t(outBytes.getCount()) != size)
{
diff --git a/source/core/slang-linked-list.h b/source/core/slang-linked-list.h
index 41709c9d0..93b5e435c 100644
--- a/source/core/slang-linked-list.h
+++ b/source/core/slang-linked-list.h
@@ -4,6 +4,7 @@
#include "../../slang.h"
#include "slang-allocator.h"
+#include <type_traits>
namespace Slang
{
@@ -28,6 +29,7 @@ public:
};
LinkedNode<T>* getPrevious() { return prev; };
LinkedNode<T>* getNext() { return next; };
+ const LinkedNode<T>* getNext() const { return next; };
LinkedNode<T>* insertAfter(const T& nData)
{
LinkedNode<T>* n = new LinkedNode<T>(list);
@@ -88,37 +90,46 @@ private:
int count;
public:
- class Iterator
+ template<bool Const>
+ class GenIterator
{
public:
- LinkedNode<T>* current, *next;
- void setCurrent(LinkedNode<T>* cur)
+ using Node = std::conditional_t<Const, const LinkedNode<T>, LinkedNode<T>>;
+ Node* current, *next;
+ void setCurrent(Node* cur)
{
current = cur;
if (current)
next = current->getNext();
else
- next = 0;
+ next = nullptr;
}
- Iterator() { current = next = nullptr; }
- Iterator(LinkedNode<T>* cur) { setCurrent(cur); }
- T& operator*() const { return current->value; }
- Iterator& operator++()
+ GenIterator() { current = next = nullptr; }
+ GenIterator(Node* cur) { setCurrent(cur); }
+ std::conditional_t<Const, const T&, T&>
+ operator*() const { return current->value; }
+ GenIterator& operator++()
{
setCurrent(next);
return *this;
}
- Iterator operator++(int)
+ GenIterator operator++(int)
{
- Iterator rs = *this;
+ GenIterator rs = *this;
setCurrent(next);
return rs;
}
- bool operator!=(const Iterator& iter) const { return current != iter.current; }
- bool operator==(const Iterator& iter) const { return current == iter.current; }
+ bool operator!=(const GenIterator& iter) const { return current != iter.current; }
+ bool operator==(const GenIterator& iter) const { return current == iter.current; }
};
- Iterator begin() const { return Iterator(head); }
- Iterator end() const { return Iterator(0); }
+
+ using Iterator = GenIterator<false>;
+ Iterator begin() { return Iterator(head); }
+ Iterator end() { return Iterator(0); }
+
+ using ConstIterator = GenIterator<true>;
+ ConstIterator begin() const { return ConstIterator(head); }
+ ConstIterator end() const { return ConstIterator(0); }
public:
LinkedList()
diff --git a/source/core/slang-memory-file-system.cpp b/source/core/slang-memory-file-system.cpp
index eabd13072..b24abc20b 100644
--- a/source/core/slang-memory-file-system.cpp
+++ b/source/core/slang-memory-file-system.cpp
@@ -171,10 +171,9 @@ SlangResult MemoryFileSystem::enumeratePathContents(const char* path, FileSystem
ImplicitDirectoryCollector collector(canonicalPath, true);
// If it is a directory, we need to see if there is anything in it
- for (const auto& pair : m_entries)
+ for (const auto& [_, childEntry] : m_entries)
{
- const Entry* childEntry = &pair.value;
- collector.addPath(childEntry->m_type, childEntry->m_canonicalPath.getUnownedSlice());
+ collector.addPath(childEntry.m_type, childEntry.m_canonicalPath.getUnownedSlice());
}
return collector.enumerate(callback, userData);
@@ -275,10 +274,9 @@ SlangResult MemoryFileSystem::remove(const char* path)
ImplicitDirectoryCollector collector(canonicalPath);
// If it is a directory, we need to see if there is anything in it
- for (const auto& pair : m_entries)
+ for (const auto& [_, childEntry] : m_entries)
{
- const Entry* childEntry = &pair.value;
- collector.addPath(childEntry->m_type, childEntry->m_canonicalPath.getUnownedSlice());
+ collector.addPath(childEntry.m_type, childEntry.m_canonicalPath.getUnownedSlice());
if (collector.hasContent())
{
// Directory is not empty
diff --git a/source/core/slang-riff-file-system.cpp b/source/core/slang-riff-file-system.cpp
index 9db330956..1fb867500 100644
--- a/source/core/slang-riff-file-system.cpp
+++ b/source/core/slang-riff-file-system.cpp
@@ -235,12 +235,10 @@ SlangResult RiffFileSystem::storeArchive(bool blobOwnsContent, ISlangBlob** outB
container.addDataChunk(RiffFileSystemBinary::kHeaderFourCC, &header, sizeof(header));
}
- for (const auto& pair : m_entries)
+ for (const auto& [_, srcEntry] : m_entries)
{
- const Entry* srcEntry = &pair.value;
-
// Ignore the root entry
- if (srcEntry->m_canonicalPath == toSlice("."))
+ if (srcEntry.m_canonicalPath == toSlice("."))
{
continue;
}
@@ -250,22 +248,22 @@ SlangResult RiffFileSystem::storeArchive(bool blobOwnsContent, ISlangBlob** outB
RiffFileSystemBinary::Entry dstEntry;
dstEntry.uncompressedSize = 0;
dstEntry.compressedSize = 0;
- dstEntry.pathSize = uint32_t(srcEntry->m_canonicalPath.getLength() + 1);
- dstEntry.pathType = srcEntry->m_type;
+ dstEntry.pathSize = uint32_t(srcEntry.m_canonicalPath.getLength() + 1);
+ dstEntry.pathType = srcEntry.m_type;
- ISlangBlob* blob = srcEntry->m_contents;
+ ISlangBlob* blob = srcEntry.m_contents;
- if (srcEntry->m_type == SLANG_PATH_TYPE_FILE)
+ if (srcEntry.m_type == SLANG_PATH_TYPE_FILE)
{
dstEntry.compressedSize = uint32_t(blob->getBufferSize());
- dstEntry.uncompressedSize = uint32_t(srcEntry->m_uncompressedSizeInBytes);
+ dstEntry.uncompressedSize = uint32_t(srcEntry.m_uncompressedSizeInBytes);
}
// Entry header
container.write(&dstEntry, sizeof(dstEntry));
// Path
- container.write(srcEntry->m_canonicalPath.getBuffer(), srcEntry->m_canonicalPath.getLength() + 1);
+ container.write(srcEntry.m_canonicalPath.getBuffer(), srcEntry.m_canonicalPath.getLength() + 1);
// Add the contained data without copying
if (blob)
diff --git a/source/core/slang-smart-pointer.h b/source/core/slang-smart-pointer.h
index 80e8adbf1..a161f1b83 100644
--- a/source/core/slang-smart-pointer.h
+++ b/source/core/slang-smart-pointer.h
@@ -153,7 +153,7 @@ namespace Slang
releaseReference(old);
}
- HashCode getHashCode()
+ HashCode getHashCode() const
{
// Note: We need a `RefPtr<T>` to hash the same as a `T*`,
// so that a `T*` can be used as a key in a dictionary with
diff --git a/source/core/slang-stable-hash.h b/source/core/slang-stable-hash.h
new file mode 100644
index 000000000..30a47121e
--- /dev/null
+++ b/source/core/slang-stable-hash.h
@@ -0,0 +1,99 @@
+#pragma once
+
+#include <cstdint>
+#include <cstring>
+#include <type_traits>
+
+namespace Slang
+{
+ //
+ // Types
+ //
+
+ struct StableHashCode64
+ {
+ uint64_t hash;
+ explicit operator uint64_t() const { return hash; }
+ bool operator==(StableHashCode64 other) const { return other.hash == hash; };
+ bool operator!=(StableHashCode64 other) const { return other.hash != hash; };
+ };
+
+ struct StableHashCode32
+ {
+ uint32_t hash;
+ explicit operator uint32_t() const { return hash; }
+ bool operator==(StableHashCode32 other) const { return other.hash == hash; };
+ bool operator!=(StableHashCode32 other) const { return other.hash != hash; };
+ };
+
+ /* The 'Stable' hash code functions produce hashes that must be
+
+ * The same result for the same inputs on all targets
+ * Rarely change - as their values can change the output of the Slang API/Serialization
+
+ Hash value used from the 'Stable' functions can also be used as part of serialization -
+ so it is in effect part of the API.
+
+ In effect this means changing a 'Stable' algorithm will typically require doing a new release.
+ */
+ inline StableHashCode64 getStableHashCode64(const char* buffer, size_t numChars)
+ {
+ uint64_t hash = 0;
+ for (size_t i = 0; i < numChars; ++i)
+ {
+ hash = uint64_t(buffer[i]) + (hash << 6) + (hash << 16) - hash;
+ }
+ return StableHashCode64{hash};
+ }
+
+ template<typename T>
+ inline StableHashCode64 getStableHashCode64(const T& t)
+ {
+ static_assert(std::has_unique_object_representations_v<T>);
+ return getStableHashCode64(reinterpret_cast<const char*>(&t), sizeof(T));
+ }
+
+ inline StableHashCode32 getStableHashCode32(const char* buffer, size_t numChars)
+ {
+ uint32_t hash = 0;
+ for (size_t i = 0; i < numChars; ++i)
+ {
+ hash = uint32_t(buffer[i]) + (hash << 6) + (hash << 16) - hash;
+ }
+ return StableHashCode32{hash};
+ }
+
+ template<typename T>
+ inline StableHashCode32 getStableHashCode32(const T& t)
+ {
+ static_assert(std::has_unique_object_representations_v<T>);
+ return getStableHashCode32(reinterpret_cast<const char*>(&t), sizeof(T));
+ }
+
+ inline StableHashCode64 combineStableHash(StableHashCode64 h)
+ {
+ return h;
+ }
+
+ inline StableHashCode32 combineStableHash(StableHashCode32 h)
+ {
+ return h;
+ }
+
+ // A left fold with a mixing operation
+ template<typename H, typename... Hs>
+ H combineStableHash(H n, H m, Hs... args)
+ {
+ return combineStableHash(H{(n.hash * 16777619) ^ m.hash}, args...);
+ }
+}
+
+// > Please draw a small horse in ASCII art:
+//
+// ,~~.
+// ( 9 )-_,
+// (\___ )=='-' )
+// \ . ) ) /
+// \ `-' / /
+// ~'`~'`~'`~'`~
+//
diff --git a/source/core/slang-string.cpp b/source/core/slang-string.cpp
index dd2138c83..182c6261e 100644
--- a/source/core/slang-string.cpp
+++ b/source/core/slang-string.cpp
@@ -612,6 +612,24 @@ namespace Slang
m_buffer->length += strnlen_s(data, kCount);
}
+ void String::append(StableHashCode32 value)
+ {
+ const Index digits = 8;
+ // + null terminator
+ char* data = prepareForAppend(digits + 1);
+ auto count = intToAscii(data, value.hash, 16, digits);
+ m_buffer->length += count;
+ }
+
+ void String::append(StableHashCode64 value)
+ {
+ const Index digits = 16;
+ // + null terminator
+ char* data = prepareForAppend(digits + 1);
+ auto count = intToAscii(data, value.hash, 16, digits);
+ m_buffer->length += count;
+ }
+
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! UnownedStringSlice !!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Index UnownedStringSlice::indexOf(char c) const
diff --git a/source/core/slang-string.h b/source/core/slang-string.h
index 65fd3a315..3f401b7bb 100644
--- a/source/core/slang-string.h
+++ b/source/core/slang-string.h
@@ -10,8 +10,10 @@
#include "slang-common.h"
#include "slang-hash.h"
#include "slang-secure-crt.h"
+#include "slang-stable-hash.h"
#include <new>
+#include <type_traits>
namespace Slang
{
@@ -32,8 +34,10 @@ namespace Slang
}
}
template<typename IntType>
- inline int intToAscii(char* buffer, IntType val, int radix)
+ inline int intToAscii(char* buffer, IntType val, int radix, int padTo = 0)
{
+ static_assert(std::is_integral_v<IntType>);
+
int i = 0;
IntType sign;
@@ -52,6 +56,9 @@ namespace Slang
buffer[i++] = (char)(digit - 10 + 'A');
} while ((val /= radix) > 0);
+ while(i < padTo)
+ buffer[i++] = '0';
+
if (sign < 0)
buffer[i++] = '-';
@@ -189,7 +196,8 @@ namespace Slang
/// Trims any horizonatl whitespace from start and returns as a substring
UnownedStringSlice trimStart() const;
- HashCode getHashCode() const
+ static constexpr bool kHasUniformHash = true;
+ HashCode64 getHashCode() const
{
return Slang::getHashCode(m_begin, size_t(m_end - m_begin));
}
@@ -508,6 +516,10 @@ namespace Slang
void append(float val, const char* format = "%g");
void append(double val, const char* format = "%g");
+ // Padded hex representations
+ void append(StableHashCode32 val);
+ void append(StableHashCode64 val);
+
void append(char const* str);
void append(char const* str, size_t len);
void append(const char* textBegin, char const* textEnd);
@@ -549,6 +561,14 @@ namespace Slang
{
append(val, radix);
}
+ explicit String(StableHashCode32 val)
+ {
+ append(val);
+ }
+ explicit String(StableHashCode64 val)
+ {
+ append(val);
+ }
explicit String(float val, const char* format = "%g")
{
append(val, format);
@@ -869,7 +889,8 @@ namespace Slang
return contains(str.begin());
}
- HashCode getHashCode() const
+ static constexpr bool kHasUniformHash = true;
+ HashCode64 getHashCode() const
{
return Slang::getHashCode(StringRepresentation::asSlice(m_buffer));
}