vknulldrv: Use a default allocator when none provided

The loader used to always provide an allocator, even if the app
didn't. It no longer does that, so the null driver must be prepared to
provide its own default allocator.

Fixes bug 28303142

Change-Id: I6ab946913dc4edaf2a0b07ab9ae8c43be69b3ed6
diff --git a/vulkan/nulldrv/null_driver.cpp b/vulkan/nulldrv/null_driver.cpp
index 6f57238..f29cb68 100644
--- a/vulkan/nulldrv/null_driver.cpp
+++ b/vulkan/nulldrv/null_driver.cpp
@@ -19,6 +19,7 @@
 #include <algorithm>
 #include <array>
 #include <inttypes.h>
+#include <stdlib.h>
 #include <string.h>
 
 #include <log/log.h>
@@ -186,6 +187,58 @@
         AllocHandle(type, &device->next_handle[type]));
 }
 
+VKAPI_ATTR void* DefaultAllocate(void*,
+                                 size_t size,
+                                 size_t alignment,
+                                 VkSystemAllocationScope) {
+    void* ptr = nullptr;
+    // Vulkan requires 'alignment' to be a power of two, but posix_memalign
+    // additionally requires that it be at least sizeof(void*).
+    int ret = posix_memalign(&ptr, std::max(alignment, sizeof(void*)), size);
+    return ret == 0 ? ptr : nullptr;
+}
+
+VKAPI_ATTR void* DefaultReallocate(void*,
+                                   void* ptr,
+                                   size_t size,
+                                   size_t alignment,
+                                   VkSystemAllocationScope) {
+    if (size == 0) {
+        free(ptr);
+        return nullptr;
+    }
+
+    // TODO(jessehall): Right now we never shrink allocations; if the new
+    // request is smaller than the existing chunk, we just continue using it.
+    // The null driver never reallocs, so this doesn't matter. If that changes,
+    // or if this code is copied into some other project, this should probably
+    // have a heuristic to allocate-copy-free when doing so will save "enough"
+    // space.
+    size_t old_size = ptr ? malloc_usable_size(ptr) : 0;
+    if (size <= old_size)
+        return ptr;
+
+    void* new_ptr = nullptr;
+    if (posix_memalign(&new_ptr, std::max(alignment, sizeof(void*)), size) != 0)
+        return nullptr;
+    if (ptr) {
+        memcpy(new_ptr, ptr, std::min(old_size, size));
+        free(ptr);
+    }
+    return new_ptr;
+}
+
+VKAPI_ATTR void DefaultFree(void*, void* ptr) {
+    free(ptr);
+}
+
+const VkAllocationCallbacks kDefaultAllocCallbacks = {
+    .pUserData = nullptr,
+    .pfnAllocation = DefaultAllocate,
+    .pfnReallocation = DefaultReallocate,
+    .pfnFree = DefaultFree,
+};
+
 }  // namespace
 
 namespace null_driver {
@@ -239,10 +292,8 @@
 VkResult CreateInstance(const VkInstanceCreateInfo* create_info,
                         const VkAllocationCallbacks* allocator,
                         VkInstance* out_instance) {
-    // Assume the loader provided alloc callbacks even if the app didn't.
-    ALOG_ASSERT(
-        allocator,
-        "Missing alloc callbacks, loader or app should have provided them");
+    if (!allocator)
+        allocator = &kDefaultAllocCallbacks;
 
     VkInstance_T* instance =
         static_cast<VkInstance_T*>(allocator->pfnAllocation(