yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
f65d756bf
master
1#include "slang-perfect-hash.h" 2 3#include "../core/slang-string-util.h" 4#include "../core/slang-writer.h" 5 6namespace Slang 7{ 8 9// Implemented according to "Hash, displace, and compress" 10// https://cmph.sourceforge.net/papers/esa09.pdf 11HashFindResult minimalPerfectHash (const List < String >& ss ,HashParams & hashParams ) 12{ 13// Check for uniqueness 14for (Index i = 0 ;i < ss .getCount ();++ i ) 15 { 16for (Index j = i + 1 ;j < ss .getCount ();++ j ) 17 { 18if (ss [i ]== ss [j ]) 19 { 20return HashFindResult ::NonUniqueKeys ; 21 } 22 } 23 } 24 25SLANG_ASSERT (UIndex (ss .getCount ())< std::numeric_limits < UInt32 > ::max ()); 26const UInt32 nBuckets = UInt32 (ss .getCount ()); 27List < List < String >> initialBuckets ; 28initialBuckets .setCount (nBuckets ); 29 30const auto hash = [& ](const String & s ,const HashCode32 salt = 0 )-> UInt32 31 { 32// 33// The current getStableHashCode is susceptible to patterns of 34// collisions causing the search to fail for the SPIR-V opnames; it 35// performs poorly on short strings, taking over 300000 iterations to 36// diverge on "Ceil" and "FMix" (and place them in already unoccupied 37// slots)! 38// 39// Use FNV Hash here which seem perform much better on these short inputs 40// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function 41// 42// If you change this, don't forget to also sync the version below in 43// the printing code. 44UInt32 h = salt ; 45for (const char c :s ) 46h = (h * 0x01000193 ) ^c ; 47return h %nBuckets ; 48 }; 49 50// Assign the inputs into their buckets according to the hash without salt. 51// Sort the buckets according to size, so that later we can make these have 52// unique destinations starting with the largest ones first as they are at 53// most risk of collision. 54for (const auto & s :ss ) 55 { 56initialBuckets [hash (s )].add (s ); 57 } 58initialBuckets .stableSort ([](const List < String >& a ,const List < String >& b ) 59 {return a .getCount ()> b .getCount (); }); 60 61// These are our outputs, the salts are calculated such that for all input 62// word, x, hash(x, salt[hash(x, 0)]) is unique 63// 64// We keep the final table as we need to detect when we've been given a 65// word not in our language. 66hashParams .saltTable .setCount (nBuckets ); 67for (auto & s :hashParams .saltTable ) 68s = 0 ; 69hashParams .destTable .setCount (nBuckets ); 70for (auto & s :hashParams .destTable ) 71s .reduceLength (0 ); 72 73// This mask will, in each salt tryout, be used to prevent collisions 74// within a single bucket. 75List < bool > bucketDestinations = List < bool > ::makeRepeated (false,nBuckets ); 76 77for (const auto & b :initialBuckets ) 78 { 79// Break if we've reached the empty buckets 80if (!b .getCount ()) 81 { 82break ; 83 } 84 85// Try out all the salts until we get one which has no internal 86// collisions for this bucket and also no collisions with the buckets 87// we've processed so far. 88UInt32 salt = 1 ; 89while (true) 90 { 91bool collision = false; 92for (auto & d :bucketDestinations ) 93 { 94d = false; 95 } 96 97for (const auto & s :b ) 98 { 99const auto i = hash (s ,salt ); 100if (hashParams .destTable [i ].getLength ()|| bucketDestinations [i ]) 101 { 102collision = true; 103break ; 104 } 105bucketDestinations [i ]= true; 106 } 107if (!collision ) 108 { 109break ; 110 } 111salt ++ ; 112 113// If we fail to find a solution after some massive amount of tries 114// it's almost certainly because of some property of the hash 115// function and language causing an irresolvable collision. 116if (salt > 10000 * nBuckets ) 117 { 118return HashFindResult ::UnavoidableHashCollision ; 119 } 120 } 121for (const auto & s :b ) 122 { 123hashParams .saltTable [hash (s )]= salt ; 124hashParams .destTable [hash (s ,salt )]= s ; 125 } 126 } 127return HashFindResult ::Success ; 128} 129 130String perfectHashToEmbeddableCpp ( 131const HashParams & hashParams , 132const UnownedStringSlice & valueType , 133const UnownedStringSlice & funcName , 134const List < String >& values ) 135{ 136SLANG_ASSERT (hashParams .saltTable .getCount ()== hashParams .destTable .getCount ()); 137SLANG_ASSERT (hashParams .saltTable .getCount ()== values .getCount ()); 138 139StringBuilder sb ; 140StringWriter writer (& sb ,WriterFlags (0 )); 141WriterHelper w (& writer ); 142const auto line = [& ](const char * l ) 143 { 144w .put (l ); 145w .put ("\n" ); 146 }; 147 148w ."bool %s(const UnownedStringSlice& str, %s& value)\n" , 150String (funcName ).getBuffer (), 151String (valueType ).getBuffer ()); 152line ("{" ); 153 154w ." static const unsigned tableSalt[%d] = {\n" , (int )hashParams .saltTable .getCount ()); 155w ." " ); 156for (Index i = 0 ;i < hashParams .saltTable .getCount ();++ i ) 157 { 158const auto salt = hashParams .saltTable [i ]; 159if (i != hashParams .saltTable .getCount ()- 1 ) 160 { 161w ." %d," ,salt ); 162if (i %16 == 15 ) 163 { 164w ."\n " ); 165 } 166 } 167else 168 { 169w ." %d" ,salt ); 170 } 171 } 172line ("\n };" ); 173line ("" ); 174 175w ." using KV = std::pair<const char*, %s>;\n" ,String (valueType ).getBuffer ()); 176line ("" ); 177 178w ." static const KV words[%d] =\n" , (int )hashParams .destTable .getCount ()); 179line (" {" ); 180for (Index i = 0 ;i < hashParams .destTable .getCount ();++ i ) 181 { 182const auto & s = hashParams .destTable [i ]; 183const auto & v = values [i ]; 184w ." {\"%s\", %s},\n" ,s .getBuffer (),v .getBuffer ()); 185 } 186line (" };" ); 187line ("" ); 188 189// Make sure to update the hash function in the search function above if 190// you change this. 191line (" static const auto hash = [](const UnownedStringSlice& str, UInt32 salt){" ); 192line (" UInt32 h = salt;" ); 193line (" for (const char c : str)" ); 194line (" h = (h * 0x01000193) ^ c;" ); 195w ." return h %% %d;\n" , (int )hashParams .saltTable .getCount ()); 196line (" };" ); 197line ("" ); 198 199line (" const auto i = hash(str, tableSalt[hash(str, 0)]);" ); 200line (" if(str == words[i].first)" ); 201line (" {" ); 202line (" value = words[i].second;" ); 203line (" return true;" ); 204line (" }" ); 205line (" else" ); 206line (" {" ); 207line (" return false;" ); 208line (" }" ); 209line ("}" ); 210line ("" ); 211 212return sb .produceString (); 213} 214 215}// namespace Slang