vulkan: Support VK_KHR_external_memory_fd on OS X.

This CL adds a Posix-based implementation for the
VK_KHR_external_memory_fd Vulkan extension, using the SysV
shm_open() / shm_unlink() API to create the shared
memory region.

The Linux backend still uses memfd-based regions which
are easier to create, and because Android does not provide
shm_open() intentionally [1].

[1] https://android.googlesource.com/platform/ndk/+/4e159d95ebf23b5f72bb707b0cb1518ef96b3d03/docs/system/libc/SYSV-IPC.TXT

Bug: b/140419396
Tests: dEQP-VK.*
Change-Id: Ibbb23c3af59e81f76e41a0e71281f6d1a8b07c01
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/41408
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Yilong Li <liyl@google.com>
Commit-Queue: Nicolas Capens <nicolascapens@google.com>
Kokoro-Result: kokoro <noreply+kokoro@google.com>
diff --git a/src/Vulkan/BUILD.gn b/src/Vulkan/BUILD.gn
index 880ba73..8ba0b9b 100644
--- a/src/Vulkan/BUILD.gn
+++ b/src/Vulkan/BUILD.gn
@@ -101,6 +101,10 @@
       "VkDeviceMemoryExternalLinux.hpp",
       "VkSemaphoreExternalLinux.hpp",
     ]
+  } else if (is_mac) {
+    sources += [
+      "VkDeviceMemoryExternalMac.hpp",
+    ]
   } else if (is_fuchsia) {
     sources += [ "VkSemaphoreExternalFuchsia.hpp" ]
   }
diff --git a/src/Vulkan/VkConfig.hpp b/src/Vulkan/VkConfig.hpp
index 5838736..e5518e8 100644
--- a/src/Vulkan/VkConfig.hpp
+++ b/src/Vulkan/VkConfig.hpp
@@ -92,6 +92,9 @@
 #elif defined(__ANDROID__)
 #	define SWIFTSHADER_EXTERNAL_SEMAPHORE_OPAQUE_FD 1
 #endif
+#if defined(__APPLE__)
+#	define SWIFTSHADER_EXTERNAL_MEMORY_OPAQUE_FD 1
+#endif
 
 constexpr VkDeviceSize MAX_MEMORY_ALLOCATION_SIZE = 0x40000000ull;  // 0x40000000 = 1 GiB
 
diff --git a/src/Vulkan/VkDeviceMemory.cpp b/src/Vulkan/VkDeviceMemory.cpp
index 5f3ac19..9921328 100644
--- a/src/Vulkan/VkDeviceMemory.cpp
+++ b/src/Vulkan/VkDeviceMemory.cpp
@@ -17,6 +17,7 @@
 #include "VkDevice.hpp"
 #include "VkDeviceMemoryExternalBase.hpp"
 #include "VkImage.hpp"
+#include "VkStringify.hpp"
 
 #include "VkConfig.hpp"
 
@@ -182,7 +183,65 @@
 };
 
 #if SWIFTSHADER_EXTERNAL_MEMORY_OPAQUE_FD
-#	if defined(__linux__) && !defined(__ANDROID__)
+
+// Helper struct to parse the VkMemoryAllocateInfo.pNext chain and
+// extract relevant information related to the handle type supported
+// by this DeviceMemory;:ExternalBase subclass.
+struct OpaqueFdAllocateInfo
+{
+	bool importFd = false;
+	bool exportFd = false;
+	int fd = -1;
+
+	OpaqueFdAllocateInfo() = default;
+
+	// Parse the VkMemoryAllocateInfo.pNext chain to initialize an OpaqueFdAllocateInfo.
+	OpaqueFdAllocateInfo(const VkMemoryAllocateInfo *pAllocateInfo)
+	{
+		const auto *createInfo = reinterpret_cast<const VkBaseInStructure *>(pAllocateInfo->pNext);
+		while(createInfo)
+		{
+			switch(createInfo->sType)
+			{
+				case VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR:
+				{
+					const auto *importInfo = reinterpret_cast<const VkImportMemoryFdInfoKHR *>(createInfo);
+
+					if(importInfo->handleType != VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT)
+					{
+						UNSUPPORTED("VkImportMemoryFdInfoKHR::handleType %d", int(importInfo->handleType));
+					}
+					importFd = true;
+					fd = importInfo->fd;
+				}
+				break;
+				case VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO:
+				{
+					const auto *exportInfo = reinterpret_cast<const VkExportMemoryAllocateInfo *>(createInfo);
+
+					if(exportInfo->handleTypes != VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT)
+					{
+						UNSUPPORTED("VkExportMemoryAllocateInfo::handleTypes %d", int(exportInfo->handleTypes));
+					}
+					exportFd = true;
+				}
+				break;
+				case VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO:
+					// This can safely be ignored, as the Vulkan spec mentions:
+					// "If the pNext chain includes a VkMemoryDedicatedAllocateInfo structure, then that structure
+					//  includes a handle of the sole buffer or image resource that the memory *can* be bound to."
+					break;
+				default:
+					WARN("VkMemoryAllocateInfo->pNext sType = %s", vk::Stringify(createInfo->sType).c_str());
+			}
+			createInfo = createInfo->pNext;
+		}
+	}
+};
+
+#	if defined(__APPLE__)
+#		include "VkDeviceMemoryExternalMac.hpp"
+#	elif defined(__linux__) && !defined(__ANDROID__)
 #		include "VkDeviceMemoryExternalLinux.hpp"
 #	else
 #		error "Missing VK_KHR_external_memory_fd implementation for this platform!"
diff --git a/src/Vulkan/VkDeviceMemoryExternalLinux.hpp b/src/Vulkan/VkDeviceMemoryExternalLinux.hpp
index b4f22fc..af69b2f 100644
--- a/src/Vulkan/VkDeviceMemoryExternalLinux.hpp
+++ b/src/Vulkan/VkDeviceMemoryExternalLinux.hpp
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "VkStringify.hpp"
-
 #include "System/Debug.hpp"
 #include "System/Linux/MemFd.hpp"
 
@@ -24,67 +22,11 @@
 class OpaqueFdExternalMemory : public vk::DeviceMemory::ExternalBase
 {
 public:
-	// Helper struct to parse the VkMemoryAllocateInfo.pNext chain and
-	// extract relevant information related to the handle type supported
-	// by this DeviceMemory;:ExternalBase subclass.
-	struct AllocateInfo
-	{
-		bool importFd = false;
-		bool exportFd = false;
-		int fd = -1;
-
-		AllocateInfo() = default;
-
-		// Parse the VkMemoryAllocateInfo.pNext chain to initialize an AllocateInfo.
-		AllocateInfo(const VkMemoryAllocateInfo *pAllocateInfo)
-		{
-			const auto *createInfo = reinterpret_cast<const VkBaseInStructure *>(pAllocateInfo->pNext);
-			while(createInfo)
-			{
-				switch(createInfo->sType)
-				{
-					case VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR:
-					{
-						const auto *importInfo = reinterpret_cast<const VkImportMemoryFdInfoKHR *>(createInfo);
-
-						if(importInfo->handleType != VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT)
-						{
-							UNSUPPORTED("VkImportMemoryFdInfoKHR::handleType %d", int(importInfo->handleType));
-						}
-						importFd = true;
-						fd = importInfo->fd;
-					}
-					break;
-					case VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO:
-					{
-						const auto *exportInfo = reinterpret_cast<const VkExportMemoryAllocateInfo *>(createInfo);
-
-						if(exportInfo->handleTypes != VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT)
-						{
-							UNSUPPORTED("VkExportMemoryAllocateInfo::handleTypes %d", int(exportInfo->handleTypes));
-						}
-						exportFd = true;
-					}
-					break;
-					case VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO:
-						// This can safely be ignored, as the Vulkan spec mentions:
-						// "If the pNext chain includes a VkMemoryDedicatedAllocateInfo structure, then that structure
-						//  includes a handle of the sole buffer or image resource that the memory *can* be bound to."
-						break;
-
-					default:
-						WARN("VkMemoryAllocateInfo->pNext sType = %s", vk::Stringify(createInfo->sType).c_str());
-				}
-				createInfo = createInfo->pNext;
-			}
-		}
-	};
-
 	static const VkExternalMemoryHandleTypeFlagBits typeFlagBit = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
 
 	static bool SupportsAllocateInfo(const VkMemoryAllocateInfo *pAllocateInfo)
 	{
-		AllocateInfo info(pAllocateInfo);
+		OpaqueFdAllocateInfo info(pAllocateInfo);
 		return info.importFd || info.exportFd;
 	}
 
@@ -152,5 +94,5 @@
 
 private:
 	LinuxMemFd memfd;
-	AllocateInfo allocateInfo;
+	OpaqueFdAllocateInfo allocateInfo;
 };
diff --git a/src/Vulkan/VkDeviceMemoryExternalMac.hpp b/src/Vulkan/VkDeviceMemoryExternalMac.hpp
new file mode 100644
index 0000000..bcd06c5
--- /dev/null
+++ b/src/Vulkan/VkDeviceMemoryExternalMac.hpp
@@ -0,0 +1,197 @@
+// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "System/Debug.hpp"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#ifndef __APPLE__
+#	error "This file is for macOS only!"
+#endif  // __APPLE__
+
+#if __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_12
+#	include <mach/mach_time.h>
+#endif  // __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_12
+
+namespace {
+
+struct timespec GetTime()
+{
+	struct timespec tv;
+
+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_12
+	clock_gettime(CLOCK_REALTIME, &tv);
+#else
+	mach_timebase_info_data_t timebase;
+	mach_timebase_info(&timebase);
+	uint64_t time;
+	time = mach_absolute_time();
+
+	double convert_ratio = (double)timebase.numer / (double)timebase.denom;
+	uint64_t secs = (uint64_t)((double)time * convert_ratio / 1e-9);
+	uint64_t usecs = (uint64_t)((double)time * convert_ratio - secs * 1e9);
+	tv.tv_sec = secs;
+	tv.tv_nsec = usecs;
+#endif
+	return tv;
+}
+
+}  // namespace
+
+// An implementation of OpaqueFdExternalMemory that relies on shm_open().
+// Useful on OS X which do not have Linux memfd regions.
+class OpaqueFdExternalMemory : public vk::DeviceMemory::ExternalBase
+{
+public:
+	static const VkExternalMemoryHandleTypeFlagBits typeFlagBit = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
+
+	static bool SupportsAllocateInfo(const VkMemoryAllocateInfo *pAllocateInfo)
+	{
+		OpaqueFdAllocateInfo info(pAllocateInfo);
+		return info.importFd || info.exportFd;
+	}
+
+	explicit OpaqueFdExternalMemory(const VkMemoryAllocateInfo *pAllocateInfo)
+	    : allocateInfo(pAllocateInfo)
+	{
+	}
+
+	~OpaqueFdExternalMemory()
+	{
+		if(shm_fd_ >= 0)
+		{
+			::close(shm_fd_);
+			shm_fd_ = -1;
+		}
+	}
+
+	VkResult allocate(size_t size, void **pBuffer) override
+	{
+		if(allocateInfo.importFd)
+		{
+			shm_fd_ = allocateInfo.fd;
+			if(shm_fd_ < 0)
+			{
+				return VK_ERROR_INVALID_EXTERNAL_HANDLE;
+			}
+		}
+		else
+		{
+			ASSERT(allocateInfo.exportFd);
+			// Create shared memory region with shm_open() and a randomly-generated region name.
+			static const char kPrefix[] = "/SwiftShader-";
+			const size_t kPrefixSize = sizeof(kPrefix) - 1;
+			const size_t kRandomSize = 8;
+
+			char name[kPrefixSize + kRandomSize + 1u];
+			memcpy(name, kPrefix, kPrefixSize);
+
+			int fd = -1;
+			for(int tries = 0; tries < 6; ++tries)
+			{
+				struct timespec tv = GetTime();
+				uint64_t r = (uint64_t)tv.tv_sec + (uint64_t)tv.tv_nsec;
+				for(size_t pos = 0; pos < kRandomSize; ++pos, r /= 8)
+				{
+					name[kPrefixSize + pos] = '0' + (r % 8);
+				}
+				name[kPrefixSize + kRandomSize] = '\0';
+
+				fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW, 0600);
+				if(fd >= 0)
+					break;
+
+				if(errno != EEXIST)
+				{
+					TRACE("shm_open() failed with: %s", strerror(errno));
+					break;
+				}
+			}
+
+			// Unlink the name since it's not needed anymore.
+			if(fd >= 0)
+			{
+				if(shm_unlink(name) == -1)
+				{
+					TRACE("shm_unlink() failed with: %s", strerror(errno));
+					close(fd);
+					fd = -1;
+				}
+			}
+
+			// Ensure there is enough space.
+			if(fd >= 0 && size > 0)
+			{
+				if(::ftruncate(fd, size) < 0)
+				{
+					TRACE("ftruncate() failed with: %s", strerror(errno));
+					close(fd);
+					fd = -1;
+				}
+			}
+
+			if(fd < 0)
+			{
+				TRACE("Could not allocate shared memory region");
+				return VK_ERROR_OUT_OF_DEVICE_MEMORY;
+			}
+
+			shm_fd_ = fd;
+		}
+
+		void *addr = ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED,
+		                    shm_fd_, 0);
+
+		if(addr == MAP_FAILED)
+		{
+			return VK_ERROR_MEMORY_MAP_FAILED;
+		}
+		*pBuffer = addr;
+		return VK_SUCCESS;
+	}
+
+	void deallocate(void *buffer, size_t size) override
+	{
+		::munmap(buffer, size);
+	}
+
+	VkExternalMemoryHandleTypeFlagBits getFlagBit() const override
+	{
+		return typeFlagBit;
+	}
+
+	VkResult exportFd(int *pFd) const override
+	{
+		int fd = dup(shm_fd_);
+		if(fd < 0)
+		{
+			return VK_ERROR_INVALID_EXTERNAL_HANDLE;
+		}
+
+		// Set the clo-on-exec flag.
+		int flags = ::fcntl(fd, F_GETFD);
+		::fcntl(fd, F_SETFL, flags | FD_CLOEXEC);
+
+		*pFd = fd;
+		return VK_SUCCESS;
+	}
+
+private:
+	int shm_fd_ = -1;
+	OpaqueFdAllocateInfo allocateInfo;
+};