aboutsummaryrefslogtreecommitdiff
path: root/src/zencore/iobuffer.cpp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2023-05-02 10:01:47 +0200
committerGitHub <[email protected]>2023-05-02 10:01:47 +0200
commit075d17f8ada47e990fe94606c3d21df409223465 (patch)
treee50549b766a2f3c354798a54ff73404217b4c9af /src/zencore/iobuffer.cpp
parentfix: bundle shouldn't append content zip to zen (diff)
downloadzen-075d17f8ada47e990fe94606c3d21df409223465.tar.xz
zen-075d17f8ada47e990fe94606c3d21df409223465.zip
moved source directories into `/src` (#264)
* moved source directories into `/src` * updated bundle.lua for new `src` path * moved some docs, icon * removed old test trees
Diffstat (limited to 'src/zencore/iobuffer.cpp')
-rw-r--r--src/zencore/iobuffer.cpp653
1 files changed, 653 insertions, 0 deletions
diff --git a/src/zencore/iobuffer.cpp b/src/zencore/iobuffer.cpp
new file mode 100644
index 000000000..1d7d47695
--- /dev/null
+++ b/src/zencore/iobuffer.cpp
@@ -0,0 +1,653 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include <zencore/iobuffer.h>
+
+#include <zencore/except.h>
+#include <zencore/filesystem.h>
+#include <zencore/fmtutils.h>
+#include <zencore/iohash.h>
+#include <zencore/logging.h>
+#include <zencore/memory.h>
+#include <zencore/testing.h>
+#include <zencore/thread.h>
+
+#include <memory.h>
+#include <system_error>
+
+#if ZEN_USE_MIMALLOC
+ZEN_THIRD_PARTY_INCLUDES_START
+# include <mimalloc.h>
+ZEN_THIRD_PARTY_INCLUDES_END
+#endif
+
+#if ZEN_PLATFORM_WINDOWS
+# include <atlfile.h>
+#else
+# include <sys/stat.h>
+# include <sys/mman.h>
+#endif
+
+#include <gsl/gsl-lite.hpp>
+
+namespace zen {
+
+//////////////////////////////////////////////////////////////////////////
+
+void
+IoBufferCore::AllocateBuffer(size_t InSize, size_t Alignment) const
+{
+#if ZEN_PLATFORM_WINDOWS
+ if (((InSize & 0xffFF) == 0) && (Alignment == 0x10000))
+ {
+ m_Flags.fetch_or(kLowLevelAlloc, std::memory_order_relaxed);
+ m_DataPtr = VirtualAlloc(nullptr, InSize, MEM_COMMIT, PAGE_READWRITE);
+
+ return;
+ }
+#endif // ZEN_PLATFORM_WINDOWS
+
+#if ZEN_USE_MIMALLOC
+ void* Ptr = mi_aligned_alloc(Alignment, RoundUp(InSize, Alignment));
+ m_Flags.fetch_or(kIoBufferAlloc, std::memory_order_relaxed);
+#else
+ void* Ptr = Memory::Alloc(InSize, Alignment);
+#endif
+
+ ZEN_ASSERT(Ptr);
+
+ m_DataPtr = Ptr;
+}
+
+void
+IoBufferCore::FreeBuffer()
+{
+ if (!m_DataPtr)
+ {
+ return;
+ }
+
+ const uint32_t LocalFlags = m_Flags.load(std::memory_order_relaxed);
+#if ZEN_PLATFORM_WINDOWS
+ if (LocalFlags & kLowLevelAlloc)
+ {
+ VirtualFree(const_cast<void*>(m_DataPtr), 0, MEM_DECOMMIT);
+
+ return;
+ }
+#endif // ZEN_PLATFORM_WINDOWS
+
+#if ZEN_USE_MIMALLOC
+ if (LocalFlags & kIoBufferAlloc)
+ {
+ return mi_free(const_cast<void*>(m_DataPtr));
+ }
+#endif
+
+ ZEN_UNUSED(LocalFlags);
+ return Memory::Free(const_cast<void*>(m_DataPtr));
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+static_assert(sizeof(IoBufferCore) == 32);
+
+IoBufferCore::IoBufferCore(size_t InSize)
+{
+ ZEN_ASSERT(InSize);
+
+ AllocateBuffer(InSize, sizeof(void*));
+ m_DataBytes = InSize;
+
+ SetIsOwnedByThis(true);
+}
+
+IoBufferCore::IoBufferCore(size_t InSize, size_t Alignment)
+{
+ ZEN_ASSERT(InSize);
+
+ AllocateBuffer(InSize, Alignment);
+ m_DataBytes = InSize;
+
+ SetIsOwnedByThis(true);
+}
+
+IoBufferCore::~IoBufferCore()
+{
+ if (IsOwnedByThis() && m_DataPtr)
+ {
+ FreeBuffer();
+ m_DataPtr = nullptr;
+ }
+}
+
+void
+IoBufferCore::DeleteThis() const
+{
+ // We do this just to avoid paying for the cost of a vtable
+ if (const IoBufferExtendedCore* _ = ExtendedCore())
+ {
+ delete _;
+ }
+ else
+ {
+ delete this;
+ }
+}
+
+void
+IoBufferCore::Materialize() const
+{
+ if (const IoBufferExtendedCore* _ = ExtendedCore())
+ {
+ _->Materialize();
+ }
+}
+
+void
+IoBufferCore::MakeOwned(bool Immutable)
+{
+ if (!IsOwned())
+ {
+ const void* OldDataPtr = m_DataPtr;
+ AllocateBuffer(m_DataBytes, sizeof(void*));
+ memcpy(const_cast<void*>(m_DataPtr), OldDataPtr, m_DataBytes);
+ SetIsOwnedByThis(true);
+ }
+
+ SetIsImmutable(Immutable);
+}
+
+void*
+IoBufferCore::MutableDataPointer() const
+{
+ EnsureDataValid();
+ ZEN_ASSERT(!IsImmutable());
+ return const_cast<void*>(m_DataPtr);
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+IoBufferExtendedCore::IoBufferExtendedCore(void* FileHandle, uint64_t Offset, uint64_t Size, bool TransferHandleOwnership)
+: IoBufferCore(nullptr, Size)
+, m_FileHandle(FileHandle)
+, m_FileOffset(Offset)
+{
+ uint32_t NewFlags = kIsOwnedByThis | kIsExtended;
+
+ if (TransferHandleOwnership)
+ {
+ NewFlags |= kOwnsFile;
+ }
+ m_Flags.fetch_or(NewFlags, std::memory_order_relaxed);
+}
+
+IoBufferExtendedCore::IoBufferExtendedCore(const IoBufferExtendedCore* Outer, uint64_t Offset, uint64_t Size)
+: IoBufferCore(Outer, nullptr, Size)
+, m_FileHandle(Outer->m_FileHandle)
+, m_FileOffset(Outer->m_FileOffset + Offset)
+{
+ m_Flags.fetch_or(kIsExtended, std::memory_order_relaxed);
+}
+
+IoBufferExtendedCore::~IoBufferExtendedCore()
+{
+ if (m_MappedPointer)
+ {
+#if ZEN_PLATFORM_WINDOWS
+ UnmapViewOfFile(m_MappedPointer);
+#else
+ uint64_t MapSize = ~uint64_t(uintptr_t(m_MmapHandle));
+ munmap(m_MappedPointer, MapSize);
+#endif
+ }
+
+ const uint32_t LocalFlags = m_Flags.load(std::memory_order_relaxed);
+#if ZEN_PLATFORM_WINDOWS
+ if (LocalFlags & kOwnsMmap)
+ {
+ CloseHandle(m_MmapHandle);
+ }
+#endif
+
+ if (LocalFlags & kOwnsFile)
+ {
+ if (m_DeleteOnClose)
+ {
+#if ZEN_PLATFORM_WINDOWS
+ // Mark file for deletion when final handle is closed
+ FILE_DISPOSITION_INFO Fdi{.DeleteFile = TRUE};
+
+ SetFileInformationByHandle(m_FileHandle, FileDispositionInfo, &Fdi, sizeof Fdi);
+#else
+ std::filesystem::path FilePath = zen::PathFromHandle(m_FileHandle);
+ unlink(FilePath.c_str());
+#endif
+ }
+#if ZEN_PLATFORM_WINDOWS
+ BOOL Success = CloseHandle(m_FileHandle);
+#else
+ int Fd = int(uintptr_t(m_FileHandle));
+ bool Success = (close(Fd) == 0);
+#endif
+ if (!Success)
+ {
+ ZEN_WARN("Error reported on file handle close, reason '{}'", GetLastErrorAsString());
+ }
+ }
+
+ m_DataPtr = nullptr;
+}
+
+static constexpr size_t MappingLockCount = 128;
+static_assert(IsPow2(MappingLockCount), "MappingLockCount must be power of two");
+
+static RwLock g_MappingLocks[MappingLockCount];
+
+static RwLock&
+MappingLockForInstance(const IoBufferExtendedCore* instance)
+{
+ intptr_t base = (intptr_t)instance;
+ size_t lock_index = ((base >> 5) ^ (base >> 13)) & (MappingLockCount - 1u);
+ return g_MappingLocks[lock_index];
+}
+
+void
+IoBufferExtendedCore::Materialize() const
+{
+ // The synchronization scheme here is very primitive, if we end up with
+ // a lot of contention we can make it more fine-grained
+
+ if (m_Flags.load(std::memory_order_acquire) & kIsMaterialized)
+ return;
+
+ RwLock::ExclusiveLockScope _(MappingLockForInstance(this));
+
+ // Someone could have gotten here first
+ // We can use memory_order_relaxed on this load because the mutex has already provided the fence
+ if (m_Flags.load(std::memory_order_relaxed) & kIsMaterialized)
+ return;
+
+ uint32_t NewFlags = kIsMaterialized;
+
+ if (m_DataBytes == 0)
+ {
+ // Fake a "valid" pointer, nobody should read this as size is zero
+ m_DataPtr = reinterpret_cast<uint8_t*>(&m_MmapHandle);
+ m_Flags.fetch_or(NewFlags, std::memory_order_release);
+ return;
+ }
+
+ const size_t DisableMMapSizeLimit = 0x1000ull;
+
+ if (m_DataBytes < DisableMMapSizeLimit)
+ {
+ AllocateBuffer(m_DataBytes, sizeof(void*));
+ NewFlags |= kIsOwnedByThis;
+
+#if ZEN_PLATFORM_WINDOWS
+ OVERLAPPED Ovl{};
+
+ Ovl.Offset = DWORD(m_FileOffset & 0xffff'ffffu);
+ Ovl.OffsetHigh = DWORD(m_FileOffset >> 32);
+
+ DWORD dwNumberOfBytesRead = 0;
+ BOOL Success = ::ReadFile(m_FileHandle, (void*)m_DataPtr, DWORD(m_DataBytes), &dwNumberOfBytesRead, &Ovl);
+
+ ZEN_ASSERT(Success);
+ ZEN_ASSERT(dwNumberOfBytesRead == m_DataBytes);
+#else
+ static_assert(sizeof(off_t) >= sizeof(uint64_t), "sizeof(off_t) does not support large files");
+ int Fd = int(uintptr_t(m_FileHandle));
+ int BytesRead = pread(Fd, (void*)m_DataPtr, m_DataBytes, m_FileOffset);
+ bool Success = (BytesRead > 0);
+#endif // ZEN_PLATFORM_WINDOWS
+
+ m_Flags.fetch_or(NewFlags, std::memory_order_release);
+ return;
+ }
+
+ void* NewMmapHandle;
+
+ const uint64_t MapOffset = m_FileOffset & ~0xffffull;
+ const uint64_t MappedOffsetDisplacement = m_FileOffset - MapOffset;
+ const uint64_t MapSize = m_DataBytes + MappedOffsetDisplacement;
+
+ ZEN_ASSERT(MapSize > 0);
+
+#if ZEN_PLATFORM_WINDOWS
+ NewMmapHandle = CreateFileMapping(m_FileHandle,
+ /* lpFileMappingAttributes */ nullptr,
+ /* flProtect */ PAGE_READONLY,
+ /* dwMaximumSizeLow */ 0,
+ /* dwMaximumSizeHigh */ 0,
+ /* lpName */ nullptr);
+
+ if (NewMmapHandle == nullptr)
+ {
+ int32_t Error = zen::GetLastError();
+ ZEN_ERROR("CreateFileMapping failed on file '{}', {}", zen::PathFromHandle(m_FileHandle), GetSystemErrorAsString(Error));
+ throw std::system_error(std::error_code(Error, std::system_category()),
+ fmt::format("CreateFileMapping failed on file '{}'", zen::PathFromHandle(m_FileHandle)));
+ }
+
+ NewFlags |= kOwnsMmap;
+
+ void* MappedBase = MapViewOfFile(NewMmapHandle,
+ /* dwDesiredAccess */ FILE_MAP_READ,
+ /* FileOffsetHigh */ uint32_t(MapOffset >> 32),
+ /* FileOffsetLow */ uint32_t(MapOffset & 0xffFFffFFu),
+ /* dwNumberOfBytesToMap */ MapSize);
+#else
+ NewMmapHandle = (void*)uintptr_t(~MapSize); // ~ so it's never null (assuming MapSize >= 0)
+ NewFlags |= kOwnsMmap;
+
+ void* MappedBase = mmap(
+ /* addr */ nullptr,
+ /* length */ MapSize,
+ /* prot */ PROT_READ,
+ /* flags */ MAP_SHARED | MAP_NORESERVE,
+ /* fd */ int(uintptr_t(m_FileHandle)),
+ /* offset */ MapOffset);
+#endif // ZEN_PLATFORM_WINDOWS
+
+ if (MappedBase == nullptr)
+ {
+ int32_t Error = zen::GetLastError();
+#if ZEN_PLATFORM_WINDOWS
+ CloseHandle(NewMmapHandle);
+#endif // ZEN_PLATFORM_WINDOWS
+ ZEN_ERROR("MapViewOfFile failed (offset {:#x}, size {:#x}) file: '{}', {}",
+ MapOffset,
+ MapSize,
+ zen::PathFromHandle(m_FileHandle),
+ GetSystemErrorAsString(Error));
+ throw std::system_error(std::error_code(Error, std::system_category()),
+ fmt::format("MapViewOfFile failed (offset {:#x}, size {:#x}) file: '{}'",
+ MapOffset,
+ MapSize,
+ zen::PathFromHandle(m_FileHandle)));
+ }
+
+ m_MappedPointer = MappedBase;
+ m_DataPtr = reinterpret_cast<uint8_t*>(MappedBase) + MappedOffsetDisplacement;
+ m_MmapHandle = NewMmapHandle;
+
+ m_Flags.fetch_or(NewFlags, std::memory_order_release);
+}
+
+bool
+IoBufferExtendedCore::GetFileReference(IoBufferFileReference& OutRef) const
+{
+ if (m_FileHandle == nullptr)
+ {
+ return false;
+ }
+
+ OutRef.FileHandle = m_FileHandle;
+ OutRef.FileChunkOffset = m_FileOffset;
+ OutRef.FileChunkSize = m_DataBytes;
+
+ return true;
+}
+
+void
+IoBufferExtendedCore::MarkAsDeleteOnClose()
+{
+ m_DeleteOnClose = true;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+IoBuffer::IoBuffer(size_t InSize) : m_Core(new IoBufferCore(InSize))
+{
+ m_Core->SetIsImmutable(false);
+}
+
+IoBuffer::IoBuffer(size_t InSize, uint64_t InAlignment) : m_Core(new IoBufferCore(InSize, InAlignment))
+{
+ m_Core->SetIsImmutable(false);
+}
+
+IoBuffer::IoBuffer(const IoBuffer& OuterBuffer, size_t Offset, size_t Size)
+{
+ if (Size == ~(0ull))
+ {
+ Size = std::clamp<size_t>(Size, 0, OuterBuffer.Size() - Offset);
+ }
+
+ ZEN_ASSERT(Offset <= OuterBuffer.Size());
+ ZEN_ASSERT((Offset + Size) <= OuterBuffer.Size());
+
+ if (IoBufferExtendedCore* Extended = OuterBuffer.m_Core->ExtendedCore())
+ {
+ m_Core = new IoBufferExtendedCore(Extended, Offset, Size);
+ }
+ else
+ {
+ m_Core = new IoBufferCore(OuterBuffer.m_Core, reinterpret_cast<const uint8_t*>(OuterBuffer.Data()) + Offset, Size);
+ }
+}
+
+IoBuffer::IoBuffer(EFileTag, void* FileHandle, uint64_t ChunkFileOffset, uint64_t ChunkSize)
+: m_Core(new IoBufferExtendedCore(FileHandle, ChunkFileOffset, ChunkSize, /* owned */ true))
+{
+}
+
+IoBuffer::IoBuffer(EBorrowedFileTag, void* FileHandle, uint64_t ChunkFileOffset, uint64_t ChunkSize)
+: m_Core(new IoBufferExtendedCore(FileHandle, ChunkFileOffset, ChunkSize, /* owned */ false))
+{
+}
+
+bool
+IoBuffer::GetFileReference(IoBufferFileReference& OutRef) const
+{
+ if (IoBufferExtendedCore* ExtCore = m_Core->ExtendedCore())
+ {
+ if (ExtCore->GetFileReference(OutRef))
+ {
+ return true;
+ }
+ }
+
+ // Not a file reference
+
+ OutRef.FileHandle = 0;
+ OutRef.FileChunkOffset = ~0ull;
+ OutRef.FileChunkSize = 0;
+
+ return false;
+}
+
+void
+IoBuffer::MarkAsDeleteOnClose()
+{
+ if (IoBufferExtendedCore* ExtCore = m_Core->ExtendedCore())
+ {
+ ExtCore->MarkAsDeleteOnClose();
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+IoBuffer
+IoBufferBuilder::ReadFromFileMaybe(IoBuffer& InBuffer)
+{
+ IoBufferFileReference FileRef;
+ if (InBuffer.GetFileReference(/* out */ FileRef))
+ {
+ IoBuffer OutBuffer(FileRef.FileChunkSize);
+
+#if ZEN_PLATFORM_WINDOWS
+ OVERLAPPED Ovl{};
+
+ const uint64_t NumberOfBytesToRead = FileRef.FileChunkSize;
+ const uint64_t& FileOffset = FileRef.FileChunkOffset;
+
+ Ovl.Offset = DWORD(FileOffset & 0xffff'ffffu);
+ Ovl.OffsetHigh = DWORD(FileOffset >> 32);
+
+ DWORD dwNumberOfBytesRead = 0;
+ BOOL Success = ::ReadFile(FileRef.FileHandle, OutBuffer.MutableData(), DWORD(NumberOfBytesToRead), &dwNumberOfBytesRead, &Ovl);
+#else
+ int Fd = int(intptr_t(FileRef.FileHandle));
+ int Result = pread(Fd, OutBuffer.MutableData(), size_t(FileRef.FileChunkSize), off_t(FileRef.FileChunkOffset));
+ bool Success = (Result < 0);
+
+ uint32_t dwNumberOfBytesRead = uint32_t(Result);
+#endif
+
+ if (!Success)
+ {
+ ThrowLastError("ReadFile failed in IoBufferBuilder::ReadFromFileMaybe");
+ }
+
+ ZEN_ASSERT(dwNumberOfBytesRead == FileRef.FileChunkSize);
+
+ return OutBuffer;
+ }
+ else
+ {
+ return InBuffer;
+ }
+}
+
+IoBuffer
+IoBufferBuilder::MakeFromFileHandle(void* FileHandle, uint64_t Offset, uint64_t Size)
+{
+ return IoBuffer(IoBuffer::BorrowedFile, FileHandle, Offset, Size);
+}
+
+IoBuffer
+IoBufferBuilder::MakeFromFile(const std::filesystem::path& FileName, uint64_t Offset, uint64_t Size)
+{
+ uint64_t FileSize;
+
+#if ZEN_PLATFORM_WINDOWS
+ CAtlFile DataFile;
+
+ DWORD ShareOptions = FILE_SHARE_DELETE | FILE_SHARE_WRITE | FILE_SHARE_DELETE | FILE_SHARE_READ;
+ HRESULT hRes = DataFile.Create(FileName.c_str(), GENERIC_READ, ShareOptions, OPEN_EXISTING);
+
+ if (FAILED(hRes))
+ {
+ return {};
+ }
+
+ DataFile.GetSize((ULONGLONG&)FileSize);
+#else
+ int Flags = O_RDONLY | O_CLOEXEC;
+ int Fd = open(FileName.c_str(), Flags);
+ if (Fd < 0)
+ {
+ return {};
+ }
+
+ static_assert(sizeof(decltype(stat::st_size)) == sizeof(uint64_t), "fstat() doesn't support large files");
+ struct stat Stat;
+ fstat(Fd, &Stat);
+ FileSize = Stat.st_size;
+#endif // ZEN_PLATFORM_WINDOWS
+
+ // TODO: should validate that offset is in range
+
+ if (Size == ~0ull)
+ {
+ Size = FileSize - Offset;
+ }
+ else
+ {
+ // Clamp size
+ if ((Offset + Size) > FileSize)
+ {
+ Size = FileSize - Offset;
+ }
+ }
+
+ if (Size)
+ {
+#if ZEN_PLATFORM_WINDOWS
+ void* Fd = DataFile.Detach();
+#endif
+ IoBuffer Iob(IoBuffer::File, (void*)uintptr_t(Fd), Offset, Size);
+ Iob.m_Core->SetIsWholeFile(Offset == 0 && Size == FileSize);
+ return Iob;
+ }
+
+#if !ZEN_PLATFORM_WINDOWS
+ close(Fd);
+#endif
+
+ // For an empty file, we may as well just return an empty memory IoBuffer
+ return IoBuffer(IoBuffer::Wrap, "", 0);
+}
+
+IoBuffer
+IoBufferBuilder::MakeFromTemporaryFile(const std::filesystem::path& FileName)
+{
+ uint64_t FileSize;
+ void* Handle;
+
+#if ZEN_PLATFORM_WINDOWS
+ CAtlFile DataFile;
+
+ // We need to open with DELETE since this is used for the case
+ // when a file has been written to a staging directory, and is going
+ // to be moved in place
+
+ HRESULT hRes = DataFile.Create(FileName.native().c_str(), GENERIC_READ | DELETE, FILE_SHARE_READ | FILE_SHARE_DELETE, OPEN_EXISTING);
+
+ if (FAILED(hRes))
+ {
+ return {};
+ }
+
+ DataFile.GetSize((ULONGLONG&)FileSize);
+
+ Handle = DataFile.Detach();
+#else
+ int Fd = open(FileName.native().c_str(), O_RDONLY);
+ if (Fd < 0)
+ {
+ return {};
+ }
+
+ static_assert(sizeof(decltype(stat::st_size)) == sizeof(uint64_t), "fstat() doesn't support large files");
+ struct stat Stat;
+ fstat(Fd, &Stat);
+ FileSize = Stat.st_size;
+
+ Handle = (void*)uintptr_t(Fd);
+#endif // ZEN_PLATFORM_WINDOWS
+
+ IoBuffer Iob(IoBuffer::File, Handle, 0, FileSize);
+ Iob.m_Core->SetIsWholeFile(true);
+
+ return Iob;
+}
+
+IoHash
+HashBuffer(IoBuffer& Buffer)
+{
+ // TODO: handle disk buffers with special path
+ return IoHash::HashBuffer(Buffer.Data(), Buffer.Size());
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+#if ZEN_WITH_TESTS
+
+void
+iobuffer_forcelink()
+{
+}
+
+TEST_CASE("IoBuffer")
+{
+ zen::IoBuffer buffer1;
+ zen::IoBuffer buffer2(16384);
+ zen::IoBuffer buffer3(buffer2, 0, buffer2.Size());
+}
+
+#endif
+
+} // namespace zen