Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 21 additions & 15 deletions clang/include/clang/Frontend/CompilerInstance.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/BuryPointer.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/VirtualOutputBackend.h"
#include <cassert>
#include <list>
#include <memory>
Expand Down Expand Up @@ -92,6 +93,9 @@ class CompilerInstance : public ModuleLoader {
/// The file manager.
IntrusiveRefCntPtr<FileManager> FileMgr;

/// The output context.
IntrusiveRefCntPtr<llvm::vfs::OutputBackend> TheOutputBackend;

/// The source manager.
IntrusiveRefCntPtr<SourceManager> SourceMgr;

Expand Down Expand Up @@ -164,22 +168,8 @@ class CompilerInstance : public ModuleLoader {
/// The stream for verbose output.
raw_ostream *VerboseOutputStream = &llvm::errs();

/// Holds information about the output file.
///
/// If TempFilename is not empty we must rename it to Filename at the end.
/// TempFilename may be empty and Filename non-empty if creating the temporary
/// failed.
struct OutputFile {
std::string Filename;
std::optional<llvm::sys::fs::TempFile> File;

OutputFile(std::string filename,
std::optional<llvm::sys::fs::TempFile> file)
: Filename(std::move(filename)), File(std::move(file)) {}
};

/// The list of active output files.
std::list<OutputFile> OutputFiles;
std::list<llvm::vfs::OutputFile> OutputFiles;

/// Force an output buffer.
std::unique_ptr<llvm::raw_pwrite_stream> OutputStream;
Expand Down Expand Up @@ -430,6 +420,22 @@ class CompilerInstance : public ModuleLoader {

/// Replace the current file manager and virtual file system.
void setFileManager(FileManager *Value);
/// @name Output Backend.
/// {

/// Set the output backend.
void
setOutputBackend(IntrusiveRefCntPtr<llvm::vfs::OutputBackend> NewOutputs);

/// Create an output manager.
void createOutputBackend();

bool hasOutputBackend() const { return bool(TheOutputBackend); }

llvm::vfs::OutputBackend &getOutputBackend();
llvm::vfs::OutputBackend &getOrCreateOutputBackend();

/// }

/// @}
/// @name Source Manager
Expand Down
174 changes: 56 additions & 118 deletions clang/lib/Frontend/CompilerInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
#include "llvm/Support/Signals.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/Timer.h"
#include "llvm/Support/VirtualOutputBackends.h"
#include "llvm/Support/VirtualOutputError.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/TargetParser/Host.h"
#include <optional>
Expand Down Expand Up @@ -516,6 +518,10 @@ void CompilerInstance::createPreprocessor(TranslationUnitKind TUKind) {
collectVFSEntries(*this, ModuleDepCollector);
}

// Modules need an output manager.
if (!hasOutputBackend())
createOutputBackend();

for (auto &Listener : DependencyCollectors)
Listener->attachToPreprocessor(*PP);

Expand Down Expand Up @@ -759,32 +765,19 @@ void CompilerInstance::createSema(TranslationUnitKind TUKind,
void CompilerInstance::clearOutputFiles(bool EraseFiles) {
// The ASTConsumer can own streams that write to the output files.
assert(!hasASTConsumer() && "ASTConsumer should be reset");
// Ignore errors that occur when trying to discard the temp file.
for (OutputFile &OF : OutputFiles) {
if (EraseFiles) {
if (OF.File)
consumeError(OF.File->discard());
if (!OF.Filename.empty())
llvm::sys::fs::remove(OF.Filename);
continue;
}

if (!OF.File)
continue;

if (OF.File->TmpName.empty()) {
consumeError(OF.File->discard());
continue;
}

llvm::Error E = OF.File->keep(OF.Filename);
if (!E)
continue;

getDiagnostics().Report(diag::err_unable_to_rename_temp)
<< OF.File->TmpName << OF.Filename << std::move(E);

llvm::sys::fs::remove(OF.File->TmpName);
if (!EraseFiles) {
for (auto &O : OutputFiles)
llvm::handleAllErrors(
O.keep(),
[&](const llvm::vfs::TempFileOutputError &E) {
getDiagnostics().Report(diag::err_unable_to_rename_temp)
<< E.getTempPath() << E.getOutputPath()
<< E.convertToErrorCode().message();
},
[&](const llvm::vfs::OutputError &E) {
getDiagnostics().Report(diag::err_fe_unable_to_open_output)
<< E.getOutputPath() << E.convertToErrorCode().message();
});
}
OutputFiles.clear();
if (DeleteBuiltModules) {
Expand Down Expand Up @@ -818,6 +811,29 @@ std::unique_ptr<raw_pwrite_stream> CompilerInstance::createNullOutputFile() {
return std::make_unique<llvm::raw_null_ostream>();
}

void CompilerInstance::setOutputBackend(
IntrusiveRefCntPtr<llvm::vfs::OutputBackend> NewOutputs) {
assert(!TheOutputBackend && "Already has an output manager");
TheOutputBackend = std::move(NewOutputs);
}

void CompilerInstance::createOutputBackend() {
assert(!TheOutputBackend && "Already has an output manager");
TheOutputBackend =
llvm::makeIntrusiveRefCnt<llvm::vfs::OnDiskOutputBackend>();
}

llvm::vfs::OutputBackend &CompilerInstance::getOutputBackend() {
assert(TheOutputBackend);
return *TheOutputBackend;
}

llvm::vfs::OutputBackend &CompilerInstance::getOrCreateOutputBackend() {
if (!hasOutputBackend())
createOutputBackend();
return getOutputBackend();
}

std::unique_ptr<raw_pwrite_stream>
CompilerInstance::createOutputFile(StringRef OutputPath, bool Binary,
bool RemoveFileOnSignal, bool UseTemporary,
Expand Down Expand Up @@ -852,98 +868,20 @@ CompilerInstance::createOutputFileImpl(StringRef OutputPath, bool Binary,
OutputPath = *AbsPath;
}

std::unique_ptr<llvm::raw_fd_ostream> OS;
std::optional<StringRef> OSFile;

if (UseTemporary) {
if (OutputPath == "-")
UseTemporary = false;
else {
llvm::sys::fs::file_status Status;
llvm::sys::fs::status(OutputPath, Status);
if (llvm::sys::fs::exists(Status)) {
// Fail early if we can't write to the final destination.
if (!llvm::sys::fs::can_write(OutputPath))
return llvm::errorCodeToError(
make_error_code(llvm::errc::operation_not_permitted));

// Don't use a temporary if the output is a special file. This handles
// things like '-o /dev/null'
if (!llvm::sys::fs::is_regular_file(Status))
UseTemporary = false;
}
}
}

std::optional<llvm::sys::fs::TempFile> Temp;
if (UseTemporary) {
// 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 = llvm::sys::path::extension(OutputPath);
SmallString<128> TempPath =
StringRef(OutputPath).drop_back(OutputExtension.size());
TempPath += "-%%%%%%%%";
TempPath += OutputExtension;
TempPath += ".tmp";
llvm::sys::fs::OpenFlags BinaryFlags =
Binary ? llvm::sys::fs::OF_None : llvm::sys::fs::OF_Text;
Expected<llvm::sys::fs::TempFile> ExpectedFile =
llvm::sys::fs::TempFile::create(
TempPath, llvm::sys::fs::all_read | llvm::sys::fs::all_write,
BinaryFlags);

llvm::Error E = handleErrors(
ExpectedFile.takeError(), [&](const llvm::ECError &E) -> llvm::Error {
std::error_code EC = E.convertToErrorCode();
if (CreateMissingDirectories &&
EC == llvm::errc::no_such_file_or_directory) {
StringRef Parent = llvm::sys::path::parent_path(OutputPath);
EC = llvm::sys::fs::create_directories(Parent);
if (!EC) {
ExpectedFile = llvm::sys::fs::TempFile::create(
TempPath, llvm::sys::fs::all_read | llvm::sys::fs::all_write,
BinaryFlags);
if (!ExpectedFile)
return llvm::errorCodeToError(
llvm::errc::no_such_file_or_directory);
}
}
return llvm::errorCodeToError(EC);
});

if (E) {
consumeError(std::move(E));
} else {
Temp = std::move(ExpectedFile.get());
OS.reset(new llvm::raw_fd_ostream(Temp->FD, /*shouldClose=*/false));
OSFile = Temp->TmpName;
}
// If we failed to create the temporary, fallback to writing to the file
// directly. This handles the corner case where we cannot write to the
// directory, but can write to the file.
}

if (!OS) {
OSFile = OutputPath;
std::error_code EC;
OS.reset(new llvm::raw_fd_ostream(
*OSFile, EC,
(Binary ? llvm::sys::fs::OF_None : llvm::sys::fs::OF_TextWithCRLF)));
if (EC)
return llvm::errorCodeToError(EC);
}

// Add the output file -- but don't try to remove "-", since this means we are
// using stdin.
OutputFiles.emplace_back(((OutputPath != "-") ? OutputPath : "").str(),
std::move(Temp));

if (!Binary || OS->supportsSeeking())
return std::move(OS);

return std::make_unique<llvm::buffer_unique_ostream>(std::move(OS));
using namespace llvm::vfs;
Expected<OutputFile> O = getOrCreateOutputBackend().createFile(
OutputPath,
OutputConfig()
.setTextWithCRLF(!Binary)
.setDiscardOnSignal(RemoveFileOnSignal)
.setAtomicWrite(UseTemporary)
.setImplyCreateDirectories(UseTemporary && CreateMissingDirectories));
if (!O)
return O.takeError();

O->discardOnDestroy([](llvm::Error E) { consumeError(std::move(E)); });
OutputFiles.push_back(std::move(*O));
return OutputFiles.back().createProxy();
}

// Initialization Utilities
Expand Down
112 changes: 112 additions & 0 deletions llvm/include/llvm/Support/HashingOutputBackend.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//===- HashingOutputBackends.h - Hashing output backends --------*- 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_SUPPORT_HASHINGOUTPUTBACKEND_H
#define LLVM_SUPPORT_HASHINGOUTPUTBACKEND_H

#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/HashBuilder.h"
#include "llvm/Support/VirtualOutputBackend.h"
#include "llvm/Support/VirtualOutputConfig.h"
#include "llvm/Support/raw_ostream.h"

namespace llvm::vfs {

/// raw_pwrite_stream that writes to a hasher.
template <typename HasherT>
class HashingStream : public llvm::raw_pwrite_stream {
private:
SmallVector<char> Buffer;
raw_svector_ostream OS;

using HashBuilderT = HashBuilder<HasherT, support::endianness::native>;
HashBuilderT Builder;

void write_impl(const char *Ptr, size_t Size) override {
OS.write(Ptr, Size);
}

void pwrite_impl(const char *Ptr, size_t Size, uint64_t Offset) override {
OS.pwrite(Ptr, Size, Offset);
}

uint64_t current_pos() const override { return OS.str().size(); }

public:
HashingStream() : OS(Buffer) { SetUnbuffered(); }

auto final() {
Builder.update(OS.str());
return Builder.final();
}
};

template <typename HasherT> class HashingOutputFile;

/// An output backend that only generates the hash for outputs.
template <typename HasherT> class HashingOutputBackend : public OutputBackend {
private:
friend class HashingOutputFile<HasherT>;
void addOutputFile(StringRef Path, StringRef Hash) {
OutputHashes[Path] = std::string(Hash);
}

protected:
IntrusiveRefCntPtr<OutputBackend> cloneImpl() const override {
return const_cast<HashingOutputBackend<HasherT> *>(this);
}

Expected<std::unique_ptr<OutputFileImpl>>
createFileImpl(StringRef Path, std::optional<OutputConfig> Config) override {
return std::make_unique<HashingOutputFile<HasherT>>(Path, *this);
}

public:
/// Iterator for all the output file names.
auto outputFiles() const { return OutputHashes.keys(); }

/// Get hash value for the output files in hex representation.
/// Return None if the requested path is not generated.
std::optional<std::string> getHashValueForFile(StringRef Path) {
auto F = OutputHashes.find(Path);
if (F == OutputHashes.end())
return std::nullopt;
return toHex(F->second);
}

private:
StringMap<std::string> OutputHashes;
};

/// HashingOutputFile.
template <typename HasherT>
class HashingOutputFile final : public OutputFileImpl {
public:
Error keep() override {
auto Result = OS.final();
Backend.addOutputFile(OutputPath, toStringRef(Result));
return Error::success();
}
Error discard() override { return Error::success(); }
raw_pwrite_stream &getOS() override { return OS; }

HashingOutputFile(StringRef OutputPath,
HashingOutputBackend<HasherT> &Backend)
: OutputPath(OutputPath.str()), Backend(Backend) {}

private:
const std::string OutputPath;
HashingStream<HasherT> OS;
HashingOutputBackend<HasherT> &Backend;
};

} // namespace llvm::vfs

#endif // LLVM_SUPPORT_HASHINGOUTPUTBACKEND_H
Loading