Skip to content

Commit 07d0225

Browse files
Support: Add vfs::OutputBackend and OutputFile to virtualize compiler outputs (#113363)
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. - OnlyIfDifferent: skip writting the output file if the existing file at the output path is identical to the content to be written. - 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 984a275 commit 07d0225

18 files changed

+3215
-0
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
//===----------------------------------------------------------------------===//
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+
/// \file
10+
/// This file contains the declarations of the HashingOutputBackend class, which
11+
/// is the VirtualOutputBackend that only produces the hashes for the output
12+
/// files. This is useful for checking if the outputs are deterministic without
13+
/// storing output files in memory or on disk.
14+
///
15+
//===----------------------------------------------------------------------===//
16+
17+
#ifndef LLVM_SUPPORT_HASHINGOUTPUTBACKEND_H
18+
#define LLVM_SUPPORT_HASHINGOUTPUTBACKEND_H
19+
20+
#include "llvm/ADT/StringExtras.h"
21+
#include "llvm/ADT/StringMap.h"
22+
#include "llvm/Support/HashBuilder.h"
23+
#include "llvm/Support/VirtualOutputBackend.h"
24+
#include "llvm/Support/VirtualOutputConfig.h"
25+
#include "llvm/Support/raw_ostream.h"
26+
#include <mutex>
27+
28+
namespace llvm::vfs {
29+
30+
/// raw_pwrite_stream that writes to a hasher.
31+
template <typename HasherT>
32+
class HashingStream : public llvm::raw_pwrite_stream {
33+
private:
34+
SmallVector<char> Buffer;
35+
raw_svector_ostream OS;
36+
37+
using HashBuilderT = HashBuilder<HasherT, endianness::native>;
38+
HashBuilderT Builder;
39+
40+
void write_impl(const char *Ptr, size_t Size) override {
41+
OS.write(Ptr, Size);
42+
}
43+
44+
void pwrite_impl(const char *Ptr, size_t Size, uint64_t Offset) override {
45+
OS.pwrite(Ptr, Size, Offset);
46+
}
47+
48+
uint64_t current_pos() const override { return OS.str().size(); }
49+
50+
public:
51+
HashingStream() : OS(Buffer) { SetUnbuffered(); }
52+
53+
auto final() {
54+
Builder.update(OS.str());
55+
return Builder.final();
56+
}
57+
};
58+
59+
template <typename HasherT> class HashingOutputFile;
60+
61+
/// An output backend that only generates the hash for outputs.
62+
template <typename HasherT> class HashingOutputBackend : public OutputBackend {
63+
private:
64+
friend class HashingOutputFile<HasherT>;
65+
void addOutputFile(StringRef Path, StringRef Hash) {
66+
std::lock_guard<std::mutex> Lock(OutputHashLock);
67+
OutputHashes[Path] = Hash.str();
68+
}
69+
70+
protected:
71+
IntrusiveRefCntPtr<OutputBackend> cloneImpl() const override {
72+
return const_cast<HashingOutputBackend<HasherT> *>(this);
73+
}
74+
75+
Expected<std::unique_ptr<OutputFileImpl>>
76+
createFileImpl(StringRef Path, std::optional<OutputConfig> Config) override {
77+
return std::make_unique<HashingOutputFile<HasherT>>(Path, *this);
78+
}
79+
80+
public:
81+
/// Iterator for all the output file names.
82+
///
83+
/// Not thread safe. Should be queried after all outputs are written.
84+
auto outputFiles() const { return OutputHashes.keys(); }
85+
86+
/// Get hash value for the output files in hex representation.
87+
/// Return None if the requested path is not generated.
88+
///
89+
/// Not thread safe. Should be queried after all outputs are written.
90+
std::optional<std::string> getHashValueForFile(StringRef Path) {
91+
auto F = OutputHashes.find(Path);
92+
if (F == OutputHashes.end())
93+
return std::nullopt;
94+
return toHex(F->second);
95+
}
96+
97+
private:
98+
std::mutex OutputHashLock;
99+
StringMap<std::string> OutputHashes;
100+
};
101+
102+
/// HashingOutputFile.
103+
template <typename HasherT>
104+
class HashingOutputFile final : public OutputFileImpl {
105+
public:
106+
Error keep() override {
107+
auto Result = OS.final();
108+
Backend.addOutputFile(OutputPath, toStringRef(Result));
109+
return Error::success();
110+
}
111+
Error discard() override { return Error::success(); }
112+
raw_pwrite_stream &getOS() override { return OS; }
113+
114+
HashingOutputFile(StringRef OutputPath,
115+
HashingOutputBackend<HasherT> &Backend)
116+
: OutputPath(OutputPath.str()), Backend(Backend) {}
117+
118+
private:
119+
const std::string OutputPath;
120+
HashingStream<HasherT> OS;
121+
HashingOutputBackend<HasherT> &Backend;
122+
};
123+
124+
} // namespace llvm::vfs
125+
126+
#endif // LLVM_SUPPORT_HASHINGOUTPUTBACKEND_H
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//===----------------------------------------------------------------------===//
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+
/// \file
10+
/// This file contains the declarations of the VirtualOutputBackend class, which
11+
/// can be used to virtualized output files from LLVM tools.
12+
/// VirtualOutputBackend provides an unified interface to write outputs and a
13+
/// configurable interface for tools to operate on those outputs.
14+
/// VirtualOutputBackend contains basic implementations like writing to disk
15+
/// with different configurations, or advanced logics like output filtering
16+
/// and duplicating.
17+
///
18+
//===----------------------------------------------------------------------===//
19+
20+
#ifndef LLVM_SUPPORT_VIRTUALOUTPUTBACKEND_H
21+
#define LLVM_SUPPORT_VIRTUALOUTPUTBACKEND_H
22+
23+
#include "llvm/ADT/IntrusiveRefCntPtr.h"
24+
#include "llvm/Support/Error.h"
25+
#include "llvm/Support/VirtualOutputConfig.h"
26+
#include "llvm/Support/VirtualOutputFile.h"
27+
28+
namespace llvm::vfs {
29+
30+
/// Interface for virtualized outputs.
31+
///
32+
/// If virtual functions are added here, also add them to \a
33+
/// ProxyOutputBackend.
34+
class OutputBackend : public RefCountedBase<OutputBackend> {
35+
virtual void anchor();
36+
37+
public:
38+
/// Get a backend that points to the same destination as this one but that
39+
/// has independent settings.
40+
///
41+
/// Not thread-safe, but all operations are thread-safe when performed on
42+
/// separate clones of the same backend.
43+
IntrusiveRefCntPtr<OutputBackend> clone() const { return cloneImpl(); }
44+
45+
/// Create a file. If \p Config is \c std::nullopt, uses the backend's default
46+
/// OutputConfig (may match \a OutputConfig::OutputConfig(), or may
47+
/// have been customized).
48+
///
49+
/// Thread-safe.
50+
Expected<OutputFile>
51+
createFile(const Twine &Path,
52+
std::optional<OutputConfig> Config = std::nullopt);
53+
54+
protected:
55+
/// Must be thread-safe. Virtual function has a different name than \a
56+
/// clone() so that implementations can override the return value.
57+
virtual IntrusiveRefCntPtr<OutputBackend> cloneImpl() const = 0;
58+
59+
/// Create a file for \p Path. Must be thread-safe.
60+
///
61+
/// \pre \p Config is valid or std::nullopt.
62+
virtual Expected<std::unique_ptr<OutputFileImpl>>
63+
createFileImpl(StringRef Path, std::optional<OutputConfig> Config) = 0;
64+
65+
OutputBackend() = default;
66+
67+
public:
68+
virtual ~OutputBackend() = default;
69+
};
70+
71+
} // namespace llvm::vfs
72+
73+
#endif // LLVM_SUPPORT_VIRTUALOUTPUTBACKEND_H
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
//===----------------------------------------------------------------------===//
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+
/// \file
10+
/// This file contains the declarations of the concrete VirtualOutputBackend
11+
/// classes, which are the implementation for different output style and
12+
/// functions. This file contains:
13+
/// * NullOutputBackend: discard all outputs written.
14+
/// * OnDiskOutputBackend: write output to disk, with support for common output
15+
/// types, like append, or atomic update.
16+
/// * FilteringOutputBackend: filer some output paths to underlying output
17+
/// backend.
18+
/// * MirrorOutputBackend: mirror output to two output backends.
19+
///
20+
//===----------------------------------------------------------------------===//
21+
22+
#ifndef LLVM_SUPPORT_VIRTUALOUTPUTBACKENDS_H
23+
#define LLVM_SUPPORT_VIRTUALOUTPUTBACKENDS_H
24+
25+
#include "llvm/ADT/IntrusiveRefCntPtr.h"
26+
#include "llvm/Support/VirtualOutputBackend.h"
27+
#include "llvm/Support/VirtualOutputConfig.h"
28+
29+
namespace llvm::vfs {
30+
31+
/// Create a backend that ignores all output.
32+
IntrusiveRefCntPtr<OutputBackend> makeNullOutputBackend();
33+
34+
/// Make a backend where \a OutputBackend::createFile() forwards to
35+
/// \p UnderlyingBackend when \p Filter is true, and otherwise returns a
36+
/// \a NullOutput.
37+
IntrusiveRefCntPtr<OutputBackend> makeFilteringOutputBackend(
38+
IntrusiveRefCntPtr<OutputBackend> UnderlyingBackend,
39+
std::function<bool(StringRef, std::optional<OutputConfig>)> Filter);
40+
41+
/// Create a backend that forwards \a OutputBackend::createFile() to both \p
42+
/// Backend1 and \p Backend2. Writing to such backend will create identical
43+
/// outputs using two different backends.
44+
IntrusiveRefCntPtr<OutputBackend>
45+
makeMirroringOutputBackend(IntrusiveRefCntPtr<OutputBackend> Backend1,
46+
IntrusiveRefCntPtr<OutputBackend> Backend2);
47+
48+
/// A helper class for proxying another backend, with the default
49+
/// implementation to forward to the underlying backend.
50+
class ProxyOutputBackend : public OutputBackend {
51+
void anchor() override;
52+
53+
protected:
54+
// Require subclass to implement cloneImpl().
55+
//
56+
// IntrusiveRefCntPtr<OutputBackend> cloneImpl() const override;
57+
58+
Expected<std::unique_ptr<OutputFileImpl>>
59+
createFileImpl(StringRef Path, std::optional<OutputConfig> Config) override {
60+
OutputFile File;
61+
if (Error E = UnderlyingBackend->createFile(Path, Config).moveInto(File))
62+
return std::move(E);
63+
return File.takeImpl();
64+
}
65+
66+
OutputBackend &getUnderlyingBackend() const { return *UnderlyingBackend; }
67+
68+
public:
69+
ProxyOutputBackend(IntrusiveRefCntPtr<OutputBackend> UnderlyingBackend)
70+
: UnderlyingBackend(std::move(UnderlyingBackend)) {
71+
assert(this->UnderlyingBackend && "Expected non-null backend");
72+
}
73+
74+
private:
75+
IntrusiveRefCntPtr<OutputBackend> UnderlyingBackend;
76+
};
77+
78+
/// An output backend that creates files on disk, wrapping APIs in sys::fs.
79+
class OnDiskOutputBackend : public OutputBackend {
80+
void anchor() override;
81+
82+
protected:
83+
IntrusiveRefCntPtr<OutputBackend> cloneImpl() const override {
84+
return clone();
85+
}
86+
87+
Expected<std::unique_ptr<OutputFileImpl>>
88+
createFileImpl(StringRef Path, std::optional<OutputConfig> Config) override;
89+
90+
public:
91+
/// Resolve an absolute path.
92+
Error makeAbsolute(SmallVectorImpl<char> &Path) const;
93+
94+
/// On disk output settings.
95+
struct OutputSettings {
96+
/// Register output files to be deleted if a signal is received. Also
97+
/// enabled for outputs with \a OutputConfig::getDiscardOnSignal().
98+
bool RemoveOnSignal = true;
99+
100+
/// Use temporary files. Also enabled for outputs with \a
101+
/// OutputConfig::getAtomicWrite().
102+
bool UseTemporaries = true;
103+
104+
// Default configuration for this backend.
105+
OutputConfig DefaultConfig;
106+
};
107+
108+
IntrusiveRefCntPtr<OnDiskOutputBackend> clone() const {
109+
auto Clone = makeIntrusiveRefCnt<OnDiskOutputBackend>();
110+
Clone->Settings = Settings;
111+
return Clone;
112+
}
113+
114+
OnDiskOutputBackend() = default;
115+
116+
/// Settings for this backend.
117+
///
118+
/// Access is not thread-safe.
119+
OutputSettings Settings;
120+
};
121+
122+
} // namespace llvm::vfs
123+
124+
#endif // LLVM_SUPPORT_VIRTUALOUTPUTBACKENDS_H
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//===----------------------------------------------------------------------===//
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+
/// \file This file defines the virtual output configurations.
10+
///
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef HANDLE_OUTPUT_CONFIG_FLAG
14+
#error "Missing macro definition of HANDLE_OUTPUT_CONFIG_FLAG"
15+
#endif
16+
17+
// Define HANDLE_OUTPUT_CONFIG_FLAG before including.
18+
//
19+
// #define HANDLE_OUTPUT_CONFIG_FLAG(NAME, DEFAULT)
20+
21+
HANDLE_OUTPUT_CONFIG_FLAG(Text, false) // OF_Text.
22+
HANDLE_OUTPUT_CONFIG_FLAG(CRLF, false) // OF_CRLF.
23+
HANDLE_OUTPUT_CONFIG_FLAG(Append, false) // OF_Append.
24+
HANDLE_OUTPUT_CONFIG_FLAG(DiscardOnSignal, true) // E.g., RemoveFileOnSignal.
25+
HANDLE_OUTPUT_CONFIG_FLAG(AtomicWrite, true) // E.g., use temporaries.
26+
HANDLE_OUTPUT_CONFIG_FLAG(ImplyCreateDirectories, true)
27+
// Skip atomic write if existing file content is the same
28+
HANDLE_OUTPUT_CONFIG_FLAG(OnlyIfDifferent, false)
29+
30+
#undef HANDLE_OUTPUT_CONFIG_FLAG

0 commit comments

Comments
 (0)