diff --git a/source/BUILD b/source/BUILD new file mode 100644 index 000000000000..4866c7a85271 --- /dev/null +++ b/source/BUILD @@ -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", + ], +) diff --git a/source/source_buffer.cpp b/source/source_buffer.cpp new file mode 100644 index 000000000000..19a8fc72d004 --- /dev/null +++ b/source/source_buffer.cpp @@ -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 +#include +#include +#include +#include +#include + +#include + +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 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(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((const void*)text_.data()), text_.size()); + (void)result; + assert(result != -1 && "Unmapping text failed!"); + } +} + +} // namespace Carbon diff --git a/source/source_buffer.h b/source/source_buffer.h new file mode 100644 index 000000000000..4ed34d755fe4 --- /dev/null +++ b/source/source_buffer.h @@ -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 +#include + +#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; + + // 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_ diff --git a/source/source_buffer_test.cpp b/source/source_buffer_test.cpp new file mode 100644 index 000000000000..d9d61ae30a96 --- /dev/null +++ b/source/source_buffer_test.cpp @@ -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(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