diff --git a/llvm/CMakeLists.txt b/llvm/CMakeLists.txt index cde4a999ea2e7..3ac4dde16d878 100644 --- a/llvm/CMakeLists.txt +++ b/llvm/CMakeLists.txt @@ -837,6 +837,13 @@ option (LLVM_ENABLE_SPHINX "Use Sphinx to generate llvm documentation." OFF) option (LLVM_ENABLE_OCAMLDOC "Build OCaml bindings documentation." ON) option (LLVM_ENABLE_BINDINGS "Build bindings." ON) +if(UNIX AND CMAKE_SIZEOF_VOID_P GREATER_EQUAL 8) + set(LLVM_ENABLE_ONDISK_CAS_default ON) +else() + set(LLVM_ENABLE_ONDISK_CAS_default OFF) +endif() +option(LLVM_ENABLE_ONDISK_CAS "Build OnDiskCAS." ${LLVM_ENABLE_ONDISK_CAS_default}) + set(LLVM_INSTALL_DOXYGEN_HTML_DIR "${CMAKE_INSTALL_DOCDIR}/llvm/doxygen-html" CACHE STRING "Doxygen-generated HTML documentation install directory") set(LLVM_INSTALL_OCAMLDOC_HTML_DIR "${CMAKE_INSTALL_DOCDIR}/llvm/ocaml-html" diff --git a/llvm/include/llvm/CAS/MappedFileRegionBumpPtr.h b/llvm/include/llvm/CAS/MappedFileRegionBumpPtr.h new file mode 100644 index 0000000000000..0df816375876a --- /dev/null +++ b/llvm/include/llvm/CAS/MappedFileRegionBumpPtr.h @@ -0,0 +1,110 @@ +//===- MappedFileRegionBumpPtr.h --------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CAS_MAPPEDFILEREGIONBUMPPTR_H +#define LLVM_CAS_MAPPEDFILEREGIONBUMPPTR_H + +#include "llvm/Support/Alignment.h" +#include "llvm/Support/FileSystem.h" +#include + +namespace llvm::cas { + +/// Allocator for an owned mapped file region that supports thread-safe and +/// process-safe bump pointer allocation. +/// +/// This allocator is designed to create a sparse file when supported by the +/// filesystem's \c ftruncate so that it can be used with a large maximum size. +/// It will also attempt to shrink the underlying file down to its current +/// allocation size when the last concurrent mapping is closed. +/// +/// Process-safe. Uses file locks when resizing the file during initialization +/// and destruction. +/// +/// Thread-safe, assuming all threads use the same instance to talk to a given +/// file/mapping. Unsafe to have multiple instances talking to the same file +/// in the same process since file locks will misbehave. Clients should +/// coordinate (somehow). +/// +/// \note Currently we allocate the whole file without sparseness on Windows. +/// +/// Provides 8-byte alignment for all allocations. +class MappedFileRegionBumpPtr { +public: + using RegionT = sys::fs::mapped_file_region; + + /// Create a \c MappedFileRegionBumpPtr. + /// + /// \param Path the path to open the mapped region. + /// \param Capacity the maximum size for the mapped file region. + /// \param BumpPtrOffset the offset at which to store the bump pointer. + /// \param NewFileConstructor is for constructing new files. It has exclusive + /// access to the file. Must call \c initializeBumpPtr. + static Expected + create(const Twine &Path, uint64_t Capacity, int64_t BumpPtrOffset, + function_ref NewFileConstructor); + + /// Finish initializing the bump pointer. Must be called by + /// \c NewFileConstructor. + void initializeBumpPtr(int64_t BumpPtrOffset); + + /// Minimum alignment for allocations, currently hardcoded to 8B. + static constexpr Align getAlign() { + // Trick Align into giving us '8' as a constexpr. + struct alignas(8) T {}; + static_assert(alignof(T) == 8, "Tautology failed?"); + return Align::Of(); + } + + /// Allocate at least \p AllocSize. Rounds up to \a getAlign(). + char *allocate(uint64_t AllocSize) { + return data() + allocateOffset(AllocSize); + } + /// Allocate, returning the offset from \a data() instead of a pointer. + int64_t allocateOffset(uint64_t AllocSize); + + char *data() const { return Region.data(); } + uint64_t size() const { return *BumpPtr; } + uint64_t capacity() const { return Region.size(); } + + RegionT &getRegion() { return Region; } + + ~MappedFileRegionBumpPtr() { destroyImpl(); } + + MappedFileRegionBumpPtr() = default; + MappedFileRegionBumpPtr(MappedFileRegionBumpPtr &&RHS) { moveImpl(RHS); } + MappedFileRegionBumpPtr &operator=(MappedFileRegionBumpPtr &&RHS) { + destroyImpl(); + moveImpl(RHS); + return *this; + } + + MappedFileRegionBumpPtr(const MappedFileRegionBumpPtr &) = delete; + MappedFileRegionBumpPtr &operator=(const MappedFileRegionBumpPtr &) = delete; + +private: + void destroyImpl(); + void moveImpl(MappedFileRegionBumpPtr &RHS) { + std::swap(Region, RHS.Region); + std::swap(BumpPtr, RHS.BumpPtr); + std::swap(Path, RHS.Path); + std::swap(FD, RHS.FD); + std::swap(SharedLockFD, RHS.SharedLockFD); + } + +private: + RegionT Region; + std::atomic *BumpPtr = nullptr; + std::string Path; + std::optional FD; + std::optional SharedLockFD; +}; + +} // namespace llvm::cas + +#endif // LLVM_CAS_MAPPEDFILEREGIONBUMPPTR_H diff --git a/llvm/lib/CAS/CMakeLists.txt b/llvm/lib/CAS/CMakeLists.txt index e4d87a941423b..8ddaef8d7eb89 100644 --- a/llvm/lib/CAS/CMakeLists.txt +++ b/llvm/lib/CAS/CMakeLists.txt @@ -1,9 +1,15 @@ +if (LLVM_ENABLE_ONDISK_CAS) + add_definitions(-DLLVM_ENABLE_ONDISK_CAS=1) +endif() + add_llvm_component_library(LLVMCAS ActionCache.cpp ActionCaches.cpp BuiltinCAS.cpp InMemoryCAS.cpp + MappedFileRegionBumpPtr.cpp ObjectStore.cpp + OnDiskCommon.cpp ADDITIONAL_HEADER_DIRS ${LLVM_MAIN_INCLUDE_DIR}/llvm/CAS diff --git a/llvm/lib/CAS/MappedFileRegionBumpPtr.cpp b/llvm/lib/CAS/MappedFileRegionBumpPtr.cpp new file mode 100644 index 0000000000000..222264441ed99 --- /dev/null +++ b/llvm/lib/CAS/MappedFileRegionBumpPtr.cpp @@ -0,0 +1,233 @@ +//===- MappedFileRegionBumpPtr.cpp ------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// \file +/// +/// A bump pointer allocator, backed by a memory-mapped file. +/// +/// The effect we want is: +/// +/// 1. If it doesn't exist, create the file with an initial size. +/// 2. Reserve virtual memory large enough for the max file size. +/// 3. Map the file into memory in the reserved region. +/// 4. Increase the file size and update the mapping when necessary. +/// +/// However, updating the mapping is challenging when it needs to work portably, +/// and across multiple processes without locking for every read. Our current +/// implementation strategy is: +/// +/// 1. Use \c ftruncate (\c sys::fs::resize_file) to grow the file to its max +/// size (typically several GB). Many modern filesystems will create a sparse +/// file, so that the trailing unused pages do not take space on disk. +/// 2. Call \c mmap (\c sys::fs::mapped_file_region) +/// 3. [Automatic as part of 2.] +/// 4. [Automatic as part of 2.] +/// +/// Additionally, we attempt to resize the file to its actual data size when +/// closing the mapping, if this is the only concurrent instance. This is done +/// using file locks. Shrinking the file mitigates problems with having large +/// files: on filesystems without sparse files it avoids unnecessary space use; +/// it also avoids allocating the full size if another process copies the file, +/// which typically loses sparseness. These mitigations only work while the file +/// is not in use. +/// +/// FIXME: we assume that all concurrent users of the file will use the same +/// value for Capacity. Otherwise a process with a larger capacity can write +/// data that is "out of bounds" for processes with smaller capacity. Currently +/// this is true in the CAS. +/// +/// To support resizing, we use two separate file locks: +/// 1. We use a shared reader lock on a ".shared" file until destruction. +/// 2. We use a lock on the main file during initialization - shared to check +/// the status, upgraded to exclusive to resize/initialize the file. +/// +/// Then during destruction we attempt to get exclusive access on (1), which +/// requires no concurrent readers. If so, we shrink the file. Using two +/// separate locks simplifies the implementation and enables it to work on +/// platforms (e.g. Windows) where a shared/reader lock prevents writing. +//===----------------------------------------------------------------------===// + +#include "llvm/CAS/MappedFileRegionBumpPtr.h" +#include "OnDiskCommon.h" + +using namespace llvm; +using namespace llvm::cas; +using namespace llvm::cas::ondisk; + +namespace { +struct FileLockRAII { + std::string Path; + int FD; + enum LockKind { Shared, Exclusive }; + std::optional Locked; + + FileLockRAII(std::string Path, int FD) : Path(std::move(Path)), FD(FD) {} + ~FileLockRAII() { consumeError(unlock()); } + + Error lock(LockKind LK) { + if (std::error_code EC = lockFileThreadSafe(FD, LK == Exclusive)) + return createFileError(Path, EC); + Locked = LK; + return Error::success(); + } + + Error unlock() { + if (Locked) { + Locked = std::nullopt; + if (std::error_code EC = unlockFileThreadSafe(FD)) + return createFileError(Path, EC); + } + return Error::success(); + } +}; +} // end anonymous namespace + +Expected MappedFileRegionBumpPtr::create( + const Twine &Path, uint64_t Capacity, int64_t BumpPtrOffset, + function_ref NewFileConstructor) { + MappedFileRegionBumpPtr Result; + Result.Path = Path.str(); + // Open the main file. + int FD; + if (std::error_code EC = sys::fs::openFileForReadWrite( + Result.Path, FD, sys::fs::CD_OpenAlways, sys::fs::OF_None)) + return createFileError(Path, EC); + Result.FD = FD; + + // Open the shared lock file. See file comment for details of locking scheme. + SmallString<128> SharedLockPath(Result.Path); + SharedLockPath.append(".shared"); + int SharedLockFD; + if (std::error_code EC = sys::fs::openFileForReadWrite( + SharedLockPath, SharedLockFD, sys::fs::CD_OpenAlways, + sys::fs::OF_None)) + return createFileError(SharedLockPath, EC); + Result.SharedLockFD = SharedLockFD; + + // Take shared/reader lock that will be held until we close the file; unlocked + // by destroyImpl. + if (std::error_code EC = + lockFileThreadSafe(SharedLockFD, /*Exclusive=*/false)) + return createFileError(Path, EC); + + // Take shared/reader lock for initialization. + FileLockRAII InitLock(Result.Path, FD); + if (Error E = InitLock.lock(FileLockRAII::Shared)) + return std::move(E); + + sys::fs::file_t File = sys::fs::convertFDToNativeFile(FD); + sys::fs::file_status Status; + if (std::error_code EC = sys::fs::status(File, Status)) + return createFileError(Result.Path, EC); + + if (Status.getSize() < Capacity) { + // Lock the file exclusively so only one process will do the initialization. + if (Error E = InitLock.unlock()) + return std::move(E); + if (Error E = InitLock.lock(FileLockRAII::Exclusive)) + return std::move(E); + // Retrieve the current size now that we have exclusive access. + if (std::error_code EC = sys::fs::status(File, Status)) + return createFileError(Result.Path, EC); + } + + // At this point either the file is still under-sized, or we have the size for + // the completely initialized file. + + if (Status.getSize() < Capacity) { + // We are initializing the file; it may be empty, or may have been shrunk + // during a previous close. + // FIXME: Detect a case where someone opened it with a smaller capacity. + // FIXME: On Windows we should use FSCTL_SET_SPARSE and FSCTL_SET_ZERO_DATA + // to make this a sparse region, if supported. + if (std::error_code EC = sys::fs::resize_file(FD, Capacity)) + return createFileError(Result.Path, EC); + } else { + // Someone else initialized it. + Capacity = Status.getSize(); + } + + // Create the mapped region. + { + std::error_code EC; + sys::fs::mapped_file_region Map( + File, sys::fs::mapped_file_region::readwrite, Capacity, 0, EC); + if (EC) + return createFileError(Result.Path, EC); + Result.Region = std::move(Map); + } + + if (Status.getSize() == 0) { + // We are creating a new file; run the constructor. + if (Error E = NewFileConstructor(Result)) + return std::move(E); + } else { + Result.initializeBumpPtr(BumpPtrOffset); + } + + return Result; +} + +void MappedFileRegionBumpPtr::destroyImpl() { + if (!FD) + return; + + // Drop the shared lock indicating we are no longer accessing the file. + if (SharedLockFD) + (void)unlockFileThreadSafe(*SharedLockFD); + + // Attempt to truncate the file if we can get exclusive access. Ignore any + // errors. + if (BumpPtr) { + assert(SharedLockFD && "Must have shared lock file open"); + if (tryLockFileThreadSafe(*SharedLockFD) == std::error_code()) { + assert(size() <= capacity()); + (void)sys::fs::resize_file(*FD, size()); + (void)unlockFileThreadSafe(*SharedLockFD); + } + } + + auto Close = [](std::optional &FD) { + if (FD) { + sys::fs::file_t File = sys::fs::convertFDToNativeFile(*FD); + sys::fs::closeFile(File); + FD = std::nullopt; + } + }; + + // Close the file and shared lock. + Close(FD); + Close(SharedLockFD); +} + +void MappedFileRegionBumpPtr::initializeBumpPtr(int64_t BumpPtrOffset) { + assert(capacity() < (uint64_t)INT64_MAX && "capacity must fit in int64_t"); + int64_t BumpPtrEndOffset = BumpPtrOffset + sizeof(decltype(*BumpPtr)); + assert(BumpPtrEndOffset <= (int64_t)capacity() && + "Expected end offset to be pre-allocated"); + assert(isAligned(Align::Of(), BumpPtrOffset) && + "Expected end offset to be aligned"); + BumpPtr = reinterpret_cast(data() + BumpPtrOffset); + + int64_t ExistingValue = 0; + if (!BumpPtr->compare_exchange_strong(ExistingValue, BumpPtrEndOffset)) + assert(ExistingValue >= BumpPtrEndOffset && + "Expected 0, or past the end of the BumpPtr itself"); +} + +int64_t MappedFileRegionBumpPtr::allocateOffset(uint64_t AllocSize) { + AllocSize = alignTo(AllocSize, getAlign()); + int64_t OldEnd = BumpPtr->fetch_add(AllocSize); + int64_t NewEnd = OldEnd + AllocSize; + if (LLVM_UNLIKELY(NewEnd > (int64_t)capacity())) { + // Try to return the allocation. + (void)BumpPtr->compare_exchange_strong(OldEnd, NewEnd); + report_fatal_error( + errorCodeToError(std::make_error_code(std::errc::not_enough_memory))); + } + return OldEnd; +} diff --git a/llvm/lib/CAS/OnDiskCommon.cpp b/llvm/lib/CAS/OnDiskCommon.cpp new file mode 100644 index 0000000000000..12df239ee54cb --- /dev/null +++ b/llvm/lib/CAS/OnDiskCommon.cpp @@ -0,0 +1,73 @@ +//===- OnDiskCommon.cpp ---------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "OnDiskCommon.h" +#include + +#if __has_include() +#include +#ifdef LOCK_SH +#define HAVE_FLOCK 1 +#else +#define HAVE_FLOCK 0 +#endif +#endif + +using namespace llvm; + +std::error_code cas::ondisk::lockFileThreadSafe(int FD, bool Exclusive) { +#if HAVE_FLOCK + if (flock(FD, Exclusive ? LOCK_EX : LOCK_SH) == 0) + return std::error_code(); + return std::error_code(errno, std::generic_category()); +#elif defined(_WIN32) + // On Windows this implementation is thread-safe. + return sys::fs::lockFile(FD, Exclusive); +#else + return make_error_code(std::errc::no_lock_available); +#endif +} + +std::error_code cas::ondisk::unlockFileThreadSafe(int FD) { +#if HAVE_FLOCK + if (flock(FD, LOCK_UN) == 0) + return std::error_code(); + return std::error_code(errno, std::generic_category()); +#elif defined(_WIN32) + // On Windows this implementation is thread-safe. + return sys::fs::unlockFile(FD); +#else + return make_error_code(std::errc::no_lock_available); +#endif +} + +std::error_code +cas::ondisk::tryLockFileThreadSafe(int FD, std::chrono::milliseconds Timeout, + bool Exclusive) { +#if HAVE_FLOCK + auto Start = std::chrono::steady_clock::now(); + auto End = Start + Timeout; + do { + if (flock(FD, (Exclusive ? LOCK_EX : LOCK_SH) | LOCK_NB) == 0) + return std::error_code(); + int Error = errno; + if (Error == EWOULDBLOCK) { + // Match sys::fs::tryLockFile, which sleeps for 1 ms per attempt. + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + return std::error_code(Error, std::generic_category()); + } while (std::chrono::steady_clock::now() < End); + return make_error_code(std::errc::no_lock_available); +#elif defined(_WIN32) + // On Windows this implementation is thread-safe. + return sys::fs::tryLockFile(FD, Timeout, Exclusive); +#else + return make_error_code(std::errc::no_lock_available); +#endif +} diff --git a/llvm/lib/CAS/OnDiskCommon.h b/llvm/lib/CAS/OnDiskCommon.h new file mode 100644 index 0000000000000..08da355c84453 --- /dev/null +++ b/llvm/lib/CAS/OnDiskCommon.h @@ -0,0 +1,35 @@ +//===- OnDiskCommon.h -------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIB_CAS_ONDISKCOMMON_H +#define LLVM_LIB_CAS_ONDISKCOMMON_H + +#include + +namespace llvm::cas::ondisk { + +/// Thread-safe alternative to \c sys::fs::lockFile. This does not support all +/// the platforms that \c sys::fs::lockFile does, so keep it in the CAS library +/// for now. +std::error_code lockFileThreadSafe(int FD, bool Exclusive = true); + +/// Thread-safe alternative to \c sys::fs::unlockFile. This does not support all +/// the platforms that \c sys::fs::lockFile does, so keep it in the CAS library +/// for now. +std::error_code unlockFileThreadSafe(int FD); + +/// Thread-safe alternative to \c sys::fs::tryLockFile. This does not support +/// all the platforms that \c sys::fs::lockFile does, so keep it in the CAS +/// library for now. +std::error_code tryLockFileThreadSafe( + int FD, std::chrono::milliseconds Timeout = std::chrono::milliseconds(0), + bool Exclusive = true); + +} // namespace llvm::cas::ondisk + +#endif // LLVM_LIB_CAS_ONDISKCOMMON_H diff --git a/llvm/unittests/CAS/CMakeLists.txt b/llvm/unittests/CAS/CMakeLists.txt index ff081007f31bc..ab709e30369bf 100644 --- a/llvm/unittests/CAS/CMakeLists.txt +++ b/llvm/unittests/CAS/CMakeLists.txt @@ -1,3 +1,7 @@ +if (LLVM_ENABLE_ONDISK_CAS) + add_definitions(-DLLVM_ENABLE_ONDISK_CAS=1) +endif() + set(LLVM_LINK_COMPONENTS Support CAS @@ -8,6 +12,7 @@ add_llvm_unittest(CASTests ActionCacheTest.cpp CASTestConfig.cpp ObjectStoreTest.cpp + ProgramTest.cpp ) target_link_libraries(CASTests PRIVATE LLVMTestingSupport) diff --git a/llvm/unittests/CAS/ProgramTest.cpp b/llvm/unittests/CAS/ProgramTest.cpp new file mode 100644 index 0000000000000..95cc35917bb7a --- /dev/null +++ b/llvm/unittests/CAS/ProgramTest.cpp @@ -0,0 +1,151 @@ +//===- MappedFileRegionBumpPtrTest.cpp ------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/Program.h" +#include "llvm/CAS/MappedFileRegionBumpPtr.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/ThreadPool.h" +#include "gtest/gtest.h" +#if defined(__APPLE__) +#include +#elif !defined(_MSC_VER) +// Forward declare environ in case it's not provided by stdlib.h. +extern char **environ; +#endif + +using namespace llvm; +using namespace llvm::cas; + +extern const char *TestMainArgv0; +static char ProgramID = 0; + +class CASProgramTest : public testing::Test { + std::vector EnvTable; + std::vector EnvStorage; + +protected: + void SetUp() override { + auto EnvP = [] { +#if defined(_WIN32) + _wgetenv(L"TMP"); // Populate _wenviron, initially is null + return _wenviron; +#elif defined(__APPLE__) + return *_NSGetEnviron(); +#else + return environ; +#endif + }(); + ASSERT_TRUE(EnvP); + + auto prepareEnvVar = [this](decltype(*EnvP) Var) -> StringRef { +#if defined(_WIN32) + // On Windows convert UTF16 encoded variable to UTF8 + auto Len = wcslen(Var); + ArrayRef Ref{reinterpret_cast(Var), + Len * sizeof(*Var)}; + EnvStorage.emplace_back(); + auto convStatus = convertUTF16ToUTF8String(Ref, EnvStorage.back()); + EXPECT_TRUE(convStatus); + return EnvStorage.back(); +#else + (void)this; + return StringRef(Var); +#endif + }; + + while (*EnvP != nullptr) { + auto S = prepareEnvVar(*EnvP); + if (!StringRef(S).starts_with("GTEST_")) + EnvTable.emplace_back(S); + ++EnvP; + } + } + + void TearDown() override { + EnvTable.clear(); + EnvStorage.clear(); + } + + void addEnvVar(StringRef Var) { EnvTable.emplace_back(Var); } + + ArrayRef getEnviron() const { return EnvTable; } +}; + +#if LLVM_ENABLE_ONDISK_CAS + +TEST_F(CASProgramTest, MappedFileRegionBumpPtrTest) { + auto TestAllocator = [](StringRef Path) { + auto NewFileConstructor = [&](MappedFileRegionBumpPtr &Alloc) -> Error { + Alloc.initializeBumpPtr(0); + return Error::success(); + }; + + auto Alloc = MappedFileRegionBumpPtr::create( + Path, /*Capacity=*/10 * 1024 * 1024, + /*BumpPtrOffset=*/0, NewFileConstructor); + if (!Alloc) + ASSERT_TRUE(false); + + std::vector AllocatedPtr; + AllocatedPtr.resize(100); + DefaultThreadPool Threads; + for (unsigned I = 0; I < 100; ++I) { + Threads.async( + [&](unsigned Idx) { + // Allocate a buffer that is larger than needed so allocator hits + // additional pages for test coverage. + unsigned *P = (unsigned *)Alloc->allocate(100); + *P = Idx; + AllocatedPtr[Idx] = P; + }, + I); + } + + Threads.wait(); + for (unsigned I = 0; I < 100; ++I) + EXPECT_EQ(*AllocatedPtr[I], I); + }; + + if (const char *File = getenv("LLVM_CAS_TEST_MAPPED_FILE_REGION")) { + TestAllocator(File); + exit(0); + } + + SmallString<128> FilePath; + sys::fs::createUniqueDirectory("MappedFileRegionBumpPtr", FilePath); + sys::path::append(FilePath, "allocation-file"); + + std::string Executable = + sys::fs::getMainExecutable(TestMainArgv0, &ProgramID); + StringRef Argv[] = { + Executable, "--gtest_filter=CASProgramTest.MappedFileRegionBumpPtrTest"}; + + // Add LLVM_PROGRAM_TEST_LOCKED_FILE to the environment of the child. + std::string EnvVar = "LLVM_CAS_TEST_MAPPED_FILE_REGION="; + EnvVar += FilePath.str(); + addEnvVar(EnvVar); + + std::string Error; + bool ExecutionFailed; + sys::ProcessInfo PI = sys::ExecuteNoWait(Executable, Argv, getEnviron(), {}, + 0, &Error, &ExecutionFailed); + TestAllocator(FilePath); + + ASSERT_FALSE(ExecutionFailed) << Error; + ASSERT_TRUE(Error.empty()); + ASSERT_NE(PI.Pid, sys::ProcessInfo::InvalidPid) << "Invalid process id"; + llvm::sys::Wait(PI, /*SecondsToWait=*/5, &Error); + ASSERT_TRUE(Error.empty()); + + // Clean up after both processes finish testing. + sys::fs::remove(FilePath); + sys::fs::remove_directories(sys::path::parent_path(FilePath)); +} + +#endif // LLVM_ENABLE_ONDISK_CAS