Merge the source library from the toolchain repository. (#210)

This library manages buffers of source code, either in-memory or mapped
from the filesystem. At the moment it is a bit simplistic and assumes
`mmap` is available in its implementation details. We should make this
more portable in the future, but this is currently just a direct copy
from the toolchain repository.
This commit is contained in:
Chandler Carruth
2020-12-04 22:39:11 -08:00
committed by GitHub
parent d7143521f6
commit 2ddf0936fd
4 changed files with 271 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
# Exceptions. See /LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
package(default_visibility = ["//visibility:public"])
cc_library(
name = "source_buffer",
srcs = ["source_buffer.cpp"],
hdrs = ["source_buffer.h"],
deps = ["@llvm-project//llvm:Support"],
)
cc_test(
name = "source_buffer_test",
srcs = ["source_buffer_test.cpp"],
deps = [
":source_buffer",
"@llvm-project//llvm:Support",
"@llvm-project//llvm:gmock",
"@llvm-project//llvm:gtest",
"@llvm-project//llvm:gtest_main",
],
)
+88
View File
@@ -0,0 +1,88 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "source/source_buffer.h"
#include "llvm/ADT/ScopeExit.h"
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <system_error>
namespace Carbon {
auto SourceBuffer::CreateFromText(llvm::Twine text, llvm::StringRef filename)
-> SourceBuffer {
return SourceBuffer(filename, text.str());
}
static auto ErrnoToError(int errno_value) -> llvm::Error {
return llvm::errorCodeToError(
std::error_code(errno_value, std::generic_category()));
}
auto SourceBuffer::CreateFromFile(llvm::StringRef filename)
-> llvm::Expected<SourceBuffer> {
SourceBuffer buffer(filename);
errno = 0;
int file_descriptor = open(buffer.filename_.c_str(), O_RDONLY);
if (file_descriptor == -1)
return ErrnoToError(errno);
// Now that we have an open file, we need to close it on any error.
auto closer = llvm::make_scope_exit([file_descriptor] { close(file_descriptor); });
struct stat stat_buffer = {};
errno = 0;
if (fstat(file_descriptor, &stat_buffer) == -1)
return ErrnoToError(errno);
int64_t size = stat_buffer.st_size;
if (size == 0) {
// Nothing to do for an empty file.
return {std::move(buffer)};
}
errno = 0;
void* mapped_text = mmap(nullptr, size, PROT_READ, MAP_PRIVATE | MAP_POPULATE,
file_descriptor, /*offset=*/0);
if (mapped_text == MAP_FAILED)
return ErrnoToError(errno);
errno = 0;
closer.release();
if (close(file_descriptor) == -1) {
// Try to unmap the text. No errer handling as this is just best-effort
// cleanup.
munmap(mapped_text, size);
return ErrnoToError(errno);
}
buffer.text_ = llvm::StringRef(static_cast<const char*>(mapped_text), size);
assert(!buffer.text_.empty() &&
"Must not have an empty text when we have mapped data from a file!");
return {std::move(buffer)};
}
SourceBuffer::~SourceBuffer() {
if (is_string_rep_) {
string_storage_.~decltype(string_storage_)();
return;
}
if (!text_.empty()) {
errno = 0;
int result =
munmap(const_cast<void*>((const void*)text_.data()), text_.size());
(void)result;
assert(result != -1 && "Unmapping text failed!");
}
}
} // namespace Carbon
+98
View File
@@ -0,0 +1,98 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef SOURCE_SOURCEBUFFER_H_
#define SOURCE_SOURCEBUFFER_H_
#include <string>
#include <utility>
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/Error.h"
namespace Carbon {
// A buffer of Carbon source code.
//
// This class holds a buffer of Carbon source code as text and makes it
// available for use in the rest of the Carbon compiler. It owns the memory for
// the underlying source code text and ensures it lives as long as the buffer
// objects.
//
// Every buffer of source code text is notionally loaded from a Carbon source
// file, even if provided directly when constructing the buffer. The name that
// should be used for that Carbon source file is also retained and made
// available.
//
// Because the underlying memory for the source code text may have been read
// from a file, and we may want to use facilities like `mmap` to simply map that
// file into memory, the buffer itself is not copyable to avoid needing to
// define copy semantics for a mapped file. We can relax this restriction with
// some implementation complexity in the future if needed.
class SourceBuffer {
public:
static auto CreateFromText(llvm::Twine text,
llvm::StringRef filename = "/text")
-> SourceBuffer;
static auto CreateFromFile(llvm::StringRef filename)
-> llvm::Expected<SourceBuffer>;
// Use one of the factory functions above to create a source buffer.
SourceBuffer() = delete;
// Cannot copy as there may be non-trivial owned file data, see the class
// comment for details.
SourceBuffer(const SourceBuffer& arg) = delete;
SourceBuffer(SourceBuffer&& arg)
: filename_(std::move(arg.filename_)),
text_(arg.text_),
is_string_rep_(arg.is_string_rep_) {
// The easy case in when we don't need to transfer an allocated string
// representation.
if (!arg.is_string_rep_) {
// Take ownership of a non-string representation by clearing its text.
arg.text_ = llvm::StringRef();
return;
}
// If the argument is using a string rep we need to move that storage over
// and recreate our text `StringRef` to point at our storage.
new (&string_storage_) std::string(std::move(arg.string_storage_));
text_ = string_storage_;
}
~SourceBuffer();
llvm::StringRef Filename() const { return filename_; }
llvm::StringRef Text() const { return text_; }
private:
std::string filename_;
llvm::StringRef text_;
bool is_string_rep_;
// We use a transparent union to avoid constructing the storage.
// FIXME: We should replace this and the boolean with an optional which would
// be much simpler.
union {
std::string string_storage_;
};
explicit SourceBuffer(llvm::StringRef fake_filename, std::string buffer_text)
: filename_(fake_filename.str()), is_string_rep_(true), string_storage_(buffer_text) {
text_ = string_storage_;
}
explicit SourceBuffer(llvm::StringRef filename)
: filename_(filename.str()), text_(), is_string_rep_(false) {}
};
} // namespace Carbon
#endif // SOURCE_SOURCEBUFFER_H_
+59
View File
@@ -0,0 +1,59 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "source/source_buffer.h"
#include "gtest/gtest.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/raw_ostream.h"
namespace Carbon {
namespace {
TEST(SourceBufferTest, StringRep) {
SourceBuffer buffer =
SourceBuffer::CreateFromText(llvm::Twine("Hello") + " World");
EXPECT_EQ("/text", buffer.Filename());
EXPECT_EQ("Hello World", buffer.Text());
// Give a custom filename.
auto buffer2 = SourceBuffer::CreateFromText("Hello World Again!", "/custom/text");
EXPECT_EQ("/custom/text", buffer2.Filename());
EXPECT_EQ("Hello World Again!", buffer2.Text());
}
auto CreateTestFile(llvm::StringRef text) -> std::string {
int fd = -1;
llvm::SmallString<1024> path;
auto error_code = llvm::sys::fs::createTemporaryFile("test_file", ".txt", fd, path);
if (error_code) {
llvm::report_fatal_error(llvm::Twine("Failed to create temporary file: ") +
error_code.message());
}
llvm::raw_fd_ostream out_stream(fd, /*shouldClose=*/true);
out_stream << text;
out_stream.close();
return path.str().str();
}
TEST(SourceBufferTest, FileRep) {
auto test_file_path = CreateTestFile("Hello World");
auto expected_buffer = SourceBuffer::CreateFromFile(test_file_path);
ASSERT_TRUE(static_cast<bool>(expected_buffer))
<< "Error message: " << expected_buffer.takeError();
SourceBuffer& buffer = *expected_buffer;
EXPECT_EQ(test_file_path, buffer.Filename());
EXPECT_EQ("Hello World", buffer.Text());
}
} // namespace
} // namespace Carbon