lib/mesa/0001-static-lavapipe.patch
$ cat 0001-static-lavapipe.patch
diff --git a/meson.build b/meson.build
index 5eaae97..f9023c9 100644
--- a/meson.build
+++ b/meson.build
@@ -1928,8 +1928,7 @@ if dep_llvm.found()
     if dep_llvm.type_name() == 'internal'
       _llvm_rtti = subproject('llvm').get_variable('has_rtti', true)
     else
-      # The CMake finder will return 'ON', the llvm-config will return 'YES'
-      _llvm_rtti = ['ON', 'YES'].contains(dep_llvm.get_variable(cmake : 'LLVM_ENABLE_RTTI', configtool: 'has-rtti'))
+      _llvm_rtti = true
     endif
     if _rtti != _llvm_rtti
       if _llvm_rtti
@@ -2104,21 +2103,11 @@ if build_machine.system() == 'windows'
     prog_bison = find_program('bison', 'yacc', required : needs_flex_bison, disabler : true)
   endif
 else
-  prog_bison = find_program('bison', required : false)
+  # fuck gnu!!!!
+  prog_bison = find_program('byacc', required : needs_flex_bison, disabler : true)
+  yacc_is_bison = false
 
-  if not prog_bison.found()
-    prog_bison = find_program('byacc', required : needs_flex_bison, disabler : true)
-    yacc_is_bison = false
-  endif
-
-  # Disable deprecated keyword warnings, since we have to use them for
-  # old-bison compat.  See discussion in
-  # https://gitlab.freedesktop.org/mesa/mesa/merge_requests/2161
-  if find_program('bison', required : false, version : '> 2.3').found()
-    prog_bison = [prog_bison, '-Wno-deprecated']
-  endif
-
-  prog_flex = find_program('flex', required : needs_flex_bison, disabler : true)
+  prog_flex = find_program('reflex', required : needs_flex_bison, disabler : true)
   prog_flex_cpp = prog_flex
 endif
 
@@ -2159,8 +2148,7 @@ if with_platform_wayland
       dependencies: dep_wayland_client)
     pre_args += ['-DHAVE_WL_CREATE_QUEUE_WITH_NAME']
   endif
-  # This can be renamed just `wayland` when we require at least 1.8
-  mod_wl = import('unstable-wayland')
+  mod_wl = import('wayland')
 endif
 
 # Even if we find OpenMP, Gitlab CI fails to link with gcc/i386 and clang/anyarch.
diff --git a/src/gallium/auxiliary/gallivm/lp_bld_init_orc.cpp b/src/gallium/auxiliary/gallivm/lp_bld_init_orc.cpp
index 16357fb..60dd3ac 100644
--- a/src/gallium/auxiliary/gallivm/lp_bld_init_orc.cpp
+++ b/src/gallium/auxiliary/gallivm/lp_bld_init_orc.cpp
@@ -62,6 +62,21 @@
 #endif
 /* else use old RTDyldObjectLinkingLayer (RuntimeDyld backend) */
 
+extern "C" {
+
+/* just stub everything it wants */
+void __register_frame(const void *) {}
+void __deregister_frame(const void *) {}
+void __register_frame_info(const void *, void *) {}
+void __register_frame_info_bases(const void *, void *, void *, void *) {}
+void __register_frame_info_table(const void *, void *) {}
+void __register_frame_info_table_bases(const void *, void *, void *, void *) {}
+void __register_frame_table(const void *) {}
+void __deregister_frame_info(const void *) {}
+void __deregister_frame_info_bases(const void *) {}
+
+}
+
 namespace {
 
 class LPObjectCacheORC : public llvm::ObjectCache {
@@ -320,6 +335,76 @@ LLVMErrorRef module_transform_wrapper(
    return LLVMOrcThreadSafeModuleWithModuleDo(*ModInOut, *module_transform, Ctx);
 }
 
+static llvm::Expected<llvm::orc::JITDylibSP>
+lpjit_create_static_process_symbols_jd(llvm::orc::LLJIT &J)
+{
+   using llvm::orc::JITDylibSP;
+
+   auto &ES = J.getExecutionSession();
+   auto &JD = ES.createBareJITDylib("process_symbols");
+
+   /* ok, so lljit normally wires the process-symbols jitdylib to a
+    * DynamicLibrarySearchGenerator, which resolves the symbold through dlopen(NULL)
+    * for static we still need the process symbols jitdylib itself
+    * but we need definitions to come from explicit add_mapping_to_jd()
+    * calls instead of the host process loader
+    *
+    * this might mean we have to defin some external symbols in the future,
+    * if some new helper is called, and no one maps it
+    */
+   return JITDylibSP(&JD);
+}
+
+static void
+lpjit_define_process_symbol(llvm::orc::LLJIT &J, llvm::orc::JITDylib &JD,
+                            const char *name, void *addr)
+{
+#if LLVM_VERSION_MAJOR >= 17
+   using llvm::orc::ExecutorAddr;
+   using llvm::orc::ExecutorSymbolDef;
+   using llvm::JITSymbolFlags;
+#else
+   using llvm::JITEvaluatedSymbol;
+#endif
+   using llvm::orc::SymbolMap;
+
+   SymbolMap map(1);
+#if LLVM_VERSION_MAJOR >= 17
+   map[J.mangleAndIntern(name)] =
+      ExecutorSymbolDef(ExecutorAddr::fromPtr(addr), JITSymbolFlags::Exported);
+#else
+   map[J.mangleAndIntern(name)] = JITEvaluatedSymbol::fromPointer(addr);
+#endif
+   llvm::cantFail(JD.define(llvm::orc::absoluteSymbols(map)));
+}
+
+static void
+lpjit_define_static_process_symbols(llvm::orc::LLJIT &J)
+{
+   auto JD = J.getProcessSymbolsJITDylib();
+   if (!JD)
+      return;
+
+   lpjit_define_process_symbol(J, *JD, "__register_frame",
+                               (void *)__register_frame);
+   lpjit_define_process_symbol(J, *JD, "__deregister_frame",
+                               (void *)__deregister_frame);
+   lpjit_define_process_symbol(J, *JD, "__register_frame_info",
+                               (void *)__register_frame_info);
+   lpjit_define_process_symbol(J, *JD, "__register_frame_info_bases",
+                               (void *)__register_frame_info_bases);
+   lpjit_define_process_symbol(J, *JD, "__register_frame_info_table",
+                               (void *)__register_frame_info_table);
+   lpjit_define_process_symbol(J, *JD, "__register_frame_info_table_bases",
+                               (void *)__register_frame_info_table_bases);
+   lpjit_define_process_symbol(J, *JD, "__register_frame_table",
+                               (void *)__register_frame_table);
+   lpjit_define_process_symbol(J, *JD, "__deregister_frame_info",
+                               (void *)__deregister_frame_info);
+   lpjit_define_process_symbol(J, *JD, "__deregister_frame_info_bases",
+                               (void *)__deregister_frame_info_bases);
+}
+
 LPJit::LPJit() :jit_dylib_count(0) {
    using namespace llvm::orc;
 
@@ -337,6 +422,9 @@ LPJit::LPJit() :jit_dylib_count(0) {
    lljit = ExitOnErr(
       LLJITBuilder()
          .setJITTargetMachineBuilder(std::move(JTMB))
+         .setLinkProcessSymbolsByDefault(true)
+         .setProcessSymbolsJITDylibSetup(*lpjit_create_static_process_symbols_jd)
+         .setPlatformSetUp(llvm::orc::setUpInactivePlatform)
 #ifdef USE_JITLINK
          .setObjectLinkingLayerCreator(
 #if LLVM_VERSION_MAJOR >= 21
@@ -356,6 +444,8 @@ LPJit::LPJit() :jit_dylib_count(0) {
 #endif
          .create());
 
+   lpjit_define_static_process_symbols(*lljit);
+
    LLVMOrcIRTransformLayerRef TL = wrap(&lljit->getIRTransformLayer());
    LLVMOrcIRTransformLayerSetTransform(TL, *module_transform_wrapper, NULL);
 }
diff --git a/src/gallium/auxiliary/gallivm/lp_bld_misc.cpp b/src/gallium/auxiliary/gallivm/lp_bld_misc.cpp
index ce8ca02..5689dcf 100644
--- a/src/gallium/auxiliary/gallivm/lp_bld_misc.cpp
+++ b/src/gallium/auxiliary/gallivm/lp_bld_misc.cpp
@@ -599,6 +599,9 @@ lp_build_create_jit_compiler_for_module(LLVMExecutionEngineRef *OutJIT,
 
    JIT = builder.create();
 
+   if (!JIT)
+      goto fail;
+
    if (cache_out) {
       LPObjectCache *objcache = new LPObjectCache(cache_out);
       JIT->setObjectCache(objcache);
@@ -609,13 +612,16 @@ lp_build_create_jit_compiler_for_module(LLVMExecutionEngineRef *OutJIT,
    JITEventListener *JEL = JITEventListener::createIntelJITEventListener();
    JIT->RegisterJITEventListener(JEL);
 #endif
-   if (JIT) {
-      *OutJIT = wrap(JIT);
-      return 0;
-   }
+
+   *OutJIT = wrap(JIT);
+   return 0;
+
+fail:
    lp_free_generated_code(*OutCode);
    *OutCode = 0;
    delete MM;
+   if (Error.empty())
+      Error = "LLVM JIT creation failed";
    *OutError = strdup(Error.c_str());
    return 1;
 }
diff --git a/src/gallium/frontends/lavapipe/lvp_device.c b/src/gallium/frontends/lavapipe/lvp_device.c
index 187f17c..42de27a 100644
--- a/src/gallium/frontends/lavapipe/lvp_device.c
+++ b/src/gallium/frontends/lavapipe/lvp_device.c
@@ -27,6 +27,7 @@
 
 #include "pipe-loader/pipe_loader.h"
 #include "git_sha1.h"
+#include "vk_common_entrypoints.h"
 #include "vk_cmd_enqueue_entrypoints.h"
 #include "vk_sampler.h"
 #include "vk_util.h"
@@ -1421,11 +1422,56 @@ lvp_physical_device_init(struct lvp_physical_device *device,
 {
    VkResult result;
 
-   struct vk_physical_device_dispatch_table dispatch_table;
+   struct vk_physical_device_dispatch_table dispatch_table = {0};
    vk_physical_device_dispatch_table_from_entrypoints(
       &dispatch_table, &lvp_physical_device_entrypoints, true);
    vk_physical_device_dispatch_table_from_entrypoints(
       &dispatch_table, &wsi_physical_device_entrypoints, false);
+   dispatch_table.GetPhysicalDeviceFeatures2 = vk_common_GetPhysicalDeviceFeatures2;
+   dispatch_table.GetPhysicalDeviceFeatures2KHR =
+      (PFN_vkGetPhysicalDeviceFeatures2KHR)vk_common_GetPhysicalDeviceFeatures2;
+   dispatch_table.GetPhysicalDeviceProperties2 = lvp_GetPhysicalDeviceProperties2;
+   dispatch_table.GetPhysicalDeviceProperties2KHR = lvp_GetPhysicalDeviceProperties2;
+   dispatch_table.GetPhysicalDeviceFormatProperties2 =
+      lvp_GetPhysicalDeviceFormatProperties2;
+   dispatch_table.GetPhysicalDeviceFormatProperties2KHR =
+      (PFN_vkGetPhysicalDeviceFormatProperties2KHR)
+      lvp_GetPhysicalDeviceFormatProperties2;
+   dispatch_table.GetPhysicalDeviceImageFormatProperties2 =
+      lvp_GetPhysicalDeviceImageFormatProperties2;
+   dispatch_table.GetPhysicalDeviceImageFormatProperties2KHR =
+      (PFN_vkGetPhysicalDeviceImageFormatProperties2KHR)
+      lvp_GetPhysicalDeviceImageFormatProperties2;
+   dispatch_table.GetPhysicalDeviceQueueFamilyProperties2 =
+      lvp_GetPhysicalDeviceQueueFamilyProperties2;
+   dispatch_table.GetPhysicalDeviceQueueFamilyProperties2KHR =
+      (PFN_vkGetPhysicalDeviceQueueFamilyProperties2KHR)
+      lvp_GetPhysicalDeviceQueueFamilyProperties2;
+   dispatch_table.GetPhysicalDeviceMemoryProperties2 =
+      lvp_GetPhysicalDeviceMemoryProperties2;
+   dispatch_table.GetPhysicalDeviceMemoryProperties2KHR =
+      (PFN_vkGetPhysicalDeviceMemoryProperties2KHR)
+      lvp_GetPhysicalDeviceMemoryProperties2;
+   dispatch_table.GetPhysicalDeviceSparseImageFormatProperties2 =
+      lvp_GetPhysicalDeviceSparseImageFormatProperties2;
+   dispatch_table.GetPhysicalDeviceSparseImageFormatProperties2KHR =
+      (PFN_vkGetPhysicalDeviceSparseImageFormatProperties2KHR)
+      lvp_GetPhysicalDeviceSparseImageFormatProperties2;
+   dispatch_table.GetPhysicalDeviceExternalBufferProperties =
+      lvp_GetPhysicalDeviceExternalBufferProperties;
+   dispatch_table.GetPhysicalDeviceExternalBufferPropertiesKHR =
+      (PFN_vkGetPhysicalDeviceExternalBufferPropertiesKHR)
+      lvp_GetPhysicalDeviceExternalBufferProperties;
+   dispatch_table.GetPhysicalDeviceExternalFenceProperties =
+      lvp_GetPhysicalDeviceExternalFenceProperties;
+   dispatch_table.GetPhysicalDeviceExternalFencePropertiesKHR =
+      (PFN_vkGetPhysicalDeviceExternalFencePropertiesKHR)
+      lvp_GetPhysicalDeviceExternalFenceProperties;
+   dispatch_table.GetPhysicalDeviceExternalSemaphoreProperties =
+      lvp_GetPhysicalDeviceExternalSemaphoreProperties;
+   dispatch_table.GetPhysicalDeviceExternalSemaphorePropertiesKHR =
+      (PFN_vkGetPhysicalDeviceExternalSemaphorePropertiesKHR)
+      lvp_GetPhysicalDeviceExternalSemaphoreProperties;
    result = vk_physical_device_init(&device->vk, &instance->vk,
                                     NULL, NULL, NULL, &dispatch_table);
    if (result != VK_SUCCESS) {
@@ -1642,6 +1688,13 @@ lvp_device_get_cache_uuid(void *uuid)
       memcpy(uuid, PACKAGE_VERSION, MIN2(strlen(PACKAGE_VERSION), VK_UUID_SIZE));
 }
 
+VKAPI_ATTR void VKAPI_CALL lvp_GetPhysicalDeviceProperties2(
+   VkPhysicalDevice                            physicalDevice,
+   VkPhysicalDeviceProperties2                *pProperties)
+{
+   vk_common_GetPhysicalDeviceProperties2(physicalDevice, pProperties);
+}
+
 VKAPI_ATTR void VKAPI_CALL lvp_GetPhysicalDeviceQueueFamilyProperties2(
    VkPhysicalDevice                            physicalDevice,
    uint32_t*                                   pCount,
@@ -1745,6 +1798,41 @@ VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL lvp_GetInstanceProcAddr(
                                     pName);
 }
 
+PUBLIC
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(
+   const VkInstanceCreateInfo*                 pCreateInfo,
+   const VkAllocationCallbacks*                pAllocator,
+   VkInstance*                                 pInstance)
+{
+   return lvp_CreateInstance(pCreateInfo, pAllocator, pInstance);
+}
+
+PUBLIC
+VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceVersion(
+   uint32_t*                                   pApiVersion)
+{
+   return lvp_EnumerateInstanceVersion(pApiVersion);
+}
+
+PUBLIC
+VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties(
+   const char*                                 pLayerName,
+   uint32_t*                                   pPropertyCount,
+   VkExtensionProperties*                      pProperties)
+{
+   return lvp_EnumerateInstanceExtensionProperties(pLayerName,
+                                                   pPropertyCount,
+                                                   pProperties);
+}
+
+PUBLIC
+VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceLayerProperties(
+   uint32_t*                                   pPropertyCount,
+   VkLayerProperties*                          pProperties)
+{
+   return lvp_EnumerateInstanceLayerProperties(pPropertyCount, pProperties);
+}
+
 /* The loader wants us to expose a second GetInstanceProcAddr function
  * to work around certain LD_PRELOAD issues seen in apps.
  */
@@ -1756,6 +1844,14 @@ VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vk_icdGetInstanceProcAddr(
    return lvp_GetInstanceProcAddr(instance, pName);
 }
 
+PUBLIC
+VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(
+   VkInstance                                  instance,
+   const char*                                 pName)
+{
+   return vk_icdGetInstanceProcAddr(instance, pName);
+}
+
 static void
 destroy_pipelines(struct lvp_queue *queue)
 {
@@ -1887,12 +1983,43 @@ VKAPI_ATTR VkResult VKAPI_CALL lvp_CreateDevice(
    device->poison_mem = debug_get_bool_option("LVP_POISON_MEMORY", false);
    device->print_cmds = debug_get_bool_option("LVP_CMD_DEBUG", false);
 
-   struct vk_device_dispatch_table dispatch_table;
+   struct vk_device_dispatch_table dispatch_table = {0};
    vk_device_dispatch_table_from_entrypoints(&dispatch_table,
       &lvp_device_entrypoints, true);
    lvp_add_enqueue_cmd_entrypoints(&dispatch_table);
    vk_device_dispatch_table_from_entrypoints(&dispatch_table,
       &wsi_device_entrypoints, false);
+   dispatch_table.CreateCommandPool = vk_common_CreateCommandPool;
+   dispatch_table.AllocateCommandBuffers = vk_common_AllocateCommandBuffers;
+   dispatch_table.CreateFence = vk_common_CreateFence;
+   dispatch_table.DestroyFence = vk_common_DestroyFence;
+   dispatch_table.ResetFences = vk_common_ResetFences;
+   dispatch_table.GetFenceStatus = vk_common_GetFenceStatus;
+   dispatch_table.WaitForFences = vk_common_WaitForFences;
+   dispatch_table.CreateSemaphore = vk_common_CreateSemaphore;
+   dispatch_table.DestroySemaphore = vk_common_DestroySemaphore;
+   dispatch_table.CreateShaderModule = vk_common_CreateShaderModule;
+   dispatch_table.DestroyShaderModule = vk_common_DestroyShaderModule;
+   dispatch_table.CreateRenderPass = vk_common_CreateRenderPass;
+   dispatch_table.CreateRenderPass2 = vk_common_CreateRenderPass2;
+   dispatch_table.CreateRenderPass2KHR =
+      (PFN_vkCreateRenderPass2KHR)vk_common_CreateRenderPass2;
+   dispatch_table.DestroyRenderPass = vk_common_DestroyRenderPass;
+   dispatch_table.CreateQueryPool = lvp_CreateQueryPool;
+   dispatch_table.DestroyQueryPool = lvp_DestroyQueryPool;
+   dispatch_table.CreateFramebuffer = vk_common_CreateFramebuffer;
+   dispatch_table.DestroyFramebuffer = vk_common_DestroyFramebuffer;
+   dispatch_table.DestroyCommandPool = vk_common_DestroyCommandPool;
+   dispatch_table.FreeCommandBuffers = vk_common_FreeCommandBuffers;
+   dispatch_table.FreeMemory = lvp_FreeMemory;
+   dispatch_table.MapMemory = vk_common_MapMemory;
+   dispatch_table.BindBufferMemory = vk_common_BindBufferMemory;
+   dispatch_table.BindImageMemory = vk_common_BindImageMemory;
+   dispatch_table.GetImageSubresourceLayout = vk_common_GetImageSubresourceLayout;
+   dispatch_table.CmdPipelineBarrier = vk_common_CmdPipelineBarrier;
+   dispatch_table.CmdCopyImage = vk_common_CmdCopyImage;
+   dispatch_table.CmdCopyImageToBuffer = vk_common_CmdCopyImageToBuffer;
+   dispatch_table.CmdWriteTimestamp = vk_common_CmdWriteTimestamp;
    VkResult result = vk_device_init(&device->vk,
                                     &physical_device->vk,
                                     &dispatch_table, pCreateInfo,
diff --git a/src/gallium/frontends/lavapipe/lvp_wsi.c b/src/gallium/frontends/lavapipe/lvp_wsi.c
index 83e9659..5d439a7 100644
--- a/src/gallium/frontends/lavapipe/lvp_wsi.c
+++ b/src/gallium/frontends/lavapipe/lvp_wsi.c
@@ -44,7 +44,7 @@ lvp_init_wsi(struct lvp_physical_device *physical_device)
    if (result != VK_SUCCESS)
       return result;
 
-   physical_device->wsi_device.wants_linear = true;
+   physical_device->wsi_device.wants_linear = false;
    physical_device->vk.wsi_device = &physical_device->wsi_device;
 
    return VK_SUCCESS;
diff --git a/src/gallium/targets/lavapipe/lvp_static_shim_gen.py b/src/gallium/targets/lavapipe/lvp_static_shim_gen.py
new file mode 100644
index 0000000..1a7c6fe
--- /dev/null
+++ b/src/gallium/targets/lavapipe/lvp_static_shim_gen.py
@@ -0,0 +1,172 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import sys
+
+
+def _import_vk_entrypoints():
+    util_dir = os.path.normpath(
+        os.path.join(os.path.dirname(__file__), "..", "..", "..", "vulkan", "util")
+    )
+    sys.path.insert(0, util_dir)
+    from vk_entrypoints import get_entrypoints_from_xml
+
+    return get_entrypoints_from_xml
+
+
+def decl_params(entrypoint):
+    return ", ".join(p.decl for p in entrypoint.params) or "void"
+
+
+def call_params(entrypoint):
+    return ", ".join(p.name for p in entrypoint.params)
+
+
+def dispatch_kind(entrypoint):
+    if not entrypoint.params:
+        return "global", None
+
+    first_param = entrypoint.params[0]
+    if first_param.type not in (
+        "VkInstance",
+        "VkPhysicalDevice",
+        "VkDevice",
+        "VkQueue",
+        "VkCommandBuffer",
+    ):
+        return "global", None
+
+    if first_param.type == "VkInstance":
+        return "instance", first_param.name
+    if first_param.type == "VkPhysicalDevice":
+        return "physical_device", first_param.name
+    if first_param.type == "VkDevice":
+        return "device", first_param.name
+    if first_param.type == "VkQueue":
+        return "queue", first_param.name
+    if first_param.type == "VkCommandBuffer":
+        return "command_buffer", first_param.name
+
+    raise ValueError(f"Unhandled dispatch type for vk{entrypoint.name}")
+
+
+def emit_wrapper(entrypoint):
+    body = []
+
+    if entrypoint.name == "GetInstanceProcAddr":
+        body.append(
+            "   return vk_icdGetInstanceProcAddr(instance, pName);\n"
+        )
+    elif entrypoint.name == "GetDeviceProcAddr":
+        body.append(
+            "   VK_FROM_HANDLE(vk_device, vk_device, device);\n"
+            "   return vk_device->dispatch_table.GetDeviceProcAddr(device, pName);\n"
+        )
+    else:
+        kind, handle_name = dispatch_kind(entrypoint)
+        if kind == "global":
+            body.append(
+                f'   PFN_vk{entrypoint.name} proc = (PFN_vk{entrypoint.name})'
+                f'vk_icdGetInstanceProcAddr(VK_NULL_HANDLE, "vk{entrypoint.name}");\n'
+            )
+        elif kind == "instance":
+            body.append(
+                "   VK_FROM_HANDLE(vk_instance, vk_instance, instance);\n"
+                f"   PFN_vk{entrypoint.name} proc = (PFN_vk{entrypoint.name})"
+                f"vk_instance->dispatch_table.{entrypoint.name};\n"
+            )
+        elif kind == "physical_device":
+            body.append(
+                "   VK_FROM_HANDLE(vk_physical_device, pdevice, physicalDevice);\n"
+                f"   PFN_vk{entrypoint.name} proc = (PFN_vk{entrypoint.name})"
+                f"pdevice->dispatch_table.{entrypoint.name};\n"
+            )
+        elif kind == "device":
+            body.append(
+                "   VK_FROM_HANDLE(vk_device, vk_device, device);\n"
+                f"   PFN_vk{entrypoint.name} proc = (PFN_vk{entrypoint.name})"
+                f"vk_device->dispatch_table.{entrypoint.name};\n"
+            )
+        elif kind == "queue":
+            body.append(
+                "   VK_FROM_HANDLE(vk_queue, vk_queue, queue);\n"
+                f"   PFN_vk{entrypoint.name} proc = (PFN_vk{entrypoint.name})"
+                f"vk_queue->base.device->dispatch_table.{entrypoint.name};\n"
+            )
+        elif kind == "command_buffer":
+            body.append(
+                "   VK_FROM_HANDLE(vk_command_buffer, cmd_buffer, commandBuffer);\n"
+                f"   PFN_vk{entrypoint.name} proc = (PFN_vk{entrypoint.name})"
+                f"cmd_buffer->base.device->dispatch_table.{entrypoint.name};\n"
+            )
+        else:
+            raise ValueError(f"Unhandled dispatch kind {kind} for vk{entrypoint.name}")
+        body.append("   assert(proc != NULL);\n")
+
+        params = call_params(entrypoint)
+        if entrypoint.return_type == "void":
+            body.append(f"   proc({params});\n")
+        else:
+            body.append(f"   return proc({params});\n")
+
+    return "".join(body)
+
+
+def render(entrypoints):
+    out = []
+    out.append("/* this file generated from lvp_static_shim_gen.py; don't edit directly */\n\n")
+    out.append('#include "lvp_private.h"\n\n')
+
+    skipped_entrypoints = {
+        "CreateInstance",
+        "EnumerateInstanceVersion",
+        "EnumerateInstanceLayerProperties",
+        "EnumerateInstanceExtensionProperties",
+        "GetExternalComputeQueueDataNV",
+        "GetInstanceProcAddr",
+    }
+
+    for entrypoint in entrypoints:
+        if entrypoint.name in skipped_entrypoints:
+            continue
+        if entrypoint.guard is not None:
+            out.append(f"#ifdef {entrypoint.guard}\n")
+
+        params = decl_params(entrypoint)
+        out.append(
+            f"PUBLIC VKAPI_ATTR {entrypoint.return_type} VKAPI_CALL\n"
+            f"vk{entrypoint.name}({params})\n"
+            "{\n"
+        )
+        out.append(emit_wrapper(entrypoint))
+        out.append("}\n")
+
+        if entrypoint.guard is not None:
+            out.append(f"#endif /* {entrypoint.guard} */\n")
+        out.append("\n")
+
+    return "".join(out)
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--out-c", required=True)
+    parser.add_argument(
+        "--xml",
+        required=True,
+        action="append",
+        dest="xml_files",
+    )
+    parser.add_argument("--beta", required=True)
+    args = parser.parse_args()
+
+    get_entrypoints_from_xml = _import_vk_entrypoints()
+    entrypoints = get_entrypoints_from_xml(args.xml_files, args.beta)
+
+    with open(args.out_c, "w", encoding="utf-8") as out_c:
+        out_c.write(render(entrypoints))
+
+
+if __name__ == "__main__":
+    main()
diff --git a/src/gallium/targets/lavapipe/meson.build b/src/gallium/targets/lavapipe/meson.build
index 41de1e5..8b1697f 100644
--- a/src/gallium/targets/lavapipe/meson.build
+++ b/src/gallium/targets/lavapipe/meson.build
@@ -1,4 +1,48 @@
 
+static_lvp_shim = custom_target(
+  'lvp_static_shim',
+  input : [files('lvp_static_shim_gen.py'), vk_api_xml],
+  output : 'lvp_static_shim.c',
+  command : [
+    prog_python, '@INPUT0@', '--xml', '@INPUT1@',
+    '--out-c', '@OUTPUT@',
+    '--beta', with_vulkan_beta.to_string(),
+  ],
+  depend_files : vk_entrypoints_gen_depend_files,
+)
+
+libvulkan_lvp_static = static_library(
+  'vulkan_lvp_static',
+  [ 'lavapipe_target.c', static_lvp_shim ],
+  include_directories : [ inc_include, inc_src, inc_util, inc_gallium, inc_gallium_aux, inc_gallium_winsys, inc_gallium_drivers, inc_llvmpipe, include_directories('../../frontends/lavapipe') ],
+  link_whole : [
+    liblavapipe_st,
+    libpipe_loader_static,
+    libloader,
+    _libmesa_util,
+    libmesa_util_clflush,
+    libmesa_util_clflushopt,
+    libmesa_util_simd,
+    blake3,
+    _libmesa_util_c11,
+    _libxmlconfig,
+    libgallium,
+    _libnir,
+    libcompiler,
+    libwsw,
+    libws_null,
+    libllvmpipe,
+    libvulkan_util,
+    libloader_wayland_helper,
+    libvtn,
+  ],
+  gnu_symbol_visibility : 'hidden',
+  override_options : ['b_lundef=@0@'.format(host_machine.system() == 'darwin' ? 'false' : get_option('b_lundef').to_string())],
+  dependencies : [ driver_llvmpipe, idep_mesautil, idep_nir, idep_vulkan_util,
+                   idep_vulkan_wsi, idep_vulkan_runtime, lvp_deps ],
+  install : false,
+)
+
 
 libvulkan_lvp = shared_library(
   'vulkan_lvp',
diff --git a/src/loader/meson.build b/src/loader/meson.build
index a8603ad..e070c05 100644
--- a/src/loader/meson.build
+++ b/src/loader/meson.build
@@ -4,14 +4,15 @@
 inc_loader = include_directories('.')
 
 if with_platform_wayland
+  wp_dir = '/usr/share/wayland-protocols'
   wp_protos = {
-    'fifo-v1': mod_wl.find_protocol('fifo', state : 'staging', version : 1),
-    'commit-timing-v1': mod_wl.find_protocol('commit-timing', state : 'staging', version : 1),
-    'linux-dmabuf-unstable-v1': mod_wl.find_protocol('linux-dmabuf', state : 'unstable', version : 1),
-    'presentation-time': mod_wl.find_protocol('presentation-time'),
-    'tearing-control-v1': mod_wl.find_protocol('tearing-control', state : 'staging', version : 1),
-    'linux-drm-syncobj-v1': mod_wl.find_protocol('linux-drm-syncobj', state : 'staging', version : 1),
-    'color-management-v1': mod_wl.find_protocol('color-management', state : 'staging', version : 1)
+    'fifo-v1': join_paths(wp_dir, 'staging', 'fifo', 'fifo-v1.xml'),
+    'commit-timing-v1': join_paths(wp_dir, 'staging', 'commit-timing', 'commit-timing-v1.xml'),
+    'linux-dmabuf-unstable-v1': join_paths(wp_dir, 'unstable', 'linux-dmabuf', 'linux-dmabuf-unstable-v1.xml'),
+    'presentation-time': join_paths(wp_dir, 'stable', 'presentation-time', 'presentation-time.xml'),
+    'tearing-control-v1': join_paths(wp_dir, 'staging', 'tearing-control', 'tearing-control-v1.xml'),
+    'linux-drm-syncobj-v1': join_paths(wp_dir, 'staging', 'linux-drm-syncobj', 'linux-drm-syncobj-v1.xml'),
+    'color-management-v1': join_paths(wp_dir, 'staging', 'color-management', 'color-management-v1.xml')
   }
   wp_files = {}
   foreach name, xml : wp_protos
@@ -21,7 +22,6 @@ if with_platform_wayland
   libloader_wayland_helper = static_library(
     'loader_wayland_helper',
     'loader_wayland_helper.c',
-    wp_files['presentation-time'],
     gnu_symbol_visibility : 'hidden',
     include_directories : [inc_include, inc_src, inc_gallium],
     dependencies : [