diff options
| author | Ellie Hermaszewska <ellieh@nvidia.com> | 2023-08-16 08:57:47 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-08-16 08:57:47 +0800 |
| commit | 45d9961a6a86d184248ef84f6a07125b0c224f97 (patch) | |
| tree | c91d9b9aa722ceb727f7f1c8c2041d7d2bb13466 /source/core | |
| parent | e34b005c47d265105e7bba509cadaa3e225237af (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.h | 637 | ||||
| -rw-r--r-- | source/core/slang-file-system.cpp | 19 | ||||
| -rw-r--r-- | source/core/slang-hash.h | 282 | ||||
| -rw-r--r-- | source/core/slang-hex-dump-util.cpp | 10 | ||||
| -rw-r--r-- | source/core/slang-linked-list.h | 39 | ||||
| -rw-r--r-- | source/core/slang-memory-file-system.cpp | 10 | ||||
| -rw-r--r-- | source/core/slang-riff-file-system.cpp | 18 | ||||
| -rw-r--r-- | source/core/slang-smart-pointer.h | 2 | ||||
| -rw-r--r-- | source/core/slang-stable-hash.h | 99 | ||||
| -rw-r--r-- | source/core/slang-string.cpp | 18 | ||||
| -rw-r--r-- | source/core/slang-string.h | 27 |
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)); } |
