// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #if (defined(__AUTORTFM) && __AUTORTFM) #include "AutoRTFM.h" #include "ExternAPI.h" #include #include #include #include #include // If 0 then verbose logging is compiled-out. #define AUTORTFM_VERBOSE_ENABLED 0 namespace AutoRTFM { // Reports an internal AutoRTFM issue. The behavior of this function will vary // based on ForTheRuntime::GetInternalAbortAction() and // ForTheRuntime::GetEnsureOnInternalAbort(). // If ProgramCounter is null, then the return address of the call will be used. __attribute__((__format__(__printf__, 4, 5))) UE_AUTORTFM_API void ReportError(const char* File, int Line, void* ProgramCounter, const char* Format, ...); [[noreturn]] inline void InternalUnreachable(); //-V1082 Silence PVS [[noreturn]] false positive warning std::string GetFunctionDescription(void* FunctionPtr); template std::string GetFunctionDescription(TReturnType (*FunctionPtr)(TParameterTypes...)) { return GetFunctionDescription(reinterpret_cast(FunctionPtr)); } // We use a special calling convention for this call to minimize // the cost of calling this function (which is super unlikely to // be called because it is the asserting true codepath, EG. only // fatal process destroying explosions happen here). [[clang::preserve_most]] [[noreturn]] UE_AUTORTFM_FORCENOINLINE inline void DoAssert(void(*Logger)()) //-V1082 Silence PVS [[noreturn]] false positive warning { Logger(); __builtin_unreachable(); } // We use a special calling convention for this call to minimize // the cost of calling this function (which is super unlikely to // be called because it is the expecting true codepath, EG. only // things we don't expect to happen get here). [[clang::preserve_most]] UE_AUTORTFM_FORCENOINLINE inline void DoExpect(void(*Logger)()) { Logger(); } // Logs a message using a printf-style format string and va_list arguments. inline void LogV(const char* File, int Line, void* ProgramCounter, autortfm_log_severity Severity, const char* Format, va_list Args) { if (ProgramCounter == nullptr) { ProgramCounter = __builtin_return_address(0); } GExternAPI.Log(File, Line, ProgramCounter, Severity, Format, Args); } // Logs a message using a printf-style format string and variadic arguments. __attribute__((__format__(__printf__, 5, 6))) inline void Log(const char* File, int Line, void* ProgramCounter, autortfm_log_severity Severity, const char* Format, ...) { if (ProgramCounter == nullptr) { ProgramCounter = __builtin_return_address(0); } va_list Args; va_start(Args, Format); GExternAPI.Log(File, Line, ProgramCounter, Severity, Format, Args); va_end(Args); } // Logs a message with a callstack using a printf-style format string and va_list arguments. inline void LogWithCallstackV(autortfm_log_severity Severity, const char* Format, va_list Args) { GExternAPI.LogWithCallstack(__builtin_return_address(0), Severity, Format, Args); } // Logs a message with a callstack using a printf-style format string and variadic arguments. __attribute__((__format__(__printf__, 2, 3))) inline void LogWithCallstack(autortfm_log_severity Severity, const char* Format, ...) { va_list Args; va_start(Args, Format); GExternAPI.LogWithCallstack(__builtin_return_address(0), Severity, Format, Args); va_end(Args); } // Reports an ensure failure using a printf-style format string and va_list arguments. // If ProgramCounter is null, then the return address of the call will be used. inline void EnsureFailureV(const char* File, int Line, void* ProgramCounter, const char* Condition, const char* Format, va_list Args) { if (ProgramCounter == nullptr) { ProgramCounter = __builtin_return_address(0); } GExternAPI.EnsureFailure(File, Line, ProgramCounter, Condition, Format, Args); } // Reports an ensure failure using a printf-style format string and variadic arguments. // If ProgramCounter is null, then the return address of the call will be used. __attribute__((__format__(__printf__, 5, 6))) inline void EnsureFailure(const char* File, int Line, void* ProgramCounter, const char* Condition, const char* Format, ...) { if (ProgramCounter == nullptr) { ProgramCounter = __builtin_return_address(0); } va_list Args; va_start(Args, Format); GExternAPI.EnsureFailure(File, Line, ProgramCounter, Condition, Format, Args); va_end(Args); } // Rounds Value up to the next multiple of Alignment. // Alignment must be a power of two. template inline constexpr T AlignDown(T Value, T Alignment) { static_assert(std::is_integral_v); return Value & ~(Alignment - 1); } // Rounds Value down to the next multiple of Alignment. // Alignment must be a power of two. template inline constexpr T AlignUp(T Value, T Alignment) { static_assert(std::is_integral_v); return (Value + Alignment - 1) & ~(Alignment - 1); } // Rounds Value up to the next multiple of Multiple. // Unlike AlignDown(), Multiple does not need to be a power of two. template inline constexpr T RoundDown(T Value, T Multiple) { static_assert(std::is_integral_v); return (Value / Multiple) * Multiple; } // Rounds Value down to the next multiple of Multiple. // Unlike AlignUp(), Multiple does not need to be a power of two. template inline constexpr T RoundUp(T Value, T Multiple) { static_assert(std::is_integral_v); return RoundDown(Value + Multiple - 1, Multiple); } // Returns the linear interpolation between Start and End using the coefficient Fraction. template inline constexpr T Lerp(T Start, T End, T Fraction) { return Start + Fraction * (End - Start); } } // namespace AutoRTFM #define AUTORTFM_LIKELY(x) __builtin_expect(!!(x), 1) #define AUTORTFM_UNLIKELY(x) __builtin_expect(!!(x), 0) #define AUTORTFM_REQUIRE_SEMICOLON static_assert(true) #define AUTORTFM_REPORT_ERROR(Format, ...) ::AutoRTFM::ReportError(__FILE__, __LINE__, /* ProgramCounter */ nullptr, Format, ##__VA_ARGS__) #define AUTORTFM_VERBOSE(Format, ...) ::AutoRTFM::Log(__FILE__, __LINE__, /* ProgramCounter */ nullptr, autortfm_log_verbose, Format, ##__VA_ARGS__) #define AUTORTFM_LOG(Format, ...) ::AutoRTFM::Log(__FILE__, __LINE__, /* ProgramCounter */ nullptr, autortfm_log_info, Format, ##__VA_ARGS__) #define AUTORTFM_WARN(Format, ...) ::AutoRTFM::Log(__FILE__, __LINE__, /* ProgramCounter */ nullptr, autortfm_log_warn, Format, ##__VA_ARGS__) #define AUTORTFM_ERROR(Format, ...) ::AutoRTFM::Log(__FILE__, __LINE__, /* ProgramCounter */ nullptr, autortfm_log_error, Format, ##__VA_ARGS__) #define AUTORTFM_FATAL(Format, ...) ::AutoRTFM::Log(__FILE__, __LINE__, /* ProgramCounter */ nullptr, autortfm_log_fatal, Format, ##__VA_ARGS__) #define AUTORTFM_VERBOSE_V(Format, Args) ::AutoRTFM::LogV(__FILE__, __LINE__, /* ProgramCounter */ nullptr, autortfm_log_verbose, Format, Args) #define AUTORTFM_LOG_V(Format, Args) ::AutoRTFM::LogV(__FILE__, __LINE__, /* ProgramCounter */ nullptr, autortfm_log_info, Format, Args) #define AUTORTFM_WARN_V(Format, Args) ::AutoRTFM::LogV(__FILE__, __LINE__, /* ProgramCounter */ nullptr, autortfm_log_warn, Format, Args) #define AUTORTFM_ERROR_V(Format, Args) ::AutoRTFM::LogV(__FILE__, __LINE__, /* ProgramCounter */ nullptr, autortfm_log_error, Format, Args) #define AUTORTFM_FATAL_V(Format, Args) ::AutoRTFM::LogV(__FILE__, __LINE__, /* ProgramCounter */ nullptr, autortfm_log_fatal, Format, Args) #define AUTORTFM_VERBOSE_IF(Condition, Format, ...) do { if (Condition) { AUTORTFM_VERBOSE(Format, ##__VA_ARGS__); } } while(false) #define AUTORTFM_LOG_IF(Condition, Format, ...) do { if (Condition) { AUTORTFM_LOG(Format, ##__VA_ARGS__); } } while(false) #define AUTORTFM_WARN_IF(Condition, Format, ...) do { if (Condition) { AUTORTFM_WARN(Format, ##__VA_ARGS__); } } while(false) #define AUTORTFM_ERROR_IF(Condition, Format, ...) do { if (AUTORTFM_UNLIKELY(Condition)) { AUTORTFM_ERROR(Format, ##__VA_ARGS__); } } while(false) #define AUTORTFM_FATAL_IF(Condition, Format, ...) do { if (AUTORTFM_UNLIKELY(Condition)) { AUTORTFM_FATAL(Format, ##__VA_ARGS__); } } while(false) // If AUTORTFM_VERBOSE_ENABLED is 0, then replace all the verbose logging macros // with no-ops. #if !AUTORTFM_VERBOSE_ENABLED #undef AUTORTFM_VERBOSE #undef AUTORTFM_VERBOSE_V #undef AUTORTFM_VERBOSE_IF #define AUTORTFM_VERBOSE(...) AUTORTFM_REQUIRE_SEMICOLON #define AUTORTFM_VERBOSE_V(...) AUTORTFM_REQUIRE_SEMICOLON #define AUTORTFM_VERBOSE_IF(...) AUTORTFM_REQUIRE_SEMICOLON #endif #define AUTORTFM_ENSURE(Condition) do { \ if (AUTORTFM_UNLIKELY(!(Condition))) { \ [[maybe_unused]] static bool bCalled = [] { \ ::AutoRTFM::EnsureFailure(__FILE__, __LINE__, /* ProgramCounter */ nullptr, #Condition, nullptr); \ return true; \ }(); \ } } while(false) #define AUTORTFM_ENSURE_MSG(Condition, Format, ...) do { \ if (AUTORTFM_UNLIKELY(!(Condition))) { \ [[maybe_unused]] static bool bCalled = [&] { \ ::AutoRTFM::EnsureFailure(__FILE__, __LINE__, /* ProgramCounter */ nullptr, #Condition, Format, ##__VA_ARGS__); \ return true; \ }(); \ } } while(false) #define AUTORTFM_ENSURE_MSG_V(Condition, Format, Args) do { \ if (AUTORTFM_UNLIKELY(!(Condition))) { \ [[maybe_unused]] static bool bCalled = [&] { \ ::AutoRTFM::EnsureFailureV(__FILE__, __LINE__, /* ProgramCounter */ nullptr, #Condition, Format, Args); \ return true; \ }(); \ } } while(false) #define AUTORTFM_DUMP_STACKTRACE(Message) ::AutoRTFM::Log(Message, ##__VA_ARGS__) // This is all a bit funky - but for good reason! We want `AUTORTFM_ASSERT` // to be as close to *zero* cost if the assert wouldn't trigger. // We will always pay the cost of the unlikely branch, but we need // to make the body of taking the assert happen in another function // so as not to affect codegen, register allocation, and stack // reservation. But we also want the `AUTORTFM_ASSERT` to give us accurate // `__FILE__` and `__LINE__` information for the line that it was // triggered on. So what we do is have a lambda at the line that // actually does the assert (but crucially gets the correct file // and line numbers), but then pass this to another function // `DoAssert` which has a special calling convention that makes // the caller as optimal as possible (at the expense of the callee). #define AUTORTFM_ASSERT(exp) \ if (AUTORTFM_UNLIKELY(!(exp))) \ { \ auto Lambda = [] \ { \ AUTORTFM_FATAL("AUTORTFM_ASSERT(%s) failure", #exp); \ }; \ AutoRTFM::DoAssert(Lambda); \ } // Same funkiness as AUTORTFM_ASSERT, except that it doesn't cause a fatal error. // TODO: Maybe upgrade the Info -> Warning? #define AUTORTFM_EXPECT(exp) \ if (AUTORTFM_UNLIKELY(!(exp))) \ { \ auto Lambda = [] \ { \ ::AutoRTFM::LogWithCallstack(autortfm_log_info, "AUTORTFM_EXPECT(%s) failure", #exp); \ }; \ AutoRTFM::DoExpect(Lambda); \ } #if defined(__has_feature) #if __has_feature(address_sanitizer) #define AUTORTFM_NO_ASAN [[clang::no_sanitize("address")]] #endif #endif #if !defined(AUTORTFM_NO_ASAN) #define AUTORTFM_NO_ASAN #endif #if defined(__clang__) #define AUTORTFM_MUST_TAIL [[clang::musttail]] #endif #if !defined(AUTORTFM_MUST_TAIL) #define AUTORTFM_MUST_TAIL #endif #if !defined(CA_SUPPRESS) #define CA_SUPPRESS( WarningNumber ) #endif [[noreturn]] inline void AutoRTFM::InternalUnreachable() //-V1082 Silence PVS [[noreturn]] false positive warning { AUTORTFM_FATAL("Unreachable encountered!"); __builtin_unreachable(); } #endif // (defined(__AUTORTFM) && __AUTORTFM)