summaryrefslogtreecommitdiff
path: root/llvm/lib/Support/VirtualOutputBackends.cpp
diff options
context:
space:
mode:
authorMingming Liu <mingmingl@google.com>2025-09-10 15:25:31 -0700
committerGitHub <noreply@github.com>2025-09-10 15:25:31 -0700
commit1417dafa1db9cb1b2b09438aa9f53ea5ab6e36e2 (patch)
tree57f4b1f313c8cf74eed8819870f39c36ea263c68 /llvm/lib/Support/VirtualOutputBackends.cpp
parent898b813bc8a6d0276bf0f4769f5f2f64b34e632d (diff)
parentb8cefcb601ddaa18482555c4ff363c01a270c2fe (diff)
Merge branch 'main' into users/mingmingl-llvm/samplefdo-profile-formatusers/mingmingl-llvm/samplefdo-profile-format
Diffstat (limited to 'llvm/lib/Support/VirtualOutputBackends.cpp')
-rw-r--r--llvm/lib/Support/VirtualOutputBackends.cpp598
1 files changed, 598 insertions, 0 deletions
diff --git a/llvm/lib/Support/VirtualOutputBackends.cpp b/llvm/lib/Support/VirtualOutputBackends.cpp
new file mode 100644
index 000000000000..d6d7b8715bd4
--- /dev/null
+++ b/llvm/lib/Support/VirtualOutputBackends.cpp
@@ -0,0 +1,598 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// This file implements the VirtualOutputBackend types, including:
+/// * NullOutputBackend: Outputs to NullOutputBackend are discarded.
+/// * FilteringOutputBackend: Filter paths from output.
+/// * MirroringOutputBackend: Mirror the output into two different backend.
+/// * OnDiskOutputBackend: Write output files to disk.
+///
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/VirtualOutputBackends.h"
+#include "llvm/ADT/ScopeExit.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/LockFileManager.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Process.h"
+#include "llvm/Support/Signals.h"
+#include "llvm/Support/VirtualOutputConfig.h"
+#include "llvm/Support/VirtualOutputError.h"
+
+using namespace llvm;
+using namespace llvm::vfs;
+
+void ProxyOutputBackend::anchor() {}
+void OnDiskOutputBackend::anchor() {}
+
+IntrusiveRefCntPtr<OutputBackend> vfs::makeNullOutputBackend() {
+ struct NullOutputBackend : public OutputBackend {
+ IntrusiveRefCntPtr<OutputBackend> cloneImpl() const override {
+ return const_cast<NullOutputBackend *>(this);
+ }
+ Expected<std::unique_ptr<OutputFileImpl>>
+ createFileImpl(StringRef Path, std::optional<OutputConfig>) override {
+ return std::make_unique<NullOutputFileImpl>();
+ }
+ };
+
+ return makeIntrusiveRefCnt<NullOutputBackend>();
+}
+
+IntrusiveRefCntPtr<OutputBackend> vfs::makeFilteringOutputBackend(
+ IntrusiveRefCntPtr<OutputBackend> UnderlyingBackend,
+ std::function<bool(StringRef, std::optional<OutputConfig>)> Filter) {
+ struct FilteringOutputBackend : public ProxyOutputBackend {
+ Expected<std::unique_ptr<OutputFileImpl>>
+ createFileImpl(StringRef Path,
+ std::optional<OutputConfig> Config) override {
+ if (Filter(Path, Config))
+ return ProxyOutputBackend::createFileImpl(Path, Config);
+ return std::make_unique<NullOutputFileImpl>();
+ }
+
+ IntrusiveRefCntPtr<OutputBackend> cloneImpl() const override {
+ return makeIntrusiveRefCnt<FilteringOutputBackend>(
+ getUnderlyingBackend().clone(), Filter);
+ }
+
+ FilteringOutputBackend(
+ IntrusiveRefCntPtr<OutputBackend> UnderlyingBackend,
+ std::function<bool(StringRef, std::optional<OutputConfig>)> Filter)
+ : ProxyOutputBackend(std::move(UnderlyingBackend)),
+ Filter(std::move(Filter)) {
+ assert(this->Filter && "Expected a non-null function");
+ }
+ std::function<bool(StringRef, std::optional<OutputConfig>)> Filter;
+ };
+
+ return makeIntrusiveRefCnt<FilteringOutputBackend>(
+ std::move(UnderlyingBackend), std::move(Filter));
+}
+
+IntrusiveRefCntPtr<OutputBackend>
+vfs::makeMirroringOutputBackend(IntrusiveRefCntPtr<OutputBackend> Backend1,
+ IntrusiveRefCntPtr<OutputBackend> Backend2) {
+ struct ProxyOutputBackend1 : public ProxyOutputBackend {
+ using ProxyOutputBackend::ProxyOutputBackend;
+ };
+ struct ProxyOutputBackend2 : public ProxyOutputBackend {
+ using ProxyOutputBackend::ProxyOutputBackend;
+ };
+ struct MirroringOutput final : public OutputFileImpl, raw_pwrite_stream {
+ Error keep() final {
+ flush();
+ return joinErrors(F1->keep(), F2->keep());
+ }
+ Error discard() final {
+ flush();
+ return joinErrors(F1->discard(), F2->discard());
+ }
+ raw_pwrite_stream &getOS() final { return *this; }
+
+ void write_impl(const char *Ptr, size_t Size) override {
+ F1->getOS().write(Ptr, Size);
+ F2->getOS().write(Ptr, Size);
+ }
+ void pwrite_impl(const char *Ptr, size_t Size, uint64_t Offset) override {
+ this->flush();
+ F1->getOS().pwrite(Ptr, Size, Offset);
+ F2->getOS().pwrite(Ptr, Size, Offset);
+ }
+ uint64_t current_pos() const override { return F1->getOS().tell(); }
+ size_t preferred_buffer_size() const override {
+ return PreferredBufferSize;
+ }
+ void reserveExtraSpace(uint64_t ExtraSize) override {
+ F1->getOS().reserveExtraSpace(ExtraSize);
+ F2->getOS().reserveExtraSpace(ExtraSize);
+ }
+ bool is_displayed() const override {
+ return F1->getOS().is_displayed() && F2->getOS().is_displayed();
+ }
+ bool has_colors() const override {
+ return F1->getOS().has_colors() && F2->getOS().has_colors();
+ }
+ void enable_colors(bool enable) override {
+ raw_pwrite_stream::enable_colors(enable);
+ F1->getOS().enable_colors(enable);
+ F2->getOS().enable_colors(enable);
+ }
+
+ MirroringOutput(std::unique_ptr<OutputFileImpl> F1,
+ std::unique_ptr<OutputFileImpl> F2)
+ : PreferredBufferSize(std::max(F1->getOS().GetBufferSize(),
+ F1->getOS().GetBufferSize())),
+ F1(std::move(F1)), F2(std::move(F2)) {
+ // Don't double buffer.
+ this->F1->getOS().SetUnbuffered();
+ this->F2->getOS().SetUnbuffered();
+ }
+ size_t PreferredBufferSize;
+ std::unique_ptr<OutputFileImpl> F1;
+ std::unique_ptr<OutputFileImpl> F2;
+ };
+ struct MirroringOutputBackend : public ProxyOutputBackend1,
+ public ProxyOutputBackend2 {
+ Expected<std::unique_ptr<OutputFileImpl>>
+ createFileImpl(StringRef Path,
+ std::optional<OutputConfig> Config) override {
+ std::unique_ptr<OutputFileImpl> File1;
+ std::unique_ptr<OutputFileImpl> File2;
+ if (Error E =
+ ProxyOutputBackend1::createFileImpl(Path, Config).moveInto(File1))
+ return std::move(E);
+ if (Error E =
+ ProxyOutputBackend2::createFileImpl(Path, Config).moveInto(File2))
+ return joinErrors(std::move(E), File1->discard());
+
+ // Skip the extra indirection if one of these is a null output.
+ if (isa<NullOutputFileImpl>(*File1)) {
+ consumeError(File1->discard());
+ return std::move(File2);
+ }
+ if (isa<NullOutputFileImpl>(*File2)) {
+ consumeError(File2->discard());
+ return std::move(File1);
+ }
+ return std::make_unique<MirroringOutput>(std::move(File1),
+ std::move(File2));
+ }
+
+ IntrusiveRefCntPtr<OutputBackend> cloneImpl() const override {
+ return IntrusiveRefCntPtr<ProxyOutputBackend1>(
+ makeIntrusiveRefCnt<MirroringOutputBackend>(
+ ProxyOutputBackend1::getUnderlyingBackend().clone(),
+ ProxyOutputBackend2::getUnderlyingBackend().clone()));
+ }
+ void Retain() const { ProxyOutputBackend1::Retain(); }
+ void Release() const { ProxyOutputBackend1::Release(); }
+
+ MirroringOutputBackend(IntrusiveRefCntPtr<OutputBackend> Backend1,
+ IntrusiveRefCntPtr<OutputBackend> Backend2)
+ : ProxyOutputBackend1(std::move(Backend1)),
+ ProxyOutputBackend2(std::move(Backend2)) {}
+ };
+
+ assert(Backend1 && "Expected actual backend");
+ assert(Backend2 && "Expected actual backend");
+ return IntrusiveRefCntPtr<ProxyOutputBackend1>(
+ makeIntrusiveRefCnt<MirroringOutputBackend>(std::move(Backend1),
+ std::move(Backend2)));
+}
+
+static OutputConfig
+applySettings(std::optional<OutputConfig> &&Config,
+ const OnDiskOutputBackend::OutputSettings &Settings) {
+ if (!Config)
+ Config = Settings.DefaultConfig;
+ if (!Settings.UseTemporaries)
+ Config->setNoAtomicWrite();
+ if (!Settings.RemoveOnSignal)
+ Config->setNoDiscardOnSignal();
+ return *Config;
+}
+
+namespace {
+class OnDiskOutputFile final : public OutputFileImpl {
+public:
+ Error keep() override;
+ Error discard() override;
+ raw_pwrite_stream &getOS() override {
+ assert(FileOS && "Expected valid file");
+ if (BufferOS)
+ return *BufferOS;
+ return *FileOS;
+ }
+
+ /// Attempt to open a temporary file for \p OutputPath.
+ ///
+ /// This tries to open a uniquely-named temporary file for \p OutputPath,
+ /// possibly also creating any missing directories if \a
+ /// OnDiskOutputConfig::UseTemporaryCreateMissingDirectories is set in \a
+ /// Config.
+ ///
+ /// \post FD and \a TempPath are initialized if this is successful.
+ Error tryToCreateTemporary(std::optional<int> &FD);
+
+ Error initializeFile(std::optional<int> &FD);
+ Error initializeStream();
+ Error reset();
+
+ OnDiskOutputFile(StringRef OutputPath, std::optional<OutputConfig> Config,
+ const OnDiskOutputBackend::OutputSettings &Settings)
+ : Config(applySettings(std::move(Config), Settings)),
+ OutputPath(OutputPath.str()) {}
+
+ OutputConfig Config;
+ const std::string OutputPath;
+ std::optional<std::string> TempPath;
+ std::optional<raw_fd_ostream> FileOS;
+ std::optional<buffer_ostream> BufferOS;
+};
+} // end namespace
+
+static Error createDirectoriesOnDemand(StringRef OutputPath,
+ OutputConfig Config,
+ llvm::function_ref<Error()> CreateFile) {
+ return handleErrors(CreateFile(), [&](std::unique_ptr<ECError> EC) {
+ if (EC->convertToErrorCode() != std::errc::no_such_file_or_directory ||
+ Config.getNoImplyCreateDirectories())
+ return Error(std::move(EC));
+
+ StringRef ParentPath = sys::path::parent_path(OutputPath);
+ if (std::error_code EC = sys::fs::create_directories(ParentPath))
+ return make_error<OutputError>(ParentPath, EC);
+ return CreateFile();
+ });
+}
+
+Error OnDiskOutputFile::tryToCreateTemporary(std::optional<int> &FD) {
+ // Create a temporary file.
+ // Insert -%%%%%%%% before the extension (if any), and because some tools
+ // (noticeable, clang's own GlobalModuleIndex.cpp) glob for build
+ // artifacts, also append .tmp.
+ StringRef OutputExtension = sys::path::extension(OutputPath);
+ SmallString<128> ModelPath =
+ StringRef(OutputPath).drop_back(OutputExtension.size());
+ ModelPath += "-%%%%%%%%";
+ ModelPath += OutputExtension;
+ ModelPath += ".tmp";
+
+ return createDirectoriesOnDemand(OutputPath, Config, [&]() -> Error {
+ int NewFD;
+ SmallString<128> UniquePath;
+ if (std::error_code EC =
+ sys::fs::createUniqueFile(ModelPath, NewFD, UniquePath))
+ return make_error<TempFileOutputError>(ModelPath, OutputPath, EC);
+
+ if (Config.getDiscardOnSignal())
+ sys::RemoveFileOnSignal(UniquePath);
+
+ TempPath = UniquePath.str().str();
+ FD.emplace(NewFD);
+ return Error::success();
+ });
+}
+
+Error OnDiskOutputFile::initializeFile(std::optional<int> &FD) {
+ assert(OutputPath != "-" && "Unexpected request for FD of stdout");
+
+ // Disable temporary file for other non-regular files, and if we get a status
+ // object, also check if we can write and disable write-through buffers if
+ // appropriate.
+ if (Config.getAtomicWrite()) {
+ sys::fs::file_status Status;
+ sys::fs::status(OutputPath, Status);
+ if (sys::fs::exists(Status)) {
+ if (!sys::fs::is_regular_file(Status))
+ Config.setNoAtomicWrite();
+
+ // Fail now if we can't write to the final destination.
+ if (!sys::fs::can_write(OutputPath))
+ return make_error<OutputError>(
+ OutputPath,
+ std::make_error_code(std::errc::operation_not_permitted));
+ }
+ }
+
+ // If (still) using a temporary file, try to create it (and return success if
+ // that works).
+ if (Config.getAtomicWrite())
+ if (!errorToBool(tryToCreateTemporary(FD)))
+ return Error::success();
+
+ // Not using a temporary file. Open the final output file.
+ return createDirectoriesOnDemand(OutputPath, Config, [&]() -> Error {
+ int NewFD;
+ sys::fs::OpenFlags OF = sys::fs::OF_None;
+ if (Config.getTextWithCRLF())
+ OF |= sys::fs::OF_TextWithCRLF;
+ else if (Config.getText())
+ OF |= sys::fs::OF_Text;
+ if (Config.getAppend())
+ OF |= sys::fs::OF_Append;
+ if (std::error_code EC = sys::fs::openFileForWrite(
+ OutputPath, NewFD, sys::fs::CD_CreateAlways, OF))
+ return convertToOutputError(OutputPath, EC);
+ FD.emplace(NewFD);
+
+ if (Config.getDiscardOnSignal())
+ sys::RemoveFileOnSignal(OutputPath);
+ return Error::success();
+ });
+}
+
+Error OnDiskOutputFile::initializeStream() {
+ // Open the file stream.
+ if (OutputPath == "-") {
+ std::error_code EC;
+ FileOS.emplace(OutputPath, EC);
+ if (EC)
+ return make_error<OutputError>(OutputPath, EC);
+ } else {
+ std::optional<int> FD;
+ if (Error E = initializeFile(FD))
+ return E;
+ FileOS.emplace(*FD, /*shouldClose=*/true);
+ }
+
+ // Buffer the stream if necessary.
+ if (!FileOS->supportsSeeking() && !Config.getText())
+ BufferOS.emplace(*FileOS);
+
+ return Error::success();
+}
+
+namespace {
+class OpenFileRAII {
+ static const int InvalidFd = -1;
+
+public:
+ int Fd = InvalidFd;
+
+ ~OpenFileRAII() {
+ if (Fd != InvalidFd)
+ llvm::sys::Process::SafelyCloseFileDescriptor(Fd);
+ }
+};
+
+enum class FileDifference : uint8_t {
+ /// The source and destination paths refer to the exact same file.
+ IdenticalFile,
+ /// The source and destination paths refer to separate files with identical
+ /// contents.
+ SameContents,
+ /// The source and destination paths refer to separate files with different
+ /// contents.
+ DifferentContents
+};
+} // end anonymous namespace
+
+static Expected<FileDifference>
+areFilesDifferent(const llvm::Twine &Source, const llvm::Twine &Destination) {
+ if (sys::fs::equivalent(Source, Destination))
+ return FileDifference::IdenticalFile;
+
+ OpenFileRAII SourceFile;
+ sys::fs::file_status SourceStatus;
+ // If we can't open the source file, fail.
+ if (std::error_code EC = sys::fs::openFileForRead(Source, SourceFile.Fd))
+ return convertToOutputError(Source, EC);
+
+ // If we can't stat the source file, fail.
+ if (std::error_code EC = sys::fs::status(SourceFile.Fd, SourceStatus))
+ return convertToOutputError(Source, EC);
+
+ OpenFileRAII DestFile;
+ sys::fs::file_status DestStatus;
+ // If we can't open the destination file, report different.
+ if (std::error_code Error =
+ sys::fs::openFileForRead(Destination, DestFile.Fd))
+ return FileDifference::DifferentContents;
+
+ // If we can't open the destination file, report different.
+ if (std::error_code Error = sys::fs::status(DestFile.Fd, DestStatus))
+ return FileDifference::DifferentContents;
+
+ // If the files are different sizes, they must be different.
+ uint64_t Size = SourceStatus.getSize();
+ if (Size != DestStatus.getSize())
+ return FileDifference::DifferentContents;
+
+ // If both files are zero size, they must be the same.
+ if (Size == 0)
+ return FileDifference::SameContents;
+
+ // The two files match in size, so we have to compare the bytes to determine
+ // if they're the same.
+ std::error_code SourceRegionErr;
+ sys::fs::mapped_file_region SourceRegion(
+ sys::fs::convertFDToNativeFile(SourceFile.Fd),
+ sys::fs::mapped_file_region::readonly, Size, 0, SourceRegionErr);
+ if (SourceRegionErr)
+ return convertToOutputError(Source, SourceRegionErr);
+
+ std::error_code DestRegionErr;
+ sys::fs::mapped_file_region DestRegion(
+ sys::fs::convertFDToNativeFile(DestFile.Fd),
+ sys::fs::mapped_file_region::readonly, Size, 0, DestRegionErr);
+
+ if (DestRegionErr)
+ return FileDifference::DifferentContents;
+
+ if (memcmp(SourceRegion.const_data(), DestRegion.const_data(), Size) != 0)
+ return FileDifference::DifferentContents;
+
+ return FileDifference::SameContents;
+}
+
+Error OnDiskOutputFile::reset() {
+ // Destroy the streams to flush them.
+ BufferOS.reset();
+ if (!FileOS)
+ return Error::success();
+
+ // Remember the error in raw_fd_ostream to be reported later.
+ std::error_code EC = FileOS->error();
+ // Clear the error to avoid fatal error when reset.
+ FileOS->clear_error();
+ FileOS.reset();
+ return errorCodeToError(EC);
+}
+
+Error OnDiskOutputFile::keep() {
+ if (auto E = reset())
+ return E;
+
+ // Close the file descriptor and remove crash cleanup before exit.
+ auto RemoveDiscardOnSignal = make_scope_exit([&]() {
+ if (Config.getDiscardOnSignal())
+ sys::DontRemoveFileOnSignal(TempPath ? *TempPath : OutputPath);
+ });
+
+ if (!TempPath)
+ return Error::success();
+
+ // See if we should append instead of move.
+ if (Config.getAppend() && OutputPath != "-") {
+ // Read TempFile for the content to append.
+ auto Content = MemoryBuffer::getFile(*TempPath);
+ if (!Content)
+ return convertToTempFileOutputError(*TempPath, OutputPath,
+ Content.getError());
+ while (1) {
+ // Attempt to lock the output file.
+ // Only one process is allowed to append to this file at a time.
+ llvm::LockFileManager Lock(OutputPath);
+ bool Owned;
+ if (Error Err = Lock.tryLock().moveInto(Owned)) {
+ // If we error acquiring a lock, we cannot ensure appends
+ // to the trace file are atomic - cannot ensure output correctness.
+ Lock.unsafeMaybeUnlock();
+ return convertToOutputError(
+ OutputPath, std::make_error_code(std::errc::no_lock_available));
+ }
+ if (Owned) {
+ // Lock acquired, perform the write and release the lock.
+ std::error_code EC;
+ llvm::raw_fd_ostream Out(OutputPath, EC, llvm::sys::fs::OF_Append);
+ if (EC)
+ return convertToOutputError(OutputPath, EC);
+ Out << (*Content)->getBuffer();
+ Out.close();
+ Lock.unsafeMaybeUnlock();
+ if (Out.has_error())
+ return convertToOutputError(OutputPath, Out.error());
+ // Remove temp file and done.
+ (void)sys::fs::remove(*TempPath);
+ return Error::success();
+ }
+ // Someone else owns the lock on this file, wait.
+ switch (Lock.waitForUnlockFor(std::chrono::seconds(256))) {
+ case WaitForUnlockResult::Success:
+ LLVM_FALLTHROUGH;
+ case WaitForUnlockResult::OwnerDied: {
+ continue; // try again to get the lock.
+ }
+ case WaitForUnlockResult::Timeout: {
+ // We could error on timeout to avoid potentially hanging forever, but
+ // it may be more likely that an interrupted process failed to clear
+ // the lock, causing other waiting processes to time-out. Let's clear
+ // the lock and try again right away. If we do start seeing compiler
+ // hangs in this location, we will need to re-consider.
+ Lock.unsafeMaybeUnlock();
+ continue;
+ }
+ }
+ break;
+ }
+ }
+
+ if (Config.getOnlyIfDifferent()) {
+ auto Result = areFilesDifferent(*TempPath, OutputPath);
+ if (!Result)
+ return Result.takeError();
+ switch (*Result) {
+ case FileDifference::IdenticalFile:
+ // Do nothing for a self-move.
+ return Error::success();
+
+ case FileDifference::SameContents:
+ // Files are identical; remove the source file.
+ (void)sys::fs::remove(*TempPath);
+ return Error::success();
+
+ case FileDifference::DifferentContents:
+ break; // Rename the file.
+ }
+ }
+
+ // Move temporary to the final output path and remove it if that fails.
+ std::error_code RenameEC = sys::fs::rename(*TempPath, OutputPath);
+ if (!RenameEC)
+ return Error::success();
+
+ // FIXME: TempPath should be in the same directory as OutputPath but try to
+ // copy the output to see if makes any difference. If this path is used,
+ // investigate why we need to copy.
+ RenameEC = sys::fs::copy_file(*TempPath, OutputPath);
+ (void)sys::fs::remove(*TempPath);
+
+ if (!RenameEC)
+ return Error::success();
+
+ return make_error<TempFileOutputError>(*TempPath, OutputPath, RenameEC);
+}
+
+Error OnDiskOutputFile::discard() {
+ // Destroy the streams to flush them.
+ if (auto E = reset())
+ return E;
+
+ // Nothing on the filesystem to remove for stdout.
+ if (OutputPath == "-")
+ return Error::success();
+
+ auto discardPath = [&](StringRef Path) {
+ std::error_code EC = sys::fs::remove(Path);
+ sys::DontRemoveFileOnSignal(Path);
+ return EC;
+ };
+
+ // Clean up the file that's in-progress.
+ if (!TempPath)
+ return convertToOutputError(OutputPath, discardPath(OutputPath));
+ return convertToTempFileOutputError(*TempPath, OutputPath,
+ discardPath(*TempPath));
+}
+
+Error OnDiskOutputBackend::makeAbsolute(SmallVectorImpl<char> &Path) const {
+ return convertToOutputError(StringRef(Path.data(), Path.size()),
+ sys::fs::make_absolute(Path));
+}
+
+Expected<std::unique_ptr<OutputFileImpl>>
+OnDiskOutputBackend::createFileImpl(StringRef Path,
+ std::optional<OutputConfig> Config) {
+ SmallString<256> AbsPath;
+ if (Path != "-") {
+ AbsPath = Path;
+ if (Error E = makeAbsolute(AbsPath))
+ return std::move(E);
+ Path = AbsPath;
+ }
+
+ auto File = std::make_unique<OnDiskOutputFile>(Path, Config, Settings);
+ if (Error E = File->initializeStream())
+ return std::move(E);
+
+ return std::move(File);
+}