Skip to content

Commit 3e5a6f0

Browse files
Support: Add vfs::OutputBackend and OutputFile to virtualize compiler outputs
Add OutputBackend and OutputFile to the `llvm::vfs` namespace for virtualizing compiler outputs. This is intended for use in Clang, The headers are: - llvm/Support/VirtualOutputConfig.h - llvm/Support/VirtualOutputError.h - llvm/Support/VirtualOutputFile.h - llvm/Support/VirtualOutputBackend.h OutputFile is moveable and owns an OutputFileImpl, which is provided by the derived OutputBackend. - OutputFileImpl::keep() and OutputFileImpl::discard() should keep or discard the output. OutputFile guarantees that exactly one of these will be called before destruction. - OutputFile::keep() and OutputFile::discard() wrap OutputFileImpl and catch usage errors such as double-close. - OutputFile::discardOnDestroy() installs an error handler for the destructor to use if the file is still open. The handler will be called if discard() fails. - OutputFile::~OutputFile() calls report_fatal_error() if none of keep(), discard(), or discardOnDestroy() has been called. It still calls OutputFileImpl::discard(). - getOS() returns the wrapped raw_pwrite_stream. For convenience, OutputFile has an implicit conversion to `raw_ostream` and `raw_ostream &operator<<(OutputFile&, T&&)`. OutputBackend can be stored in IntrusiveRefCntPtr. - Most operations are thread-safe. - clone() returns a backend that targets the same destination. All operations are thread-safe when done on different clones. - createFile() takes a path and an OutputConfig (see below) and returns an OutputFile. Backends implement createFileImpl(). OutputConfig has flags to configure the output. Backends may ignore or override flags that aren't relevant or implementable. - The initial flags are: - AtomicWrite: whether the output should appear atomically (e.g., by using a temporary file and renaming it). - CrashCleanup: whether the output should be cleaned up if there's a crash (e.g., with RemoveFileOnSignal). - ImplyCreateDirectories: whether to implicitly create missing directories in the path to the file. - Text: matches sys::fs::OF_Text. - CRLF: matches sys::fs::OF_CRLF. - Append: matches sys::fs::OF_Append and can use with AtomicWrite for atomic append. - Each "Flag" has `setFlag(bool)` and `bool getFlag()` and shortcuts `setFlag()` and `setNoFlag()`. The setters are `constexpr` and return `OutputConfig&` to make it easy to declare a default value for a filed in a class or struct. - Setters and getters for Binary and TextWithCRLF are derived from Text and CRLF. For convenience, sys::fs::OpenFlags can be passed directly to setOpenFlags(). This patch intentionally lacks a number of important features that have been left for follow-ups: - Set a (virtual) current working directory. - Create a directory. - Create a file or directory with a unique name (avoiding collisions with existing filenames). Patch originally by dexonsmith
1 parent 8bd2161 commit 3e5a6f0

18 files changed

+3074
-0
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//===- HashingOutputBackends.h - Hashing output backends --------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_SUPPORT_HASHINGOUTPUTBACKEND_H
10+
#define LLVM_SUPPORT_HASHINGOUTPUTBACKEND_H
11+
12+
#include "llvm/ADT/StringExtras.h"
13+
#include "llvm/ADT/StringMap.h"
14+
#include "llvm/Support/Endian.h"
15+
#include "llvm/Support/HashBuilder.h"
16+
#include "llvm/Support/VirtualOutputBackend.h"
17+
#include "llvm/Support/VirtualOutputConfig.h"
18+
#include "llvm/Support/raw_ostream.h"
19+
20+
namespace llvm::vfs {
21+
22+
/// raw_pwrite_stream that writes to a hasher.
23+
template <typename HasherT>
24+
class HashingStream : public llvm::raw_pwrite_stream {
25+
private:
26+
SmallVector<char> Buffer;
27+
raw_svector_ostream OS;
28+
29+
using HashBuilderT = HashBuilder<HasherT, support::endianness::native>;
30+
HashBuilderT Builder;
31+
32+
void write_impl(const char *Ptr, size_t Size) override {
33+
OS.write(Ptr, Size);
34+
}
35+
36+
void pwrite_impl(const char *Ptr, size_t Size, uint64_t Offset) override {
37+
OS.pwrite(Ptr, Size, Offset);
38+
}
39+
40+
uint64_t current_pos() const override { return OS.str().size(); }
41+
42+
public:
43+
HashingStream() : OS(Buffer) { SetUnbuffered(); }
44+
45+
auto final() {
46+
Builder.update(OS.str());
47+
return Builder.final();
48+
}
49+
};
50+
51+
template <typename HasherT> class HashingOutputFile;
52+
53+
/// An output backend that only generates the hash for outputs.
54+
template <typename HasherT> class HashingOutputBackend : public OutputBackend {
55+
private:
56+
friend class HashingOutputFile<HasherT>;
57+
void addOutputFile(StringRef Path, StringRef Hash) {
58+
OutputHashes[Path] = std::string(Hash);
59+
}
60+
61+
protected:
62+
IntrusiveRefCntPtr<OutputBackend> cloneImpl() const override {
63+
return const_cast<HashingOutputBackend<HasherT> *>(this);
64+
}
65+
66+
Expected<std::unique_ptr<OutputFileImpl>>
67+
createFileImpl(StringRef Path, std::optional<OutputConfig> Config) override {
68+
return std::make_unique<HashingOutputFile<HasherT>>(Path, *this);
69+
}
70+
71+
public:
72+
/// Iterator for all the output file names.
73+
auto outputFiles() const { return OutputHashes.keys(); }
74+
75+
/// Get hash value for the output files in hex representation.
76+
/// Return None if the requested path is not generated.
77+
std::optional<std::string> getHashValueForFile(StringRef Path) {
78+
auto F = OutputHashes.find(Path);
79+
if (F == OutputHashes.end())
80+
return std::nullopt;
81+
return toHex(F->second);
82+
}
83+
84+
private:
85+
StringMap<std::string> OutputHashes;
86+
};
87+
88+
/// HashingOutputFile.
89+
template <typename HasherT>
90+
class HashingOutputFile final : public OutputFileImpl {
91+
public:
92+
Error keep() override {
93+
auto Result = OS.final();
94+
Backend.addOutputFile(OutputPath, toStringRef(Result));
95+
return Error::success();
96+
}
97+
Error discard() override { return Error::success(); }
98+
raw_pwrite_stream &getOS() override { return OS; }
99+
100+
HashingOutputFile(StringRef OutputPath,
101+
HashingOutputBackend<HasherT> &Backend)
102+
: OutputPath(OutputPath.str()), Backend(Backend) {}
103+
104+
private:
105+
const std::string OutputPath;
106+
HashingStream<HasherT> OS;
107+
HashingOutputBackend<HasherT> &Backend;
108+
};
109+
110+
} // namespace llvm::vfs
111+
112+
#endif // LLVM_SUPPORT_HASHINGOUTPUTBACKEND_H
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//===- VirtualOutputBackend.h - Output virtualization -----------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_SUPPORT_VIRTUALOUTPUTBACKEND_H
10+
#define LLVM_SUPPORT_VIRTUALOUTPUTBACKEND_H
11+
12+
#include "llvm/ADT/IntrusiveRefCntPtr.h"
13+
#include "llvm/Support/Error.h"
14+
#include "llvm/Support/VirtualOutputConfig.h"
15+
#include "llvm/Support/VirtualOutputFile.h"
16+
17+
namespace llvm::vfs {
18+
19+
/// Interface for virtualized outputs.
20+
///
21+
/// If virtual functions are added here, also add them to \a
22+
/// ProxyOutputBackend.
23+
class OutputBackend : public RefCountedBase<OutputBackend> {
24+
virtual void anchor();
25+
26+
public:
27+
/// Get a backend that points to the same destination as this one but that
28+
/// has independent settings.
29+
///
30+
/// Not thread-safe, but all operations are thread-safe when performed on
31+
/// separate clones of the same backend.
32+
IntrusiveRefCntPtr<OutputBackend> clone() const { return cloneImpl(); }
33+
34+
/// Create a file. If \p Config is \c std::nullopt, uses the backend's default
35+
/// OutputConfig (may match \a OutputConfig::OutputConfig(), or may
36+
/// have been customized).
37+
///
38+
/// Thread-safe.
39+
Expected<OutputFile>
40+
createFile(const Twine &Path,
41+
std::optional<OutputConfig> Config = std::nullopt);
42+
43+
protected:
44+
/// Must be thread-safe. Virtual function has a different name than \a
45+
/// clone() so that implementations can override the return value.
46+
virtual IntrusiveRefCntPtr<OutputBackend> cloneImpl() const = 0;
47+
48+
/// Create a file for \p Path. Must be thread-safe.
49+
///
50+
/// \pre \p Config is valid or std::nullopt.
51+
virtual Expected<std::unique_ptr<OutputFileImpl>>
52+
createFileImpl(StringRef Path, std::optional<OutputConfig> Config) = 0;
53+
54+
OutputBackend() = default;
55+
56+
public:
57+
virtual ~OutputBackend() = default;
58+
};
59+
60+
} // namespace llvm::vfs
61+
62+
#endif // LLVM_SUPPORT_VIRTUALOUTPUTBACKEND_H
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//===- VirtualOutputBackends.h - Virtual output backends --------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_SUPPORT_VIRTUALOUTPUTBACKENDS_H
10+
#define LLVM_SUPPORT_VIRTUALOUTPUTBACKENDS_H
11+
12+
#include "llvm/ADT/IntrusiveRefCntPtr.h"
13+
#include "llvm/Support/VirtualOutputBackend.h"
14+
#include "llvm/Support/VirtualOutputConfig.h"
15+
16+
namespace llvm::vfs {
17+
18+
/// Create a backend that ignores all output.
19+
IntrusiveRefCntPtr<OutputBackend> makeNullOutputBackend();
20+
21+
/// Make a backend where \a OutputBackend::createFile() forwards to
22+
/// \p UnderlyingBackend when \p Filter is true, and otherwise returns a
23+
/// \a NullOutput.
24+
IntrusiveRefCntPtr<OutputBackend> makeFilteringOutputBackend(
25+
IntrusiveRefCntPtr<OutputBackend> UnderlyingBackend,
26+
std::function<bool(StringRef, std::optional<OutputConfig>)> Filter);
27+
28+
/// Create a backend that forwards \a OutputBackend::createFile() to both \p
29+
/// Backend1 and \p Backend2 and sends content to both places.
30+
IntrusiveRefCntPtr<OutputBackend>
31+
makeMirroringOutputBackend(IntrusiveRefCntPtr<OutputBackend> Backend1,
32+
IntrusiveRefCntPtr<OutputBackend> Backend2);
33+
34+
/// A helper class for proxying another backend, with the default
35+
/// implementation to forward to the underlying backend.
36+
class ProxyOutputBackend : public OutputBackend {
37+
void anchor() override;
38+
39+
protected:
40+
// Require subclass to implement cloneImpl().
41+
//
42+
// IntrusiveRefCntPtr<OutputBackend> cloneImpl() const override;
43+
44+
Expected<std::unique_ptr<OutputFileImpl>>
45+
createFileImpl(StringRef Path, std::optional<OutputConfig> Config) override {
46+
OutputFile File;
47+
if (Error E = UnderlyingBackend->createFile(Path, Config).moveInto(File))
48+
return std::move(E);
49+
return File.takeImpl();
50+
}
51+
52+
OutputBackend &getUnderlyingBackend() const { return *UnderlyingBackend; }
53+
54+
public:
55+
ProxyOutputBackend(IntrusiveRefCntPtr<OutputBackend> UnderlyingBackend)
56+
: UnderlyingBackend(std::move(UnderlyingBackend)) {
57+
assert(this->UnderlyingBackend && "Expected non-null backend");
58+
}
59+
60+
private:
61+
IntrusiveRefCntPtr<OutputBackend> UnderlyingBackend;
62+
};
63+
64+
/// An output backend that creates files on disk, wrapping APIs in sys::fs.
65+
class OnDiskOutputBackend : public OutputBackend {
66+
void anchor() override;
67+
68+
protected:
69+
IntrusiveRefCntPtr<OutputBackend> cloneImpl() const override {
70+
return clone();
71+
}
72+
73+
Expected<std::unique_ptr<OutputFileImpl>>
74+
createFileImpl(StringRef Path, std::optional<OutputConfig> Config) override;
75+
76+
public:
77+
/// Resolve an absolute path.
78+
Error makeAbsolute(SmallVectorImpl<char> &Path) const;
79+
80+
/// On disk output settings.
81+
struct OutputSettings {
82+
/// Register output files to be deleted if a signal is received. Also
83+
/// disabled for outputs with \a OutputConfig::getNoDiscardOnSignal().
84+
bool DisableRemoveOnSignal = false;
85+
86+
/// Disable temporary files. Also disabled for outputs with \a
87+
/// OutputConfig::getNoAtomicWrite().
88+
bool DisableTemporaries = false;
89+
90+
// Default configuration for this backend.
91+
OutputConfig DefaultConfig;
92+
};
93+
94+
IntrusiveRefCntPtr<OnDiskOutputBackend> clone() const {
95+
auto Clone = makeIntrusiveRefCnt<OnDiskOutputBackend>();
96+
Clone->Settings = Settings;
97+
return Clone;
98+
}
99+
100+
OnDiskOutputBackend() = default;
101+
102+
/// Settings for this backend.
103+
///
104+
/// Access is not thread-safe.
105+
OutputSettings Settings;
106+
};
107+
108+
} // namespace llvm::vfs
109+
110+
#endif // LLVM_SUPPORT_VIRTUALOUTPUTBACKENDS_H
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//===- VirtualOutputConfig.def - Virtual output config defs -----*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef HANDLE_OUTPUT_CONFIG_FLAG
10+
#error "Missing macro definition of HANDLE_OUTPUT_CONFIG_FLAG"
11+
#endif
12+
13+
// Define HANDLE_OUTPUT_CONFIG_FLAG before including.
14+
//
15+
// #define HANDLE_OUTPUT_CONFIG_FLAG(NAME, DEFAULT)
16+
17+
HANDLE_OUTPUT_CONFIG_FLAG(Text, false) // OF_Text.
18+
HANDLE_OUTPUT_CONFIG_FLAG(CRLF, false) // OF_CRLF.
19+
HANDLE_OUTPUT_CONFIG_FLAG(Append, false) // OF_Append.
20+
HANDLE_OUTPUT_CONFIG_FLAG(DiscardOnSignal, true) // E.g., RemoveFileOnSignal.
21+
HANDLE_OUTPUT_CONFIG_FLAG(AtomicWrite, true) // E.g., use temporaries.
22+
HANDLE_OUTPUT_CONFIG_FLAG(ImplyCreateDirectories, true)
23+
// Skip atomic write if existing file content is the same
24+
HANDLE_OUTPUT_CONFIG_FLAG(OnlyIfDifferent, false)
25+
26+
#undef HANDLE_OUTPUT_CONFIG_FLAG

0 commit comments

Comments
 (0)