diff --git a/common/BUILD b/common/BUILD index cb2aa2dfcb83..da2f1f145f9c 100644 --- a/common/BUILD +++ b/common/BUILD @@ -469,6 +469,25 @@ cc_test( ], ) +cc_library( + name = "template_string", + hdrs = ["template_string.h"], + deps = [ + "@llvm-project//llvm:Support", + ], +) + +cc_test( + name = "template_string_test", + size = "small", + srcs = ["template_string_test.cpp"], + deps = [ + ":template_string", + "//testing/base:gtest_main", + "@googletest//:gtest", + ], +) + cc_library( name = "variant_helpers", hdrs = ["variant_helpers.h"], @@ -555,6 +574,7 @@ cc_library( hdrs = ["vlog.h"], deps = [ ":ostream", + ":template_string", "@llvm-project//llvm:Support", ], ) diff --git a/common/template_string.h b/common/template_string.h new file mode 100644 index 000000000000..5edba79f30c0 --- /dev/null +++ b/common/template_string.h @@ -0,0 +1,80 @@ +// 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 CARBON_COMMON_TEMPLATE_STRING_H_ +#define CARBON_COMMON_TEMPLATE_STRING_H_ + +#include "llvm/ADT/StringRef.h" + +namespace Carbon { + +// Represents a compile-time string in a form suitable for use as a non-type +// template argument. +// +// These arguments are required to be a "structural type", and so we copy the +// string contents into a public array of `char`s. For details, see: +// https://en.cppreference.com/w/cpp/language/template_parameters#Non-type_template_parameter +// +// Designed to support implicitly deduced construction from a string literal +// template argument. This type will implicitly convert to an `llvm::StringRef` +// for accessing the string contents, and also provides a dedicated `c_str()` +// method to access the string as a C string. +// +// Example usage: +// ```cpp +// template auto F() -> void { +// llvm::cout() << Str; +// } +// +// auto Example() -> void { +// F<"string contents here">(); +// } +// ``` +template +struct TemplateString { + // Constructs the template string from a string literal. + // + // Intentionally allows implicit conversion from string literals for use as a + // non-type template parameter. + // + // The closest we can get to explicitly accepting a string literal is to + // accept an array of `const char`s, so we additionally use Clang's constexpr + // `enable_if` attribute to require the array to be usable as a C string with + // the expected length. This checks both for null-termination and no embedded + // `0` bytes. + // + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr TemplateString(const char (&str)[N + 1]) __attribute__(( + enable_if(__builtin_strlen(str) == N, + "character array is not null-terminated valid C string"))) { + // Rely on Clang's constexpr `__builtin_memcpy` to minimize compile time + // overhead copying the string contents around. + __builtin_memcpy(storage_, str, N + 1); + } + + // This type is designed to act as a `StringRef` implicitly while having the + // storage necessary to be used as a template parameter. + // + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr operator llvm::StringRef() const { + return llvm::StringRef(storage_, N); + } + + // Accesses the string data directly as a compile-time C string. + constexpr auto c_str() const -> const char* { return storage_; } + + // Note that this must be public for the type to be structural and available + // as a template argument, but this is not part of the public API. + char storage_[N + 1]; +}; + +// Allow deducing `N` when implicitly constructing these so that we can directly +// use a string literal in a template argument. The array needs an extra char +// for the null terminator. +template +TemplateString(const char (&str)[M]) -> TemplateString; + +} // namespace Carbon + +#endif // CARBON_COMMON_TEMPLATE_STRING_H_ diff --git a/common/template_string_test.cpp b/common/template_string_test.cpp new file mode 100644 index 000000000000..542fa00c2311 --- /dev/null +++ b/common/template_string_test.cpp @@ -0,0 +1,76 @@ +// 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 "common/template_string.h" + +#include +#include + +namespace Carbon { +namespace { + +using ::testing::StrEq; + +template +constexpr auto FromTemplate() -> llvm::StringRef { + return S; +} + +template +constexpr auto CStrFromTemplate() -> const char* { + return S.c_str(); +} + +// An overload that will be active when it is passed a valid `TemplateString`. +// Returns a true type to allow detection of a valid `TemplateString` argument. +template +constexpr auto IsValidTemplateString(int /*unused*/) -> std::true_type { + return {}; +} + +// A struct that can be used as a template parameter for any template argument. +struct AnythingAsTemplateArg { + // An implicit constructor that can accept any argument and discards it. + template + // NOLINTNEXTLINE(google-explicit-constructor,bugprone-forwarding-reference-overload) + constexpr AnythingAsTemplateArg(T&& /*unused*/) {} +}; + +// An overload that will be active for any template argument. Returns a false +// type and is used to detect when a template argument cannot correctly match a +// `TemplateString`. +template +constexpr auto IsValidTemplateString(...) -> std::false_type { + return {}; +} + +// Compile time tests with `static_assert` +static_assert(FromTemplate<"test">().size() == 4, + "Not usable in a `constexpr` context."); +static_assert(__builtin_strlen(CStrFromTemplate<"test">()) == 4, + "Not usable in a `constexpr` context."); + +// The string must not contain embedded nulls. +static_assert(IsValidTemplateString<"test">(0)); +static_assert(!IsValidTemplateString<"test\0test">(0)); + +// The string must be null-terminated. +using FourChars = char[4]; +static_assert(IsValidTemplateString(0)); +static_assert(!IsValidTemplateString(0)); + +TEST(TemplateStringTest, Test) { + EXPECT_THAT(FromTemplate<"test">(), StrEq("test")); + EXPECT_THAT(CStrFromTemplate<"test">(), StrEq("test")); + + constexpr char GoodStr[5] = {'t', 'e', 's', 't', '\0'}; + static_assert(IsValidTemplateString(0)); + EXPECT_THAT(FromTemplate(), StrEq("test")); + + constexpr char BadStr[4] = {'t', 'e', 's', 't'}; + static_assert(!IsValidTemplateString(0)); +} + +} // namespace +} // namespace Carbon diff --git a/common/vlog.h b/common/vlog.h index bb9fbe0bc7d8..096f019abb2a 100644 --- a/common/vlog.h +++ b/common/vlog.h @@ -12,11 +12,24 @@ namespace Carbon { // Logs when verbose logging is enabled (vlog_stream_ is non-null). // // For example: -// CARBON_VLOG() << "Verbose message"; -#define CARBON_VLOG() \ - __builtin_expect(vlog_stream_ == nullptr, true) \ - ? (void)0 \ - : CARBON_VLOG_INTERNAL_STREAM(vlog_stream_) +// CARBON_VLOG("Verbose message: {0}", "extra information"); +// +// The first argument must be a string literal format string valid for passing +// to `llvm::formatv`. If it contains any substitutions, those should be passed +// as subsequent arguments. +// +// Also supports a legacy syntax where no arguments are passed and the desired +// logging is streamed into the call: +// CARBON_VLOG() << "Legacy verbose message"; +// +// However, the streaming syntax has higher overhead and can inhibit inlining. +// Code should prefer the format string form, and eventually when all code has +// migrated the streaming interface will be removed. +#define CARBON_VLOG(...) \ + __builtin_expect(vlog_stream_ == nullptr, true) \ + ? (void)0 \ + : CARBON_VLOG_INTERNAL##__VA_OPT__(_CALL)(vlog_stream_ __VA_OPT__(, ) \ + __VA_ARGS__) } // namespace Carbon diff --git a/common/vlog_internal.h b/common/vlog_internal.h index a03586b91b2b..81bb926d692a 100644 --- a/common/vlog_internal.h +++ b/common/vlog_internal.h @@ -6,11 +6,16 @@ #define CARBON_COMMON_VLOG_INTERNAL_H_ #include "common/ostream.h" +#include "common/template_string.h" +#include "llvm/Support/FormatVariadic.h" namespace Carbon::Internal { // Wraps a stream and exiting for fatal errors. Should only be used by check.h // macros. +// +// TODO: Remove this when the last streaming `vlog` is replaced with a function +// call variant. class VLoggingStream { public: // Internal type used in macros to dispatch to the `operator|` overload. @@ -38,17 +43,33 @@ class VLoggingStream { } private: - [[noreturn]] auto Done() -> void; - llvm::raw_ostream* stream_; }; +// Implements verbose logging. +// +// This is designed to minimize the overhead in callers by being a +// forcibly outlined routine that takes a minimal number of parameters. +// +// Internally uses `llvm::formatv` to render the format string with any value +// arguments, and streams the result to the provided stream. +template +[[gnu::cold, clang::noinline, clang::preserve_most]] auto VLogImpl( + llvm::raw_ostream* stream, Ts&&... values) -> void { + *stream << llvm::formatv(FormatStr.c_str(), std::forward(values)...); +} + } // namespace Carbon::Internal -// Raw logging stream. This should be used when building forms of vlog -// macros. -#define CARBON_VLOG_INTERNAL_STREAM(stream) \ +// Raw logging stream. This should be used when building the streaming forms of +// vlog macros. +#define CARBON_VLOG_INTERNAL(stream) \ Carbon::Internal::VLoggingStream::Helper() | \ Carbon::Internal::VLoggingStream(stream) +// Raw logging call. This should be used when building the format-string forms +// of vlog macros. +#define CARBON_VLOG_INTERNAL_CALL(stream, FormatStr, ...) \ + Carbon::Internal::VLogImpl<"" FormatStr>(stream __VA_OPT__(, ) __VA_ARGS__) + #endif // CARBON_COMMON_VLOG_INTERNAL_H_ diff --git a/common/vlog_test.cpp b/common/vlog_test.cpp index 7b8c54b61eca..8ab72a007c3d 100644 --- a/common/vlog_test.cpp +++ b/common/vlog_test.cpp @@ -24,7 +24,9 @@ class VLogger { } } - void VLog() { CARBON_VLOG() << "Test\n"; } + void VLog() { CARBON_VLOG("Test\n"); } + void VLogFormatArgs() { CARBON_VLOG("Test {0} {1} {2}\n", 1, 2, 3); } + void VLogStream() { CARBON_VLOG() << "Test\n"; } auto TakeStr() -> std::string { return buffer_.TakeStr(); } @@ -38,6 +40,10 @@ TEST(VLogTest, Enabled) { VLogger vlog(/*enable=*/true); vlog.VLog(); EXPECT_THAT(vlog.TakeStr(), StrEq("Test\n")); + vlog.VLogFormatArgs(); + EXPECT_THAT(vlog.TakeStr(), StrEq("Test 1 2 3\n")); + vlog.VLogStream(); + EXPECT_THAT(vlog.TakeStr(), StrEq("Test\n")); } TEST(VLogTest, Disabled) {