[PATCH] D28010: FileOutputBuffer: support non-mmap'able file.
Rafael Avila de Espindola via llvm-commits
llvm-commits at lists.llvm.org
Mon Jan 9 14:38:17 PST 2017
The lld part LGTM.
Cheers,
Rafael
Rui Ueyama via Phabricator <reviews at reviews.llvm.org> writes:
> ruiu created this revision.
> ruiu added a reviewer: rafael.
> ruiu added a subscriber: llvm-commits.
>
> Previously, FileOutputBuffer didn't work for non-mmap'able files, so
> you cannot use that class for /dev/null for example. This patch fixes
> the issue.
>
> Now, before mmap'ing a file, it checks whether a given file is a regular
> file or not. If it is a regular file, it uses mmap as before. If not, it
> creates an internal buffer and write that buffer contents on commit().
>
> This patch enables "-o /dev/null" for LLD. Previouly, it failed.
>
>
> https://reviews.llvm.org/D28010
>
> Files:
> lld/ELF/Writer.cpp
> lld/test/ELF/basic.s
> llvm/include/llvm/Support/FileOutputBuffer.h
> llvm/lib/Support/FileOutputBuffer.cpp
>
> Index: llvm/lib/Support/FileOutputBuffer.cpp
> ===================================================================
> --- llvm/lib/Support/FileOutputBuffer.cpp
> +++ llvm/lib/Support/FileOutputBuffer.cpp
> @@ -16,102 +16,155 @@
> #include "llvm/ADT/SmallString.h"
> #include "llvm/Support/Errc.h"
> #include "llvm/Support/Signals.h"
> +#include "llvm/Support/raw_ostream.h"
> #include <system_error>
>
> #if !defined(_MSC_VER) && !defined(__MINGW32__)
> #include <unistd.h>
> #else
> #include <io.h>
> #endif
>
> -using llvm::sys::fs::mapped_file_region;
> +using namespace llvm::sys;
>
> namespace llvm {
> -FileOutputBuffer::FileOutputBuffer(std::unique_ptr<mapped_file_region> R,
> - StringRef Path, StringRef TmpPath)
> - : Region(std::move(R)), FinalPath(Path), TempPath(TmpPath) {}
> -
> -FileOutputBuffer::~FileOutputBuffer() {
> - // Close the mapping before deleting the temp file, so that the removal
> - // succeeds.
> - Region.reset();
> - sys::fs::remove(Twine(TempPath));
> -}
> -
> -ErrorOr<std::unique_ptr<FileOutputBuffer>>
> -FileOutputBuffer::create(StringRef FilePath, size_t Size, unsigned Flags) {
> - // If file already exists, it must be a regular file (to be mappable).
> - sys::fs::file_status Stat;
> - std::error_code EC = sys::fs::status(FilePath, Stat);
> - switch (Stat.type()) {
> - case sys::fs::file_type::file_not_found:
> +namespace {
> +
> +// Represents a memory-mapped file.
> +class MMappedFile : public FileOutputBuffer::BaseFile {
> +public:
> + static ErrorOr<std::unique_ptr<MMappedFile>>
> + create(std::string Path, size_t Size, unsigned Flags) {
> + // If file already exists, it must be a regular file (to be mappable).
> + fs::file_status Stat;
> + std::error_code EC;
> + fs::status(Path, Stat);
> +
> + switch (Stat.type()) {
> + case fs::file_type::file_not_found:
> // If file does not exist, we'll create one.
> break;
> - case sys::fs::file_type::regular_file: {
> - // If file is not currently writable, error out.
> - // FIXME: There is no sys::fs:: api for checking this.
> - // FIXME: In posix, you use the access() call to check this.
> - }
> + case fs::file_type::regular_file:
> + // If file is not currently writable, error out.
> + // FIXME: There is no fs:: api for checking this.
> + // FIXME: In posix, you use the access() call to check this.
> break;
> default:
> if (EC)
> return EC;
> - else
> - return make_error_code(errc::operation_not_permitted);
> - }
> + return make_error_code(errc::operation_not_permitted);
> + }
>
> - // Delete target file.
> - EC = sys::fs::remove(FilePath);
> - if (EC)
> - return EC;
> + // Delete target file.
> + if ((EC = fs::remove(Path)))
> + return EC;
>
> - unsigned Mode = sys::fs::all_read | sys::fs::all_write;
> - // If requested, make the output file executable.
> - if (Flags & F_executable)
> - Mode |= sys::fs::all_exe;
> -
> - // Create new file in same directory but with random name.
> - SmallString<128> TempFilePath;
> - int FD;
> - EC = sys::fs::createUniqueFile(Twine(FilePath) + ".tmp%%%%%%%", FD,
> - TempFilePath, Mode);
> - if (EC)
> - return EC;
> + unsigned Mode = fs::all_read | fs::all_write;
> + // If requested, make the output file executable.
> + if (Flags & FileOutputBuffer::F_executable)
> + Mode |= fs::all_exe;
>
> - sys::RemoveFileOnSignal(TempFilePath);
> + // Create new file in same directory but with random name.
> + SmallString<128> TempFilePath;
> + int FD;
> + if ((EC = fs::createUniqueFile(Twine(Path) + ".tmp%%%%%%%", FD,
> + TempFilePath, Mode)))
> + return EC;
> +
> + sys::RemoveFileOnSignal(TempFilePath);
>
> #ifndef LLVM_ON_WIN32
> - // On Windows, CreateFileMapping (the mmap function on Windows)
> - // automatically extends the underlying file. We don't need to
> - // extend the file beforehand. _chsize (ftruncate on Windows) is
> - // pretty slow just like it writes specified amount of bytes,
> - // so we should avoid calling that.
> - EC = sys::fs::resize_file(FD, Size);
> - if (EC)
> - return EC;
> + // On Windows, CreateFileMapping (the mmap function on Windows)
> + // automatically extends the underlying file. We don't need to
> + // extend the file beforehand. _chsize (ftruncate on Windows) is
> + // pretty slow just like it writes specified amount of bytes,
> + // so we should avoid calling that.
> + if ((EC = fs::resize_file(FD, Size)))
> + return EC;
> #endif
>
> - auto MappedFile = llvm::make_unique<mapped_file_region>(
> - FD, mapped_file_region::readwrite, Size, 0, EC);
> - int Ret = close(FD);
> - if (EC)
> - return EC;
> - if (Ret)
> - return std::error_code(errno, std::generic_category());
> + auto MappedFile = make_unique<fs::mapped_file_region>(
> + FD, fs::mapped_file_region::readwrite, Size, 0, EC);
> + int Ret = close(FD);
> + if (EC)
> + return EC;
> + if (Ret)
> + return std::error_code(errno, std::generic_category());
>
> - std::unique_ptr<FileOutputBuffer> Buf(
> - new FileOutputBuffer(std::move(MappedFile), FilePath, TempFilePath));
> - return std::move(Buf);
> -}
> + return std::unique_ptr<MMappedFile>(
> + new MMappedFile(MappedFile, Path, TempFilePath.str()));
> + }
>
> -std::error_code FileOutputBuffer::commit() {
> - // Unmap buffer, letting OS flush dirty pages to file on disk.
> - Region.reset();
> + ~MMappedFile() override {
> + // Close the mapping before deleting the temp file, so that the removal
> + // succeeds.
> + Region.reset();
> + fs::remove(TempPath);
> + }
>
> + uint8_t *getBufferStart() override { return (uint8_t *)Region->data(); }
>
> - // Rename file to final name.
> - std::error_code EC = sys::fs::rename(Twine(TempPath), Twine(FinalPath));
> - sys::DontRemoveFileOnSignal(TempPath);
> - return EC;
> + std::error_code commit() override {
> + // Unmap buffer, letting OS flush dirty pages to file on disk.
> + Region.reset();
> +
> + // Rename file to final name.
> + std::error_code EC = fs::rename(TempPath, FinalPath);
> + sys::DontRemoveFileOnSignal(TempPath);
> + return EC;
> + }
> +
> +private:
> + MMappedFile(std::unique_ptr<fs::mapped_file_region> &R, std::string Path,
> + std::string TempPath)
> + : Region(std::move(R)), FinalPath(Path), TempPath(TempPath) {}
> +
> + std::unique_ptr<fs::mapped_file_region> Region;
> + std::string FinalPath;
> + std::string TempPath;
> +};
> +
> +// Represents a non-memory-mapped file. We need this class
> +// because not all files are mmap'able. For example, /dev/null
> +// is not mmap'able.
> +class NonMMappedFile : public FileOutputBuffer::BaseFile {
> +public:
> + NonMMappedFile(std::string Path, size_t Size)
> + : FinalPath(Path), Buffer(Size) {}
> +
> + uint8_t *getBufferStart() override { return Buffer.data(); }
> +
> + std::error_code commit() override {
> + // Open a file and then write Buffer to it.
> + std::error_code EC;
> + raw_fd_ostream OS(FinalPath, EC, fs::F_None);
> + if (EC)
> + return EC;
> + OS.write((const char *)Buffer.data(), Buffer.size());
> + return std::error_code();
> + }
> +
> +private:
> + std::string FinalPath;
> + std::vector<uint8_t> Buffer;
> +};
> }
> +
> +ErrorOr<std::unique_ptr<FileOutputBuffer>>
> +FileOutputBuffer::create(StringRef Path, size_t Size, unsigned Flags) {
> + // If it is not a regular file, do not use mmap.
> + fs::file_status Stat;
> + fs::status(Path, Stat);
> + if (fs::is_other(Stat))
> + return std::unique_ptr<FileOutputBuffer>(
> + new FileOutputBuffer(Path, Size, new NonMMappedFile(Path, Size)));
> +
> + // Otherwise, use mmap.
> + auto FileOrErr = MMappedFile::create(Path, Size, Flags);
> + if (std::error_code EC = FileOrErr.getError())
> + return EC;
> + return std::unique_ptr<FileOutputBuffer>(
> + new FileOutputBuffer(Path, Size, FileOrErr->release()));
> +}
> +
> } // namespace
> Index: llvm/include/llvm/Support/FileOutputBuffer.h
> ===================================================================
> --- llvm/include/llvm/Support/FileOutputBuffer.h
> +++ llvm/include/llvm/Support/FileOutputBuffer.h
> @@ -30,6 +30,12 @@
> /// not committed, the file will be deleted in the FileOutputBuffer destructor.
> class FileOutputBuffer {
> public:
> + class BaseFile {
> + public:
> + virtual ~BaseFile() {}
> + virtual uint8_t *getBufferStart() = 0;
> + virtual std::error_code commit() = 0;
> + };
>
> enum {
> F_executable = 1 /// set the 'x' bit on the resulting file
> @@ -42,47 +48,34 @@
> create(StringRef FilePath, size_t Size, unsigned Flags = 0);
>
> /// Returns a pointer to the start of the buffer.
> - uint8_t *getBufferStart() {
> - return (uint8_t*)Region->data();
> - }
> + uint8_t *getBufferStart() { return File->getBufferStart(); }
>
> /// Returns a pointer to the end of the buffer.
> - uint8_t *getBufferEnd() {
> - return (uint8_t*)Region->data() + Region->size();
> - }
> + uint8_t *getBufferEnd() { return getBufferStart() + Size; }
>
> /// Returns size of the buffer.
> - size_t getBufferSize() const {
> - return Region->size();
> - }
> + size_t getBufferSize() const { return Size; }
>
> /// Returns path where file will show up if buffer is committed.
> - StringRef getPath() const {
> - return FinalPath;
> - }
> + StringRef getPath() const { return FinalPath; }
>
> /// Flushes the content of the buffer to its file and deallocates the
> /// buffer. If commit() is not called before this object's destructor
> /// is called, the file is deleted in the destructor. The optional parameter
> /// is used if it turns out you want the file size to be smaller than
> /// initially requested.
> - std::error_code commit();
> -
> - /// If this object was previously committed, the destructor just deletes
> - /// this object. If this object was not committed, the destructor
> - /// deallocates the buffer and the target file is never written.
> - ~FileOutputBuffer();
> + std::error_code commit() { return File->commit(); }
>
> private:
> + FileOutputBuffer(StringRef Path, size_t Size, BaseFile *File)
> + : FinalPath(Path), Size(Size), File(File) {}
> +
> FileOutputBuffer(const FileOutputBuffer &) = delete;
> FileOutputBuffer &operator=(const FileOutputBuffer &) = delete;
>
> - FileOutputBuffer(std::unique_ptr<llvm::sys::fs::mapped_file_region> R,
> - StringRef Path, StringRef TempPath);
> -
> - std::unique_ptr<llvm::sys::fs::mapped_file_region> Region;
> - SmallString<128> FinalPath;
> - SmallString<128> TempPath;
> + std::string FinalPath;
> + size_t Size;
> + std::unique_ptr<BaseFile> File;
> };
> } // end namespace llvm
>
> Index: lld/test/ELF/basic.s
> ===================================================================
> --- lld/test/ELF/basic.s
> +++ lld/test/ELF/basic.s
> @@ -4,6 +4,7 @@
> # RUN: ld.lld %t -o %t2
> # RUN: llvm-readobj -file-headers -sections -program-headers -symbols %t2 \
> # RUN: | FileCheck %s
> +# RUN: ld.lld %t -o /dev/null
>
> # exits with return code 42 on linux
> .globl _start
> Index: lld/ELF/Writer.cpp
> ===================================================================
> --- lld/ELF/Writer.cpp
> +++ lld/ELF/Writer.cpp
> @@ -1634,10 +1634,12 @@
> // Path as a new file. If we do that in a different thread, the new
> // thread can remove the new file.
> SmallString<128> TempPath;
> - if (auto EC = sys::fs::createUniqueFile(Path + "tmp%%%%%%%%", TempPath))
> - fatal(EC, "createUniqueFile failed");
> - if (auto EC = sys::fs::rename(Path, TempPath))
> - fatal(EC, "rename failed");
> + if (sys::fs::createUniqueFile(Path + "tmp%%%%%%%%", TempPath))
> + return;
> + if (sys::fs::rename(Path, TempPath)) {
> + sys::fs::remove(TempPath);
> + return;
> + }
>
> // Remove TempPath in background.
> std::thread([=] { ::remove(TempPath.str().str().c_str()); }).detach();
More information about the llvm-commits
mailing list