104104 'vkEnumerateInstanceLayerProperties' ,
105105 'vkEnumerateInstanceVersion' ]
106106
107+ def _read_max_loader_string_length_from_c_header () -> int :
108+ header_path = os .path .join (os .path .dirname (__file__ ), '..' , '..' , 'loader' , 'loader_common.h' )
109+ with open (header_path , 'r' , encoding = 'utf-8' ) as f :
110+ header_text = f .read ()
111+ match = re .search (r'^\s*static const int MaxLoaderStringLength\s*=\s*(\d+)\s*;' , header_text , re .MULTILINE )
112+ assert match , (
113+ f'Could not find "static const int MaxLoaderStringLength = <N>;" in { header_path } . '
114+ 'loader_hash_string() below needs this bound to stay in sync with loader/loader_common.h - '
115+ 'did that declaration get renamed or reformatted?' )
116+ return int (match .group (1 ))
117+
118+ # Must match MaxLoaderStringLength in loader/loader_common.h - loader_hash_string() there stops scanning
119+ # after this many bytes (a name of exactly this length is scanned in full and hashes the same either way;
120+ # only a name LONGER than this would hash differently in C than in Python below). Parsed directly out of
121+ # the header (rather than hand-copied) so the two can never silently drift apart.
122+ MAX_LOADER_STRING_LENGTH = _read_max_loader_string_length_from_c_header ()
123+
124+ # 32-bit FNV-1a (https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function). Must stay
125+ # byte-for-byte identical to loader_hash_string() in loader/loader_common.h - this precomputes the hash
126+ # constants baked into the generated *_gpa()/dispatch-table-lookup functions below, which the C function
127+ # then has to reproduce at runtime to hit the right branch. HashString.MatchesGeneratorEmbeddedConstants in
128+ # tests/loader_hash_string_tests.cpp catches any divergence.
129+ def loader_hash_string (s : str ) -> int :
130+ assert len (s ) <= MAX_LOADER_STRING_LENGTH , (
131+ f'"{ s } " is { len (s )} bytes, over MaxLoaderStringLength ({ MAX_LOADER_STRING_LENGTH } ). '
132+ 'loader_hash_string() in loader/loader_common.h truncates its scan at that length, so this name '
133+ "would hash differently at runtime than the constant this generator is about to embed for it - "
134+ 'shorten the name or raise MaxLoaderStringLength in both the C and Python copies.' )
135+ h = 0x811c9dc5
136+ for c in s .encode ('ascii' ):
137+ h ^= c
138+ h = (h * 0x01000193 ) & 0xFFFFFFFF
139+ return h
140+
141+ # Raises if any two names in `names` share a hash - used as a generation-time tripwire so that if the
142+ # Vulkan command set ever grows into a collision, it's caught immediately with a clear message instead of
143+ # failing to compile with an inscrutable "duplicate case value" error naming neither colliding string (each
144+ # name in `names` becomes its own `case <hash>u:` label in the generated switch - see OutputLoaderLookupFunc
145+ # and InstExtensionGPA below).
146+ def check_no_hash_collisions (names , context : str ):
147+ seen = {}
148+ for name in names :
149+ h = loader_hash_string (name )
150+ if h in seen and seen [h ] != name :
151+ raise RuntimeError (
152+ f'FNV-1a hash collision in { context } : "{ seen [h ]} " and "{ name } " both hash to { h :#010x} . '
153+ 'Left unaddressed, this would make the generated switch emit two identical case labels and '
154+ 'fail to compile with a "duplicate case value" error naming neither string - '
155+ 'loader_hash_string() (C and Python copies) should be revisited (e.g. a different seed/prime).' )
156+ seen [h ] = name
157+
107158# This class is a container for any source code, data, or other behavior that is necessary to
108159# customize the generator script for a specific target API variant (e.g. Vulkan SC). As such,
109160# all of these API-specific interfaces and their use in the generator script are part of the
@@ -275,7 +326,9 @@ def OutputPrototypesInHeader(self, out: list):
275326
276327// Extension interception for vkGetInstanceProcAddr function, so we can return
277328// the appropriate information for any instance extensions we know about.
278- bool extension_instance_gpa(struct loader_instance *ptr_instance, const char *name, void **addr);
329+ // name_hash must be loader_hash_string(name) - callers that already have it (e.g.
330+ // trampoline_get_proc_addr) pass it through instead of hashing name a second time.
331+ bool extension_instance_gpa(struct loader_instance *ptr_instance, const char *name, uint32_t name_hash, void **addr);
279332
280333struct loader_instance_extension_enable_list; // Forward declaration
281334
@@ -704,6 +757,7 @@ def OutputLoaderLookupFunc(self, out):
704757 out .append (' struct loader_device* dev = (struct loader_device *)table;\n ' )
705758 out .append (' const struct loader_instance* inst = dev->phys_dev_term->this_icd_term->this_instance;\n ' )
706759 out .append (' uint32_t api_version = VK_MAKE_API_VERSION(0, inst->app_api_version.major, inst->app_api_version.minor, inst->app_api_version.patch);\n ' )
760+ out .append (' const uint32_t name_hash = loader_hash_string(name);\n ' )
707761 out .append ('\n ' )
708762 else :
709763 cur_type = 'instance'
@@ -718,8 +772,11 @@ def OutputLoaderLookupFunc(self, out):
718772 out .append ('\n ' )
719773 out .append (' *found_name = true;\n ' )
720774 out .append (' name += 2;\n ' )
775+ out .append (' const uint32_t name_hash = loader_hash_string(name);\n ' )
721776
777+ out .append (' switch (name_hash) {\n ' )
722778
779+ base_names_for_collision_check = []
723780 for command_list in [self .core_commands , self .extension_commands ]:
724781 commands = command_list
725782
@@ -729,12 +786,12 @@ def OutputLoaderLookupFunc(self, out):
729786 is_inst_handle_type = command .params [0 ].type in ['VkInstance' , 'VkPhysicalDevice' ]
730787 if ((cur_type == 'instance' and is_inst_handle_type ) or (cur_type == 'device' and not is_inst_handle_type )):
731788
732- current_block = self .DescribeBlock (command , current_block , out )
789+ current_block = self .DescribeBlock (command , current_block , out , indent = ' ' )
733790 if len (command .extensions ) == 0 :
734791 if cur_type == 'device' :
735792 effective_version_name = APISpecific .getEffectiveVersionName (self .targetApiName , command .version )
736793 api_version = effective_version_name .replace ('_VERSION_' , '_API_VERSION_' )
737- version_check = f" if (dev->should_ignore_device_commands_from_newer_version && api_version < { api_version } ) return NULL;\n "
794+ version_check = f" if (dev->should_ignore_device_commands_from_newer_version && api_version < { api_version } ) return NULL;\n "
738795 else :
739796 version_check = ''
740797
@@ -750,22 +807,29 @@ def OutputLoaderLookupFunc(self, out):
750807 if command .protect is not None :
751808 out .append (f'#if defined({ command .protect } )\n ' )
752809
753- out .append (f' if (!strcmp(name, "{ base_name } ")) ' )
810+ base_names_for_collision_check .append (base_name )
811+ out .append (f' case { loader_hash_string (base_name ):#010x} u:\n ' )
812+ out .append (f' if (!strcmp(name, "{ base_name } ")) ' )
754813 if command .name in DEVICE_CMDS_MUST_USE_TRAMP :
755814 if version_check != '' :
756- out .append (f'{{\n { version_check } return dev->layer_extensions.{ command .extensions [0 ][3 :].lower ()} _enabled ? (void *){ base_name } : NULL;\n }}\n ' )
815+ out .append (f'{{\n { version_check } return dev->layer_extensions.{ command .extensions [0 ][3 :].lower ()} _enabled ? (void *){ base_name } : NULL;\n }}\n ' )
757816 else :
758817 out .append (f'return dev->layer_extensions.{ command .extensions [0 ][3 :].lower ()} _enabled ? (void *){ base_name } : NULL;\n ' )
759818
760819 else :
761820 if version_check != '' :
762- out .append (f'{{\n { version_check } return (void *)table->{ base_name } ;\n }}\n ' )
821+ out .append (f'{{\n { version_check } return (void *)table->{ base_name } ;\n }}\n ' )
763822 else :
764823 out .append (f'return (void *)table->{ base_name } ;\n ' )
824+ out .append (' break;\n ' )
765825
766826 if command .protect is not None :
767827 out .append (f'#endif // { command .protect } \n ' )
768828
829+ check_no_hash_collisions (base_names_for_collision_check , f'loader_lookup_{ cur_type } _dispatch_table' )
830+ out .append (' default:\n ' )
831+ out .append (' break;\n ' )
832+ out .append (' }\n ' )
769833 out .append ('\n ' )
770834 out .append (' *found_name = false;\n ' )
771835 out .append (' return NULL;\n ' )
@@ -1187,9 +1251,12 @@ def InstExtensionGPA(self, out):
11871251 cur_extension_name = ''
11881252
11891253 out .append ( '// GPA helpers for extensions\n ' )
1190- out .append ( 'bool extension_instance_gpa(struct loader_instance *ptr_instance, const char *name, void **addr) {\n ' )
1191- out .append ( ' *addr = NULL;\n \n ' )
1254+ out .append ( '// name_hash must be loader_hash_string(name) - passed in by the caller so it is only computed once per lookup.\n ' )
1255+ out .append ( 'bool extension_instance_gpa(struct loader_instance *ptr_instance, const char *name, uint32_t name_hash, void **addr) {\n ' )
1256+ out .append ( ' *addr = NULL;\n ' )
1257+ out .append ( ' switch (name_hash) {\n ' )
11921258
1259+ full_names_for_collision_check = []
11931260 for command in [x for x in self .vk .commands .values () if x .extensions ]:
11941261 if (command .version or
11951262 command .extensions [0 ] in WSI_EXT_NAMES or
@@ -1198,7 +1265,7 @@ def InstExtensionGPA(self, out):
11981265 continue
11991266
12001267 if command .extensions [0 ] != cur_extension_name :
1201- out .append ( f'\n // ---- { command .extensions [0 ]} extension commands\n ' )
1268+ out .append ( f'\n // ---- { command .extensions [0 ]} extension commands\n ' )
12021269 cur_extension_name = command .extensions [0 ]
12031270
12041271 if command .protect is not None :
@@ -1207,24 +1274,31 @@ def InstExtensionGPA(self, out):
12071274 #base_name = command.name[2:]
12081275 base_name = SHARED_ALIASES [command .name ] if command .name in SHARED_ALIASES else command .name [2 :]
12091276
1277+ full_names_for_collision_check .append (command .name )
1278+ out .append (f' case { loader_hash_string (command .name ):#010x} u:\n ' )
12101279 if len (command .extensions ) > 0 and self .vk .extensions [command .extensions [0 ]].instance :
1211- out .append ( f' if (!strcmp("{ command .name } ", name)) {{\n ' )
1212- out .append ( ' *addr = (ptr_instance->enabled_extensions.' )
1280+ out .append ( f' if (!strcmp("{ command .name } ", name)) {{\n ' )
1281+ out .append ( ' *addr = (ptr_instance->enabled_extensions.' )
12131282 out .append ( command .extensions [0 ][3 :].lower ())
12141283 out .append ( ' == 1)\n ' )
1215- out .append ( f' ? (void *){ base_name } \n ' )
1216- out .append ( ' : NULL;\n ' )
1217- out .append ( ' return true;\n ' )
1218- out .append ( ' }\n ' )
1284+ out .append ( f' ? (void *){ base_name } \n ' )
1285+ out .append ( ' : NULL;\n ' )
1286+ out .append ( ' return true;\n ' )
1287+ out .append ( ' }\n ' )
12191288 else :
1220- out .append ( f' if (!strcmp("{ command .name } ", name)) {{\n ' )
1221- out .append ( f' *addr = (void *){ base_name } ;\n ' )
1222- out .append ( ' return true;\n ' )
1223- out .append ( ' }\n ' )
1289+ out .append ( f' if (!strcmp("{ command .name } ", name)) {{\n ' )
1290+ out .append ( f' *addr = (void *){ base_name } ;\n ' )
1291+ out .append ( ' return true;\n ' )
1292+ out .append ( ' }\n ' )
1293+ out .append (' break;\n ' )
12241294
12251295 if command .protect is not None :
12261296 out .append ( f'#endif // { command .protect } \n ' )
12271297
1298+ check_no_hash_collisions (full_names_for_collision_check , 'extension_instance_gpa' )
1299+ out .append ( ' default:\n ' )
1300+ out .append ( ' break;\n ' )
1301+ out .append ( ' }\n ' )
12281302 out .append ( ' return false;\n ' )
12291303 out .append ( '}\n \n ' )
12301304
0 commit comments