zeux/volk

`visibility (hidden)` is problematic in certain circumstances

radgeRayden opened this issue · 6 comments

I use volk through an FFI interface, so my only option is to build it as a shared library. At least on linux this doesn't work because of the pragma at https://github.com/zeux/volk/blob/master/volk.c#L1262, which makes all vk* functions hidden such that they can't be dynamically linked.
I understand there's probably some advantage to this if you're statically linking, but perhaps it could be configurable via a preprocessor option? Locally I just patched it so that it says "default" instead and it works for me.

zeux commented

So this is interesting because I think Linux is exactly why the hidden visibility was chosen.
Please refer to #1 (comment) for motivation and some discussion.

I'd be reluctant to change this by default, but I'm happy to accept a PR with a CMake option (or if you don't use CMake, just a define) along the lines of VOLK_DEFAULT_VISIBILITY.

zeux commented

Fixed by #52 - thanks!

I would suggest warning about this issue and mentioning the VOLK_DEFAULT_VISIBILITY define in the Readme. This might have saved me some time, since I got pretty confused about why I was getting undefined references to Vulkan functions in some code that uses another shared library where volk is included, but not in that shared library itself, until I found this issue here.

It turned out that using VOLK_DEFAULT_VISIBILITY did not work in my case. Although the undefined references were resolved then, as mentioned in #1, it seemed that the vkGetInstanceProcAddr function would then incorrectly return the addresses of the function pointers in volk, rather than of the actual functions in the Vulkan library. I am not sure how this mechanism works exactly, thus the only solution I saw was to change volk to use different names for the exported functions (which worked). To do so, I used the following small adjustment to generate.py:

diff --git a/generate.py b/generate.py
index b4c044e..cfe7c16 100755
--- a/generate.py
+++ b/generate.py
@@ -146,17 +146,19 @@ if __name__ == "__main__":
                        if name == 'vkGetDeviceProcAddr':
                                type = 'VkInstance'
 
+                       export_name = 'l' + name
+
                        if is_descendant_type(types, type, 'VkDevice') and name not in instance_commands:
-                               blocks['LOAD_DEVICE'] += '\t' + name + ' = (PFN_' + name + ')load(context, "' + name + '");\n'
-                               blocks['DEVICE_TABLE'] += '\tPFN_' + name + ' ' + name + ';\n'
-                               blocks['LOAD_DEVICE_TABLE'] += '\ttable->' + name + ' = (PFN_' + name + ')load(context, "' + name + '");\n'
+                               blocks['LOAD_DEVICE'] += '\t' + export_name + ' = (PFN_' + name + ')load(context, "' + name + '");\n'
+                               blocks['DEVICE_TABLE'] += '\tPFN_' + name + ' ' + export_name + ';\n'
+                               blocks['LOAD_DEVICE_TABLE'] += '\ttable->' + export_name + ' = (PFN_' + name + ')load(context, "' + name + '");\n'
                        elif is_descendant_type(types, type, 'VkInstance'):
-                               blocks['LOAD_INSTANCE'] += '\t' + name + ' = (PFN_' + name + ')load(context, "' + name + '");\n'
+                               blocks['LOAD_INSTANCE'] += '\t' + export_name + ' = (PFN_' + name + ')load(context, "' + name + '");\n'
                        elif type != '':
-                               blocks['LOAD_LOADER'] += '\t' + name + ' = (PFN_' + name + ')load(context, "' + name + '");\n'
+                               blocks['LOAD_LOADER'] += '\t' + export_name + ' = (PFN_' + name + ')load(context, "' + name + '");\n'
 
-                       blocks['PROTOTYPES_H'] += 'extern PFN_' + name + ' ' + name + ';\n'
-                       blocks['PROTOTYPES_C'] += 'PFN_' + name + ' ' + name + ';\n'
+                       blocks['PROTOTYPES_H'] += 'extern PFN_' + name + ' ' + export_name + ';\n'
+                       blocks['PROTOTYPES_C'] += 'PFN_' + name + ' ' + export_name + ';\n'
 
                for key in block_keys:
                        if blocks[key].endswith(ifdef):

Also, a few calls in volk.c needed to be trivially updated to use the new names:

diff --git a/volk.c b/volk.c
index e2c9e02..734830d 100644
--- a/volk.c
+++ b/volk.c
@@ -34,12 +34,12 @@ static void volkGenLoadDeviceTable(struct VolkDeviceTable* table, void* context,
 
 static PFN_vkVoidFunction vkGetInstanceProcAddrStub(void* context, const char* name)
 {
-       return vkGetInstanceProcAddr((VkInstance)context, name);
+       return lvkGetInstanceProcAddr((VkInstance)context, name);
 }
 
 static PFN_vkVoidFunction vkGetDeviceProcAddrStub(void* context, const char* name)
 {
-       return vkGetDeviceProcAddr((VkDevice)context, name);
+       return lvkGetDeviceProcAddr((VkDevice)context, name);
 }
 
 VkResult volkInitialize(void)
@@ -50,7 +50,7 @@ VkResult volkInitialize(void)
                return VK_ERROR_INITIALIZATION_FAILED;
 
        // note: function pointer is cast through void function pointer to silence cast-function-type warning on gcc
8
-       vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)(void(*)(void))GetProcAddress(module, "vkGetInstanceProcA
ddr");
+       lvkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)(void(*)(void))GetProcAddress(module, "vkGetInstanceProc
Addr");
 #elif defined(__APPLE__)
        void* module = dlopen("libvulkan.dylib", RTLD_NOW | RTLD_LOCAL);
        if (!module)
@@ -60,7 +60,7 @@ VkResult volkInitialize(void)
        if (!module)
                return VK_ERROR_INITIALIZATION_FAILED;
 
-       vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)dlsym(module, "vkGetInstanceProcAddr");
+       lvkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)dlsym(module, "vkGetInstanceProcAddr");
 #else
        void* module = dlopen("libvulkan.so.1", RTLD_NOW | RTLD_LOCAL);
        if (!module)
@@ -68,7 +68,7 @@ VkResult volkInitialize(void)
        if (!module)
                return VK_ERROR_INITIALIZATION_FAILED;
 
-       vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)dlsym(module, "vkGetInstanceProcAddr");
+       lvkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)dlsym(module, "vkGetInstanceProcAddr");
 #endif
 
        volkGenLoadLoader(NULL, vkGetInstanceProcAddrStub);
@@ -78,7 +78,7 @@ VkResult volkInitialize(void)
 
 void volkInitializeCustom(PFN_vkGetInstanceProcAddr handler)
 {
-       vkGetInstanceProcAddr = handler;
+       lvkGetInstanceProcAddr = handler;
 
        volkGenLoadLoader(NULL, vkGetInstanceProcAddrStub);
 }
@@ -87,11 +87,11 @@ uint32_t volkGetInstanceVersion(void)
 {
 #if defined(VK_VERSION_1_1)
        uint32_t apiVersion = 0;
-       if (vkEnumerateInstanceVersion && vkEnumerateInstanceVersion(&apiVersion) == VK_SUCCESS)
+       if (lvkEnumerateInstanceVersion && lvkEnumerateInstanceVersion(&apiVersion) == VK_SUCCESS)
                return apiVersion;
 #endif
 
-       if (vkCreateInstance)
+       if (lvkCreateInstance)
                return VK_API_VERSION_1_0;
 
        return 0;

Thanks for reporting, this seems to be exactly the kind of problem that justified introducing the pragma in the first case. I haven't faced it but I can't be sure whether it is due to my toolset or my machine, so if it does happen I'll use your fix.

Hello from the future in 3 years. Recently I tried using volk again (same situation, still need it to be a dynamic library for FFI), but this time it segfaulted on init, presumably from this issue. Confirmed it by making a basic C program with dynamic and static linking. I mentioned this to paniq (who makes the language using the bindings) and he pointed me to the RTLD_DEEPBIND flag for dlopen. Documentation says:

Place the lookup scope of the symbols in this library ahead of the global scope. This means that a self-contained library will use its own symbols in preference to global symbols with the same name contained in libraries that have already been loaded.

So I changed the two dlopen calls to use this flag as well and lo and behold, the problem went away. @zeux I'd be happy to make a PR for this fix as well, if it doesn't sound like it will break anything else in horrible ways.