// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "AutoRTFMConstants.h" #include #include #ifdef __cplusplus #include "AutoRTFMTask.h" #include #include #include #include #include #endif // __cplusplus #if (defined(__AUTORTFM) && __AUTORTFM) #define UE_AUTORTFM 1 // Compiler is 'verse-clang' #else #define UE_AUTORTFM 0 #endif #if (defined(__AUTORTFM_ENABLED) && __AUTORTFM_ENABLED) #define UE_AUTORTFM_ENABLED 1 // Compiled with '-fautortfm' #else #define UE_AUTORTFM_ENABLED 0 #endif #if !defined(UE_AUTORTFM_ENABLED_RUNTIME_BY_DEFAULT) #define UE_AUTORTFM_ENABLED_RUNTIME_BY_DEFAULT 1 #endif #if !defined(UE_AUTORTFM_STATIC_VERIFIER) #define UE_AUTORTFM_STATIC_VERIFIER 0 #endif #if UE_AUTORTFM #define UE_AUTORTFM_NOAUTORTFM [[clang::noautortfm]] #define UE_AUTORTFM_ALWAYS_OPEN [[clang::autortfm_always_open]] #define UE_AUTORTFM_ALWAYS_OPEN_NO_MEMORY_VALIDATION [[clang::autortfm_always_open_disable_memory_validation]] #define UE_AUTORTFM_CALLSITE_FORCEINLINE [[clang::always_inline]] #else #define UE_AUTORTFM_NOAUTORTFM #define UE_AUTORTFM_ALWAYS_OPEN #define UE_AUTORTFM_ALWAYS_OPEN_NO_MEMORY_VALIDATION #define UE_AUTORTFM_CALLSITE_FORCEINLINE #endif #if UE_AUTORTFM && UE_AUTORTFM_STATIC_VERIFIER #define UE_AUTORTFM_ENSURE_SAFE [[clang::autortfm_ensure_safe]] #define UE_AUTORTFM_ASSUME_SAFE [[clang::autortfm_assume_safe]] #else #define UE_AUTORTFM_ENSURE_SAFE #define UE_AUTORTFM_ASSUME_SAFE #endif #define UE_AUTORTFM_CONCAT_IMPL(A, B) A ## B #define UE_AUTORTFM_CONCAT(A, B) UE_AUTORTFM_CONCAT_IMPL(A, B) #if defined(__UNREAL__) && !(defined(UE_AUTORTFM_DO_NOT_INCLUDE_PLATFORM_H) && UE_AUTORTFM_DO_NOT_INCLUDE_PLATFORM_H) // Include HAL/Platform.h for DLLIMPORT / DLLEXPORT definitions, which // UBT can use as a definition for AUTORTFM_API. #include #define UE_AUTORTFM_API AUTORTFM_API #else #ifndef UE_AUTORTFM_API #define UE_AUTORTFM_API #endif #endif #if defined(_MSC_VER) #define UE_AUTORTFM_FORCEINLINE __forceinline #define UE_AUTORTFM_FORCENOINLINE __declspec(noinline) #define UE_AUTORTFM_ASSUME(x) __assume(x) #elif defined(__clang__) #define UE_AUTORTFM_FORCEINLINE __attribute__((always_inline)) inline #define UE_AUTORTFM_FORCENOINLINE __attribute__((noinline)) #define UE_AUTORTFM_ASSUME(x) __builtin_assume(x) #else #define UE_AUTORTFM_FORCEINLINE inline #define UE_AUTORTFM_FORCENOINLINE #define UE_AUTORTFM_ASSUME(x) #endif #if (defined(UE_BUILD_DEBUG) && UE_BUILD_DEBUG) || (defined(AUTORTFM_BUILD_DEBUG) && AUTORTFM_BUILD_DEBUG) // Force-inlining can make debugging glitchy. Disable this if we're running a debug build. #undef UE_AUTORTFM_CALLSITE_FORCEINLINE #define UE_AUTORTFM_CALLSITE_FORCEINLINE #undef UE_AUTORTFM_FORCEINLINE #define UE_AUTORTFM_FORCEINLINE inline #endif #ifdef _MSC_VER #define AUTORTFM_DISABLE_UNREACHABLE_CODE_WARNINGS \ __pragma (warning(push)) \ __pragma (warning(disable: 4702)) /* unreachable code */ #define AUTORTFM_RESTORE_UNREACHABLE_CODE_WARNINGS \ __pragma (warning(pop)) #else #define AUTORTFM_DISABLE_UNREACHABLE_CODE_WARNINGS #define AUTORTFM_RESTORE_UNREACHABLE_CODE_WARNINGS #endif #define UE_AUTORTFM_UNUSED(UNUSEDVAR) (void)UNUSEDVAR // It is critical that these functions are both static and forceinline to prevent binary sizes to explode // This is a trick to ensure that there will never be a non-inlined version of these functions that the linker can decide to use #ifndef UE_HEADER_UNITS #define UE_AUTORTFM_CRITICAL_INLINE static UE_AUTORTFM_FORCEINLINE #else #define UE_AUTORTFM_CRITICAL_INLINE UE_AUTORTFM_FORCEINLINE // TODO: This needs to be revisited. we don't want bloated executables when modules are enabled #endif #ifdef __cplusplus extern "C" { #endif // The C API exists for a few reasons: // // - It makes linking easy. AutoRTFM has to deal with a weird kind of linking // where the compiler directly emits calls to functions with a given name. // It's easiest to do that in llvm if the functions have C linkage and C ABI. // - It makes testing easy. Even seemingly simple C++ code introduces pitfalls // for AutoRTFM. So very focused tests work best when written in C. // - It makes compiler optimizations much easier as there is no mangling to // consider when looking for functions in the runtime we can optimize. // // We use snake_case for C API surface area to make it easy to distinguish. // // The C API should not be used directly - it is here purely as an // implementation detail. // This must match AutoRTFM::ETransactionResult. typedef enum { autortfm_aborted_by_request = 0, autortfm_aborted_by_language, autortfm_committed, autortfm_aborted_by_transact_in_on_commit, autortfm_aborted_by_transact_in_on_abort, autortfm_aborted_by_cascade, } autortfm_result; // This must match AutoRTFM::EContextStatus. typedef enum { autortfm_status_idle = 0, autortfm_status_ontrack, autortfm_status_aborted_by_failed_lock_aquisition, autortfm_status_aborted_by_language, autortfm_status_aborted_by_request, autortfm_status_committing, autortfm_status_aborted_by_cascading_abort, autortfm_status_aborted_by_cascading_retry, autortfm_status_in_static_local_initializer, autortfm_status_in_post_abort } autortfm_status; // AutoRTFM logging severity. typedef enum { autortfm_log_verbose = 0, autortfm_log_info, autortfm_log_warn, autortfm_log_error, autortfm_log_fatal, } autortfm_log_severity; // Function pointers used by AutoRTFM for heap allocations, etc. typedef struct { // The function used to allocate memory from the heap. // Must not be null. void* (*Allocate)(size_t Size, size_t Alignment); // The function used to reallocate memory from the heap. // Must not be null. void* (*Reallocate)(void* Pointer, size_t Size, size_t Alignment); // The function used to allocate zeroed memory from the heap. // Must not be null. void* (*AllocateZeroed)(size_t Size, size_t Alignment); // The function used to free memory allocated by Allocate() and AllocateZeroed(). // Must not be null. void (*Free)(void* Pointer); // Function used to log messages using a printf-style format string and va_list arguments. // Strings use UTF-8 encoding. // Must not be null. void (*Log)(const char* File, int Line, void* ProgramCounter, autortfm_log_severity Severity, const char* Format, va_list Args); // Function used to log messages with a callstack using a printf-style format string and va_list arguments. // Strings use UTF-8 encoding. // Must not be null. void (*LogWithCallstack)(void* ProgramCounter, autortfm_log_severity Severity, const char* Format, va_list Args); // Function used to report an ensure failure using a printf-style format string and va_list arguments. // Strings use UTF-8 encoding. // Must not be null. void (*EnsureFailure)(const char* File, int Line, void* ProgramCounter, const char* Condition, const char* Format, va_list Args); // Function used to query whether a log severity is active. // Must not be null. bool (*IsLogActive)(autortfm_log_severity Severity); // Optional callback to be informed when the value returned by // ForTheRuntime::IsAutoRTFMRuntimeEnabled() changes. // Can be null. void (*OnRuntimeEnabledChanged)(); // Optional callback to be informed when the value returned by // ForTheRuntime::GetRetryTransaction() changes. // Can be null. void (*OnRetryTransactionsChanged)(); // Optional callback to be informed when the value returned by // ForTheRuntime::GetMemoryValidationLevel() changes. // Can be null. void (*OnMemoryValidationLevelChanged)(); // Optional callback to be informed when the value returned by // ForTheRuntime::GetMemoryValidationThrottlingEnabled() changes. // Can be null. void (*OnMemoryValidationThrottlingChanged)(); // Optional callback to be informed when the value returned by // ForTheRuntime::GetMemoryValidationStatisticsEnabled() changes. // Can be null. void (*OnMemoryValidationStatisticsChanged)(); } autortfm_extern_api; #if UE_AUTORTFM_ENABLED // Initialize the AutoRTFM library. // Parameters: // ExternAPI - Function pointers used by AutoRTFM for heap allocations, etc. // Must be non-null. UE_AUTORTFM_API void autortfm_initialize(const autortfm_extern_api* ExternAPI); #else UE_AUTORTFM_CRITICAL_INLINE void autortfm_initialize(const autortfm_extern_api* ExternAPI) { UE_AUTORTFM_UNUSED(ExternAPI); } #endif #if UE_AUTORTFM_ENABLED // Note: There is no implementation of this function. // The AutoRTFM compiler will replace all calls to this function with a constant boolean value. UE_AUTORTFM_API bool autortfm_is_closed(void); #else UE_AUTORTFM_CRITICAL_INLINE bool autortfm_is_closed(void) { return false; } #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API bool autortfm_is_transactional(void); #else UE_AUTORTFM_CRITICAL_INLINE bool autortfm_is_transactional(void) { return false; } #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API bool autortfm_is_committing_or_aborting(void); #else UE_AUTORTFM_CRITICAL_INLINE bool autortfm_is_committing_or_aborting(void) { return false; } #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API autortfm_result autortfm_transact(void (*UninstrumentedWork)(void*), void (*InstrumentedWork)(void*), void* Arg); #else UE_AUTORTFM_CRITICAL_INLINE autortfm_result autortfm_transact(void (*UninstrumentedWork)(void*), void (*InstrumentedWork)(void*), void* Arg) { UE_AUTORTFM_UNUSED(InstrumentedWork); UninstrumentedWork(Arg); return autortfm_committed; } #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API autortfm_result autortfm_transact_then_open(void (*UninstrumentedWork)(void*), void (*InstrumentedWork)(void*), void* Arg); #else UE_AUTORTFM_CRITICAL_INLINE autortfm_result autortfm_transact_then_open(void (*UninstrumentedWork)(void*), void (*InstrumentedWork)(void*), void* Arg) { UE_AUTORTFM_UNUSED(InstrumentedWork); UninstrumentedWork(Arg); return autortfm_committed; } #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API void autortfm_commit(void (*UninstrumentedWork)(void*), void (*InstrumentedWork)(void*), void* Arg); #else UE_AUTORTFM_CRITICAL_INLINE void autortfm_commit(void (*UninstrumentedWork)(void*), void (*InstrumentedWork)(void*), void* Arg) { UE_AUTORTFM_UNUSED(InstrumentedWork); UninstrumentedWork(Arg); } #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API void autortfm_abort_transaction(); #else UE_AUTORTFM_CRITICAL_INLINE void autortfm_abort_transaction() {} #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API autortfm_result autortfm_rollback_transaction(); #else UE_AUTORTFM_CRITICAL_INLINE autortfm_result autortfm_rollback_transaction() { return autortfm_aborted_by_request; } #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API autortfm_result autortfm_cascading_rollback_transaction(); #else UE_AUTORTFM_CRITICAL_INLINE autortfm_result autortfm_cascading_rollback_transaction() { return autortfm_aborted_by_cascade; } #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API bool autortfm_start_transaction(); #else UE_AUTORTFM_CRITICAL_INLINE bool autortfm_start_transaction() { return false; } #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API autortfm_result autortfm_commit_transaction(); #else UE_AUTORTFM_CRITICAL_INLINE autortfm_result autortfm_commit_transaction() { return autortfm_aborted_by_language; } #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API void autortfm_clear_transaction_status(); #else UE_AUTORTFM_CRITICAL_INLINE void autortfm_clear_transaction_status() {} #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API autortfm_status autortfm_get_context_status(); #else UE_AUTORTFM_CRITICAL_INLINE autortfm_status autortfm_get_context_status() { return autortfm_status::autortfm_status_idle; } #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API void autortfm_open(void (*work)(void* arg), void* arg, const void* return_address); #else UE_AUTORTFM_CRITICAL_INLINE void autortfm_open(void (*work)(void* arg), void* arg, const void* /* return_address */) { work(arg); } #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API void autortfm_open_explicit_validation(autortfm_memory_validation_level, void (*work)(void* arg), void* arg, const void* return_address); #else UE_AUTORTFM_CRITICAL_INLINE void autortfm_open_explicit_validation(autortfm_memory_validation_level, void (*work)(void* arg), void* arg, const void* /* return_address */) { work(arg); } #endif #if UE_AUTORTFM_ENABLED [[nodiscard]] UE_AUTORTFM_API autortfm_status autortfm_close(void (*UninstrumentedWork)(void*), void (*InstrumentedWork)(void*), void* Arg); #else AUTORTFM_DISABLE_UNREACHABLE_CODE_WARNINGS [[nodiscard]] UE_AUTORTFM_CRITICAL_INLINE autortfm_status autortfm_close(void (*UninstrumentedWork)(void*), void (*InstrumentedWork)(void*), void* Arg) { UE_AUTORTFM_UNUSED(UninstrumentedWork); UE_AUTORTFM_UNUSED(InstrumentedWork); UE_AUTORTFM_UNUSED(Arg); abort(); return autortfm_status_aborted_by_language; } AUTORTFM_RESTORE_UNREACHABLE_CODE_WARNINGS #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API void autortfm_record_open_write(void* Ptr, size_t Size); #else UE_AUTORTFM_CRITICAL_INLINE void autortfm_record_open_write(void* Ptr, size_t Size) { UE_AUTORTFM_UNUSED(Ptr); UE_AUTORTFM_UNUSED(Size); } #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API void autortfm_record_open_write_no_memory_validation(void* Ptr, size_t Size); #else UE_AUTORTFM_CRITICAL_INLINE void autortfm_record_open_write_no_memory_validation(void* Ptr, size_t Size) { UE_AUTORTFM_UNUSED(Ptr); UE_AUTORTFM_UNUSED(Size); } #endif // autortfm_open_to_closed_mapping maps an open function to its closed variant. struct autortfm_open_to_closed_mapping { void* Open; void* Closed; }; // autortfm_open_to_closed_table holds a pointer to a null-terminated list of // autortfm_open_to_closed_mapping, and an intrusive linked-list pointer to the // previous and next registered autortfm_open_to_closed_table. struct autortfm_open_to_closed_table { // Null-terminated open function to closed function mapping table. const autortfm_open_to_closed_mapping* Mappings; // An intrusive linked-list pointer to the previous autortfm_open_to_closed_table. // Used by autortfm_register_open_to_closed_functions(). autortfm_open_to_closed_table* Prev; // An intrusive linked-list pointer to the next autortfm_open_to_closed_table. // Used by autortfm_register_open_to_closed_functions(). autortfm_open_to_closed_table* Next; }; #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API void autortfm_register_open_to_closed_functions(autortfm_open_to_closed_table* Table); UE_AUTORTFM_API void autortfm_unregister_open_to_closed_functions(autortfm_open_to_closed_table* Table); #else UE_AUTORTFM_CRITICAL_INLINE void autortfm_register_open_to_closed_functions(autortfm_open_to_closed_table* Table) { UE_AUTORTFM_UNUSED(Table); } UE_AUTORTFM_CRITICAL_INLINE void autortfm_unregister_open_to_closed_functions(autortfm_open_to_closed_table* Table) { UE_AUTORTFM_UNUSED(Table); } #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API bool autortfm_is_on_current_transaction_stack(void* Ptr); #else UE_AUTORTFM_CRITICAL_INLINE bool autortfm_is_on_current_transaction_stack(void* Ptr) { UE_AUTORTFM_UNUSED(Ptr); return false; } #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API void autortfm_on_commit(void (*work)(void* arg), void* arg); #else UE_AUTORTFM_CRITICAL_INLINE void autortfm_on_commit(void (*work)(void* arg), void* arg) { work(arg); } #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API void autortfm_on_abort(void (*work)(void* arg), void* arg); #else UE_AUTORTFM_CRITICAL_INLINE void autortfm_on_abort(void (*work)(void* arg), void* arg) { UE_AUTORTFM_UNUSED(work); UE_AUTORTFM_UNUSED(arg); } #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API void autortfm_push_on_abort_handler(const void* key, void (*work)(void* arg), void* arg); UE_AUTORTFM_API void autortfm_pop_on_abort_handler(const void* key); #else UE_AUTORTFM_CRITICAL_INLINE void autortfm_push_on_abort_handler(const void* key, void (*work)(void* arg), void* arg) { UE_AUTORTFM_UNUSED(key); UE_AUTORTFM_UNUSED(work); UE_AUTORTFM_UNUSED(arg); } UE_AUTORTFM_CRITICAL_INLINE void autortfm_pop_on_abort_handler(const void* key) { UE_AUTORTFM_UNUSED(key); } #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API void* autortfm_did_allocate(void* ptr, size_t size); #else UE_AUTORTFM_CRITICAL_INLINE void* autortfm_did_allocate(void* ptr, size_t size) { UE_AUTORTFM_UNUSED(size); return ptr; } #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API void autortfm_did_free(void* ptr); #else UE_AUTORTFM_CRITICAL_INLINE void autortfm_did_free(void* ptr) { UE_AUTORTFM_UNUSED(ptr); } #endif // If running with AutoRTFM enabled, then perform an ABI check between the // AutoRTFM compiler and the AutoRTFM runtime, to ensure that memory is being // laid out in an identical manner between the AutoRTFM runtime and the AutoRTFM // compiler pass. Should not be called manually by the user, a call to this will // be injected by the compiler into a global constructor in the AutoRTFM compiled // code. #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API void autortfm_check_abi(void* ptr, size_t size); #else UE_AUTORTFM_CRITICAL_INLINE void autortfm_check_abi(void* ptr, size_t size) { UE_AUTORTFM_UNUSED(ptr); UE_AUTORTFM_UNUSED(size); } #endif #if UE_AUTORTFM_ENABLED UE_AUTORTFM_API void autortfm_unreachable(const char* Message); #else UE_AUTORTFM_CRITICAL_INLINE void autortfm_unreachable(const char* Message) { UE_AUTORTFM_UNUSED(Message); } #endif #ifdef __cplusplus } namespace AutoRTFM { // The transaction result provides information on how a transaction completed. This is either Committed, // or one of the various AbortedBy* variants to show why an abort occurred. enum class ETransactionResult { // The transaction aborted because of an explicit call to AbortTransaction or RollbackTransaction. AbortedByRequest = autortfm_aborted_by_request, // The transaction aborted because of unhandled constructs in the code (atomics, unhandled function calls, etc). AbortedByLanguage = autortfm_aborted_by_language, // The transaction committed successfully. For a nested transaction this does not mean that the transaction effects // cannot be undone later if the parent transaction is aborted for any reason. Committed = autortfm_committed, // The transaction aborted because in a call to OnCommit, a new transaction nest was attempted which is not allowed. AbortedByTransactInOnCommit = autortfm_aborted_by_transact_in_on_commit, // The transaction aborted because in a call to OnAbort, a new transaction nest was attempted which is not allowed. AbortedByTransactInOnAbort = autortfm_aborted_by_transact_in_on_abort, // The transaction aborted because of an explicit call to CascadingAbortTransaction. AbortedByCascade = autortfm_aborted_by_cascade }; // The context status shows what state the AutoRTFM context is currently in. enum class EContextStatus : uint8_t { // An Idle status means we are not in transactional code. Idle = autortfm_status_idle, // An OnTrack status means we are in transactional code. OnTrack = autortfm_status_ontrack, // Reserved for a full STM future. AbortedByFailedLockAcquisition = autortfm_status_aborted_by_failed_lock_aquisition, // An AbortedByLanguage status means that we found some unhandled constructs in the code // (atomics, unhandled function calls, etc) and are currently aborting because of it. AbortedByLanguage = autortfm_status_aborted_by_language, // An AbortedByRequest status means that a call to AbortTransaction or RollbackTransaction occurred, // and we are currently aborting because of it. AbortedByRequest = autortfm_status_aborted_by_request, // A Committing status means we are currently attempting to commit a transaction. Committing = autortfm_status_committing, // An AbortedByCascadingAbort status means that a call to CascadingAbortTransaction or // CascadingRollbackTransaction occurred, and we are currently aborting because of it. AbortedByCascadingAbort = autortfm_status_aborted_by_cascading_abort, // An AbortedByCascadingRetry status means that a call to CascadingRetryTransaction occurred, // and we are currently aborting because of it. AbortedByCascadingRetry = autortfm_status_aborted_by_cascading_retry, // Means we are in a static local initializer which always run in the open. // `IsTransactional()` will return `false` when in this state. InStaticLocalInitializer = autortfm_status_in_static_local_initializer, // An InPostAbort status means that a transaction has just been aborted, but we have not yet // thrown back to the caller or retried the transaction. We are only in this state when // executing the post-abort callback. InPostAbort = autortfm_status_in_post_abort, }; // An enumerator of transactional memory validation levels. // Memory validation is used to detect modification by open-code to memory that was written by a // transaction. In this situation, aborting the transaction can corrupt memory as the undo will // overwrite the writes made in the open-code. enum class EMemoryValidationLevel : uint8_t { // The default memory validation level. Default = autortfm_memory_validation_level_default, // Disable memory validation. Disabled = autortfm_memory_validation_level_disabled, // Memory validation enabled as warnings. Warn = autortfm_memory_validation_level_warn, // Memory validation enabled as errors. Error = autortfm_memory_validation_level_error, }; #if UE_AUTORTFM namespace ForTheRuntime { using FExternAPI = autortfm_extern_api; UE_AUTORTFM_API void Initialize(const FExternAPI& ExternAPI); UE_AUTORTFM_API void CascadingAbortTransactionInternal(TTask&& RunAfterAbort); UE_AUTORTFM_API void CascadingRetryTransactionInternal(TTask&& RunAfterAbortBeforeRetryWork); UE_AUTORTFM_API void OnCommitInternal(TTask&& Work); UE_AUTORTFM_API void OnAbortInternal(TTask&& Work); UE_AUTORTFM_API void PushOnCommitHandlerInternal(const void* Key, TTask&& Work); UE_AUTORTFM_API void PopOnCommitHandlerInternal(const void* Key); UE_AUTORTFM_API void PopAllOnCommitHandlersInternal(const void* Key); UE_AUTORTFM_API void PushOnAbortHandlerInternal(const void* Key, TTask&& Work); UE_AUTORTFM_API void PopOnAbortHandlerInternal(const void* Key); UE_AUTORTFM_API void PopAllOnAbortHandlersInternal(const void* Key); } // namespace ForTheRuntime #endif template void AutoRTFMFunctorInvoker(void* Arg) { (*static_cast(Arg))(); } #if UE_AUTORTFM extern "C" UE_AUTORTFM_API void* autortfm_lookup_function(void* OriginalFunction, const char* Where); template auto AutoRTFMLookupInstrumentedFunctorInvoker(const FunctorType& Functor) -> void(*)(void*) { // keep this as a single expression to help ensure that even Debug builds optimize this. // if we put intermediate results in local variables then the compiler emits loads // and stores to the stack which confuse our custom pass that tries to strip away // the actual call to autortfm_lookup_function void (*Result)(void*) = reinterpret_cast(autortfm_lookup_function(reinterpret_cast(&AutoRTFMFunctorInvoker), "AutoRTFMLookupInstrumentedFunctorInvoker")); return Result; } #else template auto AutoRTFMLookupInstrumentedFunctorInvoker(const FunctorType& Functor) -> void(*)(void*) { return nullptr; } #endif // Tells if we are currently running in a transaction. This will return true in an open nest // (see `Open`). This function is handled specially in the compiler and will be constant folded // as true in closed code, or preserved as a function call in open code. UE_AUTORTFM_CRITICAL_INLINE bool IsTransactional() { return autortfm_is_transactional(); } // Tells if we are currently running in the closed nest of a transaction. By default, // transactional code is in a closed nest; the only way to be in an open nest is to request it // via `Open`. This function is handled specially in the compiler and will be constant folded // as true in closed code, and false in open code. UE_AUTORTFM_CRITICAL_INLINE bool IsClosed() { return autortfm_is_closed(); } // Tells us if we are currently committing or aborting a transaction. This will return true // in an on-abort or on-commit. UE_AUTORTFM_CRITICAL_INLINE bool IsCommittingOrAborting() { return autortfm_is_committing_or_aborting(); } // Returns true if the passed-in pointer is on the stack of the currently-executing transaction. // This is occasionally necessary when writing OnAbort handlers for objects on the stack, since // we don't want to scribble on stack memory that might have been reused. UE_AUTORTFM_CRITICAL_INLINE bool IsOnCurrentTransactionStack(void* Ptr) { return autortfm_is_on_current_transaction_stack(Ptr); } // Run the functor in a transaction. Memory writes and other side effects get instrumented // and will be reversed if the transaction aborts. // // If this begins a nested transaction, the instrumented effects are logged onto the root // transaction, so the effects can be reversed later if the root transaction aborts, even // if this nested transaction succeeds. // // If AutoRTFM is disabled, the code will be ran non-transactionally. template UE_AUTORTFM_CRITICAL_INLINE ETransactionResult Transact(const FunctorType& Functor) { ETransactionResult Result = static_cast( autortfm_transact( &AutoRTFMFunctorInvoker, AutoRTFMLookupInstrumentedFunctorInvoker(Functor), const_cast(static_cast(&Functor)))); return Result; } // This is just like calling Transact([&] { Open([&] { Functor(); }); }); // The reason we expose it is that it allows the caller's module to not // be compiled with the AutoRTFM instrumentation of functions if the only // thing that's being invoked is a function in the open. template UE_AUTORTFM_CRITICAL_INLINE ETransactionResult TransactThenOpen(const FunctorType& Functor) { ETransactionResult Result = static_cast( autortfm_transact_then_open( &AutoRTFMFunctorInvoker, AutoRTFMLookupInstrumentedFunctorInvoker(Functor), const_cast(static_cast(&Functor)))); return Result; } // Run the callback in a transaction like Transact, but abort program // execution if the result is anything other than autortfm_committed. // Useful for testing. template UE_AUTORTFM_CRITICAL_INLINE void Commit(const FunctorType& Functor) { autortfm_commit( &AutoRTFMFunctorInvoker, AutoRTFMLookupInstrumentedFunctorInvoker(Functor), const_cast(static_cast(&Functor))); } // Ends a transaction while in the closed, discarding all effects. // Sends control to the end of the transaction immediately. UE_AUTORTFM_CRITICAL_INLINE void AbortTransaction() { autortfm_abort_transaction(); } // End a transaction nest in the closed, discarding all effects. This cascades, // meaning an abort of a nested transaction will cause all transactions in the // nest to abort. Once the transaction has aborted, `RunAfterAbort` will // be called. Finally, control will be returned to the end of the outermost // Transact. #if UE_AUTORTFM template UE_AUTORTFM_CRITICAL_INLINE void CascadingAbortTransaction(FunctorType&& RunAfterAbort) { ForTheRuntime::CascadingAbortTransactionInternal(std::forward(RunAfterAbort)); } UE_AUTORTFM_CRITICAL_INLINE void CascadingAbortTransaction() { ForTheRuntime::CascadingAbortTransactionInternal({}); } #else template UE_AUTORTFM_CRITICAL_INLINE void CascadingAbortTransaction(FunctorType&&) {} UE_AUTORTFM_CRITICAL_INLINE void CascadingAbortTransaction() {} #endif namespace Detail { template struct THasAssignFromOpenToClosedMethod : std::false_type {}; template struct THasAssignFromOpenToClosedMethod(), std::declval()))>> : std::true_type {}; } // Evaluates to true if the type T has a static method with the signature: // static void AutoRTFMAssignFromOpenToClosed(T& Closed, U Open) // Where `U` is `T`, `const T&` or `T&&`. Supports both copy assignment and move assignment. template static constexpr bool HasAssignFromOpenToClosedMethod = Detail::THasAssignFromOpenToClosedMethod::value; // Template class used to declare a method for safely copying or moving an // object of type T from open to closed transactions. // Specializations of TAssignFromOpenToClosed must have at least one static // method with the signature: // static void Assign(T& Closed, U Open); // Where `U` is `T`, `const T&` or `T&&`. Supports both copy assignment and move assignment. // // TAssignFromOpenToClosed has pre-declared specializations for basic primitive // types, and can be extended with user-declared template specializations. // // TAssignFromOpenToClosed has a pre-declared specialization that detects and // calls a static method on T with the signature: // static void AutoRTFMAssignFromOpenToClosed(T& Closed, U Open) // Where `U` is `T`, `const T&` or `T&&`. Supports both copy assignment and move assignment. template struct TAssignFromOpenToClosed; namespace Detail { template struct THasAssignFromOpenToClosedTrait : std::false_type {}; template struct THasAssignFromOpenToClosedTrait::Assign(std::declval(), std::declval()))>> : std::true_type {}; } // Evaluates to true if the type T supports assigning from open to closed transactions. template static constexpr bool HasAssignFromOpenToClosedTrait = Detail::THasAssignFromOpenToClosedTrait::value; // Specialization of TAssignFromOpenToClosed for fundamental types. template struct TAssignFromOpenToClosed>> { UE_AUTORTFM_FORCEINLINE static void Assign(T& Closed, T Open) { Closed = Open; } }; // Specialization of TAssignFromOpenToClosed for raw pointer types. template struct TAssignFromOpenToClosed { UE_AUTORTFM_FORCEINLINE static void Assign(T*& Closed, T* Open) { Closed = Open; } }; // Specialization of TAssignFromOpenToClosed for std::tuple. template struct TAssignFromOpenToClosed, std::enable_if_t<(HasAssignFromOpenToClosedTrait && ...)>> { template UE_AUTORTFM_FORCEINLINE static void AssignElements(std::tuple& Closed, SRC&& Open) { if constexpr(I < sizeof...(TYPES)) { using E = std::tuple_element_t>; TAssignFromOpenToClosed::Assign(std::get(Closed), std::get(std::forward(Open))); AssignElements(Closed, std::forward(Open)); } } template UE_AUTORTFM_FORCEINLINE static void Assign(std::tuple& Closed, SRC&& Open) { AssignElements(Closed, std::forward(Open)); } }; // Specialization of TAssignFromOpenToClosed for types that have a static method // with the signature: // static void AutoRTFMAssignFromOpenToClosed(T& Closed, U Open) // Where `U` is `T`, `const T&` or `T&&`. Supports both copy assignment and move assignment. template struct TAssignFromOpenToClosed>> { template UE_AUTORTFM_FORCEINLINE static void Assign(T& Closed, OPEN&& Open) { Closed.AutoRTFMAssignFromOpenToClosed(Closed, std::forward(Open)); } }; // Specialization of TAssignFromOpenToClosed for `void` (used to make IsSafeToReturnFromOpen work). template<> struct TAssignFromOpenToClosed; // Evaluates to true if the type T is safe to return from Open(). template static constexpr bool IsSafeToReturnFromOpen = HasAssignFromOpenToClosedTrait || std::is_same_v; // Executes the given code non-transactionally regardless of whether we are in // a transaction or not. Returns the value returned by Functor. // ReturnType must be void or a type that can be safely copied from the open to a closed transaction. // TAssignFromOpenToClosed must have a specialization for the type that is being returned. template < EMemoryValidationLevel VALIDATION_LEVEL = EMemoryValidationLevel::Default, typename FunctorType = void, typename ReturnType = decltype(std::declval()()) > UE_AUTORTFM_CRITICAL_INLINE ReturnType Open(const FunctorType& Functor) { static_assert(IsSafeToReturnFromOpen, "function return type is not safe to return from Open()"); #if UE_AUTORTFM if (!autortfm_is_closed()) { return Functor(); } if constexpr (IsSafeToReturnFromOpen) { if constexpr (std::is_same_v) { struct FCallHelper { UE_AUTORTFM_NOAUTORTFM static void Call(void* Arg) { const FunctorType& Fn = *reinterpret_cast(Arg); UE_AUTORTFM_CALLSITE_FORCEINLINE Fn(); } }; if constexpr (VALIDATION_LEVEL == EMemoryValidationLevel::Default) { autortfm_open(&FCallHelper::Call, const_cast(reinterpret_cast(&Functor)), __builtin_return_address(0)); } else { autortfm_open_explicit_validation( static_cast(VALIDATION_LEVEL), &FCallHelper::Call, const_cast(static_cast(&Functor)), __builtin_return_address(0)); } } else { struct FCallHelper { UE_AUTORTFM_NOAUTORTFM static void Call(void* Arg) { FCallHelper& Self = *reinterpret_cast(Arg); UE_AUTORTFM_CALLSITE_FORCEINLINE TAssignFromOpenToClosed::Assign(Self.ReturnValue, std::move(Self.Functor())); } const FunctorType& Functor; ReturnType ReturnValue{}; }; FCallHelper Helper{Functor}; if constexpr (VALIDATION_LEVEL == EMemoryValidationLevel::Default) { autortfm_open(&FCallHelper::Call, reinterpret_cast(&Helper), __builtin_return_address(0)); } else { autortfm_open_explicit_validation( static_cast(VALIDATION_LEVEL), &FCallHelper::Call, reinterpret_cast(&Helper), __builtin_return_address(0)); } return Helper.ReturnValue; } } #else // UE_AUTORTFM return Functor(); #endif // UE_AUTORTFM } // Always executes the given code transactionally when called from a transaction nest // (whether we are in open or closed code). // // Will crash if called outside of a transaction nest. template [[nodiscard]] UE_AUTORTFM_CRITICAL_INLINE EContextStatus Close(const FunctorType& Functor) { return static_cast( autortfm_close( &AutoRTFMFunctorInvoker, AutoRTFMLookupInstrumentedFunctorInvoker(Functor), const_cast(static_cast(&Functor)))); } // Force a transaction nest to be retried. Once the transaction has aborted, // it will call `RunAfterAbortBeforeRetryWork`, before retrying the // transaction. This is an expensive operation and should thus be used with // extreme caution. If this is called outside of a transaction then the // `RunAfterAbortBeforeRetryWork` callback is never executed. #if UE_AUTORTFM template UE_AUTORTFM_CRITICAL_INLINE void CascadingRetryTransaction(FunctorType&& RunAfterAbortBeforeRetryWork) { if (autortfm_is_closed()) { ForTheRuntime::CascadingRetryTransactionInternal(std::forward(RunAfterAbortBeforeRetryWork)); } } #else template UE_AUTORTFM_CRITICAL_INLINE void CascadingRetryTransaction(FunctorType&&) { } #endif #if UE_AUTORTFM // Have some work happen when this transaction commits. For nested transactions, // this just adds the work to the work deferred until the outer nest's commit. // If this is called outside a transaction or from an open nest then the work // happens immediately. template UE_AUTORTFM_CRITICAL_INLINE void OnCommit(FunctorType&& Work) { if (autortfm_is_closed()) { ForTheRuntime::OnCommitInternal(std::forward(Work)); } else { UE_AUTORTFM_CALLSITE_FORCEINLINE Work(); } } #else // Have some work happen when this transaction commits. For nested transactions, // this just adds the work to the work deferred until the outer nest's commit. // If this is called outside a transaction or from an open nest then the work // happens immediately. template UE_AUTORTFM_CRITICAL_INLINE void OnCommit(FunctorType&& Work) { Work(); } #endif #if UE_AUTORTFM // Have some work happen when this transaction aborts. If this is called // outside a transaction or from an open nest then the work is ignored. template UE_AUTORTFM_CRITICAL_INLINE void OnAbort(FunctorType&& Work) { if (autortfm_is_closed()) { ForTheRuntime::OnAbortInternal(std::forward(Work)); } } #else // Have some work happen when this transaction aborts. If this is called // outside a transaction or from an open nest then the work is ignored. template UE_AUTORTFM_CRITICAL_INLINE void OnAbort(FunctorType&&) {} #endif #if UE_AUTORTFM // Register a handler for transaction commit. Takes a key parameter so that // the handler can be unregistered (see `PopOnCommitHandler`). This is useful // for scoped mutations that need an abort handler present unless execution // reaches the end of the relevant scope. template UE_AUTORTFM_CRITICAL_INLINE void PushOnCommitHandler(const void* Key, FunctorType&& Work) { if (autortfm_is_closed()) { ForTheRuntime::PushOnCommitHandlerInternal(Key, std::forward(Work)); } } #else // Register a handler for transaction commit. Takes a key parameter so that // the handler can be unregistered (see `PopOnCommitHandler`). This is useful // for scoped mutations that need an abort handler present unless execution // reaches the end of the relevant scope. template UE_AUTORTFM_CRITICAL_INLINE void PushOnCommitHandler(const void*, FunctorType&&) {} #endif #if UE_AUTORTFM // Unregister the most recently pushed handler (via `PushOnCommitHandler`) for the given key. UE_AUTORTFM_CRITICAL_INLINE void PopOnCommitHandler(const void* Key) { if (autortfm_is_closed()) { ForTheRuntime::PopOnCommitHandlerInternal(Key); } } #else // Unregister the most recently pushed handler (via `PushOnCommitHandler`) for the given key. UE_AUTORTFM_CRITICAL_INLINE void PopOnCommitHandler(const void*) {} #endif #if UE_AUTORTFM // Unregister all pushed handlers (via `PushOnCommitHandler`) for the given key. UE_AUTORTFM_CRITICAL_INLINE void PopAllOnCommitHandlers(const void* Key) { if (autortfm_is_closed()) { ForTheRuntime::PopAllOnCommitHandlersInternal(Key); } } #else // Unregister all pushed handlers (via `PushOnCommitHandler`) for the given key. UE_AUTORTFM_CRITICAL_INLINE void PopAllOnCommitHandlers(const void*) {} #endif #if UE_AUTORTFM // Register a handler for transaction abort. Takes a key parameter so that // the handler can be unregistered (see `PopOnAbortHandler`). This is useful // for scoped mutations that need an abort handler present unless execution // reaches the end of the relevant scope. template UE_AUTORTFM_CRITICAL_INLINE void PushOnAbortHandler(const void* Key, FunctorType&& Work) { if (autortfm_is_closed()) { ForTheRuntime::PushOnAbortHandlerInternal(Key, std::forward(Work)); } } #else // Register a handler for transaction abort. Takes a key parameter so that // the handler can be unregistered (see `PopOnAbortHandler`). This is useful // for scoped mutations that need an abort handler present unless execution // reaches the end of the relevant scope. template UE_AUTORTFM_CRITICAL_INLINE void PushOnAbortHandler(const void* Key, FunctorType&&) {} #endif #if UE_AUTORTFM // Unregister the most recently pushed handler (via `PushOnAbortHandler`) for the given key. UE_AUTORTFM_CRITICAL_INLINE void PopOnAbortHandler(const void* Key) { if (autortfm_is_closed()) { ForTheRuntime::PopOnAbortHandlerInternal(Key); } } #else // Unregister the most recently pushed handler (via `PushOnAbortHandler`) for the given key. UE_AUTORTFM_CRITICAL_INLINE void PopOnAbortHandler(const void* Key) { UE_AUTORTFM_UNUSED(Key); } #endif #if UE_AUTORTFM // Unregister all pushed handlers (via `PushOnAbortHandler`) for the given key. UE_AUTORTFM_CRITICAL_INLINE void PopAllOnAbortHandlers(const void* Key) { if (autortfm_is_closed()) { ForTheRuntime::PopAllOnAbortHandlersInternal(Key); } } #else // Unregister all pushed handlers (via `PushOnAbortHandler`) for the given key. UE_AUTORTFM_CRITICAL_INLINE void PopAllOnAbortHandlers(const void* Key) { UE_AUTORTFM_UNUSED(Key); } #endif // Inform the runtime that we have performed a new object allocation. It's only // necessary to call this inside of custom malloc implementations. As an // optimization, you can choose to then only have your malloc return the pointer // returned by this function. It's guaranteed to be equal to the pointer you // passed, but it's blessed specially from the compiler's perspective, leading // to some nice optimizations. This does nothing when called from open code. UE_AUTORTFM_CRITICAL_INLINE void* DidAllocate(void* Ptr, size_t Size) { return autortfm_did_allocate(Ptr, Size); } // Inform the runtime that we have free'd a given memory location. UE_AUTORTFM_CRITICAL_INLINE void DidFree(void* Ptr) { autortfm_did_free(Ptr); } // Informs the runtime that a block of memory is about to be overwritten in the open. // During a transaction, this allows the runtime to copy the data in preparation for // a possible abort. Normally, tracking memory overwrites should be automatically // handled by AutoRTFM, but manual overwrite tracking may be required for third-party // libraries or outside compilers (such as ISPC). UE_AUTORTFM_CRITICAL_INLINE void RecordOpenWrite(void* Ptr, size_t Size) { autortfm_record_open_write(Ptr, Size); } // Informs the runtime that a block of memory is about to be overwritten in the open. // During a transaction, this allows the runtime to copy the data in preparation for // a possible abort. Normally, tracking memory overwrites should be automatically // handled by AutoRTFM, but manual overwrite tracking may be required for third-party // libraries or outside compilers (such as ISPC). template UE_AUTORTFM_CRITICAL_INLINE void RecordOpenWrite(Type* Ptr) { autortfm_record_open_write(Ptr, sizeof(Type)); } // Same as RecordOpenWrite() but marks the write as ignorable by the memory validator. UE_AUTORTFM_CRITICAL_INLINE void RecordOpenWriteNoMemoryValidation(void* Ptr, size_t Size) { autortfm_record_open_write_no_memory_validation(Ptr, Size); } // Same as RecordOpenWrite() but marks the write as ignorable by the memory validator. template UE_AUTORTFM_CRITICAL_INLINE void RecordOpenWriteNoMemoryValidation(Type* Ptr) { autortfm_record_open_write_no_memory_validation(Ptr, sizeof(Type)); } // Report that a unreachable codepath is being hit. Used to manually ban certain codepaths // from being transactionally safe. UE_AUTORTFM_CRITICAL_INLINE void Unreachable(const char* Message = nullptr) { autortfm_unreachable(Message); } // If we are running within a transaction, call `AutoRTFM::Unreachable`. UE_AUTORTFM_CRITICAL_INLINE void UnreachableIfTransactional(const char* Message = nullptr) { if (AutoRTFM::IsTransactional()) { AutoRTFM::Unreachable(Message); } } // A collection of power-user functions that are reserved for use by the AutoRTFM runtime only. namespace ForTheRuntime { // An enum to represent the various ways we want to enable/disable the AutoRTFM runtime. // This enum has effective groups of functionality such that if a higher priority group // has enabled or disabled the runtime, a lower priority group cannot then override that. // // We have from higher to lower priority: // - Forced Enabled/Disabled - used by CVars when force enabling/disabling AutoRTFM. // - Override Enabled/Disabled - override any setting of enabled/disabled as was set by a CVar. // - Enabled/Disabled - used by CVars when enabling/disabling AutoRTFM. // - Default Enabled/Disabled - whether we should be enabled or disabled by default (used for different backend executables). // // For example the following would be valid: // - At compile time the state is compiled in as default disabled. // - The CVar is set to enabled, so we switch the state to enabled. // - At runtime we detect a mode where we want AutoRTFM and try to switch the default to enabled, but the CVar already enabled it so this is ignored. // - Then for a given codepath we override AutoRTFM to disabled so we switch the state to disabled. enum EAutoRTFMEnabledState { // Disable AutoRTFM. AutoRTFM_Disabled = 0, // Enable AutoRTFM. AutoRTFM_Enabled, // Force disable AutoRTFM. AutoRTFM_ForcedDisabled, // Force enable AutoRTFM. AutoRTFM_ForcedEnabled, // Whether our default is to be disabled. AutoRTFM_DisabledByDefault, // Whether our default is to be enabled. AutoRTFM_EnabledByDefault, // Whether we've overridden and AutoRTFM is disabled. AutoRTFM_OverriddenDisabled, // Whether we've overridden and AutoRTFM is enabled. AutoRTFM_OverriddenEnabled, }; // An enum to represent whether we should abort and retry transactions (for testing purposes). enum EAutoRTFMRetryTransactionState { // Do not abort and retry transactions (the default). NoRetry = 0, // Abort and retry non-nested transactions (EG. only abort the parent transactional nest). RetryNonNested, // Abort and retry nested-transactions too. Will be slower as each nested-transaction will // be aborted and retried at least *twice* (once when the non-nested transaction runs the // first time, and a second time when the non-nested transaction is doing its retry after // aborting). RetryNestedToo, }; enum EAutoRTFMInternalAbortActionState { // Crash the process if we hit an internal AutoRTFM abort. Crash = 0, // Just do a normal transaction abort and let the runtime recover (used to test aborting codepaths). Abort, }; // Set whether the AutoRTFM runtime is enabled or disabled. Returns true when the state was changed // successfully. UE_AUTORTFM_API bool SetAutoRTFMRuntime(EAutoRTFMEnabledState State); UE_AUTORTFM_API bool IsAutoRTFMRuntimeEnabledInternal(); // Query whether the AutoRTFM runtime is enabled. UE_AUTORTFM_CRITICAL_INLINE bool IsAutoRTFMRuntimeEnabled() { // If we are already in the closed nest of a transaction, we must have our runtime enabled! if (AutoRTFM::IsClosed()) { return true; } return IsAutoRTFMRuntimeEnabledInternal(); } // Set the percentage [0..100] chance that a call to `CoinTossDisable` will end up disabling AutoRTFM. // 100% means never disable via coin-toss, 0% means always disable. So passing `0.1` means disable // all but 1/1000's calls via `CoinTossDisable`. UE_AUTORTFM_API void SetAutoRTFMEnabledProbability(float Chance); // Get the enabled probability set via `SetAutoRTFMEnabledProbability`. UE_AUTORTFM_API float GetAutoRTFMEnabledProbability(); // Call to randomly disable AutoRTFM with a probability set with `SetAutoRTFMEnabledProbability`. // Returns true if AutoRTFM was disabled by this call. UE_AUTORTFM_API bool CoinTossDisable(); UE_AUTORTFM_API void SetInternalAbortAction(EAutoRTFMInternalAbortActionState State); UE_AUTORTFM_API EAutoRTFMInternalAbortActionState GetInternalAbortAction(); UE_AUTORTFM_API bool GetEnsureOnInternalAbort(); UE_AUTORTFM_API void SetEnsureOnInternalAbort(bool bEnabled); // Set whether we should trigger an ensure on an abort-by-language. [[deprecated("Use `SetEnsureOnInternalAbort` instead!")]] inline void SetEnsureOnAbortByLanguage(bool bEnabled) { SetEnsureOnInternalAbort(bEnabled); } // Returns whether the runtime will trigger an ensure on an abort-by-language, or not. [[deprecated("Use `GetEnsureOnInternalAbort` instead!")]] inline bool IsEnsureOnAbortByLanguageEnabled() { return GetEnsureOnInternalAbort(); } // Returns whether we want to assert or ensure on a Language Error [[deprecated("Use `GetInternalAbortAction` instead!")]] inline bool IsAutoRTFMAssertOnError() { return EAutoRTFMInternalAbortActionState::Crash == GetInternalAbortAction(); } // Set whether we should retry transactions. UE_AUTORTFM_API void SetRetryTransaction(EAutoRTFMRetryTransactionState State); // Returns whether we should retry transactions. UE_AUTORTFM_API EAutoRTFMRetryTransactionState GetRetryTransaction(); // Returns true if we should retry non-nested transactions. UE_AUTORTFM_API bool ShouldRetryNonNestedTransactions(); // Returns true if we should also retry nested transactions. UE_AUTORTFM_API bool ShouldRetryNestedTransactionsToo(); // Returns the memory validation level currently enabled. UE_AUTORTFM_API EMemoryValidationLevel GetMemoryValidationLevel(); // Sets the memory validation level. See IsWriteValidationEnabled(). UE_AUTORTFM_API void SetMemoryValidationLevel(EMemoryValidationLevel Level); // Returns true if the memory validation throttling is enabled. UE_AUTORTFM_API bool GetMemoryValidationThrottlingEnabled(); // Sets the memory validation throttling mode. UE_AUTORTFM_API void SetMemoryValidationThrottlingEnabled(bool bEnabled); // Returns true if the memory validation statistics are enabled. UE_AUTORTFM_API bool GetMemoryValidationStatisticsEnabled(); // Sets whether memory validation statistics are logged or not. UE_AUTORTFM_API void SetMemoryValidationStatisticsEnabled(bool bEnabled); // A debug helper that will break to the debugger if the hash of the memory // write locations no longer matches the hash recorded when the transaction // was opened. Useful for isolating where the open write happened. // Requires the memory validation to be enabled to be called. UE_AUTORTFM_API void DebugBreakIfMemoryValidationFails(); // Manually create a new transaction from open code and push it as a transaction nest. // Can only be called within an already active parent transaction (EG. this cannot start // a transaction nest itself). UE_AUTORTFM_CRITICAL_INLINE bool StartTransaction() { return autortfm_start_transaction(); } // Manually commit the top transaction nest, popping it from the execution scope. // Can only be called within an already active parent transaction (EG. this cannot end // a transaction nest itself). UE_AUTORTFM_CRITICAL_INLINE ETransactionResult CommitTransaction() { return static_cast(autortfm_commit_transaction()); } // Manually clear the status of a user abort from the top transaction in a nest. UE_AUTORTFM_CRITICAL_INLINE void ClearTransactionStatus() { autortfm_clear_transaction_status(); } UE_AUTORTFM_CRITICAL_INLINE EContextStatus GetContextStatus() { return static_cast(autortfm_get_context_status()); } UE_AUTORTFM_CRITICAL_INLINE EContextStatus ResetContextStatus() { return static_cast(autortfm_get_context_status()); } // Ends a transaction while in the open, discarding all effects. // Control flow is unaffected while in the open. // Sends control to the end of the transaction after leaving the open. UE_AUTORTFM_CRITICAL_INLINE ETransactionResult RollbackTransaction() { return static_cast(autortfm_rollback_transaction()); } // Ends a transaction nest in the open, discarding all effects. This cascades, // meaning an abort of a nested transaction will cause all transactions in the // nest to abort. Control flow is unaffected while in the open. // Sends control to the end of the transaction after leaving the open. UE_AUTORTFM_CRITICAL_INLINE ETransactionResult CascadingRollbackTransaction() { return static_cast(autortfm_cascading_rollback_transaction()); } // A template helper that holds the mapping from the open function pointer // OpenFn to the closed function pointer ClosedFn, both of the function type // FnType. template class TOpenToClosedMapping { public: // Returns true if the open function pointer is not null. inline constexpr bool IsValid() const { return OpenFn != nullptr; } // Returns the mapping as a autortfm_open_to_closed_mapping structure. inline constexpr autortfm_open_to_closed_mapping Get() const { return {reinterpret_cast(OpenFn), reinterpret_cast(ClosedFn)}; } }; // A helper that on construction, registers all the open to closed function // mappings expressed as the variadic TOpenToClosedMapping template // arguments. On destruction the mappings are unregistered. // Used by UE_AUTORTFM_REGISTER_OPEN_TO_CLOSED_FUNCTIONS() template class TAutoRegisterOpenToClosedFunctions { public: UE_AUTORTFM_NOAUTORTFM inline TAutoRegisterOpenToClosedFunctions() { autortfm_register_open_to_closed_functions(GetTable()); } UE_AUTORTFM_NOAUTORTFM inline ~TAutoRegisterOpenToClosedFunctions() { autortfm_unregister_open_to_closed_functions(GetTable()); } private: struct FStaticData { FStaticData() { // Some functions may dynamically resolve to nullptr, which we want to filter out // as this will be treated as a null-terminator. size_t Count = 0; auto AddMappingIfValid = [&](const auto& Mapping) { if (Mapping.IsValid()) { Mappings[Count++] = Mapping.Get(); } }; (AddMappingIfValid(OpenToClosedMappings{}), ...); Mappings[Count++] = {nullptr, nullptr}; } autortfm_open_to_closed_mapping Mappings[sizeof...(OpenToClosedMappings) + 1]{}; autortfm_open_to_closed_table Table{Mappings, /* Prev */ nullptr, /* Next */ nullptr}; }; UE_AUTORTFM_NOAUTORTFM static autortfm_open_to_closed_table* GetTable() { static FStaticData StaticData; return &StaticData.Table; } }; // Reserved for future. UE_AUTORTFM_CRITICAL_INLINE void RecordOpenRead(void const*, size_t) {} // Reserved for future. template UE_AUTORTFM_CRITICAL_INLINE void RecordOpenRead(Type*) {} } // namespace ForTheRuntime } // namespace AutoRTFM // Macro-based variants so we completely compile away when not in use, even in debug builds #if UE_AUTORTFM namespace AutoRTFM::Private { struct FOpenHelper { template void operator+(FunctorType F) { AutoRTFM::Open(std::move(F)); } }; struct FOpenNoMemoryValidationHelper { template void operator+(FunctorType F) { AutoRTFM::Open(std::move(F)); } }; struct FOnAbortHelper { template void operator+(FunctorType F) { AutoRTFM::OnAbort(std::move(F)); } }; struct FOnCommitHelper { template void operator+(FunctorType F) { AutoRTFM::OnCommit(std::move(F)); } }; struct FTransactHelper { template void operator+(FunctorType F) { AutoRTFM::Transact(std::move(F)); } }; namespace /* must have internal linkage */ { struct FThreadLocalHelper { template UE_AUTORTFM_ALWAYS_OPEN static Type& Get() { thread_local Type Data; return Data; } }; } } // namespace AutoRTFM::Private #if defined(__clang__) && __has_warning("-Wdeprecated-this-capture") #define UE_AUTORTFM_BEGIN_DISABLE_WARNINGS _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wdeprecated-this-capture\"") #define UE_AUTORTFM_END_DISABLE_WARNINGS _Pragma("clang diagnostic pop") #else #define UE_AUTORTFM_BEGIN_DISABLE_WARNINGS #define UE_AUTORTFM_END_DISABLE_WARNINGS #endif #define UE_AUTORTFM_DECLARE_THREAD_LOCAL_VAR_IMPL(Type, Name) Type& Name = ::AutoRTFM::Private::FThreadLocalHelper::Get() #define UE_AUTORTFM_OPEN_IMPL ::AutoRTFM::Private::FOpenHelper{} + [&]() #define UE_AUTORTFM_OPEN_NO_VALIDATION_IMPL ::AutoRTFM::Private::FOpenNoMemoryValidationHelper{} + [&]() #define UE_AUTORTFM_ONABORT_IMPL(...) ::AutoRTFM::Private::FOnAbortHelper{} + [__VA_ARGS__]() mutable #define UE_AUTORTFM_ONCOMMIT_IMPL(...) ::AutoRTFM::Private::FOnCommitHelper{} + [__VA_ARGS__]() mutable #define UE_AUTORTFM_TRANSACT_IMPL ::AutoRTFM::Private::FTransactHelper{} + [&]() #else // Do nothing, these should be followed by blocks that should be either executed or not executed #define UE_AUTORTFM_DECLARE_THREAD_LOCAL_VAR_IMPL(Type, Name) thread_local Type Name #define UE_AUTORTFM_OPEN_IMPL #define UE_AUTORTFM_OPEN_NO_VALIDATION_IMPL #define UE_AUTORTFM_ONABORT_IMPL(...) while (false) #define UE_AUTORTFM_ONCOMMIT_IMPL(...) #define UE_AUTORTFM_TRANSACT_IMPL #endif // Declares an AutoRTFM-aware thread local variable. `thread_local` variables are not yet natively supported (#jira SOL-7684) // Calls should be written like this: // UE_AUTORTFM_DECLARE_THREAD_LOCAL_VAR(FString, MyThreadLocalString); // MyThreadLocalString = "Hello"; #define UE_AUTORTFM_DECLARE_THREAD_LOCAL_VAR(Type, Name) UE_AUTORTFM_DECLARE_THREAD_LOCAL_VAR_IMPL(Type, Name) // Runs a block of code in the open, non-transactionally. Anything performed in the open will not be undone if a transaction fails. // Calls should be written like this: UE_AUTORTFM_OPEN { ... code ... }; #define UE_AUTORTFM_OPEN UE_AUTORTFM_OPEN_IMPL #define UE_AUTORTFM_OPEN_NO_VALIDATION UE_AUTORTFM_OPEN_NO_VALIDATION_IMPL // Runs a block of code if a transaction aborts. // In non-transactional code paths the block of code will not be executed at all. // The macro arguments are the capture specification for the lambda. // Calls should be written like this: UE_AUTORTFM_ONABORT(=) { ... code ... }; #define UE_AUTORTFM_ONABORT(...) UE_AUTORTFM_ONABORT_IMPL(__VA_ARGS__) // Runs a block of code if a transaction commits successfully. // In non-transactional code paths the block of code will be executed immediately. // The macro arguments are the capture specification for the lambda. // Calls should be written like this: UE_AUTORTFM_ONCOMMIT(=) { ... code ... }; #define UE_AUTORTFM_ONCOMMIT(...) UE_AUTORTFM_ONCOMMIT_IMPL(__VA_ARGS__) // Runs a block of code in the closed, transactionally, within a new transaction. // Calls should be written like this: UE_AUTORTFM_TRANSACT { ... code ... }; #define UE_AUTORTFM_TRANSACT UE_AUTORTFM_TRANSACT_IMPL #if UE_AUTORTFM #define UE_AUTORTFM_REGISTER_OPEN_TO_CLOSED_FUNCTIONS(...) \ static const ::AutoRTFM::ForTheRuntime::TAutoRegisterOpenToClosedFunctions<__VA_ARGS__> UE_AUTORTFM_CONCAT(AutoRTFMFunctionRegistration, __COUNTER__) #else #define UE_AUTORTFM_REGISTER_OPEN_TO_CLOSED_FUNCTIONS(...) #endif // Used as an argument to UE_AUTORTFM_REGISTER_OPEN_TO_CLOSED_FUNCTIONS() // Maps the open function Open to the closed function Closed #define UE_AUTORTFM_MAP_OPEN_TO_CLOSED(Open, Closed) ::AutoRTFM::ForTheRuntime::TOpenToClosedMapping // Used as an argument to UE_AUTORTFM_REGISTER_OPEN_TO_CLOSED_FUNCTIONS() // Similar to AUTORTFM_MAP_OPEN_TO_CLOSED(), but takes an additional Signature // parameter used to distinguish the overload that is to be registered. // Signature is in the form: ReturnType(ArgType1, ArgType2, ...) #define UE_AUTORTFM_MAP_OPEN_TO_CLOSED_OVERLOADED(Signature, Open, Closed) ::AutoRTFM::ForTheRuntime::TOpenToClosedMapping // Used as an argument to UE_AUTORTFM_REGISTER_OPEN_TO_CLOSED_FUNCTIONS() // Maps the open function Open to itself as the closed function #define UE_AUTORTFM_MAP_OPEN_TO_SELF(Open) ::AutoRTFM::ForTheRuntime::TOpenToClosedMapping // Used as an argument to UE_AUTORTFM_REGISTER_OPEN_TO_CLOSED_FUNCTIONS() // Similar to AUTORTFM_MAP_OPEN_TO_SELF(), but takes an additional Signature // parameter used to distinguish the overload that is to be registered. // Signature is in the form: ReturnType(ArgType1, ArgType2, ...) #define UE_AUTORTFM_MAP_OPEN_TO_SELF_OVERLOADED(Signature, Open) ::AutoRTFM::ForTheRuntime::TOpenToClosedMapping #endif // __cplusplus