// 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_CHECK_INTERNAL_H_ #define CARBON_COMMON_CHECK_INTERNAL_H_ #include "common/template_string.h" #include "llvm/Support/FormatVariadic.h" namespace Carbon::Internal { // Evaluates a condition in a CHECK. This diagnoses if the condition evaluates // to the constant `true` or `false`. [[clang::always_inline]] constexpr bool // Trailing GNU function attributes are incompatible with trailing return types. // Filed as https://github.com/llvm/llvm-project/issues/118697 // NOLINTNEXTLINE(modernize-use-trailing-return-type) CheckCondition(bool condition) __attribute__((diagnose_if(condition, "CHECK condition is always true; replace with " "static_assert if this is intended", "error"))) __attribute__((diagnose_if(!condition, "CHECK condition is always false; replace with " "CARBON_FATAL if this is intended", "error"))) { return condition; } // Implements the check failure message printing. // // This is out-of-line and will arrange to stop the program, print any debugging // information and the failure message. In `!NDEBUG` mode (`dbg` and // `fastbuild`), check failures can be made non-fatal by a build flag, so this // is not `[[noreturn]]` in that case. // // This API uses `const char*` C string arguments rather than `llvm::StringRef` // because we know that these are available as C strings and passing them that // way lets the code size of calling it be smaller: it only needs to materialize // a single pointer argument for each. The runtime cost of re-computing the size // should be minimal. // // The user can provide an extra format string along with an array of // type-erased format adapters. This will be rendered into the final message. #ifdef NDEBUG [[noreturn]] #endif auto CheckFailImpl( const char* kind, const char* file, int line, const char* condition_str, const char* extra_format, llvm::ArrayRef extra_adapters) -> void; // Allow custom conversion of format values; the default behaviour is to just // pass them through. template auto ConvertFormatValue(T&& t) -> T&& { return std::forward(t); } // Convert enums to larger integers so that byte-sized enums are not confused // with being chars and printed as invalid (or nul-terminating) characters. // Scoped enums are explicitly converted to integers so they can be printed // without the user writing a cast. template requires(std::is_enum_v>) auto ConvertFormatValue(T&& t) -> auto { if constexpr (std::is_signed_v< std::underlying_type_t>>) { return static_cast(t); } else { return static_cast(t); } } // Builds one type-erased format functor per value -- forwarding each value // through the conversion machinery. References to each of these functors are // then collected into an init list that can be accessed with an `ArrayRef`. All // of this is then passed to the out-of-line rendering function `CheckFailImpl`. // // This is templated only on the value types, not on the per-check-site // metadata (file, line, etc., which are passed as ordinary arguments), so the // adapter-building is instantiated once per distinct sequence of value types in // the TU. template #ifdef NDEBUG [[noreturn]] #endif auto CheckFailFormat(const char* kind, const char* file, int line, const char* condition_str, const char* extra_format, Ts&&... values) -> void { CheckFailImpl(kind, file, line, condition_str, extra_format, {llvm::support::detail::FormatFunctor( ConvertFormatValue(std::forward(values)))...}); } // Prints a check failure, including rendering any user-provided message using // a format string. // // The check-site metadata is passed as compile-time template strings to avoid // runtime cost of parameter setup in optimized builds. This function is // instantiated once per check site (its template arguments are unique to the // site), so it is kept trivial: it just lowers those template strings to // ordinary arguments and forwards everything to `CheckFailFormat`, where the // adapter-building is shared across sites with the same value types. template #ifdef NDEBUG [[noreturn]] #endif [[gnu::cold, clang::noinline]] auto CheckFail(Ts&&... values) -> void { CheckFailFormat(Kind.c_str(), File.c_str(), Line, ConditionStr.c_str(), FormatStr.c_str(), std::forward(values)...); } // Type-checks the arguments of a `DCHECK` in optimized builds, where the check // itself is dead code, without instantiating any formatting machinery for them // and without provoking unused-variable warnings. It is only ever named from // dead code, so it is never actually called. template auto IgnoreDeadCheckArgs(Ts&&... /*values*/) -> void {} } // namespace Carbon::Internal // Evaluates the condition of a CHECK as a boolean value. // // This performs a contextual conversion to bool, diagnoses if the condition is // always true or always false, and returns its value. #define CARBON_INTERNAL_CHECK_CONDITION(cond) \ (Carbon::Internal::CheckCondition(true && (cond))) // Implements check messages without any formatted values. // // Passes each of the provided components of the message to the template // parameters of the check failure printing function above, including an empty // string for the format string. Because there are multiple template arguments, // the entire call is wrapped in parentheses. #define CARBON_INTERNAL_CHECK_IMPL(kind, file, line, condition_str) \ (Carbon::Internal::CheckFail()) // Implements check messages with a format string and potentially formatted // values. // // Each of the main components is passed as a template arguments, and then any // formatted values are passed as arguments. Because there are multiple template // arguments, the entire call is wrapped in parentheses. #define CARBON_INTERNAL_CHECK_IMPL_FORMAT(kind, file, line, condition_str, \ format_str, ...) \ (Carbon::Internal::CheckFail( \ __VA_ARGS__)) // Implements the failure of a check. // // Collects all the metadata about the failure to be printed, such as source // location and stringified condition, and passes those, any format string and // formatted arguments to the correct implementation macro above. #define CARBON_INTERNAL_CHECK(condition, ...) \ CARBON_INTERNAL_CHECK_IMPL##__VA_OPT__(_FORMAT)( \ "CHECK", __FILE__, __LINE__, #condition __VA_OPT__(, ) __VA_ARGS__) // Implements the fatal macro. // // Similar to the check failure macro, but tags the message as a fatal one and // leaves the stringified condition empty. #define CARBON_INTERNAL_FATAL(...) \ (CARBON_INTERNAL_CHECK_IMPL##__VA_OPT__(_FORMAT)( \ "FATAL", __FILE__, __LINE__, "" __VA_OPT__(, ) __VA_ARGS__), \ CARBON_INTERNAL_FATAL_NORETURN_SUFFIX()) #ifdef NDEBUG // For `DCHECK` in optimized builds the check is dead code, but we still want to // type-check its arguments so they can't bitrot. We route them through // `IgnoreDeadCheckArgs`, which uses the arguments (avoiding unused-variable // warnings) but builds no format adapters, so the dead check doesn't pull in // the formatting machinery -- in particular not the per-value-type adapters // that the live `CheckFail` path would. The format string is a literal, so it // needs no type-checking and is dropped. #define CARBON_INTERNAL_DEAD_DCHECK(condition, ...) \ CARBON_INTERNAL_DEAD_DCHECK_IMPL##__VA_OPT__(_FORMAT)(__VA_ARGS__) #define CARBON_INTERNAL_DEAD_DCHECK_IMPL() \ Carbon::Internal::IgnoreDeadCheckArgs() #define CARBON_INTERNAL_DEAD_DCHECK_IMPL_FORMAT(format_str, ...) \ Carbon::Internal::IgnoreDeadCheckArgs(__VA_ARGS__) // The `CheckFail` function itself is noreturn in NDEBUG. #define CARBON_INTERNAL_FATAL_NORETURN_SUFFIX() void() #else #define CARBON_INTERNAL_FATAL_NORETURN_SUFFIX() std::abort() #endif #endif // CARBON_COMMON_CHECK_INTERNAL_H_